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