@forklaunch/core 1.3.2 → 1.3.4

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.
@@ -228,7 +228,7 @@ declare const TENANT_FILTER_NAME = "tenant";
228
228
  * Defaults to `'organizationId'`. The relation name
229
229
  * (without `Id` suffix) is also checked.
230
230
  */
231
- declare function createTenantFilterDef(column?: string): FilterDef;
231
+ declare function createTenantFilterDef(column?: string, relationName?: string): FilterDef;
232
232
  /**
233
233
  * Registers the global tenant isolation filter on the ORM's entity manager.
234
234
  * Call this once at application bootstrap after `MikroORM.init()`.
@@ -245,6 +245,8 @@ declare function setupTenantFilter(orm: {
245
245
  }, config?: {
246
246
  /** Entity property name used for tenant isolation. Defaults to `'organizationId'`. */
247
247
  column?: string;
248
+ /** Relation property name if different from column without 'Id' suffix. E.g., if column is 'orgId' but relation is 'parentOrg'. */
249
+ relation?: string;
248
250
  logger?: {
249
251
  info: (msg: string, ...args: unknown[]) => void;
250
252
  };
@@ -228,7 +228,7 @@ declare const TENANT_FILTER_NAME = "tenant";
228
228
  * Defaults to `'organizationId'`. The relation name
229
229
  * (without `Id` suffix) is also checked.
230
230
  */
231
- declare function createTenantFilterDef(column?: string): FilterDef;
231
+ declare function createTenantFilterDef(column?: string, relationName?: string): FilterDef;
232
232
  /**
233
233
  * Registers the global tenant isolation filter on the ORM's entity manager.
234
234
  * Call this once at application bootstrap after `MikroORM.init()`.
@@ -245,6 +245,8 @@ declare function setupTenantFilter(orm: {
245
245
  }, config?: {
246
246
  /** Entity property name used for tenant isolation. Defaults to `'organizationId'`. */
247
247
  column?: string;
248
+ /** Relation property name if different from column without 'Id' suffix. E.g., if column is 'orgId' but relation is 'parentOrg'. */
249
+ relation?: string;
248
250
  logger?: {
249
251
  info: (msg: string, ...args: unknown[]) => void;
250
252
  };
@@ -214,7 +214,7 @@ var EncryptedType = class extends import_core.Type {
214
214
  return this.originalType === "json" ? "object" : this.originalType;
215
215
  }
216
216
  ensureComparable() {
217
- return true;
217
+ return false;
218
218
  }
219
219
  // ---------------------------------------------------------------------------
220
220
  // Serialization helpers
@@ -268,7 +268,11 @@ function wrapUnclassified(builder) {
268
268
  }
269
269
  });
270
270
  }
271
- var ENCRYPTED_LEVELS = /* @__PURE__ */ new Set(["pii", "phi", "pci"]);
271
+ var ENCRYPTED_LEVELS = /* @__PURE__ */ new Set([
272
+ "pii",
273
+ "phi",
274
+ "pci"
275
+ ]);
272
276
  function detectOriginalType(options) {
273
277
  const type = options.type;
274
278
  if (!type) return "string";
@@ -282,7 +286,8 @@ function detectOriginalType(options) {
282
286
  return "string";
283
287
  }
284
288
  if (t.includes("json") || t === "object") return "json";
285
- if (t.includes("int") || t === "number" || t === "double" || t === "float" || t === "decimal" || t === "smallint" || t === "tinyint") return "number";
289
+ if (t.includes("int") || t === "number" || t === "double" || t === "float" || t === "decimal" || t === "smallint" || t === "tinyint")
290
+ return "number";
286
291
  if (t === "boolean" || t === "bool") return "boolean";
287
292
  return "string";
288
293
  }
@@ -494,7 +499,11 @@ var FieldEncryptor = class {
494
499
  };
495
500
 
496
501
  // src/persistence/complianceEventSubscriber.ts
