arkormx 2.0.0-next.2 → 2.0.0-next.3

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
@@ -78,6 +78,8 @@ var UnsupportedAdapterFeatureException = class extends ArkormException {
78
78
  * @since 2.0.0-next.0
79
79
  */
80
80
  var KyselyDatabaseAdapter = class KyselyDatabaseAdapter {
81
+ static migrationStateTable = "arkormx_migrations";
82
+ static migrationRunTable = "arkormx_migration_runs";
81
83
  capabilities = {
82
84
  transactions: true,
83
85
  returning: true,
@@ -95,6 +97,161 @@ var KyselyDatabaseAdapter = class KyselyDatabaseAdapter {
95
97
  this.db = db;
96
98
  this.mapping = mapping;
97
99
  }
100
+ quoteIdentifier(value) {
101
+ return `"${value.replace(/"/g, "\"\"")}"`;
102
+ }
103
+ quoteLiteral(value) {
104
+ if (value == null) return "null";
105
+ if (typeof value === "number" || typeof value === "bigint") return String(value);
106
+ if (typeof value === "boolean") return value ? "true" : "false";
107
+ if (value instanceof Date) return `'${value.toISOString().replace(/'/g, "''")}'`;
108
+ return `'${String(value).replace(/'/g, "''")}'`;
109
+ }
110
+ async executeRawStatement(statement, executor = this.db) {
111
+ await sql.raw(statement).execute(executor);
112
+ }
113
+ resolveSchemaColumnName(columnName, columns = []) {
114
+ return columns.find((column) => column.name === columnName)?.map ?? columnName;
115
+ }
116
+ resolveSchemaIndexName(table, index) {
117
+ if (typeof index.name === "string" && index.name.trim().length > 0) return index.name;
118
+ return `${table}_${index.columns.join("_")}_idx`;
119
+ }
120
+ resolveSchemaForeignKeyName(table, foreignKey) {
121
+ return `${table}_${foreignKey.column}_fkey`;
122
+ }
123
+ resolveSchemaColumnType(column) {
124
+ if (column.type === "id") return "integer";
125
+ if (column.type === "uuid") return "uuid";
126
+ if (column.type === "enum") return this.quoteIdentifier(column.enumName ?? `${column.name}_enum`);
127
+ if (column.type === "string") return "varchar(255)";
128
+ if (column.type === "text") return "text";
129
+ if (column.type === "integer") return "integer";
130
+ if (column.type === "bigInteger") return "bigint";
131
+ if (column.type === "float") return "double precision";
132
+ if (column.type === "boolean") return "boolean";
133
+ if (column.type === "json") return "jsonb";
134
+ if (column.type === "date") return "date";
135
+ return "timestamptz";
136
+ }
137
+ resolveSchemaColumnDefault(column) {
138
+ const value = column.default ?? (column.updatedAt ? "now()" : void 0);
139
+ if (value === void 0) return null;
140
+ if (value === "now()") return "now()";
141
+ return this.quoteLiteral(value);
142
+ }
143
+ shouldUseIdentity(column) {
144
+ if (column.autoIncrement === false) return false;
145
+ if (column.type === "id") return column.default === void 0;
146
+ return column.autoIncrement === true;
147
+ }
148
+ buildSchemaColumnDefinition(column) {
149
+ const parts = [this.quoteIdentifier(column.map ?? column.name), this.resolveSchemaColumnType(column)];
150
+ if (this.shouldUseIdentity(column)) parts.push("generated by default as identity");
151
+ const defaultValue = this.resolveSchemaColumnDefault(column);
152
+ if (defaultValue && !this.shouldUseIdentity(column)) parts.push(`default ${defaultValue}`);
153
+ if (column.primary) parts.push("primary key");
154
+ else if (column.unique) parts.push("unique");
155
+ if (!column.nullable && !column.primary) parts.push("not null");
156
+ return parts.join(" ");
157
+ }
158
+ buildSchemaForeignKeyConstraint(table, foreignKey, columns = []) {
159
+ const localColumn = this.resolveSchemaColumnName(foreignKey.column, columns);
160
+ const referencedTable = this.resolveMappedTable(foreignKey.referencesTable);
161
+ const action = foreignKey.onDelete ? ` on delete ${foreignKey.onDelete === "setNull" ? "set null" : foreignKey.onDelete === "setDefault" ? "set default" : foreignKey.onDelete}` : "";
162
+ return `constraint ${this.quoteIdentifier(this.resolveSchemaForeignKeyName(table, foreignKey))} foreign key (${this.quoteIdentifier(localColumn)}) references ${this.quoteIdentifier(referencedTable)} (${this.quoteIdentifier(foreignKey.referencesColumn)})${action}`;
163
+ }
164
+ buildSchemaIndexStatement(table, index, columns = []) {
165
+ const mappedColumns = index.columns.map((column) => this.quoteIdentifier(this.resolveSchemaColumnName(column, columns))).join(", ");
166
+ return `create index if not exists ${this.quoteIdentifier(this.resolveSchemaIndexName(table, index))} on ${this.quoteIdentifier(table)} (${mappedColumns})`;
167
+ }
168
+ async ensureEnumTypes(columns, executor = this.db) {
169
+ for (const column of columns) {
170
+ if (column.type !== "enum") continue;
171
+ const enumName = column.enumName ?? `${column.name}_enum`;
172
+ if ((await sql`
173
+ select exists(
174
+ select 1
175
+ from pg_type
176
+ where typname = ${enumName}
177
+ ) as exists
178
+ `.execute(executor)).rows[0]?.exists) continue;
179
+ const values = column.enumValues ?? [];
180
+ if (values.length === 0) throw new ArkormException(`Enum column [${column.name}] requires enum values for database-backed migrations.`);
181
+ await this.executeRawStatement(`create type ${this.quoteIdentifier(enumName)} as enum (${values.map((value) => this.quoteLiteral(value)).join(", ")})`, executor);
182
+ }
183
+ }
184
+ async executeCreateTableOperation(operation, executor) {
185
+ const table = this.resolveMappedTable(operation.table);
186
+ await this.ensureEnumTypes(operation.columns, executor);
187
+ const columnDefinitions = operation.columns.map((column) => this.buildSchemaColumnDefinition(column));
188
+ const foreignKeys = (operation.foreignKeys ?? []).map((foreignKey) => this.buildSchemaForeignKeyConstraint(table, foreignKey, operation.columns));
189
+ const definitions = [...columnDefinitions, ...foreignKeys].join(", ");
190
+ await this.executeRawStatement(`create table if not exists ${this.quoteIdentifier(table)} (${definitions})`, executor);
191
+ for (const index of operation.indexes ?? []) await this.executeRawStatement(this.buildSchemaIndexStatement(table, index, operation.columns), executor);
192
+ }
193
+ async executeAlterTableOperation(operation, executor) {
194
+ const table = this.resolveMappedTable(operation.table);
195
+ await this.ensureEnumTypes(operation.addColumns, executor);
196
+ for (const column of operation.addColumns) await this.executeRawStatement(`alter table ${this.quoteIdentifier(table)} add column if not exists ${this.buildSchemaColumnDefinition(column)}`, executor);
197
+ for (const column of operation.dropColumns) await this.executeRawStatement(`alter table ${this.quoteIdentifier(table)} drop column if exists ${this.quoteIdentifier(column)}`, executor);
198
+ for (const foreignKey of operation.addForeignKeys ?? []) await this.executeRawStatement(`alter table ${this.quoteIdentifier(table)} add ${this.buildSchemaForeignKeyConstraint(table, foreignKey, operation.addColumns)}`, executor);
199
+ for (const index of operation.addIndexes ?? []) await this.executeRawStatement(this.buildSchemaIndexStatement(table, index, operation.addColumns), executor);
200
+ }
201
+ async executeDropTableOperation(operation, executor) {
202
+ const table = this.resolveMappedTable(operation.table);
203
+ await this.executeRawStatement(`drop table if exists ${this.quoteIdentifier(table)} cascade`, executor);
204
+ }
205
+ async ensureMigrationStateTables(executor = this.db) {
206
+ await this.executeRawStatement(`
207
+ create table if not exists ${this.quoteIdentifier(KyselyDatabaseAdapter.migrationStateTable)} (
208
+ id text primary key,
209
+ file text not null,
210
+ class_name text not null,
211
+ applied_at timestamptz not null,
212
+ checksum text null
213
+ )
214
+ `, executor);
215
+ await this.executeRawStatement(`
216
+ create table if not exists ${this.quoteIdentifier(KyselyDatabaseAdapter.migrationRunTable)} (
217
+ id text primary key,
218
+ applied_at timestamptz not null,
219
+ migration_ids jsonb not null
220
+ )
221
+ `, executor);
222
+ }
223
+ async writeAppliedMigrationsStateInternal(state, executor) {
224
+ await this.ensureMigrationStateTables(executor);
225
+ await this.executeRawStatement(`delete from ${this.quoteIdentifier(KyselyDatabaseAdapter.migrationRunTable)}`, executor);
226
+ await this.executeRawStatement(`delete from ${this.quoteIdentifier(KyselyDatabaseAdapter.migrationStateTable)}`, executor);
227
+ for (const migration of state.migrations) await sql`
228
+ insert into ${sql.table(KyselyDatabaseAdapter.migrationStateTable)} (id, file, class_name, applied_at, checksum)
229
+ values (${migration.id}, ${migration.file}, ${migration.className}, ${migration.appliedAt}, ${migration.checksum ?? null})
230
+ `.execute(executor);
231
+ for (const run of state.runs ?? []) await sql`
232
+ insert into ${sql.table(KyselyDatabaseAdapter.migrationRunTable)} (id, applied_at, migration_ids)
233
+ values (${run.id}, ${run.appliedAt}, cast(${JSON.stringify(run.migrationIds)} as jsonb))
234
+ `.execute(executor);
235
+ }
236
+ async resetDatabaseInternal(executor) {
237
+ const tablesResult = await sql`
238
+ select table_name, table_schema
239
+ from information_schema.tables
240
+ where table_schema = current_schema()
241
+ and table_type = 'BASE TABLE'
242
+ order by table_name asc
243
+ `.execute(executor);
244
+ for (const row of tablesResult.rows) await this.executeRawStatement(`drop table if exists ${this.quoteIdentifier(row.table_schema)}.${this.quoteIdentifier(row.table_name)} cascade`, executor);
245
+ const enumTypesResult = await sql`
246
+ select t.typname as enum_name, n.nspname as enum_schema
247
+ from pg_type t
248
+ inner join pg_namespace n on n.oid = t.typnamespace
249
+ where t.typtype = 'e'
250
+ and n.nspname = current_schema()
251
+ order by t.typname asc
252
+ `.execute(executor);
253
+ for (const row of enumTypesResult.rows) await this.executeRawStatement(`drop type if exists ${this.quoteIdentifier(row.enum_schema)}.${this.quoteIdentifier(row.enum_name)} cascade`, executor);
254
+ }
98
255
  introspectionTypeToTs(typeName, enumValues) {
99
256
  if (enumValues && enumValues.length > 0) return enumValues.map((value) => `'${value.replace(/'/g, "\\'")}'`).join(" | ");
100
257
  switch (typeName) {
@@ -721,6 +878,63 @@ var KyselyDatabaseAdapter = class KyselyDatabaseAdapter {
721
878
  });
722
879
  return [...models.values()];
723
880
  }
881
+ async executeSchemaOperations(operations) {
882
+ if (operations.length === 0) return;
883
+ await this.transaction(async (adapter) => {
884
+ const transactionAdapter = adapter;
885
+ for (const operation of operations) {
886
+ if (operation.type === "createTable") {
887
+ await transactionAdapter.executeCreateTableOperation(operation, transactionAdapter.db);
888
+ continue;
889
+ }
890
+ if (operation.type === "alterTable") {
891
+ await transactionAdapter.executeAlterTableOperation(operation, transactionAdapter.db);
892
+ continue;
893
+ }
894
+ await transactionAdapter.executeDropTableOperation(operation, transactionAdapter.db);
895
+ }
896
+ });
897
+ }
898
+ async resetDatabase() {
899
+ await this.transaction(async (adapter) => {
900
+ const transactionAdapter = adapter;
901
+ await transactionAdapter.resetDatabaseInternal(transactionAdapter.db);
902
+ });
903
+ }
904
+ async readAppliedMigrationsState() {
905
+ await this.ensureMigrationStateTables();
906
+ const migrationsResult = await sql`
907
+ select id, file, class_name, applied_at, checksum
908
+ from ${sql.table(KyselyDatabaseAdapter.migrationStateTable)}
909
+ order by applied_at asc, id asc
910
+ `.execute(this.db);
911
+ const runsResult = await sql`
912
+ select id, applied_at, migration_ids
913
+ from ${sql.table(KyselyDatabaseAdapter.migrationRunTable)}
914
+ order by applied_at asc, id asc
915
+ `.execute(this.db);
916
+ return {
917
+ version: 1,
918
+ migrations: migrationsResult.rows.map((row) => ({
919
+ id: row.id,
920
+ file: row.file,
921
+ className: row.class_name,
922
+ appliedAt: row.applied_at instanceof Date ? row.applied_at.toISOString() : String(row.applied_at),
923
+ checksum: row.checksum ?? void 0
924
+ })),
925
+ runs: runsResult.rows.map((row) => ({
926
+ id: row.id,
927
+ appliedAt: row.applied_at instanceof Date ? row.applied_at.toISOString() : String(row.applied_at),
928
+ migrationIds: Array.isArray(row.migration_ids) ? row.migration_ids.filter((value) => typeof value === "string") : typeof row.migration_ids === "string" ? JSON.parse(row.migration_ids) : []
929
+ }))
930
+ };
931
+ }
932
+ async writeAppliedMigrationsState(state) {
933
+ await this.transaction(async (adapter) => {
934
+ const transactionAdapter = adapter;
935
+ await transactionAdapter.writeAppliedMigrationsStateInternal(state, transactionAdapter.db);
936
+ });
937
+ }
724
938
  /**
725
939
  * Executes a series of database operations within a transaction.
726
940
  * The provided callback function is called with a new instance of the
@@ -2909,6 +3123,28 @@ const getMigrationPlan = async (migration, direction = "up") => {
2909
3123
  else await instance.down(schema);
2910
3124
  return schema.getOperations();
2911
3125
  };
3126
+ const supportsDatabaseMigrationExecution = (adapter) => {
3127
+ return typeof adapter?.executeSchemaOperations === "function";
3128
+ };
3129
+ const supportsDatabaseReset = (adapter) => {
3130
+ return typeof adapter?.resetDatabase === "function";
3131
+ };
3132
+ const stripPrismaSchemaModelsAndEnums = (schema) => {
3133
+ const stripped = schema.replace(PRISMA_MODEL_REGEX, "").replace(PRISMA_ENUM_REGEX, "").replace(/\n{3,}/g, "\n\n").trimEnd();
3134
+ return stripped.length > 0 ? `${stripped}\n` : "";
3135
+ };
3136
+ const applyMigrationToDatabase = async (adapter, migration) => {
3137
+ if (!supportsDatabaseMigrationExecution(adapter)) throw new ArkormException("The configured adapter does not support database-backed migration execution.");
3138
+ const operations = await getMigrationPlan(migration, "up");
3139
+ await adapter.executeSchemaOperations(operations);
3140
+ return { operations };
3141
+ };
3142
+ const applyMigrationRollbackToDatabase = async (adapter, migration) => {
3143
+ if (!supportsDatabaseMigrationExecution(adapter)) throw new ArkormException("The configured adapter does not support database-backed migration execution.");
3144
+ const operations = await getMigrationPlan(migration, "down");
3145
+ await adapter.executeSchemaOperations(operations);
3146
+ return { operations };
3147
+ };
2912
3148
  /**
2913
3149
  * Apply the schema operations defined in a migration to a Prisma schema
2914
3150
  * file, updating the file on disk if specified, and return the updated
@@ -3889,10 +4125,13 @@ var MakeSeederCommand = class extends Command {
3889
4125
 
3890
4126
  //#endregion
3891
4127
  //#region src/helpers/migration-history.ts
3892
- const DEFAULT_STATE = {
4128
+ const createEmptyAppliedMigrationsState = () => ({
3893
4129
  version: 1,
3894
4130
  migrations: [],
3895
4131
  runs: []
4132
+ });
4133
+ const supportsDatabaseMigrationState = (adapter) => {
4134
+ return typeof adapter?.readAppliedMigrationsState === "function" && typeof adapter?.writeAppliedMigrationsState === "function";
3896
4135
  };
3897
4136
  const resolveMigrationStateFilePath = (cwd, configuredPath) => {
3898
4137
  if (configuredPath && configuredPath.trim().length > 0) return resolve(configuredPath);
@@ -3907,10 +4146,10 @@ const computeMigrationChecksum = (filePath) => {
3907
4146
  return createHash("sha256").update(source).digest("hex");
3908
4147
  };
3909
4148
  const readAppliedMigrationsState = (stateFilePath) => {
3910
- if (!existsSync$1(stateFilePath)) return { ...DEFAULT_STATE };
4149
+ if (!existsSync$1(stateFilePath)) return createEmptyAppliedMigrationsState();
3911
4150
  try {
3912
4151
  const parsed = JSON.parse(readFileSync$1(stateFilePath, "utf-8"));
3913
- if (!Array.isArray(parsed.migrations)) return { ...DEFAULT_STATE };
4152
+ if (!Array.isArray(parsed.migrations)) return createEmptyAppliedMigrationsState();
3914
4153
  return {
3915
4154
  version: 1,
3916
4155
  migrations: parsed.migrations.filter((migration) => {
@@ -3921,14 +4160,34 @@ const readAppliedMigrationsState = (stateFilePath) => {
3921
4160
  }) : []
3922
4161
  };
3923
4162
  } catch {
3924
- return { ...DEFAULT_STATE };
4163
+ return createEmptyAppliedMigrationsState();
3925
4164
  }
3926
4165
  };
4166
+ const readAppliedMigrationsStateFromStore = async (adapter, stateFilePath) => {
4167
+ if (supportsDatabaseMigrationState(adapter)) return await adapter.readAppliedMigrationsState();
4168
+ return readAppliedMigrationsState(stateFilePath);
4169
+ };
3927
4170
  const writeAppliedMigrationsState = (stateFilePath, state) => {
3928
4171
  const directory = dirname(stateFilePath);
3929
4172
  if (!existsSync$1(directory)) mkdirSync$1(directory, { recursive: true });
3930
4173
  writeFileSync$1(stateFilePath, JSON.stringify(state, null, 2));
3931
4174
  };
4175
+ const writeAppliedMigrationsStateToStore = async (adapter, stateFilePath, state) => {
4176
+ if (supportsDatabaseMigrationState(adapter)) {
4177
+ await adapter.writeAppliedMigrationsState(state);
4178
+ return;
4179
+ }
4180
+ writeAppliedMigrationsState(stateFilePath, state);
4181
+ };
4182
+ const deleteAppliedMigrationsStateFromStore = async (adapter, stateFilePath) => {
4183
+ if (supportsDatabaseMigrationState(adapter)) {
4184
+ await adapter.writeAppliedMigrationsState(createEmptyAppliedMigrationsState());
4185
+ return "database";
4186
+ }
4187
+ if (!existsSync$1(stateFilePath)) return "missing-file";
4188
+ writeAppliedMigrationsState(stateFilePath, createEmptyAppliedMigrationsState());
4189
+ return "file";
4190
+ };
3932
4191
  const isMigrationApplied = (state, identity, checksum) => {
3933
4192
  const matched = state.migrations.find((migration) => migration.id === identity);
3934
4193
  if (!matched) return false;
@@ -4047,7 +4306,9 @@ var MigrateCommand = class extends Command {
4047
4306
  const classes = this.option("all") || !this.argument("name") ? await this.loadAllMigrations(migrationsDir) : (await this.loadNamedMigration(migrationsDir, this.argument("name"))).filter(([cls]) => cls !== void 0);
4048
4307
  if (classes.length === 0) return void this.error("Error: No migration classes found to run.");
4049
4308
  const stateFilePath = resolveMigrationStateFilePath(process.cwd(), this.option("state-file") ? String(this.option("state-file")) : void 0);
4050
- let appliedState = readAppliedMigrationsState(stateFilePath);
4309
+ let appliedState = await readAppliedMigrationsStateFromStore(this.app.getConfig("adapter"), stateFilePath);
4310
+ const adapter = this.app.getConfig("adapter");
4311
+ const useDatabaseMigrations = supportsDatabaseMigrationExecution(adapter);
4051
4312
  const skipped = [];
4052
4313
  const changed = [];
4053
4314
  const pending = classes.filter(([migrationClass, file]) => {
@@ -4069,10 +4330,16 @@ var MigrateCommand = class extends Command {
4069
4330
  this.success("No pending migration classes to apply.");
4070
4331
  return;
4071
4332
  }
4072
- for (const [MigrationClassItem] of pending) await applyMigrationToPrismaSchema(MigrationClassItem, {
4073
- schemaPath,
4074
- write: true
4075
- });
4333
+ for (const [MigrationClassItem] of pending) {
4334
+ if (useDatabaseMigrations) {
4335
+ await applyMigrationToDatabase(adapter, MigrationClassItem);
4336
+ continue;
4337
+ }
4338
+ await applyMigrationToPrismaSchema(MigrationClassItem, {
4339
+ schemaPath,
4340
+ write: true
4341
+ });
4342
+ }
4076
4343
  if (appliedState) {
4077
4344
  const runAppliedIds = [];
4078
4345
  for (const [migrationClass, file] of pending) {
@@ -4091,10 +4358,10 @@ var MigrateCommand = class extends Command {
4091
4358
  appliedAt: (/* @__PURE__ */ new Date()).toISOString(),
4092
4359
  migrationIds: runAppliedIds
4093
4360
  });
