@xylex-group/athena 1.2.0 → 1.3.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/README.md +188 -85
- package/dist/{errors-DKNcLa5O.d.cts → errors-DHmpYG46.d.cts} +2 -1
- package/dist/{errors-DKNcLa5O.d.ts → errors-DHmpYG46.d.ts} +2 -1
- package/dist/index.cjs +331 -7
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +117 -3
- package/dist/index.d.ts +117 -3
- package/dist/index.js +322 -8
- package/dist/index.js.map +1 -1
- package/dist/react.d.cts +2 -2
- package/dist/react.d.ts +2 -2
- package/package.json +1 -1
package/dist/index.d.cts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { B as BackendConfig, a as BackendType, A as AthenaConditionValue, b as AthenaConditionArrayValue, c as AthenaConditionOperator, d as AthenaGatewayCallOptions, e as AthenaGatewayErrorDetails, f as AthenaRpcCallOptions } from './errors-
|
|
2
|
-
export { g as AthenaGatewayError, h as AthenaGatewayErrorCode, i as AthenaRpcFilter, j as AthenaRpcFilterOperator, k as AthenaRpcOrder, l as AthenaRpcPayload, m as Backend, n as isAthenaGatewayError } from './errors-
|
|
1
|
+
import { B as BackendConfig, a as BackendType, A as AthenaConditionValue, b as AthenaConditionArrayValue, c as AthenaConditionOperator, d as AthenaGatewayCallOptions, e as AthenaGatewayErrorDetails, f as AthenaRpcCallOptions } from './errors-DHmpYG46.cjs';
|
|
2
|
+
export { g as AthenaGatewayError, h as AthenaGatewayErrorCode, i as AthenaRpcFilter, j as AthenaRpcFilterOperator, k as AthenaRpcOrder, l as AthenaRpcPayload, m as Backend, n as isAthenaGatewayError } from './errors-DHmpYG46.cjs';
|
|
3
3
|
|
|
4
4
|
interface AthenaResult<T> {
|
|
5
5
|
data: T | null;
|
|
@@ -123,4 +123,118 @@ declare class AthenaClient {
|
|
|
123
123
|
/** Create client (convenience wrapper; use AthenaClient.builder() for full control) */
|
|
124
124
|
declare function createClient(url: string, apiKey: string, options?: Pick<AthenaGatewayCallOptions, 'client' | 'headers' | 'backend'>): AthenaSdkClient;
|
|
125
125
|
|
|
126
|
-
|
|
126
|
+
type AthenaErrorKind = 'unique_violation' | 'not_found' | 'validation' | 'auth' | 'rate_limit' | 'transient' | 'unknown';
|
|
127
|
+
interface AthenaOperationContext {
|
|
128
|
+
table?: string;
|
|
129
|
+
operation?: string;
|
|
130
|
+
identity?: string | Record<string, unknown>;
|
|
131
|
+
}
|
|
132
|
+
interface NormalizedAthenaError {
|
|
133
|
+
kind: AthenaErrorKind;
|
|
134
|
+
status?: number;
|
|
135
|
+
constraint?: string;
|
|
136
|
+
table?: string;
|
|
137
|
+
operation?: string;
|
|
138
|
+
message: string;
|
|
139
|
+
raw: unknown;
|
|
140
|
+
}
|
|
141
|
+
interface UnwrapOptions {
|
|
142
|
+
allowNull?: boolean;
|
|
143
|
+
context?: AthenaOperationContext;
|
|
144
|
+
}
|
|
145
|
+
interface UnwrapOneOptions extends UnwrapOptions {
|
|
146
|
+
requireExactlyOne?: boolean;
|
|
147
|
+
}
|
|
148
|
+
interface IntCoercionOptions {
|
|
149
|
+
strictBigInt?: boolean;
|
|
150
|
+
min?: number;
|
|
151
|
+
max?: number;
|
|
152
|
+
}
|
|
153
|
+
type RetryBackoffStrategy = 'linear' | 'exponential' | ((attempt: number, error: unknown) => number);
|
|
154
|
+
interface RetryConfig {
|
|
155
|
+
retries: number;
|
|
156
|
+
baseDelayMs?: number;
|
|
157
|
+
maxDelayMs?: number;
|
|
158
|
+
backoff?: RetryBackoffStrategy;
|
|
159
|
+
jitter?: boolean | number;
|
|
160
|
+
shouldRetry?: (error: unknown, attempt: number) => boolean | Promise<boolean>;
|
|
161
|
+
}
|
|
162
|
+
interface RequireAffectedOptions {
|
|
163
|
+
min?: number;
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* Returns `true` when a result is successful (`2xx` status and no `error`).
|
|
167
|
+
*/
|
|
168
|
+
declare function isOk<T>(result: AthenaResult<T>): boolean;
|
|
169
|
+
/**
|
|
170
|
+
* Normalizes any Athena failure shape into a stable, typed error envelope.
|
|
171
|
+
*
|
|
172
|
+
* Accepts `AthenaResult`, `AthenaGatewayError`, native `Error`, or unknown values.
|
|
173
|
+
* Optional `context` can override inferred table/operation metadata for clearer diagnostics.
|
|
174
|
+
*/
|
|
175
|
+
declare function normalizeAthenaError(resultOrError: unknown, context?: AthenaOperationContext): NormalizedAthenaError;
|
|
176
|
+
/**
|
|
177
|
+
* Unwraps a successful result into a row array.
|
|
178
|
+
*
|
|
179
|
+
* - Throws on failed results.
|
|
180
|
+
* - Converts `null` data to an empty array.
|
|
181
|
+
* - Wraps scalar data in a single-element array.
|
|
182
|
+
*/
|
|
183
|
+
declare function unwrapRows<T>(result: AthenaResult<T[] | T | null>, options?: UnwrapOptions): T[];
|
|
184
|
+
/**
|
|
185
|
+
* Unwraps successful result data from `AthenaResult<T | null>`.
|
|
186
|
+
*
|
|
187
|
+
* By default, `null` data throws. Pass `{ allowNull: true }` to permit nullable payloads.
|
|
188
|
+
*/
|
|
189
|
+
declare function unwrap<T>(result: AthenaResult<T | null>, options: UnwrapOptions & {
|
|
190
|
+
allowNull: true;
|
|
191
|
+
}): T | null;
|
|
192
|
+
declare function unwrap<T>(result: AthenaResult<T | null>, options?: UnwrapOptions): T;
|
|
193
|
+
/**
|
|
194
|
+
* Unwraps the first row from a successful result that may contain arrays/scalars/null.
|
|
195
|
+
*
|
|
196
|
+
* - Throws on failed results.
|
|
197
|
+
* - Throws when no row exists unless `allowNull: true` is provided.
|
|
198
|
+
* - Optionally enforces exact cardinality via `requireExactlyOne`.
|
|
199
|
+
*/
|
|
200
|
+
declare function unwrapOne<T>(result: AthenaResult<T[] | T | null>, options: UnwrapOneOptions & {
|
|
201
|
+
allowNull: true;
|
|
202
|
+
}): T | null;
|
|
203
|
+
declare function unwrapOne<T>(result: AthenaResult<T[] | T | null>, options?: UnwrapOneOptions): T;
|
|
204
|
+
/**
|
|
205
|
+
* Asserts that an Athena result is successful.
|
|
206
|
+
*
|
|
207
|
+
* Returns the original result for fluent composition and throws `AthenaGatewayError` on failure.
|
|
208
|
+
*/
|
|
209
|
+
declare function requireSuccess<T>(result: AthenaResult<T>, context?: AthenaOperationContext): AthenaResult<T>;
|
|
210
|
+
/**
|
|
211
|
+
* Enforces mutation postconditions based on `result.count`.
|
|
212
|
+
*
|
|
213
|
+
* - Validates success first.
|
|
214
|
+
* - Requires a non-null count in the response.
|
|
215
|
+
* - Validates `count >= min` (default: `1`).
|
|
216
|
+
*
|
|
217
|
+
* Useful for guaranteeing that critical writes actually affected rows.
|
|
218
|
+
*/
|
|
219
|
+
declare function requireAffected<T>(result: AthenaResult<T>, options?: RequireAffectedOptions, context?: AthenaOperationContext): number;
|
|
220
|
+
/**
|
|
221
|
+
* Safely coerces `unknown` values into finite integers.
|
|
222
|
+
*
|
|
223
|
+
* Returns `null` when coercion fails or bounds/strict bigint checks are violated.
|
|
224
|
+
*/
|
|
225
|
+
declare function coerceInt(value: unknown, options?: IntCoercionOptions): number | null;
|
|
226
|
+
/**
|
|
227
|
+
* Strict integer assertion wrapper around `coerceInt`.
|
|
228
|
+
*
|
|
229
|
+
* Throws a `TypeError` with the provided label when coercion fails.
|
|
230
|
+
*/
|
|
231
|
+
declare function assertInt(value: unknown, label?: string, options?: IntCoercionOptions): number;
|
|
232
|
+
/**
|
|
233
|
+
* Retries an async operation with configurable backoff and retry policy.
|
|
234
|
+
*
|
|
235
|
+
* `retries` represents additional attempts after the first failure.
|
|
236
|
+
* By default, transient and rate-limit errors are retried.
|
|
237
|
+
*/
|
|
238
|
+
declare function withRetry<T>(config: RetryConfig, fn: () => Promise<T>): Promise<T>;
|
|
239
|
+
|
|
240
|
+
export { AthenaClient, type AthenaErrorKind, AthenaGatewayCallOptions, AthenaGatewayErrorDetails, type AthenaOperationContext, type AthenaResult, AthenaRpcCallOptions, type AthenaSdkClient, BackendConfig, BackendType, type IntCoercionOptions, type NormalizedAthenaError, type RequireAffectedOptions, type RetryBackoffStrategy, type RetryConfig, type RpcOrderOptions, type RpcQueryBuilder, type TableQueryBuilder, type UnwrapOneOptions, type UnwrapOptions, assertInt, coerceInt, createClient, isOk, normalizeAthenaError, requireAffected, requireSuccess, unwrap, unwrapOne, unwrapRows, withRetry };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { B as BackendConfig, a as BackendType, A as AthenaConditionValue, b as AthenaConditionArrayValue, c as AthenaConditionOperator, d as AthenaGatewayCallOptions, e as AthenaGatewayErrorDetails, f as AthenaRpcCallOptions } from './errors-
|
|
2
|
-
export { g as AthenaGatewayError, h as AthenaGatewayErrorCode, i as AthenaRpcFilter, j as AthenaRpcFilterOperator, k as AthenaRpcOrder, l as AthenaRpcPayload, m as Backend, n as isAthenaGatewayError } from './errors-
|
|
1
|
+
import { B as BackendConfig, a as BackendType, A as AthenaConditionValue, b as AthenaConditionArrayValue, c as AthenaConditionOperator, d as AthenaGatewayCallOptions, e as AthenaGatewayErrorDetails, f as AthenaRpcCallOptions } from './errors-DHmpYG46.js';
|
|
2
|
+
export { g as AthenaGatewayError, h as AthenaGatewayErrorCode, i as AthenaRpcFilter, j as AthenaRpcFilterOperator, k as AthenaRpcOrder, l as AthenaRpcPayload, m as Backend, n as isAthenaGatewayError } from './errors-DHmpYG46.js';
|
|
3
3
|
|
|
4
4
|
interface AthenaResult<T> {
|
|
5
5
|
data: T | null;
|
|
@@ -123,4 +123,118 @@ declare class AthenaClient {
|
|
|
123
123
|
/** Create client (convenience wrapper; use AthenaClient.builder() for full control) */
|
|
124
124
|
declare function createClient(url: string, apiKey: string, options?: Pick<AthenaGatewayCallOptions, 'client' | 'headers' | 'backend'>): AthenaSdkClient;
|
|
125
125
|
|
|
126
|
-
|
|
126
|
+
type AthenaErrorKind = 'unique_violation' | 'not_found' | 'validation' | 'auth' | 'rate_limit' | 'transient' | 'unknown';
|
|
127
|
+
interface AthenaOperationContext {
|
|
128
|
+
table?: string;
|
|
129
|
+
operation?: string;
|
|
130
|
+
identity?: string | Record<string, unknown>;
|
|
131
|
+
}
|
|
132
|
+
interface NormalizedAthenaError {
|
|
133
|
+
kind: AthenaErrorKind;
|
|
134
|
+
status?: number;
|
|
135
|
+
constraint?: string;
|
|
136
|
+
table?: string;
|
|
137
|
+
operation?: string;
|
|
138
|
+
message: string;
|
|
139
|
+
raw: unknown;
|
|
140
|
+
}
|
|
141
|
+
interface UnwrapOptions {
|
|
142
|
+
allowNull?: boolean;
|
|
143
|
+
context?: AthenaOperationContext;
|
|
144
|
+
}
|
|
145
|
+
interface UnwrapOneOptions extends UnwrapOptions {
|
|
146
|
+
requireExactlyOne?: boolean;
|
|
147
|
+
}
|
|
148
|
+
interface IntCoercionOptions {
|
|
149
|
+
strictBigInt?: boolean;
|
|
150
|
+
min?: number;
|
|
151
|
+
max?: number;
|
|
152
|
+
}
|
|
153
|
+
type RetryBackoffStrategy = 'linear' | 'exponential' | ((attempt: number, error: unknown) => number);
|
|
154
|
+
interface RetryConfig {
|
|
155
|
+
retries: number;
|
|
156
|
+
baseDelayMs?: number;
|
|
157
|
+
maxDelayMs?: number;
|
|
158
|
+
backoff?: RetryBackoffStrategy;
|
|
159
|
+
jitter?: boolean | number;
|
|
160
|
+
shouldRetry?: (error: unknown, attempt: number) => boolean | Promise<boolean>;
|
|
161
|
+
}
|
|
162
|
+
interface RequireAffectedOptions {
|
|
163
|
+
min?: number;
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* Returns `true` when a result is successful (`2xx` status and no `error`).
|
|
167
|
+
*/
|
|
168
|
+
declare function isOk<T>(result: AthenaResult<T>): boolean;
|
|
169
|
+
/**
|
|
170
|
+
* Normalizes any Athena failure shape into a stable, typed error envelope.
|
|
171
|
+
*
|
|
172
|
+
* Accepts `AthenaResult`, `AthenaGatewayError`, native `Error`, or unknown values.
|
|
173
|
+
* Optional `context` can override inferred table/operation metadata for clearer diagnostics.
|
|
174
|
+
*/
|
|
175
|
+
declare function normalizeAthenaError(resultOrError: unknown, context?: AthenaOperationContext): NormalizedAthenaError;
|
|
176
|
+
/**
|
|
177
|
+
* Unwraps a successful result into a row array.
|
|
178
|
+
*
|
|
179
|
+
* - Throws on failed results.
|
|
180
|
+
* - Converts `null` data to an empty array.
|
|
181
|
+
* - Wraps scalar data in a single-element array.
|
|
182
|
+
*/
|
|
183
|
+
declare function unwrapRows<T>(result: AthenaResult<T[] | T | null>, options?: UnwrapOptions): T[];
|
|
184
|
+
/**
|
|
185
|
+
* Unwraps successful result data from `AthenaResult<T | null>`.
|
|
186
|
+
*
|
|
187
|
+
* By default, `null` data throws. Pass `{ allowNull: true }` to permit nullable payloads.
|
|
188
|
+
*/
|
|
189
|
+
declare function unwrap<T>(result: AthenaResult<T | null>, options: UnwrapOptions & {
|
|
190
|
+
allowNull: true;
|
|
191
|
+
}): T | null;
|
|
192
|
+
declare function unwrap<T>(result: AthenaResult<T | null>, options?: UnwrapOptions): T;
|
|
193
|
+
/**
|
|
194
|
+
* Unwraps the first row from a successful result that may contain arrays/scalars/null.
|
|
195
|
+
*
|
|
196
|
+
* - Throws on failed results.
|
|
197
|
+
* - Throws when no row exists unless `allowNull: true` is provided.
|
|
198
|
+
* - Optionally enforces exact cardinality via `requireExactlyOne`.
|
|
199
|
+
*/
|
|
200
|
+
declare function unwrapOne<T>(result: AthenaResult<T[] | T | null>, options: UnwrapOneOptions & {
|
|
201
|
+
allowNull: true;
|
|
202
|
+
}): T | null;
|
|
203
|
+
declare function unwrapOne<T>(result: AthenaResult<T[] | T | null>, options?: UnwrapOneOptions): T;
|
|
204
|
+
/**
|
|
205
|
+
* Asserts that an Athena result is successful.
|
|
206
|
+
*
|
|
207
|
+
* Returns the original result for fluent composition and throws `AthenaGatewayError` on failure.
|
|
208
|
+
*/
|
|
209
|
+
declare function requireSuccess<T>(result: AthenaResult<T>, context?: AthenaOperationContext): AthenaResult<T>;
|
|
210
|
+
/**
|
|
211
|
+
* Enforces mutation postconditions based on `result.count`.
|
|
212
|
+
*
|
|
213
|
+
* - Validates success first.
|
|
214
|
+
* - Requires a non-null count in the response.
|
|
215
|
+
* - Validates `count >= min` (default: `1`).
|
|
216
|
+
*
|
|
217
|
+
* Useful for guaranteeing that critical writes actually affected rows.
|
|
218
|
+
*/
|
|
219
|
+
declare function requireAffected<T>(result: AthenaResult<T>, options?: RequireAffectedOptions, context?: AthenaOperationContext): number;
|
|
220
|
+
/**
|
|
221
|
+
* Safely coerces `unknown` values into finite integers.
|
|
222
|
+
*
|
|
223
|
+
* Returns `null` when coercion fails or bounds/strict bigint checks are violated.
|
|
224
|
+
*/
|
|
225
|
+
declare function coerceInt(value: unknown, options?: IntCoercionOptions): number | null;
|
|
226
|
+
/**
|
|
227
|
+
* Strict integer assertion wrapper around `coerceInt`.
|
|
228
|
+
*
|
|
229
|
+
* Throws a `TypeError` with the provided label when coercion fails.
|
|
230
|
+
*/
|
|
231
|
+
declare function assertInt(value: unknown, label?: string, options?: IntCoercionOptions): number;
|
|
232
|
+
/**
|
|
233
|
+
* Retries an async operation with configurable backoff and retry policy.
|
|
234
|
+
*
|
|
235
|
+
* `retries` represents additional attempts after the first failure.
|
|
236
|
+
* By default, transient and rate-limit errors are retried.
|
|
237
|
+
*/
|
|
238
|
+
declare function withRetry<T>(config: RetryConfig, fn: () => Promise<T>): Promise<T>;
|
|
239
|
+
|
|
240
|
+
export { AthenaClient, type AthenaErrorKind, AthenaGatewayCallOptions, AthenaGatewayErrorDetails, type AthenaOperationContext, type AthenaResult, AthenaRpcCallOptions, type AthenaSdkClient, BackendConfig, BackendType, type IntCoercionOptions, type NormalizedAthenaError, type RequireAffectedOptions, type RetryBackoffStrategy, type RetryConfig, type RpcOrderOptions, type RpcQueryBuilder, type TableQueryBuilder, type UnwrapOneOptions, type UnwrapOptions, assertInt, coerceInt, createClient, isOk, normalizeAthenaError, requireAffected, requireSuccess, unwrap, unwrapOne, unwrapRows, withRetry };
|
package/dist/index.js
CHANGED
|
@@ -389,7 +389,7 @@ function mergeOptions(...options) {
|
|
|
389
389
|
}, void 0);
|
|
390
390
|
}
|
|
391
391
|
function createMutationQuery(executor, defaultColumns = DEFAULT_COLUMNS) {
|
|
392
|
-
let selectedColumns = defaultColumns;
|
|
392
|
+
let selectedColumns = defaultColumns === null ? void 0 : defaultColumns;
|
|
393
393
|
let selectedOptions;
|
|
394
394
|
let promise = null;
|
|
395
395
|
const run = (columns, options) => {
|
|
@@ -800,15 +800,15 @@ function createTableBuilder(tableName, client) {
|
|
|
800
800
|
const mergedOptions = mergeOptions(options, selectOptions);
|
|
801
801
|
const payload = {
|
|
802
802
|
table_name: tableName,
|
|
803
|
-
|
|
803
|
+
set: values,
|
|
804
804
|
conditions: filters,
|
|
805
|
-
columns,
|
|
806
805
|
strip_nulls: mergedOptions?.stripNulls ?? true
|
|
807
806
|
};
|
|
807
|
+
if (columns) payload.columns = columns;
|
|
808
808
|
const response = await client.updateGateway(payload, mergedOptions);
|
|
809
809
|
return formatResult(response);
|
|
810
810
|
};
|
|
811
|
-
const mutation = createMutationQuery(executeUpdate);
|
|
811
|
+
const mutation = createMutationQuery(executeUpdate, null);
|
|
812
812
|
const updateChain = {};
|
|
813
813
|
const filterMethods2 = createFilterMethods(state, addCondition, updateChain);
|
|
814
814
|
Object.assign(updateChain, filterMethods2, mutation);
|
|
@@ -825,13 +825,13 @@ function createTableBuilder(tableName, client) {
|
|
|
825
825
|
const payload = {
|
|
826
826
|
table_name: tableName,
|
|
827
827
|
resource_id: resourceId,
|
|
828
|
-
conditions: filters
|
|
829
|
-
columns
|
|
828
|
+
conditions: filters
|
|
830
829
|
};
|
|
830
|
+
if (columns) payload.columns = columns;
|
|
831
831
|
const response = await client.deleteGateway(payload, mergedOptions);
|
|
832
832
|
return formatResult(response);
|
|
833
833
|
};
|
|
834
|
-
return createMutationQuery(executeDelete);
|
|
834
|
+
return createMutationQuery(executeDelete, null);
|
|
835
835
|
},
|
|
836
836
|
async single(columns, options) {
|
|
837
837
|
const response = await builder.select(columns, options);
|
|
@@ -962,6 +962,320 @@ var Backend = {
|
|
|
962
962
|
ScyllaDB: { type: "scylladb" }
|
|
963
963
|
};
|
|
964
964
|
|
|
965
|
-
|
|
965
|
+
// src/auxiliaries.ts
|
|
966
|
+
function isRecord2(value) {
|
|
967
|
+
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
968
|
+
}
|
|
969
|
+
function safeStringify(value) {
|
|
970
|
+
try {
|
|
971
|
+
const serialized = JSON.stringify(value);
|
|
972
|
+
return serialized ?? String(value);
|
|
973
|
+
} catch {
|
|
974
|
+
return "[unserializable]";
|
|
975
|
+
}
|
|
976
|
+
}
|
|
977
|
+
function contextHint(context) {
|
|
978
|
+
if (!context?.identity) return void 0;
|
|
979
|
+
const identity = typeof context.identity === "string" ? context.identity : safeStringify(context.identity);
|
|
980
|
+
return `Identity: ${identity}`;
|
|
981
|
+
}
|
|
982
|
+
function isAthenaResultLike(value) {
|
|
983
|
+
return isRecord2(value) && "status" in value && "error" in value && "data" in value && typeof value.status === "number";
|
|
984
|
+
}
|
|
985
|
+
function operationFromDetails(details) {
|
|
986
|
+
if (!details?.endpoint) return void 0;
|
|
987
|
+
if (details.endpoint === "/gateway/fetch" || details.endpoint === "/gateway/query") return "select";
|
|
988
|
+
if (details.endpoint === "/gateway/insert") return "insert";
|
|
989
|
+
if (details.endpoint === "/gateway/update") return "update";
|
|
990
|
+
if (details.endpoint === "/gateway/delete") return "delete";
|
|
991
|
+
if (details.endpoint === "/gateway/rpc" || details.endpoint.startsWith("/rpc/")) return "rpc";
|
|
992
|
+
return void 0;
|
|
993
|
+
}
|
|
994
|
+
function matchRegex(input, patterns) {
|
|
995
|
+
for (const pattern of patterns) {
|
|
996
|
+
const match = pattern.exec(input);
|
|
997
|
+
if (match?.[1]) return match[1];
|
|
998
|
+
}
|
|
999
|
+
return void 0;
|
|
1000
|
+
}
|
|
1001
|
+
function extractConstraint(message) {
|
|
1002
|
+
return matchRegex(message, [
|
|
1003
|
+
/unique constraint\s+["'`]([^"'`]+)["'`]/i,
|
|
1004
|
+
/constraint\s+["'`]([^"'`]+)["'`]/i
|
|
1005
|
+
]);
|
|
1006
|
+
}
|
|
1007
|
+
function extractTable(message) {
|
|
1008
|
+
return matchRegex(message, [
|
|
1009
|
+
/(?:table|relation)\s+["'`]([^"'`]+)["'`]/i,
|
|
1010
|
+
/on\s+table\s+([a-zA-Z0-9_.]+)/i
|
|
1011
|
+
]);
|
|
1012
|
+
}
|
|
1013
|
+
function classifyKind(status, code, message) {
|
|
1014
|
+
const lower = message.toLowerCase();
|
|
1015
|
+
const hasUniquePattern = lower.includes("unique constraint") || lower.includes("duplicate key") || lower.includes("already exists") || lower.includes("duplicate");
|
|
1016
|
+
const hasNotFoundPattern = lower.includes("not found") || lower.includes("no rows");
|
|
1017
|
+
const hasValidationPattern = lower.includes("validation") || lower.includes("invalid") || lower.includes("malformed");
|
|
1018
|
+
const hasAuthPattern = lower.includes("unauthorized") || lower.includes("forbidden") || lower.includes("auth");
|
|
1019
|
+
const hasRateLimitPattern = lower.includes("rate limit") || lower.includes("too many requests");
|
|
1020
|
+
const hasTransientPattern = lower.includes("timeout") || lower.includes("temporar") || lower.includes("connection reset") || lower.includes("socket") || lower.includes("deadlock");
|
|
1021
|
+
if (status === 409 || hasUniquePattern) return "unique_violation";
|
|
1022
|
+
if (status === 404 || hasNotFoundPattern) return "not_found";
|
|
1023
|
+
if (status === 401 || status === 403 || hasAuthPattern) return "auth";
|
|
1024
|
+
if (status === 429 || hasRateLimitPattern) return "rate_limit";
|
|
1025
|
+
if (status === 400 || status === 422 || hasValidationPattern) return "validation";
|
|
1026
|
+
if (code === "NETWORK_ERROR" || status === 0 || status !== void 0 && status >= 500 || hasTransientPattern) {
|
|
1027
|
+
return "transient";
|
|
1028
|
+
}
|
|
1029
|
+
return "unknown";
|
|
1030
|
+
}
|
|
1031
|
+
function toGatewayCode(kind, status) {
|
|
1032
|
+
if (kind === "transient" && (status === 0 || status === void 0)) return "NETWORK_ERROR";
|
|
1033
|
+
if (kind === "validation") return "INVALID_JSON";
|
|
1034
|
+
if (status !== void 0 && status >= 400) return "HTTP_ERROR";
|
|
1035
|
+
return "UNKNOWN_ERROR";
|
|
1036
|
+
}
|
|
1037
|
+
function toAthenaGatewayError(source, fallbackMessage, context) {
|
|
1038
|
+
if (isAthenaGatewayError(source)) {
|
|
1039
|
+
return source;
|
|
1040
|
+
}
|
|
1041
|
+
if (isAthenaResultLike(source) && source.errorDetails) {
|
|
1042
|
+
return new AthenaGatewayError({
|
|
1043
|
+
code: source.errorDetails.code,
|
|
1044
|
+
message: source.error ?? source.errorDetails.message,
|
|
1045
|
+
status: source.status,
|
|
1046
|
+
endpoint: source.errorDetails.endpoint,
|
|
1047
|
+
method: source.errorDetails.method,
|
|
1048
|
+
requestId: source.errorDetails.requestId,
|
|
1049
|
+
hint: source.errorDetails.hint,
|
|
1050
|
+
cause: source.errorDetails.cause
|
|
1051
|
+
});
|
|
1052
|
+
}
|
|
1053
|
+
const normalized = normalizeAthenaError(source, context);
|
|
1054
|
+
const message = isAthenaResultLike(source) && source.error == null && source.errorDetails == null ? fallbackMessage : normalized.message || fallbackMessage;
|
|
1055
|
+
return new AthenaGatewayError({
|
|
1056
|
+
code: toGatewayCode(normalized.kind, normalized.status),
|
|
1057
|
+
message,
|
|
1058
|
+
status: normalized.status ?? 0,
|
|
1059
|
+
hint: normalized.constraint != null ? `Constraint: ${normalized.constraint}` : contextHint(context),
|
|
1060
|
+
cause: typeof normalized.raw === "string" ? normalized.raw : safeStringify(normalized.raw)
|
|
1061
|
+
});
|
|
1062
|
+
}
|
|
1063
|
+
function isOk(result) {
|
|
1064
|
+
return result.error == null && result.status >= 200 && result.status < 300;
|
|
1065
|
+
}
|
|
1066
|
+
function normalizeAthenaError(resultOrError, context) {
|
|
1067
|
+
if (isAthenaResultLike(resultOrError)) {
|
|
1068
|
+
const details = resultOrError.errorDetails;
|
|
1069
|
+
const message2 = resultOrError.error ?? details?.message ?? `Athena ${context?.operation ?? operationFromDetails(details) ?? "request"} failed`;
|
|
1070
|
+
const operation = context?.operation ?? operationFromDetails(details);
|
|
1071
|
+
const table = context?.table ?? extractTable(message2);
|
|
1072
|
+
const constraint = extractConstraint(message2);
|
|
1073
|
+
const kind = classifyKind(resultOrError.status, details?.code, message2);
|
|
1074
|
+
return {
|
|
1075
|
+
kind,
|
|
1076
|
+
status: resultOrError.status,
|
|
1077
|
+
constraint,
|
|
1078
|
+
table,
|
|
1079
|
+
operation,
|
|
1080
|
+
message: message2,
|
|
1081
|
+
raw: resultOrError.raw
|
|
1082
|
+
};
|
|
1083
|
+
}
|
|
1084
|
+
if (isAthenaGatewayError(resultOrError)) {
|
|
1085
|
+
const details = resultOrError.toDetails();
|
|
1086
|
+
const operation = context?.operation ?? operationFromDetails(details);
|
|
1087
|
+
const table = context?.table ?? extractTable(resultOrError.message);
|
|
1088
|
+
const constraint = extractConstraint(resultOrError.message);
|
|
1089
|
+
const kind = classifyKind(resultOrError.status, resultOrError.code, resultOrError.message);
|
|
1090
|
+
return {
|
|
1091
|
+
kind,
|
|
1092
|
+
status: resultOrError.status,
|
|
1093
|
+
constraint,
|
|
1094
|
+
table,
|
|
1095
|
+
operation,
|
|
1096
|
+
message: resultOrError.message,
|
|
1097
|
+
raw: resultOrError
|
|
1098
|
+
};
|
|
1099
|
+
}
|
|
1100
|
+
if (resultOrError instanceof Error) {
|
|
1101
|
+
const maybeStatus = isRecord2(resultOrError) && typeof resultOrError.status === "number" ? resultOrError.status : void 0;
|
|
1102
|
+
return {
|
|
1103
|
+
kind: classifyKind(maybeStatus, void 0, resultOrError.message),
|
|
1104
|
+
status: maybeStatus,
|
|
1105
|
+
constraint: extractConstraint(resultOrError.message),
|
|
1106
|
+
table: context?.table ?? extractTable(resultOrError.message),
|
|
1107
|
+
operation: context?.operation,
|
|
1108
|
+
message: resultOrError.message,
|
|
1109
|
+
raw: resultOrError
|
|
1110
|
+
};
|
|
1111
|
+
}
|
|
1112
|
+
const message = typeof resultOrError === "string" ? resultOrError : "Unknown Athena error";
|
|
1113
|
+
return {
|
|
1114
|
+
kind: classifyKind(void 0, void 0, message),
|
|
1115
|
+
status: void 0,
|
|
1116
|
+
constraint: extractConstraint(message),
|
|
1117
|
+
table: context?.table ?? extractTable(message),
|
|
1118
|
+
operation: context?.operation,
|
|
1119
|
+
message,
|
|
1120
|
+
raw: resultOrError
|
|
1121
|
+
};
|
|
1122
|
+
}
|
|
1123
|
+
function unwrapRows(result, options) {
|
|
1124
|
+
if (!isOk(result)) {
|
|
1125
|
+
throw toAthenaGatewayError(result, "Athena request failed", options?.context);
|
|
1126
|
+
}
|
|
1127
|
+
if (result.data == null) return [];
|
|
1128
|
+
return Array.isArray(result.data) ? result.data : [result.data];
|
|
1129
|
+
}
|
|
1130
|
+
function unwrap(result, options) {
|
|
1131
|
+
if (!isOk(result)) {
|
|
1132
|
+
throw toAthenaGatewayError(result, "Athena request failed", options?.context);
|
|
1133
|
+
}
|
|
1134
|
+
if (result.data == null && !options?.allowNull) {
|
|
1135
|
+
throw toAthenaGatewayError(result, "Expected data but received null", options?.context);
|
|
1136
|
+
}
|
|
1137
|
+
return result.data;
|
|
1138
|
+
}
|
|
1139
|
+
function unwrapOne(result, options) {
|
|
1140
|
+
const rows = unwrapRows(result, options);
|
|
1141
|
+
if (!rows.length) {
|
|
1142
|
+
if (options?.allowNull) return null;
|
|
1143
|
+
throw toAthenaGatewayError(result, "Expected one row but received none", options?.context);
|
|
1144
|
+
}
|
|
1145
|
+
if (options?.requireExactlyOne && rows.length !== 1) {
|
|
1146
|
+
throw toAthenaGatewayError(
|
|
1147
|
+
result,
|
|
1148
|
+
`Expected exactly one row but received ${rows.length}`,
|
|
1149
|
+
options.context
|
|
1150
|
+
);
|
|
1151
|
+
}
|
|
1152
|
+
return rows[0];
|
|
1153
|
+
}
|
|
1154
|
+
function requireSuccess(result, context) {
|
|
1155
|
+
if (!isOk(result)) {
|
|
1156
|
+
throw toAthenaGatewayError(
|
|
1157
|
+
result,
|
|
1158
|
+
`Athena ${context?.operation ?? "request"} failed`,
|
|
1159
|
+
context
|
|
1160
|
+
);
|
|
1161
|
+
}
|
|
1162
|
+
return result;
|
|
1163
|
+
}
|
|
1164
|
+
function requireAffected(result, options, context) {
|
|
1165
|
+
requireSuccess(result, context);
|
|
1166
|
+
const minimum = options?.min ?? 1;
|
|
1167
|
+
const count = result.count;
|
|
1168
|
+
if (count == null) {
|
|
1169
|
+
throw new AthenaGatewayError({
|
|
1170
|
+
code: "UNKNOWN_ERROR",
|
|
1171
|
+
status: result.status,
|
|
1172
|
+
message: "Expected affected row count but response.count is missing",
|
|
1173
|
+
hint: 'Set call option { count: "exact" } for mutation postcondition checks.',
|
|
1174
|
+
cause: safeStringify(result.raw)
|
|
1175
|
+
});
|
|
1176
|
+
}
|
|
1177
|
+
if (count < minimum) {
|
|
1178
|
+
throw new AthenaGatewayError({
|
|
1179
|
+
code: "UNKNOWN_ERROR",
|
|
1180
|
+
status: result.status,
|
|
1181
|
+
message: `Expected at least ${minimum} affected rows but received ${count}`,
|
|
1182
|
+
hint: contextHint(context),
|
|
1183
|
+
cause: safeStringify(result.raw)
|
|
1184
|
+
});
|
|
1185
|
+
}
|
|
1186
|
+
return count;
|
|
1187
|
+
}
|
|
1188
|
+
function applyBounds(value, options) {
|
|
1189
|
+
if (options?.min !== void 0 && value < options.min) return null;
|
|
1190
|
+
if (options?.max !== void 0 && value > options.max) return null;
|
|
1191
|
+
return value;
|
|
1192
|
+
}
|
|
1193
|
+
function parseIntegerString(value) {
|
|
1194
|
+
const normalized = value.trim();
|
|
1195
|
+
if (!/^[-+]?\d+$/.test(normalized)) return null;
|
|
1196
|
+
const parsed = Number(normalized);
|
|
1197
|
+
if (!Number.isFinite(parsed) || !Number.isInteger(parsed)) return null;
|
|
1198
|
+
return parsed;
|
|
1199
|
+
}
|
|
1200
|
+
function coerceInt(value, options) {
|
|
1201
|
+
if (value == null) return null;
|
|
1202
|
+
if (typeof value === "number") {
|
|
1203
|
+
if (!Number.isFinite(value) || !Number.isInteger(value)) return null;
|
|
1204
|
+
return applyBounds(value, options);
|
|
1205
|
+
}
|
|
1206
|
+
if (typeof value === "string") {
|
|
1207
|
+
const parsed = parseIntegerString(value);
|
|
1208
|
+
if (parsed == null) return null;
|
|
1209
|
+
return applyBounds(parsed, options);
|
|
1210
|
+
}
|
|
1211
|
+
if (typeof value === "bigint") {
|
|
1212
|
+
if (options?.strictBigInt) {
|
|
1213
|
+
if (value > BigInt(Number.MAX_SAFE_INTEGER) || value < BigInt(Number.MIN_SAFE_INTEGER)) {
|
|
1214
|
+
return null;
|
|
1215
|
+
}
|
|
1216
|
+
}
|
|
1217
|
+
const parsed = Number(value);
|
|
1218
|
+
if (!Number.isFinite(parsed) || !Number.isInteger(parsed)) return null;
|
|
1219
|
+
return applyBounds(parsed, options);
|
|
1220
|
+
}
|
|
1221
|
+
return null;
|
|
1222
|
+
}
|
|
1223
|
+
function assertInt(value, label = "value", options) {
|
|
1224
|
+
const parsed = coerceInt(value, options);
|
|
1225
|
+
if (parsed == null) {
|
|
1226
|
+
throw new TypeError(`${label} must be a finite integer`);
|
|
1227
|
+
}
|
|
1228
|
+
return parsed;
|
|
1229
|
+
}
|
|
1230
|
+
function defaultShouldRetry(error) {
|
|
1231
|
+
const normalized = normalizeAthenaError(error);
|
|
1232
|
+
return normalized.kind === "transient" || normalized.kind === "rate_limit";
|
|
1233
|
+
}
|
|
1234
|
+
function computeDelayMs(attempt, error, config) {
|
|
1235
|
+
const baseDelay = config.baseDelayMs;
|
|
1236
|
+
const rawDelay = typeof config.backoff === "function" ? config.backoff(attempt, error) : config.backoff === "linear" ? baseDelay * attempt : baseDelay * Math.pow(2, attempt - 1);
|
|
1237
|
+
const safeDelay = Number.isFinite(rawDelay) ? Math.max(0, rawDelay) : 0;
|
|
1238
|
+
const clamped = Math.min(config.maxDelayMs, safeDelay);
|
|
1239
|
+
const jitterFactor = typeof config.jitter === "number" ? Math.max(0, Math.min(1, config.jitter)) : config.jitter ? 0.2 : 0;
|
|
1240
|
+
if (!jitterFactor) return clamped;
|
|
1241
|
+
const deviation = clamped * jitterFactor;
|
|
1242
|
+
const offset = (Math.random() * 2 - 1) * deviation;
|
|
1243
|
+
return Math.max(0, clamped + offset);
|
|
1244
|
+
}
|
|
1245
|
+
function sleep(ms) {
|
|
1246
|
+
if (ms <= 0) return Promise.resolve();
|
|
1247
|
+
return new Promise((resolve) => {
|
|
1248
|
+
setTimeout(resolve, ms);
|
|
1249
|
+
});
|
|
1250
|
+
}
|
|
1251
|
+
async function withRetry(config, fn) {
|
|
1252
|
+
const retries = Math.max(0, Math.trunc(config.retries));
|
|
1253
|
+
const shouldRetry = config.shouldRetry ?? defaultShouldRetry;
|
|
1254
|
+
const resolvedConfig = {
|
|
1255
|
+
baseDelayMs: config.baseDelayMs ?? 100,
|
|
1256
|
+
maxDelayMs: config.maxDelayMs ?? 1e4,
|
|
1257
|
+
backoff: config.backoff ?? "exponential",
|
|
1258
|
+
jitter: config.jitter ?? false
|
|
1259
|
+
};
|
|
1260
|
+
for (let attempts = 0; attempts <= retries; attempts += 1) {
|
|
1261
|
+
try {
|
|
1262
|
+
return await fn();
|
|
1263
|
+
} catch (error) {
|
|
1264
|
+
if (attempts >= retries) {
|
|
1265
|
+
throw error;
|
|
1266
|
+
}
|
|
1267
|
+
const currentAttempt = attempts + 1;
|
|
1268
|
+
const retry = await shouldRetry(error, currentAttempt);
|
|
1269
|
+
if (!retry) {
|
|
1270
|
+
throw error;
|
|
1271
|
+
}
|
|
1272
|
+
const delay = computeDelayMs(currentAttempt, error, resolvedConfig);
|
|
1273
|
+
await sleep(delay);
|
|
1274
|
+
}
|
|
1275
|
+
}
|
|
1276
|
+
throw new Error("withRetry reached an unexpected state");
|
|
1277
|
+
}
|
|
1278
|
+
|
|
1279
|
+
export { AthenaClient, AthenaGatewayError, Backend, assertInt, coerceInt, createClient, isAthenaGatewayError, isOk, normalizeAthenaError, requireAffected, requireSuccess, unwrap, unwrapOne, unwrapRows, withRetry };
|
|
966
1280
|
//# sourceMappingURL=index.js.map
|
|
967
1281
|
//# sourceMappingURL=index.js.map
|