@mikro-orm/core 7.1.16-dev.10 → 7.1.16-dev.12
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.
- package/EntityManager.d.ts +41 -7
- package/EntityManager.js +202 -42
- package/MikroORM.d.ts +4 -0
- package/MikroORM.js +9 -0
- package/README.md +1 -0
- package/cache/FileCacheAdapter.js +1 -1
- package/connections/Connection.d.ts +10 -1
- package/connections/Connection.js +9 -0
- package/drivers/DatabaseDriver.d.ts +14 -5
- package/drivers/DatabaseDriver.js +145 -55
- package/entity/Collection.js +4 -2
- package/entity/EntityLoader.js +1 -1
- package/entity/EntityRepository.d.ts +4 -5
- package/entity/EntityRepository.js +2 -1
- package/entity/defineEntity.d.ts +17 -1
- package/entity/defineEntity.js +31 -0
- package/enums.d.ts +3 -1
- package/errors.d.ts +35 -0
- package/errors.js +87 -0
- package/exceptions.d.ts +5 -0
- package/exceptions.js +5 -0
- package/index.d.ts +1 -1
- package/metadata/MetadataDiscovery.d.ts +3 -0
- package/metadata/MetadataDiscovery.js +94 -7
- package/metadata/types.d.ts +19 -3
- package/package.json +1 -1
- package/platforms/Platform.d.ts +19 -1
- package/platforms/Platform.js +56 -0
- package/types/BigIntType.d.ts +1 -0
- package/types/BigIntType.js +23 -0
- package/types/DateTimeType.d.ts +1 -0
- package/types/DateTimeType.js +8 -0
- package/types/StringType.d.ts +14 -3
- package/types/StringType.js +34 -4
- package/types/TextType.d.ts +2 -4
- package/types/TextType.js +2 -8
- package/types/Type.d.ts +11 -0
- package/types/index.d.ts +2 -2
- package/typings.d.ts +47 -0
- package/typings.js +1 -0
- package/unit-of-work/UnitOfWork.js +1 -0
- package/utils/Configuration.d.ts +21 -1
- package/utils/Configuration.js +12 -1
- package/utils/Cursor.d.ts +2 -0
- package/utils/Cursor.js +43 -33
- package/utils/QueryHelper.d.ts +12 -0
- package/utils/QueryHelper.js +63 -0
- package/utils/RawQueryFragment.d.ts +6 -0
- package/utils/RawQueryFragment.js +15 -6
- package/utils/RequestContext.d.ts +2 -2
- package/utils/RequestContext.js +11 -2
- package/utils/TransactionManager.js +1 -1
- package/utils/Utils.d.ts +2 -0
- package/utils/Utils.js +7 -2
- package/utils/env-vars.js +2 -0
- package/utils/index.d.ts +1 -0
- package/utils/index.js +1 -0
- package/utils/rls-utils.d.ts +35 -0
- package/utils/rls-utils.js +97 -0
|
@@ -65,13 +65,22 @@ export declare abstract class DatabaseDriver<C extends Connection> implements ID
|
|
|
65
65
|
where: FilterQuery<T>;
|
|
66
66
|
};
|
|
67
67
|
/**
|
|
68
|
-
*
|
|
69
|
-
*
|
|
70
|
-
*
|
|
71
|
-
*
|
|
68
|
+
* Resolves a leaf `orderBy` direction into the two flags the rewritten `orderBy` and the cursor
|
|
69
|
+
* condition have to agree on, or pagination skips rows at the null boundary. A placement the caller
|
|
70
|
+
* asked for wins where the platform can honor it, anything else follows the platform's own default,
|
|
71
|
+
* which is what the untouched `orderBy` will get.
|
|
72
|
+
*/
|
|
73
|
+
private parseCursorDirection;
|
|
74
|
+
/**
|
|
75
|
+
* Restores the JS value of a single cursor offset. String-cursor values (`fromJson`) are decoded
|
|
76
|
+
* JSON: types implementing `fromJSON` own the round trip, other custom types restore via
|
|
77
|
+
* `convertToJSValue`, with `Date` healing for date-like columns based on the property type (never
|
|
78
|
+
* based on the string shape alone). POJO values are already JS values and only date-like strings
|
|
79
|
+
* are healed. Values compared against a JSON document keep their serialized form instead, unless
|
|
80
|
+
* the platform preserves native date types inside JSON documents (mongo).
|
|
72
81
|
*/
|
|
73
82
|
private mapCursorOffset;
|
|
74
|
-
protected createCursorCondition<T extends object>(definition: (readonly [keyof T & string, QueryOrder])[], offsets: Dictionary[], inverse: boolean, meta: EntityMetadata<T
|
|
83
|
+
protected createCursorCondition<T extends object>(definition: (readonly [keyof T & string, QueryOrder])[], offsets: Dictionary[], inverse: boolean, meta: EntityMetadata<T>, fromJson?: boolean): FilterQuery<T>;
|
|
75
84
|
/** @internal */
|
|
76
85
|
mapDataToFieldNames(data: Dictionary, stringifyJsonArrays: boolean, properties?: Record<string, EntityProperty>, convertCustomTypes?: boolean, object?: boolean): Dictionary;
|
|
77
86
|
protected inlineEmbeddables<T extends object>(meta: EntityMetadata<T>, data: T, where?: boolean): void;
|
|
@@ -10,7 +10,6 @@ import { helper } from '../entity/wrap.js';
|
|
|
10
10
|
import { Reference } from '../entity/Reference.js';
|
|
11
11
|
import { PolymorphicRef } from '../entity/PolymorphicRef.js';
|
|
12
12
|
import { JsonType } from '../types/JsonType.js';
|
|
13
|
-
import { DateTimeType } from '../types/DateTimeType.js';
|
|
14
13
|
import { QueryHelper } from '../utils/QueryHelper.js';
|
|
15
14
|
import { MikroORM } from '../MikroORM.js';
|
|
16
15
|
/** Abstract base class for all database drivers, implementing common driver logic. */
|
|
@@ -146,6 +145,7 @@ export class DatabaseDriver {
|
|
|
146
145
|
const createCursor = (val, key, inverse = false) => {
|
|
147
146
|
const def = Reference.unwrapReference((isCursor(val, key) ? val[key] : val));
|
|
148
147
|
let offsets;
|
|
148
|
+
let fromJson = false;
|
|
149
149
|
// entity (and reference) instances are supported as cursors too, their properties are read the same way
|
|
150
150
|
if (Utils.isPlainObject(def) || Utils.isEntity(def)) {
|
|
151
151
|
// POJO values are already JS values, extract them ordered per the definition,
|
|
@@ -158,11 +158,17 @@ export class DatabaseDriver {
|
|
|
158
158
|
});
|
|
159
159
|
}
|
|
160
160
|
else {
|
|
161
|
-
|
|
162
|
-
|
|
161
|
+
try {
|
|
162
|
+
/* v8 ignore next */
|
|
163
|
+
offsets = def ? Cursor.decode(def) : [];
|
|
164
|
+
}
|
|
165
|
+
catch (error) {
|
|
166
|
+
throw CursorError.invalidCursor(meta.className, error);
|
|
167
|
+
}
|
|
168
|
+
fromJson = true;
|
|
163
169
|
}
|
|
164
170
|
if (definition.length > 0 && definition.length === offsets.length) {
|
|
165
|
-
return this.createCursorCondition(definition, offsets, inverse, meta);
|
|
171
|
+
return this.createCursorCondition(definition, offsets, inverse, meta, fromJson);
|
|
166
172
|
}
|
|
167
173
|
/* v8 ignore next */
|
|
168
174
|
return {};
|
|
@@ -176,21 +182,23 @@ export class DatabaseDriver {
|
|
|
176
182
|
if (limit != null) {
|
|
177
183
|
options.limit = limit + (overfetch ? 1 : 0);
|
|
178
184
|
}
|
|
179
|
-
const createOrderBy = (prop, direction) => {
|
|
185
|
+
const createOrderBy = (prop, direction, properties = meta.properties) => {
|
|
186
|
+
const propMeta = properties[prop];
|
|
180
187
|
if (Utils.isPlainObject(direction)) {
|
|
188
|
+
const childProps = propMeta?.kind === ReferenceKind.EMBEDDED ? propMeta.embeddedProps : propMeta?.targetMeta?.properties;
|
|
181
189
|
const value = Utils.getObjectQueryKeys(direction).reduce((o, key) => {
|
|
182
|
-
Object.assign(o, createOrderBy(key, direction[key]));
|
|
190
|
+
Object.assign(o, createOrderBy(key, direction[key], childProps ?? {}));
|
|
183
191
|
return o;
|
|
184
192
|
}, {});
|
|
185
193
|
return { [prop]: value };
|
|
186
194
|
}
|
|
187
|
-
const
|
|
188
|
-
const
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
195
|
+
const { desc, nullsFirst, explicit } = this.parseCursorDirection(direction);
|
|
196
|
+
const dir = Utils.xor(desc, isLast) ? 'desc' : 'asc';
|
|
197
|
+
// only a requested placement is spelled out, an unqualified direction already lands where the
|
|
198
|
+
// condition expects it; backward pagination reverses the ordering, so the placement flips too
|
|
199
|
+
if (explicit) {
|
|
200
|
+
const nulls = Utils.xor(nullsFirst, isLast) ? 'first' : 'last';
|
|
201
|
+
return { [prop]: `${dir} nulls ${nulls}` };
|
|
194
202
|
}
|
|
195
203
|
return { [prop]: dir };
|
|
196
204
|
};
|
|
@@ -209,12 +217,30 @@ export class DatabaseDriver {
|
|
|
209
217
|
};
|
|
210
218
|
}
|
|
211
219
|
/**
|
|
212
|
-
*
|
|
213
|
-
*
|
|
214
|
-
*
|
|
215
|
-
*
|
|
220
|
+
* Resolves a leaf `orderBy` direction into the two flags the rewritten `orderBy` and the cursor
|
|
221
|
+
* condition have to agree on, or pagination skips rows at the null boundary. A placement the caller
|
|
222
|
+
* asked for wins where the platform can honor it, anything else follows the platform's own default,
|
|
223
|
+
* which is what the untouched `orderBy` will get.
|
|
216
224
|
*/
|
|
217
|
-
|
|
225
|
+
parseCursorDirection(direction) {
|
|
226
|
+
const dir = ('' + direction).toLowerCase();
|
|
227
|
+
const desc = direction === QueryOrderNumeric.DESC || dir.startsWith('desc');
|
|
228
|
+
const nullsFirst = dir.includes('nulls first');
|
|
229
|
+
const explicit = (nullsFirst || dir.includes('nulls last')) && this.platform.supportsNullsOrdering();
|
|
230
|
+
if (!explicit) {
|
|
231
|
+
return { desc, nullsFirst: this.platform.sortsNullsLowest() ? !desc : desc, explicit };
|
|
232
|
+
}
|
|
233
|
+
return { desc, nullsFirst, explicit };
|
|
234
|
+
}
|
|
235
|
+
/**
|
|
236
|
+
* Restores the JS value of a single cursor offset. String-cursor values (`fromJson`) are decoded
|
|
237
|
+
* JSON: types implementing `fromJSON` own the round trip, other custom types restore via
|
|
238
|
+
* `convertToJSValue`, with `Date` healing for date-like columns based on the property type (never
|
|
239
|
+
* based on the string shape alone). POJO values are already JS values and only date-like strings
|
|
240
|
+
* are healed. Values compared against a JSON document keep their serialized form instead, unless
|
|
241
|
+
* the platform preserves native date types inside JSON documents (mongo).
|
|
242
|
+
*/
|
|
243
|
+
mapCursorOffset(prop, value, insideJson, fromJson) {
|
|
218
244
|
if (Utils.isScalarReference(value)) {
|
|
219
245
|
value = value.unwrap();
|
|
220
246
|
}
|
|
@@ -225,25 +251,65 @@ export class DatabaseDriver {
|
|
|
225
251
|
if (value == null) {
|
|
226
252
|
return value;
|
|
227
253
|
}
|
|
254
|
+
// mongo preserves native date types inside JSON documents, restored JS values compare directly
|
|
228
255
|
if (insideJson && !this.platform.preservesDatesInsideJson()) {
|
|
229
|
-
// compared against the JSON document, which holds the
|
|
256
|
+
// compared against the JSON document, which holds the JSON form of the database value
|
|
230
257
|
if (value instanceof Date) {
|
|
231
258
|
return value.toISOString();
|
|
232
259
|
}
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
260
|
+
if (prop?.customType && fromJson) {
|
|
261
|
+
const restored = prop.customType.fromJSON
|
|
262
|
+
? prop.customType.fromJSON(value, this.platform)
|
|
263
|
+
: prop.customType.convertToJSValue(value, this.platform);
|
|
264
|
+
// a restored `Date` compares against its ISO string in the document, everything else
|
|
265
|
+
// keeps the JS value and gets its single `convertToDatabaseValue` in `processWhere`
|
|
266
|
+
if (restored instanceof Date) {
|
|
267
|
+
const converted = prop.customType.convertToDatabaseValue(restored, this.platform, {
|
|
268
|
+
fromQuery: true,
|
|
269
|
+
key: prop.name,
|
|
270
|
+
mode: 'query',
|
|
271
|
+
});
|
|
272
|
+
if (converted instanceof Date) {
|
|
273
|
+
return converted.toISOString();
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
return restored;
|
|
277
|
+
}
|
|
278
|
+
return value;
|
|
236
279
|
}
|
|
237
|
-
if (
|
|
238
|
-
(
|
|
239
|
-
|
|
240
|
-
|
|
280
|
+
if (prop?.customType) {
|
|
281
|
+
if (fromJson && prop.customType.fromJSON) {
|
|
282
|
+
// the type owns its JSON round trip
|
|
283
|
+
return prop.customType.fromJSON(value, this.platform);
|
|
284
|
+
}
|
|
285
|
+
// A string is either a serialized form (string cursor) or a hand-written one (POJO).
|
|
286
|
+
// Types whose JS value is a `Date` must receive one. The rest get the string first,
|
|
287
|
+
// so any sub-millisecond precision survives.
|
|
288
|
+
if (typeof value === 'string' &&
|
|
289
|
+
(prop.runtimeType === 'Date' || this.platform.getMappedType(prop.columnTypes?.[0] ?? '').runtimeType === 'Date')) {
|
|
290
|
+
if (prop.runtimeType !== 'Date') {
|
|
291
|
+
try {
|
|
292
|
+
return prop.customType.convertToJSValue(value, this.platform);
|
|
293
|
+
}
|
|
294
|
+
catch {
|
|
295
|
+
// the type cannot read the serialized string, fall back to the `Date` form
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
return prop.customType.convertToJSValue(new Date(value), this.platform);
|
|
299
|
+
}
|
|
300
|
+
// serialized non-string values still need restoring; POJO values are already JS values
|
|
301
|
+
return fromJson ? prop.customType.convertToJSValue(value, this.platform) : value;
|
|
302
|
+
}
|
|
303
|
+
if (typeof value === 'string' && prop?.runtimeType === 'Date') {
|
|
304
|
+
return new Date(value);
|
|
241
305
|
}
|
|
242
|
-
return
|
|
306
|
+
return value;
|
|
243
307
|
}
|
|
244
|
-
createCursorCondition(definition, offsets, inverse, meta) {
|
|
245
|
-
const createCondition = (prop, direction, offset, eq = false, path = prop, properties = meta.properties, insideJson = false) => {
|
|
308
|
+
createCursorCondition(definition, offsets, inverse, meta, fromJson = false) {
|
|
309
|
+
const createCondition = (prop, direction, offset, eq = false, path = prop, properties = meta.properties, insideJson = false, nullable = false) => {
|
|
246
310
|
const propMeta = properties[prop];
|
|
311
|
+
// nullable relations and embeddables null out their joined columns, and a formula can yield null unannounced
|
|
312
|
+
nullable ||= !!propMeta?.nullable || !!propMeta?.formula;
|
|
247
313
|
if (Utils.isPlainObject(direction)) {
|
|
248
314
|
if (offset === undefined) {
|
|
249
315
|
throw CursorError.missingValue(meta.className, path);
|
|
@@ -253,40 +319,54 @@ export class DatabaseDriver {
|
|
|
253
319
|
const childProps = propMeta?.kind === ReferenceKind.EMBEDDED ? propMeta.embeddedProps : propMeta?.targetMeta?.properties;
|
|
254
320
|
insideJson ||=
|
|
255
321
|
(propMeta?.kind === ReferenceKind.EMBEDDED && !!propMeta.object) || propMeta?.customType instanceof JsonType;
|
|
256
|
-
const
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
//
|
|
261
|
-
//
|
|
322
|
+
const keys = Utils.keys(direction);
|
|
323
|
+
const child = (key, childEq) => createCondition(key, direction[key],
|
|
324
|
+
// a null relation offset means the whole sort key is null, propagate it to the leaves
|
|
325
|
+
offset === null ? null : offset[key], childEq, `${path}.${key}`, childProps ?? {}, insideJson, nullable);
|
|
326
|
+
// the group's own keys are a keyset in their own right, so they decompose the same way the
|
|
327
|
+
// top level does; merging them into one object would compare every key independently instead
|
|
328
|
+
const lex = (index) => {
|
|
329
|
+
const key = keys[index];
|
|
330
|
+
if (index === keys.length - 1) {
|
|
331
|
+
return child(key, eq);
|
|
332
|
+
}
|
|
333
|
+
const atOrPast = child(key, true);
|
|
334
|
+
const tail = lex(index + 1);
|
|
335
|
+
// an unconstrained tail matches anything, leaving only the `at or past` prefix to constrain
|
|
336
|
+
const past = Utils.hasObjectKeys(tail) ? { $or: [child(key, false), tail] } : {};
|
|
337
|
+
if (!Utils.hasObjectKeys(past)) {
|
|
338
|
+
return atOrPast;
|
|
339
|
+
}
|
|
340
|
+
return Utils.hasObjectKeys(atOrPast) ? { $and: [atOrPast, past] } : past;
|
|
341
|
+
};
|
|
342
|
+
const value = keys.length > 0 ? lex(0) : {};
|
|
343
|
+
// an unconstrained group condition must not degrade to `{ [prop]: {} }`
|
|
262
344
|
return Utils.hasObjectKeys(value) ? { [prop]: value } : {};
|
|
263
345
|
}
|
|
264
|
-
const
|
|
265
|
-
const isDesc = direction === QueryOrderNumeric.DESC || dirStr.startsWith('desc');
|
|
266
|
-
let nullsFirst;
|
|
267
|
-
if (dirStr.includes('nulls first')) {
|
|
268
|
-
nullsFirst = true;
|
|
269
|
-
}
|
|
270
|
-
else if (dirStr.includes('nulls last')) {
|
|
271
|
-
nullsFirst = false;
|
|
272
|
-
}
|
|
273
|
-
else {
|
|
274
|
-
// Default: NULLS LAST for ASC, NULLS FIRST for DESC (matches most databases)
|
|
275
|
-
nullsFirst = isDesc;
|
|
276
|
-
}
|
|
346
|
+
const { desc: isDesc, nullsFirst } = this.parseCursorDirection(direction);
|
|
277
347
|
const operator = Utils.xor(isDesc, inverse) ? '$lt' : '$gt';
|
|
278
348
|
// For leaf-level properties, undefined means missing value
|
|
279
349
|
if (offset === undefined) {
|
|
280
350
|
throw CursorError.missingValue(meta.className, path);
|
|
281
351
|
}
|
|
282
|
-
|
|
352
|
+
// string-cursor values are client supplied, so a value the type cannot restore is an
|
|
353
|
+
// invalid cursor, letting callers map `CursorError` to a client error response
|
|
354
|
+
try {
|
|
355
|
+
offset = this.mapCursorOffset(propMeta, offset, insideJson, fromJson);
|
|
356
|
+
}
|
|
357
|
+
catch (error) {
|
|
358
|
+
if (!fromJson || error instanceof CursorError) {
|
|
359
|
+
throw error;
|
|
360
|
+
}
|
|
361
|
+
throw CursorError.invalidCursor(meta.className, error);
|
|
362
|
+
}
|
|
283
363
|
// Handle null offset (intentional null cursor value)
|
|
284
364
|
if (offset === null) {
|
|
285
365
|
// hasItemsAfterNull: forward + nullsFirst, or backward + nullsLast
|
|
286
366
|
const hasItemsAfterNull = Utils.xor(nullsFirst, inverse);
|
|
287
367
|
if (eq) {
|
|
288
|
-
//
|
|
289
|
-
//
|
|
368
|
+
// at-or-after a null sort key means every row when non-null rows follow the null block,
|
|
369
|
+
// so the tie-breaker in the `$or` sibling can reach past it
|
|
290
370
|
return hasItemsAfterNull ? {} : { [prop]: null };
|
|
291
371
|
}
|
|
292
372
|
// Strict comparison with null cursor value
|
|
@@ -297,7 +377,12 @@ export class DatabaseDriver {
|
|
|
297
377
|
return { [prop]: [] };
|
|
298
378
|
}
|
|
299
379
|
// Non-null offset
|
|
300
|
-
|
|
380
|
+
const condition = { [prop]: { [operator + (eq ? 'e' : '')]: offset } };
|
|
381
|
+
// null sort keys lie past the offset here, and no comparison ever matches them
|
|
382
|
+
if (nullable && !Utils.xor(nullsFirst, inverse)) {
|
|
383
|
+
return { $or: [condition, { [prop]: null }] };
|
|
384
|
+
}
|
|
385
|
+
return condition;
|
|
301
386
|
};
|
|
302
387
|
const [order, ...otherOrders] = definition;
|
|
303
388
|
const [offset, ...otherOffsets] = offsets;
|
|
@@ -305,13 +390,18 @@ export class DatabaseDriver {
|
|
|
305
390
|
if (!otherOrders.length) {
|
|
306
391
|
return createCondition(prop, direction, offset);
|
|
307
392
|
}
|
|
308
|
-
|
|
309
|
-
|
|
393
|
+
const atOrPast = createCondition(prop, direction, offset, true);
|
|
394
|
+
const past = {
|
|
310
395
|
$or: [
|
|
311
396
|
createCondition(prop, direction, offset),
|
|
312
|
-
this.createCursorCondition(otherOrders, otherOffsets, inverse, meta),
|
|
397
|
+
this.createCursorCondition(otherOrders, otherOffsets, inverse, meta, fromJson),
|
|
313
398
|
],
|
|
314
399
|
};
|
|
400
|
+
// the `at or past` prefix is itself a group condition when it compares against nulls
|
|
401
|
+
if ('$or' in atOrPast) {
|
|
402
|
+
return { $and: [atOrPast, past] };
|
|
403
|
+
}
|
|
404
|
+
return { ...atOrPast, ...past };
|
|
315
405
|
}
|
|
316
406
|
/** @internal */
|
|
317
407
|
mapDataToFieldNames(data, stringifyJsonArrays, properties, convertCustomTypes, object) {
|
package/entity/Collection.js
CHANGED
|
@@ -117,9 +117,11 @@ export class Collection {
|
|
|
117
117
|
opts.orderBy = QueryHelper.mergeOrderBy(opts.orderBy, this.property.orderBy, this.property.targetMeta?.orderBy);
|
|
118
118
|
options.populate = (await em.preparePopulate(this.property.targetMeta.class, options));
|
|
119
119
|
const cond = (await em.applyFilters(this.property.targetMeta.class, where, options.filters ?? {}, 'read'));
|
|
120
|
-
|
|
120
|
+
// fall back to the ambient transaction context, or `withSessionContext` would wrap the pivot load in a
|
|
121
|
+
// second concurrent transaction (a deadlock with a single-connection pool) instead of joining the open one
|
|
122
|
+
const map = await em.withSessionContext(ctx ?? em.getTransactionContext(), trx => em
|
|
121
123
|
.getDriver()
|
|
122
|
-
.loadFromPivotTable(this.property, [helper(this.owner).__primaryKeys], cond, opts.orderBy,
|
|
124
|
+
.loadFromPivotTable(this.property, [helper(this.owner).__primaryKeys], cond, opts.orderBy, trx, options));
|
|
123
125
|
items = map[helper(this.owner).getSerializedPrimaryKey()].map((item) => em.merge(this.property.targetMeta.class, item, { convertCustomTypes: true }));
|
|
124
126
|
await em.populate(items, options.populate, options);
|
|
125
127
|
}
|
package/entity/EntityLoader.js
CHANGED
|
@@ -635,7 +635,7 @@ export class EntityLoader {
|
|
|
635
635
|
if (!Utils.isEmpty(prop.where)) {
|
|
636
636
|
where = { $and: [where, prop.where] };
|
|
637
637
|
}
|
|
638
|
-
const map = await this.#driver.loadFromPivotTable(prop, ids, where, orderBy,
|
|
638
|
+
const map = await this.#em.withSessionContext(this.#em.getTransactionContext(), ctx => this.#driver.loadFromPivotTable(prop, ids, where, orderBy, ctx, options2, pivotJoin));
|
|
639
639
|
const children = [];
|
|
640
640
|
const isUnionTargetMN = QueryHelper.isUnionTargetPolymorphic(prop);
|
|
641
641
|
for (let i = 0; i < filtered.length; i++) {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { PopulatePath } from '../enums.js';
|
|
2
|
-
import type { CreateOptions, EntityManager, MergeOptions } from '../EntityManager.js';
|
|
2
|
+
import type { CreateOptions, EntityManager, MapOptions, MergeOptions } from '../EntityManager.js';
|
|
3
3
|
import type { AssignOptions } from './EntityAssigner.js';
|
|
4
4
|
import type { Dictionary, EntityData, EntityDictionary, EntityKey, EntityName, FilterQuery, Loaded, Primary, AutoPath, RequiredEntityData, Ref, EntityType, EntityDTO, MergeSelected, FromEntityType, IsSubset, MergeLoaded, ArrayElement, IndexFilterQuery, WithUsingOptions } from '../typings.js';
|
|
5
5
|
import type { CountByOptions, CountOptions, DeleteOptions, FindAllOptions, FindByCursorOptions, FindOneOptions, FindOneOrFailOptions, FindOptions, GetReferenceOptions, NativeInsertUpdateOptions, StreamOptions, UpdateOptions, UpsertManyOptions, UpsertOptions } from '../drivers/IDatabaseDriver.js';
|
|
@@ -115,11 +115,10 @@ export declare class EntityRepository<Entity extends object> {
|
|
|
115
115
|
*/
|
|
116
116
|
nativeDelete(where: FilterQuery<Entity>, options?: DeleteOptions<Entity>): Promise<number>;
|
|
117
117
|
/**
|
|
118
|
-
* Maps raw database result to an entity and merges it to this EntityManager.
|
|
118
|
+
* Maps raw database result to an entity and merges it to this EntityManager by default.
|
|
119
|
+
* Use `disableIdentityMap` to return an isolated entity without affecting the current context.
|
|
119
120
|
*/
|
|
120
|
-
map(result: EntityDictionary<Entity>, options?:
|
|
121
|
-
schema?: string;
|
|
122
|
-
}): Entity;
|
|
121
|
+
map(result: EntityDictionary<Entity>, options?: Omit<MapOptions, 'mapped'>): Entity;
|
|
123
122
|
/**
|
|
124
123
|
* Gets a reference to the entity identified by the given type and alternate key property without actually loading it.
|
|
125
124
|
* The key option specifies which property to use for identity map lookup instead of the primary key.
|
|
@@ -131,7 +131,8 @@ export class EntityRepository {
|
|
|
131
131
|
return this.getEntityManager().nativeDelete(this.entityName, where, options);
|
|
132
132
|
}
|
|
133
133
|
/**
|
|
134
|
-
* Maps raw database result to an entity and merges it to this EntityManager.
|
|
134
|
+
* Maps raw database result to an entity and merges it to this EntityManager by default.
|
|
135
|
+
* Use `disableIdentityMap` to return an isolated entity without affecting the current context.
|
|
135
136
|
*/
|
|
136
137
|
map(result, options) {
|
|
137
138
|
return this.getEntityManager().map(this.entityName, result, options);
|
package/entity/defineEntity.d.ts
CHANGED
|
@@ -148,6 +148,8 @@ export interface PropertyChain<in out Value, in out Options> {
|
|
|
148
148
|
orphanRemoval(orphanRemoval?: boolean): HasKind<Options, '1:m' | '1:1'> extends true ? PropertyChain<Value, Options> : never;
|
|
149
149
|
discriminator(discriminator: string): HasKind<Options, 'm:1' | '1:1' | 'm:n'> extends true ? PropertyChain<Value, Options> : never;
|
|
150
150
|
discriminatorMap(discriminatorMap: Dictionary<string>): HasKind<Options, 'm:1' | '1:1' | 'm:n'> extends true ? PropertyChain<Value, Options> : never;
|
|
151
|
+
/** Resolve this read-only to-one relation via a subquery on another entity (see {@doclink relationships#to-one-relations-through-another-entity | To-one relations through another entity}). */
|
|
152
|
+
through(through: () => EntityName): HasKind<Options, 'm:1' | '1:1'> extends true ? PropertyChain<Value, Options> : never;
|
|
151
153
|
pivotTable(pivotTable: string): HasKind<Options, 'm:n'> extends true ? PropertyChain<Value, Options> : never;
|
|
152
154
|
pivotEntity(pivotEntity: () => EntityName): HasKind<Options, 'm:n'> extends true ? PropertyChain<Value, Options> : never;
|
|
153
155
|
fixedOrder(fixedOrder?: boolean): HasKind<Options, 'm:n'> extends true ? PropertyChain<Value, Options> : never;
|
|
@@ -528,6 +530,8 @@ export declare class UniversalPropertyOptionsBuilder<in out Value, in out Option
|
|
|
528
530
|
fixedOrderColumn(fixedOrderColumn: string): Pick<UniversalPropertyOptionsBuilder<Value, Options, IncludeKeys>, IncludeKeys>;
|
|
529
531
|
/** Override default name for pivot table (see {@doclink naming-strategy | Naming Strategy}). */
|
|
530
532
|
pivotTable(pivotTable: string): Pick<UniversalPropertyOptionsBuilder<Value, Options, IncludeKeys>, IncludeKeys>;
|
|
533
|
+
/** Resolve this read-only to-one relation via a subquery on another entity (see {@doclink relationships#to-one-relations-through-another-entity | To-one relations through another entity}). */
|
|
534
|
+
through(through: () => EntityName): Pick<UniversalPropertyOptionsBuilder<Value, Options, IncludeKeys>, IncludeKeys>;
|
|
531
535
|
/** Set pivot entity for this relation (see {@doclink collections#custom-pivot-table-entity | Custom pivot table entity}). */
|
|
532
536
|
pivotEntity(pivotEntity: () => EntityName): Pick<UniversalPropertyOptionsBuilder<Value, Options, IncludeKeys>, IncludeKeys>;
|
|
533
537
|
/** Override the default database column name on the owning side (see {@doclink naming-strategy | Naming Strategy}). This option is only for simple properties represented by a single column. */
|
|
@@ -570,6 +574,13 @@ export declare class UniversalPropertyOptionsBuilder<in out Value, in out Option
|
|
|
570
574
|
export interface EmptyOptions extends Partial<Record<UniversalPropertyKeys, unknown>> {
|
|
571
575
|
}
|
|
572
576
|
/** @internal */
|
|
577
|
+
export declare class StringPropertyOptionsBuilder<Value, Options> extends UniversalPropertyOptionsBuilder<Value, Options, IncludeKeysForProperty> {
|
|
578
|
+
trim(): StringPropertyOptionsBuilder<Value, Options>;
|
|
579
|
+
lowercase(): StringPropertyOptionsBuilder<Value, Options>;
|
|
580
|
+
uppercase(): StringPropertyOptionsBuilder<Value, Options>;
|
|
581
|
+
private withOptions;
|
|
582
|
+
}
|
|
583
|
+
/** @internal */
|
|
573
584
|
export declare class OneToManyOptionsBuilderOnlyMappedBy<Value extends object> extends UniversalPropertyOptionsBuilder<Value, EmptyOptions & {
|
|
574
585
|
kind: '1:m';
|
|
575
586
|
}, IncludeKeysForOneToManyOptions> {
|
|
@@ -582,7 +593,7 @@ type EntityTarget = {
|
|
|
582
593
|
'~entity': any;
|
|
583
594
|
} | EntityClass;
|
|
584
595
|
declare const propertyBuilders: PropertyBuilders;
|
|
585
|
-
type PropertyBuildersOverrideKeys = 'bigint' | 'array' | 'decimal' | 'json' | 'datetime' | 'time' | 'enum';
|
|
596
|
+
type PropertyBuildersOverrideKeys = 'bigint' | 'array' | 'decimal' | 'json' | 'string' | 'text' | 'datetime' | 'time' | 'enum';
|
|
586
597
|
/** Map of factory functions for creating type-safe property builders (scalars, enums, embeddables, and relations). */
|
|
587
598
|
export type PropertyBuilders = {
|
|
588
599
|
[K in Exclude<keyof typeof types, PropertyBuildersOverrideKeys>]: () => UniversalPropertyOptionsBuilder<InferPropertyValueType<(typeof types)[K]>, EmptyOptions, IncludeKeysForProperty>;
|
|
@@ -591,6 +602,8 @@ export type PropertyBuilders = {
|
|
|
591
602
|
array: <T = string>(toJsValue?: (i: string) => T, toDbValue?: (i: T) => string) => UniversalPropertyOptionsBuilder<InferPropertyValueType<typeof types.array<T>>, EmptyOptions, IncludeKeysForProperty>;
|
|
592
603
|
decimal: <Mode extends 'number' | 'string' = 'string'>(mode?: Mode) => UniversalPropertyOptionsBuilder<InferPropertyValueType<typeof types.decimal<Mode>>, EmptyOptions, IncludeKeysForProperty>;
|
|
593
604
|
json: <T>() => UniversalPropertyOptionsBuilder<T, EmptyOptions, IncludeKeysForProperty>;
|
|
605
|
+
string: () => StringPropertyOptionsBuilder<InferPropertyValueType<typeof types.string>, EmptyOptions>;
|
|
606
|
+
text: () => StringPropertyOptionsBuilder<InferPropertyValueType<typeof types.text>, EmptyOptions>;
|
|
594
607
|
formula: <T>(formula: string | FormulaCallback<any>) => UniversalPropertyOptionsBuilder<T, EmptyOptions, IncludeKeysForProperty>;
|
|
595
608
|
datetime: (length?: number) => UniversalPropertyOptionsBuilder<InferPropertyValueType<typeof types.datetime>, EmptyOptions, IncludeKeysForProperty>;
|
|
596
609
|
time: (length?: number) => UniversalPropertyOptionsBuilder<InferPropertyValueType<typeof types.time>, EmptyOptions, IncludeKeysForProperty>;
|
|
@@ -638,6 +651,9 @@ export interface EntityMetadataWithProperties<TName extends string, TTableName e
|
|
|
638
651
|
entity?: EntityName<any> | EntityName<any>[];
|
|
639
652
|
args?: boolean;
|
|
640
653
|
strict?: boolean;
|
|
654
|
+
rls?: boolean | {
|
|
655
|
+
setting?: string;
|
|
656
|
+
};
|
|
641
657
|
}>;
|
|
642
658
|
forceObject?: TForceObject;
|
|
643
659
|
embeddable?: TEmbeddable;
|
package/entity/defineEntity.js
CHANGED
|
@@ -403,6 +403,10 @@ export class UniversalPropertyOptionsBuilder {
|
|
|
403
403
|
pivotTable(pivotTable) {
|
|
404
404
|
return this.assignOptions({ pivotTable });
|
|
405
405
|
}
|
|
406
|
+
/** Resolve this read-only to-one relation via a subquery on another entity (see {@doclink relationships#to-one-relations-through-another-entity | To-one relations through another entity}). */
|
|
407
|
+
through(through) {
|
|
408
|
+
return this.assignOptions({ through });
|
|
409
|
+
}
|
|
406
410
|
/** Set pivot entity for this relation (see {@doclink collections#custom-pivot-table-entity | Custom pivot table entity}). */
|
|
407
411
|
pivotEntity(pivotEntity) {
|
|
408
412
|
return this.assignOptions({ pivotEntity });
|
|
@@ -472,6 +476,27 @@ export class UniversalPropertyOptionsBuilder {
|
|
|
472
476
|
}
|
|
473
477
|
}
|
|
474
478
|
/** @internal */
|
|
479
|
+
export class StringPropertyOptionsBuilder extends UniversalPropertyOptionsBuilder {
|
|
480
|
+
trim() {
|
|
481
|
+
return this.withOptions({ trim: true });
|
|
482
|
+
}
|
|
483
|
+
lowercase() {
|
|
484
|
+
return this.withOptions({ case: 'lower' });
|
|
485
|
+
}
|
|
486
|
+
uppercase() {
|
|
487
|
+
return this.withOptions({ case: 'upper' });
|
|
488
|
+
}
|
|
489
|
+
withOptions(options) {
|
|
490
|
+
const type = this['~options'].type;
|
|
491
|
+
const TypeClass = typeof type === 'function' ? type : type.constructor;
|
|
492
|
+
const currentOptions = typeof type === 'function' ? {} : type.options;
|
|
493
|
+
return new StringPropertyOptionsBuilder({
|
|
494
|
+
...this['~options'],
|
|
495
|
+
type: new TypeClass({ ...currentOptions, ...options }),
|
|
496
|
+
});
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
/** @internal */
|
|
475
500
|
export class OneToManyOptionsBuilderOnlyMappedBy extends UniversalPropertyOptionsBuilder {
|
|
476
501
|
/** Point to the owning side property name. */
|
|
477
502
|
mappedBy(mappedBy) {
|
|
@@ -487,6 +512,12 @@ const propertyBuilders = {
|
|
|
487
512
|
array: (toJsValue = i => i, toDbValue = i => i) => new UniversalPropertyOptionsBuilder({ type: new types.array(toJsValue, toDbValue) }),
|
|
488
513
|
decimal: (mode) => new UniversalPropertyOptionsBuilder({ type: new types.decimal(mode) }),
|
|
489
514
|
json: () => new UniversalPropertyOptionsBuilder({ type: types.json }),
|
|
515
|
+
string: () => new StringPropertyOptionsBuilder({
|
|
516
|
+
type: types.string,
|
|
517
|
+
}),
|
|
518
|
+
text: () => new StringPropertyOptionsBuilder({
|
|
519
|
+
type: types.text,
|
|
520
|
+
}),
|
|
490
521
|
formula: (formula) => new UniversalPropertyOptionsBuilder({ formula }),
|
|
491
522
|
datetime: (length) => new UniversalPropertyOptionsBuilder({ type: types.datetime, length }),
|
|
492
523
|
time: (length) => new UniversalPropertyOptionsBuilder({ type: types.time, length }),
|
package/enums.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { EntityKey, ExpandProperty } from './typings.js';
|
|
1
|
+
import type { EntityKey, ExpandProperty, SessionContext } from './typings.js';
|
|
2
2
|
import type { InflightQueryAbortStrategy, Transaction } from './connections/Connection.js';
|
|
3
3
|
import type { LogContext } from './logging/Logger.js';
|
|
4
4
|
/** Controls when the `EntityManager` flushes pending changes to the database. */
|
|
@@ -309,6 +309,8 @@ export interface TransactionOptions {
|
|
|
309
309
|
flushMode?: FlushMode | `${FlushMode}`;
|
|
310
310
|
ignoreNestedTransactions?: boolean;
|
|
311
311
|
loggerContext?: LogContext;
|
|
312
|
+
/** @internal database session context applied on `begin()` (set via `em.setSessionContext()`). */
|
|
313
|
+
sessionContext?: SessionContext;
|
|
312
314
|
/**
|
|
313
315
|
* `AbortSignal` cancelling every query within the transaction (including the implicit flush).
|
|
314
316
|
* Cancelling mid-transaction triggers a rollback once the in-flight query settles.
|
package/errors.d.ts
CHANGED
|
@@ -25,6 +25,13 @@ export declare class ValidationError<T extends AnyEntity = AnyEntity> extends Er
|
|
|
25
25
|
static invalidCompositeIdentifier(meta: EntityMetadata): ValidationError;
|
|
26
26
|
static cannotCommit(): ValidationError;
|
|
27
27
|
static cannotUseGlobalContext(): ValidationError;
|
|
28
|
+
static sessionContextNotSupported(): ValidationError;
|
|
29
|
+
static sessionContextRequiresImplicitTransactions(): ValidationError;
|
|
30
|
+
static sessionContextWithDisabledTransactions(): ValidationError;
|
|
31
|
+
static sessionContextInsideTransaction(action?: 'set' | 'clear'): ValidationError;
|
|
32
|
+
static cannotStageNonScalarSessionVariable(filterName: string, argName: string): ValidationError;
|
|
33
|
+
static sessionContextStreamRequiresTransaction(): ValidationError;
|
|
34
|
+
static connectionSessionContextNotSupported(): ValidationError;
|
|
28
35
|
static cannotUseOperatorsInsideEmbeddables(entityName: EntityName, propName: string, payload: unknown): ValidationError;
|
|
29
36
|
static cannotUseGroupOperatorsInsideScalars(entityName: EntityName, propName: string, payload: unknown): ValidationError;
|
|
30
37
|
static invalidEmbeddableQuery(entityName: EntityName, propName: string, embeddableType: string): ValidationError;
|
|
@@ -34,6 +41,7 @@ export declare class ValidationError<T extends AnyEntity = AnyEntity> extends Er
|
|
|
34
41
|
export declare class CursorError<T extends AnyEntity = AnyEntity> extends ValidationError<T> {
|
|
35
42
|
static entityNotPopulated(entity: AnyEntity, prop: string): ValidationError;
|
|
36
43
|
static missingValue(entityName: string, prop: string): ValidationError;
|
|
44
|
+
static invalidCursor(entityName: string, cause: Error): CursorError;
|
|
37
45
|
}
|
|
38
46
|
/** Error thrown when an optimistic lock conflict is detected during entity persistence. */
|
|
39
47
|
export declare class OptimisticLockError<T extends AnyEntity = AnyEntity> extends ValidationError<T> {
|
|
@@ -66,6 +74,9 @@ export declare class MetadataError<T extends AnyEntity = AnyEntity> extends Vali
|
|
|
66
74
|
static targetIsAbstract(meta: EntityMetadata, prop: EntityProperty): MetadataError;
|
|
67
75
|
static nonPersistentCompositeProp(meta: EntityMetadata, prop: EntityProperty): MetadataError;
|
|
68
76
|
static propertyTargetsEntityType(meta: EntityMetadata, prop: EntityProperty, target: EntityMetadata): MetadataError;
|
|
77
|
+
static throughRelationMissingProperty(meta: EntityMetadata, prop: EntityProperty, through: EntityMetadata, side: 'owner' | 'target'): MetadataError;
|
|
78
|
+
static throughRelationCompositeTarget(meta: EntityMetadata, prop: EntityProperty): MetadataError;
|
|
79
|
+
static throughRelationInvalidKind(meta: EntityMetadata, prop: EntityProperty): MetadataError;
|
|
69
80
|
static fromMissingOption(meta: EntityMetadata, prop: EntityProperty, option: string): MetadataError;
|
|
70
81
|
static targetKeyOnManyToMany(meta: EntityMetadata, prop: EntityProperty): MetadataError;
|
|
71
82
|
static targetKeyNotUnique(meta: EntityMetadata, prop: EntityProperty, target?: EntityMetadata): MetadataError;
|
|
@@ -77,6 +88,30 @@ export declare class MetadataError<T extends AnyEntity = AnyEntity> extends Vali
|
|
|
77
88
|
static tptNotSupportedByDriver(meta: EntityMetadata): MetadataError;
|
|
78
89
|
/** Thrown when database triggers are defined on an entity using a driver that does not support them. */
|
|
79
90
|
static triggersNotSupportedByDriver(meta: EntityMetadata): MetadataError;
|
|
91
|
+
/** Thrown when row level security is declared on an entity using a driver that does not support it. */
|
|
92
|
+
static rowLevelSecurityNotSupportedByDriver(meta: EntityMetadata): MetadataError;
|
|
93
|
+
/** Thrown when row level security is declared on a non-root entity of an STI hierarchy. */
|
|
94
|
+
static rowLevelSecurityOnNonRootStiEntity(meta: EntityMetadata): MetadataError;
|
|
95
|
+
/** Thrown when two policies on the same entity are given the same explicit name. */
|
|
96
|
+
static duplicatePolicyName(meta: EntityMetadata, name: string): MetadataError;
|
|
97
|
+
/** Thrown when a filter flagged with `rls` is declared on a driver that does not support row level security. */
|
|
98
|
+
static rlsFilterNotSupportedByDriver(meta: EntityMetadata, filterName: string): MetadataError;
|
|
99
|
+
/** Thrown when a filter flagged with `rls` is declared on a non-root entity of an STI hierarchy. */
|
|
100
|
+
static rlsFilterOnNonRootStiEntity(meta: EntityMetadata, filterName: string): MetadataError;
|
|
101
|
+
/** Thrown when a global (config or EM registered) filter is flagged with `rls`; RLS filters must be entity scoped. */
|
|
102
|
+
static rlsFilterMustBeEntityScoped(filterName: string): MetadataError;
|
|
103
|
+
/** Thrown when an entity-scoped `rls` filter is registered at runtime via `em.addFilter()` instead of in metadata. */
|
|
104
|
+
static rlsFilterCannotBeRegisteredAtRuntime(filterName: string): MetadataError;
|
|
105
|
+
/** Thrown when a filter's custom `setting` is used with more than one argument. */
|
|
106
|
+
static rlsFilterMultiArgSetting(filterName: string, args: string[]): MetadataError;
|
|
107
|
+
/** Thrown when an `rls` filter's condition depends on runtime state and cannot be compiled to a static policy. */
|
|
108
|
+
static rlsFilterDependsOnRuntimeState(filterName: string): MetadataError;
|
|
109
|
+
/** Thrown when an `rls` filter compares against a column whose type has no automatic session-variable cast. */
|
|
110
|
+
static rlsFilterUncastableType(filterName: string, columnType: string): MetadataError;
|
|
111
|
+
/** Thrown when an `rls` filter references an argument outside of a direct comparison, which cannot be compiled. */
|
|
112
|
+
static rlsFilterUnsupportedCond(filterName: string): MetadataError;
|
|
113
|
+
/** Thrown when an `rls` filter compares against a column the schema generator does not manage. */
|
|
114
|
+
static rlsFilterUnmanagedColumn(filterName: string, column: string): MetadataError;
|
|
80
115
|
private static fromMessage;
|
|
81
116
|
}
|
|
82
117
|
/** Error thrown when an entity lookup fails to find the expected result. */
|