@xylex-group/athena 2.0.0 → 2.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.
Files changed (48) hide show
  1. package/README.md +287 -95
  2. package/dist/browser.cjs +4791 -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 +4745 -0
  7. package/dist/browser.js.map +1 -0
  8. package/dist/cli/index.cjs +2087 -220
  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 +2089 -222
  13. package/dist/cli/index.js.map +1 -1
  14. package/dist/cookies.cjs +890 -0
  15. package/dist/cookies.cjs.map +1 -0
  16. package/dist/cookies.d.cts +174 -0
  17. package/dist/cookies.d.ts +174 -0
  18. package/dist/cookies.js +869 -0
  19. package/dist/cookies.js.map +1 -0
  20. package/dist/index.cjs +3046 -1200
  21. package/dist/index.cjs.map +1 -1
  22. package/dist/index.d.cts +8 -408
  23. package/dist/index.d.ts +8 -408
  24. package/dist/index.js +3045 -1202
  25. package/dist/index.js.map +1 -1
  26. package/dist/{model-form-CVOtC8jq.d.ts → model-form-BpDXlbxb.d.ts} +97 -2
  27. package/dist/{model-form-hXkvHS_3.d.cts → model-form-hoE2jHIi.d.cts} +97 -2
  28. package/dist/pipeline-BNIw8pDQ.d.ts +8 -0
  29. package/dist/pipeline-DNIpEsN8.d.cts +8 -0
  30. package/dist/react-email-BvyCZnfW.d.cts +610 -0
  31. package/dist/react-email-qPA1wjFV.d.ts +610 -0
  32. package/dist/react.cjs +30 -9
  33. package/dist/react.cjs.map +1 -1
  34. package/dist/react.d.cts +4 -4
  35. package/dist/react.d.ts +4 -4
  36. package/dist/react.js +30 -9
  37. package/dist/react.js.map +1 -1
  38. package/dist/{types-BnzoaNRC.d.cts → types-A5e97acl.d.cts} +2 -1
  39. package/dist/{types-BnzoaNRC.d.ts → types-A5e97acl.d.ts} +2 -1
  40. package/dist/{pipeline-CQgV-Yfo.d.ts → types-BnD22-vb.d.ts} +2 -7
  41. package/dist/{pipeline-C-cN0ACi.d.cts → types-bDlr4u7p.d.cts} +2 -7
  42. package/dist/utils.cjs +153 -0
  43. package/dist/utils.cjs.map +1 -0
  44. package/dist/utils.d.cts +23 -0
  45. package/dist/utils.d.ts +23 -0
  46. package/dist/utils.js +146 -0
  47. package/dist/utils.js.map +1 -0
  48. package/package.json +87 -9
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # athena-js
2
2
 
