@forklaunch/core 1.3.17 → 1.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,84 +1,58 @@
1
1
  import { Readable } from 'stream';
2
+ import { C as ComplianceContext } from '../ttlCacheRecord.types-oX0ti5SP.mjs';
2
3
 
3
4
  /**
4
- * Interface representing a objectstore.
5
+ * Interface representing an object store.
6
+ *
7
+ * Methods that read or write object bodies accept an optional `compliance` parameter.
8
+ * When provided, bodies are encrypted on write and decrypted on read using
9
+ * the tenant ID for key derivation. When omitted, bodies are stored as plaintext.
5
10
  */
6
11
  interface ObjectStore<Client> {
7
12
  /**
8
13
  * Puts a record into the objectstore.
9
- *
10
- * @param {T} object - The object to put into the objectstore.
11
- * @returns {Promise<void>} - A promise that resolves when the record is put into the objectstore.
12
14
  */
13
- putObject<T>(object: T): Promise<void>;
15
+ putObject<T>(object: T, compliance?: ComplianceContext): Promise<void>;
14
16
  /**
15
17
  * Puts a batch of records into the objectstore.
16
- *
17
- * @param {T[]} objects - The objects to put into the objectstore.
18
- * @returns {Promise<void>} - A promise that resolves when the records are put into the objectstore.
19
18
  */
20
- putBatchObjects<T>(objects: T[]): Promise<void>;
19
+ putBatchObjects<T>(objects: T[], compliance?: ComplianceContext): Promise<void>;
21
20
  /**
22
- * Enqueues a record into the objectstore.
23
- *
24
- * @param {T} object - The object to enqueue into the objectstore.
25
- * @returns {Promise<void>} - A promise that resolves when the record is enqueued into the objectstore.
21
+ * Streams an object upload to the objectstore.
26
22
  */
27
- streamUploadObject<T>(object: T): Promise<void>;
23
+ streamUploadObject<T>(object: T, compliance?: ComplianceContext): Promise<void>;
28
24
  /**
29
- *
30
- * Enqueues a batch of records into the objectstore.
31
- *
32
- * @param {T[]} objects - The objects to enqueue into the objectstore.
33
- * @returns {Promise<void>} - A promise that resolves when the records are enqueued into the objectstore.
25
+ * Streams a batch of object uploads to the objectstore.
34
26
  */
35
- streamUploadBatchObjects<T>(objects: T[]): Promise<void>;
27
+ streamUploadBatchObjects<T>(objects: T[], compliance?: ComplianceContext): Promise<void>;
36
28
  /**
37
29
  * Deletes a record from the objectstore.
38
- *
39
- * @param {string} objectKey - The key of the object to delete.
40
- * @returns {Promise<void>} - A promise that resolves when the object is deleted from the objectstore.
41
30
  */
42
31
  deleteObject(objectKey: string): Promise<void>;
43
32
  /**
44
33
  * Deletes a batch of records from the objectstore.
45
- *
46
- * @param {string[]} objectKeys - The keys of the objects to delete.
47
- * @returns {Promise<void>} - A promise that resolves when the records are deleted from the objectstore.
48
34
  */
49
35
  deleteBatchObjects(objectKeys: string[]): Promise<void>;
50
36
  /**
51
37
  * Reads a record from the objectstore.
52
- *
53
- * @param {string} objectKey - The key of the object to read.
54
- * @returns {Promise<TtlCacheRecord>} - A promise that resolves with the objectstore record.
55
38
  */
56
- readObject<T>(objectKey: string): Promise<T>;
39
+ readObject<T>(objectKey: string, compliance?: ComplianceContext): Promise<T>;
57
40
  /**
58
41
  * Reads a batch of records from the objectstore.
59
- *
60
- * @param {string[]} objectKeys - The keys of the objects to read.
61
- * @returns {Promise<TtlCacheRecord<T>[]>} - A promise that resolves with the objects.
62
42
  */
63
- readBatchObjects<T>(objectKeys: string[]): Promise<T[]>;
43
+ readBatchObjects<T>(objectKeys: string[], compliance?: ComplianceContext): Promise<T[]>;
64
44
  /**
65
- * Peeks at a record in the objectstore to check if it exists.
66
- *
67
- * @param {string} objectstoreRecordKey - The key of the objectstore record to peek at.
68
- * @returns {Promise<Readable>} - A promise that resolves with a boolean indicating if the record exists.
45
+ * Streams a download from the objectstore.
46
+ * Note: Streaming bypasses application-level encryption/decryption.
69
47
  */
70
48
  streamDownloadObject(objectKey: string): Promise<Readable>;
71
49
  /**
72
- * Peeks at a batch of records in the objectstore to check if they exist.
73
- *
74
- * @param {string[]} objectKeys - The keys of the objectstore records to peek at or a prefix to match.
75
- * @returns {Promise<Readable[]>} - A promise that resolves with an array of booleans indicating if the records exist.
50
+ * Streams multiple downloads from the objectstore.
51
+ * Note: Streaming bypasses application-level encryption/decryption.
76
52
  */
77
53
  streamDownloadBatchObjects(objectKeys: string[]): Promise<Readable[]>;
78
54
  /**
79
55
  * Gets the underlying objectstore client instance.
80
- *
81
- * @returns {Client} The objectstore client instance
82
56
  */
83
57
  getClient(): Client;
84
58
  }
@@ -1,84 +1,58 @@
1
1
  import { Readable } from 'stream';
2
+ import { C as ComplianceContext } from '../ttlCacheRecord.types-oX0ti5SP.js';
2
3
 
3
4
  /**
4
- * Interface representing a objectstore.
5
+ * Interface representing an object store.
6
+ *
7
+ * Methods that read or write object bodies accept an optional `compliance` parameter.
8
+ * When provided, bodies are encrypted on write and decrypted on read using
9
+ * the tenant ID for key derivation. When omitted, bodies are stored as plaintext.
5
10
  */
