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