3
- current version: `2.0.0`
3
+ current version: `2.3.0`
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,66 +24,144 @@ 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("characters").select(`
33
- id,
34
- name,
35
- from:sender_id(name),
36
- to:receiver_id(name)
37
- `);
38
-
39
- if (error) {
40
- console.error("gateway error", error);
41
- } else {
42
- console.table(data);
43
- }
44
- ```
45
-
46
- ### Auth client (Athena Auth server)
47
-
48
- If your auth backend is now Athena Auth, you can keep core login/session flows in this SDK:
49
-
50
- ```ts
51
- import { createClient } from "@xylex-group/athena";
52
-
53
- const athena = createClient(ATHENA_URL, ATHENA_API_KEY, {
27
+ const athenaClient = createClient(ATHENA_URL, ATHENA_API_KEY, {
54
28
  client: "CLIENT_NAME",
55
- auth: {
56
- baseUrl: "http://localhost:3001/api/auth",
57
- // optional: bearer token if you are not using cookie-based sessions
58
- bearerToken: process.env.AUTH_BEARER_TOKEN,
59
- },
29
+ backend: { type: "athena" },
60
30
  });
61
31
 
62
- const login = await athena.auth.signIn.email({
63
- email: "demo@example.com",
64
- password: "super-secret",
65
- rememberMe: true,
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
+ },
66
41
  });
67
42
 
68
- const session = await athena.auth.getSession();
69
- const sessions = await athena.auth.session.list();
70
-
71
- // clear one session
72
- await athena.auth.session.revoke({ token: "session_token_here" });
73
- // or clear all sessions
74
- await athena.auth.session.revoke([{ token: "session_token_here" }, { token: "session_token_2" }]);
75
-
76
- await athena.auth.signOut();
77
-
78
- // additional core flows
79
- await athena.auth.forgetPassword({ email: "demo@example.com", redirectTo: "https://app/reset-password" });
80
- await athena.auth.resetPassword({ newPassword: "new-secret", token: "reset_token" });
81
- await athena.auth.verifyEmail({ token: "verify_token", callbackURL: "https://app/verified" });
82
- await athena.auth.changePassword({ currentPassword: "old-secret", newPassword: "new-secret" });
83
- await athena.auth.user.update({ name: "Demo User" });
43
+ if (error) {
44
+ console.error("gateway error", error);
45
+ } else {
46
+ console.table(data);
47
+ }
84
48
  ```
85
49
 
86
- Auth responses follow the same envelope style: `{ ok, status, data, error, errorDetails, raw }`.
50
+ Example version baseline: SDK `@xylex-group/athena` `2.3.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
+
58
+ ### Auth client (Athena Auth server)
59
+
60
+ If your auth backend is now Athena Auth, you can keep core login/session flows in this SDK:
61
+
62
+ ```ts
63
+ import { createClient } from "@xylex-group/athena";
64
+
65
+ const athena = createClient(ATHENA_URL, ATHENA_API_KEY, {
66
+ client: "CLIENT_NAME",
67
+ auth: {
68
+ baseUrl: "http://localhost:3001/api/auth",
69
+ // optional: bearer token if you are not using cookie-based sessions
70
+ bearerToken: process.env.AUTH_BEARER_TOKEN,
71
+ },
72
+ });
73
+
74
+ const login = await athena.auth.signIn.email({
75
+ email: "demo@example.com",
76
+ password: "super-secret",
77
+ rememberMe: true,
78
+ });
79
+
80
+ const session = await athena.auth.getSession();
81
+ const sessions = await athena.auth.session.list();
82
+
83
+ // clear one session
84
+ await athena.auth.session.revoke({ token: "session_token_here" });
85
+ // or clear all sessions
86
+ await athena.auth.session.revoke([{ token: "session_token_here" }, { token: "session_token_2" }]);
87
+
88
+ await athena.auth.signOut();
89
+
90
+ // additional core flows
91
+ await athena.auth.forgetPassword({ email: "demo@example.com", redirectTo: "https://app/reset-password" });
92
+ await athena.auth.resetPassword({ newPassword: "new-secret", token: "reset_token" });
93
+ await athena.auth.verifyEmail({ token: "verify_token", callbackURL: "https://app/verified" });
94
+ await athena.auth.changePassword({ currentPassword: "old-secret", newPassword: "new-secret" });
95
+ await athena.auth.user.update({ name: "Demo User" });
96
+ ```
97
+
98
+ #### React Email templates for admin HTML routes
99
+
100
+ If you use `@react-email/components`, you can pass component+props payloads directly on admin email/template routes:
101
+
102
+ ```ts
103
+ import { Body, Html, Text } from "@react-email/components";
104
+
105
+ function WelcomeEmail(props: { name: string }) {
106
+ return (
107
+ <Html lang="en">
108
+ <Body>
109
+ <Text>Welcome {props.name}</Text>
110
+ </Body>
111
+ </Html>
112
+ );
113
+ }
114
+
115
+ await athena.auth.admin.email.template.create({
116
+ templateKey: "welcome",
117
+ subjectTemplate: "Welcome",
118
+ react: {
119
+ component: WelcomeEmail,
120
+ props: { name: "Ava" },
121
+ },
122
+ });
123
+ ```
124
+
125
+ For reusable templates, use `defineAuthEmailTemplate(...)`:
126
+
127
+ ```ts
128
+ import { defineAuthEmailTemplate } from "@xylex-group/athena";
129
+
130
+ const welcomeTemplate = defineAuthEmailTemplate({
131
+ component: WelcomeEmail,
132
+ templateKey: "welcome",
133
+ subjectTemplate: "Welcome",
134
+ });
135
+
136
+ await athena.auth.admin.email.template.create(
137
+ welcomeTemplate.toTemplateCreate({
138
+ props: { name: "Ava" },
139
+ }),
140
+ );
141
+ ```
142
+
143
+ You can also observe render timing/errors:
144
+
145
+ ```ts
146
+ const athena = createClient(ATHENA_URL, ATHENA_API_KEY, {
147
+ auth: {
148
+ baseUrl: AUTH_BASE_URL,
149
+ reactEmail: {
150
+ observe: (event) => {
151
+ console.log(JSON.stringify(event));
152
+ },
153
+ },
154
+ },
155
+ });
156
+ ```
157
+
158
+ Install support packages in your app:
159
+
160
+ ```bash
161
+ pnpm add @react-email/components @react-email/render
162
+ ```
163
+
164
+ Auth responses follow the same envelope style: `{ ok, status, data, error, errorDetails, raw }`.
87
165
 
88
166
  ### Typed schema registry (model-first)
89
167
 
@@ -125,6 +203,8 @@ await typed
125
203
 
126
204
  For full details, see [`docs/typed-schema-registry.md`](./docs/typed-schema-registry.md).
127
205
 
206
+ For exhaustive method-by-method documentation with usage snippets (root client, runtime builders, auth bindings, react runtime, cookies, and utils), see [`docs/complete-method-reference.md`](./docs/complete-method-reference.md).
207
+
128
208
  ### Typed schema generator
129
209
 
130
210
  Schema generation is additive. Existing `createClient(...).from<T>(...)` usage remains valid while teams migrate to generated registry files.
@@ -165,9 +245,19 @@ For prompt-ready documentation handoff text, see [`docs/generator-codex-handoff-
165
245
  - `athena-rs` for Rust backend throughput
166
246
  - `athena-js` for app/tooling layers that need TypeScript contracts and frontend-facing ergonomics
167
247
 
168
- Every query resolves to `{ data, error, errorDetails?, status, count?, raw }`. `data` is `null` on error; `error` is `null` on success.
169
-
170
- For richer handling, inspect `errorDetails` (`code`, `status`, `endpoint`, `method`, `requestId`, etc.) or use `AthenaGatewayError` / `isAthenaGatewayError` from the package exports.
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.).
171
261
 
172
262
  ## Reliability helper APIs
173
263
 
@@ -206,26 +296,78 @@ requireAffected(inserted, { min: 1 }, { table: "users", operation: "insert" });
206
296
 
207
297
  `requireAffected` uses `result.count`; request it on writes with `{ count: "exact" }` when you need enforced postconditions.
208
298
 
209
- ### Error normalization
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.
210
319
 
211
- ```ts
212
- import { normalizeAthenaError } from "@xylex-group/athena";
320
+ ### Query tracing (experimental)
213
321
 
214
- const result = await athena.from("users").insert({ id: 1 }).select();
215
- if (result.error) {
216
- const err = normalizeAthenaError(result, {
217
- table: "users",
218
- operation: "insert",
219
- });
220
- if (err.kind === "unique_violation") {
221
- // deterministic conflict handling
222
- }
223
- }
322
+ ```ts
323
+ const athena = createClient(ATHENA_URL, ATHENA_API_KEY, {
324
+ experimental: { traceQueries: true },
325
+ });
224
326
  ```
225
327
 
226
- Normalized errors expose stable `kind` values (`unique_violation`, `validation`, `auth`, `rate_limit`, `transient`, etc.) plus operation metadata.
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.
337
+
338
+ Use a custom sink:
227
339
 
228
- ### Numeric coercion
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
+ ### findMany AST transport (experimental)
354
+
355
+ ```ts
356
+ const athena = createClient(ATHENA_URL, ATHENA_API_KEY, {
357
+ experimental: {
358
+ findManyAst: true,
359
+ },
360
+ });
361
+ ```
362
+
363
+ With `findManyAst: true`, clean `findMany(...)` calls send the original object AST body to `/gateway/fetch` instead of compiling the select tree down to `columns` and `conditions` first.
364
+
365
+ - this is opt-in and meant for gateways that explicitly support direct AST bodies
366
+ - existing compiled `findMany(...)` transport remains the default
367
+ - chained builder filters or pagination state that the AST body cannot represent losslessly yet continue to use the legacy compiled path
368
+ - trace output still includes synthesized SQL so diagnostics stay readable
369
+
370
+ ### Numeric coercion
229
371
 
230
372
  ```ts
231
373
  import { coerceInt, assertInt } from "@xylex-group/athena";
@@ -236,6 +378,38 @@ if (maybeCaseId == null) throw new Error("Invalid case id");
236
378
  const caseId = assertInt(req.query.case_id, "case_id", { min: 1 });
237
379
  ```
238
380
 
381
+ ### Utilities subpath
382
+
383
+ Utilities that are intentionally not exported from the root package are available from `@xylex-group/athena/utils`.
384
+
385
+ ```ts
386
+ import {
387
+ slugify,
388
+ trimTrailingSlashes,
389
+ parseBooleanFlag,
390
+ isLocalHostname,
391
+ clearAuthCookies,
392
+ proxyRequestHeaders,
393
+ } from "@xylex-group/athena/utils";
394
+ ```
395
+
396
+ Examples:
397
+
398
+ ```ts
399
+ const slug = slugify("Customer Success / Q4 Report"); // customer-success-q4-report
400
+ const local = isLocalHostname("api.localhost"); // true
401
+ const normalized = trimTrailingSlashes("https://example.com///"); // https://example.com
402
+ const enabled = parseBooleanFlag(process.env.FEATURE_FLAG, false);
403
+
404
+ // Browser-only helper (safe no-op on server runtimes)
405
+ clearAuthCookies();
406
+
407
+ // Preserve forwarded headers when proxying auth requests
408
+ const upstreamHeaders = proxyRequestHeaders(request);
409
+ ```
410
+
411
+ `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.
412
+
239
413
  ### Retry helper
240
414
 
241
415
  ```ts
@@ -256,6 +430,24 @@ By default, retries target transient/rate-limit failures; use `shouldRetry` for
256
430
 
257
431
  ## Query builder
258
432
 
433
+ ### DB module namespace
434
+
435
+ `createClient()` keeps root methods (`from`, `rpc`, `query`) and now also exposes `db` as an additive namespace.
436
+
437
+ ```ts
438
+ const athena = createClient(ATHENA_URL, ATHENA_API_KEY);
439
+
440
+ await athena.db.from("users").select("id,name").eq("active", true).limit(20);
441
+
442
+ await athena.db.select("users", "id,name").eq("id", 1).single();
443
+
444
+ await athena.db.insert("users", { id: 1, name: "Alice" }).select("id");
445
+ await athena.db.update("users", { name: "Updated" }).eq("id", 1).select("id,name");
446
+ await athena.db.delete("users", { resourceId: "r-1" }).select("id");
447
+ ```
448
+
449
+ `db` mirrors the existing query builder semantics while providing a module seam for future database-surface expansion.
450
+
259
451
  ### Reading rows
260
452
 
261
453
  ```ts
@@ -387,23 +579,23 @@ const { data: user } = await athena
387
579
  .single();
388
580
  ```
389
581
 
390
- `maybeSingle` behaves identically — both return the first element of the result set.
391
-
392
- ### Table schema targeting
393
-
394
- Use `schema` in table call options to qualify unqualified table names:
395
-
396
- ```ts
397
- const { data } = await athena
398
- .from("users")
399
- .select("id,email", { schema: "public" });
400
- ```
401
-
402
- This resolves the table target to `public.users`.
403
-
404
- ### RPC
405
-
406
- Use `athena.rpc(...)` for Postgres function calls. By default it calls `POST /gateway/rpc`, and with `{ get: true }` it uses the compatibility route `GET /rpc/{function_name}`.
582
+ `maybeSingle` behaves identically — both return the first element of the result set.
583
+
584
+ ### Table schema targeting
585
+
586
+ Use `schema` in table call options to qualify unqualified table names:
587
+
588
+ ```ts
589
+ const { data } = await athena
590
+ .from("users")
591
+ .select("id,email", { schema: "public" });
592
+ ```
593
+
594
+ This resolves the table target to `public.users`.
595
+
596
+ ### RPC
597
+
598
+ Use `athena.rpc(...)` for Postgres function calls. By default it calls `POST /gateway/rpc`, and with `{ get: true }` it uses the compatibility route `GET /rpc/{function_name}`.
407
599
 
408
600
  ```ts
409
601
  const { data, count } = await athena
@@ -437,12 +629,12 @@ RPC options: `schema`, `count` (`"exact" | "planned" | "estimated"`), `head`, `g
437
629
 
438
630
  Pass options as the second argument to `.select()`:
439
631
 
440
- | Option | Type | Description |
441
- | ------------ | ------------------------------------- | -------------------------------------------- |
442
- | `schema` | `string` | qualify unqualified table names for table calls |
443
- | `count` | `"exact" \| "planned" \| "estimated"` | request a row count alongside the data |
444
- | `head` | `boolean` | return response headers only (no rows) |
445
- | `stripNulls` | `boolean` | strip null values from rows (default `true`) |
632
+ | Option | Type | Description |
633
+ | ------------ | ------------------------------------- | -------------------------------------------- |
634
+ | `schema` | `string` | qualify unqualified table names for table calls |
635
+ | `count` | `"exact" \| "planned" \| "estimated"` | request a row count alongside the data |
636
+ | `head` | `boolean` | return response headers only (no rows) |
637
+ | `stripNulls` | `boolean` | strip null values from rows (default `true`) |
446
638
 
447
639
  ```ts
448
640
  const { data } = await athena