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/cli.mjs CHANGED
@@ -1371,6 +1371,28 @@ const getMigrationPlan = async (migration, direction = "up") => {
1371
1371
  else await instance.down(schema);
1372
1372
  return schema.getOperations();
1373
1373
  };
1374
+ const supportsDatabaseMigrationExecution = (adapter) => {
1375
+ return typeof adapter?.executeSchemaOperations === "function";
1376
+ };
1377
+ const supportsDatabaseReset = (adapter) => {
1378
+ return typeof adapter?.resetDatabase === "function";
1379
+ };
1380
+ const stripPrismaSchemaModelsAndEnums = (schema) => {
1381
+ const stripped = schema.replace(PRISMA_MODEL_REGEX, "").replace(PRISMA_ENUM_REGEX, "").replace(/\n{3,}/g, "\n\n").trimEnd();
1382
+ return stripped.length > 0 ? `${stripped}\n` : "";
1383
+ };
1384
+ const applyMigrationToDatabase = async (adapter, migration) => {
1385
+ if (!supportsDatabaseMigrationExecution(adapter)) throw new ArkormException("The configured adapter does not support database-backed migration execution.");
1386
+ const operations = await getMigrationPlan(migration, "up");
1387
+ await adapter.executeSchemaOperations(operations);
1388
+ return { operations };
1389
+ };
1390
+ const applyMigrationRollbackToDatabase = async (adapter, migration) => {
1391
+ if (!supportsDatabaseMigrationExecution(adapter)) throw new ArkormException("The configured adapter does not support database-backed migration execution.");
1392
+ const operations = await getMigrationPlan(migration, "down");
1393
+ await adapter.executeSchemaOperations(operations);
1394
+ return { operations };
1395
+ };
1374
1396
  /**
1375
1397
  * Apply the schema operations defined in a migration to a Prisma schema
1376
1398
  * file, updating the file on disk if specified, and return the updated
@@ -2509,10 +2531,13 @@ var MakeSeederCommand = class extends Command {
2509
2531
 
2510
2532
  //#endregion
2511
2533
  //#region src/helpers/migration-history.ts
2512
- const DEFAULT_STATE = {
2534
+ const createEmptyAppliedMigrationsState = () => ({
2513
2535
  version: 1,
2514
2536
  migrations: [],
2515
2537
  runs: []
2538
+ });
2539
+ const supportsDatabaseMigrationState = (adapter) => {
2540
+ return typeof adapter?.readAppliedMigrationsState === "function" && typeof adapter?.writeAppliedMigrationsState === "function";
2516
2541
  };
2517
2542
  const resolveMigrationStateFilePath = (cwd, configuredPath) => {
2518
2543
  if (configuredPath && configuredPath.trim().length > 0) return resolve(configuredPath);
@@ -2527,10 +2552,10 @@ const computeMigrationChecksum = (filePath) => {
2527
2552
  return createHash("sha256").update(source).digest("hex");
2528
2553
  };
2529
2554
  const readAppliedMigrationsState = (stateFilePath) => {
2530
- if (!existsSync(stateFilePath)) return { ...DEFAULT_STATE };
2555
+ if (!existsSync(stateFilePath)) return createEmptyAppliedMigrationsState();
2531
2556
  try {
2532
2557
  const parsed = JSON.parse(readFileSync(stateFilePath, "utf-8"));
2533
- if (!Array.isArray(parsed.migrations)) return { ...DEFAULT_STATE };
2558
+ if (!Array.isArray(parsed.migrations)) return createEmptyAppliedMigrationsState();
2534
2559
  return {
2535
2560
  version: 1,
2536
2561
  migrations: parsed.migrations.filter((migration) => {
@@ -2541,14 +2566,25 @@ const readAppliedMigrationsState = (stateFilePath) => {
2541
2566
  }) : []
2542
2567
  };
2543
2568
  } catch {
2544
- return { ...DEFAULT_STATE };
2569
+ return createEmptyAppliedMigrationsState();
2545
2570
  }
2546
2571
  };
2572
+ const readAppliedMigrationsStateFromStore = async (adapter, stateFilePath) => {
2573
+ if (supportsDatabaseMigrationState(adapter)) return await adapter.readAppliedMigrationsState();
2574
+ return readAppliedMigrationsState(stateFilePath);
2575
+ };
2547
2576
  const writeAppliedMigrationsState = (stateFilePath, state) => {
2548
2577
  const directory = dirname(stateFilePath);
2549
2578
  if (!existsSync(directory)) mkdirSync(directory, { recursive: true });
2550
2579
  writeFileSync(stateFilePath, JSON.stringify(state, null, 2));
2551
2580
  };
2581
+ const writeAppliedMigrationsStateToStore = async (adapter, stateFilePath, state) => {
2582
+ if (supportsDatabaseMigrationState(adapter)) {
2583
+ await adapter.writeAppliedMigrationsState(state);
2584
+ return;
2585
+ }
2586
+ writeAppliedMigrationsState(stateFilePath, state);
2587
+ };
2552
2588
  const isMigrationApplied = (state, identity, checksum) => {
2553
2589
  const matched = state.migrations.find((migration) => migration.id === identity);
2554
2590
  if (!matched) return false;
@@ -2667,7 +2703,9 @@ var MigrateCommand = class extends Command {
2667
2703
  const classes = this.option("all") || !this.argument("name") ? await this.loadAllMigrations(migrationsDir) : (await this.loadNamedMigration(migrationsDir, this.argument("name"))).filter(([cls]) => cls !== void 0);
2668
2704
  if (classes.length === 0) return void this.error("Error: No migration classes found to run.");
2669
2705
  const stateFilePath = resolveMigrationStateFilePath(process.cwd(), this.option("state-file") ? String(this.option("state-file")) : void 0);
2670
- let appliedState = readAppliedMigrationsState(stateFilePath);
2706
+ let appliedState = await readAppliedMigrationsStateFromStore(this.app.getConfig("adapter"), stateFilePath);
2707
+ const adapter = this.app.getConfig("adapter");
2708
+ const useDatabaseMigrations = supportsDatabaseMigrationExecution(adapter);
2671
2709
  const skipped = [];
2672
2710
  const changed = [];
2673
2711
  const pending = classes.filter(([migrationClass, file]) => {
@@ -2689,10 +2727,16 @@ var MigrateCommand = class extends Command {
2689
2727
  this.success("No pending migration classes to apply.");
2690
2728
  return;
2691
2729
  }
2692
- for (const [MigrationClassItem] of pending) await applyMigrationToPrismaSchema(MigrationClassItem, {
2693
- schemaPath,
2694
- write: true
2695
- });
2730
+ for (const [MigrationClassItem] of pending) {
2731
+ if (useDatabaseMigrations) {
2732
+ await applyMigrationToDatabase(adapter, MigrationClassItem);
2733
+ continue;
2734
+ }
2735
+ await applyMigrationToPrismaSchema(MigrationClassItem, {
2736
+ schemaPath,
2737
+ write: true
2738
+ });
2739
+ }
2696
2740
  if (appliedState) {
2697
2741
  const runAppliedIds = [];
2698
2742
  for (const [migrationClass, file] of pending) {
@@ -2711,10 +2755,10 @@ var MigrateCommand = class extends Command {
2711
2755
  appliedAt: (/* @__PURE__ */ new Date()).toISOString(),
