metal-orm 1.0.4 → 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 (57) 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 +77 -3
  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 +630 -592
  15. package/src/ast/query.ts +110 -49
  16. package/src/builder/delete-query-state.ts +42 -0
  17. package/src/builder/delete.ts +57 -0
  18. package/src/builder/hydration-manager.ts +3 -2
  19. package/src/builder/hydration-planner.ts +163 -107
  20. package/src/builder/insert-query-state.ts +62 -0
  21. package/src/builder/insert.ts +59 -0
  22. package/src/builder/operations/relation-manager.ts +1 -23
  23. package/src/builder/relation-conditions.ts +45 -1
  24. package/src/builder/relation-service.ts +81 -18
  25. package/src/builder/relation-types.ts +15 -0
  26. package/src/builder/relation-utils.ts +12 -0
  27. package/src/builder/select.ts +427 -394
  28. package/src/builder/update-query-state.ts +59 -0
  29. package/src/builder/update.ts +61 -0
  30. package/src/constants/sql-operator-config.ts +3 -0
  31. package/src/constants/sql.ts +38 -32
  32. package/src/dialect/abstract.ts +107 -47
  33. package/src/dialect/mssql/index.ts +31 -6
  34. package/src/dialect/mysql/index.ts +31 -6
  35. package/src/dialect/postgres/index.ts +45 -6
  36. package/src/dialect/sqlite/index.ts +45 -6
  37. package/src/index.ts +22 -11
  38. package/src/playground/features/playground/data/scenarios/hydration.ts +23 -11
  39. package/src/playground/features/playground/data/scenarios/types.ts +18 -15
  40. package/src/playground/features/playground/data/schema.ts +6 -2
  41. package/src/playground/features/playground/services/QueryExecutionService.ts +2 -1
  42. package/src/runtime/entity-meta.ts +52 -0
  43. package/src/runtime/entity.ts +252 -0
  44. package/src/runtime/execute.ts +36 -0
  45. package/src/runtime/hydration.ts +100 -38
  46. package/src/runtime/lazy-batch.ts +205 -0
  47. package/src/runtime/orm-context.ts +539 -0
  48. package/src/runtime/relations/belongs-to.ts +92 -0
  49. package/src/runtime/relations/has-many.ts +111 -0
  50. package/src/runtime/relations/many-to-many.ts +149 -0
  51. package/src/schema/column.ts +15 -1
  52. package/src/schema/relation.ts +105 -40
  53. package/src/schema/table.ts +34 -22
  54. package/src/schema/types.ts +76 -0
  55. package/tests/belongs-to-many.test.ts +57 -0
  56. package/tests/dml.test.ts +206 -0
  57. package/tests/orm-runtime.test.ts +254 -0
