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/README.md
CHANGED
|
@@ -549,16 +549,16 @@ const [user] = await selectFromEntity(User)
|
|
|
549
549
|
.where(eq(U.id, 1))
|
|
550
550
|
.execute(session); // user is an actual instance of the User class!
|
|
551
551
|
|
|
552
|
-
// Use executePlain() if you want raw POJOs instead of class instances
|
|
553
|
-
const [rawUser] = await selectFromEntity(User).executePlain(session);
|
|
554
|
-
|
|
555
|
-
user.posts.add({ title: 'From decorators' });
|
|
556
|
-
await session.commit();
|
|
557
|
-
```
|
|
558
|
-
|
|
559
|
-
Note: relation helpers like `add`/`attach` are only available on tracked entities returned by `execute(session)`. `executePlain()` returns POJOs without relation wrappers. Make sure the primary key (e.g. `id`) is selected so relation adds can link correctly.
|
|
560
|
-
|
|
561
|
-
Tip: to keep selections terse, use `select`, `include` (with `columns`), or the `sel`/`esel` helpers instead of spelling `table.columns.*` over and over. By default, `selectFromEntity` selects all columns if you don't specify any.
|
|
552
|
+
// Use executePlain() if you want raw POJOs instead of class instances
|
|
553
|
+
const [rawUser] = await selectFromEntity(User).executePlain(session);
|
|
554
|
+
|
|
555
|
+
user.posts.add({ title: 'From decorators' });
|
|
556
|
+
await session.commit();
|
|
557
|
+
```
|
|
558
|
+
|
|
559
|
+
Note: relation helpers like `add`/`attach` are only available on tracked entities returned by `execute(session)`. `executePlain()` returns POJOs without relation wrappers. Make sure the primary key (e.g. `id`) is selected so relation adds can link correctly.
|
|
560
|
+
|
|
561
|
+
Tip: to keep selections terse, use `select`, `include` (with `columns`), or the `sel`/`esel` helpers instead of spelling `table.columns.*` over and over. By default, `selectFromEntity` selects all columns if you don't specify any.
|
|
562
562
|
|
|
563
563
|
|
|
564
564
|
This level is nice when:
|
package/dist/index.cjs
CHANGED
|
@@ -144,6 +144,7 @@ __export(index_exports, {
|
|
|
144
144
|
div: () => div,
|
|
145
145
|
endOfMonth: () => endOfMonth,
|
|
146
146
|
entityRef: () => entityRef,
|
|
147
|
+
entityRefs: () => entityRefs,
|
|
147
148
|
eq: () => eq,
|
|
148
149
|
esel: () => esel,
|
|
149
150
|
executeHydrated: () => executeHydrated,
|
|
@@ -4893,6 +4894,58 @@ var resolveSelectQueryBuilderDependencies = (overrides = {}) => {
|
|
|
4893
4894
|
};
|
|
4894
4895
|
var defaultSelectQueryBuilderDependencies = resolveSelectQueryBuilderDependencies();
|
|
4895
4896
|
|
|
4897
|
+
// src/query-builder/relation-include-tree.ts
|
|
4898
|
+
var isObject = (value) => Boolean(value && typeof value === "object");
|
|
4899
|
+
var normalizeRelationIncludeNode = (value) => {
|
|
4900
|
+
if (!value || value === true) {
|
|
4901
|
+
return {};
|
|
4902
|
+
}
|
|
4903
|
+
if (!isObject(value)) {
|
|
4904
|
+
return {};
|
|
4905
|
+
}
|
|
4906
|
+
const { include, ...rest } = value;
|
|
4907
|
+
const options = Object.keys(rest).length ? rest : void 0;
|
|
4908
|
+
const normalizedInclude = isObject(include) ? normalizeRelationInclude(include) : void 0;
|
|
4909
|
+
if (normalizedInclude && Object.keys(normalizedInclude).length > 0) {
|
|
4910
|
+
return { options, include: normalizedInclude };
|
|
4911
|
+
}
|
|
4912
|
+
return { options };
|
|
4913
|
+
};
|
|
4914
|
+
var normalizeRelationInclude = (input) => {
|
|
4915
|
+
if (!input) return {};
|
|
4916
|
+
const tree = {};
|
|
4917
|
+
for (const [key, value] of Object.entries(input)) {
|
|
4918
|
+
tree[key] = normalizeRelationIncludeNode(value);
|
|
4919
|
+
}
|
|
4920
|
+
return tree;
|
|
4921
|
+
};
|
|
4922
|
+
var mergeRelationIncludeTrees = (base, next) => {
|
|
4923
|
+
const merged = { ...base };
|
|
4924
|
+
for (const [key, node] of Object.entries(next)) {
|
|
4925
|
+
const existing = merged[key];
|
|
4926
|
+
if (!existing) {
|
|
4927
|
+
merged[key] = node;
|
|
4928
|
+
continue;
|
|
4929
|
+
}
|
|
4930
|
+
const include = existing.include && node.include ? mergeRelationIncludeTrees(existing.include, node.include) : node.include ?? existing.include;
|
|
4931
|
+
merged[key] = {
|
|
4932
|
+
options: node.options ?? existing.options,
|
|
4933
|
+
...include ? { include } : {}
|
|
4934
|
+
};
|
|
4935
|
+
}
|
|
4936
|
+
return merged;
|
|
4937
|
+
};
|
|
4938
|
+
var cloneRelationIncludeTree = (tree) => {
|
|
4939
|
+
const cloned = {};
|
|
4940
|
+
for (const [key, node] of Object.entries(tree)) {
|
|
4941
|
+
cloned[key] = {
|
|
4942
|
+
options: node.options,
|
|
4943
|
+
...node.include ? { include: cloneRelationIncludeTree(node.include) } : {}
|
|
4944
|
+
};
|
|
4945
|
+
}
|
|
4946
|
+
return cloned;
|
|
4947
|
+
};
|
|
4948
|
+
|
|
4896
4949
|
// src/orm/hydration.ts
|
|
4897
4950
|
var hydrateRows = (rows, plan) => {
|
|
4898
4951
|
if (!plan || !rows.length) return rows;
|
|
@@ -6025,14 +6078,14 @@ var getRelationWrapper = (meta, relationName, owner, createEntity) => {
|
|
|
6025
6078
|
};
|
|
6026
6079
|
var instantiateWrapper = (meta, relationName, relation, owner, createEntity) => {
|
|
6027
6080
|
const metaBase = meta;
|
|
6028
|
-
const lazyOptions = meta.lazyRelationOptions.get(relationName);
|
|
6029
6081
|
const loadCached = (factory) => relationLoaderCache(metaBase, relationName, factory);
|
|
6082
|
+
const resolveOptions = () => meta.lazyRelationOptions.get(relationName);
|
|
6030
6083
|
switch (relation.type) {
|
|
6031
6084
|
case RelationKinds.HasOne: {
|
|
6032
6085
|
const hasOne2 = relation;
|
|
6033
6086
|
const localKey = hasOne2.localKey || findPrimaryKey(meta.table);
|
|
6034
6087
|
const loader = () => loadCached(
|
|
6035
|
-
() => loadHasOneRelation(meta.ctx, meta.table, relationName, hasOne2,
|
|
6088
|
+
() => loadHasOneRelation(meta.ctx, meta.table, relationName, hasOne2, resolveOptions())
|
|
6036
6089
|
);
|
|
6037
6090
|
return new DefaultHasOneReference(
|
|
6038
6091
|
meta.ctx,
|
|
@@ -6050,7 +6103,7 @@ var instantiateWrapper = (meta, relationName, relation, owner, createEntity) =>
|
|
|
6050
6103
|
const hasMany2 = relation;
|
|
6051
6104
|
const localKey = hasMany2.localKey || findPrimaryKey(meta.table);
|
|
6052
6105
|
const loader = () => loadCached(
|
|
6053
|
-
() => loadHasManyRelation(meta.ctx, meta.table, relationName, hasMany2,
|
|
6106
|
+
() => loadHasManyRelation(meta.ctx, meta.table, relationName, hasMany2, resolveOptions())
|
|
6054
6107
|
);
|
|
6055
6108
|
return new DefaultHasManyCollection(
|
|
6056
6109
|
meta.ctx,
|
|
@@ -6068,7 +6121,7 @@ var instantiateWrapper = (meta, relationName, relation, owner, createEntity) =>
|
|
|
6068
6121
|
const belongsTo2 = relation;
|
|
6069
6122
|
const targetKey = belongsTo2.localKey || findPrimaryKey(belongsTo2.target);
|
|
6070
6123
|
const loader = () => loadCached(
|
|
6071
|
-
() => loadBelongsToRelation(meta.ctx, meta.table, relationName, belongsTo2,
|
|
6124
|
+
() => loadBelongsToRelation(meta.ctx, meta.table, relationName, belongsTo2, resolveOptions())
|
|
6072
6125
|
);
|
|
6073
6126
|
return new DefaultBelongsToReference(
|
|
6074
6127
|
meta.ctx,
|
|
@@ -6086,7 +6139,7 @@ var instantiateWrapper = (meta, relationName, relation, owner, createEntity) =>
|
|
|
6086
6139
|
const many = relation;
|
|
6087
6140
|
const localKey = many.localKey || findPrimaryKey(meta.table);
|
|
6088
6141
|
const loader = () => loadCached(
|
|
6089
|
-
() => loadBelongsToManyRelation(meta.ctx, meta.table, relationName, many,
|
|
6142
|
+
() => loadBelongsToManyRelation(meta.ctx, meta.table, relationName, many, resolveOptions())
|
|
6090
6143
|
);
|
|
6091
6144
|
return new DefaultManyToManyCollection(
|
|
6092
6145
|
meta.ctx,
|
|
@@ -6193,6 +6246,58 @@ var createEntityFromRow = (ctx, table, row, lazyRelations = [], lazyRelationOpti
|
|
|
6193
6246
|
return entity;
|
|
6194
6247
|
};
|
|
6195
6248
|
|
|
6249
|
+
// src/orm/relation-preload.ts
|
|
6250
|
+
var collectEntities = (value) => {
|
|
6251
|
+
if (!value) return [];
|
|
6252
|
+
if (Array.isArray(value)) {
|
|
6253
|
+
return value.filter((item) => item && typeof item === "object");
|
|
6254
|
+
}
|
|
6255
|
+
if (typeof value === "object") {
|
|
6256
|
+
return [value];
|
|
6257
|
+
}
|
|
6258
|
+
return [];
|
|
6259
|
+
};
|
|
6260
|
+
var loadRelation = async (entity, relationName) => {
|
|
6261
|
+
const wrapper = entity[relationName];
|
|
6262
|
+
if (!wrapper) return [];
|
|
6263
|
+
if (typeof wrapper.load === "function") {
|
|
6264
|
+
const loaded = await wrapper.load();
|
|
6265
|
+
return collectEntities(loaded);
|
|
6266
|
+
}
|
|
6267
|
+
if (typeof wrapper.getItems === "function") {
|
|
6268
|
+
return collectEntities(wrapper.getItems());
|
|
6269
|
+
}
|
|
6270
|
+
if (typeof wrapper.get === "function") {
|
|
6271
|
+
return collectEntities(wrapper.get());
|
|
6272
|
+
}
|
|
6273
|
+
return collectEntities(wrapper);
|
|
6274
|
+
};
|
|
6275
|
+
var setLazyOptionsIfEmpty = (entity, relationName, options) => {
|
|
6276
|
+
if (!options) return;
|
|
6277
|
+
const meta = getEntityMeta(entity);
|
|
6278
|
+
if (!meta || meta.lazyRelationOptions.has(relationName)) return;
|
|
6279
|
+
meta.lazyRelationOptions.set(relationName, options);
|
|
6280
|
+
};
|
|
6281
|
+
var preloadRelationIncludes = async (entities, includeTree, depth = 0) => {
|
|
6282
|
+
if (!entities.length) return;
|
|
6283
|
+
const entries = Object.entries(includeTree);
|
|
6284
|
+
if (!entries.length) return;
|
|
6285
|
+
for (const [relationName, node] of entries) {
|
|
6286
|
+
const shouldLoad = depth > 0 || Boolean(node.include);
|
|
6287
|
+
if (!shouldLoad) continue;
|
|
6288
|
+
for (const entity of entities) {
|
|
6289
|
+
setLazyOptionsIfEmpty(entity, relationName, node.options);
|
|
6290
|
+
}
|
|
6291
|
+
const loaded = await Promise.all(
|
|
6292
|
+
entities.map((entity) => loadRelation(entity, relationName))
|
|
6293
|
+
);
|
|
6294
|
+
const relatedEntities = loaded.flat();
|
|
6295
|
+
if (node.include && relatedEntities.length) {
|
|
6296
|
+
await preloadRelationIncludes(relatedEntities, node.include, depth + 1);
|
|
6297
|
+
}
|
|
6298
|
+
}
|
|
6299
|
+
};
|
|
6300
|
+
|
|
6196
6301
|
// src/orm/execute.ts
|
|
6197
6302
|
var flattenResults = (results) => {
|
|
6198
6303
|
const rows = [];
|
|
@@ -6215,14 +6320,17 @@ var executeWithContexts = async (execCtx, entityCtx, qb) => {
|
|
|
6215
6320
|
const rows = flattenResults(executed);
|
|
6216
6321
|
const lazyRelations = qb.getLazyRelations();
|
|
6217
6322
|
const lazyRelationOptions = qb.getLazyRelationOptions();
|
|
6323
|
+
const includeTree = qb.getIncludeTree();
|
|
6218
6324
|
if (ast.setOps && ast.setOps.length > 0) {
|
|
6219
6325
|
const proxies = rows.map((row) => createEntityProxy(entityCtx, qb.getTable(), row, lazyRelations, lazyRelationOptions));
|
|
6220
6326
|
await loadLazyRelationsForTable(entityCtx, qb.getTable(), lazyRelations, lazyRelationOptions);
|
|
6327
|
+
await preloadRelationIncludes(proxies, includeTree);
|
|
6221
6328
|
return proxies;
|
|
6222
6329
|
}
|
|
6223
6330
|
const hydrated = hydrateRows(rows, qb.getHydrationPlan());
|
|
6224
6331
|
const entities = hydrated.map((row) => createEntityFromRow(entityCtx, qb.getTable(), row, lazyRelations, lazyRelationOptions));
|
|
6225
6332
|
await loadLazyRelationsForTable(entityCtx, qb.getTable(), lazyRelations, lazyRelationOptions);
|
|
6333
|
+
await preloadRelationIncludes(entities, includeTree);
|
|
6226
6334
|
return entities;
|
|
6227
6335
|
};
|
|
6228
6336
|
var executePlainWithContexts = async (execCtx, qb) => {
|
|
@@ -6849,6 +6957,7 @@ var SelectQueryBuilder = class _SelectQueryBuilder {
|
|
|
6849
6957
|
lazyRelations;
|
|
6850
6958
|
lazyRelationOptions;
|
|
6851
6959
|
entityConstructor;
|
|
6960
|
+
includeTree;
|
|
6852
6961
|
/**
|
|
6853
6962
|
* Creates a new SelectQueryBuilder instance
|
|
6854
6963
|
* @param table - Table definition to query
|
|
@@ -6856,7 +6965,7 @@ var SelectQueryBuilder = class _SelectQueryBuilder {
|
|
|
6856
6965
|
* @param hydration - Optional hydration manager
|
|
6857
6966
|
* @param dependencies - Optional query builder dependencies
|
|
6858
6967
|
*/
|
|
6859
|
-
constructor(table, state, hydration, dependencies, lazyRelations, lazyRelationOptions, entityConstructor) {
|
|
6968
|
+
constructor(table, state, hydration, dependencies, lazyRelations, lazyRelationOptions, entityConstructor, includeTree) {
|
|
6860
6969
|
const deps = resolveSelectQueryBuilderDependencies(dependencies);
|
|
6861
6970
|
this.env = { table, deps };
|
|
6862
6971
|
const createAstService = (nextState) => deps.createQueryAstService(table, nextState);
|
|
@@ -6869,6 +6978,7 @@ var SelectQueryBuilder = class _SelectQueryBuilder {
|
|
|
6869
6978
|
this.lazyRelations = new Set(lazyRelations ?? []);
|
|
6870
6979
|
this.lazyRelationOptions = new Map(lazyRelationOptions ?? []);
|
|
6871
6980
|
this.entityConstructor = entityConstructor;
|
|
6981
|
+
this.includeTree = includeTree ?? {};
|
|
6872
6982
|
this.columnSelector = deps.createColumnSelector(this.env);
|
|
6873
6983
|
const relationManager = deps.createRelationManager(this.env);
|
|
6874
6984
|
this.fromFacet = new SelectFromFacet(this.env, createAstService);
|
|
@@ -6885,7 +6995,7 @@ var SelectQueryBuilder = class _SelectQueryBuilder {
|
|
|
6885
6995
|
* @param lazyRelations - Updated lazy relations set
|
|
6886
6996
|
* @returns New SelectQueryBuilder instance
|
|
6887
6997
|
*/
|
|
6888
|
-
clone(context = this.context, lazyRelations = new Set(this.lazyRelations), lazyRelationOptions = new Map(this.lazyRelationOptions)) {
|
|
6998
|
+
clone(context = this.context, lazyRelations = new Set(this.lazyRelations), lazyRelationOptions = new Map(this.lazyRelationOptions), includeTree = this.includeTree) {
|
|
6889
6999
|
return new _SelectQueryBuilder(
|
|
6890
7000
|
this.env.table,
|
|
6891
7001
|
context.state,
|
|
@@ -6893,7 +7003,8 @@ var SelectQueryBuilder = class _SelectQueryBuilder {
|
|
|
6893
7003
|
this.env.deps,
|
|
6894
7004
|
lazyRelations,
|
|
6895
7005
|
lazyRelationOptions,
|
|
6896
|
-
this.entityConstructor
|
|
7006
|
+
this.entityConstructor,
|
|
7007
|
+
includeTree
|
|
6897
7008
|
);
|
|
6898
7009
|
}
|
|
6899
7010
|
/**
|
|
@@ -7181,24 +7292,22 @@ var SelectQueryBuilder = class _SelectQueryBuilder {
|
|
|
7181
7292
|
const nextContext = this.relationFacet.joinRelation(this.context, relationName, joinKind, extraCondition);
|
|
7182
7293
|
return this.clone(nextContext);
|
|
7183
7294
|
}
|
|
7184
|
-
|
|
7185
|
-
|
|
7186
|
-
|
|
7187
|
-
|
|
7188
|
-
|
|
7189
|
-
|
|
7190
|
-
|
|
7191
|
-
|
|
7192
|
-
|
|
7193
|
-
|
|
7194
|
-
|
|
7195
|
-
|
|
7196
|
-
|
|
7197
|
-
|
|
7198
|
-
|
|
7199
|
-
|
|
7200
|
-
const nextContext = this.relationFacet.include(this.context, relationName, options);
|
|
7201
|
-
return this.clone(nextContext);
|
|
7295
|
+
include(relationNameOrRelations, options) {
|
|
7296
|
+
if (typeof relationNameOrRelations === "object" && relationNameOrRelations !== null) {
|
|
7297
|
+
const normalized = normalizeRelationInclude(relationNameOrRelations);
|
|
7298
|
+
let nextContext2 = this.context;
|
|
7299
|
+
for (const [relationName2, node] of Object.entries(normalized)) {
|
|
7300
|
+
nextContext2 = this.relationFacet.include(nextContext2, relationName2, node.options);
|
|
7301
|
+
}
|
|
7302
|
+
const nextTree2 = mergeRelationIncludeTrees(this.includeTree, normalized);
|
|
7303
|
+
return this.clone(nextContext2, void 0, void 0, nextTree2);
|
|
7304
|
+
}
|
|
7305
|
+
const relationName = relationNameOrRelations;
|
|
7306
|
+
const normalizedNode = normalizeRelationIncludeNode(options);
|
|
7307
|
+
const nextContext = this.relationFacet.include(this.context, relationName, normalizedNode.options);
|
|
7308
|
+
const shouldStore = Boolean(normalizedNode.include || normalizedNode.options);
|
|
7309
|
+
const nextTree = shouldStore ? mergeRelationIncludeTrees(this.includeTree, { [relationName]: normalizedNode }) : this.includeTree;
|
|
7310
|
+
return this.clone(nextContext, void 0, void 0, nextTree);
|
|
7202
7311
|
}
|
|
7203
7312
|
/**
|
|
7204
7313
|
* Includes a relation lazily in the query results
|
|
@@ -7282,6 +7391,12 @@ var SelectQueryBuilder = class _SelectQueryBuilder {
|
|
|
7282
7391
|
getLazyRelationOptions() {
|
|
7283
7392
|
return new Map(this.lazyRelationOptions);
|
|
7284
7393
|
}
|
|
7394
|
+
/**
|
|
7395
|
+
* Gets normalized nested include information for runtime preloading.
|
|
7396
|
+
*/
|
|
7397
|
+
getIncludeTree() {
|
|
7398
|
+
return cloneRelationIncludeTree(this.includeTree);
|
|
7399
|
+
}
|
|
7285
7400
|
/**
|
|
7286
7401
|
* Gets the table definition for this query builder
|
|
7287
7402
|
* @returns Table definition
|
|
@@ -7818,6 +7933,9 @@ var entityRef = (ctor) => {
|
|
|
7818
7933
|
}
|
|
7819
7934
|
return tableRef(table);
|
|
7820
7935
|
};
|
|
7936
|
+
var entityRefs = (...ctors) => {
|
|
7937
|
+
return ctors.map((ctor) => entityRef(ctor));
|
|
7938
|
+
};
|
|
7821
7939
|
|
|
7822
7940
|
// src/query-builder/select-helpers.ts
|
|
7823
7941
|
function sel(table, ...cols) {
|
|
@@ -13245,6 +13363,7 @@ function createPooledExecutorFactory(opts) {
|
|
|
13245
13363
|
div,
|
|
13246
13364
|
endOfMonth,
|
|
13247
13365
|
entityRef,
|
|
13366
|
+
entityRefs,
|
|
13248
13367
|
eq,
|
|
13249
13368
|
esel,
|
|
13250
13369
|
executeHydrated,
|