metal-orm 1.0.82 → 1.0.83

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.
@@ -68,6 +68,7 @@ import { SelectCTEFacet } from './select/cte-facet.js';
68
68
  import { SelectSetOpFacet } from './select/setop-facet.js';
69
69
  import { SelectRelationFacet } from './select/relation-facet.js';
70
70
  import { buildFilterParameters, extractSchema, SchemaOptions, OpenApiSchemaBundle } from '../openapi/index.js';
71
+ import { hasParamOperandsInQuery } from '../core/ast/ast-validation.js';
71
72
 
72
73
  type ColumnSelectionValue =
73
74
  | ColumnDef
@@ -713,120 +714,138 @@ export class SelectQueryBuilder<T = EntityInstance<TableDef>, TTable extends Tab
713
714
  return this.env.table as TTable;
714
715
  }
715
716
 
716
- /**
717
- * Ensures that if no columns are selected, all columns from the table are selected by default.
718
- */
719
- private ensureDefaultSelection(): SelectQueryBuilder<T, TTable> {
720
- const columns = this.context.state.ast.columns;
721
- if (!columns || columns.length === 0) {
722
- const columnKeys = Object.keys(this.env.table.columns) as (keyof TTable['columns'] & string)[];
723
- return this.select(...columnKeys);
724
- }
725
- return this;
726
- }
727
-
728
- /**
729
- * Executes the query and returns hydrated results.
730
- * If the builder was created with an entity constructor (e.g. via selectFromEntity),
731
- * this will automatically return fully materialized entity instances.
732
- *
733
- * @param ctx - ORM session context
734
- * @returns Promise of entity instances (or objects if generic T is not an entity)
735
- * @example
736
- * const users = await selectFromEntity(User).execute(session);
737
- * // users is User[]
738
- * users[0] instanceof User; // true
739
- */
740
- async execute(ctx: OrmSession): Promise<T[]> {
741
- if (this.entityConstructor) {
742
- return this.executeAs(this.entityConstructor, ctx) as unknown as T[];
743
- }
744
- const builder = this.ensureDefaultSelection();
745
- return executeHydrated(ctx, builder) as unknown as T[];
746
- }
717
+ /**
718
+ * Ensures that if no columns are selected, all columns from the table are selected by default.
719
+ */
720
+ private ensureDefaultSelection(): SelectQueryBuilder<T, TTable> {
721
+ const columns = this.context.state.ast.columns;
722
+ if (!columns || columns.length === 0) {
723
+ const columnKeys = Object.keys(this.env.table.columns) as (keyof TTable['columns'] & string)[];
724
+ return this.select(...columnKeys);
725
+ }
726
+ return this;
727
+ }
728
+
729
+ /**
730
+ * Validates that the query does not contain Param operands.
731
+ * Param proxies are only for schema generation, not execution.
732
+ */
733
+ private validateNoParamOperands(): void {
734
+ const ast = this.context.hydration.applyToAst(this.context.state.ast);
735
+ const hasParams = hasParamOperandsInQuery(ast);
736
+ if (hasParams) {
737
+ throw new Error('Cannot execute query containing Param operands. Param proxies are only for schema generation (getSchema()). If you need real parameters, use literal values.');
738
+ }
739
+ }
747
740
 
748
- /**
749
- * Executes the query and returns plain row objects (POJOs), ignoring any entity materialization.
750
- * Use this if you want raw data even when using selectFromEntity.
751
- *
752
- * @param ctx - ORM session context
753
- * @returns Promise of plain entity instances
754
- * @example
755
- * const rows = await selectFromEntity(User).executePlain(session);
756
- * // rows is EntityInstance<UserTable>[] (plain objects)
757
- * rows[0] instanceof User; // false
758
- */
759
- async executePlain(ctx: OrmSession): Promise<EntityInstance<TTable>[]> {
760
- const builder = this.ensureDefaultSelection();
761
- const rows = await executeHydratedPlain(ctx, builder);
762
- return rows as EntityInstance<TTable>[];
763
- }
741
+ /**
742
+ * Executes the query and returns hydrated results.
743
+ * If the builder was created with an entity constructor (e.g. via selectFromEntity),
744
+ * this will automatically return fully materialized entity instances.
745
+ *
746
+ * @param ctx - ORM session context
747
+ * @returns Promise of entity instances (or objects if generic T is not an entity)
748
+ * @example
749
+ * const users = await selectFromEntity(User).execute(session);
750
+ * // users is User[]
751
+ * users[0] instanceof User; // true
752
+ */
753
+ async execute(ctx: OrmSession): Promise<T[]> {
754
+ this.validateNoParamOperands();
755
+ if (this.entityConstructor) {
756
+ return this.executeAs(this.entityConstructor, ctx) as unknown as T[];
757
+ }
758
+ const builder = this.ensureDefaultSelection();
759
+ return executeHydrated(ctx, builder) as unknown as T[];
760
+ }
764
761
 
