metal-orm 1.0.8 → 1.0.9
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 +12 -1
- package/dist/decorators/index.cjs +2564 -0
- package/dist/decorators/index.cjs.map +1 -0
- package/dist/decorators/index.d.cts +53 -0
- package/dist/decorators/index.d.ts +53 -0
- package/dist/decorators/index.js +2530 -0
- package/dist/decorators/index.js.map +1 -0
- package/dist/index.cjs +4227 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +701 -0
- package/dist/index.d.ts +701 -0
- package/dist/index.js +4131 -0
- package/dist/index.js.map +1 -0
- package/dist/select-654m4qy8.d.cts +1522 -0
- package/dist/select-654m4qy8.d.ts +1522 -0
- package/package.json +27 -20
- package/src/codegen/typescript.ts +405 -393
- package/src/core/ast/aggregate-functions.ts +30 -0
- package/src/core/ast/builders.ts +43 -0
- package/src/core/ast/expression-builders.ts +310 -0
- package/src/core/ast/expression-nodes.ts +211 -0
- package/src/core/ast/expression-visitor.ts +99 -0
- package/src/core/ast/expression.ts +5 -0
- package/src/{utils → core/ast}/join-node.ts +20 -20
- package/src/{ast → core/ast}/join.ts +18 -18
- package/src/{ast → core/ast}/query.ts +113 -113
- package/src/core/ast/window-functions.ts +140 -0
- package/src/{dialect → core/dialect}/abstract.ts +94 -94
- package/src/{dialect → core/dialect}/mssql/index.ts +31 -31
- package/src/{dialect → core/dialect}/mysql/index.ts +31 -31
- package/src/{dialect → core/dialect}/postgres/index.ts +45 -45
- package/src/{dialect → core/dialect}/sqlite/index.ts +45 -45
- package/src/{constants → core/sql}/sql-operator-config.ts +39 -39
- package/src/decorators/bootstrap.ts +126 -0
- package/src/decorators/column.ts +78 -0
- package/src/decorators/entity.ts +36 -0
- package/src/decorators/index.ts +4 -0
- package/src/decorators/relations.ts +107 -0
- package/src/global.d.ts +1 -0
- package/src/index.ts +22 -22
- package/src/orm/db-executor.ts +11 -0
- package/src/orm/domain-event-bus.ts +52 -0
- package/src/{runtime → orm}/entity-meta.ts +52 -52
- package/src/orm/entity-metadata.ts +140 -0
- package/src/{runtime → orm}/entity.ts +252 -252
- package/src/{runtime → orm}/execute.ts +36 -36
- package/src/{runtime → orm}/hydration.ts +103 -103
- package/src/orm/identity-map.ts +37 -0
- package/src/{runtime → orm}/lazy-batch.ts +205 -205
- package/src/orm/orm-context.ts +154 -0
- package/src/orm/relation-change-processor.ts +140 -0
- package/src/{runtime → orm}/relations/belongs-to.ts +92 -92
- package/src/{runtime → orm}/relations/has-many.ts +111 -111
- package/src/{runtime → orm}/relations/many-to-many.ts +149 -149
- package/src/orm/runtime-types.ts +39 -0
- package/src/orm/transaction-runner.ts +17 -0
- package/src/orm/unit-of-work.ts +232 -0
- package/src/{builder/operations → query-builder}/column-selector.ts +78 -78
- package/src/{builder → query-builder}/delete-query-state.ts +38 -42
- package/src/{builder → query-builder}/delete.ts +46 -57
- package/src/{builder → query-builder}/hydration-manager.ts +87 -87
- package/src/{builder → query-builder}/hydration-planner.ts +182 -182
- package/src/{builder → query-builder}/insert-query-state.ts +51 -62
- package/src/{builder → query-builder}/insert.ts +48 -59
- package/src/{builder → query-builder}/query-ast-service.ts +208 -226
- package/src/{utils → query-builder}/raw-column-parser.ts +32 -32
- package/src/{builder → query-builder}/relation-conditions.ts +112 -112
- package/src/{builder/operations → query-builder}/relation-manager.ts +82 -82
- package/src/{builder → query-builder}/relation-projection-helper.ts +101 -101
- package/src/{builder → query-builder}/relation-service.ts +284 -284
- package/src/{builder → query-builder}/relation-types.ts +21 -21
- package/src/{builder → query-builder}/relation-utils.ts +12 -12
- package/src/{builder → query-builder}/select-query-builder-deps.ts +112 -94
- package/src/{builder → query-builder}/select-query-state.ts +179 -179
- package/src/{builder → query-builder}/select.ts +78 -69
- package/src/{builder → query-builder}/update-query-state.ts +55 -59
- package/src/{builder → query-builder}/update.ts +50 -61
- package/src/schema/column.ts +25 -25
- package/src/schema/relation.ts +116 -116
- package/src/schema/table.ts +34 -34
- package/src/schema/types.ts +76 -76
- package/.github/workflows/publish-metal-orm.yml +0 -38
- package/ROADMAP.md +0 -125
- package/docs/CHANGES.md +0 -104
- package/docs/advanced-features.md +0 -176
- package/docs/api-reference.md +0 -31
- package/docs/dml-operations.md +0 -156
- package/docs/getting-started.md +0 -171
- package/docs/hydration.md +0 -115
- package/docs/index.md +0 -36
- package/docs/multi-dialect-support.md +0 -59
- package/docs/query-builder.md +0 -135
- package/docs/runtime.md +0 -105
- package/docs/schema-definition.md +0 -112
- package/metadata.json +0 -5
- package/playground/api/playground-api.ts +0 -94
- package/playground/index.html +0 -15
- package/playground/src/App.css +0 -1
- package/playground/src/App.tsx +0 -114
- package/playground/src/components/CodeDisplay.tsx +0 -43
- package/playground/src/components/QueryExecutor.tsx +0 -189
- package/playground/src/components/ResultsTable.tsx +0 -67
- package/playground/src/components/ResultsTabs.tsx +0 -105
- package/playground/src/components/ScenarioList.tsx +0 -56
- package/playground/src/components/logo.svg +0 -45
- package/playground/src/data/scenarios.ts +0 -2
- package/playground/src/main.tsx +0 -9
- package/playground/src/services/PlaygroundApiService.ts +0 -60
- package/postcss.config.cjs +0 -5
- package/sql_sql-ansi-cheatsheet-2025.md +0 -264
- package/src/ast/expression.ts +0 -658
- package/src/builder/operations/cte-manager.ts +0 -34
- package/src/builder/operations/filter-manager.ts +0 -68
- package/src/builder/operations/join-manager.ts +0 -36
- package/src/builder/operations/pagination-manager.ts +0 -36
- package/src/playground/features/playground/api/types.ts +0 -16
- package/src/playground/features/playground/clients/MockClient.ts +0 -17
- package/src/playground/features/playground/clients/SqliteClient.ts +0 -57
- package/src/playground/features/playground/common/IDatabaseClient.ts +0 -10
- package/src/playground/features/playground/data/scenarios/aggregation.ts +0 -36
- package/src/playground/features/playground/data/scenarios/basics.ts +0 -25
- package/src/playground/features/playground/data/scenarios/edge_cases.ts +0 -57
- package/src/playground/features/playground/data/scenarios/filtering.ts +0 -94
- package/src/playground/features/playground/data/scenarios/hydration.ts +0 -27
- package/src/playground/features/playground/data/scenarios/index.ts +0 -29
- package/src/playground/features/playground/data/scenarios/ordering.ts +0 -25
- package/src/playground/features/playground/data/scenarios/pagination.ts +0 -16
- package/src/playground/features/playground/data/scenarios/relationships.ts +0 -75
- package/src/playground/features/playground/data/scenarios/types.ts +0 -70
- package/src/playground/features/playground/data/schema.ts +0 -91
- package/src/playground/features/playground/data/seed.ts +0 -104
- package/src/playground/features/playground/services/QueryExecutionService.ts +0 -121
- package/src/runtime/orm-context.ts +0 -539
- package/tests/belongs-to-many.test.ts +0 -57
- package/tests/between.test.ts +0 -43
- package/tests/case-expression.test.ts +0 -58
- package/tests/complex-exists.test.ts +0 -230
- package/tests/cte.test.ts +0 -118
- package/tests/dml.test.ts +0 -206
- package/tests/exists.test.ts +0 -127
- package/tests/like.test.ts +0 -33
- package/tests/orm-runtime.test.ts +0 -254
- package/tests/postgres.test.ts +0 -30
- package/tests/right-join.test.ts +0 -89
- package/tests/subquery-having.test.ts +0 -193
- package/tests/window-function.test.ts +0 -151
- package/tsconfig.json +0 -30
- package/tsup.config.ts +0 -10
- package/vite.config.ts +0 -22
- package/vitest.config.ts +0 -14
- /package/src/{constants → core/sql}/sql.ts +0 -0
- /package/src/{runtime → orm}/als.ts +0 -0
- /package/src/{utils → query-builder}/relation-alias.ts +0 -0
|
@@ -1,252 +1,252 @@
|
|
|
1
|
-
import { TableDef } from '../schema/table';
|
|
2
|
-
import { Entity, RelationMap, HasManyCollection, BelongsToReference, ManyToManyCollection } from '../schema/types';
|
|
3
|
-
import { OrmContext } from './orm-context';
|
|
4
|
-
import { ENTITY_META, EntityMeta, getEntityMeta } from './entity-meta';
|
|
5
|
-
import { DefaultHasManyCollection } from './relations/has-many';
|
|
6
|
-
import { DefaultBelongsToReference } from './relations/belongs-to';
|
|
7
|
-
import { DefaultManyToManyCollection } from './relations/many-to-many';
|
|
8
|
-
import { HasManyRelation, BelongsToRelation, BelongsToManyRelation, RelationKinds } from '../schema/relation';
|
|
9
|
-
import { loadHasManyRelation, loadBelongsToRelation, loadBelongsToManyRelation } from './lazy-batch';
|
|
10
|
-
import { findPrimaryKey } from '../builder/hydration-planner';
|
|
11
|
-
|
|
12
|
-
type Rows = Record<string, any>[];
|
|
13
|
-
|
|
14
|
-
const relationLoaderCache = <T extends Map<string, any>>(
|
|
15
|
-
meta: EntityMeta<any>,
|
|
16
|
-
relationName: string,
|
|
17
|
-
factory: () => Promise<T>
|
|
18
|
-
): Promise<T> => {
|
|
19
|
-
if (meta.relationCache.has(relationName)) {
|
|
20
|
-
return meta.relationCache.get(relationName)! as Promise<T>;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
const promise = factory().then(value => {
|
|
24
|
-
for (const tracked of meta.ctx.getEntitiesForTable(meta.table)) {
|
|
25
|
-
const otherMeta = getEntityMeta(tracked.entity);
|
|
26
|
-
if (!otherMeta) continue;
|
|
27
|
-
otherMeta.relationHydration.set(relationName, value);
|
|
28
|
-
}
|
|
29
|
-
return value;
|
|
30
|
-
});
|
|
31
|
-
|
|
32
|
-
meta.relationCache.set(relationName, promise);
|
|
33
|
-
|
|
34
|
-
for (const tracked of meta.ctx.getEntitiesForTable(meta.table)) {
|
|
35
|
-
const otherMeta = getEntityMeta(tracked.entity);
|
|
36
|
-
if (!otherMeta) continue;
|
|
37
|
-
otherMeta.relationCache.set(relationName, promise);
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
return promise;
|
|
41
|
-
};
|
|
42
|
-
|
|
43
|
-
export const createEntityProxy = <
|
|
44
|
-
TTable extends TableDef,
|
|
45
|
-
TLazy extends keyof RelationMap<TTable> = keyof RelationMap<TTable>
|
|
46
|
-
>(
|
|
47
|
-
ctx: OrmContext,
|
|
48
|
-
table: TTable,
|
|
49
|
-
row: Record<string, any>,
|
|
50
|
-
lazyRelations: TLazy[] = [] as TLazy[]
|
|
51
|
-
): Entity<TTable> => {
|
|
52
|
-
const target: Record<string, any> = { ...row };
|
|
53
|
-
const meta: EntityMeta<TTable> = {
|
|
54
|
-
ctx,
|
|
55
|
-
table,
|
|
56
|
-
lazyRelations: [...lazyRelations],
|
|
57
|
-
relationCache: new Map(),
|
|
58
|
-
relationHydration: new Map(),
|
|
59
|
-
relationWrappers: new Map()
|
|
60
|
-
};
|
|
61
|
-
|
|
62
|
-
Object.defineProperty(target, ENTITY_META, {
|
|
63
|
-
value: meta,
|
|
64
|
-
enumerable: false,
|
|
65
|
-
writable: false
|
|
66
|
-
});
|
|
67
|
-
|
|
68
|
-
let proxy: Entity<TTable>;
|
|
69
|
-
const handler: ProxyHandler<any> = {
|
|
70
|
-
get(targetObj, prop, receiver) {
|
|
71
|
-
if (prop === ENTITY_META) {
|
|
72
|
-
return meta;
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
if (prop === '$load') {
|
|
76
|
-
return async (relationName: keyof RelationMap<TTable>) => {
|
|
77
|
-
const wrapper = getRelationWrapper(meta, relationName as string, proxy);
|
|
78
|
-
if (wrapper && typeof wrapper.load === 'function') {
|
|
79
|
-
return wrapper.load();
|
|
80
|
-
}
|
|
81
|
-
return undefined;
|
|
82
|
-
};
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
if (typeof prop === 'string' && table.relations[prop]) {
|
|
86
|
-
return getRelationWrapper(meta, prop, proxy);
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
return Reflect.get(targetObj, prop, receiver);
|
|
90
|
-
},
|
|
91
|
-
|
|
92
|
-
set(targetObj, prop, value, receiver) {
|
|
93
|
-
const result = Reflect.set(targetObj, prop, value, receiver);
|
|
94
|
-
if (typeof prop === 'string' && table.columns[prop]) {
|
|
95
|
-
ctx.markDirty(proxy);
|
|
96
|
-
}
|
|
97
|
-
return result;
|
|
98
|
-
}
|
|
99
|
-
};
|
|
100
|
-
|
|
101
|
-
proxy = new Proxy(target, handler) as Entity<TTable>;
|
|
102
|
-
populateHydrationCache(proxy, row, meta);
|
|
103
|
-
return proxy;
|
|
104
|
-
};
|
|
105
|
-
|
|
106
|
-
export const createEntityFromRow = <TTable extends TableDef>(
|
|
107
|
-
ctx: OrmContext,
|
|
108
|
-
table: TTable,
|
|
109
|
-
row: Record<string, any>,
|
|
110
|
-
lazyRelations: (keyof RelationMap<TTable>)[] = []
|
|
111
|
-
): Entity<TTable> => {
|
|
112
|
-
const pkName = findPrimaryKey(table);
|
|
113
|
-
const pkValue = row[pkName];
|
|
114
|
-
if (pkValue !== undefined && pkValue !== null) {
|
|
115
|
-
const tracked = ctx.getEntity(table, pkValue);
|
|
116
|
-
if (tracked) return tracked;
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
const entity = createEntityProxy(ctx, table, row, lazyRelations);
|
|
120
|
-
if (pkValue !== undefined && pkValue !== null) {
|
|
121
|
-
ctx.trackManaged(table, pkValue, entity);
|
|
122
|
-
} else {
|
|
123
|
-
ctx.trackNew(table, entity);
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
return entity;
|
|
127
|
-
};
|
|
128
|
-
|
|
129
|
-
const toKey = (value: unknown): string => (value === null || value === undefined ? '' : String(value));
|
|
130
|
-
|
|
131
|
-
const populateHydrationCache = <TTable extends TableDef>(
|
|
132
|
-
entity: any,
|
|
133
|
-
row: Record<string, any>,
|
|
134
|
-
meta: EntityMeta<TTable>
|
|
135
|
-
): void => {
|
|
136
|
-
for (const relationName of Object.keys(meta.table.relations)) {
|
|
137
|
-
const relation = meta.table.relations[relationName];
|
|
138
|
-
const data = row[relationName];
|
|
139
|
-
if (!Array.isArray(data)) continue;
|
|
140
|
-
|
|
141
|
-
if (relation.type === RelationKinds.HasMany || relation.type === RelationKinds.BelongsToMany) {
|
|
142
|
-
const localKey = relation.localKey || findPrimaryKey(meta.table);
|
|
143
|
-
const rootValue = entity[localKey];
|
|
144
|
-
if (rootValue === undefined || rootValue === null) continue;
|
|
145
|
-
const cache = new Map<string, Rows>();
|
|
146
|
-
cache.set(toKey(rootValue), data as Rows);
|
|
147
|
-
meta.relationHydration.set(relationName, cache);
|
|
148
|
-
meta.relationCache.set(relationName, Promise.resolve(cache));
|
|
149
|
-
continue;
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
if (relation.type === RelationKinds.BelongsTo) {
|
|
153
|
-
const targetKey = relation.localKey || findPrimaryKey(relation.target);
|
|
154
|
-
const cache = new Map<string, Record<string, any>>();
|
|
155
|
-
for (const item of data) {
|
|
156
|
-
const pkValue = item[targetKey];
|
|
157
|
-
if (pkValue === undefined || pkValue === null) continue;
|
|
158
|
-
cache.set(toKey(pkValue), item);
|
|
159
|
-
}
|
|
160
|
-
if (cache.size) {
|
|
161
|
-
meta.relationHydration.set(relationName, cache);
|
|
162
|
-
meta.relationCache.set(relationName, Promise.resolve(cache));
|
|
163
|
-
}
|
|
164
|
-
}
|
|
165
|
-
}
|
|
166
|
-
};
|
|
167
|
-
|
|
168
|
-
const getRelationWrapper = (
|
|
169
|
-
meta: EntityMeta<any>,
|
|
170
|
-
relationName: string,
|
|
171
|
-
owner: any
|
|
172
|
-
): HasManyCollection<any> | BelongsToReference<any> | ManyToManyCollection<any> | undefined => {
|
|
173
|
-
if (meta.relationWrappers.has(relationName)) {
|
|
174
|
-
return meta.relationWrappers.get(relationName) as HasManyCollection<any>;
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
const relation = meta.table.relations[relationName];
|
|
178
|
-
if (!relation) return undefined;
|
|
179
|
-
|
|
180
|
-
const wrapper = instantiateWrapper(meta, relationName, relation as any, owner);
|
|
181
|
-
if (wrapper) {
|
|
182
|
-
meta.relationWrappers.set(relationName, wrapper);
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
return wrapper;
|
|
186
|
-
};
|
|
187
|
-
|
|
188
|
-
const instantiateWrapper = (
|
|
189
|
-
meta: EntityMeta<any>,
|
|
190
|
-
relationName: string,
|
|
191
|
-
relation: HasManyRelation | BelongsToRelation | BelongsToManyRelation,
|
|
192
|
-
owner: any
|
|
193
|
-
): HasManyCollection<any> | BelongsToReference<any> | ManyToManyCollection<any> | undefined => {
|
|
194
|
-
switch (relation.type) {
|
|
195
|
-
case RelationKinds.HasMany: {
|
|
196
|
-
const hasMany = relation as HasManyRelation;
|
|
197
|
-
const localKey = hasMany.localKey || findPrimaryKey(meta.table);
|
|
198
|
-
const loader = () => relationLoaderCache(meta, relationName, () =>
|
|
199
|
-
loadHasManyRelation(meta.ctx, meta.table, relationName, hasMany)
|
|
200
|
-
);
|
|
201
|
-
return new DefaultHasManyCollection(
|
|
202
|
-
meta.ctx,
|
|
203
|
-
meta,
|
|
204
|
-
owner,
|
|
205
|
-
relationName,
|
|
206
|
-
hasMany,
|
|
207
|
-
meta.table,
|
|
208
|
-
loader,
|
|
209
|
-
(row: Record<string, any>) => createEntityFromRow(meta.ctx, relation.target, row),
|
|
210
|
-
localKey
|
|
211
|
-
);
|
|
212
|
-
}
|
|
213
|
-
case RelationKinds.BelongsTo: {
|
|
214
|
-
const belongsTo = relation as BelongsToRelation;
|
|
215
|
-
const targetKey = belongsTo.localKey || findPrimaryKey(belongsTo.target);
|
|
216
|
-
const loader = () => relationLoaderCache(meta, relationName, () =>
|
|
217
|
-
loadBelongsToRelation(meta.ctx, meta.table, relationName, belongsTo)
|
|
218
|
-
);
|
|
219
|
-
return new DefaultBelongsToReference(
|
|
220
|
-
meta.ctx,
|
|
221
|
-
meta,
|
|
222
|
-
owner,
|
|
223
|
-
relationName,
|
|
224
|
-
belongsTo,
|
|
225
|
-
meta.table,
|
|
226
|
-
loader,
|
|
227
|
-
(row: Record<string, any>) => createEntityFromRow(meta.ctx, relation.target, row),
|
|
228
|
-
targetKey
|
|
229
|
-
);
|
|
230
|
-
}
|
|
231
|
-
case RelationKinds.BelongsToMany: {
|
|
232
|
-
const many = relation as BelongsToManyRelation;
|
|
233
|
-
const localKey = many.localKey || findPrimaryKey(meta.table);
|
|
234
|
-
const loader = () => relationLoaderCache(meta, relationName, () =>
|
|
235
|
-
loadBelongsToManyRelation(meta.ctx, meta.table, relationName, many)
|
|
236
|
-
);
|
|
237
|
-
return new DefaultManyToManyCollection(
|
|
238
|
-
meta.ctx,
|
|
239
|
-
meta,
|
|
240
|
-
owner,
|
|
241
|
-
relationName,
|
|
242
|
-
many,
|
|
243
|
-
meta.table,
|
|
244
|
-
loader,
|
|
245
|
-
(row: Record<string, any>) => createEntityFromRow(meta.ctx, relation.target, row),
|
|
246
|
-
localKey
|
|
247
|
-
);
|
|
248
|
-
}
|
|
249
|
-
default:
|
|
250
|
-
return undefined;
|
|
251
|
-
}
|
|
252
|
-
};
|
|
1
|
+
import { TableDef } from '../schema/table.js';
|
|
2
|
+
import { Entity, RelationMap, HasManyCollection, BelongsToReference, ManyToManyCollection } from '../schema/types.js';
|
|
3
|
+
import { OrmContext } from './orm-context.js';
|
|
4
|
+
import { ENTITY_META, EntityMeta, getEntityMeta } from './entity-meta.js';
|
|
5
|
+
import { DefaultHasManyCollection } from './relations/has-many.js';
|
|
6
|
+
import { DefaultBelongsToReference } from './relations/belongs-to.js';
|
|
7
|
+
import { DefaultManyToManyCollection } from './relations/many-to-many.js';
|
|
8
|
+
import { HasManyRelation, BelongsToRelation, BelongsToManyRelation, RelationKinds } from '../schema/relation.js';
|
|
9
|
+
import { loadHasManyRelation, loadBelongsToRelation, loadBelongsToManyRelation } from './lazy-batch.js';
|
|
10
|
+
import { findPrimaryKey } from '../query-builder/hydration-planner.js';
|
|
11
|
+
|
|
12
|
+
type Rows = Record<string, any>[];
|
|
13
|
+
|
|
14
|
+
const relationLoaderCache = <T extends Map<string, any>>(
|
|
15
|
+
meta: EntityMeta<any>,
|
|
16
|
+
relationName: string,
|
|
17
|
+
factory: () => Promise<T>
|
|
18
|
+
): Promise<T> => {
|
|
19
|
+
if (meta.relationCache.has(relationName)) {
|
|
20
|
+
return meta.relationCache.get(relationName)! as Promise<T>;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const promise = factory().then(value => {
|
|
24
|
+
for (const tracked of meta.ctx.getEntitiesForTable(meta.table)) {
|
|
25
|
+
const otherMeta = getEntityMeta(tracked.entity);
|
|
26
|
+
if (!otherMeta) continue;
|
|
27
|
+
otherMeta.relationHydration.set(relationName, value);
|
|
28
|
+
}
|
|
29
|
+
return value;
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
meta.relationCache.set(relationName, promise);
|
|
33
|
+
|
|
34
|
+
for (const tracked of meta.ctx.getEntitiesForTable(meta.table)) {
|
|
35
|
+
const otherMeta = getEntityMeta(tracked.entity);
|
|
36
|
+
if (!otherMeta) continue;
|
|
37
|
+
otherMeta.relationCache.set(relationName, promise);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return promise;
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
export const createEntityProxy = <
|
|
44
|
+
TTable extends TableDef,
|
|
45
|
+
TLazy extends keyof RelationMap<TTable> = keyof RelationMap<TTable>
|
|
46
|
+
>(
|
|
47
|
+
ctx: OrmContext,
|
|
48
|
+
table: TTable,
|
|
49
|
+
row: Record<string, any>,
|
|
50
|
+
lazyRelations: TLazy[] = [] as TLazy[]
|
|
51
|
+
): Entity<TTable> => {
|
|
52
|
+
const target: Record<string, any> = { ...row };
|
|
53
|
+
const meta: EntityMeta<TTable> = {
|
|
54
|
+
ctx,
|
|
55
|
+
table,
|
|
56
|
+
lazyRelations: [...lazyRelations],
|
|
57
|
+
relationCache: new Map(),
|
|
58
|
+
relationHydration: new Map(),
|
|
59
|
+
relationWrappers: new Map()
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
Object.defineProperty(target, ENTITY_META, {
|
|
63
|
+
value: meta,
|
|
64
|
+
enumerable: false,
|
|
65
|
+
writable: false
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
let proxy: Entity<TTable>;
|
|
69
|
+
const handler: ProxyHandler<any> = {
|
|
70
|
+
get(targetObj, prop, receiver) {
|
|
71
|
+
if (prop === ENTITY_META) {
|
|
72
|
+
return meta;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (prop === '$load') {
|
|
76
|
+
return async (relationName: keyof RelationMap<TTable>) => {
|
|
77
|
+
const wrapper = getRelationWrapper(meta, relationName as string, proxy);
|
|
78
|
+
if (wrapper && typeof wrapper.load === 'function') {
|
|
79
|
+
return wrapper.load();
|
|
80
|
+
}
|
|
81
|
+
return undefined;
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
if (typeof prop === 'string' && table.relations[prop]) {
|
|
86
|
+
return getRelationWrapper(meta, prop, proxy);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
return Reflect.get(targetObj, prop, receiver);
|
|
90
|
+
},
|
|
91
|
+
|
|
92
|
+
set(targetObj, prop, value, receiver) {
|
|
93
|
+
const result = Reflect.set(targetObj, prop, value, receiver);
|
|
94
|
+
if (typeof prop === 'string' && table.columns[prop]) {
|
|
95
|
+
ctx.markDirty(proxy);
|
|
96
|
+
}
|
|
97
|
+
return result;
|
|
98
|
+
}
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
proxy = new Proxy(target, handler) as Entity<TTable>;
|
|
102
|
+
populateHydrationCache(proxy, row, meta);
|
|
103
|
+
return proxy;
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
export const createEntityFromRow = <TTable extends TableDef>(
|
|
107
|
+
ctx: OrmContext,
|
|
108
|
+
table: TTable,
|
|
109
|
+
row: Record<string, any>,
|
|
110
|
+
lazyRelations: (keyof RelationMap<TTable>)[] = []
|
|
111
|
+
): Entity<TTable> => {
|
|
112
|
+
const pkName = findPrimaryKey(table);
|
|
113
|
+
const pkValue = row[pkName];
|
|
114
|
+
if (pkValue !== undefined && pkValue !== null) {
|
|
115
|
+
const tracked = ctx.getEntity(table, pkValue);
|
|
116
|
+
if (tracked) return tracked;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const entity = createEntityProxy(ctx, table, row, lazyRelations);
|
|
120
|
+
if (pkValue !== undefined && pkValue !== null) {
|
|
121
|
+
ctx.trackManaged(table, pkValue, entity);
|
|
122
|
+
} else {
|
|
123
|
+
ctx.trackNew(table, entity);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
return entity;
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
const toKey = (value: unknown): string => (value === null || value === undefined ? '' : String(value));
|
|
130
|
+
|
|
131
|
+
const populateHydrationCache = <TTable extends TableDef>(
|
|
132
|
+
entity: any,
|
|
133
|
+
row: Record<string, any>,
|
|
134
|
+
meta: EntityMeta<TTable>
|
|
135
|
+
): void => {
|
|
136
|
+
for (const relationName of Object.keys(meta.table.relations)) {
|
|
137
|
+
const relation = meta.table.relations[relationName];
|
|
138
|
+
const data = row[relationName];
|
|
139
|
+
if (!Array.isArray(data)) continue;
|
|
140
|
+
|
|
141
|
+
if (relation.type === RelationKinds.HasMany || relation.type === RelationKinds.BelongsToMany) {
|
|
142
|
+
const localKey = relation.localKey || findPrimaryKey(meta.table);
|
|
143
|
+
const rootValue = entity[localKey];
|
|
144
|
+
if (rootValue === undefined || rootValue === null) continue;
|
|
145
|
+
const cache = new Map<string, Rows>();
|
|
146
|
+
cache.set(toKey(rootValue), data as Rows);
|
|
147
|
+
meta.relationHydration.set(relationName, cache);
|
|
148
|
+
meta.relationCache.set(relationName, Promise.resolve(cache));
|
|
149
|
+
continue;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
if (relation.type === RelationKinds.BelongsTo) {
|
|
153
|
+
const targetKey = relation.localKey || findPrimaryKey(relation.target);
|
|
154
|
+
const cache = new Map<string, Record<string, any>>();
|
|
155
|
+
for (const item of data) {
|
|
156
|
+
const pkValue = item[targetKey];
|
|
157
|
+
if (pkValue === undefined || pkValue === null) continue;
|
|
158
|
+
cache.set(toKey(pkValue), item);
|
|
159
|
+
}
|
|
160
|
+
if (cache.size) {
|
|
161
|
+
meta.relationHydration.set(relationName, cache);
|
|
162
|
+
meta.relationCache.set(relationName, Promise.resolve(cache));
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
const getRelationWrapper = (
|
|
169
|
+
meta: EntityMeta<any>,
|
|
170
|
+
relationName: string,
|
|
171
|
+
owner: any
|
|
172
|
+
): HasManyCollection<any> | BelongsToReference<any> | ManyToManyCollection<any> | undefined => {
|
|
173
|
+
if (meta.relationWrappers.has(relationName)) {
|
|
174
|
+
return meta.relationWrappers.get(relationName) as HasManyCollection<any>;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
const relation = meta.table.relations[relationName];
|
|
178
|
+
if (!relation) return undefined;
|
|
179
|
+
|
|
180
|
+
const wrapper = instantiateWrapper(meta, relationName, relation as any, owner);
|
|
181
|
+
if (wrapper) {
|
|
182
|
+
meta.relationWrappers.set(relationName, wrapper);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
return wrapper;
|
|
186
|
+
};
|
|
187
|
+
|
|
188
|
+
const instantiateWrapper = (
|
|
189
|
+
meta: EntityMeta<any>,
|
|
190
|
+
relationName: string,
|
|
191
|
+
relation: HasManyRelation | BelongsToRelation | BelongsToManyRelation,
|
|
192
|
+
owner: any
|
|
193
|
+
): HasManyCollection<any> | BelongsToReference<any> | ManyToManyCollection<any> | undefined => {
|
|
194
|
+
switch (relation.type) {
|
|
195
|
+
case RelationKinds.HasMany: {
|
|
196
|
+
const hasMany = relation as HasManyRelation;
|
|
197
|
+
const localKey = hasMany.localKey || findPrimaryKey(meta.table);
|
|
198
|
+
const loader = () => relationLoaderCache(meta, relationName, () =>
|
|
199
|
+
loadHasManyRelation(meta.ctx, meta.table, relationName, hasMany)
|
|
200
|
+
);
|
|
201
|
+
return new DefaultHasManyCollection(
|
|
202
|
+
meta.ctx,
|
|
203
|
+
meta,
|
|
204
|
+
owner,
|
|
205
|
+
relationName,
|
|
206
|
+
hasMany,
|
|
207
|
+
meta.table,
|
|
208
|
+
loader,
|
|
209
|
+
(row: Record<string, any>) => createEntityFromRow(meta.ctx, relation.target, row),
|
|
210
|
+
localKey
|
|
211
|
+
);
|
|
212
|
+
}
|
|
213
|
+
case RelationKinds.BelongsTo: {
|
|
214
|
+
const belongsTo = relation as BelongsToRelation;
|
|
215
|
+
const targetKey = belongsTo.localKey || findPrimaryKey(belongsTo.target);
|
|
216
|
+
const loader = () => relationLoaderCache(meta, relationName, () =>
|
|
217
|
+
loadBelongsToRelation(meta.ctx, meta.table, relationName, belongsTo)
|
|
218
|
+
);
|
|
219
|
+
return new DefaultBelongsToReference(
|
|
220
|
+
meta.ctx,
|
|
221
|
+
meta,
|
|
222
|
+
owner,
|
|
223
|
+
relationName,
|
|
224
|
+
belongsTo,
|
|
225
|
+
meta.table,
|
|
226
|
+
loader,
|
|
227
|
+
(row: Record<string, any>) => createEntityFromRow(meta.ctx, relation.target, row),
|
|
228
|
+
targetKey
|
|
229
|
+
);
|
|
230
|
+
}
|
|
231
|
+
case RelationKinds.BelongsToMany: {
|
|
232
|
+
const many = relation as BelongsToManyRelation;
|
|
233
|
+
const localKey = many.localKey || findPrimaryKey(meta.table);
|
|
234
|
+
const loader = () => relationLoaderCache(meta, relationName, () =>
|
|
235
|
+
loadBelongsToManyRelation(meta.ctx, meta.table, relationName, many)
|
|
236
|
+
);
|
|
237
|
+
return new DefaultManyToManyCollection(
|
|
238
|
+
meta.ctx,
|
|
239
|
+
meta,
|
|
240
|
+
owner,
|
|
241
|
+
relationName,
|
|
242
|
+
many,
|
|
243
|
+
meta.table,
|
|
244
|
+
loader,
|
|
245
|
+
(row: Record<string, any>) => createEntityFromRow(meta.ctx, relation.target, row),
|
|
246
|
+
localKey
|
|
247
|
+
);
|
|
248
|
+
}
|
|
249
|
+
default:
|
|
250
|
+
return undefined;
|
|
251
|
+
}
|
|
252
|
+
};
|
|
@@ -1,36 +1,36 @@
|
|
|
1
|
-
import { TableDef } from '../schema/table';
|
|
2
|
-
import { Entity } from '../schema/types';
|
|
3
|
-
import { hydrateRows } from './hydration';
|
|
4
|
-
import { OrmContext } from './orm-context';
|
|
5
|
-
import { SelectQueryBuilder } from '../builder/select';
|
|
6
|
-
import { createEntityFromRow } from './entity';
|
|
7
|
-
|
|
8
|
-
type Row = Record<string, any>;
|
|
9
|
-
|
|
10
|
-
const flattenResults = (results: { columns: string[]; values: unknown[][] }[]): Row[] => {
|
|
11
|
-
const rows: Row[] = [];
|
|
12
|
-
for (const result of results) {
|
|
13
|
-
const { columns, values } = result;
|
|
14
|
-
for (const valueRow of values) {
|
|
15
|
-
const row: Row = {};
|
|
16
|
-
columns.forEach((column, idx) => {
|
|
17
|
-
row[column] = valueRow[idx];
|
|
18
|
-
});
|
|
19
|
-
rows.push(row);
|
|
20
|
-
}
|
|
21
|
-
}
|
|
22
|
-
return rows;
|
|
23
|
-
};
|
|
24
|
-
|
|
25
|
-
export async function executeHydrated<TTable extends TableDef>(
|
|
26
|
-
ctx: OrmContext,
|
|
27
|
-
qb: SelectQueryBuilder<any, TTable>
|
|
28
|
-
): Promise<Entity<TTable>[]> {
|
|
29
|
-
const compiled = ctx.dialect.compileSelect(qb.getAST());
|
|
30
|
-
const executed = await ctx.executor.executeSql(compiled.sql, compiled.params);
|
|
31
|
-
const rows = flattenResults(executed);
|
|
32
|
-
const hydrated = hydrateRows(rows, qb.getHydrationPlan());
|
|
33
|
-
return hydrated.map(row =>
|
|
34
|
-
createEntityFromRow(ctx, qb.getTable(), row, qb.getLazyRelations())
|
|
35
|
-
);
|
|
36
|
-
}
|
|
1
|
+
import { TableDef } from '../schema/table.js';
|
|
2
|
+
import { Entity } from '../schema/types.js';
|
|
3
|
+
import { hydrateRows } from './hydration.js';
|
|
4
|
+
import { OrmContext } from './orm-context.js';
|
|
5
|
+
import { SelectQueryBuilder } from '../query-builder/select.js';
|
|
6
|
+
import { createEntityFromRow } from './entity.js';
|
|
7
|
+
|
|
8
|
+
type Row = Record<string, any>;
|
|
9
|
+
|
|
10
|
+
const flattenResults = (results: { columns: string[]; values: unknown[][] }[]): Row[] => {
|
|
11
|
+
const rows: Row[] = [];
|
|
12
|
+
for (const result of results) {
|
|
13
|
+
const { columns, values } = result;
|
|
14
|
+
for (const valueRow of values) {
|
|
15
|
+
const row: Row = {};
|
|
16
|
+
columns.forEach((column, idx) => {
|
|
17
|
+
row[column] = valueRow[idx];
|
|
18
|
+
});
|
|
19
|
+
rows.push(row);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
return rows;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
export async function executeHydrated<TTable extends TableDef>(
|
|
26
|
+
ctx: OrmContext,
|
|
27
|
+
qb: SelectQueryBuilder<any, TTable>
|
|
28
|
+
): Promise<Entity<TTable>[]> {
|
|
29
|
+
const compiled = ctx.dialect.compileSelect(qb.getAST());
|
|
30
|
+
const executed = await ctx.executor.executeSql(compiled.sql, compiled.params);
|
|
31
|
+
const rows = flattenResults(executed);
|
|
32
|
+
const hydrated = hydrateRows(rows, qb.getHydrationPlan());
|
|
33
|
+
return hydrated.map(row =>
|
|
34
|
+
createEntityFromRow(ctx, qb.getTable(), row, qb.getLazyRelations())
|
|
35
|
+
);
|
|
36
|
+
}
|