metal-orm 1.0.66 → 1.0.68
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/dist/index.cjs +89 -7
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +44 -9
- package/dist/index.d.ts +44 -9
- package/dist/index.js +89 -7
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/core/ast/builders.ts +38 -38
- package/src/core/ast/expression-builders.ts +10 -6
- package/src/core/ast/expression-nodes.ts +5 -5
- package/src/core/ast/join.ts +16 -16
- package/src/core/ast/query.ts +219 -219
- package/src/core/dialect/abstract.ts +20 -20
- package/src/core/functions/array.ts +35 -35
- package/src/core/functions/json.ts +70 -70
- package/src/orm/entity-hydration.ts +72 -72
- package/src/orm/entity-materializer.ts +41 -41
- package/src/orm/entity-meta.ts +18 -18
- package/src/orm/entity-relation-cache.ts +39 -39
- package/src/orm/entity.ts +3 -3
- package/src/orm/orm-session.ts +139 -50
- package/src/orm/relations/belongs-to.ts +2 -2
- package/src/orm/relations/has-many.ts +24 -24
- package/src/orm/relations/has-one.ts +2 -2
- package/src/orm/relations/many-to-many.ts +29 -29
- package/src/orm/save-graph-types.ts +51 -50
- package/src/orm/save-graph.ts +48 -31
|
@@ -1,39 +1,39 @@
|
|
|
1
|
-
import { TableDef } from '../schema/table.js';
|
|
2
|
-
import { EntityMeta, getEntityMeta } from './entity-meta.js';
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Caches relation loader results across entities of the same type.
|
|
6
|
-
* @template T - The cache type
|
|
7
|
-
* @param meta - The entity metadata
|
|
8
|
-
* @param relationName - The relation name
|
|
9
|
-
* @param factory - The factory function to create the cache
|
|
10
|
-
* @returns Promise with the cached relation data
|
|
11
|
-
*/
|
|
12
|
-
export const relationLoaderCache = <TTable extends TableDef, T extends Map<string, unknown>>(
|
|
13
|
-
meta: EntityMeta<TTable>,
|
|
14
|
-
relationName: string,
|
|
15
|
-
factory: () => Promise<T>
|
|
16
|
-
): Promise<T> => {
|
|
17
|
-
if (meta.relationCache.has(relationName)) {
|
|
18
|
-
return meta.relationCache.get(relationName)! as Promise<T>;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
const promise = factory().then(value => {
|
|
22
|
-
for (const tracked of meta.ctx.getEntitiesForTable(meta.table)) {
|
|
23
|
-
const otherMeta = getEntityMeta(tracked.entity);
|
|
24
|
-
if (!otherMeta) continue;
|
|
25
|
-
otherMeta.relationHydration.set(relationName, value);
|
|
26
|
-
}
|
|
27
|
-
return value;
|
|
28
|
-
});
|
|
29
|
-
|
|
30
|
-
meta.relationCache.set(relationName, promise);
|
|
31
|
-
|
|
32
|
-
for (const tracked of meta.ctx.getEntitiesForTable(meta.table)) {
|
|
33
|
-
const otherMeta = getEntityMeta(tracked.entity);
|
|
34
|
-
if (!otherMeta) continue;
|
|
35
|
-
otherMeta.relationCache.set(relationName, promise);
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
return promise;
|
|
39
|
-
};
|
|
1
|
+
import { TableDef } from '../schema/table.js';
|
|
2
|
+
import { EntityMeta, getEntityMeta } from './entity-meta.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Caches relation loader results across entities of the same type.
|
|
6
|
+
* @template T - The cache type
|
|
7
|
+
* @param meta - The entity metadata
|
|
8
|
+
* @param relationName - The relation name
|
|
9
|
+
* @param factory - The factory function to create the cache
|
|
10
|
+
* @returns Promise with the cached relation data
|
|
11
|
+
*/
|
|
12
|
+
export const relationLoaderCache = <TTable extends TableDef, T extends Map<string, unknown>>(
|
|
13
|
+
meta: EntityMeta<TTable>,
|
|
14
|
+
relationName: string,
|
|
15
|
+
factory: () => Promise<T>
|
|
16
|
+
): Promise<T> => {
|
|
17
|
+
if (meta.relationCache.has(relationName)) {
|
|
18
|
+
return meta.relationCache.get(relationName)! as Promise<T>;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const promise = factory().then(value => {
|
|
22
|
+
for (const tracked of meta.ctx.getEntitiesForTable(meta.table)) {
|
|
23
|
+
const otherMeta = getEntityMeta(tracked.entity);
|
|
24
|
+
if (!otherMeta) continue;
|
|
25
|
+
otherMeta.relationHydration.set(relationName, value);
|
|
26
|
+
}
|
|
27
|
+
return value;
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
meta.relationCache.set(relationName, promise);
|
|
31
|
+
|
|
32
|
+
for (const tracked of meta.ctx.getEntitiesForTable(meta.table)) {
|
|
33
|
+
const otherMeta = getEntityMeta(tracked.entity);
|
|
34
|
+
if (!otherMeta) continue;
|
|
35
|
+
otherMeta.relationCache.set(relationName, promise);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
return promise;
|
|
39
|
+
};
|
package/src/orm/entity.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { TableDef } from '../schema/table.js';
|
|
2
2
|
import { EntityInstance } from '../schema/types.js';
|
|
3
|
-
import type { EntityContext, PrimaryKey } from './entity-context.js';
|
|
3
|
+
import type { EntityContext, PrimaryKey } from './entity-context.js';
|
|
4
4
|
import { ENTITY_META, EntityMeta, RelationKey } from './entity-meta.js';
|
|
5
5
|
import { findPrimaryKey } from '../query-builder/hydration-planner.js';
|
|
6
6
|
import { RelationIncludeOptions } from '../query-builder/relation-types.js';
|
|
@@ -109,13 +109,13 @@ export const createEntityFromRow = <
|
|
|
109
109
|
const pkName = findPrimaryKey(table);
|
|
110
110
|
const pkValue = row[pkName];
|
|
111
111
|
if (pkValue !== undefined && pkValue !== null) {
|
|
112
|
-
const tracked = ctx.getEntity(table, pkValue as PrimaryKey);
|
|
112
|
+
const tracked = ctx.getEntity(table, pkValue as PrimaryKey);
|
|
113
113
|
if (tracked) return tracked as TResult;
|
|
114
114
|
}
|
|
115
115
|
|
|
116
116
|
const entity = createEntityProxy(ctx, table, row, lazyRelations, lazyRelationOptions);
|
|
117
117
|
if (pkValue !== undefined && pkValue !== null) {
|
|
118
|
-
ctx.trackManaged(table, pkValue as PrimaryKey, entity);
|
|
118
|
+
ctx.trackManaged(table, pkValue as PrimaryKey, entity);
|
|
119
119
|
} else {
|
|
120
120
|
ctx.trackNew(table, entity);
|
|
121
121
|
}
|
package/src/orm/orm-session.ts
CHANGED
|
@@ -28,9 +28,9 @@ import {
|
|
|
28
28
|
TrackedEntity
|
|
29
29
|
} from './runtime-types.js';
|
|
30
30
|
import { executeHydrated } from './execute.js';
|
|
31
|
-
import { runInTransaction } from './transaction-runner.js';
|
|
32
|
-
import { saveGraphInternal, SaveGraphOptions } from './save-graph.js';
|
|
33
|
-
import type { SaveGraphInputPayload } from './save-graph-types.js';
|
|
31
|
+
import { runInTransaction } from './transaction-runner.js';
|
|
32
|
+
import { saveGraphInternal, SaveGraphOptions } from './save-graph.js';
|
|
33
|
+
import type { SaveGraphInputPayload } from './save-graph-types.js';
|
|
34
34
|
|
|
35
35
|
/**
|
|
36
36
|
* Interface for ORM interceptors that allow hooking into the flush lifecycle.
|
|
@@ -53,7 +53,7 @@ export interface OrmInterceptor {
|
|
|
53
53
|
* Options for creating an OrmSession instance.
|
|
54
54
|
* @template E - The domain event type
|
|
55
55
|
*/
|
|
56
|
-
export interface OrmSessionOptions<E extends DomainEvent = OrmDomainEvent> {
|
|
56
|
+
export interface OrmSessionOptions<E extends DomainEvent = OrmDomainEvent> {
|
|
57
57
|
/** The ORM instance */
|
|
58
58
|
orm: Orm<E>;
|
|
59
59
|
/** The database executor */
|
|
@@ -63,14 +63,21 @@ export interface OrmSessionOptions<E extends DomainEvent = OrmDomainEvent> {
|
|
|
63
63
|
/** Optional interceptors for flush lifecycle hooks */
|
|
64
64
|
interceptors?: OrmInterceptor[];
|
|
65
65
|
/** Optional domain event handlers */
|
|
66
|
-
domainEventHandlers?: InitialHandlers<E, OrmSession<E>>;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
*/
|
|
73
|
-
|
|
66
|
+
domainEventHandlers?: InitialHandlers<E, OrmSession<E>>;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export interface SaveGraphSessionOptions extends SaveGraphOptions {
|
|
70
|
+
/** Wrap the save operation in a transaction (default: true). */
|
|
71
|
+
transactional?: boolean;
|
|
72
|
+
/** Flush after saveGraph when not transactional (default: false). */
|
|
73
|
+
flush?: boolean;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* ORM Session that manages entity lifecycle, identity mapping, and database operations.
|
|
78
|
+
* @template E - The domain event type
|
|
79
|
+
*/
|
|
80
|
+
export class OrmSession<E extends DomainEvent = OrmDomainEvent> implements EntityContext {
|
|
74
81
|
/** The ORM instance */
|
|
75
82
|
readonly orm: Orm<E>;
|
|
76
83
|
/** The database executor */
|
|
@@ -81,10 +88,11 @@ export class OrmSession<E extends DomainEvent = OrmDomainEvent> implements Entit
|
|
|
81
88
|
readonly unitOfWork: UnitOfWork;
|
|
82
89
|
/** The domain event bus */
|
|
83
90
|
readonly domainEvents: DomainEventBus<E, OrmSession<E>>;
|
|
84
|
-
/** The relation change processor */
|
|
85
|
-
readonly relationChanges: RelationChangeProcessor;
|
|
86
|
-
|
|
87
|
-
private readonly interceptors: OrmInterceptor[];
|
|
91
|
+
/** The relation change processor */
|
|
92
|
+
readonly relationChanges: RelationChangeProcessor;
|
|
93
|
+
|
|
94
|
+
private readonly interceptors: OrmInterceptor[];
|
|
95
|
+
private saveGraphDefaults?: SaveGraphSessionOptions;
|
|
88
96
|
|
|
89
97
|
/**
|
|
90
98
|
* Creates a new OrmSession instance.
|
|
@@ -236,12 +244,22 @@ export class OrmSession<E extends DomainEvent = OrmDomainEvent> implements Entit
|
|
|
236
244
|
* @param type - The event type
|
|
237
245
|
* @param handler - The event handler
|
|
238
246
|
*/
|
|
239
|
-
registerDomainEventHandler<TType extends E['type']>(
|
|
240
|
-
type: TType,
|
|
241
|
-
handler: DomainEventHandler<Extract<E, { type: TType }>, OrmSession<E>>
|
|
242
|
-
): void {
|
|
243
|
-
this.domainEvents.on(type, handler);
|
|
244
|
-
}
|
|
247
|
+
registerDomainEventHandler<TType extends E['type']>(
|
|
248
|
+
type: TType,
|
|
249
|
+
handler: DomainEventHandler<Extract<E, { type: TType }>, OrmSession<E>>
|
|
250
|
+
): void {
|
|
251
|
+
this.domainEvents.on(type, handler);
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* Sets default options applied to all saveGraph calls for this session.
|
|
256
|
+
* Per-call options override these defaults.
|
|
257
|
+
* @param defaults - Default saveGraph options for the session
|
|
258
|
+
*/
|
|
259
|
+
withSaveGraphDefaults(defaults: SaveGraphSessionOptions): this {
|
|
260
|
+
this.saveGraphDefaults = { ...defaults };
|
|
261
|
+
return this;
|
|
262
|
+
}
|
|
245
263
|
|
|
246
264
|
/**
|
|
247
265
|
* Finds an entity by its primary key.
|
|
@@ -305,23 +323,85 @@ export class OrmSession<E extends DomainEvent = OrmDomainEvent> implements Entit
|
|
|
305
323
|
* @param options - Graph save options
|
|
306
324
|
* @returns The root entity instance
|
|
307
325
|
*/
|
|
308
|
-
async saveGraph<TCtor extends EntityConstructor<object>>(
|
|
309
|
-
entityClass: TCtor,
|
|
310
|
-
payload: SaveGraphInputPayload<InstanceType<TCtor>>,
|
|
311
|
-
options?:
|
|
312
|
-
): Promise<InstanceType<TCtor>>;
|
|
313
|
-
async saveGraph<TCtor extends EntityConstructor<object>>(
|
|
314
|
-
entityClass: TCtor,
|
|
315
|
-
payload: Record<string, unknown>,
|
|
316
|
-
options?:
|
|
317
|
-
): Promise<InstanceType<TCtor>> {
|
|
318
|
-
const
|
|
319
|
-
const
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
326
|
+
async saveGraph<TCtor extends EntityConstructor<object>>(
|
|
327
|
+
entityClass: TCtor,
|
|
328
|
+
payload: SaveGraphInputPayload<InstanceType<TCtor>>,
|
|
329
|
+
options?: SaveGraphSessionOptions
|
|
330
|
+
): Promise<InstanceType<TCtor>>;
|
|
331
|
+
async saveGraph<TCtor extends EntityConstructor<object>>(
|
|
332
|
+
entityClass: TCtor,
|
|
333
|
+
payload: Record<string, unknown>,
|
|
334
|
+
options?: SaveGraphSessionOptions
|
|
335
|
+
): Promise<InstanceType<TCtor>> {
|
|
336
|
+
const resolved = this.resolveSaveGraphOptions(options);
|
|
337
|
+
const { transactional = true, flush = false, ...graphOptions } = resolved;
|
|
338
|
+
const execute = () => saveGraphInternal(this, entityClass, payload, graphOptions);
|
|
339
|
+
if (!transactional) {
|
|
340
|
+
const result = await execute();
|
|
341
|
+
if (flush) {
|
|
342
|
+
await this.flush();
|
|
343
|
+
}
|
|
344
|
+
return result;
|
|
345
|
+
}
|
|
346
|
+
return this.transaction(() => execute());
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
/**
|
|
350
|
+
* Saves an entity graph and flushes immediately (defaults to transactional: false).
|
|
351
|
+
* @param entityClass - Root entity constructor
|
|
352
|
+
* @param payload - DTO payload containing column values and nested relations
|
|
353
|
+
* @param options - Graph save options
|
|
354
|
+
* @returns The root entity instance
|
|
355
|
+
*/
|
|
356
|
+
async saveGraphAndFlush<TCtor extends EntityConstructor<object>>(
|
|
357
|
+
entityClass: TCtor,
|
|
358
|
+
payload: SaveGraphInputPayload<InstanceType<TCtor>>,
|
|
359
|
+
options?: SaveGraphSessionOptions
|
|
360
|
+
): Promise<InstanceType<TCtor>> {
|
|
361
|
+
const merged = { ...(options ?? {}), flush: true, transactional: options?.transactional ?? false };
|
|
362
|
+
return this.saveGraph(entityClass, payload, merged);
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
/**
|
|
366
|
+
* Updates an existing entity graph (requires a primary key in the payload).
|
|
367
|
+
* @param entityClass - Root entity constructor
|
|
368
|
+
* @param payload - DTO payload containing column values and nested relations
|
|
369
|
+
* @param options - Graph save options
|
|
370
|
+
* @returns The root entity instance or null if not found
|
|
371
|
+
*/
|
|
372
|
+
async updateGraph<TCtor extends EntityConstructor<object>>(
|
|
373
|
+
entityClass: TCtor,
|
|
374
|
+
payload: SaveGraphInputPayload<InstanceType<TCtor>>,
|
|
375
|
+
options?: SaveGraphSessionOptions
|
|
376
|
+
): Promise<InstanceType<TCtor> | null> {
|
|
377
|
+
const table = getTableDefFromEntity(entityClass);
|
|
378
|
+
if (!table) {
|
|
379
|
+
throw new Error('Entity metadata has not been bootstrapped');
|
|
380
|
+
}
|
|
381
|
+
const primaryKey = findPrimaryKey(table);
|
|
382
|
+
const pkValue = (payload as Record<string, unknown>)[primaryKey];
|
|
383
|
+
if (pkValue === undefined || pkValue === null) {
|
|
384
|
+
throw new Error(`updateGraph requires a primary key value for "${primaryKey}"`);
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
const resolved = this.resolveSaveGraphOptions(options);
|
|
388
|
+
const { transactional = true, flush = false, ...graphOptions } = resolved;
|
|
389
|
+
const execute = async (): Promise<InstanceType<TCtor> | null> => {
|
|
390
|
+
const tracked = this.getEntity(table, pkValue as PrimaryKey) as InstanceType<TCtor> | undefined;
|
|
391
|
+
const existing = tracked ?? await this.find(entityClass, pkValue);
|
|
392
|
+
if (!existing) return null;
|
|
393
|
+
return saveGraphInternal(this, entityClass, payload, graphOptions);
|
|
394
|
+
};
|
|
395
|
+
|
|
396
|
+
if (!transactional) {
|
|
397
|
+
const result = await execute();
|
|
398
|
+
if (result && flush) {
|
|
399
|
+
await this.flush();
|
|
400
|
+
}
|
|
401
|
+
return result;
|
|
402
|
+
}
|
|
403
|
+
return this.transaction(() => execute());
|
|
404
|
+
}
|
|
325
405
|
|
|
326
406
|
/**
|
|
327
407
|
* Persists an entity (either inserts or updates).
|
|
@@ -396,7 +476,7 @@ export class OrmSession<E extends DomainEvent = OrmDomainEvent> implements Entit
|
|
|
396
476
|
* @returns The result of the function
|
|
397
477
|
* @throws If the transaction fails
|
|
398
478
|
*/
|
|
399
|
-
async transaction<T>(fn: (session: OrmSession<E>) => Promise<T>): Promise<T> {
|
|
479
|
+
async transaction<T>(fn: (session: OrmSession<E>) => Promise<T>): Promise<T> {
|
|
400
480
|
// If the executor can't do transactions, just run and commit once.
|
|
401
481
|
if (!this.executor.capabilities.transactions) {
|
|
402
482
|
const result = await fn(this);
|
|
@@ -444,16 +524,25 @@ export class OrmSession<E extends DomainEvent = OrmDomainEvent> implements Entit
|
|
|
444
524
|
* Gets the hydration context.
|
|
445
525
|
* @returns The hydration context
|
|
446
526
|
*/
|
|
447
|
-
getHydrationContext(): HydrationContext<E> {
|
|
448
|
-
return {
|
|
449
|
-
identityMap: this.identityMap,
|
|
450
|
-
unitOfWork: this.unitOfWork,
|
|
451
|
-
domainEvents: this.domainEvents,
|
|
452
|
-
relationChanges: this.relationChanges,
|
|
453
|
-
entityContext: this
|
|
454
|
-
};
|
|
455
|
-
}
|
|
456
|
-
|
|
527
|
+
getHydrationContext(): HydrationContext<E> {
|
|
528
|
+
return {
|
|
529
|
+
identityMap: this.identityMap,
|
|
530
|
+
unitOfWork: this.unitOfWork,
|
|
531
|
+
domainEvents: this.domainEvents,
|
|
532
|
+
relationChanges: this.relationChanges,
|
|
533
|
+
entityContext: this
|
|
534
|
+
};
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
/**
|
|
538
|
+
* Merges session defaults with per-call saveGraph options.
|
|
539
|
+
* @param options - Per-call saveGraph options
|
|
540
|
+
* @returns Combined options with per-call values taking precedence
|
|
541
|
+
*/
|
|
542
|
+
private resolveSaveGraphOptions(options?: SaveGraphSessionOptions): SaveGraphSessionOptions {
|
|
543
|
+
return { ...(this.saveGraphDefaults ?? {}), ...(options ?? {}) };
|
|
544
|
+
}
|
|
545
|
+
}
|
|
457
546
|
|
|
458
547
|
const buildRelationChangeEntry = (
|
|
459
548
|
root: unknown,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { BelongsToReferenceApi } from '../../schema/types.js';
|
|
1
|
+
import { BelongsToReferenceApi } from '../../schema/types.js';
|
|
2
2
|
import { EntityContext } from '../entity-context.js';
|
|
3
3
|
import { RelationKey } from '../runtime-types.js';
|
|
4
4
|
import { BelongsToRelation } from '../../schema/relation.js';
|
|
@@ -26,7 +26,7 @@ const hideInternal = (obj: object, keys: string[]): void => {
|
|
|
26
26
|
*
|
|
27
27
|
* @template TParent The type of the parent entity.
|
|
28
28
|
*/
|
|
29
|
-
export class DefaultBelongsToReference<TParent extends object> implements BelongsToReferenceApi<TParent> {
|
|
29
|
+
export class DefaultBelongsToReference<TParent extends object> implements BelongsToReferenceApi<TParent> {
|
|
30
30
|
private loaded = false;
|
|
31
31
|
private current: TParent | null = null;
|
|
32
32
|
|
|
@@ -69,29 +69,29 @@ export class DefaultHasManyCollection<TChild> implements HasManyCollection<TChil
|
|
|
69
69
|
this.items = rows.map(row => this.createEntity(row));
|
|
70
70
|
this.loaded = true;
|
|
71
71
|
return this.items;
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
/**
|
|
75
|
-
* Gets the current items in the collection.
|
|
76
|
-
* @returns Array of child entities
|
|
77
|
-
*/
|
|
78
|
-
getItems(): TChild[] {
|
|
79
|
-
return this.items;
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
/**
|
|
83
|
-
* Array-compatible length for testing frameworks.
|
|
84
|
-
*/
|
|
85
|
-
get length(): number {
|
|
86
|
-
return this.items.length;
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
/**
|
|
90
|
-
* Enables iteration over the collection like an array.
|
|
91
|
-
*/
|
|
92
|
-
[Symbol.iterator](): Iterator<TChild> {
|
|
93
|
-
return this.items[Symbol.iterator]();
|
|
94
|
-
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Gets the current items in the collection.
|
|
76
|
+
* @returns Array of child entities
|
|
77
|
+
*/
|
|
78
|
+
getItems(): TChild[] {
|
|
79
|
+
return this.items;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Array-compatible length for testing frameworks.
|
|
84
|
+
*/
|
|
85
|
+
get length(): number {
|
|
86
|
+
return this.items.length;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Enables iteration over the collection like an array.
|
|
91
|
+
*/
|
|
92
|
+
[Symbol.iterator](): Iterator<TChild> {
|
|
93
|
+
return this.items[Symbol.iterator]();
|
|
94
|
+
}
|
|
95
95
|
|
|
96
96
|
/**
|
|
97
97
|
* Adds a new child entity to the collection.
|
|
@@ -125,7 +125,7 @@ export class DefaultHasManyCollection<TChild> implements HasManyCollection<TChil
|
|
|
125
125
|
attach(entity: TChild): void {
|
|
126
126
|
const keyValue = this.root[this.localKey];
|
|
127
127
|
(entity as Record<string, unknown>)[this.relation.foreignKey] = keyValue;
|
|
128
|
-
this.ctx.markDirty(entity as object);
|
|
128
|
+
this.ctx.markDirty(entity as object);
|
|
129
129
|
this.items.push(entity);
|
|
130
130
|
this.ctx.registerRelationChange(
|
|
131
131
|
this.root,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { HasOneReferenceApi } from '../../schema/types.js';
|
|
1
|
+
import { HasOneReferenceApi } from '../../schema/types.js';
|
|
2
2
|
import { EntityContext } from '../entity-context.js';
|
|
3
3
|
import { RelationKey } from '../runtime-types.js';
|
|
4
4
|
import { HasOneRelation } from '../../schema/relation.js';
|
|
@@ -26,7 +26,7 @@ const hideInternal = (obj: object, keys: string[]): void => {
|
|
|
26
26
|
*
|
|
27
27
|
* @template TChild The type of the child entity.
|
|
28
28
|
*/
|
|
29
|
-
export class DefaultHasOneReference<TChild extends object> implements HasOneReferenceApi<TChild> {
|
|
29
|
+
export class DefaultHasOneReference<TChild extends object> implements HasOneReferenceApi<TChild> {
|
|
30
30
|
private loaded = false;
|
|
31
31
|
private current: TChild | null = null;
|
|
32
32
|
|
|
@@ -28,8 +28,8 @@ const hideInternal = (obj: object, keys: string[]): void => {
|
|
|
28
28
|
*
|
|
29
29
|
* @template TTarget The type of the target entities in the collection.
|
|
30
30
|
*/
|
|
31
|
-
export class DefaultManyToManyCollection<TTarget, TPivot extends object | undefined = undefined>
|
|
32
|
-
implements ManyToManyCollection<TTarget, TPivot> {
|
|
31
|
+
export class DefaultManyToManyCollection<TTarget, TPivot extends object | undefined = undefined>
|
|
32
|
+
implements ManyToManyCollection<TTarget, TPivot> {
|
|
33
33
|
private loaded = false;
|
|
34
34
|
private items: TTarget[] = [];
|
|
35
35
|
|
|
@@ -79,33 +79,33 @@ export class DefaultManyToManyCollection<TTarget, TPivot extends object | undefi
|
|
|
79
79
|
return this.items;
|
|
80
80
|
}
|
|
81
81
|
|
|
82
|
-
/**
|
|
83
|
-
* Returns the currently loaded items.
|
|
84
|
-
* @returns Array of target entities.
|
|
85
|
-
*/
|
|
86
|
-
getItems(): TTarget[] {
|
|
87
|
-
return this.items;
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
/**
|
|
91
|
-
* Array-compatible length for testing frameworks.
|
|
92
|
-
*/
|
|
93
|
-
get length(): number {
|
|
94
|
-
return this.items.length;
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
/**
|
|
98
|
-
* Enables iteration over the collection like an array.
|
|
99
|
-
*/
|
|
100
|
-
[Symbol.iterator](): Iterator<TTarget> {
|
|
101
|
-
return this.items[Symbol.iterator]();
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
/**
|
|
105
|
-
* Attaches an entity to the collection.
|
|
106
|
-
* Registers an 'attach' change in the entity context.
|
|
107
|
-
* @param target Entity instance or its primary key value.
|
|
108
|
-
*/
|
|
82
|
+
/**
|
|
83
|
+
* Returns the currently loaded items.
|
|
84
|
+
* @returns Array of target entities.
|
|
85
|
+
*/
|
|
86
|
+
getItems(): TTarget[] {
|
|
87
|
+
return this.items;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Array-compatible length for testing frameworks.
|
|
92
|
+
*/
|
|
93
|
+
get length(): number {
|
|
94
|
+
return this.items.length;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Enables iteration over the collection like an array.
|
|
99
|
+
*/
|
|
100
|
+
[Symbol.iterator](): Iterator<TTarget> {
|
|
101
|
+
return this.items[Symbol.iterator]();
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Attaches an entity to the collection.
|
|
106
|
+
* Registers an 'attach' change in the entity context.
|
|
107
|
+
* @param target Entity instance or its primary key value.
|
|
108
|
+
*/
|
|
109
109
|
attach(target: TTarget | number | string): void {
|
|
110
110
|
const entity = this.ensureEntity(target);
|
|
111
111
|
const id = this.extractId(entity);
|
|
@@ -1,55 +1,56 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
BelongsToReference,
|
|
3
|
-
HasManyCollection,
|
|
4
|
-
HasOneReference,
|
|
5
|
-
ManyToManyCollection
|
|
6
|
-
} from '../schema/types.js';
|
|
7
|
-
|
|
8
|
-
type AnyId = number | string;
|
|
9
|
-
type AnyFn = (...args: unknown[]) => unknown;
|
|
10
|
-
|
|
11
|
-
type RelationWrapper =
|
|
12
|
-
| HasManyCollection<unknown>
|
|
13
|
-
| HasOneReference
|
|
14
|
-
| BelongsToReference
|
|
15
|
-
| ManyToManyCollection<unknown>;
|
|
16
|
-
|
|
17
|
-
type FunctionKeys<T> = {
|
|
18
|
-
[K in keyof T & string]-?: T[K] extends AnyFn ? K : never;
|
|
19
|
-
}[keyof T & string];
|
|
20
|
-
|
|
21
|
-
type RelationKeys<T> = {
|
|
22
|
-
[K in keyof T & string]-?: NonNullable<T[K]> extends RelationWrapper ? K : never;
|
|
23
|
-
}[keyof T & string];
|
|
24
|
-
|
|
25
|
-
type ColumnKeys<T> = Exclude<keyof T & string, FunctionKeys<T> | RelationKeys<T>>;
|
|
26
|
-
|
|
27
|
-
export type SaveGraphJsonScalar<T> = T extends Date ? string : T;
|
|
28
|
-
|
|
1
|
+
import type {
|
|
2
|
+
BelongsToReference,
|
|
3
|
+
HasManyCollection,
|
|
4
|
+
HasOneReference,
|
|
5
|
+
ManyToManyCollection
|
|
6
|
+
} from '../schema/types.js';
|
|
7
|
+
|
|
8
|
+
type AnyId = number | string;
|
|
9
|
+
type AnyFn = (...args: unknown[]) => unknown;
|
|
10
|
+
|
|
11
|
+
type RelationWrapper =
|
|
12
|
+
| HasManyCollection<unknown>
|
|
13
|
+
| HasOneReference
|
|
14
|
+
| BelongsToReference
|
|
15
|
+
| ManyToManyCollection<unknown>;
|
|
16
|
+
|
|
17
|
+
type FunctionKeys<T> = {
|
|
18
|
+
[K in keyof T & string]-?: T[K] extends AnyFn ? K : never;
|
|
19
|
+
}[keyof T & string];
|
|
20
|
+
|
|
21
|
+
type RelationKeys<T> = {
|
|
22
|
+
[K in keyof T & string]-?: NonNullable<T[K]> extends RelationWrapper ? K : never;
|
|
23
|
+
}[keyof T & string];
|
|
24
|
+
|
|
25
|
+
type ColumnKeys<T> = Exclude<keyof T & string, FunctionKeys<T> | RelationKeys<T>>;
|
|
26
|
+
|
|
27
|
+
export type SaveGraphJsonScalar<T> = T extends Date ? string : T;
|
|
28
|
+
|
|
29
29
|
/**
|
|
30
30
|
* Input scalar type for `OrmSession.saveGraph` payloads.
|
|
31
31
|
*
|
|
32
32
|
* Note: runtime coercion is opt-in via `SaveGraphOptions.coerce`.
|
|
33
|
+
* For Date columns, string input is only safe when using `coerce: 'json-in'`.
|
|
33
34
|
*/
|
|
34
|
-
export type SaveGraphInputScalar<T> = T;
|
|
35
|
-
|
|
36
|
-
type ColumnInput<TEntity> = {
|
|
37
|
-
[K in ColumnKeys<TEntity>]?: SaveGraphInputScalar<TEntity[K]>;
|
|
38
|
-
};
|
|
39
|
-
|
|
40
|
-
type RelationInputValue<T> =
|
|
41
|
-
T extends HasManyCollection<infer C> ? Array<SaveGraphInputPayload<C> | AnyId> :
|
|
42
|
-
T extends HasOneReference<infer C> ? SaveGraphInputPayload<C> | AnyId | null :
|
|
43
|
-
T extends BelongsToReference<infer P> ? SaveGraphInputPayload<P> | AnyId | null :
|
|
44
|
-
T extends ManyToManyCollection<infer Tgt> ? Array<SaveGraphInputPayload<Tgt> | AnyId> :
|
|
45
|
-
never;
|
|
46
|
-
|
|
47
|
-
type RelationInput<TEntity> = {
|
|
48
|
-
[K in RelationKeys<TEntity>]?: RelationInputValue<NonNullable<TEntity[K]>>;
|
|
49
|
-
};
|
|
50
|
-
|
|
51
|
-
/**
|
|
52
|
-
* Typed payload accepted by `OrmSession.saveGraph`:
|
|
53
|
-
* - Only entity scalar keys + relation keys are accepted.
|
|
54
|
-
*/
|
|
55
|
-
export type SaveGraphInputPayload<TEntity> = ColumnInput<TEntity> & RelationInput<TEntity>;
|
|
35
|
+
export type SaveGraphInputScalar<T> = T extends Date ? T | string | number : T;
|
|
36
|
+
|
|
37
|
+
type ColumnInput<TEntity> = {
|
|
38
|
+
[K in ColumnKeys<TEntity>]?: SaveGraphInputScalar<TEntity[K]>;
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
type RelationInputValue<T> =
|
|
42
|
+
T extends HasManyCollection<infer C> ? Array<SaveGraphInputPayload<C> | AnyId> :
|
|
43
|
+
T extends HasOneReference<infer C> ? SaveGraphInputPayload<C> | AnyId | null :
|
|
44
|
+
T extends BelongsToReference<infer P> ? SaveGraphInputPayload<P> | AnyId | null :
|
|
45
|
+
T extends ManyToManyCollection<infer Tgt> ? Array<SaveGraphInputPayload<Tgt> | AnyId> :
|
|
46
|
+
never;
|
|
47
|
+
|
|
48
|
+
type RelationInput<TEntity> = {
|
|
49
|
+
[K in RelationKeys<TEntity>]?: RelationInputValue<NonNullable<TEntity[K]>>;
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Typed payload accepted by `OrmSession.saveGraph`:
|
|
54
|
+
* - Only entity scalar keys + relation keys are accepted.
|
|
55
|
+
*/
|
|
56
|
+
export type SaveGraphInputPayload<TEntity> = ColumnInput<TEntity> & RelationInput<TEntity>;
|