Skip to content

refactor: eliminate TypeScript any types across backend and frontend#122

Merged
GitAddRemote merged 20 commits into
mainfrom
feat/ISSUE-100-eliminate-typescript-any
Apr 22, 2026
Merged

refactor: eliminate TypeScript any types across backend and frontend#122
GitAddRemote merged 20 commits into
mainfrom
feat/ISSUE-100-eliminate-typescript-any

Conversation

@GitAddRemote
Copy link
Copy Markdown
Owner

Summary

  • Replaces all any types with proper TypeScript types across backend and frontend
  • Enforces @typescript-eslint/no-explicit-any via ESLint in both packages

Changes

Backend:

  • Typed JWT auth interfaces for strategies and guards
  • AuthenticatedRequest type replacing any in controllers
  • Typed DTOs for UEX repository update operations
  • Proper type narrowing in error handling (error instanceof Error)
  • Record<string, unknown> for audit log metadata

Frontend:

  • Inventory components, pages, services, and utils fully typed
  • focusController typed
  • Test files updated to match typed signatures

Test plan

  • pnpm typecheck passes with zero errors
  • pnpm lint passes with no no-explicit-any violations
  • pnpm test passes

Closes #100

GitAddRemote and others added 8 commits April 11, 2026 20:06
Implements type-safe authentication interfaces to eliminate any types
in auth-critical code paths:

- JwtPayload: JWT token payload structure with sub, username, iat, exp
- AuthenticatedUser: User object attached to JWT-authenticated requests
- AuthenticatedRequest: Express Request with authenticated user
- RefreshTokenUser: User object for refresh token authentication
- RefreshTokenRequest: Express Request with refresh token
- ValidatedUser: User type without password (uses Omit utility type)

These interfaces provide:
- Type safety at JWT/auth boundaries
- Prevention of password leakage through types
- Clear separation between different auth contexts
- Self-documenting authentication flow

Related to #100

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
Replace any types with proper TypeScript interfaces in auth module:

- auth.service.ts: Use ValidatedUser and JwtPayload types
- jwt.strategy.ts: Type validate() with JwtPayload and AuthenticatedUser
- auth.controller.ts: Use AuthenticatedRequest and RefreshTokenRequest

Security improvements:
- Type-safe JWT payload handling prevents injection bugs
- Impossible to accidentally expose user passwords via types
- Compiler enforces correct user object structure

Related to #100

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
Update all controllers to use AuthenticatedRequest interface instead of
req: any pattern:

- org-inventory.controller.ts: Use AuthenticatedRequest for all endpoints
- user-inventory.controller.ts: Use AuthenticatedRequest for user context
- users.controller.ts: Type-safe request handling

Also adds QueryParams interface for type-safe query parameter handling
in inventory controllers.

Benefits:
- Type-safe access to req.user.userId and req.user.username
- Compiler catches typos in request property access
- Better IDE autocomplete and refactoring support

Related to #100

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
Replace Partial<T> & intersection types with explicit update DTOs
in base-uex.repository.ts to avoid TypeORM's QueryDeepPartialEntity
type complexity:

- SoftDeleteUpdate interface: Enforces deleted: true (literal type)
- ActivationUpdate interface: Handles active: boolean with modifiedById

Implementation pattern:
1. Create typed DTO (e.g., SoftDeleteUpdate)
2. Construct updateData with explicit type
3. Use 'as unknown as QueryDeepPartialEntity<T>' at boundary

Benefits:
- Type-safe at construction (compiler catches mistakes)
- Self-documenting (DTOs show intent)
- Single assertion point (only at TypeORM boundary)
- Prevents bugs (deleted: true literal prevents un-deletion)

Technical notes:
- Uses literal type 'deleted: true' to enforce soft-delete semantics
- 'as unknown as' pattern is TypeScript-recommended for unavoidable casts
- QueryDeepPartialEntity imported from internal TypeORM path (stable API)

Related to #100

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
Replace error.stack and error.message access on unknown types with
type-safe narrowing pattern across UEX sync modules:

- uex-sync.scheduler.ts: Type-safe error logging in all cron jobs
- locations-sync.service.ts: Proper error message extraction
- uex-items.client.ts: Remove duplicate errorResponse declaration

