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.cjs CHANGED
@@ -107,6 +107,8 @@ var UnsupportedAdapterFeatureException = class extends ArkormException {
107
107
  * @since 2.0.0-next.0
108
108
  */
109
109
  var KyselyDatabaseAdapter = class KyselyDatabaseAdapter {
110
+ static migrationStateTable = "arkormx_migrations";
111
+ static migrationRunTable = "arkormx_migration_runs";
110
112
  capabilities = {
111
113
  transactions: true,
112
114
  returning: true,
@@ -124,6 +126,161 @@ var KyselyDatabaseAdapter = class KyselyDatabaseAdapter {
124
126
  this.db = db;
125
127
  this.mapping = mapping;
126
128
  }
129
+ quoteIdentifier(value) {
130
+ return `"${value.replace(/"/g, "\"\"")}"`;
131
+ }
132
+ quoteLiteral(value) {
133
+ if (value == null) return "null";
134
+ if (typeof value === "number" || typeof value === "bigint") return String(value);
135
+ if (typeof value === "boolean") return value ? "true" : "false";
136
+ if (value instanceof Date) return `'${value.toISOString().replace(/'/g, "''")}'`;
137
+ return `'${String(value).replace(/'/g, "''")}'`;
138
+ }
139
+ async executeRawStatement(statement, executor = this.db) {
140
+ await kysely.sql.raw(statement).execute(executor);
141
+ }
142
+ resolveSchemaColumnName(columnName, columns = []) {
143
+ return columns.find((column) => column.name === columnName)?.map ?? columnName;
144
+ }
145
+ resolveSchemaIndexName(table, index) {
146
+ if (typeof index.name === "string" && index.name.trim().length > 0) return index.name;
147
+ return `${table}_${index.columns.join("_")}_idx`;
148
+ }
149
+ resolveSchemaForeignKeyName(table, foreignKey) {
150
+ return `${table}_${foreignKey.column}_fkey`;
151
+ }
152
+ resolveSchemaColumnType(column) {
153
+ if (column.type === "id") return "integer";
154
+ if (column.type === "uuid") return "uuid";
155
+ if (column.type === "enum") return this.quoteIdentifier(column.enumName ?? `${column.name}_enum`);
156
+ if (column.type === "string") return "varchar(255)";
157
+ if (column.type === "text") return "text";
158
+ if (column.type === "integer") return "integer";
159
+ if (column.type === "bigInteger") return "bigint";
160
+ if (column.type === "float") return "double precision";
161
+ if (column.type === "boolean") return "boolean";
162
+ if (column.type === "json") return "jsonb";
163
+ if (column.type === "date") return "date";
164
+ return "timestamptz";
165
+ }
166
+ resolveSchemaColumnDefault(column) {
167
+ const value = column.default ?? (column.updatedAt ? "now()" : void 0);
168
+ if (value === void 0) return null;
169
+ if (value === "now()") return "now()";
170
+ return this.quoteLiteral(value);
171
+ }
172
+ shouldUseIdentity(column) {
173
+ if (column.autoIncrement === false) return false;
174
+ if (column.type === "id") return column.default === void 0;
175
+ return column.autoIncrement === true;
176
+ }
177
+ buildSchemaColumnDefinition(column) {
178
+ const parts = [this.quoteIdentifier(column.map ?? column.name), this.resolveSchemaColumnType(column)];
179
+ if (this.shouldUseIdentity(column)) parts.push("generated by default as identity");
180
+ const defaultValue = this.resolveSchemaColumnDefault(column);
181
+ if (defaultValue && !this.shouldUseIdentity(column)) parts.push(`default ${defaultValue}`);
182
+ if (column.primary) parts.push("primary key");
183
+ else if (column.unique) parts.push("unique");
184
+ if (!column.nullable && !column.primary) parts.push("not null");
185
+ return parts.join(" ");
186
+ }
187
+ buildSchemaForeignKeyConstraint(table, foreignKey, columns = []) {
188
+ const localColumn = this.resolveSchemaColumnName(foreignKey.column, columns);
189
+ const referencedTable = this.resolveMappedTable(foreignKey.referencesTable);
190
+ const action = foreignKey.onDelete ? ` on delete ${foreignKey.onDelete === "setNull" ? "set null" : foreignKey.onDelete === "setDefault" ? "set default" : foreignKey.onDelete}` : "";
191
+ return `constraint ${this.quoteIdentifier(this.resolveSchemaForeignKeyName(table, foreignKey))} foreign key (${this.quoteIdentifier(localColumn)}) references ${this.quoteIdentifier(referencedTable)} (${this.quoteIdentifier(foreignKey.referencesColumn)})${action}`;
192
+ }
193
+ buildSchemaIndexStatement(table, index, columns = []) {
194
+ const mappedColumns = index.columns.map((column) => this.quoteIdentifier(this.resolveSchemaColumnName(column, columns))).join(", ");
195
+ return `create index if not exists ${this.quoteIdentifier(this.resolveSchemaIndexName(table, index))} on ${this.quoteIdentifier(table)} (${mappedColumns})`;
196
+ }
197
+ async ensureEnumTypes(columns, executor = this.db) {
198
+ for (const column of columns) {
199
+ if (column.type !== "enum") continue;
200
+ const enumName = column.enumName ?? `${column.name}_enum`;
201
+ if ((await kysely.sql`
202
+ select exists(
203
+ select 1
204
+ from pg_type
205
+ where typname = ${enumName}
206
+ ) as exists
207
+ `.execute(executor)).rows[0]?.exists) continue;
208
+ const values = column.enumValues ?? [];
209
+ if (values.length === 0) throw new ArkormException(`Enum column [${column.name}] requires enum values for database-backed migrations.`);
210
+ await this.executeRawStatement(`create type ${this.quoteIdentifier(enumName)} as enum (${values.map((value) => this.quoteLiteral(value)).join(", ")})`, executor);
211
+ }
212
+ }
213
+ async executeCreateTableOperation(operation, executor) {
214
+ const table = this.resolveMappedTable(operation.table);
215
+ await this.ensureEnumTypes(operation.columns, executor);
216
+ const columnDefinitions = operation.columns.map((column) => this.buildSchemaColumnDefinition(column));
217
+ const foreignKeys = (operation.foreignKeys ?? []).map((foreignKey) => this.buildSchemaForeignKeyConstraint(table, foreignKey, operation.columns));
218
+ const definitions = [...columnDefinitions, ...foreignKeys].join(", ");
219
+ await this.executeRawStatement(`create table if not exists ${this.quoteIdentifier(table)} (${definitions})`, executor);
220
+ for (const index of operation.indexes ?? []) await this.executeRawStatement(this.buildSchemaIndexStatement(table, index, operation.columns), executor);
221
+ }
222
+ async executeAlterTableOperation(operation, executor) {
223
+ const table = this.resolveMappedTable(operation.table);
224
+ await this.ensureEnumTypes(operation.addColumns, executor);
225
+ for (const column of operation.addColumns) await this.executeRawStatement(`alter table ${this.quoteIdentifier(table)} add column if not exists ${this.buildSchemaColumnDefinition(column)}`, executor);
226
+ for (const column of operation.dropColumns) await this.executeRawStatement(`alter table ${this.quoteIdentifier(table)} drop column if exists ${this.quoteIdentifier(column)}`, executor);
227
+ for (const foreignKey of operation.addForeignKeys ?? []) await this.executeRawStatement(`alter table ${this.quoteIdentifier(table)} add ${this.buildSchemaForeignKeyConstraint(table, foreignKey, operation.addColumns)}`, executor);
228
+ for (const index of operation.addIndexes ?? []) await this.executeRawStatement(this.buildSchemaIndexStatement(table, index, operation.addColumns), executor);
229
+ }
230
+ async executeDropTableOperation(operation, executor) {
231
+ const table = this.resolveMappedTable(operation.table);
232
+ await this.executeRawStatement(`drop table if exists ${this.quoteIdentifier(table)} cascade`, executor);
233
+ }
234
+ async ensureMigrationStateTables(executor = this.db) {
235
+ await this.executeRawStatement(`
236
+ create table if not exists ${this.quoteIdentifier(KyselyDatabaseAdapter.migrationStateTable)} (
237
+ id text primary key,
238
+ file text not null,
239
+ class_name text not null,
240
+ applied_at timestamptz not null,
241
+ checksum text null
242
+ )
243
+ `, executor);
244
+ await this.executeRawStatement(`
245
+ create table if not exists ${this.quoteIdentifier(KyselyDatabaseAdapter.migrationRunTable)} (
246
+ id text primary key,
247
+ applied_at timestamptz not null,
248
+ migration_ids jsonb not null
249
+ )
250
+ `, executor);
251
+ }
252
+ async writeAppliedMigrationsStateInternal(state, executor) {
253
+ await this.ensureMigrationStateTables(executor);
254
+ await this.executeRawStatement(`delete from ${this.quoteIdentifier(KyselyDatabaseAdapter.migrationRunTable)}`, executor);
255
+ await this.executeRawStatement(`delete from ${this.quoteIdentifier(KyselyDatabaseAdapter.migrationStateTable)}`, executor);
256
+ for (const migration of state.migrations) await kysely.sql`
257
+ insert into ${kysely.sql.table(KyselyDatabaseAdapter.migrationStateTable)} (id, file, class_name, applied_at, checksum)
258
+ values (${migration.id}, ${migration.file}, ${migration.className}, ${migration.appliedAt}, ${migration.checksum ?? null})
259
+ `.execute(executor);
260
+ for (const run of state.runs ?? []) await kysely.sql`
261
+ insert into ${kysely.sql.table(KyselyDatabaseAdapter.migrationRunTable)} (id, applied_at, migration_ids)
262
+ values (${run.id}, ${run.appliedAt}, cast(${JSON.stringify(run.migrationIds)} as jsonb))
263
+ `.execute(executor);
264
+ }
265
+ async resetDatabaseInternal(executor) {
266
+ const tablesResult = await kysely.sql`
267
+ select table_name, table_schema
268
+ from information_schema.tables
269
+ where table_schema = current_schema()
270
+ and table_type = 'BASE TABLE'
271
+ order by table_name asc
272
+ `.execute(executor);
273
+ 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);
274
+ const enumTypesResult = await kysely.sql`
275
+ select t.typname as enum_name, n.nspname as enum_schema
276
+ from pg_type t
277
+ inner join pg_namespace n on n.oid = t.typnamespace
278
+ where t.typtype = 'e'
279
+ and n.nspname = current_schema()
280
+ order by t.typname asc
281
+ `.execute(executor);
282
+ 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);
283
+ }
127
284
  introspectionTypeToTs(typeName, enumValues) {
128
285
  if (enumValues && enumValues.length > 0) return enumValues.map((value) => `'${value.replace(/'/g, "\\'")}'`).join(" | ");
129
286
  switch (typeName) {
@@ -750,6 +907,63 @@ var KyselyDatabaseAdapter = class KyselyDatabaseAdapter {
750
907
  });
751
908
  return [...models.values()];
752
909
  }
910
+ async executeSchemaOperations(operations) {
911
+ if (operations.length === 0) return;
912
+ await this.transaction(async (adapter) => {
913
+ const transactionAdapter = adapter;
914
+ for (const operation of operations) {
915
+ if (operation.type === "createTable") {
916
+ await transactionAdapter.executeCreateTableOperation(operation, transactionAdapter.db);
917
+ continue;
918
+ }
919
+ if (operation.type === "alterTable") {
920
+ await transactionAdapter.executeAlterTableOperation(operation, transactionAdapter.db);
921
+ continue;
922
+ }
923
+ await transactionAdapter.executeDropTableOperation(operation, transactionAdapter.db);
924
+ }
925
+ });
926
+ }
927
+ async resetDatabase() {
928
+ await this.transaction(async (adapter) => {
929
+ const transactionAdapter = adapter;
930
+ await transactionAdapter.resetDatabaseInternal(transactionAdapter.db);
931
+ });
932
+ }
933
+ async readAppliedMigrationsState() {
934
+ await this.ensureMigrationStateTables();
935
+ const migrationsResult = await kysely.sql`
936
+ select id, file, class_name, applied_at, checksum
937
+ from ${kysely.sql.table(KyselyDatabaseAdapter.migrationStateTable)}
938
+ order by applied_at asc, id asc
939
+ `.execute(this.db);
940
+ const runsResult = await kysely.sql`
941
+ select id, applied_at, migration_ids
942
+ from ${kysely.sql.table(KyselyDatabaseAdapter.migrationRunTable)}
943
+ order by applied_at asc, id asc
944
+ `.execute(this.db);
945
+ return {
946
+ version: 1,
947
+ migrations: migrationsResult.rows.map((row) => ({
948
+ id: row.id,
949
+ file: row.file,
950
+ className: row.class_name,
951
+ appliedAt: row.applied_at instanceof Date ? row.applied_at.toISOString() : String(row.applied_at),
952
+ checksum: row.checksum ?? void 0
953
+ })),
954
+ runs: runsResult.rows.map((row) => ({
955
+ id: row.id,
956
+ appliedAt: row.applied_at instanceof Date ? row.applied_at.toISOString() : String(row.applied_at),
957
+ migrationIds: Array.isArray(row.migration_ids) ? row.migration_ids.filter((value) => typeof value === "string") : typeof row.migration_ids === "string" ? JSON.parse(row.migration_ids) : []
958
+ }))
959
+ };
960
+ }
961
+ async writeAppliedMigrationsState(state) {
962
+ await this.transaction(async (adapter) => {
963
+ const transactionAdapter = adapter;
964
+ await transactionAdapter.writeAppliedMigrationsStateInternal(state, transactionAdapter.db);
965
+ });
966
+ }
753
967
  /**
754
968
  * Executes a series of database operations within a transaction.
755
969
  * The provided callback function is called with a new instance of the
@@ -2938,6 +3152,28 @@ const getMigrationPlan = async (migration, direction = "up") => {
2938
3152
  else await instance.down(schema);
2939
3153
  return schema.getOperations();
2940
3154
  };
3155
+ const supportsDatabaseMigrationExecution = (adapter) => {
3156
+ return typeof adapter?.executeSchemaOperations === "function";
3157
+ };
3158
+ const supportsDatabaseReset = (adapter) => {
3159
+ return typeof adapter?.resetDatabase === "function";
3160
+ };
3161
+ const stripPrismaSchemaModelsAndEnums = (schema) => {
3162
+ const stripped = schema.replace(PRISMA_MODEL_REGEX, "").replace(PRISMA_ENUM_REGEX, "").replace(/\n{3,}/g, "\n\n").trimEnd();
3163
+ return stripped.length > 0 ? `${stripped}\n` : "";
3164
+ };
3165
+ const applyMigrationToDatabase = async (adapter, migration) => {
3166
+ if (!supportsDatabaseMigrationExecution(adapter)) throw new ArkormException("The configured adapter does not support database-backed migration execution.");
3167
+ const operations = await getMigrationPlan(migration, "up");
3168
+ await adapter.executeSchemaOperations(operations);
3169
+ return { operations };
3170
+ };
3171
+ const applyMigrationRollbackToDatabase = async (adapter, migration) => {
3172
+ if (!supportsDatabaseMigrationExecution(adapter)) throw new ArkormException("The configured adapter does not support database-backed migration execution.");
3173
+ const operations = await getMigrationPlan(migration, "down");
3174
+ await adapter.executeSchemaOperations(operations);
3175
+ return { operations };
3176
+ };
2941
3177
  /**
2942
3178
  * Apply the schema operations defined in a migration to a Prisma schema
2943
3179
  * file, updating the file on disk if specified, and return the updated
@@ -3918,10 +4154,13 @@ var MakeSeederCommand = class extends _h3ravel_musket.Command {
3918
4154
 
3919
4155
  //#endregion
3920
4156
  //#region src/helpers/migration-history.ts
3921
- const DEFAULT_STATE = {
4157
+ const createEmptyAppliedMigrationsState = () => ({
3922
4158
  version: 1,
3923
4159
  migrations: [],
3924
4160
  runs: []
4161
+ });
4162
+ const supportsDatabaseMigrationState = (adapter) => {
4163
+ return typeof adapter?.readAppliedMigrationsState === "function" && typeof adapter?.writeAppliedMigrationsState === "function";
3925
4164
  };
3926
4165
  const resolveMigrationStateFilePath = (cwd, configuredPath) => {
3927
4166
  if (configuredPath && configuredPath.trim().length > 0) return (0, node_path.resolve)(configuredPath);
@@ -3936,10 +4175,10 @@ const computeMigrationChecksum = (filePath) => {
3936
4175
  return (0, node_crypto.createHash)("sha256").update(source).digest("hex");
3937
4176
  };
3938
4177
  const readAppliedMigrationsState = (stateFilePath) => {
3939
- if (!(0, node_fs.existsSync)(stateFilePath)) return { ...DEFAULT_STATE };
4178
+ if (!(0, node_fs.existsSync)(stateFilePath)) return createEmptyAppliedMigrationsState();
3940
4179
  try {
3941
4180
  const parsed = JSON.parse((0, node_fs.readFileSync)(stateFilePath, "utf-8"));
3942
- if (!Array.isArray(parsed.migrations)) return { ...DEFAULT_STATE };
4181
+ if (!Array.isArray(parsed.migrations)) return createEmptyAppliedMigrationsState();
3943
4182
  return {
3944
4183
  version: 1,
3945
4184
  migrations: parsed.migrations.filter((migration) => {
@@ -3950,14 +4189,34 @@ const readAppliedMigrationsState = (stateFilePath) => {
3950
4189
  }) : []
3951
4190
  };
3952
4191
  } catch {
3953
- return { ...DEFAULT_STATE };
4192
+ return createEmptyAppliedMigrationsState();
3954
4193
  }
3955
4194
  };
4195
+ const readAppliedMigrationsStateFromStore = async (adapter, stateFilePath) => {
4196
+ if (supportsDatabaseMigrationState(adapter)) return await adapter.readAppliedMigrationsState();
4197
+ return readAppliedMigrationsState(stateFilePath);
4198
+ };
3956
4199
  const writeAppliedMigrationsState = (stateFilePath, state) => {
3957
4200
  const directory = (0, node_path.dirname)(stateFilePath);
3958
4201
  if (!(0, node_fs.existsSync)(directory)) (0, node_fs.mkdirSync)(directory, { recursive: true });
3959
4202
  (0, node_fs.writeFileSync)(stateFilePath, JSON.stringify(state, null, 2));
3960
4203
  };
4204
+ const writeAppliedMigrationsStateToStore = async (adapter, stateFilePath, state) => {
4205
+ if (supportsDatabaseMigrationState(adapter)) {
4206
+ await adapter.writeAppliedMigrationsState(state);
4207
+ return;
4208
+ }
4209
+ writeAppliedMigrationsState(stateFilePath, state);
4210
+ };
4211
+ const deleteAppliedMigrationsStateFromStore = async (adapter, stateFilePath) => {
4212
+ if (supportsDatabaseMigrationState(adapter)) {
4213
+ await adapter.writeAppliedMigrationsState(createEmptyAppliedMigrationsState());
4214
+ return "database";
4215
+ }
4216
+ if (!(0, node_fs.existsSync)(stateFilePath)) return "missing-file";
4217
+ writeAppliedMigrationsState(stateFilePath, createEmptyAppliedMigrationsState());
4218
+ return "file";
4219
+ };
3961
4220
  const isMigrationApplied = (state, identity, checksum) => {
3962
4221
  const matched = state.migrations.find((migration) => migration.id === identity);
3963
4222
  if (!matched) return false;
@@ -4076,7 +4335,9 @@ var MigrateCommand = class extends _h3ravel_musket.Command {
4076
4335
  const classes = this.option("all") || !this.argument("name") ? await this.loadAllMigrations(migrationsDir) : (await this.loadNamedMigration(migrationsDir, this.argument("name"))).filter(([cls]) => cls !== void 0);
4077
4336
  if (classes.length === 0) return void this.error("Error: No migration classes found to run.");
4078
4337
  const stateFilePath = resolveMigrationStateFilePath(process.cwd(), this.option("state-file") ? String(this.option("state-file")) : void 0);
4079
- let appliedState = readAppliedMigrationsState(stateFilePath);
4338
+ let appliedState = await readAppliedMigrationsStateFromStore(this.app.getConfig("adapter"), stateFilePath);
4339
+ const adapter = this.app.getConfig("adapter");
4340
+ const useDatabaseMigrations = supportsDatabaseMigrationExecution(adapter);
4080
4341
  const skipped = [];
4081
4342
  const changed = [];
4082
4343
  const pending = classes.filter(([migrationClass, file]) => {
@@ -4098,10 +4359,16 @@ var MigrateCommand = class extends _h3ravel_musket.Command {
4098
4359
  this.success("No pending migration classes to apply.");
4099
4360
  return;
4100
4361
  }
4101
- for (const [MigrationClassItem] of pending) await applyMigrationToPrismaSchema(MigrationClassItem, {
4102
- schemaPath,
4103
- write: true
4104
- });
4362
+ for (const [MigrationClassItem] of pending) {
4363
+ if (useDatabaseMigrations) {
4364
+ await applyMigrationToDatabase(adapter, MigrationClassItem);
4365
+ continue;
4366
+ }
4367
+ await applyMigrationToPrismaSchema(MigrationClassItem, {
4368
+ schemaPath,
4369
+ write: true
4370
+ });
4371
+ }
4105
4372
  if (appliedState) {
4106
4373
  const runAppliedIds = [];
4107
4374
  for (const [migrationClass, file] of pending) {
@@ -4120,10 +4387,10 @@ var MigrateCommand = class extends _h3ravel_musket.Command {
4120
4387
  appliedAt: (/* @__PURE__ */ new Date()).toISOString(),
