metal-orm 1.0.78 → 1.0.79

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "metal-orm",
3
- "version": "1.0.78",
3
+ "version": "1.0.79",
4
4
  "type": "module",
5
5
  "types": "./dist/index.d.ts",
6
6
  "engines": {
@@ -73,43 +73,41 @@ export async function executeCount(
73
73
  if (typeof value === 'number') return value;
74
74
  if (typeof value === 'bigint') return Number(value);
75
75
  if (typeof value === 'string') return Number(value);
76
- return value === null || value === undefined ? 0 : Number(value);
77
- }
78
-
79
- export interface PaginatedResult<T> {
80
- items: T[];
81
- totalItems: number;
82
- page: number;
83
- pageSize: number;
84
- }
85
-
86
- /**
87
- * Executes paged queries using the provided builder helpers.
88
- */
89
- export async function executePagedQuery<T, TTable extends TableDef>(
90
- builder: SelectQueryBuilder<T, TTable>,
91
- session: OrmSession,
92
- options: { page: number; pageSize: number },
93
- countCallback: (session: OrmSession) => Promise<number>
94
- ): Promise<PaginatedResult<T>> {
95
- const { page, pageSize } = options;
96
-
97
- if (!Number.isInteger(page) || page < 1) {
98
- throw new Error('executePaged: page must be an integer >= 1');
99
- }
100
- if (!Number.isInteger(pageSize) || pageSize < 1) {
101
- throw new Error('executePaged: pageSize must be an integer >= 1');
102
- }
103
-
104
- const offset = (page - 1) * pageSize;
105
-
106
- const [items, totalItems] = await Promise.all([
107
- builder.limit(pageSize).offset(offset).execute(session),
108
- countCallback(session)
109
- ]);
110
-
111
- return { items, totalItems, page, pageSize };
112
- }
76
+ return value === null || value === undefined ? 0 : Number(value);
77
+ }
78
+
79
+ export interface PaginatedResult<T> {
80
+ items: T[];
81
+ totalItems: number;
82
+ page: number;
83
+ pageSize: number;
84
+ }
85
+
86
+ /**
87
+ * Executes paged queries using the provided builder helpers.
88
+ */
89
+ export async function executePagedQuery<T, TTable extends TableDef>(
90
+ builder: SelectQueryBuilder<T, TTable>,
91
+ session: OrmSession,
92
+ options: { page: number; pageSize: number },
93
+ countCallback: (session: OrmSession) => Promise<number>
94
+ ): Promise<PaginatedResult<T>> {
95
+ const { page, pageSize } = options;
96
+
97
+ if (!Number.isInteger(page) || page < 1) {
98
+ throw new Error('executePaged: page must be an integer >= 1');
99
+ }
100
+ if (!Number.isInteger(pageSize) || pageSize < 1) {
101
+ throw new Error('executePaged: pageSize must be an integer >= 1');
102
+ }
103
+
104
+ const offset = (page - 1) * pageSize;
105
+
106
+ const totalItems = await countCallback(session);
107
+ const items = await builder.limit(pageSize).offset(offset).execute(session);
108
+
109
+ return { items, totalItems, page, pageSize };
110
+ }
113
111
 
