@forklaunch/core 1.3.16 → 1.4.0

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.
@@ -86,8 +86,8 @@ import { safeParse, safeStringify } from "@forklaunch/common";
86
86
  import { p, Type as Type2 } from "@mikro-orm/core";
87
87
 
88
88
  // src/persistence/encryptedType.ts
89
- import { AsyncLocalStorage } from "async_hooks";
90
89
  import { Type } from "@mikro-orm/core";
90
+ import { AsyncLocalStorage } from "async_hooks";
91
91
  var ENCRYPTED_PREFIXES = ["v1:", "v2:"];
92
92
  function isEncrypted(value) {
93
93
  return ENCRYPTED_PREFIXES.some((p3) => value.startsWith(p3));
@@ -162,10 +162,15 @@ var EncryptedType = class extends Type {
162
162
  convertToDatabaseValue(value, _platform, _context) {
163
163
  if (value === null || value === void 0) return null;
164
164
  if (typeof value === "string" && value.length === 0) return "";
165
- if (this._enumValues && !this._enumValues.includes(value)) {
166
- throw new Error(
167
- `Invalid enum value: ${String(value)}. Allowed values: ${this._enumValues.join(", ")}`
168
- );
165
+ if (this._enumValues) {
166
+ const valuesToCheck = this._isArray && Array.isArray(value) ? value : [value];
167
+ for (const v of valuesToCheck) {
168
+ if (!this._enumValues.includes(v)) {
169
+ throw new Error(
170
+ `Invalid enum value: ${String(v)}. Allowed values: ${this._enumValues.join(", ")}`
171
+ );
172
+ }
173
+ }
169
174
  }
170
175
  if (!_encryptor) {
171
176
  return this.serialize(value);
@@ -182,14 +187,23 @@ var EncryptedType = class extends Type {
182
187
  if (!isEncrypted(value)) {
183
188
  return this.deserialize(value);
184
189
  }
185
- if (!_encryptor) return value;
190
+ if (!_encryptor) {
191
+ throw new Error(
192
+ "EncryptedType: no encryptor registered but database contains encrypted value. Call registerEncryptor() at application bootstrap."
193
+ );
194
+ }
195
+ let decrypted;
196
+ const tenantId = getCurrentTenantId();
186
197
  try {
187
- const decrypted = _encryptor.decrypt(value, getCurrentTenantId());
188
- if (decrypted === null) return null;
189
- return this.deserialize(decrypted);
190
- } catch {
191
- return value;
198
+ decrypted = _encryptor.decrypt(value, tenantId);
199
+ } catch (err) {
200
+ throw new Error(
201
+ `Failed to decrypt encrypted column value: ${err instanceof Error ? err.message : String(err)}`,
202
+ { cause: err }
203
+ );
192
204
  }
205
+ if (decrypted === null) return null;
206
+ return this.deserialize(decrypted);
193
207
  }
194
208
  getColumnType() {
195
209
  return "text";
@@ -306,6 +320,7 @@ function wrapClassified(builder, level) {
306
320
  enumValues = extractEnumValues(options.items);
307
321
  delete options.items;
308
322
  delete options.nativeEnumName;
323
+ delete options.enum;
309
324
  }
310
325
  options.type = new EncryptedType(elementRuntimeType, isArray, enumValues);
311
326
  options.columnType = "text";
@@ -344,6 +359,9 @@ function isRelationMethod(prop) {
344
359
  return typeof prop === "string" && RELATION_METHODS.has(prop);
345
360
  }
346
361
  function extractEnumValues(items) {
362
+ if (typeof items === "function") {
363
+ return extractEnumValues(items());
364
+ }
347
365
  if (Array.isArray(items)) return items;
348
366
  if (items != null && typeof items === "object") {
349
367
  const obj = items;
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/persistence/complianceTypes.ts","../../src/persistence/compliancePropertyBuilder.ts","../../src/persistence/encryptedType.ts","../../src/persistence/defineComplianceEntity.ts","../../src/persistence/fieldEncryptor.ts","../../src/persistence/complianceEventSubscriber.ts","../../src/persistence/tenantFilter.ts","../../src/persistence/rls.ts"],"sourcesContent":["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 compliance(\n level: ComplianceLevel\n ): Pick<\n UniversalPropertyOptionsBuilder<\n Value,\n Options & { readonly '~c': true },\n IncludeKeys\n >,\n IncludeKeys & keyof UniversalPropertyOptionsBuilder<never, never, never>\n >;\n }\n}\n","import { safeParse, safeStringify } from '@forklaunch/common';\nimport { p, Type, type PropertyBuilders } from '@mikro-orm/core';\nimport { COMPLIANCE_KEY, type ComplianceLevel } from './complianceTypes';\nimport { EncryptedType, resolveTypeInstance } from './encryptedType';\n\n// ---------------------------------------------------------------------------\n// Runtime Proxy implementation\n// ---------------------------------------------------------------------------\n\n/**\n * Check whether a value is a MikroORM property builder (has ~options).\n */\nfunction isBuilder(value: unknown): value is object {\n return (\n value != null &&\n typeof value === 'object' &&\n '~options' in (value as Record<string, unknown>)\n );\n}\n\n/**\n * Wraps a MikroORM scalar PropertyBuilder in a Proxy that:\n * 1. Adds a `.compliance(level)` method\n * 2. Forwards all other method calls to the underlying builder\n * 3. Re-wraps returned builders so `.compliance()` persists through chains\n */\nfunction wrapUnclassified(builder: unknown): unknown {\n return new Proxy(builder as object, {\n get(target: Record<string | symbol, unknown>, prop) {\n if (prop === 'compliance') {\n return (level: ComplianceLevel) => wrapClassified(target, level);\n }\n if (prop === '~options') return Reflect.get(target, prop, target);\n if (prop === COMPLIANCE_KEY) return undefined;\n\n const value = Reflect.get(target, prop, target);\n if (typeof value === 'function') {\n return (...args: unknown[]) => {\n const result = (value as (...args: unknown[]) => unknown).apply(\n target,\n args\n );\n return isBuilder(result) ? wrapUnclassified(result) : result;\n };\n }\n return value;\n }\n });\n}\n\n/**\n * Compliance levels that require field-level encryption via EncryptedType.\n */\nconst ENCRYPTED_LEVELS: ReadonlySet<ComplianceLevel> = new Set([\n 'pii',\n 'phi',\n 'pci'\n]);\n\n/**\n * Resolve the element runtimeType and isArray flag from builder options.\n * Uses the actual MikroORM Type instance's runtimeType — no manual\n * type-name enumeration needed.\n */\nfunction resolveEncryptedTypeArgs(options: Record<string, unknown>): {\n elementRuntimeType: string;\n isArray: boolean;\n} {\n const type = options.type;\n const isArray = options.array === true;\n\n // For arrays, the type is the element type (constructor or instance).\n // For p.array() with no chained element type, type is an ArrayType instance.\n const resolved = resolveTypeInstance(type);\n if (resolved) {\n const rt = resolved.runtimeType;\n // ArrayType's runtimeType is 'string[]' — treat as array of strings\n if (rt.endsWith('[]')) {\n return { elementRuntimeType: rt.slice(0, -2) || 'string', isArray: true };\n }\n return { elementRuntimeType: rt, isArray };\n }\n\n // No type set (e.g., plain enum) — default to string\n return { elementRuntimeType: 'string', isArray };\n}\n\n/**\n * Wraps a builder that has been classified via `.compliance()`.\n * Stores the compliance level under `~compliance` for `defineComplianceEntity`.\n * For encrypted levels (pii/phi/pci), applies EncryptedType to the builder\n * so MikroORM handles encryption at the data conversion layer.\n * Chaining after `.compliance()` propagates the level through subsequent builders.\n */\nfunction wrapClassified(builder: object, level: ComplianceLevel): unknown {\n // Apply EncryptedType for encrypted compliance levels\n if (ENCRYPTED_LEVELS.has(level)) {\n const options = (builder as Record<string | symbol, unknown>)[\n '~options'\n ] as Record<string, unknown> | undefined;\n if (options) {\n const { elementRuntimeType, isArray } = resolveEncryptedTypeArgs(options);\n\n // For enum fields, extract allowed values for app-level validation\n // then remove enum metadata so MikroORM won't generate a DB check\n // constraint (the encrypted ciphertext would never satisfy it).\n let enumValues: unknown[] | undefined;\n if (options.items) {\n enumValues = extractEnumValues(options.items);\n delete options.items;\n delete options.nativeEnumName;\n }\n\n options.type = new EncryptedType(elementRuntimeType, isArray, enumValues);\n // Force column type to text since encrypted output is always a string\n options.columnType = 'text';\n options.runtimeType = isArray ? 'object' : elementRuntimeType;\n }\n }\n\n return new Proxy(builder, {\n get(target: Record<string | symbol, unknown>, prop) {\n if (prop === COMPLIANCE_KEY) return level;\n if (prop === '~options') return Reflect.get(target, prop, target);\n\n const value = Reflect.get(target, prop, target);\n if (typeof value === 'function') {\n return (...args: unknown[]) => {\n const result = (value as (...args: unknown[]) => unknown).apply(\n target,\n args\n );\n return isBuilder(result) ? wrapClassified(result, level) : result;\n };\n }\n return value;\n }\n });\n}\n\n/**\n * Wraps a relation PropertyBuilder (manyToOne, oneToMany, etc.).\n * Auto-classified as 'none' — no `.compliance()` call needed.\n * All chained methods preserve the auto-classification.\n */\nfunction wrapRelation(builder: object): unknown {\n return wrapClassified(builder, 'none');\n}\n\n// ---------------------------------------------------------------------------\n// Relation method detection\n// ---------------------------------------------------------------------------\n\nconst RELATION_METHODS = new Set([\n 'manyToOne',\n 'oneToMany',\n 'manyToMany',\n 'oneToOne',\n 'embedded'\n]);\n\nfunction isRelationMethod(prop: string | symbol): boolean {\n return typeof prop === 'string' && RELATION_METHODS.has(prop);\n}\n\n// ---------------------------------------------------------------------------\n// Enum type inference\n// ---------------------------------------------------------------------------\n\n/**\n * Extract the raw values from enum items (array or native TS enum object).\n */\nfunction extractEnumValues(items: unknown): unknown[] {\n if (Array.isArray(items)) return items;\n if (items != null && typeof items === 'object') {\n // Native TS numeric enums have reverse mappings (value → key).\n // Filter those out by keeping only values whose key is not itself a value.\n const obj = items as Record<string, unknown>;\n const allValues = Object.values(obj);\n return allValues.filter((v) => typeof v !== 'string' || !(v in obj));\n }\n return [];\n}\n\n/**\n * MikroORM custom Type that stores mixed-type enum values as text\n * using safeStringify/safeParse for round-trip fidelity.\n */\nclass EnumTextType extends Type<unknown, string> {\n override convertToDatabaseValue(value: unknown): string {\n return safeStringify(value);\n }\n\n override convertToJSValue(value: unknown): unknown {\n return safeParse(value);\n }\n\n override getColumnType(): string {\n return 'text';\n }\n\n get runtimeType(): string {\n return 'string';\n }\n}\n\n/**\n * Infer the MikroORM type for an enum property from its items.\n *\n * - All items are strings → 'text'\n * - All items are numbers → 'integer'\n * - Mixed types → EnumTextType (safeStringify/safeParse round-trip)\n * - No items → 'text'\n */\nfunction inferEnumType(items: unknown): string | EnumTextType {\n const values = extractEnumValues(items);\n if (values.length === 0) return 'text';\n\n const allStrings = values.every((v) => typeof v === 'string');\n if (allStrings) return 'text';\n\n const allNumbers = values.every((v) => typeof v === 'number');\n if (allNumbers) return 'integer';\n\n return new EnumTextType();\n}\n\n// ---------------------------------------------------------------------------\n// fp — the ForkLaunch property builder\n// ---------------------------------------------------------------------------\n\n/**\n * ForkLaunch property builder. Drop-in replacement for MikroORM's `p`\n * that adds `.compliance(level)` to every scalar property builder\n * and auto-classifies relation builders as 'none'.\n *\n * - Scalar fields: `fp.string().compliance('pii')` — must call `.compliance()`\n * - Relation fields: `fp.manyToOne(Target)` — auto-classified, no `.compliance()` needed\n *\n * @example\n * ```typescript\n * import { defineComplianceEntity, fp } from '@forklaunch/core/persistence';\n *\n * const User = defineComplianceEntity({\n * name: 'User',\n * properties: {\n * id: fp.uuid().primary().compliance('none'),\n * email: fp.string().unique().compliance('pii'),\n * medicalRecord: fp.string().nullable().compliance('phi'),\n * organization: () => fp.manyToOne(Organization).nullable(),\n * }\n * });\n * ```\n */\nexport const fp: PropertyBuilders = new Proxy(p, {\n get(target: Record<string | symbol, unknown>, prop) {\n const value = Reflect.get(target, prop, target);\n if (typeof value !== 'function') return value;\n\n if (isRelationMethod(prop)) {\n // Relation methods: call the original, wrap result as auto-classified 'none'\n return (...args: unknown[]) => {\n const result = (value as (...args: unknown[]) => unknown).apply(\n target,\n args\n );\n return isBuilder(result) ? wrapRelation(result) : result;\n };\n }\n\n // Scalar methods: call the original, wrap result with .compliance()\n return (...args: unknown[]) => {\n const result = (value as (...args: unknown[]) => unknown).apply(\n target,\n args\n );\n if (isBuilder(result)) {\n // For enum(), infer a default type so MikroORM metadata\n // discovery doesn't require an explicit .type() call.\n if (prop === 'enum') {\n const options = (result as Record<string | symbol, unknown>)[\n '~options'\n ] as Record<string, unknown> | undefined;\n if (options && !options.type) {\n options.type = inferEnumType(options.items);\n }\n }\n return wrapUnclassified(result);\n }\n return result;\n };\n }\n}) as PropertyBuilders;\n","import { AsyncLocalStorage } from 'node:async_hooks';\nimport { Type, type Platform, type TransformContext } from '@mikro-orm/core';\nimport type { FieldEncryptor } from './fieldEncryptor';\n\nconst ENCRYPTED_PREFIXES = ['v1:', 'v2:'] 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 * Run a callback with the given tenant ID available to EncryptedType\n * for per-tenant key derivation.\n */\nexport function withEncryptionContext<T>(tenantId: string, fn: () => T): T {\n return _tenantContext.run({ tenantId }, fn);\n}\n\n/**\n * Set the encryption tenant ID for the current async context.\n * Called automatically by the tenant context middleware — users\n * don't need to call this directly.\n */\nexport function setEncryptionTenantId(tenantId: string): void {\n _tenantContext.enterWith({ tenantId });\n}\n\n/**\n * Get the current tenant ID. Returns empty string when no context is set\n * (startup, seeders, better-auth, background jobs).\n */\nexport function getCurrentTenantId(): string {\n return _tenantContext.getStore()?.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 && !this._enumValues.includes(value)) {\n throw new Error(\n `Invalid enum value: ${String(value)}. Allowed values: ${this._enumValues.join(', ')}`\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 return _encryptor.encrypt(serialized, getCurrentTenantId()) ?? serialized;\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) return value;\n\n try {\n const decrypted = _encryptor.decrypt(value, getCurrentTenantId());\n if (decrypted === null) return null;\n return this.deserialize(decrypted);\n } catch {\n return value;\n }\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","import {\n defineEntity,\n p,\n type EntityMetadataWithProperties,\n type EntitySchemaWithMeta,\n type InferEntityFromProperties\n} from '@mikro-orm/core';\nimport {\n COMPLIANCE_KEY,\n parseDuration,\n registerEntityCompliance,\n registerEntityRetention,\n registerEntityUserIdField,\n type ComplianceLevel,\n type RetentionPolicy\n} from './complianceTypes';\n\ntype ValidateProperties<T> = {\n [K in keyof T]: T[K] extends\n | { '~options': { readonly '~c': true } }\n | ((...args: never[]) => unknown)\n ? T[K]\n : { '~options': { readonly '~c': true } };\n};\n\nfunction readComplianceLevel(builder: unknown): ComplianceLevel | undefined {\n if (builder == null || typeof builder !== 'object') return undefined;\n return (builder as Record<string, unknown>)[COMPLIANCE_KEY] as\n | ComplianceLevel\n | undefined;\n}\n\nexport function defineComplianceEntity<\n const TName extends string,\n const TTableName extends string,\n const TProperties extends Record<string, unknown>,\n const TPK extends (keyof TProperties)[] | undefined = undefined,\n const TBase = never,\n const TRepository = never,\n const TForceObject extends boolean = false\n>(\n meta: EntityMetadataWithProperties<\n TName,\n TTableName,\n TProperties & ValidateProperties<TProperties>,\n TPK,\n TBase,\n TRepository,\n TForceObject\n > & { retention?: RetentionPolicy; userIdField?: string }\n): EntitySchemaWithMeta<\n TName,\n TTableName,\n InferEntityFromProperties<TProperties, TPK, TBase, TRepository, TForceObject>,\n TBase,\n TProperties\n> {\n const entityName = 'name' in meta ? (meta.name as string) : 'Unknown';\n const complianceFields = new Map<string, ComplianceLevel>();\n\n const rawProperties = meta.properties;\n const resolvedProperties: Record<string, unknown> =\n typeof rawProperties === 'function' ? rawProperties(p) : rawProperties;\n\n for (const [fieldName, rawProp] of Object.entries(resolvedProperties)) {\n if (typeof rawProp === 'function') {\n // Relations are arrow-wrapped and auto-classified as 'none' — don't\n // call the arrow, since the referenced entity may not be initialized\n // yet (circular reference). MikroORM resolves these lazily.\n complianceFields.set(fieldName, 'none');\n continue;\n }\n const level = readComplianceLevel(rawProp);\n\n if (level == null) {\n throw new Error(\n `Field '${entityName}.${fieldName}' is missing compliance classification. ` +\n `Call .compliance('pii' | 'phi' | 'pci' | 'none') on this property, ` +\n `or use a relation method (fp.manyToOne, etc.) which is auto-classified.`\n );\n }\n complianceFields.set(fieldName, level);\n }\n\n registerEntityCompliance(entityName, complianceFields);\n\n // Handle retention policy\n if (meta.retention) {\n parseDuration(meta.retention.duration); // validates at boot — throws if invalid\n\n if (!resolvedProperties['createdAt']) {\n throw new Error(\n `Entity '${entityName}' has a retention policy but no 'createdAt' property. ` +\n `Retention requires createdAt to compute expiration.`\n );\n }\n\n registerEntityRetention(entityName, meta.retention);\n }\n\n // Register userIdField (defaults to 'userId' if not specified)\n if (meta.userIdField) {\n registerEntityUserIdField(entityName, meta.userIdField);\n }\n\n return defineEntity(\n meta as EntityMetadataWithProperties<\n TName,\n TTableName,\n TProperties & ValidateProperties<TProperties>,\n TPK,\n TBase,\n TRepository,\n TForceObject\n >\n ) as EntitySchemaWithMeta<\n TName,\n TTableName,\n InferEntityFromProperties<\n TProperties,\n TPK,\n TBase,\n TRepository,\n TForceObject\n >,\n TBase,\n TProperties\n >;\n}\n","import crypto from 'crypto';\n\n// ---------------------------------------------------------------------------\n// Error types\n// ---------------------------------------------------------------------------\n\nexport class MissingEncryptionKeyError extends Error {\n readonly name = 'MissingEncryptionKeyError' as const;\n constructor(message = 'Master encryption key must be provided') {\n super(message);\n }\n}\n\nexport class DecryptionError extends Error {\n readonly name = 'DecryptionError' as const;\n constructor(\n message = 'Decryption failed: ciphertext is corrupted or the wrong key was used'\n ) {\n super(message);\n }\n}\n\nexport class EncryptionRequiredError extends Error {\n readonly name = 'EncryptionRequiredError' as const;\n constructor(\n message = 'Encryption is required before persisting this compliance field'\n ) {\n super(message);\n }\n}\n\n// ---------------------------------------------------------------------------\n// Constants\n// ---------------------------------------------------------------------------\n\nconst ALGORITHM = 'aes-256-gcm' as const;\nconst IV_BYTES = 12;\nconst KEY_BYTES = 32;\nconst HKDF_HASH = 'sha256' as const;\nconst HKDF_SALT = Buffer.alloc(0); // empty salt – key material is already high-entropy\n\n// ---------------------------------------------------------------------------\n// FieldEncryptor\n// ---------------------------------------------------------------------------\n\nexport class FieldEncryptor {\n private readonly masterKey: string;\n\n constructor(masterKey: string) {\n if (!masterKey) {\n throw new MissingEncryptionKeyError();\n }\n this.masterKey = masterKey;\n }\n\n /**\n * Derive a per-tenant 32-byte key using HKDF-SHA256.\n * The master key is used as input key material and the tenantId as info context.\n */\n deriveKey(tenantId: string): Buffer {\n return Buffer.from(\n crypto.hkdfSync(HKDF_HASH, this.masterKey, HKDF_SALT, tenantId, KEY_BYTES)\n );\n }\n\n /**\n * Derive a deterministic IV from the key and plaintext using HMAC-SHA256,\n * truncated to IV_BYTES. Same plaintext + same key → same IV → same\n * ciphertext. This enables WHERE clause matching on encrypted columns\n * while maintaining AES-256-GCM authenticated encryption.\n *\n * Meets SOC 2, HIPAA, PCI DSS, GDPR requirements for encryption at rest.\n */\n private deriveDeterministicIv(key: Buffer, plaintext: string): Buffer {\n return crypto\n .createHmac('sha256', key)\n .update(plaintext)\n .digest()\n .subarray(0, IV_BYTES);\n }\n\n /**\n * Encrypt a plaintext string.\n *\n * Uses deterministic encryption (HMAC-derived IV) so the same plaintext\n * always produces the same ciphertext. This enables database WHERE clause\n * matching and UNIQUE constraints on encrypted columns.\n *\n * @returns Format: `v2:{base64(iv)}:{base64(authTag)}:{base64(ciphertext)}`\n */\n encrypt(plaintext: string | null): string | null;\n encrypt(plaintext: string | null, tenantId: string): string | null;\n encrypt(plaintext: string | null, tenantId?: string): string | null {\n if (plaintext === null || plaintext === undefined) {\n return null;\n }\n\n const key = this.deriveKey(tenantId ?? '');\n const iv = this.deriveDeterministicIv(key, plaintext);\n\n const cipher = crypto.createCipheriv(ALGORITHM, key, iv);\n const encrypted = Buffer.concat([\n cipher.update(plaintext, 'utf8'),\n cipher.final()\n ]);\n const authTag = cipher.getAuthTag();\n\n return [\n 'v2',\n iv.toString('base64'),\n authTag.toString('base64'),\n encrypted.toString('base64')\n ].join(':');\n }\n\n /**\n * Decrypt a ciphertext string produced by {@link encrypt}.\n * Supports both v1 (random IV) and v2 (deterministic IV) formats.\n */\n decrypt(ciphertext: string | null): string | null;\n decrypt(ciphertext: string | null, tenantId: string): string | null;\n decrypt(ciphertext: string | null, tenantId?: string): string | null {\n if (ciphertext === null || ciphertext === undefined) {\n return null;\n }\n\n const parts = ciphertext.split(':');\n if (parts.length !== 4 || (parts[0] !== 'v1' && parts[0] !== 'v2')) {\n throw new DecryptionError(\n `Unknown ciphertext version or malformed format`\n );\n }\n\n const [, ivB64, authTagB64, encryptedB64] = parts;\n const iv = Buffer.from(ivB64, 'base64');\n const authTag = Buffer.from(authTagB64, 'base64');\n const encrypted = Buffer.from(encryptedB64, 'base64');\n const key = this.deriveKey(tenantId ?? '');\n\n try {\n const decipher = crypto.createDecipheriv(ALGORITHM, key, iv);\n decipher.setAuthTag(authTag);\n const decrypted = Buffer.concat([\n decipher.update(encrypted),\n decipher.final()\n ]);\n return decrypted.toString('utf8');\n } catch {\n throw new DecryptionError();\n }\n }\n}\n","import { EncryptionRequiredError } from './fieldEncryptor';\nimport {\n type ComplianceLevel,\n getEntityComplianceFields\n} from './complianceTypes';\n\n/**\n * Compliance levels that require field-level encryption.\n */\nconst ENCRYPTED_LEVELS: ReadonlySet<ComplianceLevel> = new Set([\n 'pii',\n 'phi',\n 'pci'\n]);\n\n// ---------------------------------------------------------------------------\n// Native query blocking\n// ---------------------------------------------------------------------------\n\n/**\n * Wraps an EntityManager to block `nativeInsert`, `nativeUpdate`, and\n * `nativeDelete` on entities that have PII, PHI, or PCI compliance fields.\n *\n * This prevents bypassing the EncryptedType's encryption by using raw\n * queries. Call this in the tenant context middleware when creating the\n * request-scoped EM.\n *\n * @returns A Proxy-wrapped EntityManager that throws on native query\n * operations targeting compliance entities.\n */\nexport function wrapEmWithNativeQueryBlocking<T extends object>(em: T): T {\n const BLOCKED_METHODS = [\n 'nativeInsert',\n 'nativeUpdate',\n 'nativeDelete'\n ] as const;\n\n return new Proxy(em, {\n get(target, prop, receiver) {\n if (\n typeof prop === 'string' &&\n BLOCKED_METHODS.includes(prop as (typeof BLOCKED_METHODS)[number])\n ) {\n return (entityNameOrEntity: unknown, ...rest: unknown[]) => {\n const entityName = resolveEntityName(entityNameOrEntity);\n if (entityName) {\n const fields = getEntityComplianceFields(entityName);\n if (fields) {\n for (const [fieldName, level] of fields) {\n if (ENCRYPTED_LEVELS.has(level)) {\n throw new EncryptionRequiredError(\n `${prop}() blocked on entity '${entityName}' because field ` +\n `'${fieldName}' has compliance level '${level}'. ` +\n `Use em.create() + em.flush() instead to ensure encryption.`\n );\n }\n }\n }\n }\n const method = Reflect.get(target, prop, receiver);\n return (method as (...args: unknown[]) => unknown).call(\n target,\n entityNameOrEntity,\n ...rest\n );\n };\n }\n return Reflect.get(target, prop, receiver);\n }\n });\n}\n\n/**\n * Resolve an entity name from the first argument to nativeInsert/Update/Delete.\n */\nfunction resolveEntityName(entityNameOrEntity: unknown): string | undefined {\n if (typeof entityNameOrEntity === 'string') {\n return entityNameOrEntity;\n }\n if (typeof entityNameOrEntity === 'function') {\n return (entityNameOrEntity as { name?: string }).name;\n }\n if (\n entityNameOrEntity != null &&\n typeof entityNameOrEntity === 'object' &&\n 'constructor' in entityNameOrEntity\n ) {\n return (entityNameOrEntity.constructor as { name?: string }).name;\n }\n return undefined;\n}\n","import type { Dictionary, EntityManager, FilterDef } from '@mikro-orm/core';\n\n/**\n * The name used to register the tenant isolation filter.\n */\nexport const TENANT_FILTER_NAME = 'tenant';\n\n/**\n * Creates the tenant filter definition.\n *\n * The filter adds `WHERE <column> = :tenantId` to all queries\n * on entities that have the configured tenant column (or its relation).\n * Entities without the property are unaffected (empty condition).\n *\n * @param column - The entity property name used for tenant isolation.\n * Defaults to `'organizationId'`. The relation name\n * (without `Id` suffix) is also checked.\n */\nexport function createTenantFilterDef(\n column: string = 'organizationId',\n relationName?: string\n): FilterDef {\n const relation = relationName ?? column.replace(/Id$/, '');\n return {\n name: TENANT_FILTER_NAME,\n cond(\n args: Dictionary,\n _type: 'read' | 'update' | 'delete',\n em: EntityManager,\n _options?: unknown,\n entityName?: string\n ) {\n if (!entityName) {\n return {};\n }\n\n // If no tenant context is set (startup, background jobs, better-auth),\n // skip filtering entirely. This is safe: tenant-scoped endpoints always\n // set filter params before querying.\n if (!args?.tenantId) {\n return {};\n }\n\n try {\n const metadata = em.getMetadata().getByClassName(entityName, false);\n if (!metadata) {\n return {};\n }\n\n const hasColumn = metadata.properties[column] != null;\n const hasRelation =\n relation !== column && metadata.properties[relation] != null;\n\n if (hasColumn) {\n return { [column]: args.tenantId };\n }\n if (hasRelation) {\n return { [relation]: args.tenantId };\n }\n } catch {\n // Entity not found in metadata — skip filtering\n }\n\n return {};\n },\n default: true,\n args: false\n };\n}\n\n/**\n * Registers the global tenant isolation filter on the ORM's entity manager.\n * Call this once at application bootstrap after `MikroORM.init()`.\n *\n * After calling this, every fork of the EM will inherit the filter.\n * Set the tenant ID per-request via:\n *\n * ```ts\n * em.setFilterParams('tenant', { tenantId: 'org-123' });\n * ```\n */\nexport function setupTenantFilter(\n orm: {\n em: Pick<EntityManager, 'addFilter'>;\n },\n config: {\n /** Entity property name used for tenant isolation. Defaults to `'organizationId'`. */\n column?: string;\n /** Relation property name if different from column without 'Id' suffix. E.g., if column is 'orgId' but relation is 'parentOrg'. */\n relation?: string;\n logger?: { info: (msg: string, ...args: unknown[]) => void };\n } = {}\n): void {\n orm.em.addFilter(createTenantFilterDef(config.column, config.relation));\n (config.logger ?? console).info(\n `[compliance] Tenant isolation filter registered on column '${config.column ?? 'organizationId'}'`\n );\n}\n\n/**\n * Returns a forked EntityManager with the tenant filter disabled.\n *\n * Use this only from code paths that have verified super-admin permissions.\n * Queries executed through the returned EM will return cross-tenant data.\n */\nexport function getSuperAdminContext(\n em: Pick<EntityManager, 'fork'>\n): ReturnType<EntityManager['fork']> {\n const forked = em.fork();\n forked.setFilterParams(TENANT_FILTER_NAME, { tenantId: undefined });\n // Disable the filter by passing false for the filter in each query isn't\n // sufficient globally; instead we add the filter with enabled = false.\n // The cleanest way is to re-add the filter as disabled on this fork.\n forked.addFilter({\n name: TENANT_FILTER_NAME,\n cond: {},\n default: false\n });\n return forked;\n}\n","import type { EntityManager, EventSubscriber, MikroORM } from '@mikro-orm/core';\nimport { TENANT_FILTER_NAME } from './tenantFilter';\n\n/**\n * Structural subset of {@link TransactionEventArgs} used by\n * `RlsEventSubscriber` so that callers (and tests) only need to provide\n * the properties the subscriber actually reads.\n */\nexport interface RlsTransactionEventArgs {\n em: {\n getFilterParams: EntityManager['getFilterParams'];\n getConnection(): {\n execute(\n query: string,\n params?: unknown[],\n method?: 'all' | 'get' | 'run',\n ctx?: unknown\n ): Promise<unknown>;\n };\n };\n transaction?: unknown;\n}\n\n// ---------------------------------------------------------------------------\n// Configuration\n// ---------------------------------------------------------------------------\n\nexport interface RlsConfig {\n /**\n * Whether to enable PostgreSQL Row-Level Security.\n * Defaults to `true` when the driver is PostgreSQL, `false` otherwise.\n * Set to `false` to opt out even on PostgreSQL.\n */\n enabled?: boolean;\n /** Optional logger for compliance messages. Falls back to console. */\n logger?: {\n info: (msg: string, ...args: unknown[]) => void;\n warn: (msg: string, ...args: unknown[]) => void;\n };\n}\n\n// ---------------------------------------------------------------------------\n// RLS EventSubscriber\n// ---------------------------------------------------------------------------\n\n/**\n * MikroORM EventSubscriber that executes `SET LOCAL app.tenant_id = :tenantId`\n * at the start of every transaction when PostgreSQL RLS is enabled.\n *\n * This ensures that even if the MikroORM global filter is somehow bypassed,\n * the database-level RLS policy enforces tenant isolation.\n *\n * The tenant ID is read from the EntityManager's filter parameters\n * (set by the tenant context middleware).\n */\nexport class RlsEventSubscriber implements EventSubscriber {\n async afterTransactionStart(args: RlsTransactionEventArgs): Promise<void> {\n const tenantId = this.getTenantId(args.em);\n if (!tenantId) {\n // No tenant context (e.g., super-admin or public route) — skip SET LOCAL\n return;\n }\n\n const connection = args.em.getConnection();\n // Execute SET LOCAL within the transaction context\n // SET LOCAL only persists for the current transaction — no connection leakage\n await connection.execute(\n `SET LOCAL app.tenant_id = '${escapeSqlString(tenantId)}'`,\n [],\n 'run',\n args.transaction\n );\n }\n\n private getTenantId(\n em: Pick<EntityManager, 'getFilterParams'>\n ): string | undefined {\n const params = em.getFilterParams(TENANT_FILTER_NAME) as\n | { tenantId?: string }\n | undefined;\n return params?.tenantId;\n }\n}\n\n// ---------------------------------------------------------------------------\n// Setup\n// ---------------------------------------------------------------------------\n\n/**\n * Sets up PostgreSQL Row-Level Security integration.\n *\n * 1. Registers the `RlsEventSubscriber` to run `SET LOCAL app.tenant_id`\n * at the start of every transaction.\n * 2. Validates that RLS policies exist on tenant-scoped tables (warns if missing).\n *\n * Call this at application bootstrap after `MikroORM.init()` and `setupTenantFilter()`.\n *\n * @param orm - The initialized MikroORM instance\n * @param config - RLS configuration (enabled defaults to auto-detect PostgreSQL)\n */\nexport function setupRls(orm: MikroORM, config: RlsConfig = {}): void {\n const isPostgres = isPostgresDriver(orm);\n const enabled = config.enabled ?? isPostgres;\n const log = config.logger ?? { info: console.info, warn: console.warn };\n\n if (!enabled) {\n if (!isPostgres) {\n // Non-PostgreSQL — RLS not available, ORM filter is the sole enforcement\n log.info(\n '[compliance] Non-PostgreSQL database detected. RLS is not available; ORM filter is the sole tenant enforcement layer.'\n );\n return;\n }\n // PostgreSQL but explicitly disabled\n log.info(\n '[compliance] PostgreSQL RLS disabled by configuration. ORM filter is the sole tenant enforcement layer.'\n );\n return;\n }\n\n if (!isPostgres) {\n log.warn(\n '[compliance] RLS enabled but database driver is not PostgreSQL. ' +\n 'RLS is only supported on PostgreSQL. Falling back to ORM filter only.'\n );\n return;\n }\n\n // Register the RLS transaction subscriber\n orm.em.getEventManager().registerSubscriber(new RlsEventSubscriber());\n log.info('[compliance] PostgreSQL RLS event subscriber registered');\n\n // Validate RLS policies exist\n validateRlsPolicies(orm).catch((err) => {\n log.warn('[compliance] Failed to validate RLS policies:', err);\n });\n}\n\n// ---------------------------------------------------------------------------\n// RLS policy validation\n// ---------------------------------------------------------------------------\n\n/**\n * Checks that tenant-scoped entities have RLS policies on their tables.\n * Logs warnings with the SQL needed to create missing policies.\n */\nasync function validateRlsPolicies(orm: MikroORM): Promise<void> {\n const metadata = orm.em.getMetadata().getAll();\n\n for (const meta of Object.values(metadata)) {\n const hasOrgId = meta.properties['organizationId'] != null;\n const hasOrg = meta.properties['organization'] != null;\n\n if (!hasOrgId && !hasOrg) continue;\n\n const tableName = meta.tableName;\n try {\n const connection = orm.em.getConnection();\n const result = await connection.execute<{ policyname: string }[]>(\n `SELECT policyname FROM pg_policies WHERE tablename = '${escapeSqlString(tableName)}'`,\n [],\n 'all'\n );\n\n const policies = Array.isArray(result) ? result : [];\n const hasTenantPolicy = policies.some((p: { policyname: string }) =>\n p.policyname.includes('tenant')\n );\n\n if (!hasTenantPolicy) {\n console.warn(\n `[compliance] No tenant RLS policy found on table '${tableName}'. ` +\n `Create one with:\\n` +\n ` ALTER TABLE \"${tableName}\" ENABLE ROW LEVEL SECURITY;\\n` +\n ` CREATE POLICY tenant_isolation ON \"${tableName}\"\\n` +\n ` USING (organization_id = current_setting('app.tenant_id'));`\n );\n }\n } catch {\n // Query failed — likely not connected yet or table doesn't exist\n // Skip validation for this table\n }\n }\n}\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\n/**\n * Detect whether the ORM is using a PostgreSQL driver.\n * Checks the platform constructor name which is 'PostgreSqlPlatform' for PG.\n */\nfunction isPostgresDriver(orm: MikroORM): boolean {\n try {\n const platform = orm.em.getPlatform();\n const name = platform.constructor.name.toLowerCase();\n return name.includes('postgre');\n } catch {\n return false;\n }\n}\n\n/**\n * Escape a string for safe inclusion in SQL. Prevents SQL injection in\n * the SET LOCAL statement.\n */\nfunction escapeSqlString(value: string): string {\n return value.replace(/'/g, \"''\");\n}\n"],"mappings":"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","names":["Type","p","Type","p","p","ENCRYPTED_LEVELS","p"]}
1
+ {"version":3,"sources":["../../src/persistence/complianceTypes.ts","../../src/persistence/compliancePropertyBuilder.ts","../../src/persistence/encryptedType.ts","../../src/persistence/defineComplianceEntity.ts","../../src/persistence/fieldEncryptor.ts","../../src/persistence/complianceEventSubscriber.ts","../../src/persistence/tenantFilter.ts","../../src/persistence/rls.ts"],"sourcesContent":["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 compliance(\n level: ComplianceLevel\n ): Pick<\n UniversalPropertyOptionsBuilder<\n Value,\n Options & { readonly '~c': true },\n IncludeKeys\n >,\n IncludeKeys & keyof UniversalPropertyOptionsBuilder<never, never, never>\n >;\n }\n}\n","import { safeParse, safeStringify } from '@forklaunch/common';\nimport { p, Type, type PropertyBuilders } from '@mikro-orm/core';\nimport { COMPLIANCE_KEY, type ComplianceLevel } from './complianceTypes';\nimport { EncryptedType, resolveTypeInstance } from './encryptedType';\n\n// ---------------------------------------------------------------------------\n// Runtime Proxy implementation\n// ---------------------------------------------------------------------------\n\n/**\n * Check whether a value is a MikroORM property builder (has ~options).\n */\nfunction isBuilder(value: unknown): value is object {\n return (\n value != null &&\n typeof value === 'object' &&\n '~options' in (value as Record<string, unknown>)\n );\n}\n\n/**\n * Wraps a MikroORM scalar PropertyBuilder in a Proxy that:\n * 1. Adds a `.compliance(level)` method\n * 2. Forwards all other method calls to the underlying builder\n * 3. Re-wraps returned builders so `.compliance()` persists through chains\n */\nfunction wrapUnclassified(builder: unknown): unknown {\n return new Proxy(builder as object, {\n get(target: Record<string | symbol, unknown>, prop) {\n if (prop === 'compliance') {\n return (level: ComplianceLevel) => wrapClassified(target, level);\n }\n if (prop === '~options') return Reflect.get(target, prop, target);\n if (prop === COMPLIANCE_KEY) return undefined;\n\n const value = Reflect.get(target, prop, target);\n if (typeof value === 'function') {\n return (...args: unknown[]) => {\n const result = (value as (...args: unknown[]) => unknown).apply(\n target,\n args\n );\n return isBuilder(result) ? wrapUnclassified(result) : result;\n };\n }\n return value;\n }\n });\n}\n\n/**\n * Compliance levels that require field-level encryption via EncryptedType.\n */\nconst ENCRYPTED_LEVELS: ReadonlySet<ComplianceLevel> = new Set([\n 'pii',\n 'phi',\n 'pci'\n]);\n\n/**\n * Resolve the element runtimeType and isArray flag from builder options.\n * Uses the actual MikroORM Type instance's runtimeType — no manual\n * type-name enumeration needed.\n */\nfunction resolveEncryptedTypeArgs(options: Record<string, unknown>): {\n elementRuntimeType: string;\n isArray: boolean;\n} {\n const type = options.type;\n const isArray = options.array === true;\n\n // For arrays, the type is the element type (constructor or instance).\n // For p.array() with no chained element type, type is an ArrayType instance.\n const resolved = resolveTypeInstance(type);\n if (resolved) {\n const rt = resolved.runtimeType;\n // ArrayType's runtimeType is 'string[]' — treat as array of strings\n if (rt.endsWith('[]')) {\n return { elementRuntimeType: rt.slice(0, -2) || 'string', isArray: true };\n }\n return { elementRuntimeType: rt, isArray };\n }\n\n // No type set (e.g., plain enum) — default to string\n return { elementRuntimeType: 'string', isArray };\n}\n\n/**\n * Wraps a builder that has been classified via `.compliance()`.\n * Stores the compliance level under `~compliance` for `defineComplianceEntity`.\n * For encrypted levels (pii/phi/pci), applies EncryptedType to the builder\n * so MikroORM handles encryption at the data conversion layer.\n * Chaining after `.compliance()` propagates the level through subsequent builders.\n */\nfunction wrapClassified(builder: object, level: ComplianceLevel): unknown {\n // Apply EncryptedType for encrypted compliance levels\n if (ENCRYPTED_LEVELS.has(level)) {\n const options = (builder as Record<string | symbol, unknown>)[\n '~options'\n ] as Record<string, unknown> | undefined;\n if (options) {\n const { elementRuntimeType, isArray } = resolveEncryptedTypeArgs(options);\n\n // For enum fields, extract allowed values for app-level validation\n // then remove enum metadata so MikroORM won't generate a DB check\n // constraint (the encrypted ciphertext would never satisfy it).\n let enumValues: unknown[] | undefined;\n if (options.items) {\n enumValues = extractEnumValues(options.items);\n // Remove all enum metadata so MikroORM won't generate a DB\n // check constraint (encrypted ciphertext never matches plaintext).\n delete options.items;\n delete options.nativeEnumName;\n delete options.enum;\n }\n\n options.type = new EncryptedType(elementRuntimeType, isArray, enumValues);\n // Force column type to text since encrypted output is always a string\n options.columnType = 'text';\n options.runtimeType = isArray ? 'object' : elementRuntimeType;\n }\n }\n\n return new Proxy(builder, {\n get(target: Record<string | symbol, unknown>, prop) {\n if (prop === COMPLIANCE_KEY) return level;\n if (prop === '~options') return Reflect.get(target, prop, target);\n\n const value = Reflect.get(target, prop, target);\n if (typeof value === 'function') {\n return (...args: unknown[]) => {\n const result = (value as (...args: unknown[]) => unknown).apply(\n target,\n args\n );\n return isBuilder(result) ? wrapClassified(result, level) : result;\n };\n }\n return value;\n }\n });\n}\n\n/**\n * Wraps a relation PropertyBuilder (manyToOne, oneToMany, etc.).\n * Auto-classified as 'none' — no `.compliance()` call needed.\n * All chained methods preserve the auto-classification.\n */\nfunction wrapRelation(builder: object): unknown {\n return wrapClassified(builder, 'none');\n}\n\n// ---------------------------------------------------------------------------\n// Relation method detection\n// ---------------------------------------------------------------------------\n\nconst RELATION_METHODS = new Set([\n 'manyToOne',\n 'oneToMany',\n 'manyToMany',\n 'oneToOne',\n 'embedded'\n]);\n\nfunction isRelationMethod(prop: string | symbol): boolean {\n return typeof prop === 'string' && RELATION_METHODS.has(prop);\n}\n\n// ---------------------------------------------------------------------------\n// Enum type inference\n// ---------------------------------------------------------------------------\n\n/**\n * Extract the raw values from enum items (array, native TS enum object,\n * or lazy factory function `() => EnumObj`).\n */\nfunction extractEnumValues(items: unknown): unknown[] {\n // Resolve lazy enum factory: p.enum(() => MyEnum) stores the function\n if (typeof items === 'function') {\n return extractEnumValues((items as () => unknown)());\n }\n if (Array.isArray(items)) return items;\n if (items != null && typeof items === 'object') {\n // Native TS numeric enums have reverse mappings (value → key).\n // Filter those out by keeping only values whose key is not itself a value.\n const obj = items as Record<string, unknown>;\n const allValues = Object.values(obj);\n return allValues.filter((v) => typeof v !== 'string' || !(v in obj));\n }\n return [];\n}\n\n/**\n * MikroORM custom Type that stores mixed-type enum values as text\n * using safeStringify/safeParse for round-trip fidelity.\n */\nclass EnumTextType extends Type<unknown, string> {\n override convertToDatabaseValue(value: unknown): string {\n return safeStringify(value);\n }\n\n override convertToJSValue(value: unknown): unknown {\n return safeParse(value);\n }\n\n override getColumnType(): string {\n return 'text';\n }\n\n get runtimeType(): string {\n return 'string';\n }\n}\n\n/**\n * Infer the MikroORM type for an enum property from its items.\n *\n * - All items are strings → 'text'\n * - All items are numbers → 'integer'\n * - Mixed types → EnumTextType (safeStringify/safeParse round-trip)\n * - No items → 'text'\n */\nfunction inferEnumType(items: unknown): string | EnumTextType {\n const values = extractEnumValues(items);\n if (values.length === 0) return 'text';\n\n const allStrings = values.every((v) => typeof v === 'string');\n if (allStrings) return 'text';\n\n const allNumbers = values.every((v) => typeof v === 'number');\n if (allNumbers) return 'integer';\n\n return new EnumTextType();\n}\n\n// ---------------------------------------------------------------------------\n// fp — the ForkLaunch property builder\n// ---------------------------------------------------------------------------\n\n/**\n * ForkLaunch property builder. Drop-in replacement for MikroORM's `p`\n * that adds `.compliance(level)` to every scalar property builder\n * and auto-classifies relation builders as 'none'.\n *\n * - Scalar fields: `fp.string().compliance('pii')` — must call `.compliance()`\n * - Relation fields: `fp.manyToOne(Target)` — auto-classified, no `.compliance()` needed\n *\n * @example\n * ```typescript\n * import { defineComplianceEntity, fp } from '@forklaunch/core/persistence';\n *\n * const User = defineComplianceEntity({\n * name: 'User',\n * properties: {\n * id: fp.uuid().primary().compliance('none'),\n * email: fp.string().unique().compliance('pii'),\n * medicalRecord: fp.string().nullable().compliance('phi'),\n * organization: () => fp.manyToOne(Organization).nullable(),\n * }\n * });\n * ```\n */\nexport const fp: PropertyBuilders = new Proxy(p, {\n get(target: Record<string | symbol, unknown>, prop) {\n const value = Reflect.get(target, prop, target);\n if (typeof value !== 'function') return value;\n\n if (isRelationMethod(prop)) {\n // Relation methods: call the original, wrap result as auto-classified 'none'\n return (...args: unknown[]) => {\n const result = (value as (...args: unknown[]) => unknown).apply(\n target,\n args\n );\n return isBuilder(result) ? wrapRelation(result) : result;\n };\n }\n\n // Scalar methods: call the original, wrap result with .compliance()\n return (...args: unknown[]) => {\n const result = (value as (...args: unknown[]) => unknown).apply(\n target,\n args\n );\n if (isBuilder(result)) {\n // For enum(), infer a default type so MikroORM metadata\n // discovery doesn't require an explicit .type() call.\n if (prop === 'enum') {\n const options = (result as Record<string | symbol, unknown>)[\n '~options'\n ] as Record<string, unknown> | undefined;\n if (options && !options.type) {\n options.type = inferEnumType(options.items);\n }\n }\n return wrapUnclassified(result);\n }\n return result;\n };\n }\n}) as PropertyBuilders;\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:'] 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 * Run a callback with the given tenant ID available to EncryptedType\n * for per-tenant key derivation.\n */\nexport function withEncryptionContext<T>(tenantId: string, fn: () => T): T {\n return _tenantContext.run({ tenantId }, fn);\n}\n\n/**\n * Set the encryption tenant ID for the current async context.\n * Called automatically by the tenant context middleware — users\n * don't need to call this directly.\n */\nexport function setEncryptionTenantId(tenantId: string): void {\n _tenantContext.enterWith({ tenantId });\n}\n\n/**\n * Get the current tenant ID. Returns empty string when no context is set\n * (startup, seeders, better-auth, background jobs).\n */\nexport function getCurrentTenantId(): string {\n return _tenantContext.getStore()?.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 return _encryptor.encrypt(serialized, getCurrentTenantId()) ?? serialized;\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 = getCurrentTenantId();\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","import {\n defineEntity,\n p,\n type EntityMetadataWithProperties,\n type EntitySchemaWithMeta,\n type InferEntityFromProperties\n} from '@mikro-orm/core';\nimport {\n COMPLIANCE_KEY,\n parseDuration,\n registerEntityCompliance,\n registerEntityRetention,\n registerEntityUserIdField,\n type ComplianceLevel,\n type RetentionPolicy\n} from './complianceTypes';\n\ntype ValidateProperties<T> = {\n [K in keyof T]: T[K] extends\n | { '~options': { readonly '~c': true } }\n | ((...args: never[]) => unknown)\n ? T[K]\n : { '~options': { readonly '~c': true } };\n};\n\nfunction readComplianceLevel(builder: unknown): ComplianceLevel | undefined {\n if (builder == null || typeof builder !== 'object') return undefined;\n return (builder as Record<string, unknown>)[COMPLIANCE_KEY] as\n | ComplianceLevel\n | undefined;\n}\n\nexport function defineComplianceEntity<\n const TName extends string,\n const TTableName extends string,\n const TProperties extends Record<string, unknown>,\n const TPK extends (keyof TProperties)[] | undefined = undefined,\n const TBase = never,\n const TRepository = never,\n const TForceObject extends boolean = false\n>(\n meta: EntityMetadataWithProperties<\n TName,\n TTableName,\n TProperties & ValidateProperties<TProperties>,\n TPK,\n TBase,\n TRepository,\n TForceObject\n > & { retention?: RetentionPolicy; userIdField?: string }\n): EntitySchemaWithMeta<\n TName,\n TTableName,\n InferEntityFromProperties<TProperties, TPK, TBase, TRepository, TForceObject>,\n TBase,\n TProperties\n> {\n const entityName = 'name' in meta ? (meta.name as string) : 'Unknown';\n const complianceFields = new Map<string, ComplianceLevel>();\n\n const rawProperties = meta.properties;\n const resolvedProperties: Record<string, unknown> =\n typeof rawProperties === 'function' ? rawProperties(p) : rawProperties;\n\n for (const [fieldName, rawProp] of Object.entries(resolvedProperties)) {\n if (typeof rawProp === 'function') {\n // Relations are arrow-wrapped and auto-classified as 'none' — don't\n // call the arrow, since the referenced entity may not be initialized\n // yet (circular reference). MikroORM resolves these lazily.\n complianceFields.set(fieldName, 'none');\n continue;\n }\n const level = readComplianceLevel(rawProp);\n\n if (level == null) {\n throw new Error(\n `Field '${entityName}.${fieldName}' is missing compliance classification. ` +\n `Call .compliance('pii' | 'phi' | 'pci' | 'none') on this property, ` +\n `or use a relation method (fp.manyToOne, etc.) which is auto-classified.`\n );\n }\n complianceFields.set(fieldName, level);\n }\n\n registerEntityCompliance(entityName, complianceFields);\n\n // Handle retention policy\n if (meta.retention) {\n parseDuration(meta.retention.duration); // validates at boot — throws if invalid\n\n if (!resolvedProperties['createdAt']) {\n throw new Error(\n `Entity '${entityName}' has a retention policy but no 'createdAt' property. ` +\n `Retention requires createdAt to compute expiration.`\n );\n }\n\n registerEntityRetention(entityName, meta.retention);\n }\n\n // Register userIdField (defaults to 'userId' if not specified)\n if (meta.userIdField) {\n registerEntityUserIdField(entityName, meta.userIdField);\n }\n\n return defineEntity(\n meta as EntityMetadataWithProperties<\n TName,\n TTableName,\n TProperties & ValidateProperties<TProperties>,\n TPK,\n TBase,\n TRepository,\n TForceObject\n >\n ) as EntitySchemaWithMeta<\n TName,\n TTableName,\n InferEntityFromProperties<\n TProperties,\n TPK,\n TBase,\n TRepository,\n TForceObject\n >,\n TBase,\n TProperties\n >;\n}\n","import crypto from 'crypto';\n\n// ---------------------------------------------------------------------------\n// Error types\n// ---------------------------------------------------------------------------\n\nexport class MissingEncryptionKeyError extends Error {\n readonly name = 'MissingEncryptionKeyError' as const;\n constructor(message = 'Master encryption key must be provided') {\n super(message);\n }\n}\n\nexport class DecryptionError extends Error {\n readonly name = 'DecryptionError' as const;\n constructor(\n message = 'Decryption failed: ciphertext is corrupted or the wrong key was used'\n ) {\n super(message);\n }\n}\n\nexport class EncryptionRequiredError extends Error {\n readonly name = 'EncryptionRequiredError' as const;\n constructor(\n message = 'Encryption is required before persisting this compliance field'\n ) {\n super(message);\n }\n}\n\n// ---------------------------------------------------------------------------\n// Constants\n// ---------------------------------------------------------------------------\n\nconst ALGORITHM = 'aes-256-gcm' as const;\nconst IV_BYTES = 12;\nconst KEY_BYTES = 32;\nconst HKDF_HASH = 'sha256' as const;\nconst HKDF_SALT = Buffer.alloc(0); // empty salt – key material is already high-entropy\n\n// ---------------------------------------------------------------------------\n// FieldEncryptor\n// ---------------------------------------------------------------------------\n\nexport class FieldEncryptor {\n private readonly masterKey: string;\n\n constructor(masterKey: string) {\n if (!masterKey) {\n throw new MissingEncryptionKeyError();\n }\n this.masterKey = masterKey;\n }\n\n /**\n * Derive a per-tenant 32-byte key using HKDF-SHA256.\n * The master key is used as input key material and the tenantId as info context.\n */\n deriveKey(tenantId: string): Buffer {\n return Buffer.from(\n crypto.hkdfSync(HKDF_HASH, this.masterKey, HKDF_SALT, tenantId, KEY_BYTES)\n );\n }\n\n /**\n * Derive a deterministic IV from the key and plaintext using HMAC-SHA256,\n * truncated to IV_BYTES. Same plaintext + same key → same IV → same\n * ciphertext. This enables WHERE clause matching on encrypted columns\n * while maintaining AES-256-GCM authenticated encryption.\n *\n * Meets SOC 2, HIPAA, PCI DSS, GDPR requirements for encryption at rest.\n */\n private deriveDeterministicIv(key: Buffer, plaintext: string): Buffer {\n return crypto\n .createHmac('sha256', key)\n .update(plaintext)\n .digest()\n .subarray(0, IV_BYTES);\n }\n\n /**\n * Encrypt a plaintext string.\n *\n * Uses deterministic encryption (HMAC-derived IV) so the same plaintext\n * always produces the same ciphertext. This enables database WHERE clause\n * matching and UNIQUE constraints on encrypted columns.\n *\n * @returns Format: `v2:{base64(iv)}:{base64(authTag)}:{base64(ciphertext)}`\n */\n encrypt(plaintext: string | null): string | null;\n encrypt(plaintext: string | null, tenantId: string): string | null;\n encrypt(plaintext: string | null, tenantId?: string): string | null {\n if (plaintext === null || plaintext === undefined) {\n return null;\n }\n\n const key = this.deriveKey(tenantId ?? '');\n const iv = this.deriveDeterministicIv(key, plaintext);\n\n const cipher = crypto.createCipheriv(ALGORITHM, key, iv);\n const encrypted = Buffer.concat([\n cipher.update(plaintext, 'utf8'),\n cipher.final()\n ]);\n const authTag = cipher.getAuthTag();\n\n return [\n 'v2',\n iv.toString('base64'),\n authTag.toString('base64'),\n encrypted.toString('base64')\n ].join(':');\n }\n\n /**\n * Decrypt a ciphertext string produced by {@link encrypt}.\n * Supports both v1 (random IV) and v2 (deterministic IV) formats.\n */\n decrypt(ciphertext: string | null): string | null;\n decrypt(ciphertext: string | null, tenantId: string): string | null;\n decrypt(ciphertext: string | null, tenantId?: string): string | null {\n if (ciphertext === null || ciphertext === undefined) {\n return null;\n }\n\n const parts = ciphertext.split(':');\n if (parts.length !== 4 || (parts[0] !== 'v1' && parts[0] !== 'v2')) {\n throw new DecryptionError(\n `Unknown ciphertext version or malformed format`\n );\n }\n\n const [, ivB64, authTagB64, encryptedB64] = parts;\n const iv = Buffer.from(ivB64, 'base64');\n const authTag = Buffer.from(authTagB64, 'base64');\n const encrypted = Buffer.from(encryptedB64, 'base64');\n const key = this.deriveKey(tenantId ?? '');\n\n try {\n const decipher = crypto.createDecipheriv(ALGORITHM, key, iv);\n decipher.setAuthTag(authTag);\n const decrypted = Buffer.concat([\n decipher.update(encrypted),\n decipher.final()\n ]);\n return decrypted.toString('utf8');\n } catch {\n throw new DecryptionError();\n }\n }\n}\n","import { EncryptionRequiredError } from './fieldEncryptor';\nimport {\n type ComplianceLevel,\n getEntityComplianceFields\n} from './complianceTypes';\n\n/**\n * Compliance levels that require field-level encryption.\n */\nconst ENCRYPTED_LEVELS: ReadonlySet<ComplianceLevel> = new Set([\n 'pii',\n 'phi',\n 'pci'\n]);\n\n// ---------------------------------------------------------------------------\n// Native query blocking\n// ---------------------------------------------------------------------------\n\n/**\n * Wraps an EntityManager to block `nativeInsert`, `nativeUpdate`, and\n * `nativeDelete` on entities that have PII, PHI, or PCI compliance fields.\n *\n * This prevents bypassing the EncryptedType's encryption by using raw\n * queries. Call this in the tenant context middleware when creating the\n * request-scoped EM.\n *\n * @returns A Proxy-wrapped EntityManager that throws on native query\n * operations targeting compliance entities.\n */\nexport function wrapEmWithNativeQueryBlocking<T extends object>(em: T): T {\n const BLOCKED_METHODS = [\n 'nativeInsert',\n 'nativeUpdate',\n 'nativeDelete'\n ] as const;\n\n return new Proxy(em, {\n get(target, prop, receiver) {\n if (\n typeof prop === 'string' &&\n BLOCKED_METHODS.includes(prop as (typeof BLOCKED_METHODS)[number])\n ) {\n return (entityNameOrEntity: unknown, ...rest: unknown[]) => {\n const entityName = resolveEntityName(entityNameOrEntity);\n if (entityName) {\n const fields = getEntityComplianceFields(entityName);\n if (fields) {\n for (const [fieldName, level] of fields) {\n if (ENCRYPTED_LEVELS.has(level)) {\n throw new EncryptionRequiredError(\n `${prop}() blocked on entity '${entityName}' because field ` +\n `'${fieldName}' has compliance level '${level}'. ` +\n `Use em.create() + em.flush() instead to ensure encryption.`\n );\n }\n }\n }\n }\n const method = Reflect.get(target, prop, receiver);\n return (method as (...args: unknown[]) => unknown).call(\n target,\n entityNameOrEntity,\n ...rest\n );\n };\n }\n return Reflect.get(target, prop, receiver);\n }\n });\n}\n\n/**\n * Resolve an entity name from the first argument to nativeInsert/Update/Delete.\n */\nfunction resolveEntityName(entityNameOrEntity: unknown): string | undefined {\n if (typeof entityNameOrEntity === 'string') {\n return entityNameOrEntity;\n }\n if (typeof entityNameOrEntity === 'function') {\n return (entityNameOrEntity as { name?: string }).name;\n }\n if (\n entityNameOrEntity != null &&\n typeof entityNameOrEntity === 'object' &&\n 'constructor' in entityNameOrEntity\n ) {\n return (entityNameOrEntity.constructor as { name?: string }).name;\n }\n return undefined;\n}\n","import type { Dictionary, EntityManager, FilterDef } from '@mikro-orm/core';\n\n/**\n * The name used to register the tenant isolation filter.\n */\nexport const TENANT_FILTER_NAME = 'tenant';\n\n/**\n * Creates the tenant filter definition.\n *\n * The filter adds `WHERE <column> = :tenantId` to all queries\n * on entities that have the configured tenant column (or its relation).\n * Entities without the property are unaffected (empty condition).\n *\n * @param column - The entity property name used for tenant isolation.\n * Defaults to `'organizationId'`. The relation name\n * (without `Id` suffix) is also checked.\n */\nexport function createTenantFilterDef(\n column: string = 'organizationId',\n relationName?: string\n): FilterDef {\n const relation = relationName ?? column.replace(/Id$/, '');\n return {\n name: TENANT_FILTER_NAME,\n cond(\n args: Dictionary,\n _type: 'read' | 'update' | 'delete',\n em: EntityManager,\n _options?: unknown,\n entityName?: string\n ) {\n if (!entityName) {\n return {};\n }\n\n // If no tenant context is set (startup, background jobs, better-auth),\n // skip filtering entirely. This is safe: tenant-scoped endpoints always\n // set filter params before querying.\n if (!args?.tenantId) {\n return {};\n }\n\n try {\n const metadata = em.getMetadata().getByClassName(entityName, false);\n if (!metadata) {\n return {};\n }\n\n const hasColumn = metadata.properties[column] != null;\n const hasRelation =\n relation !== column && metadata.properties[relation] != null;\n\n if (hasColumn) {\n return { [column]: args.tenantId };\n }\n if (hasRelation) {\n return { [relation]: args.tenantId };\n }\n } catch {\n // Entity not found in metadata — skip filtering\n }\n\n return {};\n },\n default: true,\n args: false\n };\n}\n\n/**\n * Registers the global tenant isolation filter on the ORM's entity manager.\n * Call this once at application bootstrap after `MikroORM.init()`.\n *\n * After calling this, every fork of the EM will inherit the filter.\n * Set the tenant ID per-request via:\n *\n * ```ts\n * em.setFilterParams('tenant', { tenantId: 'org-123' });\n * ```\n */\nexport function setupTenantFilter(\n orm: {\n em: Pick<EntityManager, 'addFilter'>;\n },\n config: {\n /** Entity property name used for tenant isolation. Defaults to `'organizationId'`. */\n column?: string;\n /** Relation property name if different from column without 'Id' suffix. E.g., if column is 'orgId' but relation is 'parentOrg'. */\n relation?: string;\n logger?: { info: (msg: string, ...args: unknown[]) => void };\n } = {}\n): void {\n orm.em.addFilter(createTenantFilterDef(config.column, config.relation));\n (config.logger ?? console).info(\n `[compliance] Tenant isolation filter registered on column '${config.column ?? 'organizationId'}'`\n );\n}\n\n/**\n * Returns a forked EntityManager with the tenant filter disabled.\n *\n * Use this only from code paths that have verified super-admin permissions.\n * Queries executed through the returned EM will return cross-tenant data.\n */\nexport function getSuperAdminContext(\n em: Pick<EntityManager, 'fork'>\n): ReturnType<EntityManager['fork']> {\n const forked = em.fork();\n forked.setFilterParams(TENANT_FILTER_NAME, { tenantId: undefined });\n // Disable the filter by passing false for the filter in each query isn't\n // sufficient globally; instead we add the filter with enabled = false.\n // The cleanest way is to re-add the filter as disabled on this fork.\n forked.addFilter({\n name: TENANT_FILTER_NAME,\n cond: {},\n default: false\n });\n return forked;\n}\n","import type { EntityManager, EventSubscriber, MikroORM } from '@mikro-orm/core';\nimport { TENANT_FILTER_NAME } from './tenantFilter';\n\n/**\n * Structural subset of {@link TransactionEventArgs} used by\n * `RlsEventSubscriber` so that callers (and tests) only need to provide\n * the properties the subscriber actually reads.\n */\nexport interface RlsTransactionEventArgs {\n em: {\n getFilterParams: EntityManager['getFilterParams'];\n getConnection(): {\n execute(\n query: string,\n params?: unknown[],\n method?: 'all' | 'get' | 'run',\n ctx?: unknown\n ): Promise<unknown>;\n };\n };\n transaction?: unknown;\n}\n\n// ---------------------------------------------------------------------------\n// Configuration\n// ---------------------------------------------------------------------------\n\nexport interface RlsConfig {\n /**\n * Whether to enable PostgreSQL Row-Level Security.\n * Defaults to `true` when the driver is PostgreSQL, `false` otherwise.\n * Set to `false` to opt out even on PostgreSQL.\n */\n enabled?: boolean;\n /** Optional logger for compliance messages. Falls back to console. */\n logger?: {\n info: (msg: string, ...args: unknown[]) => void;\n warn: (msg: string, ...args: unknown[]) => void;\n };\n}\n\n// ---------------------------------------------------------------------------\n// RLS EventSubscriber\n// ---------------------------------------------------------------------------\n\n/**\n * MikroORM EventSubscriber that executes `SET LOCAL app.tenant_id = :tenantId`\n * at the start of every transaction when PostgreSQL RLS is enabled.\n *\n * This ensures that even if the MikroORM global filter is somehow bypassed,\n * the database-level RLS policy enforces tenant isolation.\n *\n * The tenant ID is read from the EntityManager's filter parameters\n * (set by the tenant context middleware).\n */\nexport class RlsEventSubscriber implements EventSubscriber {\n async afterTransactionStart(args: RlsTransactionEventArgs): Promise<void> {\n const tenantId = this.getTenantId(args.em);\n if (!tenantId) {\n // No tenant context (e.g., super-admin or public route) — skip SET LOCAL\n return;\n }\n\n const connection = args.em.getConnection();\n // Execute SET LOCAL within the transaction context\n // SET LOCAL only persists for the current transaction — no connection leakage\n await connection.execute(\n `SET LOCAL app.tenant_id = '${escapeSqlString(tenantId)}'`,\n [],\n 'run',\n args.transaction\n );\n }\n\n private getTenantId(\n em: Pick<EntityManager, 'getFilterParams'>\n ): string | undefined {\n const params = em.getFilterParams(TENANT_FILTER_NAME) as\n | { tenantId?: string }\n | undefined;\n return params?.tenantId;\n }\n}\n\n// ---------------------------------------------------------------------------\n// Setup\n// ---------------------------------------------------------------------------\n\n/**\n * Sets up PostgreSQL Row-Level Security integration.\n *\n * 1. Registers the `RlsEventSubscriber` to run `SET LOCAL app.tenant_id`\n * at the start of every transaction.\n * 2. Validates that RLS policies exist on tenant-scoped tables (warns if missing).\n *\n * Call this at application bootstrap after `MikroORM.init()` and `setupTenantFilter()`.\n *\n * @param orm - The initialized MikroORM instance\n * @param config - RLS configuration (enabled defaults to auto-detect PostgreSQL)\n */\nexport function setupRls(orm: MikroORM, config: RlsConfig = {}): void {\n const isPostgres = isPostgresDriver(orm);\n const enabled = config.enabled ?? isPostgres;\n const log = config.logger ?? { info: console.info, warn: console.warn };\n\n if (!enabled) {\n if (!isPostgres) {\n // Non-PostgreSQL — RLS not available, ORM filter is the sole enforcement\n log.info(\n '[compliance] Non-PostgreSQL database detected. RLS is not available; ORM filter is the sole tenant enforcement layer.'\n );\n return;\n }\n // PostgreSQL but explicitly disabled\n log.info(\n '[compliance] PostgreSQL RLS disabled by configuration. ORM filter is the sole tenant enforcement layer.'\n );\n return;\n }\n\n if (!isPostgres) {\n log.warn(\n '[compliance] RLS enabled but database driver is not PostgreSQL. ' +\n 'RLS is only supported on PostgreSQL. Falling back to ORM filter only.'\n );\n return;\n }\n\n // Register the RLS transaction subscriber\n orm.em.getEventManager().registerSubscriber(new RlsEventSubscriber());\n log.info('[compliance] PostgreSQL RLS event subscriber registered');\n\n // Validate RLS policies exist\n validateRlsPolicies(orm).catch((err) => {\n log.warn('[compliance] Failed to validate RLS policies:', err);\n });\n}\n\n// ---------------------------------------------------------------------------\n// RLS policy validation\n// ---------------------------------------------------------------------------\n\n/**\n * Checks that tenant-scoped entities have RLS policies on their tables.\n * Logs warnings with the SQL needed to create missing policies.\n */\nasync function validateRlsPolicies(orm: MikroORM): Promise<void> {\n const metadata = orm.em.getMetadata().getAll();\n\n for (const meta of Object.values(metadata)) {\n const hasOrgId = meta.properties['organizationId'] != null;\n const hasOrg = meta.properties['organization'] != null;\n\n if (!hasOrgId && !hasOrg) continue;\n\n const tableName = meta.tableName;\n try {\n const connection = orm.em.getConnection();\n const result = await connection.execute<{ policyname: string }[]>(\n `SELECT policyname FROM pg_policies WHERE tablename = '${escapeSqlString(tableName)}'`,\n [],\n 'all'\n );\n\n const policies = Array.isArray(result) ? result : [];\n const hasTenantPolicy = policies.some((p: { policyname: string }) =>\n p.policyname.includes('tenant')\n );\n\n if (!hasTenantPolicy) {\n console.warn(\n `[compliance] No tenant RLS policy found on table '${tableName}'. ` +\n `Create one with:\\n` +\n ` ALTER TABLE \"${tableName}\" ENABLE ROW LEVEL SECURITY;\\n` +\n ` CREATE POLICY tenant_isolation ON \"${tableName}\"\\n` +\n ` USING (organization_id = current_setting('app.tenant_id'));`\n );\n }\n } catch {\n // Query failed — likely not connected yet or table doesn't exist\n // Skip validation for this table\n }\n }\n}\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\n/**\n * Detect whether the ORM is using a PostgreSQL driver.\n * Checks the platform constructor name which is 'PostgreSqlPlatform' for PG.\n */\nfunction isPostgresDriver(orm: MikroORM): boolean {\n try {\n const platform = orm.em.getPlatform();\n const name = platform.constructor.name.toLowerCase();\n return name.includes('postgre');\n } catch {\n return false;\n }\n}\n\n/**\n * Escape a string for safe inclusion in SQL. Prevents SQL injection in\n * the SET LOCAL statement.\n */\nfunction escapeSqlString(value: string): string {\n return value.replace(/'/g, \"''\");\n}\n"],"mappings":"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","names":["Type","p","Type","p","p","ENCRYPTED_LEVELS","p"]}
@@ -197,6 +197,8 @@ var ConfigInjector = class _ConfigInjector {
197
197
  {}
198
198
  );
199
199
  }
200
+ schemaValidator;
201
+ dependenciesDefinition;
200
202
  instances = {};
201
203
  configShapes;
202
204
  load(inheritedScopeInstances) {
@@ -528,6 +530,8 @@ var RetentionService = class {
528
530
  this.orm = orm;
529
531
  this.otel = otel;
530
532
  }
533
+ orm;
534
+ otel;
531
535
  DEFAULT_BATCH_SIZE = 1e3;
532
536
  async enforce(options) {
533
537
  const start = Date.now();
@@ -705,6 +709,8 @@ var ComplianceDataService = class {
705
709
  this.otel = otel;
706
710
  this.userIdFieldOverrides = userIdFieldOverrides ?? {};
707
711
  }
712
+ orm;
713
+ otel;
708
714
  userIdFieldOverrides;
709
715
  /**
710
716
  * Resolve the field linking an entity to a user.