@@ -0,0 +1,205 @@
1
+ import { TableDef } from '../schema/table';
2
+ import { BelongsToManyRelation, HasManyRelation, BelongsToRelation } from '../schema/relation';
3
+ import { SelectQueryBuilder } from '../builder/select';
4
+ import { inList, LiteralNode } from '../ast/expression';
5
+ import { OrmContext, QueryResult } from './orm-context';
6
+ import { ColumnDef } from '../schema/column';
7
+ import { findPrimaryKey } from '../builder/hydration-planner';
8
+
9
+ type Rows = Record<string, any>[];
10
+
11
+ const selectAllColumns = (table: TableDef): Record<string, ColumnDef> =>
12
+ Object.entries(table.columns).reduce((acc, [name, def]) => {
13
+ acc[name] = def;
14
+ return acc;
15
+ }, {} as Record<string, ColumnDef>);
16
+
17
+ const rowsFromResults = (results: QueryResult[]): Rows => {
18
+ const rows: Rows = [];
19
+ for (const result of results) {
20
+ const { columns, values } = result;
21
+ for (const valueRow of values) {
22
+ const row: Record<string, any> = {};
23
+ columns.forEach((column, idx) => {
24
+ row[column] = valueRow[idx];
25
+ });
26
+ rows.push(row);
27
+ }
28
+ }
29
+ return rows;
30
+ };
31
+
32
+ const executeQuery = async (ctx: OrmContext, qb: SelectQueryBuilder<any, TableDef<any>>): Promise<Rows> => {
33
+ const compiled = ctx.dialect.compileSelect(qb.getAST());
34
+ const results = await ctx.executor.executeSql(compiled.sql, compiled.params);
35
+ return rowsFromResults(results);
36
+ };
37
+
38
+ const toKey = (value: unknown): string => (value === null || value === undefined ? '' : String(value));
39
+
40
+ export const loadHasManyRelation = async (
41
+ ctx: OrmContext,
42
+ rootTable: TableDef,
43
+ _relationName: string,
44
+ relation: HasManyRelation
45
+ ): Promise<Map<string, Rows>> => {
46
+ const localKey = relation.localKey || findPrimaryKey(rootTable);
47
+ const roots = ctx.getEntitiesForTable(rootTable);
48
+ const keys = new Set<unknown>();
49
+
50
+ for (const tracked of roots) {
51
+ const value = tracked.entity[localKey];
52
+ if (value !== null && value !== undefined) {
53
+ keys.add(value);
54
+ }
55
+ }
56
+
57
+ if (!keys.size) {
58
+ return new Map();
59
+ }
60
+
61
+ const selectMap = selectAllColumns(relation.target);
62
+ const fb = new SelectQueryBuilder(relation.target).select(selectMap);
63
+ const fkColumn = relation.target.columns[relation.foreignKey];
64
+ if (!fkColumn) return new Map();
65
+
66
+ fb.where(inList(fkColumn, Array.from(keys) as (string | number | LiteralNode)[]));
67
+
68
+ const rows = await executeQuery(ctx, fb);
69
+ const grouped = new Map<string, Rows>();
70
+
71
+ for (const row of rows) {
72
+ const fkValue = row[relation.foreignKey];
73
+ if (fkValue === null || fkValue === undefined) continue;
74
+ const key = toKey(fkValue);
75
+ const bucket = grouped.get(key) ?? [];
76
+ bucket.push(row);
77
+ grouped.set(key, bucket);
78
+ }
79
+
80
+ return grouped;
81
+ };
82
+
83
+ export const loadBelongsToRelation = async (
84
+ ctx: OrmContext,
85
+ rootTable: TableDef,
86
+ _relationName: string,
87
+ relation: BelongsToRelation
88
+ ): Promise<Map<string, Record<string, any>>> => {
89
+ const roots = ctx.getEntitiesForTable(rootTable);
90
+ const foreignKeys = new Set<unknown>();
91
+
92
+ for (const tracked of roots) {
93
+ const value = tracked.entity[relation.foreignKey];
94
+ if (value !== null && value !== undefined) {
95
+ foreignKeys.add(value);
96
+ }
97
+ }
98
+
99
+ if (!foreignKeys.size) {
100
+ return new Map();
101
+ }
102
+
103
+ const selectMap = selectAllColumns(relation.target);
104
+ const qb = new SelectQueryBuilder(relation.target).select(selectMap);
105
+ const targetKey = relation.localKey || findPrimaryKey(relation.target);
106
+ const pkColumn = relation.target.columns[targetKey];
107
+ if (!pkColumn) return new Map();
108
+
109
+ qb.where(inList(pkColumn, Array.from(foreignKeys) as (string | number | LiteralNode)[]));
110
+ const rows = await executeQuery(ctx, qb);
111
+ const map = new Map<string, Record<string, any>>();
112
+
113
+ for (const row of rows) {
114
+ const keyValue = row[targetKey];
115
+ if (keyValue === null || keyValue === undefined) continue;
116
+ map.set(toKey(keyValue), row);
117
+ }
118
+
119
+ return map;
120
+ };
121
+
122
+ export const loadBelongsToManyRelation = async (
123
+ ctx: OrmContext,
124
+ rootTable: TableDef,
125
+ _relationName: string,
126
+ relation: BelongsToManyRelation
127
+ ): Promise<Map<string, Rows>> => {
128
+ const rootKey = relation.localKey || findPrimaryKey(rootTable);
129
+ const roots = ctx.getEntitiesForTable(rootTable);
130
+ const rootIds = new Set<unknown>();
131
+
132
+ for (const tracked of roots) {
133
+ const value = tracked.entity[rootKey];
134
+ if (value !== null && value !== undefined) {
135
+ rootIds.add(value);
136
+ }
137
+ }
138
+
139
+ if (!rootIds.size) {
140
+ return new Map();
141
+ }
142
+
143
+ const pivotSelect = selectAllColumns(relation.pivotTable);
144
+ const pivotQb = new SelectQueryBuilder(relation.pivotTable).select(pivotSelect);
145
+ const pivotFkCol = relation.pivotTable.columns[relation.pivotForeignKeyToRoot];
146
+ if (!pivotFkCol) return new Map();
147
+
148
+ pivotQb.where(inList(pivotFkCol, Array.from(rootIds) as (string | number | LiteralNode)[]));
149
+ const pivotRows = await executeQuery(ctx, pivotQb);
150
+
151
+ const rootLookup = new Map<string, { targetId: unknown; pivot: Record<string, any> }[]>();
152
+ const targetIds = new Set<unknown>();
153
+
154
+ for (const pivot of pivotRows) {
155
+ const rootValue = pivot[relation.pivotForeignKeyToRoot];
156
+ const targetValue = pivot[relation.pivotForeignKeyToTarget];
157
+ if (rootValue === null || rootValue === undefined || targetValue === null || targetValue === undefined) {
158
+ continue;
159
+ }
160
+ const bucket = rootLookup.get(toKey(rootValue)) ?? [];
161
+ bucket.push({
162
+ targetId: targetValue,
163
+ pivot: { ...pivot }
164
+ });
165
+ rootLookup.set(toKey(rootValue), bucket);
166
+ targetIds.add(targetValue);
167
+ }
168
+
169
+ if (!targetIds.size) {
170
+ return new Map();
171
+ }
172
+
173
+ const targetSelect = selectAllColumns(relation.target);
174
+ const targetKey = relation.targetKey || findPrimaryKey(relation.target);
175
+ const targetPkColumn = relation.target.columns[targetKey];
176
+ if (!targetPkColumn) return new Map();
177
+
178
+ const targetQb = new SelectQueryBuilder(relation.target).select(targetSelect);
179
+ targetQb.where(inList(targetPkColumn, Array.from(targetIds) as (string | number | LiteralNode)[]));
180
+ const targetRows = await executeQuery(ctx, targetQb);
181
+ const targetMap = new Map<string, Record<string, any>>();
182
+
183
+ for (const row of targetRows) {
184
+ const pkValue = row[targetKey];
185
+ if (pkValue === null || pkValue === undefined) continue;
186
+ targetMap.set(toKey(pkValue), row);
187
+ }
188
+
189
+ const result = new Map<string, Rows>();
190
+
191
+ for (const [rootId, entries] of rootLookup.entries()) {
192
+ const bucket: Rows = [];
193
+ for (const entry of entries) {
194
+ const targetRow = targetMap.get(toKey(entry.targetId));
195
+ if (!targetRow) continue;
196
+ bucket.push({
197
+ ...targetRow,
198
+ _pivot: entry.pivot
199
+ });
200
+ }
201
+ result.set(rootId, bucket);
202
+ }
203
+
204
+ return result;
205
+ };