@xylex-group/athena 2.0.0 → 2.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. package/README.md +47 -26
  2. package/dist/browser.cjs +3319 -0
  3. package/dist/browser.cjs.map +1 -0
  4. package/dist/browser.d.cts +25 -0
  5. package/dist/browser.d.ts +25 -0
  6. package/dist/browser.js +3276 -0
  7. package/dist/browser.js.map +1 -0
  8. package/dist/cli/index.cjs +599 -119
  9. package/dist/cli/index.cjs.map +1 -1
  10. package/dist/cli/index.d.cts +3 -2
  11. package/dist/cli/index.d.ts +3 -2
  12. package/dist/cli/index.js +600 -120
  13. package/dist/cli/index.js.map +1 -1
  14. package/dist/client-BX0NQqOn.d.ts +435 -0
  15. package/dist/client-dpAp-NZK.d.cts +435 -0
  16. package/dist/cookies.cjs +890 -0
  17. package/dist/cookies.cjs.map +1 -0
  18. package/dist/cookies.d.cts +174 -0
  19. package/dist/cookies.d.ts +174 -0
  20. package/dist/cookies.js +869 -0
  21. package/dist/cookies.js.map +1 -0
  22. package/dist/index.cjs +1378 -1023
  23. package/dist/index.cjs.map +1 -1
  24. package/dist/index.d.cts +8 -408
  25. package/dist/index.d.ts +8 -408
  26. package/dist/index.js +1379 -1024
  27. package/dist/index.js.map +1 -1
  28. package/dist/{model-form-CVOtC8jq.d.ts → model-form-2hqmoOUX.d.ts} +2 -2
  29. package/dist/{model-form-hXkvHS_3.d.cts → model-form-Cy-zaO0u.d.cts} +2 -2
  30. package/dist/pipeline-BOPszLsL.d.ts +8 -0
  31. package/dist/pipeline-E3FDbs4W.d.cts +8 -0
  32. package/dist/react.d.cts +4 -4
  33. package/dist/react.d.ts +4 -4
  34. package/dist/{types-BnzoaNRC.d.cts → types-BaBzjwXr.d.cts} +1 -1
  35. package/dist/{types-BnzoaNRC.d.ts → types-BaBzjwXr.d.ts} +1 -1
  36. package/dist/{pipeline-CQgV-Yfo.d.ts → types-CeBPrnGj.d.ts} +2 -7
  37. package/dist/{pipeline-C-cN0ACi.d.cts → types-CpqL-pZx.d.cts} +2 -7
  38. package/package.json +21 -1
package/dist/index.js CHANGED
@@ -1,5 +1,4 @@
1
- import { Pool } from 'pg';
2
- import { existsSync } from 'fs';
1
+ import { existsSync, readFileSync } from 'fs';
3
2
  import { resolve, dirname, posix } from 'path';
4
3
  import { pathToFileURL } from 'url';
5
4
  import { mkdir, writeFile } from 'fs/promises';
@@ -1539,460 +1538,588 @@ function createAuthClient(config = {}) {
1539
1538
  };
1540
1539
  }
1541
1540
 
