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 +42 -13
- package/dist/cli.mjs +273 -63
- package/dist/index.cjs +1707 -102
- package/dist/index.d.cts +539 -115
- package/dist/index.d.mts +539 -115
- package/dist/index.mjs +1694 -102
- package/package.json +3 -1
package/README.md
CHANGED
|
@@ -7,12 +7,12 @@
|
|
|
7
7
|
[](https://github.com/arkstack-hq/arkormx/actions/workflows/deploy-docs.yml)
|
|
8
8
|
[](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
|
|
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
|
-
-
|
|
15
|
-
-
|
|
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
|
|
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
|
|
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:
|
|
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
|
-
|
|
61
|
-
|
|
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
|
-
###
|
|
92
|
+
### Optional Prisma compatibility
|
|
66
93
|
|
|
67
94
|
```sh
|
|
68
|
-
pnpm prisma
|
|
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"
|
|
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
|
|
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
|
|
2242
|
-
|
|
2243
|
-
|
|
2244
|
-
|
|
2245
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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 =
|
|
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)
|
|
2620
|
-
|
|
2621
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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)
|
|
2755
|
-
|
|
2756
|
-
|
|
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
|
-
|
|
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
|
-
|
|
2819
|
-
|
|
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 =
|
|
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
|
|
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.
|
|
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("
|
|
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
|
],
|