metal-orm 1.0.64 → 1.0.66
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 +1 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +119 -117
- package/dist/index.d.ts +119 -117
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/core/ast/aggregate-functions.ts +1 -1
- package/src/core/ast/window-functions.ts +6 -6
- package/src/core/functions/array.ts +4 -4
- package/src/core/functions/control-flow.ts +6 -6
- package/src/core/functions/json.ts +6 -6
- package/src/index.ts +2 -1
- package/src/orm/entity-context.ts +9 -7
- package/src/orm/entity-relations.ts +207 -207
- package/src/orm/entity.ts +124 -124
- package/src/orm/execute.ts +166 -166
- package/src/orm/identity-map.ts +3 -2
- package/src/orm/lazy-batch/shared.ts +1 -1
- package/src/orm/orm-session.ts +54 -54
- package/src/orm/relation-change-processor.ts +3 -3
- package/src/orm/relations/has-many.ts +1 -1
- package/src/orm/runtime-types.ts +5 -5
- package/src/orm/save-graph.ts +164 -166
- package/src/orm/unit-of-work.ts +17 -14
- package/src/query-builder/insert-query-state.ts +156 -155
- package/src/query-builder/insert.ts +5 -2
- package/src/query-builder/select.ts +112 -111
- package/src/schema/column-types.ts +14 -14
- package/src/schema/table.ts +39 -31
- package/src/schema/types.ts +54 -54
|
@@ -2,19 +2,19 @@ import { TableDef } from '../schema/table.js';
|
|
|
2
2
|
import { ColumnDef } from '../schema/column-types.js';
|
|
3
3
|
import { OrderingTerm, SelectQueryNode, SetOperationKind } from '../core/ast/query.js';
|
|
4
4
|
import { HydrationPlan } from '../core/hydration/types.js';
|
|
5
|
-
import {
|
|
6
|
-
ColumnNode,
|
|
7
|
-
ExpressionNode,
|
|
8
|
-
FunctionNode,
|
|
9
|
-
BinaryExpressionNode,
|
|
10
|
-
CaseExpressionNode,
|
|
11
|
-
WindowFunctionNode,
|
|
12
|
-
and,
|
|
13
|
-
exists,
|
|
14
|
-
notExists,
|
|
15
|
-
OperandNode
|
|
16
|
-
} from '../core/ast/expression.js';
|
|
17
|
-
import type { TypedExpression } from '../core/ast/expression.js';
|
|
5
|
+
import {
|
|
6
|
+
ColumnNode,
|
|
7
|
+
ExpressionNode,
|
|
8
|
+
FunctionNode,
|
|
9
|
+
BinaryExpressionNode,
|
|
10
|
+
CaseExpressionNode,
|
|
11
|
+
WindowFunctionNode,
|
|
12
|
+
and,
|
|
13
|
+
exists,
|
|
14
|
+
notExists,
|
|
15
|
+
OperandNode
|
|
16
|
+
} from '../core/ast/expression.js';
|
|
17
|
+
import type { TypedExpression } from '../core/ast/expression.js';
|
|
18
18
|
import { CompiledQuery, Dialect } from '../core/dialect/abstract.js';
|
|
19
19
|
import { DialectKey, resolveDialectInput } from '../core/dialect/dialect-factory.js';
|
|
20
20
|
|
|
@@ -32,12 +32,12 @@ import { ColumnSelector } from './column-selector.js';
|
|
|
32
32
|
import { RelationIncludeOptions, RelationTargetColumns, TypedRelationIncludeOptions } from './relation-types.js';
|
|
33
33
|
import { RelationKinds } from '../schema/relation.js';
|
|
34
34
|
import { JOIN_KINDS, JoinKind, ORDER_DIRECTIONS, OrderDirection } from '../core/sql/sql.js';
|
|
35
|
-
import { EntityInstance, RelationMap } from '../schema/types.js';
|
|
36
|
-
import type { ColumnToTs, InferRow } from '../schema/types.js';
|
|
35
|
+
import { EntityInstance, RelationMap } from '../schema/types.js';
|
|
36
|
+
import type { ColumnToTs, InferRow } from '../schema/types.js';
|
|
37
37
|
import { OrmSession } from '../orm/orm-session.ts';
|
|
38
38
|
import { ExecutionContext } from '../orm/execution-context.js';
|
|
39
39
|
import { HydrationContext } from '../orm/hydration-context.js';
|
|
40
|
-
import { executeHydrated, executeHydratedPlain, executeHydratedWithContexts } from '../orm/execute.js';
|
|
40
|
+
import { executeHydrated, executeHydratedPlain, executeHydratedWithContexts } from '../orm/execute.js';
|
|
41
41
|
import { EntityConstructor } from '../orm/entity-metadata.js';
|
|
42
42
|
import { materializeAs } from '../orm/entity-materializer.js';
|
|
43
43
|
import { resolveSelectQuery } from './query-resolution.js';
|
|
@@ -57,27 +57,27 @@ import { SelectCTEFacet } from './select/cte-facet.js';
|
|
|
57
57
|
import { SelectSetOpFacet } from './select/setop-facet.js';
|
|
58
58
|
import { SelectRelationFacet } from './select/relation-facet.js';
|
|
59
59
|
|
|
60
|
-
type ColumnSelectionValue =
|
|
61
|
-
| ColumnDef
|
|
62
|
-
| FunctionNode
|
|
63
|
-
| CaseExpressionNode
|
|
64
|
-
| WindowFunctionNode
|
|
65
|
-
| TypedExpression<unknown>;
|
|
66
|
-
|
|
67
|
-
type SelectionValueType<TValue> =
|
|
68
|
-
TValue extends TypedExpression<infer TRuntime> ? TRuntime :
|
|
69
|
-
TValue extends ColumnDef ? ColumnToTs<TValue> :
|
|
70
|
-
unknown;
|
|
71
|
-
|
|
72
|
-
type SelectionResult<TSelection extends Record<string, ColumnSelectionValue>> = {
|
|
73
|
-
[K in keyof TSelection]: SelectionValueType<TSelection[K]>;
|
|
74
|
-
};
|
|
75
|
-
|
|
76
|
-
type SelectionFromKeys<
|
|
77
|
-
TTable extends TableDef,
|
|
78
|
-
K extends keyof TTable['columns'] & string
|
|
79
|
-
> = Pick<InferRow<TTable>, K>;
|
|
80
|
-
|
|
60
|
+
type ColumnSelectionValue =
|
|
61
|
+
| ColumnDef
|
|
62
|
+
| FunctionNode
|
|
63
|
+
| CaseExpressionNode
|
|
64
|
+
| WindowFunctionNode
|
|
65
|
+
| TypedExpression<unknown>;
|
|
66
|
+
|
|
67
|
+
type SelectionValueType<TValue> =
|
|
68
|
+
TValue extends TypedExpression<infer TRuntime> ? TRuntime :
|
|
69
|
+
TValue extends ColumnDef ? ColumnToTs<TValue> :
|
|
70
|
+
unknown;
|
|
71
|
+
|
|
72
|
+
type SelectionResult<TSelection extends Record<string, ColumnSelectionValue>> = {
|
|
73
|
+
[K in keyof TSelection]: SelectionValueType<TSelection[K]>;
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
type SelectionFromKeys<
|
|
77
|
+
TTable extends TableDef,
|
|
78
|
+
K extends keyof TTable['columns'] & string
|
|
79
|
+
> = Pick<InferRow<TTable>, K>;
|
|
80
|
+
|
|
81
81
|
type DeepSelectEntry<TTable extends TableDef> = {
|
|
82
82
|
type: 'root';
|
|
83
83
|
columns: (keyof TTable['columns'] & string)[];
|
|
@@ -94,7 +94,7 @@ type DeepSelectConfig<TTable extends TableDef> = DeepSelectEntry<TTable>[];
|
|
|
94
94
|
* @typeParam T - Result type for projections (unused)
|
|
95
95
|
* @typeParam TTable - Table definition being queried
|
|
96
96
|
*/
|
|
97
|
-
export class SelectQueryBuilder<T = EntityInstance<TableDef>, TTable extends TableDef = TableDef> {
|
|
97
|
+
export class SelectQueryBuilder<T = EntityInstance<TableDef>, TTable extends TableDef = TableDef> {
|
|
98
98
|
private readonly env: SelectQueryBuilderEnvironment;
|
|
99
99
|
private readonly context: SelectQueryBuilderContext;
|
|
100
100
|
private readonly columnSelector: ColumnSelector;
|
|
@@ -107,7 +107,7 @@ export class SelectQueryBuilder<T = EntityInstance<TableDef>, TTable extends Tab
|
|
|
107
107
|
private readonly relationFacet: SelectRelationFacet;
|
|
108
108
|
private readonly lazyRelations: Set<string>;
|
|
109
109
|
private readonly lazyRelationOptions: Map<string, RelationIncludeOptions>;
|
|
110
|
-
private readonly entityConstructor?: EntityConstructor;
|
|
110
|
+
private readonly entityConstructor?: EntityConstructor;
|
|
111
111
|
|
|
112
112
|
/**
|
|
113
113
|
* Creates a new SelectQueryBuilder instance
|
|
@@ -123,7 +123,7 @@ export class SelectQueryBuilder<T = EntityInstance<TableDef>, TTable extends Tab
|
|
|
123
123
|
dependencies?: Partial<SelectQueryBuilderDependencies>,
|
|
124
124
|
lazyRelations?: Set<string>,
|
|
125
125
|
lazyRelationOptions?: Map<string, RelationIncludeOptions>,
|
|
126
|
-
entityConstructor?: EntityConstructor
|
|
126
|
+
entityConstructor?: EntityConstructor
|
|
127
127
|
) {
|
|
128
128
|
const deps = resolveSelectQueryBuilderDependencies(dependencies);
|
|
129
129
|
this.env = { table, deps };
|
|
@@ -154,21 +154,21 @@ export class SelectQueryBuilder<T = EntityInstance<TableDef>, TTable extends Tab
|
|
|
154
154
|
* @param lazyRelations - Updated lazy relations set
|
|
155
155
|
* @returns New SelectQueryBuilder instance
|
|
156
156
|
*/
|
|
157
|
-
private clone<TNext = T>(
|
|
158
|
-
context: SelectQueryBuilderContext = this.context,
|
|
159
|
-
lazyRelations = new Set(this.lazyRelations),
|
|
160
|
-
lazyRelationOptions = new Map(this.lazyRelationOptions)
|
|
161
|
-
): SelectQueryBuilder<TNext, TTable> {
|
|
162
|
-
return new SelectQueryBuilder(
|
|
163
|
-
this.env.table as TTable,
|
|
164
|
-
context.state,
|
|
165
|
-
context.hydration,
|
|
166
|
-
this.env.deps,
|
|
167
|
-
lazyRelations,
|
|
168
|
-
lazyRelationOptions,
|
|
169
|
-
this.entityConstructor
|
|
170
|
-
) as SelectQueryBuilder<TNext, TTable>;
|
|
171
|
-
}
|
|
157
|
+
private clone<TNext = T>(
|
|
158
|
+
context: SelectQueryBuilderContext = this.context,
|
|
159
|
+
lazyRelations = new Set(this.lazyRelations),
|
|
160
|
+
lazyRelationOptions = new Map(this.lazyRelationOptions)
|
|
161
|
+
): SelectQueryBuilder<TNext, TTable> {
|
|
162
|
+
return new SelectQueryBuilder(
|
|
163
|
+
this.env.table as TTable,
|
|
164
|
+
context.state,
|
|
165
|
+
context.hydration,
|
|
166
|
+
this.env.deps,
|
|
167
|
+
lazyRelations,
|
|
168
|
+
lazyRelationOptions,
|
|
169
|
+
this.entityConstructor
|
|
170
|
+
) as SelectQueryBuilder<TNext, TTable>;
|
|
171
|
+
}
|
|
172
172
|
|
|
173
173
|
/**
|
|
174
174
|
* Applies an alias to the root FROM table.
|
|
@@ -213,7 +213,8 @@ export class SelectQueryBuilder<T = EntityInstance<TableDef>, TTable extends Tab
|
|
|
213
213
|
*/
|
|
214
214
|
private applySetOperation<TSub extends TableDef>(
|
|
215
215
|
operator: SetOperationKind,
|
|
216
|
-
query: SelectQueryBuilder<
|
|
216
|
+
query: SelectQueryBuilder<T, TSub> | SelectQueryNode
|
|
217
|
+
|
|
217
218
|
): SelectQueryBuilderContext {
|
|
218
219
|
const subAst = resolveSelectQuery(query);
|
|
219
220
|
return this.setOpFacet.applySetOperation(this.context, operator, subAst);
|
|
@@ -234,41 +235,41 @@ export class SelectQueryBuilder<T = EntityInstance<TableDef>, TTable extends Tab
|
|
|
234
235
|
* fullName: concat(userTable.columns.firstName, ' ', userTable.columns.lastName)
|
|
235
236
|
* });
|
|
236
237
|
*/
|
|
237
|
-
select<K extends keyof TTable['columns'] & string>(
|
|
238
|
-
...args: K[]
|
|
239
|
-
): SelectQueryBuilder<T & SelectionFromKeys<TTable, K>, TTable>;
|
|
240
|
-
select<TSelection extends Record<string, ColumnSelectionValue>>(
|
|
241
|
-
columns: TSelection
|
|
242
|
-
): SelectQueryBuilder<T & SelectionResult<TSelection>, TTable>;
|
|
243
|
-
select<
|
|
244
|
-
K extends keyof TTable['columns'] & string,
|
|
245
|
-
TSelection extends Record<string, ColumnSelectionValue>
|
|
246
|
-
>(
|
|
247
|
-
...args: K[] | [TSelection]
|
|
248
|
-
): SelectQueryBuilder<T, TTable> {
|
|
238
|
+
select<K extends keyof TTable['columns'] & string>(
|
|
239
|
+
...args: K[]
|
|
240
|
+
): SelectQueryBuilder<T & SelectionFromKeys<TTable, K>, TTable>;
|
|
241
|
+
select<TSelection extends Record<string, ColumnSelectionValue>>(
|
|
242
|
+
columns: TSelection
|
|
243
|
+
): SelectQueryBuilder<T & SelectionResult<TSelection>, TTable>;
|
|
244
|
+
select<
|
|
245
|
+
K extends keyof TTable['columns'] & string,
|
|
246
|
+
TSelection extends Record<string, ColumnSelectionValue>
|
|
247
|
+
>(
|
|
248
|
+
...args: K[] | [TSelection]
|
|
249
|
+
): SelectQueryBuilder<T, TTable> {
|
|
249
250
|
// If first arg is an object (not a string), treat as projection map
|
|
250
251
|
if (args.length === 1 && typeof args[0] === 'object' && args[0] !== null && typeof args[0] !== 'string') {
|
|
251
|
-
const columns = args[0] as TSelection;
|
|
252
|
-
return this.clone<T & SelectionResult<TSelection>>(
|
|
253
|
-
this.projectionFacet.select(this.context, columns)
|
|
254
|
-
);
|
|
255
|
-
}
|
|
252
|
+
const columns = args[0] as TSelection;
|
|
253
|
+
return this.clone<T & SelectionResult<TSelection>>(
|
|
254
|
+
this.projectionFacet.select(this.context, columns)
|
|
255
|
+
);
|
|
256
|
+
}
|
|
256
257
|
|
|
257
258
|
// Otherwise, treat as column names
|
|
258
259
|
const cols = args as K[];
|
|
259
260
|
const selection: Record<string, ColumnDef> = {};
|
|
260
|
-
for (const key of cols) {
|
|
261
|
-
const col = this.env.table.columns[key];
|
|
262
|
-
if (!col) {
|
|
263
|
-
throw new Error(`Column '${key}' not found on table '${this.env.table.name}'`);
|
|
264
|
-
}
|
|
265
|
-
selection[key] = col;
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
return this.clone<T & SelectionFromKeys<TTable, K>>(
|
|
269
|
-
this.projectionFacet.select(this.context, selection)
|
|
270
|
-
);
|
|
271
|
-
}
|
|
261
|
+
for (const key of cols) {
|
|
262
|
+
const col = this.env.table.columns[key];
|
|
263
|
+
if (!col) {
|
|
264
|
+
throw new Error(`Column '${key}' not found on table '${this.env.table.name}'`);
|
|
265
|
+
}
|
|
266
|
+
selection[key] = col;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
return this.clone<T & SelectionFromKeys<TTable, K>>(
|
|
270
|
+
this.projectionFacet.select(this.context, selection)
|
|
271
|
+
);
|
|
272
|
+
}
|
|
272
273
|
|
|
273
274
|
/**
|
|
274
275
|
* Selects raw column expressions
|
|
@@ -384,15 +385,15 @@ export class SelectQueryBuilder<T = EntityInstance<TableDef>, TTable extends Tab
|
|
|
384
385
|
* qb.select('id', 'name')
|
|
385
386
|
* .selectSubquery('postCount', postCount);
|
|
386
387
|
*/
|
|
387
|
-
selectSubquery<TValue = unknown, K extends string = string, TSub extends TableDef = TableDef>(
|
|
388
|
-
alias: K,
|
|
389
|
-
sub: SelectQueryBuilder<unknown, TSub> | SelectQueryNode
|
|
390
|
-
): SelectQueryBuilder<T & Record<K, TValue>, TTable> {
|
|
391
|
-
const query = resolveSelectQuery(sub);
|
|
392
|
-
return this.clone<T & Record<K, TValue>>(
|
|
393
|
-
this.projectionFacet.selectSubquery(this.context, alias, query)
|
|
394
|
-
);
|
|
395
|
-
}
|
|
388
|
+
selectSubquery<TValue = unknown, K extends string = string, TSub extends TableDef = TableDef>(
|
|
389
|
+
alias: K,
|
|
390
|
+
sub: SelectQueryBuilder<unknown, TSub> | SelectQueryNode
|
|
391
|
+
): SelectQueryBuilder<T & Record<K, TValue>, TTable> {
|
|
392
|
+
const query = resolveSelectQuery(sub);
|
|
393
|
+
return this.clone<T & Record<K, TValue>>(
|
|
394
|
+
this.projectionFacet.selectSubquery(this.context, alias, query)
|
|
395
|
+
);
|
|
396
|
+
}
|
|
396
397
|
|
|
397
398
|
/**
|
|
398
399
|
* Adds a JOIN against a derived table (subquery with alias)
|
|
@@ -668,14 +669,14 @@ export class SelectQueryBuilder<T = EntityInstance<TableDef>, TTable extends Tab
|
|
|
668
669
|
/**
|
|
669
670
|
* Ensures that if no columns are selected, all columns from the table are selected by default.
|
|
670
671
|
*/
|
|
671
|
-
private ensureDefaultSelection(): SelectQueryBuilder<T, TTable> {
|
|
672
|
-
const columns = this.context.state.ast.columns;
|
|
673
|
-
if (!columns || columns.length === 0) {
|
|
674
|
-
const columnKeys = Object.keys(this.env.table.columns) as (keyof TTable['columns'] & string)[];
|
|
675
|
-
return this.select(...columnKeys);
|
|
676
|
-
}
|
|
677
|
-
return this;
|
|
678
|
-
}
|
|
672
|
+
private ensureDefaultSelection(): SelectQueryBuilder<T, TTable> {
|
|
673
|
+
const columns = this.context.state.ast.columns;
|
|
674
|
+
if (!columns || columns.length === 0) {
|
|
675
|
+
const columnKeys = Object.keys(this.env.table.columns) as (keyof TTable['columns'] & string)[];
|
|
676
|
+
return this.select(...columnKeys);
|
|
677
|
+
}
|
|
678
|
+
return this;
|
|
679
|
+
}
|
|
679
680
|
|
|
680
681
|
/**
|
|
681
682
|
* Executes the query and returns hydrated results.
|
|
@@ -708,11 +709,11 @@ export class SelectQueryBuilder<T = EntityInstance<TableDef>, TTable extends Tab
|
|
|
708
709
|
* // rows is EntityInstance<UserTable>[] (plain objects)
|
|
709
710
|
* rows[0] instanceof User; // false
|
|
710
711
|
*/
|
|
711
|
-
async executePlain(ctx: OrmSession): Promise<EntityInstance<TTable>[]> {
|
|
712
|
-
const builder = this.ensureDefaultSelection();
|
|
713
|
-
const rows = await executeHydratedPlain(ctx, builder);
|
|
714
|
-
return rows as EntityInstance<TTable>[];
|
|
715
|
-
}
|
|
712
|
+
async executePlain(ctx: OrmSession): Promise<EntityInstance<TTable>[]> {
|
|
713
|
+
const builder = this.ensureDefaultSelection();
|
|
714
|
+
const rows = await executeHydratedPlain(ctx, builder);
|
|
715
|
+
return rows as EntityInstance<TTable>[];
|
|
716
|
+
}
|
|
716
717
|
|
|
717
718
|
/**
|
|
718
719
|
* Executes the query and returns results as real class instances.
|
|
@@ -894,7 +895,7 @@ export class SelectQueryBuilder<T = EntityInstance<TableDef>, TTable extends Tab
|
|
|
894
895
|
* .where(eq(userTable.columns.active, false));
|
|
895
896
|
* qb.union(activeUsers).union(inactiveUsers);
|
|
896
897
|
*/
|
|
897
|
-
union<TSub extends TableDef>(query: SelectQueryBuilder<
|
|
898
|
+
union<TSub extends TableDef>(query: SelectQueryBuilder<T, TSub> | SelectQueryNode): SelectQueryBuilder<T, TTable> {
|
|
898
899
|
return this.clone(this.applySetOperation('UNION', query));
|
|
899
900
|
}
|
|
900
901
|
|
|
@@ -907,7 +908,7 @@ export class SelectQueryBuilder<T = EntityInstance<TableDef>, TTable extends Tab
|
|
|
907
908
|
* const q2 = new SelectQueryBuilder(userTable).where(lt(userTable.columns.score, 20));
|
|
908
909
|
* qb.unionAll(q1).unionAll(q2);
|
|
909
910
|
*/
|
|
910
|
-
unionAll<TSub extends TableDef>(query: SelectQueryBuilder<
|
|
911
|
+
unionAll<TSub extends TableDef>(query: SelectQueryBuilder<T, TSub> | SelectQueryNode): SelectQueryBuilder<T, TTable> {
|
|
911
912
|
return this.clone(this.applySetOperation('UNION ALL', query));
|
|
912
913
|
}
|
|
913
914
|
|
|
@@ -922,7 +923,7 @@ export class SelectQueryBuilder<T = EntityInstance<TableDef>, TTable extends Tab
|
|
|
922
923
|
* .where(eq(userTable.columns.premium, true));
|
|
923
924
|
* qb.intersect(activeUsers).intersect(premiumUsers);
|
|
924
925
|
*/
|
|
925
|
-
intersect<TSub extends TableDef>(query: SelectQueryBuilder<
|
|
926
|
+
intersect<TSub extends TableDef>(query: SelectQueryBuilder<T, TSub> | SelectQueryNode): SelectQueryBuilder<T, TTable> {
|
|
926
927
|
return this.clone(this.applySetOperation('INTERSECT', query));
|
|
927
928
|
}
|
|
928
929
|
|
|
@@ -936,7 +937,7 @@ export class SelectQueryBuilder<T = EntityInstance<TableDef>, TTable extends Tab
|
|
|
936
937
|
* .where(eq(userTable.columns.active, false));
|
|
937
938
|
* qb.except(allUsers).except(inactiveUsers); // Only active users
|
|
938
939
|
*/
|
|
939
|
-
except<TSub extends TableDef>(query: SelectQueryBuilder<
|
|
940
|
+
except<TSub extends TableDef>(query: SelectQueryBuilder<T, TSub> | SelectQueryNode): SelectQueryBuilder<T, TTable> {
|
|
940
941
|
return this.clone(this.applySetOperation('EXCEPT', query));
|
|
941
942
|
}
|
|
942
943
|
|
|
@@ -134,20 +134,20 @@ export const col = {
|
|
|
134
134
|
|
|
135
135
|
/**
|
|
136
136
|
* Creates a variable character column definition
|
|
137
|
-
* @param length - Maximum length of the string
|
|
138
|
-
* @returns ColumnDef with VARCHAR type
|
|
139
|
-
*/
|
|
140
|
-
varchar: (length: number): ColumnDef<'VARCHAR'> => ({ name: '', type: 'VARCHAR', args: [length] }),
|
|
141
|
-
|
|
142
|
-
/**
|
|
143
|
-
* Creates a text column definition
|
|
144
|
-
*/
|
|
145
|
-
text: (): ColumnDef<'TEXT'> => ({ name: '', type: 'TEXT' }),
|
|
146
|
-
|
|
147
|
-
/**
|
|
148
|
-
* Creates a fixed precision decimal column definition
|
|
149
|
-
*/
|
|
150
|
-
decimal: (precision: number, scale = 0): ColumnDef<'DECIMAL'> => ({
|
|
137
|
+
* @param length - Maximum length of the string
|
|
138
|
+
* @returns ColumnDef with VARCHAR type
|
|
139
|
+
*/
|
|
140
|
+
varchar: (length: number): ColumnDef<'VARCHAR'> => ({ name: '', type: 'VARCHAR', args: [length] }),
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Creates a text column definition
|
|
144
|
+
*/
|
|
145
|
+
text: (): ColumnDef<'TEXT'> => ({ name: '', type: 'TEXT' }),
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Creates a fixed precision decimal column definition
|
|
149
|
+
*/
|
|
150
|
+
decimal: (precision: number, scale = 0): ColumnDef<'DECIMAL'> => ({
|
|
151
151
|
name: '',
|
|
152
152
|
type: 'DECIMAL',
|
|
153
153
|
args: [precision, scale]
|
package/src/schema/table.ts
CHANGED
|
@@ -30,20 +30,24 @@ export interface TableOptions {
|
|
|
30
30
|
collation?: string;
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
-
export interface TableHooks {
|
|
34
|
-
beforeInsert?(ctx:
|
|
35
|
-
afterInsert?(ctx:
|
|
36
|
-
beforeUpdate?(ctx:
|
|
37
|
-
afterUpdate?(ctx:
|
|
38
|
-
beforeDelete?(ctx:
|
|
39
|
-
afterDelete?(ctx:
|
|
33
|
+
export interface TableHooks<TEntity = unknown, TContext = unknown> {
|
|
34
|
+
beforeInsert?(ctx: TContext, entity: TEntity): Promise<void> | void;
|
|
35
|
+
afterInsert?(ctx: TContext, entity: TEntity): Promise<void> | void;
|
|
36
|
+
beforeUpdate?(ctx: TContext, entity: TEntity): Promise<void> | void;
|
|
37
|
+
afterUpdate?(ctx: TContext, entity: TEntity): Promise<void> | void;
|
|
38
|
+
beforeDelete?(ctx: TContext, entity: TEntity): Promise<void> | void;
|
|
39
|
+
afterDelete?(ctx: TContext, entity: TEntity): Promise<void> | void;
|
|
40
40
|
}
|
|
41
41
|
|
|
42
42
|
/**
|
|
43
43
|
* Definition of a database table with its columns and relationships
|
|
44
44
|
* @typeParam T - Type of the columns record
|
|
45
45
|
*/
|
|
46
|
-
export interface TableDef<
|
|
46
|
+
export interface TableDef<
|
|
47
|
+
T extends Record<string, ColumnDef> = Record<string, ColumnDef>,
|
|
48
|
+
TEntity = unknown,
|
|
49
|
+
TContext = unknown
|
|
50
|
+
> {
|
|
47
51
|
/** Name of the table */
|
|
48
52
|
name: string;
|
|
49
53
|
/** Optional schema/catalog name */
|
|
@@ -53,7 +57,7 @@ export interface TableDef<T extends Record<string, ColumnDef> = Record<string, C
|
|
|
53
57
|
/** Record of relationship definitions keyed by relation name */
|
|
54
58
|
relations: Record<string, RelationDef>;
|
|
55
59
|
/** Optional lifecycle hooks */
|
|
56
|
-
hooks?: TableHooks
|
|
60
|
+
hooks?: TableHooks<TEntity, TContext>;
|
|
57
61
|
/** Composite primary key definition (falls back to column.primary flags) */
|
|
58
62
|
primaryKey?: string[];
|
|
59
63
|
/** Secondary indexes */
|
|
@@ -85,13 +89,17 @@ export interface TableDef<T extends Record<string, ColumnDef> = Record<string, C
|
|
|
85
89
|
* });
|
|
86
90
|
* ```
|
|
87
91
|
*/
|
|
88
|
-
export const defineTable = <
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
92
|
+
export const defineTable = <
|
|
93
|
+
T extends Record<string, ColumnDef>,
|
|
94
|
+
TEntity = unknown,
|
|
95
|
+
TContext = unknown
|
|
96
|
+
>(
|
|
97
|
+
name: string,
|
|
98
|
+
columns: T,
|
|
99
|
+
relations: Record<string, RelationDef> = {},
|
|
100
|
+
hooks?: TableHooks<TEntity, TContext>,
|
|
101
|
+
options: TableOptions = {}
|
|
102
|
+
): TableDef<T, TEntity, TContext> => {
|
|
95
103
|
// Runtime mutability to assign names to column definitions for convenience
|
|
96
104
|
const colsWithNames = Object.entries(columns).reduce((acc, [key, def]) => {
|
|
97
105
|
const colDef = { ...def, name: key, table: name };
|
|
@@ -112,21 +120,21 @@ export const defineTable = <T extends Record<string, ColumnDef>>(
|
|
|
112
120
|
engine: options.engine,
|
|
113
121
|
charset: options.charset,
|
|
114
122
|
collation: options.collation
|
|
115
|
-
};
|
|
116
|
-
};
|
|
117
|
-
|
|
118
|
-
/**
|
|
119
|
-
* Assigns relations to a table definition while preserving literal typing.
|
|
120
|
-
*/
|
|
121
|
-
export function setRelations<
|
|
122
|
-
TTable extends TableDef,
|
|
123
|
-
TRelations extends Record<string, RelationDef>
|
|
124
|
-
>(
|
|
125
|
-
table: TTable,
|
|
126
|
-
relations: TRelations
|
|
127
|
-
): asserts table is TTable & { relations: TRelations } {
|
|
128
|
-
table.relations = relations;
|
|
129
|
-
}
|
|
123
|
+
};
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Assigns relations to a table definition while preserving literal typing.
|
|
128
|
+
*/
|
|
129
|
+
export function setRelations<
|
|
130
|
+
TTable extends TableDef,
|
|
131
|
+
TRelations extends Record<string, RelationDef>
|
|
132
|
+
>(
|
|
133
|
+
table: TTable,
|
|
134
|
+
relations: TRelations
|
|
135
|
+
): asserts table is TTable & { relations: TRelations } {
|
|
136
|
+
table.relations = relations;
|
|
137
|
+
}
|
|
130
138
|
|
|
131
139
|
type DirectColumnKeys<T extends TableDef> =
|
|
132
140
|
Exclude<keyof T["columns"] & string, keyof T | "$">;
|
package/src/schema/types.ts
CHANGED
|
@@ -12,12 +12,12 @@ import {
|
|
|
12
12
|
/**
|
|
13
13
|
* Resolves a relation definition to its target table type.
|
|
14
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, TableDef> ? TTarget :
|
|
20
|
-
never;
|
|
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, TableDef> ? TTarget :
|
|
20
|
+
never;
|
|
21
21
|
|
|
22
22
|
type NormalizedColumnType<T extends ColumnDef> = Lowercase<T['type'] & string>;
|
|
23
23
|
|
|
@@ -43,12 +43,12 @@ export type InferRow<TTable extends TableDef> = {
|
|
|
43
43
|
[K in keyof TTable['columns']]: ColumnToTs<TTable['columns'][K]>;
|
|
44
44
|
};
|
|
45
45
|
|
|
46
|
-
type RelationResult<T extends RelationDef> =
|
|
47
|
-
T extends HasManyRelation<infer TTarget> ? InferRow<TTarget>[] :
|
|
48
|
-
T extends HasOneRelation<infer TTarget> ? InferRow<TTarget> | null :
|
|
49
|
-
T extends BelongsToRelation<infer TTarget> ? InferRow<TTarget> | null :
|
|
50
|
-
T extends BelongsToManyRelation<infer TTarget,
|
|
51
|
-
never;
|
|
46
|
+
type RelationResult<T extends RelationDef> =
|
|
47
|
+
T extends HasManyRelation<infer TTarget> ? InferRow<TTarget>[] :
|
|
48
|
+
T extends HasOneRelation<infer TTarget> ? InferRow<TTarget> | null :
|
|
49
|
+
T extends BelongsToRelation<infer TTarget> ? InferRow<TTarget> | null :
|
|
50
|
+
T extends BelongsToManyRelation<infer TTarget, infer TPivot> ? (InferRow<TTarget> & { _pivot?: InferRow<TPivot> })[] :
|
|
51
|
+
never;
|
|
52
52
|
|
|
53
53
|
/**
|
|
54
54
|
* Maps relation names to the expected row results
|
|
@@ -57,26 +57,26 @@ export type RelationMap<TTable extends TableDef> = {
|
|
|
57
57
|
[K in keyof TTable['relations']]: RelationResult<TTable['relations'][K]>;
|
|
58
58
|
};
|
|
59
59
|
|
|
60
|
-
type RelationWrapper<TRel extends RelationDef> =
|
|
61
|
-
TRel extends HasManyRelation<infer TTarget>
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
export interface HasManyCollection<TChild> {
|
|
73
|
-
length: number;
|
|
74
|
-
[Symbol.iterator](): Iterator<TChild>;
|
|
75
|
-
load(): Promise<TChild[]>;
|
|
76
|
-
getItems(): TChild[];
|
|
77
|
-
add(data: Partial<TChild>): TChild;
|
|
78
|
-
attach(entity: TChild): void;
|
|
79
|
-
remove(entity: TChild): void;
|
|
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, infer TPivot>
|
|
66
|
+
? ManyToManyCollection<EntityInstance<TTarget> & { _pivot?: InferRow<TPivot> }>
|
|
67
|
+
& ReadonlyArray<EntityInstance<TTarget> & { _pivot?: InferRow<TPivot> }>
|
|
68
|
+
: TRel extends BelongsToRelation<infer TTarget>
|
|
69
|
+
? BelongsToReference<EntityInstance<TTarget>>
|
|
70
|
+
: never;
|
|
71
|
+
|
|
72
|
+
export interface HasManyCollection<TChild> {
|
|
73
|
+
length: number;
|
|
74
|
+
[Symbol.iterator](): Iterator<TChild>;
|
|
75
|
+
load(): Promise<TChild[]>;
|
|
76
|
+
getItems(): TChild[];
|
|
77
|
+
add(data: Partial<TChild>): TChild;
|
|
78
|
+
attach(entity: TChild): void;
|
|
79
|
+
remove(entity: TChild): void;
|
|
80
80
|
clear(): void;
|
|
81
81
|
}
|
|
82
82
|
|
|
@@ -96,17 +96,17 @@ export interface HasOneReferenceApi<TChild extends object = object> {
|
|
|
96
96
|
|
|
97
97
|
export type HasOneReference<TChild extends object = object> = HasOneReferenceApi<TChild> & Partial<TChild>;
|
|
98
98
|
|
|
99
|
-
export interface ManyToManyCollection<TTarget, TPivot extends object | undefined = undefined> {
|
|
100
|
-
length: number;
|
|
101
|
-
[Symbol.iterator](): Iterator<TTarget>;
|
|
102
|
-
load(): Promise<TTarget[]>;
|
|
103
|
-
getItems(): TTarget[];
|
|
104
|
-
attach(target: TTarget | number | string): void;
|
|
105
|
-
detach(target: TTarget | number | string): void;
|
|
106
|
-
syncByIds(ids: (number | string)[]): Promise<void>;
|
|
107
|
-
/** @internal Type-level marker for the related pivot entity */
|
|
108
|
-
readonly __pivotType?: TPivot;
|
|
109
|
-
}
|
|
99
|
+
export interface ManyToManyCollection<TTarget, TPivot extends object | undefined = undefined> {
|
|
100
|
+
length: number;
|
|
101
|
+
[Symbol.iterator](): Iterator<TTarget>;
|
|
102
|
+
load(): Promise<TTarget[]>;
|
|
103
|
+
getItems(): TTarget[];
|
|
104
|
+
attach(target: TTarget | number | string): void;
|
|
105
|
+
detach(target: TTarget | number | string): void;
|
|
106
|
+
syncByIds(ids: (number | string)[]): Promise<void>;
|
|
107
|
+
/** @internal Type-level marker for the related pivot entity */
|
|
108
|
+
readonly __pivotType?: TPivot;
|
|
109
|
+
}
|
|
110
110
|
|
|
111
111
|
export type EntityInstance<
|
|
112
112
|
TTable extends TableDef,
|
|
@@ -117,14 +117,14 @@ export type EntityInstance<
|
|
|
117
117
|
$load<K extends keyof RelationMap<TTable>>(relation: K): Promise<RelationMap<TTable>[K]>;
|
|
118
118
|
};
|
|
119
119
|
|
|
120
|
-
export type Primitive = string | number | boolean | Date | bigint | Buffer | null | undefined;
|
|
121
|
-
|
|
122
|
-
type IsAny<T> = 0 extends (1 & T) ? true : false;
|
|
123
|
-
|
|
124
|
-
export type SelectableKeys<T> = {
|
|
125
|
-
[K in keyof T]-?: IsAny<T[K]> extends true
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
}[keyof T];
|
|
120
|
+
export type Primitive = string | number | boolean | Date | bigint | Buffer | null | undefined;
|
|
121
|
+
|
|
122
|
+
type IsAny<T> = 0 extends (1 & T) ? true : false;
|
|
123
|
+
|
|
124
|
+
export type SelectableKeys<T> = {
|
|
125
|
+
[K in keyof T]-?: IsAny<T[K]> extends true
|
|
126
|
+
? never
|
|
127
|
+
: NonNullable<T[K]> extends Primitive
|
|
128
|
+
? K
|
|
129
|
+
: never
|
|
130
|
+
}[keyof T];
|