497
- var ENCRYPTED_LEVELS2 = /* @__PURE__ */ new Set(["pii", "phi", "pci"]);
502
+ var ENCRYPTED_LEVELS2 = /* @__PURE__ */ new Set([
503
+ "pii",
504
+ "phi",
505
+ "pci"
506
+ ]);
498
507
  function wrapEmWithNativeQueryBlocking(em) {
499
508
  const BLOCKED_METHODS = [
500
509
  "nativeInsert",
@@ -545,8 +554,8 @@ function resolveEntityName(entityNameOrEntity) {
545
554
 
546
555
  // src/persistence/tenantFilter.ts
547
556
  var TENANT_FILTER_NAME = "tenant";
548
- function createTenantFilterDef(column = "organizationId") {
549
- const relation = column.replace(/Id$/, "");
557
+ function createTenantFilterDef(column = "organizationId", relationName) {
558
+ const relation = relationName ?? column.replace(/Id$/, "");
550
559
  return {
551
560
  name: TENANT_FILTER_NAME,
552
561
  cond(args, _type, em, _options, entityName) {
@@ -563,9 +572,12 @@ function createTenantFilterDef(column = "organizationId") {
563
572
  }
564
573
  const hasColumn = metadata.properties[column] != null;
565
574
  const hasRelation = relation !== column && metadata.properties[relation] != null;
566
- if (hasColumn || hasRelation) {
575
+ if (hasColumn) {
567
576
  return { [column]: args.tenantId };
568
577
  }
578
+ if (hasRelation) {
579
+ return { [relation]: args.tenantId };
580
+ }
569
581
  } catch {
570
582
  }
571
583
  return {};
@@ -575,7 +587,7 @@ function createTenantFilterDef(column = "organizationId") {
575
587
  };
576
588
  }
577
589
  function setupTenantFilter(orm, config = {}) {
578
- orm.em.addFilter(createTenantFilterDef(config.column));
590
+ orm.em.addFilter(createTenantFilterDef(config.column, config.relation));
579
591
  (config.logger ?? console).info(
580
592
  `[compliance] Tenant isolation filter registered on column '${config.column ?? "organizationId"}'`
581
593
  );
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/persistence/index.ts","../../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":["// Compliance types and registry\nexport {\n ComplianceLevel,\n type ComplianceLevel as ComplianceLevelType,\n getComplianceMetadata,\n getEntityComplianceFields,\n entityHasEncryptedFields,\n // Retention\n RetentionAction,\n type RetentionAction as RetentionActionType,\n type RetentionPolicy,\n RetentionDuration,\n parseDuration,\n subtractDuration,\n type ParsedDuration,\n getEntityRetention,\n getAllRetentionPolicies,\n getEntityUserIdField,\n getAllUserIdFields\n} from './complianceTypes';\n\n// Compliance-aware property builder (drop-in replacement for MikroORM's p)\nexport { fp } from './compliancePropertyBuilder';\n\n// Compliance-aware entity definition (drop-in replacement for MikroORM's defineEntity)\nexport { defineComplianceEntity } from './defineComplianceEntity';\n\n// Native query blocking (prevents bypassing EncryptedType via raw queries)\nexport {\n wrapEmWithNativeQueryBlocking\n} from './complianceEventSubscriber';\n\n// Encrypted custom type (transparent field encryption at the MikroORM data layer)\nexport {\n EncryptedType,\n registerEncryptor,\n withEncryptionContext,\n setEncryptionTenantId,\n getCurrentTenantId\n} from './encryptedType';\n\n// Field encryption\nexport {\n FieldEncryptor,\n MissingEncryptionKeyError,\n DecryptionError,\n EncryptionRequiredError\n} from './fieldEncryptor';\n\n// Tenant isolation filter\nexport {\n setupTenantFilter,\n getSuperAdminContext,\n createTenantFilterDef,\n TENANT_FILTER_NAME\n} from './tenantFilter';\n\n// PostgreSQL Row-Level Security\nexport { setupRls, RlsEventSubscriber, type RlsConfig } from './rls';\n","export const ComplianceLevel = {\n pii: 'pii',\n phi: 'phi',\n pci: 'pci',\n none: 'none'\n} as const;\nexport type ComplianceLevel =\n (typeof ComplianceLevel)[keyof typeof ComplianceLevel];\n\nexport const COMPLIANCE_KEY = '~compliance' as const;\n\n// ---------------------------------------------------------------------------\n// Registry\n// ---------------------------------------------------------------------------\n\nconst complianceRegistry = new Map<string, Map<string, ComplianceLevel>>();\n\nexport function registerEntityCompliance(\n entityName: string,\n fields: Map<string, ComplianceLevel>\n): void {\n complianceRegistry.set(entityName, fields);\n}\n\nexport function getComplianceMetadata(\n entityName: string,\n fieldName: string\n): ComplianceLevel {\n return complianceRegistry.get(entityName)?.get(fieldName) ?? 'none';\n}\n\nexport function getEntityComplianceFields(\n entityName: string\n): Map<string, ComplianceLevel> | undefined {\n return complianceRegistry.get(entityName);\n}\n\nexport function entityHasEncryptedFields(entityName: string): boolean {\n const fields = complianceRegistry.get(entityName);\n if (!fields) return false;\n for (const level of fields.values()) {\n if (level === 'phi' || level === 'pci') return true;\n }\n return false;\n}\n\n// ---------------------------------------------------------------------------\n// Retention types and registry\n// ---------------------------------------------------------------------------\n\nexport const RetentionAction = {\n delete: 'delete',\n anonymize: 'anonymize'\n} as const;\nexport type RetentionAction =\n (typeof RetentionAction)[keyof typeof RetentionAction];\n\nexport interface RetentionPolicy {\n duration: string;\n action: RetentionAction;\n}\n\nexport const RetentionDuration = {\n days: (n: number): string => `P${n}D`,\n months: (n: number): string => `P${n}M`,\n years: (n: number): string => `P${n}Y`\n} as const;\n\nexport interface ParsedDuration {\n years: number;\n months: number;\n days: number;\n}\n\nconst DURATION_REGEX = /^P(?:(\\d+)Y)?(?:(\\d+)M)?(?:(\\d+)D)?$/;\n\n/**\n * Parse an ISO 8601 duration string into calendar units.\n * Returns structured units to enable calendar-aware date arithmetic.\n */\nexport function parseDuration(iso: string): ParsedDuration {\n const match = DURATION_REGEX.exec(iso);\n if (!match) {\n throw new Error(\n `Invalid ISO 8601 duration: '${iso}'. Expected format: P[n]Y[n]M[n]D`\n );\n }\n const years = parseInt(match[1] || '0', 10);\n const months = parseInt(match[2] || '0', 10);\n const days = parseInt(match[3] || '0', 10);\n\n // Approximate total days for minimum validation only\n const approxDays = years * 365 + months * 30 + days;\n if (approxDays < 1) {\n throw new Error(\n `Retention duration must be >= 1 day (P1D). Got: '${iso}' (${approxDays} approx days)`\n );\n }\n\n return { years, months, days };\n}\n\n/**\n * Subtract a parsed duration from a date using calendar-aware arithmetic.\n * Handles month-end clamping and leap years correctly.\n */\nexport function subtractDuration(from: Date, duration: ParsedDuration): Date {\n const result = new Date(from);\n result.setFullYear(result.getFullYear() - duration.years);\n result.setMonth(result.getMonth() - duration.months);\n result.setDate(result.getDate() - duration.days);\n return result;\n}\n\nconst retentionRegistry = new Map<string, RetentionPolicy>();\n\n// ---------------------------------------------------------------------------\n// User ID field registry — maps entity name to the field linking records to a user\n// ---------------------------------------------------------------------------\n\nconst DEFAULT_USER_ID_FIELD = 'userId';\nconst userIdFieldRegistry = new Map<string, string>();\n\nexport function registerEntityUserIdField(\n entityName: string,\n field: string\n): void {\n userIdFieldRegistry.set(entityName, field);\n}\n\nexport function getEntityUserIdField(entityName: string): string {\n return userIdFieldRegistry.get(entityName) ?? DEFAULT_USER_ID_FIELD;\n}\n\nexport function getAllUserIdFields(): ReadonlyMap<string, string> {\n return userIdFieldRegistry;\n}\n\nexport function registerEntityRetention(\n entityName: string,\n policy: RetentionPolicy\n): void {\n retentionRegistry.set(entityName, policy);\n}\n\nexport function getEntityRetention(\n entityName: string\n): RetentionPolicy | undefined {\n return retentionRegistry.get(entityName);\n}\n\nexport function getAllRetentionPolicies(): ReadonlyMap<\n string,\n RetentionPolicy\n> {\n return retentionRegistry;\n}\n\n// ---------------------------------------------------------------------------\n// Module augmentation — adds .compliance() via PropertyOptions\n// ---------------------------------------------------------------------------\n\n/**\n * Adds `compliance` to PropertyOptions, which flows into IncludeKeys\n * for all scalar/enum/embedded builders (PropertyOptions is extended by\n * EnumOptions and EmbeddedOptions). Relation builders use ReferenceOptions\n * instead, so they don't get .compliance() — which is what we want.\n */\ndeclare module '@mikro-orm/core' {\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n interface PropertyOptions<Owner> {\n compliance?: ComplianceLevel;\n }\n\n interface UniversalPropertyOptionsBuilder<Value, Options, IncludeKeys> {\n 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 { p, type PropertyBuilders } from '@mikro-orm/core';\nimport { COMPLIANCE_KEY, type ComplianceLevel } from './complianceTypes';\nimport { EncryptedType } 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(['pii', 'phi', 'pci']);\n\n/**\n * Detect the original type hint from the builder's ~options to create\n * the appropriate EncryptedType variant for deserialization.\n */\nfunction detectOriginalType(options: Record<string, unknown>): string {\n const type = options.type;\n if (!type) return 'string';\n\n // type can be a string name or a Type instance\n let t: string;\n if (typeof type === 'string') {\n t = type.toLowerCase();\n } else if (typeof type === 'object' && type !== null) {\n // Type instance — use constructor name or runtimeType\n const rt = (type as { runtimeType?: string }).runtimeType;\n t = rt ?? type.constructor?.name?.toLowerCase() ?? 'string';\n } else {\n return 'string';\n }\n\n if (t.includes('json') || t === 'object') return 'json';\n if (t.includes('int') || t === 'number' || t === 'double' || t === 'float' || t === 'decimal' || t === 'smallint' || t === 'tinyint') return 'number';\n if (t === 'boolean' || t === 'bool') return 'boolean';\n return 'string';\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>)['~options'] as Record<string, unknown> | undefined;\n if (options) {\n const originalType = detectOriginalType(options);\n options.type = new EncryptedType(originalType);\n // Force column type to text since encrypted output is always a string\n options.columnType = 'text';\n options.runtimeType = originalType === 'json' ? 'object' : originalType;\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// 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 return isBuilder(result) ? wrapUnclassified(result) : 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// EncryptedType\n// ---------------------------------------------------------------------------\n\n/**\n * MikroORM custom Type that transparently encrypts/decrypts values.\n *\n * Works with any JS type (string, number, boolean, object/JSON).\n * Non-string values are JSON-serialized before encryption and\n * JSON-parsed after decryption.\n *\n * DB column is always `text` — the encrypted ciphertext is a string\n * regardless of the original JS type.\n *\n * Operates at the data conversion layer — before the identity map,\n * during hydration, and during persistence. Entities always hold\n * their original JS values (plaintext).\n */\nexport class EncryptedType extends Type<unknown, string | null> {\n private readonly originalType: string;\n\n /**\n * @param originalType - The original JS type hint ('string' | 'json' | 'number' | 'boolean').\n * Used to determine serialization strategy.\n */\n constructor(originalType: string = 'string') {\n super();\n this.originalType = originalType;\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 if (!_encryptor) {\n return this.serializeValue(value);\n }\n\n if (typeof value === 'string' && isEncrypted(value)) {\n return value;\n }\n\n const serialized = this.serializeValue(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.deserializeValue(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.deserializeValue(decrypted);\n } catch {\n return value;\n }\n }\n\n override getColumnType(): string {\n return 'text';\n }\n\n override get runtimeType(): string {\n return this.originalType === 'json' ? 'object' : this.originalType;\n }\n\n override ensureComparable(): boolean {\n return true;\n }\n\n // ---------------------------------------------------------------------------\n // Serialization helpers\n // ---------------------------------------------------------------------------\n\n private serializeValue(value: unknown): string {\n if (typeof value === 'string') return value;\n return JSON.stringify(value);\n }\n\n private deserializeValue(value: string): unknown {\n switch (this.originalType) {\n case 'string':\n return value;\n case 'number':\n return Number(value);\n case 'boolean':\n return value === 'true';\n case 'json':\n default:\n try {\n return JSON.parse(value);\n } catch {\n return value;\n }\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 {\n EncryptionRequiredError\n} 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(['pii', 'phi', 'pci']);\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): FilterDef {\n const relation = 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 || hasRelation) {\n return { [column]: 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 logger?: { info: (msg: string, ...args: unknown[]) => void };\n } = {}\n): void {\n orm.em.addFilter(createTenantFilterDef(config.column));\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":["import_core","p","import_core","crypto","ENCRYPTED_LEVELS","p"]}
1
+ {"version":3,"sources":["../../src/persistence/index.ts","../../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":["// Compliance types and registry\nexport {\n ComplianceLevel,\n type ComplianceLevel as ComplianceLevelType,\n getComplianceMetadata,\n getEntityComplianceFields,\n entityHasEncryptedFields,\n // Retention\n RetentionAction,\n type RetentionAction as RetentionActionType,\n type RetentionPolicy,\n RetentionDuration,\n parseDuration,\n subtractDuration,\n type ParsedDuration,\n getEntityRetention,\n getAllRetentionPolicies,\n getEntityUserIdField,\n getAllUserIdFields\n} from './complianceTypes';\n\n// Compliance-aware property builder (drop-in replacement for MikroORM's p)\nexport { fp } from './compliancePropertyBuilder';\n\n// Compliance-aware entity definition (drop-in replacement for MikroORM's defineEntity)\nexport { defineComplianceEntity } from './defineComplianceEntity';\n\n// Native query blocking (prevents bypassing EncryptedType via raw queries)\nexport { wrapEmWithNativeQueryBlocking } from './complianceEventSubscriber';\n\n// Encrypted custom type (transparent field encryption at the MikroORM data layer)\nexport {\n EncryptedType,\n registerEncryptor,\n withEncryptionContext,\n setEncryptionTenantId,\n getCurrentTenantId\n} from './encryptedType';\n\n// Field encryption\nexport {\n FieldEncryptor,\n MissingEncryptionKeyError,\n DecryptionError,\n EncryptionRequiredError\n} from './fieldEncryptor';\n\n// Tenant isolation filter\nexport {\n setupTenantFilter,\n getSuperAdminContext,\n createTenantFilterDef,\n TENANT_FILTER_NAME\n} from './tenantFilter';\n\n// PostgreSQL Row-Level Security\nexport { setupRls, RlsEventSubscriber, type RlsConfig } from './rls';\n","export const ComplianceLevel = {\n pii: 'pii',\n phi: 'phi',\n pci: 'pci',\n none: 'none'\n} as const;\nexport type ComplianceLevel =\n (typeof ComplianceLevel)[keyof typeof ComplianceLevel];\n\nexport const COMPLIANCE_KEY = '~compliance' as const;\n\n// ---------------------------------------------------------------------------\n// Registry\n// ---------------------------------------------------------------------------\n\nconst complianceRegistry = new Map<string, Map<string, ComplianceLevel>>();\n\nexport function registerEntityCompliance(\n entityName: string,\n fields: Map<string, ComplianceLevel>\n): void {\n complianceRegistry.set(entityName, fields);\n}\n\nexport function getComplianceMetadata(\n entityName: string,\n fieldName: string\n): ComplianceLevel {\n return complianceRegistry.get(entityName)?.get(fieldName) ?? 'none';\n}\n\nexport function getEntityComplianceFields(\n entityName: string\n): Map<string, ComplianceLevel> | undefined {\n return complianceRegistry.get(entityName);\n}\n\nexport function entityHasEncryptedFields(entityName: string): boolean {\n const fields = complianceRegistry.get(entityName);\n if (!fields) return false;\n for (const level of fields.values()) {\n if (level === 'phi' || level === 'pci') return true;\n }\n return false;\n}\n\n// ---------------------------------------------------------------------------\n// Retention types and registry\n// ---------------------------------------------------------------------------\n\nexport const RetentionAction = {\n delete: 'delete',\n anonymize: 'anonymize'\n} as const;\nexport type RetentionAction =\n (typeof RetentionAction)[keyof typeof RetentionAction];\n\nexport interface RetentionPolicy {\n duration: string;\n action: RetentionAction;\n}\n\nexport const RetentionDuration = {\n days: (n: number): string => `P${n}D`,\n months: (n: number): string => `P${n}M`,\n years: (n: number): string => `P${n}Y`\n} as const;\n\nexport interface ParsedDuration {\n years: number;\n months: number;\n days: number;\n}\n\nconst DURATION_REGEX = /^P(?:(\\d+)Y)?(?:(\\d+)M)?(?:(\\d+)D)?$/;\n\n/**\n * Parse an ISO 8601 duration string into calendar units.\n * Returns structured units to enable calendar-aware date arithmetic.\n */\nexport function parseDuration(iso: string): ParsedDuration {\n const match = DURATION_REGEX.exec(iso);\n if (!match) {\n throw new Error(\n `Invalid ISO 8601 duration: '${iso}'. Expected format: P[n]Y[n]M[n]D`\n );\n }\n const years = parseInt(match[1] || '0', 10);\n const months = parseInt(match[2] || '0', 10);\n const days = parseInt(match[3] || '0', 10);\n\n // Approximate total days for minimum validation only\n const approxDays = years * 365 + months * 30 + days;\n if (approxDays < 1) {\n throw new Error(\n `Retention duration must be >= 1 day (P1D). Got: '${iso}' (${approxDays} approx days)`\n );\n }\n\n return { years, months, days };\n}\n\n/**\n * Subtract a parsed duration from a date using calendar-aware arithmetic.\n * Handles month-end clamping and leap years correctly.\n */\nexport function subtractDuration(from: Date, duration: ParsedDuration): Date {\n const result = new Date(from);\n result.setFullYear(result.getFullYear() - duration.years);\n result.setMonth(result.getMonth() - duration.months);\n result.setDate(result.getDate() - duration.days);\n return result;\n}\n\nconst retentionRegistry = new Map<string, RetentionPolicy>();\n\n// ---------------------------------------------------------------------------\n// User ID field registry — maps entity name to the field linking records to a user\n// ---------------------------------------------------------------------------\n\nconst DEFAULT_USER_ID_FIELD = 'userId';\nconst userIdFieldRegistry = new Map<string, string>();\n\nexport function registerEntityUserIdField(\n entityName: string,\n field: string\n): void {\n userIdFieldRegistry.set(entityName, field);\n}\n\nexport function getEntityUserIdField(entityName: string): string {\n return userIdFieldRegistry.get(entityName) ?? DEFAULT_USER_ID_FIELD;\n}\n\nexport function getAllUserIdFields(): ReadonlyMap<string, string> {\n return userIdFieldRegistry;\n}\n\nexport function registerEntityRetention(\n entityName: string,\n policy: RetentionPolicy\n): void {\n retentionRegistry.set(entityName, policy);\n}\n\nexport function getEntityRetention(\n entityName: string\n): RetentionPolicy | undefined {\n return retentionRegistry.get(entityName);\n}\n\nexport function getAllRetentionPolicies(): ReadonlyMap<\n string,\n RetentionPolicy\n> {\n return retentionRegistry;\n}\n\n// ---------------------------------------------------------------------------\n// Module augmentation — adds .compliance() via PropertyOptions\n// ---------------------------------------------------------------------------\n\n/**\n * Adds `compliance` to PropertyOptions, which flows into IncludeKeys\n * for all scalar/enum/embedded builders (PropertyOptions is extended by\n * EnumOptions and EmbeddedOptions). Relation builders use ReferenceOptions\n * instead, so they don't get .compliance() — which is what we want.\n */\ndeclare module '@mikro-orm/core' {\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n interface PropertyOptions<Owner> {\n compliance?: ComplianceLevel;\n }\n\n interface UniversalPropertyOptionsBuilder<Value, Options, IncludeKeys> {\n 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 { p, type PropertyBuilders } from '@mikro-orm/core';\nimport { COMPLIANCE_KEY, type ComplianceLevel } from './complianceTypes';\nimport { EncryptedType } 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 * Detect the original type hint from the builder's ~options to create\n * the appropriate EncryptedType variant for deserialization.\n */\nfunction detectOriginalType(options: Record<string, unknown>): string {\n const type = options.type;\n if (!type) return 'string';\n\n // type can be a string name or a Type instance\n let t: string;\n if (typeof type === 'string') {\n t = type.toLowerCase();\n } else if (typeof type === 'object' && type !== null) {\n // Type instance — use constructor name or runtimeType\n const rt = (type as { runtimeType?: string }).runtimeType;\n t = rt ?? type.constructor?.name?.toLowerCase() ?? 'string';\n } else {\n return 'string';\n }\n\n if (t.includes('json') || t === 'object') return 'json';\n if (\n t.includes('int') ||\n t === 'number' ||\n t === 'double' ||\n t === 'float' ||\n t === 'decimal' ||\n t === 'smallint' ||\n t === 'tinyint'\n )\n return 'number';\n if (t === 'boolean' || t === 'bool') return 'boolean';\n return 'string';\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 originalType = detectOriginalType(options);\n options.type = new EncryptedType(originalType);\n // Force column type to text since encrypted output is always a string\n options.columnType = 'text';\n options.runtimeType = originalType === 'json' ? 'object' : originalType;\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// 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 return isBuilder(result) ? wrapUnclassified(result) : 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// EncryptedType\n// ---------------------------------------------------------------------------\n\n/**\n * MikroORM custom Type that transparently encrypts/decrypts values.\n *\n * Works with any JS type (string, number, boolean, object/JSON).\n * Non-string values are JSON-serialized before encryption and\n * JSON-parsed after decryption.\n *\n * DB column is always `text` — the encrypted ciphertext is a string\n * regardless of the original JS type.\n *\n * Operates at the data conversion layer — before the identity map,\n * during hydration, and during persistence. Entities always hold\n * their original JS values (plaintext).\n */\nexport class EncryptedType extends Type<unknown, string | null> {\n private readonly originalType: string;\n\n /**\n * @param originalType - The original JS type hint ('string' | 'json' | 'number' | 'boolean').\n * Used to determine serialization strategy.\n */\n constructor(originalType: string = 'string') {\n super();\n this.originalType = originalType;\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 if (!_encryptor) {\n return this.serializeValue(value);\n }\n\n if (typeof value === 'string' && isEncrypted(value)) {\n return value;\n }\n\n const serialized = this.serializeValue(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.deserializeValue(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.deserializeValue(decrypted);\n } catch {\n return value;\n }\n }\n\n override getColumnType(): string {\n return 'text';\n }\n\n override get runtimeType(): string {\n return this.originalType === 'json' ? 'object' : this.originalType;\n }\n\n override ensureComparable(): boolean {\n // Return false so MikroORM does NOT run convertToDatabaseValue on raw\n // DB data during entity hydration. With deterministic encryption, the\n // same plaintext always produces the same ciphertext, so raw DB values\n // can be compared directly for change detection.\n return false;\n }\n\n // ---------------------------------------------------------------------------\n // Serialization helpers\n // ---------------------------------------------------------------------------\n\n private serializeValue(value: unknown): string {\n if (typeof value === 'string') return value;\n return JSON.stringify(value);\n }\n\n private deserializeValue(value: string): unknown {\n switch (this.originalType) {\n case 'string':\n return value;\n case 'number':\n return Number(value);\n case 'boolean':\n return value === 'true';\n case 'json':\n default:\n try {\n return JSON.parse(value);\n } catch {\n return value;\n }\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":["import_core","p","import_core","crypto","ENCRYPTED_LEVELS","p"]}
@@ -149,7 +149,7 @@ var EncryptedType = class extends Type {
149
149
  return this.originalType === "json" ? "object" : this.originalType;
150
150
  }
151
151
  ensureComparable() {
152
- return true;
152
+ return false;
153
153
  }
154
154
  // ---------------------------------------------------------------------------
155
155
  // Serialization helpers
@@ -203,7 +203,11 @@ function wrapUnclassified(builder) {
203
203
  }
204
204
  });
205
205
  }
206
- var ENCRYPTED_LEVELS = /* @__PURE__ */ new Set(["pii", "phi", "pci"]);
206
+ var ENCRYPTED_LEVELS = /* @__PURE__ */ new Set([
207
+ "pii",
208
+ "phi",
209
+ "pci"
210
+ ]);
207
211
  function detectOriginalType(options) {
208
212
  const type = options.type;
209
213
  if (!type) return "string";
@@ -217,7 +221,8 @@ function detectOriginalType(options) {
217
221
  return "string";
218
222
  }
219
223
  if (t.includes("json") || t === "object") return "json";
220
- if (t.includes("int") || t === "number" || t === "double" || t === "float" || t === "decimal" || t === "smallint" || t === "tinyint") return "number";
224
+ if (t.includes("int") || t === "number" || t === "double" || t === "float" || t === "decimal" || t === "smallint" || t === "tinyint")
225
+ return "number";
221
226
  if (t === "boolean" || t === "bool") return "boolean";
222
227
  return "string";
223
228
  }
@@ -432,7 +437,11 @@ var FieldEncryptor = class {
432
437
  };
433
438
 
434
439
  // src/persistence/complianceEventSubscriber.ts
435
- var ENCRYPTED_LEVELS2 = /* @__PURE__ */ new Set(["pii", "phi", "pci"]);
440
+ var ENCRYPTED_LEVELS2 = /* @__PURE__ */ new Set([
441
+ "pii",
442
+ "phi",
443
+ "pci"
444
+ ]);
436
445
  function wrapEmWithNativeQueryBlocking(em) {
437
446
  const BLOCKED_METHODS = [
438
447
  "nativeInsert",
@@ -483,8 +492,8 @@ function resolveEntityName(entityNameOrEntity) {
483
492
 
484
493
  // src/persistence/tenantFilter.ts
485
494
  var TENANT_FILTER_NAME = "tenant";
486
- function createTenantFilterDef(column = "organizationId") {
487
- const relation = column.replace(/Id$/, "");
495
+ function createTenantFilterDef(column = "organizationId", relationName) {
496
+ const relation = relationName ?? column.replace(/Id$/, "");
488
497
  return {
489
498
  name: TENANT_FILTER_NAME,
490
499
  cond(args, _type, em, _options, entityName) {
@@ -501,9 +510,12 @@ function createTenantFilterDef(column = "organizationId") {
501
510
  }
502
511
  const hasColumn = metadata.properties[column] != null;
503
512
  const hasRelation = relation !== column && metadata.properties[relation] != null;
504
- if (hasColumn || hasRelation) {
513
+ if (hasColumn) {
505
514
  return { [column]: args.tenantId };
506
515
  }
516
+ if (hasRelation) {
517
+ return { [relation]: args.tenantId };
518
+ }
507
519
  } catch {
508
520
  }
509
521
  return {};
@@ -513,7 +525,7 @@ function createTenantFilterDef(column = "organizationId") {
513
525
  };
514
526
  }
515
527
  function setupTenantFilter(orm, config = {}) {
516
- orm.em.addFilter(createTenantFilterDef(config.column));
528
+ orm.em.addFilter(createTenantFilterDef(config.column, config.relation));
517
529
  (config.logger ?? console).info(
518
530
  `[compliance] Tenant isolation filter registered on column '${config.column ?? "organizationId"}'`
519
531
  );