metal-orm 1.0.71 → 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.
@@ -29,8 +29,17 @@ import {
29
29
  SelectQueryBuilderEnvironment
30
30
  } from './select-query-builder-deps.js';
31
31
  import { ColumnSelector } from './column-selector.js';
32
- import { RelationIncludeOptions, RelationTargetColumns, TypedRelationIncludeOptions } from './relation-types.js';
33
- import { RelationKinds } from '../schema/relation.js';
32
+ import { RelationIncludeOptions, RelationTargetColumns, TypedRelationIncludeOptions } from './relation-types.js';
33
+ import { RelationKinds } from '../schema/relation.js';
34
+ import {
35
+ RelationIncludeInput,
36
+ RelationIncludeNodeInput,
37
+ NormalizedRelationIncludeTree,
38
+ cloneRelationIncludeTree,
39
+ mergeRelationIncludeTrees,
40
+ normalizeRelationInclude,
41
+ normalizeRelationIncludeNode
42
+ } from './relation-include-tree.js';
34
43
  import { JOIN_KINDS, JoinKind, ORDER_DIRECTIONS, OrderDirection } from '../core/sql/sql.js';
35
44
  import { EntityInstance, RelationMap } from '../schema/types.js';
36
45
  import type { ColumnToTs, InferRow } from '../schema/types.js';
@@ -104,10 +113,11 @@ export class SelectQueryBuilder<T = EntityInstance<TableDef>, TTable extends Tab
104
113
  private readonly predicateFacet: SelectPredicateFacet;
105
114
  private readonly cteFacet: SelectCTEFacet;
106
115
  private readonly setOpFacet: SelectSetOpFacet;
107
- private readonly relationFacet: SelectRelationFacet;
108
- private readonly lazyRelations: Set<string>;
109
- private readonly lazyRelationOptions: Map<string, RelationIncludeOptions>;
110
- private readonly entityConstructor?: EntityConstructor;
116
+ private readonly relationFacet: SelectRelationFacet;
117
+ private readonly lazyRelations: Set<string>;
118
+ private readonly lazyRelationOptions: Map<string, RelationIncludeOptions>;
119
+ private readonly entityConstructor?: EntityConstructor;
120
+ private readonly includeTree: NormalizedRelationIncludeTree;
111
121
 
112
122
  /**
113
123
  * Creates a new SelectQueryBuilder instance
@@ -116,15 +126,16 @@ export class SelectQueryBuilder<T = EntityInstance<TableDef>, TTable extends Tab
116
126
  * @param hydration - Optional hydration manager
117
127
  * @param dependencies - Optional query builder dependencies
118
128
  */
