@forklaunch/core 1.2.9 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,4 +1,4 @@
1
- import { PropertyBuilders, EntityMetadataWithProperties, EntitySchemaWithMeta, InferEntityFromProperties, EventSubscriber, EventArgs, FilterDef, EntityManager, MikroORM } from '@mikro-orm/core';
1
+ import { PropertyBuilders, EntityMetadataWithProperties, EntitySchemaWithMeta, InferEntityFromProperties, Type, Platform, TransformContext, FilterDef, EntityManager, EventSubscriber, MikroORM } from '@mikro-orm/core';
2
2
 
3
3
  declare const ComplianceLevel: {
4
4
  readonly pii: "pii";
@@ -101,6 +101,19 @@ declare function defineComplianceEntity<const TName extends string, const TTable
101
101
  userIdField?: string;
102
102
  }): EntitySchemaWithMeta<TName, TTableName, InferEntityFromProperties<TProperties, TPK, TBase, TRepository, TForceObject>, TBase, TProperties>;
103
103
 
104
+ /**
105
+ * Wraps an EntityManager to block `nativeInsert`, `nativeUpdate`, and
106
+ * `nativeDelete` on entities that have PII, PHI, or PCI compliance fields.
107
+ *
108
+ * This prevents bypassing the EncryptedType's encryption by using raw
109
+ * queries. Call this in the tenant context middleware when creating the
110
+ * request-scoped EM.
111
+ *
112
+ * @returns A Proxy-wrapped EntityManager that throws on native query
113
+ * operations targeting compliance entities.
114
+ */
115
+ declare function wrapEmWithNativeQueryBlocking<T extends object>(em: T): T;
116
+
104
117
  declare class MissingEncryptionKeyError extends Error {
105
118
  readonly name: "MissingEncryptionKeyError";
106
119
  constructor(message?: string);
@@ -122,60 +135,83 @@ declare class FieldEncryptor {
122
135
  */
123
136
  deriveKey(tenantId: string): Buffer;
124
137
  /**
125
- * Encrypt a plaintext string for a specific tenant.
138
+ * Derive a deterministic IV from the key and plaintext using HMAC-SHA256,
139
+ * truncated to IV_BYTES. Same plaintext + same key → same IV → same
140
+ * ciphertext. This enables WHERE clause matching on encrypted columns
141
+ * while maintaining AES-256-GCM authenticated encryption.
142
+ *
143
+ * Meets SOC 2, HIPAA, PCI DSS, GDPR requirements for encryption at rest.
144
+ */
145
+ private deriveDeterministicIv;
146
+ /**
147
+ * Encrypt a plaintext string.
126
148
  *
127
- * @returns Format: `v1:{base64(iv)}:{base64(authTag)}:{base64(ciphertext)}`
149
+ * Uses deterministic encryption (HMAC-derived IV) so the same plaintext
150
+ * always produces the same ciphertext. This enables database WHERE clause
151
+ * matching and UNIQUE constraints on encrypted columns.
152
+ *
153
+ * @returns Format: `v2:{base64(iv)}:{base64(authTag)}:{base64(ciphertext)}`
128
154
  */
129
155
  encrypt(plaintext: string | null): string | null;
130
156
  encrypt(plaintext: string | null, tenantId: string): string | null;
131
157
  /**
132
158
  * Decrypt a ciphertext string produced by {@link encrypt}.
159
+ * Supports both v1 (random IV) and v2 (deterministic IV) formats.
133
160
  */
134
161
  decrypt(ciphertext: string | null): string | null;
135
162
  decrypt(ciphertext: string | null, tenantId: string): string | null;
136
163
  }
137
164
 
138
165
  /**
139
- * MikroORM EventSubscriber that enforces field-level encryption for
140
- * compliance-classified fields (PHI and PCI).
166
+ * Register the FieldEncryptor instance for use by EncryptedType.
167
+ * Call this once at application bootstrap (e.g., in mikro-orm.config.ts).
168
+ */
169
+ declare function registerEncryptor(encryptor: FieldEncryptor): void;
170
+ /**
171
+ * Run a callback with the given tenant ID available to EncryptedType
172
+ * for per-tenant key derivation.
173
+ */
174
+ declare function withEncryptionContext<T>(tenantId: string, fn: () => T): T;
175
+ /**
176
+ * Set the encryption tenant ID for the current async context.
177
+ * Called automatically by the tenant context middleware — users
178
+ * don't need to call this directly.
179
+ */
180
+ declare function setEncryptionTenantId(tenantId: string): void;
181
+ /**
182
+ * Get the current tenant ID. Returns empty string when no context is set
183
+ * (startup, seeders, better-auth, background jobs).
184
+ */
185
+ declare function getCurrentTenantId(): string;
186
+ /**
187
+ * MikroORM custom Type that transparently encrypts/decrypts values.
188
+ *
189
+ * Works with any JS type (string, number, boolean, object/JSON).
190
+ * Non-string values are JSON-serialized before encryption and
191
+ * JSON-parsed after decryption.
141
192
  *
142
- * - **onBeforeCreate / onBeforeUpdate**: Encrypts PHI/PCI fields before
143
- * database persistence. Throws `EncryptionRequiredError` if the encryption
144
- * key is unavailable.
145
- * - **onLoad**: Decrypts PHI/PCI fields after loading from the database.
146
- * Pre-migration plaintext (no `v1:` prefix) is returned as-is with a
147
- * console warning to support rolling deployments.
193
+ * DB column is always `text` the encrypted ciphertext is a string
194
+ * regardless of the original JS type.
148
195
  *
149
- * The tenant ID for key derivation is read from the EntityManager's filter
150
- * parameters (set by the tenant context middleware).
196
+ * Operates at the data conversion layer before the identity map,
197
+ * during hydration, and during persistence. Entities always hold
198
+ * their original JS values (plaintext).
151
199
  */
152
- declare class ComplianceEventSubscriber implements EventSubscriber {
153
- private readonly encryptor;
154
- constructor(encryptor: FieldEncryptor);
155
- beforeCreate(args: EventArgs<unknown>): Promise<void>;
156
- beforeUpdate(args: EventArgs<unknown>): Promise<void>;
157
- onLoad(args: EventArgs<unknown>): Promise<void>;
158
- private encryptFields;
159
- private decryptFields;
200
+ declare class EncryptedType extends Type<unknown, string | null> {
201
+ private readonly originalType;
160
202
  /**
161
- * Read the tenant ID from the EntityManager's filter parameters.
162
- * Returns undefined when no tenant context is set (startup, seeders,
163
- * better-auth). Callers skip encryption/decryption in that case.
203
+ * @param originalType - The original JS type hint ('string' | 'json' | 'number' | 'boolean').
204
+ * Used to determine serialization strategy.
164
205
  */
165
- private getTenantId;
206
+ constructor(originalType?: string);
207
+ convertToDatabaseValue(value: unknown, _platform: Platform, _context?: TransformContext): string | null;
208
+ convertToJSValue(value: string | null, _platform: Platform): unknown;
209
+ getColumnType(): string;
210
+ get runtimeType(): string;
211
+ ensureComparable(): boolean;
212
+ private serializeValue;
213
+ private deserializeValue;
166
214
  }
167
- /**
168
- * Wraps an EntityManager to block `nativeInsert`, `nativeUpdate`, and
169
- * `nativeDelete` on entities that have PHI or PCI compliance fields.
170
- *
171
- * This prevents bypassing the ComplianceEventSubscriber's encryption
172
- * by using raw queries. Call this in the tenant context middleware when
173
- * creating the request-scoped EM.
174
- *
175
- * @returns A Proxy-wrapped EntityManager that throws on native query
176
- * operations targeting compliance entities.
177
- */
178
- declare function wrapEmWithNativeQueryBlocking<T extends object>(em: T): T;
179
215
 
180
216
  /**
181
217
  * The name used to register the tenant isolation filter.
@@ -276,4 +312,4 @@ declare class RlsEventSubscriber implements EventSubscriber {
276
312
  */
277
313
  declare function setupRls(orm: MikroORM, config?: RlsConfig): void;
278
314
 
279
- export { ComplianceEventSubscriber, ComplianceLevel, ComplianceLevel as ComplianceLevelType, DecryptionError, EncryptionRequiredError, FieldEncryptor, MissingEncryptionKeyError, type ParsedDuration, RetentionAction, RetentionAction as RetentionActionType, RetentionDuration, type RetentionPolicy, type RlsConfig, RlsEventSubscriber, TENANT_FILTER_NAME, createTenantFilterDef, defineComplianceEntity, entityHasEncryptedFields, fp, getAllRetentionPolicies, getAllUserIdFields, getComplianceMetadata, getEntityComplianceFields, getEntityRetention, getEntityUserIdField, getSuperAdminContext, parseDuration, setupRls, setupTenantFilter, subtractDuration, wrapEmWithNativeQueryBlocking };
315
+ export { ComplianceLevel, ComplianceLevel as ComplianceLevelType, DecryptionError, EncryptedType, EncryptionRequiredError, FieldEncryptor, MissingEncryptionKeyError, type ParsedDuration, RetentionAction, RetentionAction as RetentionActionType, RetentionDuration, type RetentionPolicy, type RlsConfig, RlsEventSubscriber, TENANT_FILTER_NAME, createTenantFilterDef, defineComplianceEntity, entityHasEncryptedFields, fp, getAllRetentionPolicies, getAllUserIdFields, getComplianceMetadata, getCurrentTenantId, getEntityComplianceFields, getEntityRetention, getEntityUserIdField, getSuperAdminContext, parseDuration, registerEncryptor, setEncryptionTenantId, setupRls, setupTenantFilter, subtractDuration, withEncryptionContext, wrapEmWithNativeQueryBlocking };
@@ -1,4 +1,4 @@
1
- import { PropertyBuilders, EntityMetadataWithProperties, EntitySchemaWithMeta, InferEntityFromProperties, EventSubscriber, EventArgs, FilterDef, EntityManager, MikroORM } from '@mikro-orm/core';
1
+ import { PropertyBuilders, EntityMetadataWithProperties, EntitySchemaWithMeta, InferEntityFromProperties, Type, Platform, TransformContext, FilterDef, EntityManager, EventSubscriber, MikroORM } from '@mikro-orm/core';
2
2
 
3
3
  declare const ComplianceLevel: {
4
4
  readonly pii: "pii";
@@ -101,6 +101,19 @@ declare function defineComplianceEntity<const TName extends string, const TTable
101
101
  userIdField?: string;
102
102
  }): EntitySchemaWithMeta<TName, TTableName, InferEntityFromProperties<TProperties, TPK, TBase, TRepository, TForceObject>, TBase, TProperties>;
103
103
 
104
+ /**
105
+ * Wraps an EntityManager to block `nativeInsert`, `nativeUpdate`, and
106
+ * `nativeDelete` on entities that have PII, PHI, or PCI compliance fields.
107
+ *
108
+ * This prevents bypassing the EncryptedType's encryption by using raw
109
+ * queries. Call this in the tenant context middleware when creating the
110
+ * request-scoped EM.
111
+ *
112
+ * @returns A Proxy-wrapped EntityManager that throws on native query
113
+ * operations targeting compliance entities.
114
+ */
115
+ declare function wrapEmWithNativeQueryBlocking<T extends object>(em: T): T;
116
+
104
117
  declare class MissingEncryptionKeyError extends Error {
105
118
  readonly name: "MissingEncryptionKeyError";
106
119
  constructor(message?: string);
@@ -122,60 +135,83 @@ declare class FieldEncryptor {
122
135
  */
123
136
  deriveKey(tenantId: string): Buffer;
124
137
  /**
125
- * Encrypt a plaintext string for a specific tenant.
138
+ * Derive a deterministic IV from the key and plaintext using HMAC-SHA256,
139
+ * truncated to IV_BYTES. Same plaintext + same key → same IV → same
140
+ * ciphertext. This enables WHERE clause matching on encrypted columns
141
+ * while maintaining AES-256-GCM authenticated encryption.
142
+ *
143
+ * Meets SOC 2, HIPAA, PCI DSS, GDPR requirements for encryption at rest.
144
+ */
145
+ private deriveDeterministicIv;
146
+ /**
147
+ * Encrypt a plaintext string.
126
148
  *
127
- * @returns Format: `v1:{base64(iv)}:{base64(authTag)}:{base64(ciphertext)}`
149
+ * Uses deterministic encryption (HMAC-derived IV) so the same plaintext
150
+ * always produces the same ciphertext. This enables database WHERE clause
151
+ * matching and UNIQUE constraints on encrypted columns.
152
+ *
153
+ * @returns Format: `v2:{base64(iv)}:{base64(authTag)}:{base64(ciphertext)}`
128
154
  */
129
155
  encrypt(plaintext: string | null): string | null;
130
156
  encrypt(plaintext: string | null, tenantId: string): string | null;
131
157
  /**
132
158
  * Decrypt a ciphertext string produced by {@link encrypt}.
159
+ * Supports both v1 (random IV) and v2 (deterministic IV) formats.
133
160
  */
134
161
  decrypt(ciphertext: string | null): string | null;
135
162
  decrypt(ciphertext: string | null, tenantId: string): string | null;
136
163
  }
137
164
 
138
165
  /**
139
- * MikroORM EventSubscriber that enforces field-level encryption for
140
- * compliance-classified fields (PHI and PCI).
166
+ * Register the FieldEncryptor instance for use by EncryptedType.
167
+ * Call this once at application bootstrap (e.g., in mikro-orm.config.ts).
168
+ */
169
+ declare function registerEncryptor(encryptor: FieldEncryptor): void;
170
+ /**
171
+ * Run a callback with the given tenant ID available to EncryptedType
172
+ * for per-tenant key derivation.
173
+ */
174
+ declare function withEncryptionContext<T>(tenantId: string, fn: () => T): T;
175
+ /**
176
+ * Set the encryption tenant ID for the current async context.
177
+ * Called automatically by the tenant context middleware — users
178
+ * don't need to call this directly.
179
+ */
180
+ declare function setEncryptionTenantId(tenantId: string): void;
181
+ /**
182
+ * Get the current tenant ID. Returns empty string when no context is set
183
+ * (startup, seeders, better-auth, background jobs).
184
+ */
185
+ declare function getCurrentTenantId(): string;
186
+ /**
187
+ * MikroORM custom Type that transparently encrypts/decrypts values.
188
+ *
189
+ * Works with any JS type (string, number, boolean, object/JSON).
190
+ * Non-string values are JSON-serialized before encryption and
191
+ * JSON-parsed after decryption.
141
192
  *
142
- * - **onBeforeCreate / onBeforeUpdate**: Encrypts PHI/PCI fields before
143
- * database persistence. Throws `EncryptionRequiredError` if the encryption
144
- * key is unavailable.
145
- * - **onLoad**: Decrypts PHI/PCI fields after loading from the database.
146
- * Pre-migration plaintext (no `v1:` prefix) is returned as-is with a
147
- * console warning to support rolling deployments.
193
+ * DB column is always `text` the encrypted ciphertext is a string
194
+ * regardless of the original JS type.
148
195
  *
149
- * The tenant ID for key derivation is read from the EntityManager's filter
150
- * parameters (set by the tenant context middleware).
196
+ * Operates at the data conversion layer before the identity map,
197
+ * during hydration, and during persistence. Entities always hold
198
+ * their original JS values (plaintext).
151
199
  */
152
- declare class ComplianceEventSubscriber implements EventSubscriber {
153
- private readonly encryptor;
154
- constructor(encryptor: FieldEncryptor);
155
- beforeCreate(args: EventArgs<unknown>): Promise<void>;
156
- beforeUpdate(args: EventArgs<unknown>): Promise<void>;
157
- onLoad(args: EventArgs<unknown>): Promise<void>;
158
- private encryptFields;
159
- private decryptFields;
200
+ declare class EncryptedType extends Type<unknown, string | null> {
201
+ private readonly originalType;
160
202
  /**
161
- * Read the tenant ID from the EntityManager's filter parameters.
162
- * Returns undefined when no tenant context is set (startup, seeders,
163
- * better-auth). Callers skip encryption/decryption in that case.
203
+ * @param originalType - The original JS type hint ('string' | 'json' | 'number' | 'boolean').
204
+ * Used to determine serialization strategy.
164
205
  */
165
- private getTenantId;
206
+ constructor(originalType?: string);
207
+ convertToDatabaseValue(value: unknown, _platform: Platform, _context?: TransformContext): string | null;
208
+ convertToJSValue(value: string | null, _platform: Platform): unknown;
209
+ getColumnType(): string;
210
+ get runtimeType(): string;
211
+ ensureComparable(): boolean;
212
+ private serializeValue;
213
+ private deserializeValue;
166
214
  }
167
- /**
168
- * Wraps an EntityManager to block `nativeInsert`, `nativeUpdate`, and
169
- * `nativeDelete` on entities that have PHI or PCI compliance fields.
170
- *
171
- * This prevents bypassing the ComplianceEventSubscriber's encryption
172
- * by using raw queries. Call this in the tenant context middleware when
173
- * creating the request-scoped EM.
174
- *
175
- * @returns A Proxy-wrapped EntityManager that throws on native query
176
- * operations targeting compliance entities.
177
- */
178
- declare function wrapEmWithNativeQueryBlocking<T extends object>(em: T): T;
179
215
 
180
216
  /**
181
217
  * The name used to register the tenant isolation filter.
@@ -276,4 +312,4 @@ declare class RlsEventSubscriber implements EventSubscriber {
276
312
  */
277
313
  declare function setupRls(orm: MikroORM, config?: RlsConfig): void;
278
314
 
279
- export { ComplianceEventSubscriber, ComplianceLevel, ComplianceLevel as ComplianceLevelType, DecryptionError, EncryptionRequiredError, FieldEncryptor, MissingEncryptionKeyError, type ParsedDuration, RetentionAction, RetentionAction as RetentionActionType, RetentionDuration, type RetentionPolicy, type RlsConfig, RlsEventSubscriber, TENANT_FILTER_NAME, createTenantFilterDef, defineComplianceEntity, entityHasEncryptedFields, fp, getAllRetentionPolicies, getAllUserIdFields, getComplianceMetadata, getEntityComplianceFields, getEntityRetention, getEntityUserIdField, getSuperAdminContext, parseDuration, setupRls, setupTenantFilter, subtractDuration, wrapEmWithNativeQueryBlocking };
315
+ export { ComplianceLevel, ComplianceLevel as ComplianceLevelType, DecryptionError, EncryptedType, EncryptionRequiredError, FieldEncryptor, MissingEncryptionKeyError, type ParsedDuration, RetentionAction, RetentionAction as RetentionActionType, RetentionDuration, type RetentionPolicy, type RlsConfig, RlsEventSubscriber, TENANT_FILTER_NAME, createTenantFilterDef, defineComplianceEntity, entityHasEncryptedFields, fp, getAllRetentionPolicies, getAllUserIdFields, getComplianceMetadata, getCurrentTenantId, getEntityComplianceFields, getEntityRetention, getEntityUserIdField, getSuperAdminContext, parseDuration, registerEncryptor, setEncryptionTenantId, setupRls, setupTenantFilter, subtractDuration, withEncryptionContext, wrapEmWithNativeQueryBlocking };