1542
- // src/client.ts
1543
- var DEFAULT_COLUMNS = "*";
1544
- 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;
1545
- var SAFE_CAST_PATTERN = /^[a-z_][a-z0-9_]*(?:\[\])?$/i;
1546
- function formatResult(response) {
1547
- const result = {
1548
- data: response.data ?? null,
1549
- error: response.error ?? null,
1550
- errorDetails: response.errorDetails ?? null,
1551
- status: response.status,
1552
- raw: response.raw
1553
- };
1554
- if (response.count !== void 0) {
1555
- result.count = response.count;
1541
+ // src/auxiliaries.ts
1542
+ var AthenaErrorKind = {
1543
+ UniqueViolation: "unique_violation",
1544
+ NotFound: "not_found",
1545
+ Validation: "validation",
1546
+ Auth: "auth",
1547
+ RateLimit: "rate_limit",
1548
+ Transient: "transient",
1549
+ Unknown: "unknown"
1550
+ };
1551
+ var AthenaErrorCode = {
1552
+ UniqueViolation: "UNIQUE_VIOLATION",
1553
+ NotFound: "NOT_FOUND",
1554
+ ValidationFailed: "VALIDATION_FAILED",
1555
+ AuthUnauthorized: "AUTH_UNAUTHORIZED",
1556
+ AuthForbidden: "AUTH_FORBIDDEN",
1557
+ RateLimited: "RATE_LIMITED",
1558
+ NetworkUnavailable: "NETWORK_UNAVAILABLE",
1559
+ TransientFailure: "TRANSIENT_FAILURE",
1560
+ HttpFailure: "HTTP_FAILURE",
1561
+ Unknown: "UNKNOWN"
1562
+ };
1563
+ var AthenaErrorCategory = {
1564
+ Transport: "transport",
1565
+ Client: "client",
1566
+ Server: "server",
1567
+ Database: "database",
1568
+ Unknown: "unknown"
1569
+ };
1570
+ function parseBooleanFlag(rawValue, fallback) {
1571
+ if (!rawValue) return fallback;
1572
+ const normalized = rawValue.trim().toLowerCase();
1573
+ if (normalized === "1" || normalized === "true" || normalized === "yes" || normalized === "on") {
1574
+ return true;
1556
1575
  }
1557
- return result;
1576
+ if (normalized === "0" || normalized === "false" || normalized === "no" || normalized === "off") {
1577
+ return false;
1578
+ }
1579
+ return fallback;
1558
1580
  }
1559
- function toSingleResult(response) {
1560
- const payload = response.data;
1561
- const singleData = Array.isArray(payload) ? payload.length ? payload[0] : null : payload ?? null;
1562
- return {
1563
- ...response,
1564
- data: singleData
1565
- };
1581
+ var AthenaError = class extends Error {
1582
+ code;
1583
+ kind;
1584
+ category;
1585
+ status;
1586
+ retryable;
1587
+ requestId;
1588
+ context;
1589
+ raw;
1590
+ constructor(input) {
1591
+ super(input.message);
1592
+ this.name = "AthenaError";
1593
+ this.code = input.code;
1594
+ this.kind = input.kind;
1595
+ this.category = input.category;
1596
+ this.status = input.status;
1597
+ this.retryable = input.retryable ?? false;
1598
+ this.requestId = input.requestId;
1599
+ this.context = input.context;
1600
+ this.raw = input.raw;
1601
+ }
1602
+ };
1603
+ function isRecord3(value) {
1604
+ return Boolean(value) && typeof value === "object" && !Array.isArray(value);
1566
1605
  }
1567
- function mergeOptions(...options) {
1568
- return options.reduce((acc, next) => {
1569
- if (!next) return acc;
1570
- return { ...acc, ...next };
1571
- }, void 0);
1606
+ function isAthenaErrorKind(value) {
1607
+ return value === "unique_violation" || value === "not_found" || value === "validation" || value === "auth" || value === "rate_limit" || value === "transient" || value === "unknown";
1572
1608
  }
1573
- function asAthenaJsonObject(value) {
1574
- return value;
1609
+ function isAthenaErrorCode(value) {
1610
+ return value === "UNIQUE_VIOLATION" || value === "NOT_FOUND" || value === "VALIDATION_FAILED" || value === "AUTH_UNAUTHORIZED" || value === "AUTH_FORBIDDEN" || value === "RATE_LIMITED" || value === "NETWORK_UNAVAILABLE" || value === "TRANSIENT_FAILURE" || value === "HTTP_FAILURE" || value === "UNKNOWN";
1575
1611
  }
1576
- function asAthenaJsonObjectArray(values) {
1577
- return values;
1612
+ function isAthenaErrorCategory(value) {
1613
+ return value === "transport" || value === "client" || value === "server" || value === "database" || value === "unknown";
1578
1614
  }
1579
- function createMutationQuery(executor, defaultColumns = DEFAULT_COLUMNS) {
1580
- let selectedColumns = defaultColumns === null ? void 0 : defaultColumns;
1581
- let selectedOptions;
1582
- let promise = null;
1583
- const run = (columns, options) => {
1584
- const payloadColumns = columns ?? selectedColumns;
1585
- const payloadOptions = options ?? selectedOptions;
1586
- if (!promise) {
1587
- promise = executor(payloadColumns, payloadOptions);
1588
- }
1589
- return promise;
1590
- };
1591
- const mutationQuery = {
1592
- select(columns = selectedColumns, options) {
1593
- selectedColumns = columns;
1594
- selectedOptions = options ?? selectedOptions;
1595
- return run(columns, options);
1596
- },
1597
- returning(columns = selectedColumns, options) {
1598
- return mutationQuery.select(columns, options);
1599
- },
1600
- single(columns = selectedColumns, options) {
1601
- selectedColumns = columns;
1602
- selectedOptions = options ?? selectedOptions;
1603
- return run(columns, options).then(toSingleResult);
1604
- },
1605
- maybeSingle(columns = selectedColumns, options) {
1606
- return mutationQuery.single(columns, options);
1607
- },
1608
- then(onfulfilled, onrejected) {
1609
- return run(selectedColumns, selectedOptions).then(onfulfilled, onrejected);
1610
- },
1611
- catch(onrejected) {
1612
- return run(selectedColumns, selectedOptions).catch(onrejected);
1613
- },
1614
- finally(onfinally) {
1615
- return run(selectedColumns, selectedOptions).finally(onfinally);
1616
- }
1615
+ function isNormalizedAthenaError(value) {
1616
+ return isRecord3(value) && isAthenaErrorKind(value.kind) && isAthenaErrorCode(value.code) && isAthenaErrorCategory(value.category) && typeof value.retryable === "boolean" && typeof value.message === "string" && "raw" in value;
1617
+ }
1618
+ function withContextOverrides(normalized, context) {
1619
+ if (!context?.table && !context?.operation) {
1620
+ return normalized;
1621
+ }
1622
+ return {
1623
+ ...normalized,
1624
+ table: context.table ?? normalized.table,
1625
+ operation: context.operation ?? normalized.operation
1617
1626
  };
1618
- return mutationQuery;
1619
1627
  }
1620
- function getResourceId(state) {
1621
- const candidate = state.conditions.find(
1622
- (condition) => condition.operator === "eq" && (condition.column === "resource_id" || condition.column === "id")
1623
- );
1624
- return candidate?.value?.toString();
1628
+ function resolveAttachedNormalizedError(resultOrError) {
1629
+ if (!isRecord3(resultOrError)) return void 0;
1630
+ if (!("__athenaNormalizedError" in resultOrError)) return void 0;
1631
+ const candidate = resultOrError.__athenaNormalizedError;
1632
+ return isNormalizedAthenaError(candidate) ? candidate : void 0;
1625
1633
  }
1626
- function stringifyFilterValue(value) {
1627
- if (Array.isArray(value)) {
1628
- return value.join(",");
1634
+ function safeStringify(value) {
1635
+ try {
1636
+ const serialized = JSON.stringify(value);
1637
+ return serialized ?? String(value);
1638
+ } catch {
1639
+ return "[unserializable]";
1629
1640
  }
1630
- return String(value);
1631
1641
  }
1632
- function isUuidString(value) {
1633
- return UUID_PATTERN.test(value.trim());
1642
+ function contextHint(context) {
1643
+ if (!context?.identity) return void 0;
1644
+ const identity = typeof context.identity === "string" ? context.identity : safeStringify(context.identity);
1645
+ return `Identity: ${identity}`;
1634
1646
  }
1635
- function isUuidIdentifierColumn(column) {
1636
- return column === "id" || /(?:^|_)uuid(?:_|$)/i.test(column) || /_id$/i.test(column);
1647
+ function isAthenaResultLike(value) {
1648
+ return isRecord3(value) && "status" in value && "error" in value && "data" in value && typeof value.status === "number";
1637
1649
  }
1638
- function shouldUseUuidTextComparison(column, value) {
1639
- return typeof value === "string" && isUuidString(value) && isUuidIdentifierColumn(column);
1650
+ function operationFromDetails(details) {
1651
+ if (!details?.endpoint) return void 0;
1652
+ if (details.endpoint === "/gateway/fetch" || details.endpoint === "/gateway/query") return "select";
1653
+ if (details.endpoint === "/gateway/insert") return "insert";
1654
+ if (details.endpoint === "/gateway/update") return "update";
1655
+ if (details.endpoint === "/gateway/delete") return "delete";
1656
+ if (details.endpoint === "/gateway/rpc" || details.endpoint.startsWith("/rpc/")) return "rpc";
1657
+ return void 0;
1640
1658
  }
1641
- function normalizeCast(cast) {
1642
- const normalized = cast.trim().toLowerCase();
1643
- if (!SAFE_CAST_PATTERN.test(normalized)) {
1644
- throw new Error(`Invalid cast type "${cast}"`);
1659
+ function matchRegex(input, patterns) {
1660
+ for (const pattern of patterns) {
1661
+ const match = pattern.exec(input);
1662
+ if (match?.[1]) return match[1];
1645
1663
  }
1646
- return normalized;
1647
- }
1648
- function escapeSqlStringLiteral(value) {
1649
- return value.replace(/'/g, "''");
1664
+ return void 0;
1650
1665
  }
1651
- function toSqlLiteral(value) {
1652
- if (value === null) return "NULL";
1653
- if (typeof value === "number") return Number.isFinite(value) ? String(value) : "NULL";
1654
- if (typeof value === "boolean") return value ? "TRUE" : "FALSE";
1655
- return `'${escapeSqlStringLiteral(value)}'`;
1666
+ function extractConstraint(message) {
1667
+ return matchRegex(message, [
1668
+ /unique constraint\s+["'`]([^"'`]+)["'`]/i,
1669
+ /constraint\s+["'`]([^"'`]+)["'`]/i
1670
+ ]);
1656
1671
  }
1657
- function withCast(expression, cast) {
1658
- if (!cast) return expression;
1659
- return `${expression}::${normalizeCast(cast)}`;
1672
+ function extractTable(message) {
1673
+ return matchRegex(message, [
1674
+ /(?:table|relation)\s+["'`]([^"'`]+)["'`]/i,
1675
+ /on\s+table\s+([a-zA-Z0-9_.]+)/i
1676
+ ]);
1660
1677
  }
1661
- function buildSelectColumnsClause(columns) {
1662
- if (Array.isArray(columns)) {
1663
- return columns.map((column) => quoteQualifiedIdentifier(column)).join(", ");
1678
+ function classifyKind(status, code, message) {
1679
+ const lower = message.toLowerCase();
1680
+ const hasUniquePattern = lower.includes("unique constraint") || lower.includes("duplicate key") || lower.includes("already exists") || lower.includes("duplicate");
1681
+ const hasNotFoundPattern = lower.includes("not found") || lower.includes("no rows");
1682
+ const hasValidationPattern = lower.includes("validation") || lower.includes("invalid") || lower.includes("malformed");
1683
+ const hasAuthPattern = lower.includes("unauthorized") || lower.includes("forbidden") || lower.includes("auth");
1684
+ const hasRateLimitPattern = lower.includes("rate limit") || lower.includes("too many requests");
1685
+ const hasTransientPattern = lower.includes("timeout") || lower.includes("temporar") || lower.includes("connection reset") || lower.includes("socket") || lower.includes("deadlock");
1686
+ if (status === 409 || hasUniquePattern) return "unique_violation";
1687
+ if (status === 404 || hasNotFoundPattern) return "not_found";
1688
+ if (status === 401 || status === 403 || hasAuthPattern) return "auth";
1689
+ if (status === 429 || hasRateLimitPattern) return "rate_limit";
1690
+ if (status === 400 || status === 422 || hasValidationPattern) return "validation";
1691
+ if (code === "NETWORK_ERROR" || status === 0 || status !== void 0 && status >= 500 || hasTransientPattern) {
1692
+ return "transient";
1664
1693
  }
1665
- return quoteSelectColumnsExpression(columns);
1694
+ return "unknown";
1666
1695
  }
1667
- function resolveTableNameForCall(tableName, schema) {
1668
- if (!schema) return tableName;
1669
- const normalizedSchema = schema.trim();
1670
- if (!normalizedSchema) {
1671
- throw new Error("schema option must be a non-empty string");
1696
+ function toAthenaErrorCode(kind, status, gatewayCode) {
1697
+ if (gatewayCode === "NETWORK_ERROR" || kind === "transient" && status === 0) {
1698
+ return AthenaErrorCode.NetworkUnavailable;
1672
1699
  }
1673
- if (tableName.includes(".")) {
1674
- if (tableName.startsWith(`${normalizedSchema}.`)) {
1675
- return tableName;
1676
- }
1677
- throw new Error(
1678
- `schema option "${normalizedSchema}" conflicts with schema-qualified table "${tableName}"`
1679
- );
1700
+ switch (kind) {
1701
+ case "unique_violation":
1702
+ return AthenaErrorCode.UniqueViolation;
1703
+ case "not_found":
1704
+ return AthenaErrorCode.NotFound;
1705
+ case "validation":
1706
+ return AthenaErrorCode.ValidationFailed;
1707
+ case "rate_limit":
1708
+ return AthenaErrorCode.RateLimited;
1709
+ case "auth":
1710
+ if (status === 403) {
1711
+ return AthenaErrorCode.AuthForbidden;
1712
+ }
1713
+ return AthenaErrorCode.AuthUnauthorized;
1714
+ case "transient":
1715
+ return status !== void 0 && status >= 500 ? AthenaErrorCode.HttpFailure : AthenaErrorCode.TransientFailure;
1716
+ case "unknown":
1717
+ default:
1718
+ return status !== void 0 && status >= 400 ? AthenaErrorCode.HttpFailure : AthenaErrorCode.Unknown;
1680
1719
  }
1681
- return `${normalizedSchema}.${tableName}`;
1682
1720
  }
1683
- function conditionToSqlClause(condition) {
1684
- if (!condition.column) return null;
1685
- const column = withCast(quoteQualifiedIdentifier(condition.column), condition.column_cast);
1686
- const value = condition.value;
1687
- const sqlOperator = {
1688
- eq: "=",
1689
- neq: "!=",
1690
- gt: ">",
1691
- gte: ">=",
1692
- lt: "<",
1693
- lte: "<=",
1694
- like: "LIKE",
1695
- ilike: "ILIKE"
1721
+ function toAthenaErrorCategory(kind, status) {
1722
+ if (kind === "transient" && (status === 0 || status === void 0)) {
1723
+ return AthenaErrorCategory.Transport;
1724
+ }
1725
+ if (kind === "unique_violation") return AthenaErrorCategory.Database;
1726
+ if (kind === "validation" || kind === "auth" || kind === "not_found") return AthenaErrorCategory.Client;
1727
+ if (kind === "rate_limit" || kind === "transient") return AthenaErrorCategory.Server;
1728
+ return AthenaErrorCategory.Unknown;
1729
+ }
1730
+ function isRetryable(kind, status) {
1731
+ if (kind === "rate_limit" || kind === "transient") return true;
1732
+ return status !== void 0 && status >= 500;
1733
+ }
1734
+ function toGatewayCode(kind, status) {
1735
+ if (kind === "transient" && (status === 0 || status === void 0)) return "NETWORK_ERROR";
1736
+ if (kind === "validation") return "INVALID_JSON";
1737
+ if (status !== void 0 && status >= 400) return "HTTP_ERROR";
1738
+ return "UNKNOWN_ERROR";
1739
+ }
1740
+ function toAthenaGatewayError(source, fallbackMessage, context) {
1741
+ if (isAthenaGatewayError(source)) {
1742
+ return source;
1743
+ }
1744
+ if (isAthenaResultLike(source) && source.errorDetails) {
1745
+ return new AthenaGatewayError({
1746
+ code: source.errorDetails.code,
1747
+ message: source.error ?? source.errorDetails.message,
1748
+ status: source.status,
1749
+ endpoint: source.errorDetails.endpoint,
1750
+ method: source.errorDetails.method,
1751
+ requestId: source.errorDetails.requestId,
1752
+ hint: source.errorDetails.hint,
1753
+ cause: source.errorDetails.cause
1754
+ });
1755
+ }
1756
+ const normalized = normalizeAthenaError(source, context);
1757
+ const message = isAthenaResultLike(source) && source.error == null && source.errorDetails == null ? fallbackMessage : normalized.message || fallbackMessage;
1758
+ return new AthenaGatewayError({
1759
+ code: toGatewayCode(normalized.kind, normalized.status),
1760
+ message,
1761
+ status: normalized.status ?? 0,
1762
+ hint: normalized.constraint != null ? `Constraint: ${normalized.constraint}` : contextHint(context),
1763
+ cause: typeof normalized.raw === "string" ? normalized.raw : safeStringify(normalized.raw)
1764
+ });
1765
+ }
1766
+ function isOk(result) {
1767
+ return result.error == null && result.status >= 200 && result.status < 300;
1768
+ }
1769
+ function normalizeAthenaError(resultOrError, context) {
1770
+ const attached = resolveAttachedNormalizedError(resultOrError);
1771
+ if (attached) {
1772
+ return withContextOverrides(attached, context);
1773
+ }
1774
+ if (isAthenaResultLike(resultOrError)) {
1775
+ const details = resultOrError.errorDetails;
1776
+ const message2 = resultOrError.error ?? details?.message ?? `Athena ${context?.operation ?? operationFromDetails(details) ?? "request"} failed`;
1777
+ const operation = context?.operation ?? operationFromDetails(details);
1778
+ const table = context?.table ?? extractTable(message2);
1779
+ const constraint = extractConstraint(message2);
1780
+ const kind2 = classifyKind(resultOrError.status, details?.code, message2);
1781
+ const code = toAthenaErrorCode(kind2, resultOrError.status, details?.code);
1782
+ const category = toAthenaErrorCategory(kind2, resultOrError.status);
1783
+ return {
1784
+ kind: kind2,
1785
+ code,
1786
+ category,
1787
+ retryable: isRetryable(kind2, resultOrError.status),
1788
+ status: resultOrError.status,
1789
+ constraint,
1790
+ table,
1791
+ operation,
1792
+ message: message2,
1793
+ raw: resultOrError.raw
1794
+ };
1795
+ }
1796
+ if (isAthenaGatewayError(resultOrError)) {
1797
+ const details = resultOrError.toDetails();
1798
+ const operation = context?.operation ?? operationFromDetails(details);
1799
+ const table = context?.table ?? extractTable(resultOrError.message);
1800
+ const constraint = extractConstraint(resultOrError.message);
1801
+ const kind2 = classifyKind(resultOrError.status, resultOrError.code, resultOrError.message);
1802
+ const code = toAthenaErrorCode(kind2, resultOrError.status, resultOrError.code);
1803
+ const category = toAthenaErrorCategory(kind2, resultOrError.status);
1804
+ return {
1805
+ kind: kind2,
1806
+ code,
1807
+ category,
1808
+ retryable: isRetryable(kind2, resultOrError.status),
1809
+ status: resultOrError.status,
1810
+ constraint,
1811
+ table,
1812
+ operation,
1813
+ message: resultOrError.message,
1814
+ raw: resultOrError
1815
+ };
1816
+ }
1817
+ if (resultOrError instanceof Error) {
1818
+ const maybeStatus = isRecord3(resultOrError) && typeof resultOrError.status === "number" ? resultOrError.status : void 0;
1819
+ const kind2 = classifyKind(maybeStatus, void 0, resultOrError.message);
1820
+ return {
1821
+ kind: kind2,
1822
+ code: toAthenaErrorCode(kind2, maybeStatus),
1823
+ category: toAthenaErrorCategory(kind2, maybeStatus),
1824
+ retryable: isRetryable(kind2, maybeStatus),
1825
+ status: maybeStatus,
1826
+ constraint: extractConstraint(resultOrError.message),
1827
+ table: context?.table ?? extractTable(resultOrError.message),
1828
+ operation: context?.operation,
1829
+ message: resultOrError.message,
1830
+ raw: resultOrError
1831
+ };
1832
+ }
1833
+ const message = typeof resultOrError === "string" ? resultOrError : "Unknown Athena error";
1834
+ const kind = classifyKind(void 0, void 0, message);
1835
+ return {
1836
+ kind,
1837
+ code: toAthenaErrorCode(kind, void 0),
1838
+ category: toAthenaErrorCategory(kind, void 0),
1839
+ retryable: isRetryable(kind, void 0),
1840
+ status: void 0,
1841
+ constraint: extractConstraint(message),
1842
+ table: context?.table ?? extractTable(message),
1843
+ operation: context?.operation,
1844
+ message,
1845
+ raw: resultOrError
1696
1846
  };
1697
- switch (condition.operator) {
1698
- case "eq":
1699
- case "neq":
1700
- case "gt":
1701
- case "gte":
1702
- case "lt":
1703
- case "lte":
1704
- case "like":
1705
- case "ilike": {
1706
- if (Array.isArray(value) || value === void 0) return null;
1707
- const rhs = withCast(toSqlLiteral(value), condition.value_cast);
1708
- return `${column} ${sqlOperator[condition.operator]} ${rhs}`;
1709
- }
1710
- case "is": {
1711
- if (value === null) return `${column} IS NULL`;
1712
- if (value === true) return `${column} IS TRUE`;
1713
- if (value === false) return `${column} IS FALSE`;
1714
- return null;
1715
- }
1716
- case "in": {
1717
- if (!Array.isArray(value)) return null;
1718
- if (value.length === 0) return "FALSE";
1719
- const values = value.map((item) => withCast(toSqlLiteral(item), condition.value_cast));
1720
- return `${column} IN (${values.join(", ")})`;
1721
- }
1722
- default:
1723
- return null;
1847
+ }
1848
+ function unwrapRows(result, options) {
1849
+ if (!isOk(result)) {
1850
+ throw toAthenaGatewayError(result, "Athena request failed", options?.context);
1724
1851
  }
1852
+ if (result.data == null) return [];
1853
+ return Array.isArray(result.data) ? result.data : [result.data];
1725
1854
  }
1726
- function buildTypedSelectQuery(input) {
1727
- const whereClauses = [];
1728
- for (const condition of input.conditions) {
1729
- const clause = conditionToSqlClause(condition);
1730
- if (!clause) return null;
1731
- whereClauses.push(clause);
1855
+ function unwrap(result, options) {
1856
+ if (!isOk(result)) {
1857
+ throw toAthenaGatewayError(result, "Athena request failed", options?.context);
1732
1858
  }
1733
- let limit = input.limit;
1734
- let offset = input.offset;
1735
- if (limit === void 0 && input.pageSize !== void 0) {
1736
- limit = input.pageSize;
1859
+ if (result.data == null && !options?.allowNull) {
1860
+ throw toAthenaGatewayError(result, "Expected data but received null", options?.context);
1737
1861
  }
1738
- if (offset === void 0 && input.pageSize !== void 0 && input.currentPage !== void 0 && input.currentPage > 0) {
1739
- offset = (input.currentPage - 1) * input.pageSize;
1862
+ return result.data;
1863
+ }
1864
+ function unwrapOne(result, options) {
1865
+ const rows = unwrapRows(result, options);
1866
+ if (!rows.length) {
1867
+ if (options?.allowNull) return null;
1868
+ throw toAthenaGatewayError(result, "Expected one row but received none", options?.context);
1740
1869
  }
1741
- const sqlParts = [
1742
- `SELECT ${buildSelectColumnsClause(input.columns)} FROM ${quoteQualifiedIdentifier(input.tableName)}`
1743
- ];
1744
- if (whereClauses.length > 0) {
1745
- sqlParts.push(`WHERE ${whereClauses.join(" AND ")}`);
1870
+ if (options?.requireExactlyOne && rows.length !== 1) {
1871
+ throw toAthenaGatewayError(
1872
+ result,
1873
+ `Expected exactly one row but received ${rows.length}`,
1874
+ options.context
1875
+ );
1746
1876
  }
1747
- if (input.order?.field) {
1748
- const direction = input.order.direction === "descending" ? "DESC" : "ASC";
1749
- sqlParts.push(`ORDER BY ${quoteQualifiedIdentifier(input.order.field)} ${direction}`);
1877
+ return rows[0];
1878
+ }
1879
+ function requireSuccess(result, context) {
1880
+ if (!isOk(result)) {
1881
+ throw toAthenaGatewayError(
1882
+ result,
1883
+ `Athena ${context?.operation ?? "request"} failed`,
1884
+ context
1885
+ );
1750
1886
  }
1751
- if (limit !== void 0) {
1752
- sqlParts.push(`LIMIT ${Math.max(0, Math.trunc(limit))}`);
1887
+ return result;
1888
+ }
1889
+ function requireAffected(result, options, context) {
1890
+ requireSuccess(result, context);
1891
+ const minimum = options?.min ?? 1;
1892
+ const count = result.count;
1893
+ if (count == null) {
1894
+ throw new AthenaGatewayError({
1895
+ code: "UNKNOWN_ERROR",
1896
+ status: result.status,
1897
+ message: "Expected affected row count but response.count is missing",
1898
+ hint: 'Set call option { count: "exact" } for mutation postcondition checks.',
1899
+ cause: safeStringify(result.raw)
1900
+ });
1753
1901
  }
1754
- if (offset !== void 0) {
1755
- sqlParts.push(`OFFSET ${Math.max(0, Math.trunc(offset))}`);
1902
+ if (count < minimum) {
1903
+ throw new AthenaGatewayError({
1904
+ code: "UNKNOWN_ERROR",
1905
+ status: result.status,
1906
+ message: `Expected at least ${minimum} affected rows but received ${count}`,
1907
+ hint: contextHint(context),
1908
+ cause: safeStringify(result.raw)
1909
+ });
1756
1910
  }
1757
- return `${sqlParts.join(" ")};`;
1911
+ return count;
1758
1912
  }
1759
- function createFilterMethods(state, addCondition, self) {
1760
- return {
1761
- eq(column, value) {
1762
- const columnName = String(column);
1763
- if (shouldUseUuidTextComparison(columnName, value)) {
1764
- addCondition("eq", columnName, value, { columnCast: "text" });
1765
- } else {
1766
- addCondition("eq", columnName, value);
1767
- }
1768
- return self;
1769
- },
1770
- eqCast(column, value, cast) {
1771
- addCondition("eq", String(column), value, { valueCast: cast });
1772
- return self;
1773
- },
1774
- eqUuid(column, value) {
1775
- addCondition("eq", String(column), value, { valueCast: "uuid" });
1776
- return self;
1777
- },
1778
- match(filters) {
1779
- Object.entries(filters).forEach(([column, value]) => {
1780
- if (value === void 0) {
1781
- return;
1782
- }
1783
- if (shouldUseUuidTextComparison(column, value)) {
1784
- addCondition("eq", column, value, { columnCast: "text" });
1785
- } else {
1786
- addCondition("eq", column, value);
1787
- }
1788
- });
1789
- return self;
1790
- },
1791
- range(from, to) {
1792
- state.offset = from;
1793
- state.limit = to - from + 1;
1794
- return self;
1795
- },
1796
- limit(count) {
1797
- state.limit = count;
1798
- return self;
1799
- },
1800
- offset(count) {
1801
- state.offset = count;
1802
- return self;
1803
- },
1804
- currentPage(value) {
1805
- state.currentPage = value;
1806
- return self;
1807
- },
1808
- pageSize(value) {
1809
- state.pageSize = value;
1810
- return self;
1811
- },
1812
- totalPages(value) {
1813
- state.totalPages = value;
1814
- return self;
1815
- },
1816
- order(column, options) {
1817
- state.order = {
1818
- field: String(column),
1819
- direction: options?.ascending === false ? "descending" : "ascending"
1820
- };
1821
- return self;
1822
- },
1823
- gt(column, value) {
1824
- addCondition("gt", String(column), value);
1825
- return self;
1826
- },
1827
- gte(column, value) {
1828
- addCondition("gte", String(column), value);
1829
- return self;
1830
- },
1831
- lt(column, value) {
1832
- addCondition("lt", String(column), value);
1833
- return self;
1834
- },
1835
- lte(column, value) {
1836
- addCondition("lte", String(column), value);
1837
- return self;
1838
- },
1839
- neq(column, value) {
1840
- addCondition("neq", String(column), value);
1841
- return self;
1842
- },
1843
- like(column, value) {
1844
- addCondition("like", String(column), value);
1845
- return self;
1846
- },
1847
- ilike(column, value) {
1848
- addCondition("ilike", String(column), value);
1849
- return self;
1850
- },
1851
- is(column, value) {
1852
- addCondition("is", String(column), value);
1853
- return self;
1854
- },
1855
- in(column, values) {
1856
- addCondition("in", String(column), values);
1857
- return self;
1858
- },
1859
- contains(column, values) {
1860
- addCondition("contains", String(column), values);
1861
- return self;
1862
- },
1863
- containedBy(column, values) {
1864
- addCondition("containedBy", String(column), values);
1865
- return self;
1866
- },
1867
- not(columnOrExpression, operator, value) {
1868
- const expression = String(columnOrExpression);
1869
- if (operator != null && value !== void 0) {
1870
- addCondition("not", void 0, `${expression}.${operator}.${stringifyFilterValue(value)}`);
1871
- } else {
1872
- addCondition("not", void 0, expression);
1913
+ function applyBounds(value, options) {
1914
+ if (options?.min !== void 0 && value < options.min) return null;
1915
+ if (options?.max !== void 0 && value > options.max) return null;
1916
+ return value;
1917
+ }
1918
+ function parseIntegerString(value) {
1919
+ const normalized = value.trim();
1920
+ if (!/^[-+]?\d+$/.test(normalized)) return null;
1921
+ const parsed = Number(normalized);
1922
+ if (!Number.isFinite(parsed) || !Number.isInteger(parsed)) return null;
1923
+ return parsed;
1924
+ }
1925
+ function coerceInt(value, options) {
1926
+ if (value == null) return null;
1927
+ if (typeof value === "number") {
1928
+ if (!Number.isFinite(value) || !Number.isInteger(value)) return null;
1929
+ return applyBounds(value, options);
1930
+ }
1931
+ if (typeof value === "string") {
1932
+ const parsed = parseIntegerString(value);
1933
+ if (parsed == null) return null;
1934
+ return applyBounds(parsed, options);
1935
+ }
1936
+ if (typeof value === "bigint") {
1937
+ if (options?.strictBigInt) {
1938
+ if (value > BigInt(Number.MAX_SAFE_INTEGER) || value < BigInt(Number.MIN_SAFE_INTEGER)) {
1939
+ return null;
1873
1940
  }
1874
- return self;
1875
- },
1876
- or(expression) {
1877
- addCondition("or", void 0, expression);
1878
- return self;
1879
1941
  }
1880
- };
1942
+ const parsed = Number(value);
1943
+ if (!Number.isFinite(parsed) || !Number.isInteger(parsed)) return null;
1944
+ return applyBounds(parsed, options);
1945
+ }
1946
+ return null;
1881
1947
  }
1882
- function toRpcSelect(columns) {
1883
- if (!columns) return void 0;
1884
- return Array.isArray(columns) ? columns.join(",") : columns;
1948
+ function assertInt(value, label = "value", options) {
1949
+ const parsed = coerceInt(value, options);
1950
+ if (parsed == null) {
1951
+ throw new TypeError(`${label} must be a finite integer`);
1952
+ }
1953
+ return parsed;
1885
1954
  }
1886
- function createRpcFilterMethods(filters, self) {
1887
- const addFilter = (operator, column, value) => {
1888
- filters.push({ column, operator, value });
1955
+ function defaultShouldRetry(error) {
1956
+ const normalized = normalizeAthenaError(error);
1957
+ return normalized.kind === "transient" || normalized.kind === "rate_limit";
1958
+ }
1959
+ function computeDelayMs(attempt, error, config) {
1960
+ const baseDelay = config.baseDelayMs;
1961
+ const rawDelay = typeof config.backoff === "function" ? config.backoff(attempt, error) : config.backoff === "linear" ? baseDelay * attempt : baseDelay * Math.pow(2, attempt - 1);
1962
+ const safeDelay = Number.isFinite(rawDelay) ? Math.max(0, rawDelay) : 0;
1963
+ const clamped = Math.min(config.maxDelayMs, safeDelay);
1964
+ const jitterFactor = typeof config.jitter === "number" ? Math.max(0, Math.min(1, config.jitter)) : config.jitter ? 0.2 : 0;
1965
+ if (!jitterFactor) return clamped;
1966
+ const deviation = clamped * jitterFactor;
1967
+ const offset = (Math.random() * 2 - 1) * deviation;
1968
+ return Math.max(0, clamped + offset);
1969
+ }
1970
+ function sleep(ms) {
1971
+ if (ms <= 0) return Promise.resolve();
1972
+ return new Promise((resolve3) => {
1973
+ setTimeout(resolve3, ms);
1974
+ });
1975
+ }
1976
+ async function withRetry(config, fn) {
1977
+ const retries = Math.max(0, Math.trunc(config.retries));
1978
+ const shouldRetry = config.shouldRetry ?? defaultShouldRetry;
1979
+ const resolvedConfig = {
1980
+ baseDelayMs: config.baseDelayMs ?? 100,
1981
+ maxDelayMs: config.maxDelayMs ?? 1e4,
1982
+ backoff: config.backoff ?? "exponential",
1983
+ jitter: config.jitter ?? false
1889
1984
  };
1890
- return {
1891
- eq(column, value) {
1892
- addFilter("eq", column, value);
1893
- return self;
1894
- },
1895
- neq(column, value) {
1896
- addFilter("neq", column, value);
1897
- return self;
1898
- },
1899
- gt(column, value) {
1900
- addFilter("gt", column, value);
1901
- return self;
1985
+ for (let attempts = 0; attempts <= retries; attempts += 1) {
1986
+ try {
1987
+ return await fn();
1988
+ } catch (error) {
1989
+ if (attempts >= retries) {
1990
+ throw error;
1991
+ }
1992
+ const currentAttempt = attempts + 1;
1993
+ const retry = await shouldRetry(error, currentAttempt);
1994
+ if (!retry) {
1995
+ throw error;
1996
+ }
1997
+ const delay = computeDelayMs(currentAttempt, error, resolvedConfig);
1998
+ await sleep(delay);
1999
+ }
2000
+ }
2001
+ throw new Error("withRetry reached an unexpected state");
2002
+ }
2003
+
2004
+ // src/db/module.ts
2005
+ function createDbModule(input) {
2006
+ const db = {
2007
+ from(table) {
2008
+ return input.from(table);
1902
2009
  },
1903
- gte(column, value) {
1904
- addFilter("gte", column, value);
1905
- return self;
2010
+ select(table, columns, options) {
2011
+ return input.from(table).select(columns, options);
1906
2012
  },
1907
- lt(column, value) {
1908
- addFilter("lt", column, value);
1909
- return self;
2013
+ insert(table, values, options) {
2014
+ return Array.isArray(values) ? input.from(table).insert(values, options) : input.from(table).insert(values, options);
1910
2015
  },
1911
- lte(column, value) {
1912
- addFilter("lte", column, value);
1913
- return self;
2016
+ upsert(table, values, options) {
2017
+ return Array.isArray(values) ? input.from(table).upsert(values, options) : input.from(table).upsert(values, options);
1914
2018
  },
1915
- like(column, value) {
1916
- addFilter("like", column, value);
1917
- return self;
2019
+ update(table, values, options) {
2020
+ return input.from(table).update(values, options);
1918
2021
  },
1919
- ilike(column, value) {
1920
- addFilter("ilike", column, value);
1921
- return self;
2022
+ delete(table, options) {
2023
+ return input.from(table).delete(options);
1922
2024
  },
1923
- is(column, value) {
1924
- addFilter("is", column, value);
1925
- return self;
2025
+ rpc(fn, args, options) {
2026
+ return input.rpc(fn, args, options);
1926
2027
  },
1927
- in(column, values) {
1928
- addFilter("in", column, values);
1929
- return self;
2028
+ query(query, options) {
2029
+ return input.query(query, options);
1930
2030
  }
1931
2031
  };
2032
+ return db;
1932
2033
  }
1933
- function createRpcBuilder(functionName, args, baseOptions, client) {
1934
- const state = {
1935
- filters: []
2034
+
2035
+ // src/client.ts
2036
+ var DEFAULT_COLUMNS = "*";
2037
+ 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;
2038
+ var SAFE_CAST_PATTERN = /^[a-z_][a-z0-9_]*(?:\[\])?$/i;
2039
+ var ATHENA_NORMALIZED_ERROR_KEY = "__athenaNormalizedError";
2040
+ function formatResult(response) {
2041
+ const result = {
2042
+ data: response.data ?? null,
2043
+ error: response.error ?? null,
2044
+ errorDetails: response.errorDetails ?? null,
2045
+ status: response.status,
2046
+ raw: response.raw
1936
2047
  };
1937
- let selectedColumns;
1938
- let selectedOptions;
1939
- let promise = null;
1940
- const executeRpc = async (columns, options) => {
1941
- const mergedOptions = mergeOptions(baseOptions, options);
1942
- const payload = {
1943
- function: functionName,
1944
- args,
1945
- schema: mergedOptions?.schema,
1946
- select: toRpcSelect(columns),
1947
- filters: state.filters.length ? [...state.filters] : void 0,
1948
- count: mergedOptions?.count,
1949
- head: mergedOptions?.head,
1950
- limit: state.limit,
1951
- offset: state.offset,
1952
- order: state.order
1953
- };
1954
- const response = await client.rpcGateway(payload, mergedOptions);
1955
- return formatResult(response);
2048
+ if (response.count !== void 0) {
2049
+ result.count = response.count;
2050
+ }
2051
+ return result;
2052
+ }
2053
+ function attachNormalizedError(result, normalizedError) {
2054
+ Object.defineProperty(result, ATHENA_NORMALIZED_ERROR_KEY, {
2055
+ value: normalizedError,
2056
+ enumerable: false,
2057
+ configurable: true,
2058
+ writable: false
2059
+ });
2060
+ }
2061
+ function createResultFormatter(experimental) {
2062
+ if (!experimental?.enableErrorNormalization) {
2063
+ return formatResult;
2064
+ }
2065
+ return (response, context) => {
2066
+ const result = formatResult(response);
2067
+ if (result.error == null) {
2068
+ return result;
2069
+ }
2070
+ const normalizedError = normalizeAthenaError(result, context);
2071
+ attachNormalizedError(result, normalizedError);
2072
+ return result;
2073
+ };
2074
+ }
2075
+ function toSingleResult(response) {
2076
+ const payload = response.data;
2077
+ const singleData = Array.isArray(payload) ? payload.length ? payload[0] : null : payload ?? null;
2078
+ return {
2079
+ ...response,
2080
+ data: singleData
1956
2081
  };
2082
+ }
2083
+ function mergeOptions(...options) {
2084
+ return options.reduce((acc, next) => {
2085
+ if (!next) return acc;
2086
+ return { ...acc, ...next };
2087
+ }, void 0);
2088
+ }
2089
+ function asAthenaJsonObject(value) {
2090
+ return value;
2091
+ }
2092
+ function asAthenaJsonObjectArray(values) {
2093
+ return values;
2094
+ }
2095
+ function createMutationQuery(executor, defaultColumns = DEFAULT_COLUMNS) {
2096
+ let selectedColumns = defaultColumns === null ? void 0 : defaultColumns;
2097
+ let selectedOptions;
2098
+ let promise = null;
1957
2099
  const run = (columns, options) => {
1958
2100
  const payloadColumns = columns ?? selectedColumns;
1959
2101
  const payloadOptions = options ?? selectedOptions;
1960
2102
  if (!promise) {
1961
- promise = executeRpc(payloadColumns, payloadOptions);
2103
+ promise = executor(payloadColumns, payloadOptions);
1962
2104
  }
1963
2105
  return promise;
1964
2106
  };
1965
- const builder = {};
1966
- const filterMethods = createRpcFilterMethods(state.filters, builder);
1967
- Object.assign(builder, filterMethods, {
2107
+ const mutationQuery = {
1968
2108
  select(columns = selectedColumns, options) {
1969
2109
  selectedColumns = columns;
1970
2110
  selectedOptions = options ?? selectedOptions;
1971
2111
  return run(columns, options);
1972
2112
  },
1973
- async single(columns, options) {
1974
- const result = await run(columns, options);
1975
- return toSingleResult(result);
1976
- },
1977
- maybeSingle(columns, options) {
1978
- return builder.single(columns, options);
1979
- },
1980
- order(column, options) {
1981
- state.order = { column, ascending: options?.ascending ?? true };
1982
- return builder;
1983
- },
1984
- limit(count) {
1985
- state.limit = count;
1986
- return builder;
2113
+ returning(columns = selectedColumns, options) {
2114
+ return mutationQuery.select(columns, options);
1987
2115
  },
1988
- offset(count) {
1989
- state.offset = count;
1990
- return builder;
2116
+ single(columns = selectedColumns, options) {
2117
+ selectedColumns = columns;
2118
+ selectedOptions = options ?? selectedOptions;
2119
+ return run(columns, options).then(toSingleResult);
1991
2120
  },
1992
- range(from, to) {
1993
- state.offset = from;
1994
- state.limit = to - from + 1;
1995
- return builder;
2121
+ maybeSingle(columns = selectedColumns, options) {
2122
+ return mutationQuery.single(columns, options);
1996
2123
  },
1997
2124
  then(onfulfilled, onrejected) {
1998
2125
  return run(selectedColumns, selectedOptions).then(onfulfilled, onrejected);
@@ -2003,122 +2130,511 @@ function createRpcBuilder(functionName, args, baseOptions, client) {
2003
2130
  finally(onfinally) {
2004
2131
  return run(selectedColumns, selectedOptions).finally(onfinally);
2005
2132
  }
2006
- });
2007
- return builder;
2133
+ };
2134
+ return mutationQuery;
2008
2135
  }
2009
- function createTableBuilder(tableName, client) {
2010
- const state = {
2011
- conditions: []
2136
+ function getResourceId(state) {
2137
+ const candidate = state.conditions.find(
2138
+ (condition) => condition.operator === "eq" && (condition.column === "resource_id" || condition.column === "id")
2139
+ );
2140
+ return candidate?.value?.toString();
2141
+ }
2142
+ function stringifyFilterValue(value) {
2143
+ if (Array.isArray(value)) {
2144
+ return value.join(",");
2145
+ }
2146
+ return String(value);
2147
+ }
2148
+ function isUuidString(value) {
2149
+ return UUID_PATTERN.test(value.trim());
2150
+ }
2151
+ function isUuidIdentifierColumn(column) {
2152
+ return column === "id" || /(?:^|_)uuid(?:_|$)/i.test(column) || /_id$/i.test(column);
2153
+ }
2154
+ function shouldUseUuidTextComparison(column, value) {
2155
+ return typeof value === "string" && isUuidString(value) && isUuidIdentifierColumn(column);
2156
+ }
2157
+ function normalizeCast(cast) {
2158
+ const normalized = cast.trim().toLowerCase();
2159
+ if (!SAFE_CAST_PATTERN.test(normalized)) {
2160
+ throw new Error(`Invalid cast type "${cast}"`);
2161
+ }
2162
+ return normalized;
2163
+ }
2164
+ function escapeSqlStringLiteral(value) {
2165
+ return value.replace(/'/g, "''");
2166
+ }
2167
+ function toSqlLiteral(value) {
2168
+ if (value === null) return "NULL";
2169
+ if (typeof value === "number") return Number.isFinite(value) ? String(value) : "NULL";
2170
+ if (typeof value === "boolean") return value ? "TRUE" : "FALSE";
2171
+ return `'${escapeSqlStringLiteral(value)}'`;
2172
+ }
2173
+ function withCast(expression, cast) {
2174
+ if (!cast) return expression;
2175
+ return `${expression}::${normalizeCast(cast)}`;
2176
+ }
2177
+ function buildSelectColumnsClause(columns) {
2178
+ if (Array.isArray(columns)) {
2179
+ return columns.map((column) => quoteQualifiedIdentifier(column)).join(", ");
2180
+ }
2181
+ return quoteSelectColumnsExpression(columns);
2182
+ }
2183
+ function resolveTableNameForCall(tableName, schema) {
2184
+ if (!schema) return tableName;
2185
+ const normalizedSchema = schema.trim();
2186
+ if (!normalizedSchema) {
2187
+ throw new Error("schema option must be a non-empty string");
2188
+ }
2189
+ if (tableName.includes(".")) {
2190
+ if (tableName.startsWith(`${normalizedSchema}.`)) {
2191
+ return tableName;
2192
+ }
2193
+ throw new Error(
2194
+ `schema option "${normalizedSchema}" conflicts with schema-qualified table "${tableName}"`
2195
+ );
2196
+ }
2197
+ return `${normalizedSchema}.${tableName}`;
2198
+ }
2199
+ function conditionToSqlClause(condition) {
2200
+ if (!condition.column) return null;
2201
+ const column = withCast(quoteQualifiedIdentifier(condition.column), condition.column_cast);
2202
+ const value = condition.value;
2203
+ const sqlOperator = {
2204
+ eq: "=",
2205
+ neq: "!=",
2206
+ gt: ">",
2207
+ gte: ">=",
2208
+ lt: "<",
2209
+ lte: "<=",
2210
+ like: "LIKE",
2211
+ ilike: "ILIKE"
2012
2212
  };
2013
- const addCondition = (operator, column, value, hints) => {
2014
- const condition = { operator };
2015
- if (column) {
2016
- condition.column = column;
2017
- if (operator === "eq") {
2018
- condition.eq_column = column;
2019
- }
2213
+ switch (condition.operator) {
2214
+ case "eq":
2215
+ case "neq":
2216
+ case "gt":
2217
+ case "gte":
2218
+ case "lt":
2219
+ case "lte":
2220
+ case "like":
2221
+ case "ilike": {
2222
+ if (Array.isArray(value) || value === void 0) return null;
2223
+ const rhs = withCast(toSqlLiteral(value), condition.value_cast);
2224
+ return `${column} ${sqlOperator[condition.operator]} ${rhs}`;
2020
2225
  }
2021
- if (value !== void 0) {
2022
- condition.value = value;
2023
- if (operator === "eq") {
2024
- condition.eq_value = value;
2025
- }
2226
+ case "is": {
2227
+ if (value === null) return `${column} IS NULL`;
2228
+ if (value === true) return `${column} IS TRUE`;
2229
+ if (value === false) return `${column} IS FALSE`;
2230
+ return null;
2026
2231
  }
2027
- if (hints?.valueCast) {
2028
- condition.value_cast = hints.valueCast;
2029
- if (operator === "eq") {
2030
- condition.eq_value_cast = hints.valueCast;
2031
- }
2232
+ case "in": {
2233
+ if (!Array.isArray(value)) return null;
2234
+ if (value.length === 0) return "FALSE";
2235
+ const values = value.map((item) => withCast(toSqlLiteral(item), condition.value_cast));
2236
+ return `${column} IN (${values.join(", ")})`;
2032
2237
  }
2033
- if (hints?.columnCast) {
2034
- condition.column_cast = hints.columnCast;
2035
- if (operator === "eq") {
2036
- condition.eq_column_cast = hints.columnCast;
2238
+ default:
2239
+ return null;
2240
+ }
2241
+ }
2242
+ function buildTypedSelectQuery(input) {
2243
+ const whereClauses = [];
2244
+ for (const condition of input.conditions) {
2245
+ const clause = conditionToSqlClause(condition);
2246
+ if (!clause) return null;
2247
+ whereClauses.push(clause);
2248
+ }
2249
+ let limit = input.limit;
2250
+ let offset = input.offset;
2251
+ if (limit === void 0 && input.pageSize !== void 0) {
2252
+ limit = input.pageSize;
2253
+ }
2254
+ if (offset === void 0 && input.pageSize !== void 0 && input.currentPage !== void 0 && input.currentPage > 0) {
2255
+ offset = (input.currentPage - 1) * input.pageSize;
2256
+ }
2257
+ const sqlParts = [
2258
+ `SELECT ${buildSelectColumnsClause(input.columns)} FROM ${quoteQualifiedIdentifier(input.tableName)}`
2259
+ ];
2260
+ if (whereClauses.length > 0) {
2261
+ sqlParts.push(`WHERE ${whereClauses.join(" AND ")}`);
2262
+ }
2263
+ if (input.order?.field) {
2264
+ const direction = input.order.direction === "descending" ? "DESC" : "ASC";
2265
+ sqlParts.push(`ORDER BY ${quoteQualifiedIdentifier(input.order.field)} ${direction}`);
2266
+ }
2267
+ if (limit !== void 0) {
2268
+ sqlParts.push(`LIMIT ${Math.max(0, Math.trunc(limit))}`);
2269
+ }
2270
+ if (offset !== void 0) {
2271
+ sqlParts.push(`OFFSET ${Math.max(0, Math.trunc(offset))}`);
2272
+ }
2273
+ return `${sqlParts.join(" ")};`;
2274
+ }
2275
+ function createFilterMethods(state, addCondition, self) {
2276
+ return {
2277
+ eq(column, value) {
2278
+ const columnName = String(column);
2279
+ if (shouldUseUuidTextComparison(columnName, value)) {
2280
+ addCondition("eq", columnName, value, { columnCast: "text" });
2281
+ } else {
2282
+ addCondition("eq", columnName, value);
2037
2283
  }
2038
- }
2039
- state.conditions.push(condition);
2040
- };
2041
- const builder = {};
2042
- const filterMethods = createFilterMethods(
2043
- state,
2044
- addCondition,
2045
- builder
2046
- );
2047
- const runSelect = async (columns = DEFAULT_COLUMNS, options) => {
2048
- const resolvedTableName = resolveTableNameForCall(tableName, options?.schema);
2049
- const conditions = state.conditions.length ? state.conditions.map((condition) => ({ ...condition })) : void 0;
2050
- const hasTypedEqualityComparison = conditions?.some(
2051
- (condition) => condition.operator === "eq" && (condition.value_cast !== void 0 || condition.column_cast !== void 0)
2052
- ) ?? false;
2053
- if (hasTypedEqualityComparison && !options?.head && !options?.count && conditions) {
2054
- const query = buildTypedSelectQuery({
2055
- tableName: resolvedTableName,
2056
- columns,
2057
- conditions,
2058
- limit: state.limit,
2059
- offset: state.offset,
2060
- currentPage: state.currentPage,
2061
- pageSize: state.pageSize,
2062
- order: state.order
2284
+ return self;
2285
+ },
2286
+ eqCast(column, value, cast) {
2287
+ addCondition("eq", String(column), value, { valueCast: cast });
2288
+ return self;
2289
+ },
2290
+ eqUuid(column, value) {
2291
+ addCondition("eq", String(column), value, { valueCast: "uuid" });
2292
+ return self;
2293
+ },
2294
+ match(filters) {
2295
+ Object.entries(filters).forEach(([column, value]) => {
2296
+ if (value === void 0) {
2297
+ return;
2298
+ }
2299
+ if (shouldUseUuidTextComparison(column, value)) {
2300
+ addCondition("eq", column, value, { columnCast: "text" });
2301
+ } else {
2302
+ addCondition("eq", column, value);
2303
+ }
2063
2304
  });
2064
- if (query) {
2065
- const queryResponse = await client.queryGateway({ query }, options);
2066
- return formatResult(queryResponse);
2305
+ return self;
2306
+ },
2307
+ range(from, to) {
2308
+ state.offset = from;
2309
+ state.limit = to - from + 1;
2310
+ return self;
2311
+ },
2312
+ limit(count) {
2313
+ state.limit = count;
2314
+ return self;
2315
+ },
2316
+ offset(count) {
2317
+ state.offset = count;
2318
+ return self;
2319
+ },
2320
+ currentPage(value) {
2321
+ state.currentPage = value;
2322
+ return self;
2323
+ },
2324
+ pageSize(value) {
2325
+ state.pageSize = value;
2326
+ return self;
2327
+ },
2328
+ totalPages(value) {
2329
+ state.totalPages = value;
2330
+ return self;
2331
+ },
2332
+ order(column, options) {
2333
+ state.order = {
2334
+ field: String(column),
2335
+ direction: options?.ascending === false ? "descending" : "ascending"
2336
+ };
2337
+ return self;
2338
+ },
2339
+ gt(column, value) {
2340
+ addCondition("gt", String(column), value);
2341
+ return self;
2342
+ },
2343
+ gte(column, value) {
2344
+ addCondition("gte", String(column), value);
2345
+ return self;
2346
+ },
2347
+ lt(column, value) {
2348
+ addCondition("lt", String(column), value);
2349
+ return self;
2350
+ },
2351
+ lte(column, value) {
2352
+ addCondition("lte", String(column), value);
2353
+ return self;
2354
+ },
2355
+ neq(column, value) {
2356
+ addCondition("neq", String(column), value);
2357
+ return self;
2358
+ },
2359
+ like(column, value) {
2360
+ addCondition("like", String(column), value);
2361
+ return self;
2362
+ },
2363
+ ilike(column, value) {
2364
+ addCondition("ilike", String(column), value);
2365
+ return self;
2366
+ },
2367
+ is(column, value) {
2368
+ addCondition("is", String(column), value);
2369
+ return self;
2370
+ },
2371
+ in(column, values) {
2372
+ addCondition("in", String(column), values);
2373
+ return self;
2374
+ },
2375
+ contains(column, values) {
2376
+ addCondition("contains", String(column), values);
2377
+ return self;
2378
+ },
2379
+ containedBy(column, values) {
2380
+ addCondition("containedBy", String(column), values);
2381
+ return self;
2382
+ },
2383
+ not(columnOrExpression, operator, value) {
2384
+ const expression = String(columnOrExpression);
2385
+ if (operator != null && value !== void 0) {
2386
+ addCondition("not", void 0, `${expression}.${operator}.${stringifyFilterValue(value)}`);
2387
+ } else {
2388
+ addCondition("not", void 0, expression);
2067
2389
  }
2390
+ return self;
2391
+ },
2392
+ or(expression) {
2393
+ addCondition("or", void 0, expression);
2394
+ return self;
2395
+ }
2396
+ };
2397
+ }
2398
+ function toRpcSelect(columns) {
2399
+ if (!columns) return void 0;
2400
+ return Array.isArray(columns) ? columns.join(",") : columns;
2401
+ }
2402
+ function createRpcFilterMethods(filters, self) {
2403
+ const addFilter = (operator, column, value) => {
2404
+ filters.push({ column, operator, value });
2405
+ };
2406
+ return {
2407
+ eq(column, value) {
2408
+ addFilter("eq", column, value);
2409
+ return self;
2410
+ },
2411
+ neq(column, value) {
2412
+ addFilter("neq", column, value);
2413
+ return self;
2414
+ },
2415
+ gt(column, value) {
2416
+ addFilter("gt", column, value);
2417
+ return self;
2418
+ },
2419
+ gte(column, value) {
2420
+ addFilter("gte", column, value);
2421
+ return self;
2422
+ },
2423
+ lt(column, value) {
2424
+ addFilter("lt", column, value);
2425
+ return self;
2426
+ },
2427
+ lte(column, value) {
2428
+ addFilter("lte", column, value);
2429
+ return self;
2430
+ },
2431
+ like(column, value) {
2432
+ addFilter("like", column, value);
2433
+ return self;
2434
+ },
2435
+ ilike(column, value) {
2436
+ addFilter("ilike", column, value);
2437
+ return self;
2438
+ },
2439
+ is(column, value) {
2440
+ addFilter("is", column, value);
2441
+ return self;
2442
+ },
2443
+ in(column, values) {
2444
+ addFilter("in", column, values);
2445
+ return self;
2068
2446
  }
2447
+ };
2448
+ }
2449
+ function createRpcBuilder(functionName, args, baseOptions, client, formatGatewayResult) {
2450
+ const state = {
2451
+ filters: []
2452
+ };
2453
+ let selectedColumns;
2454
+ let selectedOptions;
2455
+ let promise = null;
2456
+ const executeRpc = async (columns, options) => {
2457
+ const mergedOptions = mergeOptions(baseOptions, options);
2069
2458
  const payload = {
2070
- table_name: resolvedTableName,
2071
- columns,
2072
- conditions,
2459
+ function: functionName,
2460
+ args,
2461
+ schema: mergedOptions?.schema,
2462
+ select: toRpcSelect(columns),
2463
+ filters: state.filters.length ? [...state.filters] : void 0,
2464
+ count: mergedOptions?.count,
2465
+ head: mergedOptions?.head,
2073
2466
  limit: state.limit,
2074
2467
  offset: state.offset,
2075
- current_page: state.currentPage,
2076
- page_size: state.pageSize,
2077
- total_pages: state.totalPages,
2078
- sort_by: state.order,
2079
- strip_nulls: options?.stripNulls ?? true,
2080
- count: options?.count,
2081
- head: options?.head
2468
+ order: state.order
2082
2469
  };
2083
- const response = await client.fetchGateway(payload, options);
2084
- return formatResult(response);
2470
+ const response = await client.rpcGateway(payload, mergedOptions);
2471
+ return formatGatewayResult(response, { operation: "rpc" });
2085
2472
  };
2086
- const createSelectChain = (columns, options) => {
2087
- const chain = {};
2088
- const filterMethods2 = createFilterMethods(state, addCondition, chain);
2089
- Object.assign(chain, filterMethods2, {
2090
- async single(cols, opts) {
2091
- const r = await runSelect(cols ?? columns, opts ?? options);
2092
- return toSingleResult(r);
2093
- },
2094
- maybeSingle(cols, opts) {
2095
- return chain.single(cols, opts);
2096
- },
2097
- then(onfulfilled, onrejected) {
2098
- return runSelect(columns, options).then(onfulfilled, onrejected);
2099
- },
2100
- catch(onrejected) {
2101
- return runSelect(columns, options).catch(onrejected);
2102
- },
2103
- finally(onfinally) {
2104
- return runSelect(columns, options).finally(onfinally);
2105
- }
2106
- });
2107
- return chain;
2473
+ const run = (columns, options) => {
2474
+ const payloadColumns = columns ?? selectedColumns;
2475
+ const payloadOptions = options ?? selectedOptions;
2476
+ if (!promise) {
2477
+ promise = executeRpc(payloadColumns, payloadOptions);
2478
+ }
2479
+ return promise;
2108
2480
  };
2481
+ const builder = {};
2482
+ const filterMethods = createRpcFilterMethods(state.filters, builder);
2109
2483
  Object.assign(builder, filterMethods, {
2110
- reset() {
2111
- state.conditions = [];
2112
- state.limit = void 0;
2113
- state.offset = void 0;
2114
- state.order = void 0;
2115
- state.currentPage = void 0;
2116
- state.pageSize = void 0;
2117
- state.totalPages = void 0;
2118
- return builder;
2484
+ select(columns = selectedColumns, options) {
2485
+ selectedColumns = columns;
2486
+ selectedOptions = options ?? selectedOptions;
2487
+ return run(columns, options);
2119
2488
  },
2120
- select(columns = DEFAULT_COLUMNS, options) {
2121
- return createSelectChain(columns, options);
2489
+ async single(columns, options) {
2490
+ const result = await run(columns, options);
2491
+ return toSingleResult(result);
2492
+ },
2493
+ maybeSingle(columns, options) {
2494
+ return builder.single(columns, options);
2495
+ },
2496
+ order(column, options) {
2497
+ state.order = { column, ascending: options?.ascending ?? true };
2498
+ return builder;
2499
+ },
2500
+ limit(count) {
2501
+ state.limit = count;
2502
+ return builder;
2503
+ },
2504
+ offset(count) {
2505
+ state.offset = count;
2506
+ return builder;
2507
+ },
2508
+ range(from, to) {
2509
+ state.offset = from;
2510
+ state.limit = to - from + 1;
2511
+ return builder;
2512
+ },
2513
+ then(onfulfilled, onrejected) {
2514
+ return run(selectedColumns, selectedOptions).then(onfulfilled, onrejected);
2515
+ },
2516
+ catch(onrejected) {
2517
+ return run(selectedColumns, selectedOptions).catch(onrejected);
2518
+ },
2519
+ finally(onfinally) {
2520
+ return run(selectedColumns, selectedOptions).finally(onfinally);
2521
+ }
2522
+ });
2523
+ return builder;
2524
+ }
2525
+ function createTableBuilder(tableName, client, formatGatewayResult) {
2526
+ const state = {
2527
+ conditions: []
2528
+ };
2529
+ const addCondition = (operator, column, value, hints) => {
2530
+ const condition = { operator };
2531
+ if (column) {
2532
+ condition.column = column;
2533
+ if (operator === "eq") {
2534
+ condition.eq_column = column;
2535
+ }
2536
+ }
2537
+ if (value !== void 0) {
2538
+ condition.value = value;
2539
+ if (operator === "eq") {
2540
+ condition.eq_value = value;
2541
+ }
2542
+ }
2543
+ if (hints?.valueCast) {
2544
+ condition.value_cast = hints.valueCast;
2545
+ if (operator === "eq") {
2546
+ condition.eq_value_cast = hints.valueCast;
2547
+ }
2548
+ }
2549
+ if (hints?.columnCast) {
2550
+ condition.column_cast = hints.columnCast;
2551
+ if (operator === "eq") {
2552
+ condition.eq_column_cast = hints.columnCast;
2553
+ }
2554
+ }
2555
+ state.conditions.push(condition);
2556
+ };
2557
+ const builder = {};
2558
+ const filterMethods = createFilterMethods(
2559
+ state,
2560
+ addCondition,
2561
+ builder
2562
+ );
2563
+ const runSelect = async (columns = DEFAULT_COLUMNS, options) => {
2564
+ const resolvedTableName = resolveTableNameForCall(tableName, options?.schema);
2565
+ const conditions = state.conditions.length ? state.conditions.map((condition) => ({ ...condition })) : void 0;
2566
+ const hasTypedEqualityComparison = conditions?.some(
2567
+ (condition) => condition.operator === "eq" && (condition.value_cast !== void 0 || condition.column_cast !== void 0)
2568
+ ) ?? false;
2569
+ if (hasTypedEqualityComparison && !options?.head && !options?.count && conditions) {
2570
+ const query = buildTypedSelectQuery({
2571
+ tableName: resolvedTableName,
2572
+ columns,
2573
+ conditions,
2574
+ limit: state.limit,
2575
+ offset: state.offset,
2576
+ currentPage: state.currentPage,
2577
+ pageSize: state.pageSize,
2578
+ order: state.order
2579
+ });
2580
+ if (query) {
2581
+ const queryResponse = await client.queryGateway({ query }, options);
2582
+ return formatGatewayResult(queryResponse, { table: resolvedTableName, operation: "select" });
2583
+ }
2584
+ }
2585
+ const payload = {
2586
+ table_name: resolvedTableName,
2587
+ columns,
2588
+ conditions,
2589
+ limit: state.limit,
2590
+ offset: state.offset,
2591
+ current_page: state.currentPage,
2592
+ page_size: state.pageSize,
2593
+ total_pages: state.totalPages,
2594
+ sort_by: state.order,
2595
+ strip_nulls: options?.stripNulls ?? true,
2596
+ count: options?.count,
2597
+ head: options?.head
2598
+ };
2599
+ const response = await client.fetchGateway(payload, options);
2600
+ return formatGatewayResult(response, { table: resolvedTableName, operation: "select" });
2601
+ };
2602
+ const createSelectChain = (columns, options) => {
2603
+ const chain = {};
2604
+ const filterMethods2 = createFilterMethods(state, addCondition, chain);
2605
+ Object.assign(chain, filterMethods2, {
2606
+ async single(cols, opts) {
2607
+ const r = await runSelect(cols ?? columns, opts ?? options);
2608
+ return toSingleResult(r);
2609
+ },
2610
+ maybeSingle(cols, opts) {
2611
+ return chain.single(cols, opts);
2612
+ },
2613
+ then(onfulfilled, onrejected) {
2614
+ return runSelect(columns, options).then(onfulfilled, onrejected);
2615
+ },
2616
+ catch(onrejected) {
2617
+ return runSelect(columns, options).catch(onrejected);
2618
+ },
2619
+ finally(onfinally) {
2620
+ return runSelect(columns, options).finally(onfinally);
2621
+ }
2622
+ });
2623
+ return chain;
2624
+ };
2625
+ Object.assign(builder, filterMethods, {
2626
+ reset() {
2627
+ state.conditions = [];
2628
+ state.limit = void 0;
2629
+ state.offset = void 0;
2630
+ state.order = void 0;
2631
+ state.currentPage = void 0;
2632
+ state.pageSize = void 0;
2633
+ state.totalPages = void 0;
2634
+ return builder;
2635
+ },
2636
+ select(columns = DEFAULT_COLUMNS, options) {
2637
+ return createSelectChain(columns, options);
2122
2638
  },
2123
2639
  insert(values, options) {
2124
2640
  if (Array.isArray(values)) {
@@ -2136,7 +2652,7 @@ function createTableBuilder(tableName, client) {
2136
2652
  payload.default_to_null = mergedOptions.defaultToNull;
2137
2653
  }
2138
2654
  const response = await client.insertGateway(payload, mergedOptions);
2139
- return formatResult(response);
2655
+ return formatGatewayResult(response, { table: resolvedTableName, operation: "insert" });
2140
2656
  };
2141
2657
  return createMutationQuery(executeInsertMany);
2142
2658
  }
@@ -2154,7 +2670,7 @@ function createTableBuilder(tableName, client) {
2154
2670
  payload.default_to_null = mergedOptions.defaultToNull;
2155
2671
  }
2156
2672
  const response = await client.insertGateway(payload, mergedOptions);
2157
- return formatResult(response);
2673
+ return formatGatewayResult(response, { table: resolvedTableName, operation: "insert" });
2158
2674
  };
2159
2675
  return createMutationQuery(executeInsertOne);
2160
2676
  },
@@ -2176,7 +2692,7 @@ function createTableBuilder(tableName, client) {
2176
2692
  payload.default_to_null = mergedOptions.defaultToNull;
2177
2693
  }
2178
2694
  const response = await client.insertGateway(payload, mergedOptions);
2179
- return formatResult(response);
2695
+ return formatGatewayResult(response, { table: resolvedTableName, operation: "insert" });
2180
2696
  };
2181
2697
  return createMutationQuery(executeUpsertMany);
2182
2698
  }
@@ -2196,7 +2712,7 @@ function createTableBuilder(tableName, client) {
2196
2712
  payload.default_to_null = mergedOptions.defaultToNull;
2197
2713
  }
2198
2714
  const response = await client.insertGateway(payload, mergedOptions);
2199
- return formatResult(response);
2715
+ return formatGatewayResult(response, { table: resolvedTableName, operation: "insert" });
2200
2716
  };
2201
2717
  return createMutationQuery(executeUpsertOne);
2202
2718
  },
@@ -2217,7 +2733,7 @@ function createTableBuilder(tableName, client) {
2217
2733
  if (state.totalPages !== void 0) payload.total_pages = state.totalPages;
2218
2734
  if (columns) payload.columns = columns;
2219
2735
  const response = await client.updateGateway(payload, mergedOptions);
2220
- return formatResult(response);
2736
+ return formatGatewayResult(response, { table: resolvedTableName, operation: "update" });
2221
2737
  };
2222
2738
  const mutation = createMutationQuery(executeUpdate, null);
2223
2739
  const updateChain = {};
@@ -2245,7 +2761,7 @@ function createTableBuilder(tableName, client) {
2245
2761
  if (state.totalPages !== void 0) payload.total_pages = state.totalPages;
2246
2762
  if (columns) payload.columns = columns;
2247
2763
  const response = await client.deleteGateway(payload, mergedOptions);
2248
- return formatResult(response);
2764
+ return formatGatewayResult(response, { table: resolvedTableName, operation: "delete" });
2249
2765
  };
2250
2766
  return createMutationQuery(executeDelete, null);
2251
2767
  },
@@ -2259,14 +2775,14 @@ function createTableBuilder(tableName, client) {
2259
2775
  });
2260
2776
  return builder;
2261
2777
  }
2262
- function createQueryBuilder(client) {
2778
+ function createQueryBuilder(client, formatGatewayResult) {
2263
2779
  return async function query(query, options) {
2264
2780
  const normalizedQuery = query.trim();
2265
2781
  if (!normalizedQuery) {
2266
2782
  throw new Error("query requires a non-empty string");
2267
2783
  }
2268
2784
  const response = await client.queryGateway({ query: normalizedQuery }, options);
2269
- return formatResult(response);
2785
+ return formatGatewayResult(response, { operation: "query" });
2270
2786
  };
2271
2787
  }
2272
2788
  function createClientFromConfig(config) {
@@ -2277,24 +2793,29 @@ function createClientFromConfig(config) {
2277
2793
  backend: config.backend,
2278
2794
  headers: config.headers
2279
2795
  });
2796
+ const formatGatewayResult = createResultFormatter(config.experimental);
2280
2797
  const auth = createAuthClient(config.auth);
2798
+ const from = (table) => createTableBuilder(table, gateway, formatGatewayResult);
2799
+ const rpc = (fn, args, options) => {
2800
+ const normalizedFn = fn.trim();
2801
+ if (!normalizedFn) {
2802
+ throw new Error("rpc requires a function name");
2803
+ }
2804
+ return createRpcBuilder(
2805
+ normalizedFn,
2806
+ args,
2807
+ options,
2808
+ gateway,
2809
+ formatGatewayResult
2810
+ );
2811
+ };
2812
+ const query = createQueryBuilder(gateway, formatGatewayResult);
2813
+ const db = createDbModule({ from, rpc, query });
2281
2814
  return {
2282
- from(table) {
2283
- return createTableBuilder(table, gateway);
2284
- },
2285
- rpc(fn, args, options) {
2286
- const normalizedFn = fn.trim();
2287
- if (!normalizedFn) {
2288
- throw new Error("rpc requires a function name");
2289
- }
2290
- return createRpcBuilder(
2291
- normalizedFn,
2292
- args,
2293
- options,
2294
- gateway
2295
- );
2296
- },
2297
- query: createQueryBuilder(gateway),
2815
+ from,
2816
+ db,
2817
+ rpc,
2818
+ query,
2298
2819
  auth: auth.auth
2299
2820
  };
2300
2821
  }
@@ -2305,516 +2826,86 @@ function toBackendConfig(b) {
2305
2826
  }
2306
2827
  var AthenaClientBuilderImpl = class {
2307
2828
  baseUrl;
2308
- apiKey;
2309
- backendConfig = DEFAULT_BACKEND;
2310
- clientName;
2311
- defaultHeaders;
2312
- isHealthTrackingEnabled = false;
2313
- url(url) {
2314
- this.baseUrl = url;
2315
- return this;
2316
- }
2317
- key(apiKey) {
2318
- this.apiKey = apiKey;
2319
- return this;
2320
- }
2321
- backend(backend) {
2322
- this.backendConfig = toBackendConfig(backend);
2323
- return this;
2324
- }
2325
- client(clientName) {
2326
- this.clientName = clientName;
2327
- return this;
2328
- }
2329
- headers(headers) {
2330
- this.defaultHeaders = headers;
2331
- return this;
2332
- }
2333
- healthTracking(enabled) {
2334
- this.isHealthTrackingEnabled = enabled;
2335
- return this;
2336
- }
2337
- build() {
2338
- if (!this.baseUrl || !this.apiKey) {
2339
- throw new Error("AthenaClient requires url and key; call .url() and .key() before .build()");
2340
- }
2341
- return createClientFromConfig({
2342
- baseUrl: this.baseUrl,
2343
- apiKey: this.apiKey,
2344
- client: this.clientName,
2345
- backend: this.backendConfig,
2346
- headers: this.defaultHeaders,
2347
- healthTracking: this.isHealthTrackingEnabled
2348
- });
2349
- }
2350
- };
2351
- var AthenaClient = class _AthenaClient {
2352
- /** Create a fluent builder for a strongly-typed Athena SDK client. */
2353
- static builder() {
2354
- return new AthenaClientBuilderImpl();
2355
- }
2356
- /** Build a client from process environment variables. */
2357
- static fromEnvironment() {
2358
- const url = process.env.ATHENA_URL ?? process.env.ATHENA_GATEWAY_URL;
2359
- const key = process.env.ATHENA_API_KEY ?? process.env.ATHENA_GATEWAY_API_KEY;
2360
- if (!url || !key) {
2361
- throw new Error(
2362
- "ATHENA_URL and ATHENA_API_KEY (or ATHENA_GATEWAY_URL and ATHENA_GATEWAY_API_KEY) are required"
2363
- );
2364
- }
2365
- return _AthenaClient.builder().url(url).key(key).build();
2366
- }
2367
- };
2368
- function createClient(url, apiKey, options) {
2369
- const b = AthenaClient.builder().url(url).key(apiKey).backend(toBackendConfig(options?.backend));
2370
- if (options?.client) b.client(options.client);
2371
- if (options?.headers && Object.keys(options.headers).length > 0) b.headers(options.headers);
2372
- const client = b.build();
2373
- if (options?.auth) {
2374
- client.auth = createAuthClient(options.auth).auth;
2375
- }
2376
- return client;
2377
- }
2378
-
2379
- // src/gateway/types.ts
2380
- var Backend = {
2381
- Athena: { type: "athena" },
2382
- Postgrest: { type: "postgrest" },
2383
- PostgreSQL: { type: "postgresql" },
2384
- ScyllaDB: { type: "scylladb" }
2385
- };
2386
-
2387
- // src/auxiliaries.ts
2388
- var AthenaErrorKind = {
2389
- UniqueViolation: "unique_violation",
2390
- NotFound: "not_found",
2391
- Validation: "validation",
2392
- Auth: "auth",
2393
- RateLimit: "rate_limit",
2394
- Transient: "transient",
2395
- Unknown: "unknown"
2396
- };
2397
- var AthenaErrorCode = {
2398
- UniqueViolation: "UNIQUE_VIOLATION",
2399
- NotFound: "NOT_FOUND",
2400
- ValidationFailed: "VALIDATION_FAILED",
2401
- AuthUnauthorized: "AUTH_UNAUTHORIZED",
2402
- AuthForbidden: "AUTH_FORBIDDEN",
2403
- RateLimited: "RATE_LIMITED",
2404
- NetworkUnavailable: "NETWORK_UNAVAILABLE",
2405
- TransientFailure: "TRANSIENT_FAILURE",
2406
- HttpFailure: "HTTP_FAILURE",
2407
- Unknown: "UNKNOWN"
2408
- };
2409
- var AthenaErrorCategory = {
2410
- Transport: "transport",
2411
- Client: "client",
2412
- Server: "server",
2413
- Database: "database",
2414
- Unknown: "unknown"
2415
- };
2416
- function parseBooleanFlag(rawValue, fallback) {
2417
- if (!rawValue) return fallback;
2418
- const normalized = rawValue.trim().toLowerCase();
2419
- if (normalized === "1" || normalized === "true" || normalized === "yes" || normalized === "on") {
2420
- return true;
2421
- }
2422
- if (normalized === "0" || normalized === "false" || normalized === "no" || normalized === "off") {
2423
- return false;
2424
- }
2425
- return fallback;
2426
- }
2427
- var AthenaError = class extends Error {
2428
- code;
2429
- kind;
2430
- category;
2431
- status;
2432
- retryable;
2433
- requestId;
2434
- context;
2435
- raw;
2436
- constructor(input) {
2437
- super(input.message);
2438
- this.name = "AthenaError";
2439
- this.code = input.code;
2440
- this.kind = input.kind;
2441
- this.category = input.category;
2442
- this.status = input.status;
2443
- this.retryable = input.retryable ?? false;
2444
- this.requestId = input.requestId;
2445
- this.context = input.context;
2446
- this.raw = input.raw;
2447
- }
2448
- };
2449
- function isRecord3(value) {
2450
- return Boolean(value) && typeof value === "object" && !Array.isArray(value);
2451
- }
2452
- function safeStringify(value) {
2453
- try {
2454
- const serialized = JSON.stringify(value);
2455
- return serialized ?? String(value);
2456
- } catch {
2457
- return "[unserializable]";
2458
- }
2459
- }
2460
- function contextHint(context) {
2461
- if (!context?.identity) return void 0;
2462
- const identity = typeof context.identity === "string" ? context.identity : safeStringify(context.identity);
2463
- return `Identity: ${identity}`;
2464
- }
2465
- function isAthenaResultLike(value) {
2466
- return isRecord3(value) && "status" in value && "error" in value && "data" in value && typeof value.status === "number";
2467
- }
2468
- function operationFromDetails(details) {
2469
- if (!details?.endpoint) return void 0;
2470
- if (details.endpoint === "/gateway/fetch" || details.endpoint === "/gateway/query") return "select";
2471
- if (details.endpoint === "/gateway/insert") return "insert";
2472
- if (details.endpoint === "/gateway/update") return "update";
2473
- if (details.endpoint === "/gateway/delete") return "delete";
2474
- if (details.endpoint === "/gateway/rpc" || details.endpoint.startsWith("/rpc/")) return "rpc";
2475
- return void 0;
2476
- }
2477
- function matchRegex(input, patterns) {
2478
- for (const pattern of patterns) {
2479
- const match = pattern.exec(input);
2480
- if (match?.[1]) return match[1];
2481
- }
2482
- return void 0;
2483
- }
2484
- function extractConstraint(message) {
2485
- return matchRegex(message, [
2486
- /unique constraint\s+["'`]([^"'`]+)["'`]/i,
2487
- /constraint\s+["'`]([^"'`]+)["'`]/i
2488
- ]);
2489
- }
2490
- function extractTable(message) {
2491
- return matchRegex(message, [
2492
- /(?:table|relation)\s+["'`]([^"'`]+)["'`]/i,
2493
- /on\s+table\s+([a-zA-Z0-9_.]+)/i
2494
- ]);
2495
- }
2496
- function classifyKind(status, code, message) {
2497
- const lower = message.toLowerCase();
2498
- const hasUniquePattern = lower.includes("unique constraint") || lower.includes("duplicate key") || lower.includes("already exists") || lower.includes("duplicate");
2499
- const hasNotFoundPattern = lower.includes("not found") || lower.includes("no rows");
2500
- const hasValidationPattern = lower.includes("validation") || lower.includes("invalid") || lower.includes("malformed");
2501
- const hasAuthPattern = lower.includes("unauthorized") || lower.includes("forbidden") || lower.includes("auth");
2502
- const hasRateLimitPattern = lower.includes("rate limit") || lower.includes("too many requests");
2503
- const hasTransientPattern = lower.includes("timeout") || lower.includes("temporar") || lower.includes("connection reset") || lower.includes("socket") || lower.includes("deadlock");
2504
- if (status === 409 || hasUniquePattern) return "unique_violation";
2505
- if (status === 404 || hasNotFoundPattern) return "not_found";
2506
- if (status === 401 || status === 403 || hasAuthPattern) return "auth";
2507
- if (status === 429 || hasRateLimitPattern) return "rate_limit";
2508
- if (status === 400 || status === 422 || hasValidationPattern) return "validation";
2509
- if (code === "NETWORK_ERROR" || status === 0 || status !== void 0 && status >= 500 || hasTransientPattern) {
2510
- return "transient";
2511
- }
2512
- return "unknown";
2513
- }
2514
- function toAthenaErrorCode(kind, status, gatewayCode) {
2515
- if (gatewayCode === "NETWORK_ERROR" || kind === "transient" && status === 0) {
2516
- return AthenaErrorCode.NetworkUnavailable;
2517
- }
2518
- switch (kind) {
2519
- case "unique_violation":
2520
- return AthenaErrorCode.UniqueViolation;
2521
- case "not_found":
2522
- return AthenaErrorCode.NotFound;
2523
- case "validation":
2524
- return AthenaErrorCode.ValidationFailed;
2525
- case "rate_limit":
2526
- return AthenaErrorCode.RateLimited;
2527
- case "auth":
2528
- if (status === 403) {
2529
- return AthenaErrorCode.AuthForbidden;
2530
- }
2531
- return AthenaErrorCode.AuthUnauthorized;
2532
- case "transient":
2533
- return status !== void 0 && status >= 500 ? AthenaErrorCode.HttpFailure : AthenaErrorCode.TransientFailure;
2534
- case "unknown":
2535
- default:
2536
- return status !== void 0 && status >= 400 ? AthenaErrorCode.HttpFailure : AthenaErrorCode.Unknown;
2537
- }
2538
- }
2539
- function toAthenaErrorCategory(kind, status) {
2540
- if (kind === "transient" && (status === 0 || status === void 0)) {
2541
- return AthenaErrorCategory.Transport;
2542
- }
2543
- if (kind === "unique_violation") return AthenaErrorCategory.Database;
2544
- if (kind === "validation" || kind === "auth" || kind === "not_found") return AthenaErrorCategory.Client;
2545
- if (kind === "rate_limit" || kind === "transient") return AthenaErrorCategory.Server;
2546
- return AthenaErrorCategory.Unknown;
2547
- }
2548
- function isRetryable(kind, status) {
2549
- if (kind === "rate_limit" || kind === "transient") return true;
2550
- return status !== void 0 && status >= 500;
2551
- }
2552
- function toGatewayCode(kind, status) {
2553
- if (kind === "transient" && (status === 0 || status === void 0)) return "NETWORK_ERROR";
2554
- if (kind === "validation") return "INVALID_JSON";
2555
- if (status !== void 0 && status >= 400) return "HTTP_ERROR";
2556
- return "UNKNOWN_ERROR";
2557
- }
2558
- function toAthenaGatewayError(source, fallbackMessage, context) {
2559
- if (isAthenaGatewayError(source)) {
2560
- return source;
2561
- }
2562
- if (isAthenaResultLike(source) && source.errorDetails) {
2563
- return new AthenaGatewayError({
2564
- code: source.errorDetails.code,
2565
- message: source.error ?? source.errorDetails.message,
2566
- status: source.status,
2567
- endpoint: source.errorDetails.endpoint,
2568
- method: source.errorDetails.method,
2569
- requestId: source.errorDetails.requestId,
2570
- hint: source.errorDetails.hint,
2571
- cause: source.errorDetails.cause
2572
- });
2573
- }
2574
- const normalized = normalizeAthenaError(source, context);
2575
- const message = isAthenaResultLike(source) && source.error == null && source.errorDetails == null ? fallbackMessage : normalized.message || fallbackMessage;
2576
- return new AthenaGatewayError({
2577
- code: toGatewayCode(normalized.kind, normalized.status),
2578
- message,
2579
- status: normalized.status ?? 0,
2580
- hint: normalized.constraint != null ? `Constraint: ${normalized.constraint}` : contextHint(context),
2581
- cause: typeof normalized.raw === "string" ? normalized.raw : safeStringify(normalized.raw)
2582
- });
2583
- }
2584
- function isOk(result) {
2585
- return result.error == null && result.status >= 200 && result.status < 300;
2586
- }
2587
- function normalizeAthenaError(resultOrError, context) {
2588
- if (isAthenaResultLike(resultOrError)) {
2589
- const details = resultOrError.errorDetails;
2590
- const message2 = resultOrError.error ?? details?.message ?? `Athena ${context?.operation ?? operationFromDetails(details) ?? "request"} failed`;
2591
- const operation = context?.operation ?? operationFromDetails(details);
2592
- const table = context?.table ?? extractTable(message2);
2593
- const constraint = extractConstraint(message2);
2594
- const kind2 = classifyKind(resultOrError.status, details?.code, message2);
2595
- const code = toAthenaErrorCode(kind2, resultOrError.status, details?.code);
2596
- const category = toAthenaErrorCategory(kind2, resultOrError.status);
2597
- return {
2598
- kind: kind2,
2599
- code,
2600
- category,
2601
- retryable: isRetryable(kind2, resultOrError.status),
2602
- status: resultOrError.status,
2603
- constraint,
2604
- table,
2605
- operation,
2606
- message: message2,
2607
- raw: resultOrError.raw
2608
- };
2609
- }
2610
- if (isAthenaGatewayError(resultOrError)) {
2611
- const details = resultOrError.toDetails();
2612
- const operation = context?.operation ?? operationFromDetails(details);
2613
- const table = context?.table ?? extractTable(resultOrError.message);
2614
- const constraint = extractConstraint(resultOrError.message);
2615
- const kind2 = classifyKind(resultOrError.status, resultOrError.code, resultOrError.message);
2616
- const code = toAthenaErrorCode(kind2, resultOrError.status, resultOrError.code);
2617
- const category = toAthenaErrorCategory(kind2, resultOrError.status);
2618
- return {
2619
- kind: kind2,
2620
- code,
2621
- category,
2622
- retryable: isRetryable(kind2, resultOrError.status),
2623
- status: resultOrError.status,
2624
- constraint,
2625
- table,
2626
- operation,
2627
- message: resultOrError.message,
2628
- raw: resultOrError
2629
- };
2630
- }
2631
- if (resultOrError instanceof Error) {
2632
- const maybeStatus = isRecord3(resultOrError) && typeof resultOrError.status === "number" ? resultOrError.status : void 0;
2633
- const kind2 = classifyKind(maybeStatus, void 0, resultOrError.message);
2634
- return {
2635
- kind: kind2,
2636
- code: toAthenaErrorCode(kind2, maybeStatus),
2637
- category: toAthenaErrorCategory(kind2, maybeStatus),
2638
- retryable: isRetryable(kind2, maybeStatus),
2639
- status: maybeStatus,
2640
- constraint: extractConstraint(resultOrError.message),
2641
- table: context?.table ?? extractTable(resultOrError.message),
2642
- operation: context?.operation,
2643
- message: resultOrError.message,
2644
- raw: resultOrError
2645
- };
2646
- }
2647
- const message = typeof resultOrError === "string" ? resultOrError : "Unknown Athena error";
2648
- const kind = classifyKind(void 0, void 0, message);
2649
- return {
2650
- kind,
2651
- code: toAthenaErrorCode(kind, void 0),
2652
- category: toAthenaErrorCategory(kind, void 0),
2653
- retryable: isRetryable(kind, void 0),
2654
- status: void 0,
2655
- constraint: extractConstraint(message),
2656
- table: context?.table ?? extractTable(message),
2657
- operation: context?.operation,
2658
- message,
2659
- raw: resultOrError
2660
- };
2661
- }
2662
- function unwrapRows(result, options) {
2663
- if (!isOk(result)) {
2664
- throw toAthenaGatewayError(result, "Athena request failed", options?.context);
2665
- }
2666
- if (result.data == null) return [];
2667
- return Array.isArray(result.data) ? result.data : [result.data];
2668
- }
2669
- function unwrap(result, options) {
2670
- if (!isOk(result)) {
2671
- throw toAthenaGatewayError(result, "Athena request failed", options?.context);
2672
- }
2673
- if (result.data == null && !options?.allowNull) {
2674
- throw toAthenaGatewayError(result, "Expected data but received null", options?.context);
2675
- }
2676
- return result.data;
2677
- }
2678
- function unwrapOne(result, options) {
2679
- const rows = unwrapRows(result, options);
2680
- if (!rows.length) {
2681
- if (options?.allowNull) return null;
2682
- throw toAthenaGatewayError(result, "Expected one row but received none", options?.context);
2683
- }
2684
- if (options?.requireExactlyOne && rows.length !== 1) {
2685
- throw toAthenaGatewayError(
2686
- result,
2687
- `Expected exactly one row but received ${rows.length}`,
2688
- options.context
2689
- );
2829
+ apiKey;
2830
+ backendConfig = DEFAULT_BACKEND;
2831
+ clientName;
2832
+ defaultHeaders;
2833
+ isHealthTrackingEnabled = false;
2834
+ url(url) {
2835
+ this.baseUrl = url;
2836
+ return this;
2690
2837
  }
2691
- return rows[0];
2692
- }
2693
- function requireSuccess(result, context) {
2694
- if (!isOk(result)) {
2695
- throw toAthenaGatewayError(
2696
- result,
2697
- `Athena ${context?.operation ?? "request"} failed`,
2698
- context
2699
- );
2838
+ key(apiKey) {
2839
+ this.apiKey = apiKey;
2840
+ return this;
2700
2841
  }
2701
- return result;
2702
- }
2703
- function requireAffected(result, options, context) {
2704
- requireSuccess(result, context);
2705
- const minimum = options?.min ?? 1;
2706
- const count = result.count;
2707
- if (count == null) {
2708
- throw new AthenaGatewayError({
2709
- code: "UNKNOWN_ERROR",
2710
- status: result.status,
2711
- message: "Expected affected row count but response.count is missing",
2712
- hint: 'Set call option { count: "exact" } for mutation postcondition checks.',
2713
- cause: safeStringify(result.raw)
2714
- });
2842
+ backend(backend) {
2843
+ this.backendConfig = toBackendConfig(backend);
2844
+ return this;
2715
2845
  }
2716
- if (count < minimum) {
2717
- throw new AthenaGatewayError({
2718
- code: "UNKNOWN_ERROR",
2719
- status: result.status,
2720
- message: `Expected at least ${minimum} affected rows but received ${count}`,
2721
- hint: contextHint(context),
2722
- cause: safeStringify(result.raw)
2723
- });
2846
+ client(clientName) {
2847
+ this.clientName = clientName;
2848
+ return this;
2724
2849
  }
2725
- return count;
2726
- }
2727
- function applyBounds(value, options) {
2728
- if (options?.min !== void 0 && value < options.min) return null;
2729
- if (options?.max !== void 0 && value > options.max) return null;
2730
- return value;
2731
- }
2732
- function parseIntegerString(value) {
2733
- const normalized = value.trim();
2734
- if (!/^[-+]?\d+$/.test(normalized)) return null;
2735
- const parsed = Number(normalized);
2736
- if (!Number.isFinite(parsed) || !Number.isInteger(parsed)) return null;
2737
- return parsed;
2738
- }
2739
- function coerceInt(value, options) {
2740
- if (value == null) return null;
2741
- if (typeof value === "number") {
2742
- if (!Number.isFinite(value) || !Number.isInteger(value)) return null;
2743
- return applyBounds(value, options);
2850
+ headers(headers) {
2851
+ this.defaultHeaders = headers;
2852
+ return this;
2744
2853
  }
2745
- if (typeof value === "string") {
2746
- const parsed = parseIntegerString(value);
2747
- if (parsed == null) return null;
2748
- return applyBounds(parsed, options);
2854
+ healthTracking(enabled) {
2855
+ this.isHealthTrackingEnabled = enabled;
2856
+ return this;
2749
2857
  }
2750
- if (typeof value === "bigint") {
2751
- if (options?.strictBigInt) {
2752
- if (value > BigInt(Number.MAX_SAFE_INTEGER) || value < BigInt(Number.MIN_SAFE_INTEGER)) {
2753
- return null;
2754
- }
2858
+ build() {
2859
+ if (!this.baseUrl || !this.apiKey) {
2860
+ throw new Error("AthenaClient requires url and key; call .url() and .key() before .build()");
2755
2861
  }
2756
- const parsed = Number(value);
2757
- if (!Number.isFinite(parsed) || !Number.isInteger(parsed)) return null;
2758
- return applyBounds(parsed, options);
2862
+ return createClientFromConfig({
2863
+ baseUrl: this.baseUrl,
2864
+ apiKey: this.apiKey,
2865
+ client: this.clientName,
2866
+ backend: this.backendConfig,
2867
+ headers: this.defaultHeaders,
2868
+ healthTracking: this.isHealthTrackingEnabled
2869
+ });
2759
2870
  }
2760
- return null;
2761
- }
2762
- function assertInt(value, label = "value", options) {
2763
- const parsed = coerceInt(value, options);
2764
- if (parsed == null) {
2765
- throw new TypeError(`${label} must be a finite integer`);
2871
+ };
2872
+ var AthenaClient = class _AthenaClient {
2873
+ /** Create a fluent builder for a strongly-typed Athena SDK client. */
2874
+ static builder() {
2875
+ return new AthenaClientBuilderImpl();
2766
2876
  }
2767
- return parsed;
2768
- }
2769
- function defaultShouldRetry(error) {
2770
- const normalized = normalizeAthenaError(error);
2771
- return normalized.kind === "transient" || normalized.kind === "rate_limit";
2772
- }
2773
- function computeDelayMs(attempt, error, config) {
2774
- const baseDelay = config.baseDelayMs;
2775
- const rawDelay = typeof config.backoff === "function" ? config.backoff(attempt, error) : config.backoff === "linear" ? baseDelay * attempt : baseDelay * Math.pow(2, attempt - 1);
2776
- const safeDelay = Number.isFinite(rawDelay) ? Math.max(0, rawDelay) : 0;
2777
- const clamped = Math.min(config.maxDelayMs, safeDelay);
2778
- const jitterFactor = typeof config.jitter === "number" ? Math.max(0, Math.min(1, config.jitter)) : config.jitter ? 0.2 : 0;
2779
- if (!jitterFactor) return clamped;
2780
- const deviation = clamped * jitterFactor;
2781
- const offset = (Math.random() * 2 - 1) * deviation;
2782
- return Math.max(0, clamped + offset);
2783
- }
2784
- function sleep(ms) {
2785
- if (ms <= 0) return Promise.resolve();
2786
- return new Promise((resolve3) => {
2787
- setTimeout(resolve3, ms);
2788
- });
2789
- }
2790
- async function withRetry(config, fn) {
2791
- const retries = Math.max(0, Math.trunc(config.retries));
2792
- const shouldRetry = config.shouldRetry ?? defaultShouldRetry;
2793
- const resolvedConfig = {
2794
- baseDelayMs: config.baseDelayMs ?? 100,
2795
- maxDelayMs: config.maxDelayMs ?? 1e4,
2796
- backoff: config.backoff ?? "exponential",
2797
- jitter: config.jitter ?? false
2798
- };
2799
- for (let attempts = 0; attempts <= retries; attempts += 1) {
2800
- try {
2801
- return await fn();
2802
- } catch (error) {
2803
- if (attempts >= retries) {
2804
- throw error;
2805
- }
2806
- const currentAttempt = attempts + 1;
2807
- const retry = await shouldRetry(error, currentAttempt);
2808
- if (!retry) {
2809
- throw error;
2810
- }
2811
- const delay = computeDelayMs(currentAttempt, error, resolvedConfig);
2812
- await sleep(delay);
2877
+ /** Build a client from process environment variables. */
2878
+ static fromEnvironment() {
2879
+ const url = process.env.ATHENA_URL ?? process.env.ATHENA_GATEWAY_URL;
2880
+ const key = process.env.ATHENA_API_KEY ?? process.env.ATHENA_GATEWAY_API_KEY;
2881
+ if (!url || !key) {
2882
+ throw new Error(
2883
+ "ATHENA_URL and ATHENA_API_KEY (or ATHENA_GATEWAY_URL and ATHENA_GATEWAY_API_KEY) are required"
2884
+ );
2813
2885
  }
2886
+ return _AthenaClient.builder().url(url).key(key).build();
2814
2887
  }
2815
- throw new Error("withRetry reached an unexpected state");
2888
+ };
2889
+ function createClient(url, apiKey, options) {
2890
+ return createClientFromConfig({
2891
+ baseUrl: url,
2892
+ apiKey,
2893
+ client: options?.client,
2894
+ backend: toBackendConfig(options?.backend),
2895
+ headers: options?.headers,
2896
+ auth: options?.auth,
2897
+ experimental: options?.experimental
2898
+ });
2816
2899
  }
2817
2900
 
2901
+ // src/gateway/types.ts
2902
+ var Backend = {
2903
+ Athena: { type: "athena" },
2904
+ Postgrest: { type: "postgrest" },
2905
+ PostgreSQL: { type: "postgresql" },
2906
+ ScyllaDB: { type: "scylladb" }
2907
+ };
2908
+
2818
2909
  // src/schema/definitions.ts
2819
2910
  function defineModel(input) {
2820
2911
  return input;
@@ -2880,6 +2971,7 @@ var TypedAthenaClientImpl = class _TypedAthenaClientImpl {
2880
2971
  registry;
2881
2972
  tenantKeyMap;
2882
2973
  tenantContext;
2974
+ db;
2883
2975
  baseClient;
2884
2976
  registryNavigator;
2885
2977
  tenantHeaderMapper;
@@ -2906,6 +2998,7 @@ var TypedAthenaClientImpl = class _TypedAthenaClientImpl {
2906
2998
  client: this.clientOptions.client,
2907
2999
  headers: this.tenantHeaderMapper.apply(this.clientOptions.headers, tenantContext)
2908
3000
  });
3001
+ this.db = this.baseClient.db;
2909
3002
  }
2910
3003
  from(table) {
2911
3004
  return this.baseClient.from(table);
@@ -3295,6 +3388,21 @@ var PostgresCatalogSnapshotAssembler = class {
3295
3388
  };
3296
3389
 
3297
3390
  // src/schema/postgres-provider.ts
3391
+ var pgPoolConstructorPromise;
3392
+ async function loadPgPoolConstructor() {
3393
+ if (!pgPoolConstructorPromise) {
3394
+ pgPoolConstructorPromise = import('pg').then((module) => {
3395
+ const poolConstructor = module.Pool ?? module.default?.Pool;
3396
+ if (!poolConstructor) {
3397
+ throw new Error(
3398
+ '@xylex-group/athena: Unable to load the PostgreSQL driver. Ensure "pg" is installed and this API runs in a Node.js server runtime.'
3399
+ );
3400
+ }
3401
+ return poolConstructor;
3402
+ });
3403
+ }
3404
+ return pgPoolConstructorPromise;
3405
+ }
3298
3406
  var PgCatalogClient = class {
3299
3407
  constructor(pool) {
3300
3408
  this.pool = pool;
@@ -3334,7 +3442,8 @@ var PostgresIntrospectionProvider = class {
3334
3442
  }
3335
3443
  async inspect(options) {
3336
3444
  const schemas = options?.schemas && options.schemas.length > 0 ? normalizePostgresCatalogSchemas(options.schemas) : this.schemas;
3337
- const pool = new Pool({
3445
+ const PoolConstructor = await loadPgPoolConstructor();
3446
+ const pool = new PoolConstructor({
3338
3447
  connectionString: this.connectionString
3339
3448
  });
3340
3449
  const catalogClient = new PgCatalogClient(pool);
@@ -3456,6 +3565,7 @@ function resolveProviderSchemas(providerConfig) {
3456
3565
  }
3457
3566
 
3458
3567
  // src/generator/config.ts
3568
+ var POSTGRES_PROTOCOLS = /* @__PURE__ */ new Set(["postgres:", "postgresql:"]);
3459
3569
  var DEFAULT_CONFIG_CANDIDATES = [
3460
3570
  "athena.config.ts",
3461
3571
  "athena.config.js",
@@ -3485,6 +3595,151 @@ var DEFAULT_EXPERIMENTAL_FLAGS = {
3485
3595
  postgresGatewayIntrospection: false,
3486
3596
  scyllaProviderContracts: true
3487
3597
  };
3598
+ var PROJECT_ENV_FILENAMES = [".env", ".env.local"];
3599
+ var DIRECT_CONNECTION_STRING_ENV_KEYS = [
3600
+ "ATHENA_GENERATOR_PG_URL",
3601
+ "DATABASE_URL",
3602
+ "PG_URL",
3603
+ "POSTGRES_URL",
3604
+ "POSTGRESQL_URL"
3605
+ ];
3606
+ var POSTGRES_DATABASE_ENV_KEYS = ["ATHENA_GENERATOR_DB", "ATHENA_DATABASE", "PGDATABASE"];
3607
+ var POSTGRES_PASSWORD_ENV_KEYS = ["ATHENA_GENERATOR_PG_PASSWORD", "PGPASSWORD"];
3608
+ var GATEWAY_URL_ENV_KEYS = ["ATHENA_URL", "ATHENA_GATEWAY_URL", "ATHENA_GENERATOR_URL"];
3609
+ var GATEWAY_API_KEY_ENV_KEYS = [
3610
+ "ATHENA_API_KEY",
3611
+ "ATHENA_GATEWAY_API_KEY",
3612
+ "ATHENA_GENERATOR_API_KEY"
3613
+ ];
3614
+ function normalizeRawEnvValue(rawValue) {
3615
+ if (rawValue.startsWith('"') && rawValue.endsWith('"') && rawValue.length >= 2) {
3616
+ const inner = rawValue.slice(1, -1);
3617
+ return inner.replace(/\\n/g, "\n").replace(/\\r/g, "\r").replace(/\\t/g, " ").replace(/\\"/g, '"').replace(/\\\\/g, "\\");
3618
+ }
3619
+ if (rawValue.startsWith("'") && rawValue.endsWith("'") && rawValue.length >= 2) {
3620
+ return rawValue.slice(1, -1);
3621
+ }
3622
+ const commentIndex = rawValue.search(/\s+#/);
3623
+ const withoutComment = commentIndex >= 0 ? rawValue.slice(0, commentIndex) : rawValue;
3624
+ return withoutComment.trim();
3625
+ }
3626
+ function parseEnvLine(line) {
3627
+ const trimmed = line.trim();
3628
+ if (!trimmed || trimmed.startsWith("#")) {
3629
+ return void 0;
3630
+ }
3631
+ const match = trimmed.match(/^(?:export\s+)?([A-Za-z_][A-Za-z0-9_]*)\s*=\s*(.*)$/);
3632
+ if (!match) {
3633
+ return void 0;
3634
+ }
3635
+ const [, key, rawValue] = match;
3636
+ return [key, normalizeRawEnvValue(rawValue.trim())];
3637
+ }
3638
+ function readProjectEnvEntries(cwd) {
3639
+ const nodeEnv = process.env.NODE_ENV?.trim();
3640
+ const filenames = [
3641
+ ...PROJECT_ENV_FILENAMES,
3642
+ ...nodeEnv ? [`.env.${nodeEnv}`, `.env.${nodeEnv}.local`] : []
3643
+ ];
3644
+ const entries = /* @__PURE__ */ new Map();
3645
+ for (const filename of filenames) {
3646
+ const absolutePath = resolve(cwd, filename);
3647
+ if (!existsSync(absolutePath)) {
3648
+ continue;
3649
+ }
3650
+ const content = readFileSync(absolutePath, "utf8");
3651
+ const lines = content.split(/\r?\n/g);
3652
+ for (const line of lines) {
3653
+ const parsed = parseEnvLine(line);
3654
+ if (!parsed) {
3655
+ continue;
3656
+ }
3657
+ const [key, value] = parsed;
3658
+ entries.set(key, value);
3659
+ }
3660
+ }
3661
+ return entries;
3662
+ }
3663
+ function applyProjectEnv(cwd) {
3664
+ const envEntries = readProjectEnvEntries(cwd);
3665
+ if (envEntries.size === 0) {
3666
+ return () => {
3667
+ };
3668
+ }
3669
+ const initialKeys = new Set(
3670
+ Object.keys(process.env).filter((key) => process.env[key] !== void 0)
3671
+ );
3672
+ const staged = /* @__PURE__ */ new Map();
3673
+ for (const [key, value] of envEntries.entries()) {
3674
+ if (initialKeys.has(key)) {
3675
+ continue;
3676
+ }
3677
+ staged.set(key, value);
3678
+ }
3679
+ for (const [key, value] of staged.entries()) {
3680
+ process.env[key] = value;
3681
+ }
3682
+ return () => {
3683
+ for (const key of staged.keys()) {
3684
+ delete process.env[key];
3685
+ }
3686
+ };
3687
+ }
3688
+ function readEnvStringValue(key) {
3689
+ const value = process.env[key];
3690
+ if (typeof value !== "string") {
3691
+ return void 0;
3692
+ }
3693
+ const trimmed = value.trim();
3694
+ return trimmed.length > 0 ? trimmed : void 0;
3695
+ }
3696
+ function resolveFallbackValue(fallbackKeys) {
3697
+ for (const key of fallbackKeys) {
3698
+ const value = readEnvStringValue(key);
3699
+ if (value) {
3700
+ return value;
3701
+ }
3702
+ }
3703
+ return void 0;
3704
+ }
3705
+ function normalizeOptionalString(value, fallbackKeys) {
3706
+ if (typeof value === "string") {
3707
+ const trimmed = value.trim();
3708
+ if (trimmed.length > 0) {
3709
+ return trimmed;
3710
+ }
3711
+ }
3712
+ return resolveFallbackValue(fallbackKeys);
3713
+ }
3714
+ function normalizeRequiredString(value, fieldLabel, fallbackKeys) {
3715
+ const resolved = normalizeOptionalString(value, fallbackKeys);
3716
+ if (resolved) {
3717
+ return resolved;
3718
+ }
3719
+ throw new Error(
3720
+ `Generator config is missing ${fieldLabel}. Set ${fieldLabel} directly or provide one of: ${fallbackKeys.join(", ")}.`
3721
+ );
3722
+ }
3723
+ function applyPostgresPasswordFallback(connectionString) {
3724
+ let parsedUrl;
3725
+ try {
3726
+ parsedUrl = new URL(connectionString);
3727
+ } catch {
3728
+ return connectionString;
3729
+ }
3730
+ if (!POSTGRES_PROTOCOLS.has(parsedUrl.protocol)) {
3731
+ return connectionString;
3732
+ }
3733
+ if (!parsedUrl.username || parsedUrl.password) {
3734
+ return connectionString;
3735
+ }
3736
+ const fallbackPassword = resolveFallbackValue(POSTGRES_PASSWORD_ENV_KEYS);
3737
+ if (!fallbackPassword) {
3738
+ return connectionString;
3739
+ }
3740
+ parsedUrl.password = fallbackPassword;
3741
+ return parsedUrl.toString();
3742
+ }
3488
3743
  function normalizeBooleanFlag(rawValue, fallback) {
3489
3744
  if (typeof rawValue === "boolean") {
3490
3745
  return rawValue;
@@ -3530,9 +3785,41 @@ function normalizeOutputConfig(output) {
3530
3785
  };
3531
3786
  }
3532
3787
  function normalizeProviderConfig(provider) {
3533
- if (provider.kind === "postgres") {
3788
+ if (provider.kind === "postgres" && provider.mode === "direct") {
3789
+ const connectionString = normalizeRequiredString(
3790
+ provider.connectionString,
3791
+ "provider.connectionString",
3792
+ DIRECT_CONNECTION_STRING_ENV_KEYS
3793
+ );
3794
+ const database = normalizeOptionalString(provider.database, POSTGRES_DATABASE_ENV_KEYS);
3534
3795
  return {
3535
3796
  ...provider,
3797
+ connectionString: applyPostgresPasswordFallback(connectionString),
3798
+ database,
3799
+ schemas: normalizeSchemaSelection(provider.schemas)
3800
+ };
3801
+ }
3802
+ if (provider.kind === "postgres" && provider.mode === "gateway") {
3803
+ const gatewayUrl = normalizeRequiredString(
3804
+ provider.gatewayUrl,
3805
+ "provider.gatewayUrl",
3806
+ GATEWAY_URL_ENV_KEYS
3807
+ );
3808
+ const apiKey = normalizeRequiredString(
3809
+ provider.apiKey,
3810
+ "provider.apiKey",
3811
+ GATEWAY_API_KEY_ENV_KEYS
3812
+ );
3813
+ const database = normalizeRequiredString(
3814
+ provider.database,
3815
+ "provider.database",
3816
+ POSTGRES_DATABASE_ENV_KEYS
3817
+ );
3818
+ return {
3819
+ ...provider,
3820
+ gatewayUrl,
3821
+ apiKey,
3822
+ database,
3536
3823
  schemas: normalizeSchemaSelection(provider.schemas)
3537
3824
  };
3538
3825
  }
@@ -3594,6 +3881,11 @@ function extractConfigExport(module) {
3594
3881
  if (moduleExports && typeof moduleExports === "object") {
3595
3882
  queue.push(moduleExports);
3596
3883
  }
3884
+ for (const value of Object.values(record)) {
3885
+ if (value && typeof value === "object") {
3886
+ queue.push(value);
3887
+ }
3888
+ }
3597
3889
  }
3598
3890
  throw new Error(
3599
3891
  "Generator config file must export a config object as default export or `config`."
@@ -3608,19 +3900,24 @@ function importConfigModule(moduleSpecifier) {
3608
3900
  }
3609
3901
  async function loadGeneratorConfig(options = {}) {
3610
3902
  const cwd = options.cwd ?? process.cwd();
3903
+ const restoreProjectEnv = applyProjectEnv(cwd);
3611
3904
  const resolvedPath = options.configPath ? resolve(cwd, options.configPath) : findGeneratorConfigPath(cwd);
3612
3905
  if (!resolvedPath) {
3613
3906
  throw new Error(
3614
3907
  `No generator config found in ${cwd}. Expected one of: ${DEFAULT_CONFIG_CANDIDATES.join(", ")}`
3615
3908
  );
3616
3909
  }
3617
- const moduleUrl = pathToFileURL(resolvedPath);
3618
- const module = await importConfigModule(`${moduleUrl.href}?cacheBust=${Date.now()}`);
3619
- const rawConfig = extractConfigExport(module);
3620
- return {
3621
- configPath: resolvedPath,
3622
- config: normalizeGeneratorConfig(rawConfig)
3623
- };
3910
+ try {
3911
+ const moduleUrl = pathToFileURL(resolvedPath);
3912
+ const module = await importConfigModule(`${moduleUrl.href}?cacheBust=${Date.now()}`);
3913
+ const rawConfig = extractConfigExport(module);
3914
+ return {
3915
+ configPath: resolvedPath,
3916
+ config: normalizeGeneratorConfig(rawConfig)
3917
+ };
3918
+ } finally {
3919
+ restoreProjectEnv();
3920
+ }
3624
3921
  }
3625
3922
 
3626
3923
  // src/generator/naming.ts
@@ -3764,9 +4061,13 @@ function resolvePlaceholderMap(baseTokens, outputConfig) {
3764
4061
  const resolved = {
3765
4062
  ...baseTokens
3766
4063
  };
4064
+ const reservedTokenKeys = new Set(Object.keys(baseTokens));
3767
4065
  const entries = Object.entries(outputConfig.placeholderMap ?? {});
3768
4066
  for (let index = 0; index < entries.length; index += 1) {
3769
4067
  const [key, value] = entries[index];
4068
+ if (reservedTokenKeys.has(key)) {
4069
+ continue;
4070
+ }
3770
4071
  let current = value;
3771
4072
  for (let depth = 0; depth < 8; depth += 1) {
3772
4073
  const next = renderTemplate(current, resolved);
@@ -4046,13 +4347,65 @@ function assertNoDuplicatePaths(files) {
4046
4347
  [
4047
4348
  `Generator output collision detected for path: ${file.path}`,
4048
4349
  `Collision: ${existing.kind} and ${file.kind}.`,
4049
- "When syncing multiple schemas, include a schema placeholder such as {schema} or {schema_kebab} in model/schema output targets."
4350
+ "Use explicit placeholders such as {model}, {model_kebab}, {schema}, or {schema_kebab} in output targets so each artifact resolves to a unique path."
4050
4351
  ].join(" ")
4051
4352
  );
4052
4353
  }
4053
4354
  seen.set(file.path, file);
4054
4355
  }
4055
4356
  }
4357
+ function addSchemaSegmentToPath(pathValue, schemaName) {
4358
+ const normalizedPath = normalizePath(pathValue);
4359
+ const parsedPath = posix.parse(normalizedPath);
4360
+ const schemaSegment = applyNamingStyle(schemaName, "kebab");
4361
+ if (!schemaSegment) {
4362
+ return normalizedPath;
4363
+ }
4364
+ const dir = parsedPath.dir.length > 0 ? `${parsedPath.dir}/${schemaSegment}` : schemaSegment;
4365
+ return normalizePath(posix.join(dir, parsedPath.base));
4366
+ }
4367
+ function scopeDuplicateDescriptorPathsBySchema(descriptors) {
4368
+ const nextDescriptors = descriptors.map((descriptor) => ({ ...descriptor }));
4369
+ const duplicates = /* @__PURE__ */ new Map();
4370
+ for (let index = 0; index < nextDescriptors.length; index += 1) {
4371
+ const descriptor = nextDescriptors[index];
4372
+ const indexes = duplicates.get(descriptor.filePath) ?? [];
4373
+ indexes.push(index);
4374
+ duplicates.set(descriptor.filePath, indexes);
4375
+ }
4376
+ let appliedSchemaScoping = false;
4377
+ for (const indexes of duplicates.values()) {
4378
+ if (indexes.length <= 1) {
4379
+ continue;
4380
+ }
4381
+ const schemaNames = new Set(indexes.map((index) => nextDescriptors[index].schemaName));
4382
+ if (schemaNames.size <= 1) {
4383
+ continue;
4384
+ }
4385
+ for (const index of indexes) {
4386
+ const descriptor = nextDescriptors[index];
4387
+ descriptor.filePath = addSchemaSegmentToPath(descriptor.filePath, descriptor.schemaName);
4388
+ }
4389
+ appliedSchemaScoping = true;
4390
+ }
4391
+ if (!appliedSchemaScoping) {
4392
+ return nextDescriptors;
4393
+ }
4394
+ const normalizedPaths = /* @__PURE__ */ new Set();
4395
+ for (const descriptor of nextDescriptors) {
4396
+ if (normalizedPaths.has(descriptor.filePath)) {
4397
+ throw new Error(
4398
+ [
4399
+ `Generator output collision detected for path: ${descriptor.filePath}`,
4400
+ "Automatic schema path scoping was applied but collisions remain.",
4401
+ "Add explicit placeholders such as {model}, {model_kebab}, {schema}, or {schema_kebab} to your output targets."
4402
+ ].join(" ")
4403
+ );
4404
+ }
4405
+ normalizedPaths.add(descriptor.filePath);
4406
+ }
4407
+ return nextDescriptors;
4408
+ }
4056
4409
  var ArtifactComposer = class {
4057
4410
  constructor(snapshot, config) {
4058
4411
  this.snapshot = snapshot;
@@ -4091,7 +4444,8 @@ var ArtifactComposer = class {
4091
4444
  });
4092
4445
  }
4093
4446
  }
4094
- const schemaDescriptors = Object.keys(this.snapshot.schemas).sort().map((schemaName) => {
4447
+ const scopedModelDescriptors = scopeDuplicateDescriptorPathsBySchema(modelDescriptors);
4448
+ let schemaDescriptors = Object.keys(this.snapshot.schemas).sort().map((schemaName) => {
4095
4449
  const schemaPath = normalizePath(
4096
4450
  renderOutputPath(this.config.output.targets.schema, {
4097
4451
  provider: providerName,
@@ -4109,9 +4463,10 @@ var ArtifactComposer = class {
4109
4463
  this.config.naming.schemaConst,
4110
4464
  "schema"
4111
4465
  ),
4112
- models: modelDescriptors.filter((model) => model.schemaName === schemaName)
4466
+ models: scopedModelDescriptors.filter((model) => model.schemaName === schemaName)
4113
4467
  };
4114
4468
  });
4469
+ schemaDescriptors = scopeDuplicateDescriptorPathsBySchema(schemaDescriptors);
4115
4470
  const databasePath = normalizePath(
4116
4471
  renderOutputPath(this.config.output.targets.database, {
4117
4472
  provider: providerName,
@@ -4131,7 +4486,7 @@ var ArtifactComposer = class {
4131
4486
  schemas: schemaDescriptors
4132
4487
  };
4133
4488
  const files = [];
4134
- for (const modelDescriptor of modelDescriptors) {
4489
+ for (const modelDescriptor of scopedModelDescriptors) {
4135
4490
  files.push(renderModelArtifact(this.snapshot, modelDescriptor, this.config));
4136
4491
  }
4137
4492
  for (const schemaDescriptor of schemaDescriptors) {