chore: resolve strict TS errors and missing JS extensions for NodeNext ESM resolution - #83
Conversation
📝 WalkthroughWalkthroughThis PR migrates the monorepo from CommonJS to ESM by updating TypeScript/build configurations, appending explicit Changes
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 15
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
apps/api/src/main.ts (1)
51-52: 🧹 Nitpick | 🔵 TrivialComment may be outdated after ESM migration.
The comment states "Top-level await is not available in CommonJS," but since this PR migrates to ESM, top-level await is now supported. The
start()wrapper is still valid but no longer strictly necessary.♻️ Optional: Simplify bootstrap with top-level await
-// Top-level await is not available in CommonJS. -const start = async () => { - try { - await bootstrap(); - } catch (err) { - Logger.error('Bootstrap failed', err); - process.exit(1); - } -}; - -void start(); +try { + await bootstrap(); +} catch (err) { + Logger.error('Bootstrap failed', err); + process.exit(1); +}🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/api/src/main.ts` around lines 51 - 52, The comment "Top-level await is not available in CommonJS" is outdated after the ESM migration; update or remove it and either (a) remove the unnecessary async wrapper function start() by inlining its startup logic at top-level using top-level await, or (b) update the comment to reflect that ESM supports top-level await while keeping start() if you prefer the wrapper for clarity; locate the async start() function and adjust its surrounding comment and usage accordingly.apps/api/src/db/database-manager.spec.ts (1)
61-61:⚠️ Potential issue | 🟡 MinorAlign mock path with actual import path.
The mock path
'../constants'should match the import path'../constants.js'indatabase-manager.ts(line 225). With NodeNext module resolution enabled (tsconfig.json), mismatched paths may prevent proper mock interception.Proposed fix
-vi.mock('../constants', () => constantMocks); +vi.mock('../constants.js', () => constantMocks);🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/api/src/db/database-manager.spec.ts` at line 61, The test's mock path string doesn't match the module import used by DatabaseManager; update the vi.mock call in database-manager.spec.ts so the mocked module path exactly matches the import in database-manager.ts (change vi.mock('../constants', () => constantMocks) to use '../constants.js'), ensuring the mock intercepts correctly under NodeNext resolution; also verify any other mocks use the .js suffix to match their real imports like the one in database-manager.ts.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@apps/api/nest-cli.json`:
- Around line 8-9: The nest-cli configuration sets "builder": "swc" (and
"typeCheck": true) but the required CLI package is missing; add the missing
devDependency `@swc/cli` to package.json devDependencies and install it (e.g., run
npm install -D `@swc/cli`) so the SWC builder in nest-cli.json can run correctly
with type checking enabled.
In `@apps/api/src/db/reset-e2e.ts`:
- Around line 6-9: The runtime error comes from using __dirname in ESM; replace
its usage in the dotEnv.config call (currently: dotEnv.config({ path:
path.resolve(__dirname, '../../.env') })) by deriving a dirname from
import.meta.url (use fileURLToPath(import.meta.url) and path.dirname(...) to
compute the directory) and pass that resolved path into dotEnv.config; also
ensure fileURLToPath is imported from 'url' and remove reliance on the undefined
__dirname symbol.
In `@apps/api/src/modules/connections/connections/callback.controller.spec.ts`:
- Around line 3-7: The import block currently brings Request, Response and
Mocked in as runtime imports; change them to type-only imports so they are
erased in emitted ESM: replace the plain imports of Request and Response from
'express' with type-only imports (type { Request, Response }) and change the
Mocked import from 'vitest' to a type-only import (type { Mocked }), leaving
OAuthCallbackController, ProviderRegistryService, vi, describe, it, expect,
beforeEach and OauthStateService as value imports.
In `@apps/api/src/modules/identity/system-admin/system-admin.controller.spec.ts`:
- Around line 14-15: The test's mock registration uses vi.mock() with the wrong
module specifier — update the mocked path used in the vi.mock call to exactly
match the import ('../../../constants.js') so Vitest intercepts the import;
locate the vi.mock('../../../constants') call in system-admin.controller.spec.ts
and change the specifier to '../../../constants.js' to match the import that
brings in the constants module.
In `@apps/api/src/modules/identity/users/users.controller.ts`:
- Around line 125-145: The mapping for invitedUsers is assigning inv.role (type
string | null) into UserListItem.role which is declared as string; normalize the
nullable value or adjust the type: either change the UserListItem.role
definition to allow string | null, or in the pendingInvitations.map normalize
inv.role to a concrete string (e.g., inv.role ?? 'member' or another default)
before returning the object; update the mapping in the invitedUsers creation
(pendingInvitations, inv) so UserListItem.role receives a non-null string to
satisfy the type.
- Around line 16-17: The import currently brings User as a runtime value; change
it to a type-only import so runtime providers remain separate: keep
USER_PROVIDER and TENANT_PROVIDER as value imports but import User using the
TypeScript type-only form alongside ITenantProvider and IUserProvider, and
update any references in the UserListItem type to use the type import (symbols:
USER_PROVIDER, TENANT_PROVIDER, User, ITenantProvider, IUserProvider,
UserListItem).
In `@apps/api/src/modules/trigger/trigger.module.ts`:
- Around line 12-13: Change the current runtime import of DatabaseManager to a
type-only import: replace the value import of DatabaseManager from
'@nexiom/dbmanager' with a type import so it won't emit a runtime require;
update the import statement that currently sits alongside DB_MANAGER in
trigger.module.ts to use "import type { DatabaseManager }" so the
factory/provider signature (the provider factory that references DatabaseManager
in its parameter type) retains type information without introducing a runtime
dependency.
In `@apps/api/src/scripts/admin-bootstrap.ts`:
- Line 8: Import NodePgDatabase as a type-only import so it is erased from
emitted JS: change the import that currently brings in NodePgDatabase from
'drizzle-orm/node-postgres' to a type import for NodePgDatabase only, leaving
the runtime import for drizzle intact; update the import statement that includes
NodePgDatabase (used in the type annotations at NodePgDatabase references on the
bootstrap functions) to use "import type { NodePgDatabase } ..." while keeping
"import { drizzle } ..." as a normal import.
- Around line 8-18: Replace CommonJS __dirname usage by deriving the directory
from import.meta.url and make NodePgDatabase a type-only import: add an ESM-safe
dirname like "const __filename = fileURLToPath(import.meta.url); const __dirname
= path.dirname(__filename);" (import fileURLToPath from 'url' and path from
'path') and change any path.resolve(__dirname, ...) calls used before
dotenv.config() to use that __dirname variable; also change the drizzle import
to a type-only import (e.g., import { drizzle, type NodePgDatabase } from
'drizzle-orm/node-postgres') so NodePgDatabase is not imported as a runtime
value.
In `@packages/connectors/src/crypto/encryption.service.ts`:
- Line 7: Restore the class inheritance so AesEncryptionService extends the
abstract EncryptionService to re-enable compile-time contract checks; update the
class declaration for AesEncryptionService to explicitly extend
EncryptionService (keeping the existing encrypt and decrypt method
implementations) so DI config that provides EncryptionService via useClass:
AesEncryptionService remains valid and type-safe.
In `@packages/database/package.json`:
- Around line 7-24: Add explicit "types" conditions to the package.json
"exports" subpaths so TypeScript can resolve declarations: update the root
export (".") to include "types": "./dist/index.d.ts" and add "types" entries to
each subpath export such as "./schema/identity", "./schema/tenant" (and the
duplicate "./dist/schema/identity") pointing to their corresponding declaration
files (e.g., "./dist/schema/identity.d.ts" and "./dist/schema/tenant.d.ts");
ensure the file names match the built .d.ts outputs and keep both
"import"/"default" and the new "types" conditions for each export entry.
In `@packages/identity/src/adapters/drizzle-user.adapter.spec.ts`:
- Line 1: Add a file-level ESLint disable for unbound-method to match the
existing file-level no-unsafe-return suppression and remove the inline disable
near the affected assertion; specifically, add "/* eslint-disable
`@typescript-eslint/unbound-method` */" at the top of the test file (same area
where /* eslint-disable `@typescript-eslint/no-unsafe-return` */ lives) so mock
method assertions like expect(db.update).not.toHaveBeenCalled() and other
expect(db.xxx) calls (seen around the inline disable at the current inline
suppression and elsewhere) no longer require inline disables.
In `@packages/identity/src/services/permission-seeder.spec.ts`:
- Around line 197-198: The mocks registered with vi.doMock/vi.doUnmock are using
the wrong module specifier ("../constants") so they don't apply to the ESM
import ("../constants.js"); update every vi.doMock("../constants") and
corresponding vi.doUnmock("../constants") in this test (including the calls
around the vi.doMock at lines shown) to use the exact ESM specifier
"../constants.js" so the mocks match the imports used by rbac-seeding.js.
In `@packages/pieces/salesforce/tsconfig.json`:
- Line 11: Update the tsconfig "module" value from "NodeNext" to the lowercase
"nodenext" for consistency with TypeScript docs and the project's
tsconfig.lib.json; locate the "module" property in the
packages/pieces/salesforce tsconfig and replace "NodeNext" with "nodenext" (the
JSON key is "module") so casing matches the documented convention.
In `@TECHNICAL_DEBT.md`:
- Around line 23-26: Decide and document a single PII retention model and make
the docs and artifacts consistent: either (A) choose immediate minimization —
keep the `anonymizeIp` and `anonymizeUserAgent` helpers and remove the
`runPIICleanup` job, background module, and backfill migration; or (B) choose
time-bounded retention — keep raw IP/user-agent on insert, remove or rename the
pre-insert anonymization helpers, and implement `runPIICleanup` inside the
`background`/`jobs` module (using `@nestjs/schedule`) to anonymize sessions older
than 30 days and add a Drizzle migration to backfill existing old sessions;
update TECHNICAL_DEBT.md so it clearly states which model is chosen and lists
the matching set of tasks (helpers vs job + migration) and references to
`anonymizeIp`, `anonymizeUserAgent`, `runPIICleanup`, and the background/jobs
module.
---
Outside diff comments:
In `@apps/api/src/db/database-manager.spec.ts`:
- Line 61: The test's mock path string doesn't match the module import used by
DatabaseManager; update the vi.mock call in database-manager.spec.ts so the
mocked module path exactly matches the import in database-manager.ts (change
vi.mock('../constants', () => constantMocks) to use '../constants.js'), ensuring
the mock intercepts correctly under NodeNext resolution; also verify any other
mocks use the .js suffix to match their real imports like the one in
database-manager.ts.
In `@apps/api/src/main.ts`:
- Around line 51-52: The comment "Top-level await is not available in CommonJS"
is outdated after the ESM migration; update or remove it and either (a) remove
the unnecessary async wrapper function start() by inlining its startup logic at
top-level using top-level await, or (b) update the comment to reflect that ESM
supports top-level await while keeping start() if you prefer the wrapper for
clarity; locate the async start() function and adjust its surrounding comment
and usage accordingly.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro
Run ID: c1573f1e-c821-466d-a692-5e827600fa54
⛔ Files ignored due to path filters (1)
pnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (138)
TECHNICAL_DEBT.mdapps/api/nest-cli.jsonapps/api/package.jsonapps/api/src/app/app.controller.spec.tsapps/api/src/app/app.controller.tsapps/api/src/app/app.module.tsapps/api/src/app/app.service.spec.tsapps/api/src/common/utils/headers.util.spec.tsapps/api/src/db/database-manager.spec.tsapps/api/src/db/database-manager.tsapps/api/src/db/db-cli.tsapps/api/src/db/db.module.tsapps/api/src/db/db.provider.tsapps/api/src/db/reset-e2e.tsapps/api/src/db/schema.spec.tsapps/api/src/db/schema.tsapps/api/src/main.tsapps/api/src/modules/connections/connections.module.tsapps/api/src/modules/connections/connections/callback.controller.spec.tsapps/api/src/modules/connections/connections/callback.controller.tsapps/api/src/modules/connections/connections/connectors.controller.spec.tsapps/api/src/modules/connections/connections/connectors.controller.tsapps/api/src/modules/connections/connections/token-refresh.service.spec.tsapps/api/src/modules/connections/connections/token-refresh.service.tsapps/api/src/modules/connections/connectors.service.spec.tsapps/api/src/modules/connections/connectors.service.tsapps/api/src/modules/connections/oauth-state.service.spec.tsapps/api/src/modules/dbmanager/dbmanager.module.tsapps/api/src/modules/email/console-email.service.spec.tsapps/api/src/modules/email/console-email.service.tsapps/api/src/modules/email/email.module.tsapps/api/src/modules/email/nodemailer.service.spec.tsapps/api/src/modules/email/nodemailer.service.tsapps/api/src/modules/identity/auth/auth.controller.coverage.spec.tsapps/api/src/modules/identity/auth/auth.controller.spec.tsapps/api/src/modules/identity/auth/auth.controller.tsapps/api/src/modules/identity/auth/auth.module.tsapps/api/src/modules/identity/auth/platform.guard.spec.tsapps/api/src/modules/identity/auth/platform.guard.tsapps/api/src/modules/identity/auth/system-admin.guard.spec.tsapps/api/src/modules/identity/auth/system-admin.guard.tsapps/api/src/modules/identity/invitations/invitations.controller.spec.tsapps/api/src/modules/identity/invitations/invitations.controller.tsapps/api/src/modules/identity/invitations/invitations.module.tsapps/api/src/modules/identity/invitations/invitations.service.spec.tsapps/api/src/modules/identity/invitations/invitations.service.tsapps/api/src/modules/identity/invitations/invitations.validation.spec.tsapps/api/src/modules/identity/roles/roles.controller.spec.tsapps/api/src/modules/identity/roles/roles.controller.tsapps/api/src/modules/identity/roles/roles.controller.visibility.spec.tsapps/api/src/modules/identity/roles/roles.module.tsapps/api/src/modules/identity/system-admin/system-admin.controller.spec.tsapps/api/src/modules/identity/system-admin/system-admin.controller.tsapps/api/src/modules/identity/system-admin/system-admin.module.tsapps/api/src/modules/identity/system-admin/system-admin.validation.spec.tsapps/api/src/modules/identity/system-admin/system-admin.validation.tsapps/api/src/modules/identity/tenants/tenants.controller.spec.tsapps/api/src/modules/identity/tenants/tenants.controller.tsapps/api/src/modules/identity/tenants/tenants.module.tsapps/api/src/modules/identity/tenants/tenants.validation.spec.tsapps/api/src/modules/identity/users/users.controller.spec.tsapps/api/src/modules/identity/users/users.controller.tsapps/api/src/modules/identity/users/users.module.tsapps/api/src/modules/identity/users/users.validation.spec.tsapps/api/src/modules/storage-resolver/storage-resolver.module.tsapps/api/src/modules/storage-resolver/storage-resolver.service.spec.tsapps/api/src/modules/storage-resolver/storage-resolver.service.tsapps/api/src/modules/trigger/dlq-processor.service.spec.tsapps/api/src/modules/trigger/dlq-processor.service.tsapps/api/src/modules/trigger/piece-registry.service.spec.tsapps/api/src/modules/trigger/poller.service.spec.tsapps/api/src/modules/trigger/poller.service.tsapps/api/src/modules/trigger/redis-trigger-store.spec.tsapps/api/src/modules/trigger/trigger-executor.service.spec.tsapps/api/src/modules/trigger/trigger-executor.service.tsapps/api/src/modules/trigger/trigger.module.tsapps/api/src/modules/trigger/webhooks.controller.spec.tsapps/api/src/modules/trigger/webhooks.controller.tsapps/api/src/scripts/admin-bootstrap.tsapps/api/test/app.e2e-spec.tsapps/api/test/invitations.e2e-spec.tsapps/api/test/system-admin.tenants.e2e-spec.tsapps/api/tsconfig.jsonpackage.jsonpackages/auth/package.jsonpackages/auth/src/auth.module.tspackages/auth/src/guards/auth.guard.tspackages/auth/src/guards/permissions.guard.tspackages/auth/src/index.tspackages/auth/tsconfig.build.jsonpackages/cache/package.jsonpackages/cache/src/cache.module.tspackages/connectors/package.jsonpackages/connectors/src/crypto/encryption.service.spec.tspackages/connectors/src/crypto/encryption.service.tspackages/connectors/src/oauth/token-manager.service.tspackages/database/package.jsonpackages/database/src/client.tspackages/database/src/database.module.tspackages/database/src/index.tspackages/database/src/schema/storage_registry.tspackages/database/src/schema/tenant.tspackages/dbmanager/package.jsonpackages/dbmanager/src/impl/sql-database-manager.tspackages/dbmanager/src/index.tspackages/identity/package.jsonpackages/identity/src/adapters/better-auth.abac.spec.tspackages/identity/src/adapters/better-auth.adapter.spec.tspackages/identity/src/adapters/better-auth.adapter.tspackages/identity/src/adapters/drizzle-permission.adapter.spec.tspackages/identity/src/adapters/drizzle-permission.adapter.tspackages/identity/src/adapters/drizzle-role.adapter.spec.tspackages/identity/src/adapters/drizzle-role.adapter.tspackages/identity/src/adapters/drizzle-tenant.adapter.spec.tspackages/identity/src/adapters/drizzle-tenant.adapter.tspackages/identity/src/adapters/drizzle-user.adapter.spec.tspackages/identity/src/adapters/drizzle-user.adapter.tspackages/identity/src/background/pii-cleanup.spec.tspackages/identity/src/background/pii-cleanup.tspackages/identity/src/better-auth.config.tspackages/identity/src/identity.module.spec.tspackages/identity/src/identity.module.tspackages/identity/src/index.spec.tspackages/identity/src/index.tspackages/identity/src/interfaces/auth-provider.interface.tspackages/identity/src/interfaces/index.tspackages/identity/src/interfaces/permission-provider.interface.tspackages/identity/src/interfaces/tenant-provider.interface.tspackages/identity/src/interfaces/user-provider.interface.tspackages/identity/src/services/permission-seeder.spec.tspackages/identity/src/services/permission-seeder.tspackages/identity/src/utils/name-generator.spec.tspackages/identity/src/utils/rbac-seeding.tspackages/identity/src/utils/role-visibility.tspackages/identity/tsconfig.jsonpackages/pieces/quickbooks/tsconfig.jsonpackages/pieces/salesforce/tsconfig.jsontsconfig.base.json
| "builder": "swc", | ||
| "typeCheck": true |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Check if `@swc/cli` and `@swc/core` are in package.json dependencies
fd -t f "package.json" apps/api --exec cat {} \; | jq '.devDependencies | keys | map(select(startswith("@swc")))' 2>/dev/null || echo "Check apps/api/package.json manually"Repository: pramodnarayana/nexiom
Length of output: 82
🏁 Script executed:
#!/bin/bash
# Get complete list of all `@swc` packages in devDependencies
fd -t f "package.json" apps/api --exec cat {} \; | jq '.devDependencies | with_entries(select(.key | startswith("@swc")))'Repository: pramodnarayana/nexiom
Length of output: 94
🌐 Web query:
NestJS SWC builder configuration required dependencies packages
💡 Result:
Standard NestJS app (Nest CLI SWC builder)
Install (dev deps):
@swc/core@swc/cli[1][2]
npm i -D `@swc/core` `@swc/cli`Enable in nest-cli.json:
{
"compilerOptions": { "builder": "swc" }
}[1]
Monorepo (Nest CLI uses webpack; wire SWC via swc-loader)
Install (dev deps):
swc-loader(and keep@swc/coreavailable because the loader relies on SWC) [1]
npm i -D swc-loader `@swc/core`Then point webpack to Nest’s SWC defaults (example shown in the Nest docs). [1]
If you also want type-checking with SWC
SWC doesn’t type-check; Nest CLI runs tsc in parallel when you enable type checking. [1]
(So make sure typescript is installed in your project, as in a normal Nest setup.)
Enable:
- CLI:
nest start -b swc --type-check, or nest-cli.json:"compilerOptions": { "builder": "swc", "typeCheck": true }[1]
Sources: [1] https://docs.nestjs.com/recipes/swc, [2] https://tessl.io/registry/tessl/npm-nestjs--cli/11.0.0/files/docs/application-building.md
Install @swc/cli to complete SWC builder configuration.
The nest-cli.json configuration specifies "builder": "swc", but the required dependency @swc/cli is missing from devDependencies. According to NestJS documentation, both @swc/core and @swc/cli are required for the SWC builder to function. Currently only @swc/core is installed.
Add @swc/cli to devDependencies:
npm install -D `@swc/cli`The "typeCheck": true setting is correctly configured and will enable TypeScript type checking during SWC builds once the dependency is installed.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@apps/api/nest-cli.json` around lines 8 - 9, The nest-cli configuration sets
"builder": "swc" (and "typeCheck": true) but the required CLI package is
missing; add the missing devDependency `@swc/cli` to package.json devDependencies
and install it (e.g., run npm install -D `@swc/cli`) so the SWC builder in
nest-cli.json can run correctly with type checking enabled.
| import { ALL_PERMISSIONS, isSystemPermission } from '../constants.js'; | ||
|
|
||
| // Load .env from apps/api root | ||
| dotEnv.config({ path: path.resolve(__dirname, '../../.env') }); |
There was a problem hiding this comment.
__dirname is not available in ES modules — this will cause a runtime error.
With the migration to ESM ("type": "module"), the __dirname global is no longer defined. Line 9 will throw ReferenceError: __dirname is not defined at runtime.
🐛 Proposed fix using import.meta.url
import * as dotEnv from 'dotenv';
-import * as path from 'node:path';
+import path from 'node:path';
+import { fileURLToPath } from 'node:url';
import { ALL_PERMISSIONS, isSystemPermission } from '../constants.js';
+const __filename = fileURLToPath(import.meta.url);
+const __dirname = path.dirname(__filename);
+
// Load .env from apps/api root
dotEnv.config({ path: path.resolve(__dirname, '../../.env') });🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@apps/api/src/db/reset-e2e.ts` around lines 6 - 9, The runtime error comes
from using __dirname in ESM; replace its usage in the dotEnv.config call
(currently: dotEnv.config({ path: path.resolve(__dirname, '../../.env') })) by
deriving a dirname from import.meta.url (use fileURLToPath(import.meta.url) and
path.dirname(...) to compute the directory) and pass that resolved path into
dotEnv.config; also ensure fileURLToPath is imported from 'url' and remove
reliance on the undefined __dirname symbol.
| import { OAuthCallbackController } from './callback.controller.js'; | ||
| import { ProviderRegistryService } from '@nexiom/connectors'; | ||
| import { Request, Response } from 'express'; | ||
| import { vi, describe, it, expect, beforeEach, Mocked } from 'vitest'; | ||
| import { OauthStateService } from '../oauth-state.service'; | ||
| import { OauthStateService } from '../oauth-state.service.js'; |
There was a problem hiding this comment.
🧹 Nitpick | 🔵 Trivial
Finish the type-only cleanup in this import block.
Request, Response, and Mocked are only used in type positions here. Keeping them as value imports preserves avoidable runtime imports in the emitted ESM.
♻️ Proposed fix
import { Test, TestingModule } from '@nestjs/testing';
import { OAuthCallbackController } from './callback.controller.js';
import { ProviderRegistryService } from '@nexiom/connectors';
-import { Request, Response } from 'express';
-import { vi, describe, it, expect, beforeEach, Mocked } from 'vitest';
+import type { Request, Response } from 'express';
+import { vi, describe, it, expect, beforeEach } from 'vitest';
+import type { Mocked } from 'vitest';
import { OauthStateService } from '../oauth-state.service.js';
import { ConfigService } from '@nestjs/config';📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| import { OAuthCallbackController } from './callback.controller.js'; | |
| import { ProviderRegistryService } from '@nexiom/connectors'; | |
| import { Request, Response } from 'express'; | |
| import { vi, describe, it, expect, beforeEach, Mocked } from 'vitest'; | |
| import { OauthStateService } from '../oauth-state.service'; | |
| import { OauthStateService } from '../oauth-state.service.js'; | |
| import { OAuthCallbackController } from './callback.controller.js'; | |
| import { ProviderRegistryService } from '@nexiom/connectors'; | |
| import type { Request, Response } from 'express'; | |
| import { vi, describe, it, expect, beforeEach } from 'vitest'; | |
| import type { Mocked } from 'vitest'; | |
| import { OauthStateService } from '../oauth-state.service.js'; |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@apps/api/src/modules/connections/connections/callback.controller.spec.ts`
around lines 3 - 7, The import block currently brings Request, Response and
Mocked in as runtime imports; change them to type-only imports so they are
erased in emitted ESM: replace the plain imports of Request and Response from
'express' with type-only imports (type { Request, Response }) and change the
Mocked import from 'vitest' to a type-only import (type { Mocked }), leaving
OAuthCallbackController, ProviderRegistryService, vi, describe, it, expect,
beforeEach and OauthStateService as value imports.
| } from '../../../constants.js'; | ||
| import { SystemAdminGuard } from '../auth/system-admin.guard.js'; |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
find . -name "system-admin.controller.spec.ts" -type fRepository: pramodnarayana/nexiom
Length of output: 141
🏁 Script executed:
cat -n apps/api/src/modules/identity/system-admin/system-admin.controller.spec.ts | head -35Repository: pramodnarayana/nexiom
Length of output: 1372
🌐 Web query:
Vitest vi.mock ESM module specifier path matching documentation
💡 Result:
Vitest’s ESM mocking is keyed off the module ID/specifier string Vitest registers for the import, so your vi.mock(...) argument must refer to the same module specifier (i.e., resolve to the same file/module ID) that your code under test imports.
Where this is documented (official docs):
vi.mockAPI (ESM-specific notes):- Examples consistently include explicit ESM file extensions (e.g.
./path/to/module.js), and it notes Vitest “still operates on a string” under the hood (even when you passvi.mock(import(...))). It also warns that if you use TSpathsaliases, you may need to use the corresponding relative path instead (e.g.import('./path/to/module.js')instead ofimport('@/module')). [1]
- Examples consistently include explicit ESM file extensions (e.g.
- Mocking Modules guide (how Vitest wires ESM mocks):
- Explains Vitest rewrites ESM imports and uses a wrapper to ensure the mock is resolved before the module import is evaluated—i.e., mocking is tied to the module import/ID Vitest sees. [2]
- Config
deps.moduleDirectories(how Vitest decides what a “module path” matches):- States this option affects
vi.mock: when no factory is provided and the path you’re mocking matches one of the configuredmoduleDirectories, Vitest will look for__mocks__accordingly—reinforcing that matching is based on the mocked path/ID. [3]
- States this option affects
Practical takeaway: in ESM, ensure the vi.mock() specifier matches what your code imports (including extension/relative-vs-alias), or use vi.mock(import('./exact/path.js')) to keep it aligned. [1]
References:
[1] Vitest API: vi / vi.mock – https://vitest.dev/api/vi (vitest.dev)
[2] Vitest Guide: Mocking Modules – https://vitest.dev/guide/mocking/modules.html (vitest.dev)
[3] Vitest v2 Config: deps.moduleDirectories (mentions vi.mock path matching) – https://v2.vitest.dev/config/ (v2.vitest.dev)
Citations:
- 1: https://vitest.dev/api/vi
- 2: https://vitest.dev/guide/mocking/modules.html
- 3: https://v2.vitest.dev/config/
Update the mocked module path to match the import specifier.
Line 14 imports from '../../../constants.js' with the .js extension, but line 23 mocks '../../../constants' without it. In ESM, Vitest's vi.mock() registers mocks by the exact module specifier string—if the paths don't match, the mock won't intercept the import and the real module will load silently instead of the stub. Align the mock path with the import by adding .js:
Proposed fix
-vi.mock('../../../constants', () => ({
+vi.mock('../../../constants.js', () => ({
getRequiredAdminRoleId: vi.fn(() => 'admin-role-id'),
getRequiredOwnerRoleId: vi.fn(() => 'owner-role-id'),
getRequiredSystemTenantId: vi.fn(
() => '00000000-0000-0000-0000-000000000000',
),
getRequiredMemberRoleId: vi.fn(() => 'member-role-id'),
}));📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| } from '../../../constants.js'; | |
| import { SystemAdminGuard } from '../auth/system-admin.guard.js'; | |
| vi.mock('../../../constants.js', () => ({ | |
| getRequiredAdminRoleId: vi.fn(() => 'admin-role-id'), | |
| getRequiredOwnerRoleId: vi.fn(() => 'owner-role-id'), | |
| getRequiredSystemTenantId: vi.fn( | |
| () => '00000000-0000-0000-0000-000000000000', | |
| ), | |
| getRequiredMemberRoleId: vi.fn(() => 'member-role-id'), | |
| })); |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@apps/api/src/modules/identity/system-admin/system-admin.controller.spec.ts`
around lines 14 - 15, The test's mock registration uses vi.mock() with the wrong
module specifier — update the mocked path used in the vi.mock call to exactly
match the import ('../../../constants.js') so Vitest intercepts the import;
locate the vi.mock('../../../constants') call in system-admin.controller.spec.ts
and change the specifier to '../../../constants.js' to match the import that
brings in the constants module.
| import { USER_PROVIDER, TENANT_PROVIDER, User } from '@nexiom/identity'; | ||
| import type { ITenantProvider, IUserProvider } from '@nexiom/identity'; |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
cat -n apps/api/src/modules/identity/users/users.controller.ts | head -100Repository: pramodnarayana/nexiom
Length of output: 3787
🏁 Script executed:
cat -n apps/api/src/modules/identity/users/users.controller.ts | tail -80Repository: pramodnarayana/nexiom
Length of output: 3229
🏁 Script executed:
# Check TypeScript config for NodeNext and verbatim-module-syntax
find . -name "tsconfig*.json" -type f | head -5Repository: pramodnarayana/nexiom
Length of output: 204
🏁 Script executed:
cat -n apps/api/tsconfig.jsonRepository: pramodnarayana/nexiom
Length of output: 1080
🏁 Script executed:
cat -n tsconfig.jsonRepository: pramodnarayana/nexiom
Length of output: 110
🏁 Script executed:
find . -maxdepth 2 -name "tsconfig.json" -type f -exec grep -l "verbatimModuleSyntax" {} \;Repository: pramodnarayana/nexiom
Length of output: 47
🏁 Script executed:
grep -r "verbatimModuleSyntax" . --include="*.json" 2>/dev/null | head -20Repository: pramodnarayana/nexiom
Length of output: 191
🏁 Script executed:
# Look for `@nexiom/identity` package location and User export
find . -path "*/node_modules" -prune -o -path "*/@nexiom/identity*" -type f -print | head -20Repository: pramodnarayana/nexiom
Length of output: 47
🏁 Script executed:
find . -type f -name "*.ts" -o -name "*.d.ts" | xargs grep -l "export.*User" | grep -i identity | head -10Repository: pramodnarayana/nexiom
Length of output: 604
🏁 Script executed:
cat -n packages/identity/src/interfaces/types.tsRepository: pramodnarayana/nexiom
Length of output: 1708
🏁 Script executed:
find packages/identity/src -name "index.ts" -o -name "index.js" | xargs cat -nRepository: pramodnarayana/nexiom
Length of output: 1667
🏁 Script executed:
cat -n packages/identity/src/interfaces/index.tsRepository: pramodnarayana/nexiom
Length of output: 462
🏁 Script executed:
# Verify all uses of User in the file to be absolutely certain
rg "User" apps/api/src/modules/identity/users/users.controller.ts -nRepository: pramodnarayana/nexiom
Length of output: 1873
Move User to a type-only import.
User is only used in the UserListItem type definition (line 25) and should be imported as a type alongside ITenantProvider and IUserProvider. This keeps runtime values (USER_PROVIDER, TENANT_PROVIDER) separate from types, improving clarity and consistency.
Suggested fix
-import { USER_PROVIDER, TENANT_PROVIDER, User } from '@nexiom/identity';
-import type { ITenantProvider, IUserProvider } from '@nexiom/identity';
+import { USER_PROVIDER, TENANT_PROVIDER } from '@nexiom/identity';
+import type { User, ITenantProvider, IUserProvider } from '@nexiom/identity';📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| import { USER_PROVIDER, TENANT_PROVIDER, User } from '@nexiom/identity'; | |
| import type { ITenantProvider, IUserProvider } from '@nexiom/identity'; | |
| import { USER_PROVIDER, TENANT_PROVIDER } from '@nexiom/identity'; | |
| import type { User, ITenantProvider, IUserProvider } from '@nexiom/identity'; |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@apps/api/src/modules/identity/users/users.controller.ts` around lines 16 -
17, The import currently brings User as a runtime value; change it to a
type-only import so runtime providers remain separate: keep USER_PROVIDER and
TENANT_PROVIDER as value imports but import User using the TypeScript type-only
form alongside ITenantProvider and IUserProvider, and update any references in
the UserListItem type to use the type import (symbols: USER_PROVIDER,
TENANT_PROVIDER, User, ITenantProvider, IUserProvider, UserListItem).
| "exports": { | ||
| ".": { | ||
| "import": "./dist/index.js", | ||
| "default": "./dist/index.js" | ||
| }, | ||
| "scripts": { | ||
| "build": "tsc", | ||
| "db:generate": "drizzle-kit generate", | ||
| "db:migrate": "drizzle-kit migrate", | ||
| "db:studio": "drizzle-kit studio" | ||
| "./schema/identity": { | ||
| "import": "./dist/schema/identity.js", | ||
| "default": "./dist/schema/identity.js" | ||
| }, | ||
| "dependencies": { | ||
| "@nestjs/common": "^11.1.11", | ||
| "drizzle-orm": "^0.45.1", | ||
| "pg": "^8.16.3" | ||
| "./schema/tenant": { | ||
| "import": "./dist/schema/tenant.js", | ||
| "default": "./dist/schema/tenant.js" | ||
| }, | ||
| "devDependencies": { | ||
| "@types/pg": "^8.16.0", | ||
| "drizzle-kit": "^0.31.8", | ||
| "typescript": "^5.7.3" | ||
| "./dist/schema/identity": { | ||
| "import": "./dist/schema/identity.js", | ||
| "default": "./dist/schema/identity.js" | ||
| } | ||
| } No newline at end of file | ||
| }, |
There was a problem hiding this comment.
🧹 Nitpick | 🔵 Trivial
Consider adding types conditions to subpath exports for TypeScript consumers.
For better TypeScript support with subpath exports, consider adding explicit types conditions. This ensures TypeScript can correctly resolve type declarations for each subpath.
♻️ Proposed enhancement for type resolution
"exports": {
".": {
+ "types": "./dist/index.d.ts",
"import": "./dist/index.js",
"default": "./dist/index.js"
},
"./schema/identity": {
+ "types": "./dist/schema/identity.d.ts",
"import": "./dist/schema/identity.js",
"default": "./dist/schema/identity.js"
},
"./schema/tenant": {
+ "types": "./dist/schema/tenant.d.ts",
"import": "./dist/schema/tenant.js",
"default": "./dist/schema/tenant.js"
}
},🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@packages/database/package.json` around lines 7 - 24, Add explicit "types"
conditions to the package.json "exports" subpaths so TypeScript can resolve
declarations: update the root export (".") to include "types":
"./dist/index.d.ts" and add "types" entries to each subpath export such as
"./schema/identity", "./schema/tenant" (and the duplicate
"./dist/schema/identity") pointing to their corresponding declaration files
(e.g., "./dist/schema/identity.d.ts" and "./dist/schema/tenant.d.ts"); ensure
the file names match the built .d.ts outputs and keep both "import"/"default"
and the new "types" conditions for each export entry.
| @@ -1,14 +1,14 @@ | |||
| /* eslint-disable @typescript-eslint/no-unsafe-argument, @typescript-eslint/no-unsafe-return, @typescript-eslint/unbound-method */ | |||
| /* eslint-disable @typescript-eslint/no-unsafe-return */ | |||
There was a problem hiding this comment.
🧹 Nitpick | 🔵 Trivial
Consider consolidating ESLint suppressions for consistency.
The file uses a file-level disable for no-unsafe-return but adds an inline disable for unbound-method only at line 149. However, similar assertions exist throughout the file (e.g., lines 172, 193, 374, 378, etc.) that would also trigger the unbound-method rule when checking mock method calls like expect(db.update).not.toHaveBeenCalled().
For consistency with how invitations.controller.spec.ts handles this, consider using a file-level disable for unbound-method as well:
-/* eslint-disable `@typescript-eslint/no-unsafe-return` */
+/* eslint-disable `@typescript-eslint/no-unsafe-return`, `@typescript-eslint/unbound-method` */This would eliminate the need for the inline disable at line 149 and preempt any future linting errors on similar assertions.
Also applies to: 149-150
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@packages/identity/src/adapters/drizzle-user.adapter.spec.ts` at line 1, Add a
file-level ESLint disable for unbound-method to match the existing file-level
no-unsafe-return suppression and remove the inline disable near the affected
assertion; specifically, add "/* eslint-disable
`@typescript-eslint/unbound-method` */" at the top of the test file (same area
where /* eslint-disable `@typescript-eslint/no-unsafe-return` */ lives) so mock
method assertions like expect(db.update).not.toHaveBeenCalled() and other
expect(db.xxx) calls (seen around the inline disable at the current inline
suppression and elsewhere) no longer require inline disables.
| vi.doMock("../constants", async (importOriginal) => { | ||
| const actual = await importOriginal<typeof import("../constants")>(); | ||
| const actual = await importOriginal<typeof import("../constants.js")>(); |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
find . -type f -name "permission-seeder.spec.ts" | head -5Repository: pramodnarayana/nexiom
Length of output: 123
🏁 Script executed:
git ls-files | grep -E "(permission-seeder|rbac-seeding|constants)" | head -20Repository: pramodnarayana/nexiom
Length of output: 444
🏁 Script executed:
cat -n packages/identity/src/services/permission-seeder.spec.ts | sed -n '190,205p'Repository: pramodnarayana/nexiom
Length of output: 695
🏁 Script executed:
cat -n packages/identity/src/services/permission-seeder.spec.ts | sed -n '280,295p'Repository: pramodnarayana/nexiom
Length of output: 720
🏁 Script executed:
head -20 packages/identity/src/utils/rbac-seeding.tsRepository: pramodnarayana/nexiom
Length of output: 580
🏁 Script executed:
cat -n packages/identity/src/services/permission-seeder.spec.ts | sed -n '1,50p'Repository: pramodnarayana/nexiom
Length of output: 2235
🏁 Script executed:
cat packages/identity/package.json | grep -A 5 '"vitest"'Repository: pramodnarayana/nexiom
Length of output: 93
🏁 Script executed:
grep -r "vi.doMock" packages/identity/src --include="*.ts" | head -10Repository: pramodnarayana/nexiom
Length of output: 411
🏁 Script executed:
cat -n packages/identity/src/utils/rbac-seeding.ts | head -15Repository: pramodnarayana/nexiom
Length of output: 581
🏁 Script executed:
ls -la packages/identity/src/constants* 2>/dev/null || echo "No constants files found"Repository: pramodnarayana/nexiom
Length of output: 148
🏁 Script executed:
cat -n packages/identity/src/services/permission-seeder.spec.ts | sed -n '195,230p'Repository: pramodnarayana/nexiom
Length of output: 1669
🏁 Script executed:
cat -n packages/identity/src/services/permission-seeder.spec.ts | sed -n '283,320p'Repository: pramodnarayana/nexiom
Length of output: 955
🏁 Script executed:
cat -n packages/identity/src/services/permission-seeding.ts | head -30Repository: pramodnarayana/nexiom
Length of output: 149
🏁 Script executed:
find . -name "vitest.config.*" -o -name "vite.config.*" | xargs cat 2>/dev/null | head -50Repository: pramodnarayana/nexiom
Length of output: 1619
🏁 Script executed:
cat -n packages/identity/src/services/permission-seeder.spec.ts | sed -n '210,220p'Repository: pramodnarayana/nexiom
Length of output: 496
🏁 Script executed:
cat -n packages/identity/src/services/permission-seeder.spec.ts | sed -n '300,310p'Repository: pramodnarayana/nexiom
Length of output: 211
🏁 Script executed:
cat -n packages/identity/src/services/permission-seeder.spec.ts | grep -A 5 -B 5 "doUnmock"Repository: pramodnarayana/nexiom
Length of output: 688
🏁 Script executed:
rg "vi\.doMock|vi\.doUnmock" packages/identity/src --type ts -A 2 -B 2Repository: pramodnarayana/nexiom
Length of output: 2289
🌐 Web query:
Vitest doMock ESM module specifier matching .js extension
💡 Result:
In Vitest, vi.doMock() matches the module by its specifier string (the same string your code imports). So for ESM, if your code imports with a .js extension, your mock must use the exact same specifier, including .js:
// production code
import { foo } from './dep.js'
// test
vi.doMock('./dep.js', () => ({ foo: () => 'mocked' }))If you write vi.doMock('./dep', ...) it won’t match import './dep.js'.
To make this harder to mess up (and friendlier to refactors), Vitest also supports passing a module promise to vi.doMock, e.g.:
vi.doMock(import('./dep.js'), () => ({ foo: () => 'mocked' }))Vitest documents this “module promise” signature for both vi.mock and vi.doMock. (vitest.dev)
Separately, in Node ESM, relative imports must include a file extension (like .js), which is why your app code often needs ./dep.js in the first place. (nodejs.org)
Citations:
🏁 Script executed:
cat packages/identity/package.json | jq '.devDependencies'Repository: pramodnarayana/nexiom
Length of output: 628
🏁 Script executed:
find . -name "*.json" -path "*/node_modules/.bin*" -prune -o -type f -name "package.json" -exec grep -l "vitest" {} \; | head -5Repository: pramodnarayana/nexiom
Length of output: 319
🏁 Script executed:
cat -n packages/identity/src/services/permission-seeder.spec.ts | sed -n '195,250p'Repository: pramodnarayana/nexiom
Length of output: 2475
🏁 Script executed:
cat -n packages/identity/src/services/permission-seeder.spec.ts | sed -n '283,305p'Repository: pramodnarayana/nexiom
Length of output: 955
🏁 Script executed:
rg "importOriginal" packages/identity/src --type ts -B 2 -A 2Repository: pramodnarayana/nexiom
Length of output: 1279
Match the mocked specifier to the ESM import exactly: use ../constants.js.
In Vitest ESM mode, vi.doMock() requires the module specifier to match exactly what the code imports. Lines 197 and 285 register mocks for ../constants, but rbac-seeding.js imports from ../constants.js, so the mocks never apply—the real constants module is used instead.
Suggested fix
- vi.doMock("../constants", async (importOriginal) => {
+ vi.doMock("../constants.js", async (importOriginal) => {
const actual = await importOriginal<typeof import("../constants.js")>();Apply the same change to both vi.doMock() calls (lines 197 and 285) and their corresponding vi.doUnmock("../constants") calls (lines 280 and 302).
Also applies to: 285–286 and 280, 302
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@packages/identity/src/services/permission-seeder.spec.ts` around lines 197 -
198, The mocks registered with vi.doMock/vi.doUnmock are using the wrong module
specifier ("../constants") so they don't apply to the ESM import
("../constants.js"); update every vi.doMock("../constants") and corresponding
vi.doUnmock("../constants") in this test (including the calls around the
vi.doMock at lines shown) to use the exact ESM specifier "../constants.js" so
the mocks match the imports used by rbac-seeding.js.
| ], | ||
| "compilerOptions": { | ||
| "module": "commonjs", | ||
| "module": "NodeNext", |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Check if tsconfig.lib.json exists and has compatible module settings
fd -t f "tsconfig.lib.json" packages/pieces/salesforce --exec cat {}Repository: pramodnarayana/nexiom
Length of output: 414
🌐 Web query:
TypeScript module option valid values nodenext capitalization
💡 Result:
For tsconfig.json’s compilerOptions.module, the official value is spelled nodenext (along with node16, node18, node20, etc.). TypeScript’s TSConfig reference lists it in lowercase. [1]
Capitalization isn’t significant in practice: TypeScript normalizes these enum-like option values (lowercasing them internally), so "module": "NodeNext" and "module": "nodenext" are treated the same—even though diagnostics often print NodeNext. [2]
Recommendation: use the spelling shown in the TSConfig docs ("module": "nodenext") for consistency. [1]
Sources
- [1] TypeScript TSConfig option:
module(listsnode16/node18/node20/nodenext) — https://www.typescriptlang.org/tsconfig/module.html - [2] Stack Overflow: TSConfig option values are normalized/lowercased (case-insensitive in effect) — https://stackoverflow.com/questions/77955801/are-typescript-configuration-options-case-sensitive
Use lowercase nodenext for consistency with TypeScript documentation.
The module system is correctly updated to nodenext for ESM compatibility. While NodeNext is functionally equivalent (TypeScript normalizes the casing internally), the official TypeScript documentation and your referenced tsconfig.lib.json use lowercase nodenext. Align to that convention for consistency.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@packages/pieces/salesforce/tsconfig.json` at line 11, Update the tsconfig
"module" value from "NodeNext" to the lowercase "nodenext" for consistency with
TypeScript docs and the project's tsconfig.lib.json; locate the "module"
property in the packages/pieces/salesforce tsconfig and replace "NodeNext" with
"nodenext" (the JSON key is "module") so casing matches the documented
convention.
| - Update the schema with `anonymizeIp` and `anonymizeUserAgent` helpers to hash/truncate data before insert. | ||
| - Create a `background` (or `jobs`) module in `apps/api` using `@nestjs/schedule`. | ||
| - Implement `runPIICleanup` to run daily, finding sessions older than 30 days and anonymizing their PII, emitting audit logs. | ||
| - Add a Drizzle migration to backfill and anonymize existing old sessions. |
There was a problem hiding this comment.
🧹 Nitpick | 🔵 Trivial
Pick one retention model for session PII.
Line 23 conflicts with Lines 25-26: anonymizing IP/user-agent before insert removes the raw data immediately, which undercuts the proposed “retain for 30 days, then anonymize” cleanup flow. Please decide whether the goal is immediate minimization or time-bounded retention, then document one consistent approach.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@TECHNICAL_DEBT.md` around lines 23 - 26, Decide and document a single PII
retention model and make the docs and artifacts consistent: either (A) choose
immediate minimization — keep the `anonymizeIp` and `anonymizeUserAgent` helpers
and remove the `runPIICleanup` job, background module, and backfill migration;
or (B) choose time-bounded retention — keep raw IP/user-agent on insert, remove
or rename the pre-insert anonymization helpers, and implement `runPIICleanup`
inside the `background`/`jobs` module (using `@nestjs/schedule`) to anonymize
sessions older than 30 days and add a Drizzle migration to backfill existing old
sessions; update TECHNICAL_DEBT.md so it clearly states which model is chosen
and lists the matching set of tasks (helpers vs job + migration) and references
to `anonymizeIp`, `anonymizeUserAgent`, `runPIICleanup`, and the background/jobs
module.
@coderabbitai skip
Summary by CodeRabbit
New Features
Chores