@forklaunch/core 2.0.0 → 2.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/services/complianceDataService.d.ts +42 -2
- package/lib/services/complianceDataService.d.ts.map +1 -1
- package/lib/services/index.js +123 -15
- package/lib/services/index.js.map +1 -1
- package/lib/services/index.mjs +123 -15
- package/lib/services/index.mjs.map +1 -1
- package/lib/services/retentionService.d.ts +13 -0
- package/lib/services/retentionService.d.ts.map +1 -1
- package/package.json +9 -9
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/services/index.ts","../../src/environment/loadCascadingEnv.ts","../../src/services/configInjector.ts","../../src/services/guards/isConstructed.ts","../../src/services/types/configInjector.types.ts","../../src/services/guards/isConstructedSingleton.ts","../../src/services/guards/isConstructor.ts","../../src/persistence/complianceTypes.ts","../../src/services/retentionService.ts","../../src/services/complianceDataService.ts","../../src/services/runRetentionEnforcement.ts"],"sourcesContent":["export { getEnvVar } from '@forklaunch/common';\nexport * from '../environment';\nexport { createConfigInjector } from './configInjector';\nexport type { ConfigInjector, ValidConfigInjector } from './configInjector';\nexport * from './types/configInjector.types';\nexport * from './types/entityManager.types';\nexport * from './types/service.types';\nexport {\n RetentionService,\n type EnforcementOptions,\n type EnforcementResult\n} from './retentionService';\nexport {\n ComplianceDataService,\n type EraseResult,\n type ExportResult,\n type UserIdFieldOverrides\n} from './complianceDataService';\nexport { runRetentionEnforcement } from './runRetentionEnforcement';\n","import dotenv from 'dotenv';\nimport { existsSync } from 'fs';\nimport { dirname, resolve } from 'path';\n\n/**\n * Gets cascading environment file paths: collects all .env.local files from project directory up to root\n * Root detection uses: .forklaunch/manifest.toml\n */\nexport function getCascadingEnvPaths(\n projectEnvPath: string | undefined,\n projectRoot: string = process.cwd()\n): {\n rootEnvExists: boolean;\n projectEnvExists: boolean;\n rootEnvPath?: string;\n projectEnvFilePath?: string;\n loadOrder: string[];\n} {\n const result = {\n rootEnvExists: false,\n projectEnvExists: false,\n rootEnvPath: undefined as string | undefined,\n projectEnvFilePath: undefined as string | undefined,\n loadOrder: [] as string[]\n };\n\n const applicationRoot = findApplicationRoot(projectRoot);\n\n const envLocalFiles = collectEnvLocalFiles(projectRoot, applicationRoot);\n result.loadOrder.push(...envLocalFiles);\n\n const rootEnvPath = resolve(applicationRoot, '.env.local');\n if (envLocalFiles.includes(rootEnvPath)) {\n result.rootEnvExists = true;\n result.rootEnvPath = rootEnvPath;\n }\n\n if (projectEnvPath) {\n const fullProjectEnvPath = resolve(projectRoot, projectEnvPath);\n if (existsSync(fullProjectEnvPath)) {\n result.projectEnvExists = true;\n result.projectEnvFilePath = fullProjectEnvPath;\n if (!result.loadOrder.includes(fullProjectEnvPath)) {\n result.loadOrder.push(fullProjectEnvPath);\n }\n }\n }\n\n return result;\n}\n\n/**\n * Loads environment variables with cascading precedence: all .env.local files from root to project, then project env file\n */\nexport function loadCascadingEnv(\n projectEnvPath: string | undefined,\n projectRoot: string = process.cwd()\n): {\n rootEnvLoaded: boolean;\n projectEnvLoaded: boolean;\n rootEnvPath?: string;\n projectEnvFilePath?: string;\n envFilesLoaded: string[];\n totalEnvFilesLoaded: number;\n} {\n const paths = getCascadingEnvPaths(projectEnvPath, projectRoot);\n const result = {\n rootEnvLoaded: false,\n projectEnvLoaded: false,\n rootEnvPath: paths.rootEnvPath,\n projectEnvFilePath: paths.projectEnvFilePath,\n envFilesLoaded: [] as string[],\n totalEnvFilesLoaded: 0\n };\n\n const seen = new Set<string>();\n for (const envPath of paths.loadOrder) {\n if (seen.has(envPath)) {\n continue;\n }\n seen.add(envPath);\n\n const envResult = dotenv.config({\n path: envPath,\n override: true\n });\n\n if (!envResult?.error) {\n result.envFilesLoaded.push(envPath);\n result.totalEnvFilesLoaded++;\n\n if (envPath === paths.rootEnvPath) {\n result.rootEnvLoaded = true;\n }\n if (envPath === paths.projectEnvFilePath) {\n result.projectEnvLoaded = true;\n }\n }\n }\n\n return result;\n}\n\n/**\n * Collects all .env.local files from project directory up to application root\n * Returns paths in order from root to project (for proper precedence)\n */\nfunction collectEnvLocalFiles(\n projectRoot: string,\n applicationRoot: string\n): string[] {\n const envLocalPaths: string[] = [];\n\n let currentPath = resolve(projectRoot);\n const normalizedAppRoot = resolve(applicationRoot);\n\n while (currentPath.length >= normalizedAppRoot.length) {\n const envLocalPath = resolve(currentPath, '.env.local');\n if (existsSync(envLocalPath)) {\n envLocalPaths.push(envLocalPath);\n }\n\n if (currentPath === normalizedAppRoot) {\n break;\n }\n\n const parentPath = dirname(currentPath);\n if (parentPath === currentPath) {\n break;\n }\n\n currentPath = parentPath;\n }\n\n return envLocalPaths.reverse();\n}\n\n/**\n * Finds application root by looking for .forklaunch/manifest.toml\n */\nexport function findApplicationRoot(startPath: string): string {\n let currentPath = resolve(startPath);\n const originalStart = currentPath;\n const maxDepth = 10;\n let depth = 0;\n\n while (depth < maxDepth) {\n const forklaunchManifest = resolve(\n currentPath,\n '.forklaunch',\n 'manifest.toml'\n );\n if (existsSync(forklaunchManifest)) {\n return currentPath;\n }\n\n const parentPath = dirname(currentPath);\n if (parentPath === currentPath) {\n break;\n }\n\n currentPath = parentPath;\n depth++;\n }\n\n return originalStart;\n}\n","import { extractArgumentNames, isNever } from '@forklaunch/common';\nimport {\n AnySchemaValidator,\n IdiomaticSchema,\n ParseResult,\n prettyPrintParseErrors,\n SchemaValidator\n} from '@forklaunch/validator';\nimport { isConstructed } from './guards/isConstructed';\nimport { isConstructedSingleton } from './guards/isConstructedSingleton';\nimport { isConstructor } from './guards/isConstructor';\nimport {\n ConfigValidator,\n Constructed,\n ConstructedSingleton,\n Lifetime,\n ResolvedConfigValidator,\n SchemaConstructor,\n SchemaFunction,\n Singleton\n} from './types/configInjector.types';\n\nexport function createConfigInjector<\n SV extends AnySchemaValidator,\n CV extends ConfigValidator<SV>\n>(\n schemaValidator: SV,\n dependenciesDefinition: {\n [K in keyof CV]:\n | Singleton<\n CV[K],\n Omit<ResolvedConfigValidator<SV, CV>, K>,\n ResolvedConfigValidator<SV, CV>[K]\n >\n | Constructed<\n CV[K],\n Omit<ResolvedConfigValidator<SV, CV>, K>,\n ResolvedConfigValidator<SV, CV>[K]\n >;\n }\n) {\n return new ConfigInjector<SV, CV>(\n schemaValidator,\n dependenciesDefinition\n ).load();\n}\n\nexport class ConfigInjector<\n SV extends AnySchemaValidator,\n CV extends ConfigValidator<SV>\n> {\n instances: {\n [K in keyof CV]?: ResolvedConfigValidator<SV, CV>[K];\n } = {};\n\n readonly configShapes: CV;\n\n load(inheritedScopeInstances?: {\n [K in keyof CV]?: ResolvedConfigValidator<SV, CV>[K];\n }): this {\n for (const token in inheritedScopeInstances) {\n this.instances[token] = inheritedScopeInstances[token];\n }\n\n for (const token in this.dependenciesDefinition) {\n const definition = this.dependenciesDefinition[token];\n if (\n definition.lifetime === Lifetime.Singleton &&\n !this.instances[token]\n ) {\n if (\n isConstructedSingleton<\n CV[typeof token],\n Omit<ResolvedConfigValidator<SV, CV>, typeof token>,\n ResolvedConfigValidator<SV, CV>[typeof token]\n >(definition)\n ) {\n this.instances[token] = this.resolveInstance<typeof token>(\n token,\n definition\n );\n } else {\n this.instances[token] = definition.value;\n }\n }\n }\n return this;\n }\n\n private resolveInstance<T extends keyof CV>(\n token: T,\n definition:\n | ConstructedSingleton<\n CV[T],\n Omit<ResolvedConfigValidator<SV, CV>, T>,\n ResolvedConfigValidator<SV, CV>[T]\n >\n | Constructed<\n CV[T],\n Omit<ResolvedConfigValidator<SV, CV>, T>,\n ResolvedConfigValidator<SV, CV>[T]\n >,\n context?: Record<string, unknown>,\n resolutionPath: (keyof CV)[] = []\n ): ResolvedConfigValidator<SV, CV>[T] {\n if (process.env.FORKLAUNCH_MODE === 'openapi') {\n // The handler below installs `apply` and `construct` traps, which are\n // only valid on a callable target — hence a function rather than `{}`.\n // The proxy is what makes every string key resolve; the target itself\n // has none, so this shape cannot be inferred and has to be asserted.\n // Named and asserted once, rather than laundered through `unknown`.\n type NoopTarget = ((...args: never[]) => unknown) &\n Record<string, unknown>;\n const noopProxy: Record<string, unknown> = new Proxy(\n function () {} as NoopTarget,\n {\n get(_target, prop) {\n if (prop === Symbol.toPrimitive) return () => '';\n if (prop === 'toString' || prop === 'valueOf') return () => '';\n if (prop === 'then') return undefined;\n if (prop === Symbol.iterator) return function* () {};\n if (prop === Symbol.asyncIterator) return async function* () {};\n return noopProxy;\n },\n set() {\n return true;\n },\n has() {\n return true;\n },\n deleteProperty() {\n return true;\n },\n ownKeys() {\n return ['prototype'];\n },\n getOwnPropertyDescriptor(_target, prop) {\n if (prop === 'prototype') {\n return { configurable: false, enumerable: false, writable: true };\n }\n return { configurable: true, enumerable: true };\n },\n apply() {\n return noopProxy;\n },\n construct() {\n return noopProxy as object;\n }\n }\n );\n return noopProxy as ResolvedConfigValidator<SV, CV>[T];\n }\n\n const rawInjectorArgument = extractArgumentNames(definition.factory)[0];\n // short circuit as no args\n if (!rawInjectorArgument || rawInjectorArgument === '_args') {\n return definition.factory(\n {} as Omit<ResolvedConfigValidator<SV, CV>, T>,\n context ?? {},\n this.resolve.bind(this)\n );\n }\n\n // Normalize whitespace to handle both single-line and multiline formatting\n const injectorArgument = rawInjectorArgument.replace(/\\s+/g, '');\n\n if (!injectorArgument.startsWith('{') || !injectorArgument.endsWith('}')) {\n throw new Error(\n `Invalid injector argument for ${String(\n token\n )}: ${injectorArgument}. Please use object destructuring syntax: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment.`\n );\n }\n const resolvedArguments = Object.fromEntries(\n injectorArgument\n .slice(1, -1)\n .split(',')\n .filter((arg) => arg.length > 0)\n .map((arg) => arg.split(':')[0].trim())\n .map((arg) => {\n const newResolutionPath = [...resolutionPath, token];\n if (resolutionPath.includes(arg)) {\n throw new Error(\n `Circular dependency detected: ${newResolutionPath.join(\n ' -> '\n )} -> ${arg}`\n );\n }\n const resolvedArg = this.resolve(arg, context, newResolutionPath);\n return [arg, resolvedArg];\n })\n ) as Omit<ResolvedConfigValidator<SV, CV>, T>;\n return definition.factory(\n resolvedArguments,\n context ?? {},\n this.resolve.bind(this)\n );\n }\n\n constructor(\n private schemaValidator: SV,\n private dependenciesDefinition: {\n [K in keyof CV]: (\n | Singleton<\n CV[K],\n Omit<ResolvedConfigValidator<SV, CV>, K>,\n ResolvedConfigValidator<SV, CV>[K]\n >\n | Constructed<\n CV[K],\n Omit<ResolvedConfigValidator<SV, CV>, K>,\n ResolvedConfigValidator<SV, CV>[K]\n >\n ) & {\n type: CV[K];\n };\n }\n ) {\n this.configShapes = Object.entries(this.dependenciesDefinition).reduce(\n (acc, [key, { type }]) => ({\n ...acc,\n [key]: type\n }),\n {} as Record<keyof CV, CV[keyof CV]>\n ) as CV;\n }\n\n safeValidateConfigSingletons(): ParseResult<ValidConfigInjector<SV, CV>> {\n const validNonSchemaSingletons = Object.entries(this.configShapes).reduce<\n ParseResult<ResolvedConfigValidator<SV, CV>>\n >(\n (acc, [key, value]) => {\n if (\n this.dependenciesDefinition[key].lifetime === Lifetime.Singleton &&\n !(this.schemaValidator as SchemaValidator).isSchema<\n SchemaFunction<SV> | SchemaConstructor<SV> | IdiomaticSchema<SV>\n >(value) &&\n isConstructor(value)\n ) {\n if (!(this.instances[key] instanceof value)) {\n const expected = value.name;\n const receivedValue: unknown = this.instances[key];\n const received = isConstructed(receivedValue)\n ? receivedValue.constructor.name\n : typeof receivedValue;\n\n if (acc.ok) {\n acc = {\n ok: false,\n errors: []\n };\n }\n acc.errors?.push({\n message: `Expected ${expected}, received ${received}`,\n path: [key]\n });\n } else {\n if (acc.ok) {\n acc = {\n ok: true,\n value: {\n ...acc.value,\n [key]: this.instances[key]\n }\n };\n }\n }\n return acc;\n }\n return acc;\n },\n {\n ok: true,\n value: {} as ResolvedConfigValidator<SV, CV>\n }\n );\n\n const singletons = Object.fromEntries(\n Object.entries(this.configShapes).filter(\n ([key, value]) =>\n this.dependenciesDefinition[key].lifetime === Lifetime.Singleton &&\n (this.schemaValidator as SchemaValidator).isSchema(value)\n )\n );\n const schemaSingletonParseResult = (\n this.schemaValidator as SchemaValidator\n ).parse(\n (this.schemaValidator as SchemaValidator).schemify(singletons),\n Object.fromEntries(\n Object.keys(singletons).map((key) => {\n const dependency = this.dependenciesDefinition[key];\n return [\n key,\n dependency.lifetime === Lifetime.Singleton\n ? this.instances[key]\n : undefined\n ];\n })\n )\n );\n\n const configKeys = Object.keys(this.configShapes);\n\n return validNonSchemaSingletons.ok && schemaSingletonParseResult.ok\n ? {\n ok: true as const,\n value: new ValidConfigInjector<SV, CV>(\n this.schemaValidator,\n this.dependenciesDefinition\n ).load({ ...this.instances })\n }\n : {\n ok: false as const,\n errors: [\n ...(!validNonSchemaSingletons.ok && validNonSchemaSingletons.errors\n ? validNonSchemaSingletons.errors\n : []),\n ...(!schemaSingletonParseResult.ok &&\n schemaSingletonParseResult.errors\n ? schemaSingletonParseResult.errors\n : [])\n ].sort(\n (a, b) =>\n configKeys.indexOf(a.path[0]) - configKeys.indexOf(b.path[0])\n )\n };\n }\n\n validateConfigSingletons(configName: string): ValidConfigInjector<SV, CV> {\n if (process.env.FORKLAUNCH_MODE === 'openapi') {\n return this.createScope() as ValidConfigInjector<SV, CV>;\n }\n\n const safeValidateResult = this.safeValidateConfigSingletons();\n\n if (safeValidateResult.ok) {\n return safeValidateResult.value;\n }\n\n throw new Error(\n prettyPrintParseErrors(safeValidateResult.errors, configName)\n );\n }\n\n resolve<T extends keyof CV>(\n token: T,\n context?: Record<string, unknown>,\n resolutionPath: (keyof CV)[] = []\n ): ResolvedConfigValidator<SV, CV>[T] {\n const instance = this.instances[token];\n if (!instance) {\n const definition = this.dependenciesDefinition[token];\n\n if (!definition) {\n throw new Error(`Unable to resolve dependency ${String(token)}`);\n }\n\n switch (definition.lifetime) {\n case Lifetime.Singleton: {\n if (\n isConstructedSingleton<\n CV[T],\n Omit<ResolvedConfigValidator<SV, CV>, T>,\n ResolvedConfigValidator<SV, CV>[T]\n >(definition) &&\n !this.instances[token]\n ) {\n this.instances[token] = this.resolveInstance<T>(\n token,\n definition,\n context,\n resolutionPath\n );\n }\n return this.instances[token] as ResolvedConfigValidator<SV, CV>[T];\n }\n case Lifetime.Scoped: {\n if (\n !isConstructed<\n CV[T],\n Omit<ResolvedConfigValidator<SV, CV>, T>,\n ResolvedConfigValidator<SV, CV>[T]\n >(definition)\n ) {\n throw new Error(\n `Invalid dependency definition for ${String(token)}`\n );\n }\n\n const scopedInstance = this.resolveInstance<T>(\n token,\n definition,\n context,\n resolutionPath\n );\n this.instances[token] = scopedInstance;\n return scopedInstance;\n }\n case Lifetime.Transient: {\n if (\n !isConstructed<\n CV[T],\n Omit<ResolvedConfigValidator<SV, CV>, T>,\n ResolvedConfigValidator<SV, CV>[T]\n >(definition)\n ) {\n throw new Error(\n `Invalid dependency definition for ${String(token)}`\n );\n }\n\n return this.resolveInstance<T>(\n token,\n definition,\n context,\n resolutionPath\n );\n }\n default: {\n isNever(definition);\n throw new Error(\n `Unable to resolve lifetime for dependency ${String(\n token\n )}, ${resolutionPath}`\n );\n }\n }\n } else {\n return instance;\n }\n }\n\n scopedResolver<T extends keyof CV>(\n token: T,\n context?: Record<string, unknown>,\n resolutionPath: (keyof CV)[] = []\n ): (options?: {\n scope?: ConfigInjector<SV, CV>;\n context?: Record<string, unknown>;\n }) => ResolvedConfigValidator<SV, CV>[T] {\n return (options) =>\n (options?.scope ?? this.createScope()).resolve<T>(\n token,\n options?.context ? { ...context, ...options.context } : context,\n resolutionPath\n );\n }\n\n createScope(): ConfigInjector<SV, CV> {\n const singletons: Record<string, unknown> = {};\n for (const dependency in this.dependenciesDefinition) {\n if (\n this.dependenciesDefinition[dependency].lifetime === Lifetime.Singleton\n ) {\n singletons[dependency] = this.instances[dependency];\n }\n }\n return new ConfigInjector<SV, CV>(\n this.schemaValidator,\n this.dependenciesDefinition\n ).load(singletons as ResolvedConfigValidator<SV, CV>);\n }\n\n dispose(): void {\n this.instances = {};\n this.load();\n }\n\n chain<ChainedCV extends ConfigValidator<SV>>(dependenciesDefinition: {\n [K in keyof ChainedCV]: {\n type: ChainedCV[K];\n } & (\n | Singleton<\n ChainedCV[K],\n Omit<ResolvedConfigValidator<SV, CV & ChainedCV>, K>,\n ResolvedConfigValidator<SV, ChainedCV>[K]\n >\n | Constructed<\n ChainedCV[K],\n Omit<ResolvedConfigValidator<SV, CV & ChainedCV>, K>,\n ResolvedConfigValidator<SV, ChainedCV>[K]\n >\n );\n }): ConfigInjector<SV, CV & ChainedCV> {\n return new ConfigInjector<SV, CV>(this.schemaValidator, {\n ...this.dependenciesDefinition,\n ...dependenciesDefinition\n // The hop through unknown is required, not habitual. A direct assertion\n // makes the compiler structurally compare two deeply recursive\n // ConfigInjector instantiations and it gives up with TS2589, \"type\n // instantiation is excessively deep and possibly infinite\". Going via\n // unknown stops that comparison. The widening itself is sound: the\n // dependencies of both injectors are merged immediately above.\n }).load({ ...this.instances }) as unknown as ConfigInjector<\n SV,\n CV & ChainedCV\n >;\n }\n\n tokens(): {\n [K in keyof CV]: K;\n } {\n return Object.fromEntries(\n Object.keys(this.dependenciesDefinition).map((key) => [key, key])\n ) as {\n [K in keyof CV]: K;\n };\n }\n}\n\nexport class ValidConfigInjector<\n SV extends AnySchemaValidator,\n CV extends ConfigValidator<SV>\n> extends ConfigInjector<SV, CV> {\n validConfigInjector!: void;\n}\n","import { Constructed } from '../types/configInjector.types';\n\nexport function isConstructed<Type, Args, Return>(\n value: unknown\n): value is Constructed<Type, Args, Return> {\n return (\n typeof value === 'object' &&\n value != null &&\n 'constructor' in value &&\n value.constructor != null\n );\n}\n","import {\n AnySchemaValidator,\n IdiomaticSchema,\n Schema\n} from '@forklaunch/validator';\nimport { ConfigInjector } from '../configInjector';\n\nexport const Lifetime = {\n Singleton: 0,\n Transient: 1,\n Scoped: 2\n} as const;\nexport type Lifetime = (typeof Lifetime)[keyof typeof Lifetime];\n\nexport type Singleton<Type, Args, Value> =\n | {\n lifetime: typeof Lifetime.Singleton;\n type: Type;\n value: Value;\n }\n | ConstructedSingleton<Type, Args, Value>;\n\nexport type ConstructedSingleton<Type, Args, Return> = {\n lifetime: typeof Lifetime.Singleton;\n type: Type;\n factory: (\n args: Args,\n context: Record<string, unknown>,\n resolve: <T extends keyof Args>(\n token: T,\n context?: Record<string, unknown>\n ) => Args[T]\n ) => Return;\n};\n\nexport type Constructed<Type, Args, Return> = {\n lifetime: typeof Lifetime.Transient | typeof Lifetime.Scoped;\n type: Type;\n factory: (\n args: Args,\n context: Record<string, unknown>,\n resolve: <T extends keyof Args>(\n token: T,\n context?: Record<string, unknown>\n ) => Args[T]\n ) => Return;\n};\n\nexport type Constructor = new (...args: never[]) => unknown;\nexport type SchemaConstructor<SV extends AnySchemaValidator> = new (\n ...args: unknown[]\n) => IdiomaticSchema<SV>;\nexport type Function = (...args: never[]) => unknown;\nexport type FunctionToConstructor = (\n ...args: never[]\n) => new (...args: never[]) => unknown;\nexport type SchemaFunction<SV extends AnySchemaValidator> = (\n args: unknown\n) => IdiomaticSchema<SV>;\n\nexport type ConfigTypes<SV extends AnySchemaValidator> =\n | Function\n | SchemaFunction<SV>\n | Constructor\n | SchemaConstructor<SV>\n | IdiomaticSchema<SV>;\n\nexport type ConfigValidator<SV extends AnySchemaValidator> = Record<\n string,\n ConfigTypes<SV> | Record<string, ConfigTypes<SV>>\n>;\n\ntype ResolveConfigValue<SV extends AnySchemaValidator, T> =\n T extends SchemaConstructor<SV>\n ? Schema<InstanceType<T>, SV>\n : T extends SchemaFunction<SV>\n ? (...args: Parameters<T>) => Schema<ReturnType<T>, SV>\n : T extends FunctionToConstructor\n ? (...args: Parameters<T>) => InstanceType<ReturnType<T>>\n : T extends Function\n ? (...args: Parameters<T>) => ReturnType<T>\n : T extends Constructor\n ? InstanceType<T>\n : T extends IdiomaticSchema<SV>\n ? Schema<T, SV>\n : T extends Record<string, ConfigTypes<SV>>\n ? {\n [K in keyof T]: ResolveConfigValue<SV, T[K]>;\n }\n : Schema<T, SV>;\n\nexport type ResolvedConfigValidator<\n SV extends AnySchemaValidator,\n CV extends ConfigValidator<SV>\n> = {\n [M in keyof CV]: ResolveConfigValue<SV, CV[M]>;\n};\n\nexport type ScopedDependencyFactory<\n SV extends AnySchemaValidator,\n CV extends ConfigValidator<SV>,\n M extends keyof CV\n> = (scope?: ConfigInjector<SV, CV>) => ResolvedConfigValidator<SV, CV>[M];\n","import { ConstructedSingleton, Lifetime } from '../types/configInjector.types';\n\nexport function isConstructedSingleton<Type, Args, Return>(\n value: unknown\n): value is ConstructedSingleton<Type, Args, Return> {\n return (\n typeof value === 'object' &&\n value != null &&\n 'lifetime' in value &&\n value.lifetime === Lifetime.Singleton &&\n 'factory' in value\n );\n}\n","import { Constructor } from '../types/configInjector.types';\n\nexport function isConstructor(value: unknown): value is Constructor {\n return (\n typeof value === 'function' &&\n value.constructor != null &&\n value.prototype != null\n );\n}\n","export const ComplianceLevel = {\n pii: 'pii',\n phi: 'phi',\n pci: 'pci',\n none: 'none'\n} as const;\nexport type ComplianceLevel =\n (typeof ComplianceLevel)[keyof typeof ComplianceLevel];\n\nexport const COMPLIANCE_KEY = '~compliance' as const;\n\n// ---------------------------------------------------------------------------\n// Registry\n// ---------------------------------------------------------------------------\n\nconst complianceRegistry = new Map<string, Map<string, ComplianceLevel>>();\n\nexport function registerEntityCompliance(\n entityName: string,\n fields: Map<string, ComplianceLevel>\n): void {\n complianceRegistry.set(entityName, fields);\n}\n\nexport function getComplianceMetadata(\n entityName: string,\n fieldName: string\n): ComplianceLevel {\n return complianceRegistry.get(entityName)?.get(fieldName) ?? 'none';\n}\n\nexport function getEntityComplianceFields(\n entityName: string\n): Map<string, ComplianceLevel> | undefined {\n return complianceRegistry.get(entityName);\n}\n\nexport function entityHasEncryptedFields(entityName: string): boolean {\n const fields = complianceRegistry.get(entityName);\n if (!fields) return false;\n for (const level of fields.values()) {\n if (level === 'phi' || level === 'pci') return true;\n }\n return false;\n}\n\n// ---------------------------------------------------------------------------\n// Retention types and registry\n// ---------------------------------------------------------------------------\n\nexport const RetentionAction = {\n delete: 'delete',\n anonymize: 'anonymize'\n} as const;\nexport type RetentionAction =\n (typeof RetentionAction)[keyof typeof RetentionAction];\n\nexport interface RetentionPolicy {\n duration: string;\n action: RetentionAction;\n}\n\nexport const RetentionDuration = {\n days: (n: number): string => `P${n}D`,\n months: (n: number): string => `P${n}M`,\n years: (n: number): string => `P${n}Y`\n} as const;\n\nexport interface ParsedDuration {\n years: number;\n months: number;\n days: number;\n}\n\nconst DURATION_REGEX = /^P(?:(\\d+)Y)?(?:(\\d+)M)?(?:(\\d+)D)?$/;\n\n/**\n * Parse an ISO 8601 duration string into calendar units.\n * Returns structured units to enable calendar-aware date arithmetic.\n */\nexport function parseDuration(iso: string): ParsedDuration {\n const match = DURATION_REGEX.exec(iso);\n if (!match) {\n throw new Error(\n `Invalid ISO 8601 duration: '${iso}'. Expected format: P[n]Y[n]M[n]D`\n );\n }\n const years = parseInt(match[1] || '0', 10);\n const months = parseInt(match[2] || '0', 10);\n const days = parseInt(match[3] || '0', 10);\n\n // Approximate total days for minimum validation only\n const approxDays = years * 365 + months * 30 + days;\n if (approxDays < 1) {\n throw new Error(\n `Retention duration must be >= 1 day (P1D). Got: '${iso}' (${approxDays} approx days)`\n );\n }\n\n return { years, months, days };\n}\n\n/**\n * Subtract a parsed duration from a date using calendar-aware arithmetic.\n * Handles month-end clamping and leap years correctly.\n */\nexport function subtractDuration(from: Date, duration: ParsedDuration): Date {\n const result = new Date(from);\n result.setFullYear(result.getFullYear() - duration.years);\n result.setMonth(result.getMonth() - duration.months);\n result.setDate(result.getDate() - duration.days);\n return result;\n}\n\nconst retentionRegistry = new Map<string, RetentionPolicy>();\n\n// ---------------------------------------------------------------------------\n// User ID field registry — maps entity name to the field linking records to a user\n// ---------------------------------------------------------------------------\n\nconst DEFAULT_USER_ID_FIELD = 'userId';\nconst userIdFieldRegistry = new Map<string, string>();\n\nexport function registerEntityUserIdField(\n entityName: string,\n field: string\n): void {\n userIdFieldRegistry.set(entityName, field);\n}\n\nexport function getEntityUserIdField(entityName: string): string {\n return userIdFieldRegistry.get(entityName) ?? DEFAULT_USER_ID_FIELD;\n}\n\nexport function getAllUserIdFields(): ReadonlyMap<string, string> {\n return userIdFieldRegistry;\n}\n\nexport function registerEntityRetention(\n entityName: string,\n policy: RetentionPolicy\n): void {\n retentionRegistry.set(entityName, policy);\n}\n\nexport function getEntityRetention(\n entityName: string\n): RetentionPolicy | undefined {\n return retentionRegistry.get(entityName);\n}\n\nexport function getAllRetentionPolicies(): ReadonlyMap<\n string,\n RetentionPolicy\n> {\n return retentionRegistry;\n}\n\n// ---------------------------------------------------------------------------\n// Module augmentation — adds .compliance() via PropertyOptions\n// ---------------------------------------------------------------------------\n\n/**\n * Adds `compliance` to PropertyOptions, which flows into IncludeKeys\n * for all scalar/enum/embedded builders (PropertyOptions is extended by\n * EnumOptions and EmbeddedOptions). Relation builders use ReferenceOptions\n * instead, so they don't get .compliance() — which is what we want.\n */\ndeclare module '@mikro-orm/core' {\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n interface PropertyOptions<Owner> {\n compliance?: ComplianceLevel;\n }\n\n interface UniversalPropertyOptionsBuilder<Value, Options, IncludeKeys> {\n /**\n * The level is captured as a literal type parameter rather than widened to\n * `ComplianceLevel`, so the marker records WHICH classification a property\n * carries and not merely that it has one.\n *\n * That distinction is what makes {@link EncryptedKeysOf} possible: `pii`,\n * `phi` and `pci` are encrypted at rest and `none` is not, and a `true`\n * marker cannot tell them apart. See {@link RequiresEncryptionContext} for\n * why the difference matters at a call site.\n */\n compliance<const L extends ComplianceLevel>(\n level: L\n ): Pick<\n UniversalPropertyOptionsBuilder<\n Value,\n Options & { readonly '~c': L },\n IncludeKeys\n >,\n IncludeKeys & keyof UniversalPropertyOptionsBuilder<never, never, never>\n >;\n }\n}\n\n// ---------------------------------------------------------------------------\n// Type-level compliance introspection\n// ---------------------------------------------------------------------------\n\n/**\n * The classifications that cause a column to be encrypted at rest by\n * `EncryptedType`. `none` is deliberately excluded — it is a real, deliberate\n * classification, just not an encrypting one.\n */\nexport type EncryptedComplianceLevel = Extract<\n ComplianceLevel,\n 'pii' | 'phi' | 'pci'\n>;\n\n/**\n * The property names of `TProperties` whose classification means the column is\n * encrypted at rest.\n *\n * Reading such a column requires the same encryption context it was written\n * under, because `FieldEncryptor` derives its key per tenant. When a read\n * happens BEFORE that context is known — a lookup whose whole purpose is to\n * discover which organisation a row belongs to — hydrating these columns cannot\n * succeed, and fails with \"ciphertext is corrupted or the wrong key was used\".\n *\n * The remedy at such a call site is a partial select that omits these keys, so\n * hydration never touches them. This type is what lets that be checked rather\n * than remembered.\n */\nexport type EncryptedKeysOf<TProperties> = {\n [K in keyof TProperties]: TProperties[K] extends {\n '~options': { readonly '~c': infer L };\n }\n ? L extends EncryptedComplianceLevel\n ? K\n : never\n : never;\n}[keyof TProperties];\n\n/**\n * True when reading `TProperties` in full would decrypt something, and so\n * requires an encryption context to already be established.\n */\nexport type RequiresEncryptionContext<TProperties> = [\n EncryptedKeysOf<TProperties>\n] extends [never]\n ? false\n : true;\n\n/**\n * True when `TFields` is a selection that provably avoids every encrypted\n * column of `TProperties` — the shape that makes a context-free read safe.\n */\nexport type SelectionAvoidsEncryptedColumns<\n TProperties,\n TFields extends readonly PropertyKey[]\n> = [Extract<TFields[number], EncryptedKeysOf<TProperties>>] extends [never]\n ? true\n : false;\n","import type { MetricsDefinition } from '../http/types/openTelemetryCollector.types';\nimport type { OpenTelemetryCollector } from '../http/telemetry/openTelemetryCollector';\nimport type { ComplianceOrm } from './complianceDataService';\nimport {\n getAllRetentionPolicies,\n getEntityComplianceFields,\n parseDuration,\n subtractDuration,\n type RetentionPolicy\n} from '../persistence/complianceTypes';\n\nexport interface EnforcementOptions {\n /** Filter to specific entity names (default: all with retention policies) */\n entities?: string[];\n /** Records per batch (default: 1000) */\n batchSize?: number;\n /** Log what would happen without mutating */\n dryRun?: boolean;\n}\n\nexport interface EnforcementResult {\n processed: number;\n deleted: number;\n anonymized: number;\n errors: number;\n byEntity: Record<\n string,\n { deleted: number; anonymized: number; errors: number }\n >;\n durationMs: number;\n}\n\n// Parameterised on the metrics definition rather than typed against\n// `OpenTelemetryCollector<any>`. This service only logs — info, warn, error —\n// and never touches a metric, so it has no reason to know the definition; but\n// `any` there switched off checking on every call through this collector, and\n// the collector's generic appears in its own method parameters, so widening to\n// the base constraint would not have been sound either. The default keeps\n// existing construction sites inferring exactly as before.\nexport class RetentionService<\n AppliedMetricsDefinition extends MetricsDefinition = MetricsDefinition\n> {\n private readonly DEFAULT_BATCH_SIZE = 1000;\n\n constructor(\n private readonly orm: ComplianceOrm,\n private readonly otel: OpenTelemetryCollector<AppliedMetricsDefinition>\n ) {}\n\n async enforce(options?: EnforcementOptions): Promise<EnforcementResult> {\n const start = Date.now();\n const batchSize = options?.batchSize ?? this.DEFAULT_BATCH_SIZE;\n const dryRun = options?.dryRun ?? false;\n const policies = getAllRetentionPolicies();\n const result: EnforcementResult = {\n processed: 0,\n deleted: 0,\n anonymized: 0,\n errors: 0,\n byEntity: {},\n durationMs: 0\n };\n\n if (policies.size === 0) {\n this.otel.info('[RetentionService] No retention policies registered');\n result.durationMs = Date.now() - start;\n return result;\n }\n\n this.otel.info('[RetentionService] Starting enforcement', {\n entityCount: policies.size,\n batchSize,\n dryRun\n });\n\n for (const [entityName, policy] of policies) {\n if (options?.entities && !options.entities.includes(entityName)) continue;\n\n const entityResult = { deleted: 0, anonymized: 0, errors: 0 };\n\n try {\n await this.enforceEntity(\n entityName,\n policy,\n batchSize,\n entityResult,\n dryRun\n );\n } catch (err) {\n entityResult.errors++;\n this.otel.error('[RetentionService] Entity-level failure', {\n entityName,\n error: String(err)\n });\n }\n\n result.byEntity[entityName] = entityResult;\n result.deleted += entityResult.deleted;\n result.anonymized += entityResult.anonymized;\n result.errors += entityResult.errors;\n result.processed += entityResult.deleted + entityResult.anonymized;\n }\n\n result.durationMs = Date.now() - start;\n this.otel.info('[RetentionService] Enforcement complete', {\n processed: result.processed,\n deleted: result.deleted,\n anonymized: result.anonymized,\n errors: result.errors,\n durationMs: result.durationMs\n });\n\n return result;\n }\n\n private async enforceEntity(\n entityName: string,\n policy: RetentionPolicy,\n batchSize: number,\n stats: { deleted: number; anonymized: number; errors: number },\n dryRun: boolean\n ): Promise<void> {\n const metadata = [...this.orm.getMetadata().getAll().values()].find(\n (m) => m.className === entityName\n );\n\n if (!metadata) {\n this.otel.warn(\n '[RetentionService] Entity not in MikroORM metadata — skipping',\n { entityName }\n );\n return;\n }\n\n const duration = parseDuration(policy.duration);\n const cutoff = subtractDuration(new Date(), duration);\n const entityClass = metadata.class ?? metadata.className;\n\n // For anonymize: only null out PII/PHI/PCI fields that are nullable.\n // Non-nullable fields are skipped to avoid flush errors.\n const piiFieldNames: string[] = [];\n if (policy.action === 'anonymize') {\n const complianceFields = getEntityComplianceFields(entityName);\n if (complianceFields) {\n for (const [fieldName, level] of complianceFields) {\n if (level === 'none') continue;\n const prop = metadata.properties[fieldName];\n if (prop && prop.nullable) {\n piiFieldNames.push(fieldName);\n } else {\n this.otel.warn(\n '[RetentionService] Skipping non-nullable PII field for anonymize',\n { entityName, fieldName, level }\n );\n }\n }\n }\n }\n\n const filter: Record<string, unknown> = {\n createdAt: { $lt: cutoff }\n };\n if (policy.action === 'anonymize') {\n filter['retentionAnonymizedAt'] = null;\n }\n\n // Dry run: use count instead of fetching records\n if (dryRun) {\n const em = this.orm.em.fork();\n try {\n const count = await em.count(entityClass, filter);\n this.otel.info('[RetentionService] Dry run — would process', {\n entityName,\n action: policy.action,\n count\n });\n if (policy.action === 'delete') stats.deleted += count;\n else stats.anonymized += count;\n } catch (err) {\n stats.errors++;\n this.otel.error('[RetentionService] Dry run count failed', {\n entityName,\n error: String(err)\n });\n }\n return;\n }\n\n let batchNum = 0;\n\n while (true) {\n batchNum++;\n const em = this.orm.em.fork();\n\n try {\n const records = await em.find(entityClass, filter, {\n limit: batchSize,\n orderBy: { createdAt: 'ASC' }\n });\n\n if (records.length === 0) break;\n\n if (policy.action === 'delete') {\n records.forEach((r) => em.remove(r));\n await em.flush();\n stats.deleted += records.length;\n } else {\n for (const record of records) {\n const rec = record as Record<string, unknown>;\n for (const field of piiFieldNames) {\n rec[field] = null;\n }\n rec['retentionAnonymizedAt'] = new Date();\n }\n await em.flush();\n stats.anonymized += records.length;\n }\n\n this.otel.info('[RetentionService] Batch processed', {\n entityName,\n action: policy.action,\n batch: batchNum,\n count: records.length\n });\n } catch (err) {\n stats.errors++;\n this.otel.error('[RetentionService] Batch failed', {\n entityName,\n batch: batchNum,\n error: String(err)\n });\n break; // stop batching this entity, continue to next\n }\n }\n }\n}\n","import type { EntityManager, MetadataStorage } from '@mikro-orm/core';\nimport type { OpenTelemetryCollector } from '../http/telemetry/openTelemetryCollector';\n\n/**\n * Structural subset of `MikroORM` used by the compliance services. Typed\n * structurally (rather than as `MikroORM`) because the class's `Entities`\n * type parameter defaults to a mutable array while `MikroORM.init()` returns\n * a readonly one, making concrete instances unassignable to a bare\n * `MikroORM` parameter.\n */\nexport interface ComplianceOrm {\n em: Pick<EntityManager, 'fork'>;\n getMetadata(): MetadataStorage;\n}\nimport { MetricsDefinition } from '../http/types/openTelemetryCollector.types';\nimport {\n getEntityComplianceFields,\n getEntityUserIdField\n} from '../persistence/complianceTypes';\n\nexport interface EraseResult {\n entitiesAffected: string[];\n recordsDeleted: number;\n}\n\nexport interface ExportResult {\n userId: string;\n entities: Record<string, unknown[]>;\n}\n\n/**\n * Per-entity userIdField overrides.\n * Keys are entity names, values are the field name linking records to a user.\n *\n * @example\n * {\n * User: 'id', // the User entity IS the user record\n * Subscription: 'partyId', // billing links via partyId\n * Account: 'userId', // default, can be omitted\n * }\n */\nexport type UserIdFieldOverrides = Record<string, string>;\n\n/**\n * Common field names that link an entity to a user, tried in order.\n * Used for optimistic search when no explicit userIdField is configured.\n */\nconst CANDIDATE_USER_FIELDS = [\n 'userId',\n 'user',\n 'id',\n 'partyId',\n 'customerId',\n 'ownerId',\n 'createdBy',\n 'email'\n];\n\n/**\n * Generic compliance data service that walks all compliance-registered entities\n * and erases or exports PII/PHI/PCI data for a given user.\n *\n * Resolution order for userIdField per entity:\n * 1. Constructor overrides (highest priority)\n * 2. defineComplianceEntity({ userIdField }) registry\n * 3. Optimistic search: first CANDIDATE_USER_FIELDS match in entity metadata\n * 4. Skip entity (no user link found)\n */\nexport class ComplianceDataService {\n private readonly userIdFieldOverrides: UserIdFieldOverrides;\n\n constructor(\n private readonly orm: ComplianceOrm,\n private readonly otel: OpenTelemetryCollector<MetricsDefinition>,\n userIdFieldOverrides?: UserIdFieldOverrides\n ) {\n this.userIdFieldOverrides = userIdFieldOverrides ?? {};\n }\n\n /**\n * Resolve the field linking an entity to a user.\n * Returns undefined if no link can be determined.\n */\n private resolveUserIdField(\n entityName: string,\n entityProperties: Record<string, unknown>\n ): string | undefined {\n // 1. Constructor override\n const override = this.userIdFieldOverrides[entityName];\n if (override) return override;\n\n // 2. Registry (from defineComplianceEntity({ userIdField }))\n const registered = getEntityUserIdField(entityName);\n if (registered !== 'userId' || entityProperties['userId']) {\n // Registry returned a non-default value, or the default 'userId' exists\n if (entityProperties[registered]) return registered;\n }\n\n // 3. Optimistic search\n for (const candidate of CANDIDATE_USER_FIELDS) {\n if (entityProperties[candidate]) return candidate;\n }\n\n // 4. No link found\n return undefined;\n }\n\n async erase(userId: string): Promise<EraseResult> {\n const em = this.orm.em.fork();\n const entitiesAffected: string[] = [];\n let recordsDeleted = 0;\n\n const allMetadata = [...this.orm.getMetadata().getAll().values()];\n\n for (const metadata of allMetadata) {\n const entityName = metadata.className;\n const fields = getEntityComplianceFields(entityName);\n if (!fields) continue;\n\n const hasPii = [...fields.values()].some(\n (level) => level === 'pii' || level === 'phi' || level === 'pci'\n );\n if (!hasPii) continue;\n\n const userIdField = this.resolveUserIdField(\n entityName,\n metadata.properties\n );\n\n if (!userIdField) {\n this.otel.warn(\n '[ComplianceDataService] No user-linking field found — skipping',\n { entityName, candidates: CANDIDATE_USER_FIELDS }\n );\n continue;\n }\n\n try {\n const entityClass = metadata.class ?? metadata.className;\n const records = await em.find(entityClass, {\n [userIdField]: userId\n });\n\n if (records.length > 0) {\n entitiesAffected.push(entityName);\n recordsDeleted += records.length;\n records.forEach((r) => em.remove(r));\n }\n } catch (err) {\n this.otel.error('[ComplianceDataService] Failed to erase entity', {\n entityName,\n userIdField,\n error: String(err)\n });\n }\n }\n\n if (recordsDeleted > 0) {\n await em.flush();\n }\n\n this.otel.info('[ComplianceDataService] Erase complete', {\n userId,\n entitiesAffected: entitiesAffected.join(','),\n recordsDeleted\n });\n\n return { entitiesAffected, recordsDeleted };\n }\n\n async export(userId: string): Promise<ExportResult> {\n const em = this.orm.em.fork();\n const entities: Record<string, unknown[]> = {};\n\n const allMetadata = [...this.orm.getMetadata().getAll().values()];\n\n for (const metadata of allMetadata) {\n const entityName = metadata.className;\n const fields = getEntityComplianceFields(entityName);\n if (!fields) continue;\n\n const hasPii = [...fields.values()].some(\n (level) => level === 'pii' || level === 'phi' || level === 'pci'\n );\n if (!hasPii) continue;\n\n const userIdField = this.resolveUserIdField(\n entityName,\n metadata.properties\n );\n\n if (!userIdField) {\n continue;\n }\n\n try {\n const entityClass = metadata.class ?? metadata.className;\n const records = await em.find(entityClass, {\n [userIdField]: userId\n });\n\n if (records.length > 0) {\n const piiFieldNames = [...fields.entries()]\n .filter(([, level]) => level !== 'none')\n .map(([name]) => name);\n\n entities[entityName] = records.map((record) => {\n const filtered: Record<string, unknown> = {};\n filtered['id'] = (record as Record<string, unknown>)['id'];\n for (const fieldName of piiFieldNames) {\n filtered[fieldName] = (record as Record<string, unknown>)[\n fieldName\n ];\n }\n return filtered;\n });\n }\n } catch (err) {\n this.otel.error('[ComplianceDataService] Failed to export entity', {\n entityName,\n userIdField,\n error: String(err)\n });\n }\n }\n\n this.otel.info('[ComplianceDataService] Export complete', {\n userId,\n entityCount: Object.keys(entities).length\n });\n\n return { userId, entities };\n }\n}\n","import type { MetricsDefinition } from '../http/types/openTelemetryCollector.types';\nimport type { OpenTelemetryCollector } from '../http/telemetry/openTelemetryCollector';\nimport type { RetentionService } from './retentionService';\n\n/**\n * Shared retention enforcement runner.\n * Designed to be called from a one-shot script (ECS RunTask / cron).\n *\n * @example\n * ```ts\n * import { ci, tokens } from '../bootstrapper';\n * import { runRetentionEnforcement } from '@myapp/core';\n *\n * runRetentionEnforcement(\n * ci.resolve(tokens.RetentionService),\n * ci.resolve(tokens.OtelCollector)\n * );\n * ```\n */\nexport async function runRetentionEnforcement<\n AppliedMetricsDefinition extends MetricsDefinition = MetricsDefinition\n>(\n retentionService: RetentionService<AppliedMetricsDefinition>,\n otel: OpenTelemetryCollector<AppliedMetricsDefinition>\n): Promise<void> {\n const dryRun = process.argv.includes('--dry-run');\n\n otel.info('[RetentionEnforcement] Starting', { dryRun });\n\n const result = await retentionService.enforce({ dryRun });\n\n otel.info('[RetentionEnforcement] Complete', {\n processed: result.processed,\n deleted: result.deleted,\n anonymized: result.anonymized,\n errors: result.errors,\n durationMs: result.durationMs\n });\n\n if (result.errors > 0) {\n otel.warn('[RetentionEnforcement] Completed with errors', {\n errors: result.errors,\n byEntity: result.byEntity\n });\n process.exit(1);\n }\n\n process.exit(0);\n}\n"],"mappings":"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","names":["import_common","dotenv"]}
|
|
1
|
+
{"version":3,"sources":["../../src/services/index.ts","../../src/environment/loadCascadingEnv.ts","../../src/services/configInjector.ts","../../src/services/guards/isConstructed.ts","../../src/services/types/configInjector.types.ts","../../src/services/guards/isConstructedSingleton.ts","../../src/services/guards/isConstructor.ts","../../src/persistence/encryptedType.ts","../../src/persistence/complianceTypes.ts","../../src/services/retentionService.ts","../../src/services/complianceDataService.ts","../../src/services/runRetentionEnforcement.ts"],"sourcesContent":["export { getEnvVar } from '@forklaunch/common';\nexport * from '../environment';\nexport { createConfigInjector } from './configInjector';\nexport type { ConfigInjector, ValidConfigInjector } from './configInjector';\nexport * from './types/configInjector.types';\nexport * from './types/entityManager.types';\nexport * from './types/service.types';\nexport {\n RetentionService,\n type EnforcementOptions,\n type EnforcementResult\n} from './retentionService';\nexport {\n ComplianceDataService,\n type EraseResult,\n type ExportResult,\n type UserIdFieldOverrides\n} from './complianceDataService';\nexport { runRetentionEnforcement } from './runRetentionEnforcement';\n","import dotenv from 'dotenv';\nimport { existsSync } from 'fs';\nimport { dirname, resolve } from 'path';\n\n/**\n * Gets cascading environment file paths: collects all .env.local files from project directory up to root\n * Root detection uses: .forklaunch/manifest.toml\n */\nexport function getCascadingEnvPaths(\n projectEnvPath: string | undefined,\n projectRoot: string = process.cwd()\n): {\n rootEnvExists: boolean;\n projectEnvExists: boolean;\n rootEnvPath?: string;\n projectEnvFilePath?: string;\n loadOrder: string[];\n} {\n const result = {\n rootEnvExists: false,\n projectEnvExists: false,\n rootEnvPath: undefined as string | undefined,\n projectEnvFilePath: undefined as string | undefined,\n loadOrder: [] as string[]\n };\n\n const applicationRoot = findApplicationRoot(projectRoot);\n\n const envLocalFiles = collectEnvLocalFiles(projectRoot, applicationRoot);\n result.loadOrder.push(...envLocalFiles);\n\n const rootEnvPath = resolve(applicationRoot, '.env.local');\n if (envLocalFiles.includes(rootEnvPath)) {\n result.rootEnvExists = true;\n result.rootEnvPath = rootEnvPath;\n }\n\n if (projectEnvPath) {\n const fullProjectEnvPath = resolve(projectRoot, projectEnvPath);\n if (existsSync(fullProjectEnvPath)) {\n result.projectEnvExists = true;\n result.projectEnvFilePath = fullProjectEnvPath;\n if (!result.loadOrder.includes(fullProjectEnvPath)) {\n result.loadOrder.push(fullProjectEnvPath);\n }\n }\n }\n\n return result;\n}\n\n/**\n * Loads environment variables with cascading precedence: all .env.local files from root to project, then project env file\n */\nexport function loadCascadingEnv(\n projectEnvPath: string | undefined,\n projectRoot: string = process.cwd()\n): {\n rootEnvLoaded: boolean;\n projectEnvLoaded: boolean;\n rootEnvPath?: string;\n projectEnvFilePath?: string;\n envFilesLoaded: string[];\n totalEnvFilesLoaded: number;\n} {\n const paths = getCascadingEnvPaths(projectEnvPath, projectRoot);\n const result = {\n rootEnvLoaded: false,\n projectEnvLoaded: false,\n rootEnvPath: paths.rootEnvPath,\n projectEnvFilePath: paths.projectEnvFilePath,\n envFilesLoaded: [] as string[],\n totalEnvFilesLoaded: 0\n };\n\n const seen = new Set<string>();\n for (const envPath of paths.loadOrder) {\n if (seen.has(envPath)) {\n continue;\n }\n seen.add(envPath);\n\n const envResult = dotenv.config({\n path: envPath,\n override: true\n });\n\n if (!envResult?.error) {\n result.envFilesLoaded.push(envPath);\n result.totalEnvFilesLoaded++;\n\n if (envPath === paths.rootEnvPath) {\n result.rootEnvLoaded = true;\n }\n if (envPath === paths.projectEnvFilePath) {\n result.projectEnvLoaded = true;\n }\n }\n }\n\n return result;\n}\n\n/**\n * Collects all .env.local files from project directory up to application root\n * Returns paths in order from root to project (for proper precedence)\n */\nfunction collectEnvLocalFiles(\n projectRoot: string,\n applicationRoot: string\n): string[] {\n const envLocalPaths: string[] = [];\n\n let currentPath = resolve(projectRoot);\n const normalizedAppRoot = resolve(applicationRoot);\n\n while (currentPath.length >= normalizedAppRoot.length) {\n const envLocalPath = resolve(currentPath, '.env.local');\n if (existsSync(envLocalPath)) {\n envLocalPaths.push(envLocalPath);\n }\n\n if (currentPath === normalizedAppRoot) {\n break;\n }\n\n const parentPath = dirname(currentPath);\n if (parentPath === currentPath) {\n break;\n }\n\n currentPath = parentPath;\n }\n\n return envLocalPaths.reverse();\n}\n\n/**\n * Finds application root by looking for .forklaunch/manifest.toml\n */\nexport function findApplicationRoot(startPath: string): string {\n let currentPath = resolve(startPath);\n const originalStart = currentPath;\n const maxDepth = 10;\n let depth = 0;\n\n while (depth < maxDepth) {\n const forklaunchManifest = resolve(\n currentPath,\n '.forklaunch',\n 'manifest.toml'\n );\n if (existsSync(forklaunchManifest)) {\n return currentPath;\n }\n\n const parentPath = dirname(currentPath);\n if (parentPath === currentPath) {\n break;\n }\n\n currentPath = parentPath;\n depth++;\n }\n\n return originalStart;\n}\n","import { extractArgumentNames, isNever } from '@forklaunch/common';\nimport {\n AnySchemaValidator,\n IdiomaticSchema,\n ParseResult,\n prettyPrintParseErrors,\n SchemaValidator\n} from '@forklaunch/validator';\nimport { isConstructed } from './guards/isConstructed';\nimport { isConstructedSingleton } from './guards/isConstructedSingleton';\nimport { isConstructor } from './guards/isConstructor';\nimport {\n ConfigValidator,\n Constructed,\n ConstructedSingleton,\n Lifetime,\n ResolvedConfigValidator,\n SchemaConstructor,\n SchemaFunction,\n Singleton\n} from './types/configInjector.types';\n\nexport function createConfigInjector<\n SV extends AnySchemaValidator,\n CV extends ConfigValidator<SV>\n>(\n schemaValidator: SV,\n dependenciesDefinition: {\n [K in keyof CV]:\n | Singleton<\n CV[K],\n Omit<ResolvedConfigValidator<SV, CV>, K>,\n ResolvedConfigValidator<SV, CV>[K]\n >\n | Constructed<\n CV[K],\n Omit<ResolvedConfigValidator<SV, CV>, K>,\n ResolvedConfigValidator<SV, CV>[K]\n >;\n }\n) {\n return new ConfigInjector<SV, CV>(\n schemaValidator,\n dependenciesDefinition\n ).load();\n}\n\nexport class ConfigInjector<\n SV extends AnySchemaValidator,\n CV extends ConfigValidator<SV>\n> {\n instances: {\n [K in keyof CV]?: ResolvedConfigValidator<SV, CV>[K];\n } = {};\n\n readonly configShapes: CV;\n\n load(inheritedScopeInstances?: {\n [K in keyof CV]?: ResolvedConfigValidator<SV, CV>[K];\n }): this {\n for (const token in inheritedScopeInstances) {\n this.instances[token] = inheritedScopeInstances[token];\n }\n\n for (const token in this.dependenciesDefinition) {\n const definition = this.dependenciesDefinition[token];\n if (\n definition.lifetime === Lifetime.Singleton &&\n !this.instances[token]\n ) {\n if (\n isConstructedSingleton<\n CV[typeof token],\n Omit<ResolvedConfigValidator<SV, CV>, typeof token>,\n ResolvedConfigValidator<SV, CV>[typeof token]\n >(definition)\n ) {\n this.instances[token] = this.resolveInstance<typeof token>(\n token,\n definition\n );\n } else {\n this.instances[token] = definition.value;\n }\n }\n }\n return this;\n }\n\n private resolveInstance<T extends keyof CV>(\n token: T,\n definition:\n | ConstructedSingleton<\n CV[T],\n Omit<ResolvedConfigValidator<SV, CV>, T>,\n ResolvedConfigValidator<SV, CV>[T]\n >\n | Constructed<\n CV[T],\n Omit<ResolvedConfigValidator<SV, CV>, T>,\n ResolvedConfigValidator<SV, CV>[T]\n >,\n context?: Record<string, unknown>,\n resolutionPath: (keyof CV)[] = []\n ): ResolvedConfigValidator<SV, CV>[T] {\n if (process.env.FORKLAUNCH_MODE === 'openapi') {\n // The handler below installs `apply` and `construct` traps, which are\n // only valid on a callable target — hence a function rather than `{}`.\n // The proxy is what makes every string key resolve; the target itself\n // has none, so this shape cannot be inferred and has to be asserted.\n // Named and asserted once, rather than laundered through `unknown`.\n type NoopTarget = ((...args: never[]) => unknown) &\n Record<string, unknown>;\n const noopProxy: Record<string, unknown> = new Proxy(\n function () {} as NoopTarget,\n {\n get(_target, prop) {\n if (prop === Symbol.toPrimitive) return () => '';\n if (prop === 'toString' || prop === 'valueOf') return () => '';\n if (prop === 'then') return undefined;\n if (prop === Symbol.iterator) return function* () {};\n if (prop === Symbol.asyncIterator) return async function* () {};\n return noopProxy;\n },\n set() {\n return true;\n },\n has() {\n return true;\n },\n deleteProperty() {\n return true;\n },\n ownKeys() {\n return ['prototype'];\n },\n getOwnPropertyDescriptor(_target, prop) {\n if (prop === 'prototype') {\n return { configurable: false, enumerable: false, writable: true };\n }\n return { configurable: true, enumerable: true };\n },\n apply() {\n return noopProxy;\n },\n construct() {\n return noopProxy as object;\n }\n }\n );\n return noopProxy as ResolvedConfigValidator<SV, CV>[T];\n }\n\n const rawInjectorArgument = extractArgumentNames(definition.factory)[0];\n // short circuit as no args\n if (!rawInjectorArgument || rawInjectorArgument === '_args') {\n return definition.factory(\n {} as Omit<ResolvedConfigValidator<SV, CV>, T>,\n context ?? {},\n this.resolve.bind(this)\n );\n }\n\n // Normalize whitespace to handle both single-line and multiline formatting\n const injectorArgument = rawInjectorArgument.replace(/\\s+/g, '');\n\n if (!injectorArgument.startsWith('{') || !injectorArgument.endsWith('}')) {\n throw new Error(\n `Invalid injector argument for ${String(\n token\n )}: ${injectorArgument}. Please use object destructuring syntax: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment.`\n );\n }\n const resolvedArguments = Object.fromEntries(\n injectorArgument\n .slice(1, -1)\n .split(',')\n .filter((arg) => arg.length > 0)\n .map((arg) => arg.split(':')[0].trim())\n .map((arg) => {\n const newResolutionPath = [...resolutionPath, token];\n if (resolutionPath.includes(arg)) {\n throw new Error(\n `Circular dependency detected: ${newResolutionPath.join(\n ' -> '\n )} -> ${arg}`\n );\n }\n const resolvedArg = this.resolve(arg, context, newResolutionPath);\n return [arg, resolvedArg];\n })\n ) as Omit<ResolvedConfigValidator<SV, CV>, T>;\n return definition.factory(\n resolvedArguments,\n context ?? {},\n this.resolve.bind(this)\n );\n }\n\n constructor(\n private schemaValidator: SV,\n private dependenciesDefinition: {\n [K in keyof CV]: (\n | Singleton<\n CV[K],\n Omit<ResolvedConfigValidator<SV, CV>, K>,\n ResolvedConfigValidator<SV, CV>[K]\n >\n | Constructed<\n CV[K],\n Omit<ResolvedConfigValidator<SV, CV>, K>,\n ResolvedConfigValidator<SV, CV>[K]\n >\n ) & {\n type: CV[K];\n };\n }\n ) {\n this.configShapes = Object.entries(this.dependenciesDefinition).reduce(\n (acc, [key, { type }]) => ({\n ...acc,\n [key]: type\n }),\n {} as Record<keyof CV, CV[keyof CV]>\n ) as CV;\n }\n\n safeValidateConfigSingletons(): ParseResult<ValidConfigInjector<SV, CV>> {\n const validNonSchemaSingletons = Object.entries(this.configShapes).reduce<\n ParseResult<ResolvedConfigValidator<SV, CV>>\n >(\n (acc, [key, value]) => {\n if (\n this.dependenciesDefinition[key].lifetime === Lifetime.Singleton &&\n !(this.schemaValidator as SchemaValidator).isSchema<\n SchemaFunction<SV> | SchemaConstructor<SV> | IdiomaticSchema<SV>\n >(value) &&\n isConstructor(value)\n ) {\n if (!(this.instances[key] instanceof value)) {\n const expected = value.name;\n const receivedValue: unknown = this.instances[key];\n const received = isConstructed(receivedValue)\n ? receivedValue.constructor.name\n : typeof receivedValue;\n\n if (acc.ok) {\n acc = {\n ok: false,\n errors: []\n };\n }\n acc.errors?.push({\n message: `Expected ${expected}, received ${received}`,\n path: [key]\n });\n } else {\n if (acc.ok) {\n acc = {\n ok: true,\n value: {\n ...acc.value,\n [key]: this.instances[key]\n }\n };\n }\n }\n return acc;\n }\n return acc;\n },\n {\n ok: true,\n value: {} as ResolvedConfigValidator<SV, CV>\n }\n );\n\n const singletons = Object.fromEntries(\n Object.entries(this.configShapes).filter(\n ([key, value]) =>\n this.dependenciesDefinition[key].lifetime === Lifetime.Singleton &&\n (this.schemaValidator as SchemaValidator).isSchema(value)\n )\n );\n const schemaSingletonParseResult = (\n this.schemaValidator as SchemaValidator\n ).parse(\n (this.schemaValidator as SchemaValidator).schemify(singletons),\n Object.fromEntries(\n Object.keys(singletons).map((key) => {\n const dependency = this.dependenciesDefinition[key];\n return [\n key,\n dependency.lifetime === Lifetime.Singleton\n ? this.instances[key]\n : undefined\n ];\n })\n )\n );\n\n const configKeys = Object.keys(this.configShapes);\n\n return validNonSchemaSingletons.ok && schemaSingletonParseResult.ok\n ? {\n ok: true as const,\n value: new ValidConfigInjector<SV, CV>(\n this.schemaValidator,\n this.dependenciesDefinition\n ).load({ ...this.instances })\n }\n : {\n ok: false as const,\n errors: [\n ...(!validNonSchemaSingletons.ok && validNonSchemaSingletons.errors\n ? validNonSchemaSingletons.errors\n : []),\n ...(!schemaSingletonParseResult.ok &&\n schemaSingletonParseResult.errors\n ? schemaSingletonParseResult.errors\n : [])\n ].sort(\n (a, b) =>\n configKeys.indexOf(a.path[0]) - configKeys.indexOf(b.path[0])\n )\n };\n }\n\n validateConfigSingletons(configName: string): ValidConfigInjector<SV, CV> {\n if (process.env.FORKLAUNCH_MODE === 'openapi') {\n return this.createScope() as ValidConfigInjector<SV, CV>;\n }\n\n const safeValidateResult = this.safeValidateConfigSingletons();\n\n if (safeValidateResult.ok) {\n return safeValidateResult.value;\n }\n\n throw new Error(\n prettyPrintParseErrors(safeValidateResult.errors, configName)\n );\n }\n\n resolve<T extends keyof CV>(\n token: T,\n context?: Record<string, unknown>,\n resolutionPath: (keyof CV)[] = []\n ): ResolvedConfigValidator<SV, CV>[T] {\n const instance = this.instances[token];\n if (!instance) {\n const definition = this.dependenciesDefinition[token];\n\n if (!definition) {\n throw new Error(`Unable to resolve dependency ${String(token)}`);\n }\n\n switch (definition.lifetime) {\n case Lifetime.Singleton: {\n if (\n isConstructedSingleton<\n CV[T],\n Omit<ResolvedConfigValidator<SV, CV>, T>,\n ResolvedConfigValidator<SV, CV>[T]\n >(definition) &&\n !this.instances[token]\n ) {\n this.instances[token] = this.resolveInstance<T>(\n token,\n definition,\n context,\n resolutionPath\n );\n }\n return this.instances[token] as ResolvedConfigValidator<SV, CV>[T];\n }\n case Lifetime.Scoped: {\n if (\n !isConstructed<\n CV[T],\n Omit<ResolvedConfigValidator<SV, CV>, T>,\n ResolvedConfigValidator<SV, CV>[T]\n >(definition)\n ) {\n throw new Error(\n `Invalid dependency definition for ${String(token)}`\n );\n }\n\n const scopedInstance = this.resolveInstance<T>(\n token,\n definition,\n context,\n resolutionPath\n );\n this.instances[token] = scopedInstance;\n return scopedInstance;\n }\n case Lifetime.Transient: {\n if (\n !isConstructed<\n CV[T],\n Omit<ResolvedConfigValidator<SV, CV>, T>,\n ResolvedConfigValidator<SV, CV>[T]\n >(definition)\n ) {\n throw new Error(\n `Invalid dependency definition for ${String(token)}`\n );\n }\n\n return this.resolveInstance<T>(\n token,\n definition,\n context,\n resolutionPath\n );\n }\n default: {\n isNever(definition);\n throw new Error(\n `Unable to resolve lifetime for dependency ${String(\n token\n )}, ${resolutionPath}`\n );\n }\n }\n } else {\n return instance;\n }\n }\n\n scopedResolver<T extends keyof CV>(\n token: T,\n context?: Record<string, unknown>,\n resolutionPath: (keyof CV)[] = []\n ): (options?: {\n scope?: ConfigInjector<SV, CV>;\n context?: Record<string, unknown>;\n }) => ResolvedConfigValidator<SV, CV>[T] {\n return (options) =>\n (options?.scope ?? this.createScope()).resolve<T>(\n token,\n options?.context ? { ...context, ...options.context } : context,\n resolutionPath\n );\n }\n\n createScope(): ConfigInjector<SV, CV> {\n const singletons: Record<string, unknown> = {};\n for (const dependency in this.dependenciesDefinition) {\n if (\n this.dependenciesDefinition[dependency].lifetime === Lifetime.Singleton\n ) {\n singletons[dependency] = this.instances[dependency];\n }\n }\n return new ConfigInjector<SV, CV>(\n this.schemaValidator,\n this.dependenciesDefinition\n ).load(singletons as ResolvedConfigValidator<SV, CV>);\n }\n\n dispose(): void {\n this.instances = {};\n this.load();\n }\n\n chain<ChainedCV extends ConfigValidator<SV>>(dependenciesDefinition: {\n [K in keyof ChainedCV]: {\n type: ChainedCV[K];\n } & (\n | Singleton<\n ChainedCV[K],\n Omit<ResolvedConfigValidator<SV, CV & ChainedCV>, K>,\n ResolvedConfigValidator<SV, ChainedCV>[K]\n >\n | Constructed<\n ChainedCV[K],\n Omit<ResolvedConfigValidator<SV, CV & ChainedCV>, K>,\n ResolvedConfigValidator<SV, ChainedCV>[K]\n >\n );\n }): ConfigInjector<SV, CV & ChainedCV> {\n return new ConfigInjector<SV, CV>(this.schemaValidator, {\n ...this.dependenciesDefinition,\n ...dependenciesDefinition\n // The hop through unknown is required, not habitual. A direct assertion\n // makes the compiler structurally compare two deeply recursive\n // ConfigInjector instantiations and it gives up with TS2589, \"type\n // instantiation is excessively deep and possibly infinite\". Going via\n // unknown stops that comparison. The widening itself is sound: the\n // dependencies of both injectors are merged immediately above.\n }).load({ ...this.instances }) as unknown as ConfigInjector<\n SV,\n CV & ChainedCV\n >;\n }\n\n tokens(): {\n [K in keyof CV]: K;\n } {\n return Object.fromEntries(\n Object.keys(this.dependenciesDefinition).map((key) => [key, key])\n ) as {\n [K in keyof CV]: K;\n };\n }\n}\n\nexport class ValidConfigInjector<\n SV extends AnySchemaValidator,\n CV extends ConfigValidator<SV>\n> extends ConfigInjector<SV, CV> {\n validConfigInjector!: void;\n}\n","import { Constructed } from '../types/configInjector.types';\n\nexport function isConstructed<Type, Args, Return>(\n value: unknown\n): value is Constructed<Type, Args, Return> {\n return (\n typeof value === 'object' &&\n value != null &&\n 'constructor' in value &&\n value.constructor != null\n );\n}\n","import {\n AnySchemaValidator,\n IdiomaticSchema,\n Schema\n} from '@forklaunch/validator';\nimport { ConfigInjector } from '../configInjector';\n\nexport const Lifetime = {\n Singleton: 0,\n Transient: 1,\n Scoped: 2\n} as const;\nexport type Lifetime = (typeof Lifetime)[keyof typeof Lifetime];\n\nexport type Singleton<Type, Args, Value> =\n | {\n lifetime: typeof Lifetime.Singleton;\n type: Type;\n value: Value;\n }\n | ConstructedSingleton<Type, Args, Value>;\n\nexport type ConstructedSingleton<Type, Args, Return> = {\n lifetime: typeof Lifetime.Singleton;\n type: Type;\n factory: (\n args: Args,\n context: Record<string, unknown>,\n resolve: <T extends keyof Args>(\n token: T,\n context?: Record<string, unknown>\n ) => Args[T]\n ) => Return;\n};\n\nexport type Constructed<Type, Args, Return> = {\n lifetime: typeof Lifetime.Transient | typeof Lifetime.Scoped;\n type: Type;\n factory: (\n args: Args,\n context: Record<string, unknown>,\n resolve: <T extends keyof Args>(\n token: T,\n context?: Record<string, unknown>\n ) => Args[T]\n ) => Return;\n};\n\nexport type Constructor = new (...args: never[]) => unknown;\nexport type SchemaConstructor<SV extends AnySchemaValidator> = new (\n ...args: unknown[]\n) => IdiomaticSchema<SV>;\nexport type Function = (...args: never[]) => unknown;\nexport type FunctionToConstructor = (\n ...args: never[]\n) => new (...args: never[]) => unknown;\nexport type SchemaFunction<SV extends AnySchemaValidator> = (\n args: unknown\n) => IdiomaticSchema<SV>;\n\nexport type ConfigTypes<SV extends AnySchemaValidator> =\n | Function\n | SchemaFunction<SV>\n | Constructor\n | SchemaConstructor<SV>\n | IdiomaticSchema<SV>;\n\nexport type ConfigValidator<SV extends AnySchemaValidator> = Record<\n string,\n ConfigTypes<SV> | Record<string, ConfigTypes<SV>>\n>;\n\ntype ResolveConfigValue<SV extends AnySchemaValidator, T> =\n T extends SchemaConstructor<SV>\n ? Schema<InstanceType<T>, SV>\n : T extends SchemaFunction<SV>\n ? (...args: Parameters<T>) => Schema<ReturnType<T>, SV>\n : T extends FunctionToConstructor\n ? (...args: Parameters<T>) => InstanceType<ReturnType<T>>\n : T extends Function\n ? (...args: Parameters<T>) => ReturnType<T>\n : T extends Constructor\n ? InstanceType<T>\n : T extends IdiomaticSchema<SV>\n ? Schema<T, SV>\n : T extends Record<string, ConfigTypes<SV>>\n ? {\n [K in keyof T]: ResolveConfigValue<SV, T[K]>;\n }\n : Schema<T, SV>;\n\nexport type ResolvedConfigValidator<\n SV extends AnySchemaValidator,\n CV extends ConfigValidator<SV>\n> = {\n [M in keyof CV]: ResolveConfigValue<SV, CV[M]>;\n};\n\nexport type ScopedDependencyFactory<\n SV extends AnySchemaValidator,\n CV extends ConfigValidator<SV>,\n M extends keyof CV\n> = (scope?: ConfigInjector<SV, CV>) => ResolvedConfigValidator<SV, CV>[M];\n","import { ConstructedSingleton, Lifetime } from '../types/configInjector.types';\n\nexport function isConstructedSingleton<Type, Args, Return>(\n value: unknown\n): value is ConstructedSingleton<Type, Args, Return> {\n return (\n typeof value === 'object' &&\n value != null &&\n 'lifetime' in value &&\n value.lifetime === Lifetime.Singleton &&\n 'factory' in value\n );\n}\n","import { Constructor } from '../types/configInjector.types';\n\nexport function isConstructor(value: unknown): value is Constructor {\n return (\n typeof value === 'function' &&\n value.constructor != null &&\n value.prototype != null\n );\n}\n","import { Type, type Platform, type TransformContext } from '@mikro-orm/core';\nimport { AsyncLocalStorage } from 'node:async_hooks';\nimport type { FieldEncryptor } from './fieldEncryptor';\n\nconst ENCRYPTED_PREFIXES = ['v1:', 'v2:', 'v3:'] as const;\n\nfunction isEncrypted(value: string): boolean {\n return ENCRYPTED_PREFIXES.some((p) => value.startsWith(p));\n}\n\n// ---------------------------------------------------------------------------\n// Module-level state\n// ---------------------------------------------------------------------------\n\nlet _encryptor: FieldEncryptor | undefined;\n\n/**\n * AsyncLocalStorage for the current tenant ID. This is set automatically\n * by `withEncryptionContext()` — users don't interact with it directly.\n */\nconst _tenantContext = new AsyncLocalStorage<{ tenantId: string }>();\n\n/**\n * Register the FieldEncryptor instance for use by EncryptedType.\n * Call this once at application bootstrap (e.g., in mikro-orm.config.ts).\n */\nexport function registerEncryptor(encryptor: FieldEncryptor): void {\n _encryptor = encryptor;\n}\n\n/**\n * The empty string was offered as a tenant id.\n *\n * `''` used to mean \"the global tenant\", which made it impossible to tell a\n * deliberate global row from a tenant that was never resolved — the two are\n * the same key. Rows written by mistake under `''` read back fine from any\n * other unbound path and fail only when a correctly-bound caller finally\n * touches them, arbitrarily far from the code that caused it.\n *\n * Global rows need a tenant id you can name and search for — a constant like\n * `'_internal'` that no organization can collide with. Pick one and pass it.\n */\nexport class EmptyTenantError extends Error {\n constructor(api: string) {\n super(\n `${api}: '' is not a tenant id. It used to mean \"global\", which made a ` +\n `deliberate global row indistinguishable from a tenant nobody resolved. ` +\n `Bind an explicit constant (for example '_internal') for rows that ` +\n `genuinely belong to no organization.`\n );\n this.name = 'EmptyTenantError';\n }\n}\n\n/**\n * An encrypted column was read or written with no tenant bound.\n *\n * This is the failure the empty tenant used to hide. An unbound write\n * encrypted under `''` and looked like it worked; the row then failed to\n * decrypt for the tenant that owned it, and the stack trace pointed at the\n * innocent reader. Failing here names the actual culprit: the write path\n * that never bound a tenant.\n *\n * Bind one with `wrapEmWithTenantContext(em, tenantId)` in the DI\n * `EntityManager` factory, or `withEncryptionContext(tenantId, fn)` around\n * the work. A raw manager — `orm.em.fork()`, or the `__em` off a wrapped\n * entity — escapes the proxy and arrives here unbound.\n */\nexport class UnboundTenantError extends Error {\n constructor(operation: string) {\n super(\n `Cannot ${operation} an encrypted column with no tenant bound. Wrap the ` +\n `work in withEncryptionContext(tenantId, fn), or take the entity ` +\n `manager from a factory that calls wrapEmWithTenantContext. A raw ` +\n `em.fork() (or wrap(entity).__em) escapes the tenant proxy.`\n );\n this.name = 'UnboundTenantError';\n }\n}\n\n/**\n * Reject `''` at every door that binds a tenant.\n *\n * Exported so the binding helpers in this package share one rule, and so an\n * application's own factory can apply the same one at its own boundary.\n */\nexport function assertBindableTenantId(\n tenantId: string,\n api: string\n): asserts tenantId is string {\n if (tenantId === '') {\n throw new EmptyTenantError(api);\n }\n}\n\n/**\n * Set the encryption tenant ID for the current async context.\n *\n * IMPORTANT: this uses `AsyncLocalStorage.enterWith`, which mutates the\n * current async resource's store. It does NOT reliably propagate through\n * `pg` connection pool callback async resources — pooled connections are\n * long-lived async resources created at pool init, and resumption\n * callbacks run in the pool's resource, not the caller's. As a result,\n * MikroORM hydration of encrypted columns can read an empty / wrong\n * tenant id from `getCurrentTenantId()` even when this was called\n * correctly at the start of the request.\n *\n * Prefer `withEncryptionContext(tenantId, fn)` (or the\n * `wrapEmWithTenantContext` helper) for any code path that hits the\n * database. `setEncryptionTenantId` remains useful as a best-effort seed\n * for purely synchronous code paths.\n */\nexport function setEncryptionTenantId(tenantId: string): void {\n assertBindableTenantId(tenantId, 'setEncryptionTenantId');\n _tenantContext.enterWith({ tenantId });\n}\n\n/**\n * Run `fn` inside a fresh AsyncLocalStorage scope bound to `tenantId`.\n *\n * Unlike `setEncryptionTenantId`, this uses `AsyncLocalStorage.run`, which\n * creates a new async resource bound to the store. Node's promise hooks\n * propagate the store forward through pool callback async resources, so\n * MikroORM hydration of encrypted columns inside `fn` (even after multiple\n * awaits and a pg pool roundtrip) sees the correct tenant id.\n *\n * Most application code should not call this directly — use\n * `wrapEmWithTenantContext(em, tenantId)` in your DI EntityManager\n * factory and every method on the returned EM will be wrapped\n * automatically.\n */\nexport function withEncryptionContext<T>(tenantId: string, fn: () => T): T {\n assertBindableTenantId(tenantId, 'withEncryptionContext');\n return _tenantContext.run({ tenantId }, fn);\n}\n\n/**\n * Get the current tenant ID, or the empty string when none is bound.\n *\n * @deprecated The empty string it returns when nothing is bound is the whole\n * problem: \"no tenant\" and \"the global tenant\" became the same value, so an\n * unbound write encrypted under `''` and surfaced as somebody else's decrypt\n * failure. Encryption no longer reads through this — it uses the bound tenant\n * and throws {@link UnboundTenantError} when there is none. Use\n * {@link getBoundTenantId} and handle `undefined` explicitly.\n */\nexport function getCurrentTenantId(): string {\n return _tenantContext.getStore()?.tenantId ?? '';\n}\n\n/**\n * The tenant bound to the current async context, or `undefined` if none is.\n *\n * The distinction {@link getCurrentTenantId} cannot make: `undefined` is\n * \"nobody bound a tenant\", which is a bug on any path that touches an\n * encrypted column, and never a key.\n */\nexport function getBoundTenantId(): string | undefined {\n return _tenantContext.getStore()?.tenantId;\n}\n\n/** The bound tenant, or {@link UnboundTenantError} naming what was attempted. */\nfunction requireBoundTenantId(operation: string): string {\n const tenantId = _tenantContext.getStore()?.tenantId;\n if (tenantId === undefined || tenantId === '') {\n throw new UnboundTenantError(operation);\n }\n return tenantId;\n}\n\n// ---------------------------------------------------------------------------\n// Type resolution helpers\n// ---------------------------------------------------------------------------\n\n/**\n * Resolve a MikroORM type value (string, constructor, or instance) to a\n * Type instance. Returns undefined if unresolvable.\n */\nexport function resolveTypeInstance(\n type: unknown\n): Type<unknown, unknown> | undefined {\n if (type instanceof Type) return type;\n if (typeof type === 'function') {\n try {\n const inst = new (type as new () => unknown)();\n return inst instanceof Type ? inst : undefined;\n } catch {\n return undefined;\n }\n }\n return undefined;\n}\n\n/**\n * Hydrate a raw value (after JSON.parse or string) to the JS type\n * indicated by `runtimeType`. This covers the cases where MikroORM's\n * own convertToJSValue is a NOOP (relies on the DB driver).\n */\nfunction hydrateValue(value: unknown, runtimeType: string): unknown {\n if (value === null || value === undefined) return value;\n switch (runtimeType) {\n case 'string':\n return typeof value === 'string' ? value : String(value);\n case 'number':\n return typeof value === 'number' ? value : Number(value);\n case 'boolean':\n return typeof value === 'boolean'\n ? value\n : typeof value === 'string'\n ? value === 'true'\n : Boolean(value);\n case 'Date':\n return value instanceof Date ? value : new Date(value as string | number);\n case 'bigint':\n return typeof value === 'bigint' ? value : BigInt(value as string);\n case 'Buffer':\n return Buffer.isBuffer(value)\n ? value\n : typeof value === 'string'\n ? Buffer.from(value, 'base64')\n : Buffer.from(value as Uint8Array);\n case 'any': // json — return as-is after JSON.parse\n default:\n return value;\n }\n}\n\n// ---------------------------------------------------------------------------\n// EncryptedType\n// ---------------------------------------------------------------------------\n\n/**\n * MikroORM custom Type that transparently encrypts/decrypts values.\n *\n * Works with any JS type. Non-string values are serialized before\n * encryption and hydrated after decryption using the original Type's\n * runtimeType for correct JS type reconstruction.\n *\n * DB column is always `text` — the encrypted ciphertext is a string\n * regardless of the original JS type.\n *\n * For array container types, the element type is tracked separately\n * so each element is hydrated individually after decryption.\n */\nexport class EncryptedType extends Type<unknown, string | null> {\n // NOTE: Using regular properties instead of #private fields to avoid\n // \"Cannot read private member\" errors when multiple copies of this class\n // are loaded (dual-package hazard with CJS/ESM or duplicate node_modules).\n readonly _elementRuntimeType: string;\n readonly _isArray: boolean;\n readonly _enumValues: unknown[] | undefined;\n\n /**\n * @param elementRuntimeType - The runtimeType of the (element) type,\n * e.g. 'string', 'number', 'Date', 'bigint', 'Buffer', 'any'.\n * @param isArray - Whether this is an array container type.\n * @param enumValues - Optional list of allowed enum values for app-level\n * validation (used when an enum field has encrypted compliance).\n */\n constructor(\n elementRuntimeType: string = 'string',\n isArray: boolean = false,\n enumValues?: unknown[]\n ) {\n super();\n this._elementRuntimeType = elementRuntimeType;\n this._isArray = isArray;\n this._enumValues = enumValues;\n }\n\n override convertToDatabaseValue(\n value: unknown,\n _platform: Platform,\n _context?: TransformContext\n ): string | null {\n if (value === null || value === undefined) return null;\n if (typeof value === 'string' && value.length === 0) return '';\n\n // Validate enum values at app level before encryption replaces them\n // with ciphertext that would never pass a DB check constraint.\n if (this._enumValues) {\n const valuesToCheck =\n this._isArray && Array.isArray(value) ? value : [value];\n for (const v of valuesToCheck) {\n if (!this._enumValues.includes(v)) {\n throw new Error(\n `Invalid enum value: ${String(v)}. Allowed values: ${this._enumValues.join(', ')}`\n );\n }\n }\n }\n\n if (!_encryptor) {\n return this.serialize(value);\n }\n\n if (typeof value === 'string' && isEncrypted(value)) {\n return value;\n }\n\n const serialized = this.serialize(value);\n // Also the WHERE-clause path: a query value is encrypted under the bound\n // tenant before it is compared. Unbound, that used to produce ciphertext\n // under `''` which simply matched nothing — a silent empty result set,\n // not an error.\n return (\n _encryptor.encrypt(serialized, requireBoundTenantId('write')) ??\n serialized\n );\n }\n\n override convertToJSValue(\n value: string | null,\n _platform: Platform\n ): unknown {\n if (value === null || value === undefined) return null;\n if (typeof value !== 'string') return value;\n\n if (!isEncrypted(value)) {\n return this.deserialize(value);\n }\n\n if (!_encryptor) {\n throw new Error(\n 'EncryptedType: no encryptor registered but database contains encrypted value. ' +\n 'Call registerEncryptor() at application bootstrap.'\n );\n }\n\n let decrypted: string | null;\n const tenantId = requireBoundTenantId('read');\n try {\n decrypted = _encryptor.decrypt(value, tenantId);\n } catch (err) {\n throw new Error(\n `Failed to decrypt encrypted column value: ${err instanceof Error ? err.message : String(err)}`,\n { cause: err }\n );\n }\n if (decrypted === null) return null;\n return this.deserialize(decrypted);\n }\n\n override getColumnType(): string {\n return 'text';\n }\n\n override get runtimeType(): string {\n if (this._isArray) return 'object';\n return this._elementRuntimeType;\n }\n\n override ensureComparable(): boolean {\n return false;\n }\n\n // ---------------------------------------------------------------------------\n // Serialization helpers\n // ---------------------------------------------------------------------------\n\n private serialize(value: unknown): string {\n if (typeof value === 'string') return value;\n if (value instanceof Date) return value.toISOString();\n if (typeof value === 'bigint') return value.toString();\n if (Buffer.isBuffer(value) || value instanceof Uint8Array)\n return Buffer.from(value as Uint8Array).toString('base64');\n return JSON.stringify(value);\n }\n\n private deserialize(value: string): unknown {\n if (this._isArray) {\n try {\n const arr = JSON.parse(value);\n if (!Array.isArray(arr)) return arr;\n return arr.map((el: unknown) =>\n hydrateValue(el, this._elementRuntimeType)\n );\n } catch {\n return value;\n }\n }\n\n switch (this._elementRuntimeType) {\n case 'string':\n return value;\n case 'any':\n try {\n return JSON.parse(value);\n } catch {\n return value;\n }\n // Types serialized via toString/toISOString/base64 — hydrate from string directly\n case 'bigint':\n case 'Date':\n case 'Buffer':\n return hydrateValue(value, this._elementRuntimeType);\n // Types where JSON.parse recovers the native JS value (number, boolean)\n default:\n return hydrateValue(this.tryJsonParse(value), this._elementRuntimeType);\n }\n }\n\n private tryJsonParse(value: string): unknown {\n try {\n return JSON.parse(value);\n } catch {\n return value;\n }\n }\n}\n","export const ComplianceLevel = {\n pii: 'pii',\n phi: 'phi',\n pci: 'pci',\n none: 'none'\n} as const;\nexport type ComplianceLevel =\n (typeof ComplianceLevel)[keyof typeof ComplianceLevel];\n\nexport const COMPLIANCE_KEY = '~compliance' as const;\n\n// ---------------------------------------------------------------------------\n// Registry\n// ---------------------------------------------------------------------------\n\nconst complianceRegistry = new Map<string, Map<string, ComplianceLevel>>();\n\nexport function registerEntityCompliance(\n entityName: string,\n fields: Map<string, ComplianceLevel>\n): void {\n complianceRegistry.set(entityName, fields);\n}\n\nexport function getComplianceMetadata(\n entityName: string,\n fieldName: string\n): ComplianceLevel {\n return complianceRegistry.get(entityName)?.get(fieldName) ?? 'none';\n}\n\nexport function getEntityComplianceFields(\n entityName: string\n): Map<string, ComplianceLevel> | undefined {\n return complianceRegistry.get(entityName);\n}\n\nexport function entityHasEncryptedFields(entityName: string): boolean {\n const fields = complianceRegistry.get(entityName);\n if (!fields) return false;\n for (const level of fields.values()) {\n if (level === 'phi' || level === 'pci') return true;\n }\n return false;\n}\n\n// ---------------------------------------------------------------------------\n// Retention types and registry\n// ---------------------------------------------------------------------------\n\nexport const RetentionAction = {\n delete: 'delete',\n anonymize: 'anonymize'\n} as const;\nexport type RetentionAction =\n (typeof RetentionAction)[keyof typeof RetentionAction];\n\nexport interface RetentionPolicy {\n duration: string;\n action: RetentionAction;\n}\n\nexport const RetentionDuration = {\n days: (n: number): string => `P${n}D`,\n months: (n: number): string => `P${n}M`,\n years: (n: number): string => `P${n}Y`\n} as const;\n\nexport interface ParsedDuration {\n years: number;\n months: number;\n days: number;\n}\n\nconst DURATION_REGEX = /^P(?:(\\d+)Y)?(?:(\\d+)M)?(?:(\\d+)D)?$/;\n\n/**\n * Parse an ISO 8601 duration string into calendar units.\n * Returns structured units to enable calendar-aware date arithmetic.\n */\nexport function parseDuration(iso: string): ParsedDuration {\n const match = DURATION_REGEX.exec(iso);\n if (!match) {\n throw new Error(\n `Invalid ISO 8601 duration: '${iso}'. Expected format: P[n]Y[n]M[n]D`\n );\n }\n const years = parseInt(match[1] || '0', 10);\n const months = parseInt(match[2] || '0', 10);\n const days = parseInt(match[3] || '0', 10);\n\n // Approximate total days for minimum validation only\n const approxDays = years * 365 + months * 30 + days;\n if (approxDays < 1) {\n throw new Error(\n `Retention duration must be >= 1 day (P1D). Got: '${iso}' (${approxDays} approx days)`\n );\n }\n\n return { years, months, days };\n}\n\n/**\n * Subtract a parsed duration from a date using calendar-aware arithmetic.\n * Handles month-end clamping and leap years correctly.\n */\nexport function subtractDuration(from: Date, duration: ParsedDuration): Date {\n const result = new Date(from);\n result.setFullYear(result.getFullYear() - duration.years);\n result.setMonth(result.getMonth() - duration.months);\n result.setDate(result.getDate() - duration.days);\n return result;\n}\n\nconst retentionRegistry = new Map<string, RetentionPolicy>();\n\n// ---------------------------------------------------------------------------\n// User ID field registry — maps entity name to the field linking records to a user\n// ---------------------------------------------------------------------------\n\nconst DEFAULT_USER_ID_FIELD = 'userId';\nconst userIdFieldRegistry = new Map<string, string>();\n\nexport function registerEntityUserIdField(\n entityName: string,\n field: string\n): void {\n userIdFieldRegistry.set(entityName, field);\n}\n\nexport function getEntityUserIdField(entityName: string): string {\n return userIdFieldRegistry.get(entityName) ?? DEFAULT_USER_ID_FIELD;\n}\n\nexport function getAllUserIdFields(): ReadonlyMap<string, string> {\n return userIdFieldRegistry;\n}\n\nexport function registerEntityRetention(\n entityName: string,\n policy: RetentionPolicy\n): void {\n retentionRegistry.set(entityName, policy);\n}\n\nexport function getEntityRetention(\n entityName: string\n): RetentionPolicy | undefined {\n return retentionRegistry.get(entityName);\n}\n\nexport function getAllRetentionPolicies(): ReadonlyMap<\n string,\n RetentionPolicy\n> {\n return retentionRegistry;\n}\n\n// ---------------------------------------------------------------------------\n// Module augmentation — adds .compliance() via PropertyOptions\n// ---------------------------------------------------------------------------\n\n/**\n * Adds `compliance` to PropertyOptions, which flows into IncludeKeys\n * for all scalar/enum/embedded builders (PropertyOptions is extended by\n * EnumOptions and EmbeddedOptions). Relation builders use ReferenceOptions\n * instead, so they don't get .compliance() — which is what we want.\n */\ndeclare module '@mikro-orm/core' {\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n interface PropertyOptions<Owner> {\n compliance?: ComplianceLevel;\n }\n\n interface UniversalPropertyOptionsBuilder<Value, Options, IncludeKeys> {\n /**\n * The level is captured as a literal type parameter rather than widened to\n * `ComplianceLevel`, so the marker records WHICH classification a property\n * carries and not merely that it has one.\n *\n * That distinction is what makes {@link EncryptedKeysOf} possible: `pii`,\n * `phi` and `pci` are encrypted at rest and `none` is not, and a `true`\n * marker cannot tell them apart. See {@link RequiresEncryptionContext} for\n * why the difference matters at a call site.\n */\n compliance<const L extends ComplianceLevel>(\n level: L\n ): Pick<\n UniversalPropertyOptionsBuilder<\n Value,\n Options & { readonly '~c': L },\n IncludeKeys\n >,\n IncludeKeys & keyof UniversalPropertyOptionsBuilder<never, never, never>\n >;\n }\n}\n\n// ---------------------------------------------------------------------------\n// Type-level compliance introspection\n// ---------------------------------------------------------------------------\n\n/**\n * The classifications that cause a column to be encrypted at rest by\n * `EncryptedType`. `none` is deliberately excluded — it is a real, deliberate\n * classification, just not an encrypting one.\n */\nexport type EncryptedComplianceLevel = Extract<\n ComplianceLevel,\n 'pii' | 'phi' | 'pci'\n>;\n\n/**\n * The property names of `TProperties` whose classification means the column is\n * encrypted at rest.\n *\n * Reading such a column requires the same encryption context it was written\n * under, because `FieldEncryptor` derives its key per tenant. When a read\n * happens BEFORE that context is known — a lookup whose whole purpose is to\n * discover which organisation a row belongs to — hydrating these columns cannot\n * succeed, and fails with \"ciphertext is corrupted or the wrong key was used\".\n *\n * The remedy at such a call site is a partial select that omits these keys, so\n * hydration never touches them. This type is what lets that be checked rather\n * than remembered.\n */\nexport type EncryptedKeysOf<TProperties> = {\n [K in keyof TProperties]: TProperties[K] extends {\n '~options': { readonly '~c': infer L };\n }\n ? L extends EncryptedComplianceLevel\n ? K\n : never\n : never;\n}[keyof TProperties];\n\n/**\n * True when reading `TProperties` in full would decrypt something, and so\n * requires an encryption context to already be established.\n */\nexport type RequiresEncryptionContext<TProperties> = [\n EncryptedKeysOf<TProperties>\n] extends [never]\n ? false\n : true;\n\n/**\n * True when `TFields` is a selection that provably avoids every encrypted\n * column of `TProperties` — the shape that makes a context-free read safe.\n */\nexport type SelectionAvoidsEncryptedColumns<\n TProperties,\n TFields extends readonly PropertyKey[]\n> = [Extract<TFields[number], EncryptedKeysOf<TProperties>>] extends [never]\n ? true\n : false;\n","import { withEncryptionContext } from '../persistence/encryptedType';\nimport type { MetricsDefinition } from '../http/types/openTelemetryCollector.types';\nimport type { OpenTelemetryCollector } from '../http/telemetry/openTelemetryCollector';\nimport type { ComplianceOrm } from './complianceDataService';\nimport {\n getAllRetentionPolicies,\n getEntityComplianceFields,\n parseDuration,\n subtractDuration,\n type RetentionPolicy\n} from '../persistence/complianceTypes';\n\nexport interface EnforcementOptions {\n /** Filter to specific entity names (default: all with retention policies) */\n entities?: string[];\n /** Records per batch (default: 1000) */\n batchSize?: number;\n /** Log what would happen without mutating */\n dryRun?: boolean;\n /**\n * Tenants to enforce under, one pass each.\n *\n * Retention reads and rewrites PII columns, and every one of those is\n * encrypted under its row's tenant. An unbound pass anonymises with a key\n * nothing was written with: the `$lt` cutoff on `createdAt` still matches\n * (that column is plaintext), but hydrating the row's encrypted fields\n * fails, the entity is counted as an error and skipped — so retention\n * quietly stops enforcing on exactly the data it exists to age out.\n *\n * Omit only for a schema whose retained entities carry no encrypted column.\n */\n tenantIds?: readonly string[];\n}\n\nexport interface EnforcementResult {\n processed: number;\n deleted: number;\n anonymized: number;\n errors: number;\n byEntity: Record<\n string,\n { deleted: number; anonymized: number; errors: number }\n >;\n durationMs: number;\n}\n\n// Parameterised on the metrics definition rather than typed against\n// `OpenTelemetryCollector<any>`. This service only logs — info, warn, error —\n// and never touches a metric, so it has no reason to know the definition; but\n// `any` there switched off checking on every call through this collector, and\n// the collector's generic appears in its own method parameters, so widening to\n// the base constraint would not have been sound either. The default keeps\n// existing construction sites inferring exactly as before.\nexport class RetentionService<\n AppliedMetricsDefinition extends MetricsDefinition = MetricsDefinition\n> {\n private readonly DEFAULT_BATCH_SIZE = 1000;\n\n constructor(\n private readonly orm: ComplianceOrm,\n private readonly otel: OpenTelemetryCollector<AppliedMetricsDefinition>\n ) {}\n\n async enforce(options?: EnforcementOptions): Promise<EnforcementResult> {\n const tenantIds = options?.tenantIds ?? [];\n if (tenantIds.length > 1) {\n // One pass per tenant, merged. Written this way rather than threading a\n // tenant through every helper: the encryption context is ambient, so\n // wrapping the whole pass is both simpler and harder to get wrong.\n const merged: EnforcementResult = {\n processed: 0,\n deleted: 0,\n anonymized: 0,\n errors: 0,\n byEntity: {},\n durationMs: 0\n };\n const start = Date.now();\n for (const tenantId of tenantIds) {\n const one = await withEncryptionContext(tenantId, () =>\n this.enforce({ ...options, tenantIds: [tenantId] })\n );\n merged.processed += one.processed;\n merged.deleted += one.deleted;\n merged.anonymized += one.anonymized;\n merged.errors += one.errors;\n for (const [entityName, counts] of Object.entries(one.byEntity)) {\n const into = (merged.byEntity[entityName] ??= {\n deleted: 0,\n anonymized: 0,\n errors: 0\n });\n into.deleted += counts.deleted;\n into.anonymized += counts.anonymized;\n into.errors += counts.errors;\n }\n }\n merged.durationMs = Date.now() - start;\n return merged;\n }\n if (tenantIds.length === 1) {\n const only = tenantIds[0];\n // Re-entering with no tenantIds is what stops this recursing.\n return withEncryptionContext(only, () =>\n this.enforce({ ...options, tenantIds: undefined })\n );\n }\n\n const start = Date.now();\n const batchSize = options?.batchSize ?? this.DEFAULT_BATCH_SIZE;\n const dryRun = options?.dryRun ?? false;\n const policies = getAllRetentionPolicies();\n const result: EnforcementResult = {\n processed: 0,\n deleted: 0,\n anonymized: 0,\n errors: 0,\n byEntity: {},\n durationMs: 0\n };\n\n if (policies.size === 0) {\n this.otel.info('[RetentionService] No retention policies registered');\n result.durationMs = Date.now() - start;\n return result;\n }\n\n this.otel.info('[RetentionService] Starting enforcement', {\n entityCount: policies.size,\n batchSize,\n dryRun\n });\n\n for (const [entityName, policy] of policies) {\n if (options?.entities && !options.entities.includes(entityName)) continue;\n\n const entityResult = { deleted: 0, anonymized: 0, errors: 0 };\n\n try {\n await this.enforceEntity(\n entityName,\n policy,\n batchSize,\n entityResult,\n dryRun\n );\n } catch (err) {\n entityResult.errors++;\n this.otel.error('[RetentionService] Entity-level failure', {\n entityName,\n error: String(err)\n });\n }\n\n result.byEntity[entityName] = entityResult;\n result.deleted += entityResult.deleted;\n result.anonymized += entityResult.anonymized;\n result.errors += entityResult.errors;\n result.processed += entityResult.deleted + entityResult.anonymized;\n }\n\n result.durationMs = Date.now() - start;\n this.otel.info('[RetentionService] Enforcement complete', {\n processed: result.processed,\n deleted: result.deleted,\n anonymized: result.anonymized,\n errors: result.errors,\n durationMs: result.durationMs\n });\n\n return result;\n }\n\n private async enforceEntity(\n entityName: string,\n policy: RetentionPolicy,\n batchSize: number,\n stats: { deleted: number; anonymized: number; errors: number },\n dryRun: boolean\n ): Promise<void> {\n const metadata = [...this.orm.getMetadata().getAll().values()].find(\n (m) => m.className === entityName\n );\n\n if (!metadata) {\n this.otel.warn(\n '[RetentionService] Entity not in MikroORM metadata — skipping',\n { entityName }\n );\n return;\n }\n\n const duration = parseDuration(policy.duration);\n const cutoff = subtractDuration(new Date(), duration);\n const entityClass = metadata.class ?? metadata.className;\n\n // For anonymize: only null out PII/PHI/PCI fields that are nullable.\n // Non-nullable fields are skipped to avoid flush errors.\n const piiFieldNames: string[] = [];\n if (policy.action === 'anonymize') {\n const complianceFields = getEntityComplianceFields(entityName);\n if (complianceFields) {\n for (const [fieldName, level] of complianceFields) {\n if (level === 'none') continue;\n const prop = metadata.properties[fieldName];\n if (prop && prop.nullable) {\n piiFieldNames.push(fieldName);\n } else {\n this.otel.warn(\n '[RetentionService] Skipping non-nullable PII field for anonymize',\n { entityName, fieldName, level }\n );\n }\n }\n }\n }\n\n const filter: Record<string, unknown> = {\n createdAt: { $lt: cutoff }\n };\n if (policy.action === 'anonymize') {\n filter['retentionAnonymizedAt'] = null;\n }\n\n // Dry run: use count instead of fetching records\n if (dryRun) {\n const em = this.orm.em.fork();\n try {\n const count = await em.count(entityClass, filter);\n this.otel.info('[RetentionService] Dry run — would process', {\n entityName,\n action: policy.action,\n count\n });\n if (policy.action === 'delete') stats.deleted += count;\n else stats.anonymized += count;\n } catch (err) {\n stats.errors++;\n this.otel.error('[RetentionService] Dry run count failed', {\n entityName,\n error: String(err)\n });\n }\n return;\n }\n\n let batchNum = 0;\n\n while (true) {\n batchNum++;\n const em = this.orm.em.fork();\n\n try {\n const records = await em.find(entityClass, filter, {\n limit: batchSize,\n orderBy: { createdAt: 'ASC' }\n });\n\n if (records.length === 0) break;\n\n if (policy.action === 'delete') {\n records.forEach((r) => em.remove(r));\n await em.flush();\n stats.deleted += records.length;\n } else {\n for (const record of records) {\n const rec = record as Record<string, unknown>;\n for (const field of piiFieldNames) {\n rec[field] = null;\n }\n rec['retentionAnonymizedAt'] = new Date();\n }\n await em.flush();\n stats.anonymized += records.length;\n }\n\n this.otel.info('[RetentionService] Batch processed', {\n entityName,\n action: policy.action,\n batch: batchNum,\n count: records.length\n });\n } catch (err) {\n stats.errors++;\n this.otel.error('[RetentionService] Batch failed', {\n entityName,\n batch: batchNum,\n error: String(err)\n });\n break; // stop batching this entity, continue to next\n }\n }\n }\n}\n","import type { EntityManager, MetadataStorage } from '@mikro-orm/core';\nimport type { OpenTelemetryCollector } from '../http/telemetry/openTelemetryCollector';\n\n/**\n * Structural subset of `MikroORM` used by the compliance services. Typed\n * structurally (rather than as `MikroORM`) because the class's `Entities`\n * type parameter defaults to a mutable array while `MikroORM.init()` returns\n * a readonly one, making concrete instances unassignable to a bare\n * `MikroORM` parameter.\n */\nexport interface ComplianceOrm {\n em: Pick<EntityManager, 'fork'>;\n getMetadata(): MetadataStorage;\n}\nimport { MetricsDefinition } from '../http/types/openTelemetryCollector.types';\nimport { withEncryptionContext } from '../persistence/encryptedType';\nimport {\n getEntityComplianceFields,\n getEntityUserIdField\n} from '../persistence/complianceTypes';\n\n/**\n * Which tenants a subject's rows live under.\n *\n * A compliance walk cannot be tenant-blind. Every PII column is encrypted\n * under its row's tenant, and a WHERE value on one is encrypted under the\n * CURRENT tenant before it is compared — so an unbound walk searches with a\n * key nothing was written with. It matches nothing, everywhere, and an erase\n * that finds nothing looks exactly like an erase with nothing to do.\n *\n * Pass every tenant the subject may have rows under — their organizations,\n * plus whatever constant holds rows belonging to no organization. The walk\n * runs once per tenant and the results are merged.\n */\nexport interface ComplianceTenantScope {\n tenantIds?: readonly string[];\n}\n\n/** An entity the walk could not read, and why. */\nexport interface ComplianceEntityFailure {\n entityName: string;\n tenantId?: string;\n reason: string;\n}\n\nexport interface EraseResult {\n entitiesAffected: string[];\n recordsDeleted: number;\n /**\n * Entities the walk could not read. NON-EMPTY MEANS THE ERASE IS\n * INCOMPLETE: the caller asked for a subject's data to be deleted and some\n * of it may remain. It is reported rather than thrown so the rows that\n * could be erased still are, but it must not be ignored — treat a non-empty\n * list as a failed erasure request.\n */\n failures: ComplianceEntityFailure[];\n}\n\nexport interface ExportResult {\n userId: string;\n entities: Record<string, unknown[]>;\n /** As `EraseResult.failures`: non-empty means the export is incomplete. */\n failures: ComplianceEntityFailure[];\n}\n\n/**\n * Per-entity userIdField overrides.\n * Keys are entity names, values are the field name linking records to a user.\n *\n * @example\n * {\n * User: 'id', // the User entity IS the user record\n * Subscription: 'partyId', // billing links via partyId\n * Account: 'userId', // default, can be omitted\n * }\n */\nexport type UserIdFieldOverrides = Record<string, string>;\n\n/**\n * Common field names that link an entity to a user, tried in order.\n * Used for optimistic search when no explicit userIdField is configured.\n */\nconst CANDIDATE_USER_FIELDS = [\n 'userId',\n 'user',\n 'id',\n 'partyId',\n 'customerId',\n 'ownerId',\n 'createdBy',\n 'email'\n];\n\n/**\n * Generic compliance data service that walks all compliance-registered entities\n * and erases or exports PII/PHI/PCI data for a given user.\n *\n * Resolution order for userIdField per entity:\n * 1. Constructor overrides (highest priority)\n * 2. defineComplianceEntity({ userIdField }) registry\n * 3. Optimistic search: first CANDIDATE_USER_FIELDS match in entity metadata\n * 4. Skip entity (no user link found)\n */\nexport class ComplianceDataService {\n private readonly userIdFieldOverrides: UserIdFieldOverrides;\n\n constructor(\n private readonly orm: ComplianceOrm,\n private readonly otel: OpenTelemetryCollector<MetricsDefinition>,\n userIdFieldOverrides?: UserIdFieldOverrides\n ) {\n this.userIdFieldOverrides = userIdFieldOverrides ?? {};\n }\n\n /**\n * Resolve the field linking an entity to a user.\n * Returns undefined if no link can be determined.\n */\n private resolveUserIdField(\n entityName: string,\n entityProperties: Record<string, unknown>\n ): string | undefined {\n // 1. Constructor override\n const override = this.userIdFieldOverrides[entityName];\n if (override) return override;\n\n // 2. Registry (from defineComplianceEntity({ userIdField }))\n const registered = getEntityUserIdField(entityName);\n if (registered !== 'userId' || entityProperties['userId']) {\n // Registry returned a non-default value, or the default 'userId' exists\n if (entityProperties[registered]) return registered;\n }\n\n // 3. Optimistic search\n for (const candidate of CANDIDATE_USER_FIELDS) {\n if (entityProperties[candidate]) return candidate;\n }\n\n // 4. No link found\n return undefined;\n }\n\n /**\n * Run `fn` once per tenant in scope, each inside its own encryption\n * context. With no tenants named it runs once unbound — which is correct\n * only for entities with no encrypted column, and fails loudly otherwise.\n */\n private async perTenant<T>(\n scope: ComplianceTenantScope | undefined,\n fn: (tenantId?: string) => Promise<T>\n ): Promise<T[]> {\n const tenantIds = scope?.tenantIds ?? [];\n if (tenantIds.length === 0) return [await fn(undefined)];\n const results: T[] = [];\n for (const tenantId of tenantIds) {\n results.push(await withEncryptionContext(tenantId, () => fn(tenantId)));\n }\n return results;\n }\n\n async erase(\n userId: string,\n scope?: ComplianceTenantScope\n ): Promise<EraseResult> {\n const entitiesAffected = new Set<string>();\n const failures: ComplianceEntityFailure[] = [];\n let recordsDeleted = 0;\n\n await this.perTenant(scope, async (tenantId) => {\n recordsDeleted += await this.eraseUnderCurrentTenant(\n userId,\n tenantId,\n entitiesAffected,\n failures\n );\n });\n\n this.otel.info('[ComplianceDataService] Erase complete', {\n userId,\n entitiesAffected: [...entitiesAffected].join(','),\n recordsDeleted,\n failures: failures.length\n });\n\n return {\n entitiesAffected: [...entitiesAffected],\n recordsDeleted,\n failures\n };\n }\n\n private async eraseUnderCurrentTenant(\n userId: string,\n tenantId: string | undefined,\n entitiesAffected: Set<string>,\n failures: ComplianceEntityFailure[]\n ): Promise<number> {\n const em = this.orm.em.fork();\n let recordsDeleted = 0;\n\n const allMetadata = [...this.orm.getMetadata().getAll().values()];\n\n for (const metadata of allMetadata) {\n const entityName = metadata.className;\n const fields = getEntityComplianceFields(entityName);\n if (!fields) continue;\n\n const hasPii = [...fields.values()].some(\n (level) => level === 'pii' || level === 'phi' || level === 'pci'\n );\n if (!hasPii) continue;\n\n const userIdField = this.resolveUserIdField(\n entityName,\n metadata.properties\n );\n\n if (!userIdField) {\n this.otel.warn(\n '[ComplianceDataService] No user-linking field found — skipping',\n { entityName, candidates: CANDIDATE_USER_FIELDS }\n );\n continue;\n }\n\n try {\n const entityClass = metadata.class ?? metadata.className;\n const records = await em.find(entityClass, {\n [userIdField]: userId\n });\n\n if (records.length > 0) {\n entitiesAffected.add(entityName);\n recordsDeleted += records.length;\n records.forEach((r) => em.remove(r));\n }\n } catch (err) {\n // RECORDED, NOT SWALLOWED. This used to log and continue, so an erase\n // that could not read a single entity still returned a clean result\n // and the caller reported the subject's data deleted.\n failures.push({ entityName, tenantId, reason: String(err) });\n this.otel.error('[ComplianceDataService] Failed to erase entity', {\n entityName,\n userIdField,\n tenantId,\n error: String(err)\n });\n }\n }\n\n if (recordsDeleted > 0) {\n await em.flush();\n }\n\n return recordsDeleted;\n }\n\n async export(\n userId: string,\n scope?: ComplianceTenantScope\n ): Promise<ExportResult> {\n const entities: Record<string, unknown[]> = {};\n const failures: ComplianceEntityFailure[] = [];\n\n await this.perTenant(scope, (tenantId) =>\n this.exportUnderCurrentTenant(userId, tenantId, entities, failures)\n );\n\n this.otel.info('[ComplianceDataService] Export complete', {\n userId,\n entityCount: Object.keys(entities).length,\n failures: failures.length\n });\n\n return { userId, entities, failures };\n }\n\n private async exportUnderCurrentTenant(\n userId: string,\n tenantId: string | undefined,\n entities: Record<string, unknown[]>,\n failures: ComplianceEntityFailure[]\n ): Promise<void> {\n const em = this.orm.em.fork();\n\n const allMetadata = [...this.orm.getMetadata().getAll().values()];\n\n for (const metadata of allMetadata) {\n const entityName = metadata.className;\n const fields = getEntityComplianceFields(entityName);\n if (!fields) continue;\n\n const hasPii = [...fields.values()].some(\n (level) => level === 'pii' || level === 'phi' || level === 'pci'\n );\n if (!hasPii) continue;\n\n const userIdField = this.resolveUserIdField(\n entityName,\n metadata.properties\n );\n\n if (!userIdField) {\n continue;\n }\n\n try {\n const entityClass = metadata.class ?? metadata.className;\n const records = await em.find(entityClass, {\n [userIdField]: userId\n });\n\n if (records.length > 0) {\n const piiFieldNames = [...fields.entries()]\n .filter(([, level]) => level !== 'none')\n .map(([name]) => name);\n\n const rows = records.map((record) => {\n const filtered: Record<string, unknown> = {};\n filtered['id'] = (record as Record<string, unknown>)['id'];\n for (const fieldName of piiFieldNames) {\n filtered[fieldName] = (record as Record<string, unknown>)[\n fieldName\n ];\n }\n return filtered;\n });\n // Appended, not assigned: the same entity can hold the subject's\n // rows under more than one tenant, and the second pass must not\n // erase the first one's findings.\n entities[entityName] = [...(entities[entityName] ?? []), ...rows];\n }\n } catch (err) {\n // Recorded, not swallowed — an export missing an entity is an\n // INCOMPLETE subject access request, not a smaller one.\n failures.push({ entityName, tenantId, reason: String(err) });\n this.otel.error('[ComplianceDataService] Failed to export entity', {\n entityName,\n userIdField,\n tenantId,\n error: String(err)\n });\n }\n }\n }\n}\n","import type { MetricsDefinition } from '../http/types/openTelemetryCollector.types';\nimport type { OpenTelemetryCollector } from '../http/telemetry/openTelemetryCollector';\nimport type { RetentionService } from './retentionService';\n\n/**\n * Shared retention enforcement runner.\n * Designed to be called from a one-shot script (ECS RunTask / cron).\n *\n * @example\n * ```ts\n * import { ci, tokens } from '../bootstrapper';\n * import { runRetentionEnforcement } from '@myapp/core';\n *\n * runRetentionEnforcement(\n * ci.resolve(tokens.RetentionService),\n * ci.resolve(tokens.OtelCollector)\n * );\n * ```\n */\nexport async function runRetentionEnforcement<\n AppliedMetricsDefinition extends MetricsDefinition = MetricsDefinition\n>(\n retentionService: RetentionService<AppliedMetricsDefinition>,\n otel: OpenTelemetryCollector<AppliedMetricsDefinition>\n): Promise<void> {\n const dryRun = process.argv.includes('--dry-run');\n\n otel.info('[RetentionEnforcement] Starting', { dryRun });\n\n const result = await retentionService.enforce({ dryRun });\n\n otel.info('[RetentionEnforcement] Complete', {\n processed: result.processed,\n deleted: result.deleted,\n anonymized: result.anonymized,\n errors: result.errors,\n durationMs: result.durationMs\n });\n\n if (result.errors > 0) {\n otel.warn('[RetentionEnforcement] Completed with errors', {\n errors: result.errors,\n byEntity: result.byEntity\n });\n process.exit(1);\n }\n\n process.exit(0);\n}\n"],"mappings":"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","names":["import_common","dotenv","start"]}
|
package/lib/services/index.mjs
CHANGED
|
@@ -449,6 +449,28 @@ var ValidConfigInjector = class extends ConfigInjector {
|
|
|
449
449
|
validConfigInjector;
|
|
450
450
|
};
|
|
451
451
|
|
|
452
|
+
// src/persistence/encryptedType.ts
|
|
453
|
+
import { Type } from "@mikro-orm/core";
|
|
454
|
+
import { AsyncLocalStorage } from "async_hooks";
|
|
455
|
+
var _tenantContext = new AsyncLocalStorage();
|
|
456
|
+
var EmptyTenantError = class extends Error {
|
|
457
|
+
constructor(api) {
|
|
458
|
+
super(
|
|
459
|
+
`${api}: '' is not a tenant id. It used to mean "global", which made a deliberate global row indistinguishable from a tenant nobody resolved. Bind an explicit constant (for example '_internal') for rows that genuinely belong to no organization.`
|
|
460
|
+
);
|
|
461
|
+
this.name = "EmptyTenantError";
|
|
462
|
+
}
|
|
463
|
+
};
|
|
464
|
+
function assertBindableTenantId(tenantId, api) {
|
|
465
|
+
if (tenantId === "") {
|
|
466
|
+
throw new EmptyTenantError(api);
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
function withEncryptionContext(tenantId, fn) {
|
|
470
|
+
assertBindableTenantId(tenantId, "withEncryptionContext");
|
|
471
|
+
return _tenantContext.run({ tenantId }, fn);
|
|
472
|
+
}
|
|
473
|
+
|
|
452
474
|
// src/persistence/complianceTypes.ts
|
|
453
475
|
var complianceRegistry = /* @__PURE__ */ new Map();
|
|
454
476
|
function getEntityComplianceFields(entityName) {
|
|
@@ -500,6 +522,47 @@ var RetentionService = class {
|
|
|
500
522
|
otel;
|
|
501
523
|
DEFAULT_BATCH_SIZE = 1e3;
|
|
502
524
|
async enforce(options) {
|
|
525
|
+
const tenantIds = options?.tenantIds ?? [];
|
|
526
|
+
if (tenantIds.length > 1) {
|
|
527
|
+
const merged = {
|
|
528
|
+
processed: 0,
|
|
529
|
+
deleted: 0,
|
|
530
|
+
anonymized: 0,
|
|
531
|
+
errors: 0,
|
|
532
|
+
byEntity: {},
|
|
533
|
+
durationMs: 0
|
|
534
|
+
};
|
|
535
|
+
const start2 = Date.now();
|
|
536
|
+
for (const tenantId of tenantIds) {
|
|
537
|
+
const one = await withEncryptionContext(
|
|
538
|
+
tenantId,
|
|
539
|
+
() => this.enforce({ ...options, tenantIds: [tenantId] })
|
|
540
|
+
);
|
|
541
|
+
merged.processed += one.processed;
|
|
542
|
+
merged.deleted += one.deleted;
|
|
543
|
+
merged.anonymized += one.anonymized;
|
|
544
|
+
merged.errors += one.errors;
|
|
545
|
+
for (const [entityName, counts] of Object.entries(one.byEntity)) {
|
|
546
|
+
const into = merged.byEntity[entityName] ??= {
|
|
547
|
+
deleted: 0,
|
|
548
|
+
anonymized: 0,
|
|
549
|
+
errors: 0
|
|
550
|
+
};
|
|
551
|
+
into.deleted += counts.deleted;
|
|
552
|
+
into.anonymized += counts.anonymized;
|
|
553
|
+
into.errors += counts.errors;
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
merged.durationMs = Date.now() - start2;
|
|
557
|
+
return merged;
|
|
558
|
+
}
|
|
559
|
+
if (tenantIds.length === 1) {
|
|
560
|
+
const only = tenantIds[0];
|
|
561
|
+
return withEncryptionContext(
|
|
562
|
+
only,
|
|
563
|
+
() => this.enforce({ ...options, tenantIds: void 0 })
|
|
564
|
+
);
|
|
565
|
+
}
|
|
503
566
|
const start = Date.now();
|
|
504
567
|
const batchSize = options?.batchSize ?? this.DEFAULT_BATCH_SIZE;
|
|
505
568
|
const dryRun = options?.dryRun ?? false;
|
|
@@ -694,9 +757,46 @@ var ComplianceDataService = class {
|
|
|
694
757
|
}
|
|
695
758
|
return void 0;
|
|
696
759
|
}
|
|
697
|
-
|
|
760
|
+
/**
|
|
761
|
+
* Run `fn` once per tenant in scope, each inside its own encryption
|
|
762
|
+
* context. With no tenants named it runs once unbound — which is correct
|
|
763
|
+
* only for entities with no encrypted column, and fails loudly otherwise.
|
|
764
|
+
*/
|
|
765
|
+
async perTenant(scope, fn) {
|
|
766
|
+
const tenantIds = scope?.tenantIds ?? [];
|
|
767
|
+
if (tenantIds.length === 0) return [await fn(void 0)];
|
|
768
|
+
const results = [];
|
|
769
|
+
for (const tenantId of tenantIds) {
|
|
770
|
+
results.push(await withEncryptionContext(tenantId, () => fn(tenantId)));
|
|
771
|
+
}
|
|
772
|
+
return results;
|
|
773
|
+
}
|
|
774
|
+
async erase(userId, scope) {
|
|
775
|
+
const entitiesAffected = /* @__PURE__ */ new Set();
|
|
776
|
+
const failures = [];
|
|
777
|
+
let recordsDeleted = 0;
|
|
778
|
+
await this.perTenant(scope, async (tenantId) => {
|
|
779
|
+
recordsDeleted += await this.eraseUnderCurrentTenant(
|
|
780
|
+
userId,
|
|
781
|
+
tenantId,
|
|
782
|
+
entitiesAffected,
|
|
783
|
+
failures
|
|
784
|
+
);
|
|
785
|
+
});
|
|
786
|
+
this.otel.info("[ComplianceDataService] Erase complete", {
|
|
787
|
+
userId,
|
|
788
|
+
entitiesAffected: [...entitiesAffected].join(","),
|
|
789
|
+
recordsDeleted,
|
|
790
|
+
failures: failures.length
|
|
791
|
+
});
|
|
792
|
+
return {
|
|
793
|
+
entitiesAffected: [...entitiesAffected],
|
|
794
|
+
recordsDeleted,
|
|
795
|
+
failures
|
|
796
|
+
};
|
|
797
|
+
}
|
|
798
|
+
async eraseUnderCurrentTenant(userId, tenantId, entitiesAffected, failures) {
|
|
698
799
|
const em = this.orm.em.fork();
|
|
699
|
-
const entitiesAffected = [];
|
|
700
800
|
let recordsDeleted = 0;
|
|
701
801
|
const allMetadata = [...this.orm.getMetadata().getAll().values()];
|
|
702
802
|
for (const metadata of allMetadata) {
|
|
@@ -724,14 +824,16 @@ var ComplianceDataService = class {
|
|
|
724
824
|
[userIdField]: userId
|
|
725
825
|
});
|
|
726
826
|
if (records.length > 0) {
|
|
727
|
-
entitiesAffected.
|
|
827
|
+
entitiesAffected.add(entityName);
|
|
728
828
|
recordsDeleted += records.length;
|
|
729
829
|
records.forEach((r) => em.remove(r));
|
|
730
830
|
}
|
|
731
831
|
} catch (err) {
|
|
832
|
+
failures.push({ entityName, tenantId, reason: String(err) });
|
|
732
833
|
this.otel.error("[ComplianceDataService] Failed to erase entity", {
|
|
733
834
|
entityName,
|
|
734
835
|
userIdField,
|
|
836
|
+
tenantId,
|
|
735
837
|
error: String(err)
|
|
736
838
|
});
|
|
737
839
|
}
|
|
@@ -739,16 +841,24 @@ var ComplianceDataService = class {
|
|
|
739
841
|
if (recordsDeleted > 0) {
|
|
740
842
|
await em.flush();
|
|
741
843
|
}
|
|
742
|
-
|
|
844
|
+
return recordsDeleted;
|
|
845
|
+
}
|
|
846
|
+
async export(userId, scope) {
|
|
847
|
+
const entities = {};
|
|
848
|
+
const failures = [];
|
|
849
|
+
await this.perTenant(
|
|
850
|
+
scope,
|
|
851
|
+
(tenantId) => this.exportUnderCurrentTenant(userId, tenantId, entities, failures)
|
|
852
|
+
);
|
|
853
|
+
this.otel.info("[ComplianceDataService] Export complete", {
|
|
743
854
|
userId,
|
|
744
|
-
|
|
745
|
-
|
|
855
|
+
entityCount: Object.keys(entities).length,
|
|
856
|
+
failures: failures.length
|
|
746
857
|
});
|
|
747
|
-
return {
|
|
858
|
+
return { userId, entities, failures };
|
|
748
859
|
}
|
|
749
|
-
async
|
|
860
|
+
async exportUnderCurrentTenant(userId, tenantId, entities, failures) {
|
|
750
861
|
const em = this.orm.em.fork();
|
|
751
|
-
const entities = {};
|
|
752
862
|
const allMetadata = [...this.orm.getMetadata().getAll().values()];
|
|
753
863
|
for (const metadata of allMetadata) {
|
|
754
864
|
const entityName = metadata.className;
|
|
@@ -772,7 +882,7 @@ var ComplianceDataService = class {
|
|
|
772
882
|
});
|
|
773
883
|
if (records.length > 0) {
|
|
774
884
|
const piiFieldNames = [...fields.entries()].filter(([, level]) => level !== "none").map(([name]) => name);
|
|
775
|
-
|
|
885
|
+
const rows = records.map((record) => {
|
|
776
886
|
const filtered = {};
|
|
777
887
|
filtered["id"] = record["id"];
|
|
778
888
|
for (const fieldName of piiFieldNames) {
|
|
@@ -780,20 +890,18 @@ var ComplianceDataService = class {
|
|
|
780
890
|
}
|
|
781
891
|
return filtered;
|
|
782
892
|
});
|
|
893
|
+
entities[entityName] = [...entities[entityName] ?? [], ...rows];
|
|
783
894
|
}
|
|
784
895
|
} catch (err) {
|
|
896
|
+
failures.push({ entityName, tenantId, reason: String(err) });
|
|
785
897
|
this.otel.error("[ComplianceDataService] Failed to export entity", {
|
|
786
898
|
entityName,
|
|
787
899
|
userIdField,
|
|
900
|
+
tenantId,
|
|
788
901
|
error: String(err)
|
|
789
902
|
});
|
|
790
903
|
}
|
|
791
904
|
}
|
|
792
|
-
this.otel.info("[ComplianceDataService] Export complete", {
|
|
793
|
-
userId,
|
|
794
|
-
entityCount: Object.keys(entities).length
|
|
795
|
-
});
|
|
796
|
-
return { userId, entities };
|
|
797
905
|
}
|
|
798
906
|
};
|
|
799
907
|
|