metal-orm 1.0.7 → 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.
Files changed (153) hide show
  1. package/README.md +133 -121
  2. package/dist/decorators/index.cjs +2564 -0
  3. package/dist/decorators/index.cjs.map +1 -0
  4. package/dist/decorators/index.d.cts +53 -0
  5. package/dist/decorators/index.d.ts +53 -0
  6. package/dist/decorators/index.js +2530 -0
  7. package/dist/decorators/index.js.map +1 -0
  8. package/dist/index.cjs +4227 -0
  9. package/dist/index.cjs.map +1 -0
  10. package/dist/index.d.cts +701 -0
  11. package/dist/index.d.ts +701 -0
  12. package/dist/index.js +4131 -0
  13. package/dist/index.js.map +1 -0
  14. package/dist/select-654m4qy8.d.cts +1522 -0
  15. package/dist/select-654m4qy8.d.ts +1522 -0
  16. package/package.json +27 -20
  17. package/src/codegen/typescript.ts +405 -393
  18. package/src/core/ast/aggregate-functions.ts +30 -0
  19. package/src/core/ast/builders.ts +43 -0
  20. package/src/core/ast/expression-builders.ts +310 -0
  21. package/src/core/ast/expression-nodes.ts +211 -0
  22. package/src/core/ast/expression-visitor.ts +99 -0
  23. package/src/core/ast/expression.ts +5 -0
  24. package/src/{utils → core/ast}/join-node.ts +20 -20
  25. package/src/{ast → core/ast}/join.ts +18 -18
  26. package/src/{ast → core/ast}/query.ts +113 -113
  27. package/src/core/ast/window-functions.ts +140 -0
  28. package/src/{dialect → core/dialect}/abstract.ts +94 -94
  29. package/src/{dialect → core/dialect}/mssql/index.ts +31 -31
  30. package/src/{dialect → core/dialect}/mysql/index.ts +31 -31
  31. package/src/{dialect → core/dialect}/postgres/index.ts +45 -45
  32. package/src/{dialect → core/dialect}/sqlite/index.ts +45 -45
  33. package/src/{constants → core/sql}/sql-operator-config.ts +39 -39
  34. package/src/decorators/bootstrap.ts +126 -0
  35. package/src/decorators/column.ts +78 -0
  36. package/src/decorators/entity.ts +36 -0
  37. package/src/decorators/index.ts +4 -0
  38. package/src/decorators/relations.ts +107 -0
  39. package/src/global.d.ts +1 -0
  40. package/src/index.ts +22 -22
  41. package/src/orm/db-executor.ts +11 -0
  42. package/src/orm/domain-event-bus.ts +52 -0
  43. package/src/{runtime → orm}/entity-meta.ts +52 -52
  44. package/src/orm/entity-metadata.ts +140 -0
  45. package/src/{runtime → orm}/entity.ts +252 -252
  46. package/src/{runtime → orm}/execute.ts +36 -36
  47. package/src/{runtime → orm}/hydration.ts +103 -103
  48. package/src/orm/identity-map.ts +37 -0
  49. package/src/{runtime → orm}/lazy-batch.ts +205 -205
  50. package/src/orm/orm-context.ts +154 -0
  51. package/src/orm/relation-change-processor.ts +140 -0
  52. package/src/{runtime → orm}/relations/belongs-to.ts +92 -92
  53. package/src/{runtime → orm}/relations/has-many.ts +111 -111
  54. package/src/{runtime → orm}/relations/many-to-many.ts +149 -149
  55. package/src/orm/runtime-types.ts +39 -0
  56. package/src/orm/transaction-runner.ts +17 -0
  57. package/src/orm/unit-of-work.ts +232 -0
  58. package/src/{builder/operations → query-builder}/column-selector.ts +78 -78
  59. package/src/{builder → query-builder}/delete-query-state.ts +38 -42
  60. package/src/{builder → query-builder}/delete.ts +46 -57
  61. package/src/{builder → query-builder}/hydration-manager.ts +87 -87
  62. package/src/{builder → query-builder}/hydration-planner.ts +182 -182
  63. package/src/{builder → query-builder}/insert-query-state.ts +51 -62
  64. package/src/{builder → query-builder}/insert.ts +48 -59
  65. package/src/{builder → query-builder}/query-ast-service.ts +208 -226
  66. package/src/{utils → query-builder}/raw-column-parser.ts +32 -32
  67. package/src/{builder → query-builder}/relation-conditions.ts +112 -112
  68. package/src/{builder/operations → query-builder}/relation-manager.ts +82 -82
  69. package/src/{builder → query-builder}/relation-projection-helper.ts +101 -101
  70. package/src/{builder → query-builder}/relation-service.ts +284 -284
  71. package/src/{builder → query-builder}/relation-types.ts +21 -21
  72. package/src/{builder → query-builder}/relation-utils.ts +12 -12
  73. package/src/{builder → query-builder}/select-query-builder-deps.ts +112 -94
  74. package/src/{builder → query-builder}/select-query-state.ts +179 -179
  75. package/src/{builder → query-builder}/select.ts +78 -69
  76. package/src/{builder → query-builder}/update-query-state.ts +55 -59
  77. package/src/{builder → query-builder}/update.ts +50 -61
  78. package/src/schema/column.ts +25 -25
  79. package/src/schema/relation.ts +116 -116
  80. package/src/schema/table.ts +34 -34
  81. package/src/schema/types.ts +76 -76
  82. package/.github/workflows/publish-metal-orm.yml +0 -38
  83. package/ROADMAP.md +0 -125
  84. package/docs/CHANGES.md +0 -104
  85. package/docs/advanced-features.md +0 -176
  86. package/docs/api-reference.md +0 -31
  87. package/docs/dml-operations.md +0 -156
  88. package/docs/getting-started.md +0 -171
  89. package/docs/hydration.md +0 -115
  90. package/docs/index.md +0 -36
  91. package/docs/multi-dialect-support.md +0 -59
  92. package/docs/query-builder.md +0 -135
  93. package/docs/runtime.md +0 -105
  94. package/docs/schema-definition.md +0 -112
  95. package/metadata.json +0 -5
  96. package/playground/api/playground-api.ts +0 -94
  97. package/playground/index.html +0 -15
  98. package/playground/src/App.css +0 -1
  99. package/playground/src/App.tsx +0 -114
  100. package/playground/src/components/CodeDisplay.tsx +0 -43
  101. package/playground/src/components/QueryExecutor.tsx +0 -189
  102. package/playground/src/components/ResultsTable.tsx +0 -67
  103. package/playground/src/components/ResultsTabs.tsx +0 -105
  104. package/playground/src/components/ScenarioList.tsx +0 -56
  105. package/playground/src/components/logo.svg +0 -45
  106. package/playground/src/data/scenarios.ts +0 -2
  107. package/playground/src/main.tsx +0 -9
  108. package/playground/src/services/PlaygroundApiService.ts +0 -60
  109. package/postcss.config.cjs +0 -5
  110. package/sql_sql-ansi-cheatsheet-2025.md +0 -264
  111. package/src/ast/expression.ts +0 -658
  112. package/src/builder/operations/cte-manager.ts +0 -34
  113. package/src/builder/operations/filter-manager.ts +0 -68
  114. package/src/builder/operations/join-manager.ts +0 -36
  115. package/src/builder/operations/pagination-manager.ts +0 -36
  116. package/src/playground/features/playground/api/types.ts +0 -16
  117. package/src/playground/features/playground/clients/MockClient.ts +0 -17
  118. package/src/playground/features/playground/clients/SqliteClient.ts +0 -57
  119. package/src/playground/features/playground/common/IDatabaseClient.ts +0 -10
  120. package/src/playground/features/playground/data/scenarios/aggregation.ts +0 -36
  121. package/src/playground/features/playground/data/scenarios/basics.ts +0 -25
  122. package/src/playground/features/playground/data/scenarios/edge_cases.ts +0 -57
  123. package/src/playground/features/playground/data/scenarios/filtering.ts +0 -94
  124. package/src/playground/features/playground/data/scenarios/hydration.ts +0 -27
  125. package/src/playground/features/playground/data/scenarios/index.ts +0 -29
  126. package/src/playground/features/playground/data/scenarios/ordering.ts +0 -25
  127. package/src/playground/features/playground/data/scenarios/pagination.ts +0 -16
  128. package/src/playground/features/playground/data/scenarios/relationships.ts +0 -75
  129. package/src/playground/features/playground/data/scenarios/types.ts +0 -70
  130. package/src/playground/features/playground/data/schema.ts +0 -91
  131. package/src/playground/features/playground/data/seed.ts +0 -104
  132. package/src/playground/features/playground/services/QueryExecutionService.ts +0 -121
  133. package/src/runtime/orm-context.ts +0 -539
  134. package/tests/belongs-to-many.test.ts +0 -57
  135. package/tests/between.test.ts +0 -43
  136. package/tests/case-expression.test.ts +0 -58
  137. package/tests/complex-exists.test.ts +0 -230
  138. package/tests/cte.test.ts +0 -118
  139. package/tests/dml.test.ts +0 -206
  140. package/tests/exists.test.ts +0 -127
  141. package/tests/like.test.ts +0 -33
  142. package/tests/orm-runtime.test.ts +0 -254
  143. package/tests/postgres.test.ts +0 -30
  144. package/tests/right-join.test.ts +0 -89
  145. package/tests/subquery-having.test.ts +0 -193
  146. package/tests/window-function.test.ts +0 -151
  147. package/tsconfig.json +0 -30
  148. package/tsup.config.ts +0 -10
  149. package/vite.config.ts +0 -22
  150. package/vitest.config.ts +0 -14
  151. /package/src/{constants → core/sql}/sql.ts +0 -0
  152. /package/src/{runtime → orm}/als.ts +0 -0
  153. /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
+ }