metal-orm 1.0.5 → 1.0.6

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 (36) hide show
  1. package/README.md +299 -113
  2. package/docs/CHANGES.md +104 -0
  3. package/docs/advanced-features.md +92 -1
  4. package/docs/api-reference.md +13 -4
  5. package/docs/dml-operations.md +156 -0
  6. package/docs/getting-started.md +122 -55
  7. package/docs/hydration.md +78 -13
  8. package/docs/index.md +19 -14
  9. package/docs/multi-dialect-support.md +25 -0
  10. package/docs/query-builder.md +60 -0
  11. package/docs/runtime.md +105 -0
  12. package/docs/schema-definition.md +52 -1
  13. package/package.json +1 -1
  14. package/src/ast/expression.ts +38 -18
  15. package/src/builder/hydration-planner.ts +74 -74
  16. package/src/builder/select.ts +427 -395
  17. package/src/constants/sql-operator-config.ts +3 -0
  18. package/src/constants/sql.ts +38 -32
  19. package/src/index.ts +16 -8
  20. package/src/playground/features/playground/data/scenarios/types.ts +18 -15
  21. package/src/playground/features/playground/data/schema.ts +10 -10
  22. package/src/playground/features/playground/services/QueryExecutionService.ts +2 -1
  23. package/src/runtime/entity-meta.ts +52 -0
  24. package/src/runtime/entity.ts +252 -0
  25. package/src/runtime/execute.ts +36 -0
  26. package/src/runtime/hydration.ts +99 -49
  27. package/src/runtime/lazy-batch.ts +205 -0
  28. package/src/runtime/orm-context.ts +539 -0
  29. package/src/runtime/relations/belongs-to.ts +92 -0
  30. package/src/runtime/relations/has-many.ts +111 -0
  31. package/src/runtime/relations/many-to-many.ts +149 -0
  32. package/src/schema/column.ts +15 -1
  33. package/src/schema/relation.ts +82 -58
  34. package/src/schema/table.ts +34 -22
  35. package/src/schema/types.ts +76 -0
  36. package/tests/orm-runtime.test.ts +254 -0
