arkormx 2.0.0-next.1 → 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/README.md CHANGED
@@ -7,12 +7,12 @@
7
7
  [![Deploy Documentation](https://github.com/arkstack-hq/arkormx/actions/workflows/deploy-docs.yml/badge.svg)](https://github.com/arkstack-hq/arkormx/actions/workflows/deploy-docs.yml)
8
8
  [![codecov](https://codecov.io/gh/arkstack-hq/arkormx/graph/badge.svg?token=ls1VVoFkYh)](https://codecov.io/gh/arkstack-hq/arkormx)
9
9
 
10
- Arkormˣ is a framework-agnostic ORM designed to run anywhere Node.js runs. It brings a familiar model layer and fluent query builder on top of Prisma delegates, enabling clean, modern, and type-safe development.
10
+ Arkormˣ is a framework-agnostic ORM designed to run anywhere Node.js runs. It brings a familiar model layer and fluent query builder on top of adapter-backed execution, with Prisma compatibility kept during the current transition window.
11
11
 
12
12
  ## Features
13
13
 
14
- - Arkormˣ is built on top of Prisma, providing a familiar and powerful API for database interactions.
15
- - Delegate-backed query execution with practical ORM ergonomics.
14
+ - Adapter-backed query execution with practical ORM ergonomics.
15
+ - Adapter-first runtime setup with Kysely/Postgres support and a Prisma compatibility adapter during migration.
16
16
  - End-to-end guides for setup, querying, relationships, migrations, and CLI usage.
17
17
  - Full TypeScript support, providing strong typing and improved developer experience.
18
18
  - Follows best practices for security, ensuring your data is protected.
@@ -26,27 +26,49 @@ Arkormˣ is a framework-agnostic ORM designed to run anywhere Node.js runs. It b
26
26
  Stable release:
27
27
 
28
28
  ```sh
29
- pnpm add arkormx @prisma/client
30
- pnpm add -D prisma
29
+ pnpm add arkormx kysely pg
31
30
  ```
32
31
 
33
32
  Preview release (`next`):
34
33
 
35
34
  ```sh
36
- pnpm add arkormx@next @prisma/client
37
- pnpm add -D prisma
35
+ pnpm add arkormx@next kysely pg
38
36
  ```
39
37
 
40
38
  ### Configuration
41
39
 
40
+ Primary runtime path:
41
+
42
+ ```ts
43
+ import { createKyselyAdapter, defineConfig } from 'arkormx';
44
+ import { Kysely, PostgresDialect } from 'kysely';
45
+ import { Pool } from 'pg';
46
+
47
+ export default defineConfig({
48
+ adapter: createKyselyAdapter(
49
+ new Kysely<Record<string, never>>({
50
+ dialect: new PostgresDialect({
51
+ pool: new Pool({
52
+ connectionString: process.env.DATABASE_URL,
53
+ }),
54
+ }),
55
+ }),
56
+ ),
57
+ });
58
+ ```
59
+
60
+ Optional compatibility/runtime config for CLI and transaction helpers:
61
+
42
62
  Create `arkormx.config.js` in your project root:
43
63
 
44
64
  ```ts
45
65
  import { defineConfig } from 'arkormx';
46
66
  import { PrismaClient } from '@prisma/client';
47
67
 
68
+ const prisma = new PrismaClient();
69
+
48
70
  export default defineConfig({
49
- prisma: new PrismaClient(...),
71
+ prisma: () => prisma,
50
72
  });
51
73
  ```
52
74
 
@@ -57,15 +79,21 @@ Or run the Arkormˣ CLI command `npx arkorm init` to initialize your project alo
57
79
  ```ts
58
80
  import { Model } from 'arkormx';
59
81
 
60
- export class User extends Model<'users'> {
61
- protected static override delegate = 'users'; // not required if your model name matches the delegate name or the pluralized form of it
62
- }
82
+ type UserAttributes = {
83
+ id: number;
84
+ email: string;
85
+ name: string;
86
+ isActive: boolean;
87
+ };
88
+
89
+ export class User extends Model<UserAttributes> {}
63
90
  ```
64
91
 
65
- ### Generate Prisma client
92
+ ### Optional Prisma compatibility
66
93
 
67
94
  ```sh
68
- pnpm prisma generate
95
+ pnpm add @prisma/client
96
+ pnpm add -D prisma
69
97
  ```
70
98
 
71
99
  ### Run queries
@@ -98,6 +126,7 @@ await User.transaction(async () => {
98
126
 
99
127
  - [Setup](https://arkormx.toneflix.net/guide/setup)
100
128
  - [Configuration](https://arkormx.dev/guide/configuration)
129
+ - [Prisma Compatibility](https://arkormx.dev/guide/prisma-compatibility)
101
130
  - [Typing](https://arkormx.dev/guide/typing)
102
131
  - [Models](https://arkormx.dev/guide/models)
103
132
  - [Query Builder](https://arkormx.dev/guide/query-builder)
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
@@ -1458,6 +1480,7 @@ const userConfig = {
1458
1480
  let runtimeConfigLoaded = false;
1459
1481
  let runtimeConfigLoadingPromise;
1460
1482
  let runtimeClientResolver;
1483
+ let runtimeAdapter;
1461
1484
  let runtimePaginationURLDriverFactory;
1462
1485
  let runtimePaginationCurrentPageResolver;
1463
1486
  const transactionClientStorage = new AsyncLocalStorage();
@@ -1474,6 +1497,12 @@ const mergePathConfig = (paths) => {
1474
1497
  ...incoming
1475
1498
  };
1476
1499
  };
1500
+ const bindAdapterToModels = (adapter, models) => {
1501
+ models.forEach((model) => {
1502
+ model.setAdapter(adapter);
1503
+ });
1504
+ return adapter;
1505
+ };
1477
1506
  /**
1478
1507
  * Get the user-provided ArkORM configuration.
1479
1508
  *
@@ -1493,15 +1522,35 @@ const getUserConfig = (key) => {
1493
1522
  const configureArkormRuntime = (prisma, options = {}) => {
1494
1523
  const nextConfig = {
1495
1524
  ...userConfig,
1496
- prisma,
1497
1525
  paths: mergePathConfig(options.paths)
1498
1526
  };
1527
+ nextConfig.prisma = prisma;
1499
1528
  if (options.pagination !== void 0) nextConfig.pagination = options.pagination;
1529
+ if (options.adapter !== void 0) nextConfig.adapter = options.adapter;
1530
+ if (options.boot !== void 0) nextConfig.boot = options.boot;
1500
1531
  if (options.outputExt !== void 0) nextConfig.outputExt = options.outputExt;
1501
1532
  Object.assign(userConfig, { ...nextConfig });
1502
1533
  runtimeClientResolver = prisma;
1534
+ runtimeAdapter = options.adapter;
1503
1535
  runtimePaginationURLDriverFactory = nextConfig.pagination?.urlDriver;
1504
1536
  runtimePaginationCurrentPageResolver = nextConfig.pagination?.resolveCurrentPage;
1537
+ options.boot?.({
1538
+ prisma: resolveClient(prisma),
1539
+ bindAdapter: bindAdapterToModels
1540
+ });
1541
+ };
1542
+ /**
1543
+ * Resolve a Prisma client instance from the provided resolver, which can be either
1544
+ * a direct client instance or a function that returns a client instance.
1545
+ *
1546
+ * @param resolver
1547
+ * @returns
1548
+ */
1549
+ const resolveClient = (resolver) => {
1550
+ if (!resolver) return void 0;
1551
+ const client = typeof resolver === "function" ? resolver() : resolver;
1552
+ if (!client || typeof client !== "object") return void 0;
1553
+ return client;
1505
1554
  };
1506
1555
  /**
1507
1556
  * Resolve and apply the ArkORM configuration from an imported module.
@@ -1513,8 +1562,10 @@ const configureArkormRuntime = (prisma, options = {}) => {
1513
1562
  */
1514
1563
  const resolveAndApplyConfig = (imported) => {
1515
1564
  const config = imported?.default ?? imported;
1516
- if (!config || typeof config !== "object" || !config.prisma) return;
1565
+ if (!config || typeof config !== "object") return;
1517
1566
  configureArkormRuntime(config.prisma, {
1567
+ adapter: config.adapter,
1568
+ boot: config.boot,
1518
1569
  pagination: config.pagination,
1519
1570
  paths: config.paths,
1520
1571
  outputExt: config.outputExt
@@ -2072,6 +2123,40 @@ var CliApp = class {
2072
2123
  lines.splice(insertionIndex, 0, `import type { ${enumTypes.join(", ")} } from '@prisma/client'`);
2073
2124
  return lines.join("\n");
2074
2125
  }
2126
+ parseModelSyncSource(modelSource) {
2127
+ const classMatch = modelSource.match(/export\s+class\s+(\w+)\s+extends\s+Model(?:<[^\n]+>)?\s*\{/);
2128
+ if (!classMatch) return null;
2129
+ const className = classMatch[1];
2130
+ const tableMatch = modelSource.match(/protected\s+static\s+override\s+table\s*=\s*['"]([^'"]+)['"]/) ?? modelSource.match(/static\s+table\s*=\s*['"]([^'"]+)['"]/);
2131
+ const delegateMatch = modelSource.match(/protected\s+static\s+override\s+delegate\s*=\s*['"]([^'"]+)['"]/) ?? modelSource.match(/static\s+delegate\s*=\s*['"]([^'"]+)['"]/);
2132
+ return {
2133
+ className,
2134
+ table: tableMatch?.[1] ?? delegateMatch?.[1] ?? str(className).camel().plural().toString()
2135
+ };
2136
+ }
2137
+ syncModelFiles(modelFiles, resolveStructure, enums) {
2138
+ const updated = [];
2139
+ const skipped = [];
2140
+ modelFiles.forEach((filePath) => {
2141
+ const source = readFileSync$1(filePath, "utf-8");
2142
+ const structure = resolveStructure(filePath, source);
2143
+ if (!structure || structure.fields.length === 0) {
2144
+ skipped.push(filePath);
2145
+ return;
2146
+ }
2147
+ const synced = this.syncModelDeclarations(source, structure.fields, enums);
2148
+ if (!synced.updated) {
2149
+ skipped.push(filePath);
2150
+ return;
2151
+ }
2152
+ writeFileSync$1(filePath, synced.content);
2153
+ updated.push(filePath);
2154
+ });
2155
+ return {
2156
+ updated,
2157
+ skipped
2158
+ };
2159
+ }
2075
2160
  /**
2076
2161
  * Parse Prisma enum definitions from a schema and return their member names.
2077
2162
  *
@@ -2164,7 +2249,7 @@ var CliApp = class {
2164
2249
  */
2165
2250
  syncModelDeclarations(modelSource, declarations, enums) {
2166
2251
  const lines = modelSource.split("\n");
2167
- const classIndex = lines.findIndex((line) => /export\s+class\s+\w+\s+extends\s+Model<.+>\s*\{/.test(line));
2252
+ const classIndex = lines.findIndex((line) => /export\s+class\s+\w+\s+extends\s+Model(?:<[^\n]+>)?\s*\{/.test(line));
2168
2253
  if (classIndex < 0) return {
2169
2254
  content: modelSource,
2170
2255
  updated: false
@@ -2218,6 +2303,36 @@ var CliApp = class {
2218
2303
  updated: contentWithImports !== modelSource
2219
2304
  };
2220
2305
  }
2306
+ async syncModels(options = {}) {
2307
+ const modelsDir = options.modelsDir ?? this.resolveConfigPath("models", join$1(process.cwd(), "src", "models"));
2308
+ if (!existsSync$1(modelsDir)) throw new Error(`Models directory not found: ${modelsDir}`);
2309
+ const modelFiles = readdirSync$1(modelsDir).filter((file) => file.endsWith(".ts")).map((file) => join$1(modelsDir, file));
2310
+ const adapter = this.getConfig("adapter");
2311
+ if (adapter && typeof adapter.introspectModels === "function") {
2312
+ const sources = modelFiles.reduce((all, filePath) => {
2313
+ const parsed = this.parseModelSyncSource(readFileSync$1(filePath, "utf-8"));
2314
+ if (parsed) all.set(filePath, parsed);
2315
+ return all;
2316
+ }, /* @__PURE__ */ new Map());
2317
+ const discovered = await adapter.introspectModels({ tables: [...new Set([...sources.values()].map((source) => source.table))] });
2318
+ const structuresByTable = new Map(discovered.map((model) => [model.table, model]));
2319
+ const result = this.syncModelFiles(modelFiles, (filePath) => {
2320
+ const parsed = sources.get(filePath);
2321
+ return parsed ? structuresByTable.get(parsed.table) : void 0;
2322
+ }, /* @__PURE__ */ new Map());
2323
+ return {
2324
+ source: "adapter",
2325
+ modelsDir,
2326
+ total: modelFiles.length,
2327
+ updated: result.updated,
2328
+ skipped: result.skipped
2329
+ };
2330
+ }
2331
+ return {
2332
+ source: "prisma",
2333
+ ...this.syncModelsFromPrisma(options)
2334
+ };
2335
+ }
2221
2336
  /**
2222
2337
  * Sync model attribute declarations in model files based on the Prisma schema.
2223
2338
  * This method reads the Prisma schema to extract model definitions and their
@@ -2237,38 +2352,18 @@ var CliApp = class {
2237
2352
  const schema = readFileSync$1(schemaPath, "utf-8");
2238
2353
  const prismaEnums = this.parsePrismaEnums(schema);
2239
2354
  const prismaModels = this.parsePrismaModels(schema);
2240
- const modelFiles = readdirSync$1(modelsDir).filter((file) => file.endsWith(".ts"));
2241
- const updated = [];
2242
- const skipped = [];
2243
- modelFiles.forEach((file) => {
2244
- const filePath = join$1(modelsDir, file);
2245
- const source = readFileSync$1(filePath, "utf-8");
2246
- const classMatch = source.match(/export\s+class\s+(\w+)\s+extends\s+Model<'([^']+)'>/);
2247
- if (!classMatch) {
2248
- skipped.push(filePath);
2249
- return;
2250
- }
2251
- const className = classMatch[1];
2252
- const delegate = classMatch[2];
2253
- const prismaModel = prismaModels.find((model) => model.table === delegate) ?? prismaModels.find((model) => model.name === className);
2254
- if (!prismaModel || prismaModel.fields.length === 0) {
2255
- skipped.push(filePath);
2256
- return;
2257
- }
2258
- const synced = this.syncModelDeclarations(source, prismaModel.fields, prismaEnums);
2259
- if (!synced.updated) {
2260
- skipped.push(filePath);
2261
- return;
2262
- }
2263
- writeFileSync$1(filePath, synced.content);
2264
- updated.push(filePath);
2265
- });
2355
+ const modelFiles = readdirSync$1(modelsDir).filter((file) => file.endsWith(".ts")).map((file) => join$1(modelsDir, file));
2356
+ const result = this.syncModelFiles(modelFiles, (filePath, source) => {
2357
+ const parsed = this.parseModelSyncSource(source);
2358
+ if (!parsed) return void 0;
2359
+ return prismaModels.find((model) => model.table === parsed.table) ?? prismaModels.find((model) => model.name === parsed.className);
2360
+ }, prismaEnums);
2266
2361
  return {
2267
2362
  schemaPath,
2268
2363
  modelsDir,
2269
2364
  total: modelFiles.length,
2270
- updated,
2271
- skipped
2365
+ updated: result.updated,
2366
+ skipped: result.skipped
2272
2367
  };
2273
2368
  }
2274
2369
  };
@@ -2436,10 +2531,13 @@ var MakeSeederCommand = class extends Command {
2436
2531
 
2437
2532
  //#endregion
2438
2533
  //#region src/helpers/migration-history.ts
2439
- const DEFAULT_STATE = {
2534
+ const createEmptyAppliedMigrationsState = () => ({
2440
2535
  version: 1,
2441
2536
  migrations: [],
2442
2537
  runs: []
2538
+ });
2539
+ const supportsDatabaseMigrationState = (adapter) => {
2540
+ return typeof adapter?.readAppliedMigrationsState === "function" && typeof adapter?.writeAppliedMigrationsState === "function";
2443
2541
  };
2444
2542
  const resolveMigrationStateFilePath = (cwd, configuredPath) => {
2445
2543
  if (configuredPath && configuredPath.trim().length > 0) return resolve(configuredPath);
@@ -2454,10 +2552,10 @@ const computeMigrationChecksum = (filePath) => {
2454
2552
  return createHash("sha256").update(source).digest("hex");
2455
2553
  };
2456
2554
  const readAppliedMigrationsState = (stateFilePath) => {
2457
- if (!existsSync(stateFilePath)) return { ...DEFAULT_STATE };
2555
+ if (!existsSync(stateFilePath)) return createEmptyAppliedMigrationsState();
2458
2556
  try {
2459
2557
  const parsed = JSON.parse(readFileSync(stateFilePath, "utf-8"));
2460
- if (!Array.isArray(parsed.migrations)) return { ...DEFAULT_STATE };
2558
+ if (!Array.isArray(parsed.migrations)) return createEmptyAppliedMigrationsState();
2461
2559
  return {
2462
2560
  version: 1,
2463
2561
  migrations: parsed.migrations.filter((migration) => {
@@ -2468,14 +2566,25 @@ const readAppliedMigrationsState = (stateFilePath) => {
2468
2566
  }) : []
2469
2567
  };
2470
2568
  } catch {
2471
- return { ...DEFAULT_STATE };
2569
+ return createEmptyAppliedMigrationsState();
2472
2570
  }
2473
2571
  };
2572
+ const readAppliedMigrationsStateFromStore = async (adapter, stateFilePath) => {
2573
+ if (supportsDatabaseMigrationState(adapter)) return await adapter.readAppliedMigrationsState();
2574
+ return readAppliedMigrationsState(stateFilePath);
2575
+ };
2474
2576
  const writeAppliedMigrationsState = (stateFilePath, state) => {
2475
2577
  const directory = dirname(stateFilePath);
2476
2578
  if (!existsSync(directory)) mkdirSync(directory, { recursive: true });
2477
2579
  writeFileSync(stateFilePath, JSON.stringify(state, null, 2));
2478
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
+ };
2479
2588
  const isMigrationApplied = (state, identity, checksum) => {
2480
2589
  const matched = state.migrations.find((migration) => migration.id === identity);
2481
2590
  if (!matched) return false;
@@ -2594,7 +2703,9 @@ var MigrateCommand = class extends Command {
2594
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);
2595
2704
  if (classes.length === 0) return void this.error("Error: No migration classes found to run.");
2596
2705
  const stateFilePath = resolveMigrationStateFilePath(process.cwd(), this.option("state-file") ? String(this.option("state-file")) : void 0);
2597
- 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);
2598
2709
  const skipped = [];
2599
2710
  const changed = [];
2600
2711
  const pending = classes.filter(([migrationClass, file]) => {
@@ -2616,10 +2727,16 @@ var MigrateCommand = class extends Command {
2616
2727
  this.success("No pending migration classes to apply.");
2617
2728
  return;
2618
2729
  }
2619
- for (const [MigrationClassItem] of pending) await applyMigrationToPrismaSchema(MigrationClassItem, {
2620
- schemaPath,
2621
- write: true
2622
- });
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
+ }
2623
2740
  if (appliedState) {
2624
2741
  const runAppliedIds = [];
2625
2742
  for (const [migrationClass, file] of pending) {
@@ -2638,10 +2755,10 @@ var MigrateCommand = class extends Command {
2638
2755
  appliedAt: (/* @__PURE__ */ new Date()).toISOString(),
2639
2756
  migrationIds: runAppliedIds
2640
2757
  });
2641
- writeAppliedMigrationsState(stateFilePath, appliedState);
2758
+ await writeAppliedMigrationsStateToStore(adapter, stateFilePath, appliedState);
2642
2759
  }
2643
- if (!this.option("skip-generate")) runPrismaCommand(["generate"], process.cwd());
2644
- 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());
2645
2762
  else runPrismaCommand([
2646
2763
  "migrate",
2647
2764
  "dev",
@@ -2701,6 +2818,85 @@ var MigrateCommand = class extends Command {
2701
2818
  }
2702
2819
  };
2703
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
+
2704
2900
  //#endregion
2705
2901
  //#region src/cli/commands/MigrateRollbackCommand.ts
2706
2902
  /**
@@ -2729,7 +2925,9 @@ var MigrateRollbackCommand = class extends Command {
2729
2925
  if (!existsSync(migrationsDir)) return void this.error(`Error: Migrations directory not found: ${this.app.formatPathForLog(configuredMigrationsDir)}`);
2730
2926
  const schemaPath = this.option("schema") ? resolve(String(this.option("schema"))) : join(process.cwd(), "prisma", "schema.prisma");
2731
2927
  const stateFilePath = resolveMigrationStateFilePath(process.cwd(), this.option("state-file") ? String(this.option("state-file")) : void 0);
2732
- let appliedState = readAppliedMigrationsState(stateFilePath);
2928
+ const adapter = this.app.getConfig("adapter");
2929
+ const useDatabaseMigrations = supportsDatabaseMigrationExecution(adapter);
2930
+ let appliedState = await readAppliedMigrationsStateFromStore(adapter, stateFilePath);
2733
2931
  const stepOption = this.option("step");
2734
2932
  const stepCount = stepOption == null ? void 0 : Number(stepOption);
2735
2933
  if (stepCount != null && (!Number.isFinite(stepCount) || stepCount <= 0 || !Number.isInteger(stepCount))) return void this.error("Error: --step must be a positive integer.");
@@ -2751,17 +2949,23 @@ var MigrateRollbackCommand = class extends Command {
2751
2949
  rollbackClasses.forEach(([_, file]) => this.success(this.app.splitLogger("WouldRollback", file)));
2752
2950
  return;
2753
2951
  }
2754
- for (const [MigrationClassItem] of rollbackClasses) await applyMigrationRollbackToPrismaSchema(MigrationClassItem, {
2755
- schemaPath,
2756
- write: true
2757
- });
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
+ }
2758
2962
  for (const [migrationClass, file] of rollbackClasses) {
2759
2963
  const identity = buildMigrationIdentity(file, migrationClass.name);
2760
2964
  appliedState = removeAppliedMigration(appliedState, identity);
2761
2965
  }
2762
- writeAppliedMigrationsState(stateFilePath, appliedState);
2763
- if (!this.option("skip-generate")) runPrismaCommand(["generate"], process.cwd());
2764
- 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());
2765
2969
  else runPrismaCommand([
2766
2970
  "migrate",
2767
2971
  "dev",
@@ -2805,7 +3009,14 @@ var MigrationHistoryCommand = class extends Command {
2805
3009
  async handle() {
2806
3010
  this.app.command = this;
2807
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);
2808
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
+ }
2809
3020
  if (!existsSync(stateFilePath)) {
2810
3021
  this.success(`No migration state file found at ${this.app.formatPathForLog(stateFilePath)}`);
2811
3022
  return;
@@ -2815,22 +3026,19 @@ var MigrationHistoryCommand = class extends Command {
2815
3026
  return;
2816
3027
  }
2817
3028
  if (this.option("reset")) {
2818
- writeAppliedMigrationsState(stateFilePath, {
2819
- version: 1,
2820
- migrations: []
2821
- });
2822
- 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)}`);
2823
3031
  return;
2824
3032
  }
2825
- const state = readAppliedMigrationsState(stateFilePath);
3033
+ const state = await readAppliedMigrationsStateFromStore(adapter, stateFilePath);
2826
3034
  if (this.option("json")) {
2827
3035
  this.success(JSON.stringify({
2828
- path: stateFilePath,
3036
+ path: usesDatabaseState ? "database" : stateFilePath,
2829
3037
  ...state
2830
3038
  }, null, 2));
2831
3039
  return;
2832
3040
  }
2833
- this.success(this.app.splitLogger("State", stateFilePath));
3041
+ this.success(this.app.splitLogger("State", usesDatabaseState ? "database" : stateFilePath));
2834
3042
  this.success(this.app.splitLogger("Tracked", String(state.migrations.length)));
2835
3043
  if (state.migrations.length === 0) {
2836
3044
  this.success("No tracked migrations found.");
@@ -2846,20 +3054,21 @@ var MigrationHistoryCommand = class extends Command {
2846
3054
  //#region src/cli/commands/ModelsSyncCommand.ts
2847
3055
  var ModelsSyncCommand = class extends Command {
2848
3056
  signature = `models:sync
2849
- {--schema= : Path to prisma schema file}
3057
+ {--schema= : Path to prisma schema file used when adapter introspection is unavailable}
2850
3058
  {--models= : Path to models directory}
2851
3059
  `;
2852
- description = "Sync model declare attributes from prisma schema for all model files";
3060
+ description = "Sync model declare attributes from the active adapter when supported, otherwise fall back to the Prisma schema";
2853
3061
  async handle() {
2854
3062
  this.app.command = this;
2855
- const result = this.app.syncModelsFromPrisma({
3063
+ const result = await this.app.syncModels({
2856
3064
  schemaPath: this.option("schema") ? resolve(String(this.option("schema"))) : void 0,
2857
3065
  modelsDir: this.option("models") ? resolve(String(this.option("models"))) : void 0
2858
3066
  });
2859
3067
  const updatedLines = result.updated.length === 0 ? [this.app.splitLogger("Updated", "none")] : result.updated.map((path) => this.app.splitLogger("Updated", path));
2860
3068
  this.success("SUCCESS: Model sync completed with the following results:");
2861
3069
  [
2862
- this.app.splitLogger("Schema", result.schemaPath),
3070
+ this.app.splitLogger("Source", result.source === "adapter" ? "adapter introspection" : "prisma schema"),
3071
+ ...result.schemaPath ? [this.app.splitLogger("Schema", result.schemaPath)] : [],
2863
3072
  this.app.splitLogger("Models", result.modelsDir),
2864
3073
  this.app.splitLogger("Processed", String(result.total)),
2865
3074
  ...updatedLines,
@@ -3022,6 +3231,7 @@ await Kernel.init(app, {
3022
3231
  ModelsSyncCommand,
3023
3232
  SeedCommand,
3024
3233
  MigrateCommand,
3234
+ MigrateFreshCommand,
3025
3235
  MigrateRollbackCommand,
3026
3236
  MigrationHistoryCommand
3027
3237
  ],