@xylex-group/athena 1.6.1 → 1.7.0

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.
@@ -1,6 +1,25 @@
1
+ import { F as runSchemaGenerator } from '../pipeline-BsKuBqmE.cjs';
2
+ import '../types-wPA1Z4vQ.cjs';
3
+
4
+ interface GenerateCommand {
5
+ command: 'generate';
6
+ configPath?: string;
7
+ dryRun: boolean;
8
+ }
9
+ interface HelpCommand {
10
+ command: 'help';
11
+ topic: 'root' | 'generate';
12
+ }
13
+ type CliCommand = GenerateCommand | HelpCommand;
14
+ interface CliRuntime {
15
+ runGenerator?: typeof runSchemaGenerator;
16
+ log?: (message: string) => void;
17
+ }
18
+ declare function usage(topic?: HelpCommand['topic']): string;
19
+ declare function parseCommand(argv: string[]): CliCommand;
1
20
  /**
2
21
  * CLI entrypoint used by `bin/athena-js.js`.
3
22
  */
4
- declare function runCLI(argv: string[]): Promise<void>;
23
+ declare function runCLI(argv: string[], runtime?: CliRuntime): Promise<void>;
5
24
 
6
- export { runCLI };
25
+ export { type CliRuntime, parseCommand, runCLI, usage };
@@ -1,6 +1,25 @@
1
+ import { F as runSchemaGenerator } from '../pipeline-xQSjGbqF.js';
2
+ import '../types-wPA1Z4vQ.js';
3
+
4
+ interface GenerateCommand {
5
+ command: 'generate';
6
+ configPath?: string;
7
+ dryRun: boolean;
8
+ }
9
+ interface HelpCommand {
10
+ command: 'help';
11
+ topic: 'root' | 'generate';
12
+ }
13
+ type CliCommand = GenerateCommand | HelpCommand;
14
+ interface CliRuntime {
15
+ runGenerator?: typeof runSchemaGenerator;
16
+ log?: (message: string) => void;
17
+ }
18
+ declare function usage(topic?: HelpCommand['topic']): string;
19
+ declare function parseCommand(argv: string[]): CliCommand;
1
20
  /**
2
21
  * CLI entrypoint used by `bin/athena-js.js`.
3
22
  */
4
- declare function runCLI(argv: string[]): Promise<void>;
23
+ declare function runCLI(argv: string[], runtime?: CliRuntime): Promise<void>;
5
24
 
6
- export { runCLI };
25
+ export { type CliRuntime, parseCommand, runCLI, usage };
package/dist/cli/index.js CHANGED
@@ -289,6 +289,37 @@ function resolvePostgresColumnType(column) {
289
289
  }
290
290
  return wrapArrayType(baseType, column.arrayDimensions);
291
291
  }
