@xylex-group/athena 1.4.1 → 1.6.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  # athena-js
2
2
 
3
- current version: `1.4.1`
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 and a React hook for client-side use.
3
+ current version: `1.6.0`
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
7
7
 
@@ -13,7 +13,7 @@ pnpm add @xylex-group/athena
13
13
  yarn add @xylex-group/athena
14
14
  ```
15
15
 
16
- React peer dependency is optional — only needed if you use `useAthenaGateway`.
16
+ React peer dependency is optional — only needed if you use `@xylex-group/athena/react` hooks.
17
17
 
18
18
  ```bash
19
19
  npm install react # React >=17 required for the hook
@@ -43,6 +43,62 @@ if (error) {
43
43
  }
44
44
  ```
45
45
 
46
+ ### Typed schema registry (model-first)
47
+
48
+ You can keep `createClient(...).from<T>(...)` as-is, or opt into a typed registry:
49
+
50
+ ```ts
51
+ import {
52
+ createTypedClient,
53
+ defineDatabase,
54
+ defineModel,
55
+ defineRegistry,
56
+ defineSchema,
57
+ } from "@xylex-group/athena";
58
+
59
+ const registry = defineRegistry({
60
+ primary: defineDatabase({
61
+ public: defineSchema({
62
+ users: defineModel<{ id: string; email: string }>({
63
+ meta: {
64
+ primaryKey: ["id"],
65
+ nullable: { id: false, email: false },
66
+ },
67
+ }),
68
+ }),
69
+ }),
70
+ });
71
+
72
+ const typed = createTypedClient(registry, ATHENA_URL, ATHENA_API_KEY, {
73
+ tenantKeyMap: {
74
+ organizationId: "X-Organization-Id",
75
+ },
76
+ });
77
+
78
+ await typed
79
+ .withTenantContext({ organizationId: "org_1" })
80
+ .fromModel("primary", "public", "users")
81
+ .select("*");
82
+ ```
83
+
84
+ For full details, see [`docs/typed-schema-registry.md`](./docs/typed-schema-registry.md).
85
+
86
+ ### Typed schema generator (phase 2 scaffolding)
87
+
88
+ The SDK now includes a project-root generator config system (`athena.config.ts`) and CLI command:
89
+
90
+ ```bash
91
+ athena-js generate
92
+ athena-js generate --dry-run
93
+ athena-js generate --config ./athena.config.ts
94
+ ```
95
+
96
+ Generator output paths support placeholder tokens (database/schema/model + case variants), feature flags, and experimental provider contracts.
97
+ PostgreSQL introspection works both via direct `pg_url` and gateway-only `/gateway/query` execution.
98
+
99
+ For full generator configuration, see [`docs/generator-config.md`](./docs/generator-config.md).
100
+ For prompt-ready documentation handoff text, see [`docs/generator-codex-handoff-prompt-pack.md`](./docs/generator-codex-handoff-prompt-pack.md).
101
+
46
102
  Every query resolves to `{ data, error, errorDetails?, status, count?, raw }`. `data` is `null` on error; `error` is `null` on success.
47
103
 
48
104
  For richer handling, inspect `errorDetails` (`code`, `status`, `endpoint`, `method`, `requestId`, etc.) or use `AthenaGatewayError` / `isAthenaGatewayError` from the package exports.
@@ -153,12 +209,12 @@ Filters accumulate on the builder and are sent together when the query executes.
153
209
 
154
210
  ```ts
155
211
  const { data } = await athena
156
- .from("characters")
157
- .select("id, name")
158
- .eq("active", true) // column = value
159
- .eqUuid("session_id", "550e8400-e29b-41d4-a716-446655440000") // explicit UUID cast
160
- .eqCast("session_id", "550e8400-e29b-41d4-a716-446655440000", "uuid") // explicit cast type
161
- .neq("role", "guest") // column != value
212
+ .from("characters")
213
+ .select("id, name")
214
+ .eq("active", true) // column = value
215
+ .eqUuid("session_id", "550e8400-e29b-41d4-a716-446655440000") // explicit UUID cast
216
+ .eqCast("session_id", "550e8400-e29b-41d4-a716-446655440000", "uuid") // explicit cast type
217
+ .neq("role", "guest") // column != value
162
218
  .gt("level", 5) // column > value
