@xylex-group/athena 1.2.1 → 1.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -61,6 +61,10 @@ function isAthenaGatewayError(error) {
61
61
  // src/gateway/client.ts
62
62
  var DEFAULT_BASE_URL = "https://athena-db.com";
63
63
  var DEFAULT_CLIENT = "railway_direct";
64
+ var FALLBACK_SDK_VERSION = "1.3.0";
65
+ var SDK_NAME = "xylex-group/athena";
66
+ var SDK_VERSION = typeof process !== "undefined" && process?.env?.npm_package_version ? process.env.npm_package_version : FALLBACK_SDK_VERSION;
67
+ var SDK_HEADER_VALUE = `${SDK_NAME} ${SDK_VERSION}`;
64
68
  function parseResponseBody(rawText, contentType) {
65
69
  if (!rawText) {
66
70
  return { parsed: null, parseFailed: false };
@@ -199,7 +203,8 @@ function buildHeaders(config, options) {
199
203
  const finalApiKey = options?.apiKey ?? config.apiKey;
200
204
  const finalPublishEvent = options?.publishEvent ?? config.publishEvent;
201
205
  const headers = {
202
- "Content-Type": "application/json"
206
+ "Content-Type": "application/json",
207
+ "X-Athena-Sdk": SDK_HEADER_VALUE
203
208
  };
204
209
  if (options?.userId ?? config.userId) {
205
210
  headers["X-User-Id"] = options?.userId ?? config.userId ?? "";
@@ -464,6 +469,25 @@ function createFilterMethods(state, addCondition, self) {
464
469
  state.offset = count;
465
470
  return self;
466
471
  },
472
+ currentPage(value) {
473
+ state.currentPage = value;
474
+ return self;
475
+ },
476
+ pageSize(value) {
477
+ state.pageSize = value;
478
+ return self;
479
+ },
480
+ totalPages(value) {
481
+ state.totalPages = value;
482
+ return self;
483
+ },
484
+ order(column, options) {
485
+ state.order = {
486
+ field: column,
487
+ direction: options?.ascending === false ? "descending" : "ascending"
488
+ };
489
+ return self;
490
+ },
467
491
  gt(column, value) {
468
492
  addCondition("gt", column, value);
469
493
  return self;
@@ -678,6 +702,10 @@ function createTableBuilder(tableName, client) {
678
702
  conditions: state.conditions.length ? [...state.conditions] : void 0,
679
703
  limit: state.limit,
680
704
  offset: state.offset,
705
+ current_page: state.currentPage,
706
+ page_size: state.pageSize,
707
+ total_pages: state.totalPages,
708
+ sort_by: state.order,
681
709
  strip_nulls: options?.stripNulls ?? true,
682
710
  count: options?.count,
683
711
  head: options?.head
@@ -713,6 +741,10 @@ function createTableBuilder(tableName, client) {
713
741
  state.conditions = [];
714
742
  state.limit = void 0;
715
743
  state.offset = void 0;
744
+ state.order = void 0;
745
+ state.currentPage = void 0;
746
+ state.pageSize = void 0;
747
+ state.totalPages = void 0;
716
748
  return builder;
717
749
  },
718
750
  select(columns = DEFAULT_COLUMNS, options) {
@@ -804,6 +836,10 @@ function createTableBuilder(tableName, client) {
804
836
  conditions: filters,
805
837
  strip_nulls: mergedOptions?.stripNulls ?? true
806
838
  };
839
+ if (state.order) payload.sort_by = state.order;
840
+ if (state.currentPage !== void 0) payload.current_page = state.currentPage;
841
+ if (state.pageSize !== void 0) payload.page_size = state.pageSize;
842
+ if (state.totalPages !== void 0) payload.total_pages = state.totalPages;
807
843
  if (columns) payload.columns = columns;
808
844
  const response = await client.updateGateway(payload, mergedOptions);
809
845
  return formatResult(response);
@@ -827,6 +863,10 @@ function createTableBuilder(tableName, client) {
827
863
  resource_id: resourceId,
828
864
  conditions: filters
829
865
  };
866
+ if (state.order) payload.sort_by = state.order;
867
+ if (state.currentPage !== void 0) payload.current_page = state.currentPage;
868
+ if (state.pageSize !== void 0) payload.page_size = state.pageSize;
869
+ if (state.totalPages !== void 0) payload.total_pages = state.totalPages;
830
870
  if (columns) payload.columns = columns;
831
871
  const response = await client.deleteGateway(payload, mergedOptions);
832
872
  return formatResult(response);
@@ -962,6 +1002,320 @@ var Backend = {
962
1002
  ScyllaDB: { type: "scylladb" }
963
1003
  };
964
1004
 
965
- export { AthenaClient, AthenaGatewayError, Backend, createClient, isAthenaGatewayError };
1005
+ // src/auxiliaries.ts
1006
+ function isRecord2(value) {
1007
+ return Boolean(value) && typeof value === "object" && !Array.isArray(value);
1008
+ }
1009
+ function safeStringify(value) {
1010
+ try {
1011
+ const serialized = JSON.stringify(value);
1012
+ return serialized ?? String(value);
1013
+ } catch {
1014
+ return "[unserializable]";
1015
+ }
1016
+ }
1017
+ function contextHint(context) {
1018
+ if (!context?.identity) return void 0;
1019
+ const identity = typeof context.identity === "string" ? context.identity : safeStringify(context.identity);
1020
+ return `Identity: ${identity}`;
1021
+ }
1022
+ function isAthenaResultLike(value) {
1023
+ return isRecord2(value) && "status" in value && "error" in value && "data" in value && typeof value.status === "number";
1024
+ }
1025
+ function operationFromDetails(details) {
1026
+ if (!details?.endpoint) return void 0;
1027
+ if (details.endpoint === "/gateway/fetch" || details.endpoint === "/gateway/query") return "select";
1028
+ if (details.endpoint === "/gateway/insert") return "insert";
1029
+ if (details.endpoint === "/gateway/update") return "update";
1030
+ if (details.endpoint === "/gateway/delete") return "delete";
1031
+ if (details.endpoint === "/gateway/rpc" || details.endpoint.startsWith("/rpc/")) return "rpc";
1032
+ return void 0;
1033
+ }
1034
+ function matchRegex(input, patterns) {
1035
+ for (const pattern of patterns) {
1036
+ const match = pattern.exec(input);
1037
+ if (match?.[1]) return match[1];
1038
+ }
1039
+ return void 0;
1040
+ }
1041
+ function extractConstraint(message) {
1042
+ return matchRegex(message, [
1043
+ /unique constraint\s+["'`]([^"'`]+)["'`]/i,
1044
+ /constraint\s+["'`]([^"'`]+)["'`]/i
1045
+ ]);
1046
+ }
1047
+ function extractTable(message) {
1048
+ return matchRegex(message, [
1049
+ /(?:table|relation)\s+["'`]([^"'`]+)["'`]/i,
1050
+ /on\s+table\s+([a-zA-Z0-9_.]+)/i
1051
+ ]);
1052
+ }
1053
+ function classifyKind(status, code, message) {
1054
+ const lower = message.toLowerCase();
1055
+ const hasUniquePattern = lower.includes("unique constraint") || lower.includes("duplicate key") || lower.includes("already exists") || lower.includes("duplicate");
1056
+ const hasNotFoundPattern = lower.includes("not found") || lower.includes("no rows");
1057
+ const hasValidationPattern = lower.includes("validation") || lower.includes("invalid") || lower.includes("malformed");
1058
+ const hasAuthPattern = lower.includes("unauthorized") || lower.includes("forbidden") || lower.includes("auth");
1059
+ const hasRateLimitPattern = lower.includes("rate limit") || lower.includes("too many requests");
1060
+ const hasTransientPattern = lower.includes("timeout") || lower.includes("temporar") || lower.includes("connection reset") || lower.includes("socket") || lower.includes("deadlock");
1061
+ if (status === 409 || hasUniquePattern) return "unique_violation";
1062
+ if (status === 404 || hasNotFoundPattern) return "not_found";
1063
+ if (status === 401 || status === 403 || hasAuthPattern) return "auth";
1064
+ if (status === 429 || hasRateLimitPattern) return "rate_limit";
1065
+ if (status === 400 || status === 422 || hasValidationPattern) return "validation";
1066
+ if (code === "NETWORK_ERROR" || status === 0 || status !== void 0 && status >= 500 || hasTransientPattern) {
1067
+ return "transient";
1068
+ }
1069
+ return "unknown";
1070
+ }
1071
+ function toGatewayCode(kind, status) {
1072
+ if (kind === "transient" && (status === 0 || status === void 0)) return "NETWORK_ERROR";
1073
+ if (kind === "validation") return "INVALID_JSON";
1074
+ if (status !== void 0 && status >= 400) return "HTTP_ERROR";
1075
+ return "UNKNOWN_ERROR";
1076
+ }
1077
+ function toAthenaGatewayError(source, fallbackMessage, context) {
1078
+ if (isAthenaGatewayError(source)) {
1079
+ return source;
1080
+ }
1081
+ if (isAthenaResultLike(source) && source.errorDetails) {
1082
+ return new AthenaGatewayError({
1083
+ code: source.errorDetails.code,
1084
+ message: source.error ?? source.errorDetails.message,
1085
+ status: source.status,
1086
+ endpoint: source.errorDetails.endpoint,
1087
+ method: source.errorDetails.method,
1088
+ requestId: source.errorDetails.requestId,
1089
+ hint: source.errorDetails.hint,
1090
+ cause: source.errorDetails.cause
1091
+ });
1092
+ }
1093
+ const normalized = normalizeAthenaError(source, context);
1094
+ const message = isAthenaResultLike(source) && source.error == null && source.errorDetails == null ? fallbackMessage : normalized.message || fallbackMessage;
1095
+ return new AthenaGatewayError({
1096
+ code: toGatewayCode(normalized.kind, normalized.status),
1097
+ message,
1098
+ status: normalized.status ?? 0,
1099
+ hint: normalized.constraint != null ? `Constraint: ${normalized.constraint}` : contextHint(context),
1100
+ cause: typeof normalized.raw === "string" ? normalized.raw : safeStringify(normalized.raw)
1101
+ });
1102
+ }
1103
+ function isOk(result) {
1104
+ return result.error == null && result.status >= 200 && result.status < 300;
1105
+ }
1106
+ function normalizeAthenaError(resultOrError, context) {
1107
+ if (isAthenaResultLike(resultOrError)) {
1108
+ const details = resultOrError.errorDetails;
1109
+ const message2 = resultOrError.error ?? details?.message ?? `Athena ${context?.operation ?? operationFromDetails(details) ?? "request"} failed`;
1110
+ const operation = context?.operation ?? operationFromDetails(details);
1111
+ const table = context?.table ?? extractTable(message2);
1112
+ const constraint = extractConstraint(message2);
1113
+ const kind = classifyKind(resultOrError.status, details?.code, message2);
1114
+ return {
1115
+ kind,
1116
+ status: resultOrError.status,
1117
+ constraint,
1118
+ table,
1119
+ operation,
1120
+ message: message2,
1121
+ raw: resultOrError.raw
1122
+ };
1123
+ }
1124
+ if (isAthenaGatewayError(resultOrError)) {
1125
+ const details = resultOrError.toDetails();
1126
+ const operation = context?.operation ?? operationFromDetails(details);
1127
+ const table = context?.table ?? extractTable(resultOrError.message);
1128
+ const constraint = extractConstraint(resultOrError.message);
1129
+ const kind = classifyKind(resultOrError.status, resultOrError.code, resultOrError.message);
1130
+ return {
1131
+ kind,
1132
+ status: resultOrError.status,
1133
+ constraint,
1134
+ table,
1135
+ operation,
1136
+ message: resultOrError.message,
1137
+ raw: resultOrError
1138
+ };
1139
+ }
1140
+ if (resultOrError instanceof Error) {
1141
+ const maybeStatus = isRecord2(resultOrError) && typeof resultOrError.status === "number" ? resultOrError.status : void 0;
1142
+ return {
1143
+ kind: classifyKind(maybeStatus, void 0, resultOrError.message),
1144
+ status: maybeStatus,
1145
+ constraint: extractConstraint(resultOrError.message),
1146
+ table: context?.table ?? extractTable(resultOrError.message),
1147
+ operation: context?.operation,
1148
+ message: resultOrError.message,
1149
+ raw: resultOrError
1150
+ };
1151
+ }
1152
+ const message = typeof resultOrError === "string" ? resultOrError : "Unknown Athena error";
1153
+ return {
1154
+ kind: classifyKind(void 0, void 0, message),
1155
+ status: void 0,
1156
+ constraint: extractConstraint(message),
1157
+ table: context?.table ?? extractTable(message),
1158
+ operation: context?.operation,
1159
+ message,
1160
+ raw: resultOrError
1161
+ };
1162
+ }
1163
+ function unwrapRows(result, options) {
1164
+ if (!isOk(result)) {
1165
+ throw toAthenaGatewayError(result, "Athena request failed", options?.context);
1166
+ }
1167
+ if (result.data == null) return [];
1168
+ return Array.isArray(result.data) ? result.data : [result.data];
1169
+ }
1170
+ function unwrap(result, options) {
1171
+ if (!isOk(result)) {
1172
+ throw toAthenaGatewayError(result, "Athena request failed", options?.context);
1173
+ }
1174
+ if (result.data == null && !options?.allowNull) {
1175
+ throw toAthenaGatewayError(result, "Expected data but received null", options?.context);
1176
+ }
1177
+ return result.data;
1178
+ }
1179
+ function unwrapOne(result, options) {
1180
+ const rows = unwrapRows(result, options);
1181
+ if (!rows.length) {
1182
+ if (options?.allowNull) return null;
1183
+ throw toAthenaGatewayError(result, "Expected one row but received none", options?.context);
1184
+ }
1185
+ if (options?.requireExactlyOne && rows.length !== 1) {
1186
+ throw toAthenaGatewayError(
1187
+ result,
1188
+ `Expected exactly one row but received ${rows.length}`,
1189
+ options.context
1190
+ );
1191
+ }
1192
+ return rows[0];
1193
+ }
1194
+ function requireSuccess(result, context) {
1195
+ if (!isOk(result)) {
1196
+ throw toAthenaGatewayError(
1197
+ result,
1198
+ `Athena ${context?.operation ?? "request"} failed`,
1199
+ context
1200
+ );
1201
+ }
1202
+ return result;
1203
+ }
1204
+ function requireAffected(result, options, context) {
1205
+ requireSuccess(result, context);
1206
+ const minimum = options?.min ?? 1;
1207
+ const count = result.count;
1208
+ if (count == null) {
1209
+ throw new AthenaGatewayError({
1210
+ code: "UNKNOWN_ERROR",
1211
+ status: result.status,
1212
+ message: "Expected affected row count but response.count is missing",
1213
+ hint: 'Set call option { count: "exact" } for mutation postcondition checks.',
1214
+ cause: safeStringify(result.raw)
1215
+ });
1216
+ }
1217
+ if (count < minimum) {
1218
+ throw new AthenaGatewayError({
1219
+ code: "UNKNOWN_ERROR",
1220
+ status: result.status,
1221
+ message: `Expected at least ${minimum} affected rows but received ${count}`,
1222
+ hint: contextHint(context),
1223
+ cause: safeStringify(result.raw)
1224
+ });
1225
+ }
1226
+ return count;
1227
+ }
1228
+ function applyBounds(value, options) {
1229
+ if (options?.min !== void 0 && value < options.min) return null;
1230
+ if (options?.max !== void 0 && value > options.max) return null;
1231
+ return value;
1232
+ }
1233
+ function parseIntegerString(value) {
1234
+ const normalized = value.trim();
1235
+ if (!/^[-+]?\d+$/.test(normalized)) return null;
1236
+ const parsed = Number(normalized);
1237
+ if (!Number.isFinite(parsed) || !Number.isInteger(parsed)) return null;
1238
+ return parsed;
1239
+ }
1240
+ function coerceInt(value, options) {
1241
+ if (value == null) return null;
1242
+ if (typeof value === "number") {
1243
+ if (!Number.isFinite(value) || !Number.isInteger(value)) return null;
1244
+ return applyBounds(value, options);
1245
+ }
1246
+ if (typeof value === "string") {
1247
+ const parsed = parseIntegerString(value);
1248
+ if (parsed == null) return null;
1249
+ return applyBounds(parsed, options);
1250
+ }
1251
+ if (typeof value === "bigint") {
1252
+ if (options?.strictBigInt) {
1253
+ if (value > BigInt(Number.MAX_SAFE_INTEGER) || value < BigInt(Number.MIN_SAFE_INTEGER)) {
1254
+ return null;
1255
+ }
1256
+ }
1257
+ const parsed = Number(value);
1258
+ if (!Number.isFinite(parsed) || !Number.isInteger(parsed)) return null;
1259
+ return applyBounds(parsed, options);
1260
+ }
1261
+ return null;
1262
+ }
1263
+ function assertInt(value, label = "value", options) {
1264
+ const parsed = coerceInt(value, options);
1265
+ if (parsed == null) {
1266
+ throw new TypeError(`${label} must be a finite integer`);
1267
+ }
1268
+ return parsed;
1269
+ }
1270
+ function defaultShouldRetry(error) {
1271
+ const normalized = normalizeAthenaError(error);
1272
+ return normalized.kind === "transient" || normalized.kind === "rate_limit";
1273
+ }
1274
+ function computeDelayMs(attempt, error, config) {
1275
+ const baseDelay = config.baseDelayMs;
1276
+ const rawDelay = typeof config.backoff === "function" ? config.backoff(attempt, error) : config.backoff === "linear" ? baseDelay * attempt : baseDelay * Math.pow(2, attempt - 1);
1277
+ const safeDelay = Number.isFinite(rawDelay) ? Math.max(0, rawDelay) : 0;
1278
+ const clamped = Math.min(config.maxDelayMs, safeDelay);
1279
+ const jitterFactor = typeof config.jitter === "number" ? Math.max(0, Math.min(1, config.jitter)) : config.jitter ? 0.2 : 0;
1280
+ if (!jitterFactor) return clamped;
1281
+ const deviation = clamped * jitterFactor;
1282
+ const offset = (Math.random() * 2 - 1) * deviation;
1283
+ return Math.max(0, clamped + offset);
1284
+ }
1285
+ function sleep(ms) {
1286
+ if (ms <= 0) return Promise.resolve();
1287
+ return new Promise((resolve) => {
1288
+ setTimeout(resolve, ms);
1289
+ });
1290
+ }
1291
+ async function withRetry(config, fn) {
1292
+ const retries = Math.max(0, Math.trunc(config.retries));
1293
+ const shouldRetry = config.shouldRetry ?? defaultShouldRetry;
1294
+ const resolvedConfig = {
1295
+ baseDelayMs: config.baseDelayMs ?? 100,
1296
+ maxDelayMs: config.maxDelayMs ?? 1e4,
1297
+ backoff: config.backoff ?? "exponential",
1298
+ jitter: config.jitter ?? false
1299
+ };
1300
+ for (let attempts = 0; attempts <= retries; attempts += 1) {
1301
+ try {
1302
+ return await fn();
1303
+ } catch (error) {
1304
+ if (attempts >= retries) {
1305
+ throw error;
1306
+ }
1307
+ const currentAttempt = attempts + 1;
1308
+ const retry = await shouldRetry(error, currentAttempt);
1309
+ if (!retry) {
1310
+ throw error;
1311
+ }
1312
+ const delay = computeDelayMs(currentAttempt, error, resolvedConfig);
1313
+ await sleep(delay);
1314
+ }
1315
+ }
1316
+ throw new Error("withRetry reached an unexpected state");
1317
+ }
1318
+
1319
+ export { AthenaClient, AthenaGatewayError, Backend, assertInt, coerceInt, createClient, isAthenaGatewayError, isOk, normalizeAthenaError, requireAffected, requireSuccess, unwrap, unwrapOne, unwrapRows, withRetry };
966
1320
  //# sourceMappingURL=index.js.map
967
1321
  //# sourceMappingURL=index.js.map