6
11
  interface ObjectStore<Client> {
7
12
  /**
8
13
  * Puts a record into the objectstore.
9
- *
10
- * @param {T} object - The object to put into the objectstore.
11
- * @returns {Promise<void>} - A promise that resolves when the record is put into the objectstore.
12
14
  */
13
- putObject<T>(object: T): Promise<void>;
15
+ putObject<T>(object: T, compliance?: ComplianceContext): Promise<void>;
14
16
  /**
15
17
  * Puts a batch of records into the objectstore.
16
- *
17
- * @param {T[]} objects - The objects to put into the objectstore.
18
- * @returns {Promise<void>} - A promise that resolves when the records are put into the objectstore.
19
18
  */
20
- putBatchObjects<T>(objects: T[]): Promise<void>;
19
+ putBatchObjects<T>(objects: T[], compliance?: ComplianceContext): Promise<void>;
21
20
  /**
22
- * Enqueues a record into the objectstore.
23
- *
24
- * @param {T} object - The object to enqueue into the objectstore.
25
- * @returns {Promise<void>} - A promise that resolves when the record is enqueued into the objectstore.
21
+ * Streams an object upload to the objectstore.
26
22
  */
27
- streamUploadObject<T>(object: T): Promise<void>;
23
+ streamUploadObject<T>(object: T, compliance?: ComplianceContext): Promise<void>;
28
24
  /**
29
- *
30
- * Enqueues a batch of records into the objectstore.
31
- *
32
- * @param {T[]} objects - The objects to enqueue into the objectstore.
33
- * @returns {Promise<void>} - A promise that resolves when the records are enqueued into the objectstore.
25
+ * Streams a batch of object uploads to the objectstore.
34
26
  */
35
- streamUploadBatchObjects<T>(objects: T[]): Promise<void>;
27
+ streamUploadBatchObjects<T>(objects: T[], compliance?: ComplianceContext): Promise<void>;
36
28
  /**
37
29
  * Deletes a record from the objectstore.
38
- *
39
- * @param {string} objectKey - The key of the object to delete.
40
- * @returns {Promise<void>} - A promise that resolves when the object is deleted from the objectstore.
41
30
  */
42
31
  deleteObject(objectKey: string): Promise<void>;
43
32
  /**
44
33
  * Deletes a batch of records from the objectstore.
45
- *
46
- * @param {string[]} objectKeys - The keys of the objects to delete.
47
- * @returns {Promise<void>} - A promise that resolves when the records are deleted from the objectstore.
48
34
  */
49
35
  deleteBatchObjects(objectKeys: string[]): Promise<void>;
50
36
  /**
51
37
  * Reads a record from the objectstore.
52
- *
53
- * @param {string} objectKey - The key of the object to read.
54
- * @returns {Promise<TtlCacheRecord>} - A promise that resolves with the objectstore record.
55
38
  */
56
- readObject<T>(objectKey: string): Promise<T>;
39
+ readObject<T>(objectKey: string, compliance?: ComplianceContext): Promise<T>;
57
40
  /**
58
41
  * Reads a batch of records from the objectstore.
59
- *
60
- * @param {string[]} objectKeys - The keys of the objects to read.
61
- * @returns {Promise<TtlCacheRecord<T>[]>} - A promise that resolves with the objects.
62
42
  */
63
- readBatchObjects<T>(objectKeys: string[]): Promise<T[]>;
43
+ readBatchObjects<T>(objectKeys: string[], compliance?: ComplianceContext): Promise<T[]>;
64
44
  /**
65
- * Peeks at a record in the objectstore to check if it exists.
66
- *
67
- * @param {string} objectstoreRecordKey - The key of the objectstore record to peek at.
68
- * @returns {Promise<Readable>} - A promise that resolves with a boolean indicating if the record exists.
45
+ * Streams a download from the objectstore.
46
+ * Note: Streaming bypasses application-level encryption/decryption.
69
47
  */
70
48
  streamDownloadObject(objectKey: string): Promise<Readable>;
71
49
  /**
72
- * Peeks at a batch of records in the objectstore to check if they exist.
73
- *
74
- * @param {string[]} objectKeys - The keys of the objectstore records to peek at or a prefix to match.
75
- * @returns {Promise<Readable[]>} - A promise that resolves with an array of booleans indicating if the records exist.
50
+ * Streams multiple downloads from the objectstore.
51
+ * Note: Streaming bypasses application-level encryption/decryption.
76
52
  */
77
53
  streamDownloadBatchObjects(objectKeys: string[]): Promise<Readable[]>;
78
54
  /**
79
55
  * Gets the underlying objectstore client instance.
80
- *
81
- * @returns {Client} The objectstore client instance
82
56
  */
83
57
  getClient(): Client;
84
58
  }
@@ -151,8 +151,8 @@ var import_common = require("@forklaunch/common");
151
151
  var import_core2 = require("@mikro-orm/core");
152
152
 
153
153
  // src/persistence/encryptedType.ts
154
- var import_node_async_hooks = require("async_hooks");
155
154
  var import_core = require("@mikro-orm/core");
155
+ var import_node_async_hooks = require("async_hooks");
156
156
  var ENCRYPTED_PREFIXES = ["v1:", "v2:"];
