@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 +169 -18
- package/bin/athena-js.js +7 -8
- package/dist/cli/index.cjs +2472 -0
- package/dist/cli/index.cjs.map +1 -0
- package/dist/cli/index.d.cts +6 -0
- package/dist/cli/index.d.ts +6 -0
- package/dist/cli/index.js +2470 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/index.cjs +1524 -15
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +484 -1
- package/dist/index.d.ts +484 -1
- package/dist/index.js +1506 -16
- package/dist/index.js.map +1 -1
- package/dist/react.cjs +965 -0
- package/dist/react.cjs.map +1 -1
- package/dist/react.d.cts +232 -1
- package/dist/react.d.ts +232 -1
- package/dist/react.js +960 -2
- package/dist/react.js.map +1 -1
- package/package.json +10 -3
package/README.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# athena-js
|
|
2
2
|
|
|
3
|
-
current version: `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
|
|
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 `
|
|
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
|
|
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
|
-
|
|
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
|
+
})
|