metal-orm 1.0.72 → 1.0.73

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/dist/index.cjs CHANGED
@@ -4893,6 +4893,58 @@ var resolveSelectQueryBuilderDependencies = (overrides = {}) => {
4893
4893
  };
4894
4894
  var defaultSelectQueryBuilderDependencies = resolveSelectQueryBuilderDependencies();
4895
4895
 
4896
+ // src/query-builder/relation-include-tree.ts
4897
+ var isObject = (value) => Boolean(value && typeof value === "object");
4898
+ var normalizeRelationIncludeNode = (value) => {
4899
+ if (!value || value === true) {
4900
+ return {};
4901
+ }
4902
+ if (!isObject(value)) {
4903
+ return {};
4904
+ }
4905
+ const { include, ...rest } = value;
4906
+ const options = Object.keys(rest).length ? rest : void 0;
4907
+ const normalizedInclude = isObject(include) ? normalizeRelationInclude(include) : void 0;
4908
+ if (normalizedInclude && Object.keys(normalizedInclude).length > 0) {
4909
+ return { options, include: normalizedInclude };
4910
+ }
4911
+ return { options };
4912
+ };
4913
+ var normalizeRelationInclude = (input) => {
4914
+ if (!input) return {};
4915
+ const tree = {};
4916
+ for (const [key, value] of Object.entries(input)) {
4917
+ tree[key] = normalizeRelationIncludeNode(value);
4918
+ }
4919
+ return tree;
4920
+ };
4921
+ var mergeRelationIncludeTrees = (base, next) => {
4922
+ const merged = { ...base };
4923
+ for (const [key, node] of Object.entries(next)) {
4924
+ const existing = merged[key];
4925
+ if (!existing) {
4926
+ merged[key] = node;
4927
+ continue;
4928
+ }
4929
+ const include = existing.include && node.include ? mergeRelationIncludeTrees(existing.include, node.include) : node.include ?? existing.include;
4930
+ merged[key] = {
4931
+ options: node.options ?? existing.options,
4932
+ ...include ? { include } : {}
4933
+ };
4934
+ }
4935
+ return merged;
4936
+ };
4937
+ var cloneRelationIncludeTree = (tree) => {
4938
+ const cloned = {};
4939
+ for (const [key, node] of Object.entries(tree)) {
4940
+ cloned[key] = {
4941
+ options: node.options,
4942
+ ...node.include ? { include: cloneRelationIncludeTree(node.include) } : {}
4943
+ };
4944
+ }
4945
+ return cloned;
4946
+ };
4947
+
4896
4948
  // src/orm/hydration.ts