292
+
293
+ // src/generator/schema-selection.ts
294
+ var DEFAULT_POSTGRES_SCHEMAS = ["public"];
295
+ function collectSchemaNames(input) {
296
+ if (!input) {
297
+ return [];
298
+ }
299
+ const values = typeof input === "string" ? [input] : input;
300
+ return values.flatMap((value) => String(value).split(","));
301
+ }
302
+ function normalizeSchemaSelection(input) {
303
+ const schemas = [];
304
+ const seen = /* @__PURE__ */ new Set();
305
+ for (const value of collectSchemaNames(input)) {
306
+ const schema = value.trim();
307
+ if (!schema || seen.has(schema)) {
308
+ continue;
309
+ }
310
+ seen.add(schema);
311
+ schemas.push(schema);
312
+ }
313
+ return schemas.length > 0 ? schemas : [...DEFAULT_POSTGRES_SCHEMAS];
314
+ }
315
+ function resolveProviderSchemas(providerConfig) {
316
+ if (providerConfig.kind === "postgres") {
317
+ return normalizeSchemaSelection(providerConfig.schemas);
318
+ }
319
+ return [...DEFAULT_POSTGRES_SCHEMAS];
320
+ }
321
+
322
+ // src/generator/config.ts
292
323
  var DEFAULT_CONFIG_CANDIDATES = [
293
324
  "athena.config.ts",
294
325
  "athena.config.js",
@@ -298,10 +329,10 @@ var DEFAULT_CONFIG_CANDIDATES = [
298
329
  ".athena.config.js"
299
330
  ];
300
331
  var DEFAULT_TARGETS = {
301
- model: "src/generated/{database_kebab}/{schema_kebab}/{model_kebab}.model.ts",
302
- schema: "src/generated/{database_kebab}/{schema_kebab}/index.ts",
303
- database: "src/generated/{database_kebab}/index.ts",
304
- registry: "src/generated/index.ts"
332
+ model: "athena/models/{schema_kebab}/{model_kebab}.ts",
333
+ schema: "athena/schemas/{schema_kebab}.ts",
334
+ database: "athena/relations.ts",
335
+ registry: "athena/config.ts"
305
336
  };
306
337
  var DEFAULT_NAMING = {
307
338
  modelType: "pascal",
@@ -329,6 +360,15 @@ function normalizeOutputConfig(output) {
329
360
  }
330
361
  };
331
362
  }