@@ -19,8 +19,11 @@ export interface SqlOperatorConfig {
19
19
  */
20
20
  export const SQL_OPERATOR_REGISTRY: Record<SqlOperator, SqlOperatorConfig> = {
21
21
  [SQL_OPERATORS.EQUALS]: { sql: SQL_OPERATORS.EQUALS, tsName: 'eq' },
22
+ [SQL_OPERATORS.NOT_EQUALS]: { sql: SQL_OPERATORS.NOT_EQUALS, tsName: 'neq' },
22
23
  [SQL_OPERATORS.GREATER_THAN]: { sql: SQL_OPERATORS.GREATER_THAN, tsName: 'gt' },
24
+ [SQL_OPERATORS.GREATER_OR_EQUAL]: { sql: SQL_OPERATORS.GREATER_OR_EQUAL, tsName: 'gte' },
23
25
  [SQL_OPERATORS.LESS_THAN]: { sql: SQL_OPERATORS.LESS_THAN, tsName: 'lt' },
26
+ [SQL_OPERATORS.LESS_OR_EQUAL]: { sql: SQL_OPERATORS.LESS_OR_EQUAL, tsName: 'lte' },
24
27
  [SQL_OPERATORS.LIKE]: { sql: SQL_OPERATORS.LIKE, tsName: 'like' },
25
28
  [SQL_OPERATORS.NOT_LIKE]: { sql: SQL_OPERATORS.NOT_LIKE, tsName: 'notLike' },
26
29
  [SQL_OPERATORS.IN]: { sql: SQL_OPERATORS.IN, tsName: 'inList' },
@@ -33,38 +33,44 @@ export const SQL_KEYWORDS = {
33
33
  /**
34
34
  * SQL operators used in query conditions
35
35
  */
36
- export const SQL_OPERATORS = {
37
- /** Equality operator */
38
- EQUALS: '=',
39
- /** Greater than operator */
40
- GREATER_THAN: '>',
41
- /** Less than operator */
42
- LESS_THAN: '<',
43
- /** LIKE pattern matching operator */
44
- LIKE: 'LIKE',
45
- /** NOT LIKE pattern matching operator */
46
- NOT_LIKE: 'NOT LIKE',
47
- /** IN membership operator */
48
- IN: 'IN',
49
- /** NOT IN membership operator */
50
- NOT_IN: 'NOT IN',
51
- /** BETWEEN range operator */
52
- BETWEEN: 'BETWEEN',
53
- /** NOT BETWEEN range operator */
54
- NOT_BETWEEN: 'NOT BETWEEN',
55
- /** IS NULL null check operator */
56
- IS_NULL: 'IS NULL',
57
- /** IS NOT NULL null check operator */
58
- IS_NOT_NULL: 'IS NOT NULL',
59
- /** Logical AND operator */
60
- AND: 'AND',
61
- /** Logical OR operator */
62
- OR: 'OR',
63
- /** EXISTS operator */
64
- EXISTS: 'EXISTS',
65
- /** NOT EXISTS operator */
66
- NOT_EXISTS: 'NOT EXISTS'
67
- } as const;
36
+ export const SQL_OPERATORS = {
37
+ /** Equality operator */
38
+ EQUALS: '=',
39
+ /** Not equals operator */
40
+ NOT_EQUALS: '!=',
41
+ /** Greater than operator */
42
+ GREATER_THAN: '>',
43
+ /** Greater than or equal operator */
44
+ GREATER_OR_EQUAL: '>=',
45
+ /** Less than operator */
46
+ LESS_THAN: '<',
47
+ /** Less than or equal operator */
48
+ LESS_OR_EQUAL: '<=',
49
+ /** LIKE pattern matching operator */
50
+ LIKE: 'LIKE',
51
+ /** NOT LIKE pattern matching operator */
52
+ NOT_LIKE: 'NOT LIKE',
53
+ /** IN membership operator */
54
+ IN: 'IN',
55
+ /** NOT IN membership operator */
56
+ NOT_IN: 'NOT IN',
57
+ /** BETWEEN range operator */
58
+ BETWEEN: 'BETWEEN',
59
+ /** NOT BETWEEN range operator */
60
+ NOT_BETWEEN: 'NOT BETWEEN',
61
+ /** IS NULL null check operator */
62
+ IS_NULL: 'IS NULL',
63
+ /** IS NOT NULL null check operator */
64
+ IS_NOT_NULL: 'IS NOT NULL',
65
+ /** Logical AND operator */
66
+ AND: 'AND',
67
+ /** Logical OR operator */
68
+ OR: 'OR',
69
+ /** EXISTS operator */
70
+ EXISTS: 'EXISTS',
71
+ /** NOT EXISTS operator */
72
+ NOT_EXISTS: 'NOT EXISTS'
73
+ } as const;
68
74
 
69
75
  /**
70
76
  * Type representing any supported SQL operator
package/src/index.ts CHANGED
@@ -1,15 +1,23 @@
1
1
 
2
- export * from './schema/table';
3
- export * from './schema/column';
2
+ export * from './schema/table';
3
+ export * from './schema/column';
4
4
  export * from './schema/relation';
5
+ export * from './schema/types';
5
6
  export * from './builder/select';
6
7
  export * from './builder/insert';
7
8
  export * from './builder/update';
8
9
  export * from './builder/delete';
9
10
  export * from './ast/expression';
10
- export * from './dialect/mysql';
11
- export * from './dialect/mssql';
12
- export * from './dialect/sqlite';
13
- export * from './runtime/als';
14
- export * from './runtime/hydration';
15
- export * from './codegen/typescript';
11
+ export * from './dialect/mysql';
12
+ export * from './dialect/mssql';
13
+ export * from './dialect/sqlite';
14
+ export * from './runtime/als';
15
+ export * from './runtime/hydration';
16
+ export * from './codegen/typescript';
17
+ export * from './runtime/orm-context';
18
+ export * from './runtime/entity';
19
+ export * from './runtime/lazy-batch';
20
+ export * from './runtime/relations/has-many';
21
+ export * from './runtime/relations/belongs-to';
22
+ export * from './runtime/relations/many-to-many';
23
+ export * from './runtime/execute';
@@ -1,9 +1,12 @@
1
- import { SelectQueryBuilder } from '../../../../../builder/select';
1
+ import { SelectQueryBuilder } from '../../../../../builder/select';
2
+ import { TableDef } from '../../../../../schema/table';
2
3
 
3
4
  /**
4
5
  * Extracts the TypeScript code from a build function
5
6
  */
6
- function extractTypeScriptCode(buildFn: (builder: SelectQueryBuilder<any>) => SelectQueryBuilder<any>): string {
7
+ function extractTypeScriptCode<TTable extends TableDef>(
8
+ buildFn: (builder: SelectQueryBuilder<any, TTable>) => SelectQueryBuilder<any, TTable>
9
+ ): string {
7
10
  const fnString = buildFn.toString();
8
11
 
9
12
  // Remove the function wrapper and return statement
@@ -34,12 +37,12 @@ function extractTypeScriptCode(buildFn: (builder: SelectQueryBuilder<any>) => Se
34
37
  return fnString;
35
38
  }
36
39
 
37
- export interface Scenario {
38
- id: string;
39
- title: string;
40
- description: string;
41
- category: string;
42
- build: (builder: SelectQueryBuilder<any>) => SelectQueryBuilder<any>;
40
+ export interface Scenario {
41
+ id: string;
42
+ title: string;
43
+ description: string;
44
+ category: string;
45
+ build: <TTable extends TableDef>(builder: SelectQueryBuilder<any, TTable>) => SelectQueryBuilder<any, TTable>;
43
46
 
44
47
  code?: string;
45
48
  typescriptCode?: string;
@@ -48,13 +51,13 @@ export interface Scenario {
48
51
  /**
49
52
  * Creates a scenario with auto-extracted TypeScript code
50
53
  */
51
- export function createScenario(config: {
52
- id: string;
53
- title: string;
54
- description: string;
55
- category: string;
56
- build: (builder: SelectQueryBuilder<any>) => SelectQueryBuilder<any>;
57
- }): Scenario {
54
+ export function createScenario(config: {
55
+ id: string;
56
+ title: string;
57
+ description: string;
58
+ category: string;
59
+ build: <TTable extends TableDef>(builder: SelectQueryBuilder<any, TTable>) => SelectQueryBuilder<any, TTable>;
60
+ }): Scenario {
58
61
  return {
59
62
  ...config,
60
63
  get code() {
@@ -1,6 +1,6 @@
1
1
  import { defineTable } from '../../../../schema/table';
2
2
  import { col } from '../../../../schema/column';
3
- import { hasMany, belongsTo, belongsToMany } from '../../../../schema/relation';
3
+ import { hasMany, belongsTo, belongsToMany } from '../../../../schema/relation';
4
4
 
5
5
  export const Users = defineTable('users', {
6
6
  id: col.primaryKey(col.int()),
@@ -52,15 +52,15 @@ export const ProjectAssignments = defineTable('project_assignments', {
52
52
  assigned_at: col.varchar(50)
53
53
  });
54
54
 
55
- Users.relations = {
56
- orders: hasMany(Orders, 'user_id'),
57
- profiles: hasMany(Profiles, 'user_id'),
58
- userRoles: hasMany(UserRoles, 'user_id'),
59
- projects: belongsToMany(Projects, ProjectAssignments, {
60
- pivotForeignKeyToRoot: 'user_id',
61
- pivotForeignKeyToTarget: 'project_id'
62
- })
63
- };
55
+ Users.relations = {
56
+ orders: hasMany(Orders, 'user_id'),
57
+ profiles: hasMany(Profiles, 'user_id'),
58
+ userRoles: hasMany(UserRoles, 'user_id'),
59
+ projects: belongsToMany(Projects, ProjectAssignments, {
60
+ pivotForeignKeyToRoot: 'user_id',
61
+ pivotForeignKeyToTarget: 'project_id'
62
+ })
63
+ };
64
64
 
65
65
  Orders.relations = {
66
66
  user: belongsTo(Users, 'user_id')
@@ -6,11 +6,12 @@ import { hydrateRows } from '../../../../runtime/hydration';
6
6
  import type { IDatabaseClient } from '../common/IDatabaseClient';
7
7
  import type { QueryExecutionResult } from '../api/types';
8
8
  import type { Scenario } from '../data/scenarios';
9
+ import type { TableDef } from '../../../../schema/table';
9
10
 
10
11
  /**
11
12
  * Extracts the TypeScript code from a build function
12
13
  */
13
- function extractTypeScriptCode(buildFn: (builder: SelectQueryBuilder<any>) => SelectQueryBuilder<any>): string {
14
+ function extractTypeScriptCode<TTable extends TableDef>(buildFn: (builder: SelectQueryBuilder<any, TTable>) => SelectQueryBuilder<any, TTable>): string {
14
15
  const fnString = buildFn.toString();
15
16
 
16
17
  // Remove the function wrapper and return statement
@@ -0,0 +1,52 @@
1
+ import { TableDef } from '../schema/table';
2
+ import { OrmContext } from './orm-context';
3
+ import { RelationMap } from '../schema/types';
4
+
5
+ export const ENTITY_META = Symbol('EntityMeta');
6
+
7
+ const toKey = (value: unknown): string => (value === null || value === undefined ? '' : String(value));
8
+
9
+ export interface EntityMeta<TTable extends TableDef> {
10
+ ctx: OrmContext;
11
+ table: TTable;
12
+ lazyRelations: (keyof RelationMap<TTable>)[];
13
+ relationCache: Map<string, Promise<any>>;
14
+ relationHydration: Map<string, Map<string, any>>;
15
+ relationWrappers: Map<string, unknown>;
16
+ }
17
+
18
+ export const getHydrationRows = <TTable extends TableDef>(
19
+ meta: EntityMeta<TTable>,
20
+ relationName: string,
21
+ key: unknown
22
+ ): Record<string, any>[] | undefined => {
23
+ const map = meta.relationHydration.get(relationName);
24
+ if (!map) return undefined;
25
+ const rows = map.get(toKey(key));
26
+ if (!rows) return undefined;
27
+ return Array.isArray(rows) ? rows : undefined;
28
+ };
29
+
30
+ export const getHydrationRecord = <TTable extends TableDef>(
31
+ meta: EntityMeta<TTable>,
32
+ relationName: string,
33
+ key: unknown
34
+ ): Record<string, any> | undefined => {
35
+ const map = meta.relationHydration.get(relationName);
36
+ if (!map) return undefined;
37
+ const value = map.get(toKey(key));
38
+ if (!value) return undefined;
39
+ if (Array.isArray(value)) {
40
+ return value[0];
41
+ }
42
+ return value;
43
+ };
44
+
45
+ export const getEntityMeta = <TTable extends TableDef>(entity: any): EntityMeta<TTable> | undefined => {
46
+ if (!entity || typeof entity !== 'object') return undefined;
47
+ return (entity as any)[ENTITY_META];
48
+ };
49
+
50
+ export const hasEntityMeta = (entity: any): entity is { [ENTITY_META]: EntityMeta<TableDef> } => {
51
+ return Boolean(getEntityMeta(entity));
52
+ };
@@ -0,0 +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
+ };
@@ -0,0 +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
+ }