4897
4949
  var hydrateRows = (rows, plan) => {
4898
4950
  if (!plan || !rows.length) return rows;
@@ -6025,14 +6077,14 @@ var getRelationWrapper = (meta, relationName, owner, createEntity) => {
6025
6077
  };
6026
6078
  var instantiateWrapper = (meta, relationName, relation, owner, createEntity) => {
6027
6079
  const metaBase = meta;
6028
- const lazyOptions = meta.lazyRelationOptions.get(relationName);
6029
6080
  const loadCached = (factory) => relationLoaderCache(metaBase, relationName, factory);
6081
+ const resolveOptions = () => meta.lazyRelationOptions.get(relationName);
6030
6082
  switch (relation.type) {
6031
6083
  case RelationKinds.HasOne: {
6032
6084
  const hasOne2 = relation;
6033
6085
  const localKey = hasOne2.localKey || findPrimaryKey(meta.table);
6034
6086
  const loader = () => loadCached(
6035
- () => loadHasOneRelation(meta.ctx, meta.table, relationName, hasOne2, lazyOptions)
6087
+ () => loadHasOneRelation(meta.ctx, meta.table, relationName, hasOne2, resolveOptions())
6036
6088
  );
6037
6089
  return new DefaultHasOneReference(
6038
6090
  meta.ctx,
@@ -6050,7 +6102,7 @@ var instantiateWrapper = (meta, relationName, relation, owner, createEntity) =>
6050
6102
  const hasMany2 = relation;
6051
6103
  const localKey = hasMany2.localKey || findPrimaryKey(meta.table);
6052
6104
  const loader = () => loadCached(
6053
- () => loadHasManyRelation(meta.ctx, meta.table, relationName, hasMany2, lazyOptions)
6105
+ () => loadHasManyRelation(meta.ctx, meta.table, relationName, hasMany2, resolveOptions())
6054
6106
  );
6055
6107
  return new DefaultHasManyCollection(
6056
6108
  meta.ctx,
@@ -6068,7 +6120,7 @@ var instantiateWrapper = (meta, relationName, relation, owner, createEntity) =>
6068
6120
  const belongsTo2 = relation;
6069
6121
  const targetKey = belongsTo2.localKey || findPrimaryKey(belongsTo2.target);
6070
6122
  const loader = () => loadCached(
6071
- () => loadBelongsToRelation(meta.ctx, meta.table, relationName, belongsTo2, lazyOptions)
6123
+ () => loadBelongsToRelation(meta.ctx, meta.table, relationName, belongsTo2, resolveOptions())
6072
6124
  );
6073
6125
  return new DefaultBelongsToReference(
6074
6126
  meta.ctx,
@@ -6086,7 +6138,7 @@ var instantiateWrapper = (meta, relationName, relation, owner, createEntity) =>
6086
6138
  const many = relation;
6087
6139
  const localKey = many.localKey || findPrimaryKey(meta.table);
6088
6140
  const loader = () => loadCached(
6089
- () => loadBelongsToManyRelation(meta.ctx, meta.table, relationName, many, lazyOptions)
6141
+ () => loadBelongsToManyRelation(meta.ctx, meta.table, relationName, many, resolveOptions())
6090
6142
  );
6091
6143
  return new DefaultManyToManyCollection(
6092
6144
  meta.ctx,
@@ -6193,6 +6245,58 @@ var createEntityFromRow = (ctx, table, row, lazyRelations = [], lazyRelationOpti
6193
6245
  return entity;
6194
6246
  };
6195
6247
 
6248
+ // src/orm/relation-preload.ts
6249
+ var collectEntities = (value) => {
6250
+ if (!value) return [];
6251
+ if (Array.isArray(value)) {
6252
+ return value.filter((item) => item && typeof item === "object");
6253
+ }
6254
+ if (typeof value === "object") {
6255
+ return [value];
6256
+ }
6257
+ return [];
6258
+ };
6259
+ var loadRelation = async (entity, relationName) => {
6260
+ const wrapper = entity[relationName];
6261
+ if (!wrapper) return [];
6262
+ if (typeof wrapper.load === "function") {
6263
+ const loaded = await wrapper.load();
6264
+ return collectEntities(loaded);
6265
+ }
6266
+ if (typeof wrapper.getItems === "function") {
6267
+ return collectEntities(wrapper.getItems());
6268
+ }
6269
+ if (typeof wrapper.get === "function") {
6270
+ return collectEntities(wrapper.get());
6271
+ }
6272
+ return collectEntities(wrapper);
6273
+ };
6274
+ var setLazyOptionsIfEmpty = (entity, relationName, options) => {
6275
+ if (!options) return;
6276
+ const meta = getEntityMeta(entity);
6277
+ if (!meta || meta.lazyRelationOptions.has(relationName)) return;
6278
+ meta.lazyRelationOptions.set(relationName, options);
6279
+ };
6280
+ var preloadRelationIncludes = async (entities, includeTree, depth = 0) => {
6281
+ if (!entities.length) return;
6282
+ const entries = Object.entries(includeTree);
6283
+ if (!entries.length) return;
6284
+ for (const [relationName, node] of entries) {
6285
+ const shouldLoad = depth > 0 || Boolean(node.include);
6286
+ if (!shouldLoad) continue;
6287
+ for (const entity of entities) {
6288
+ setLazyOptionsIfEmpty(entity, relationName, node.options);
6289
+ }
6290
+ const loaded = await Promise.all(
6291
+ entities.map((entity) => loadRelation(entity, relationName))
6292
+ );
6293
+ const relatedEntities = loaded.flat();
6294
+ if (node.include && relatedEntities.length) {
6295
+ await preloadRelationIncludes(relatedEntities, node.include, depth + 1);
6296
+ }
6297
+ }
6298
+ };
6299
+
6196
6300
  // src/orm/execute.ts
6197
6301
  var flattenResults = (results) => {
6198
6302
  const rows = [];
@@ -6215,14 +6319,17 @@ var executeWithContexts = async (execCtx, entityCtx, qb) => {
6215
6319
  const rows = flattenResults(executed);
6216
6320
  const lazyRelations = qb.getLazyRelations();
6217
6321
  const lazyRelationOptions = qb.getLazyRelationOptions();
6322
+ const includeTree = qb.getIncludeTree();
6218
6323
  if (ast.setOps && ast.setOps.length > 0) {
6219
6324
  const proxies = rows.map((row) => createEntityProxy(entityCtx, qb.getTable(), row, lazyRelations, lazyRelationOptions));
6220
6325
  await loadLazyRelationsForTable(entityCtx, qb.getTable(), lazyRelations, lazyRelationOptions);
6326
+ await preloadRelationIncludes(proxies, includeTree);
6221
6327
  return proxies;
6222
6328
  }
6223
6329
  const hydrated = hydrateRows(rows, qb.getHydrationPlan());
6224
6330
  const entities = hydrated.map((row) => createEntityFromRow(entityCtx, qb.getTable(), row, lazyRelations, lazyRelationOptions));
6225
6331
  await loadLazyRelationsForTable(entityCtx, qb.getTable(), lazyRelations, lazyRelationOptions);
6332
+ await preloadRelationIncludes(entities, includeTree);
6226
6333
  return entities;
6227
6334
  };
6228
6335
  var executePlainWithContexts = async (execCtx, qb) => {
@@ -6849,6 +6956,7 @@ var SelectQueryBuilder = class _SelectQueryBuilder {
6849
6956
  lazyRelations;
6850
6957
  lazyRelationOptions;
6851
6958
  entityConstructor;
6959
+ includeTree;
6852
6960
  /**
6853
6961
  * Creates a new SelectQueryBuilder instance
6854
6962
  * @param table - Table definition to query
@@ -6856,7 +6964,7 @@ var SelectQueryBuilder = class _SelectQueryBuilder {
6856
6964
  * @param hydration - Optional hydration manager
6857
6965
  * @param dependencies - Optional query builder dependencies
6858
6966
  */
6859
- constructor(table, state, hydration, dependencies, lazyRelations, lazyRelationOptions, entityConstructor) {
6967
+ constructor(table, state, hydration, dependencies, lazyRelations, lazyRelationOptions, entityConstructor, includeTree) {
6860
6968
  const deps = resolveSelectQueryBuilderDependencies(dependencies);
6861
6969
  this.env = { table, deps };
6862
6970
  const createAstService = (nextState) => deps.createQueryAstService(table, nextState);
@@ -6869,6 +6977,7 @@ var SelectQueryBuilder = class _SelectQueryBuilder {
6869
6977
  this.lazyRelations = new Set(lazyRelations ?? []);
6870
6978
  this.lazyRelationOptions = new Map(lazyRelationOptions ?? []);
6871
6979
  this.entityConstructor = entityConstructor;
6980
+ this.includeTree = includeTree ?? {};
6872
6981
  this.columnSelector = deps.createColumnSelector(this.env);
6873
6982
  const relationManager = deps.createRelationManager(this.env);
6874
6983
  this.fromFacet = new SelectFromFacet(this.env, createAstService);
@@ -6885,7 +6994,7 @@ var SelectQueryBuilder = class _SelectQueryBuilder {
6885
6994
  * @param lazyRelations - Updated lazy relations set
6886
6995
  * @returns New SelectQueryBuilder instance
6887
6996
  */
6888
- clone(context = this.context, lazyRelations = new Set(this.lazyRelations), lazyRelationOptions = new Map(this.lazyRelationOptions)) {
6997
+ clone(context = this.context, lazyRelations = new Set(this.lazyRelations), lazyRelationOptions = new Map(this.lazyRelationOptions), includeTree = this.includeTree) {
6889
6998
  return new _SelectQueryBuilder(
6890
6999
  this.env.table,
6891
7000
  context.state,
@@ -6893,7 +7002,8 @@ var SelectQueryBuilder = class _SelectQueryBuilder {
6893
7002
  this.env.deps,
6894
7003
  lazyRelations,
6895
7004
  lazyRelationOptions,
6896
- this.entityConstructor
7005
+ this.entityConstructor,
7006
+ includeTree
6897
7007
  );
6898
7008
  }
6899
7009
  /**
@@ -7181,24 +7291,22 @@ var SelectQueryBuilder = class _SelectQueryBuilder {
7181
7291
  const nextContext = this.relationFacet.joinRelation(this.context, relationName, joinKind, extraCondition);
7182
7292
  return this.clone(nextContext);
7183
7293
  }
7184
- /**
7185
- * Includes related data in the query results
7186
- * @param relationName - Name of the relationship to include
7187
- * @param options - Optional include options
7188
- * @returns New query builder instance with the relationship inclusion
7189
- * @example
7190
- * qb.include('posts');
7191
- * @example
7192
- * qb.include('posts', { columns: ['id', 'title', 'published'] });
7193
- * @example
7194
- * qb.include('posts', {
7195
- * columns: ['id', 'title'],
7196
- * where: eq(postTable.columns.published, true)
7197
- * });
7198
- */
7199
- include(relationName, options) {
7200
- const nextContext = this.relationFacet.include(this.context, relationName, options);
7201
- return this.clone(nextContext);
7294
+ include(relationNameOrRelations, options) {
7295
+ if (typeof relationNameOrRelations === "object" && relationNameOrRelations !== null) {
7296
+ const normalized = normalizeRelationInclude(relationNameOrRelations);
7297
+ let nextContext2 = this.context;
7298
+ for (const [relationName2, node] of Object.entries(normalized)) {
7299
+ nextContext2 = this.relationFacet.include(nextContext2, relationName2, node.options);
7300
+ }
7301
+ const nextTree2 = mergeRelationIncludeTrees(this.includeTree, normalized);
7302
+ return this.clone(nextContext2, void 0, void 0, nextTree2);
7303
+ }
7304
+ const relationName = relationNameOrRelations;
7305
+ const normalizedNode = normalizeRelationIncludeNode(options);
7306
+ const nextContext = this.relationFacet.include(this.context, relationName, normalizedNode.options);
7307
+ const shouldStore = Boolean(normalizedNode.include || normalizedNode.options);
7308
+ const nextTree = shouldStore ? mergeRelationIncludeTrees(this.includeTree, { [relationName]: normalizedNode }) : this.includeTree;
7309
+ return this.clone(nextContext, void 0, void 0, nextTree);
7202
7310
  }
7203
7311
  /**
7204
7312
  * Includes a relation lazily in the query results
@@ -7282,6 +7390,12 @@ var SelectQueryBuilder = class _SelectQueryBuilder {
7282
7390
  getLazyRelationOptions() {
7283
7391
  return new Map(this.lazyRelationOptions);
7284
7392
  }
7393
+ /**
7394
+ * Gets normalized nested include information for runtime preloading.
7395
+ */
7396
+ getIncludeTree() {
7397
+ return cloneRelationIncludeTree(this.includeTree);
7398
+ }
7285
7399
  /**
7286
7400
  * Gets the table definition for this query builder
7287
7401
  * @returns Table definition