@mikro-orm/core 7.2.0-dev.9 → 7.2.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.
Files changed (69) hide show
  1. package/EntityManager.d.ts +41 -7
  2. package/EntityManager.js +225 -45
  3. package/MikroORM.js +3 -0
  4. package/README.md +1 -0
  5. package/connections/Connection.d.ts +3 -1
  6. package/drivers/DatabaseDriver.d.ts +14 -5
  7. package/drivers/DatabaseDriver.js +151 -52
  8. package/drivers/IDatabaseDriver.d.ts +1 -0
  9. package/entity/Collection.js +4 -2
  10. package/entity/EntityFactory.js +6 -0
  11. package/entity/EntityLoader.d.ts +7 -1
  12. package/entity/EntityLoader.js +46 -11
  13. package/entity/EntityRepository.d.ts +4 -5
  14. package/entity/EntityRepository.js +7 -2
  15. package/entity/defineEntity.d.ts +48 -14
  16. package/entity/defineEntity.js +32 -1
  17. package/enums.d.ts +5 -1
  18. package/enums.js +2 -0
  19. package/errors.d.ts +36 -0
  20. package/errors.js +90 -0
  21. package/events/EventManager.js +6 -3
  22. package/exceptions.d.ts +5 -0
  23. package/exceptions.js +5 -0
  24. package/hydration/ObjectHydrator.d.ts +2 -0
  25. package/hydration/ObjectHydrator.js +12 -8
  26. package/index.d.ts +1 -1
  27. package/metadata/MetadataDiscovery.d.ts +3 -0
  28. package/metadata/MetadataDiscovery.js +127 -17
  29. package/metadata/MetadataStorage.js +17 -1
  30. package/metadata/types.d.ts +19 -3
  31. package/package.json +1 -1
  32. package/platforms/Platform.d.ts +22 -3
  33. package/platforms/Platform.js +59 -1
  34. package/types/BigIntType.d.ts +1 -0
  35. package/types/BigIntType.js +23 -0
  36. package/types/DateTimeType.d.ts +1 -0
  37. package/types/DateTimeType.js +8 -0
  38. package/types/StringType.d.ts +14 -3
  39. package/types/StringType.js +34 -4
  40. package/types/TextType.d.ts +2 -4
  41. package/types/TextType.js +2 -8
  42. package/types/Type.d.ts +11 -0
  43. package/types/Type.js +4 -4
  44. package/types/index.d.ts +2 -2
  45. package/typings.d.ts +56 -2
  46. package/typings.js +24 -1
  47. package/unit-of-work/ChangeSetPersister.js +17 -13
  48. package/unit-of-work/UnitOfWork.js +11 -4
  49. package/utils/Configuration.d.ts +15 -1
  50. package/utils/Configuration.js +11 -1
  51. package/utils/Cursor.d.ts +2 -0
  52. package/utils/Cursor.js +43 -33
  53. package/utils/DataloaderUtils.js +2 -1
  54. package/utils/EntityComparator.d.ts +2 -0
  55. package/utils/EntityComparator.js +7 -3
  56. package/utils/QueryHelper.d.ts +12 -0
  57. package/utils/QueryHelper.js +75 -4
  58. package/utils/RawQueryFragment.d.ts +6 -0
  59. package/utils/RawQueryFragment.js +15 -6
  60. package/utils/TransactionManager.js +1 -1
  61. package/utils/Utils.d.ts +14 -2
  62. package/utils/Utils.js +31 -4
  63. package/utils/env-vars.js +1 -0
  64. package/utils/index.d.ts +1 -0
  65. package/utils/index.js +1 -0
  66. package/utils/rls-utils.d.ts +35 -0
  67. package/utils/rls-utils.js +97 -0
  68. package/utils/upsert-utils.d.ts +9 -1
  69. package/utils/upsert-utils.js +26 -3
@@ -587,7 +587,7 @@ export class EntityComparator {
587
587
  }
588
588
  }