765
- /**
766
- * Executes the query and returns results as real class instances.
767
- * Unlike execute(), this returns actual instances of the decorated entity class
768
- * with working methods and proper instanceof checks.
769
- * @param entityClass - The entity class constructor
770
- * @param ctx - ORM session context
771
- * @returns Promise of entity class instances
772
- * @example
773
- * const users = await selectFromEntity(User)
774
- * .include('posts')
775
- * .executeAs(User, session);
776
- * users[0] instanceof User; // true!
777
- * users[0].getFullName(); // works!
778
- */
779
- async executeAs<TEntity extends object>(
780
- entityClass: EntityConstructor<TEntity>,
781
- ctx: OrmSession
782
- ): Promise<TEntity[]> {
783
- const builder = this.ensureDefaultSelection();
784
- const results = await executeHydrated(ctx, builder);
785
- return materializeAs(entityClass, results as unknown as Record<string, unknown>[]);
786
- }
762
+ /**
763
+ * Executes the query and returns plain row objects (POJOs), ignoring any entity materialization.
764
+ * Use this if you want raw data even when using selectFromEntity.
765
+ *
766
+ * @param ctx - ORM session context
767
+ * @returns Promise of plain entity instances
768
+ * @example
769
+ * const rows = await selectFromEntity(User).executePlain(session);
770
+ * // rows is EntityInstance<UserTable>[] (plain objects)
771
+ * rows[0] instanceof User; // false
772
+ */
773
+ async executePlain(ctx: OrmSession): Promise<EntityInstance<TTable>[]> {
774
+ this.validateNoParamOperands();
775
+ const builder = this.ensureDefaultSelection();
776
+ const rows = await executeHydratedPlain(ctx, builder);
777
+ return rows as EntityInstance<TTable>[];
778
+ }
787
779
 
788
- /**
789
- * Executes a count query for the current builder without LIMIT/OFFSET clauses.
790
- *
791
- * @example
792
- * const total = await qb.count(session);
793
- */
794
- async count(session: OrmSession): Promise<number> {
795
- return executeCount(this.context, this.env, session);
796
- }
780
+ /**
781
+ * Executes the query and returns results as real class instances.
782
+ * Unlike execute(), this returns actual instances of the decorated entity class
783
+ * with working methods and proper instanceof checks.
784
+ * @param entityClass - The entity class constructor
785
+ * @param ctx - ORM session context
786
+ * @returns Promise of entity class instances
787
+ * @example
788
+ * const users = await selectFromEntity(User)
789
+ * .include('posts')
790
+ * .executeAs(User, session);
791
+ * users[0] instanceof User; // true!
792
+ * users[0].getFullName(); // works!
793
+ */
794
+ async executeAs<TEntity extends object>(
795
+ entityClass: EntityConstructor<TEntity>,
796
+ ctx: OrmSession
797
+ ): Promise<TEntity[]> {
798
+ this.validateNoParamOperands();
799
+ const builder = this.ensureDefaultSelection();
800
+ const results = await executeHydrated(ctx, builder);
801
+ return materializeAs(entityClass, results as unknown as Record<string, unknown>[]);
802
+ }
797
803
 
798
- /**
799
- * Executes the query and returns both the paged items and the total.
800
- *
801
- * @example
802
- * const { items, totalItems, page, pageSize } = await qb.executePaged(session, { page: 1, pageSize: 20 });
803
- */
804
- async executePaged(
805
- session: OrmSession,
806
- options: { page: number; pageSize: number }
807
- ): Promise<PaginatedResult<T>> {
808
- const builder = this.ensureDefaultSelection();
809
- return executePagedQuery(builder, session, options, sess => builder.count(sess));
810
- }
804
+ /**
805
+ * Executes a count query for the current builder without LIMIT/OFFSET clauses.
806
+ *
807
+ * @example
808
+ * const total = await qb.count(session);
809
+ */
810
+ async count(session: OrmSession): Promise<number> {
811
+ this.validateNoParamOperands();
812
+ return executeCount(this.context, this.env, session);
813
+ }
814
+
815
+ /**
816
+ * Executes the query and returns both the paged items and the total.
817
+ *
818
+ * @example
819
+ * const { items, totalItems, page, pageSize } = await qb.executePaged(session, { page: 1, pageSize: 20 });
820
+ */
821
+ async executePaged(
822
+ session: OrmSession,
823
+ options: { page: number; pageSize: number }
824
+ ): Promise<PaginatedResult<T>> {
825
+ this.validateNoParamOperands();
826
+ const builder = this.ensureDefaultSelection();
827
+ return executePagedQuery(builder, session, options, sess => builder.count(sess));
828
+ }
811
829
 
812
- /**
813
- * Executes the query with provided execution and hydration contexts
814
- * @param execCtx - Execution context
815
- * @param hydCtx - Hydration context
816
- * @returns Promise of entity instances
817
- * @example
818
- * const execCtx = new ExecutionContext(session);
819
- * const hydCtx = new HydrationContext();
820
- * const users = await qb.executeWithContexts(execCtx, hydCtx);
821
- */
822
- async executeWithContexts(execCtx: ExecutionContext, hydCtx: HydrationContext): Promise<T[]> {
823
- const builder = this.ensureDefaultSelection();
824
- const results = await executeHydratedWithContexts(execCtx, hydCtx, builder);
825
- if (this.entityConstructor) {
826
- return materializeAs(this.entityConstructor, results as unknown as Record<string, unknown>[]) as unknown as T[];
827
- }
828
- return results as unknown as T[];
829
- }
830
+ /**
831
+ * Executes the query with provided execution and hydration contexts
832
+ * @param execCtx - Execution context
833
+ * @param hydCtx - Hydration context
834
+ * @returns Promise of entity instances
835
+ * @example
836
+ * const execCtx = new ExecutionContext(session);
837
+ * const hydCtx = new HydrationContext();
838
+ * const users = await qb.executeWithContexts(execCtx, hydCtx);
839
+ */
840
+ async executeWithContexts(execCtx: ExecutionContext, hydCtx: HydrationContext): Promise<T[]> {
841
+ this.validateNoParamOperands();
842
+ const builder = this.ensureDefaultSelection();
843
+ const results = await executeHydratedWithContexts(execCtx, hydCtx, builder);
844
+ if (this.entityConstructor) {
845
+ return materializeAs(this.entityConstructor, results as unknown as Record<string, unknown>[]) as unknown as T[];
846
+ }
847
+ return results as unknown as T[];
848
+ }
830
849
 
831
850
  /**
832
851
  * Adds a WHERE condition to the query