363
+ function normalizeProviderConfig(provider) {
364
+ if (provider.kind === "postgres") {
365
+ return {
366
+ ...provider,
367
+ schemas: normalizeSchemaSelection(provider.schemas)
368
+ };
369
+ }
370
+ return provider;
371
+ }
332
372
  function isAthenaGeneratorConfig(value) {
333
373
  if (!value || typeof value !== "object") {
334
374
  return false;
@@ -338,7 +378,7 @@ function isAthenaGeneratorConfig(value) {
338
378
  }
339
379
  function normalizeGeneratorConfig(input) {
340
380
  return {
341
- provider: input.provider,
381
+ provider: normalizeProviderConfig(input.provider),
342
382
  output: normalizeOutputConfig(input.output),
343
383
  naming: {
344
384
  ...DEFAULT_NAMING,
@@ -548,12 +588,19 @@ export const ${registryConstName} = defineRegistry({
548
588
  };
549
589
  }
550
590
  function assertNoDuplicatePaths(files) {
551
- const seen = /* @__PURE__ */ new Set();
591
+ const seen = /* @__PURE__ */ new Map();
552
592
  for (const file of files) {
553
- if (seen.has(file.path)) {
554
- throw new Error(`Generator output collision detected for path: ${file.path}`);
593
+ const existing = seen.get(file.path);
594
+ if (existing) {
595
+ throw new Error(
596
+ [
597
+ `Generator output collision detected for path: ${file.path}`,
598
+ `Collision: ${existing.kind} and ${file.kind}.`,
599
+ "When syncing multiple schemas, include a schema placeholder such as {schema} or {schema_kebab} in model/schema output targets."
600
+ ].join(" ")
601
+ );
555
602
  }
556
- seen.add(file.path);
603
+ seen.set(file.path, file);
557
604
  }
558
605
  }
559
606
  var ArtifactComposer = class {
@@ -2057,8 +2104,73 @@ function toTypeKind(code) {
2057
2104
  function escapeSqlLiteral(value) {
2058
2105
  return value.replace(/'/g, "''");
2059
2106
  }
2107
+ function parsePostgresArrayLiteral(text) {
2108
+ const body = text.slice(1, -1).trim();
2109
+ if (!body) return [];
2110
+ const values = [];
2111
+ let current = "";
2112
+ let inQuotes = false;
2113
+ let escaped = false;
2114
+ for (const char of body) {
2115
+ if (escaped) {
2116
+ current += char;
2117
+ escaped = false;
2118
+ continue;
2119
+ }
2120
+ if (char === "\\") {
2121
+ escaped = true;
2122
+ continue;
2123
+ }
2124
+ if (char === '"') {
2125
+ inQuotes = !inQuotes;
2126
+ continue;
2127
+ }
2128
+ if (char === "," && !inQuotes) {
2129
+ values.push(current);
2130
+ current = "";
2131
+ continue;
2132
+ }
2133
+ current += char;
2134
+ }
2135
+ values.push(current);
2136
+ return values.map((value) => value.trim()).filter((value) => value.length > 0);
2137
+ }
2138
+ function coerceStringArray(value) {
2139
+ if (Array.isArray(value)) {
2140
+ return value.map((item) => typeof item === "string" ? item : String(item)).map((item) => item.trim()).filter((item) => item.length > 0);
2141
+ }
2142
+ if (typeof value === "string") {
2143
+ const trimmed = value.trim();
2144
+ if (!trimmed) return [];
2145
+ if (trimmed.startsWith("[") && trimmed.endsWith("]")) {
2146
+ try {
2147
+ const parsed = JSON.parse(trimmed);
2148
+ return coerceStringArray(parsed);
2149
+ } catch {
2150
+ }
2151
+ }
2152
+ if (trimmed.startsWith("{") && trimmed.endsWith("}")) {
2153
+ return parsePostgresArrayLiteral(trimmed);
2154
+ }
2155
+ return trimmed.split(",").map((item) => item.trim()).filter((item) => item.length > 0);
2156
+ }
2157
+ return [];
2158
+ }
2159
+ function normalizePostgresCatalogSchemas(schemas) {
2160
+ const normalized = [];
2161
+ const seen = /* @__PURE__ */ new Set();
2162
+ for (const value of schemas ?? []) {
2163
+ const schema = value.trim();
2164
+ if (!schema || seen.has(schema)) {
2165
+ continue;
2166
+ }
2167
+ seen.add(schema);
2168
+ normalized.push(schema);
2169
+ }
2170
+ return normalized.length > 0 ? normalized : ["public"];
2171
+ }
2060
2172
  function buildSchemaArrayLiteral(schemas) {
2061
- const normalized = schemas.length > 0 ? schemas : ["public"];
2173
+ const normalized = normalizePostgresCatalogSchemas(schemas);
2062
2174
  const literals = normalized.map((schema) => `'${escapeSqlLiteral(schema)}'`).join(", ");
2063
2175
  return `ARRAY[${literals}]`;
2064
2176
  }
@@ -2096,8 +2208,10 @@ var PostgresCatalogSnapshotAssembler = class {
2096
2208
  addPrimaryKeyRows(primaryKeyRows) {
2097
2209
  for (const row of primaryKeyRows) {
2098
2210
  const table = this.ensureTable(row.schema_name, row.table_name);
2099
- table.primaryKey = row.columns;
2100
- for (const columnName of row.columns) {
2211
+ const primaryKeyColumns = coerceStringArray(row.columns);
2212
+ row.columns = primaryKeyColumns;
2213
+ table.primaryKey = primaryKeyColumns;
2214
+ for (const columnName of primaryKeyColumns) {
2101
2215
  const column = table.columns[columnName];
2102
2216
  if (column) {
2103
2217
  column.isPrimaryKey = true;
@@ -2109,23 +2223,27 @@ var PostgresCatalogSnapshotAssembler = class {
2109
2223
  for (const row of foreignKeyRows) {
2110
2224
  const sourceTable = this.ensureTable(row.source_schema, row.source_table);
2111
2225
  const targetTable = this.ensureTable(row.target_schema, row.target_table);
2226
+ const sourceColumns = coerceStringArray(row.source_columns);
2227
+ const targetColumns = coerceStringArray(row.target_columns);
2228
+ row.source_columns = sourceColumns;
2229
+ row.target_columns = targetColumns;
2112
2230
  const sourceRelationKind = row.source_is_unique ? "one-to-one" : "many-to-one";
2113
2231
  this.upsertRelation(sourceTable, relationKey(row.constraint_name, row.target_table), {
2114
2232
  name: row.constraint_name,
2115
2233
  kind: sourceRelationKind,
2116
- sourceColumns: row.source_columns,
2234
+ sourceColumns,
2117
2235
  targetSchema: row.target_schema,
2118
2236
  targetModel: row.target_table,
2119
- targetColumns: row.target_columns
2237
+ targetColumns
2120
2238
  });
2121
2239
  const targetRelationKind = row.source_is_unique ? "one-to-one" : "one-to-many";
2122
2240
  this.upsertRelation(targetTable, relationKey(row.source_table), {
2123
2241
  name: relationKey(row.source_table, row.constraint_name),
2124
2242
  kind: targetRelationKind,
2125
- sourceColumns: row.target_columns,
2243
+ sourceColumns: targetColumns,
2126
2244
  targetSchema: row.source_schema,
2127
2245
  targetModel: row.source_table,
2128
- targetColumns: row.source_columns
2246
+ targetColumns: sourceColumns
2129
2247
  });
2130
2248
  }
2131
2249
  }
@@ -2251,12 +2369,14 @@ var PostgresIntrospectionProvider = class {
2251
2369
  backend = "postgresql";
2252
2370
  connectionString;
2253
2371
  database;
2372
+ schemas;
2254
2373
  constructor(options) {
2255
2374
  this.connectionString = options.connectionString;
2256
2375
  this.database = options.database ?? "postgres";
2376
+ this.schemas = normalizePostgresCatalogSchemas(options.schemas);
2257
2377
  }
2258
2378
  async inspect(options) {
2259
- const schemas = options?.schemas && options.schemas.length > 0 ? options.schemas : ["public"];
2379
+ const schemas = options?.schemas && options.schemas.length > 0 ? normalizePostgresCatalogSchemas(options.schemas) : this.schemas;
2260
2380
  const pool = new Pool({
2261
2381
  connectionString: this.connectionString
2262
2382
  });
@@ -2328,11 +2448,13 @@ var AthenaGatewayPostgresIntrospectionProvider = class {
2328
2448
  type: this.config.backend ?? "postgresql"
2329
2449
  }
2330
2450
  });
2451
+ this.schemas = normalizeSchemaSelection(this.config.schemas);
2331
2452
  }
2332
2453
  backend = "postgresql";
2333
2454
  client;
2455
+ schemas;
2334
2456
  async inspect(options) {
2335
- const schemas = options?.schemas && options.schemas.length > 0 ? options.schemas : this.config.schemas && this.config.schemas.length > 0 ? this.config.schemas : ["public"];
2457
+ const schemas = options?.schemas && options.schemas.length > 0 ? normalizeSchemaSelection(options.schemas) : this.schemas;
2336
2458
  const catalogClient = new AthenaGatewayCatalogClient(this.client);
2337
2459
  const queries = buildGatewayCatalogQueries(schemas);
2338
2460
  const [columnRows, enumMap, primaryKeyRows, foreignKeyRows] = await Promise.all([
@@ -2368,7 +2490,8 @@ var ScyllaIntrospectionProvider = class {
2368
2490
  function createPostgresProvider(config) {
2369
2491
  return createPostgresIntrospectionProvider({
2370
2492
  connectionString: config.connectionString,
2371
- database: config.database
2493
+ database: config.database,
2494
+ schemas: normalizeSchemaSelection(config.schemas)
2372
2495
  });
2373
2496
  }
2374
2497
  function resolveGeneratorProvider(providerConfig, experimentalFlags) {
@@ -2390,12 +2513,6 @@ function resolveGeneratorProvider(providerConfig, experimentalFlags) {
2390
2513
  }
2391
2514
 
2392
2515
  // src/generator/pipeline.ts
2393
- function extractProviderSchemas(providerConfig) {
2394
- if (!("schemas" in providerConfig) || !providerConfig.schemas || providerConfig.schemas.length === 0) {
2395
- return void 0;
2396
- }
2397
- return providerConfig.schemas;
2398
- }
2399
2516
  async function writeArtifacts(files, cwd) {
2400
2517
  const writtenFiles = [];
2401
2518
  for (const file of files) {
@@ -2415,7 +2532,7 @@ async function runSchemaGenerator(options = {}) {
2415
2532
  const { configPath, config } = await loadGeneratorConfig(configOptions);
2416
2533
  const provider = options.provider ?? resolveGeneratorProvider(config.provider, config.experimental);
2417
2534
  const snapshot = await provider.inspect({
2418
- schemas: extractProviderSchemas(config.provider)
2535
+ schemas: resolveProviderSchemas(config.provider)
2419
2536
  });
2420
2537
  const generated = generateArtifactsFromSnapshot(snapshot, config);
2421
2538
  const writtenFiles = options.dryRun ? [] : await writeArtifacts(generated.files, cwd);
@@ -2427,7 +2544,7 @@ async function runSchemaGenerator(options = {}) {
2427
2544
  }
2428
2545
 
2429
2546
  // src/cli/index.ts
2430
- function usage() {
2547
+ function rootUsage() {
2431
2548
  return [
2432
2549
  "athena-js CLI",
2433
2550
  "",
@@ -2436,12 +2553,42 @@ function usage() {
2436
2553
  "",
2437
2554
  "Examples:",
2438
2555
  " athena-js generate",
2556
+ " athena-js generate --config ./athena.config.ts --dry-run",
2557
+ " athena-js generate --help"
2558
+ ].join("\n");
2559
+ }
2560
+ function generateUsage() {
2561
+ return [
2562
+ "athena-js generate",
2563
+ "",
2564
+ "Usage:",
2565
+ " athena-js generate [--config <path>] [--dry-run]",
2566
+ "",
2567
+ "Options:",
2568
+ " --config <path> Explicit path to athena.config.ts or athena-js.config.ts",
2569
+ " --dry-run Build generated files in memory without writing them to disk",
2570
+ " -h, --help Show help for generate",
2571
+ "",
2572
+ "Examples:",
2573
+ " athena-js generate",
2439
2574
  " athena-js generate --config ./athena.config.ts --dry-run"
2440
2575
  ].join("\n");
2441
2576
  }
2577
+ function usage(topic = "root") {
2578
+ return topic === "generate" ? generateUsage() : rootUsage();
2579
+ }
2442
2580
  function parseCommand(argv) {
2443
- if (argv.length === 0 || argv[0] === "help" || argv[0] === "--help" || argv[0] === "-h") {
2444
- return { command: "help" };
2581
+ if (argv.length === 0 || argv[0] === "--help" || argv[0] === "-h") {
2582
+ return { command: "help", topic: "root" };
2583
+ }
2584
+ if (argv[0] === "help") {
2585
+ if (argv.length === 1) {
2586
+ return { command: "help", topic: "root" };
2587
+ }
2588
+ if (argv[1] === "generate") {
2589
+ return { command: "help", topic: "generate" };
2590
+ }
2591
+ throw new Error(`Unknown command "${argv[1]}".`);
2445
2592
  }
2446
2593
  const [command, ...rest] = argv;
2447
2594
  if (command !== "generate") {
@@ -2451,13 +2598,16 @@ function parseCommand(argv) {
2451
2598
  let dryRun = false;
2452
2599
  for (let index = 0; index < rest.length; index += 1) {
2453
2600
  const token = rest[index];
2601
+ if (token === "--help" || token === "-h") {
2602
+ return { command: "help", topic: "generate" };
2603
+ }
2454
2604
  if (token === "--dry-run") {
2455
2605
  dryRun = true;
2456
2606
  continue;
2457
2607
  }
2458
2608
  if (token === "--config") {
2459
2609
  const nextValue = rest[index + 1];
2460
- if (!nextValue) {
2610
+ if (!nextValue || nextValue.startsWith("-")) {
2461
2611
  throw new Error("Missing value for --config option.");
2462
2612
  }
2463
2613
  configPath = nextValue;
@@ -2472,29 +2622,70 @@ function parseCommand(argv) {
2472
2622
  dryRun
2473
2623
  };
2474
2624
  }
2475
- async function runCLI(argv) {
2625
+ function normalizeErrorMessage(error) {
2626
+ if (error instanceof Error) {
2627
+ return error.message;
2628
+ }
2629
+ if (typeof error === "string") {
2630
+ return error;
2631
+ }
2632
+ return "Unknown generator error.";
2633
+ }
2634
+ function extractMissingDatabaseName(message) {
2635
+ const match = message.match(/database "([^"]+)" does not exist/i);
2636
+ return match?.[1];
2637
+ }
2638
+ function isErrorWithCode(error) {
2639
+ return typeof error === "object" && error !== null && "code" in error;
2640
+ }
2641
+ function formatGeneratorError(error, configPath) {
2642
+ if (isErrorWithCode(error) && error.code === "3D000") {
2643
+ const message = normalizeErrorMessage(error);
2644
+ const databaseName = extractMissingDatabaseName(message);
2645
+ const databaseLabel = databaseName ? `PostgreSQL database "${databaseName}" does not exist` : "The target PostgreSQL database does not exist";
2646
+ const configLabel = configPath ? `config "${configPath}"` : "the resolved athena config";
2647
+ return new Error(
2648
+ [
2649
+ `${databaseLabel} (code 3D000).`,
2650
+ `Update provider.connectionString (and provider.database, if set) in ${configLabel}, or create that database before running generate.`
2651
+ ].join("\n")
2652
+ );
2653
+ }
2654
+ if (error instanceof Error) {
2655
+ return error;
2656
+ }
2657
+ return new Error(normalizeErrorMessage(error));
2658
+ }
2659
+ async function runCLI(argv, runtime = {}) {
2660
+ const log = runtime.log ?? console.log;
2661
+ const runGenerator = runtime.runGenerator ?? runSchemaGenerator;
2476
2662
  const parsed = parseCommand(argv);
2477
2663
  if (parsed.command === "help") {
2478
- console.log(usage());
2664
+ log(usage(parsed.topic));
2479
2665
  return;
2480
2666
  }
2481
- const result = await runSchemaGenerator({
2482
- configPath: parsed.configPath,
2483
- dryRun: parsed.dryRun
2484
- });
2667
+ let result;
2668
+ try {
2669
+ result = await runGenerator({
2670
+ configPath: parsed.configPath,
2671
+ dryRun: parsed.dryRun
2672
+ });
2673
+ } catch (error) {
2674
+ throw formatGeneratorError(error, parsed.configPath);
2675
+ }
2485
2676
  if (parsed.dryRun) {
2486
- console.log(`[dry-run] Generated ${result.files.length} files from ${result.configPath}`);
2677
+ log(`[dry-run] Generated ${result.files.length} files from ${result.configPath}`);
2487
2678
  for (const file of result.files) {
2488
- console.log(` - ${file.path}`);
2679
+ log(` - ${file.path}`);
2489
2680
  }
2490
2681
  return;
2491
2682
  }
2492
- console.log(`Generated ${result.writtenFiles.length} files from ${result.configPath}`);
2683
+ log(`Generated ${result.writtenFiles.length} files from ${result.configPath}`);
2493
2684
  for (const filePath of result.writtenFiles) {
2494
- console.log(` - ${filePath}`);
2685
+ log(` - ${filePath}`);
2495
2686
  }
2496
2687
  }
2497
2688
 
2498
- export { runCLI };
2689
+ export { parseCommand, runCLI, usage };
2499
2690
  //# sourceMappingURL=index.js.map
2500
2691
  //# sourceMappingURL=index.js.map