2712
2756
  migrationIds: runAppliedIds
2713
2757
  });
2714
- writeAppliedMigrationsState(stateFilePath, appliedState);
2758
+ await writeAppliedMigrationsStateToStore(adapter, stateFilePath, appliedState);
2715
2759
  }
2716
- if (!this.option("skip-generate")) runPrismaCommand(["generate"], process.cwd());
2717
- if (!this.option("skip-migrate")) if (this.option("deploy")) runPrismaCommand(["migrate", "deploy"], process.cwd());
2760
+ if (!useDatabaseMigrations && !this.option("skip-generate")) runPrismaCommand(["generate"], process.cwd());
2761
+ if (!useDatabaseMigrations && !this.option("skip-migrate")) if (this.option("deploy")) runPrismaCommand(["migrate", "deploy"], process.cwd());
2718
2762
  else runPrismaCommand([
2719
2763
  "migrate",
2720
2764
  "dev",
@@ -2774,6 +2818,85 @@ var MigrateCommand = class extends Command {
2774
2818
  }
2775
2819
  };
2776
2820
 
2821
+ //#endregion
2822
+ //#region src/cli/commands/MigrateFreshCommand.ts
2823
+ var MigrateFreshCommand = class extends Command {
2824
+ signature = `migrate:fresh
2825
+ {--skip-generate : Skip prisma generate}
2826
+ {--skip-migrate : Skip prisma database sync}
2827
+ {--state-file= : Path to applied migration state file}
2828
+ {--schema= : Explicit prisma schema path}
2829
+ `;
2830
+ description = "Reset the database and rerun all migration classes";
2831
+ async handle() {
2832
+ this.app.command = this;
2833
+ const configuredMigrationsDir = this.app.getConfig("paths")?.migrations ?? join(process.cwd(), "database", "migrations");
2834
+ const migrationsDir = this.app.resolveRuntimeDirectoryPath(configuredMigrationsDir);
2835
+ if (!existsSync(migrationsDir)) return void this.error(`Error: Migrations directory not found: ${this.app.formatPathForLog(configuredMigrationsDir)}`);
2836
+ const adapter = this.app.getConfig("adapter");
2837
+ const useDatabaseMigrations = supportsDatabaseMigrationExecution(adapter);
2838
+ const schemaPath = this.option("schema") ? resolve(String(this.option("schema"))) : join(process.cwd(), "prisma", "schema.prisma");
2839
+ const stateFilePath = resolveMigrationStateFilePath(process.cwd(), this.option("state-file") ? String(this.option("state-file")) : void 0);
2840
+ const migrations = await this.loadAllMigrations(migrationsDir);
2841
+ if (migrations.length === 0) return void this.error("Error: No migration classes found to run.");
2842
+ if (supportsDatabaseReset(adapter)) await adapter.resetDatabase();
2843
+ else {
2844
+ if (!existsSync(schemaPath)) return void this.error(`Error: Prisma schema file not found: ${this.app.formatPathForLog(schemaPath)}`);
2845
+ writeFileSync(schemaPath, stripPrismaSchemaModelsAndEnums(readFileSync(schemaPath, "utf-8")));
2846
+ }
2847
+ let appliedState = createEmptyAppliedMigrationsState();
2848
+ await writeAppliedMigrationsStateToStore(adapter, stateFilePath, appliedState);
2849
+ for (const [MigrationClassItem] of migrations) {
2850
+ if (useDatabaseMigrations) {
2851
+ await applyMigrationToDatabase(adapter, MigrationClassItem);
2852
+ continue;
2853
+ }
2854
+ await applyMigrationToPrismaSchema(MigrationClassItem, {
2855
+ schemaPath,
2856
+ write: true
2857
+ });
2858
+ }
2859
+ for (const [migrationClass, file] of migrations) appliedState = markMigrationApplied(appliedState, {
2860
+ id: buildMigrationIdentity(file, migrationClass.name),
2861
+ file,
2862
+ className: migrationClass.name,
2863
+ appliedAt: (/* @__PURE__ */ new Date()).toISOString(),
2864
+ checksum: computeMigrationChecksum(file)
2865
+ });
2866
+ appliedState = markMigrationRun(appliedState, {
2867
+ id: buildMigrationRunId(),
2868
+ appliedAt: (/* @__PURE__ */ new Date()).toISOString(),
2869
+ migrationIds: appliedState.migrations.map((migration) => migration.id)
2870
+ });
2871
+ await writeAppliedMigrationsStateToStore(adapter, stateFilePath, appliedState);
2872
+ if (!useDatabaseMigrations) {
2873
+ const schemaArgs = this.option("schema") ? ["--schema", schemaPath] : [];
2874
+ if (!this.option("skip-generate")) runPrismaCommand(["generate", ...schemaArgs], process.cwd());
2875
+ if (!this.option("skip-migrate")) runPrismaCommand([
2876
+ "db",
2877
+ "push",
2878
+ "--force-reset",
2879
+ ...schemaArgs
2880
+ ], process.cwd());
2881
+ }
2882
+ this.success(`Refreshed database with ${migrations.length} migration(s).`);
2883
+ migrations.forEach(([_, file]) => this.success(this.app.splitLogger("Migrated", file)));
2884
+ }
2885
+ async loadAllMigrations(migrationsDir) {
2886
+ const files = readdirSync(migrationsDir).filter((file) => /\.(ts|js|mjs|cjs)$/i.test(file)).sort((left, right) => left.localeCompare(right)).map((file) => this.app.resolveRuntimeScriptPath(join(migrationsDir, file)));
2887
+ return (await Promise.all(files.map(async (file) => (await this.loadMigrationClassesFromFile(file)).map((cls) => [cls, file])))).flat();
2888
+ }
2889
+ async loadMigrationClassesFromFile(filePath) {
2890
+ const imported = await RuntimeModuleLoader.load(filePath);
2891
+ return Object.values(imported).filter((value) => {
2892
+ if (typeof value !== "function") return false;
2893
+ const candidate = value;
2894
+ const prototype = candidate.prototype;
2895
+ return candidate[MIGRATION_BRAND] === true || typeof prototype?.up === "function" && typeof prototype?.down === "function";
2896
+ });
2897
+ }
2898
+ };
2899
+
2777
2900
  //#endregion
2778
2901
  //#region src/cli/commands/MigrateRollbackCommand.ts
2779
2902
  /**
@@ -2802,7 +2925,9 @@ var MigrateRollbackCommand = class extends Command {
2802
2925
  if (!existsSync(migrationsDir)) return void this.error(`Error: Migrations directory not found: ${this.app.formatPathForLog(configuredMigrationsDir)}`);
2803
2926
  const schemaPath = this.option("schema") ? resolve(String(this.option("schema"))) : join(process.cwd(), "prisma", "schema.prisma");
2804
2927
  const stateFilePath = resolveMigrationStateFilePath(process.cwd(), this.option("state-file") ? String(this.option("state-file")) : void 0);
2805
- let appliedState = readAppliedMigrationsState(stateFilePath);
2928
+ const adapter = this.app.getConfig("adapter");
2929
+ const useDatabaseMigrations = supportsDatabaseMigrationExecution(adapter);
2930
+ let appliedState = await readAppliedMigrationsStateFromStore(adapter, stateFilePath);
2806
2931
  const stepOption = this.option("step");
2807
2932
  const stepCount = stepOption == null ? void 0 : Number(stepOption);
2808
2933
  if (stepCount != null && (!Number.isFinite(stepCount) || stepCount <= 0 || !Number.isInteger(stepCount))) return void this.error("Error: --step must be a positive integer.");
@@ -2824,17 +2949,23 @@ var MigrateRollbackCommand = class extends Command {
2824
2949
  rollbackClasses.forEach(([_, file]) => this.success(this.app.splitLogger("WouldRollback", file)));
2825
2950
  return;
2826
2951
  }
2827
- for (const [MigrationClassItem] of rollbackClasses) await applyMigrationRollbackToPrismaSchema(MigrationClassItem, {
2828
- schemaPath,
2829
- write: true
2830
- });
2952
+ for (const [MigrationClassItem] of rollbackClasses) {
2953
+ if (useDatabaseMigrations) {
2954
+ await applyMigrationRollbackToDatabase(adapter, MigrationClassItem);
2955
+ continue;
2956
+ }
2957
+ await applyMigrationRollbackToPrismaSchema(MigrationClassItem, {
2958
+ schemaPath,
2959
+ write: true
2960
+ });
2961
+ }
2831
2962
  for (const [migrationClass, file] of rollbackClasses) {
2832
2963
  const identity = buildMigrationIdentity(file, migrationClass.name);
2833
2964
  appliedState = removeAppliedMigration(appliedState, identity);
2834
2965
  }
2835
- writeAppliedMigrationsState(stateFilePath, appliedState);
2836
- if (!this.option("skip-generate")) runPrismaCommand(["generate"], process.cwd());
2837
- if (!this.option("skip-migrate")) if (this.option("deploy")) runPrismaCommand(["migrate", "deploy"], process.cwd());
2966
+ await writeAppliedMigrationsStateToStore(adapter, stateFilePath, appliedState);
2967
+ if (!useDatabaseMigrations && !this.option("skip-generate")) runPrismaCommand(["generate"], process.cwd());
2968
+ if (!useDatabaseMigrations && !this.option("skip-migrate")) if (this.option("deploy")) runPrismaCommand(["migrate", "deploy"], process.cwd());
2838
2969
  else runPrismaCommand([
2839
2970
  "migrate",
2840
2971
  "dev",
@@ -2878,7 +3009,14 @@ var MigrationHistoryCommand = class extends Command {
2878
3009
  async handle() {
2879
3010
  this.app.command = this;
2880
3011
  const stateFilePath = resolveMigrationStateFilePath(process.cwd(), this.option("state-file") ? String(this.option("state-file")) : void 0);
3012
+ const adapter = this.app.getConfig("adapter");
3013
+ const usesDatabaseState = supportsDatabaseMigrationState(adapter);
2881
3014
  if (this.option("delete")) {
3015
+ if (usesDatabaseState) {
3016
+ await adapter.writeAppliedMigrationsState(createEmptyAppliedMigrationsState());
3017
+ this.success("Deleted tracked migration state from database.");
3018
+ return;
3019
+ }
2882
3020
  if (!existsSync(stateFilePath)) {
2883
3021
  this.success(`No migration state file found at ${this.app.formatPathForLog(stateFilePath)}`);
2884
3022
  return;
@@ -2888,22 +3026,19 @@ var MigrationHistoryCommand = class extends Command {
2888
3026
  return;
2889
3027
  }
2890
3028
  if (this.option("reset")) {
2891
- writeAppliedMigrationsState(stateFilePath, {
2892
- version: 1,
2893
- migrations: []
2894
- });
2895
- this.success(`Reset migration state: ${this.app.formatPathForLog(stateFilePath)}`);
3029
+ await writeAppliedMigrationsStateToStore(adapter, stateFilePath, createEmptyAppliedMigrationsState());
3030
+ this.success(usesDatabaseState ? "Reset migration state in database." : `Reset migration state: ${this.app.formatPathForLog(stateFilePath)}`);
2896
3031
  return;
2897
3032
  }
2898
- const state = readAppliedMigrationsState(stateFilePath);
3033
+ const state = await readAppliedMigrationsStateFromStore(adapter, stateFilePath);
2899
3034
  if (this.option("json")) {
2900
3035
  this.success(JSON.stringify({
2901
- path: stateFilePath,
3036
+ path: usesDatabaseState ? "database" : stateFilePath,
2902
3037
  ...state
2903
3038
  }, null, 2));
2904
3039
  return;
2905
3040
  }
2906
- this.success(this.app.splitLogger("State", stateFilePath));
3041
+ this.success(this.app.splitLogger("State", usesDatabaseState ? "database" : stateFilePath));
2907
3042
  this.success(this.app.splitLogger("Tracked", String(state.migrations.length)));
2908
3043
  if (state.migrations.length === 0) {
2909
3044
  this.success("No tracked migrations found.");
@@ -3096,6 +3231,7 @@ await Kernel.init(app, {
3096
3231
  ModelsSyncCommand,
3097
3232
  SeedCommand,
3098
3233
  MigrateCommand,
3234
+ MigrateFreshCommand,
3099
3235
  MigrateRollbackCommand,
3100
3236
  MigrationHistoryCommand
3101
3237
  ],