4121
4388
  migrationIds: runAppliedIds
4122
4389
  });
4123
- writeAppliedMigrationsState(stateFilePath, appliedState);
4390
+ await writeAppliedMigrationsStateToStore(adapter, stateFilePath, appliedState);
4124
4391
  }
4125
- if (!this.option("skip-generate")) runPrismaCommand(["generate"], process.cwd());
4126
- if (!this.option("skip-migrate")) if (this.option("deploy")) runPrismaCommand(["migrate", "deploy"], process.cwd());
4392
+ if (!useDatabaseMigrations && !this.option("skip-generate")) runPrismaCommand(["generate"], process.cwd());
4393
+ if (!useDatabaseMigrations && !this.option("skip-migrate")) if (this.option("deploy")) runPrismaCommand(["migrate", "deploy"], process.cwd());
4127
4394
  else runPrismaCommand([
4128
4395
  "migrate",
4129
4396
  "dev",
@@ -4183,6 +4450,85 @@ var MigrateCommand = class extends _h3ravel_musket.Command {
4183
4450
  }
4184
4451
  };
4185
4452
 
4453
+ //#endregion
4454
+ //#region src/cli/commands/MigrateFreshCommand.ts
4455
+ var MigrateFreshCommand = class extends _h3ravel_musket.Command {
4456
+ signature = `migrate:fresh
4457
+ {--skip-generate : Skip prisma generate}
4458
+ {--skip-migrate : Skip prisma database sync}
4459
+ {--state-file= : Path to applied migration state file}
4460
+ {--schema= : Explicit prisma schema path}
4461
+ `;
4462
+ description = "Reset the database and rerun all migration classes";
4463
+ async handle() {
4464
+ this.app.command = this;
4465
+ const configuredMigrationsDir = this.app.getConfig("paths")?.migrations ?? (0, node_path.join)(process.cwd(), "database", "migrations");
4466
+ const migrationsDir = this.app.resolveRuntimeDirectoryPath(configuredMigrationsDir);
4467
+ if (!(0, node_fs.existsSync)(migrationsDir)) return void this.error(`Error: Migrations directory not found: ${this.app.formatPathForLog(configuredMigrationsDir)}`);
4468
+ const adapter = this.app.getConfig("adapter");
4469
+ const useDatabaseMigrations = supportsDatabaseMigrationExecution(adapter);
4470
+ const schemaPath = this.option("schema") ? (0, node_path.resolve)(String(this.option("schema"))) : (0, node_path.join)(process.cwd(), "prisma", "schema.prisma");
4471
+ const stateFilePath = resolveMigrationStateFilePath(process.cwd(), this.option("state-file") ? String(this.option("state-file")) : void 0);
4472
+ const migrations = await this.loadAllMigrations(migrationsDir);
4473
+ if (migrations.length === 0) return void this.error("Error: No migration classes found to run.");
4474
+ if (supportsDatabaseReset(adapter)) await adapter.resetDatabase();
4475
+ else {
4476
+ if (!(0, node_fs.existsSync)(schemaPath)) return void this.error(`Error: Prisma schema file not found: ${this.app.formatPathForLog(schemaPath)}`);
4477
+ (0, node_fs.writeFileSync)(schemaPath, stripPrismaSchemaModelsAndEnums((0, node_fs.readFileSync)(schemaPath, "utf-8")));
4478
+ }
4479
+ let appliedState = createEmptyAppliedMigrationsState();
4480
+ await writeAppliedMigrationsStateToStore(adapter, stateFilePath, appliedState);
4481
+ for (const [MigrationClassItem] of migrations) {
4482
+ if (useDatabaseMigrations) {
4483
+ await applyMigrationToDatabase(adapter, MigrationClassItem);
4484
+ continue;
4485
+ }
4486
+ await applyMigrationToPrismaSchema(MigrationClassItem, {
4487
+ schemaPath,
4488
+ write: true
4489
+ });
4490
+ }
4491
+ for (const [migrationClass, file] of migrations) appliedState = markMigrationApplied(appliedState, {
4492
+ id: buildMigrationIdentity(file, migrationClass.name),
4493
+ file,
4494
+ className: migrationClass.name,
4495
+ appliedAt: (/* @__PURE__ */ new Date()).toISOString(),
4496
+ checksum: computeMigrationChecksum(file)
4497
+ });
4498
+ appliedState = markMigrationRun(appliedState, {
4499
+ id: buildMigrationRunId(),
4500
+ appliedAt: (/* @__PURE__ */ new Date()).toISOString(),
4501
+ migrationIds: appliedState.migrations.map((migration) => migration.id)
4502
+ });
4503
+ await writeAppliedMigrationsStateToStore(adapter, stateFilePath, appliedState);
4504
+ if (!useDatabaseMigrations) {
4505
+ const schemaArgs = this.option("schema") ? ["--schema", schemaPath] : [];
4506
+ if (!this.option("skip-generate")) runPrismaCommand(["generate", ...schemaArgs], process.cwd());
4507
+ if (!this.option("skip-migrate")) runPrismaCommand([
4508
+ "db",
4509
+ "push",
4510
+ "--force-reset",
4511
+ ...schemaArgs
4512
+ ], process.cwd());
4513
+ }
4514
+ this.success(`Refreshed database with ${migrations.length} migration(s).`);
4515
+ migrations.forEach(([_, file]) => this.success(this.app.splitLogger("Migrated", file)));
4516
+ }
4517
+ async loadAllMigrations(migrationsDir) {
4518
+ const files = (0, node_fs.readdirSync)(migrationsDir).filter((file) => /\.(ts|js|mjs|cjs)$/i.test(file)).sort((left, right) => left.localeCompare(right)).map((file) => this.app.resolveRuntimeScriptPath((0, node_path.join)(migrationsDir, file)));
4519
+ return (await Promise.all(files.map(async (file) => (await this.loadMigrationClassesFromFile(file)).map((cls) => [cls, file])))).flat();
4520
+ }
4521
+ async loadMigrationClassesFromFile(filePath) {
4522
+ const imported = await RuntimeModuleLoader.load(filePath);
4523
+ return Object.values(imported).filter((value) => {
4524
+ if (typeof value !== "function") return false;
4525
+ const candidate = value;
4526
+ const prototype = candidate.prototype;
4527
+ return candidate[MIGRATION_BRAND] === true || typeof prototype?.up === "function" && typeof prototype?.down === "function";
4528
+ });
4529
+ }
4530
+ };
4531
+
4186
4532
  //#endregion
4187
4533
  //#region src/cli/commands/MigrateRollbackCommand.ts
4188
4534
  /**
@@ -4211,7 +4557,9 @@ var MigrateRollbackCommand = class extends _h3ravel_musket.Command {
4211
4557
  if (!(0, node_fs.existsSync)(migrationsDir)) return void this.error(`Error: Migrations directory not found: ${this.app.formatPathForLog(configuredMigrationsDir)}`);
4212
4558
  const schemaPath = this.option("schema") ? (0, node_path.resolve)(String(this.option("schema"))) : (0, node_path.join)(process.cwd(), "prisma", "schema.prisma");
4213
4559
  const stateFilePath = resolveMigrationStateFilePath(process.cwd(), this.option("state-file") ? String(this.option("state-file")) : void 0);
4214
- let appliedState = readAppliedMigrationsState(stateFilePath);
4560
+ const adapter = this.app.getConfig("adapter");
4561
+ const useDatabaseMigrations = supportsDatabaseMigrationExecution(adapter);
4562
+ let appliedState = await readAppliedMigrationsStateFromStore(adapter, stateFilePath);
4215
4563
  const stepOption = this.option("step");
4216
4564
  const stepCount = stepOption == null ? void 0 : Number(stepOption);
4217
4565
  if (stepCount != null && (!Number.isFinite(stepCount) || stepCount <= 0 || !Number.isInteger(stepCount))) return void this.error("Error: --step must be a positive integer.");
@@ -4233,17 +4581,23 @@ var MigrateRollbackCommand = class extends _h3ravel_musket.Command {
4233
4581
  rollbackClasses.forEach(([_, file]) => this.success(this.app.splitLogger("WouldRollback", file)));
4234
4582
  return;
4235
4583
  }
4236
- for (const [MigrationClassItem] of rollbackClasses) await applyMigrationRollbackToPrismaSchema(MigrationClassItem, {
4237
- schemaPath,
4238
- write: true
4239
- });
4584
+ for (const [MigrationClassItem] of rollbackClasses) {
4585
+ if (useDatabaseMigrations) {
4586
+ await applyMigrationRollbackToDatabase(adapter, MigrationClassItem);
4587
+ continue;
4588
+ }
4589
+ await applyMigrationRollbackToPrismaSchema(MigrationClassItem, {
4590
+ schemaPath,
4591
+ write: true
4592
+ });
4593
+ }
4240
4594
  for (const [migrationClass, file] of rollbackClasses) {
4241
4595
  const identity = buildMigrationIdentity(file, migrationClass.name);
4242
4596
  appliedState = removeAppliedMigration(appliedState, identity);
4243
4597
  }
4244
- writeAppliedMigrationsState(stateFilePath, appliedState);
4245
- if (!this.option("skip-generate")) runPrismaCommand(["generate"], process.cwd());
4246
- if (!this.option("skip-migrate")) if (this.option("deploy")) runPrismaCommand(["migrate", "deploy"], process.cwd());
4598
+ await writeAppliedMigrationsStateToStore(adapter, stateFilePath, appliedState);
4599
+ if (!useDatabaseMigrations && !this.option("skip-generate")) runPrismaCommand(["generate"], process.cwd());
4600
+ if (!useDatabaseMigrations && !this.option("skip-migrate")) if (this.option("deploy")) runPrismaCommand(["migrate", "deploy"], process.cwd());
4247
4601
  else runPrismaCommand([
4248
4602
  "migrate",
4249
4603
  "dev",
@@ -4287,7 +4641,14 @@ var MigrationHistoryCommand = class extends _h3ravel_musket.Command {
4287
4641
  async handle() {
4288
4642
  this.app.command = this;
4289
4643
  const stateFilePath = resolveMigrationStateFilePath(process.cwd(), this.option("state-file") ? String(this.option("state-file")) : void 0);
4644
+ const adapter = this.app.getConfig("adapter");
4645
+ const usesDatabaseState = supportsDatabaseMigrationState(adapter);
4290
4646
  if (this.option("delete")) {
4647
+ if (usesDatabaseState) {
4648
+ await adapter.writeAppliedMigrationsState(createEmptyAppliedMigrationsState());
4649
+ this.success("Deleted tracked migration state from database.");
4650
+ return;
4651
+ }
4291
4652
  if (!(0, node_fs.existsSync)(stateFilePath)) {
4292
4653
  this.success(`No migration state file found at ${this.app.formatPathForLog(stateFilePath)}`);
4293
4654
  return;
@@ -4297,22 +4658,19 @@ var MigrationHistoryCommand = class extends _h3ravel_musket.Command {
4297
4658
  return;
4298
4659
  }
4299
4660
  if (this.option("reset")) {
4300
- writeAppliedMigrationsState(stateFilePath, {
4301
- version: 1,
4302
- migrations: []
4303
- });
4304
- this.success(`Reset migration state: ${this.app.formatPathForLog(stateFilePath)}`);
4661
+ await writeAppliedMigrationsStateToStore(adapter, stateFilePath, createEmptyAppliedMigrationsState());
4662
+ this.success(usesDatabaseState ? "Reset migration state in database." : `Reset migration state: ${this.app.formatPathForLog(stateFilePath)}`);
4305
4663
  return;
4306
4664
  }
4307
- const state = readAppliedMigrationsState(stateFilePath);
4665
+ const state = await readAppliedMigrationsStateFromStore(adapter, stateFilePath);
4308
4666
  if (this.option("json")) {
4309
4667
  this.success(JSON.stringify({
4310
- path: stateFilePath,
4668
+ path: usesDatabaseState ? "database" : stateFilePath,
4311
4669
  ...state
4312
4670
  }, null, 2));
4313
4671
  return;
4314
4672
  }
4315
- this.success(this.app.splitLogger("State", stateFilePath));
4673
+ this.success(this.app.splitLogger("State", usesDatabaseState ? "database" : stateFilePath));
4316
4674
  this.success(this.app.splitLogger("Tracked", String(state.migrations.length)));
4317
4675
  if (state.migrations.length === 0) {
4318
4676
  this.success("No tracked migrations found.");
@@ -9295,6 +9653,7 @@ exports.MakeMigrationCommand = MakeMigrationCommand;
9295
9653
  exports.MakeModelCommand = MakeModelCommand;
9296
9654
  exports.MakeSeederCommand = MakeSeederCommand;
9297
9655
  exports.MigrateCommand = MigrateCommand;
9656
+ exports.MigrateFreshCommand = MigrateFreshCommand;
9298
9657
  exports.MigrateRollbackCommand = MigrateRollbackCommand;
9299
9658
  exports.Migration = Migration;
9300
9659
  exports.MigrationHistoryCommand = MigrationHistoryCommand;
@@ -9324,7 +9683,9 @@ exports.UnsupportedAdapterFeatureException = UnsupportedAdapterFeatureException;
9324
9683
  exports.applyAlterTableOperation = applyAlterTableOperation;
9325
9684
  exports.applyCreateTableOperation = applyCreateTableOperation;
9326
9685
  exports.applyDropTableOperation = applyDropTableOperation;
9686
+ exports.applyMigrationRollbackToDatabase = applyMigrationRollbackToDatabase;
9327
9687
  exports.applyMigrationRollbackToPrismaSchema = applyMigrationRollbackToPrismaSchema;
9688
+ exports.applyMigrationToDatabase = applyMigrationToDatabase;
9328
9689
  exports.applyMigrationToPrismaSchema = applyMigrationToPrismaSchema;
9329
9690
  exports.applyOperationsToPrismaSchema = applyOperationsToPrismaSchema;
9330
9691
  exports.bindAdapterToModels = bindAdapterToModels;
@@ -9339,6 +9700,7 @@ exports.buildModelBlock = buildModelBlock;
9339
9700
  exports.buildRelationLine = buildRelationLine;
9340
9701
  exports.computeMigrationChecksum = computeMigrationChecksum;
9341
9702
  exports.configureArkormRuntime = configureArkormRuntime;
9703
+ exports.createEmptyAppliedMigrationsState = createEmptyAppliedMigrationsState;
9342
9704
  exports.createKyselyAdapter = createKyselyAdapter;
9343
9705
  exports.createMigrationTimestamp = createMigrationTimestamp;
9344
9706
  exports.createPrismaAdapter = createPrismaAdapter;
@@ -9347,6 +9709,7 @@ exports.createPrismaDatabaseAdapter = createPrismaDatabaseAdapter;
9347
9709
  exports.createPrismaDelegateMap = createPrismaDelegateMap;
9348
9710
  exports.defineConfig = defineConfig;
9349
9711
  exports.defineFactory = defineFactory;
9712
+ exports.deleteAppliedMigrationsStateFromStore = deleteAppliedMigrationsStateFromStore;
9350
9713
  exports.deriveCollectionFieldName = deriveCollectionFieldName;
9351
9714
  exports.deriveInverseRelationAlias = deriveInverseRelationAlias;
9352
9715
  exports.deriveRelationAlias = deriveRelationAlias;
@@ -9380,6 +9743,7 @@ exports.markMigrationApplied = markMigrationApplied;
9380
9743
  exports.markMigrationRun = markMigrationRun;
9381
9744
  exports.pad = pad;
9382
9745
  exports.readAppliedMigrationsState = readAppliedMigrationsState;
9746
+ exports.readAppliedMigrationsStateFromStore = readAppliedMigrationsStateFromStore;
9383
9747
  exports.removeAppliedMigration = removeAppliedMigration;
9384
9748
  exports.resetArkormRuntimeForTests = resetArkormRuntimeForTests;
9385
9749
  exports.resolveCast = resolveCast;
@@ -9390,6 +9754,11 @@ exports.resolvePrismaType = resolvePrismaType;
9390
9754
  exports.runArkormTransaction = runArkormTransaction;
9391
9755
  exports.runMigrationWithPrisma = runMigrationWithPrisma;
9392
9756
  exports.runPrismaCommand = runPrismaCommand;
9757
+ exports.stripPrismaSchemaModelsAndEnums = stripPrismaSchemaModelsAndEnums;
9758
+ exports.supportsDatabaseMigrationExecution = supportsDatabaseMigrationExecution;
9759
+ exports.supportsDatabaseMigrationState = supportsDatabaseMigrationState;
9760
+ exports.supportsDatabaseReset = supportsDatabaseReset;
9393
9761
  exports.toMigrationFileSlug = toMigrationFileSlug;
9394
9762
  exports.toModelName = toModelName;
9395
- exports.writeAppliedMigrationsState = writeAppliedMigrationsState;
9763
+ exports.writeAppliedMigrationsState = writeAppliedMigrationsState;
9764
+ exports.writeAppliedMigrationsStateToStore = writeAppliedMigrationsStateToStore;