163
219
  .gte("score", 100) // column >= value
164
220
  .lt("age", 30) // column < value
@@ -171,10 +227,10 @@ const { data } = await athena
171
227
  .containedBy("tags", ["hero", "villain"]) // array is subset of value
172
228
  .match({ role: "admin", active: true }) // multiple eq filters at once
173
229
  .not("role", "eq", "banned") // NOT col op val
174
- .or("status.eq.active,status.eq.pending"); // OR expression
175
- ```
176
-
177
- `eq()` now auto-detects UUID-like values on identifier columns (for example `id`, `*_id`, `*uuid*`) and uses a safe typed comparison path so UUID columns no longer require app-side manual `::uuid` / `::text` casts.
230
+ .or("status.eq.active,status.eq.pending"); // OR expression
231
+ ```
232
+
233
+ `eq()` now auto-detects UUID-like values on identifier columns (for example `id`, `*_id`, `*uuid*`) and uses a safe typed comparison path so UUID columns no longer require app-side manual `::uuid` / `::text` casts.
178
234
 
179
235
  Canonical style for reads is to call `.select(...)` first, then apply filters:
180
236
 
@@ -412,11 +468,105 @@ mutation.finally(() => …);
412
468
 
413
469
  The request fires only once regardless of how many times you call `.then()` or await the object.
414
470
 
415
- ## React hook
471
+ ## React hooks
416
472
 
417
473
  ```tsx
418
474
  "use client";
419
475
 
476
+ import {
477
+ AthenaQueryClientProvider,
478
+ createAthenaQueryClient,
479
+ useAthenaGateway,
480
+ useMutation,
481
+ useQuery,
482
+ } from "@xylex-group/athena/react";
483
+ import { createClient } from "@xylex-group/athena";
484
+
485
+ const queryClient = createAthenaQueryClient({
486
+ cache: { mode: "none" }, // default: no persistent data cache, inflight dedupe only
487
+ });
488
+
489
+ const athena = createClient(
490
+ process.env.NEXT_PUBLIC_ATHENA_URL!,
491
+ process.env.NEXT_PUBLIC_ATHENA_API_KEY!,
492
+ );
493
+
494
+ type Product = {
495
+ id: string;
496
+ name: string;
497
+ price: number;
498
+ };
499
+
500
+ type CreateProductInput = {
501
+ name: string;
502
+ price: number;
503
+ };
504
+
505
+ function ProductsInner() {
506
+ const products = useQuery<Product[]>({
507
+ queryKey: ["products"],
508
+ queryFn: () =>
509
+ athena.from("products").select("id,name,price").limit(50),
510
+ });
511
+
512
+ const createProduct = useMutation<CreateProductInput, Product>({
513
+ mutationFn: (input) =>
514
+ athena.from("products").insert(input).select("id,name,price").single(),
515
+ onSuccess: () => {
516
+ void products.refetch();
517
+ },
518
+ });
519
+
520
+ if (products.isLoading) return <div>Loading...</div>;
521
+ if (products.error) return <div>{products.error.message}</div>;
522
+
523
+ return (
524
+ <div>
525
+ <button
526
+ onClick={() => {
527
+ createProduct.mutate({ name: "New product", price: 99 });
528
+ }}
529
+ >
530
+ Add Product
531
+ </button>
532
+ {products.data?.map((product) => (
533
+ <div key={product.id}>
534
+ {product.name} - {product.price}
535
+ </div>
536
+ ))}
537
+ </div>
538
+ );
539
+ }
540
+
541
+ export function Products() {
542
+ return (
543
+ <AthenaQueryClientProvider client={queryClient}>
544
+ <ProductsInner />
545
+ </AthenaQueryClientProvider>
546
+ );
547
+ }
548
+ ```
549
+
550
+ Available React APIs:
551
+
552
+ - `useAthenaGateway`: low-level gateway hook (`fetchGateway`, `insertGateway`, `updateGateway`, `deleteGateway`, `rpcGateway`) with request/response logging.
553
+ - `createAthenaQueryClient` + `AthenaQueryClientProvider`: Athena query runtime boundary for scoped state and subscriptions.
554
+ - `useQuery`: lightweight read lifecycle hook (`status`, `isFetching`, `refetch`, `reset`) with normalized Athena error/result handling.
555
+ - `useMutation`: lightweight write lifecycle hook (`mutate`, `mutateAsync`, `reset`) with manual refetch/invalidation flow.
556
+
557
+ By design this is not a cache-heavy React Query clone:
558
+
559
+ - No TanStack/React Query dependency.
560
+ - No persistent data cache by default (`cache.mode = "none"`).
561
+ - Inflight dedupe for identical query keys is enabled.
562
+ - Manual `refetch()` after mutations is the default invalidation strategy.
563
+
564
+ `test-sdk` includes runnable local examples for these hooks in
565
+ `test-sdk/examples/react-hooks`, where `queryFn`/`mutationFn` call Athena directly via `createClient(...).from(...).select()/insert()/eq()`.
566
+
567
+ `useAthenaGateway` example:
568
+
569
+ ```tsx
420
570
  import { useAthenaGateway } from "@xylex-group/athena/react";
421
571
  import { useEffect } from "react";
422
572
 
@@ -427,7 +577,7 @@ export function UsersPanel() {
427
577
  });
428
578
 
429
579
  useEffect(() => {
430
- fetchGateway({
580
+ void fetchGateway({
431
581
  table_name: "users",
432
582
  columns: ["id", "email"],
433
583
  limit: 25,
@@ -441,9 +591,7 @@ export function UsersPanel() {
441
591
  }
442
592
  ```
