metal-orm 1.0.41 → 1.0.43
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 +74 -20
- package/dist/index.cjs +180 -74
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +142 -96
- package/dist/index.d.ts +142 -96
- package/dist/index.js +177 -74
- package/dist/index.js.map +1 -1
- package/package.json +8 -2
- package/scripts/run-eslint.mjs +34 -0
- package/src/codegen/typescript.ts +32 -15
- package/src/core/ast/builders.ts +7 -2
- package/src/core/ast/expression-builders.ts +0 -2
- package/src/core/ast/expression-nodes.ts +14 -5
- package/src/core/ast/expression-visitor.ts +11 -8
- package/src/core/ast/expression.ts +2 -2
- package/src/core/ast/join-node.ts +1 -1
- package/src/core/ast/query.ts +6 -6
- package/src/core/ast/window-functions.ts +10 -2
- package/src/core/ddl/dialects/base-schema-dialect.ts +30 -3
- package/src/core/ddl/dialects/mssql-schema-dialect.ts +4 -0
- package/src/core/ddl/dialects/mysql-schema-dialect.ts +2 -0
- package/src/core/ddl/dialects/postgres-schema-dialect.ts +13 -1
- package/src/core/ddl/dialects/render-reference.test.ts +69 -0
- package/src/core/ddl/dialects/sqlite-schema-dialect.ts +9 -0
- package/src/core/ddl/introspect/mssql.ts +42 -8
- package/src/core/ddl/introspect/mysql.ts +30 -6
- package/src/core/ddl/introspect/postgres.ts +88 -34
- package/src/core/ddl/introspect/run-select.ts +6 -4
- package/src/core/ddl/introspect/sqlite.ts +56 -11
- package/src/core/ddl/introspect/types.ts +0 -1
- package/src/core/ddl/introspect/utils.ts +3 -3
- package/src/core/ddl/schema-dialect.ts +1 -0
- package/src/core/ddl/schema-generator.ts +4 -12
- package/src/core/ddl/sql-writing.ts +4 -4
- package/src/core/dialect/abstract.ts +18 -6
- package/src/core/dialect/base/function-table-formatter.ts +3 -2
- package/src/core/dialect/base/join-compiler.ts +5 -3
- package/src/core/dialect/base/returning-strategy.ts +1 -0
- package/src/core/dialect/base/sql-dialect.ts +3 -3
- package/src/core/dialect/mssql/functions.ts +24 -25
- package/src/core/dialect/mssql/index.ts +1 -4
- package/src/core/dialect/mysql/functions.ts +0 -1
- package/src/core/dialect/postgres/functions.ts +33 -34
- package/src/core/dialect/postgres/index.ts +1 -0
- package/src/core/dialect/sqlite/functions.ts +18 -19
- package/src/core/dialect/sqlite/index.ts +2 -0
- package/src/core/execution/db-executor.ts +1 -1
- package/src/core/execution/executors/mysql-executor.ts +2 -2
- package/src/core/execution/executors/postgres-executor.ts +1 -1
- package/src/core/execution/pooling/pool.ts +2 -0
- package/src/core/functions/datetime.ts +1 -1
- package/src/core/functions/numeric.ts +1 -1
- package/src/core/functions/text.ts +1 -1
- package/src/decorators/bootstrap.ts +27 -8
- package/src/decorators/column.ts +3 -11
- package/src/decorators/decorator-metadata.ts +3 -9
- package/src/decorators/entity.ts +21 -5
- package/src/decorators/relations.ts +2 -11
- package/src/orm/entity-context.ts +8 -8
- package/src/orm/entity-meta.ts +8 -8
- package/src/orm/entity-metadata.ts +11 -9
- package/src/orm/entity.ts +28 -29
- package/src/orm/execute.ts +4 -4
- package/src/orm/hydration.ts +42 -39
- package/src/orm/identity-map.ts +1 -1
- package/src/orm/lazy-batch.ts +9 -9
- package/src/orm/orm-session.ts +24 -23
- package/src/orm/orm.ts +2 -5
- package/src/orm/relation-change-processor.ts +12 -11
- package/src/orm/relations/belongs-to.ts +11 -11
- package/src/orm/relations/has-many.ts +10 -10
- package/src/orm/relations/has-one.ts +8 -7
- package/src/orm/relations/many-to-many.ts +13 -13
- package/src/orm/runtime-types.ts +4 -4
- package/src/orm/save-graph.ts +31 -25
- package/src/orm/unit-of-work.ts +17 -17
- package/src/query-builder/delete.ts +4 -3
- package/src/query-builder/hydration-manager.ts +6 -5
- package/src/query-builder/insert.ts +12 -8
- package/src/query-builder/query-ast-service.ts +2 -2
- package/src/query-builder/raw-column-parser.ts +2 -1
- package/src/query-builder/select-helpers.ts +2 -2
- package/src/query-builder/select.ts +31 -31
- package/src/query-builder/update.ts +4 -3
- package/src/schema/column.ts +26 -26
- package/src/schema/table.ts +239 -115
- package/src/schema/types.ts +22 -22
|
@@ -8,23 +8,23 @@ export interface EntityContext {
|
|
|
8
8
|
dialect: Dialect;
|
|
9
9
|
executor: DbExecutor;
|
|
10
10
|
|
|
11
|
-
getEntity(table: TableDef, pk:
|
|
12
|
-
setEntity(table: TableDef, pk:
|
|
11
|
+
getEntity(table: TableDef, pk: unknown): unknown;
|
|
12
|
+
setEntity(table: TableDef, pk: unknown, entity: unknown): void;
|
|
13
13
|
|
|
14
|
-
trackNew(table: TableDef, entity:
|
|
15
|
-
trackManaged(table: TableDef, pk:
|
|
14
|
+
trackNew(table: TableDef, entity: unknown, pk?: unknown): void;
|
|
15
|
+
trackManaged(table: TableDef, pk: unknown, entity: unknown): void;
|
|
16
16
|
|
|
17
|
-
markDirty(entity:
|
|
18
|
-
markRemoved(entity:
|
|
17
|
+
markDirty(entity: unknown): void;
|
|
18
|
+
markRemoved(entity: unknown): void;
|
|
19
19
|
|
|
20
20
|
getEntitiesForTable(table: TableDef): TrackedEntity[];
|
|
21
21
|
|
|
22
22
|
registerRelationChange(
|
|
23
|
-
root:
|
|
23
|
+
root: unknown,
|
|
24
24
|
relationKey: RelationKey,
|
|
25
25
|
rootTable: TableDef,
|
|
26
26
|
relationName: string,
|
|
27
27
|
relation: RelationDef,
|
|
28
|
-
change: RelationChange<
|
|
28
|
+
change: RelationChange<unknown>
|
|
29
29
|
): void;
|
|
30
30
|
}
|
package/src/orm/entity-meta.ts
CHANGED
|
@@ -21,9 +21,9 @@ export interface EntityMeta<TTable extends TableDef> {
|
|
|
21
21
|
/** Relations that should be loaded lazily */
|
|
22
22
|
lazyRelations: (keyof RelationMap<TTable>)[];
|
|
23
23
|
/** Cache for relation promises */
|
|
24
|
-
relationCache: Map<string, Promise<
|
|
24
|
+
relationCache: Map<string, Promise<unknown>>;
|
|
25
25
|
/** Hydration data for relations */
|
|
26
|
-
relationHydration: Map<string, Map<string,
|
|
26
|
+
relationHydration: Map<string, Map<string, unknown>>;
|
|
27
27
|
/** Relation wrapper instances */
|
|
28
28
|
relationWrappers: Map<string, unknown>;
|
|
29
29
|
}
|
|
@@ -40,7 +40,7 @@ export const getHydrationRows = <TTable extends TableDef>(
|
|
|
40
40
|
meta: EntityMeta<TTable>,
|
|
41
41
|
relationName: string,
|
|
42
42
|
key: unknown
|
|
43
|
-
): Record<string,
|
|
43
|
+
): Record<string, unknown>[] | undefined => {
|
|
44
44
|
const map = meta.relationHydration.get(relationName);
|
|
45
45
|
if (!map) return undefined;
|
|
46
46
|
const rows = map.get(toKey(key));
|
|
@@ -60,7 +60,7 @@ export const getHydrationRecord = <TTable extends TableDef>(
|
|
|
60
60
|
meta: EntityMeta<TTable>,
|
|
61
61
|
relationName: string,
|
|
62
62
|
key: unknown
|
|
63
|
-
): Record<string,
|
|
63
|
+
): Record<string, unknown> | undefined => {
|
|
64
64
|
const map = meta.relationHydration.get(relationName);
|
|
65
65
|
if (!map) return undefined;
|
|
66
66
|
const value = map.get(toKey(key));
|
|
@@ -68,7 +68,7 @@ export const getHydrationRecord = <TTable extends TableDef>(
|
|
|
68
68
|
if (Array.isArray(value)) {
|
|
69
69
|
return value[0];
|
|
70
70
|
}
|
|
71
|
-
return value
|
|
71
|
+
return value as Record<string, unknown>;
|
|
72
72
|
};
|
|
73
73
|
|
|
74
74
|
/**
|
|
@@ -77,9 +77,9 @@ export const getHydrationRecord = <TTable extends TableDef>(
|
|
|
77
77
|
* @returns Entity metadata or undefined if not found
|
|
78
78
|
* @typeParam TTable - Table definition type
|
|
79
79
|
*/
|
|
80
|
-
export const getEntityMeta = <TTable extends TableDef>(entity:
|
|
80
|
+
export const getEntityMeta = <TTable extends TableDef>(entity: unknown): EntityMeta<TTable> | undefined => {
|
|
81
81
|
if (!entity || typeof entity !== 'object') return undefined;
|
|
82
|
-
return (entity as
|
|
82
|
+
return (entity as { [ENTITY_META]: EntityMeta<TTable> })[ENTITY_META];
|
|
83
83
|
};
|
|
84
84
|
|
|
85
85
|
/**
|
|
@@ -87,6 +87,6 @@ export const getEntityMeta = <TTable extends TableDef>(entity: any): EntityMeta<
|
|
|
87
87
|
* @param entity - Entity instance to check
|
|
88
88
|
* @returns True if the entity has metadata, false otherwise
|
|
89
89
|
*/
|
|
90
|
-
export const hasEntityMeta = (entity:
|
|
90
|
+
export const hasEntityMeta = (entity: unknown): entity is { [ENTITY_META]: EntityMeta<TableDef> } => {
|
|
91
91
|
return Boolean(getEntityMeta(entity));
|
|
92
92
|
};
|
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { ColumnDef } from '../schema/column.js';
|
|
2
2
|
import { defineTable, TableDef, TableHooks } from '../schema/table.js';
|
|
3
3
|
import { CascadeMode, RelationKinds } from '../schema/relation.js';
|
|
4
4
|
|
|
5
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
5
6
|
export type EntityConstructor<T = object> = new (...args: any[]) => T;
|
|
6
|
-
export type EntityOrTableTarget = EntityConstructor
|
|
7
|
+
export type EntityOrTableTarget = EntityConstructor | TableDef;
|
|
7
8
|
export type EntityOrTableTargetResolver = EntityOrTableTarget | (() => EntityOrTableTarget);
|
|
8
9
|
|
|
9
10
|
export type ColumnDefLike<T extends ColumnDef = ColumnDef> = Omit<T, 'name' | 'table'>;
|
|
@@ -57,7 +58,7 @@ export type RelationMetadata =
|
|
|
57
58
|
| BelongsToManyRelationMetadata;
|
|
58
59
|
|
|
59
60
|
export interface EntityMetadata<TColumns extends Record<string, ColumnDefLike> = Record<string, ColumnDefLike>> {
|
|
60
|
-
target: EntityConstructor
|
|
61
|
+
target: EntityConstructor;
|
|
61
62
|
tableName: string;
|
|
62
63
|
columns: TColumns;
|
|
63
64
|
relations: Record<string, RelationMetadata>;
|
|
@@ -65,13 +66,13 @@ export interface EntityMetadata<TColumns extends Record<string, ColumnDefLike> =
|
|
|
65
66
|
table?: TableDef<MaterializeColumns<TColumns>>;
|
|
66
67
|
}
|
|
67
68
|
|
|
68
|
-
const metadataMap = new Map<EntityConstructor
|
|
69
|
+
const metadataMap = new Map<EntityConstructor, EntityMetadata>();
|
|
69
70
|
|
|
70
71
|
export const registerEntityMetadata = (meta: EntityMetadata): void => {
|
|
71
72
|
metadataMap.set(meta.target, meta);
|
|
72
73
|
};
|
|
73
74
|
|
|
74
|
-
export const ensureEntityMetadata = (target: EntityConstructor
|
|
75
|
+
export const ensureEntityMetadata = (target: EntityConstructor): EntityMetadata => {
|
|
75
76
|
let meta = metadataMap.get(target);
|
|
76
77
|
if (!meta) {
|
|
77
78
|
meta = {
|
|
@@ -85,7 +86,7 @@ export const ensureEntityMetadata = (target: EntityConstructor<any>): EntityMeta
|
|
|
85
86
|
return meta;
|
|
86
87
|
};
|
|
87
88
|
|
|
88
|
-
export const getEntityMetadata = (target: EntityConstructor
|
|
89
|
+
export const getEntityMetadata = (target: EntityConstructor): EntityMetadata | undefined => {
|
|
89
90
|
return metadataMap.get(target);
|
|
90
91
|
};
|
|
91
92
|
|
|
@@ -98,7 +99,7 @@ export const clearEntityMetadata = (): void => {
|
|
|
98
99
|
};
|
|
99
100
|
|
|
100
101
|
export const addColumnMetadata = (
|
|
101
|
-
target: EntityConstructor
|
|
102
|
+
target: EntityConstructor,
|
|
102
103
|
propertyKey: string,
|
|
103
104
|
column: ColumnDefLike
|
|
104
105
|
): void => {
|
|
@@ -107,7 +108,7 @@ export const addColumnMetadata = (
|
|
|
107
108
|
};
|
|
108
109
|
|
|
109
110
|
export const addRelationMetadata = (
|
|
110
|
-
target: EntityConstructor
|
|
111
|
+
target: EntityConstructor,
|
|
111
112
|
propertyKey: string,
|
|
112
113
|
relation: RelationMetadata
|
|
113
114
|
): void => {
|
|
@@ -116,7 +117,7 @@ export const addRelationMetadata = (
|
|
|
116
117
|
};
|
|
117
118
|
|
|
118
119
|
export const setEntityTableName = (
|
|
119
|
-
target: EntityConstructor
|
|
120
|
+
target: EntityConstructor,
|
|
120
121
|
tableName: string,
|
|
121
122
|
hooks?: TableHooks
|
|
122
123
|
): void => {
|
|
@@ -135,6 +136,7 @@ export const buildTableDef = <TColumns extends Record<string, ColumnDefLike>>(me
|
|
|
135
136
|
}
|
|
136
137
|
|
|
137
138
|
const columns = Object.entries(meta.columns).reduce<MaterializeColumns<TColumns>>((acc, [key, def]) => {
|
|
139
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
138
140
|
(acc as any)[key] = {
|
|
139
141
|
...def,
|
|
140
142
|
name: key,
|
package/src/orm/entity.ts
CHANGED
|
@@ -13,7 +13,7 @@ import { findPrimaryKey } from '../query-builder/hydration-planner.js';
|
|
|
13
13
|
/**
|
|
14
14
|
* Type representing an array of database rows.
|
|
15
15
|
*/
|
|
16
|
-
type Rows = Record<string,
|
|
16
|
+
type Rows = Record<string, unknown>[];
|
|
17
17
|
|
|
18
18
|
/**
|
|
19
19
|
* Caches relation loader results across entities of the same type.
|
|
@@ -23,8 +23,8 @@ type Rows = Record<string, any>[];
|
|
|
23
23
|
* @param factory - The factory function to create the cache
|
|
24
24
|
* @returns Promise with the cached relation data
|
|
25
25
|
*/
|
|
26
|
-
const relationLoaderCache = <T extends Map<string,
|
|
27
|
-
meta: EntityMeta<
|
|
26
|
+
const relationLoaderCache = <T extends Map<string, unknown>>(
|
|
27
|
+
meta: EntityMeta<TableDef>,
|
|
28
28
|
relationName: string,
|
|
29
29
|
factory: () => Promise<T>
|
|
30
30
|
): Promise<T> => {
|
|
@@ -68,10 +68,10 @@ export const createEntityProxy = <
|
|
|
68
68
|
>(
|
|
69
69
|
ctx: EntityContext,
|
|
70
70
|
table: TTable,
|
|
71
|
-
row: Record<string,
|
|
71
|
+
row: Record<string, unknown>,
|
|
72
72
|
lazyRelations: TLazy[] = [] as TLazy[]
|
|
73
73
|
): EntityInstance<TTable> => {
|
|
74
|
-
const target: Record<string,
|
|
74
|
+
const target: Record<string, unknown> = { ...row };
|
|
75
75
|
const meta: EntityMeta<TTable> = {
|
|
76
76
|
ctx,
|
|
77
77
|
table,
|
|
@@ -87,8 +87,7 @@ export const createEntityProxy = <
|
|
|
87
87
|
writable: false
|
|
88
88
|
});
|
|
89
89
|
|
|
90
|
-
|
|
91
|
-
const handler: ProxyHandler<any> = {
|
|
90
|
+
const handler: ProxyHandler<object> = {
|
|
92
91
|
get(targetObj, prop, receiver) {
|
|
93
92
|
if (prop === ENTITY_META) {
|
|
94
93
|
return meta;
|
|
@@ -96,7 +95,7 @@ export const createEntityProxy = <
|
|
|
96
95
|
|
|
97
96
|
if (prop === '$load') {
|
|
98
97
|
return async (relationName: keyof RelationMap<TTable>) => {
|
|
99
|
-
const wrapper = getRelationWrapper(meta
|
|
98
|
+
const wrapper = getRelationWrapper(meta as unknown as EntityMeta<TableDef>, relationName as string, receiver);
|
|
100
99
|
if (wrapper && typeof wrapper.load === 'function') {
|
|
101
100
|
return wrapper.load();
|
|
102
101
|
}
|
|
@@ -105,7 +104,7 @@ export const createEntityProxy = <
|
|
|
105
104
|
}
|
|
106
105
|
|
|
107
106
|
if (typeof prop === 'string' && table.relations[prop]) {
|
|
108
|
-
return getRelationWrapper(meta
|
|
107
|
+
return getRelationWrapper(meta as unknown as EntityMeta<TableDef>, prop, receiver);
|
|
109
108
|
}
|
|
110
109
|
|
|
111
110
|
return Reflect.get(targetObj, prop, receiver);
|
|
@@ -114,13 +113,13 @@ export const createEntityProxy = <
|
|
|
114
113
|
set(targetObj, prop, value, receiver) {
|
|
115
114
|
const result = Reflect.set(targetObj, prop, value, receiver);
|
|
116
115
|
if (typeof prop === 'string' && table.columns[prop]) {
|
|
117
|
-
ctx.markDirty(
|
|
116
|
+
ctx.markDirty(receiver);
|
|
118
117
|
}
|
|
119
118
|
return result;
|
|
120
119
|
}
|
|
121
120
|
};
|
|
122
121
|
|
|
123
|
-
proxy = new Proxy(target, handler) as EntityInstance<TTable>;
|
|
122
|
+
const proxy = new Proxy(target, handler) as EntityInstance<TTable>;
|
|
124
123
|
populateHydrationCache(proxy, row, meta);
|
|
125
124
|
return proxy;
|
|
126
125
|
};
|
|
@@ -141,7 +140,7 @@ export const createEntityFromRow = <
|
|
|
141
140
|
>(
|
|
142
141
|
ctx: EntityContext,
|
|
143
142
|
table: TTable,
|
|
144
|
-
row: Record<string,
|
|
143
|
+
row: Record<string, unknown>,
|
|
145
144
|
lazyRelations: (keyof RelationMap<TTable>)[] = []
|
|
146
145
|
): TResult => {
|
|
147
146
|
const pkName = findPrimaryKey(table);
|
|
@@ -176,8 +175,8 @@ const toKey = (value: unknown): string => (value === null || value === undefined
|
|
|
176
175
|
* @param meta - The entity metadata
|
|
177
176
|
*/
|
|
178
177
|
const populateHydrationCache = <TTable extends TableDef>(
|
|
179
|
-
entity:
|
|
180
|
-
row: Record<string,
|
|
178
|
+
entity: Record<string, unknown>,
|
|
179
|
+
row: Record<string, unknown>,
|
|
181
180
|
meta: EntityMeta<TTable>
|
|
182
181
|
): void => {
|
|
183
182
|
for (const relationName of Object.keys(meta.table.relations)) {
|
|
@@ -188,8 +187,8 @@ const populateHydrationCache = <TTable extends TableDef>(
|
|
|
188
187
|
const rootValue = entity[localKey];
|
|
189
188
|
if (rootValue === undefined || rootValue === null) continue;
|
|
190
189
|
if (!data || typeof data !== 'object') continue;
|
|
191
|
-
const cache = new Map<string, Record<string,
|
|
192
|
-
cache.set(toKey(rootValue), data as Record<string,
|
|
190
|
+
const cache = new Map<string, Record<string, unknown>>();
|
|
191
|
+
cache.set(toKey(rootValue), data as Record<string, unknown>);
|
|
193
192
|
meta.relationHydration.set(relationName, cache);
|
|
194
193
|
meta.relationCache.set(relationName, Promise.resolve(cache));
|
|
195
194
|
continue;
|
|
@@ -210,7 +209,7 @@ const populateHydrationCache = <TTable extends TableDef>(
|
|
|
210
209
|
|
|
211
210
|
if (relation.type === RelationKinds.BelongsTo) {
|
|
212
211
|
const targetKey = relation.localKey || findPrimaryKey(relation.target);
|
|
213
|
-
const cache = new Map<string, Record<string,
|
|
212
|
+
const cache = new Map<string, Record<string, unknown>>();
|
|
214
213
|
for (const item of data) {
|
|
215
214
|
const pkValue = item[targetKey];
|
|
216
215
|
if (pkValue === undefined || pkValue === null) continue;
|
|
@@ -232,18 +231,18 @@ const populateHydrationCache = <TTable extends TableDef>(
|
|
|
232
231
|
* @returns The relation wrapper or undefined
|
|
233
232
|
*/
|
|
234
233
|
const getRelationWrapper = (
|
|
235
|
-
meta: EntityMeta<
|
|
234
|
+
meta: EntityMeta<TableDef>,
|
|
236
235
|
relationName: string,
|
|
237
|
-
owner:
|
|
238
|
-
): HasManyCollection<
|
|
236
|
+
owner: unknown
|
|
237
|
+
): HasManyCollection<unknown> | HasOneReference<unknown> | BelongsToReference<unknown> | ManyToManyCollection<unknown> | undefined => {
|
|
239
238
|
if (meta.relationWrappers.has(relationName)) {
|
|
240
|
-
return meta.relationWrappers.get(relationName) as HasManyCollection<
|
|
239
|
+
return meta.relationWrappers.get(relationName) as HasManyCollection<unknown>;
|
|
241
240
|
}
|
|
242
241
|
|
|
243
242
|
const relation = meta.table.relations[relationName];
|
|
244
243
|
if (!relation) return undefined;
|
|
245
244
|
|
|
246
|
-
const wrapper = instantiateWrapper(meta, relationName, relation
|
|
245
|
+
const wrapper = instantiateWrapper(meta, relationName, relation, owner);
|
|
247
246
|
if (wrapper) {
|
|
248
247
|
meta.relationWrappers.set(relationName, wrapper);
|
|
249
248
|
}
|
|
@@ -260,11 +259,11 @@ const getRelationWrapper = (
|
|
|
260
259
|
* @returns The relation wrapper or undefined
|
|
261
260
|
*/
|
|
262
261
|
const instantiateWrapper = (
|
|
263
|
-
meta: EntityMeta<
|
|
262
|
+
meta: EntityMeta<TableDef>,
|
|
264
263
|
relationName: string,
|
|
265
264
|
relation: HasManyRelation | HasOneRelation | BelongsToRelation | BelongsToManyRelation,
|
|
266
|
-
owner:
|
|
267
|
-
): HasManyCollection<
|
|
265
|
+
owner: unknown
|
|
266
|
+
): HasManyCollection<unknown> | HasOneReference<unknown> | BelongsToReference<unknown> | ManyToManyCollection<unknown> | undefined => {
|
|
268
267
|
switch (relation.type) {
|
|
269
268
|
case RelationKinds.HasOne: {
|
|
270
269
|
const hasOne = relation as HasOneRelation;
|
|
@@ -280,7 +279,7 @@ const instantiateWrapper = (
|
|
|
280
279
|
hasOne,
|
|
281
280
|
meta.table,
|
|
282
281
|
loader,
|
|
283
|
-
(row: Record<string,
|
|
282
|
+
(row: Record<string, unknown>) => createEntityFromRow(meta.ctx, hasOne.target, row),
|
|
284
283
|
localKey
|
|
285
284
|
);
|
|
286
285
|
}
|
|
@@ -298,7 +297,7 @@ const instantiateWrapper = (
|
|
|
298
297
|
hasMany,
|
|
299
298
|
meta.table,
|
|
300
299
|
loader,
|
|
301
|
-
(row: Record<string,
|
|
300
|
+
(row: Record<string, unknown>) => createEntityFromRow(meta.ctx, relation.target, row),
|
|
302
301
|
localKey
|
|
303
302
|
);
|
|
304
303
|
}
|
|
@@ -316,7 +315,7 @@ const instantiateWrapper = (
|
|
|
316
315
|
belongsTo,
|
|
317
316
|
meta.table,
|
|
318
317
|
loader,
|
|
319
|
-
(row: Record<string,
|
|
318
|
+
(row: Record<string, unknown>) => createEntityFromRow(meta.ctx, relation.target, row),
|
|
320
319
|
targetKey
|
|
321
320
|
);
|
|
322
321
|
}
|
|
@@ -334,7 +333,7 @@ const instantiateWrapper = (
|
|
|
334
333
|
many,
|
|
335
334
|
meta.table,
|
|
336
335
|
loader,
|
|
337
|
-
(row: Record<string,
|
|
336
|
+
(row: Record<string, unknown>) => createEntityFromRow(meta.ctx, relation.target, row),
|
|
338
337
|
localKey
|
|
339
338
|
);
|
|
340
339
|
}
|
package/src/orm/execute.ts
CHANGED
|
@@ -8,7 +8,7 @@ import { EntityContext } from './entity-context.js';
|
|
|
8
8
|
import { ExecutionContext } from './execution-context.js';
|
|
9
9
|
import { HydrationContext } from './hydration-context.js';
|
|
10
10
|
|
|
11
|
-
type Row = Record<string,
|
|
11
|
+
type Row = Record<string, unknown>;
|
|
12
12
|
|
|
13
13
|
const flattenResults = (results: { columns: string[]; values: unknown[][] }[]): Row[] => {
|
|
14
14
|
const rows: Row[] = [];
|
|
@@ -27,7 +27,7 @@ const flattenResults = (results: { columns: string[]; values: unknown[][] }[]):
|
|
|
27
27
|
|
|
28
28
|
const executeWithEntityContext = async <TTable extends TableDef>(
|
|
29
29
|
entityCtx: EntityContext,
|
|
30
|
-
qb: SelectQueryBuilder<
|
|
30
|
+
qb: SelectQueryBuilder<unknown, TTable>
|
|
31
31
|
): Promise<EntityInstance<TTable>[]> => {
|
|
32
32
|
const ast = qb.getAST();
|
|
33
33
|
const compiled = entityCtx.dialect.compileSelect(ast);
|
|
@@ -44,7 +44,7 @@ const executeWithEntityContext = async <TTable extends TableDef>(
|
|
|
44
44
|
|
|
45
45
|
export async function executeHydrated<TTable extends TableDef>(
|
|
46
46
|
session: OrmSession,
|
|
47
|
-
qb: SelectQueryBuilder<
|
|
47
|
+
qb: SelectQueryBuilder<unknown, TTable>
|
|
48
48
|
): Promise<EntityInstance<TTable>[]> {
|
|
49
49
|
return executeWithEntityContext(session, qb);
|
|
50
50
|
}
|
|
@@ -52,7 +52,7 @@ export async function executeHydrated<TTable extends TableDef>(
|
|
|
52
52
|
export async function executeHydratedWithContexts<TTable extends TableDef>(
|
|
53
53
|
_execCtx: ExecutionContext,
|
|
54
54
|
hydCtx: HydrationContext,
|
|
55
|
-
qb: SelectQueryBuilder<
|
|
55
|
+
qb: SelectQueryBuilder<unknown, TTable>
|
|
56
56
|
): Promise<EntityInstance<TTable>[]> {
|
|
57
57
|
const entityCtx = hydCtx.entityContext;
|
|
58
58
|
if (!entityCtx) {
|
package/src/orm/hydration.ts
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
|
-
import { HydrationPlan, HydrationRelationPlan } from '../core/hydration/types.js';
|
|
2
|
-
import { RelationKinds } from '../schema/relation.js';
|
|
3
|
-
import { isRelationAlias, makeRelationAlias } from '../query-builder/relation-alias.js';
|
|
1
|
+
import { HydrationPlan, HydrationRelationPlan } from '../core/hydration/types.js';
|
|
2
|
+
import { RelationKinds } from '../schema/relation.js';
|
|
3
|
+
import { isRelationAlias, makeRelationAlias } from '../query-builder/relation-alias.js';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Hydrates query results according to a hydration plan
|
|
7
|
+
* @param rows - Raw database rows
|
|
4
8
|
|
|
5
9
|
/**
|
|
6
10
|
* Hydrates query results according to a hydration plan
|
|
@@ -8,13 +12,13 @@ import { isRelationAlias, makeRelationAlias } from '../query-builder/relation-al
|
|
|
8
12
|
* @param plan - Hydration plan
|
|
9
13
|
* @returns Hydrated result objects with nested relations
|
|
10
14
|
*/
|
|
11
|
-
export const hydrateRows = (rows: Record<string,
|
|
15
|
+
export const hydrateRows = (rows: Record<string, unknown>[], plan?: HydrationPlan): Record<string, unknown>[] => {
|
|
12
16
|
if (!plan || !rows.length) return rows;
|
|
13
17
|
|
|
14
|
-
const rootMap = new Map<
|
|
15
|
-
const relationIndex = new Map<
|
|
18
|
+
const rootMap = new Map<unknown, Record<string, unknown>>();
|
|
19
|
+
const relationIndex = new Map<unknown, Record<string, Set<unknown>>>();
|
|
16
20
|
|
|
17
|
-
const getOrCreateParent = (row: Record<string,
|
|
21
|
+
const getOrCreateParent = (row: Record<string, unknown>) => {
|
|
18
22
|
const rootId = row[plan.rootPrimaryKey];
|
|
19
23
|
if (rootId === undefined) return undefined;
|
|
20
24
|
|
|
@@ -25,7 +29,7 @@ export const hydrateRows = (rows: Record<string, any>[], plan?: HydrationPlan):
|
|
|
25
29
|
return rootMap.get(rootId);
|
|
26
30
|
};
|
|
27
31
|
|
|
28
|
-
const getRelationSeenSet = (rootId:
|
|
32
|
+
const getRelationSeenSet = (rootId: unknown, relationName: string): Set<unknown> => {
|
|
29
33
|
let byRelation = relationIndex.get(rootId);
|
|
30
34
|
if (!byRelation) {
|
|
31
35
|
byRelation = {};
|
|
@@ -34,7 +38,7 @@ export const hydrateRows = (rows: Record<string, any>[], plan?: HydrationPlan):
|
|
|
34
38
|
|
|
35
39
|
let seen = byRelation[relationName];
|
|
36
40
|
if (!seen) {
|
|
37
|
-
seen = new Set<
|
|
41
|
+
seen = new Set<unknown>();
|
|
38
42
|
byRelation[relationName] = seen;
|
|
39
43
|
}
|
|
40
44
|
|
|
@@ -48,32 +52,32 @@ export const hydrateRows = (rows: Record<string, any>[], plan?: HydrationPlan):
|
|
|
48
52
|
const parent = getOrCreateParent(row);
|
|
49
53
|
if (!parent) continue;
|
|
50
54
|
|
|
51
|
-
for (const rel of plan.relations) {
|
|
52
|
-
const childPkKey = makeRelationAlias(rel.aliasPrefix, rel.targetPrimaryKey);
|
|
53
|
-
const childPk = row[childPkKey];
|
|
54
|
-
if (childPk === null || childPk === undefined) continue;
|
|
55
|
-
|
|
56
|
-
const seen = getRelationSeenSet(rootId, rel.name);
|
|
57
|
-
if (seen.has(childPk)) continue;
|
|
58
|
-
seen.add(childPk);
|
|
59
|
-
|
|
60
|
-
if (rel.type === RelationKinds.HasOne) {
|
|
61
|
-
if (!parent[rel.name]) {
|
|
62
|
-
parent[rel.name] = buildChild(row, rel);
|
|
63
|
-
}
|
|
64
|
-
continue;
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
const bucket = parent[rel.name] as
|
|
68
|
-
bucket.push(buildChild(row, rel));
|
|
69
|
-
}
|
|
55
|
+
for (const rel of plan.relations) {
|
|
56
|
+
const childPkKey = makeRelationAlias(rel.aliasPrefix, rel.targetPrimaryKey);
|
|
57
|
+
const childPk = row[childPkKey];
|
|
58
|
+
if (childPk === null || childPk === undefined) continue;
|
|
59
|
+
|
|
60
|
+
const seen = getRelationSeenSet(rootId, rel.name);
|
|
61
|
+
if (seen.has(childPk)) continue;
|
|
62
|
+
seen.add(childPk);
|
|
63
|
+
|
|
64
|
+
if (rel.type === RelationKinds.HasOne) {
|
|
65
|
+
if (!parent[rel.name]) {
|
|
66
|
+
parent[rel.name] = buildChild(row, rel);
|
|
67
|
+
}
|
|
68
|
+
continue;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const bucket = parent[rel.name] as unknown[];
|
|
72
|
+
bucket.push(buildChild(row, rel));
|
|
73
|
+
}
|
|
70
74
|
}
|
|
71
75
|
|
|
72
76
|
return Array.from(rootMap.values());
|
|
73
77
|
};
|
|
74
78
|
|
|
75
|
-
const createBaseRow = (row: Record<string,
|
|
76
|
-
const base: Record<string,
|
|
79
|
+
const createBaseRow = (row: Record<string, unknown>, plan: HydrationPlan): Record<string, unknown> => {
|
|
80
|
+
const base: Record<string, unknown> = {};
|
|
77
81
|
const baseKeys = plan.rootColumns.length
|
|
78
82
|
? plan.rootColumns
|
|
79
83
|
: Object.keys(row).filter(k => !isRelationAlias(k));
|
|
@@ -82,15 +86,14 @@ const createBaseRow = (row: Record<string, any>, plan: HydrationPlan): Record<st
|
|
|
82
86
|
base[key] = row[key];
|
|
83
87
|
}
|
|
84
88
|
|
|
85
|
-
for (const rel of plan.relations) {
|
|
86
|
-
base[rel.name] = rel.type === RelationKinds.HasOne ? null : [];
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
+
for (const rel of plan.relations) {
|
|
90
|
+
base[rel.name] = rel.type === RelationKinds.HasOne ? null : [];
|
|
91
|
+
}
|
|
89
92
|
return base;
|
|
90
93
|
};
|
|
91
94
|
|
|
92
|
-
const buildChild = (row: Record<string,
|
|
93
|
-
const child: Record<string,
|
|
95
|
+
const buildChild = (row: Record<string, unknown>, rel: HydrationRelationPlan): Record<string, unknown> => {
|
|
96
|
+
const child: Record<string, unknown> = {};
|
|
94
97
|
for (const col of rel.columns) {
|
|
95
98
|
const key = makeRelationAlias(rel.aliasPrefix, col);
|
|
96
99
|
child[col] = row[key];
|
|
@@ -98,16 +101,16 @@ const buildChild = (row: Record<string, any>, rel: HydrationRelationPlan): Recor
|
|
|
98
101
|
|
|
99
102
|
const pivot = buildPivot(row, rel);
|
|
100
103
|
if (pivot) {
|
|
101
|
-
(child as
|
|
104
|
+
(child as { _pivot: unknown })._pivot = pivot;
|
|
102
105
|
}
|
|
103
106
|
|
|
104
107
|
return child;
|
|
105
108
|
};
|
|
106
109
|
|
|
107
|
-
const buildPivot = (row: Record<string,
|
|
110
|
+
const buildPivot = (row: Record<string, unknown>, rel: HydrationRelationPlan): Record<string, unknown> | undefined => {
|
|
108
111
|
if (!rel.pivot) return undefined;
|
|
109
112
|
|
|
110
|
-
const pivot: Record<string,
|
|
113
|
+
const pivot: Record<string, unknown> = {};
|
|
111
114
|
for (const col of rel.pivot.columns) {
|
|
112
115
|
const key = makeRelationAlias(rel.pivot.aliasPrefix, col);
|
|
113
116
|
pivot[col] = row[key];
|
package/src/orm/identity-map.ts
CHANGED
|
@@ -8,7 +8,7 @@ export class IdentityMap {
|
|
|
8
8
|
return this.buckets;
|
|
9
9
|
}
|
|
10
10
|
|
|
11
|
-
getEntity(table: TableDef, pk: string | number):
|
|
11
|
+
getEntity(table: TableDef, pk: string | number): unknown | undefined {
|
|
12
12
|
const bucket = this.buckets.get(table.name);
|
|
13
13
|
return bucket?.get(this.toIdentityKey(pk))?.entity;
|
|
14
14
|
}
|
package/src/orm/lazy-batch.ts
CHANGED
|
@@ -7,7 +7,7 @@ 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
|
-
type Rows = Record<string,
|
|
10
|
+
type Rows = Record<string, unknown>[];
|
|
11
11
|
|
|
12
12
|
const selectAllColumns = (table: TableDef): Record<string, ColumnDef> =>
|
|
13
13
|
Object.entries(table.columns).reduce((acc, [name, def]) => {
|
|
@@ -20,7 +20,7 @@ const rowsFromResults = (results: QueryResult[]): Rows => {
|
|
|
20
20
|
for (const result of results) {
|
|
21
21
|
const { columns, values } = result;
|
|
22
22
|
for (const valueRow of values) {
|
|
23
|
-
const row: Record<string,
|
|
23
|
+
const row: Record<string, unknown> = {};
|
|
24
24
|
columns.forEach((column, idx) => {
|
|
25
25
|
row[column] = valueRow[idx];
|
|
26
26
|
});
|
|
@@ -30,7 +30,7 @@ const rowsFromResults = (results: QueryResult[]): Rows => {
|
|
|
30
30
|
return rows;
|
|
31
31
|
};
|
|
32
32
|
|
|
33
|
-
const executeQuery = async (ctx: EntityContext, qb: SelectQueryBuilder<
|
|
33
|
+
const executeQuery = async (ctx: EntityContext, qb: SelectQueryBuilder<unknown, TableDef>): Promise<Rows> => {
|
|
34
34
|
const compiled = ctx.dialect.compileSelect(qb.getAST());
|
|
35
35
|
const results = await ctx.executor.executeSql(compiled.sql, compiled.params);
|
|
36
36
|
return rowsFromResults(results);
|
|
@@ -86,7 +86,7 @@ export const loadHasOneRelation = async (
|
|
|
86
86
|
rootTable: TableDef,
|
|
87
87
|
_relationName: string,
|
|
88
88
|
relation: HasOneRelation
|
|
89
|
-
): Promise<Map<string, Record<string,
|
|
89
|
+
): Promise<Map<string, Record<string, unknown>>> => {
|
|
90
90
|
const localKey = relation.localKey || findPrimaryKey(rootTable);
|
|
91
91
|
const roots = ctx.getEntitiesForTable(rootTable);
|
|
92
92
|
const keys = new Set<unknown>();
|
|
@@ -110,7 +110,7 @@ export const loadHasOneRelation = async (
|
|
|
110
110
|
qb.where(inList(fkColumn, Array.from(keys) as (string | number | LiteralNode)[]));
|
|
111
111
|
|
|
112
112
|
const rows = await executeQuery(ctx, qb);
|
|
113
|
-
const lookup = new Map<string, Record<string,
|
|
113
|
+
const lookup = new Map<string, Record<string, unknown>>();
|
|
114
114
|
|
|
115
115
|
for (const row of rows) {
|
|
116
116
|
const fkValue = row[relation.foreignKey];
|
|
@@ -129,7 +129,7 @@ export const loadBelongsToRelation = async (
|
|
|
129
129
|
rootTable: TableDef,
|
|
130
130
|
_relationName: string,
|
|
131
131
|
relation: BelongsToRelation
|
|
132
|
-
): Promise<Map<string, Record<string,
|
|
132
|
+
): Promise<Map<string, Record<string, unknown>>> => {
|
|
133
133
|
const roots = ctx.getEntitiesForTable(rootTable);
|
|
134
134
|
const foreignKeys = new Set<unknown>();
|
|
135
135
|
|
|
@@ -152,7 +152,7 @@ export const loadBelongsToRelation = async (
|
|
|
152
152
|
|
|
153
153
|
qb.where(inList(pkColumn, Array.from(foreignKeys) as (string | number | LiteralNode)[]));
|
|
154
154
|
const rows = await executeQuery(ctx, qb);
|
|
155
|
-
const map = new Map<string, Record<string,
|
|
155
|
+
const map = new Map<string, Record<string, unknown>>();
|
|
156
156
|
|
|
157
157
|
for (const row of rows) {
|
|
158
158
|
const keyValue = row[targetKey];
|
|
@@ -192,7 +192,7 @@ export const loadBelongsToManyRelation = async (
|
|
|
192
192
|
pivotQb.where(inList(pivotFkCol, Array.from(rootIds) as (string | number | LiteralNode)[]));
|
|
193
193
|
const pivotRows = await executeQuery(ctx, pivotQb);
|
|
194
194
|
|
|
195
|
-
const rootLookup = new Map<string, { targetId: unknown; pivot: Record<string,
|
|
195
|
+
const rootLookup = new Map<string, { targetId: unknown; pivot: Record<string, unknown> }[]>();
|
|
196
196
|
const targetIds = new Set<unknown>();
|
|
197
197
|
|
|
198
198
|
for (const pivot of pivotRows) {
|
|
@@ -222,7 +222,7 @@ export const loadBelongsToManyRelation = async (
|
|
|
222
222
|
const targetQb = new SelectQueryBuilder(relation.target).select(targetSelect);
|
|
223
223
|
targetQb.where(inList(targetPkColumn, Array.from(targetIds) as (string | number | LiteralNode)[]));
|
|
224
224
|
const targetRows = await executeQuery(ctx, targetQb);
|
|
225
|
-
const targetMap = new Map<string, Record<string,
|
|
225
|
+
const targetMap = new Map<string, Record<string, unknown>>();
|
|
226
226
|
|
|
227
227
|
for (const row of targetRows) {
|
|
228
228
|
const pkValue = row[targetKey];
|