arkormx 2.10.1 → 2.11.0

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.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { $ as applyOperationsToPersistedColumnMappingsState, $t as resolvePrismaType, A as getRuntimePrismaClient, An as writeAppliedMigrationsStateToStore, At as buildInverseRelationLine, B as getRegisteredModels, Bt as deriveRelationFieldName, C as getActiveTransactionClient, Cn as markMigrationRun, Ct as applyMigrationRollbackToPrismaSchema, D as getRuntimeDebugHandler, Dn as resolveMigrationStateFilePath, Dt as buildEnumBlock, E as getRuntimeClient, En as removeAppliedMigration, Et as applyOperationsToPrismaSchema, F as loadArkormConfig, Fn as RelationResolutionException, Ft as buildUniqueConstraintLine, G as loadModelsFrom, Gt as formatDefaultValue, H as getRegisteredSeeders, Ht as escapeRegex, I as resetArkormRuntimeForTests, In as ArkormCollection, It as createMigrationTimestamp, J as registerMigrations, Jt as generateMigrationFile, K as loadSeedersFrom, Kt as formatEnumDefaultValue, L as runArkormTransaction, Ln as ArkormException, Lt as deriveCollectionFieldName, M as isDelegateLike, Mn as UnsupportedAdapterFeatureException, Mt as buildModelBlock, N as isQuerySchemaLike, Nn as SetBasedEagerLoader, Nt as buildPrimaryKeyLine, O as getRuntimePaginationCurrentPageResolver, On as supportsDatabaseMigrationState, Ot as buildFieldLine, P as isTransactionCapableClient, Pt as buildRelationLine, Q as resetRuntimeRegistryForTests, Qt as resolveMigrationClassName, R as getRegisteredFactories, Rt as deriveInverseRelationAlias, S as getActiveTransactionAdapter, Sn as markMigrationApplied, St as applyMigrationRollbackToDatabase, T as getRuntimeAdapter, Tn as readAppliedMigrationsStateFromStore, Tt as applyMigrationToPrismaSchema, U as loadFactoriesFrom, Ut as findEnumBlock, V as getRegisteredPaths, Vt as deriveSingularFieldName, W as loadMigrationsFrom, Wt as findModelBlock, X as registerPaths, Xt as pad, Y as registerModels, Yt as getMigrationPlan, Z as registerSeeders, Zt as resolveEnumName, _ as bindAdapterToModels, _n as deleteAppliedMigrationsStateFromStore, _t as PRISMA_ENUM_REGEX, a as HasOneThroughRelation, an as supportsDatabaseReset, at as getPersistedPrimaryKeyGeneration, b as emitRuntimeDebugEvent, bn as getLatestAppliedMigrations, bt as applyCreateTableOperation, c as HasManyRelation, cn as SchemaBuilder, ct as readPersistedColumnMappingsState, d as BelongsToManyRelation, dn as PrimaryKeyGenerationPlanner, dt as resolveColumnMappingsFilePath, en as runMigrationWithPrisma, et as createEmptyPersistedColumnMappingsState, f as Relation, fn as ForeignKeyBuilder, ft as resolvePersistedMetadataFeatures, g as awaitConfiguredModelsRegistration, gn as createEmptyAppliedMigrationsState, gt as PRISMA_ENUM_MEMBER_REGEX, h as URLDriver, hn as computeMigrationChecksum, ht as writePersistedColumnMappingsState, i as MorphManyRelation, in as supportsDatabaseMigrationExecution, it as getPersistedEnumTsType, j as getUserConfig, jn as RuntimeModuleLoader, jt as buildMigrationSource, k as getRuntimePaginationURLDriverFactory, kn as writeAppliedMigrationsState, kt as buildIndexLine, l as BelongsToRelation, ln as EnumBuilder, lt as rebuildPersistedColumnMappingsState, m as Paginator, mn as buildMigrationRunId, mt as validatePersistedMetadataFeaturesForMigrations, n as MorphToManyRelation, nn as stripPrismaSchemaModelsAndEnums, nt as getPersistedColumnMap, o as HasOneRelation, on as toMigrationFileSlug, ot as getPersistedTableMetadata, p as LengthAwarePaginator, pn as buildMigrationIdentity, pt as syncPersistedColumnMappingsFromState, q as registerFactories, qt as formatRelationAction, r as MorphOneRelation, rn as supportsDatabaseCreation, rt as getPersistedEnumMap, s as HasManyThroughRelation, sn as toModelName, st as getPersistedTimestampColumns, t as MorphToRelation, tn as runPrismaCommand, tt as deletePersistedColumnMappingsState, un as TableBuilder, ut as resetPersistedColumnMappingsCache, v as configureArkormRuntime, vn as findAppliedMigration, vt as PRISMA_MODEL_REGEX, w as getDefaultStubsPath, wn as readAppliedMigrationsState, wt as applyMigrationToDatabase, x as ensureArkormConfigLoading, xn as isMigrationApplied, xt as applyDropTableOperation, y as defineConfig, yn as getLastMigrationRun, yt as applyAlterTableOperation, z as getRegisteredMigrations, zt as deriveRelationAlias } from "./relationship-CmhzOlEo.mjs";
1
+ import { $ as applyOperationsToPersistedColumnMappingsState, $n as SetBasedEagerLoader, $t as resolvePrismaType, A as getRuntimePrismaClient, An as where, At as buildInverseRelationLine, B as getRegisteredModels, Bn as getLatestAppliedMigrations, Bt as deriveRelationFieldName, C as getActiveTransactionClient, Cn as fromExpressionNode, Ct as applyMigrationRollbackToPrismaSchema, D as getRuntimeDebugHandler, Dn as raw, Dt as buildEnumBlock, E as getRuntimeClient, En as min, Et as applyOperationsToPrismaSchema, F as loadArkormConfig, Fn as computeMigrationChecksum, Ft as buildUniqueConstraintLine, G as loadModelsFrom, Gn as readAppliedMigrationsStateFromStore, Gt as formatDefaultValue, H as getRegisteredSeeders, Hn as markMigrationApplied, Ht as escapeRegex, I as resetArkormRuntimeForTests, In as createEmptyAppliedMigrationsState, It as createMigrationTimestamp, J as registerMigrations, Jn as supportsDatabaseMigrationState, Jt as generateMigrationFile, K as loadSeedersFrom, Kn as removeAppliedMigration, Kt as formatEnumDefaultValue, L as runArkormTransaction, Ln as deleteAppliedMigrationsStateFromStore, Lt as deriveCollectionFieldName, M as isDelegateLike, Mn as ForeignKeyBuilder, Mt as buildModelBlock, N as isQuerySchemaLike, Nn as buildMigrationIdentity, Nt as buildPrimaryKeyLine, O as getRuntimePaginationCurrentPageResolver, On as sum, Ot as buildFieldLine, P as isTransactionCapableClient, Pn as buildMigrationRunId, Pt as buildRelationLine, Q as resetRuntimeRegistryForTests, Qn as UnsupportedAdapterFeatureException, Qt as resolveMigrationClassName, R as getRegisteredFactories, Rn as findAppliedMigration, Rt as deriveInverseRelationAlias, S as getActiveTransactionAdapter, Sn as fn, St as applyMigrationRollbackToDatabase, T as getRuntimeAdapter, Tn as max, Tt as applyMigrationToPrismaSchema, U as loadFactoriesFrom, Un as markMigrationRun, Ut as findEnumBlock, V as getRegisteredPaths, Vn as isMigrationApplied, Vt as deriveSingularFieldName, W as loadMigrationsFrom, Wn as readAppliedMigrationsState, Wt as findModelBlock, X as registerPaths, Xn as writeAppliedMigrationsStateToStore, Xt as pad, Y as registerModels, Yn as writeAppliedMigrationsState, Yt as getMigrationPlan, Z as registerSeeders, Zn as RuntimeModuleLoader, Zt as resolveEnumName, _ as bindAdapterToModels, _n as caseWhen, _t as PRISMA_ENUM_REGEX, a as HasOneThroughRelation, an as supportsDatabaseReset, at as getPersistedPrimaryKeyGeneration, b as emitRuntimeDebugEvent, bn as count, bt as applyCreateTableOperation, c as HasManyRelation, cn as SchemaBuilder, ct as readPersistedColumnMappingsState, d as BelongsToManyRelation, dn as resolveGeneratedExpression, dt as resolveColumnMappingsFilePath, en as runMigrationWithPrisma, et as createEmptyPersistedColumnMappingsState, f as Relation, fn as AggregateExpression, ft as resolvePersistedMetadataFeatures, g as awaitConfiguredModelsRegistration, gn as avg, gt as PRISMA_ENUM_MEMBER_REGEX, h as URLDriver, hn as JsonExpression, ht as writePersistedColumnMappingsState, i as MorphManyRelation, in as supportsDatabaseMigrationExecution, it as getPersistedEnumTsType, j as getUserConfig, jn as PrimaryKeyGenerationPlanner, jt as buildMigrationSource, k as getRuntimePaginationURLDriverFactory, kn as val, kt as buildIndexLine, l as BelongsToRelation, ln as EnumBuilder, lt as rebuildPersistedColumnMappingsState, m as Paginator, mn as Expression, mt as validatePersistedMetadataFeaturesForMigrations, n as MorphToManyRelation, nn as stripPrismaSchemaModelsAndEnums, nr as ArkormCollection, nt as getPersistedColumnMap, o as HasOneRelation, on as toMigrationFileSlug, ot as getPersistedTableMetadata, p as LengthAwarePaginator, pn as CaseExpression, pt as syncPersistedColumnMappingsFromState, q as registerFactories, qn as resolveMigrationStateFilePath, qt as formatRelationAction, r as MorphOneRelation, rn as supportsDatabaseCreation, rr as ArkormException, rt as getPersistedEnumMap, s as HasManyThroughRelation, sn as toModelName, st as getPersistedTimestampColumns, t as MorphToRelation, tn as runPrismaCommand, tr as RelationResolutionException, tt as deletePersistedColumnMappingsState, un as TableBuilder, ut as resetPersistedColumnMappingsCache, v as configureArkormRuntime, vn as coalesce, vt as PRISMA_MODEL_REGEX, w as getDefaultStubsPath, wn as json, wt as applyMigrationToDatabase, x as ensureArkormConfigLoading, xn as expressionBuilder, xt as applyDropTableOperation, y as defineConfig, yn as col, yt as applyAlterTableOperation, z as getRegisteredMigrations, zn as getLastMigrationRun, zt as deriveRelationAlias } from "./relationship-CP1xbMOa.mjs";
2
2
  import { Pool } from "pg";
