metal-orm 1.0.72 → 1.0.74
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 +10 -10
- package/dist/index.cjs +145 -26
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +32 -3
- package/dist/index.d.ts +32 -3
- package/dist/index.js +144 -26
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/decorators/bootstrap.ts +49 -32
- package/src/decorators/index.ts +12 -12
- package/src/orm/entity-relations.ts +19 -19
- package/src/orm/execute.ts +23 -19
- package/src/orm/relation-preload.ts +82 -0
- package/src/query-builder/relation-include-tree.ts +98 -0
- package/src/query-builder/select.ts +108 -64
package/package.json
CHANGED
|
@@ -25,14 +25,14 @@ import {
|
|
|
25
25
|
|
|
26
26
|
import { tableRef, type TableRef } from '../schema/table.js';
|
|
27
27
|
import {
|
|
28
|
-
SelectableKeys,
|
|
29
|
-
ColumnDef,
|
|
30
|
-
HasManyCollection,
|
|
31
|
-
HasOneReference,
|
|
32
|
-
BelongsToReference,
|
|
33
|
-
ManyToManyCollection,
|
|
34
|
-
EntityInstance
|
|
35
|
-
} from '../schema/types.js';
|
|
28
|
+
SelectableKeys,
|
|
29
|
+
ColumnDef,
|
|
30
|
+
HasManyCollection,
|
|
31
|
+
HasOneReference,
|
|
32
|
+
BelongsToReference,
|
|
33
|
+
ManyToManyCollection,
|
|
34
|
+
EntityInstance
|
|
35
|
+
} from '../schema/types.js';
|
|
36
36
|
|
|
37
37
|
const unwrapTarget = (target: EntityOrTableTargetResolver): EntityOrTableTarget => {
|
|
38
38
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-function-type
|
|
@@ -197,9 +197,9 @@ type NonFunctionKeys<T> = {
|
|
|
197
197
|
type RelationKeys<TEntity extends object> =
|
|
198
198
|
Exclude<NonFunctionKeys<TEntity>, SelectableKeys<TEntity>> & string;
|
|
199
199
|
|
|
200
|
-
type EntityTable<TEntity extends object> =
|
|
201
|
-
Omit<TableDef<{ [K in SelectableKeys<TEntity>]: ColumnDef }>, 'relations'> & {
|
|
202
|
-
relations: {
|
|
200
|
+
type EntityTable<TEntity extends object> =
|
|
201
|
+
Omit<TableDef<{ [K in SelectableKeys<TEntity>]: ColumnDef }>, 'relations'> & {
|
|
202
|
+
relations: {
|
|
203
203
|
[K in RelationKeys<TEntity>]:
|
|
204
204
|
NonNullable<TEntity[K]> extends HasManyCollection<infer TChild>
|
|
205
205
|
? HasManyRelation<EntityTable<NonNullable<TChild> & object>>
|
|
@@ -215,18 +215,18 @@ type EntityTable<TEntity extends object> =
|
|
|
215
215
|
: NonNullable<TEntity[K]> extends object
|
|
216
216
|
? BelongsToRelation<EntityTable<NonNullable<TEntity[K]> & object>>
|
|
217
217
|
: never;
|
|
218
|
-
};
|
|
219
|
-
};
|
|
220
|
-
|
|
221
|
-
export type DecoratedEntityInstance<TEntity extends object> =
|
|
222
|
-
TEntity & EntityInstance<EntityTable<TEntity>>;
|
|
223
|
-
|
|
224
|
-
export const selectFromEntity = <TEntity extends object>(
|
|
225
|
-
ctor: EntityConstructor<TEntity>
|
|
226
|
-
): SelectQueryBuilder<DecoratedEntityInstance<TEntity>, EntityTable<TEntity>> => {
|
|
227
|
-
const table = getTableDefFromEntity(ctor);
|
|
228
|
-
if (!table) {
|
|
229
|
-
throw new Error(`Entity '${ctor.name}' is not registered with decorators or has not been bootstrapped`);
|
|
218
|
+
};
|
|
219
|
+
};
|
|
220
|
+
|
|
221
|
+
export type DecoratedEntityInstance<TEntity extends object> =
|
|
222
|
+
TEntity & EntityInstance<EntityTable<TEntity>>;
|
|
223
|
+
|
|
224
|
+
export const selectFromEntity = <TEntity extends object>(
|
|
225
|
+
ctor: EntityConstructor<TEntity>
|
|
226
|
+
): SelectQueryBuilder<DecoratedEntityInstance<TEntity>, EntityTable<TEntity>> => {
|
|
227
|
+
const table = getTableDefFromEntity(ctor);
|
|
228
|
+
if (!table) {
|
|
229
|
+
throw new Error(`Entity '${ctor.name}' is not registered with decorators or has not been bootstrapped`);
|
|
230
230
|
}
|
|
231
231
|
return new SelectQueryBuilder(
|
|
232
232
|
table as unknown as EntityTable<TEntity>,
|
|
@@ -245,12 +245,29 @@ export const selectFromEntity = <TEntity extends object>(
|
|
|
245
245
|
* Lazily bootstraps entity metadata (via getTableDefFromEntity) and returns a
|
|
246
246
|
* `tableRef(...)`-style proxy so users can write `u.id` instead of `u.columns.id`.
|
|
247
247
|
*/
|
|
248
|
-
export const entityRef = <TEntity extends object>(
|
|
249
|
-
ctor: EntityConstructor<TEntity>
|
|
250
|
-
): TableRef<EntityTable<TEntity>> => {
|
|
251
|
-
const table = getTableDefFromEntity(ctor);
|
|
252
|
-
if (!table) {
|
|
253
|
-
throw new Error(`Entity '${ctor.name}' is not registered with decorators or has not been bootstrapped`);
|
|
254
|
-
}
|
|
255
|
-
return tableRef(table as EntityTable<TEntity>);
|
|
256
|
-
};
|
|
248
|
+
export const entityRef = <TEntity extends object>(
|
|
249
|
+
ctor: EntityConstructor<TEntity>
|
|
250
|
+
): TableRef<EntityTable<TEntity>> => {
|
|
251
|
+
const table = getTableDefFromEntity(ctor);
|
|
252
|
+
if (!table) {
|
|
253
|
+
throw new Error(`Entity '${ctor.name}' is not registered with decorators or has not been bootstrapped`);
|
|
254
|
+
}
|
|
255
|
+
return tableRef(table as EntityTable<TEntity>);
|
|
256
|
+
};
|
|
257
|
+
|
|
258
|
+
type EntityRefsTuple<T extends readonly EntityConstructor<object>[]> = {
|
|
259
|
+
[K in keyof T]: T[K] extends EntityConstructor<infer TEntity>
|
|
260
|
+
? TableRef<EntityTable<TEntity & object>>
|
|
261
|
+
: never;
|
|
262
|
+
};
|
|
263
|
+
|
|
264
|
+
/**
|
|
265
|
+
* Public API: variadic entity references.
|
|
266
|
+
* Usage:
|
|
267
|
+
* const [u, p] = entityRefs(User, Post);
|
|
268
|
+
*/
|
|
269
|
+
export const entityRefs = <T extends readonly EntityConstructor<object>[]>(
|
|
270
|
+
...ctors: T
|
|
271
|
+
): EntityRefsTuple<T> => {
|
|
272
|
+
return ctors.map(ctor => entityRef(ctor)) as EntityRefsTuple<T>;
|
|
273
|
+
};
|
package/src/decorators/index.ts
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Decorators for defining entities, columns, and relations in Metal ORM.
|
|
3
|
-
*/
|
|
4
|
-
export * from './entity.js';
|
|
5
|
-
export * from './column-decorator.js';
|
|
6
|
-
export * from './relations.js';
|
|
7
|
-
export * from './bootstrap.js';
|
|
8
|
-
export { getDecoratorMetadata } from './decorator-metadata.js';
|
|
9
|
-
|
|
10
|
-
// Entity Materialization - convert query results to real class instances
|
|
11
|
-
export { materializeAs, DefaultEntityMaterializer, PrototypeMaterializationStrategy, ConstructorMaterializationStrategy } from '../orm/entity-materializer.js';
|
|
12
|
-
export type { EntityMaterializer, EntityMaterializationStrategy } from '../orm/entity-materializer.js';
|
|
1
|
+
/**
|
|
2
|
+
* Decorators for defining entities, columns, and relations in Metal ORM.
|
|
3
|
+
*/
|
|
4
|
+
export * from './entity.js';
|
|
5
|
+
export * from './column-decorator.js';
|
|
6
|
+
export * from './relations.js';
|
|
7
|
+
export * from './bootstrap.js';
|
|
8
|
+
export { getDecoratorMetadata } from './decorator-metadata.js';
|
|
9
|
+
|
|
10
|
+
// Entity Materialization - convert query results to real class instances
|
|
11
|
+
export { materializeAs, DefaultEntityMaterializer, PrototypeMaterializationStrategy, ConstructorMaterializationStrategy } from '../orm/entity-materializer.js';
|
|
12
|
+
export type { EntityMaterializer, EntityMaterializationStrategy } from '../orm/entity-materializer.js';
|
|
@@ -125,16 +125,16 @@ const instantiateWrapper = <TTable extends TableDef>(
|
|
|
125
125
|
createEntity: RelationEntityFactory
|
|
126
126
|
): HasManyCollection<unknown> | HasOneReference<object> | BelongsToReference<object> | ManyToManyCollection<unknown> | undefined => {
|
|
127
127
|
const metaBase = meta as unknown as EntityMeta<TableDef>;
|
|
128
|
-
const
|
|
129
|
-
|
|
130
|
-
|
|
128
|
+
const loadCached = <T extends Map<string, unknown>>(factory: () => Promise<T>) =>
|
|
129
|
+
relationLoaderCache(metaBase, relationName, factory);
|
|
130
|
+
const resolveOptions = () => meta.lazyRelationOptions.get(relationName);
|
|
131
131
|
switch (relation.type) {
|
|
132
132
|
case RelationKinds.HasOne: {
|
|
133
133
|
const hasOne = relation as HasOneRelation;
|
|
134
|
-
const localKey = hasOne.localKey || findPrimaryKey(meta.table);
|
|
135
|
-
const loader = () => loadCached(() =>
|
|
136
|
-
loadHasOneRelation(meta.ctx, meta.table, relationName, hasOne,
|
|
137
|
-
);
|
|
134
|
+
const localKey = hasOne.localKey || findPrimaryKey(meta.table);
|
|
135
|
+
const loader = () => loadCached(() =>
|
|
136
|
+
loadHasOneRelation(meta.ctx, meta.table, relationName, hasOne, resolveOptions())
|
|
137
|
+
);
|
|
138
138
|
return new DefaultHasOneReference(
|
|
139
139
|
meta.ctx,
|
|
140
140
|
metaBase,
|
|
@@ -149,10 +149,10 @@ const instantiateWrapper = <TTable extends TableDef>(
|
|
|
149
149
|
}
|
|
150
150
|
case RelationKinds.HasMany: {
|
|
151
151
|
const hasMany = relation as HasManyRelation;
|
|
152
|
-
const localKey = hasMany.localKey || findPrimaryKey(meta.table);
|
|
153
|
-
const loader = () => loadCached(() =>
|
|
154
|
-
loadHasManyRelation(meta.ctx, meta.table, relationName, hasMany,
|
|
155
|
-
);
|
|
152
|
+
const localKey = hasMany.localKey || findPrimaryKey(meta.table);
|
|
153
|
+
const loader = () => loadCached(() =>
|
|
154
|
+
loadHasManyRelation(meta.ctx, meta.table, relationName, hasMany, resolveOptions())
|
|
155
|
+
);
|
|
156
156
|
return new DefaultHasManyCollection(
|
|
157
157
|
meta.ctx,
|
|
158
158
|
metaBase,
|
|
@@ -167,10 +167,10 @@ const instantiateWrapper = <TTable extends TableDef>(
|
|
|
167
167
|
}
|
|
168
168
|
case RelationKinds.BelongsTo: {
|
|
169
169
|
const belongsTo = relation as BelongsToRelation;
|
|
170
|
-
const targetKey = belongsTo.localKey || findPrimaryKey(belongsTo.target);
|
|
171
|
-
const loader = () => loadCached(() =>
|
|
172
|
-
loadBelongsToRelation(meta.ctx, meta.table, relationName, belongsTo,
|
|
173
|
-
);
|
|
170
|
+
const targetKey = belongsTo.localKey || findPrimaryKey(belongsTo.target);
|
|
171
|
+
const loader = () => loadCached(() =>
|
|
172
|
+
loadBelongsToRelation(meta.ctx, meta.table, relationName, belongsTo, resolveOptions())
|
|
173
|
+
);
|
|
174
174
|
return new DefaultBelongsToReference(
|
|
175
175
|
meta.ctx,
|
|
176
176
|
metaBase,
|
|
@@ -185,10 +185,10 @@ const instantiateWrapper = <TTable extends TableDef>(
|
|
|
185
185
|
}
|
|
186
186
|
case RelationKinds.BelongsToMany: {
|
|
187
187
|
const many = relation as BelongsToManyRelation;
|
|
188
|
-
const localKey = many.localKey || findPrimaryKey(meta.table);
|
|
189
|
-
const loader = () => loadCached(() =>
|
|
190
|
-
loadBelongsToManyRelation(meta.ctx, meta.table, relationName, many,
|
|
191
|
-
);
|
|
188
|
+
const localKey = many.localKey || findPrimaryKey(meta.table);
|
|
189
|
+
const loader = () => loadCached(() =>
|
|
190
|
+
loadBelongsToManyRelation(meta.ctx, meta.table, relationName, many, resolveOptions())
|
|
191
|
+
);
|
|
192
192
|
return new DefaultManyToManyCollection(
|
|
193
193
|
meta.ctx,
|
|
194
194
|
metaBase,
|
package/src/orm/execute.ts
CHANGED
|
@@ -12,8 +12,9 @@ import {
|
|
|
12
12
|
import { EntityContext } from './entity-context.js';
|
|
13
13
|
import { ExecutionContext } from './execution-context.js';
|
|
14
14
|
import { HydrationContext } from './hydration-context.js';
|
|
15
|
-
import { RelationIncludeOptions } from '../query-builder/relation-types.js';
|
|
16
|
-
import { getEntityMeta, RelationKey } from './entity-meta.js';
|
|
15
|
+
import { RelationIncludeOptions } from '../query-builder/relation-types.js';
|
|
16
|
+
import { getEntityMeta, RelationKey } from './entity-meta.js';
|
|
17
|
+
import { preloadRelationIncludes } from './relation-preload.js';
|
|
17
18
|
import {
|
|
18
19
|
loadHasManyRelation,
|
|
19
20
|
loadHasOneRelation,
|
|
@@ -44,23 +45,26 @@ const executeWithContexts = async <TTable extends TableDef>(
|
|
|
44
45
|
qb: SelectQueryBuilder<unknown, TTable>
|
|
45
46
|
): Promise<EntityInstance<TTable>[]> => {
|
|
46
47
|
const ast = qb.getAST();
|
|
47
|
-
const compiled = execCtx.dialect.compileSelect(ast);
|
|
48
|
-
const executed = await execCtx.interceptors.run({ sql: compiled.sql, params: compiled.params }, execCtx.executor);
|
|
49
|
-
const rows = flattenResults(executed);
|
|
50
|
-
const lazyRelations = qb.getLazyRelations() as RelationKey<TTable>[];
|
|
51
|
-
const lazyRelationOptions = qb.getLazyRelationOptions();
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
48
|
+
const compiled = execCtx.dialect.compileSelect(ast);
|
|
49
|
+
const executed = await execCtx.interceptors.run({ sql: compiled.sql, params: compiled.params }, execCtx.executor);
|
|
50
|
+
const rows = flattenResults(executed);
|
|
51
|
+
const lazyRelations = qb.getLazyRelations() as RelationKey<TTable>[];
|
|
52
|
+
const lazyRelationOptions = qb.getLazyRelationOptions();
|
|
53
|
+
const includeTree = qb.getIncludeTree();
|
|
54
|
+
|
|
55
|
+
if (ast.setOps && ast.setOps.length > 0) {
|
|
56
|
+
const proxies = rows.map(row => createEntityProxy(entityCtx, qb.getTable(), row, lazyRelations, lazyRelationOptions));
|
|
57
|
+
await loadLazyRelationsForTable(entityCtx, qb.getTable(), lazyRelations, lazyRelationOptions);
|
|
58
|
+
await preloadRelationIncludes(proxies as Record<string, unknown>[], includeTree);
|
|
59
|
+
return proxies;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const hydrated = hydrateRows(rows, qb.getHydrationPlan());
|
|
63
|
+
const entities = hydrated.map(row => createEntityFromRow(entityCtx, qb.getTable(), row, lazyRelations, lazyRelationOptions));
|
|
64
|
+
await loadLazyRelationsForTable(entityCtx, qb.getTable(), lazyRelations, lazyRelationOptions);
|
|
65
|
+
await preloadRelationIncludes(entities as Record<string, unknown>[], includeTree);
|
|
66
|
+
return entities;
|
|
67
|
+
};
|
|
64
68
|
|
|
65
69
|
const executePlainWithContexts = async <TTable extends TableDef>(
|
|
66
70
|
execCtx: ExecutionContext,
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import type { NormalizedRelationIncludeTree } from '../query-builder/relation-include-tree.js';
|
|
2
|
+
import type { RelationIncludeOptions } from '../query-builder/relation-types.js';
|
|
3
|
+
import { getEntityMeta } from './entity-meta.js';
|
|
4
|
+
|
|
5
|
+
type LoadableRelation = {
|
|
6
|
+
load?: () => Promise<unknown>;
|
|
7
|
+
getItems?: () => unknown;
|
|
8
|
+
get?: () => unknown;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
const collectEntities = (value: unknown): Record<string, unknown>[] => {
|
|
12
|
+
if (!value) return [];
|
|
13
|
+
if (Array.isArray(value)) {
|
|
14
|
+
return value.filter(item => item && typeof item === 'object') as Record<string, unknown>[];
|
|
15
|
+
}
|
|
16
|
+
if (typeof value === 'object') {
|
|
17
|
+
return [value as Record<string, unknown>];
|
|
18
|
+
}
|
|
19
|
+
return [];
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
const loadRelation = async (
|
|
23
|
+
entity: Record<string, unknown>,
|
|
24
|
+
relationName: string
|
|
25
|
+
): Promise<Record<string, unknown>[]> => {
|
|
26
|
+
const wrapper = entity[relationName] as LoadableRelation | undefined;
|
|
27
|
+
if (!wrapper) return [];
|
|
28
|
+
|
|
29
|
+
if (typeof wrapper.load === 'function') {
|
|
30
|
+
const loaded = await wrapper.load();
|
|
31
|
+
return collectEntities(loaded);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if (typeof wrapper.getItems === 'function') {
|
|
35
|
+
return collectEntities(wrapper.getItems());
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
if (typeof wrapper.get === 'function') {
|
|
39
|
+
return collectEntities(wrapper.get());
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return collectEntities(wrapper);
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
const setLazyOptionsIfEmpty = (
|
|
46
|
+
entity: Record<string, unknown>,
|
|
47
|
+
relationName: string,
|
|
48
|
+
options?: RelationIncludeOptions
|
|
49
|
+
): void => {
|
|
50
|
+
if (!options) return;
|
|
51
|
+
const meta = getEntityMeta(entity);
|
|
52
|
+
if (!meta || meta.lazyRelationOptions.has(relationName)) return;
|
|
53
|
+
meta.lazyRelationOptions.set(relationName, options);
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
export const preloadRelationIncludes = async (
|
|
57
|
+
entities: Record<string, unknown>[],
|
|
58
|
+
includeTree: NormalizedRelationIncludeTree,
|
|
59
|
+
depth = 0
|
|
60
|
+
): Promise<void> => {
|
|
61
|
+
if (!entities.length) return;
|
|
62
|
+
const entries = Object.entries(includeTree);
|
|
63
|
+
if (!entries.length) return;
|
|
64
|
+
|
|
65
|
+
for (const [relationName, node] of entries) {
|
|
66
|
+
const shouldLoad = depth > 0 || Boolean(node.include);
|
|
67
|
+
if (!shouldLoad) continue;
|
|
68
|
+
|
|
69
|
+
for (const entity of entities) {
|
|
70
|
+
setLazyOptionsIfEmpty(entity, relationName, node.options);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const loaded = await Promise.all(
|
|
74
|
+
entities.map(entity => loadRelation(entity, relationName))
|
|
75
|
+
);
|
|
76
|
+
const relatedEntities = loaded.flat();
|
|
77
|
+
|
|
78
|
+
if (node.include && relatedEntities.length) {
|
|
79
|
+
await preloadRelationIncludes(relatedEntities, node.include, depth + 1);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
};
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import type { TableDef } from '../schema/table.js';
|
|
2
|
+
import type { RelationDef } from '../schema/relation.js';
|
|
3
|
+
import type { RelationMap, RelationTargetTable } from '../schema/types.js';
|
|
4
|
+
import type { RelationIncludeOptions, TypedRelationIncludeOptions } from './relation-types.js';
|
|
5
|
+
|
|
6
|
+
export type RelationIncludeInput<TTable extends TableDef> = {
|
|
7
|
+
[K in keyof RelationMap<TTable> & string]?: true | RelationIncludeNodeInput<TTable['relations'][K]>;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
export type RelationIncludeNodeInput<TRel extends RelationDef> =
|
|
11
|
+
TypedRelationIncludeOptions<TRel> & {
|
|
12
|
+
include?: RelationIncludeInput<RelationTargetTable<TRel>>;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export type NormalizedRelationIncludeNode = {
|
|
16
|
+
options?: RelationIncludeOptions;
|
|
17
|
+
include?: NormalizedRelationIncludeTree;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export type NormalizedRelationIncludeTree = Record<string, NormalizedRelationIncludeNode>;
|
|
21
|
+
|
|
22
|
+
const isObject = (value: unknown): value is Record<string, unknown> =>
|
|
23
|
+
Boolean(value && typeof value === 'object');
|
|
24
|
+
|
|
25
|
+
export const normalizeRelationIncludeNode = <TRel extends RelationDef>(
|
|
26
|
+
value?: true | RelationIncludeNodeInput<TRel>
|
|
27
|
+
): NormalizedRelationIncludeNode => {
|
|
28
|
+
if (!value || value === true) {
|
|
29
|
+
return {};
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
if (!isObject(value)) {
|
|
33
|
+
return {};
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const { include, ...rest } = value as Record<string, unknown>;
|
|
37
|
+
const options = Object.keys(rest).length ? (rest as RelationIncludeOptions) : undefined;
|
|
38
|
+
const normalizedInclude = isObject(include)
|
|
39
|
+
? normalizeRelationInclude(include as RelationIncludeInput<TableDef>)
|
|
40
|
+
: undefined;
|
|
41
|
+
|
|
42
|
+
if (normalizedInclude && Object.keys(normalizedInclude).length > 0) {
|
|
43
|
+
return { options, include: normalizedInclude };
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return { options };
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
export const normalizeRelationInclude = (
|
|
50
|
+
input?: RelationIncludeInput<TableDef>
|
|
51
|
+
): NormalizedRelationIncludeTree => {
|
|
52
|
+
if (!input) return {};
|
|
53
|
+
|
|
54
|
+
const tree: NormalizedRelationIncludeTree = {};
|
|
55
|
+
for (const [key, value] of Object.entries(input)) {
|
|
56
|
+
tree[key] = normalizeRelationIncludeNode(value as RelationIncludeNodeInput<RelationDef> | true);
|
|
57
|
+
}
|
|
58
|
+
return tree;
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
export const mergeRelationIncludeTrees = (
|
|
62
|
+
base: NormalizedRelationIncludeTree,
|
|
63
|
+
next: NormalizedRelationIncludeTree
|
|
64
|
+
): NormalizedRelationIncludeTree => {
|
|
65
|
+
const merged: NormalizedRelationIncludeTree = { ...base };
|
|
66
|
+
|
|
67
|
+
for (const [key, node] of Object.entries(next)) {
|
|
68
|
+
const existing = merged[key];
|
|
69
|
+
if (!existing) {
|
|
70
|
+
merged[key] = node;
|
|
71
|
+
continue;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const include = existing.include && node.include
|
|
75
|
+
? mergeRelationIncludeTrees(existing.include, node.include)
|
|
76
|
+
: (node.include ?? existing.include);
|
|
77
|
+
|
|
78
|
+
merged[key] = {
|
|
79
|
+
options: node.options ?? existing.options,
|
|
80
|
+
...(include ? { include } : {})
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return merged;
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
export const cloneRelationIncludeTree = (
|
|
88
|
+
tree: NormalizedRelationIncludeTree
|
|
89
|
+
): NormalizedRelationIncludeTree => {
|
|
90
|
+
const cloned: NormalizedRelationIncludeTree = {};
|
|
91
|
+
for (const [key, node] of Object.entries(tree)) {
|
|
92
|
+
cloned[key] = {
|
|
93
|
+
options: node.options,
|
|
94
|
+
...(node.include ? { include: cloneRelationIncludeTree(node.include) } : {})
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
return cloned;
|
|
98
|
+
};
|