@xylex-group/athena 2.3.0 → 2.4.1
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 +195 -106
- package/dist/browser.cjs +870 -154
- package/dist/browser.cjs.map +1 -1
- package/dist/browser.d.cts +7 -7
- package/dist/browser.d.ts +7 -7
- package/dist/browser.js +869 -155
- package/dist/browser.js.map +1 -1
- package/dist/cli/index.cjs +905 -144
- package/dist/cli/index.cjs.map +1 -1
- package/dist/cli/index.d.cts +3 -3
- package/dist/cli/index.d.ts +3 -3
- package/dist/cli/index.js +905 -144
- package/dist/cli/index.js.map +1 -1
- package/dist/index.cjs +870 -154
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +7 -7
- package/dist/index.d.ts +7 -7
- package/dist/index.js +869 -155
- package/dist/index.js.map +1 -1
- package/dist/{model-form-hoE2jHIi.d.cts → model-form-4LPnOPAF.d.cts} +9 -5
- package/dist/{model-form-BpDXlbxb.d.ts → model-form-CO4-LmNC.d.ts} +9 -5
- package/dist/{pipeline-BNIw8pDQ.d.ts → pipeline-CR4V15jF.d.ts} +1 -1
- package/dist/{pipeline-DNIpEsN8.d.cts → pipeline-DZeExYMA.d.cts} +1 -1
- package/dist/{react-email-qPA1wjFV.d.ts → react-email-6mOyxBo4.d.cts} +60 -13
- package/dist/{react-email-BvyCZnfW.d.cts → react-email-Buhcpglm.d.ts} +60 -13
- package/dist/react.cjs +333 -16
- package/dist/react.cjs.map +1 -1
- package/dist/react.d.cts +4 -4
- package/dist/react.d.ts +4 -4
- package/dist/react.js +333 -16
- package/dist/react.js.map +1 -1
- package/dist/{types-A5e97acl.d.cts → types-09Q4D86N.d.cts} +23 -2
- package/dist/{types-A5e97acl.d.ts → types-09Q4D86N.d.ts} +23 -2
- package/dist/{types-bDlr4u7p.d.cts → types-D1JvL21V.d.cts} +1 -1
- package/dist/{types-BnD22-vb.d.ts → types-DU3gNdFv.d.ts} +1 -1
- package/dist/utils.cjs +131 -0
- package/dist/utils.cjs.map +1 -1
- package/dist/utils.d.cts +42 -1
- package/dist/utils.d.ts +42 -1
- package/dist/utils.js +117 -1
- package/dist/utils.js.map +1 -1
- package/package.json +40 -40
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# athena-js
|
|
2
2
|
|
|
3
|
-
current version: `2.
|
|
3
|
+
current version: `2.4.1`
|
|
4
4
|
`@xylex-group/athena` is a database driver and API gateway SDK that lets you interact with SQL backends over HTTP through a fluent builder API. It ships a typed query builder for Node.js / server environments plus Athena-native React hooks for client-side use.
|
|
5
5
|
|
|
6
6
|
## Install
|
|
@@ -24,36 +24,61 @@ npm install react # React >=17 required for the hook
|
|
|
24
24
|
```ts
|
|
25
25
|
import { createClient } from "@xylex-group/athena";
|
|
26
26
|
|
|
27
|
-
const athenaClient = createClient(ATHENA_URL, ATHENA_API_KEY, {
|
|
28
|
-
client: "CLIENT_NAME",
|
|
29
|
-
backend: { type: "athena" },
|
|
30
|
-
});
|
|
31
|
-
|
|
32
|
-
const { data, error } = await athenaClient.from("orchestral_sections").findMany({
|
|
33
|
-
select: {
|
|
34
|
-
name: true,
|
|
35
|
-
instruments: {
|
|
36
|
-
select: {
|
|
37
|
-
name: true,
|
|
38
|
-
},
|
|
39
|
-
},
|
|
40
|
-
},
|
|
41
|
-
});
|
|
42
|
-
|
|
43
|
-
if (error) {
|
|
44
|
-
console.error("gateway error", error);
|
|
45
|
-
} else {
|
|
46
|
-
console.table(data);
|
|
47
|
-
}
|
|
48
|
-
```
|
|
49
|
-
|
|
50
|
-
Example version baseline: SDK `@xylex-group/athena` `2.
|
|
51
|
-
|
|
52
|
-
`.findMany({ select, where, orderBy, limit })` is the clean canonical read surface.
|
|
53
|
-
The existing string-based `.select(...)` chain remains fully supported for compatibility,
|
|
54
|
-
including alias/FK patterns like `from:sender_id(name)`.
|
|
55
|
-
For the full AST model, route contract, error behavior, and Athena server implications,
|
|
56
|
-
see [`docs/findmany-ast-and-server-contract.md`](docs/findmany-ast-and-server-contract.md).
|
|
27
|
+
const athenaClient = createClient(ATHENA_URL, ATHENA_API_KEY, {
|
|
28
|
+
client: "CLIENT_NAME",
|
|
29
|
+
backend: { type: "athena" },
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
const { data, error } = await athenaClient.from("orchestral_sections").findMany({
|
|
33
|
+
select: {
|
|
34
|
+
name: true,
|
|
35
|
+
instruments: {
|
|
36
|
+
select: {
|
|
37
|
+
name: true,
|
|
38
|
+
},
|
|
39
|
+
},
|
|
40
|
+
},
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
if (error) {
|
|
44
|
+
console.error("gateway error", error);
|
|
45
|
+
} else {
|
|
46
|
+
console.table(data);
|
|
47
|
+
}
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
Example version baseline: SDK `@xylex-group/athena` `2.4.0`, Athena server `3.12.3` verified on 2026-06-04.
|
|
51
|
+
|
|
52
|
+
`.findMany({ select, where, orderBy, limit })` is the clean canonical read surface.
|
|
53
|
+
The existing string-based `.select(...)` chain remains fully supported for compatibility,
|
|
54
|
+
including alias/FK patterns like `from:sender_id(name)`.
|
|
55
|
+
For the full AST model, route contract, error behavior, and Athena server implications,
|
|
56
|
+
see [`docs/findmany-ast-and-server-contract.md`](docs/findmany-ast-and-server-contract.md).
|
|
57
|
+
For method-by-method runtime AST/state/payload models across `select(...)`, mutations,
|
|
58
|
+
`rpc(...)`, `query(...)`, and fluent builder filters, see
|
|
59
|
+
[`docs/runtime-method-ast-models.md`](docs/runtime-method-ast-models.md).
|
|
60
|
+
|
|
61
|
+
### Gateway auth-session forwarding
|
|
62
|
+
|
|
63
|
+
If you need Athena server-side auth rollout to inspect auth context on normal query requests, the SDK now mirrors available auth state into gateway headers while still forwarding the original headers too.
|
|
64
|
+
|
|
65
|
+
Current behavior:
|
|
66
|
+
|
|
67
|
+
- `headers.Cookie` containing an Athena auth session cookie keeps `Cookie` and also adds `X-Athena-Auth-Session-Token`
|
|
68
|
+
- `headers.Authorization: Bearer ...` keeps `Authorization` and also adds `X-Athena-Auth-Bearer-Token`
|
|
69
|
+
- `createClient(..., { auth: { bearerToken } })` mirrors that token onto gateway/query requests as `X-Athena-Auth-Bearer-Token`
|
|
70
|
+
|
|
71
|
+
Server-side cookie forwarding example:
|
|
72
|
+
|
|
73
|
+
```ts
|
|
74
|
+
const athena = createClient(ATHENA_URL, ATHENA_API_KEY, {
|
|
75
|
+
headers: {
|
|
76
|
+
Cookie: request.headers.get("cookie") ?? "",
|
|
77
|
+
},
|
|
78
|
+
});
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
For the full contract, precedence rules, browser/server caveats, and rollout guidance, see [`docs/auth-session-forwarding.md`](docs/auth-session-forwarding.md).
|
|
57
82
|
|
|
58
83
|
### Auth client (Athena Auth server)
|
|
59
84
|
|
|
@@ -245,19 +270,19 @@ For prompt-ready documentation handoff text, see [`docs/generator-codex-handoff-
|
|
|
245
270
|
- `athena-rs` for Rust backend throughput
|
|
246
271
|
- `athena-js` for app/tooling layers that need TypeScript contracts and frontend-facing ergonomics
|
|
247
272
|
|
|
248
|
-
Every query resolves to `{ data, error, errorDetails?, status, statusText?, count?, raw }`. `data` is `null` on error; `error` is `null` on success.
|
|
249
|
-
|
|
250
|
-
Failed results now include a structured `error` object with the useful fields inline:
|
|
251
|
-
|
|
252
|
-
- `message`
|
|
253
|
-
- `code`
|
|
254
|
-
- `details`
|
|
255
|
-
- `hint`
|
|
256
|
-
- `status`
|
|
257
|
-
- `statusText`
|
|
258
|
-
- normalized metadata such as `kind`, `table`, `operation`, and `retryable`
|
|
259
|
-
|
|
260
|
-
`errorDetails` is still present as a compatibility alias for low-level gateway metadata (`gatewayCode`, `endpoint`, `method`, `requestId`, etc.).
|
|
273
|
+
Every query resolves to `{ data, error, errorDetails?, status, statusText?, count?, raw }`. `data` is `null` on error; `error` is `null` on success.
|
|
274
|
+
|
|
275
|
+
Failed results now include a structured `error` object with the useful fields inline:
|
|
276
|
+
|
|
277
|
+
- `message`
|
|
278
|
+
- `code`
|
|
279
|
+
- `details`
|
|
280
|
+
- `hint`
|
|
281
|
+
- `status`
|
|
282
|
+
- `statusText`
|
|
283
|
+
- normalized metadata such as `kind`, `table`, `operation`, and `retryable`
|
|
284
|
+
|
|
285
|
+
`errorDetails` is still present as a compatibility alias for low-level gateway metadata (`gatewayCode`, `endpoint`, `method`, `requestId`, etc.).
|
|
261
286
|
|
|
262
287
|
## Reliability helper APIs
|
|
263
288
|
|
|
@@ -296,26 +321,26 @@ requireAffected(inserted, { min: 1 }, { table: "users", operation: "insert" });
|
|
|
296
321
|
|
|
297
322
|
`requireAffected` uses `result.count`; request it on writes with `{ count: "exact" }` when you need enforced postconditions.
|
|
298
323
|
|
|
299
|
-
### Structured errors by default
|
|
300
|
-
|
|
301
|
-
```ts
|
|
302
|
-
import { createClient, normalizeAthenaError } from "@xylex-group/athena";
|
|
303
|
-
|
|
304
|
-
const athena = createClient(ATHENA_URL, ATHENA_API_KEY);
|
|
305
|
-
|
|
306
|
-
const { data, error, status, statusText } = await athena.from("users").insert({ id: 1 }).select();
|
|
307
|
-
if (error) {
|
|
308
|
-
console.error(error);
|
|
309
|
-
console.error(error.hint ?? error.message, status, statusText);
|
|
310
|
-
if (error.kind === "unique_violation") {
|
|
311
|
-
// deterministic conflict handling
|
|
312
|
-
}
|
|
313
|
-
}
|
|
314
|
-
```
|
|
315
|
-
|
|
316
|
-
`result.error` already carries normalized `kind` values (`unique_violation`, `validation`, `auth`, `rate_limit`, `transient`, etc.) plus operation metadata.
|
|
317
|
-
|
|
318
|
-
`normalizeAthenaError(result)` still exists when you need the normalized envelope from an arbitrary thrown value or mixed unknown input.
|
|
324
|
+
### Structured errors by default
|
|
325
|
+
|
|
326
|
+
```ts
|
|
327
|
+
import { createClient, normalizeAthenaError } from "@xylex-group/athena";
|
|
328
|
+
|
|
329
|
+
const athena = createClient(ATHENA_URL, ATHENA_API_KEY);
|
|
330
|
+
|
|
331
|
+
const { data, error, status, statusText } = await athena.from("users").insert({ id: 1 }).select();
|
|
332
|
+
if (error) {
|
|
333
|
+
console.error(error);
|
|
334
|
+
console.error(error.hint ?? error.message, status, statusText);
|
|
335
|
+
if (error.kind === "unique_violation") {
|
|
336
|
+
// deterministic conflict handling
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
```
|
|
340
|
+
|
|
341
|
+
`result.error` already carries normalized `kind` values (`unique_violation`, `validation`, `auth`, `rate_limit`, `transient`, etc.) plus operation metadata.
|
|
342
|
+
|
|
343
|
+
`normalizeAthenaError(result)` still exists when you need the normalized envelope from an arbitrary thrown value or mixed unknown input.
|
|
319
344
|
|
|
320
345
|
### Query tracing (experimental)
|
|
321
346
|
|
|
@@ -325,49 +350,68 @@ const athena = createClient(ATHENA_URL, ATHENA_API_KEY, {
|
|
|
325
350
|
});
|
|
326
351
|
```
|
|
327
352
|
|
|
328
|
-
With `traceQueries: true`, the SDK logs every runtime execution (`select`, `insert`, `upsert`, `update`, `delete`, `rpc`, `query`) and includes:
|
|
329
|
-
|
|
330
|
-
- the gateway endpoint used
|
|
331
|
-
- synthesized SQL (or raw SQL for `query(...)` and SQL fallback reads)
|
|
332
|
-
- payload and call options
|
|
333
|
-
- full outcome (`status`, `error`, `count`, `data`, `raw`)
|
|
334
|
-
- callsite metadata (`filePath`, `fileName`, `line`, `column`)
|
|
335
|
-
|
|
336
|
-
For deferred chains, Athena captures that callsite from the public SDK seam that declared or finalized the operation and reuses it for the eventual network execution. That keeps traces pinned to user code instead of drifting into SDK internals when async stack shapes differ between local runs and CI.
|
|
353
|
+
With `traceQueries: true`, the SDK logs every runtime execution (`select`, `insert`, `upsert`, `update`, `delete`, `rpc`, `query`) and includes:
|
|
354
|
+
|
|
355
|
+
- the gateway endpoint used
|
|
356
|
+
- synthesized SQL (or raw SQL for `query(...)` and SQL fallback reads)
|
|
357
|
+
- payload and call options
|
|
358
|
+
- full outcome (`status`, `error`, `count`, `data`, `raw`)
|
|
359
|
+
- callsite metadata (`filePath`, `fileName`, `line`, `column`)
|
|
360
|
+
|
|
361
|
+
For deferred chains, Athena captures that callsite from the public SDK seam that declared or finalized the operation and reuses it for the eventual network execution. That keeps traces pinned to user code instead of drifting into SDK internals when async stack shapes differ between local runs and CI.
|
|
337
362
|
|
|
338
363
|
Use a custom sink:
|
|
339
364
|
|
|
340
|
-
```ts
|
|
341
|
-
const athena = createClient(ATHENA_URL, ATHENA_API_KEY, {
|
|
342
|
-
experimental: {
|
|
343
|
-
traceQueries: {
|
|
344
|
-
logger(event) {
|
|
345
|
-
// Forward into your logger/observability sink
|
|
346
|
-
console.log(event.operation, event.endpoint, event.sql, event.callsite);
|
|
347
|
-
},
|
|
348
|
-
},
|
|
349
|
-
},
|
|
350
|
-
});
|
|
351
|
-
```
|
|
352
|
-
|
|
353
|
-
###
|
|
354
|
-
|
|
355
|
-
```ts
|
|
356
|
-
const athena = createClient(ATHENA_URL, ATHENA_API_KEY, {
|
|
357
|
-
experimental: {
|
|
358
|
-
|
|
359
|
-
},
|
|
360
|
-
});
|
|
361
|
-
```
|
|
362
|
-
|
|
363
|
-
With `
|
|
364
|
-
|
|
365
|
-
-
|
|
366
|
-
-
|
|
367
|
-
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
365
|
+
```ts
|
|
366
|
+
const athena = createClient(ATHENA_URL, ATHENA_API_KEY, {
|
|
367
|
+
experimental: {
|
|
368
|
+
traceQueries: {
|
|
369
|
+
logger(event) {
|
|
370
|
+
// Forward into your logger/observability sink
|
|
371
|
+
console.log(event.operation, event.endpoint, event.sql, event.callsite);
|
|
372
|
+
},
|
|
373
|
+
},
|
|
374
|
+
},
|
|
375
|
+
});
|
|
376
|
+
```
|
|
377
|
+
|
|
378
|
+
### Read retries (experimental)
|
|
379
|
+
|
|
380
|
+
```ts
|
|
381
|
+
const athena = createClient(ATHENA_URL, ATHENA_API_KEY, {
|
|
382
|
+
experimental: {
|
|
383
|
+
retryReads: true,
|
|
384
|
+
},
|
|
385
|
+
});
|
|
386
|
+
```
|
|
387
|
+
|
|
388
|
+
With `retryReads: true`, the SDK automatically retries retryable read failures for `select`, `findMany(...)`, and `query(...)`.
|
|
389
|
+
|
|
390
|
+
- two additional attempts are applied internally
|
|
391
|
+
- retry classification follows the SDK's normalized `retryable` signal
|
|
392
|
+
- writes (`insert`, `upsert`, `update`, `delete`) are not retried by this flag
|
|
393
|
+
|
|
394
|
+
### findMany AST transport (experimental)
|
|
395
|
+
|
|
396
|
+
```ts
|
|
397
|
+
const athena = createClient(ATHENA_URL, ATHENA_API_KEY, {
|
|
398
|
+
experimental: {
|
|
399
|
+
findManyAst: true,
|
|
400
|
+
},
|
|
401
|
+
});
|
|
402
|
+
```
|
|
403
|
+
|
|
404
|
+
With `findManyAst: true`, clean `findMany(...)` calls can send an AST-style body to `/gateway/fetch` instead of compiling the select tree down to `columns` and `conditions` first.
|
|
405
|
+
|
|
406
|
+
- this is opt-in and meant for gateways that explicitly support direct AST bodies
|
|
407
|
+
- existing compiled `findMany(...)` transport remains the default
|
|
408
|
+
- shorthand `where` filters are normalized to explicit operator objects before the AST body is sent
|
|
409
|
+
- UUID-like equality filters that need the SDK's `::text` comparison still fall back to the legacy query/compiled path
|
|
410
|
+
- nested relation select strings stay off the SQL query fallback path and continue through `/gateway/fetch`
|
|
411
|
+
- chained builder filters or pagination state that the AST body cannot represent losslessly yet continue to use the legacy compiled path
|
|
412
|
+
- trace output still includes synthesized SQL so diagnostics stay readable
|
|
413
|
+
|
|
414
|
+
### Numeric coercion
|
|
371
415
|
|
|
372
416
|
```ts
|
|
373
417
|
import { coerceInt, assertInt } from "@xylex-group/athena";
|
|
@@ -384,12 +428,27 @@ Utilities that are intentionally not exported from the root package are availabl
|
|
|
384
428
|
|
|
385
429
|
```ts
|
|
386
430
|
import {
|
|
431
|
+
asString,
|
|
432
|
+
asBoolean,
|
|
433
|
+
asBooleanOrNull,
|
|
434
|
+
asRecord,
|
|
435
|
+
asIdentifier,
|
|
436
|
+
firstString,
|
|
437
|
+
readTrimmedString,
|
|
438
|
+
asNumber,
|
|
439
|
+
asStringArray,
|
|
387
440
|
slugify,
|
|
388
441
|
trimTrailingSlashes,
|
|
389
442
|
parseBooleanFlag,
|
|
390
443
|
isLocalHostname,
|
|
391
444
|
clearAuthCookies,
|
|
392
445
|
proxyRequestHeaders,
|
|
446
|
+
sqlText,
|
|
447
|
+
escapeLikePatternValue,
|
|
448
|
+
quoteSqlStringLiteral,
|
|
449
|
+
sqlNullableText,
|
|
450
|
+
sqlJsonbLiteral,
|
|
451
|
+
sqlBigInt,
|
|
393
452
|
} from "@xylex-group/athena/utils";
|
|
394
453
|
```
|
|
395
454
|
|
|
@@ -400,15 +459,27 @@ const slug = slugify("Customer Success / Q4 Report"); // customer-success-q4-rep
|
|
|
400
459
|
const local = isLocalHostname("api.localhost"); // true
|
|
401
460
|
const normalized = trimTrailingSlashes("https://example.com///"); // https://example.com
|
|
402
461
|
const enabled = parseBooleanFlag(process.env.FEATURE_FLAG, false);
|
|
462
|
+
const count = asNumber("42"); // 42
|
|
463
|
+
const label = asString(" ready "); // ready
|
|
464
|
+
const active = asBooleanOrNull("yes"); // true
|
|
465
|
+
const tags = asStringArray([" alpha ", "", "beta"]); // ["alpha", "beta"]
|
|
466
|
+
const likePattern = escapeLikePatternValue("%admin_"); // \%admin\_
|
|
403
467
|
|
|
404
468
|
// Browser-only helper (safe no-op on server runtimes)
|
|
405
469
|
clearAuthCookies();
|
|
406
470
|
|
|
407
471
|
// Preserve forwarded headers when proxying auth requests
|
|
408
472
|
const upstreamHeaders = proxyRequestHeaders(request);
|
|
473
|
+
|
|
474
|
+
// Safely embed raw SQL values when using athena.query(...)
|
|
475
|
+
const emailLiteral = sqlText("floris@example.com");
|
|
476
|
+
const metadataLiteral = sqlJsonbLiteral({ role: "admin" });
|
|
477
|
+
const actorIdLiteral = sqlBigInt(42);
|
|
478
|
+
const exactLiteral = quoteSqlStringLiteral("Athena's SDK");
|
|
409
479
|
```
|
|
410
480
|
|
|
411
481
|
`clearAuthCookies()` clears cookies matching Athena/Better Auth prefixes (`athena-auth`, `__Secure-athena-auth`, `better-auth`, `__Secure-better-auth`) and also attempts parent-domain cleanup for subdomain deployments.
|
|
482
|
+
For SQL identifiers, keep using `identifier(...)`; `sqlText(...)`-style helpers are for literal values only.
|
|
412
483
|
|
|
413
484
|
### Retry helper
|
|
414
485
|
|
|
@@ -583,15 +654,33 @@ const { data: user } = await athena
|
|
|
583
654
|
|
|
584
655
|
### Table schema targeting
|
|
585
656
|
|
|
586
|
-
Use `schema` in table call options to qualify unqualified table names:
|
|
657
|
+
Use `schema` either on `from(...)` itself or in table call options to qualify unqualified table names:
|
|
587
658
|
|
|
588
659
|
```ts
|
|
589
660
|
const { data } = await athena
|
|
661
|
+
.from("users", { schema: "public" })
|
|
662
|
+
.select("id,email");
|
|
663
|
+
|
|
664
|
+
const { data: sameTarget } = await athena
|
|
590
665
|
.from("users")
|
|
591
666
|
.select("id,email", { schema: "public" });
|
|
667
|
+
|
|
668
|
+
const { data: crossSchema } = await athena
|
|
669
|
+
.from("chat_subscriptions", { schema: "private" })
|
|
670
|
+
.findMany({
|
|
671
|
+
select: {
|
|
672
|
+
user_id: true,
|
|
673
|
+
user: {
|
|
674
|
+
schema: "athena",
|
|
675
|
+
select: {
|
|
676
|
+
id: true,
|
|
677
|
+
},
|
|
678
|
+
},
|
|
679
|
+
},
|
|
680
|
+
});
|
|
592
681
|
```
|
|
593
682
|
|
|
594
|
-
|
|
683
|
+
Both resolve the table target to `public.users`.
|
|
595
684
|
|
|
596
685
|
### RPC
|
|
597
686
|
|