metal-orm 1.0.57 → 1.0.58
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/README.md +22 -15
- package/dist/index.cjs +640 -83
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +113 -45
- package/dist/index.d.ts +113 -45
- package/dist/index.js +639 -83
- package/dist/index.js.map +1 -1
- package/package.json +69 -69
- package/src/decorators/bootstrap.ts +39 -3
- package/src/orm/entity-meta.ts +6 -3
- package/src/orm/entity.ts +81 -14
- package/src/orm/execute.ts +87 -20
- package/src/orm/lazy-batch.ts +237 -54
- package/src/orm/relations/belongs-to.ts +2 -2
- package/src/orm/relations/has-many.ts +23 -9
- package/src/orm/relations/has-one.ts +2 -2
- package/src/orm/relations/many-to-many.ts +27 -13
- package/src/orm/save-graph-types.ts +2 -2
- package/src/orm/save-graph.ts +18 -18
- package/src/query-builder/relation-conditions.ts +80 -59
- package/src/query-builder/relation-service.ts +399 -95
- package/src/query-builder/relation-types.ts +2 -2
- package/src/query-builder/select.ts +58 -40
- package/src/schema/types.ts +106 -89
|
@@ -32,7 +32,7 @@ import { QueryAstService } from './query-ast-service.js';
|
|
|
32
32
|
import { ColumnSelector } from './column-selector.js';
|
|
33
33
|
import { RelationManager } from './relation-manager.js';
|
|
34
34
|
import { RelationIncludeOptions } from './relation-types.js';
|
|
35
|
-
import type
|
|
35
|
+
import { RelationKinds, type RelationDef } from '../schema/relation.js';
|
|
36
36
|
import { JOIN_KINDS, JoinKind, ORDER_DIRECTIONS, OrderDirection } from '../core/sql/sql.js';
|
|
37
37
|
import { EntityInstance, RelationMap, RelationTargetTable } from '../schema/types.js';
|
|
38
38
|
import { OrmSession } from '../orm/orm-session.ts';
|
|
@@ -76,6 +76,7 @@ export class SelectQueryBuilder<T = unknown, TTable extends TableDef = TableDef>
|
|
|
76
76
|
private readonly columnSelector: ColumnSelector;
|
|
77
77
|
private readonly relationManager: RelationManager;
|
|
78
78
|
private readonly lazyRelations: Set<string>;
|
|
79
|
+
private readonly lazyRelationOptions: Map<string, RelationIncludeOptions>;
|
|
79
80
|
|
|
80
81
|
/**
|
|
81
82
|
* Creates a new SelectQueryBuilder instance
|
|
@@ -89,7 +90,8 @@ export class SelectQueryBuilder<T = unknown, TTable extends TableDef = TableDef>
|
|
|
89
90
|
state?: SelectQueryState,
|
|
90
91
|
hydration?: HydrationManager,
|
|
91
92
|
dependencies?: Partial<SelectQueryBuilderDependencies>,
|
|
92
|
-
lazyRelations?: Set<string
|
|
93
|
+
lazyRelations?: Set<string>,
|
|
94
|
+
lazyRelationOptions?: Map<string, RelationIncludeOptions>
|
|
93
95
|
) {
|
|
94
96
|
const deps = resolveSelectQueryBuilderDependencies(dependencies);
|
|
95
97
|
this.env = { table, deps };
|
|
@@ -100,6 +102,7 @@ export class SelectQueryBuilder<T = unknown, TTable extends TableDef = TableDef>
|
|
|
100
102
|
hydration: initialHydration
|
|
101
103
|
};
|
|
102
104
|
this.lazyRelations = new Set(lazyRelations ?? []);
|
|
105
|
+
this.lazyRelationOptions = new Map(lazyRelationOptions ?? []);
|
|
103
106
|
this.columnSelector = deps.createColumnSelector(this.env);
|
|
104
107
|
this.relationManager = deps.createRelationManager(this.env);
|
|
105
108
|
}
|
|
@@ -112,9 +115,17 @@ export class SelectQueryBuilder<T = unknown, TTable extends TableDef = TableDef>
|
|
|
112
115
|
*/
|
|
113
116
|
private clone(
|
|
114
117
|
context: SelectQueryBuilderContext = this.context,
|
|
115
|
-
lazyRelations = new Set(this.lazyRelations)
|
|
118
|
+
lazyRelations = new Set(this.lazyRelations),
|
|
119
|
+
lazyRelationOptions = new Map(this.lazyRelationOptions)
|
|
116
120
|
): SelectQueryBuilder<T, TTable> {
|
|
117
|
-
return new SelectQueryBuilder(
|
|
121
|
+
return new SelectQueryBuilder(
|
|
122
|
+
this.env.table as TTable,
|
|
123
|
+
context.state,
|
|
124
|
+
context.hydration,
|
|
125
|
+
this.env.deps,
|
|
126
|
+
lazyRelations,
|
|
127
|
+
lazyRelationOptions
|
|
128
|
+
);
|
|
118
129
|
}
|
|
119
130
|
|
|
120
131
|
/**
|
|
@@ -445,42 +456,41 @@ export class SelectQueryBuilder<T = unknown, TTable extends TableDef = TableDef>
|
|
|
445
456
|
/**
|
|
446
457
|
* Includes a relation lazily in the query results
|
|
447
458
|
* @param relationName - Name of the relation to include lazily
|
|
459
|
+
* @param options - Optional include options for lazy loading
|
|
448
460
|
* @returns New query builder instance with lazy relation inclusion
|
|
449
461
|
*/
|
|
450
|
-
includeLazy<K extends keyof RelationMap<TTable>>(
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
}
|
|
469
|
-
const
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
/**
|
|
483
|
-
* Convenience alias for selecting specific columns from a relation.
|
|
462
|
+
includeLazy<K extends keyof RelationMap<TTable>>(
|
|
463
|
+
relationName: K,
|
|
464
|
+
options?: RelationIncludeOptions
|
|
465
|
+
): SelectQueryBuilder<T, TTable> {
|
|
466
|
+
let nextContext = this.context;
|
|
467
|
+
const relation = this.env.table.relations[relationName as string];
|
|
468
|
+
if (relation?.type === RelationKinds.BelongsTo) {
|
|
469
|
+
const foreignKey = relation.foreignKey;
|
|
470
|
+
const fkColumn = this.env.table.columns[foreignKey];
|
|
471
|
+
if (fkColumn) {
|
|
472
|
+
const hasAlias = nextContext.state.ast.columns.some(col => {
|
|
473
|
+
const node = col as { alias?: string; name?: string };
|
|
474
|
+
return (node.alias ?? node.name) === foreignKey;
|
|
475
|
+
});
|
|
476
|
+
if (!hasAlias) {
|
|
477
|
+
nextContext = this.columnSelector.select(nextContext, { [foreignKey]: fkColumn });
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
const nextLazy = new Set(this.lazyRelations);
|
|
482
|
+
nextLazy.add(relationName as string);
|
|
483
|
+
const nextOptions = new Map(this.lazyRelationOptions);
|
|
484
|
+
if (options) {
|
|
485
|
+
nextOptions.set(relationName as string, options);
|
|
486
|
+
} else {
|
|
487
|
+
nextOptions.delete(relationName as string);
|
|
488
|
+
}
|
|
489
|
+
return this.clone(nextContext, nextLazy, nextOptions);
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
/**
|
|
493
|
+
* Convenience alias for including only specific columns from a relation.
|
|
484
494
|
*/
|
|
485
495
|
includePick<
|
|
486
496
|
K extends keyof TTable['relations'] & string,
|
|
@@ -488,7 +498,7 @@ export class SelectQueryBuilder<T = unknown, TTable extends TableDef = TableDef>
|
|
|
488
498
|
TTarget extends TableDef = RelationTargetTable<TRel>,
|
|
489
499
|
C extends keyof TTarget['columns'] & string = keyof TTarget['columns'] & string
|
|
490
500
|
>(relationName: K, cols: C[]): SelectQueryBuilder<T, TTable> {
|
|
491
|
-
return this.
|
|
501
|
+
return this.include(relationName, { columns: cols as readonly string[] });
|
|
492
502
|
}
|
|
493
503
|
|
|
494
504
|
|
|
@@ -505,7 +515,7 @@ export class SelectQueryBuilder<T = unknown, TTable extends TableDef = TableDef>
|
|
|
505
515
|
if (entry.type === 'root') {
|
|
506
516
|
currBuilder = currBuilder.select(...entry.columns);
|
|
507
517
|
} else {
|
|
508
|
-
currBuilder = currBuilder.
|
|
518
|
+
currBuilder = currBuilder.include(entry.relationName, { columns: entry.columns as string[] });
|
|
509
519
|
}
|
|
510
520
|
}
|
|
511
521
|
|
|
@@ -520,6 +530,14 @@ export class SelectQueryBuilder<T = unknown, TTable extends TableDef = TableDef>
|
|
|
520
530
|
return Array.from(this.lazyRelations) as (keyof RelationMap<TTable>)[];
|
|
521
531
|
}
|
|
522
532
|
|
|
533
|
+
/**
|
|
534
|
+
* Gets lazy relation include options
|
|
535
|
+
* @returns Map of relation names to include options
|
|
536
|
+
*/
|
|
537
|
+
getLazyRelationOptions(): Map<string, RelationIncludeOptions> {
|
|
538
|
+
return new Map(this.lazyRelationOptions);
|
|
539
|
+
}
|
|
540
|
+
|
|
523
541
|
/**
|
|
524
542
|
* Gets the table definition for this query builder
|
|
525
543
|
* @returns Table definition
|
package/src/schema/types.ts
CHANGED
|
@@ -1,111 +1,128 @@
|
|
|
1
|
-
import type { ColumnDef } from './column-types.js';
|
|
2
|
-
export type { ColumnDef };
|
|
3
|
-
import { TableDef } from './table.js';
|
|
4
|
-
import {
|
|
5
|
-
RelationDef,
|
|
6
|
-
HasManyRelation,
|
|
7
|
-
HasOneRelation,
|
|
8
|
-
BelongsToRelation,
|
|
9
|
-
BelongsToManyRelation
|
|
10
|
-
} from './relation.js';
|
|
11
|
-
|
|
12
|
-
/**
|
|
13
|
-
* Resolves a relation definition to its target table type.
|
|
14
|
-
*/
|
|
15
|
-
export type RelationTargetTable<TRel extends RelationDef> =
|
|
16
|
-
TRel extends HasManyRelation<infer TTarget> ? TTarget :
|
|
17
|
-
TRel extends HasOneRelation<infer TTarget> ? TTarget :
|
|
18
|
-
TRel extends BelongsToRelation<infer TTarget> ? TTarget :
|
|
19
|
-
TRel extends BelongsToManyRelation<infer TTarget> ? TTarget :
|
|
20
|
-
never;
|
|
21
|
-
|
|
22
|
-
type NormalizedColumnType<T extends ColumnDef> = Lowercase<T['type'] & string>;
|
|
23
|
-
|
|
24
|
-
/**
|
|
25
|
-
* Maps a ColumnDef to its TypeScript type representation
|
|
26
|
-
*/
|
|
27
|
-
export type ColumnToTs<T extends ColumnDef> =
|
|
28
|
-
[unknown] extends [T['tsType']]
|
|
29
|
-
? NormalizedColumnType<T> extends 'int' | 'integer' ? number :
|
|
30
|
-
NormalizedColumnType<T> extends 'bigint' ? number | bigint :
|
|
31
|
-
NormalizedColumnType<T> extends 'decimal' | 'float' | 'double' ? number :
|
|
32
|
-
NormalizedColumnType<T> extends 'boolean' ? boolean :
|
|
33
|
-
NormalizedColumnType<T> extends 'json' ? unknown :
|
|
34
|
-
NormalizedColumnType<T> extends 'blob' | 'binary' | 'varbinary' | 'bytea' ? Buffer :
|
|
35
|
-
NormalizedColumnType<T> extends 'date' | 'datetime' | 'timestamp' | 'timestamptz' ? string :
|
|
36
|
-
string
|
|
37
|
-
: Exclude<T['tsType'], undefined>;
|
|
38
|
-
|
|
39
|
-
/**
|
|
40
|
-
* Infers a row shape from a table definition
|
|
41
|
-
*/
|
|
42
|
-
export type InferRow<TTable extends TableDef> = {
|
|
43
|
-
[K in keyof TTable['columns']]: ColumnToTs<TTable['columns'][K]>;
|
|
44
|
-
};
|
|
45
|
-
|
|
1
|
+
import type { ColumnDef } from './column-types.js';
|
|
2
|
+
export type { ColumnDef };
|
|
3
|
+
import { TableDef } from './table.js';
|
|
4
|
+
import {
|
|
5
|
+
RelationDef,
|
|
6
|
+
HasManyRelation,
|
|
7
|
+
HasOneRelation,
|
|
8
|
+
BelongsToRelation,
|
|
9
|
+
BelongsToManyRelation
|
|
10
|
+
} from './relation.js';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Resolves a relation definition to its target table type.
|
|
14
|
+
*/
|
|
15
|
+
export type RelationTargetTable<TRel extends RelationDef> =
|
|
16
|
+
TRel extends HasManyRelation<infer TTarget> ? TTarget :
|
|
17
|
+
TRel extends HasOneRelation<infer TTarget> ? TTarget :
|
|
18
|
+
TRel extends BelongsToRelation<infer TTarget> ? TTarget :
|
|
19
|
+
TRel extends BelongsToManyRelation<infer TTarget> ? TTarget :
|
|
20
|
+
never;
|
|
21
|
+
|
|
22
|
+
type NormalizedColumnType<T extends ColumnDef> = Lowercase<T['type'] & string>;
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Maps a ColumnDef to its TypeScript type representation
|
|
26
|
+
*/
|
|
27
|
+
export type ColumnToTs<T extends ColumnDef> =
|
|
28
|
+
[unknown] extends [T['tsType']]
|
|
29
|
+
? NormalizedColumnType<T> extends 'int' | 'integer' ? number :
|
|
30
|
+
NormalizedColumnType<T> extends 'bigint' ? number | bigint :
|
|
31
|
+
NormalizedColumnType<T> extends 'decimal' | 'float' | 'double' ? number :
|
|
32
|
+
NormalizedColumnType<T> extends 'boolean' ? boolean :
|
|
33
|
+
NormalizedColumnType<T> extends 'json' ? unknown :
|
|
34
|
+
NormalizedColumnType<T> extends 'blob' | 'binary' | 'varbinary' | 'bytea' ? Buffer :
|
|
35
|
+
NormalizedColumnType<T> extends 'date' | 'datetime' | 'timestamp' | 'timestamptz' ? string :
|
|
36
|
+
string
|
|
37
|
+
: Exclude<T['tsType'], undefined>;
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Infers a row shape from a table definition
|
|
41
|
+
*/
|
|
42
|
+
export type InferRow<TTable extends TableDef> = {
|
|
43
|
+
[K in keyof TTable['columns']]: ColumnToTs<TTable['columns'][K]>;
|
|
44
|
+
};
|
|
45
|
+
|
|
46
46
|
type RelationResult<T extends RelationDef> =
|
|
47
47
|
T extends HasManyRelation<infer TTarget> ? InferRow<TTarget>[] :
|
|
48
48
|
T extends HasOneRelation<infer TTarget> ? InferRow<TTarget> | null :
|
|
49
49
|
T extends BelongsToRelation<infer TTarget> ? InferRow<TTarget> | null :
|
|
50
|
-
T extends BelongsToManyRelation<infer TTarget> ? (InferRow<TTarget> & { _pivot?: unknown })[] :
|
|
50
|
+
T extends BelongsToManyRelation<infer TTarget> ? (InferRow<TTarget> & { _pivot?: Record<string, unknown> })[] :
|
|
51
51
|
never;
|
|
52
|
-
|
|
53
|
-
/**
|
|
54
|
-
* Maps relation names to the expected row results
|
|
55
|
-
*/
|
|
56
|
-
export type RelationMap<TTable extends TableDef> = {
|
|
57
|
-
[K in keyof TTable['relations']]: RelationResult<TTable['relations'][K]>;
|
|
58
|
-
};
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Maps relation names to the expected row results
|
|
55
|
+
*/
|
|
56
|
+
export type RelationMap<TTable extends TableDef> = {
|
|
57
|
+
[K in keyof TTable['relations']]: RelationResult<TTable['relations'][K]>;
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
type RelationWrapper<TRel extends RelationDef> =
|
|
61
|
+
TRel extends HasManyRelation<infer TTarget>
|
|
62
|
+
? HasManyCollection<EntityInstance<TTarget>> & ReadonlyArray<EntityInstance<TTarget>>
|
|
63
|
+
: TRel extends HasOneRelation<infer TTarget>
|
|
64
|
+
? HasOneReference<EntityInstance<TTarget>>
|
|
65
|
+
: TRel extends BelongsToManyRelation<infer TTarget>
|
|
66
|
+
? ManyToManyCollection<EntityInstance<TTarget> & { _pivot?: Record<string, unknown> }>
|
|
67
|
+
& ReadonlyArray<EntityInstance<TTarget> & { _pivot?: Record<string, unknown> }>
|
|
68
|
+
: TRel extends BelongsToRelation<infer TTarget>
|
|
69
|
+
? BelongsToReference<EntityInstance<TTarget>>
|
|
70
|
+
: never;
|
|
59
71
|
|
|
60
72
|
export interface HasManyCollection<TChild> {
|
|
73
|
+
length: number;
|
|
74
|
+
[Symbol.iterator](): Iterator<TChild>;
|
|
61
75
|
load(): Promise<TChild[]>;
|
|
62
76
|
getItems(): TChild[];
|
|
63
77
|
add(data: Partial<TChild>): TChild;
|
|
64
78
|
attach(entity: TChild): void;
|
|
65
79
|
remove(entity: TChild): void;
|
|
66
|
-
clear(): void;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
export interface
|
|
70
|
-
load(): Promise<TParent | null>;
|
|
71
|
-
get(): TParent | null;
|
|
72
|
-
set(data: Partial<TParent> | TParent | null): TParent | null;
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
export
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
80
|
+
clear(): void;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export interface BelongsToReferenceApi<TParent extends object = object> {
|
|
84
|
+
load(): Promise<TParent | null>;
|
|
85
|
+
get(): TParent | null;
|
|
86
|
+
set(data: Partial<TParent> | TParent | null): TParent | null;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export type BelongsToReference<TParent extends object = object> = BelongsToReferenceApi<TParent> & Partial<TParent>;
|
|
90
|
+
|
|
91
|
+
export interface HasOneReferenceApi<TChild extends object = object> {
|
|
92
|
+
load(): Promise<TChild | null>;
|
|
93
|
+
get(): TChild | null;
|
|
94
|
+
set(data: Partial<TChild> | TChild | null): TChild | null;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export type HasOneReference<TChild extends object = object> = HasOneReferenceApi<TChild> & Partial<TChild>;
|
|
98
|
+
|
|
81
99
|
export interface ManyToManyCollection<TTarget> {
|
|
100
|
+
length: number;
|
|
101
|
+
[Symbol.iterator](): Iterator<TTarget>;
|
|
82
102
|
load(): Promise<TTarget[]>;
|
|
83
103
|
getItems(): TTarget[];
|
|
84
104
|
attach(target: TTarget | number | string): void;
|
|
85
105
|
detach(target: TTarget | number | string): void;
|
|
86
106
|
syncByIds(ids: (number | string)[]): Promise<void>;
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
export type EntityInstance<
|
|
90
|
-
TTable extends TableDef,
|
|
91
|
-
TRow = InferRow<TTable>
|
|
92
|
-
> = TRow & {
|
|
93
|
-
[K in keyof RelationMap<TTable>]:
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
: TTable['relations'][K] extends BelongsToManyRelation<infer TTarget>
|
|
99
|
-
? ManyToManyCollection<EntityInstance<TTarget>>
|
|
100
|
-
: TTable['relations'][K] extends BelongsToRelation<infer TTarget>
|
|
101
|
-
? BelongsToReference<EntityInstance<TTarget>>
|
|
102
|
-
: never;
|
|
103
|
-
} & {
|
|
104
|
-
$load<K extends keyof RelationMap<TTable>>(relation: K): Promise<RelationMap<TTable>[K]>;
|
|
105
|
-
};
|
|
106
|
-
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export type EntityInstance<
|
|
110
|
+
TTable extends TableDef,
|
|
111
|
+
TRow = InferRow<TTable>
|
|
112
|
+
> = TRow & {
|
|
113
|
+
[K in keyof RelationMap<TTable>]: RelationWrapper<TTable['relations'][K]>;
|
|
114
|
+
} & {
|
|
115
|
+
$load<K extends keyof RelationMap<TTable>>(relation: K): Promise<RelationMap<TTable>[K]>;
|
|
116
|
+
};
|
|
117
|
+
|
|
107
118
|
export type Primitive = string | number | boolean | Date | bigint | Buffer | null | undefined;
|
|
108
119
|
|
|
120
|
+
type IsAny<T> = 0 extends (1 & T) ? true : false;
|
|
121
|
+
|
|
109
122
|
export type SelectableKeys<T> = {
|
|
110
|
-
[K in keyof T]-?:
|
|
123
|
+
[K in keyof T]-?: IsAny<T[K]> extends true
|
|
124
|
+
? never
|
|
125
|
+
: NonNullable<T[K]> extends Primitive
|
|
126
|
+
? K
|
|
127
|
+
: never
|
|
111
128
|
}[keyof T];
|