@xylex-group/athena 1.6.0 → 1.6.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 +31 -6
- package/bin/athena-js.js +74 -6
- package/dist/cli/index.cjs +194 -30
- package/dist/cli/index.cjs.map +1 -1
- package/dist/cli/index.d.cts +21 -2
- package/dist/cli/index.d.ts +21 -2
- package/dist/cli/index.js +193 -31
- package/dist/cli/index.js.map +1 -1
- package/dist/errors-1b-LSTum.d.ts +31 -0
- package/dist/errors-BYRK5phv.d.cts +31 -0
- package/dist/index.cjs +104 -16
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +6 -338
- package/dist/index.d.ts +6 -338
- package/dist/index.js +104 -16
- package/dist/index.js.map +1 -1
- package/dist/pipeline-BQzpSLD3.d.ts +338 -0
- package/dist/pipeline-CA2aexDl.d.cts +338 -0
- package/dist/react.d.cts +3 -2
- package/dist/react.d.ts +3 -2
- package/dist/{errors-DQerAaXC.d.cts → types-v6UyGg-x.d.cts} +1 -29
- package/dist/{errors-DQerAaXC.d.ts → types-v6UyGg-x.d.ts} +1 -29
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# athena-js
|
|
2
2
|
|
|
3
|
-
current version: `1.6.
|
|
3
|
+
current version: `1.6.2`
|
|
4
4
|
`@xylex-group/athena` is a database driver and API gateway SDK that lets you interact with SQL backends over HTTP through a fluent builder API. It ships a typed query builder for Node.js / server environments plus Athena-native React hooks for client-side use.
|
|
5
5
|
|
|
6
6
|
## Install
|
|
@@ -83,22 +83,45 @@ await typed
|
|
|
83
83
|
|
|
84
84
|
For full details, see [`docs/typed-schema-registry.md`](./docs/typed-schema-registry.md).
|
|
85
85
|
|
|
86
|
-
### Typed schema generator
|
|
86
|
+
### Typed schema generator
|
|
87
87
|
|
|
88
|
-
|
|
88
|
+
Schema generation is additive. Existing `createClient(...).from<T>(...)` usage remains valid while teams migrate to generated registry files.
|
|
89
|
+
|
|
90
|
+
CLI:
|
|
89
91
|
|
|
90
92
|
```bash
|
|
91
93
|
athena-js generate
|
|
92
94
|
athena-js generate --dry-run
|
|
93
95
|
athena-js generate --config ./athena.config.ts
|
|
96
|
+
athena-js generate --help
|
|
97
|
+
athena-js help generate
|
|
94
98
|
```
|
|
95
99
|
|
|
96
|
-
Generator
|
|
97
|
-
|
|
100
|
+
Generator supports:
|
|
101
|
+
|
|
102
|
+
- PostgreSQL direct introspection (`provider.mode = "direct"`, `provider.connectionString` from your `PG_URL`/`DATABASE_URL`)
|
|
103
|
+
- PostgreSQL gateway-only introspection (`provider.mode = "gateway"` via Athena `POST /gateway/query`)
|
|
104
|
+
- Placeholder-driven output paths
|
|
105
|
+
- Feature flags (`features.emitRegistry`, `features.emitRelations`)
|
|
98
106
|
|
|
99
|
-
For full generator configuration, see [`docs/generator-config.md`](./docs/generator-config.md).
|
|
107
|
+
For full generator configuration and troubleshooting, see [`docs/generator-config.md`](./docs/generator-config.md).
|
|
108
|
+
For full CLI commands, help behavior, and troubleshooting, see [`docs/cli-command-reference.md`](./docs/cli-command-reference.md).
|
|
109
|
+
For CI/CD pipelines and generated-file branch policy, see [`docs/generator-cicd.md`](./docs/generator-cicd.md).
|
|
100
110
|
For prompt-ready documentation handoff text, see [`docs/generator-codex-handoff-prompt-pack.md`](./docs/generator-codex-handoff-prompt-pack.md).
|
|
101
111
|
|
|
112
|
+
### Athena JS and Athena RS
|
|
113
|
+
|
|
114
|
+
`athena-js` is designed to be standalone for TypeScript/Node and React-native workflows:
|
|
115
|
+
|
|
116
|
+
- query builder + hooks
|
|
117
|
+
- typed registry and generator pipeline
|
|
118
|
+
- CLI-driven codegen in JS/TS projects
|
|
119
|
+
|
|
120
|
+
`athena-rs` remains the faster fit for Rust service execution paths. Teams can run both in parallel:
|
|
121
|
+
|
|
122
|
+
- `athena-rs` for Rust backend throughput
|
|
123
|
+
- `athena-js` for app/tooling layers that need TypeScript contracts and frontend-facing ergonomics
|
|
124
|
+
|
|
102
125
|
Every query resolves to `{ data, error, errorDetails?, status, count?, raw }`. `data` is `null` on error; `error` is `null` on success.
|
|
103
126
|
|
|
104
127
|
For richer handling, inspect `errorDetails` (`code`, `status`, `endpoint`, `method`, `requestId`, etc.) or use `AthenaGatewayError` / `isAthenaGatewayError` from the package exports.
|
|
@@ -666,6 +689,8 @@ CI and publish workflows run `typecheck` before build/publish.
|
|
|
666
689
|
|
|
667
690
|
- [Documentation index](docs/index.md) — complete documentation map
|
|
668
691
|
- [Getting started](docs/getting-started.md) — step-by-step walkthrough
|
|
692
|
+
- [CLI command reference](docs/cli-command-reference.md) — all `athena-js` commands, help flows, and troubleshooting
|
|
669
693
|
- [Typed schema registry](docs/typed-schema-registry.md) — typed contracts and migration path
|
|
670
694
|
- [Generator config](docs/generator-config.md) — generator provider and output pipeline
|
|
695
|
+
- [Generator CI/CD](docs/generator-cicd.md) — pipeline patterns, secret mapping, retries, and branch policy
|
|
671
696
|
- [API reference](docs/api-reference.md) — complete method and type reference
|
package/bin/athena-js.js
CHANGED
|
@@ -1,8 +1,76 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
import
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
3
|
+
import { existsSync, readFileSync } from 'node:fs'
|
|
4
|
+
import path from 'node:path'
|
|
5
|
+
import { fileURLToPath, pathToFileURL } from 'node:url'
|
|
6
|
+
|
|
7
|
+
const binPath = fileURLToPath(import.meta.url)
|
|
8
|
+
const packageRoot = path.resolve(path.dirname(binPath), '..')
|
|
9
|
+
const cliEntrypointPath = path.resolve(packageRoot, 'dist', 'cli', 'index.js')
|
|
10
|
+
const packageJsonPath = path.resolve(packageRoot, 'package.json')
|
|
11
|
+
|
|
12
|
+
function getInstalledVersion() {
|
|
13
|
+
try {
|
|
14
|
+
const packageJsonRaw = readFileSync(packageJsonPath, 'utf8')
|
|
15
|
+
const packageJson = JSON.parse(packageJsonRaw)
|
|
16
|
+
return typeof packageJson.version === 'string' ? packageJson.version : 'unknown'
|
|
17
|
+
} catch {
|
|
18
|
+
return 'unknown'
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function printMissingEntrypointError() {
|
|
23
|
+
const installedVersion = getInstalledVersion()
|
|
24
|
+
console.error(
|
|
25
|
+
[
|
|
26
|
+
'Failed to start athena-js CLI: package install is missing the generated CLI entrypoint.',
|
|
27
|
+
`Expected file: ${cliEntrypointPath}`,
|
|
28
|
+
`Installed package version: ${installedVersion}`,
|
|
29
|
+
'',
|
|
30
|
+
'Fix by reinstalling the latest package:',
|
|
31
|
+
' pnpm add -g @xylex-group/athena@latest',
|
|
32
|
+
].join('\n'),
|
|
33
|
+
)
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function formatRuntimeError(error) {
|
|
37
|
+
if (error instanceof Error) {
|
|
38
|
+
if (process.env.ATHENA_JS_DEBUG === '1') {
|
|
39
|
+
return error.stack ?? error.message;
|
|
40
|
+
}
|
|
41
|
+
return error.message;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (typeof error === 'string') {
|
|
45
|
+
return error;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return 'Unknown error.';
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
async function main() {
|
|
52
|
+
if (!existsSync(cliEntrypointPath)) {
|
|
53
|
+
printMissingEntrypointError();
|
|
54
|
+
process.exit(1);
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
try {
|
|
59
|
+
const cliEntrypointUrl = pathToFileURL(cliEntrypointPath).href;
|
|
60
|
+
const cliModule = await import(cliEntrypointUrl);
|
|
61
|
+
if (typeof cliModule.runCLI !== 'function') {
|
|
62
|
+
throw new Error('CLI module does not export runCLI.');
|
|
63
|
+
}
|
|
64
|
+
await cliModule.runCLI(process.argv.slice(2));
|
|
65
|
+
} catch (err) {
|
|
66
|
+
const errorDetail = formatRuntimeError(err);
|
|
67
|
+
if (errorDetail.includes('\n')) {
|
|
68
|
+
console.error(`Failed to start athena-js CLI:\n${errorDetail}`);
|
|
69
|
+
} else {
|
|
70
|
+
console.error(`Failed to start athena-js CLI: ${errorDetail}`);
|
|
71
|
+
}
|
|
72
|
+
process.exit(1);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
void main()
|
package/dist/cli/index.cjs
CHANGED
|
@@ -300,10 +300,10 @@ var DEFAULT_CONFIG_CANDIDATES = [
|
|
|
300
300
|
".athena.config.js"
|
|
301
301
|
];
|
|
302
302
|
var DEFAULT_TARGETS = {
|
|
303
|
-
model: "
|
|
304
|
-
schema: "
|
|
305
|
-
database: "
|
|
306
|
-
registry: "
|
|
303
|
+
model: "athena/models/{model_kebab}.ts",
|
|
304
|
+
schema: "athena/schema.ts",
|
|
305
|
+
database: "athena/relations.ts",
|
|
306
|
+
registry: "athena/config.ts"
|
|
307
307
|
};
|
|
308
308
|
var DEFAULT_NAMING = {
|
|
309
309
|
modelType: "pascal",
|
|
@@ -331,6 +331,13 @@ function normalizeOutputConfig(output) {
|
|
|
331
331
|
}
|
|
332
332
|
};
|
|
333
333
|
}
|
|
334
|
+
function isAthenaGeneratorConfig(value) {
|
|
335
|
+
if (!value || typeof value !== "object") {
|
|
336
|
+
return false;
|
|
337
|
+
}
|
|
338
|
+
const record = value;
|
|
339
|
+
return Boolean(record.provider && typeof record.provider === "object") && Boolean(record.output && typeof record.output === "object");
|
|
340
|
+
}
|
|
334
341
|
function normalizeGeneratorConfig(input) {
|
|
335
342
|
return {
|
|
336
343
|
provider: input.provider,
|
|
@@ -359,18 +366,41 @@ function findGeneratorConfigPath(cwd = process.cwd()) {
|
|
|
359
366
|
return void 0;
|
|
360
367
|
}
|
|
361
368
|
function extractConfigExport(module) {
|
|
362
|
-
|
|
363
|
-
|
|
369
|
+
const visited = /* @__PURE__ */ new Set();
|
|
370
|
+
const queue = [module];
|
|
371
|
+
while (queue.length > 0) {
|
|
372
|
+
const current = queue.shift();
|
|
373
|
+
if (!current || typeof current !== "object" || visited.has(current)) {
|
|
374
|
+
continue;
|
|
375
|
+
}
|
|
376
|
+
visited.add(current);
|
|
377
|
+
const record = current;
|
|
378
|
+
if (isAthenaGeneratorConfig(record)) {
|
|
379
|
+
return record;
|
|
380
|
+
}
|
|
364
381
|
const defaultExport = record.default;
|
|
365
382
|
if (defaultExport && typeof defaultExport === "object") {
|
|
366
|
-
|
|
383
|
+
queue.push(defaultExport);
|
|
367
384
|
}
|
|
368
385
|
const namedConfigExport = record.config;
|
|
369
386
|
if (namedConfigExport && typeof namedConfigExport === "object") {
|
|
370
|
-
|
|
387
|
+
queue.push(namedConfigExport);
|
|
388
|
+
}
|
|
389
|
+
const moduleExports = record["module.exports"];
|
|
390
|
+
if (moduleExports && typeof moduleExports === "object") {
|
|
391
|
+
queue.push(moduleExports);
|
|
371
392
|
}
|
|
372
393
|
}
|
|
373
|
-
throw new Error(
|
|
394
|
+
throw new Error(
|
|
395
|
+
"Generator config file must export a config object as default export or `config`."
|
|
396
|
+
);
|
|
397
|
+
}
|
|
398
|
+
function importConfigModule(moduleSpecifier) {
|
|
399
|
+
const runtimeImport = new Function(
|
|
400
|
+
"moduleSpecifier",
|
|
401
|
+
"return import(moduleSpecifier)"
|
|
402
|
+
);
|
|
403
|
+
return runtimeImport(moduleSpecifier);
|
|
374
404
|
}
|
|
375
405
|
async function loadGeneratorConfig(options = {}) {
|
|
376
406
|
const cwd = options.cwd ?? process.cwd();
|
|
@@ -381,7 +411,7 @@ async function loadGeneratorConfig(options = {}) {
|
|
|
381
411
|
);
|
|
382
412
|
}
|
|
383
413
|
const moduleUrl = url.pathToFileURL(resolvedPath);
|
|
384
|
-
const module = await
|
|
414
|
+
const module = await importConfigModule(`${moduleUrl.href}?cacheBust=${Date.now()}`);
|
|
385
415
|
const rawConfig = extractConfigExport(module);
|
|
386
416
|
return {
|
|
387
417
|
configPath: resolvedPath,
|
|
@@ -2029,6 +2059,58 @@ function toTypeKind(code) {
|
|
|
2029
2059
|
function escapeSqlLiteral(value) {
|
|
2030
2060
|
return value.replace(/'/g, "''");
|
|
2031
2061
|
}
|
|
2062
|
+
function parsePostgresArrayLiteral(text) {
|
|
2063
|
+
const body = text.slice(1, -1).trim();
|
|
2064
|
+
if (!body) return [];
|
|
2065
|
+
const values = [];
|
|
2066
|
+
let current = "";
|
|
2067
|
+
let inQuotes = false;
|
|
2068
|
+
let escaped = false;
|
|
2069
|
+
for (const char of body) {
|
|
2070
|
+
if (escaped) {
|
|
2071
|
+
current += char;
|
|
2072
|
+
escaped = false;
|
|
2073
|
+
continue;
|
|
2074
|
+
}
|
|
2075
|
+
if (char === "\\") {
|
|
2076
|
+
escaped = true;
|
|
2077
|
+
continue;
|
|
2078
|
+
}
|
|
2079
|
+
if (char === '"') {
|
|
2080
|
+
inQuotes = !inQuotes;
|
|
2081
|
+
continue;
|
|
2082
|
+
}
|
|
2083
|
+
if (char === "," && !inQuotes) {
|
|
2084
|
+
values.push(current);
|
|
2085
|
+
current = "";
|
|
2086
|
+
continue;
|
|
2087
|
+
}
|
|
2088
|
+
current += char;
|
|
2089
|
+
}
|
|
2090
|
+
values.push(current);
|
|
2091
|
+
return values.map((value) => value.trim()).filter((value) => value.length > 0);
|
|
2092
|
+
}
|
|
2093
|
+
function coerceStringArray(value) {
|
|
2094
|
+
if (Array.isArray(value)) {
|
|
2095
|
+
return value.map((item) => typeof item === "string" ? item : String(item)).map((item) => item.trim()).filter((item) => item.length > 0);
|
|
2096
|
+
}
|
|
2097
|
+
if (typeof value === "string") {
|
|
2098
|
+
const trimmed = value.trim();
|
|
2099
|
+
if (!trimmed) return [];
|
|
2100
|
+
if (trimmed.startsWith("[") && trimmed.endsWith("]")) {
|
|
2101
|
+
try {
|
|
2102
|
+
const parsed = JSON.parse(trimmed);
|
|
2103
|
+
return coerceStringArray(parsed);
|
|
2104
|
+
} catch {
|
|
2105
|
+
}
|
|
2106
|
+
}
|
|
2107
|
+
if (trimmed.startsWith("{") && trimmed.endsWith("}")) {
|
|
2108
|
+
return parsePostgresArrayLiteral(trimmed);
|
|
2109
|
+
}
|
|
2110
|
+
return trimmed.split(",").map((item) => item.trim()).filter((item) => item.length > 0);
|
|
2111
|
+
}
|
|
2112
|
+
return [];
|
|
2113
|
+
}
|
|
2032
2114
|
function buildSchemaArrayLiteral(schemas) {
|
|
2033
2115
|
const normalized = schemas.length > 0 ? schemas : ["public"];
|
|
2034
2116
|
const literals = normalized.map((schema) => `'${escapeSqlLiteral(schema)}'`).join(", ");
|
|
@@ -2068,8 +2150,10 @@ var PostgresCatalogSnapshotAssembler = class {
|
|
|
2068
2150
|
addPrimaryKeyRows(primaryKeyRows) {
|
|
2069
2151
|
for (const row of primaryKeyRows) {
|
|
2070
2152
|
const table = this.ensureTable(row.schema_name, row.table_name);
|
|
2071
|
-
|
|
2072
|
-
|
|
2153
|
+
const primaryKeyColumns = coerceStringArray(row.columns);
|
|
2154
|
+
row.columns = primaryKeyColumns;
|
|
2155
|
+
table.primaryKey = primaryKeyColumns;
|
|
2156
|
+
for (const columnName of primaryKeyColumns) {
|
|
2073
2157
|
const column = table.columns[columnName];
|
|
2074
2158
|
if (column) {
|
|
2075
2159
|
column.isPrimaryKey = true;
|
|
@@ -2081,23 +2165,27 @@ var PostgresCatalogSnapshotAssembler = class {
|
|
|
2081
2165
|
for (const row of foreignKeyRows) {
|
|
2082
2166
|
const sourceTable = this.ensureTable(row.source_schema, row.source_table);
|
|
2083
2167
|
const targetTable = this.ensureTable(row.target_schema, row.target_table);
|
|
2168
|
+
const sourceColumns = coerceStringArray(row.source_columns);
|
|
2169
|
+
const targetColumns = coerceStringArray(row.target_columns);
|
|
2170
|
+
row.source_columns = sourceColumns;
|
|
2171
|
+
row.target_columns = targetColumns;
|
|
2084
2172
|
const sourceRelationKind = row.source_is_unique ? "one-to-one" : "many-to-one";
|
|
2085
2173
|
this.upsertRelation(sourceTable, relationKey(row.constraint_name, row.target_table), {
|
|
2086
2174
|
name: row.constraint_name,
|
|
2087
2175
|
kind: sourceRelationKind,
|
|
2088
|
-
sourceColumns
|
|
2176
|
+
sourceColumns,
|
|
2089
2177
|
targetSchema: row.target_schema,
|
|
2090
2178
|
targetModel: row.target_table,
|
|
2091
|
-
targetColumns
|
|
2179
|
+
targetColumns
|
|
2092
2180
|
});
|
|
2093
2181
|
const targetRelationKind = row.source_is_unique ? "one-to-one" : "one-to-many";
|
|
2094
2182
|
this.upsertRelation(targetTable, relationKey(row.source_table), {
|
|
2095
2183
|
name: relationKey(row.source_table, row.constraint_name),
|
|
2096
2184
|
kind: targetRelationKind,
|
|
2097
|
-
sourceColumns:
|
|
2185
|
+
sourceColumns: targetColumns,
|
|
2098
2186
|
targetSchema: row.source_schema,
|
|
2099
2187
|
targetModel: row.source_table,
|
|
2100
|
-
targetColumns:
|
|
2188
|
+
targetColumns: sourceColumns
|
|
2101
2189
|
});
|
|
2102
2190
|
}
|
|
2103
2191
|
}
|
|
@@ -2399,7 +2487,7 @@ async function runSchemaGenerator(options = {}) {
|
|
|
2399
2487
|
}
|
|
2400
2488
|
|
|
2401
2489
|
// src/cli/index.ts
|
|
2402
|
-
function
|
|
2490
|
+
function rootUsage() {
|
|
2403
2491
|
return [
|
|
2404
2492
|
"athena-js CLI",
|
|
2405
2493
|
"",
|
|
@@ -2408,12 +2496,42 @@ function usage() {
|
|
|
2408
2496
|
"",
|
|
2409
2497
|
"Examples:",
|
|
2410
2498
|
" athena-js generate",
|
|
2499
|
+
" athena-js generate --config ./athena.config.ts --dry-run",
|
|
2500
|
+
" athena-js generate --help"
|
|
2501
|
+
].join("\n");
|
|
2502
|
+
}
|
|
2503
|
+
function generateUsage() {
|
|
2504
|
+
return [
|
|
2505
|
+
"athena-js generate",
|
|
2506
|
+
"",
|
|
2507
|
+
"Usage:",
|
|
2508
|
+
" athena-js generate [--config <path>] [--dry-run]",
|
|
2509
|
+
"",
|
|
2510
|
+
"Options:",
|
|
2511
|
+
" --config <path> Explicit path to athena.config.ts or athena-js.config.ts",
|
|
2512
|
+
" --dry-run Build generated files in memory without writing them to disk",
|
|
2513
|
+
" -h, --help Show help for generate",
|
|
2514
|
+
"",
|
|
2515
|
+
"Examples:",
|
|
2516
|
+
" athena-js generate",
|
|
2411
2517
|
" athena-js generate --config ./athena.config.ts --dry-run"
|
|
2412
2518
|
].join("\n");
|
|
2413
2519
|
}
|
|
2520
|
+
function usage(topic = "root") {
|
|
2521
|
+
return topic === "generate" ? generateUsage() : rootUsage();
|
|
2522
|
+
}
|
|
2414
2523
|
function parseCommand(argv) {
|
|
2415
|
-
if (argv.length === 0 || argv[0] === "
|
|
2416
|
-
return { command: "help" };
|
|
2524
|
+
if (argv.length === 0 || argv[0] === "--help" || argv[0] === "-h") {
|
|
2525
|
+
return { command: "help", topic: "root" };
|
|
2526
|
+
}
|
|
2527
|
+
if (argv[0] === "help") {
|
|
2528
|
+
if (argv.length === 1) {
|
|
2529
|
+
return { command: "help", topic: "root" };
|
|
2530
|
+
}
|
|
2531
|
+
if (argv[1] === "generate") {
|
|
2532
|
+
return { command: "help", topic: "generate" };
|
|
2533
|
+
}
|
|
2534
|
+
throw new Error(`Unknown command "${argv[1]}".`);
|
|
2417
2535
|
}
|
|
2418
2536
|
const [command, ...rest] = argv;
|
|
2419
2537
|
if (command !== "generate") {
|
|
@@ -2423,13 +2541,16 @@ function parseCommand(argv) {
|
|
|
2423
2541
|
let dryRun = false;
|
|
2424
2542
|
for (let index = 0; index < rest.length; index += 1) {
|
|
2425
2543
|
const token = rest[index];
|
|
2544
|
+
if (token === "--help" || token === "-h") {
|
|
2545
|
+
return { command: "help", topic: "generate" };
|
|
2546
|
+
}
|
|
2426
2547
|
if (token === "--dry-run") {
|
|
2427
2548
|
dryRun = true;
|
|
2428
2549
|
continue;
|
|
2429
2550
|
}
|
|
2430
2551
|
if (token === "--config") {
|
|
2431
2552
|
const nextValue = rest[index + 1];
|
|
2432
|
-
if (!nextValue) {
|
|
2553
|
+
if (!nextValue || nextValue.startsWith("-")) {
|
|
2433
2554
|
throw new Error("Missing value for --config option.");
|
|
2434
2555
|
}
|
|
2435
2556
|
configPath = nextValue;
|
|
@@ -2444,29 +2565,72 @@ function parseCommand(argv) {
|
|
|
2444
2565
|
dryRun
|
|
2445
2566
|
};
|
|
2446
2567
|
}
|
|
2447
|
-
|
|
2568
|
+
function normalizeErrorMessage(error) {
|
|
2569
|
+
if (error instanceof Error) {
|
|
2570
|
+
return error.message;
|
|
2571
|
+
}
|
|
2572
|
+
if (typeof error === "string") {
|
|
2573
|
+
return error;
|
|
2574
|
+
}
|
|
2575
|
+
return "Unknown generator error.";
|
|
2576
|
+
}
|
|
2577
|
+
function extractMissingDatabaseName(message) {
|
|
2578
|
+
const match = message.match(/database "([^"]+)" does not exist/i);
|
|
2579
|
+
return match?.[1];
|
|
2580
|
+
}
|
|
2581
|
+
function isErrorWithCode(error) {
|
|
2582
|
+
return typeof error === "object" && error !== null && "code" in error;
|
|
2583
|
+
}
|
|
2584
|
+
function formatGeneratorError(error, configPath) {
|
|
2585
|
+
if (isErrorWithCode(error) && error.code === "3D000") {
|
|
2586
|
+
const message = normalizeErrorMessage(error);
|
|
2587
|
+
const databaseName = extractMissingDatabaseName(message);
|
|
2588
|
+
const databaseLabel = databaseName ? `PostgreSQL database "${databaseName}" does not exist` : "The target PostgreSQL database does not exist";
|
|
2589
|
+
const configLabel = configPath ? `config "${configPath}"` : "the resolved athena config";
|
|
2590
|
+
return new Error(
|
|
2591
|
+
[
|
|
2592
|
+
`${databaseLabel} (code 3D000).`,
|
|
2593
|
+
`Update provider.connectionString (and provider.database, if set) in ${configLabel}, or create that database before running generate.`
|
|
2594
|
+
].join("\n")
|
|
2595
|
+
);
|
|
2596
|
+
}
|
|
2597
|
+
if (error instanceof Error) {
|
|
2598
|
+
return error;
|
|
2599
|
+
}
|
|
2600
|
+
return new Error(normalizeErrorMessage(error));
|
|
2601
|
+
}
|
|
2602
|
+
async function runCLI(argv, runtime = {}) {
|
|
2603
|
+
const log = runtime.log ?? console.log;
|
|
2604
|
+
const runGenerator = runtime.runGenerator ?? runSchemaGenerator;
|
|
2448
2605
|
const parsed = parseCommand(argv);
|
|
2449
2606
|
if (parsed.command === "help") {
|
|
2450
|
-
|
|
2607
|
+
log(usage(parsed.topic));
|
|
2451
2608
|
return;
|
|
2452
2609
|
}
|
|
2453
|
-
|
|
2454
|
-
|
|
2455
|
-
|
|
2456
|
-
|
|
2610
|
+
let result;
|
|
2611
|
+
try {
|
|
2612
|
+
result = await runGenerator({
|
|
2613
|
+
configPath: parsed.configPath,
|
|
2614
|
+
dryRun: parsed.dryRun
|
|
2615
|
+
});
|
|
2616
|
+
} catch (error) {
|
|
2617
|
+
throw formatGeneratorError(error, parsed.configPath);
|
|
2618
|
+
}
|
|
2457
2619
|
if (parsed.dryRun) {
|
|
2458
|
-
|
|
2620
|
+
log(`[dry-run] Generated ${result.files.length} files from ${result.configPath}`);
|
|
2459
2621
|
for (const file of result.files) {
|
|
2460
|
-
|
|
2622
|
+
log(` - ${file.path}`);
|
|
2461
2623
|
}
|
|
2462
2624
|
return;
|
|
2463
2625
|
}
|
|
2464
|
-
|
|
2626
|
+
log(`Generated ${result.writtenFiles.length} files from ${result.configPath}`);
|
|
2465
2627
|
for (const filePath of result.writtenFiles) {
|
|
2466
|
-
|
|
2628
|
+
log(` - ${filePath}`);
|
|
2467
2629
|
}
|
|
2468
2630
|
}
|
|
2469
2631
|
|
|
2632
|
+
exports.parseCommand = parseCommand;
|
|
2470
2633
|
exports.runCLI = runCLI;
|
|
2634
|
+
exports.usage = usage;
|
|
2471
2635
|
//# sourceMappingURL=index.cjs.map
|
|
2472
2636
|
//# sourceMappingURL=index.cjs.map
|