Pattern applied:
  catch (error: unknown) {
    const errorMessage = error instanceof Error ? error.message : String(error);
    const errorStack = error instanceof Error ? error.stack : undefined;
    this.logger.error(message, errorStack);
  }

Benefits:
- Handles both Error objects and non-Error thrown values
- No TypeScript compilation errors on unknown type
- Defensive programming without paranoia
- Preserves stack traces when available

Related to #100

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
Replace Record<string, any> with Record<string, unknown> in audit
logging system:

- audit-log.entity.ts: JSONB columns now use unknown type
- audit-logs.service.ts: CreateAuditLogDto uses unknown
- inventory-audit-log.entity.ts: Consistent metadata typing

Benefits:
- Forces type checking at access point (safer than any)
- Flexible metadata structure while maintaining type discipline
- Prevents accidental usage without type narrowing
- Better TypeScript strict mode compliance

Pattern:
  const metadata: Record<string, unknown> = { ... };
  // Must narrow type before use:
  if (metadata && 'userId' in metadata) {
    const userId = metadata.userId; // still requires narrowing
  }

Related to #100

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
Implements TokenCleanupService in the auth module:
- Deletes refresh_tokens where revoked=true OR expiresAt < NOW()
- Deletes password_resets where used=true OR expiresAt < NOW()
- Logs deleted row counts and duration on success
- Swallows errors to avoid crashing the process
- Cron expression configurable via REFRESH_TOKEN_CLEANUP_CRON (default: 0 3 * * *)

ScheduleModule is conditionally excluded in test env (NODE_ENV=test)
so the @Cron decorator is never registered during tests.

Closes #98
Backend:
- ESLint config updated to enforce @typescript-eslint/no-explicit-any
- Typed JWT auth interfaces for strategies and guards
- AuthenticatedRequest type replacing any in controllers
- Typed DTOs for UEX repository update operations
- Proper type narrowing in error handling (error instanceof Error)
- Record<string, unknown> for audit log metadata

Frontend:
- ESLint config updated to enforce no-explicit-any
- Inventory components and pages fully typed
- Services, utils, and focusController typed
- Test files updated to match typed signatures

Closes #100
Copilot AI review requested due to automatic review settings April 21, 2026 04:47
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR removes explicit any usage across the backend (NestJS/TypeORM) and frontend (React/MUI) to tighten type safety at auth/request boundaries and other data-flow edges, and adds/adjusts ESLint rules to prevent regressions.

Changes:

  • Replaces any in auth + request handling with typed JWT payload/user/request interfaces across backend controllers, guards/strategies, and services.
  • Tightens typing in UEX sync/client error handling and UEX repository update paths (moving from any to unknown + narrowing / typed DTOs).
  • Enables @typescript-eslint/no-explicit-any in the frontend and (partially) in backend ESLint configs.

Reviewed changes

Copilot reviewed 56 out of 56 changed files in this pull request and generated 9 comments.