4094
- writeAppliedMigrationsState(stateFilePath, appliedState);
4361
+ await writeAppliedMigrationsStateToStore(adapter, stateFilePath, appliedState);
4095
4362
  }
4096
- if (!this.option("skip-generate")) runPrismaCommand(["generate"], process.cwd());
4097
- if (!this.option("skip-migrate")) if (this.option("deploy")) runPrismaCommand(["migrate", "deploy"], process.cwd());
4363
+ if (!useDatabaseMigrations && !this.option("skip-generate")) runPrismaCommand(["generate"], process.cwd());
4364
+ if (!useDatabaseMigrations && !this.option("skip-migrate")) if (this.option("deploy")) runPrismaCommand(["migrate", "deploy"], process.cwd());
4098
4365
  else runPrismaCommand([
4099
4366
  "migrate",
4100
4367
  "dev",
@@ -4154,6 +4421,85 @@ var MigrateCommand = class extends Command {
4154
4421
  }
4155
4422
  };
4156
4423
 
4424
+ //#endregion
4425
+ //#region src/cli/commands/MigrateFreshCommand.ts
4426
+ var MigrateFreshCommand = class extends Command {
4427
+ signature = `migrate:fresh
4428
+ {--skip-generate : Skip prisma generate}
4429
+ {--skip-migrate : Skip prisma database sync}
4430
+ {--state-file= : Path to applied migration state file}
4431
+ {--schema= : Explicit prisma schema path}
4432
+ `;
4433
+ description = "Reset the database and rerun all migration classes";
4434
+ async handle() {
4435
+ this.app.command = this;
4436
+ const configuredMigrationsDir = this.app.getConfig("paths")?.migrations ?? join(process.cwd(), "database", "migrations");
4437
+ const migrationsDir = this.app.resolveRuntimeDirectoryPath(configuredMigrationsDir);
4438
+ if (!existsSync$1(migrationsDir)) return void this.error(`Error: Migrations directory not found: ${this.app.formatPathForLog(configuredMigrationsDir)}`);
4439
+ const adapter = this.app.getConfig("adapter");
4440
+ const useDatabaseMigrations = supportsDatabaseMigrationExecution(adapter);
4441
+ const schemaPath = this.option("schema") ? resolve(String(this.option("schema"))) : join(process.cwd(), "prisma", "schema.prisma");
4442
+ const stateFilePath = resolveMigrationStateFilePath(process.cwd(), this.option("state-file") ? String(this.option("state-file")) : void 0);
4443
+ const migrations = await this.loadAllMigrations(migrationsDir);
4444
+ if (migrations.length === 0) return void this.error("Error: No migration classes found to run.");
4445
+ if (supportsDatabaseReset(adapter)) await adapter.resetDatabase();
4446
+ else {
4447
+ if (!existsSync$1(schemaPath)) return void this.error(`Error: Prisma schema file not found: ${this.app.formatPathForLog(schemaPath)}`);
4448
+ writeFileSync$1(schemaPath, stripPrismaSchemaModelsAndEnums(readFileSync$1(schemaPath, "utf-8")));
4449
+ }
4450
+ let appliedState = createEmptyAppliedMigrationsState();
4451
+ await writeAppliedMigrationsStateToStore(adapter, stateFilePath, appliedState);
4452
+ for (const [MigrationClassItem] of migrations) {
4453
+ if (useDatabaseMigrations) {
4454
+ await applyMigrationToDatabase(adapter, MigrationClassItem);
4455
+ continue;
4456
+ }
4457
+ await applyMigrationToPrismaSchema(MigrationClassItem, {
4458
+ schemaPath,
4459
+ write: true
4460
+ });
4461
+ }
4462
+ for (const [migrationClass, file] of migrations) appliedState = markMigrationApplied(appliedState, {
4463
+ id: buildMigrationIdentity(file, migrationClass.name),
4464
+ file,
4465
+ className: migrationClass.name,
4466
+ appliedAt: (/* @__PURE__ */ new Date()).toISOString(),
4467
+ checksum: computeMigrationChecksum(file)
4468
+ });
4469
+ appliedState = markMigrationRun(appliedState, {
4470
+ id: buildMigrationRunId(),
4471
+ appliedAt: (/* @__PURE__ */ new Date()).toISOString(),
4472
+ migrationIds: appliedState.migrations.map((migration) => migration.id)
4473
+ });
4474
+ await writeAppliedMigrationsStateToStore(adapter, stateFilePath, appliedState);
4475
+ if (!useDatabaseMigrations) {
4476
+ const schemaArgs = this.option("schema") ? ["--schema", schemaPath] : [];
4477
+ if (!this.option("skip-generate")) runPrismaCommand(["generate", ...schemaArgs], process.cwd());
4478
+ if (!this.option("skip-migrate")) runPrismaCommand([
4479
+ "db",
4480
+ "push",
4481
+ "--force-reset",
4482
+ ...schemaArgs
4483
+ ], process.cwd());
4484
+ }
4485
+ this.success(`Refreshed database with ${migrations.length} migration(s).`);
4486
+ migrations.forEach(([_, file]) => this.success(this.app.splitLogger("Migrated", file)));
4487
+ }
4488
+ async loadAllMigrations(migrationsDir) {
4489
+ const files = readdirSync$1(migrationsDir).filter((file) => /\.(ts|js|mjs|cjs)$/i.test(file)).sort((left, right) => left.localeCompare(right)).map((file) => this.app.resolveRuntimeScriptPath(join(migrationsDir, file)));
4490
+ return (await Promise.all(files.map(async (file) => (await this.loadMigrationClassesFromFile(file)).map((cls) => [cls, file])))).flat();
4491
+ }
4492
+ async loadMigrationClassesFromFile(filePath) {
4493
+ const imported = await RuntimeModuleLoader.load(filePath);
4494
+ return Object.values(imported).filter((value) => {
4495
+ if (typeof value !== "function") return false;
4496
+ const candidate = value;
4497
+ const prototype = candidate.prototype;
4498
+ return candidate[MIGRATION_BRAND] === true || typeof prototype?.up === "function" && typeof prototype?.down === "function";
4499
+ });
4500
+ }
4501
+ };
4502
+
4157
4503
  //#endregion
4158
4504
  //#region src/cli/commands/MigrateRollbackCommand.ts
4159
4505
  /**
@@ -4182,7 +4528,9 @@ var MigrateRollbackCommand = class extends Command {
4182
4528
  if (!existsSync$1(migrationsDir)) return void this.error(`Error: Migrations directory not found: ${this.app.formatPathForLog(configuredMigrationsDir)}`);
4183
4529
  const schemaPath = this.option("schema") ? resolve(String(this.option("schema"))) : join(process.cwd(), "prisma", "schema.prisma");
4184
4530
  const stateFilePath = resolveMigrationStateFilePath(process.cwd(), this.option("state-file") ? String(this.option("state-file")) : void 0);
4185
- let appliedState = readAppliedMigrationsState(stateFilePath);
4531
+ const adapter = this.app.getConfig("adapter");
4532
+ const useDatabaseMigrations = supportsDatabaseMigrationExecution(adapter);
4533
+ let appliedState = await readAppliedMigrationsStateFromStore(adapter, stateFilePath);
4186
4534
  const stepOption = this.option("step");
4187
4535
  const stepCount = stepOption == null ? void 0 : Number(stepOption);
4188
4536
  if (stepCount != null && (!Number.isFinite(stepCount) || stepCount <= 0 || !Number.isInteger(stepCount))) return void this.error("Error: --step must be a positive integer.");
@@ -4204,17 +4552,23 @@ var MigrateRollbackCommand = class extends Command {
4204
4552
  rollbackClasses.forEach(([_, file]) => this.success(this.app.splitLogger("WouldRollback", file)));
4205
4553
  return;
4206
4554
  }
4207
- for (const [MigrationClassItem] of rollbackClasses) await applyMigrationRollbackToPrismaSchema(MigrationClassItem, {
4208
- schemaPath,
4209
- write: true
4210
- });
4555
+ for (const [MigrationClassItem] of rollbackClasses) {
4556
+ if (useDatabaseMigrations) {
4557
+ await applyMigrationRollbackToDatabase(adapter, MigrationClassItem);
4558
+ continue;
4559
+ }
4560
+ await applyMigrationRollbackToPrismaSchema(MigrationClassItem, {
4561
+ schemaPath,
4562
+ write: true
4563
+ });
4564
+ }
4211
4565
  for (const [migrationClass, file] of rollbackClasses) {
4212
4566
  const identity = buildMigrationIdentity(file, migrationClass.name);
4213
4567
  appliedState = removeAppliedMigration(appliedState, identity);
4214
4568
  }
4215
- writeAppliedMigrationsState(stateFilePath, appliedState);
4216
- if (!this.option("skip-generate")) runPrismaCommand(["generate"], process.cwd());
4217
- if (!this.option("skip-migrate")) if (this.option("deploy")) runPrismaCommand(["migrate", "deploy"], process.cwd());
4569
+ await writeAppliedMigrationsStateToStore(adapter, stateFilePath, appliedState);
4570
+ if (!useDatabaseMigrations && !this.option("skip-generate")) runPrismaCommand(["generate"], process.cwd());
4571
+ if (!useDatabaseMigrations && !this.option("skip-migrate")) if (this.option("deploy")) runPrismaCommand(["migrate", "deploy"], process.cwd());
4218
4572
  else runPrismaCommand([
4219
4573
  "migrate",
4220
4574
  "dev",
@@ -4258,7 +4612,14 @@ var MigrationHistoryCommand = class extends Command {
4258
4612
  async handle() {
4259
4613
  this.app.command = this;
4260
4614
  const stateFilePath = resolveMigrationStateFilePath(process.cwd(), this.option("state-file") ? String(this.option("state-file")) : void 0);
4615
+ const adapter = this.app.getConfig("adapter");
4616
+ const usesDatabaseState = supportsDatabaseMigrationState(adapter);
4261
4617
  if (this.option("delete")) {
4618
+ if (usesDatabaseState) {
4619
+ await adapter.writeAppliedMigrationsState(createEmptyAppliedMigrationsState());
4620
+ this.success("Deleted tracked migration state from database.");
4621
+ return;
4622
+ }
4262
4623
  if (!existsSync$1(stateFilePath)) {
4263
4624
  this.success(`No migration state file found at ${this.app.formatPathForLog(stateFilePath)}`);
4264
4625
  return;
@@ -4268,22 +4629,19 @@ var MigrationHistoryCommand = class extends Command {
4268
4629
  return;
4269
4630
  }
4270
4631
  if (this.option("reset")) {
4271
- writeAppliedMigrationsState(stateFilePath, {
4272
- version: 1,
4273
- migrations: []
4274
- });
4275
- this.success(`Reset migration state: ${this.app.formatPathForLog(stateFilePath)}`);
4632
+ await writeAppliedMigrationsStateToStore(adapter, stateFilePath, createEmptyAppliedMigrationsState());
4633
+ this.success(usesDatabaseState ? "Reset migration state in database." : `Reset migration state: ${this.app.formatPathForLog(stateFilePath)}`);
4276
4634
  return;
4277
4635
  }
4278
- const state = readAppliedMigrationsState(stateFilePath);
4636
+ const state = await readAppliedMigrationsStateFromStore(adapter, stateFilePath);
4279
4637
  if (this.option("json")) {
4280
4638
  this.success(JSON.stringify({
4281
- path: stateFilePath,
4639
+ path: usesDatabaseState ? "database" : stateFilePath,
4282
4640
  ...state
4283
4641
  }, null, 2));
4284
4642
  return;
4285
4643
  }
4286
- this.success(this.app.splitLogger("State", stateFilePath));
4644
+ this.success(this.app.splitLogger("State", usesDatabaseState ? "database" : stateFilePath));
4287
4645
  this.success(this.app.splitLogger("Tracked", String(state.migrations.length)));
4288
4646
  if (state.migrations.length === 0) {
4289
4647
  this.success("No tracked migrations found.");
@@ -9250,4 +9608,4 @@ var Model = class Model {
9250
9608
  };
9251
9609
 
9252
9610
  //#endregion
9253
- export { ArkormCollection, ArkormException, Attribute, CliApp, EnumBuilder, ForeignKeyBuilder, InitCommand, InlineFactory, KyselyDatabaseAdapter, LengthAwarePaginator, MIGRATION_BRAND, MakeFactoryCommand, MakeMigrationCommand, MakeModelCommand, MakeSeederCommand, MigrateCommand, MigrateRollbackCommand, Migration, MigrationHistoryCommand, MissingDelegateException, Model, ModelFactory, ModelNotFoundException, ModelsSyncCommand, PRISMA_ENUM_MEMBER_REGEX, PRISMA_ENUM_REGEX, PRISMA_MODEL_REGEX, Paginator, PrismaDatabaseAdapter, QueryBuilder, QueryConstraintException, RelationResolutionException, RuntimeModuleLoader, SEEDER_BRAND, SchemaBuilder, ScopeNotDefinedException, SeedCommand, Seeder, TableBuilder, URLDriver, UniqueConstraintResolutionException, UnsupportedAdapterFeatureException, applyAlterTableOperation, applyCreateTableOperation, applyDropTableOperation, applyMigrationRollbackToPrismaSchema, applyMigrationToPrismaSchema, applyOperationsToPrismaSchema, bindAdapterToModels, buildEnumBlock, buildFieldLine, buildIndexLine, buildInverseRelationLine, buildMigrationIdentity, buildMigrationRunId, buildMigrationSource, buildModelBlock, buildRelationLine, computeMigrationChecksum, configureArkormRuntime, createKyselyAdapter, createMigrationTimestamp, createPrismaAdapter, createPrismaCompatibilityAdapter, createPrismaDatabaseAdapter, createPrismaDelegateMap, defineConfig, defineFactory, deriveCollectionFieldName, deriveInverseRelationAlias, deriveRelationAlias, deriveRelationFieldName, deriveSingularFieldName, ensureArkormConfigLoading, escapeRegex, findAppliedMigration, findEnumBlock, findModelBlock, formatDefaultValue, formatEnumDefaultValue, formatRelationAction, generateMigrationFile, getActiveTransactionClient, getDefaultStubsPath, getLastMigrationRun, getLatestAppliedMigrations, getMigrationPlan, getRuntimeAdapter, getRuntimePaginationCurrentPageResolver, getRuntimePaginationURLDriverFactory, getRuntimePrismaClient, getUserConfig, inferDelegateName, isDelegateLike, isMigrationApplied, isTransactionCapableClient, loadArkormConfig, markMigrationApplied, markMigrationRun, pad, readAppliedMigrationsState, removeAppliedMigration, resetArkormRuntimeForTests, resolveCast, resolveEnumName, resolveMigrationClassName, resolveMigrationStateFilePath, resolvePrismaType, runArkormTransaction, runMigrationWithPrisma, runPrismaCommand, toMigrationFileSlug, toModelName, writeAppliedMigrationsState };
9611
+ export { ArkormCollection, ArkormException, Attribute, CliApp, EnumBuilder, ForeignKeyBuilder, InitCommand, InlineFactory, 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, PrismaDatabaseAdapter, QueryBuilder, QueryConstraintException, RelationResolutionException, RuntimeModuleLoader, SEEDER_BRAND, SchemaBuilder, ScopeNotDefinedException, SeedCommand, Seeder, TableBuilder, URLDriver, UniqueConstraintResolutionException, UnsupportedAdapterFeatureException, applyAlterTableOperation, applyCreateTableOperation, applyDropTableOperation, applyMigrationRollbackToDatabase, applyMigrationRollbackToPrismaSchema, applyMigrationToDatabase, applyMigrationToPrismaSchema, applyOperationsToPrismaSchema, bindAdapterToModels, buildEnumBlock, buildFieldLine, buildIndexLine, buildInverseRelationLine, buildMigrationIdentity, buildMigrationRunId, buildMigrationSource, buildModelBlock, buildRelationLine, computeMigrationChecksum, configureArkormRuntime, createEmptyAppliedMigrationsState, createKyselyAdapter, createMigrationTimestamp, createPrismaAdapter, createPrismaCompatibilityAdapter, createPrismaDatabaseAdapter, createPrismaDelegateMap, defineConfig, defineFactory, deleteAppliedMigrationsStateFromStore, deriveCollectionFieldName, deriveInverseRelationAlias, deriveRelationAlias, deriveRelationFieldName, deriveSingularFieldName, ensureArkormConfigLoading, escapeRegex, findAppliedMigration, findEnumBlock, findModelBlock, formatDefaultValue, formatEnumDefaultValue, formatRelationAction, generateMigrationFile, getActiveTransactionClient, getDefaultStubsPath, getLastMigrationRun, getLatestAppliedMigrations, getMigrationPlan, getRuntimeAdapter, getRuntimePaginationCurrentPageResolver, getRuntimePaginationURLDriverFactory, getRuntimePrismaClient, getUserConfig, inferDelegateName, isDelegateLike, isMigrationApplied, isTransactionCapableClient, loadArkormConfig, markMigrationApplied, markMigrationRun, pad, readAppliedMigrationsState, readAppliedMigrationsStateFromStore, removeAppliedMigration, resetArkormRuntimeForTests, resolveCast, resolveEnumName, resolveMigrationClassName, resolveMigrationStateFilePath, resolvePrismaType, runArkormTransaction, runMigrationWithPrisma, runPrismaCommand, stripPrismaSchemaModelsAndEnums, supportsDatabaseMigrationExecution, supportsDatabaseMigrationState, supportsDatabaseReset, toMigrationFileSlug, toModelName, writeAppliedMigrationsState, writeAppliedMigrationsStateToStore };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "arkormx",
3
- "version": "2.0.0-next.2",
3
+ "version": "2.0.0-next.3",
4
4
  "description": "Modern TypeScript-first ORM for Node.js.",
5
5
  "keywords": [
6
6
  "orm",