119
- constructor(
120
- table: TTable,
121
- state?: SelectQueryState,
122
- hydration?: HydrationManager,
123
- dependencies?: Partial<SelectQueryBuilderDependencies>,
124
- lazyRelations?: Set<string>,
125
- lazyRelationOptions?: Map<string, RelationIncludeOptions>,
126
- entityConstructor?: EntityConstructor
127
- ) {
129
+ constructor(
130
+ table: TTable,
131
+ state?: SelectQueryState,
132
+ hydration?: HydrationManager,
133
+ dependencies?: Partial<SelectQueryBuilderDependencies>,
134
+ lazyRelations?: Set<string>,
135
+ lazyRelationOptions?: Map<string, RelationIncludeOptions>,
136
+ entityConstructor?: EntityConstructor,
137
+ includeTree?: NormalizedRelationIncludeTree
138
+ ) {
128
139
  const deps = resolveSelectQueryBuilderDependencies(dependencies);
129
140
  this.env = { table, deps };
130
141
  const createAstService = (nextState: SelectQueryState) => deps.createQueryAstService(table, nextState);
@@ -134,10 +145,11 @@ export class SelectQueryBuilder<T = EntityInstance<TableDef>, TTable extends Tab
134
145
  state: initialState,
135
146
  hydration: initialHydration
136
147
  };
137
- this.lazyRelations = new Set(lazyRelations ?? []);
138
- this.lazyRelationOptions = new Map(lazyRelationOptions ?? []);
139
- this.entityConstructor = entityConstructor;
140
- this.columnSelector = deps.createColumnSelector(this.env);
148
+ this.lazyRelations = new Set(lazyRelations ?? []);
149
+ this.lazyRelationOptions = new Map(lazyRelationOptions ?? []);
150
+ this.entityConstructor = entityConstructor;
151
+ this.includeTree = includeTree ?? {};
152
+ this.columnSelector = deps.createColumnSelector(this.env);
141
153
  const relationManager = deps.createRelationManager(this.env);
142
154
  this.fromFacet = new SelectFromFacet(this.env, createAstService);
143
155
  this.joinFacet = new SelectJoinFacet(this.env, createAstService);
@@ -154,21 +166,23 @@ export class SelectQueryBuilder<T = EntityInstance<TableDef>, TTable extends Tab
154
166
  * @param lazyRelations - Updated lazy relations set
155
167
  * @returns New SelectQueryBuilder instance
156
168
  */
157
- private clone<TNext = T>(
158
- context: SelectQueryBuilderContext = this.context,
159
- lazyRelations = new Set(this.lazyRelations),
160
- lazyRelationOptions = new Map(this.lazyRelationOptions)
161
- ): SelectQueryBuilder<TNext, TTable> {
162
- return new SelectQueryBuilder(
163
- this.env.table as TTable,
164
- context.state,
165
- context.hydration,
166
- this.env.deps,
167
- lazyRelations,
168
- lazyRelationOptions,
169
- this.entityConstructor
170
- ) as SelectQueryBuilder<TNext, TTable>;
171
- }
169
+ private clone<TNext = T>(
170
+ context: SelectQueryBuilderContext = this.context,
171
+ lazyRelations = new Set(this.lazyRelations),
172
+ lazyRelationOptions = new Map(this.lazyRelationOptions),
173
+ includeTree = this.includeTree
174
+ ): SelectQueryBuilder<TNext, TTable> {
175
+ return new SelectQueryBuilder(
176
+ this.env.table as TTable,
177
+ context.state,
178
+ context.hydration,
179
+ this.env.deps,
180
+ lazyRelations,
181
+ lazyRelationOptions,
182
+ this.entityConstructor,
183
+ includeTree
184
+ ) as SelectQueryBuilder<TNext, TTable>;
185
+ }
172
186
 
173
187
  /**
174
188
  * Applies an alias to the root FROM table.
@@ -548,19 +562,42 @@ export class SelectQueryBuilder<T = EntityInstance<TableDef>, TTable extends Tab
548
562
  * qb.include('posts');
549
563
  * @example
550
564
  * qb.include('posts', { columns: ['id', 'title', 'published'] });
551
- * @example
552
- * qb.include('posts', {
553
- * columns: ['id', 'title'],
554
- * where: eq(postTable.columns.published, true)
555
- * });
556
- */
557
- include<K extends keyof TTable['relations'] & string>(
558
- relationName: K,
559
- options?: TypedRelationIncludeOptions<TTable['relations'][K]>
560
- ): SelectQueryBuilder<T, TTable> {
561
- const nextContext = this.relationFacet.include(this.context, relationName, options);
562
- return this.clone(nextContext);
563
- }
565
+ * @example
566
+ * qb.include('posts', {
567
+ * columns: ['id', 'title'],
568
+ * where: eq(postTable.columns.published, true)
569
+ * });
570
+ * @example
571
+ * qb.include({ posts: { include: { author: true } } });
572
+ */
573
+ include<K extends keyof TTable['relations'] & string>(
574
+ relationName: K,
575
+ options?: RelationIncludeNodeInput<TTable['relations'][K]>
576
+ ): SelectQueryBuilder<T, TTable>;
577
+ include(relations: RelationIncludeInput<TTable>): SelectQueryBuilder<T, TTable>;
578
+ include<K extends keyof TTable['relations'] & string>(
579
+ relationNameOrRelations: K | RelationIncludeInput<TTable>,
580
+ options?: RelationIncludeNodeInput<TTable['relations'][K]>
581
+ ): SelectQueryBuilder<T, TTable> {
582
+ if (typeof relationNameOrRelations === 'object' && relationNameOrRelations !== null) {
583
+ const normalized = normalizeRelationInclude(relationNameOrRelations as RelationIncludeInput<TableDef>);
584
+ let nextContext = this.context;
585
+ for (const [relationName, node] of Object.entries(normalized)) {
586
+ nextContext = this.relationFacet.include(nextContext, relationName, node.options);
587
+ }
588
+ const nextTree = mergeRelationIncludeTrees(this.includeTree, normalized);
589
+ return this.clone(nextContext, undefined, undefined, nextTree);
590
+ }
591
+
592
+ const relationName = relationNameOrRelations as string;
593
+ const normalizedNode = normalizeRelationIncludeNode(options);
594
+ const nextContext = this.relationFacet.include(this.context, relationName, normalizedNode.options);
595
+ const shouldStore = Boolean(normalizedNode.include || normalizedNode.options);
596
+ const nextTree = shouldStore
597
+ ? mergeRelationIncludeTrees(this.includeTree, { [relationName]: normalizedNode })
598
+ : this.includeTree;
599
+ return this.clone(nextContext, undefined, undefined, nextTree);
600
+ }
564
601
 
565
602
  /**
566
603
  * Includes a relation lazily in the query results
@@ -608,13 +645,13 @@ export class SelectQueryBuilder<T = EntityInstance<TableDef>, TTable extends Tab
608
645
  * @example
609
646
  * qb.includePick('posts', ['id', 'title', 'createdAt']);
610
647
  */
611
- includePick<
612
- K extends keyof TTable['relations'] & string,
613
- C extends RelationTargetColumns<TTable['relations'][K]>
614
- >(relationName: K, cols: C[]): SelectQueryBuilder<T, TTable> {
615
- const options = { columns: cols as readonly C[] } as unknown as TypedRelationIncludeOptions<TTable['relations'][K]>;
616
- return this.include(relationName, options);
617
- }
648
+ includePick<
649
+ K extends keyof TTable['relations'] & string,
650
+ C extends RelationTargetColumns<TTable['relations'][K]>
651
+ >(relationName: K, cols: C[]): SelectQueryBuilder<T, TTable> {
652
+ const options = { columns: cols as readonly C[] } as unknown as RelationIncludeNodeInput<TTable['relations'][K]>;
653
+ return this.include(relationName, options);
654
+ }
618
655
 
619
656
  /**
620
657
  * Selects columns for the root table and relations from an array of entries
@@ -631,13 +668,13 @@ export class SelectQueryBuilder<T = EntityInstance<TableDef>, TTable extends Tab
631
668
  let currBuilder: SelectQueryBuilder<T, TTable> = this;
632
669
 
633
670
  for (const entry of config) {
634
- if (entry.type === 'root') {
635
- currBuilder = currBuilder.select(...entry.columns);
636
- } else {
637
- const options = { columns: entry.columns } as unknown as TypedRelationIncludeOptions<TTable['relations'][typeof entry.relationName]>;
638
- currBuilder = currBuilder.include(entry.relationName, options);
639
- }
640
- }
671
+ if (entry.type === 'root') {
672
+ currBuilder = currBuilder.select(...entry.columns);
673
+ } else {
674
+ const options = { columns: entry.columns } as unknown as RelationIncludeNodeInput<TTable['relations'][typeof entry.relationName]>;
675
+ currBuilder = currBuilder.include(entry.relationName, options);
676
+ }
677
+ }
641
678
 
642
679
  return currBuilder;
643
680
  }
@@ -654,9 +691,16 @@ export class SelectQueryBuilder<T = EntityInstance<TableDef>, TTable extends Tab
654
691
  * Gets lazy relation include options
655
692
  * @returns Map of relation names to include options
656
693
  */
657
- getLazyRelationOptions(): Map<string, RelationIncludeOptions> {
658
- return new Map(this.lazyRelationOptions);
659
- }
694
+ getLazyRelationOptions(): Map<string, RelationIncludeOptions> {
695
+ return new Map(this.lazyRelationOptions);
696
+ }
697
+
698
+ /**
699
+ * Gets normalized nested include information for runtime preloading.
700
+ */
701
+ getIncludeTree(): NormalizedRelationIncludeTree {
702
+ return cloneRelationIncludeTree(this.includeTree);
703
+ }
660
704
 
661
705
  /**
662
706
  * Gets the table definition for this query builder