443
593
 
444
- The hook returns `fetchGateway`, `insertGateway`, `updateGateway`, `deleteGateway`, `rpcGateway`, `isLoading`, `error`, `lastRequest`, `lastResponse`, and `baseUrl`.
445
-
446
- Hook config options mirror the client options: `baseUrl`, `apiKey`, `headers`, `userId`, `organizationId`, `publishEvent`.
594
+ `useAthenaGateway` config options mirror the client options: `baseUrl`, `apiKey`, `headers`, `userId`, `organizationId`, `publishEvent`.
447
595
 
448
596
  ## User context headers
449
597
 
@@ -516,5 +664,8 @@ CI and publish workflows run `typecheck` before build/publish.
516
664
 
517
665
  ## Learn more
518
666
 
667
+ - [Documentation index](docs/index.md) — complete documentation map
519
668
  - [Getting started](docs/getting-started.md) — step-by-step walkthrough
669
+ - [Typed schema registry](docs/typed-schema-registry.md) — typed contracts and migration path
670
+ - [Generator config](docs/generator-config.md) — generator provider and output pipeline
520
671
  - [API reference](docs/api-reference.md) — complete method and type reference
package/bin/athena-js.js CHANGED
@@ -1,9 +1,8 @@
1
- #!/usr/bin/env node
2
-
3
- import('../dist/cli/index.js').then(({ runCLI }) => {
4
- runCLI(process.argv.slice(2))
5
- }).catch(err => {
6
- console.error('Failed to start athena-js CLI:', err)
7
- process.exit(1)
8
- })
1
+ #!/usr/bin/env node
9
2
 
3
+ import('../dist/cli/index.js')
4
+ .then(({ runCLI }) => runCLI(process.argv.slice(2)))
5
+ .catch(err => {
6
+ console.error('Failed to start athena-js CLI:', err)
7
+ process.exit(1)
8
+ })