157
157
  function isEncrypted(value) {
158
158
  return ENCRYPTED_PREFIXES.some((p3) => value.startsWith(p3));
@@ -252,14 +252,23 @@ var EncryptedType = class extends import_core.Type {
252
252
  if (!isEncrypted(value)) {
253
253
  return this.deserialize(value);
254
254
  }
255
- if (!_encryptor) return value;
255
+ if (!_encryptor) {
256
+ throw new Error(
257
+ "EncryptedType: no encryptor registered but database contains encrypted value. Call registerEncryptor() at application bootstrap."
258
+ );
259
+ }
260
+ let decrypted;
261
+ const tenantId = getCurrentTenantId();
256
262
  try {
257
- const decrypted = _encryptor.decrypt(value, getCurrentTenantId());
258
- if (decrypted === null) return null;
259
- return this.deserialize(decrypted);
260
- } catch {
261
- return value;
263
+ decrypted = _encryptor.decrypt(value, tenantId);
264
+ } catch (err) {
265
+ throw new Error(
266
+ `Failed to decrypt encrypted column value: ${err instanceof Error ? err.message : String(err)}`,
267
+ { cause: err }
268
+ );
262
269
  }
270
+ if (decrypted === null) return null;
271
+ return this.deserialize(decrypted);
263
272
  }
264
273
  getColumnType() {
265
274
  return "text";
@@ -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 { 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 { 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 { 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) {\n const valuesToCheck = 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) 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":["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 { 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":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACAO,IAAM,kBAAkB;AAAA,EAC7B,KAAK;AAAA,EACL,KAAK;AAAA,EACL,KAAK;AAAA,EACL,MAAM;AACR;AAIO,IAAM,iBAAiB;AAM9B,IAAM,qBAAqB,oBAAI,IAA0C;AAElE,SAAS,yBACd,YACA,QACM;AACN,qBAAmB,IAAI,YAAY,MAAM;AAC3C;AAEO,SAAS,sBACd,YACA,WACiB;AACjB,SAAO,mBAAmB,IAAI,UAAU,GAAG,IAAI,SAAS,KAAK;AAC/D;AAEO,SAAS,0BACd,YAC0C;AAC1C,SAAO,mBAAmB,IAAI,UAAU;AAC1C;AAEO,SAAS,yBAAyB,YAA6B;AACpE,QAAM,SAAS,mBAAmB,IAAI,UAAU;AAChD,MAAI,CAAC,OAAQ,QAAO;AACpB,aAAW,SAAS,OAAO,OAAO,GAAG;AACnC,QAAI,UAAU,SAAS,UAAU,MAAO,QAAO;AAAA,EACjD;AACA,SAAO;AACT;AAMO,IAAM,kBAAkB;AAAA,EAC7B,QAAQ;AAAA,EACR,WAAW;AACb;AASO,IAAM,oBAAoB;AAAA,EAC/B,MAAM,CAAC,MAAsB,IAAI,CAAC;AAAA,EAClC,QAAQ,CAAC,MAAsB,IAAI,CAAC;AAAA,EACpC,OAAO,CAAC,MAAsB,IAAI,CAAC;AACrC;AAQA,IAAM,iBAAiB;AAMhB,SAAS,cAAc,KAA6B;AACzD,QAAM,QAAQ,eAAe,KAAK,GAAG;AACrC,MAAI,CAAC,OAAO;AACV,UAAM,IAAI;AAAA,MACR,+BAA+B,GAAG;AAAA,IACpC;AAAA,EACF;AACA,QAAM,QAAQ,SAAS,MAAM,CAAC,KAAK,KAAK,EAAE;AAC1C,QAAM,SAAS,SAAS,MAAM,CAAC,KAAK,KAAK,EAAE;AAC3C,QAAM,OAAO,SAAS,MAAM,CAAC,KAAK,KAAK,EAAE;AAGzC,QAAM,aAAa,QAAQ,MAAM,SAAS,KAAK;AAC/C,MAAI,aAAa,GAAG;AAClB,UAAM,IAAI;AAAA,MACR,oDAAoD,GAAG,MAAM,UAAU;AAAA,IACzE;AAAA,EACF;AAEA,SAAO,EAAE,OAAO,QAAQ,KAAK;AAC/B;AAMO,SAAS,iBAAiB,MAAY,UAAgC;AAC3E,QAAM,SAAS,IAAI,KAAK,IAAI;AAC5B,SAAO,YAAY,OAAO,YAAY,IAAI,SAAS,KAAK;AACxD,SAAO,SAAS,OAAO,SAAS,IAAI,SAAS,MAAM;AACnD,SAAO,QAAQ,OAAO,QAAQ,IAAI,SAAS,IAAI;AAC/C,SAAO;AACT;AAEA,IAAM,oBAAoB,oBAAI,IAA6B;AAM3D,IAAM,wBAAwB;AAC9B,IAAM,sBAAsB,oBAAI,IAAoB;AAE7C,SAAS,0BACd,YACA,OACM;AACN,sBAAoB,IAAI,YAAY,KAAK;AAC3C;AAEO,SAAS,qBAAqB,YAA4B;AAC/D,SAAO,oBAAoB,IAAI,UAAU,KAAK;AAChD;AAEO,SAAS,qBAAkD;AAChE,SAAO;AACT;AAEO,SAAS,wBACd,YACA,QACM;AACN,oBAAkB,IAAI,YAAY,MAAM;AAC1C;AAEO,SAAS,mBACd,YAC6B;AAC7B,SAAO,kBAAkB,IAAI,UAAU;AACzC;AAEO,SAAS,0BAGd;AACA,SAAO;AACT;;;AC5JA,oBAAyC;AACzC,IAAAA,eAA+C;;;ACD/C,kBAA2D;AAC3D,8BAAkC;AAGlC,IAAM,qBAAqB,CAAC,OAAO,KAAK;AAExC,SAAS,YAAY,OAAwB;AAC3C,SAAO,mBAAmB,KAAK,CAACC,OAAM,MAAM,WAAWA,EAAC,CAAC;AAC3D;AAMA,IAAI;AAMJ,IAAM,iBAAiB,IAAI,0CAAwC;AAM5D,SAAS,kBAAkB,WAAiC;AACjE,eAAa;AACf;AAMO,SAAS,sBAAyB,UAAkB,IAAgB;AACzE,SAAO,eAAe,IAAI,EAAE,SAAS,GAAG,EAAE;AAC5C;AAOO,SAAS,sBAAsB,UAAwB;AAC5D,iBAAe,UAAU,EAAE,SAAS,CAAC;AACvC;AAMO,SAAS,qBAA6B;AAC3C,SAAO,eAAe,SAAS,GAAG,YAAY;AAChD;AAUO,SAAS,oBACd,MACoC;AACpC,MAAI,gBAAgB,iBAAM,QAAO;AACjC,MAAI,OAAO,SAAS,YAAY;AAC9B,QAAI;AACF,YAAM,OAAO,IAAK,KAA2B;AAC7C,aAAO,gBAAgB,mBAAO,OAAO;AAAA,IACvC,QAAQ;AACN,aAAO;AAAA,IACT;AAAA,EACF;AACA,SAAO;AACT;AAOA,SAAS,aAAa,OAAgB,aAA8B;AAClE,MAAI,UAAU,QAAQ,UAAU,OAAW,QAAO;AAClD,UAAQ,aAAa;AAAA,IACnB,KAAK;AACH,aAAO,OAAO,UAAU,WAAW,QAAQ,OAAO,KAAK;AAAA,IACzD,KAAK;AACH,aAAO,OAAO,UAAU,WAAW,QAAQ,OAAO,KAAK;AAAA,IACzD,KAAK;AACH,aAAO,OAAO,UAAU,YACpB,QACA,OAAO,UAAU,WACf,UAAU,SACV,QAAQ,KAAK;AAAA,IACrB,KAAK;AACH,aAAO,iBAAiB,OAAO,QAAQ,IAAI,KAAK,KAAwB;AAAA,IAC1E,KAAK;AACH,aAAO,OAAO,UAAU,WAAW,QAAQ,OAAO,KAAe;AAAA,IACnE,KAAK;AACH,aAAO,OAAO,SAAS,KAAK,IACxB,QACA,OAAO,UAAU,WACf,OAAO,KAAK,OAAO,QAAQ,IAC3B,OAAO,KAAK,KAAmB;AAAA,IACvC,KAAK;AAAA;AAAA,IACL;AACE,aAAO;AAAA,EACX;AACF;AAmBO,IAAM,gBAAN,cAA4B,iBAA6B;AAAA;AAAA;AAAA;AAAA,EAIrD;AAAA,EACA;AAAA,EACA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAST,YACE,qBAA6B,UAC7B,UAAmB,OACnB,YACA;AACA,UAAM;AACN,SAAK,sBAAsB;AAC3B,SAAK,WAAW;AAChB,SAAK,cAAc;AAAA,EACrB;AAAA,EAES,uBACP,OACA,WACA,UACe;AACf,QAAI,UAAU,QAAQ,UAAU,OAAW,QAAO;AAClD,QAAI,OAAO,UAAU,YAAY,MAAM,WAAW,EAAG,QAAO;AAI5D,QAAI,KAAK,aAAa;AACpB,YAAM,gBACJ,KAAK,YAAY,MAAM,QAAQ,KAAK,IAAI,QAAQ,CAAC,KAAK;AACxD,iBAAW,KAAK,eAAe;AAC7B,YAAI,CAAC,KAAK,YAAY,SAAS,CAAC,GAAG;AACjC,gBAAM,IAAI;AAAA,YACR,uBAAuB,OAAO,CAAC,CAAC,qBAAqB,KAAK,YAAY,KAAK,IAAI,CAAC;AAAA,UAClF;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAEA,QAAI,CAAC,YAAY;AACf,aAAO,KAAK,UAAU,KAAK;AAAA,IAC7B;AAEA,QAAI,OAAO,UAAU,YAAY,YAAY,KAAK,GAAG;AACnD,aAAO;AAAA,IACT;AAEA,UAAM,aAAa,KAAK,UAAU,KAAK;AACvC,WAAO,WAAW,QAAQ,YAAY,mBAAmB,CAAC,KAAK;AAAA,EACjE;AAAA,EAES,iBACP,OACA,WACS;AACT,QAAI,UAAU,QAAQ,UAAU,OAAW,QAAO;AAClD,QAAI,OAAO,UAAU,SAAU,QAAO;AAEtC,QAAI,CAAC,YAAY,KAAK,GAAG;AACvB,aAAO,KAAK,YAAY,KAAK;AAAA,IAC/B;AAEA,QAAI,CAAC,YAAY;AACf,YAAM,IAAI;AAAA,QACR;AAAA,MAEF;AAAA,IACF;AAEA,QAAI;AACJ,UAAM,WAAW,mBAAmB;AACpC,QAAI;AACF,kBAAY,WAAW,QAAQ,OAAO,QAAQ;AAAA,IAChD,SAAS,KAAK;AACZ,YAAM,IAAI;AAAA,QACR,6CAA6C,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,CAAC;AAAA,QAC7F,EAAE,OAAO,IAAI;AAAA,MACf;AAAA,IACF;AACA,QAAI,cAAc,KAAM,QAAO;AAC/B,WAAO,KAAK,YAAY,SAAS;AAAA,EACnC;AAAA,EAES,gBAAwB;AAC/B,WAAO;AAAA,EACT;AAAA,EAEA,IAAa,cAAsB;AACjC,QAAI,KAAK,SAAU,QAAO;AAC1B,WAAO,KAAK;AAAA,EACd;AAAA,EAES,mBAA4B;AACnC,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA,EAMQ,UAAU,OAAwB;AACxC,QAAI,OAAO,UAAU,SAAU,QAAO;AACtC,QAAI,iBAAiB,KAAM,QAAO,MAAM,YAAY;AACpD,QAAI,OAAO,UAAU,SAAU,QAAO,MAAM,SAAS;AACrD,QAAI,OAAO,SAAS,KAAK,KAAK,iBAAiB;AAC7C,aAAO,OAAO,KAAK,KAAmB,EAAE,SAAS,QAAQ;AAC3D,WAAO,KAAK,UAAU,KAAK;AAAA,EAC7B;AAAA,EAEQ,YAAY,OAAwB;AAC1C,QAAI,KAAK,UAAU;AACjB,UAAI;AACF,cAAM,MAAM,KAAK,MAAM,KAAK;AAC5B,YAAI,CAAC,MAAM,QAAQ,GAAG,EAAG,QAAO;AAChC,eAAO,IAAI;AAAA,UAAI,CAAC,OACd,aAAa,IAAI,KAAK,mBAAmB;AAAA,QAC3C;AAAA,MACF,QAAQ;AACN,eAAO;AAAA,MACT;AAAA,IACF;AAEA,YAAQ,KAAK,qBAAqB;AAAA,MAChC,KAAK;AACH,eAAO;AAAA,MACT,KAAK;AACH,YAAI;AACF,iBAAO,KAAK,MAAM,KAAK;AAAA,QACzB,QAAQ;AACN,iBAAO;AAAA,QACT;AAAA;AAAA,MAEF,KAAK;AAAA,MACL,KAAK;AAAA,MACL,KAAK;AACH,eAAO,aAAa,OAAO,KAAK,mBAAmB;AAAA;AAAA,MAErD;AACE,eAAO,aAAa,KAAK,aAAa,KAAK,GAAG,KAAK,mBAAmB;AAAA,IAC1E;AAAA,EACF;AAAA,EAEQ,aAAa,OAAwB;AAC3C,QAAI;AACF,aAAO,KAAK,MAAM,KAAK;AAAA,IACzB,QAAQ;AACN,aAAO;AAAA,IACT;AAAA,EACF;AACF;;;ADnRA,SAAS,UAAU,OAAiC;AAClD,SACE,SAAS,QACT,OAAO,UAAU,YACjB,cAAe;AAEnB;AAQA,SAAS,iBAAiB,SAA2B;AACnD,SAAO,IAAI,MAAM,SAAmB;AAAA,IAClC,IAAI,QAA0C,MAAM;AAClD,UAAI,SAAS,cAAc;AACzB,eAAO,CAAC,UAA2B,eAAe,QAAQ,KAAK;AAAA,MACjE;AACA,UAAI,SAAS,WAAY,QAAO,QAAQ,IAAI,QAAQ,MAAM,MAAM;AAChE,UAAI,SAAS,eAAgB,QAAO;AAEpC,YAAM,QAAQ,QAAQ,IAAI,QAAQ,MAAM,MAAM;AAC9C,UAAI,OAAO,UAAU,YAAY;AAC/B,eAAO,IAAI,SAAoB;AAC7B,gBAAM,SAAU,MAA0C;AAAA,YACxD;AAAA,YACA;AAAA,UACF;AACA,iBAAO,UAAU,MAAM,IAAI,iBAAiB,MAAM,IAAI;AAAA,QACxD;AAAA,MACF;AACA,aAAO;AAAA,IACT;AAAA,EACF,CAAC;AACH;AAKA,IAAM,mBAAiD,oBAAI,IAAI;AAAA,EAC7D;AAAA,EACA;AAAA,EACA;AACF,CAAC;AAOD,SAAS,yBAAyB,SAGhC;AACA,QAAM,OAAO,QAAQ;AACrB,QAAM,UAAU,QAAQ,UAAU;AAIlC,QAAM,WAAW,oBAAoB,IAAI;AACzC,MAAI,UAAU;AACZ,UAAM,KAAK,SAAS;AAEpB,QAAI,GAAG,SAAS,IAAI,GAAG;AACrB,aAAO,EAAE,oBAAoB,GAAG,MAAM,GAAG,EAAE,KAAK,UAAU,SAAS,KAAK;AAAA,IAC1E;AACA,WAAO,EAAE,oBAAoB,IAAI,QAAQ;AAAA,EAC3C;AAGA,SAAO,EAAE,oBAAoB,UAAU,QAAQ;AACjD;AASA,SAAS,eAAe,SAAiB,OAAiC;AAExE,MAAI,iBAAiB,IAAI,KAAK,GAAG;AAC/B,UAAM,UAAW,QACf,UACF;AACA,QAAI,SAAS;AACX,YAAM,EAAE,oBAAoB,QAAQ,IAAI,yBAAyB,OAAO;AAKxE,UAAI;AACJ,UAAI,QAAQ,OAAO;AACjB,qBAAa,kBAAkB,QAAQ,KAAK;AAG5C,eAAO,QAAQ;AACf,eAAO,QAAQ;AACf,eAAO,QAAQ;AAAA,MACjB;AAEA,cAAQ,OAAO,IAAI,cAAc,oBAAoB,SAAS,UAAU;AAExE,cAAQ,aAAa;AACrB,cAAQ,cAAc,UAAU,WAAW;AAAA,IAC7C;AAAA,EACF;AAEA,SAAO,IAAI,MAAM,SAAS;AAAA,IACxB,IAAI,QAA0C,MAAM;AAClD,UAAI,SAAS,eAAgB,QAAO;AACpC,UAAI,SAAS,WAAY,QAAO,QAAQ,IAAI,QAAQ,MAAM,MAAM;AAEhE,YAAM,QAAQ,QAAQ,IAAI,QAAQ,MAAM,MAAM;AAC9C,UAAI,OAAO,UAAU,YAAY;AAC/B,eAAO,IAAI,SAAoB;AAC7B,gBAAM,SAAU,MAA0C;AAAA,YACxD;AAAA,YACA;AAAA,UACF;AACA,iBAAO,UAAU,MAAM,IAAI,eAAe,QAAQ,KAAK,IAAI;AAAA,QAC7D;AAAA,MACF;AACA,aAAO;AAAA,IACT;AAAA,EACF,CAAC;AACH;AAOA,SAAS,aAAa,SAA0B;AAC9C,SAAO,eAAe,SAAS,MAAM;AACvC;AAMA,IAAM,mBAAmB,oBAAI,IAAI;AAAA,EAC/B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,CAAC;AAED,SAAS,iBAAiB,MAAgC;AACxD,SAAO,OAAO,SAAS,YAAY,iBAAiB,IAAI,IAAI;AAC9D;AAUA,SAAS,kBAAkB,OAA2B;AAEpD,MAAI,OAAO,UAAU,YAAY;AAC/B,WAAO,kBAAmB,MAAwB,CAAC;AAAA,EACrD;AACA,MAAI,MAAM,QAAQ,KAAK,EAAG,QAAO;AACjC,MAAI,SAAS,QAAQ,OAAO,UAAU,UAAU;AAG9C,UAAM,MAAM;AACZ,UAAM,YAAY,OAAO,OAAO,GAAG;AACnC,WAAO,UAAU,OAAO,CAAC,MAAM,OAAO,MAAM,YAAY,EAAE,KAAK,IAAI;AAAA,EACrE;AACA,SAAO,CAAC;AACV;AAMA,IAAM,eAAN,cAA2B,kBAAsB;AAAA,EACtC,uBAAuB,OAAwB;AACtD,eAAO,6BAAc,KAAK;AAAA,EAC5B;AAAA,EAES,iBAAiB,OAAyB;AACjD,eAAO,yBAAU,KAAK;AAAA,EACxB;AAAA,EAES,gBAAwB;AAC/B,WAAO;AAAA,EACT;AAAA,EAEA,IAAI,cAAsB;AACxB,WAAO;AAAA,EACT;AACF;AAUA,SAAS,cAAc,OAAuC;AAC5D,QAAM,SAAS,kBAAkB,KAAK;AACtC,MAAI,OAAO,WAAW,EAAG,QAAO;AAEhC,QAAM,aAAa,OAAO,MAAM,CAAC,MAAM,OAAO,MAAM,QAAQ;AAC5D,MAAI,WAAY,QAAO;AAEvB,QAAM,aAAa,OAAO,MAAM,CAAC,MAAM,OAAO,MAAM,QAAQ;AAC5D,MAAI,WAAY,QAAO;AAEvB,SAAO,IAAI,aAAa;AAC1B;AA6BO,IAAM,KAAuB,IAAI,MAAM,gBAAG;AAAA,EAC/C,IAAI,QAA0C,MAAM;AAClD,UAAM,QAAQ,QAAQ,IAAI,QAAQ,MAAM,MAAM;AAC9C,QAAI,OAAO,UAAU,WAAY,QAAO;AAExC,QAAI,iBAAiB,IAAI,GAAG;AAE1B,aAAO,IAAI,SAAoB;AAC7B,cAAM,SAAU,MAA0C;AAAA,UACxD;AAAA,UACA;AAAA,QACF;AACA,eAAO,UAAU,MAAM,IAAI,aAAa,MAAM,IAAI;AAAA,MACpD;AAAA,IACF;AAGA,WAAO,IAAI,SAAoB;AAC7B,YAAM,SAAU,MAA0C;AAAA,QACxD;AAAA,QACA;AAAA,MACF;AACA,UAAI,UAAU,MAAM,GAAG;AAGrB,YAAI,SAAS,QAAQ;AACnB,gBAAM,UAAW,OACf,UACF;AACA,cAAI,WAAW,CAAC,QAAQ,MAAM;AAC5B,oBAAQ,OAAO,cAAc,QAAQ,KAAK;AAAA,UAC5C;AAAA,QACF;AACA,eAAO,iBAAiB,MAAM;AAAA,MAChC;AACA,aAAO;AAAA,IACT;AAAA,EACF;AACF,CAAC;;;AE5SD,IAAAC,eAMO;AAmBP,SAAS,oBAAoB,SAA+C;AAC1E,MAAI,WAAW,QAAQ,OAAO,YAAY,SAAU,QAAO;AAC3D,SAAQ,QAAoC,cAAc;AAG5D;AAEO,SAAS,uBASd,MAeA;AACA,QAAM,aAAa,UAAU,OAAQ,KAAK,OAAkB;AAC5D,QAAM,mBAAmB,oBAAI,IAA6B;AAE1D,QAAM,gBAAgB,KAAK;AAC3B,QAAM,qBACJ,OAAO,kBAAkB,aAAa,cAAc,cAAC,IAAI;AAE3D,aAAW,CAAC,WAAW,OAAO,KAAK,OAAO,QAAQ,kBAAkB,GAAG;AACrE,QAAI,OAAO,YAAY,YAAY;AAIjC,uBAAiB,IAAI,WAAW,MAAM;AACtC;AAAA,IACF;AACA,UAAM,QAAQ,oBAAoB,OAAO;AAEzC,QAAI,SAAS,MAAM;AACjB,YAAM,IAAI;AAAA,QACR,UAAU,UAAU,IAAI,SAAS;AAAA,MAGnC;AAAA,IACF;AACA,qBAAiB,IAAI,WAAW,KAAK;AAAA,EACvC;AAEA,2BAAyB,YAAY,gBAAgB;AAGrD,MAAI,KAAK,WAAW;AAClB,kBAAc,KAAK,UAAU,QAAQ;AAErC,QAAI,CAAC,mBAAmB,WAAW,GAAG;AACpC,YAAM,IAAI;AAAA,QACR,WAAW,UAAU;AAAA,MAEvB;AAAA,IACF;AAEA,4BAAwB,YAAY,KAAK,SAAS;AAAA,EACpD;AAGA,MAAI,KAAK,aAAa;AACpB,8BAA0B,YAAY,KAAK,WAAW;AAAA,EACxD;AAEA,aAAO;AAAA,IACL;AAAA,EASF;AAaF;;;AChIA,oBAAmB;AAMZ,IAAM,4BAAN,cAAwC,MAAM;AAAA,EAC1C,OAAO;AAAA,EAChB,YAAY,UAAU,0CAA0C;AAC9D,UAAM,OAAO;AAAA,EACf;AACF;AAEO,IAAM,kBAAN,cAA8B,MAAM;AAAA,EAChC,OAAO;AAAA,EAChB,YACE,UAAU,wEACV;AACA,UAAM,OAAO;AAAA,EACf;AACF;AAEO,IAAM,0BAAN,cAAsC,MAAM;AAAA,EACxC,OAAO;AAAA,EAChB,YACE,UAAU,kEACV;AACA,UAAM,OAAO;AAAA,EACf;AACF;AAMA,IAAM,YAAY;AAClB,IAAM,WAAW;AACjB,IAAM,YAAY;AAClB,IAAM,YAAY;AAClB,IAAM,YAAY,OAAO,MAAM,CAAC;AAMzB,IAAM,iBAAN,MAAqB;AAAA,EACT;AAAA,EAEjB,YAAY,WAAmB;AAC7B,QAAI,CAAC,WAAW;AACd,YAAM,IAAI,0BAA0B;AAAA,IACtC;AACA,SAAK,YAAY;AAAA,EACnB;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,UAAU,UAA0B;AAClC,WAAO,OAAO;AAAA,MACZ,cAAAC,QAAO,SAAS,WAAW,KAAK,WAAW,WAAW,UAAU,SAAS;AAAA,IAC3E;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAUQ,sBAAsB,KAAa,WAA2B;AACpE,WAAO,cAAAA,QACJ,WAAW,UAAU,GAAG,EACxB,OAAO,SAAS,EAChB,OAAO,EACP,SAAS,GAAG,QAAQ;AAAA,EACzB;AAAA,EAaA,QAAQ,WAA0B,UAAkC;AAClE,QAAI,cAAc,QAAQ,cAAc,QAAW;AACjD,aAAO;AAAA,IACT;AAEA,UAAM,MAAM,KAAK,UAAU,YAAY,EAAE;AACzC,UAAM,KAAK,KAAK,sBAAsB,KAAK,SAAS;AAEpD,UAAM,SAAS,cAAAA,QAAO,eAAe,WAAW,KAAK,EAAE;AACvD,UAAM,YAAY,OAAO,OAAO;AAAA,MAC9B,OAAO,OAAO,WAAW,MAAM;AAAA,MAC/B,OAAO,MAAM;AAAA,IACf,CAAC;AACD,UAAM,UAAU,OAAO,WAAW;AAElC,WAAO;AAAA,MACL;AAAA,MACA,GAAG,SAAS,QAAQ;AAAA,MACpB,QAAQ,SAAS,QAAQ;AAAA,MACzB,UAAU,SAAS,QAAQ;AAAA,IAC7B,EAAE,KAAK,GAAG;AAAA,EACZ;AAAA,EAQA,QAAQ,YAA2B,UAAkC;AACnE,QAAI,eAAe,QAAQ,eAAe,QAAW;AACnD,aAAO;AAAA,IACT;AAEA,UAAM,QAAQ,WAAW,MAAM,GAAG;AAClC,QAAI,MAAM,WAAW,KAAM,MAAM,CAAC,MAAM,QAAQ,MAAM,CAAC,MAAM,MAAO;AAClE,YAAM,IAAI;AAAA,QACR;AAAA,MACF;AAAA,IACF;AAEA,UAAM,CAAC,EAAE,OAAO,YAAY,YAAY,IAAI;AAC5C,UAAM,KAAK,OAAO,KAAK,OAAO,QAAQ;AACtC,UAAM,UAAU,OAAO,KAAK,YAAY,QAAQ;AAChD,UAAM,YAAY,OAAO,KAAK,cAAc,QAAQ;AACpD,UAAM,MAAM,KAAK,UAAU,YAAY,EAAE;AAEzC,QAAI;AACF,YAAM,WAAW,cAAAA,QAAO,iBAAiB,WAAW,KAAK,EAAE;AAC3D,eAAS,WAAW,OAAO;AAC3B,YAAM,YAAY,OAAO,OAAO;AAAA,QAC9B,SAAS,OAAO,SAAS;AAAA,QACzB,SAAS,MAAM;AAAA,MACjB,CAAC;AACD,aAAO,UAAU,SAAS,MAAM;AAAA,IAClC,QAAQ;AACN,YAAM,IAAI,gBAAgB;AAAA,IAC5B;AAAA,EACF;AACF;;;AC9IA,IAAMC,oBAAiD,oBAAI,IAAI;AAAA,EAC7D;AAAA,EACA;AAAA,EACA;AACF,CAAC;AAiBM,SAAS,8BAAgD,IAAU;AACxE,QAAM,kBAAkB;AAAA,IACtB;AAAA,IACA;AAAA,IACA;AAAA,EACF;AAEA,SAAO,IAAI,MAAM,IAAI;AAAA,IACnB,IAAI,QAAQ,MAAM,UAAU;AAC1B,UACE,OAAO,SAAS,YAChB,gBAAgB,SAAS,IAAwC,GACjE;AACA,eAAO,CAAC,uBAAgC,SAAoB;AAC1D,gBAAM,aAAa,kBAAkB,kBAAkB;AACvD,cAAI,YAAY;AACd,kBAAM,SAAS,0BAA0B,UAAU;AACnD,gBAAI,QAAQ;AACV,yBAAW,CAAC,WAAW,KAAK,KAAK,QAAQ;AACvC,oBAAIA,kBAAiB,IAAI,KAAK,GAAG;AAC/B,wBAAM,IAAI;AAAA,oBACR,GAAG,IAAI,yBAAyB,UAAU,oBACpC,SAAS,2BAA2B,KAAK;AAAA,kBAEjD;AAAA,gBACF;AAAA,cACF;AAAA,YACF;AAAA,UACF;AACA,gBAAM,SAAS,QAAQ,IAAI,QAAQ,MAAM,QAAQ;AACjD,iBAAQ,OAA2C;AAAA,YACjD;AAAA,YACA;AAAA,YACA,GAAG;AAAA,UACL;AAAA,QACF;AAAA,MACF;AACA,aAAO,QAAQ,IAAI,QAAQ,MAAM,QAAQ;AAAA,IAC3C;AAAA,EACF,CAAC;AACH;AAKA,SAAS,kBAAkB,oBAAiD;AAC1E,MAAI,OAAO,uBAAuB,UAAU;AAC1C,WAAO;AAAA,EACT;AACA,MAAI,OAAO,uBAAuB,YAAY;AAC5C,WAAQ,mBAAyC;AAAA,EACnD;AACA,MACE,sBAAsB,QACtB,OAAO,uBAAuB,YAC9B,iBAAiB,oBACjB;AACA,WAAQ,mBAAmB,YAAkC;AAAA,EAC/D;AACA,SAAO;AACT;;;ACrFO,IAAM,qBAAqB;AAa3B,SAAS,sBACd,SAAiB,kBACjB,cACW;AACX,QAAM,WAAW,gBAAgB,OAAO,QAAQ,OAAO,EAAE;AACzD,SAAO;AAAA,IACL,MAAM;AAAA,IACN,KACE,MACA,OACA,IACA,UACA,YACA;AACA,UAAI,CAAC,YAAY;AACf,eAAO,CAAC;AAAA,MACV;AAKA,UAAI,CAAC,MAAM,UAAU;AACnB,eAAO,CAAC;AAAA,MACV;AAEA,UAAI;AACF,cAAM,WAAW,GAAG,YAAY,EAAE,eAAe,YAAY,KAAK;AAClE,YAAI,CAAC,UAAU;AACb,iBAAO,CAAC;AAAA,QACV;AAEA,cAAM,YAAY,SAAS,WAAW,MAAM,KAAK;AACjD,cAAM,cACJ,aAAa,UAAU,SAAS,WAAW,QAAQ,KAAK;AAE1D,YAAI,WAAW;AACb,iBAAO,EAAE,CAAC,MAAM,GAAG,KAAK,SAAS;AAAA,QACnC;AACA,YAAI,aAAa;AACf,iBAAO,EAAE,CAAC,QAAQ,GAAG,KAAK,SAAS;AAAA,QACrC;AAAA,MACF,QAAQ;AAAA,MAER;AAEA,aAAO,CAAC;AAAA,IACV;AAAA,IACA,SAAS;AAAA,IACT,MAAM;AAAA,EACR;AACF;AAaO,SAAS,kBACd,KAGA,SAMI,CAAC,GACC;AACN,MAAI,GAAG,UAAU,sBAAsB,OAAO,QAAQ,OAAO,QAAQ,CAAC;AACtE,GAAC,OAAO,UAAU,SAAS;AAAA,IACzB,8DAA8D,OAAO,UAAU,gBAAgB;AAAA,EACjG;AACF;AAQO,SAAS,qBACd,IACmC;AACnC,QAAM,SAAS,GAAG,KAAK;AACvB,SAAO,gBAAgB,oBAAoB,EAAE,UAAU,OAAU,CAAC;AAIlE,SAAO,UAAU;AAAA,IACf,MAAM;AAAA,IACN,MAAM,CAAC;AAAA,IACP,SAAS;AAAA,EACX,CAAC;AACD,SAAO;AACT;;;AChEO,IAAM,qBAAN,MAAoD;AAAA,EACzD,MAAM,sBAAsB,MAA8C;AACxE,UAAM,WAAW,KAAK,YAAY,KAAK,EAAE;AACzC,QAAI,CAAC,UAAU;AAEb;AAAA,IACF;AAEA,UAAM,aAAa,KAAK,GAAG,cAAc;AAGzC,UAAM,WAAW;AAAA,MACf,8BAA8B,gBAAgB,QAAQ,CAAC;AAAA,MACvD,CAAC;AAAA,MACD;AAAA,MACA,KAAK;AAAA,IACP;AAAA,EACF;AAAA,EAEQ,YACN,IACoB;AACpB,UAAM,SAAS,GAAG,gBAAgB,kBAAkB;AAGpD,WAAO,QAAQ;AAAA,EACjB;AACF;AAkBO,SAAS,SAAS,KAAe,SAAoB,CAAC,GAAS;AACpE,QAAM,aAAa,iBAAiB,GAAG;AACvC,QAAM,UAAU,OAAO,WAAW;AAClC,QAAM,MAAM,OAAO,UAAU,EAAE,MAAM,QAAQ,MAAM,MAAM,QAAQ,KAAK;AAEtE,MAAI,CAAC,SAAS;AACZ,QAAI,CAAC,YAAY;AAEf,UAAI;AAAA,QACF;AAAA,MACF;AACA;AAAA,IACF;AAEA,QAAI;AAAA,MACF;AAAA,IACF;AACA;AAAA,EACF;AAEA,MAAI,CAAC,YAAY;AACf,QAAI;AAAA,MACF;AAAA,IAEF;AACA;AAAA,EACF;AAGA,MAAI,GAAG,gBAAgB,EAAE,mBAAmB,IAAI,mBAAmB,CAAC;AACpE,MAAI,KAAK,yDAAyD;AAGlE,sBAAoB,GAAG,EAAE,MAAM,CAAC,QAAQ;AACtC,QAAI,KAAK,iDAAiD,GAAG;AAAA,EAC/D,CAAC;AACH;AAUA,eAAe,oBAAoB,KAA8B;AAC/D,QAAM,WAAW,IAAI,GAAG,YAAY,EAAE,OAAO;AAE7C,aAAW,QAAQ,OAAO,OAAO,QAAQ,GAAG;AAC1C,UAAM,WAAW,KAAK,WAAW,gBAAgB,KAAK;AACtD,UAAM,SAAS,KAAK,WAAW,cAAc,KAAK;AAElD,QAAI,CAAC,YAAY,CAAC,OAAQ;AAE1B,UAAM,YAAY,KAAK;AACvB,QAAI;AACF,YAAM,aAAa,IAAI,GAAG,cAAc;AACxC,YAAM,SAAS,MAAM,WAAW;AAAA,QAC9B,yDAAyD,gBAAgB,SAAS,CAAC;AAAA,QACnF,CAAC;AAAA,QACD;AAAA,MACF;AAEA,YAAM,WAAW,MAAM,QAAQ,MAAM,IAAI,SAAS,CAAC;AACnD,YAAM,kBAAkB,SAAS;AAAA,QAAK,CAACC,OACrCA,GAAE,WAAW,SAAS,QAAQ;AAAA,MAChC;AAEA,UAAI,CAAC,iBAAiB;AACpB,gBAAQ;AAAA,UACN,qDAAqD,SAAS;AAAA,iBAE1C,SAAS;AAAA,uCACa,SAAS;AAAA;AAAA,QAErD;AAAA,MACF;AAAA,IACF,QAAQ;AAAA,IAGR;AAAA,EACF;AACF;AAUA,SAAS,iBAAiB,KAAwB;AAChD,MAAI;AACF,UAAM,WAAW,IAAI,GAAG,YAAY;AACpC,UAAM,OAAO,SAAS,YAAY,KAAK,YAAY;AACnD,WAAO,KAAK,SAAS,SAAS;AAAA,EAChC,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAMA,SAAS,gBAAgB,OAAuB;AAC9C,SAAO,MAAM,QAAQ,MAAM,IAAI;AACjC;","names":["import_core","p","import_core","crypto","ENCRYPTED_LEVELS","p"]}
@@ -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));
@@ -187,14 +187,23 @@ var EncryptedType = class extends Type {
187
187
  if (!isEncrypted(value)) {
188
188
  return this.deserialize(value);
189
189
  }
190
- 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();
191
197
  try {
192
- const decrypted = _encryptor.decrypt(value, getCurrentTenantId());
193
- if (decrypted === null) return null;
194
- return this.deserialize(decrypted);
195
- } catch {
196
- 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
+ );
197
204
  }
205
+ if (decrypted === null) return null;
206
+ return this.deserialize(decrypted);
198
207
  }
199
208
  getColumnType() {
200
209
  return "text";