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
package/package.json
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ColumnNode
|
|
1
|
+
import { ColumnNode } from './expression-nodes.js';
|
|
2
2
|
import { columnOperand, valueToOperand, ValueOperandInput } from './expression-builders.js';
|
|
3
3
|
import { ColumnRef } from './types.js';
|
|
4
4
|
import { OrderByNode } from './query.js';
|
|
@@ -5,7 +5,7 @@ import { OrderByNode } from './query.js';
|
|
|
5
5
|
import { ColumnRef } from './types.js';
|
|
6
6
|
import { TypedExpression, asType } from './expression.js';
|
|
7
7
|
|
|
8
|
-
const buildWindowFunction = <T =
|
|
8
|
+
const buildWindowFunction = <T = unknown>(
|
|
9
9
|
name: string,
|
|
10
10
|
args: (ColumnNode | LiteralNode | JsonPathNode)[] = [],
|
|
11
11
|
partitionBy?: ColumnNode[],
|
|
@@ -66,7 +66,7 @@ export const ntile = (n: number): TypedExpression<number> =>
|
|
|
66
66
|
* @param defaultValue - Optional default value.
|
|
67
67
|
* @returns A `TypedExpression<T>` representing the `LAG` window function.
|
|
68
68
|
*/
|
|
69
|
-
export const lag = <T =
|
|
69
|
+
export const lag = <T = unknown>(
|
|
70
70
|
col: ColumnRef | ColumnNode,
|
|
71
71
|
offset: number = 1,
|
|
72
72
|
defaultValue?: LiteralNode['value']
|
|
@@ -89,7 +89,7 @@ export const lag = <T = any>(
|
|
|
89
89
|
* @param defaultValue - Optional default value.
|
|
90
90
|
* @returns A `TypedExpression<T>` representing the `LEAD` window function.
|
|
91
91
|
*/
|
|
92
|
-
export const lead = <T =
|
|
92
|
+
export const lead = <T = unknown>(
|
|
93
93
|
col: ColumnRef | ColumnNode,
|
|
94
94
|
offset: number = 1,
|
|
95
95
|
defaultValue?: LiteralNode['value']
|
|
@@ -110,7 +110,7 @@ export const lead = <T = any>(
|
|
|
110
110
|
* @param col - Column or expression to get first value from.
|
|
111
111
|
* @returns A `TypedExpression<T>` representing the `FIRST_VALUE` window function.
|
|
112
112
|
*/
|
|
113
|
-
export const firstValue = <T =
|
|
113
|
+
export const firstValue = <T = unknown>(col: ColumnRef | ColumnNode): TypedExpression<T> =>
|
|
114
114
|
buildWindowFunction<T>('FIRST_VALUE', [columnOperand(col)]);
|
|
115
115
|
|
|
116
116
|
/**
|
|
@@ -119,7 +119,7 @@ export const firstValue = <T = any>(col: ColumnRef | ColumnNode): TypedExpressio
|
|
|
119
119
|
* @param col - Column or expression to get last value from.
|
|
120
120
|
* @returns A `TypedExpression<T>` representing the `LAST_VALUE` window function.
|
|
121
121
|
*/
|
|
122
|
-
export const lastValue = <T =
|
|
122
|
+
export const lastValue = <T = unknown>(col: ColumnRef | ColumnNode): TypedExpression<T> =>
|
|
123
123
|
buildWindowFunction<T>('LAST_VALUE', [columnOperand(col)]);
|
|
124
124
|
|
|
125
125
|
/**
|
|
@@ -131,7 +131,7 @@ export const lastValue = <T = any>(col: ColumnRef | ColumnNode): TypedExpression
|
|
|
131
131
|
* @param orderBy - Optional ORDER BY clauses.
|
|
132
132
|
* @returns A `TypedExpression<T>` representing the window function.
|
|
133
133
|
*/
|
|
134
|
-
export const windowFunction = <T =
|
|
134
|
+
export const windowFunction = <T = unknown>(
|
|
135
135
|
name: string,
|
|
136
136
|
args: (ColumnRef | ColumnNode | LiteralNode | JsonPathNode)[] = [],
|
|
137
137
|
partitionBy?: (ColumnRef | ColumnNode)[],
|
|
@@ -22,14 +22,14 @@ const fn = (key: string, args: OperandInput[]): FunctionNode => ({
|
|
|
22
22
|
args: args.map(toOperand)
|
|
23
23
|
});
|
|
24
24
|
|
|
25
|
-
const afn = <T =
|
|
25
|
+
const afn = <T = unknown[]>(key: string, args: OperandInput[]): TypedExpression<T> => asType<T>(fn(key, args));
|
|
26
26
|
|
|
27
27
|
/**
|
|
28
28
|
* Appends a value to the end of an array.
|
|
29
29
|
*
|
|
30
30
|
* @param array - Array column or value.
|
|
31
31
|
* @param value - Value to append.
|
|
32
|
-
* @returns A `TypedExpression<
|
|
32
|
+
* @returns A `TypedExpression<unknown[]>` representing the `ARRAY_APPEND` SQL function.
|
|
33
33
|
*/
|
|
34
|
-
export const arrayAppend = (array: OperandInput, value: OperandInput): TypedExpression<
|
|
35
|
-
afn('ARRAY_APPEND', [array, value]);
|
|
34
|
+
export const arrayAppend = (array: OperandInput, value: OperandInput): TypedExpression<unknown[]> =>
|
|
35
|
+
afn<unknown[]>('ARRAY_APPEND', [array, value]);
|
|
@@ -22,7 +22,7 @@ const fn = (key: string, args: OperandInput[]): FunctionNode => ({
|
|
|
22
22
|
args: args.map(toOperand)
|
|
23
23
|
});
|
|
24
24
|
|
|
25
|
-
const afn = <T =
|
|
25
|
+
const afn = <T = unknown>(key: string, args: OperandInput[]): TypedExpression<T> => asType<T>(fn(key, args));
|
|
26
26
|
|
|
27
27
|
/**
|
|
28
28
|
* Returns the first non-null value in a list.
|
|
@@ -33,7 +33,7 @@ const afn = <T = any>(key: string, args: OperandInput[]): TypedExpression<T> =>
|
|
|
33
33
|
* @example
|
|
34
34
|
* coalesce(users.nickname, users.firstName, 'Guest');
|
|
35
35
|
*/
|
|
36
|
-
export const coalesce = <T =
|
|
36
|
+
export const coalesce = <T = unknown>(...args: OperandInput[]): TypedExpression<T> => {
|
|
37
37
|
if (args.length < 2) throw new Error('coalesce() expects at least 2 arguments');
|
|
38
38
|
return afn<T>('COALESCE', args);
|
|
39
39
|
};
|
|
@@ -45,7 +45,7 @@ export const coalesce = <T = any>(...args: OperandInput[]): TypedExpression<T> =
|
|
|
45
45
|
* @param val2 - The second value to compare against.
|
|
46
46
|
* @returns A `TypedExpression<T>` representing the `NULLIF` SQL function.
|
|
47
47
|
*/
|
|
48
|
-
export const nullif = <T =
|
|
48
|
+
export const nullif = <T = unknown>(val1: OperandInput, val2: OperandInput): TypedExpression<T> => afn<T>('NULLIF', [val1, val2]);
|
|
49
49
|
|
|
50
50
|
/**
|
|
51
51
|
* Returns the largest value in a list.
|
|
@@ -53,7 +53,7 @@ export const nullif = <T = any>(val1: OperandInput, val2: OperandInput): TypedEx
|
|
|
53
53
|
* @param args - The list of values or columns to compare.
|
|
54
54
|
* @returns A `TypedExpression<T>` representing the `GREATEST` SQL function.
|
|
55
55
|
*/
|
|
56
|
-
export const greatest = <T =
|
|
56
|
+
export const greatest = <T = unknown>(...args: OperandInput[]): TypedExpression<T> => {
|
|
57
57
|
if (args.length < 2) throw new Error('greatest() expects at least 2 arguments');
|
|
58
58
|
return afn<T>('GREATEST', args);
|
|
59
59
|
};
|
|
@@ -64,7 +64,7 @@ export const greatest = <T = any>(...args: OperandInput[]): TypedExpression<T> =
|
|
|
64
64
|
* @param args - The list of values or columns to compare.
|
|
65
65
|
* @returns A `TypedExpression<T>` representing the `LEAST` SQL function.
|
|
66
66
|
*/
|
|
67
|
-
export const least = <T =
|
|
67
|
+
export const least = <T = unknown>(...args: OperandInput[]): TypedExpression<T> => {
|
|
68
68
|
if (args.length < 2) throw new Error('least() expects at least 2 arguments');
|
|
69
69
|
return afn<T>('LEAST', args);
|
|
70
70
|
};
|
|
@@ -76,4 +76,4 @@ export const least = <T = any>(...args: OperandInput[]): TypedExpression<T> => {
|
|
|
76
76
|
* @param defaultValue - The default value to return if val is null.
|
|
77
77
|
* @returns A `TypedExpression<T>` representing the `COALESCE` SQL function.
|
|
78
78
|
*/
|
|
79
|
-
export const ifNull = <T =
|
|
79
|
+
export const ifNull = <T = unknown>(val: OperandInput, defaultValue: OperandInput): TypedExpression<T> => coalesce<T>(val, defaultValue);
|
|
@@ -23,7 +23,7 @@ const fn = (key: string, args: OperandInput[]): FunctionNode => ({
|
|
|
23
23
|
});
|
|
24
24
|
|
|
25
25
|
const nfn = (key: string, args: OperandInput[]): TypedExpression<number> => asType<number>(fn(key, args));
|
|
26
|
-
const afn = <T =
|
|
26
|
+
const afn = <T = unknown>(key: string, args: OperandInput[]): TypedExpression<T> => asType<T>(fn(key, args));
|
|
27
27
|
|
|
28
28
|
/**
|
|
29
29
|
* Returns the number of elements in a JSON array or object.
|
|
@@ -41,18 +41,18 @@ export const jsonLength = (target: OperandInput, path?: OperandInput): TypedExpr
|
|
|
41
41
|
* @param target - JSON column or value.
|
|
42
42
|
* @param path - JSON path to set.
|
|
43
43
|
* @param value - Value to set.
|
|
44
|
-
* @returns A `TypedExpression<
|
|
44
|
+
* @returns A `TypedExpression<T>` representing the `JSON_SET` SQL function.
|
|
45
45
|
*/
|
|
46
|
-
export const jsonSet = (target: OperandInput, path: OperandInput, value: OperandInput): TypedExpression<
|
|
47
|
-
afn('JSON_SET', [target, path, value]);
|
|
46
|
+
export const jsonSet = <T = unknown>(target: OperandInput, path: OperandInput, value: OperandInput): TypedExpression<T> =>
|
|
47
|
+
afn<T>('JSON_SET', [target, path, value]);
|
|
48
48
|
|
|
49
49
|
/**
|
|
50
50
|
* Aggregates values into a JSON array.
|
|
51
51
|
*
|
|
52
52
|
* @param value - Column or expression to aggregate.
|
|
53
|
-
* @returns A `TypedExpression<
|
|
53
|
+
* @returns A `TypedExpression<unknown[]>` representing the `JSON_ARRAYAGG` SQL function.
|
|
54
54
|
*/
|
|
55
|
-
export const jsonArrayAgg = (value: OperandInput): TypedExpression<
|
|
55
|
+
export const jsonArrayAgg = (value: OperandInput): TypedExpression<unknown[]> => afn<unknown[]>('JSON_ARRAYAGG', [value]);
|
|
56
56
|
|
|
57
57
|
/**
|
|
58
58
|
* Checks if a JSON document contains a specific piece of data.
|
package/src/index.ts
CHANGED
|
@@ -41,7 +41,8 @@ export * from './orm/relations/has-many.js';
|
|
|
41
41
|
export * from './orm/relations/belongs-to.js';
|
|
42
42
|
export * from './orm/relations/many-to-many.js';
|
|
43
43
|
export * from './orm/execute.js';
|
|
44
|
-
export
|
|
44
|
+
export type { EntityContext } from './orm/entity-context.js';
|
|
45
|
+
export type { PrimaryKey as EntityPrimaryKey } from './orm/entity-context.js';
|
|
45
46
|
export * from './orm/execution-context.js';
|
|
46
47
|
export * from './orm/hydration-context.js';
|
|
47
48
|
export * from './orm/domain-event-bus.js';
|
|
@@ -2,7 +2,9 @@ import { Dialect } from '../core/dialect/abstract.js';
|
|
|
2
2
|
import type { DbExecutor } from '../core/execution/db-executor.js';
|
|
3
3
|
import { TableDef } from '../schema/table.js';
|
|
4
4
|
import { RelationDef } from '../schema/relation.js';
|
|
5
|
-
import { RelationChange, RelationKey, TrackedEntity } from './runtime-types.js';
|
|
5
|
+
import { RelationChange, RelationKey, TrackedEntity } from './runtime-types.js';
|
|
6
|
+
|
|
7
|
+
export type PrimaryKey = string | number;
|
|
6
8
|
|
|
7
9
|
/**
|
|
8
10
|
* Interface for entity context providing entity tracking and management.
|
|
@@ -19,7 +21,7 @@ export interface EntityContext {
|
|
|
19
21
|
* @param pk - The primary key value
|
|
20
22
|
* @returns The entity or undefined
|
|
21
23
|
*/
|
|
22
|
-
getEntity(table: TableDef, pk:
|
|
24
|
+
getEntity(table: TableDef, pk: PrimaryKey): object | undefined;
|
|
23
25
|
|
|
24
26
|
/**
|
|
25
27
|
* Sets an entity in the context.
|
|
@@ -27,7 +29,7 @@ export interface EntityContext {
|
|
|
27
29
|
* @param pk - The primary key value
|
|
28
30
|
* @param entity - The entity to set
|
|
29
31
|
*/
|
|
30
|
-
setEntity(table: TableDef, pk:
|
|
32
|
+
setEntity(table: TableDef, pk: PrimaryKey, entity: object): void;
|
|
31
33
|
|
|
32
34
|
/**
|
|
33
35
|
* Tracks a new entity.
|
|
@@ -35,7 +37,7 @@ export interface EntityContext {
|
|
|
35
37
|
* @param entity - The new entity
|
|
36
38
|
* @param pk - Optional primary key
|
|
37
39
|
*/
|
|
38
|
-
trackNew(table: TableDef, entity:
|
|
40
|
+
trackNew(table: TableDef, entity: object, pk?: PrimaryKey): void;
|
|
39
41
|
|
|
40
42
|
/**
|
|
41
43
|
* Tracks a managed entity.
|
|
@@ -43,19 +45,19 @@ export interface EntityContext {
|
|
|
43
45
|
* @param pk - The primary key
|
|
44
46
|
* @param entity - The managed entity
|
|
45
47
|
*/
|
|
46
|
-
trackManaged(table: TableDef, pk:
|
|
48
|
+
trackManaged(table: TableDef, pk: PrimaryKey, entity: object): void;
|
|
47
49
|
|
|
48
50
|
/**
|
|
49
51
|
* Marks an entity as dirty.
|
|
50
52
|
* @param entity - The entity to mark
|
|
51
53
|
*/
|
|
52
|
-
markDirty(entity:
|
|
54
|
+
markDirty(entity: object): void;
|
|
53
55
|
|
|
54
56
|
/**
|
|
55
57
|
* Marks an entity as removed.
|
|
56
58
|
* @param entity - The entity to mark
|
|
57
59
|
*/
|
|
58
|
-
markRemoved(entity:
|
|
60
|
+
markRemoved(entity: object): void;
|
|
59
61
|
|
|
60
62
|
/**
|
|
61
63
|
* Gets all tracked entities for a table.
|
|
@@ -1,207 +1,207 @@
|
|
|
1
|
-
import { TableDef } from '../schema/table.js';
|
|
2
|
-
import { EntityInstance, HasManyCollection, HasOneReference, BelongsToReference, ManyToManyCollection } from '../schema/types.js';
|
|
3
|
-
import { EntityMeta, RelationKey } from './entity-meta.js';
|
|
4
|
-
import { DefaultHasManyCollection } from './relations/has-many.js';
|
|
5
|
-
import { DefaultHasOneReference } from './relations/has-one.js';
|
|
6
|
-
import { DefaultBelongsToReference } from './relations/belongs-to.js';
|
|
7
|
-
import { DefaultManyToManyCollection } from './relations/many-to-many.js';
|
|
8
|
-
import { HasManyRelation, HasOneRelation, BelongsToRelation, BelongsToManyRelation, RelationKinds } from '../schema/relation.js';
|
|
9
|
-
import { loadHasManyRelation, loadHasOneRelation, loadBelongsToRelation, loadBelongsToManyRelation } from './lazy-batch.js';
|
|
10
|
-
import { findPrimaryKey } from '../query-builder/hydration-planner.js';
|
|
11
|
-
import { relationLoaderCache } from './entity-relation-cache.js';
|
|
12
|
-
|
|
13
|
-
export type RelationEntityFactory = (
|
|
14
|
-
table: TableDef,
|
|
15
|
-
row: Record<string, unknown>
|
|
16
|
-
) => EntityInstance<TableDef>;
|
|
17
|
-
|
|
18
|
-
const proxifyRelationWrapper = <T extends object>(wrapper: T): T => {
|
|
19
|
-
return new Proxy(wrapper, {
|
|
20
|
-
get(target, prop, receiver) {
|
|
21
|
-
if (typeof prop === 'symbol') {
|
|
22
|
-
return Reflect.get(target, prop, receiver);
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
if (prop in target) {
|
|
26
|
-
return Reflect.get(target, prop, receiver);
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
const getItems = (target as { getItems?: () => unknown }).getItems;
|
|
30
|
-
if (typeof getItems === 'function') {
|
|
31
|
-
const items = getItems.call(target);
|
|
32
|
-
if (items && prop in (items as object)) {
|
|
33
|
-
const propName = prop as string;
|
|
34
|
-
const value = (items as Record<string, unknown>)[propName];
|
|
35
|
-
return typeof value === 'function' ? value.bind(items) : value;
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
const getRef = (target as { get?: () => unknown }).get;
|
|
40
|
-
if (typeof getRef === 'function') {
|
|
41
|
-
const current = getRef.call(target);
|
|
42
|
-
if (current && prop in (current as object)) {
|
|
43
|
-
const propName = prop as string;
|
|
44
|
-
const value = (current as Record<string, unknown>)[propName];
|
|
45
|
-
return typeof value === 'function' ? value.bind(current) : value;
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
return undefined;
|
|
50
|
-
},
|
|
51
|
-
|
|
52
|
-
set(target, prop, value, receiver) {
|
|
53
|
-
if (typeof prop === 'symbol') {
|
|
54
|
-
return Reflect.set(target, prop, value, receiver);
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
if (prop in target) {
|
|
58
|
-
return Reflect.set(target, prop, value, receiver);
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
const getRef = (target as { get?: () => unknown }).get;
|
|
62
|
-
if (typeof getRef === 'function') {
|
|
63
|
-
const current = getRef.call(target);
|
|
64
|
-
if (current && typeof current === 'object') {
|
|
65
|
-
return Reflect.set(current as object, prop, value);
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
const getItems = (target as { getItems?: () => unknown }).getItems;
|
|
70
|
-
if (typeof getItems === 'function') {
|
|
71
|
-
const items = getItems.call(target);
|
|
72
|
-
return Reflect.set(items as object, prop, value);
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
return Reflect.set(target, prop, value, receiver);
|
|
76
|
-
}
|
|
77
|
-
});
|
|
78
|
-
};
|
|
79
|
-
|
|
80
|
-
/**
|
|
81
|
-
* Gets a relation wrapper for an entity.
|
|
82
|
-
* @param meta - The entity metadata
|
|
83
|
-
* @param relationName - The relation name
|
|
84
|
-
* @param owner - The owner entity
|
|
85
|
-
* @param createEntity - The entity factory for relation rows
|
|
86
|
-
* @returns The relation wrapper or undefined
|
|
87
|
-
*/
|
|
88
|
-
export const getRelationWrapper = <TTable extends TableDef>(
|
|
89
|
-
meta: EntityMeta<TTable>,
|
|
90
|
-
relationName: RelationKey<TTable> | string,
|
|
91
|
-
owner: unknown,
|
|
92
|
-
createEntity: RelationEntityFactory
|
|
93
|
-
): HasManyCollection<unknown> | HasOneReference<object> | BelongsToReference<object> | ManyToManyCollection<unknown> | undefined => {
|
|
94
|
-
const relationKey = relationName as string;
|
|
95
|
-
|
|
96
|
-
if (meta.relationWrappers.has(relationKey)) {
|
|
97
|
-
return meta.relationWrappers.get(relationKey) as HasManyCollection<unknown>;
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
const relation = meta.table.relations[relationKey];
|
|
101
|
-
if (!relation) return undefined;
|
|
102
|
-
|
|
103
|
-
const wrapper = instantiateWrapper(meta, relationKey, relation, owner, createEntity);
|
|
104
|
-
if (!wrapper) return undefined;
|
|
105
|
-
|
|
106
|
-
const proxied = proxifyRelationWrapper(wrapper as object);
|
|
107
|
-
meta.relationWrappers.set(relationKey, proxied);
|
|
108
|
-
return proxied as HasManyCollection<unknown>;
|
|
109
|
-
};
|
|
110
|
-
|
|
111
|
-
/**
|
|
112
|
-
* Instantiates the appropriate relation wrapper based on relation type.
|
|
113
|
-
* @param meta - The entity metadata
|
|
114
|
-
* @param relationName - The relation name
|
|
115
|
-
* @param relation - The relation definition
|
|
116
|
-
* @param owner - The owner entity
|
|
117
|
-
* @param createEntity - The entity factory for relation rows
|
|
118
|
-
* @returns The relation wrapper or undefined
|
|
119
|
-
*/
|
|
120
|
-
const instantiateWrapper = <TTable extends TableDef>(
|
|
121
|
-
meta: EntityMeta<TTable>,
|
|
122
|
-
relationName: string,
|
|
123
|
-
relation: HasManyRelation | HasOneRelation | BelongsToRelation | BelongsToManyRelation,
|
|
124
|
-
owner: unknown,
|
|
125
|
-
createEntity: RelationEntityFactory
|
|
126
|
-
): HasManyCollection<unknown> | HasOneReference<object> | BelongsToReference<object> | ManyToManyCollection<unknown> | undefined => {
|
|
127
|
-
const metaBase = meta as unknown as EntityMeta<TableDef>;
|
|
128
|
-
const lazyOptions = meta.lazyRelationOptions.get(relationName);
|
|
129
|
-
const loadCached = <T extends Map<string, unknown>>(factory: () => Promise<T>) =>
|
|
130
|
-
relationLoaderCache(metaBase, relationName, factory);
|
|
131
|
-
switch (relation.type) {
|
|
132
|
-
case RelationKinds.HasOne: {
|
|
133
|
-
const hasOne = relation as HasOneRelation;
|
|
134
|
-
const localKey = hasOne.localKey || findPrimaryKey(meta.table);
|
|
135
|
-
const loader = () => loadCached(() =>
|
|
136
|
-
loadHasOneRelation(meta.ctx, meta.table, relationName, hasOne, lazyOptions)
|
|
137
|
-
);
|
|
138
|
-
return new DefaultHasOneReference(
|
|
139
|
-
meta.ctx,
|
|
140
|
-
metaBase,
|
|
141
|
-
owner,
|
|
142
|
-
relationName,
|
|
143
|
-
hasOne,
|
|
144
|
-
meta.table,
|
|
145
|
-
loader,
|
|
146
|
-
(row: Record<string, unknown>) => createEntity(hasOne.target, row),
|
|
147
|
-
localKey
|
|
148
|
-
);
|
|
149
|
-
}
|
|
150
|
-
case RelationKinds.HasMany: {
|
|
151
|
-
const hasMany = relation as HasManyRelation;
|
|
152
|
-
const localKey = hasMany.localKey || findPrimaryKey(meta.table);
|
|
153
|
-
const loader = () => loadCached(() =>
|
|
154
|
-
loadHasManyRelation(meta.ctx, meta.table, relationName, hasMany, lazyOptions)
|
|
155
|
-
);
|
|
156
|
-
return new DefaultHasManyCollection(
|
|
157
|
-
meta.ctx,
|
|
158
|
-
metaBase,
|
|
159
|
-
owner,
|
|
160
|
-
relationName,
|
|
161
|
-
hasMany,
|
|
162
|
-
meta.table,
|
|
163
|
-
loader,
|
|
164
|
-
(row: Record<string, unknown>) => createEntity(relation.target, row),
|
|
165
|
-
localKey
|
|
166
|
-
);
|
|
167
|
-
}
|
|
168
|
-
case RelationKinds.BelongsTo: {
|
|
169
|
-
const belongsTo = relation as BelongsToRelation;
|
|
170
|
-
const targetKey = belongsTo.localKey || findPrimaryKey(belongsTo.target);
|
|
171
|
-
const loader = () => loadCached(() =>
|
|
172
|
-
loadBelongsToRelation(meta.ctx, meta.table, relationName, belongsTo, lazyOptions)
|
|
173
|
-
);
|
|
174
|
-
return new DefaultBelongsToReference(
|
|
175
|
-
meta.ctx,
|
|
176
|
-
metaBase,
|
|
177
|
-
owner,
|
|
178
|
-
relationName,
|
|
179
|
-
belongsTo,
|
|
180
|
-
meta.table,
|
|
181
|
-
loader,
|
|
182
|
-
(row: Record<string, unknown>) => createEntity(relation.target, row),
|
|
183
|
-
targetKey
|
|
184
|
-
);
|
|
185
|
-
}
|
|
186
|
-
case RelationKinds.BelongsToMany: {
|
|
187
|
-
const many = relation as BelongsToManyRelation;
|
|
188
|
-
const localKey = many.localKey || findPrimaryKey(meta.table);
|
|
189
|
-
const loader = () => loadCached(() =>
|
|
190
|
-
loadBelongsToManyRelation(meta.ctx, meta.table, relationName, many, lazyOptions)
|
|
191
|
-
);
|
|
192
|
-
return new DefaultManyToManyCollection(
|
|
193
|
-
meta.ctx,
|
|
194
|
-
metaBase,
|
|
195
|
-
owner,
|
|
196
|
-
relationName,
|
|
197
|
-
many,
|
|
198
|
-
meta.table,
|
|
199
|
-
loader,
|
|
200
|
-
(row: Record<string, unknown>) => createEntity(relation.target, row),
|
|
201
|
-
localKey
|
|
202
|
-
);
|
|
203
|
-
}
|
|
204
|
-
default:
|
|
205
|
-
return undefined;
|
|
206
|
-
}
|
|
207
|
-
};
|
|
1
|
+
import { TableDef } from '../schema/table.js';
|
|
2
|
+
import { EntityInstance, HasManyCollection, HasOneReference, BelongsToReference, ManyToManyCollection } from '../schema/types.js';
|
|
3
|
+
import { EntityMeta, RelationKey } from './entity-meta.js';
|
|
4
|
+
import { DefaultHasManyCollection } from './relations/has-many.js';
|
|
5
|
+
import { DefaultHasOneReference } from './relations/has-one.js';
|
|
6
|
+
import { DefaultBelongsToReference } from './relations/belongs-to.js';
|
|
7
|
+
import { DefaultManyToManyCollection } from './relations/many-to-many.js';
|
|
8
|
+
import { HasManyRelation, HasOneRelation, BelongsToRelation, BelongsToManyRelation, RelationKinds } from '../schema/relation.js';
|
|
9
|
+
import { loadHasManyRelation, loadHasOneRelation, loadBelongsToRelation, loadBelongsToManyRelation } from './lazy-batch.js';
|
|
10
|
+
import { findPrimaryKey } from '../query-builder/hydration-planner.js';
|
|
11
|
+
import { relationLoaderCache } from './entity-relation-cache.js';
|
|
12
|
+
|
|
13
|
+
export type RelationEntityFactory = (
|
|
14
|
+
table: TableDef,
|
|
15
|
+
row: Record<string, unknown>
|
|
16
|
+
) => EntityInstance<TableDef>;
|
|
17
|
+
|
|
18
|
+
const proxifyRelationWrapper = <T extends object>(wrapper: T): T => {
|
|
19
|
+
return new Proxy(wrapper, {
|
|
20
|
+
get(target, prop, receiver) {
|
|
21
|
+
if (typeof prop === 'symbol') {
|
|
22
|
+
return Reflect.get(target, prop, receiver);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
if (prop in target) {
|
|
26
|
+
return Reflect.get(target, prop, receiver);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const getItems = (target as { getItems?: () => unknown }).getItems;
|
|
30
|
+
if (typeof getItems === 'function') {
|
|
31
|
+
const items = getItems.call(target);
|
|
32
|
+
if (items && prop in (items as object)) {
|
|
33
|
+
const propName = prop as string;
|
|
34
|
+
const value = (items as Record<string, unknown>)[propName];
|
|
35
|
+
return typeof value === 'function' ? value.bind(items) : value;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const getRef = (target as { get?: () => unknown }).get;
|
|
40
|
+
if (typeof getRef === 'function') {
|
|
41
|
+
const current = getRef.call(target);
|
|
42
|
+
if (current && prop in (current as object)) {
|
|
43
|
+
const propName = prop as string;
|
|
44
|
+
const value = (current as Record<string, unknown>)[propName];
|
|
45
|
+
return typeof value === 'function' ? value.bind(current) : value;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return undefined;
|
|
50
|
+
},
|
|
51
|
+
|
|
52
|
+
set(target, prop, value, receiver) {
|
|
53
|
+
if (typeof prop === 'symbol') {
|
|
54
|
+
return Reflect.set(target, prop, value, receiver);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if (prop in target) {
|
|
58
|
+
return Reflect.set(target, prop, value, receiver);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const getRef = (target as { get?: () => unknown }).get;
|
|
62
|
+
if (typeof getRef === 'function') {
|
|
63
|
+
const current = getRef.call(target);
|
|
64
|
+
if (current && typeof current === 'object') {
|
|
65
|
+
return Reflect.set(current as object, prop, value);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const getItems = (target as { getItems?: () => unknown }).getItems;
|
|
70
|
+
if (typeof getItems === 'function') {
|
|
71
|
+
const items = getItems.call(target);
|
|
72
|
+
return Reflect.set(items as object, prop, value);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return Reflect.set(target, prop, value, receiver);
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Gets a relation wrapper for an entity.
|
|
82
|
+
* @param meta - The entity metadata
|
|
83
|
+
* @param relationName - The relation name
|
|
84
|
+
* @param owner - The owner entity
|
|
85
|
+
* @param createEntity - The entity factory for relation rows
|
|
86
|
+
* @returns The relation wrapper or undefined
|
|
87
|
+
*/
|
|
88
|
+
export const getRelationWrapper = <TTable extends TableDef>(
|
|
89
|
+
meta: EntityMeta<TTable>,
|
|
90
|
+
relationName: RelationKey<TTable> | string,
|
|
91
|
+
owner: unknown,
|
|
92
|
+
createEntity: RelationEntityFactory
|
|
93
|
+
): HasManyCollection<unknown> | HasOneReference<object> | BelongsToReference<object> | ManyToManyCollection<unknown> | undefined => {
|
|
94
|
+
const relationKey = relationName as string;
|
|
95
|
+
|
|
96
|
+
if (meta.relationWrappers.has(relationKey)) {
|
|
97
|
+
return meta.relationWrappers.get(relationKey) as HasManyCollection<unknown>;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const relation = meta.table.relations[relationKey];
|
|
101
|
+
if (!relation) return undefined;
|
|
102
|
+
|
|
103
|
+
const wrapper = instantiateWrapper(meta, relationKey, relation, owner, createEntity);
|
|
104
|
+
if (!wrapper) return undefined;
|
|
105
|
+
|
|
106
|
+
const proxied = proxifyRelationWrapper(wrapper as object);
|
|
107
|
+
meta.relationWrappers.set(relationKey, proxied);
|
|
108
|
+
return proxied as HasManyCollection<unknown>;
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Instantiates the appropriate relation wrapper based on relation type.
|
|
113
|
+
* @param meta - The entity metadata
|
|
114
|
+
* @param relationName - The relation name
|
|
115
|
+
* @param relation - The relation definition
|
|
116
|
+
* @param owner - The owner entity
|
|
117
|
+
* @param createEntity - The entity factory for relation rows
|
|
118
|
+
* @returns The relation wrapper or undefined
|
|
119
|
+
*/
|
|
120
|
+
const instantiateWrapper = <TTable extends TableDef>(
|
|
121
|
+
meta: EntityMeta<TTable>,
|
|
122
|
+
relationName: string,
|
|
123
|
+
relation: HasManyRelation | HasOneRelation | BelongsToRelation | BelongsToManyRelation,
|
|
124
|
+
owner: unknown,
|
|
125
|
+
createEntity: RelationEntityFactory
|
|
126
|
+
): HasManyCollection<unknown> | HasOneReference<object> | BelongsToReference<object> | ManyToManyCollection<unknown> | undefined => {
|
|
127
|
+
const metaBase = meta as unknown as EntityMeta<TableDef>;
|
|
128
|
+
const lazyOptions = meta.lazyRelationOptions.get(relationName);
|
|
129
|
+
const loadCached = <T extends Map<string, unknown>>(factory: () => Promise<T>) =>
|
|
130
|
+
relationLoaderCache(metaBase, relationName, factory);
|
|
131
|
+
switch (relation.type) {
|
|
132
|
+
case RelationKinds.HasOne: {
|
|
133
|
+
const hasOne = relation as HasOneRelation;
|
|
134
|
+
const localKey = hasOne.localKey || findPrimaryKey(meta.table);
|
|
135
|
+
const loader = () => loadCached(() =>
|
|
136
|
+
loadHasOneRelation(meta.ctx, meta.table, relationName, hasOne, lazyOptions)
|
|
137
|
+
);
|
|
138
|
+
return new DefaultHasOneReference(
|
|
139
|
+
meta.ctx,
|
|
140
|
+
metaBase,
|
|
141
|
+
owner,
|
|
142
|
+
relationName,
|
|
143
|
+
hasOne,
|
|
144
|
+
meta.table,
|
|
145
|
+
loader,
|
|
146
|
+
(row: Record<string, unknown>) => createEntity(hasOne.target, row),
|
|
147
|
+
localKey
|
|
148
|
+
);
|
|
149
|
+
}
|
|
150
|
+
case RelationKinds.HasMany: {
|
|
151
|
+
const hasMany = relation as HasManyRelation;
|
|
152
|
+
const localKey = hasMany.localKey || findPrimaryKey(meta.table);
|
|
153
|
+
const loader = () => loadCached(() =>
|
|
154
|
+
loadHasManyRelation(meta.ctx, meta.table, relationName, hasMany, lazyOptions)
|
|
155
|
+
);
|
|
156
|
+
return new DefaultHasManyCollection(
|
|
157
|
+
meta.ctx,
|
|
158
|
+
metaBase,
|
|
159
|
+
owner,
|
|
160
|
+
relationName,
|
|
161
|
+
hasMany,
|
|
162
|
+
meta.table,
|
|
163
|
+
loader,
|
|
164
|
+
(row: Record<string, unknown>) => createEntity(relation.target, row),
|
|
165
|
+
localKey
|
|
166
|
+
);
|
|
167
|
+
}
|
|
168
|
+
case RelationKinds.BelongsTo: {
|
|
169
|
+
const belongsTo = relation as BelongsToRelation;
|
|
170
|
+
const targetKey = belongsTo.localKey || findPrimaryKey(belongsTo.target);
|
|
171
|
+
const loader = () => loadCached(() =>
|
|
172
|
+
loadBelongsToRelation(meta.ctx, meta.table, relationName, belongsTo, lazyOptions)
|
|
173
|
+
);
|
|
174
|
+
return new DefaultBelongsToReference(
|
|
175
|
+
meta.ctx,
|
|
176
|
+
metaBase,
|
|
177
|
+
owner,
|
|
178
|
+
relationName,
|
|
179
|
+
belongsTo,
|
|
180
|
+
meta.table,
|
|
181
|
+
loader,
|
|
182
|
+
(row: Record<string, unknown>) => createEntity(relation.target, row),
|
|
183
|
+
targetKey
|
|
184
|
+
);
|
|
185
|
+
}
|
|
186
|
+
case RelationKinds.BelongsToMany: {
|
|
187
|
+
const many = relation as BelongsToManyRelation;
|
|
188
|
+
const localKey = many.localKey || findPrimaryKey(meta.table);
|
|
189
|
+
const loader = () => loadCached(() =>
|
|
190
|
+
loadBelongsToManyRelation(meta.ctx, meta.table, relationName, many, lazyOptions)
|
|
191
|
+
);
|
|
192
|
+
return new DefaultManyToManyCollection(
|
|
193
|
+
meta.ctx,
|
|
194
|
+
metaBase,
|
|
195
|
+
owner,
|
|
196
|
+
relationName,
|
|
197
|
+
many,
|
|
198
|
+
meta.table,
|
|
199
|
+
loader,
|
|
200
|
+
(row: Record<string, unknown>) => createEntity(relation.target, row),
|
|
201
|
+
localKey
|
|
202
|
+
);
|
|
203
|
+
}
|
|
204
|
+
default:
|
|
205
|
+
return undefined;
|
|
206
|
+
}
|
|
207
|
+
};
|