@xylex-group/athena 1.5.0 → 1.6.1

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/dist/index.cjs CHANGED
@@ -1,5 +1,11 @@
1
1
  'use strict';
2
2
 
3
+ var pg = require('pg');
4
+ var fs = require('fs');
5
+ var path = require('path');
6
+ var url = require('url');
7
+ var promises = require('fs/promises');
8
+
3
9
  // src/gateway/errors.ts
4
10
  var AthenaGatewayError = class _AthenaGatewayError extends Error {
5
11
  code;
@@ -366,6 +372,137 @@ function createAthenaGatewayClient(config = {}) {
366
372
  };
367
373
  }
368
374
 
375
+ // src/sql-identifiers.ts
376
+ var SIMPLE_IDENTIFIER_PATTERN = /^[A-Za-z_][A-Za-z0-9_]*$/;
377
+ var COMPOSITE_IDENTIFIER_PATTERN = /^[A-Za-z_][A-Za-z0-9_]*(?:\.[A-Za-z_][A-Za-z0-9_]*)*$/;
378
+ var ALIAS_PATTERN = /^([A-Za-z_][A-Za-z0-9_.]*)\s+(?:as\s+)?([A-Za-z_][A-Za-z0-9_]*)$/i;
379
+ function quoteIdentifierSegment(identifier2) {
380
+ return `"${identifier2.replace(/"/g, '""')}"`;
381
+ }
382
+ function quoteQualifiedIdentifier(identifier2) {
383
+ return identifier2.split(".").map((segment) => quoteIdentifierSegment(segment)).join(".");
384
+ }
385
+ function quoteSelectToken(token) {
386
+ if (token === "*") return token;
387
+ if (COMPOSITE_IDENTIFIER_PATTERN.test(token)) {
388
+ return quoteQualifiedIdentifier(token);
389
+ }
390
+ const aliasMatch = ALIAS_PATTERN.exec(token);
391
+ if (!aliasMatch) {
392
+ return token;
393
+ }
394
+ const [, baseIdentifier, aliasIdentifier] = aliasMatch;
395
+ if (!COMPOSITE_IDENTIFIER_PATTERN.test(baseIdentifier) || !SIMPLE_IDENTIFIER_PATTERN.test(aliasIdentifier)) {
396
+ return token;
397
+ }
398
+ return `${quoteQualifiedIdentifier(baseIdentifier)} AS ${quoteIdentifierSegment(aliasIdentifier)}`;
399
+ }
400
+ function canAutoQuoteToken(token) {
401
+ if (token === "*") return true;
402
+ if (COMPOSITE_IDENTIFIER_PATTERN.test(token)) return true;
403
+ const aliasMatch = ALIAS_PATTERN.exec(token);
404
+ if (!aliasMatch) return false;
405
+ const [, baseIdentifier, aliasIdentifier] = aliasMatch;
406
+ return COMPOSITE_IDENTIFIER_PATTERN.test(baseIdentifier) && SIMPLE_IDENTIFIER_PATTERN.test(aliasIdentifier);
407
+ }
408
+ function splitTopLevelCommaSeparated(input) {
409
+ const parts = [];
410
+ let buffer = "";
411
+ let singleQuoted = false;
412
+ let doubleQuoted = false;
413
+ let depth = 0;
414
+ for (let index = 0; index < input.length; index += 1) {
415
+ const char = input[index];
416
+ const next = index + 1 < input.length ? input[index + 1] : "";
417
+ if (singleQuoted) {
418
+ buffer += char;
419
+ if (char === "'" && next === "'") {
420
+ buffer += next;
421
+ index += 1;
422
+ continue;
423
+ }
424
+ if (char === "'") {
425
+ singleQuoted = false;
426
+ }
427
+ continue;
428
+ }
429
+ if (doubleQuoted) {
430
+ buffer += char;
431
+ if (char === '"' && next === '"') {
432
+ buffer += next;
433
+ index += 1;
434
+ continue;
435
+ }
436
+ if (char === '"') {
437
+ doubleQuoted = false;
438
+ }
439
+ continue;
440
+ }
441
+ if (char === "'") {
442
+ singleQuoted = true;
443
+ buffer += char;
444
+ continue;
445
+ }
446
+ if (char === '"') {
447
+ doubleQuoted = true;
448
+ buffer += char;
449
+ continue;
450
+ }
451
+ if (char === "(") {
452
+ depth += 1;
453
+ buffer += char;
454
+ continue;
455
+ }
456
+ if (char === ")") {
457
+ depth -= 1;
458
+ if (depth < 0) return null;
459
+ buffer += char;
460
+ continue;
461
+ }
462
+ if (char === "," && depth === 0) {
463
+ parts.push(buffer.trim());
464
+ buffer = "";
465
+ continue;
466
+ }
467
+ buffer += char;
468
+ }
469
+ if (singleQuoted || doubleQuoted || depth !== 0) {
470
+ return null;
471
+ }
472
+ if (buffer.trim().length > 0) {
473
+ parts.push(buffer.trim());
474
+ }
475
+ return parts;
476
+ }
477
+ function quoteSelectColumnsExpression(columns) {
478
+ const trimmed = columns.trim();
479
+ if (!trimmed || trimmed === "*") return trimmed || "*";
480
+ const tokens = splitTopLevelCommaSeparated(trimmed);
481
+ if (!tokens || tokens.length === 0) {
482
+ return trimmed;
483
+ }
484
+ if (!tokens.every(canAutoQuoteToken)) {
485
+ return trimmed;
486
+ }
487
+ return tokens.map(quoteSelectToken).join(", ");
488
+ }
489
+ var SqlIdentifierPath = class {
490
+ segments;
491
+ constructor(segments) {
492
+ this.segments = segments;
493
+ }
494
+ toSql() {
495
+ return this.segments.map(quoteIdentifierSegment).join(".");
496
+ }
497
+ toString() {
498
+ return this.toSql();
499
+ }
500
+ };
501
+ function identifier(...segments) {
502
+ const expandedSegments = segments.flatMap((segment) => segment.split(".")).map((segment) => segment.trim()).filter((segment) => segment.length > 0);
503
+ return new SqlIdentifierPath(expandedSegments);
504
+ }
505
+
369
506
  // src/client.ts
370
507
  var DEFAULT_COLUMNS = "*";
371
508
  var UUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