114
112
  /**
115
113
  * Builds an EXISTS or NOT EXISTS predicate for a related table.
@@ -29,17 +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';
34
- import {
35
- RelationIncludeInput,
36
- RelationIncludeNodeInput,
37
- NormalizedRelationIncludeTree,
38
- cloneRelationIncludeTree,
39
- mergeRelationIncludeTrees,
40
- normalizeRelationInclude,
41
- normalizeRelationIncludeNode
42
- } from './relation-include-tree.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';
43
43
  import { JOIN_KINDS, JoinKind, ORDER_DIRECTIONS, OrderDirection } from '../core/sql/sql.js';
44
44
  import { EntityInstance, RelationMap } from '../schema/types.js';
45
45
  import type { ColumnToTs, InferRow } from '../schema/types.js';
@@ -50,16 +50,16 @@ import { executeHydrated, executeHydratedPlain, executeHydratedWithContexts } fr
50
50
  import { EntityConstructor } from '../orm/entity-metadata.js';
51
51
  import { materializeAs } from '../orm/entity-materializer.js';
52
52
  import { resolveSelectQuery } from './query-resolution.js';
53
- import {
54
- applyOrderBy,
55
- buildWhereHasPredicate,
56
- executeCount,
57
- executePagedQuery,
58
- PaginatedResult,
59
- RelationCallback,
60
- WhereHasOptions
61
- } from './select/select-operations.js';
62
- export type { PaginatedResult };
53
+ import {
54
+ applyOrderBy,
55
+ buildWhereHasPredicate,
56
+ executeCount,
57
+ executePagedQuery,
58
+ PaginatedResult,
59
+ RelationCallback,
60
+ WhereHasOptions
61
+ } from './select/select-operations.js';
62
+ export type { PaginatedResult };
63
63
  import { SelectFromFacet } from './select/from-facet.js';
64
64
  import { SelectJoinFacet } from './select/join-facet.js';
65
65
  import { SelectProjectionFacet } from './select/projection-facet.js';
@@ -115,11 +115,11 @@ export class SelectQueryBuilder<T = EntityInstance<TableDef>, TTable extends Tab
115
115
  private readonly predicateFacet: SelectPredicateFacet;
116
116
  private readonly cteFacet: SelectCTEFacet;
117
117
  private readonly setOpFacet: SelectSetOpFacet;
118
- private readonly relationFacet: SelectRelationFacet;
119
- private readonly lazyRelations: Set<string>;
120
- private readonly lazyRelationOptions: Map<string, RelationIncludeOptions>;
121
- private readonly entityConstructor?: EntityConstructor;
122
- private readonly includeTree: NormalizedRelationIncludeTree;
118
+ private readonly relationFacet: SelectRelationFacet;
119
+ private readonly lazyRelations: Set<string>;
120
+ private readonly lazyRelationOptions: Map<string, RelationIncludeOptions>;
121
+ private readonly entityConstructor?: EntityConstructor;
122
+ private readonly includeTree: NormalizedRelationIncludeTree;
123
123
 
124
124
  /**
125
125
  * Creates a new SelectQueryBuilder instance
@@ -128,16 +128,16 @@ export class SelectQueryBuilder<T = EntityInstance<TableDef>, TTable extends Tab
128
128
  * @param hydration - Optional hydration manager
129
129
  * @param dependencies - Optional query builder dependencies
130
130
  */