589
589
  else if (prop.polymorphic) {
590
- const discriminatorMapKey = `discriminatorMapReverse_${prop.name}`;
590
+ const discriminatorMapKey = `discriminatorMapReverse_${this.safeKey(prop.name)}`;
591
591
  const reverseMap = new Map();
592
592
  for (const [key, value] of Object.entries(prop.discriminatorMap)) {
593
593
  reverseMap.set(value, key);
@@ -758,10 +758,14 @@ export class EntityComparator {
758
758
  return this.getGenericComparator(this.wrap(prop.name), `!equals(last${this.wrap(prop.name)}, current${this.wrap(prop.name)})`);
759
759
  }
760
760
  wrap(key) {
761
- if (/^\[.*]$/.exec(key)) {
761
+ if (/^\[idx_\d+]$/.exec(key)) {
762
762
  return key;
763
763
  }
764
- return /^\w+$/.exec(key) ? `.${key}` : `['${key}']`;
764
+ return /^\w+$/.exec(key) ? `.${key}` : `[${this.quote(key)}]`;
765
+ }
766
+ /** Renders a key as a single-quoted JS string literal, safe to embed in generated code. */
767
+ quote(key) {
768
+ return `'${key.replace(/\\/g, '\\\\').replace(/'/g, "\\'")}'`;
765
769
  }
766
770
  safeKey(key) {
767
771
  return key.replace(/\W/g, '_');
@@ -37,6 +37,18 @@ export declare class QueryHelper {
37
37
  static inlinePrimaryKeyObjects<T extends object>(where: Dictionary, meta: EntityMetadata<T>, metadata: MetadataStorage, key?: string): boolean;
38
38
  static processWhere<T extends object>(options: ProcessWhereOptions<T>): FilterQuery<T>;
39
39
  static getActiveFilters<T>(meta: EntityMetadata<T>, options: FilterOptions | undefined, filters: Dictionary<FilterDef>): FilterDef[];
40
+ /** @internal Sentinel wrapping for arguments accessed while statically resolving an `rls` filter condition. */
41
+ static readonly RLS_SENTINEL_PREFIX = "__mikro_rls_arg__";
42
+ /** @internal */
43
+ static readonly RLS_SENTINEL_SUFFIX = "__";
44
+ /**
45
+ * Resolves an `rls` filter's condition to a static `FilterQuery`. Function conditions are called with a proxy `args`
46
+ * that yields a unique sentinel per accessed argument, real `type`/`entityName` strings (validated to not affect the
47
+ * result), and a poison proxy or `undefined` for the remaining runtime-only parameters.
48
+ *
49
+ * @internal
50
+ */
51
+ static resolveRlsFilterCond(filter: FilterDef, accessed: Set<string>, entityName?: string): Dictionary;
40
52
  static mergePropertyFilters(propFilters: FilterOptions | undefined, options: FilterOptions | undefined): FilterOptions | undefined;
41
53
  static isFilterActive<T>(meta: EntityMetadata<T>, filterName: string, filter: FilterDef, options: Dictionary<boolean | Dictionary>): boolean;
42
54
  static processCustomType<T extends object>(prop: EntityProperty<T>, cond: FilterQuery<T>, platform: Platform, key?: string, fromQuery?: boolean): FilterQuery<T>;
@@ -1,9 +1,10 @@
1
1
  import { Reference } from '../entity/Reference.js';
2
- import { Utils } from './Utils.js';
2
+ import { DANGEROUS_PROPERTY_NAMES, Utils } from './Utils.js';
3
3
  import { ARRAY_OPERATORS, GroupOperator, JSON_KEY_OPERATORS, ReferenceKind } from '../enums.js';
4
4
  import { JsonType } from '../types/JsonType.js';
5
5
  import { helper } from '../entity/wrap.js';
6
6
  import { isRaw, Raw } from './RawQueryFragment.js';
7
+ import { MetadataError } from '../errors.js';
7
8
  /** @internal */
8
9
  export class QueryHelper {
9
10
  static SUPPORTED_OPERATORS = ['>', '<', '<=', '>=', '!', '!='];
@@ -237,7 +238,11 @@ export class QueryHelper {
237
238
  // oxfmt-ignore
238
239
  const isJsonProperty = prop?.customType instanceof JsonType && !isRaw(value) && (Utils.isPlainObject(value) ? !['$eq', '$elemMatch'].includes(Object.keys(value)[0]) : !Array.isArray(value));
239
240
  if (isJsonProperty && prop?.kind !== ReferenceKind.EMBEDDED) {
240
- return this.processJsonCondition(o, value, [prop.fieldNames[0]], platform, aliased);
241
+ // an explicit alias prefix (e.g. `a.meta`) has to survive, otherwise the condition falls back to the root alias
242
+ const explicitAlias = key.includes('.')
243
+ ? key.split('.').slice(0, -1).join('.')
244
+ : undefined;
245
+ return this.processJsonCondition(o, value, [prop.fieldNames[0]], platform, aliased && explicitAlias != null ? explicitAlias : aliased);
241
246
  }
242
247
  // oxfmt-ignore
243
248
  if (Array.isArray(value) && !Utils.isOperator(key) && !QueryHelper.isSupportedOperator(key) && !(customExpression && Raw.getKnownFragment(key).params.length > 0) && options.type !== 'orderBy') {
@@ -269,7 +274,11 @@ export class QueryHelper {
269
274
  options.forEach(filter => (opts[filter] = true));
270
275
  }
271
276
  else if (Utils.isPlainObject(options)) {
272
- Object.keys(options).forEach(filter => (opts[filter] = options[filter]));
277
+ Object.keys(options).forEach(filter => {
278
+ if (!DANGEROUS_PROPERTY_NAMES.includes(filter)) {
279
+ opts[filter] = options[filter];
280
+ }
281
+ });
273
282
  }
274
283
  return Object.keys(filters)
275
284
  .filter(f => QueryHelper.isFilterActive(meta, f, filters[f], opts))
@@ -278,6 +287,68 @@ export class QueryHelper {
278
287
  return filters[f];
279
288
  });
280
289
  }
290
+ /** @internal Sentinel wrapping for arguments accessed while statically resolving an `rls` filter condition. */
291
+ static RLS_SENTINEL_PREFIX = '__mikro_rls_arg__';
292
+ /** @internal */
293
+ static RLS_SENTINEL_SUFFIX = '__';
294
+ /**
295
+ * Resolves an `rls` filter's condition to a static `FilterQuery`. Function conditions are called with a proxy `args`
296
+ * that yields a unique sentinel per accessed argument, real `type`/`entityName` strings (validated to not affect the
297
+ * result), and a poison proxy or `undefined` for the remaining runtime-only parameters.
298
+ *
299
+ * @internal
300
+ */
301
+ static resolveRlsFilterCond(filter, accessed, entityName) {
302
+ if (!(filter.cond instanceof Function)) {
303
+ return filter.cond;
304
+ }
305
+ const args = new Proxy({}, {
306
+ get: (_target, prop) => {
307
+ if (typeof prop === 'symbol') {
308
+ // e.g. coercing `args` itself in a template literal triggers a `Symbol.toPrimitive` lookup
309
+ throw MetadataError.rlsFilterUnsupportedCond(filter.name);
310
+ }
311
+ accessed.add(prop);
312
+ return `${this.RLS_SENTINEL_PREFIX}${prop}${this.RLS_SENTINEL_SUFFIX}`;
313
+ },
314
+ });
315
+ const poison = new Proxy({}, {
316
+ get: () => {
317
+ throw MetadataError.rlsFilterDependsOnRuntimeState(filter.name);
318
+ },
319
+ });
320
+ // property access on the poison proxies throws, but equality/truthiness checks (`type === 'read'`,
321
+ // `entityName === 'X'`, `options ? a : b`) cannot be trapped — vary all three across the evaluations and require
322
+ // identical results, so a command-, entity-, or options-dependent condition cannot silently compile one branch.
323
+ // `entityName` uses the real class name (plus two derived-distinct variants) so an `=== '<name>'` check diverges;
324
+ // `options` alternates the poison proxy and `undefined` so a truthiness check flips. `em` stays poison throughout.
325
+ const name = entityName ?? `${this.RLS_SENTINEL_PREFIX}entity${this.RLS_SENTINEL_SUFFIX}`;
326
+ const evaluate = (type, entity, options) => {
327
+ let result;
328
+ try {
329
+ result = filter.cond(args, type, poison, options, entity);
330
+ }
331
+ catch (e) {
332
+ // a raw TypeError from touching the `undefined` options/em must fail closed like the poison proxy does,
333
+ // but the descriptive MetadataErrors thrown above are already correct — let them surface unchanged
334
+ if (e instanceof MetadataError) {
335
+ throw e;
336
+ }
337
+ throw MetadataError.rlsFilterDependsOnRuntimeState(filter.name);
338
+ }
339
+ if (result instanceof Promise) {
340
+ throw MetadataError.rlsFilterDependsOnRuntimeState(filter.name);
341
+ }
342
+ return result;
343
+ };
344
+ const read = evaluate('read', name, poison);
345
+ const update = evaluate('update', `${name}\0a`, undefined);
346
+ const del = evaluate('delete', `${name}\0b`, poison);
347
+ if (JSON.stringify(read) !== JSON.stringify(update) || JSON.stringify(read) !== JSON.stringify(del)) {
348
+ throw MetadataError.rlsFilterDependsOnRuntimeState(filter.name);
349
+ }
350
+ return read;
351
+ }
281
352
  static mergePropertyFilters(propFilters, options) {
282
353
  if (!options || !propFilters || options === true || propFilters === true) {
283
354
  return options ?? propFilters;
@@ -297,7 +368,7 @@ export class QueryHelper {
297
368
  return Utils.mergeConfig({}, propFilters, options);
298
369
  }
299
370
  static isFilterActive(meta, filterName, filter, options) {
300
- if (filter.entity && !filter.entity.includes(meta.className)) {
371
+ if (filter.entity && !Utils.asArray(filter.entity).some(e => Utils.matchesEntity(e, meta))) {
301
372
  return false;
302
373
  }
303
374
  if (options[filterName] === false) {
@@ -61,6 +61,12 @@ export declare const ALIAS_REPLACEMENT_RE = "\\[::alias::\\]";
61
61
  * await em.find(User, { [raw(alias => `lower(${alias}.name)`)]: name.toLowerCase() });
62
62
  * ```
63
63
  *
64
+ * Named parameters are supported via an object of parameters, use `:name` for values and `:name:` for identifiers:
65
+ *
66
+ * ```ts
67
+ * raw('select :col: from geo where city = :city or region = :city', { col: 'city', city: 'Brno' });
68
+ * ```
69
+ *
64
70
  * You can also use the `sql` tagged template function, which works the same, but supports only the simple string signature:
65
71
  *
66
72
  * ```ts
@@ -146,6 +146,12 @@ export const ALIAS_REPLACEMENT_RE = '\\[::alias::\\]';
146
146
  * await em.find(User, { [raw(alias => `lower(${alias}.name)`)]: name.toLowerCase() });
147
147
  * ```
148
148
  *
149
+ * Named parameters are supported via an object of parameters, use `:name` for values and `:name:` for identifiers:
150
+ *
151
+ * ```ts
152
+ * raw('select :col: from geo where city = :city or region = :city', { col: 'city', city: 'Brno' });
153
+ * ```
154
+ *
149
155
  * You can also use the `sql` tagged template function, which works the same, but supports only the simple string signature:
150
156
  *
151
157
  * ```ts
@@ -191,13 +197,16 @@ export function raw(sql, params) {
191
197
  return Utils.getPrimaryKeyHash(sql);
192
198
  }
193
199
  if (typeof params === 'object' && !Array.isArray(params)) {
194
- const pairs = Object.entries(params);
200
+ const dict = params;
195
201
  const objectParams = [];
196
- for (const [key, value] of pairs) {
197
- sql = sql.replace(`:${key}:`, '??');
198
- sql = sql.replace(`:${key}`, '?');
199
- objectParams.push(value);
200
- }
202
+ // single left-to-right scan keeps values in SQL-placeholder order while `::` casts and unknown tokens stay untouched
203
+ sql = sql.replace(/(?<!:):([$\w]+)(:(?!:))?/g, (match, key, identifier) => {
204
+ if (!Object.hasOwn(dict, key)) {
205
+ return match;
206
+ }
207
+ objectParams.push(dict[key]);
208
+ return identifier ? '??' : '?';
209
+ });
201
210
  return new RawQueryFragment(sql, objectParams);
202
211
  }
203
212
  return new RawQueryFragment(sql, params);
@@ -239,7 +239,7 @@ export class TransactionManager {
239
239
  return TransactionContext.create(fork, () => fork.getConnection().transactional(async (trx) => {
240
240
  fork.setTransactionContext(trx);
241
241
  return this.executeTransactionFlow(fork, cb, propagateToUpperContext, em);
242
- }, { ...options, eventBroadcaster }));
242
+ }, { sessionContext: fork.getTransactionSessionContext(), ...options, eventBroadcaster }));
243
243
  }
244
244
  /**
245
245
  * Executes transaction workflow with entity synchronization.
package/utils/Utils.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type { CompiledFunctions, Dictionary, EntityData, EntityDictionary, EntityKey, EntityMetadata, EntityName, EntityProperty, Primary } from '../typings.js';
1
+ import type { CompiledFunctions, Dictionary, EntityCtor, EntityData, EntityDictionary, EntityKey, EntityMetadata, EntityName, EntityProperty, Primary } from '../typings.js';
2
2
  import type { Platform } from '../platforms/Platform.js';
3
3
  import { ScalarReference } from '../entity/Reference.js';
4
4
  import { Collection } from '../entity/Collection.js';
@@ -33,6 +33,8 @@ export declare function parseJsonSafe<T = unknown>(value: unknown): T;
33
33
  export declare class Utils {
34
34
  #private;
35
35
  static readonly PK_SEPARATOR = "~~~";
36
+ /** Default session variable name backing an RLS filter argument (`current_setting('mikro.<filter>.<arg>')`). */
37
+ static getRlsSettingName(filterName: string, argName: string): string;
36
38
  /**
37
39
  * Checks if the argument is instance of `Object`. Returns false for arrays.
38
40
  */
@@ -58,7 +60,7 @@ export declare class Utils {
58
60
  /**
59
61
  * Gets array without duplicates.
60
62
  */
61
- static unique<T = string>(items: T[]): T[];
63
+ static unique<T = string>(items: T[], equals?: (a: T, b: T) => boolean): T[];
62
64
  /**
63
65
  * Merges all sources into the target recursively.
64
66
  */
@@ -129,6 +131,16 @@ export declare class Utils {
129
131
  * Gets string name of given class.
130
132
  */
131
133
  static className<T>(classOrName: string | EntityName<T>): string;
134
+ /**
135
+ * Normalizes an entity reference for identity-safe matching: keeps class references
136
+ * (minifiers can mangle two classes to the same name) and falls back to the class name otherwise.
137
+ */
138
+ static classOrName<T>(classOrName: string | EntityName<T>): EntityCtor<T> | string;
139
+ /**
140
+ * Checks whether the given entity reference points at the given metadata,
141
+ * comparing classes by identity and strings by class name.
142
+ */
143
+ static matchesEntity<T>(classOrName: string | EntityName<T>, meta: EntityMetadata<any>): boolean;
132
144
  static extractChildElements(items: readonly string[], prefix: string, allSymbol?: string): string[];
133
145
  /**
134
146
  * Tries to detect TypeScript support.
package/utils/Utils.js CHANGED
@@ -153,7 +153,11 @@ export function parseJsonSafe(value) {
153
153
  /** Collection of general-purpose utility methods used throughout the ORM. */
154
154
  export class Utils {
155
155
  static PK_SEPARATOR = '~~~';
156
- static #ORM_VERSION = '7.2.0-dev.9';
156
+ static #ORM_VERSION = '7.2.0';
157
+ /** Default session variable name backing an RLS filter argument (`current_setting('mikro.<filter>.<arg>')`). */
158
+ static getRlsSettingName(filterName, argName) {
159
+ return `mikro.${filterName}.${argName}`;
160
+ }
157
161
  /**
158
162
  * Checks if the argument is instance of `Object`. Returns false for arrays.
159
163
  */
@@ -216,10 +220,13 @@ export class Utils {
216
220
  /**
217
221
  * Gets array without duplicates.
218
222
  */
219
- static unique(items) {
223
+ static unique(items, equals) {
220
224
  if (items.length < 2) {
221
225
  return items;
222
226
  }
227
+ if (equals) {
228
+ return items.filter((a, idx) => items.findIndex(b => equals(a, b)) === idx);
229
+ }
223
230
  return [...new Set(items)];
224
231
  }
225
232
  /**
@@ -562,6 +569,21 @@ export class Utils {
562
569
  }
563
570
  return classOrName.name;
564
571
  }
572
+ /**
573
+ * Normalizes an entity reference for identity-safe matching: keeps class references
574
+ * (minifiers can mangle two classes to the same name) and falls back to the class name otherwise.
575
+ */
576
+ static classOrName(classOrName) {
577
+ return typeof classOrName === 'function' ? classOrName : Utils.className(classOrName);
578
+ }
579
+ /**
580
+ * Checks whether the given entity reference points at the given metadata,
581
+ * comparing classes by identity and strings by class name.
582
+ */
583
+ static matchesEntity(classOrName, meta) {
584
+ const ref = Utils.classOrName(classOrName);
585
+ return typeof ref === 'function' ? ref === meta.class : ref === meta.className;
586
+ }
565
587
  static extractChildElements(items, prefix, allSymbol) {
566
588
  return items
567
589
  .filter(field => field === allSymbol || field.startsWith(`${prefix}.`))
@@ -584,7 +606,8 @@ export class Utils {
584
606
  return (arg.includes('ts-node') || // check for ts-node loader
585
607
  arg.includes('@swc-node/register') || // check for swc-node/register loader
586
608
  arg.includes('node_modules/tsx/') || // check for tsx loader
587
- arg.includes('@oxc-node/core') // check for oxc-node loader
609
+ arg.includes('@oxc-node/core') || // check for oxc-node loader
610
+ arg.includes('@nubjs/loader') // check for Nub loader
588
611
  );
589
612
  }));
590
613
  }
@@ -836,7 +859,11 @@ export class Utils {
836
859
  return await import(module);
837
860
  }
838
861
  catch (err) {
839
- if (warning && err.code === 'ERR_MODULE_NOT_FOUND') {
862
+ // only a missing module is expected here, anything else is a real failure inside the module
863
+ if (!['ERR_MODULE_NOT_FOUND', 'MODULE_NOT_FOUND'].includes(err.code)) {
864
+ throw err;
865
+ }
866
+ if (warning) {
840
867
  // eslint-disable-next-line no-console
841
868
  console.warn(warning);
842
869
  }
package/utils/env-vars.js CHANGED
@@ -88,6 +88,7 @@ export function loadEnvironmentVars() {
88
88
  read3('createForeignKeyConstraints', bool);
89
89
  read3('ignoreTriggers', bool);
90
90
  read3('ignoreRoutines', bool);
91
+ read3('ignorePolicies', bool);
91
92
  cleanup(ret, 'schemaGenerator');
92
93
  ret.seeder = {};
93
94
  const read4 = read.bind(null, ret.seeder, 'MIKRO_ORM_SEEDER_');
package/utils/index.d.ts CHANGED
@@ -10,4 +10,5 @@ export * from './EntityComparator.js';
10
10
  export * from './RawQueryFragment.js';
11
11
  export * from './env-vars.js';
12
12
  export * from './upsert-utils.js';
13
+ export * from './rls-utils.js';
13
14
  export * from './partition-utils.js';
package/utils/index.js CHANGED
@@ -10,4 +10,5 @@ export * from './EntityComparator.js';
10
10
  export * from './RawQueryFragment.js';
11
11
  export * from './env-vars.js';
12
12
  export * from './upsert-utils.js';
13
+ export * from './rls-utils.js';
13
14
  export * from './partition-utils.js';
@@ -0,0 +1,35 @@
1
+ import type { MetadataStorage } from '../metadata/MetadataStorage.js';
2
+ import type { Dictionary, FilterDef } from '../typings.js';
3
+ /** An `rls`-flagged filter definition together with the entity it is declared on. @internal */
4
+ export interface RlsFilterEntry {
5
+ filter: FilterDef;
6
+ entityName: string;
7
+ }
8
+ /**
9
+ * Collects all `rls`-flagged filter definitions with the given name (only entity-scoped filters can be `rls`).
10
+ * The full name -> defs lookup is built once and cached on the shared (immutable) MetadataStorage, so repeated
11
+ * `setFilterParams` calls and forks reuse it instead of walking every entity each time.
12
+ * @internal
13
+ */
14
+ export declare function findRlsFilterDefs(metadata: MetadataStorage, name: string): RlsFilterEntry[];
15
+ /**
16
+ * Drops the cached `rls` filter lookup — `MikroORM.discoverEntity()` mutates the shared MetadataStorage,
17
+ * so a lookup built before the call would miss the newly discovered filters.
18
+ *
19
+ * @internal
20
+ */
21
+ export declare function clearRlsFilterDefsCache(metadata: MetadataStorage): void;
22
+ /**
23
+ * Computes the `rls` session variables a set of same-named filter defs stages for the given args, mirroring the
24
+ * policy compilation (`current_setting` names and custom `setting` binding). Shared by staging and `fork({ session })`.
25
+ * @internal
26
+ */
27
+ export declare function computeRlsFilterVariables(filters: RlsFilterEntry[], args: Dictionary): Dictionary<string | number | boolean | Date>;
28
+ /**
29
+ * Computes which staged session variables a `setFilterParams` call may prune: the variables the OLD args staged for
30
+ * this filter that the new args no longer set, minus any variable another filter's current params still stage
31
+ * (a custom `setting` name can be shared by differently named filters). Recomputing from the old args rather than
32
+ * matching by prefix keeps a filter named `tenant` from also pruning a `tenant.x` filter's `mikro.tenant.x.*` variables.
33
+ * @internal
34
+ */
35
+ export declare function computeRemovedRlsVariables(metadata: MetadataStorage, name: string, filters: RlsFilterEntry[], previousArgs: Dictionary, nextVariables: Dictionary, allFilterParams: Dictionary<Dictionary>): string[];
@@ -0,0 +1,97 @@
1
+ import { MetadataError, ValidationError } from '../errors.js';
2
+ import { QueryHelper } from './QueryHelper.js';
3
+ import { Utils } from './Utils.js';
4
+ /** Lazily-built `rls` filter lookup keyed by the shared (immutable) MetadataStorage, so all forks reuse it. */
5
+ const rlsFilterDefs = new WeakMap();
6
+ /**
7
+ * Collects all `rls`-flagged filter definitions with the given name (only entity-scoped filters can be `rls`).
8
+ * The full name -> defs lookup is built once and cached on the shared (immutable) MetadataStorage, so repeated
9
+ * `setFilterParams` calls and forks reuse it instead of walking every entity each time.
10
+ * @internal
11
+ */
12
+ export function findRlsFilterDefs(metadata, name) {
13
+ let cache = rlsFilterDefs.get(metadata);
14
+ if (!cache) {
15
+ cache = new Map();
16
+ for (const meta of metadata) {
17
+ for (const filterName of Object.keys(meta.filters)) {
18
+ const filter = meta.filters[filterName];
19
+ if (!filter.rls) {
20
+ continue;
21
+ }
22
+ const defs = cache.get(filterName) ?? [];
23
+ // inheritance shares the same filter object across base and child metadata — keep a single entry
24
+ if (!defs.some(d => d.filter === filter)) {
25
+ defs.push({ filter, entityName: meta.className });
26
+ }
27
+ cache.set(filterName, defs);
28
+ }
29
+ }
30
+ rlsFilterDefs.set(metadata, cache);
31
+ }
32
+ return cache.get(name) ?? [];
33
+ }
34
+ /**
35
+ * Drops the cached `rls` filter lookup — `MikroORM.discoverEntity()` mutates the shared MetadataStorage,
36
+ * so a lookup built before the call would miss the newly discovered filters.
37
+ *
38
+ * @internal
39
+ */
40
+ export function clearRlsFilterDefsCache(metadata) {
41
+ rlsFilterDefs.delete(metadata);
42
+ }
43
+ /**
44
+ * Computes the `rls` session variables a set of same-named filter defs stages for the given args, mirroring the
45
+ * policy compilation (`current_setting` names and custom `setting` binding). Shared by staging and `fork({ session })`.
46
+ * @internal
47
+ */
48
+ export function computeRlsFilterVariables(filters, args) {
49
+ const variables = {};
50
+ for (const { filter, entityName } of filters) {
51
+ const setting = typeof filter.rls === 'object' ? filter.rls.setting : undefined;
52
+ let settingArg;
53
+ if (setting) {
54
+ // mirror the policy compilation — a custom `setting` binds the single argument the condition accesses
55
+ const accessed = new Set();
56
+ QueryHelper.resolveRlsFilterCond(filter, accessed, entityName);
57
+ if (accessed.size > 1) {
58
+ throw MetadataError.rlsFilterMultiArgSetting(filter.name, [...accessed]);
59
+ }
60
+ settingArg = [...accessed][0];
61
+ }
62
+ for (const key of Object.keys(args)) {
63
+ const value = args[key];
64
+ // treat `undefined` like an omitted arg — staging it would serialize as the literal string 'undefined'
65
+ if (value === undefined) {
66
+ continue;
67
+ }
68
+ // a non-scalar arg has no equivalent in the compiled `= current_setting(...)` comparison — the app-level
69
+ // filter would apply `$in`/`is null` semantics while the policy compares against `String(value)`
70
+ if (value === null || (typeof value === 'object' && !(value instanceof Date))) {
71
+ throw ValidationError.cannotStageNonScalarSessionVariable(filter.name, key);
72
+ }
73
+ const settingName = key === settingArg ? setting : Utils.getRlsSettingName(filter.name, key);
74
+ variables[settingName] = value;
75
+ }
76
+ }
77
+ return variables;
78
+ }
79
+ /**
80
+ * Computes which staged session variables a `setFilterParams` call may prune: the variables the OLD args staged for
81
+ * this filter that the new args no longer set, minus any variable another filter's current params still stage
82
+ * (a custom `setting` name can be shared by differently named filters). Recomputing from the old args rather than
83
+ * matching by prefix keeps a filter named `tenant` from also pruning a `tenant.x` filter's `mikro.tenant.x.*` variables.
84
+ * @internal
85
+ */
86
+ export function computeRemovedRlsVariables(metadata, name, filters, previousArgs, nextVariables, allFilterParams) {
87
+ const removed = Object.keys(computeRlsFilterVariables(filters, previousArgs)).filter(key => !(key in nextVariables));
88
+ const keptByOthers = new Set();
89
+ for (const otherName of removed.length > 0 ? Object.keys(allFilterParams) : []) {
90
+ if (otherName !== name) {
91
+ for (const key of Object.keys(computeRlsFilterVariables(findRlsFilterDefs(metadata, otherName), allFilterParams[otherName]))) {
92
+ keptByOthers.add(key);
93
+ }
94
+ }
95
+ }
96
+ return removed.filter(key => !keptByOthers.has(key));
97
+ }
@@ -1,8 +1,16 @@
1
- import type { EntityData, EntityMetadata, FilterQuery } from '../typings.js';
1
+ import type { EntityData, EntityKey, EntityMetadata, FilterQuery } from '../typings.js';
2
2
  import type { UpsertOptions } from '../drivers/IDatabaseDriver.js';
3
3
  import { type Raw } from '../utils/RawQueryFragment.js';
4
4
  /** @internal */
5
5
  export declare function getOnConflictFields<T>(meta: EntityMetadata<T> | undefined, data: EntityData<T>, uniqueFields: (keyof T)[] | Raw, options: UpsertOptions<T>): (keyof T)[];
6
+ /**
7
+ * Detects properties that will get their value generated by an `onCreate` hook during the upsert,
8
+ * i.e. those with an `onCreate` hook and no value provided. Such values are meant for the insert
9
+ * clause only and must not overwrite an existing row via the `on conflict do update set` clause.
10
+ * The property filter mirrors `EntityFactory.assignDefaultValues`.
11
+ * @internal
12
+ */
13
+ export declare function getOnCreateGeneratedFields<T extends object>(meta: EntityMetadata<T>, data: T | EntityData<T>): EntityKey<T>[];
6
14
  /** @internal */
7
15
  export declare function getOnConflictReturningFields<T, P extends string>(meta: EntityMetadata<T> | undefined, data: EntityData<T>, uniqueFields: (keyof T)[] | Raw, options: UpsertOptions<T, P>): (keyof T)[] | '*';
8
16
  /** @internal */
@@ -72,6 +72,22 @@ export function getOnConflictFields(meta, data, uniqueFields, options) {
72
72
  }
73
73
  return keys;
74
74
  }
75
+ /**
76
+ * Detects properties that will get their value generated by an `onCreate` hook during the upsert,
77
+ * i.e. those with an `onCreate` hook and no value provided. Such values are meant for the insert
78
+ * clause only and must not overwrite an existing row via the `on conflict do update set` clause.
79
+ * The property filter mirrors `EntityFactory.assignDefaultValues`.
80
+ * @internal
81
+ */
82
+ export function getOnCreateGeneratedFields(meta, data) {
83
+ return meta.props
84
+ .filter(prop => prop.onCreate &&
85
+ !prop.embedded &&
86
+ ![ReferenceKind.MANY_TO_ONE, ReferenceKind.ONE_TO_ONE].includes(prop.kind) &&
87
+ !(prop.getter && !prop.setter) &&
88
+ data[prop.name] == null)
89
+ .map(prop => prop.name);
90
+ }
75
91
  /** @internal */
76
92
  export function getOnConflictReturningFields(meta, data, uniqueFields, options) {
77
93
  /* v8 ignore next */
@@ -122,7 +138,14 @@ function getPropertyValue(obj, key) {
122
138
  }
123
139
  /** @internal */
124
140
  export function getWhereCondition(meta, onConflictFields, data, where) {
125
- const unique = onConflictFields ?? meta.props.filter(p => p.unique).map(p => p.name);
141
+ // TPT children do not inherit the unique flags and indexes of their parent tables
142
+ const uniqueProps = new Set();
143
+ const uniques = [];
144
+ for (let current = meta; current; current = current.tptParent) {
145
+ current.props.filter(p => p.unique).forEach(p => uniqueProps.add(p.name));
146
+ uniques.push(...current.uniques);
147
+ }
148
+ const unique = onConflictFields ?? [...uniqueProps];
126
149
  const propIndex = !isRaw(unique) &&
127
150
  unique.findIndex(p => data[p] ?? data[p.substring(0, p.indexOf('.'))] != null);
128
151
  if (onConflictFields || where == null) {
@@ -136,8 +159,8 @@ export function getWhereCondition(meta, onConflictFields, data, where) {
136
159
  }
137
160
  where = { [key]: getPropertyValue(data, unique[propIndex]) };
138
161
  }
139
- else if (meta.uniques.length > 0) {
140
- for (const u of meta.uniques) {
162
+ else {
163
+ for (const u of uniques) {
141
164
  if (Utils.asArray(u.properties).every(p => data[p] != null)) {
142
165
  where = Utils.asArray(u.properties).reduce((o, key) => {
143
166
  o[key] = data[key];