@@ -469,12 +606,6 @@ function normalizeCast(cast) {
469
606
  function escapeSqlStringLiteral(value) {
470
607
  return value.replace(/'/g, "''");
471
608
  }
472
- function quoteIdentifier(identifier) {
473
- return `"${identifier.replace(/"/g, '""')}"`;
474
- }
475
- function quoteQualifiedIdentifier(identifier) {
476
- return identifier.split(".").map((segment) => quoteIdentifier(segment)).join(".");
477
- }
478
609
  function toSqlLiteral(value) {
479
610
  if (value === null) return "NULL";
480
611
  if (typeof value === "number") return Number.isFinite(value) ? String(value) : "NULL";
@@ -489,7 +620,7 @@ function buildSelectColumnsClause(columns) {
489
620
  if (Array.isArray(columns)) {
490
621
  return columns.map((column) => quoteQualifiedIdentifier(column)).join(", ");
491
622
  }
492
- return columns;
623
+ return quoteSelectColumnsExpression(columns);
493
624
  }
494
625
  function conditionToSqlClause(condition) {
495
626
  if (!condition.column) return null;
@@ -1174,6 +1305,56 @@ var Backend = {
1174
1305
  };
1175
1306
 
1176
1307
  // src/auxiliaries.ts
1308
+ var AthenaErrorKind = {
1309
+ UniqueViolation: "unique_violation",
1310
+ NotFound: "not_found",
1311
+ Validation: "validation",
1312
+ Auth: "auth",
1313
+ RateLimit: "rate_limit",
1314
+ Transient: "transient",
1315
+ Unknown: "unknown"
1316
+ };
1317
+ var AthenaErrorCode = {
1318
+ UniqueViolation: "UNIQUE_VIOLATION",
1319
+ NotFound: "NOT_FOUND",
1320
+ ValidationFailed: "VALIDATION_FAILED",
1321
+ AuthUnauthorized: "AUTH_UNAUTHORIZED",
1322
+ AuthForbidden: "AUTH_FORBIDDEN",
1323
+ RateLimited: "RATE_LIMITED",
1324
+ NetworkUnavailable: "NETWORK_UNAVAILABLE",
1325
+ TransientFailure: "TRANSIENT_FAILURE",
1326
+ HttpFailure: "HTTP_FAILURE",
1327
+ Unknown: "UNKNOWN"
1328
+ };
1329
+ var AthenaErrorCategory = {
1330
+ Transport: "transport",
1331
+ Client: "client",
1332
+ Server: "server",
1333
+ Database: "database",
1334
+ Unknown: "unknown"
1335
+ };
1336
+ var AthenaError = class extends Error {
1337
+ code;
1338
+ kind;
1339
+ category;
1340
+ status;
1341
+ retryable;
1342
+ requestId;
1343
+ context;
1344
+ raw;
1345
+ constructor(input) {
1346
+ super(input.message);
1347
+ this.name = "AthenaError";
1348
+ this.code = input.code;
1349
+ this.kind = input.kind;
1350
+ this.category = input.category;
1351
+ this.status = input.status;
1352
+ this.retryable = input.retryable ?? false;
1353
+ this.requestId = input.requestId;
1354
+ this.context = input.context;
1355
+ this.raw = input.raw;
1356
+ }
1357
+ };
1177
1358
  function isRecord2(value) {
1178
1359
  return Boolean(value) && typeof value === "object" && !Array.isArray(value);
1179
1360
  }
@@ -1239,6 +1420,44 @@ function classifyKind(status, code, message) {
1239
1420
  }
1240
1421
  return "unknown";
1241
1422
  }
1423
+ function toAthenaErrorCode(kind, status, gatewayCode) {
1424
+ if (gatewayCode === "NETWORK_ERROR" || kind === "transient" && status === 0) {
1425
+ return AthenaErrorCode.NetworkUnavailable;
1426
+ }
1427
+ switch (kind) {
1428
+ case "unique_violation":
1429
+ return AthenaErrorCode.UniqueViolation;
1430
+ case "not_found":
1431
+ return AthenaErrorCode.NotFound;
1432
+ case "validation":
1433
+ return AthenaErrorCode.ValidationFailed;
1434
+ case "rate_limit":
1435
+ return AthenaErrorCode.RateLimited;
1436
+ case "auth":
1437
+ if (status === 403) {
1438
+ return AthenaErrorCode.AuthForbidden;
1439
+ }
1440
+ return AthenaErrorCode.AuthUnauthorized;
1441
+ case "transient":
1442
+ return status !== void 0 && status >= 500 ? AthenaErrorCode.HttpFailure : AthenaErrorCode.TransientFailure;
1443
+ case "unknown":
1444
+ default:
1445
+ return status !== void 0 && status >= 400 ? AthenaErrorCode.HttpFailure : AthenaErrorCode.Unknown;
1446
+ }
1447
+ }
1448
+ function toAthenaErrorCategory(kind, status) {
1449
+ if (kind === "transient" && (status === 0 || status === void 0)) {
1450
+ return AthenaErrorCategory.Transport;
1451
+ }
1452
+ if (kind === "unique_violation") return AthenaErrorCategory.Database;
1453
+ if (kind === "validation" || kind === "auth" || kind === "not_found") return AthenaErrorCategory.Client;
1454
+ if (kind === "rate_limit" || kind === "transient") return AthenaErrorCategory.Server;
1455
+ return AthenaErrorCategory.Unknown;
1456
+ }
1457
+ function isRetryable(kind, status) {
1458
+ if (kind === "rate_limit" || kind === "transient") return true;
1459
+ return status !== void 0 && status >= 500;
1460
+ }
1242
1461
  function toGatewayCode(kind, status) {
1243
1462
  if (kind === "transient" && (status === 0 || status === void 0)) return "NETWORK_ERROR";
1244
1463
  if (kind === "validation") return "INVALID_JSON";
@@ -1281,9 +1500,14 @@ function normalizeAthenaError(resultOrError, context) {
1281
1500
  const operation = context?.operation ?? operationFromDetails(details);
1282
1501
  const table = context?.table ?? extractTable(message2);
1283
1502
  const constraint = extractConstraint(message2);
1284
- const kind = classifyKind(resultOrError.status, details?.code, message2);
1503
+ const kind2 = classifyKind(resultOrError.status, details?.code, message2);
1504
+ const code = toAthenaErrorCode(kind2, resultOrError.status, details?.code);
1505
+ const category = toAthenaErrorCategory(kind2, resultOrError.status);
1285
1506
  return {
1286
- kind,
1507
+ kind: kind2,
1508
+ code,
1509
+ category,
1510
+ retryable: isRetryable(kind2, resultOrError.status),
1287
1511
  status: resultOrError.status,
1288
1512
  constraint,
1289
1513
  table,
@@ -1297,9 +1521,14 @@ function normalizeAthenaError(resultOrError, context) {
1297
1521
  const operation = context?.operation ?? operationFromDetails(details);
1298
1522
  const table = context?.table ?? extractTable(resultOrError.message);
1299
1523
  const constraint = extractConstraint(resultOrError.message);
1300
- const kind = classifyKind(resultOrError.status, resultOrError.code, resultOrError.message);
1524
+ const kind2 = classifyKind(resultOrError.status, resultOrError.code, resultOrError.message);
1525
+ const code = toAthenaErrorCode(kind2, resultOrError.status, resultOrError.code);
1526
+ const category = toAthenaErrorCategory(kind2, resultOrError.status);
1301
1527
  return {
1302
- kind,
1528
+ kind: kind2,
1529
+ code,
1530
+ category,
1531
+ retryable: isRetryable(kind2, resultOrError.status),
1303
1532
  status: resultOrError.status,
1304
1533
  constraint,
1305
1534
  table,
@@ -1310,8 +1539,12 @@ function normalizeAthenaError(resultOrError, context) {
1310
1539
  }
1311
1540
  if (resultOrError instanceof Error) {
1312
1541
  const maybeStatus = isRecord2(resultOrError) && typeof resultOrError.status === "number" ? resultOrError.status : void 0;
1542
+ const kind2 = classifyKind(maybeStatus, void 0, resultOrError.message);
1313
1543
  return {
1314
- kind: classifyKind(maybeStatus, void 0, resultOrError.message),
1544
+ kind: kind2,
1545
+ code: toAthenaErrorCode(kind2, maybeStatus),
1546
+ category: toAthenaErrorCategory(kind2, maybeStatus),
1547
+ retryable: isRetryable(kind2, maybeStatus),
1315
1548
  status: maybeStatus,
1316
1549
  constraint: extractConstraint(resultOrError.message),
1317
1550
  table: context?.table ?? extractTable(resultOrError.message),
@@ -1321,8 +1554,12 @@ function normalizeAthenaError(resultOrError, context) {
1321
1554
  };
1322
1555
  }
1323
1556
  const message = typeof resultOrError === "string" ? resultOrError : "Unknown Athena error";
1557
+ const kind = classifyKind(void 0, void 0, message);
1324
1558
  return {
1325
- kind: classifyKind(void 0, void 0, message),
1559
+ kind,
1560
+ code: toAthenaErrorCode(kind, void 0),
1561
+ category: toAthenaErrorCategory(kind, void 0),
1562
+ retryable: isRetryable(kind, void 0),
1326
1563
  status: void 0,
1327
1564
  constraint: extractConstraint(message),
1328
1565
  table: context?.table ?? extractTable(message),
@@ -1455,8 +1692,8 @@ function computeDelayMs(attempt, error, config) {
1455
1692
  }
1456
1693
  function sleep(ms) {
1457
1694
  if (ms <= 0) return Promise.resolve();
1458
- return new Promise((resolve) => {
1459
- setTimeout(resolve, ms);
1695
+ return new Promise((resolve3) => {
1696
+ setTimeout(resolve3, ms);
1460
1697
  });
1461
1698
  }
1462
1699
  async function withRetry(config, fn) {
@@ -1487,17 +1724,1319 @@ async function withRetry(config, fn) {
1487
1724
  throw new Error("withRetry reached an unexpected state");
1488
1725
  }
1489
1726
 
1727
+ // src/schema/definitions.ts
1728
+ function defineModel(input) {
1729
+ return input;
1730
+ }
1731
+ function defineSchema(models) {
1732
+ return { models };
1733
+ }
1734
+ function defineDatabase(schemas) {
1735
+ return { schemas };
1736
+ }
1737
+ function defineRegistry(databases) {
1738
+ return databases;
1739
+ }
1740
+
1741
+ // src/schema/typed-client.ts
1742
+ var TenantHeaderMapper = class {
1743
+ constructor(tenantKeyMap) {
1744
+ this.tenantKeyMap = tenantKeyMap;
1745
+ }
1746
+ apply(baseHeaders, tenantContext) {
1747
+ const headers = {
1748
+ ...baseHeaders ?? {}
1749
+ };
1750
+ for (const [tenantKey, headerName] of Object.entries(this.tenantKeyMap)) {
1751
+ const tenantValue = tenantContext[tenantKey];
1752
+ if (tenantValue === void 0 || tenantValue === null) {
1753
+ continue;
1754
+ }
1755
+ headers[headerName] = String(tenantValue);
1756
+ }
1757
+ return Object.keys(headers).length > 0 ? headers : void 0;
1758
+ }
1759
+ };
1760
+ var RegistryNavigator = class {
1761
+ constructor(registry) {
1762
+ this.registry = registry;
1763
+ }
1764
+ resolveModel(database, schema, model) {
1765
+ const databaseDef = this.registry[database];
1766
+ if (!databaseDef) {
1767
+ throw new Error(`Unknown database "${database}"`);
1768
+ }
1769
+ const schemaDef = databaseDef.schemas[schema];
1770
+ if (!schemaDef) {
1771
+ throw new Error(`Unknown schema "${schema}" in database "${database}"`);
1772
+ }
1773
+ const modelDef = schemaDef.models[model];
1774
+ if (!modelDef) {
1775
+ throw new Error(`Unknown model "${model}" in schema "${schema}"`);
1776
+ }
1777
+ return modelDef;
1778
+ }
1779
+ resolveTableName(schema, model, modelDef) {
1780
+ if (modelDef.meta.tableName) {
1781
+ return modelDef.meta.tableName;
1782
+ }
1783
+ const schemaName = modelDef.meta.schema ?? schema;
1784
+ const modelName = modelDef.meta.model ?? model;
1785
+ return `${schemaName}.${modelName}`;
1786
+ }
1787
+ };
1788
+ var TypedAthenaClientImpl = class _TypedAthenaClientImpl {
1789
+ registry;
1790
+ tenantKeyMap;
1791
+ tenantContext;
1792
+ baseClient;
1793
+ registryNavigator;
1794
+ tenantHeaderMapper;
1795
+ clientOptions;
1796
+ url;
1797
+ apiKey;
1798
+ constructor(input) {
1799
+ this.registry = input.registry;
1800
+ this.url = input.url;
1801
+ this.apiKey = input.apiKey;
1802
+ const tenantKeyMap = input.options?.tenantKeyMap ?? {};
1803
+ const tenantContext = input.options?.tenantContext ?? {};
1804
+ this.tenantKeyMap = tenantKeyMap;
1805
+ this.tenantContext = tenantContext;
1806
+ this.tenantHeaderMapper = new TenantHeaderMapper(tenantKeyMap);
1807
+ this.registryNavigator = new RegistryNavigator(input.registry);
1808
+ this.clientOptions = {
1809
+ backend: input.options?.backend,
1810
+ client: input.options?.client,
1811
+ headers: input.options?.headers
1812
+ };
1813
+ this.baseClient = createClient(this.url, this.apiKey, {
1814
+ backend: this.clientOptions.backend,
1815
+ client: this.clientOptions.client,
1816
+ headers: this.tenantHeaderMapper.apply(this.clientOptions.headers, tenantContext)
1817
+ });
1818
+ }
1819
+ from(table) {
1820
+ return this.baseClient.from(table);
1821
+ }
1822
+ rpc(fn, args, options) {
1823
+ return this.baseClient.rpc(fn, args, options);
1824
+ }
1825
+ query(query, options) {
1826
+ return this.baseClient.query(query, options);
1827
+ }
1828
+ withTenantContext(context) {
1829
+ return new _TypedAthenaClientImpl({
1830
+ registry: this.registry,
1831
+ url: this.url,
1832
+ apiKey: this.apiKey,
1833
+ options: {
1834
+ ...this.clientOptions,
1835
+ tenantKeyMap: this.tenantKeyMap,
1836
+ tenantContext: {
1837
+ ...this.tenantContext,
1838
+ ...context ?? {}
1839
+ }
1840
+ }
1841
+ });
1842
+ }
1843
+ fromModel(database, schema, model) {
1844
+ const modelDef = this.registryNavigator.resolveModel(database, schema, model);
1845
+ const tableName = this.registryNavigator.resolveTableName(schema, model, modelDef);
1846
+ return this.baseClient.from(tableName);
1847
+ }
1848
+ };
1849
+ function createTypedClient(registry, url, apiKey, options) {
1850
+ return new TypedAthenaClientImpl({
1851
+ registry,
1852
+ url,
1853
+ apiKey,
1854
+ options
1855
+ });
1856
+ }
1857
+
1858
+ // src/schema/postgres-introspection-core.ts
1859
+ var POSTGRES_CATALOG_SQL = {
1860
+ columns: `
1861
+ SELECT
1862
+ n.nspname AS schema_name,
1863
+ c.relname AS table_name,
1864
+ a.attname AS column_name,
1865
+ format_type(a.atttypid, a.atttypmod) AS data_type,
1866
+ t.typname AS udt_name,
1867
+ t.typtype AS type_kind_code,
1868
+ t.oid AS type_oid,
1869
+ NOT a.attnotnull AS is_nullable,
1870
+ (ad.adbin IS NOT NULL) AS has_default,
1871
+ (a.attgenerated <> '') AS is_generated,
1872
+ a.attndims AS array_dimensions
1873
+ FROM pg_attribute a
1874
+ JOIN pg_class c ON c.oid = a.attrelid
1875
+ JOIN pg_namespace n ON n.oid = c.relnamespace
1876
+ JOIN pg_type t ON t.oid = a.atttypid
1877
+ LEFT JOIN pg_attrdef ad ON ad.adrelid = a.attrelid AND ad.adnum = a.attnum
1878
+ WHERE c.relkind IN ('r', 'p')
1879
+ AND a.attnum > 0
1880
+ AND NOT a.attisdropped
1881
+ AND n.nspname = ANY($1::text[])
1882
+ ORDER BY n.nspname, c.relname, a.attnum;
1883
+ `,
1884
+ enums: `
1885
+ SELECT
1886
+ t.oid AS type_oid,
1887
+ e.enumlabel AS enum_label
1888
+ FROM pg_type t
1889
+ JOIN pg_enum e ON e.enumtypid = t.oid
1890
+ ORDER BY t.oid, e.enumsortorder;
1891
+ `,
1892
+ primaryKeys: `
1893
+ SELECT
1894
+ n.nspname AS schema_name,
1895
+ c.relname AS table_name,
1896
+ ARRAY_AGG(a.attname ORDER BY ck.ordinality) AS columns
1897
+ FROM pg_constraint con
1898
+ JOIN pg_class c ON c.oid = con.conrelid
1899
+ JOIN pg_namespace n ON n.oid = c.relnamespace
1900
+ JOIN unnest(con.conkey) WITH ORDINALITY AS ck(attnum, ordinality) ON TRUE
1901
+ JOIN pg_attribute a ON a.attrelid = c.oid AND a.attnum = ck.attnum
1902
+ WHERE con.contype = 'p'
1903
+ AND n.nspname = ANY($1::text[])
1904
+ GROUP BY n.nspname, c.relname
1905
+ ORDER BY n.nspname, c.relname;
1906
+ `,
1907
+ foreignKeys: `
1908
+ SELECT
1909
+ sn.nspname AS source_schema,
1910
+ sc.relname AS source_table,
1911
+ con.conname AS constraint_name,
1912
+ ARRAY_AGG(sa.attname ORDER BY cols.ordinality) AS source_columns,
1913
+ tn.nspname AS target_schema,
1914
+ tc.relname AS target_table,
1915
+ ARRAY_AGG(ta.attname ORDER BY cols.ordinality) AS target_columns,
1916
+ EXISTS (
1917
+ SELECT 1
1918
+ FROM pg_constraint uq
1919
+ WHERE uq.conrelid = con.conrelid
1920
+ AND uq.contype IN ('p', 'u')
1921
+ AND uq.conkey = con.conkey
1922
+ ) AS source_is_unique
1923
+ FROM pg_constraint con
1924
+ JOIN pg_class sc ON sc.oid = con.conrelid
1925
+ JOIN pg_namespace sn ON sn.oid = sc.relnamespace
1926
+ JOIN pg_class tc ON tc.oid = con.confrelid
1927
+ JOIN pg_namespace tn ON tn.oid = tc.relnamespace
1928
+ JOIN unnest(con.conkey, con.confkey) WITH ORDINALITY AS cols(source_attnum, target_attnum, ordinality) ON TRUE
1929
+ JOIN pg_attribute sa ON sa.attrelid = con.conrelid AND sa.attnum = cols.source_attnum
1930
+ JOIN pg_attribute ta ON ta.attrelid = con.confrelid AND ta.attnum = cols.target_attnum
1931
+ WHERE con.contype = 'f'
1932
+ AND sn.nspname = ANY($1::text[])
1933
+ AND tn.nspname = ANY($1::text[])
1934
+ GROUP BY
1935
+ sn.nspname,
1936
+ sc.relname,
1937
+ con.conname,
1938
+ tn.nspname,
1939
+ tc.relname,
1940
+ con.conkey,
1941
+ con.conrelid
1942
+ ORDER BY sn.nspname, sc.relname, con.conname;
1943
+ `
1944
+ };
1945
+ function tableKey(schema, table) {
1946
+ return `${schema}.${table}`;
1947
+ }
1948
+ function relationKey(...parts) {
1949
+ const base = parts.join("_").replace(/[^A-Za-z0-9_]+/g, "_").replace(/^_+|_+$/g, "");
1950
+ return base.length > 0 ? base : "relation";
1951
+ }
1952
+ function toTypeKind(code) {
1953
+ switch (code) {
1954
+ case "e":
1955
+ return "enum";
1956
+ case "d":
1957
+ return "domain";
1958
+ case "r":
1959
+ return "range";
1960
+ case "m":
1961
+ return "multirange";
1962
+ case "c":
1963
+ return "composite";
1964
+ default:
1965
+ return "scalar";
1966
+ }
1967
+ }
1968
+ function escapeSqlLiteral(value) {
1969
+ return value.replace(/'/g, "''");
1970
+ }
1971
+ function buildSchemaArrayLiteral(schemas) {
1972
+ const normalized = schemas.length > 0 ? schemas : ["public"];
1973
+ const literals = normalized.map((schema) => `'${escapeSqlLiteral(schema)}'`).join(", ");
1974
+ return `ARRAY[${literals}]`;
1975
+ }
1976
+ function inlineSchemaLiteral(sql, schemas) {
1977
+ const schemaArray = `${buildSchemaArrayLiteral(schemas)}::text[]`;
1978
+ return sql.replace(/\$1::text\[\]/g, schemaArray);
1979
+ }
1980
+ function buildGatewayCatalogQueries(schemas) {
1981
+ return {
1982
+ columns: inlineSchemaLiteral(POSTGRES_CATALOG_SQL.columns, schemas),
1983
+ enums: POSTGRES_CATALOG_SQL.enums,
1984
+ primaryKeys: inlineSchemaLiteral(POSTGRES_CATALOG_SQL.primaryKeys, schemas),
1985
+ foreignKeys: inlineSchemaLiteral(POSTGRES_CATALOG_SQL.foreignKeys, schemas)
1986
+ };
1987
+ }
1988
+ var PostgresCatalogSnapshotAssembler = class {
1989
+ schemas = {};
1990
+ addColumnRows(columnRows, enumMap) {
1991
+ for (const row of columnRows) {
1992
+ const table = this.ensureTable(row.schema_name, row.table_name);
1993
+ table.columns[row.column_name] = {
1994
+ name: row.column_name,
1995
+ dataType: row.data_type,
1996
+ udtName: row.udt_name,
1997
+ typeKind: toTypeKind(row.type_kind_code),
1998
+ isNullable: row.is_nullable,
1999
+ isPrimaryKey: false,
2000
+ hasDefault: row.has_default,
2001
+ isGenerated: row.is_generated,
2002
+ arrayDimensions: row.array_dimensions ?? 0,
2003
+ enumValues: enumMap.get(row.type_oid)
2004
+ };
2005
+ }
2006
+ }
2007
+ addPrimaryKeyRows(primaryKeyRows) {
2008
+ for (const row of primaryKeyRows) {
2009
+ const table = this.ensureTable(row.schema_name, row.table_name);
2010
+ table.primaryKey = row.columns;
2011
+ for (const columnName of row.columns) {
2012
+ const column = table.columns[columnName];
2013
+ if (column) {
2014
+ column.isPrimaryKey = true;
2015
+ }
2016
+ }
2017
+ }
2018
+ }
2019
+ addForeignKeyRows(foreignKeyRows) {
2020
+ for (const row of foreignKeyRows) {
2021
+ const sourceTable = this.ensureTable(row.source_schema, row.source_table);
2022
+ const targetTable = this.ensureTable(row.target_schema, row.target_table);
2023
+ const sourceRelationKind = row.source_is_unique ? "one-to-one" : "many-to-one";
2024
+ this.upsertRelation(sourceTable, relationKey(row.constraint_name, row.target_table), {
2025
+ name: row.constraint_name,
2026
+ kind: sourceRelationKind,
2027
+ sourceColumns: row.source_columns,
2028
+ targetSchema: row.target_schema,
2029
+ targetModel: row.target_table,
2030
+ targetColumns: row.target_columns
2031
+ });
2032
+ const targetRelationKind = row.source_is_unique ? "one-to-one" : "one-to-many";
2033
+ this.upsertRelation(targetTable, relationKey(row.source_table), {
2034
+ name: relationKey(row.source_table, row.constraint_name),
2035
+ kind: targetRelationKind,
2036
+ sourceColumns: row.target_columns,
2037
+ targetSchema: row.source_schema,
2038
+ targetModel: row.source_table,
2039
+ targetColumns: row.source_columns
2040
+ });
2041
+ }
2042
+ }
2043
+ addManyToManyRows(foreignKeyRows) {
2044
+ const bySourceTable = /* @__PURE__ */ new Map();
2045
+ for (const fk of foreignKeyRows) {
2046
+ const key = tableKey(fk.source_schema, fk.source_table);
2047
+ const current = bySourceTable.get(key) ?? {
2048
+ schema: fk.source_schema,
2049
+ table: fk.source_table,
2050
+ foreignKeys: []
2051
+ };
2052
+ current.foreignKeys.push(fk);
2053
+ bySourceTable.set(key, current);
2054
+ }
2055
+ for (const candidate of bySourceTable.values()) {
2056
+ const bridgeTable = this.schemas[candidate.schema]?.tables[candidate.table];
2057
+ if (!bridgeTable) continue;
2058
+ const primaryKey = bridgeTable.primaryKey;
2059
+ if (candidate.foreignKeys.length !== 2 || primaryKey.length === 0) continue;
2060
+ const combinedForeignColumns = Array.from(
2061
+ new Set(candidate.foreignKeys.flatMap((fk) => fk.source_columns))
2062
+ );
2063
+ if (combinedForeignColumns.length !== primaryKey.length || !primaryKey.every((column) => combinedForeignColumns.includes(column))) {
2064
+ continue;
2065
+ }
2066
+ const [first, second] = candidate.foreignKeys;
2067
+ const firstTarget = this.schemas[first.target_schema]?.tables[first.target_table];
2068
+ const secondTarget = this.schemas[second.target_schema]?.tables[second.target_table];
2069
+ if (!firstTarget || !secondTarget) {
2070
+ continue;
2071
+ }
2072
+ this.upsertRelation(firstTarget, relationKey(second.target_table), {
2073
+ name: relationKey(candidate.table, first.constraint_name, second.constraint_name),
2074
+ kind: "many-to-many",
2075
+ sourceColumns: first.target_columns,
2076
+ targetSchema: second.target_schema,
2077
+ targetModel: second.target_table,
2078
+ targetColumns: second.target_columns,
2079
+ through: {
2080
+ schema: candidate.schema,
2081
+ model: candidate.table,
2082
+ sourceColumns: first.source_columns,
2083
+ targetColumns: second.source_columns
2084
+ }
2085
+ });
2086
+ this.upsertRelation(secondTarget, relationKey(first.target_table), {
2087
+ name: relationKey(candidate.table, second.constraint_name, first.constraint_name),
2088
+ kind: "many-to-many",
2089
+ sourceColumns: second.target_columns,
2090
+ targetSchema: first.target_schema,
2091
+ targetModel: first.target_table,
2092
+ targetColumns: first.target_columns,
2093
+ through: {
2094
+ schema: candidate.schema,
2095
+ model: candidate.table,
2096
+ sourceColumns: second.source_columns,
2097
+ targetColumns: first.source_columns
2098
+ }
2099
+ });
2100
+ }
2101
+ }
2102
+ toSchemas() {
2103
+ return this.schemas;
2104
+ }
2105
+ ensureTable(schemaName, tableName) {
2106
+ if (!this.schemas[schemaName]) {
2107
+ this.schemas[schemaName] = {
2108
+ name: schemaName,
2109
+ tables: {}
2110
+ };
2111
+ }
2112
+ const schema = this.schemas[schemaName];
2113
+ if (!schema.tables[tableName]) {
2114
+ schema.tables[tableName] = {
2115
+ schema: schemaName,
2116
+ name: tableName,
2117
+ columns: {},
2118
+ primaryKey: [],
2119
+ relations: {}
2120
+ };
2121
+ }
2122
+ return schema.tables[tableName];
2123
+ }
2124
+ upsertRelation(table, baseKey, relation) {
2125
+ let key = baseKey;
2126
+ let suffix = 2;
2127
+ while (table.relations[key]) {
2128
+ key = `${baseKey}_${suffix}`;
2129
+ suffix += 1;
2130
+ }
2131
+ table.relations[key] = relation;
2132
+ }
2133
+ };
2134
+
2135
+ // src/schema/postgres-provider.ts
2136
+ var PgCatalogClient = class {
2137
+ constructor(pool) {
2138
+ this.pool = pool;
2139
+ }
2140
+ async queryColumns(schemas) {
2141
+ const result = await this.pool.query(POSTGRES_CATALOG_SQL.columns, [schemas]);
2142
+ return result.rows;
2143
+ }
2144
+ async queryEnums() {
2145
+ const result = await this.pool.query(POSTGRES_CATALOG_SQL.enums);
2146
+ const enumMap = /* @__PURE__ */ new Map();
2147
+ for (const row of result.rows) {
2148
+ const existing = enumMap.get(row.type_oid) ?? [];
2149
+ existing.push(row.enum_label);
2150
+ enumMap.set(row.type_oid, existing);
2151
+ }
2152
+ return enumMap;
2153
+ }
2154
+ async queryPrimaryKeys(schemas) {
2155
+ const result = await this.pool.query(POSTGRES_CATALOG_SQL.primaryKeys, [schemas]);
2156
+ return result.rows;
2157
+ }
2158
+ async queryForeignKeys(schemas) {
2159
+ const result = await this.pool.query(POSTGRES_CATALOG_SQL.foreignKeys, [schemas]);
2160
+ return result.rows;
2161
+ }
2162
+ };
2163
+ var PostgresIntrospectionProvider = class {
2164
+ backend = "postgresql";
2165
+ connectionString;
2166
+ database;
2167
+ constructor(options) {
2168
+ this.connectionString = options.connectionString;
2169
+ this.database = options.database ?? "postgres";
2170
+ }
2171
+ async inspect(options) {
2172
+ const schemas = options?.schemas && options.schemas.length > 0 ? options.schemas : ["public"];
2173
+ const pool = new pg.Pool({
2174
+ connectionString: this.connectionString
2175
+ });
2176
+ const catalogClient = new PgCatalogClient(pool);
2177
+ try {
2178
+ const [columnRows, enumMap, primaryKeyRows, foreignKeyRows] = await Promise.all([
2179
+ catalogClient.queryColumns(schemas),
2180
+ catalogClient.queryEnums(),
2181
+ catalogClient.queryPrimaryKeys(schemas),
2182
+ catalogClient.queryForeignKeys(schemas)
2183
+ ]);
2184
+ const assembler = new PostgresCatalogSnapshotAssembler();
2185
+ assembler.addColumnRows(columnRows, enumMap);
2186
+ assembler.addPrimaryKeyRows(primaryKeyRows);
2187
+ assembler.addForeignKeyRows(foreignKeyRows);
2188
+ assembler.addManyToManyRows(foreignKeyRows);
2189
+ return {
2190
+ backend: "postgresql",
2191
+ database: this.database,
2192
+ generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
2193
+ schemas: assembler.toSchemas()
2194
+ };
2195
+ } finally {
2196
+ await pool.end();
2197
+ }
2198
+ }
2199
+ };
2200
+ function createPostgresIntrospectionProvider(options) {
2201
+ return new PostgresIntrospectionProvider(options);
2202
+ }
2203
+ var DEFAULT_CONFIG_CANDIDATES = [
2204
+ "athena.config.ts",
2205
+ "athena.config.js",
2206
+ "athena-js.config.ts",
2207
+ "athena-js.config.js",
2208
+ ".athena.config.ts",
2209
+ ".athena.config.js"
2210
+ ];
2211
+ var DEFAULT_TARGETS = {
2212
+ model: "src/generated/{database_kebab}/{schema_kebab}/{model_kebab}.model.ts",
2213
+ schema: "src/generated/{database_kebab}/{schema_kebab}/index.ts",
2214
+ database: "src/generated/{database_kebab}/index.ts",
2215
+ registry: "src/generated/index.ts"
2216
+ };
2217
+ var DEFAULT_NAMING = {
2218
+ modelType: "pascal",
2219
+ modelConst: "camel",
2220
+ schemaConst: "camel",
2221
+ databaseConst: "camel",
2222
+ registryConst: "camel"
2223
+ };
2224
+ var DEFAULT_FEATURES = {
2225
+ emitRelations: true,
2226
+ emitRegistry: true
2227
+ };
2228
+ var DEFAULT_EXPERIMENTAL_FLAGS = {
2229
+ postgresGatewayIntrospection: false,
2230
+ scyllaProviderContracts: true
2231
+ };
2232
+ function normalizeOutputConfig(output) {
2233
+ return {
2234
+ targets: {
2235
+ ...DEFAULT_TARGETS,
2236
+ ...output.targets ?? {}
2237
+ },
2238
+ placeholderMap: {
2239
+ ...output.placeholderMap ?? {}
2240
+ }
2241
+ };
2242
+ }
2243
+ function isAthenaGeneratorConfig(value) {
2244
+ if (!value || typeof value !== "object") {
2245
+ return false;
2246
+ }
2247
+ const record = value;
2248
+ return Boolean(record.provider && typeof record.provider === "object") && Boolean(record.output && typeof record.output === "object");
2249
+ }
2250
+ function normalizeGeneratorConfig(input) {
2251
+ return {
2252
+ provider: input.provider,
2253
+ output: normalizeOutputConfig(input.output),
2254
+ naming: {
2255
+ ...DEFAULT_NAMING,
2256
+ ...input.naming ?? {}
2257
+ },
2258
+ features: {
2259
+ ...DEFAULT_FEATURES,
2260
+ ...input.features ?? {}
2261
+ },
2262
+ experimental: {
2263
+ ...DEFAULT_EXPERIMENTAL_FLAGS,
2264
+ ...input.experimental ?? {}
2265
+ }
2266
+ };
2267
+ }
2268
+ function defineGeneratorConfig(config) {
2269
+ return config;
2270
+ }
2271
+ function findGeneratorConfigPath(cwd = process.cwd()) {
2272
+ for (const candidate of DEFAULT_CONFIG_CANDIDATES) {
2273
+ const absolutePath = path.resolve(cwd, candidate);
2274
+ if (fs.existsSync(absolutePath)) {
2275
+ return absolutePath;
2276
+ }
2277
+ }
2278
+ return void 0;
2279
+ }
2280
+ function extractConfigExport(module) {
2281
+ const visited = /* @__PURE__ */ new Set();
2282
+ const queue = [module];
2283
+ while (queue.length > 0) {
2284
+ const current = queue.shift();
2285
+ if (!current || typeof current !== "object" || visited.has(current)) {
2286
+ continue;
2287
+ }
2288
+ visited.add(current);
2289
+ const record = current;
2290
+ if (isAthenaGeneratorConfig(record)) {
2291
+ return record;
2292
+ }
2293
+ const defaultExport = record.default;
2294
+ if (defaultExport && typeof defaultExport === "object") {
2295
+ queue.push(defaultExport);
2296
+ }
2297
+ const namedConfigExport = record.config;
2298
+ if (namedConfigExport && typeof namedConfigExport === "object") {
2299
+ queue.push(namedConfigExport);
2300
+ }
2301
+ const moduleExports = record["module.exports"];
2302
+ if (moduleExports && typeof moduleExports === "object") {
2303
+ queue.push(moduleExports);
2304
+ }
2305
+ }
2306
+ throw new Error(
2307
+ "Generator config file must export a config object as default export or `config`."
2308
+ );
2309
+ }
2310
+ function importConfigModule(moduleSpecifier) {
2311
+ const runtimeImport = new Function(
2312
+ "moduleSpecifier",
2313
+ "return import(moduleSpecifier)"
2314
+ );
2315
+ return runtimeImport(moduleSpecifier);
2316
+ }
2317
+ async function loadGeneratorConfig(options = {}) {
2318
+ const cwd = options.cwd ?? process.cwd();
2319
+ const resolvedPath = options.configPath ? path.resolve(cwd, options.configPath) : findGeneratorConfigPath(cwd);
2320
+ if (!resolvedPath) {
2321
+ throw new Error(
2322
+ `No generator config found in ${cwd}. Expected one of: ${DEFAULT_CONFIG_CANDIDATES.join(", ")}`
2323
+ );
2324
+ }
2325
+ const moduleUrl = url.pathToFileURL(resolvedPath);
2326
+ const module = await importConfigModule(`${moduleUrl.href}?cacheBust=${Date.now()}`);
2327
+ const rawConfig = extractConfigExport(module);
2328
+ return {
2329
+ configPath: resolvedPath,
2330
+ config: normalizeGeneratorConfig(rawConfig)
2331
+ };
2332
+ }
2333
+
2334
+ // src/generator/naming.ts
2335
+ var IDENTIFIER_PATTERN = /^[A-Za-z_$][A-Za-z0-9_$]*$/;
2336
+ var RESERVED_IDENTIFIERS = /* @__PURE__ */ new Set([
2337
+ "break",
2338
+ "case",
2339
+ "catch",
2340
+ "class",
2341
+ "const",
2342
+ "continue",
2343
+ "debugger",
2344
+ "default",
2345
+ "delete",
2346
+ "do",
2347
+ "else",
2348
+ "enum",
2349
+ "export",
2350
+ "extends",
2351
+ "false",
2352
+ "finally",
2353
+ "for",
2354
+ "function",
2355
+ "if",
2356
+ "import",
2357
+ "in",
2358
+ "instanceof",
2359
+ "new",
2360
+ "null",
2361
+ "return",
2362
+ "super",
2363
+ "switch",
2364
+ "this",
2365
+ "throw",
2366
+ "true",
2367
+ "try",
2368
+ "typeof",
2369
+ "var",
2370
+ "void",
2371
+ "while",
2372
+ "with",
2373
+ "as",
2374
+ "implements",
2375
+ "interface",
2376
+ "let",
2377
+ "package",
2378
+ "private",
2379
+ "protected",
2380
+ "public",
2381
+ "static",
2382
+ "yield",
2383
+ "any",
2384
+ "boolean",
2385
+ "constructor",
2386
+ "declare",
2387
+ "get",
2388
+ "module",
2389
+ "require",
2390
+ "number",
2391
+ "set",
2392
+ "string",
2393
+ "symbol",
2394
+ "type",
2395
+ "from",
2396
+ "of"
2397
+ ]);
2398
+ function splitWords(input) {
2399
+ return input.trim().replace(/([a-z0-9])([A-Z])/g, "$1 $2").replace(/([A-Z]+)([A-Z][a-z0-9]+)/g, "$1 $2").replace(/[^A-Za-z0-9]+/g, " ").split(" ").map((word) => word.trim()).filter((word) => word.length > 0);
2400
+ }
2401
+ function capitalize(word) {
2402
+ return word.length > 0 ? `${word[0].toUpperCase()}${word.slice(1).toLowerCase()}` : word;
2403
+ }
2404
+ function ensureValidIdentifier(candidate, fallback) {
2405
+ const normalized = candidate.length > 0 ? candidate : fallback;
2406
+ const prefixed = /^[0-9]/.test(normalized) ? `_${normalized}` : normalized;
2407
+ if (RESERVED_IDENTIFIERS.has(prefixed)) {
2408
+ return `${prefixed}_value`;
2409
+ }
2410
+ return prefixed;
2411
+ }
2412
+ function applyNamingStyle(input, style) {
2413
+ const words = splitWords(input);
2414
+ if (words.length === 0) {
2415
+ return "";
2416
+ }
2417
+ switch (style) {
2418
+ case "preserve":
2419
+ return input;
2420
+ case "camel": {
2421
+ const [first, ...rest] = words;
2422
+ return `${first.toLowerCase()}${rest.map(capitalize).join("")}`;
2423
+ }
2424
+ case "pascal":
2425
+ return words.map(capitalize).join("");
2426
+ case "snake":
2427
+ return words.map((word) => word.toLowerCase()).join("_");
2428
+ case "kebab":
2429
+ return words.map((word) => word.toLowerCase()).join("-");
2430
+ default:
2431
+ return input;
2432
+ }
2433
+ }
2434
+ function toSafeIdentifier(input, style, fallback = "value") {
2435
+ const transformed = applyNamingStyle(input, style);
2436
+ const normalized = transformed.replace(/[^A-Za-z0-9_$]+/g, "_").replace(/^_+|_+$/g, "");
2437
+ const fallbackValue = splitWords(input).join("");
2438
+ return ensureValidIdentifier(
2439
+ normalized.length > 0 ? normalized : fallbackValue,
2440
+ fallback
2441
+ );
2442
+ }
2443
+ function escapeTypePropertyName(propertyName) {
2444
+ if (IDENTIFIER_PATTERN.test(propertyName) && !RESERVED_IDENTIFIERS.has(propertyName)) {
2445
+ return propertyName;
2446
+ }
2447
+ return `'${propertyName.replace(/\\/g, "\\\\").replace(/'/g, "\\'")}'`;
2448
+ }
2449
+ function escapeStringLiteral(value) {
2450
+ return `'${value.replace(/\\/g, "\\\\").replace(/'/g, "\\'")}'`;
2451
+ }
2452
+
2453
+ // src/generator/placeholders.ts
2454
+ function createStyleTokens(prefix, value) {
2455
+ return {
2456
+ [prefix]: value,
2457
+ [`${prefix}_camel`]: applyNamingStyle(value, "camel"),
2458
+ [`${prefix}_pascal`]: applyNamingStyle(value, "pascal"),
2459
+ [`${prefix}_snake`]: applyNamingStyle(value, "snake"),
2460
+ [`${prefix}_kebab`]: applyNamingStyle(value, "kebab")
2461
+ };
2462
+ }
2463
+ function renderTemplate(template, tokenMap) {
2464
+ return template.replace(/\{([A-Za-z0-9_]+)\}/g, (_match, token) => {
2465
+ if (!(token in tokenMap)) {
2466
+ throw new Error(`Unknown placeholder token "${token}" in template "${template}"`);
2467
+ }
2468
+ return tokenMap[token];
2469
+ });
2470
+ }
2471
+ function resolvePlaceholderMap(baseTokens, outputConfig) {
2472
+ const resolved = {
2473
+ ...baseTokens
2474
+ };
2475
+ const entries = Object.entries(outputConfig.placeholderMap ?? {});
2476
+ for (let index = 0; index < entries.length; index += 1) {
2477
+ const [key, value] = entries[index];
2478
+ let current = value;
2479
+ for (let depth = 0; depth < 8; depth += 1) {
2480
+ const next = renderTemplate(current, resolved);
2481
+ if (next === current) {
2482
+ break;
2483
+ }
2484
+ current = next;
2485
+ }
2486
+ resolved[key] = current;
2487
+ }
2488
+ return resolved;
2489
+ }
2490
+ function renderOutputPath(template, context, outputConfig) {
2491
+ const baseTokens = {
2492
+ provider: context.provider,
2493
+ kind: context.kind,
2494
+ ...createStyleTokens("database", context.database),
2495
+ ...createStyleTokens("schema", context.schema),
2496
+ ...createStyleTokens("model", context.model)
2497
+ };
2498
+ const tokens = resolvePlaceholderMap(baseTokens, outputConfig);
2499
+ return renderTemplate(template, tokens);
2500
+ }
2501
+
2502
+ // src/generator/postgres-type-mapping.ts
2503
+ var NUMBER_TYPES = /* @__PURE__ */ new Set([
2504
+ "int2",
2505
+ "int4",
2506
+ "float4",
2507
+ "float8",
2508
+ "smallint",
2509
+ "integer",
2510
+ "real",
2511
+ "double precision"
2512
+ ]);
2513
+ var STRING_NUMERIC_TYPES = /* @__PURE__ */ new Set([
2514
+ "int8",
2515
+ "bigint",
2516
+ "serial8",
2517
+ "bigserial",
2518
+ "numeric",
2519
+ "decimal",
2520
+ "money"
2521
+ ]);
2522
+ var TEXT_TYPES = /* @__PURE__ */ new Set([
2523
+ "char",
2524
+ "bpchar",
2525
+ "varchar",
2526
+ "character varying",
2527
+ "text",
2528
+ "citext",
2529
+ "name",
2530
+ "uuid",
2531
+ "date",
2532
+ "time",
2533
+ "timetz",
2534
+ "timestamp",
2535
+ "timestamptz",
2536
+ "interval",
2537
+ "inet",
2538
+ "cidr",
2539
+ "macaddr",
2540
+ "macaddr8",
2541
+ "point",
2542
+ "line",
2543
+ "lseg",
2544
+ "box",
2545
+ "path",
2546
+ "polygon",
2547
+ "circle",
2548
+ "bit",
2549
+ "varbit",
2550
+ "xml",
2551
+ "tsvector",
2552
+ "tsquery",
2553
+ "jsonpath"
2554
+ ]);
2555
+ function normalizeTypeLabel(column) {
2556
+ const preferred = (column.udtName || column.dataType).toLowerCase().trim();
2557
+ if (column.arrayDimensions > 0 && preferred.startsWith("_")) {
2558
+ return preferred.slice(1);
2559
+ }
2560
+ return preferred;
2561
+ }
2562
+ function wrapArrayType(baseType, depth) {
2563
+ let wrapped = baseType;
2564
+ for (let index = 0; index < depth; index += 1) {
2565
+ wrapped = `Array<${wrapped}>`;
2566
+ }
2567
+ return wrapped;
2568
+ }
2569
+ function resolveScalarType(column) {
2570
+ const label = normalizeTypeLabel(column);
2571
+ if (NUMBER_TYPES.has(label)) {
2572
+ return "number";
2573
+ }
2574
+ if (STRING_NUMERIC_TYPES.has(label)) {
2575
+ return "string";
2576
+ }
2577
+ if (label === "bool" || label === "boolean") {
2578
+ return "boolean";
2579
+ }
2580
+ if (label === "bytea") {
2581
+ return "Buffer";
2582
+ }
2583
+ if (label === "json" || label === "jsonb") {
2584
+ return "Record<string, unknown>";
2585
+ }
2586
+ if (TEXT_TYPES.has(label)) {
2587
+ return "string";
2588
+ }
2589
+ if (label.endsWith("range") || label.endsWith("multirange")) {
2590
+ return "string";
2591
+ }
2592
+ return "unknown";
2593
+ }
2594
+ function resolveKindAwareType(column) {
2595
+ if (column.typeKind === "enum") {
2596
+ const values = column.enumValues ?? [];
2597
+ if (values.length === 0) {
2598
+ return "string";
2599
+ }
2600
+ return values.map((value) => `'${value.replace(/\\/g, "\\\\").replace(/'/g, "\\'")}'`).join(" | ");
2601
+ }
2602
+ if (column.typeKind === "composite") {
2603
+ return "Record<string, unknown>";
2604
+ }
2605
+ if (column.typeKind === "domain" || column.typeKind === "range" || column.typeKind === "multirange") {
2606
+ return "string";
2607
+ }
2608
+ return resolveScalarType(column);
2609
+ }
2610
+ function resolvePostgresColumnType(column) {
2611
+ const baseType = resolveKindAwareType(column);
2612
+ if (!column.arrayDimensions || column.arrayDimensions <= 0) {
2613
+ return baseType;
2614
+ }
2615
+ return wrapArrayType(baseType, column.arrayDimensions);
2616
+ }
2617
+
2618
+ // src/generator/renderer.ts
2619
+ function normalizePath(pathValue) {
2620
+ return pathValue.replace(/\\/g, "/");
2621
+ }
2622
+ function withoutTypeScriptExtension(pathValue) {
2623
+ return pathValue.replace(/\.tsx?$/i, "");
2624
+ }
2625
+ function toModuleImportPath(fromFile, targetFile) {
2626
+ const relativePath = withoutTypeScriptExtension(
2627
+ normalizePath(path.posix.relative(path.posix.dirname(fromFile), targetFile))
2628
+ );
2629
+ return relativePath.startsWith(".") ? relativePath : `./${relativePath}`;
2630
+ }
2631
+ function renderObjectKey(key) {
2632
+ const escaped = escapeTypePropertyName(key);
2633
+ return escaped.startsWith("'") ? escaped : escaped;
2634
+ }
2635
+ function renderRelation(relation) {
2636
+ const through = relation.through ? `,
2637
+ through: {
2638
+ schema: ${escapeStringLiteral(relation.through.schema)},
2639
+ model: ${escapeStringLiteral(relation.through.model)},
2640
+ sourceColumns: [${relation.through.sourceColumns.map((value) => escapeStringLiteral(value)).join(", ")}],
2641
+ targetColumns: [${relation.through.targetColumns.map((value) => escapeStringLiteral(value)).join(", ")}],
2642
+ }` : "";
2643
+ return `{
2644
+ kind: ${escapeStringLiteral(relation.kind)},
2645
+ sourceColumns: [${relation.sourceColumns.map((value) => escapeStringLiteral(value)).join(", ")}],
2646
+ targetSchema: ${escapeStringLiteral(relation.targetSchema)},
2647
+ targetModel: ${escapeStringLiteral(relation.targetModel)},
2648
+ targetColumns: [${relation.targetColumns.map((value) => escapeStringLiteral(value)).join(", ")}]${through}
2649
+ }`;
2650
+ }
2651
+ function renderModelArtifact(snapshot, descriptor, config) {
2652
+ const columnLines = Object.values(descriptor.table.columns).map((column) => {
2653
+ const propertyName = escapeTypePropertyName(column.name);
2654
+ const baseType = resolvePostgresColumnType(column);
2655
+ const isOptional = column.isNullable;
2656
+ const typeWithNullability = column.isNullable ? `${baseType} | null` : baseType;
2657
+ return ` ${propertyName}${isOptional ? "?" : ""}: ${typeWithNullability}`;
2658
+ }).join("\n");
2659
+ const nullableLines = Object.values(descriptor.table.columns).map((column) => ` ${renderObjectKey(column.name)}: ${column.isNullable ? "true" : "false"}`).join(",\n");
2660
+ const relationEntries = Object.entries(descriptor.table.relations);
2661
+ const relationBlock = config.features.emitRelations && relationEntries.length > 0 ? `,
2662
+ relations: {
2663
+ ${relationEntries.map(([relationKey2, relationValue]) => ` ${renderObjectKey(relationKey2)}: ${renderRelation(relationValue)}`).join(",\n")}
2664
+ }` : "";
2665
+ const content = `import { defineModel } from '@xylex-group/athena'
2666
+
2667
+ export interface ${descriptor.rowTypeName} {
2668
+ ${columnLines}
2669
+ }
2670
+
2671
+ export type ${descriptor.insertTypeName} = Partial<${descriptor.rowTypeName}>
2672
+ export type ${descriptor.updateTypeName} = Partial<${descriptor.insertTypeName}>
2673
+
2674
+ export const ${descriptor.modelConstName} = defineModel<${descriptor.rowTypeName}, ${descriptor.insertTypeName}, ${descriptor.updateTypeName}>({
2675
+ meta: {
2676
+ database: ${escapeStringLiteral(snapshot.database)},
2677
+ schema: ${escapeStringLiteral(descriptor.schemaName)},
2678
+ model: ${escapeStringLiteral(descriptor.tableName)},
2679
+ tableName: ${escapeStringLiteral(`${descriptor.schemaName}.${descriptor.tableName}`)},
2680
+ primaryKey: [${descriptor.table.primaryKey.map((value) => escapeStringLiteral(value)).join(", ")}],
2681
+ nullable: {
2682
+ ${nullableLines}
2683
+ }${relationBlock}
2684
+ }
2685
+ })
2686
+ `;
2687
+ return {
2688
+ kind: "model",
2689
+ path: descriptor.filePath,
2690
+ content
2691
+ };
2692
+ }
2693
+ function renderSchemaArtifact(descriptor) {
2694
+ const importLines = descriptor.models.map((modelDescriptor) => {
2695
+ const importPath = toModuleImportPath(descriptor.filePath, modelDescriptor.filePath);
2696
+ return `import { ${modelDescriptor.modelConstName} } from '${importPath}'`;
2697
+ }).join("\n");
2698
+ const modelEntries = descriptor.models.map((modelDescriptor) => ` ${renderObjectKey(modelDescriptor.tableName)}: ${modelDescriptor.modelConstName}`).join(",\n");
2699
+ const content = `import { defineSchema } from '@xylex-group/athena'
2700
+ ${importLines ? `
2701
+ ${importLines}
2702
+ ` : "\n"}
2703
+ export const ${descriptor.schemaConstName} = defineSchema({
2704
+ ${modelEntries}
2705
+ })
2706
+ `;
2707
+ return {
2708
+ kind: "schema",
2709
+ path: descriptor.filePath,
2710
+ content
2711
+ };
2712
+ }
2713
+ function renderDatabaseArtifact(descriptor) {
2714
+ const importLines = descriptor.schemas.map((schemaDescriptor) => {
2715
+ const importPath = toModuleImportPath(descriptor.filePath, schemaDescriptor.filePath);
2716
+ return `import { ${schemaDescriptor.schemaConstName} } from '${importPath}'`;
2717
+ }).join("\n");
2718
+ const schemaEntries = descriptor.schemas.map((schemaDescriptor) => ` ${renderObjectKey(schemaDescriptor.schemaName)}: ${schemaDescriptor.schemaConstName}`).join(",\n");
2719
+ const content = `import { defineDatabase } from '@xylex-group/athena'
2720
+ ${importLines ? `
2721
+ ${importLines}
2722
+ ` : "\n"}
2723
+ export const ${descriptor.databaseConstName} = defineDatabase({
2724
+ ${schemaEntries}
2725
+ })
2726
+ `;
2727
+ return {
2728
+ kind: "database",
2729
+ path: descriptor.filePath,
2730
+ content
2731
+ };
2732
+ }
2733
+ function renderRegistryArtifact(registryPath, databasePath, databaseConstName, registryConstName, databaseName) {
2734
+ const databaseImportPath = toModuleImportPath(registryPath, databasePath);
2735
+ const content = `import { defineRegistry } from '@xylex-group/athena'
2736
+ import { ${databaseConstName} } from '${databaseImportPath}'
2737
+
2738
+ export const ${registryConstName} = defineRegistry({
2739
+ ${renderObjectKey(databaseName)}: ${databaseConstName}
2740
+ })
2741
+ `;
2742
+ return {
2743
+ kind: "registry",
2744
+ path: registryPath,
2745
+ content
2746
+ };
2747
+ }
2748
+ function assertNoDuplicatePaths(files) {
2749
+ const seen = /* @__PURE__ */ new Set();
2750
+ for (const file of files) {
2751
+ if (seen.has(file.path)) {
2752
+ throw new Error(`Generator output collision detected for path: ${file.path}`);
2753
+ }
2754
+ seen.add(file.path);
2755
+ }
2756
+ }
2757
+ var ArtifactComposer = class {
2758
+ constructor(snapshot, config) {
2759
+ this.snapshot = snapshot;
2760
+ this.config = config;
2761
+ }
2762
+ compose() {
2763
+ const providerName = this.snapshot.backend;
2764
+ const databaseName = this.snapshot.database;
2765
+ const modelDescriptors = [];
2766
+ for (const schemaName of Object.keys(this.snapshot.schemas).sort()) {
2767
+ const schema = this.snapshot.schemas[schemaName];
2768
+ for (const tableName of Object.keys(schema.tables).sort()) {
2769
+ const table = schema.tables[tableName];
2770
+ const rowTypeName = `${toSafeIdentifier(`${schemaName} ${tableName}`, this.config.naming.modelType, "Model")}Row`;
2771
+ const insertTypeName = `${toSafeIdentifier(`${schemaName} ${tableName}`, this.config.naming.modelType, "Model")}Insert`;
2772
+ const updateTypeName = `${toSafeIdentifier(`${schemaName} ${tableName}`, this.config.naming.modelType, "Model")}Update`;
2773
+ const modelConstName = `${toSafeIdentifier(`${schemaName} ${tableName} model`, this.config.naming.modelConst, "model")}`;
2774
+ const modelPath = normalizePath(
2775
+ renderOutputPath(this.config.output.targets.model, {
2776
+ provider: providerName,
2777
+ kind: "model",
2778
+ database: databaseName,
2779
+ schema: schemaName,
2780
+ model: tableName
2781
+ }, this.config.output)
2782
+ );
2783
+ modelDescriptors.push({
2784
+ schemaName,
2785
+ tableName,
2786
+ filePath: modelPath,
2787
+ rowTypeName,
2788
+ insertTypeName,
2789
+ updateTypeName,
2790
+ modelConstName,
2791
+ table
2792
+ });
2793
+ }
2794
+ }
2795
+ const schemaDescriptors = Object.keys(this.snapshot.schemas).sort().map((schemaName) => {
2796
+ const schemaPath = normalizePath(
2797
+ renderOutputPath(this.config.output.targets.schema, {
2798
+ provider: providerName,
2799
+ kind: "schema",
2800
+ database: databaseName,
2801
+ schema: schemaName,
2802
+ model: "index"
2803
+ }, this.config.output)
2804
+ );
2805
+ return {
2806
+ schemaName,
2807
+ filePath: schemaPath,
2808
+ schemaConstName: toSafeIdentifier(
2809
+ `${schemaName} schema`,
2810
+ this.config.naming.schemaConst,
2811
+ "schema"
2812
+ ),
2813
+ models: modelDescriptors.filter((model) => model.schemaName === schemaName)
2814
+ };
2815
+ });
2816
+ const databasePath = normalizePath(
2817
+ renderOutputPath(this.config.output.targets.database, {
2818
+ provider: providerName,
2819
+ kind: "database",
2820
+ database: databaseName,
2821
+ schema: "index",
2822
+ model: "index"
2823
+ }, this.config.output)
2824
+ );
2825
+ const databaseDescriptor = {
2826
+ filePath: databasePath,
2827
+ databaseConstName: toSafeIdentifier(
2828
+ `${databaseName} database`,
2829
+ this.config.naming.databaseConst,
2830
+ "database"
2831
+ ),
2832
+ schemas: schemaDescriptors
2833
+ };
2834
+ const files = [];
2835
+ for (const modelDescriptor of modelDescriptors) {
2836
+ files.push(renderModelArtifact(this.snapshot, modelDescriptor, this.config));
2837
+ }
2838
+ for (const schemaDescriptor of schemaDescriptors) {
2839
+ files.push(renderSchemaArtifact(schemaDescriptor));
2840
+ }
2841
+ files.push(renderDatabaseArtifact(databaseDescriptor));
2842
+ if (this.config.features.emitRegistry) {
2843
+ const registryPath = normalizePath(
2844
+ renderOutputPath(this.config.output.targets.registry, {
2845
+ provider: providerName,
2846
+ kind: "registry",
2847
+ database: databaseName,
2848
+ schema: "index",
2849
+ model: "index"
2850
+ }, this.config.output)
2851
+ );
2852
+ files.push(
2853
+ renderRegistryArtifact(
2854
+ registryPath,
2855
+ databaseDescriptor.filePath,
2856
+ databaseDescriptor.databaseConstName,
2857
+ toSafeIdentifier("registry", this.config.naming.registryConst, "registry"),
2858
+ databaseName
2859
+ )
2860
+ );
2861
+ }
2862
+ assertNoDuplicatePaths(files);
2863
+ return {
2864
+ snapshot: this.snapshot,
2865
+ files
2866
+ };
2867
+ }
2868
+ };
2869
+ function generateArtifactsFromSnapshot(snapshot, config) {
2870
+ const normalizedConfig = "naming" in config && "features" in config && "experimental" in config ? config : normalizeGeneratorConfig(config);
2871
+ return new ArtifactComposer(snapshot, normalizedConfig).compose();
2872
+ }
2873
+
2874
+ // src/generator/providers.ts
2875
+ var AthenaGatewayCatalogClient = class {
2876
+ constructor(client) {
2877
+ this.client = client;
2878
+ }
2879
+ async queryRows(query) {
2880
+ const result = await this.client.query(query);
2881
+ if (result.error || result.status < 200 || result.status >= 300) {
2882
+ throw new Error(result.error ?? `Gateway query failed with status ${result.status}`);
2883
+ }
2884
+ return result.data ?? [];
2885
+ }
2886
+ async queryColumns(query) {
2887
+ return this.queryRows(query);
2888
+ }
2889
+ async queryEnums(query) {
2890
+ const rows = await this.queryRows(query);
2891
+ const enumMap = /* @__PURE__ */ new Map();
2892
+ for (const row of rows) {
2893
+ const existing = enumMap.get(row.type_oid) ?? [];
2894
+ existing.push(row.enum_label);
2895
+ enumMap.set(row.type_oid, existing);
2896
+ }
2897
+ return enumMap;
2898
+ }
2899
+ async queryPrimaryKeys(query) {
2900
+ return this.queryRows(query);
2901
+ }
2902
+ async queryForeignKeys(query) {
2903
+ return this.queryRows(query);
2904
+ }
2905
+ };
2906
+ var AthenaGatewayPostgresIntrospectionProvider = class {
2907
+ constructor(config) {
2908
+ this.config = config;
2909
+ this.client = createClient(this.config.gatewayUrl, this.config.apiKey, {
2910
+ backend: {
2911
+ type: this.config.backend ?? "postgresql"
2912
+ }
2913
+ });
2914
+ }
2915
+ backend = "postgresql";
2916
+ client;
2917
+ async inspect(options) {
2918
+ const schemas = options?.schemas && options.schemas.length > 0 ? options.schemas : this.config.schemas && this.config.schemas.length > 0 ? this.config.schemas : ["public"];
2919
+ const catalogClient = new AthenaGatewayCatalogClient(this.client);
2920
+ const queries = buildGatewayCatalogQueries(schemas);
2921
+ const [columnRows, enumMap, primaryKeyRows, foreignKeyRows] = await Promise.all([
2922
+ catalogClient.queryColumns(queries.columns),
2923
+ catalogClient.queryEnums(queries.enums),
2924
+ catalogClient.queryPrimaryKeys(queries.primaryKeys),
2925
+ catalogClient.queryForeignKeys(queries.foreignKeys)
2926
+ ]);
2927
+ const assembler = new PostgresCatalogSnapshotAssembler();
2928
+ assembler.addColumnRows(columnRows, enumMap);
2929
+ assembler.addPrimaryKeyRows(primaryKeyRows);
2930
+ assembler.addForeignKeyRows(foreignKeyRows);
2931
+ assembler.addManyToManyRows(foreignKeyRows);
2932
+ return {
2933
+ backend: "postgresql",
2934
+ database: this.config.database,
2935
+ generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
2936
+ schemas: assembler.toSchemas()
2937
+ };
2938
+ }
2939
+ };
2940
+ var ScyllaIntrospectionProvider = class {
2941
+ constructor(config) {
2942
+ this.config = config;
2943
+ }
2944
+ backend = "scylladb";
2945
+ async inspect() {
2946
+ throw new Error(
2947
+ `Scylla introspection provider is not implemented yet for keyspace ${this.config.keyspace}.`
2948
+ );
2949
+ }
2950
+ };
2951
+ function createPostgresProvider(config) {
2952
+ return createPostgresIntrospectionProvider({
2953
+ connectionString: config.connectionString,
2954
+ database: config.database
2955
+ });
2956
+ }
2957
+ function resolveGeneratorProvider(providerConfig, experimentalFlags) {
2958
+ if (providerConfig.kind === "postgres" && providerConfig.mode === "direct") {
2959
+ return createPostgresProvider(providerConfig);
2960
+ }
2961
+ if (providerConfig.kind === "postgres" && providerConfig.mode === "gateway") {
2962
+ return new AthenaGatewayPostgresIntrospectionProvider(providerConfig);
2963
+ }
2964
+ if (providerConfig.kind === "scylla") {
2965
+ if (!experimentalFlags.scyllaProviderContracts) {
2966
+ throw new Error(
2967
+ "Scylla provider contracts are disabled. Set experimental.scyllaProviderContracts=true to enable placeholders."
2968
+ );
2969
+ }
2970
+ return new ScyllaIntrospectionProvider(providerConfig);
2971
+ }
2972
+ throw new Error(`Unsupported generator provider kind: ${providerConfig.kind ?? "unknown"}`);
2973
+ }
2974
+ function extractProviderSchemas(providerConfig) {
2975
+ if (!("schemas" in providerConfig) || !providerConfig.schemas || providerConfig.schemas.length === 0) {
2976
+ return void 0;
2977
+ }
2978
+ return providerConfig.schemas;
2979
+ }
2980
+ async function writeArtifacts(files, cwd) {
2981
+ const writtenFiles = [];
2982
+ for (const file of files) {
2983
+ const absolutePath = path.resolve(cwd, file.path);
2984
+ await promises.mkdir(path.dirname(absolutePath), { recursive: true });
2985
+ await promises.writeFile(absolutePath, file.content, "utf8");
2986
+ writtenFiles.push(file.path);
2987
+ }
2988
+ return writtenFiles;
2989
+ }
2990
+ async function runSchemaGenerator(options = {}) {
2991
+ const cwd = options.cwd ?? process.cwd();
2992
+ const configOptions = {
2993
+ cwd,
2994
+ configPath: options.configPath
2995
+ };
2996
+ const { configPath, config } = await loadGeneratorConfig(configOptions);
2997
+ const provider = options.provider ?? resolveGeneratorProvider(config.provider, config.experimental);
2998
+ const snapshot = await provider.inspect({
2999
+ schemas: extractProviderSchemas(config.provider)
3000
+ });
3001
+ const generated = generateArtifactsFromSnapshot(snapshot, config);
3002
+ const writtenFiles = options.dryRun ? [] : await writeArtifacts(generated.files, cwd);
3003
+ return {
3004
+ ...generated,
3005
+ configPath,
3006
+ writtenFiles
3007
+ };
3008
+ }
3009
+
1490
3010
  exports.AthenaClient = AthenaClient;
3011
+ exports.AthenaError = AthenaError;
3012
+ exports.AthenaErrorCategory = AthenaErrorCategory;
3013
+ exports.AthenaErrorCode = AthenaErrorCode;
3014
+ exports.AthenaErrorKind = AthenaErrorKind;
1491
3015
  exports.AthenaGatewayError = AthenaGatewayError;
1492
3016
  exports.Backend = Backend;
1493
3017
  exports.assertInt = assertInt;
1494
3018
  exports.coerceInt = coerceInt;
1495
3019
  exports.createClient = createClient;
3020
+ exports.createPostgresIntrospectionProvider = createPostgresIntrospectionProvider;
3021
+ exports.createTypedClient = createTypedClient;
3022
+ exports.defineDatabase = defineDatabase;
3023
+ exports.defineGeneratorConfig = defineGeneratorConfig;
3024
+ exports.defineModel = defineModel;
3025
+ exports.defineRegistry = defineRegistry;
3026
+ exports.defineSchema = defineSchema;
3027
+ exports.findGeneratorConfigPath = findGeneratorConfigPath;
3028
+ exports.generateArtifactsFromSnapshot = generateArtifactsFromSnapshot;
3029
+ exports.identifier = identifier;
1496
3030
  exports.isAthenaGatewayError = isAthenaGatewayError;
1497
3031
  exports.isOk = isOk;
3032
+ exports.loadGeneratorConfig = loadGeneratorConfig;
1498
3033
  exports.normalizeAthenaError = normalizeAthenaError;
3034
+ exports.normalizeGeneratorConfig = normalizeGeneratorConfig;
1499
3035
  exports.requireAffected = requireAffected;
1500
3036
  exports.requireSuccess = requireSuccess;
3037
+ exports.resolveGeneratorProvider = resolveGeneratorProvider;
3038
+ exports.resolvePostgresColumnType = resolvePostgresColumnType;
3039
+ exports.runSchemaGenerator = runSchemaGenerator;
1501
3040
  exports.unwrap = unwrap;
1502
3041
  exports.unwrapOne = unwrapOne;
1503
3042
  exports.unwrapRows = unwrapRows;