metal-orm 1.0.60 → 1.0.62

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.
@@ -35,7 +35,9 @@ import { EntityInstance, RelationMap } from '../schema/types.js';
35
35
  import { OrmSession } from '../orm/orm-session.ts';
36
36
  import { ExecutionContext } from '../orm/execution-context.js';
37
37
  import { HydrationContext } from '../orm/hydration-context.js';
38
- import { executeHydrated, executeHydratedWithContexts } from '../orm/execute.js';
38
+ import { executeHydrated, executeHydratedPlain, executeHydratedWithContexts } from '../orm/execute.js';
39
+ import { EntityConstructor } from '../orm/entity-metadata.js';
40
+ import { materializeAs } from '../orm/entity-materializer.js';
39
41
  import { resolveSelectQuery } from './query-resolution.js';
40
42
  import {
41
43
  applyOrderBy,
@@ -71,7 +73,7 @@ type DeepSelectConfig<TTable extends TableDef> = DeepSelectEntry<TTable>[];
71
73
  * @typeParam T - Result type for projections (unused)
72
74
  * @typeParam TTable - Table definition being queried
73
75
  */
74
- export class SelectQueryBuilder<T = unknown, TTable extends TableDef = TableDef> {
76
+ export class SelectQueryBuilder<T = EntityInstance<TableDef>, TTable extends TableDef = TableDef> {
75
77
  private readonly env: SelectQueryBuilderEnvironment;
76
78
  private readonly context: SelectQueryBuilderContext;
77
79
  private readonly columnSelector: ColumnSelector;
@@ -84,6 +86,7 @@ export class SelectQueryBuilder<T = unknown, TTable extends TableDef = TableDef>
84
86
  private readonly relationFacet: SelectRelationFacet;
85
87
  private readonly lazyRelations: Set<string>;
86
88
  private readonly lazyRelationOptions: Map<string, RelationIncludeOptions>;
89
+ private readonly entityConstructor?: EntityConstructor;
87
90
 
88
91
  /**
89
92
  * Creates a new SelectQueryBuilder instance
@@ -98,7 +101,8 @@ export class SelectQueryBuilder<T = unknown, TTable extends TableDef = TableDef>
98
101
  hydration?: HydrationManager,
99
102
  dependencies?: Partial<SelectQueryBuilderDependencies>,
100
103
  lazyRelations?: Set<string>,
101
- lazyRelationOptions?: Map<string, RelationIncludeOptions>
104
+ lazyRelationOptions?: Map<string, RelationIncludeOptions>,
105
+ entityConstructor?: EntityConstructor
102
106
  ) {
103
107
  const deps = resolveSelectQueryBuilderDependencies(dependencies);
104
108
  this.env = { table, deps };
@@ -111,6 +115,7 @@ export class SelectQueryBuilder<T = unknown, TTable extends TableDef = TableDef>
111
115
  };
112
116
  this.lazyRelations = new Set(lazyRelations ?? []);
113
117
  this.lazyRelationOptions = new Map(lazyRelationOptions ?? []);
118
+ this.entityConstructor = entityConstructor;
114
119
  this.columnSelector = deps.createColumnSelector(this.env);
115
120
  const relationManager = deps.createRelationManager(this.env);
116
121
  this.fromFacet = new SelectFromFacet(this.env, createAstService);
@@ -139,7 +144,8 @@ export class SelectQueryBuilder<T = unknown, TTable extends TableDef = TableDef>
139
144
  context.hydration,
140
145
  this.env.deps,
141
146
  lazyRelations,
142
- lazyRelationOptions
147
+ lazyRelationOptions,
148
+ this.entityConstructor
143
149
  );
144
150
  }
145
151
 
@@ -625,16 +631,74 @@ export class SelectQueryBuilder<T = unknown, TTable extends TableDef = TableDef>
625
631
  }
626
632
 
627
633
  /**
628
- * Executes the query and returns hydrated results
634
+ * Ensures that if no columns are selected, all columns from the table are selected by default.
635
+ */
636
+ private ensureDefaultSelection(): SelectQueryBuilder<T, TTable> {
637
+ const columns = this.context.state.ast.columns;
638
+ if (!columns || columns.length === 0) {
639
+ const columnKeys = Object.keys(this.env.table.columns) as (keyof TTable['columns'] & string)[];
640
+ return this.select(...columnKeys);
641
+ }
642
+ return this;
643
+ }
644
+
645
+ /**
646
+ * Executes the query and returns hydrated results.
647
+ * If the builder was created with an entity constructor (e.g. via selectFromEntity),
648
+ * this will automatically return fully materialized entity instances.
649
+ *
629
650
  * @param ctx - ORM session context
630
- * @returns Promise of entity instances
651
+ * @returns Promise of entity instances (or objects if generic T is not an entity)
631
652
  * @example
632
- * const users = await qb.select('id', 'name')
633
- * .where(eq(userTable.columns.active, true))
634
- * .execute(session);
653
+ * const users = await selectFromEntity(User).execute(session);
654
+ * // users is User[]
655
+ * users[0] instanceof User; // true
656
+ */
657
+ async execute(ctx: OrmSession): Promise<T[]> {
658
+ if (this.entityConstructor) {
659
+ return this.executeAs(this.entityConstructor, ctx) as unknown as T[];
660
+ }
661
+ const builder = this.ensureDefaultSelection();
662
+ return executeHydrated(ctx, builder) as unknown as T[];
663
+ }
664
+
665
+ /**
666
+ * Executes the query and returns plain row objects (POJOs), ignoring any entity materialization.
667
+ * Use this if you want raw data even when using selectFromEntity.
668
+ *
669
+ * @param ctx - ORM session context
670
+ * @returns Promise of plain entity instances
671
+ * @example
672
+ * const rows = await selectFromEntity(User).executePlain(session);
673
+ * // rows is EntityInstance<UserTable>[] (plain objects)
674
+ * rows[0] instanceof User; // false
675
+ */
676
+ async executePlain(ctx: OrmSession): Promise<EntityInstance<TTable>[]> {
677
+ const builder = this.ensureDefaultSelection();
678
+ return executeHydratedPlain(ctx, builder) as EntityInstance<TTable>[];
679
+ }
680
+
681
+ /**
682
+ * Executes the query and returns results as real class instances.
683
+ * Unlike execute(), this returns actual instances of the decorated entity class
684
+ * with working methods and proper instanceof checks.
685
+ * @param entityClass - The entity class constructor
686
+ * @param ctx - ORM session context
687
+ * @returns Promise of entity class instances
688
+ * @example
689
+ * const users = await selectFromEntity(User)
690
+ * .include('posts')
691
+ * .executeAs(User, session);
692
+ * users[0] instanceof User; // true!
693
+ * users[0].getFullName(); // works!
635
694
  */
636
- async execute(ctx: OrmSession): Promise<EntityInstance<TTable>[]> {
637
- return executeHydrated(ctx, this);
695
+ async executeAs<TEntity extends object>(
696
+ entityClass: EntityConstructor<TEntity>,
697
+ ctx: OrmSession
698
+ ): Promise<TEntity[]> {
699
+ const builder = this.ensureDefaultSelection();
700
+ const results = await executeHydrated(ctx, builder);
701
+ return materializeAs(entityClass, results as unknown as Record<string, unknown>[]);
638
702
  }
639
703
 
640
704
  /**
@@ -656,8 +720,9 @@ export class SelectQueryBuilder<T = unknown, TTable extends TableDef = TableDef>
656
720
  async executePaged(
657
721
  session: OrmSession,
658
722
  options: { page: number; pageSize: number }
659
- ): Promise<{ items: EntityInstance<TTable>[]; totalItems: number }> {
660
- return executePagedQuery(this, session, options, sess => this.count(sess));
723
+ ): Promise<{ items: T[]; totalItems: number }> {
724
+ const builder = this.ensureDefaultSelection();
725
+ return executePagedQuery(builder, session, options, sess => this.count(sess));
661
726
  }
662
727
 
663
728
  /**
@@ -670,8 +735,13 @@ export class SelectQueryBuilder<T = unknown, TTable extends TableDef = TableDef>
670
735
  * const hydCtx = new HydrationContext();
671
736
  * const users = await qb.executeWithContexts(execCtx, hydCtx);
672
737
  */
673
- async executeWithContexts(execCtx: ExecutionContext, hydCtx: HydrationContext): Promise<EntityInstance<TTable>[]> {
674
- return executeHydratedWithContexts(execCtx, hydCtx, this);
738
+ async executeWithContexts(execCtx: ExecutionContext, hydCtx: HydrationContext): Promise<T[]> {
739
+ const builder = this.ensureDefaultSelection();
740
+ const results = await executeHydratedWithContexts(execCtx, hydCtx, builder);
741
+ if (this.entityConstructor) {
742
+ return materializeAs(this.entityConstructor, results as unknown as Record<string, unknown>[]) as unknown as T[];
743
+ }
744
+ return results as unknown as T[];
675
745
  }
676
746
 
677
747
  /**