3
3
  import { join, resolve } from "node:path";
4
4
  import { createRequire } from "module";
@@ -64,7 +64,8 @@ var KyselyDatabaseAdapter = class KyselyDatabaseAdapter {
64
64
  rawWhere: true,
65
65
  distinct: true,
66
66
  groupBy: true,
67
- joins: true
67
+ joins: true,
68
+ expressions: true
68
69
  };
69
70
  }
70
71
  resolveConfiguredDatabaseName(connectionString) {
@@ -385,6 +386,13 @@ var KyselyDatabaseAdapter = class KyselyDatabaseAdapter {
385
386
  }
386
387
  buildSchemaColumnDefinition(table, column) {
387
388
  const parts = [this.quoteIdentifier(column.map ?? column.name), this.resolveSchemaColumnType(table, column)];
389
+ if (column.generatedExpression) {
390
+ const storage = column.generatedStored === false ? "" : " stored";
391
+ parts.push(`generated always as (${column.generatedExpression})${storage}`);
392
+ if (column.unique) parts.push("unique");
393
+ if (!column.nullable && !column.primary) parts.push("not null");
394
+ return parts.join(" ");
395
+ }
388
396
  if (this.shouldUseIdentity(column)) parts.push("generated by default as identity");
389
397
  const defaultValue = this.resolveSchemaColumnDefault(column);
390
398
  if (defaultValue && !this.shouldUseIdentity(column)) parts.push(`default ${defaultValue}`);
@@ -450,12 +458,60 @@ var KyselyDatabaseAdapter = class KyselyDatabaseAdapter {
450
458
  const table = this.resolveMappedTable(operation.table);
451
459
  await this.ensureEnumTypes(table, operation.addColumns, executor);
452
460
  for (const column of operation.addColumns) await this.executeRawStatement(`alter table ${this.quoteIdentifier(table)} add column if not exists ${this.buildSchemaColumnDefinition(table, column)}`, executor);
461
+ for (const column of operation.changeColumns ?? []) await this.executeChangeColumn(table, column, executor);
453
462
  for (const column of operation.dropColumns) await this.executeRawStatement(`alter table ${this.quoteIdentifier(table)} drop column if exists ${this.quoteIdentifier(column)}`, executor);
454
463
  for (const foreignKey of operation.addForeignKeys ?? []) await this.executeRawStatement(`alter table ${this.quoteIdentifier(table)} add ${this.buildSchemaForeignKeyConstraint(table, foreignKey, operation.addColumns)}`, executor);
455
464
  if (operation.addPrimaryKey) await this.executeRawStatement(`alter table ${this.quoteIdentifier(table)} add ${this.buildSchemaPrimaryKeyConstraint(table, operation.addPrimaryKey, operation.addColumns)}`, executor);
456
465
  for (const constraint of operation.addUniqueConstraints ?? []) await this.executeRawStatement(`alter table ${this.quoteIdentifier(table)} add ${this.buildSchemaUniqueConstraint(table, constraint, operation.addColumns)}`, executor);
457
466
  for (const index of operation.addIndexes ?? []) await this.executeRawStatement(this.buildSchemaIndexStatement(table, index, operation.addColumns), executor);
458
467
  }
468
+ async enumTypeExists(enumName, executor) {
469
+ const result = await sql`
470
+ select exists(select 1 from pg_type where typname = ${enumName}) as exists
471
+ `.execute(executor);
472
+ return Boolean(result.rows[0]?.exists);
473
+ }
474
+ /**
475
+ * Redefine an existing column in place using ALTER COLUMN statements: the
476
+ * column type (recreating the enum type when enum values change), nullability,
477
+ * default, and a conventionally-named unique constraint.
478
+ *
479
+ * @param table
480
+ * @param column
481
+ * @param executor
482
+ */
483
+ async executeChangeColumn(table, column, executor) {
484
+ const quotedTable = this.quoteIdentifier(table);
485
+ const physicalName = column.map ?? column.name;
486
+ const physical = this.quoteIdentifier(physicalName);
487
+ if (column.type === "enum") {
488
+ const enumName = this.resolveSchemaEnumName(table, column);
489
+ const values = column.enumValues ?? [];
490
+ if (values.length === 0) throw new ArkormException(`Enum column [${column.name}] requires enum values to change its definition.`);
491
+ const quotedEnum = this.quoteIdentifier(enumName);
492
+ const enumLiterals = values.map((value) => this.quoteLiteral(value)).join(", ");
493
+ if (await this.enumTypeExists(enumName, executor)) {
494
+ const tempEnum = this.quoteIdentifier(`${enumName}__arkorm_change`);
495
+ await this.executeRawStatement(`drop type if exists ${tempEnum}`, executor);
496
+ await this.executeRawStatement(`alter type ${quotedEnum} rename to ${tempEnum}`, executor);
497
+ await this.executeRawStatement(`create type ${quotedEnum} as enum (${enumLiterals})`, executor);
498
+ await this.executeRawStatement(`alter table ${quotedTable} alter column ${physical} type ${quotedEnum} using ${physical}::text::${quotedEnum}`, executor);
499
+ await this.executeRawStatement(`drop type ${tempEnum}`, executor);
500
+ } else {
501
+ await this.executeRawStatement(`create type ${quotedEnum} as enum (${enumLiterals})`, executor);
502
+ await this.executeRawStatement(`alter table ${quotedTable} alter column ${physical} type ${quotedEnum} using ${physical}::text::${quotedEnum}`, executor);
503
+ }
504
+ } else {
505
+ const targetType = this.resolveSchemaColumnType(table, column);
506
+ await this.executeRawStatement(`alter table ${quotedTable} alter column ${physical} type ${targetType} using ${physical}::${targetType}`, executor);
507
+ }
508
+ await this.executeRawStatement(column.nullable ? `alter table ${quotedTable} alter column ${physical} drop not null` : `alter table ${quotedTable} alter column ${physical} set not null`, executor);
509
+ const defaultValue = this.resolveSchemaColumnDefault(column);
510
+ await this.executeRawStatement(defaultValue ? `alter table ${quotedTable} alter column ${physical} set default ${defaultValue}` : `alter table ${quotedTable} alter column ${physical} drop default`, executor);
511
+ const constraint = this.quoteIdentifier(`${table}_${physicalName}_key`);
512
+ await this.executeRawStatement(`alter table ${quotedTable} drop constraint if exists ${constraint}`, executor);
513
+ if (column.unique) await this.executeRawStatement(`alter table ${quotedTable} add constraint ${constraint} unique (${physical})`, executor);
514
+ }
459
515
  async executeDropTableOperation(operation, executor) {
460
516
  const table = this.resolveMappedTable(operation.table);
461
517
  await this.executeRawStatement(`drop table if exists ${this.quoteIdentifier(table)} cascade`, executor);
@@ -594,11 +650,16 @@ var KyselyDatabaseAdapter = class KyselyDatabaseAdapter {
594
650
  }
595
651
  buildSelectList(target, columns) {
596
652
  if (!columns || columns.length === 0) return sql.raw("*");
597
- return sql.join(columns.map(({ column, alias, raw, wildcard }) => {
653
+ return sql.join(columns.map(({ column, alias, raw, wildcard, expression }) => {
598
654
  if (wildcard) return sql.raw("*");
655
+ if (expression) {
656
+ const compiled = this.buildExpression(target, expression);
657
+ const resultAlias = alias ?? column;
658
+ return resultAlias ? sql`${compiled} as ${sql.id(resultAlias)}` : compiled;
659
+ }
599
660
  if (raw) {
600
- const expression = sql.raw(column);
601
- return alias ? sql`${expression} as ${sql.id(alias)}` : expression;
661
+ const rawExpression = sql.raw(column);
662
+ return alias ? sql`${rawExpression} as ${sql.id(alias)}` : rawExpression;
602
663
  }
603
664
  const mappedColumn = this.mapColumn(target, column);
604
665
  const resultAlias = alias ?? column;
@@ -608,13 +669,18 @@ var KyselyDatabaseAdapter = class KyselyDatabaseAdapter {
608
669
  }
609
670
  buildOrderBy(target, orderBy) {
610
671
  if (!orderBy || orderBy.length === 0) return sql``;
611
- return sql` order by ${sql.join(orderBy.map(({ column, direction }) => {
612
- return sql`${sql.ref(this.mapColumn(target, column))} ${sql.raw(direction === "desc" ? "desc" : "asc")}`;
672
+ return sql` order by ${sql.join(orderBy.map(({ column, direction, expression }) => {
673
+ return sql`${expression ? this.buildExpression(target, expression) : sql.ref(this.mapColumn(target, column))} ${sql.raw(direction === "desc" ? "desc" : "asc")}`;
613
674
  }), sql`, `)}`;
614
675
  }
615
676
  buildGroupBy(target, groupBy) {
616
677
  if (!groupBy || groupBy.length === 0) return sql``;
617
- return sql` group by ${sql.join(groupBy.map((column) => sql.ref(this.mapColumn(target, column))), sql`, `)}`;
678
+ return sql` group by ${sql.join(groupBy.map((item) => {
679
+ if (typeof item === "string") return sql.ref(this.mapColumn(target, item));
680
+ if ("alias" in item) return sql.ref(item.alias);
681
+ if ("expression" in item) return this.buildExpression(target, item.expression);
682
+ return this.buildRawExpressionFragment(item.raw.sql, item.raw.bindings ?? []);
683
+ }), sql`, `)}`;
618
684
  }
619
685
  buildHavingClause(target, having) {
620
686
  if (!having) return sql``;
@@ -763,6 +829,96 @@ var KyselyDatabaseAdapter = class KyselyDatabaseAdapter {
763
829
  const contains = sql`${accessor} @> ${jsonValue}::jsonb`;
764
830
  return condition.not ? sql`not (${contains})` : contains;
765
831
  }
832
+ /**
833
+ * Compiles a serialized {@link ExpressionNode} into a parameterized SQL fragment.
834
+ * Shared by expression-backed select columns, `group by`, `order by`, and any
835
+ * boolean expression used as a `where`/`having` predicate.
836
+ *
837
+ * @param target
838
+ * @param node
839
+ * @returns
840
+ */
841
+ buildExpression(target, node) {
842
+ switch (node.kind) {
843
+ case "column": return this.buildExpressionColumn(target, node.name);
844
+ case "value": return sql`${node.value}`;
845
+ case "raw": return this.buildRawExpressionFragment(node.sql, node.bindings);
846
+ case "json": return this.buildJsonValueExpression(target, node);
847
+ case "function": {
848
+ const args = node.args.map((arg) => this.buildExpression(target, arg));
849
+ return sql`${sql.raw(this.sanitizeFunctionName(node.name))}(${sql.join(args, sql`, `)})`;
850
+ }
851
+ case "case": {
852
+ const branches = node.cases.map((branch) => sql`when ${this.buildExpression(target, branch.when)} then ${this.buildExpression(target, branch.then)}`);
853
+ const elseClause = node.else ? sql` else ${this.buildExpression(target, node.else)}` : sql``;
854
+ return sql`case ${sql.join(branches, sql` `)}${elseClause} end`;
855
+ }
856
+ case "binary": return this.buildBinaryExpression(target, node);
857
+ case "in": {
858
+ const operand = this.buildExpression(target, node.operand);
859
+ const values = node.values.map((value) => this.buildExpression(target, value));
860
+ const list = values.length > 0 ? sql.join(values, sql`, `) : sql`null`;
861
+ return node.not ? sql`(${operand} not in (${list}))` : sql`(${operand} in (${list}))`;
862
+ }
863
+ case "null-check": {
864
+ const operand = this.buildExpression(target, node.operand);
865
+ return node.not ? sql`(${operand} is not null)` : sql`(${operand} is null)`;
866
+ }
867
+ case "aggregate": return this.buildAggregateExpression(target, node);
868
+ default: throw new ArkormException(`Unsupported expression node [${node.kind}].`);
869
+ }
870
+ }
871
+ buildExpressionColumn(target, name) {
872
+ if (name.includes(".")) return sql.ref(name);
873
+ return sql.ref(this.mapColumn(target, name));
874
+ }
875
+ buildBinaryExpression(target, node) {
876
+ const left = this.buildExpression(target, node.left);
877
+ const right = this.buildExpression(target, node.right);
878
+ switch (node.operator) {
879
+ case "like": return sql`(${left} like ${right})`;
880
+ case "ilike": return sql`(${left} ilike ${right})`;
881
+ case "not-like": return sql`(${left} not like ${right})`;
882
+ case "not-ilike": return sql`(${left} not ilike ${right})`;
883
+ case "and": return sql`(${left} and ${right})`;
884
+ case "or": return sql`(${left} or ${right})`;
885
+ default: return sql`(${left} ${sql.raw(node.operator)} ${right})`;
886
+ }
887
+ }
888
+ buildAggregateExpression(target, node) {
889
+ const argument = node.arg ? this.buildExpression(target, node.arg) : sql.raw("*");
890
+ const distinctKeyword = node.distinct ? sql`distinct ` : sql``;
891
+ let call = sql`${sql.raw(node.fn)}(${distinctKeyword}${argument})`;
892
+ if (node.filter) call = sql`${call} filter (where ${this.buildExpression(target, node.filter)})`;
893
+ if (node.fn === "sum" || node.fn === "avg") return sql`(${call})::double precision`;
894
+ if (node.fn === "count") return sql`(${call})::bigint`;
895
+ return call;
896
+ }
897
+ buildJsonValueExpression(target, node) {
898
+ const base = sql`${sql.ref(this.mapColumn(target, node.column))}::jsonb`;
899
+ let accessor;
900
+ if (node.path.length === 0) accessor = base;
901
+ else if (node.path.length === 1) accessor = sql`(${base} ->> ${node.path[0]})`;
902
+ else accessor = sql`(${base} #>> ${`{${node.path.join(",")}}`}::text[])`;
903
+ if (node.cast === "number") return sql`(${accessor})::numeric`;
904
+ if (node.cast === "boolean") return sql`(${accessor})::boolean`;
905
+ return accessor;
906
+ }
907
+ buildRawExpressionFragment(rawSql, bindings) {
908
+ const segments = rawSql.split("?");
909
+ if (segments.length !== bindings.length + 1) throw new ArkormException("Raw expression bindings do not match the number of placeholders.");
910
+ const parts = [];
911
+ segments.forEach((segment, index) => {
912
+ if (segment.length > 0) parts.push(sql.raw(this.quoteCamelCaseIdentifiers(segment)));
913
+ if (index < bindings.length) parts.push(sql`${bindings[index]}`);
914
+ });
915
+ if (parts.length === 0) return sql``;
916
+ return sql`${sql.join(parts, sql``)}`;
917
+ }
918
+ sanitizeFunctionName(name) {
919
+ if (!/^[a-zA-Z_][a-zA-Z0-9_.]*$/.test(name)) throw new ArkormException(`Unsupported SQL function name [${name}].`);
920
+ return name;
921
+ }
766
922
  buildWhereCondition(target, condition) {
767
923
  if (!condition) return sql`1 = 1`;
768
924
  if (condition.type === "comparison") return this.buildComparisonCondition(target, condition);
@@ -772,6 +928,7 @@ var KyselyDatabaseAdapter = class KyselyDatabaseAdapter {
772
928
  if (condition.type === "exists") return this.buildExistsCondition(condition);
773
929
  if (condition.type === "full-text") return this.buildFullTextCondition(target, condition);
774
930
  if (condition.type === "json") return this.buildJsonCondition(target, condition);
931
+ if (condition.type === "expression") return sql`${this.buildExpression(target, condition.expression)}`;
775
932
  if (condition.type === "group") {
776
933
  const group = condition;
777
934
  const conditions = group.conditions.map((entry) => {
@@ -1644,6 +1801,7 @@ var PrismaDatabaseAdapter = class PrismaDatabaseAdapter {
1644
1801
  rawWhere: false,
1645
1802
  distinct: false,
1646
1803
  groupBy: false,
1804
+ expressions: false,
1647
1805
  returning: false
1648
1806
  };
1649
1807
  }
@@ -1679,6 +1837,14 @@ var PrismaDatabaseAdapter = class PrismaDatabaseAdapter {
1679
1837
  }
1680
1838
  toQuerySelect(columns) {
1681
1839
  if (!columns || columns.length === 0) return void 0;
1840
+ const expressionColumn = columns.find((column) => column.expression);
1841
+ if (expressionColumn) throw new UnsupportedAdapterFeatureException("Expression select columns are not supported by the Prisma compatibility adapter; use a SQL-backed adapter.", {
1842
+ operation: "adapter.select",
1843
+ meta: {
1844
+ feature: "expressions",
1845
+ alias: expressionColumn.alias
1846
+ }
1847
+ });
1682
1848
  const rawColumn = columns.find((column) => column.raw);
1683
1849
  if (rawColumn) throw new UnsupportedAdapterFeatureException("Raw select expressions are not supported by the Prisma compatibility adapter; use a SQL-backed adapter or DB.raw().", {
1684
1850
  operation: "adapter.select",
@@ -1696,6 +1862,10 @@ var PrismaDatabaseAdapter = class PrismaDatabaseAdapter {
1696
1862
  }
1697
1863
  toQueryOrderBy(orderBy) {
1698
1864
  if (!orderBy || orderBy.length === 0) return void 0;
1865
+ if (orderBy.some((entry) => entry.expression)) throw new UnsupportedAdapterFeatureException("Order-by expressions are not supported by the Prisma compatibility adapter; use a SQL-backed adapter.", {
1866
+ operation: "adapter.select",
1867
+ meta: { feature: "expressions" }
1868
+ });
1699
1869
  return orderBy.map((entry) => ({ [entry.column]: entry.direction }));
1700
1870
  }
1701
1871
  toComparisonWhere(condition) {
@@ -3325,13 +3495,22 @@ var Migration = class {
3325
3495
  static {
3326
3496
  this[MIGRATION_BRAND] = true;
3327
3497
  }
3498
+ /**
3499
+ * Optional lifecycle hook invoked after the migration's schema operations
3500
+ * have been applied to the database, for either direction. Override it to run
3501
+ * extra logic such as seeding or data backfills once the schema is in place.
3502
+ *
3503
+ * @param direction The direction that just ran (`'up'` or `'down'`).
3504
+ */
3505
+ done(_direction) {}
3328
3506
  };
3329
3507
 
3330
3508
  //#endregion
3331
3509
  //#region src/cli/commands/MigrateCommand.ts
3332
3510
  /**
3333
3511
  * The MigrateCommand class implements the CLI command for applying migration
3334
- * classes to the Prisma schema and running the Prisma workflow.
3512
+ * classes to the database or Prisma schema and running the Prisma workflow when
3513
+ * using the Prisma compatibility driver.
3335
3514
  *
3336
3515
  * @author Legacy (3m1n3nc3)
3337
3516
  * @since 0.1.0
@@ -3342,22 +3521,22 @@ var MigrateCommand = class extends Command {
3342
3521
  this.signature = `migrate
3343
3522
  {name? : Migration class or file name}
3344
3523
  {--all : Run all migrations from the configured migrations directory}
3345
- {--deploy : Use prisma migrate deploy instead of migrate dev}
3346
- {--skip-generate : Skip prisma generate}
3524
+ {--deploy : Use prisma migrate deploy instead of migrate dev (Prisma compatibility driver only)}
3525
+ {--skip-generate : Skip prisma generate (Prisma compatibility driver only)}
3347
3526
  {--skip-migrate : Skip prisma migrate command}
3348
3527
  {--state-file= : Path to applied migration state file}
3349
- {--schema= : Explicit prisma schema path}
3350
- {--migration-name= : Name for prisma migrate dev}
3528
+ {--schema= : Explicit prisma schema path (Prisma compatibility driver only)}
3529
+ {--migration-name= : Name for prisma migrate dev (Prisma compatibility driver only)}
3351
3530
  {--create-database : Create the configured database without prompting}
3352
3531
  `;
3353
- this.description = "Apply migration classes to schema.prisma and run Prisma workflow";
3532
+ this.description = "Apply migration classes to the database or schema.prisma and run Prisma workflow when using the Prisma compatibility driver";
3354
3533
  }
3355
3534
  /**
3356
3535
  * Command handler for the migrate command.
3357
3536
  * This method is responsible for orchestrating the migration
3358
3537
  * process, including loading migration classes, applying them to
3359
- * the Prisma schema, and running the appropriate Prisma commands
3360
- * based on the provided options.
3538
+ * the the database or Prisma schema, and running the appropriate Prisma commands
3539
+ * when using the Prisma compatibility driver based on the provided options.
3361
3540
  *
3362
3541
  * @returns
3363
3542
  */
@@ -3566,10 +3745,10 @@ var MigrateFreshCommand = class extends Command {
3566
3745
  constructor(..._args) {
3567
3746
  super(..._args);
3568
3747
  this.signature = `migrate:fresh
3569
- {--skip-generate : Skip prisma generate}
3570
- {--skip-migrate : Skip prisma database sync}
3748
+ {--skip-generate : Skip prisma generate (Prisma compatibility driver only)}
3749
+ {--skip-migrate : Skip prisma database sync (Prisma compatibility driver only)}
3571
3750
  {--state-file= : Path to applied migration state file}
3572
- {--schema= : Explicit prisma schema path}
3751
+ {--schema= : Explicit prisma schema path (Prisma compatibility driver only)}
3573
3752
  {--create-database : Create the configured database without prompting}
3574
3753
  `;
3575
3754
  this.description = "Reset the database and rerun all migration classes";
@@ -3724,12 +3903,12 @@ var MigrateRollbackCommand = class extends Command {
3724
3903
  this.signature = `migrate:rollback
3725
3904
  {--step= : Number of latest applied migration classes to rollback}
3726
3905
  {--dry-run : Preview rollback targets without applying changes}
3727
- {--deploy : Use prisma migrate deploy instead of migrate dev}
3728
- {--skip-generate : Skip prisma generate}
3729
- {--skip-migrate : Skip prisma migrate command}
3906
+ {--deploy : Use prisma migrate deploy instead of migrate dev (Prisma compatibility driver only)}
3907
+ {--skip-generate : Skip prisma generate (Prisma compatibility driver only)}
3908
+ {--skip-migrate : Skip prisma migrate command (Prisma compatibility driver only)}
3730
3909
  {--state-file= : Path to applied migration state file}
3731
- {--schema= : Explicit prisma schema path}
3732
- {--migration-name= : Name for prisma migrate dev}
3910
+ {--schema= : Explicit prisma schema path (Prisma compatibility driver only)}
3911
+ {--migration-name= : Name for prisma migrate dev (Prisma compatibility driver only)}
3733
3912
  `;
3734
3913
  this.description = "Rollback migration classes from schema.prisma and run Prisma workflow";
3735
3914
  }
@@ -3744,7 +3923,7 @@ var MigrateRollbackCommand = class extends Command {
3744
3923
  const useDatabaseMigrations = supportsDatabaseMigrationExecution(adapter);
3745
3924
  const persistedFeatures = resolvePersistedMetadataFeatures(this.app.getConfig("features"));
3746
3925
  let appliedState = await readAppliedMigrationsStateFromStore(adapter, stateFilePath);
3747
- const stepOption = this.option("step");
3926
+ const stepOption = this.option("step", 1);
3748
3927
  const stepCount = stepOption == null ? void 0 : Number(stepOption);
3749
3928
  if (stepCount != null && (!Number.isFinite(stepCount) || stepCount <= 0 || !Number.isInteger(stepCount))) return void this.error("Error: --step must be a positive integer.");
3750
3929
  const targets = stepCount ? getLatestAppliedMigrations(appliedState, stepCount) : (() => {
@@ -4419,14 +4598,34 @@ var QueryBuilder = class QueryBuilder {
4419
4598
  return Math.max(1, resolvedPage);
4420
4599
  }
4421
4600
  where(whereOrCallback) {
4601
+ if (whereOrCallback instanceof Expression) return this.appendExpressionCondition("AND", whereOrCallback);
4422
4602
  if (typeof whereOrCallback === "function") return this.appendNestedWhere("AND", whereOrCallback);
4423
4603
  return this.addLogicalWhere("AND", whereOrCallback);
4424
4604
  }
4425
4605
  orWhere(whereOrCallback) {
4606
+ if (whereOrCallback instanceof Expression) return this.appendExpressionCondition("OR", whereOrCallback);
4426
4607
  if (typeof whereOrCallback === "function") return this.appendNestedWhere("OR", whereOrCallback);
4427
4608
  return this.addLogicalWhere("OR", whereOrCallback);
4428
4609
  }
4429
4610
  /**
4611
+ * Appends a boolean {@link Expression} as a where predicate. When the query is
4612
+ * using the Prisma-like legacy where representation, the expression is merged in
4613
+ * as a structured condition alongside it.
4614
+ */
4615
+ appendExpressionCondition(boolean, expression) {
4616
+ const condition = {
4617
+ type: "expression",
4618
+ expression: expression.toExpressionNode()
4619
+ };
4620
+ if (this.legacyWhere) {
4621
+ const existing = this.tryBuildQueryCondition(this.legacyWhere);
4622
+ this.legacyWhere = void 0;
4623
+ if (existing) this.queryWhere = existing;
4624
+ }
4625
+ this.appendQueryCondition(boolean, condition);
4626
+ return this;
4627
+ }
4628
+ /**
4430
4629
  * Resolve a callback into a parenthesized group condition and append it.
4431
4630
  */
4432
4631
  appendNestedWhere(boolean, callback) {
@@ -5076,15 +5275,17 @@ var QueryBuilder = class QueryBuilder {
5076
5275
  whereIn(key, values) {
5077
5276
  return this.where({ [key]: { in: values } });
5078
5277
  }
5079
- /**
5080
- * Adds an orderBy clause to the query. This will overwrite any existing orderBy clause.
5081
- *
5082
- * @param orderBy
5083
- * @returns
5084
- */
5085
- orderBy(orderBy) {
5278
+ orderBy(orderByOrExpression, direction = "asc") {
5086
5279
  this.randomOrderEnabled = false;
5087
- const normalized = this.normalizeQueryOrderBy(orderBy);
5280
+ if (orderByOrExpression instanceof Expression) {
5281
+ this.queryOrderBy = [...this.queryOrderBy ?? [], {
5282
+ column: "",
5283
+ direction,
5284
+ expression: orderByOrExpression.toExpressionNode()
5285
+ }];
5286
+ return this;
5287
+ }
5288
+ const normalized = this.normalizeQueryOrderBy(orderByOrExpression);
5088
5289
  if (!normalized) throw new UnsupportedAdapterFeatureException("Order clauses must use Arkorm-normalizable column directions.", {
5089
5290
  operation: "orderBy",
5090
5291
  model: this.model.name
@@ -5093,6 +5294,28 @@ var QueryBuilder = class QueryBuilder {
5093
5294
  return this;
5094
5295
  }
5095
5296
  /**
5297
+ * Appends a raw SQL `order by` fragment (with positional `?` bindings), useful
5298
+ * for ordering by a computed expression the builder does not model directly.
5299
+ *
5300
+ * @param sql
5301
+ * @param bindings
5302
+ * @param direction
5303
+ * @returns
5304
+ */
5305
+ orderByRaw(sql, bindings = [], direction = "asc") {
5306
+ this.randomOrderEnabled = false;
5307
+ this.queryOrderBy = [...this.queryOrderBy ?? [], {
5308
+ column: "",
5309
+ direction,
5310
+ expression: {
5311
+ kind: "raw",
5312
+ sql,
5313
+ bindings
5314
+ }
5315
+ }];
5316
+ return this;
5317
+ }
5318
+ /**
5096
5319
  * Puts the query results in random order.
5097
5320
  *
5098
5321
  * @returns
@@ -5458,12 +5681,6 @@ var QueryBuilder = class QueryBuilder {
5458
5681
  pipe(callback) {
5459
5682
  return callback(this);
5460
5683
  }
5461
- /**
5462
- * Adds a select clause to the query. This will overwrite any existing select clause.
5463
- *
5464
- * @param select
5465
- * @returns
5466
- */
5467
5684
  select(select) {
5468
5685
  const normalized = this.normalizeQuerySelect(select);
5469
5686
  if (normalized === null) throw new UnsupportedAdapterFeatureException("Select clauses must use Arkorm-normalizable column projections.", {
@@ -5473,12 +5690,6 @@ var QueryBuilder = class QueryBuilder {
5473
5690
  this.querySelect = normalized;
5474
5691
  return this;
5475
5692
  }
5476
- /**
5477
- * Appends columns or expressions to the existing select clause.
5478
- *
5479
- * @param select
5480
- * @returns
5481
- */
5482
5693
  addSelect(select) {
5483
5694
  const normalized = this.normalizeQuerySelect(select);
5484
5695
  if (normalized === null) throw new UnsupportedAdapterFeatureException("Select clauses must use Arkorm-normalizable column projections.", {
@@ -5502,6 +5713,8 @@ var QueryBuilder = class QueryBuilder {
5502
5713
  return this;
5503
5714
  }
5504
5715
  groupBy(...columns) {
5716
+ const first = columns[0];
5717
+ if (columns.length === 1 && this.isGroupByAggregateSpec(first)) return this.executeGroupByAggregate(first);
5505
5718
  const normalized = Array.isArray(columns[0]) ? columns[0] : columns;
5506
5719
  if (normalized.length === 0) throw new QueryConstraintException("groupBy requires at least one column.", {
5507
5720
  operation: "groupBy",
@@ -5510,6 +5723,109 @@ var QueryBuilder = class QueryBuilder {
5510
5723
  this.queryGroupBy = [...normalized];
5511
5724
  return this;
5512
5725
  }
5726
+ /**
5727
+ * Appends a raw SQL `group by` fragment (with positional `?` bindings), mirroring
5728
+ * `whereRaw`/`havingRaw`. Combines with any columns/expressions already grouped.
5729
+ *
5730
+ * @param sql
5731
+ * @param bindings
5732
+ * @returns
5733
+ */
5734
+ groupByRaw(sql, bindings = []) {
5735
+ this.queryGroupBy = [...this.queryGroupBy ?? [], { raw: {
5736
+ sql,
5737
+ bindings
5738
+ } }];
5739
+ return this;
5740
+ }
5741
+ /**
5742
+ * Resolves the recorded group-by sources into adapter {@link QueryGroupByItem}s.
5743
+ * A bare string that matches a select-column alias is expanded to that column's
5744
+ * underlying expression (group-by-alias); otherwise it is treated as a column.
5745
+ */
5746
+ buildGroupByItems(selectColumns) {
5747
+ if (!this.queryGroupBy || this.queryGroupBy.length === 0) return void 0;
5748
+ const expressionAliases = /* @__PURE__ */ new Map();
5749
+ selectColumns?.forEach((column) => {
5750
+ if (column.expression && column.alias) expressionAliases.set(JSON.stringify(column.expression), column.alias);
5751
+ });
5752
+ const aliasNames = new Set(expressionAliases.values());
5753
+ return this.queryGroupBy.map((source) => {
5754
+ if (source instanceof Expression) {
5755
+ const node = source.toExpressionNode();
5756
+ const alias = expressionAliases.get(JSON.stringify(node));
5757
+ return alias ? { alias } : { expression: node };
5758
+ }
5759
+ if (typeof source === "object") return { raw: source.raw };
5760
+ return aliasNames.has(source) ? { alias: source } : source;
5761
+ });
5762
+ }
5763
+ isGroupByAggregateSpec(value) {
5764
+ return typeof value === "object" && value !== null && !Array.isArray(value) && !(value instanceof Expression) && Array.isArray(value.by);
5765
+ }
5766
+ /**
5767
+ * Executes the query and returns plain, un-hydrated rows keyed by their select
5768
+ * alias — the natural shape for `select` + `groupBy` aggregate reports. Pass a
5769
+ * row type to describe the projection.
5770
+ *
5771
+ * @returns
5772
+ */
5773
+ async getRows() {
5774
+ return await this.executeReadRows();
5775
+ }
5776
+ /**
5777
+ * Runs a Prisma-style grouped aggregate and returns typed rows shaped as
5778
+ * `{ <by columns>, _sum, _avg, _min, _max, _count }`.
5779
+ */
5780
+ async executeGroupByAggregate(spec) {
5781
+ const selectMap = {};
5782
+ spec.by.forEach((column) => {
5783
+ selectMap[column] = true;
5784
+ });
5785
+ const aggregates = [];
5786
+ const register = (group, factory, map, numeric) => {
5787
+ Object.keys(map).forEach((column) => {
5788
+ if (!map[column]) return;
5789
+ const alias = `${group}_${column}`;
5790
+ selectMap[alias] = factory(column);
5791
+ aggregates.push({
5792
+ group,
5793
+ column,
5794
+ alias,
5795
+ numeric
5796
+ });
5797
+ });
5798
+ };
5799
+ if (spec._sum) register("_sum", (column) => sum(column), spec._sum, true);
5800
+ if (spec._avg) register("_avg", (column) => avg(column), spec._avg, true);
5801
+ if (spec._min) register("_min", (column) => min(column), spec._min, false);
5802
+ if (spec._max) register("_max", (column) => max(column), spec._max, false);
5803
+ let countAllAlias;
5804
+ if (spec._count === true) {
5805
+ countAllAlias = "_count_all";
5806
+ selectMap[countAllAlias] = count();
5807
+ } else if (spec._count) register("_count", (column) => count(column), spec._count, true);
5808
+ this.select(selectMap);
5809
+ this.groupBy(spec.by);
5810
+ return (await this.getRows()).map((row) => {
5811
+ const result = {};
5812
+ spec.by.forEach((column) => {
5813
+ result[column] = row[column];
5814
+ });
5815
+ const grouped = {};
5816
+ aggregates.forEach(({ group, column, alias, numeric }) => {
5817
+ grouped[group] ??= {};
5818
+ grouped[group][column] = numeric ? this.coerceNumeric(row[alias]) : row[alias];
5819
+ });
5820
+ Object.assign(result, grouped);
5821
+ if (countAllAlias) result._count = this.coerceNumeric(row[countAllAlias]);
5822
+ return result;
5823
+ });
5824
+ }
5825
+ coerceNumeric(value) {
5826
+ if (value === null || value === void 0) return null;
5827
+ return Number(value);
5828
+ }
5513
5829
  appendHavingCondition(boolean, condition) {
5514
5830
  if (!this.queryHaving) {
5515
5831
  this.queryHaving = condition;
@@ -5530,12 +5846,35 @@ var QueryBuilder = class QueryBuilder {
5530
5846
  value: hasOperator ? maybeValue : operatorOrValue
5531
5847
  };
5532
5848
  }
5533
- having(column, operatorOrValue, maybeValue) {
5534
- this.appendHavingCondition("AND", this.buildHavingComparison(operatorOrValue, maybeValue, column));
5849
+ /**
5850
+ * Resolves the three `having` call shapes into a condition: `having(column, )`,
5851
+ * `having(expression)` (a boolean expression predicate), and
5852
+ * `having(expression, operator, value)` (compare an aggregate to a value).
5853
+ */
5854
+ buildHavingCondition(columnOrExpression, operatorOrValue, maybeValue) {
5855
+ if (columnOrExpression instanceof Expression) return {
5856
+ type: "expression",
5857
+ expression: (operatorOrValue === void 0 ? columnOrExpression : this.compareExpression(columnOrExpression, operatorOrValue, maybeValue)).toExpressionNode()
5858
+ };
5859
+ return this.buildHavingComparison(operatorOrValue, maybeValue, columnOrExpression);
5860
+ }
5861
+ compareExpression(expression, operator, value) {
5862
+ switch (operator) {
5863
+ case "=": return expression.eq(value);
5864
+ case "!=": return expression.ne(value);
5865
+ case ">": return expression.gt(value);
5866
+ case ">=": return expression.gte(value);
5867
+ case "<": return expression.lt(value);
5868
+ case "<=": return expression.lte(value);
5869
+ default: return expression.eq(value);
5870
+ }
5871
+ }
5872
+ having(columnOrExpression, operatorOrValue, maybeValue) {
5873
+ this.appendHavingCondition("AND", this.buildHavingCondition(columnOrExpression, operatorOrValue, maybeValue));
5535
5874
  return this;
5536
5875
  }
5537
- orHaving(column, operatorOrValue, maybeValue) {
5538
- this.appendHavingCondition("OR", this.buildHavingComparison(operatorOrValue, maybeValue, column));
5876
+ orHaving(columnOrExpression, operatorOrValue, maybeValue) {
5877
+ this.appendHavingCondition("OR", this.buildHavingCondition(columnOrExpression, operatorOrValue, maybeValue));
5539
5878
  return this;
5540
5879
  }
5541
5880
  /**
@@ -6693,12 +7032,19 @@ var QueryBuilder = class QueryBuilder {
6693
7032
  }
6694
7033
  if (typeof select !== "object" || !select) return null;
6695
7034
  const entries = Object.entries(select);
6696
- if (entries.some(([, value]) => value !== true && value !== false && value !== void 0 && typeof value !== "string")) return null;
6697
- const columns = entries.filter(([, value]) => value === true || typeof value === "string").map(([column, value]) => typeof value === "string" ? {
6698
- column,
6699
- alias: value,
6700
- raw: true
6701
- } : { column });
7035
+ if (entries.some(([, value]) => value !== true && value !== false && value !== void 0 && typeof value !== "string" && !(value instanceof Expression))) return null;
7036
+ const columns = entries.filter(([, value]) => value === true || typeof value === "string" || value instanceof Expression).map(([column, value]) => {
7037
+ if (value instanceof Expression) return {
7038
+ column,
7039
+ alias: column,
7040
+ expression: value.toExpressionNode()
7041
+ };
7042
+ return typeof value === "string" ? {
7043
+ column,
7044
+ alias: value,
7045
+ raw: true
7046
+ } : { column };
7047
+ });
6702
7048
  return columns.length > 0 ? columns : [];
6703
7049
  }
6704
7050
  normalizeQueryOrderBy(orderBy) {
@@ -6856,7 +7202,7 @@ var QueryBuilder = class QueryBuilder {
6856
7202
  offset: normalizedQuery.offsetValue,
6857
7203
  columns: normalizedQuery.querySelect ? [...normalizedQuery.querySelect] : void 0,
6858
7204
  distinct: normalizedQuery.queryDistinct || void 0,
6859
- groupBy: normalizedQuery.queryGroupBy ? [...normalizedQuery.queryGroupBy] : void 0,
7205
+ groupBy: normalizedQuery.queryGroupBy ? normalizedQuery.queryGroupBy.filter((source) => typeof source === "string") : void 0,
6860
7206
  relationLoads: this.mergeRelationLoadPlans(normalizedQuery.queryRelationLoads ? this.cloneRelationLoads(normalizedQuery.queryRelationLoads) : void 0, this.mergeRelationLoadPlans(callbackRelationLoads, childRelationLoads))
6861
7207
  });
6862
7208
  }
@@ -7120,11 +7466,11 @@ var QueryBuilder = class QueryBuilder {
7120
7466
  if (columns === null || orderBy === null || condition === null) return null;
7121
7467
  if (this.hasRelationFilters() && this.canExecuteRelationFiltersInAdapter() && relationFilters === null) return null;
7122
7468
  if (this.hasRelationAggregates() && this.canExecuteRelationAggregatesInAdapter() && relationAggregates === null) return null;
7123
- return {
7469
+ return this.expandComputedAttributes({
7124
7470
  target: this.buildQueryTarget(),
7125
7471
  columns,
7126
7472
  distinct: this.queryDistinct || void 0,
7127
- groupBy: this.queryGroupBy ? [...this.queryGroupBy] : void 0,
7473
+ groupBy: this.buildGroupByItems(columns ?? void 0),
7128
7474
  having: this.queryHaving,
7129
7475
  joins: this.queryJoins ? [...this.queryJoins] : void 0,
7130
7476
  where: condition,
@@ -7134,8 +7480,87 @@ var QueryBuilder = class QueryBuilder {
7134
7480
  relationLoads: this.queryRelationLoads,
7135
7481
  relationFilters: this.canExecuteRelationFiltersInAdapter() ? relationFilters ?? void 0 : void 0,
7136
7482
  relationAggregates: this.canExecuteRelationAggregatesInAdapter() ? relationAggregates ?? void 0 : void 0
7483
+ });
7484
+ }
7485
+ /** Reads and caches the model's resolved `static computed` expression map. */
7486
+ computedAttributes() {
7487
+ const model = this.model;
7488
+ return typeof model.getComputed === "function" ? model.getComputed() : {};
7489
+ }
7490
+ /**
7491
+ * Expands references to `static computed` attribute names into their backing
7492
+ * expressions across a select spec's columns, group by, order by, where, and
7493
+ * having clauses. A no-op when the model declares no computed attributes.
7494
+ */
7495
+ expandComputedAttributes(spec) {
7496
+ const computed = this.computedAttributes();
7497
+ if (Object.keys(computed).length === 0) return spec;
7498
+ const columns = spec.columns?.map((column) => {
7499
+ if (column.expression || column.raw || column.wildcard) return column;
7500
+ const expression = computed[column.column];
7501
+ return expression ? {
7502
+ ...column,
7503
+ expression,
7504
+ alias: column.alias ?? column.column
7505
+ } : column;
7506
+ });
7507
+ const selectedComputedAliases = new Set(columns?.filter((column) => column.expression && column.alias).map((column) => column.alias));
7508
+ return {
7509
+ ...spec,
7510
+ columns,
7511
+ groupBy: spec.groupBy?.map((item) => {
7512
+ if (typeof item !== "string") return item;
7513
+ const expression = computed[item];
7514
+ if (!expression) return item;
7515
+ return selectedComputedAliases.has(item) ? { alias: item } : { expression };
7516
+ }),
7517
+ orderBy: spec.orderBy?.map((clause) => {
7518
+ if (clause.expression) return clause;
7519
+ const expression = computed[clause.column];
7520
+ return expression ? {
7521
+ ...clause,
7522
+ expression
7523
+ } : clause;
7524
+ }),
7525
+ where: spec.where ? this.expandComputedCondition(spec.where, computed) : spec.where,
7526
+ having: spec.having ? this.expandComputedCondition(spec.having, computed) : spec.having
7137
7527
  };
7138
7528
  }
7529
+ expandComputedCondition(condition, computed) {
7530
+ if (condition.type === "comparison" && computed[condition.column]) return {
7531
+ type: "expression",
7532
+ expression: this.computedComparison(computed[condition.column], condition.operator, condition.value)
7533
+ };
7534
+ if (condition.type === "group") return {
7535
+ ...condition,
7536
+ conditions: condition.conditions.map((entry) => this.expandComputedCondition(entry, computed))
7537
+ };
7538
+ if (condition.type === "not") return {
7539
+ ...condition,
7540
+ condition: this.expandComputedCondition(condition.condition, computed)
7541
+ };
7542
+ return condition;
7543
+ }
7544
+ /** Converts a where comparison against a computed attribute into an expression. */
7545
+ computedComparison(node, operator, value) {
7546
+ const expression = fromExpressionNode(node);
7547
+ const list = Array.isArray(value) ? value : [];
7548
+ switch (operator) {
7549
+ case "!=": return expression.ne(value).toExpressionNode();
7550
+ case ">": return expression.gt(value).toExpressionNode();
7551
+ case ">=": return expression.gte(value).toExpressionNode();
7552
+ case "<": return expression.lt(value).toExpressionNode();
7553
+ case "<=": return expression.lte(value).toExpressionNode();
7554
+ case "in": return expression.in(list).toExpressionNode();
7555
+ case "not-in": return expression.notIn(list).toExpressionNode();
7556
+ case "is-null": return expression.isNull().toExpressionNode();
7557
+ case "is-not-null": return expression.isNotNull().toExpressionNode();
7558
+ case "contains": return expression.like(`%${String(value)}%`).toExpressionNode();
7559
+ case "starts-with": return expression.like(`${String(value)}%`).toExpressionNode();
7560
+ case "ends-with": return expression.like(`%${String(value)}`).toExpressionNode();
7561
+ default: return expression.eq(value).toExpressionNode();
7562
+ }
7563
+ }
7139
7564
  tryBuildAggregateSpec() {
7140
7565
  const condition = this.buildQueryWhereCondition(false);
7141
7566
  const relationFilters = this.tryBuildRelationFilterSpecs();
@@ -7634,6 +8059,9 @@ var Model = class Model {
7634
8059
  static {
7635
8060
  this.castMapCache = /* @__PURE__ */ new WeakMap();
7636
8061
  }
8062
+ static {
8063
+ this.computedCache = /* @__PURE__ */ new WeakMap();
8064
+ }
7637
8065
  static {
7638
8066
  this.emittedDeprecationWarnings = /* @__PURE__ */ new Set();
7639
8067
  }
@@ -7784,6 +8212,19 @@ var Model = class Model {
7784
8212
  return casts;
7785
8213
  }
7786
8214
  /**
8215
+ * Resolves the model's `static computed` declarations into expression nodes,
8216
+ * cached per class. Returns an empty map when no computed attributes exist.
8217
+ */
8218
+ static getComputed() {
8219
+ const cached = Model.computedCache.get(this);
8220
+ if (cached) return cached;
8221
+ const definitions = this.computed;
8222
+ const resolved = {};
8223
+ if (definitions) for (const [name, factory] of Object.entries(definitions)) resolved[name] = factory(expressionBuilder).toExpressionNode();
8224
+ Model.computedCache.set(this, resolved);
8225
+ return resolved;
8226
+ }
8227
+ /**
7787
8228
  * Apply built-in persistence casts (currently `json` serialisation) to a raw
7788
8229
  * attribute payload, without re-running arbitrary custom setters. Used by
7789
8230
  * both instance `save()` and the query-builder insert/update paths so a JS
@@ -8190,6 +8631,22 @@ var Model = class Model {
8190
8631
  });
8191
8632
  return this;
8192
8633
  }
8634
+ /**
8635
+ * Merge already-stored (database representation) attribute values into the
8636
+ * model without running set mutators or casts. Used to refresh the instance
8637
+ * from a row returned by a write, where the values are already in storage
8638
+ * form and must not be re-cast (re-applying a non-idempotent set-cast such as
8639
+ * a money or array cast would corrupt the value).
8640
+ *
8641
+ * @param attributes
8642
+ * @returns
8643
+ */
8644
+ fillRawAttributes(attributes) {
8645
+ Object.entries(attributes).forEach(([key, value]) => {
8646
+ this.attributes[key] = Model.cloneAttributeValue(value);
8647
+ });
8648
+ return this;
8649
+ }
8193
8650
  async update(attributes) {
8194
8651
  try {
8195
8652
  const primaryKey = this.constructor.getPrimaryKey();
@@ -8245,7 +8702,7 @@ var Model = class Model {
8245
8702
  await Model.dispatchEvent(constructor, "creating", this);
8246
8703
  const payload = this.normalizePersistenceAttributes(this.getRawAttributes());
8247
8704
  const model = await constructor.query().create(payload);
8248
- this.fill(model.getRawAttributes());
8705
+ this.fillRawAttributes(model.getRawAttributes());
8249
8706
  this.syncChanges(previousOriginal);
8250
8707
  this.syncPrevious(previousOriginal);
8251
8708
  this.syncOriginal();
@@ -8262,7 +8719,7 @@ var Model = class Model {
8262
8719
  const payload = this.normalizePersistenceAttributes(this.getDirtyAttributes());
8263
8720
  delete payload[primaryKey];
8264
8721
  const model = await constructor.query().where({ [primaryKey]: identifier }).update(payload);
8265
- this.fill(model.getRawAttributes());
8722
+ this.fillRawAttributes(model.getRawAttributes());
8266
8723
  this.syncChanges(previousOriginal);
8267
8724
  this.syncPrevious(previousOriginal);
8268
8725
  this.syncOriginal();
@@ -8305,7 +8762,7 @@ var Model = class Model {
8305
8762
  const softDeleteConfig = constructor.getSoftDeleteConfig();
8306
8763
  if (softDeleteConfig.enabled) {
8307
8764
  const model = await constructor.query().where({ [primaryKey]: identifier }).update({ [softDeleteConfig.column]: /* @__PURE__ */ new Date() });
8308
- this.fill(model.getRawAttributes());
8765
+ this.fillRawAttributes(model.getRawAttributes());
8309
8766
  this.syncChanges(previousOriginal);
8310
8767
  this.syncOriginal();
8311
8768
  await Model.dispatchEvent(constructor, "deleted", this);
@@ -8380,7 +8837,7 @@ var Model = class Model {
8380
8837
  const previousOriginal = this.getOriginal();
8381
8838
  await Model.dispatchEvent(constructor, "restoring", this);
8382
8839
  const model = await constructor.query().withTrashed().where({ [primaryKey]: identifier }).update({ [softDeleteConfig.column]: null });
8383
- this.fill(model.getRawAttributes());
8840
+ this.fillRawAttributes(model.getRawAttributes());
8384
8841
  this.syncChanges(previousOriginal);
8385
8842
  this.syncOriginal();
8386
8843
  await Model.dispatchEvent(constructor, "restored", this);
@@ -9624,4 +10081,4 @@ var PivotModel = class extends Model {
9624
10081
  };
9625
10082
 
9626
10083
  //#endregion
9627
- export { Arkorm, ArkormCollection, ArkormException, Arkormx, Attribute, CliApp, DB, EnumBuilder, ForeignKeyBuilder, InitCommand, InlineFactory, JoinClause, KyselyDatabaseAdapter, LengthAwarePaginator, MIGRATION_BRAND, MakeFactoryCommand, MakeMigrationCommand, MakeModelCommand, MakeSeederCommand, MigrateCommand, MigrateFreshCommand, MigrateRollbackCommand, Migration, MigrationHistoryCommand, MissingDelegateException, Model, ModelFactory, ModelNotFoundException, ModelsSyncCommand, PRISMA_ENUM_MEMBER_REGEX, PRISMA_ENUM_REGEX, PRISMA_MODEL_REGEX, Paginator, PivotModel, PrimaryKeyGenerationPlanner, PrismaDatabaseAdapter, QueryBuilder, QueryConstraintException, QueryExecutionException, RelationResolutionException, RuntimeModuleLoader, SEEDER_BRAND, SchemaBuilder, ScopeNotDefinedException, SeedCommand, Seeder, TableBuilder, URLDriver, UniqueConstraintResolutionException, UnsupportedAdapterFeatureException, applyAlterTableOperation, applyCreateTableOperation, applyDropTableOperation, applyMigrationRollbackToDatabase, applyMigrationRollbackToPrismaSchema, applyMigrationToDatabase, applyMigrationToPrismaSchema, applyOperationsToPersistedColumnMappingsState, applyOperationsToPrismaSchema, awaitConfiguredModelsRegistration, bindAdapterToModels, buildEnumBlock, buildFieldLine, buildIndexLine, buildInverseRelationLine, buildMigrationIdentity, buildMigrationRunId, buildMigrationSource, buildModelBlock, buildPrimaryKeyLine, buildRelationLine, buildUniqueConstraintLine, computeMigrationChecksum, configureArkormRuntime, createEmptyAppliedMigrationsState, createEmptyPersistedColumnMappingsState, createKyselyAdapter, createMigrationTimestamp, createPrismaAdapter, createPrismaCompatibilityAdapter, createPrismaDatabaseAdapter, createPrismaDelegateMap, defineConfig, defineFactory, deleteAppliedMigrationsStateFromStore, deletePersistedColumnMappingsState, deriveCollectionFieldName, deriveInverseRelationAlias, deriveRelationAlias, deriveRelationFieldName, deriveSingularFieldName, emitRuntimeDebugEvent, ensureArkormConfigLoading, escapeRegex, findAppliedMigration, findEnumBlock, findModelBlock, formatDefaultValue, formatEnumDefaultValue, formatRelationAction, generateMigrationFile, getActiveTransactionAdapter, getActiveTransactionClient, getDefaultStubsPath, getLastMigrationRun, getLatestAppliedMigrations, getMigrationPlan, getPersistedColumnMap, getPersistedEnumMap, getPersistedEnumTsType, getPersistedPrimaryKeyGeneration, getPersistedTableMetadata, getPersistedTimestampColumns, getRegisteredFactories, getRegisteredMigrations, getRegisteredModels, getRegisteredPaths, getRegisteredSeeders, getRuntimeAdapter, getRuntimeClient, getRuntimeCompatibilityAdapter, getRuntimeDebugHandler, getRuntimePaginationCurrentPageResolver, getRuntimePaginationURLDriverFactory, getRuntimePrismaClient, getUserConfig, inferDelegateName, isDelegateLike, isMigrationApplied, isQuerySchemaLike, isTransactionCapableClient, loadArkormConfig, loadFactoriesFrom, loadMigrationsFrom, loadModelsFrom, loadSeedersFrom, markMigrationApplied, markMigrationRun, pad, readAppliedMigrationsState, readAppliedMigrationsStateFromStore, readPersistedColumnMappingsState, rebuildPersistedColumnMappingsState, registerFactories, registerMigrations, registerModels, registerPaths, registerSeeders, removeAppliedMigration, resetArkormRuntimeForTests, resetPersistedColumnMappingsCache, resetRuntimeRegistryForTests, resolveCast, resolveColumnMappingsFilePath, resolveEnumName, resolveMigrationClassName, resolveMigrationStateFilePath, resolvePersistedMetadataFeatures, resolvePrismaType, resolveRuntimeCompatibilityQuerySchema, resolveRuntimeCompatibilityQuerySchemaOrThrow, runArkormTransaction, runMigrationWithPrisma, runPrismaCommand, stripPrismaSchemaModelsAndEnums, supportsDatabaseCreation, supportsDatabaseMigrationExecution, supportsDatabaseMigrationState, supportsDatabaseReset, syncPersistedColumnMappingsFromState, toMigrationFileSlug, toModelName, validatePersistedMetadataFeaturesForMigrations, writeAppliedMigrationsState, writeAppliedMigrationsStateToStore, writePersistedColumnMappingsState };
10084
+ export { AggregateExpression, Arkorm, ArkormCollection, ArkormException, Arkormx, Attribute, CaseExpression, CliApp, DB, EnumBuilder, Expression, ForeignKeyBuilder, InitCommand, InlineFactory, JoinClause, JsonExpression, KyselyDatabaseAdapter, LengthAwarePaginator, MIGRATION_BRAND, MakeFactoryCommand, MakeMigrationCommand, MakeModelCommand, MakeSeederCommand, MigrateCommand, MigrateFreshCommand, MigrateRollbackCommand, Migration, MigrationHistoryCommand, MissingDelegateException, Model, ModelFactory, ModelNotFoundException, ModelsSyncCommand, PRISMA_ENUM_MEMBER_REGEX, PRISMA_ENUM_REGEX, PRISMA_MODEL_REGEX, Paginator, PivotModel, PrimaryKeyGenerationPlanner, PrismaDatabaseAdapter, QueryBuilder, QueryConstraintException, QueryExecutionException, RelationResolutionException, RuntimeModuleLoader, SEEDER_BRAND, SchemaBuilder, ScopeNotDefinedException, SeedCommand, Seeder, TableBuilder, URLDriver, UniqueConstraintResolutionException, UnsupportedAdapterFeatureException, applyAlterTableOperation, applyCreateTableOperation, applyDropTableOperation, applyMigrationRollbackToDatabase, applyMigrationRollbackToPrismaSchema, applyMigrationToDatabase, applyMigrationToPrismaSchema, applyOperationsToPersistedColumnMappingsState, applyOperationsToPrismaSchema, avg, awaitConfiguredModelsRegistration, bindAdapterToModels, buildEnumBlock, buildFieldLine, buildIndexLine, buildInverseRelationLine, buildMigrationIdentity, buildMigrationRunId, buildMigrationSource, buildModelBlock, buildPrimaryKeyLine, buildRelationLine, buildUniqueConstraintLine, caseWhen, coalesce, col, computeMigrationChecksum, configureArkormRuntime, count, createEmptyAppliedMigrationsState, createEmptyPersistedColumnMappingsState, createKyselyAdapter, createMigrationTimestamp, createPrismaAdapter, createPrismaCompatibilityAdapter, createPrismaDatabaseAdapter, createPrismaDelegateMap, defineConfig, defineFactory, deleteAppliedMigrationsStateFromStore, deletePersistedColumnMappingsState, deriveCollectionFieldName, deriveInverseRelationAlias, deriveRelationAlias, deriveRelationFieldName, deriveSingularFieldName, emitRuntimeDebugEvent, ensureArkormConfigLoading, escapeRegex, expressionBuilder, findAppliedMigration, findEnumBlock, findModelBlock, fn, formatDefaultValue, formatEnumDefaultValue, formatRelationAction, fromExpressionNode, generateMigrationFile, getActiveTransactionAdapter, getActiveTransactionClient, getDefaultStubsPath, getLastMigrationRun, getLatestAppliedMigrations, getMigrationPlan, getPersistedColumnMap, getPersistedEnumMap, getPersistedEnumTsType, getPersistedPrimaryKeyGeneration, getPersistedTableMetadata, getPersistedTimestampColumns, getRegisteredFactories, getRegisteredMigrations, getRegisteredModels, getRegisteredPaths, getRegisteredSeeders, getRuntimeAdapter, getRuntimeClient, getRuntimeCompatibilityAdapter, getRuntimeDebugHandler, getRuntimePaginationCurrentPageResolver, getRuntimePaginationURLDriverFactory, getRuntimePrismaClient, getUserConfig, inferDelegateName, isDelegateLike, isMigrationApplied, isQuerySchemaLike, isTransactionCapableClient, json, loadArkormConfig, loadFactoriesFrom, loadMigrationsFrom, loadModelsFrom, loadSeedersFrom, markMigrationApplied, markMigrationRun, max, min, pad, raw, readAppliedMigrationsState, readAppliedMigrationsStateFromStore, readPersistedColumnMappingsState, rebuildPersistedColumnMappingsState, registerFactories, registerMigrations, registerModels, registerPaths, registerSeeders, removeAppliedMigration, resetArkormRuntimeForTests, resetPersistedColumnMappingsCache, resetRuntimeRegistryForTests, resolveCast, resolveColumnMappingsFilePath, resolveEnumName, resolveGeneratedExpression, resolveMigrationClassName, resolveMigrationStateFilePath, resolvePersistedMetadataFeatures, resolvePrismaType, resolveRuntimeCompatibilityQuerySchema, resolveRuntimeCompatibilityQuerySchemaOrThrow, runArkormTransaction, runMigrationWithPrisma, runPrismaCommand, stripPrismaSchemaModelsAndEnums, sum, supportsDatabaseCreation, supportsDatabaseMigrationExecution, supportsDatabaseMigrationState, supportsDatabaseReset, syncPersistedColumnMappingsFromState, toMigrationFileSlug, toModelName, val, validatePersistedMetadataFeaturesForMigrations, where, writeAppliedMigrationsState, writeAppliedMigrationsStateToStore, writePersistedColumnMappingsState };