arkormx 2.0.0-next.1 → 2.0.0-next.2
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 +109 -35
- package/dist/index.cjs +1308 -72
- package/dist/index.d.cts +392 -20
- package/dist/index.d.mts +392 -20
- package/dist/index.mjs +1307 -73
- 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
|
@@ -1458,6 +1458,7 @@ const userConfig = {
|
|
|
1458
1458
|
let runtimeConfigLoaded = false;
|
|
1459
1459
|
let runtimeConfigLoadingPromise;
|
|
1460
1460
|
let runtimeClientResolver;
|
|
1461
|
+
let runtimeAdapter;
|
|
1461
1462
|
let runtimePaginationURLDriverFactory;
|
|
1462
1463
|
let runtimePaginationCurrentPageResolver;
|
|
1463
1464
|
const transactionClientStorage = new AsyncLocalStorage();
|
|
@@ -1474,6 +1475,12 @@ const mergePathConfig = (paths) => {
|
|
|
1474
1475
|
...incoming
|
|
1475
1476
|
};
|
|
1476
1477
|
};
|
|
1478
|
+
const bindAdapterToModels = (adapter, models) => {
|
|
1479
|
+
models.forEach((model) => {
|
|
1480
|
+
model.setAdapter(adapter);
|
|
1481
|
+
});
|
|
1482
|
+
return adapter;
|
|
1483
|
+
};
|
|
1477
1484
|
/**
|
|
1478
1485
|
* Get the user-provided ArkORM configuration.
|
|
1479
1486
|
*
|
|
@@ -1493,15 +1500,35 @@ const getUserConfig = (key) => {
|
|
|
1493
1500
|
const configureArkormRuntime = (prisma, options = {}) => {
|
|
1494
1501
|
const nextConfig = {
|
|
1495
1502
|
...userConfig,
|
|
1496
|
-
prisma,
|
|
1497
1503
|
paths: mergePathConfig(options.paths)
|
|
1498
1504
|
};
|
|
1505
|
+
nextConfig.prisma = prisma;
|
|
1499
1506
|
if (options.pagination !== void 0) nextConfig.pagination = options.pagination;
|
|
1507
|
+
if (options.adapter !== void 0) nextConfig.adapter = options.adapter;
|
|
1508
|
+
if (options.boot !== void 0) nextConfig.boot = options.boot;
|
|
1500
1509
|
if (options.outputExt !== void 0) nextConfig.outputExt = options.outputExt;
|
|
1501
1510
|
Object.assign(userConfig, { ...nextConfig });
|
|
1502
1511
|
runtimeClientResolver = prisma;
|
|
1512
|
+
runtimeAdapter = options.adapter;
|
|
1503
1513
|
runtimePaginationURLDriverFactory = nextConfig.pagination?.urlDriver;
|
|
1504
1514
|
runtimePaginationCurrentPageResolver = nextConfig.pagination?.resolveCurrentPage;
|
|
1515
|
+
options.boot?.({
|
|
1516
|
+
prisma: resolveClient(prisma),
|
|
1517
|
+
bindAdapter: bindAdapterToModels
|
|
1518
|
+
});
|
|
1519
|
+
};
|
|
1520
|
+
/**
|
|
1521
|
+
* Resolve a Prisma client instance from the provided resolver, which can be either
|
|
1522
|
+
* a direct client instance or a function that returns a client instance.
|
|
1523
|
+
*
|
|
1524
|
+
* @param resolver
|
|
1525
|
+
* @returns
|
|
1526
|
+
*/
|
|
1527
|
+
const resolveClient = (resolver) => {
|
|
1528
|
+
if (!resolver) return void 0;
|
|
1529
|
+
const client = typeof resolver === "function" ? resolver() : resolver;
|
|
1530
|
+
if (!client || typeof client !== "object") return void 0;
|
|
1531
|
+
return client;
|
|
1505
1532
|
};
|
|
1506
1533
|
/**
|
|
1507
1534
|
* Resolve and apply the ArkORM configuration from an imported module.
|
|
@@ -1513,8 +1540,10 @@ const configureArkormRuntime = (prisma, options = {}) => {
|
|
|
1513
1540
|
*/
|
|
1514
1541
|
const resolveAndApplyConfig = (imported) => {
|
|
1515
1542
|
const config = imported?.default ?? imported;
|
|
1516
|
-
if (!config || typeof config !== "object"
|
|
1543
|
+
if (!config || typeof config !== "object") return;
|
|
1517
1544
|
configureArkormRuntime(config.prisma, {
|
|
1545
|
+
adapter: config.adapter,
|
|
1546
|
+
boot: config.boot,
|
|
1518
1547
|
pagination: config.pagination,
|
|
1519
1548
|
paths: config.paths,
|
|
1520
1549
|
outputExt: config.outputExt
|
|
@@ -2072,6 +2101,40 @@ var CliApp = class {
|
|
|
2072
2101
|
lines.splice(insertionIndex, 0, `import type { ${enumTypes.join(", ")} } from '@prisma/client'`);
|
|
2073
2102
|
return lines.join("\n");
|
|
2074
2103
|
}
|
|
2104
|
+
parseModelSyncSource(modelSource) {
|
|
2105
|
+
const classMatch = modelSource.match(/export\s+class\s+(\w+)\s+extends\s+Model(?:<[^\n]+>)?\s*\{/);
|
|
2106
|
+
if (!classMatch) return null;
|
|
2107
|
+
const className = classMatch[1];
|
|
2108
|
+
const tableMatch = modelSource.match(/protected\s+static\s+override\s+table\s*=\s*['"]([^'"]+)['"]/) ?? modelSource.match(/static\s+table\s*=\s*['"]([^'"]+)['"]/);
|
|
2109
|
+
const delegateMatch = modelSource.match(/protected\s+static\s+override\s+delegate\s*=\s*['"]([^'"]+)['"]/) ?? modelSource.match(/static\s+delegate\s*=\s*['"]([^'"]+)['"]/);
|
|
2110
|
+
return {
|
|
2111
|
+
className,
|
|
2112
|
+
table: tableMatch?.[1] ?? delegateMatch?.[1] ?? str(className).camel().plural().toString()
|
|
2113
|
+
};
|
|
2114
|
+
}
|
|
2115
|
+
syncModelFiles(modelFiles, resolveStructure, enums) {
|
|
2116
|
+
const updated = [];
|
|
2117
|
+
const skipped = [];
|
|
2118
|
+
modelFiles.forEach((filePath) => {
|
|
2119
|
+
const source = readFileSync$1(filePath, "utf-8");
|
|
2120
|
+
const structure = resolveStructure(filePath, source);
|
|
2121
|
+
if (!structure || structure.fields.length === 0) {
|
|
2122
|
+
skipped.push(filePath);
|
|
2123
|
+
return;
|
|
2124
|
+
}
|
|
2125
|
+
const synced = this.syncModelDeclarations(source, structure.fields, enums);
|
|
2126
|
+
if (!synced.updated) {
|
|
2127
|
+
skipped.push(filePath);
|
|
2128
|
+
return;
|
|
2129
|
+
}
|
|
2130
|
+
writeFileSync$1(filePath, synced.content);
|
|
2131
|
+
updated.push(filePath);
|
|
2132
|
+
});
|
|
2133
|
+
return {
|
|
2134
|
+
updated,
|
|
2135
|
+
skipped
|
|
2136
|
+
};
|
|
2137
|
+
}
|
|
2075
2138
|
/**
|
|
2076
2139
|
* Parse Prisma enum definitions from a schema and return their member names.
|
|
2077
2140
|
*
|
|
@@ -2164,7 +2227,7 @@ var CliApp = class {
|
|
|
2164
2227
|
*/
|
|
2165
2228
|
syncModelDeclarations(modelSource, declarations, enums) {
|
|
2166
2229
|
const lines = modelSource.split("\n");
|
|
2167
|
-
const classIndex = lines.findIndex((line) => /export\s+class\s+\w+\s+extends\s+Model
|
|
2230
|
+
const classIndex = lines.findIndex((line) => /export\s+class\s+\w+\s+extends\s+Model(?:<[^\n]+>)?\s*\{/.test(line));
|
|
2168
2231
|
if (classIndex < 0) return {
|
|
2169
2232
|
content: modelSource,
|
|
2170
2233
|
updated: false
|
|
@@ -2218,6 +2281,36 @@ var CliApp = class {
|
|
|
2218
2281
|
updated: contentWithImports !== modelSource
|
|
2219
2282
|
};
|
|
2220
2283
|
}
|
|
2284
|
+
async syncModels(options = {}) {
|
|
2285
|
+
const modelsDir = options.modelsDir ?? this.resolveConfigPath("models", join$1(process.cwd(), "src", "models"));
|
|
2286
|
+
if (!existsSync$1(modelsDir)) throw new Error(`Models directory not found: ${modelsDir}`);
|
|
2287
|
+
const modelFiles = readdirSync$1(modelsDir).filter((file) => file.endsWith(".ts")).map((file) => join$1(modelsDir, file));
|
|
2288
|
+
const adapter = this.getConfig("adapter");
|
|
2289
|
+
if (adapter && typeof adapter.introspectModels === "function") {
|
|
2290
|
+
const sources = modelFiles.reduce((all, filePath) => {
|
|
2291
|
+
const parsed = this.parseModelSyncSource(readFileSync$1(filePath, "utf-8"));
|
|
2292
|
+
if (parsed) all.set(filePath, parsed);
|
|
2293
|
+
return all;
|
|
2294
|
+
}, /* @__PURE__ */ new Map());
|
|
2295
|
+
const discovered = await adapter.introspectModels({ tables: [...new Set([...sources.values()].map((source) => source.table))] });
|
|
2296
|
+
const structuresByTable = new Map(discovered.map((model) => [model.table, model]));
|
|
2297
|
+
const result = this.syncModelFiles(modelFiles, (filePath) => {
|
|
2298
|
+
const parsed = sources.get(filePath);
|
|
2299
|
+
return parsed ? structuresByTable.get(parsed.table) : void 0;
|
|
2300
|
+
}, /* @__PURE__ */ new Map());
|
|
2301
|
+
return {
|
|
2302
|
+
source: "adapter",
|
|
2303
|
+
modelsDir,
|
|
2304
|
+
total: modelFiles.length,
|
|
2305
|
+
updated: result.updated,
|
|
2306
|
+
skipped: result.skipped
|
|
2307
|
+
};
|
|
2308
|
+
}
|
|
2309
|
+
return {
|
|
2310
|
+
source: "prisma",
|
|
2311
|
+
...this.syncModelsFromPrisma(options)
|
|
2312
|
+
};
|
|
2313
|
+
}
|
|
2221
2314
|
/**
|
|
2222
2315
|
* Sync model attribute declarations in model files based on the Prisma schema.
|
|
2223
2316
|
* This method reads the Prisma schema to extract model definitions and their
|
|
@@ -2237,38 +2330,18 @@ var CliApp = class {
|
|
|
2237
2330
|
const schema = readFileSync$1(schemaPath, "utf-8");
|
|
2238
2331
|
const prismaEnums = this.parsePrismaEnums(schema);
|
|
2239
2332
|
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
|
-
});
|
|
2333
|
+
const modelFiles = readdirSync$1(modelsDir).filter((file) => file.endsWith(".ts")).map((file) => join$1(modelsDir, file));
|
|
2334
|
+
const result = this.syncModelFiles(modelFiles, (filePath, source) => {
|
|
2335
|
+
const parsed = this.parseModelSyncSource(source);
|
|
2336
|
+
if (!parsed) return void 0;
|
|
2337
|
+
return prismaModels.find((model) => model.table === parsed.table) ?? prismaModels.find((model) => model.name === parsed.className);
|
|
2338
|
+
}, prismaEnums);
|
|
2266
2339
|
return {
|
|
2267
2340
|
schemaPath,
|
|
2268
2341
|
modelsDir,
|
|
2269
2342
|
total: modelFiles.length,
|
|
2270
|
-
updated,
|
|
2271
|
-
skipped
|
|
2343
|
+
updated: result.updated,
|
|
2344
|
+
skipped: result.skipped
|
|
2272
2345
|
};
|
|
2273
2346
|
}
|
|
2274
2347
|
};
|
|
@@ -2846,20 +2919,21 @@ var MigrationHistoryCommand = class extends Command {
|
|
|
2846
2919
|
//#region src/cli/commands/ModelsSyncCommand.ts
|
|
2847
2920
|
var ModelsSyncCommand = class extends Command {
|
|
2848
2921
|
signature = `models:sync
|
|
2849
|
-
{--schema= : Path to prisma schema file}
|
|
2922
|
+
{--schema= : Path to prisma schema file used when adapter introspection is unavailable}
|
|
2850
2923
|
{--models= : Path to models directory}
|
|
2851
2924
|
`;
|
|
2852
|
-
description = "Sync model declare attributes from
|
|
2925
|
+
description = "Sync model declare attributes from the active adapter when supported, otherwise fall back to the Prisma schema";
|
|
2853
2926
|
async handle() {
|
|
2854
2927
|
this.app.command = this;
|
|
2855
|
-
const result = this.app.
|
|
2928
|
+
const result = await this.app.syncModels({
|
|
2856
2929
|
schemaPath: this.option("schema") ? resolve(String(this.option("schema"))) : void 0,
|
|
2857
2930
|
modelsDir: this.option("models") ? resolve(String(this.option("models"))) : void 0
|
|
2858
2931
|
});
|
|
2859
2932
|
const updatedLines = result.updated.length === 0 ? [this.app.splitLogger("Updated", "none")] : result.updated.map((path) => this.app.splitLogger("Updated", path));
|
|
2860
2933
|
this.success("SUCCESS: Model sync completed with the following results:");
|
|
2861
2934
|
[
|
|
2862
|
-
this.app.splitLogger("
|
|
2935
|
+
this.app.splitLogger("Source", result.source === "adapter" ? "adapter introspection" : "prisma schema"),
|
|
2936
|
+
...result.schemaPath ? [this.app.splitLogger("Schema", result.schemaPath)] : [],
|
|
2863
2937
|
this.app.splitLogger("Models", result.modelsDir),
|
|
2864
2938
|
this.app.splitLogger("Processed", String(result.total)),
|
|
2865
2939
|
...updatedLines,
|