131
- constructor(
132
- table: TTable,
133
- state?: SelectQueryState,
134
- hydration?: HydrationManager,
135
- dependencies?: Partial<SelectQueryBuilderDependencies>,
136
- lazyRelations?: Set<string>,
137
- lazyRelationOptions?: Map<string, RelationIncludeOptions>,
138
- entityConstructor?: EntityConstructor,
139
- includeTree?: NormalizedRelationIncludeTree
140
- ) {
131
+ constructor(
132
+ table: TTable,
133
+ state?: SelectQueryState,
134
+ hydration?: HydrationManager,
135
+ dependencies?: Partial<SelectQueryBuilderDependencies>,
136
+ lazyRelations?: Set<string>,
137
+ lazyRelationOptions?: Map<string, RelationIncludeOptions>,
138
+ entityConstructor?: EntityConstructor,
139
+ includeTree?: NormalizedRelationIncludeTree
140
+ ) {
141
141
  const deps = resolveSelectQueryBuilderDependencies(dependencies);
142
142
  this.env = { table, deps };
143
143
  const createAstService = (nextState: SelectQueryState) => deps.createQueryAstService(table, nextState);
@@ -147,11 +147,11 @@ export class SelectQueryBuilder<T = EntityInstance<TableDef>, TTable extends Tab
147
147
  state: initialState,
148
148
  hydration: initialHydration
149
149
  };
150
- this.lazyRelations = new Set(lazyRelations ?? []);
151
- this.lazyRelationOptions = new Map(lazyRelationOptions ?? []);
152
- this.entityConstructor = entityConstructor;
153
- this.includeTree = includeTree ?? {};
154
- this.columnSelector = deps.createColumnSelector(this.env);
150
+ this.lazyRelations = new Set(lazyRelations ?? []);
151
+ this.lazyRelationOptions = new Map(lazyRelationOptions ?? []);
152
+ this.entityConstructor = entityConstructor;
153
+ this.includeTree = includeTree ?? {};
154
+ this.columnSelector = deps.createColumnSelector(this.env);
155
155
  const relationManager = deps.createRelationManager(this.env);
156
156
  this.fromFacet = new SelectFromFacet(this.env, createAstService);
157
157
  this.joinFacet = new SelectJoinFacet(this.env, createAstService);
@@ -168,23 +168,23 @@ export class SelectQueryBuilder<T = EntityInstance<TableDef>, TTable extends Tab
168
168
  * @param lazyRelations - Updated lazy relations set
169
169
  * @returns New SelectQueryBuilder instance
170
170
  */
171
- private clone<TNext = T>(
172
- context: SelectQueryBuilderContext = this.context,
173
- lazyRelations = new Set(this.lazyRelations),
174
- lazyRelationOptions = new Map(this.lazyRelationOptions),
175
- includeTree = this.includeTree
176
- ): SelectQueryBuilder<TNext, TTable> {
177
- return new SelectQueryBuilder(
178
- this.env.table as TTable,
179
- context.state,
180
- context.hydration,
181
- this.env.deps,
182
- lazyRelations,
183
- lazyRelationOptions,
184
- this.entityConstructor,
185
- includeTree
186
- ) as SelectQueryBuilder<TNext, TTable>;
187
- }
171
+ private clone<TNext = T>(
172
+ context: SelectQueryBuilderContext = this.context,
173
+ lazyRelations = new Set(this.lazyRelations),
174
+ lazyRelationOptions = new Map(this.lazyRelationOptions),
175
+ includeTree = this.includeTree
176
+ ): SelectQueryBuilder<TNext, TTable> {
177
+ return new SelectQueryBuilder(
178
+ this.env.table as TTable,
179
+ context.state,
180
+ context.hydration,
181
+ this.env.deps,
182
+ lazyRelations,
183
+ lazyRelationOptions,
184
+ this.entityConstructor,
185
+ includeTree
186
+ ) as SelectQueryBuilder<TNext, TTable>;
187
+ }
188
188
 
189
189
  /**
190
190
  * Applies an alias to the root FROM table.
@@ -564,42 +564,42 @@ export class SelectQueryBuilder<T = EntityInstance<TableDef>, TTable extends Tab
564
564
  * qb.include('posts');
565
565
  * @example
566
566
  * qb.include('posts', { columns: ['id', 'title', 'published'] });
567
- * @example
568
- * qb.include('posts', {
569
- * columns: ['id', 'title'],
570
- * where: eq(postTable.columns.published, true)
571
- * });
572
- * @example
573
- * qb.include({ posts: { include: { author: true } } });
574
- */
575
- include<K extends keyof TTable['relations'] & string>(
576
- relationName: K,
577
- options?: RelationIncludeNodeInput<TTable['relations'][K]>
578
- ): SelectQueryBuilder<T, TTable>;
579
- include(relations: RelationIncludeInput<TTable>): SelectQueryBuilder<T, TTable>;
580
- include<K extends keyof TTable['relations'] & string>(
581
- relationNameOrRelations: K | RelationIncludeInput<TTable>,
582
- options?: RelationIncludeNodeInput<TTable['relations'][K]>
583
- ): SelectQueryBuilder<T, TTable> {
584
- if (typeof relationNameOrRelations === 'object' && relationNameOrRelations !== null) {
585
- const normalized = normalizeRelationInclude(relationNameOrRelations as RelationIncludeInput<TableDef>);
586
- let nextContext = this.context;
587
- for (const [relationName, node] of Object.entries(normalized)) {
588
- nextContext = this.relationFacet.include(nextContext, relationName, node.options);
589
- }
590
- const nextTree = mergeRelationIncludeTrees(this.includeTree, normalized);
591
- return this.clone(nextContext, undefined, undefined, nextTree);
592
- }
593
-
594
- const relationName = relationNameOrRelations as string;
595
- const normalizedNode = normalizeRelationIncludeNode(options);
596
- const nextContext = this.relationFacet.include(this.context, relationName, normalizedNode.options);
597
- const shouldStore = Boolean(normalizedNode.include || normalizedNode.options);
598
- const nextTree = shouldStore
599
- ? mergeRelationIncludeTrees(this.includeTree, { [relationName]: normalizedNode })
600
- : this.includeTree;
601
- return this.clone(nextContext, undefined, undefined, nextTree);
602
- }
567
+ * @example
568
+ * qb.include('posts', {
569
+ * columns: ['id', 'title'],
570
+ * where: eq(postTable.columns.published, true)
571
+ * });
572
+ * @example
573
+ * qb.include({ posts: { include: { author: true } } });
574
+ */
575
+ include<K extends keyof TTable['relations'] & string>(
576
+ relationName: K,
577
+ options?: RelationIncludeNodeInput<TTable['relations'][K]>
578
+ ): SelectQueryBuilder<T, TTable>;
579
+ include(relations: RelationIncludeInput<TTable>): SelectQueryBuilder<T, TTable>;
580
+ include<K extends keyof TTable['relations'] & string>(
581
+ relationNameOrRelations: K | RelationIncludeInput<TTable>,
582
+ options?: RelationIncludeNodeInput<TTable['relations'][K]>
583
+ ): SelectQueryBuilder<T, TTable> {
584
+ if (typeof relationNameOrRelations === 'object' && relationNameOrRelations !== null) {
585
+ const normalized = normalizeRelationInclude(relationNameOrRelations as RelationIncludeInput<TableDef>);
586
+ let nextContext = this.context;
587
+ for (const [relationName, node] of Object.entries(normalized)) {
588
+ nextContext = this.relationFacet.include(nextContext, relationName, node.options);
589
+ }
590
+ const nextTree = mergeRelationIncludeTrees(this.includeTree, normalized);
591
+ return this.clone(nextContext, undefined, undefined, nextTree);
592
+ }
593
+
594
+ const relationName = relationNameOrRelations as string;
595
+ const normalizedNode = normalizeRelationIncludeNode(options);
596
+ const nextContext = this.relationFacet.include(this.context, relationName, normalizedNode.options);
597
+ const shouldStore = Boolean(normalizedNode.include || normalizedNode.options);
598
+ const nextTree = shouldStore
599
+ ? mergeRelationIncludeTrees(this.includeTree, { [relationName]: normalizedNode })
600
+ : this.includeTree;
601
+ return this.clone(nextContext, undefined, undefined, nextTree);
602
+ }
603
603
 
604
604
  /**
605
605
  * Includes a relation lazily in the query results
@@ -647,13 +647,13 @@ export class SelectQueryBuilder<T = EntityInstance<TableDef>, TTable extends Tab
647
647
  * @example
648
648
  * qb.includePick('posts', ['id', 'title', 'createdAt']);
649
649
  */
650
- includePick<
651
- K extends keyof TTable['relations'] & string,
652
- C extends RelationTargetColumns<TTable['relations'][K]>
653
- >(relationName: K, cols: C[]): SelectQueryBuilder<T, TTable> {
654
- const options = { columns: cols as readonly C[] } as unknown as RelationIncludeNodeInput<TTable['relations'][K]>;
655
- return this.include(relationName, options);
656
- }
650
+ includePick<
651
+ K extends keyof TTable['relations'] & string,
652
+ C extends RelationTargetColumns<TTable['relations'][K]>
653
+ >(relationName: K, cols: C[]): SelectQueryBuilder<T, TTable> {
654
+ const options = { columns: cols as readonly C[] } as unknown as RelationIncludeNodeInput<TTable['relations'][K]>;
655
+ return this.include(relationName, options);
656
+ }
657
657
 
658
658
  /**
659
659
  * Selects columns for the root table and relations from an array of entries
@@ -670,13 +670,13 @@ export class SelectQueryBuilder<T = EntityInstance<TableDef>, TTable extends Tab
670
670
  let currBuilder: SelectQueryBuilder<T, TTable> = this;
671
671
 
672
672
  for (const entry of config) {
673
- if (entry.type === 'root') {
674
- currBuilder = currBuilder.select(...entry.columns);
675
- } else {
676
- const options = { columns: entry.columns } as unknown as RelationIncludeNodeInput<TTable['relations'][typeof entry.relationName]>;
677
- currBuilder = currBuilder.include(entry.relationName, options);
678
- }
679
- }
673
+ if (entry.type === 'root') {
674
+ currBuilder = currBuilder.select(...entry.columns);
675
+ } else {
676
+ const options = { columns: entry.columns } as unknown as RelationIncludeNodeInput<TTable['relations'][typeof entry.relationName]>;
677
+ currBuilder = currBuilder.include(entry.relationName, options);
678
+ }
679
+ }
680
680
 
681
681
  return currBuilder;
682
682
  }
@@ -693,16 +693,16 @@ export class SelectQueryBuilder<T = EntityInstance<TableDef>, TTable extends Tab
693
693
  * Gets lazy relation include options
694
694
  * @returns Map of relation names to include options
695
695
  */
696
- getLazyRelationOptions(): Map<string, RelationIncludeOptions> {
697
- return new Map(this.lazyRelationOptions);
698
- }
699
-
700
- /**
701
- * Gets normalized nested include information for runtime preloading.
702
- */
703
- getIncludeTree(): NormalizedRelationIncludeTree {
704
- return cloneRelationIncludeTree(this.includeTree);
705
- }
696
+ getLazyRelationOptions(): Map<string, RelationIncludeOptions> {
697
+ return new Map(this.lazyRelationOptions);
698
+ }
699
+
700
+ /**
701
+ * Gets normalized nested include information for runtime preloading.
702
+ */
703
+ getIncludeTree(): NormalizedRelationIncludeTree {
704
+ return cloneRelationIncludeTree(this.includeTree);
705
+ }
706
706
 
707
707
  /**
708
708
  * Gets the table definition for this query builder
@@ -794,19 +794,19 @@ export class SelectQueryBuilder<T = EntityInstance<TableDef>, TTable extends Tab
794
794
  return executeCount(this.context, this.env, session);
795
795
  }
796
796
 
797
- /**
798
- * Executes the query and returns both the paged items and the total.
799
- *
800
- * @example
801
- * const { items, totalItems, page, pageSize } = await qb.executePaged(session, { page: 1, pageSize: 20 });
802
- */
803
- async executePaged(
804
- session: OrmSession,
805
- options: { page: number; pageSize: number }
806
- ): Promise<PaginatedResult<T>> {
807
- const builder = this.ensureDefaultSelection();
808
- return executePagedQuery(builder, session, options, sess => this.count(sess));
809
- }
797
+ /**
798
+ * Executes the query and returns both the paged items and the total.
799
+ *
800
+ * @example
801
+ * const { items, totalItems, page, pageSize } = await qb.executePaged(session, { page: 1, pageSize: 20 });
802
+ */
803
+ async executePaged(
804
+ session: OrmSession,
805
+ options: { page: number; pageSize: number }
806
+ ): Promise<PaginatedResult<T>> {
807
+ const builder = this.ensureDefaultSelection();
808
+ return executePagedQuery(builder, session, options, sess => builder.count(sess));
809
+ }
810
810
 
811
811
  /**
812
812
  * Executes the query with provided execution and hydration contexts