Show a summary per file
File Description
frontend/src/utils/focusController.ts Formatting/type-safety-friendly signature formatting for focus utilities.
frontend/src/utils/focusController.test.ts Updates tests to match typed/rewrapped callbacks.
frontend/src/services/uex.service.ts Tightens service method signature formatting/types.
frontend/src/services/inventory.service.ts Improves typing and readability of query building + service calls.
frontend/src/pages/ResetPassword.tsx Minor typed error handling / formatting changes.
frontend/src/pages/Register.tsx Narrows caught error to unknown and formats conditional error message.
frontend/src/pages/Profile.tsx Re-formats state and UI props; minor typing-related cleanup.
frontend/src/pages/Login.tsx Narrows caught error to unknown; formats error message selection.
frontend/src/pages/Inventory.tsx Large typing/formatting pass; stronger generic usage for focus controller, state typing.
frontend/src/pages/Inventory.editor-mode.test.tsx Updates mocks/tests for typed signatures.
frontend/src/pages/Home.tsx Formatting-only changes.
frontend/src/pages/Dashboard.tsx Formatting-only changes.
frontend/src/index.tsx Minor syntax/quote consistency updates.
frontend/src/components/location/SystemLocationSelector.tsx Tightens typing for hierarchyPath casting; formatting.
frontend/src/components/inventory/InventoryPortlet.tsx Minor formatting; typed event handler signature formatting.
frontend/src/components/inventory/InventoryNewRow.tsx Refines prop typing for draft and related expressions.
frontend/src/components/inventory/InventoryInlineRow.tsx Tightens prop typing; formatting and minor type-safe refactors.
frontend/src/components/inventory/InventoryFiltersPanel.tsx Tightens select values/casts and formatting for typed handlers.
frontend/.eslintrc.cjs Enforces @typescript-eslint/no-explicit-any on the frontend.
docs/matrix-academy-comparison.md Markdown formatting/table fixes and code sample punctuation.
backend/src/modules/users/users.controller.ts Uses AuthenticatedRequest instead of any for req.user.
backend/src/modules/user-inventory/user-inventory.controller.ts Replaces any request/query types with typed request + narrowed query shape.
backend/src/modules/user-inventory/entities/inventory-audit-log.entity.ts Changes metadata typing to Record<string, unknown>.
backend/src/modules/uex/repositories/base-uex.repository.ts Replaces as any casts with FindOptionsWhere<T> + typed update DTOs.
backend/src/modules/uex-sync/uex-sync.service.ts Narrows any to unknown (but still uses a null-cast workaround in query).
backend/src/modules/uex-sync/uex-sync.controller.ts Narrows caught errors to unknown and uses instanceof Error for messaging.
backend/src/modules/uex-sync/services/locations-sync.service.ts Removes any from errors/filters; introduces unknown + narrowing/casts.
backend/src/modules/uex-sync/services/items-sync.service.ts Removes any from errors/filters; introduces unknown + narrowing/casts.
backend/src/modules/uex-sync/services/companies-sync.service.ts Removes any from errors; introduces unknown + narrowing/casts.
backend/src/modules/uex-sync/services/categories-sync.service.ts Removes any from errors/filters; introduces unknown + narrowing/casts.
backend/src/modules/uex-sync/schedulers/uex-sync.scheduler.ts Narrows scheduler errors to unknown and logs safely.
backend/src/modules/uex-sync/clients/uex-locations.client.ts Converts any error handling to unknown + safer status/message extraction.
backend/src/modules/uex-sync/clients/uex-items.client.ts Converts any error handling to unknown + safer status/message extraction.
backend/src/modules/uex-sync/clients/uex-companies.client.ts Converts any error handling to unknown + safer status/message extraction.
backend/src/modules/uex-sync/clients/uex-categories.client.ts Converts any error handling to unknown + safer status/message extraction.
backend/src/modules/org-inventory/org-inventory.controller.ts Replaces any request/query types with typed request + narrowed query shape.
backend/src/modules/org-inventory/org-inventory.controller.spec.ts Updates controller tests to use AuthenticatedRequest typing.
backend/src/modules/auth/token-cleanup.service.ts Adds a new scheduled cleanup service for refresh tokens/password resets.
backend/src/modules/auth/jwt.strategy.ts Adds typed JwtPayload and typed validate return (AuthenticatedUser).
backend/src/modules/auth/interfaces/validated-user.interface.ts Introduces a typed ValidatedUser for local auth/login flows.
backend/src/modules/auth/interfaces/refresh-token-request.interface.ts Introduces typed request/user shape for refresh-token guard flows.
backend/src/modules/auth/interfaces/jwt-payload.interface.ts Introduces typed JWT payload interface used by strategy/service.
backend/src/modules/auth/interfaces/authenticated-request.interface.ts Introduces typed authenticated request/user interface for controllers.
backend/src/modules/auth/auth.service.ts Replaces any with `ValidatedUser
backend/src/modules/auth/auth.module.ts Registers the new token cleanup provider.
backend/src/modules/auth/auth.controller.ts Replaces any request types with typed request interfaces.
backend/src/modules/auth/auth.controller.spec.ts Updates tests to use typed authenticated request objects.
backend/src/modules/audit-logs/audit-logs.service.ts Changes metadata/values typing to Record<string, unknown>.
backend/src/modules/audit-logs/audit-log.entity.ts Changes jsonb columns typing to Record<string, unknown>.
backend/src/common/interceptors/audit-log.interceptor.ts Replaces any response/request usage with typed unknown + narrowing.
backend/src/common/filters/http-exception.filter.ts Replaces any casts with a typed response interface + narrowing.
backend/src/app.module.ts Narrows Redis connection error to unknown + safe message extraction.
backend/eslint.config.js Flat-config formatting changes; still disables no-explicit-any.
backend/data-source.js Formatting/quote normalization.
backend/.eslintrc.js Enables @typescript-eslint/no-explicit-any: 'error' in legacy config.
backend/.env.example Documents REFRESH_TOKEN_CLEANUP_CRON env var.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread backend/eslint.config.js
Comment thread backend/src/modules/auth/token-cleanup.service.ts Outdated
Comment thread backend/src/modules/auth/token-cleanup.service.ts Outdated
Comment thread backend/src/modules/uex-sync/services/locations-sync.service.ts Outdated
Comment thread backend/src/modules/uex-sync/services/items-sync.service.ts Outdated
Comment thread backend/src/modules/uex-sync/services/companies-sync.service.ts Outdated
Comment thread backend/src/modules/uex-sync/uex-sync.service.ts
Comment thread backend/src/modules/uex-sync/services/categories-sync.service.ts Outdated
Comment thread backend/src/modules/auth/token-cleanup.service.ts Outdated
- Enable @typescript-eslint/no-explicit-any: error in eslint.config.js
  (was off, conflicting with .eslintrc.js)
- Normalize caught errors before recordSyncFailure() in all four sync
  services (error instanceof Error ? error : new Error(String(error)))
  to avoid silent loss of context when thrown value isn't an Error
- Replace null as unknown as Date with IsNull() in uex-sync.service.ts
  for type-safe null comparison in TypeORM where clause
- Remove token-cleanup.service.ts and its auth.module.ts registration
  from this branch (belongs in PR #114, not here)
- Remove REFRESH_TOKEN_CLEANUP_CRON from .env.example (same reason)
Resolved conflicts in auth module by accepting main's httpOnly cookie
implementation (login/refresh/logout now set cookies via @res) while
preserving typed interfaces (JwtPayload, AuthenticatedUser, ValidatedUser,
RefreshTokenRequest) in place of any casts introduced by main.

Also fixed any violations in files added by main:
- throttler.guard.ts: eslint-disable for required base-class override signature
- auth.service.spec.ts: typed mock capture variables instead of any
- auth.controller.ts: removed unused User import
Copilot AI review requested due to automatic review settings April 21, 2026 05:18
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 56 out of 56 changed files in this pull request and generated 3 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread backend/src/common/interceptors/audit-log.interceptor.ts Outdated
Comment thread backend/src/modules/org-inventory/org-inventory.controller.ts Outdated
Comment thread backend/src/modules/users/users.controller.ts
audit-log.interceptor.ts: guard parseInt to only parse pure-digit strings,
preventing silent truncation of non-numeric IDs like UUIDs that start with
digits (e.g. "1e3..." would have become 1).

users.controller.ts: switch getProfile from Express Request to
AuthenticatedRequest so it returns a stable { id, username } shape
consistent with the rest of the auth-aware endpoints.
- Replace let mock*: any with Record<string, jest.Mock>
- Replace defaultValue: any with defaultValue: unknown in ConfigService mocks
- Replace async (callback: any) with typed callback signatures
- Replace as any casts with as unknown as ConcreteType in spyOn mocks
- Replace savedItems: any[] with Record<string, unknown>[]
- Type repository variable in base-uex.repository.spec.ts using BaseUexRepository<BaseUexEntity>
Copilot AI review requested due to automatic review settings April 21, 2026 05:48
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 65 out of 65 changed files in this pull request and generated 2 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread backend/src/common/guards/throttler.guard.ts Outdated
Comment thread backend/src/modules/user-inventory/user-inventory.controller.ts Outdated
…ation

- Use explicit interface type for mockCategoryRepository and mockItemRepository
  to allow nested manager.transaction without conflicting with Record<string, jest.Mock>
- Replace Parameters<typeof repository.findOneActive>[0] cast with
  FindOptionsWhere<BaseUexEntity> for correct where-clause typing
- Fix mockResolvedValue({}) on update spy to pass UpdateResult shape
- throttler.guard: change getTracker param from Record<string, any> to
  Record<string, unknown>, removing the eslint-disable comment
- Extract shared QueryParams type to common/types/query-params.type.ts
  and replace duplicate local interface in org-inventory and
  user-inventory controllers
Copilot AI review requested due to automatic review settings April 21, 2026 06:03
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 66 out of 66 changed files in this pull request and generated 4 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread backend/src/modules/uex/repositories/base-uex.repository.ts Outdated
Comment thread docs/matrix-academy-comparison.md Outdated
Comment thread docs/matrix-academy-comparison.md Outdated
Comment thread backend/src/common/interceptors/audit-log.interceptor.ts
- base-uex.repository: inline update payloads directly into this.update()
  calls, removing the intermediate named variables and making the single
  cast site obvious
- audit-log.interceptor: remove async from tap() callback and add .catch()
  so auditLogsService.log() failures are silently swallowed rather than
  surfacing as unhandled promise rejections
- docs/matrix-academy-comparison.md: replace exceptionResponse as any with
  Record<string, unknown> narrowing; change Observable<any> to
  Observable<unknown> in interceptor example
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 66 out of 66 changed files in this pull request and generated 1 comment.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread backend/src/modules/users/users.controller.ts
Returning { id: req.user.userId } was a breaking change — existing clients
read the userId field. Reverted to { userId, username } to match the
AuthenticatedUser shape and avoid breaking consumers.
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 66 out of 66 changed files in this pull request and generated 2 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread backend/src/modules/users/users.controller.ts
Comment thread backend/src/common/types/query-params.type.ts Outdated
- users.controller: destructure password out of updateProfile response
  so the hash is never sent to the client
- query-params.type: widen QueryParams to match Express ParsedQs
  (string | string[] | QueryParams | QueryParams[] | undefined) and
  add asString() helper to narrow safely; update both inventory
  controllers to use asString() for search/sort/order fields
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 66 out of 66 changed files in this pull request and generated 2 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread backend/src/modules/auth/auth.controller.ts Outdated
Comment thread backend/src/common/interceptors/audit-log.interceptor.ts
- auth/me: return userId field (was id) to match frontend expectations

  and ValidatedUser interface contract

- audit-log interceptor: guard newValues assignment behind isPlainObject

  check so arrays/primitives don't get cast as AuditLogResponse
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 66 out of 66 changed files in this pull request and generated 1 comment.

Comments suppressed due to low confidence (1)

backend/src/modules/user-inventory/user-inventory.controller.ts:175

  • getAuditLog() declares @Query('userId') userId?: number / orgId?: number / limit?: number, but without pipes these values arrive as strings at runtime. The current Number(userId) / Number(orgId) / Number(limit) conversions will produce NaN for invalid input and pass it downstream. Prefer typing these query params as string | undefined (or using ParseIntPipe/DefaultValuePipe) and validating so invalid values fail fast with a 400.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread backend/src/modules/user-inventory/user-inventory.controller.ts
@GitAddRemote GitAddRemote self-assigned this Apr 22, 2026
Introduces a BaseUexUpdate type alias (QueryDeepPartialEntity<BaseUexEntity>)

so update payloads are typed against the known base fields before being

narrowed to QueryDeepPartialEntity<T>. Removes the as unknown as double-cast

that the reviewer flagged as obscuring real type mismatches.
Replaces bare Number() coercions with a readOptionalNumber() helper

(matching the pattern in OrgInventoryController) that throws

BadRequestException on non-numeric or non-integer inputs.

Prevents NaN propagation into pagination and DB filter logic.
Copilot AI review requested due to automatic review settings April 22, 2026 01:11
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 66 out of 66 changed files in this pull request and generated no new comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@GitAddRemote GitAddRemote merged commit a465940 into main Apr 22, 2026
16 checks passed
@GitAddRemote GitAddRemote deleted the feat/ISSUE-100-eliminate-typescript-any branch April 22, 2026 01:17
GitAddRemote added a commit that referenced this pull request Apr 22, 2026
The no-explicit-any ESLint rule (enabled in PR #122) flagged three

remaining 'as any' casts in the @optional() spec case. Replaced with

'as unknown as Repository<T>' / 'as unknown as ConfigService'.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Tech Story: Eliminate TypeScript any types across backend and frontend

2 participants