metal-orm 1.0.44 → 1.0.45
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 +700 -700
- package/package.json +1 -1
- package/src/orm/lazy-batch.ts +90 -0
- package/src/orm/save-graph.ts +45 -0
package/package.json
CHANGED
package/src/orm/lazy-batch.ts
CHANGED
|
@@ -7,16 +7,32 @@ import type { QueryResult } from '../core/execution/db-executor.js';
|
|
|
7
7
|
import { ColumnDef } from '../schema/column.js';
|
|
8
8
|
import { findPrimaryKey } from '../query-builder/hydration-planner.js';
|
|
9
9
|
|
|
10
|
+
/**
|
|
11
|
+
* An array of database rows, each represented as a record of string keys to unknown values.
|
|
12
|
+
*/
|
|
10
13
|
type Rows = Record<string, unknown>[];
|
|
11
14
|
|
|
15
|
+
/**
|
|
16
|
+
* Represents a single tracked entity from the EntityContext for a table.
|
|
17
|
+
*/
|
|
12
18
|
type EntityTracker = ReturnType<EntityContext['getEntitiesForTable']>[number];
|
|
13
19
|
|
|
20
|
+
/**
|
|
21
|
+
* Creates a record of all columns from the given table definition.
|
|
22
|
+
* @param table - The table definition to select columns from.
|
|
23
|
+
* @returns A record mapping column names to their definitions.
|
|
24
|
+
*/
|
|
14
25
|
const selectAllColumns = (table: TableDef): Record<string, ColumnDef> =>
|
|
15
26
|
Object.entries(table.columns).reduce((acc, [name, def]) => {
|
|
16
27
|
acc[name] = def;
|
|
17
28
|
return acc;
|
|
18
29
|
}, {} as Record<string, ColumnDef>);
|
|
19
30
|
|
|
31
|
+
/**
|
|
32
|
+
* Extracts rows from query results into a standardized format.
|
|
33
|
+
* @param results - The query results to process.
|
|
34
|
+
* @returns An array of rows as records.
|
|
35
|
+
*/
|
|
20
36
|
const rowsFromResults = (results: QueryResult[]): Rows => {
|
|
21
37
|
const rows: Rows = [];
|
|
22
38
|
for (const result of results) {
|
|
@@ -32,14 +48,31 @@ const rowsFromResults = (results: QueryResult[]): Rows => {
|
|
|
32
48
|
return rows;
|
|
33
49
|
};
|
|
34
50
|
|
|
51
|
+
/**
|
|
52
|
+
* Executes a select query and returns the resulting rows.
|
|
53
|
+
* @param ctx - The entity context for execution.
|
|
54
|
+
* @param qb - The select query builder.
|
|
55
|
+
* @returns A promise resolving to the rows from the query.
|
|
56
|
+
*/
|
|
35
57
|
const executeQuery = async (ctx: EntityContext, qb: SelectQueryBuilder<unknown, TableDef>): Promise<Rows> => {
|
|
36
58
|
const compiled = ctx.dialect.compileSelect(qb.getAST());
|
|
37
59
|
const results = await ctx.executor.executeSql(compiled.sql, compiled.params);
|
|
38
60
|
return rowsFromResults(results);
|
|
39
61
|
};
|
|
40
62
|
|
|
63
|
+
/**
|
|
64
|
+
* Converts a value to a string key, handling null and undefined as empty string.
|
|
65
|
+
* @param value - The value to convert.
|
|
66
|
+
* @returns The string representation of the value.
|
|
67
|
+
*/
|
|
41
68
|
const toKey = (value: unknown): string => (value === null || value === undefined ? '' : String(value));
|
|
42
69
|
|
|
70
|
+
/**
|
|
71
|
+
* Collects unique keys from the root entities based on the specified key property.
|
|
72
|
+
* @param roots - The tracked entities to collect keys from.
|
|
73
|
+
* @param key - The property name to use as the key.
|
|
74
|
+
* @returns A set of unique key values.
|
|
75
|
+
*/
|
|
43
76
|
const collectKeysFromRoots = (roots: EntityTracker[], key: string): Set<unknown> => {
|
|
44
77
|
const collected = new Set<unknown>();
|
|
45
78
|
for (const tracked of roots) {
|
|
@@ -51,9 +84,22 @@ const collectKeysFromRoots = (roots: EntityTracker[], key: string): Set<unknown>
|
|
|
51
84
|
return collected;
|
|
52
85
|
};
|
|
53
86
|
|
|
87
|
+
/**
|
|
88
|
+
* Builds an array of values suitable for an IN list expression from a set of keys.
|
|
89
|
+
* @param keys - The set of keys to convert.
|
|
90
|
+
* @returns An array of string, number, or LiteralNode values.
|
|
91
|
+
*/
|
|
54
92
|
const buildInListValues = (keys: Set<unknown>): (string | number | LiteralNode)[] =>
|
|
55
93
|
Array.from(keys) as (string | number | LiteralNode)[];
|
|
56
94
|
|
|
95
|
+
/**
|
|
96
|
+
* Fetches rows from a table where the specified column matches any of the given keys.
|
|
97
|
+
* @param ctx - The entity context.
|
|
98
|
+
* @param table - The target table.
|
|
99
|
+
* @param column - The column to match against.
|
|
100
|
+
* @param keys - The set of keys to match.
|
|
101
|
+
* @returns A promise resolving to the matching rows.
|
|
102
|
+
*/
|
|
57
103
|
const fetchRowsForKeys = async (
|
|
58
104
|
ctx: EntityContext,
|
|
59
105
|
table: TableDef,
|
|
@@ -65,6 +111,12 @@ const fetchRowsForKeys = async (
|
|
|
65
111
|
return executeQuery(ctx, qb);
|
|
66
112
|
};
|
|
67
113
|
|
|
114
|
+
/**
|
|
115
|
+
* Groups rows by the value of a key column, allowing multiple rows per key.
|
|
116
|
+
* @param rows - The rows to group.
|
|
117
|
+
* @param keyColumn - The column name to group by.
|
|
118
|
+
* @returns A map from key strings to arrays of rows.
|
|
119
|
+
*/
|
|
68
120
|
const groupRowsByMany = (rows: Rows, keyColumn: string): Map<string, Rows> => {
|
|
69
121
|
const grouped = new Map<string, Rows>();
|
|
70
122
|
for (const row of rows) {
|
|
@@ -78,6 +130,12 @@ const groupRowsByMany = (rows: Rows, keyColumn: string): Map<string, Rows> => {
|
|
|
78
130
|
return grouped;
|
|
79
131
|
};
|
|
80
132
|
|
|
133
|
+
/**
|
|
134
|
+
* Groups rows by the value of a key column, keeping only one row per key.
|
|
135
|
+
* @param rows - The rows to group.
|
|
136
|
+
* @param keyColumn - The column name to group by.
|
|
137
|
+
* @returns A map from key strings to single rows.
|
|
138
|
+
*/
|
|
81
139
|
const groupRowsByUnique = (rows: Rows, keyColumn: string): Map<string, Record<string, unknown>> => {
|
|
82
140
|
const lookup = new Map<string, Record<string, unknown>>();
|
|
83
141
|
for (const row of rows) {
|
|
@@ -91,6 +149,14 @@ const groupRowsByUnique = (rows: Rows, keyColumn: string): Map<string, Record<st
|
|
|
91
149
|
return lookup;
|
|
92
150
|
};
|
|
93
151
|
|
|
152
|
+
/**
|
|
153
|
+
* Loads related entities for a has-many relation in batch.
|
|
154
|
+
* @param ctx - The entity context.
|
|
155
|
+
* @param rootTable - The root table of the relation.
|
|
156
|
+
* @param _relationName - The name of the relation (unused).
|
|
157
|
+
* @param relation - The has-many relation definition.
|
|
158
|
+
* @returns A promise resolving to a map of root keys to arrays of related rows.
|
|
159
|
+
*/
|
|
94
160
|
export const loadHasManyRelation = async (
|
|
95
161
|
ctx: EntityContext,
|
|
96
162
|
rootTable: TableDef,
|
|
@@ -112,6 +178,14 @@ export const loadHasManyRelation = async (
|
|
|
112
178
|
return groupRowsByMany(rows, relation.foreignKey);
|
|
113
179
|
};
|
|
114
180
|
|
|
181
|
+
/**
|
|
182
|
+
* Loads related entities for a has-one relation in batch.
|
|
183
|
+
* @param ctx - The entity context.
|
|
184
|
+
* @param rootTable - The root table of the relation.
|
|
185
|
+
* @param _relationName - The name of the relation (unused).
|
|
186
|
+
* @param relation - The has-one relation definition.
|
|
187
|
+
* @returns A promise resolving to a map of root keys to single related rows.
|
|
188
|
+
*/
|
|
115
189
|
export const loadHasOneRelation = async (
|
|
116
190
|
ctx: EntityContext,
|
|
117
191
|
rootTable: TableDef,
|
|
@@ -133,6 +207,14 @@ export const loadHasOneRelation = async (
|
|
|
133
207
|
return groupRowsByUnique(rows, relation.foreignKey);
|
|
134
208
|
};
|
|
135
209
|
|
|
210
|
+
/**
|
|
211
|
+
* Loads related entities for a belongs-to relation in batch.
|
|
212
|
+
* @param ctx - The entity context.
|
|
213
|
+
* @param rootTable - The root table of the relation.
|
|
214
|
+
* @param _relationName - The name of the relation (unused).
|
|
215
|
+
* @param relation - The belongs-to relation definition.
|
|
216
|
+
* @returns A promise resolving to a map of foreign keys to single related rows.
|
|
217
|
+
*/
|
|
136
218
|
export const loadBelongsToRelation = async (
|
|
137
219
|
ctx: EntityContext,
|
|
138
220
|
rootTable: TableDef,
|
|
@@ -154,6 +236,14 @@ export const loadBelongsToRelation = async (
|
|
|
154
236
|
return groupRowsByUnique(rows, targetKey);
|
|
155
237
|
};
|
|
156
238
|
|
|
239
|
+
/**
|
|
240
|
+
* Loads related entities for a belongs-to-many relation in batch, including pivot data.
|
|
241
|
+
* @param ctx - The entity context.
|
|
242
|
+
* @param rootTable - The root table of the relation.
|
|
243
|
+
* @param _relationName - The name of the relation (unused).
|
|
244
|
+
* @param relation - The belongs-to-many relation definition.
|
|
245
|
+
* @returns A promise resolving to a map of root keys to arrays of related rows with pivot data.
|
|
246
|
+
*/
|
|
157
247
|
export const loadBelongsToManyRelation = async (
|
|
158
248
|
ctx: EntityContext,
|
|
159
249
|
rootTable: TableDef,
|
package/src/orm/save-graph.ts
CHANGED
|
@@ -20,13 +20,30 @@ import type { EntityConstructor } from './entity-metadata.js';
|
|
|
20
20
|
import { getTableDefFromEntity } from '../decorators/bootstrap.js';
|
|
21
21
|
import type { OrmSession } from './orm-session.js';
|
|
22
22
|
|
|
23
|
+
/**
|
|
24
|
+
* Options for controlling the behavior of save graph operations.
|
|
25
|
+
*/
|
|
23
26
|
export interface SaveGraphOptions {
|
|
24
27
|
/** Remove existing collection members that are not present in the payload */
|
|
25
28
|
pruneMissing?: boolean;
|
|
26
29
|
}
|
|
27
30
|
|
|
31
|
+
/** Represents an entity object with arbitrary properties. */
|
|
32
|
+
|
|
33
|
+
/** Represents an entity object with arbitrary properties. */
|
|
34
|
+
|
|
28
35
|
type AnyEntity = Record<string, unknown>;
|
|
29
36
|
|
|
37
|
+
/**
|
|
38
|
+
|
|
39
|
+
* Converts a value to a string key, returning an empty string for null or undefined.
|
|
40
|
+
|
|
41
|
+
* @param value - The value to convert.
|
|
42
|
+
|
|
43
|
+
* @returns The string representation or empty string.
|
|
44
|
+
|
|
45
|
+
*/
|
|
46
|
+
|
|
30
47
|
const toKey = (value: unknown): string => (value === null || value === undefined ? '' : String(value));
|
|
31
48
|
|
|
32
49
|
const pickColumns = (table: TableDef, payload: AnyEntity): Record<string, unknown> => {
|
|
@@ -292,18 +309,46 @@ export const saveGraph = async <TTable extends TableDef>(
|
|
|
292
309
|
return root;
|
|
293
310
|
};
|
|
294
311
|
|
|
312
|
+
/**
|
|
313
|
+
|
|
314
|
+
* Internal version of saveGraph with typed return based on the constructor.
|
|
315
|
+
|
|
316
|
+
* @param session - The ORM session.
|
|
317
|
+
|
|
318
|
+
* @param entityClass - The entity constructor.
|
|
319
|
+
|
|
320
|
+
* @param payload - The payload data for the root entity and its relations.
|
|
321
|
+
|
|
322
|
+
* @param options - Options for the save operation.
|
|
323
|
+
|
|
324
|
+
* @returns The root entity instance.
|
|
325
|
+
|
|
326
|
+
*/
|
|
327
|
+
|
|
295
328
|
export const saveGraphInternal = async <TCtor extends EntityConstructor>(
|
|
329
|
+
|
|
296
330
|
session: OrmSession,
|
|
331
|
+
|
|
297
332
|
entityClass: TCtor,
|
|
333
|
+
|
|
298
334
|
payload: AnyEntity,
|
|
335
|
+
|
|
299
336
|
options: SaveGraphOptions = {}
|
|
337
|
+
|
|
300
338
|
): Promise<InstanceType<TCtor>> => {
|
|
339
|
+
|
|
301
340
|
const table = getTableDefFromEntity(entityClass);
|
|
341
|
+
|
|
302
342
|
if (!table) {
|
|
343
|
+
|
|
303
344
|
throw new Error('Entity metadata has not been bootstrapped');
|
|
345
|
+
|
|
304
346
|
}
|
|
305
347
|
|
|
306
348
|
const root = ensureEntity(session, table, payload);
|
|
349
|
+
|
|
307
350
|
await applyGraphToEntity(session, table, root as AnyEntity, payload, options);
|
|
351
|
+
|
|
308
352
|
return root as unknown as InstanceType<TCtor>;
|
|
353
|
+
|
|
309
354
|
};
|