prest-js-sdk 0.2.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/.changeset/config.json +11 -0
- package/.github/workflows/release.yml +42 -0
- package/CHANGELOG.md +9 -0
- package/CLAUDE.md +203 -0
- package/README.md +219 -0
- package/dist/index.d.ts +207 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +366 -0
- package/dist/index.js.map +1 -0
- package/examples/filters.mjs +131 -0
- package/package.json +20 -0
- package/src/index.ts +543 -0
- package/tsconfig.json +18 -0
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://unpkg.com/@changesets/config@3.0.0/schema.json",
|
|
3
|
+
"changelog": "@changesets/cli/changelog",
|
|
4
|
+
"commit": false,
|
|
5
|
+
"fixed": [],
|
|
6
|
+
"linked": [],
|
|
7
|
+
"access": "public",
|
|
8
|
+
"baseBranch": "master",
|
|
9
|
+
"updateInternalDependencies": "patch",
|
|
10
|
+
"ignore": []
|
|
11
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
name: Release
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches:
|
|
6
|
+
- master
|
|
7
|
+
|
|
8
|
+
concurrency: ${{ github.workflow }}-${{ github.ref }}
|
|
9
|
+
|
|
10
|
+
jobs:
|
|
11
|
+
release:
|
|
12
|
+
name: Create Release Pull Request or Publish
|
|
13
|
+
runs-on: ubuntu-latest
|
|
14
|
+
permissions:
|
|
15
|
+
contents: write
|
|
16
|
+
pull-requests: write
|
|
17
|
+
id-token: write
|
|
18
|
+
steps:
|
|
19
|
+
- name: Checkout
|
|
20
|
+
uses: actions/checkout@v4
|
|
21
|
+
with:
|
|
22
|
+
fetch-depth: 0
|
|
23
|
+
|
|
24
|
+
- name: Setup Node.js
|
|
25
|
+
uses: actions/setup-node@v4
|
|
26
|
+
with:
|
|
27
|
+
node-version: 20
|
|
28
|
+
|
|
29
|
+
- name: Install dependencies
|
|
30
|
+
run: npm ci
|
|
31
|
+
|
|
32
|
+
- name: Create Release Pull Request or Publish to npm
|
|
33
|
+
uses: changesets/action@v1
|
|
34
|
+
with:
|
|
35
|
+
publish: npm run release
|
|
36
|
+
version: npm run version-packages
|
|
37
|
+
commit: "chore(release): version packages"
|
|
38
|
+
title: "chore(release): version packages"
|
|
39
|
+
setupGitUser: true
|
|
40
|
+
env:
|
|
41
|
+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
42
|
+
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
|
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
# prest-js-sdk
|
|
2
|
+
|
|
3
|
+
## 0.2.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 6012e3a: Initial release. PrestClient with catalog, CRUD, stored-query, health, and login methods;
|
|
8
|
+
typed Filter / SelectOpts DSL that serializes to pREST's `?field=op.value` URL syntax;
|
|
9
|
+
fromKratosSession() factory for forward-compat with the planned Kratos auth integration.
|
package/CLAUDE.md
ADDED
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
# prest-js-sdk
|
|
2
|
+
|
|
3
|
+
TypeScript SDK for pREST (PostgreSQL REST API). Wraps pREST's HTTP routes with route-shaped methods and a typed filter DSL.
|
|
4
|
+
|
|
5
|
+
## Repository
|
|
6
|
+
|
|
7
|
+
- GitHub: https://github.com/yudaprama/prest-js-sdk (publish target)
|
|
8
|
+
- npm: https://www.npmjs.com/package/prest-js-sdk (publish target)
|
|
9
|
+
- Base branch: `master`
|
|
10
|
+
- Node: 20
|
|
11
|
+
|
|
12
|
+
## Sibling project
|
|
13
|
+
|
|
14
|
+
This SDK mirrors `alist-kratos-sdk/` (the AList SDK in the parent `ai-orchestration` workspace). Same shape:
|
|
15
|
+
|
|
16
|
+
- Single-file SDK at `src/index.ts` (~500 lines)
|
|
17
|
+
- tsc direct build, ESM-only
|
|
18
|
+
- Changesets + GitHub Actions release flow
|
|
19
|
+
- `fromKratosSession()` static factory (forward-compat — pREST submodule does not yet wire Kratos)
|
|
20
|
+
|
|
21
|
+
When adding capabilities here, check whether `alist-kratos-sdk` has an analogous pattern to follow.
|
|
22
|
+
|
|
23
|
+
## Project layout
|
|
24
|
+
|
|
25
|
+
```
|
|
26
|
+
src/ TypeScript source (entry: src/index.ts)
|
|
27
|
+
examples/ Standalone scripts (filter serializer test, smoke test)
|
|
28
|
+
dist/ Build output (gitignored)
|
|
29
|
+
.changeset/ Changeset markdown files (one per unreleased change)
|
|
30
|
+
.github/workflows/ GitHub Actions release.yml
|
|
31
|
+
.npmrc Auth for npm publish (uses NPM_TOKEN)
|
|
32
|
+
package.json Version, scripts, deps
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Build
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
npm ci # install (CI uses lockfile)
|
|
39
|
+
npm run build # tsc -> dist/
|
|
40
|
+
node examples/filters.ts # sanity check the filter serializer
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## pREST HTTP surface (the contract this SDK wraps)
|
|
44
|
+
|
|
45
|
+
The `prest/` submodule in the parent workspace is vanilla upstream pREST v2.0.0.
|
|
46
|
+
Routes (from `prest/router/router.go:17`):
|
|
47
|
+
|
|
48
|
+
| Method | Route | SDK method |
|
|
49
|
+
|---|---|---|
|
|
50
|
+
| GET | `/databases` | `databases()` |
|
|
51
|
+
| GET | `/schemas` | `schemas()` |
|
|
52
|
+
| GET | `/tables` | `tables()` |
|
|
53
|
+
| GET | `/{database}/{schema}` | `tablesIn(db, schema)` |
|
|
54
|
+
| GET | `/show/{database}/{schema}/{table}` | `showTable(db, schema, table)` |
|
|
55
|
+
| GET | `/{database}/{schema}/{table}` | `select(...)` |
|
|
56
|
+
| POST | `/{database}/{schema}/{table}` | `insert(...)` |
|
|
57
|
+
| POST | `/batch/{database}/{schema}/{table}` | `insertBatch(...)` |
|
|
58
|
+
| PUT, PATCH | `/{database}/{schema}/{table}` | `update(...)` |
|
|
59
|
+
| DELETE | `/{database}/{schema}/{table}` | `delete(...)` |
|
|
60
|
+
| ANY | `/_QUERIES/{location}/{script}` | `query(location, script, params)` |
|
|
61
|
+
| GET | `/_health` | `health()` |
|
|
62
|
+
| POST | `/auth` | `login(username, password)` |
|
|
63
|
+
|
|
64
|
+
**Filter URL syntax** (verified against `prest/adapters/postgres/postgres.go`):
|
|
65
|
+
- `?field=op.value` — operator-prefixed (op defaults to `eq` when bare)
|
|
66
|
+
- Multi-value ops: `?field=in.(1,2,3)` — parens, comma-separated
|
|
67
|
+
- Value-less ops: `?field=null`, `?field=true` — no trailing dot
|
|
68
|
+
- Reserved params (start with `_`): `_select`, `_order`, `_groupby`, `_join`, `_where`, `_count`, `_count_first`, `_limit`, `_offset`, `_page`, `_size`, `_distinct`, `_returning`, `_or`
|
|
69
|
+
|
|
70
|
+
Operators (`prest/adapters/postgres/postgres.go:1474` GetQueryOperator):
|
|
71
|
+
`eq, ne, gt, gte, lt, lte, in, nin, any, some, all, null, notnull, true, nottrue, false, notfalse, like, ilike, nlike, nilike` (+ ltree ops, not exposed in v0.1).
|
|
72
|
+
|
|
73
|
+
## Release workflow
|
|
74
|
+
|
|
75
|
+
Publishing is fully automated via **changesets** + GitHub Actions. **Do not run `npm version` or push tags manually.**
|
|
76
|
+
|
|
77
|
+
### How a release happens
|
|
78
|
+
|
|
79
|
+
1. A PR or direct push to `master` contains one or more `.changeset/<name>.md` files.
|
|
80
|
+
2. The `Release` workflow runs on push to `master`:
|
|
81
|
+
- **If a "Version Packages" PR is already open**, it updates that PR with the new changesets.
|
|
82
|
+
- **If no PR is open**, it creates one titled `chore(release): version packages`. This PR:
|
|
83
|
+
- bumps `version` in `package.json` (consuming the changesets)
|
|
84
|
+
- regenerates `CHANGELOG.md`
|
|
85
|
+
- deletes the consumed changeset files
|
|
86
|
+
3. When a maintainer **merges the "Version Packages" PR**:
|
|
87
|
+
- The same workflow runs on the merge commit
|
|
88
|
+
- There are no more pending changesets, so it skips versioning and runs `npm run release` (`tsc` + `changeset publish`)
|
|
89
|
+
- npm publish uses the `NPM_TOKEN` secret (granular token with **Bypass 2FA** enabled)
|
|
90
|
+
|
|
91
|
+
### Authoring a changeset
|
|
92
|
+
|
|
93
|
+
When a PR changes user-facing behavior, add a file under `.changeset/`:
|
|
94
|
+
|
|
95
|
+
```bash
|
|
96
|
+
npm run changeset
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
…or create the file manually:
|
|
100
|
+
|
|
101
|
+
```markdown
|
|
102
|
+
---
|
|
103
|
+
"prest-js-sdk": minor
|
|
104
|
+
---
|
|
105
|
+
|
|
106
|
+
Describe what changed and why. This text goes into CHANGELOG.md and the GitHub release notes.
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
Bump levels:
|
|
110
|
+
- `patch` — bug fix, internal refactor
|
|
111
|
+
- `minor` — new feature, backward-compatible
|
|
112
|
+
- `major` — breaking change
|
|
113
|
+
|
|
114
|
+
Skip a changeset for docs-only or CI-only changes.
|
|
115
|
+
|
|
116
|
+
### One-time setup (do this once when first publishing)
|
|
117
|
+
|
|
118
|
+
- `NPM_TOKEN` secret in repo Settings → Secrets → Actions (granular token, Bypass 2FA, Publish scope)
|
|
119
|
+
- Repo Settings → Actions → General → Workflow permissions: **Read and write permissions**, **Allow GitHub Actions to create and approve pull requests** enabled
|
|
120
|
+
- `NPM_TOKEN` env var is passed to the workflow and consumed by the repo's `.npmrc`
|
|
121
|
+
|
|
122
|
+
### Triggering the first publish
|
|
123
|
+
|
|
124
|
+
1. Ensure at least one `.changeset/*.md` exists on `master` (the `initial.md` is already there).
|
|
125
|
+
2. Push to `master` → workflow opens the "Version Packages" PR.
|
|
126
|
+
3. Review the PR (version bump, CHANGELOG entries look right).
|
|
127
|
+
4. Merge it → workflow publishes to npm.
|
|
128
|
+
|
|
129
|
+
### Manual override (emergency only)
|
|
130
|
+
|
|
131
|
+
```bash
|
|
132
|
+
npm login
|
|
133
|
+
npm run version-packages
|
|
134
|
+
npm run release
|
|
135
|
+
git push --follow-tags
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
**Do not do this routinely** — it bypasses the CHANGELOG PR review.
|
|
139
|
+
|
|
140
|
+
## Secrets & tokens
|
|
141
|
+
|
|
142
|
+
| Secret | Used by | Notes |
|
|
143
|
+
|---|---|---|
|
|
144
|
+
| `NPM_TOKEN` | Release workflow | Granular token, Bypass 2FA, Publish scope. Rotate if leaked. |
|
|
145
|
+
| `GITHUB_TOKEN` | Release workflow | Auto-provided by GitHub Actions; needs write perms + PR creation enabled. |
|
|
146
|
+
|
|
147
|
+
## Common tasks
|
|
148
|
+
|
|
149
|
+
| Task | Command |
|
|
150
|
+
|---|---|
|
|
151
|
+
| Add a changeset interactively | `npm run changeset` |
|
|
152
|
+
| Preview the next version locally | `npm run version-packages -- --snapshot` |
|
|
153
|
+
| Build types only | `npm run build` |
|
|
154
|
+
| Watch TS | `npm run dev` |
|
|
155
|
+
| Run filter test | `node examples/filters.ts` |
|
|
156
|
+
| Smoke test against running prest | `node examples/smoke.ts` (requires `planoctl up`) |
|
|
157
|
+
| Inspect a workflow run | `gh run list --repo yudaprama/prest-js-sdk --workflow=release.yml` |
|
|
158
|
+
| View logs | `gh run view <id> --repo yudaprama/prest-js-sdk --log` |
|
|
159
|
+
| Re-run a failed workflow | `gh run rerun <id> --repo yudaprama/prest-js-sdk` |
|
|
160
|
+
|
|
161
|
+
## Troubleshooting
|
|
162
|
+
|
|
163
|
+
- **`E404 Not Found` on publish** — `NPM_TOKEN` not reaching the publish step. Confirm `.npmrc` uses `//registry.npmjs.org/:_authToken=${NPM_TOKEN}` and the env var is set on the `changesets/action` step.
|
|
164
|
+
- **`E403 ... 2FA required`** — The token was generated without "Bypass 2FA". Regenerate a granular token with that option enabled.
|
|
165
|
+
- **`GitHub Actions is not permitted to create or approve pull requests`** — Repo Settings → Actions → General → Workflow permissions: enable "Allow GitHub Actions to create and approve pull requests".
|
|
166
|
+
- **No "Version Packages" PR appears** — No `.changeset/*.md` files exist on `master`. Add one and push.
|
|
167
|
+
- **`npm ci` fails: lockfile out of sync** — Run `npm install` locally, commit `package-lock.json`, push.
|
|
168
|
+
|
|
169
|
+
## SDK API quick reference
|
|
170
|
+
|
|
171
|
+
```ts
|
|
172
|
+
import { PrestClient } from "prest-js-sdk";
|
|
173
|
+
|
|
174
|
+
const client = new PrestClient("http://localhost:3000");
|
|
175
|
+
|
|
176
|
+
// Catalog
|
|
177
|
+
await client.databases();
|
|
178
|
+
await client.schemas();
|
|
179
|
+
await client.tables();
|
|
180
|
+
await client.tablesIn("yarsew", "public");
|
|
181
|
+
await client.showTable("yarsew", "public", "users");
|
|
182
|
+
|
|
183
|
+
// CRUD with typed filter
|
|
184
|
+
await client.select("yarsew", "public", "billing_balances", {
|
|
185
|
+
where: { actor_id: 42, status: { eq: "active" } },
|
|
186
|
+
select: ["id", "amount"],
|
|
187
|
+
order: ["amount:desc"],
|
|
188
|
+
page: 1, size: 20,
|
|
189
|
+
});
|
|
190
|
+
await client.insert("yarsew", "public", "billing_balances", { actor_id: 42, amount: 100 });
|
|
191
|
+
await client.insertBatch("yarsew", "public", "billing_balances", [/* ... */]);
|
|
192
|
+
await client.update("yarsew", "public", "billing_balances", { actor_id: 42 }, { status: "frozen" });
|
|
193
|
+
await client.delete("yarsew", "public", "billing_balances", { actor_id: 42 });
|
|
194
|
+
|
|
195
|
+
// Stored SQL
|
|
196
|
+
await client.query("reports", "top_balances", { min: 1000 });
|
|
197
|
+
|
|
198
|
+
// Health + auth
|
|
199
|
+
await client.health(); // → boolean
|
|
200
|
+
await client.login("alice", "hunter2"); // → JWT, also stores internally
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
See `README.md` for full type definitions and the auth flow.
|
package/README.md
ADDED
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
# prest-js-sdk
|
|
2
|
+
|
|
3
|
+
TypeScript SDK for [pREST](https://github.com/prest/prest) — PostgreSQL REST API. Route-shaped methods for catalog/CRUD/stored-queries, plus a typed filter DSL that serializes to pREST's `?field=op.value` URL syntax.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install prest-js-sdk
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Quick start
|
|
12
|
+
|
|
13
|
+
```ts
|
|
14
|
+
import { PrestClient } from "prest-js-sdk";
|
|
15
|
+
|
|
16
|
+
// Option 1: explicit URL (no auth — open prest)
|
|
17
|
+
const client = new PrestClient("http://localhost:3000");
|
|
18
|
+
|
|
19
|
+
// Option 2: URL + auth token (JWT from POST /auth, or Kratos session)
|
|
20
|
+
const client = new PrestClient({
|
|
21
|
+
prestUrl: "http://localhost:3000",
|
|
22
|
+
authToken: "eyJhbGci...",
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
// Option 3: auto-detect Kratos session (forward-compat — see Auth section)
|
|
26
|
+
const client = await PrestClient.fromKratosSession(
|
|
27
|
+
"http://localhost:4433", // Kratos public URL
|
|
28
|
+
"http://localhost:3000", // pREST URL (optional)
|
|
29
|
+
);
|
|
30
|
+
if (!client) throw new Error("No valid Kratos session");
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Catalog
|
|
34
|
+
|
|
35
|
+
```ts
|
|
36
|
+
await client.databases(); // GET /databases
|
|
37
|
+
await client.schemas(); // GET /schemas
|
|
38
|
+
await client.tables(); // GET /tables
|
|
39
|
+
await client.tablesIn("yarsew", "public"); // GET /yarsew/public
|
|
40
|
+
await client.showTable("yarsew", "public", "users"); // GET /show/yarsew/public/users
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## Select with typed filter
|
|
44
|
+
|
|
45
|
+
```ts
|
|
46
|
+
interface Balance { id: number; actor_id: number; amount: number; status: string }
|
|
47
|
+
|
|
48
|
+
const rows = await client.select<Balance>("yarsew", "public", "billing_balances", {
|
|
49
|
+
where: {
|
|
50
|
+
actor_id: 42, // shorthand → eq.42
|
|
51
|
+
status: { eq: "active" },
|
|
52
|
+
amount: { gte: 100 },
|
|
53
|
+
name: { like: "foo%" },
|
|
54
|
+
},
|
|
55
|
+
select: ["id", "amount", "status"],
|
|
56
|
+
order: ["amount:desc"],
|
|
57
|
+
page: 1,
|
|
58
|
+
size: 20,
|
|
59
|
+
});
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## Insert / Update / Delete
|
|
63
|
+
|
|
64
|
+
```ts
|
|
65
|
+
// Insert single row
|
|
66
|
+
const [created] = await client.insert<Balance>(
|
|
67
|
+
"yarsew", "public", "billing_balances",
|
|
68
|
+
{ actor_id: 42, amount: 99.95, status: "active" },
|
|
69
|
+
);
|
|
70
|
+
|
|
71
|
+
// Insert batch
|
|
72
|
+
await client.insertBatch("yarsew", "public", "billing_balances", [
|
|
73
|
+
{ actor_id: 1, amount: 10 },
|
|
74
|
+
{ actor_id: 2, amount: 20 },
|
|
75
|
+
]);
|
|
76
|
+
|
|
77
|
+
// Update — filter rows, then mutate
|
|
78
|
+
await client.update<Balance>(
|
|
79
|
+
"yarsew", "public", "billing_balances",
|
|
80
|
+
{ actor_id: 42 }, // WHERE
|
|
81
|
+
{ status: "frozen" }, // SET
|
|
82
|
+
);
|
|
83
|
+
|
|
84
|
+
// Delete — filter rows
|
|
85
|
+
await client.delete("yarsew", "public", "billing_balances", { actor_id: 42 });
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
## Stored SQL queries
|
|
89
|
+
|
|
90
|
+
SQL files live under `<prest queries dir>/<location>/<script>.sql` and are invoked by name:
|
|
91
|
+
|
|
92
|
+
```ts
|
|
93
|
+
// Calls /_QUERIES/reports/top_balances?min=1000
|
|
94
|
+
const top = await client.query<Balance>("reports", "top_balances", { min: 1000 });
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
Inside the `.sql` file, params are available via pREST's template helpers:
|
|
98
|
+
|
|
99
|
+
```sql
|
|
100
|
+
SELECT * FROM billing_balances
|
|
101
|
+
WHERE amount >= {{ sqlVal "min" }}
|
|
102
|
+
ORDER BY amount DESC
|
|
103
|
+
LIMIT 100
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
## Filter DSL reference
|
|
107
|
+
|
|
108
|
+
| JS shape | URL emitted | SQL |
|
|
109
|
+
|---|---|---|
|
|
110
|
+
| `{ field: 42 }` | `?field=eq.42` | `field = 42` |
|
|
111
|
+
| `{ field: "x" }` | `?field=eq.x` | `field = 'x'` |
|
|
112
|
+
| `{ field: { ne: 42 } }` | `?field=ne.42` | `field != 42` |
|
|
113
|
+
| `{ field: { gt: 100 } }` | `?field=gt.100` | `field > 100` |
|
|
114
|
+
| `{ field: { gte: 100 } }` | `?field=gte.100` | `field >= 100` |
|
|
115
|
+
| `{ field: { lt: 100 } }` | `?field=lt.100` | `field < 100` |
|
|
116
|
+
| `{ field: { lte: 100 } }` | `?field=lte.100` | `field <= 100` |
|
|
117
|
+
| `{ field: { in: [1,2,3] } }` | `?field=in.(1,2,3)` | `field IN (1,2,3)` |
|
|
118
|
+
| `{ field: { nin: [1,2] } }` | `?field=nin.(1,2)` | `field NOT IN (1,2)` |
|
|
119
|
+
| `{ field: { like: "foo%" } }` | `?field=like.foo%25` | `field LIKE 'foo%'` |
|
|
120
|
+
| `{ field: { ilike: "foo%" } }` | `?field=ilike.foo%25` | `field ILIKE 'foo%'` |
|
|
121
|
+
| `{ field: null }` | `?field=null` | `field IS NULL` |
|
|
122
|
+
| `{ field: { notnull: true } }` | `?field=notnull` | `field IS NOT NULL` |
|
|
123
|
+
| `{ field: true }` | `?field=true` | `field IS TRUE` |
|
|
124
|
+
|
|
125
|
+
Multiple fields are AND-ed. For OR across fields, use `SelectOpts.or`:
|
|
126
|
+
|
|
127
|
+
```ts
|
|
128
|
+
client.select(..., {
|
|
129
|
+
or: [
|
|
130
|
+
{ status: "active" },
|
|
131
|
+
{ amount: { gt: 1000 } },
|
|
132
|
+
],
|
|
133
|
+
});
|
|
134
|
+
// → ?_or=status=eq.active,amount=gt.1000
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
### `SelectOpts`
|
|
138
|
+
|
|
139
|
+
| Field | Maps to | Notes |
|
|
140
|
+
|---|---|---|
|
|
141
|
+
| `where` | `?field=op.value` | AND-ed; see table above |
|
|
142
|
+
| `select` | `_select=col1,col2` | column projection |
|
|
143
|
+
| `order` | `_order=col1,col2:desc` | suffix `:asc` / `:desc` per column |
|
|
144
|
+
| `page` + `size` | `_page`, `_size` | 1-indexed pagination |
|
|
145
|
+
| `limit` + `offset` | `_limit`, `_offset` | alternative pagination |
|
|
146
|
+
| `distinct` | `_distinct=true` | |
|
|
147
|
+
| `count` | `_count=true` | return row counts |
|
|
148
|
+
| `countFirst` | `_count_first=true` | single count object |
|
|
149
|
+
| `groupBy` | `_groupby=col1,col2` | |
|
|
150
|
+
| `or` | `_or=field=op.value,...` | OR-clause inside parens |
|
|
151
|
+
| `returning` | `_returning=col1,col2` | for update/delete |
|
|
152
|
+
|
|
153
|
+
## Auth
|
|
154
|
+
|
|
155
|
+
pREST supports two auth modes:
|
|
156
|
+
|
|
157
|
+
### 1. Database-backed (`[auth]` block in `prest.toml`)
|
|
158
|
+
|
|
159
|
+
```ts
|
|
160
|
+
const client = new PrestClient("http://localhost:3000");
|
|
161
|
+
const token = await client.login("alice", "hunter2");
|
|
162
|
+
// subsequent requests now carry Authorization: Bearer <token>
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
### 2. Kratos session (forward-compat)
|
|
166
|
+
|
|
167
|
+
The pREST submodule is vanilla upstream — it does not yet wire Ory Kratos. The
|
|
168
|
+
parent `ai-orchestration` project documents a planned `[auth.kratos]` integration;
|
|
169
|
+
once that lands, `PrestClient.fromKratosSession()` will work exactly like the
|
|
170
|
+
sibling `alist-kratos-sdk`:
|
|
171
|
+
|
|
172
|
+
```ts
|
|
173
|
+
const client = await PrestClient.fromKratosSession(
|
|
174
|
+
"http://localhost:4433",
|
|
175
|
+
"http://localhost:3000",
|
|
176
|
+
);
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
In Node (no cookie jar), pass the session token explicitly:
|
|
180
|
+
|
|
181
|
+
```ts
|
|
182
|
+
const client = await PrestClient.fromKratosSession(
|
|
183
|
+
"http://localhost:4433",
|
|
184
|
+
"http://localhost:3000",
|
|
185
|
+
process.env.KRATOS_SESSION_TOKEN,
|
|
186
|
+
);
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
## Low-level escape hatch
|
|
190
|
+
|
|
191
|
+
For endpoints not wrapped by the typed methods:
|
|
192
|
+
|
|
193
|
+
```ts
|
|
194
|
+
const res = await client.request<{ some: shape }>("GET", "/custom/path", {
|
|
195
|
+
params: new URLSearchParams({ foo: "bar" }),
|
|
196
|
+
headers: { "X-Custom": "value" },
|
|
197
|
+
});
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
Throws `PrestApiError` (with `.status` and `.body`) on non-2xx.
|
|
201
|
+
|
|
202
|
+
## Browser vs Node
|
|
203
|
+
|
|
204
|
+
The SDK works in both browsers and Node 18+:
|
|
205
|
+
|
|
206
|
+
- `fromKratosSession()` reads the `ory_kratos_session` cookie in browsers; in Node pass the token explicitly.
|
|
207
|
+
- `fetch` is available natively in both runtimes.
|
|
208
|
+
- No DOM dependencies (only used for cookie reading).
|
|
209
|
+
|
|
210
|
+
## Development
|
|
211
|
+
|
|
212
|
+
```bash
|
|
213
|
+
npm install
|
|
214
|
+
npm run build # tsc → dist/
|
|
215
|
+
npm run dev # tsc --watch
|
|
216
|
+
node examples/filters.ts # verify filter serializer
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
Releases are automated via changesets + GitHub Actions — see `CLAUDE.md` for the full flow.
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* prest-js-sdk
|
|
3
|
+
*
|
|
4
|
+
* TypeScript SDK for pREST (PostgreSQL REST API). Provides route-shaped methods
|
|
5
|
+
* for catalog/CRUD/stored-queries plus a typed filter DSL that serializes to
|
|
6
|
+
* pREST's `?field=op.value` URL syntax.
|
|
7
|
+
*
|
|
8
|
+
* Usage:
|
|
9
|
+
* import { PrestClient } from "prest-js-sdk";
|
|
10
|
+
*
|
|
11
|
+
* const client = new PrestClient("http://localhost:3000");
|
|
12
|
+
*
|
|
13
|
+
* // Catalog
|
|
14
|
+
* const dbs = await client.databases();
|
|
15
|
+
*
|
|
16
|
+
* // Query with typed filter
|
|
17
|
+
* const rows = await client.select<Balance>("yarsew", "public", "billing_balances", {
|
|
18
|
+
* where: { actor_id: { eq: 42 }, status: "active" },
|
|
19
|
+
* select: ["id", "balance"],
|
|
20
|
+
* order: ["balance:desc"],
|
|
21
|
+
* limit: 10,
|
|
22
|
+
* });
|
|
23
|
+
*
|
|
24
|
+
* // Insert
|
|
25
|
+
* const [row] = await client.insert<Balance>("yarsew", "public", "billing_balances", {
|
|
26
|
+
* actor_id: 42, balance: 100.50, status: "active",
|
|
27
|
+
* });
|
|
28
|
+
*
|
|
29
|
+
* // Update (filter + new values)
|
|
30
|
+
* const updated = await client.update<Balance>(
|
|
31
|
+
* "yarsew", "public", "billing_balances",
|
|
32
|
+
* { actor_id: 42 },
|
|
33
|
+
* { status: "frozen" },
|
|
34
|
+
* );
|
|
35
|
+
*
|
|
36
|
+
* // Delete
|
|
37
|
+
* await client.delete("yarsew", "public", "billing_balances", { actor_id: 42 });
|
|
38
|
+
*
|
|
39
|
+
* // Stored SQL (prest/etc/queries/reports/top_balances.sql)
|
|
40
|
+
* const top = await client.query<Balance>("reports", "top_balances", { min: 1000 });
|
|
41
|
+
*
|
|
42
|
+
* // Forward-compat Kratos auth (works once [auth.kratos] is wired into prest)
|
|
43
|
+
* const authed = await PrestClient.fromKratosSession(
|
|
44
|
+
* "http://localhost:4433",
|
|
45
|
+
* "http://localhost:3000",
|
|
46
|
+
* );
|
|
47
|
+
*/
|
|
48
|
+
export interface PrestConfig {
|
|
49
|
+
/** pREST base URL, e.g. http://localhost:3000 */
|
|
50
|
+
prestUrl: string;
|
|
51
|
+
/** Optional bearer token (JWT from POST /auth, or Kratos session for forward-compat) */
|
|
52
|
+
authToken?: string;
|
|
53
|
+
/** Optional: pre-built Authorization header value (skips "Bearer " prefix) */
|
|
54
|
+
rawAuthHeader?: string;
|
|
55
|
+
/** Optional: extra headers merged into every request */
|
|
56
|
+
headers?: Record<string, string>;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Filter operators supported by pREST's `?field=op.value` syntax.
|
|
60
|
+
* Mirrors `prest/adapters/postgres/postgres.go` GetQueryOperator (line ~1474).
|
|
61
|
+
*/
|
|
62
|
+
export type Operator = "eq" | "ne" | "gt" | "gte" | "lt" | "lte" | "in" | "nin" | "any" | "some" | "all" | "null" | "notnull" | "true" | "nottrue" | "false" | "notfalse" | "like" | "ilike" | "nlike" | "nilike";
|
|
63
|
+
export type OpValue = string | number | (string | number)[];
|
|
64
|
+
/**
|
|
65
|
+
* Filter on table columns.
|
|
66
|
+
*
|
|
67
|
+
* - `{ field: value }` — shorthand for `{ field: { eq: value } }`
|
|
68
|
+
* - `{ field: null }` — IS NULL
|
|
69
|
+
* - `{ field: { op: value } }` — operator form, e.g. `{ age: { gt: 30 } }`
|
|
70
|
+
*
|
|
71
|
+
* Multiple fields are AND-ed. For OR across fields, use `SelectOpts.or`.
|
|
72
|
+
*/
|
|
73
|
+
export type Filter = {
|
|
74
|
+
[field: string]: string | number | boolean | null | {
|
|
75
|
+
[op in Operator]?: OpValue;
|
|
76
|
+
};
|
|
77
|
+
};
|
|
78
|
+
export interface SelectOpts {
|
|
79
|
+
/** WHERE clause — AND-ed across fields */
|
|
80
|
+
where?: Filter;
|
|
81
|
+
/** Column selection — `_select=col1,col2` */
|
|
82
|
+
select?: string[];
|
|
83
|
+
/** Order — `_order=col1,col2:desc` */
|
|
84
|
+
order?: string[];
|
|
85
|
+
/** Page-based pagination (1-indexed) — `_page=N&_size=M` */
|
|
86
|
+
page?: number;
|
|
87
|
+
size?: number;
|
|
88
|
+
/** Offset-based pagination — `_limit=N&_offset=M` */
|
|
89
|
+
limit?: number;
|
|
90
|
+
offset?: number;
|
|
91
|
+
/** DISTINCT — `_distinct=true` */
|
|
92
|
+
distinct?: boolean;
|
|
93
|
+
/** COUNT rows instead of returning them — `_count=true` */
|
|
94
|
+
count?: boolean;
|
|
95
|
+
/** Return just the count as a single object — `_count_first=true` */
|
|
96
|
+
countFirst?: boolean;
|
|
97
|
+
/** GROUP BY columns — `_groupby=col1,col2` */
|
|
98
|
+
groupBy?: string[];
|
|
99
|
+
/** OR-clauses for the WHERE — joined with OR inside parens */
|
|
100
|
+
or?: Filter[];
|
|
101
|
+
/** Columns to return from update/delete — `_returning=col1,col2` */
|
|
102
|
+
returning?: string[];
|
|
103
|
+
}
|
|
104
|
+
export declare class PrestApiError extends Error {
|
|
105
|
+
readonly status: number;
|
|
106
|
+
readonly body?: unknown | undefined;
|
|
107
|
+
constructor(status: number, message: string, body?: unknown | undefined);
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Serialize a Filter into pREST URL query params.
|
|
111
|
+
*
|
|
112
|
+
* Each field becomes one or more entries: `field=op.value`. Multiple ops on the
|
|
113
|
+
* same field are emitted as repeated query params (pREST AND-s them).
|
|
114
|
+
*
|
|
115
|
+
* @example
|
|
116
|
+
* serializeFilter({ actor_id: 42 })
|
|
117
|
+
* // → "actor_id=eq.42"
|
|
118
|
+
* serializeFilter({ age: { gt: 30 }, name: { like: "foo%" } })
|
|
119
|
+
* // → "age=gt.30&name=like.foo%25"
|
|
120
|
+
* serializeFilter({ id: { in: [1, 2, 3] }, status: null })
|
|
121
|
+
* // → "id=in.(1,2,3)&status=null"
|
|
122
|
+
*/
|
|
123
|
+
export declare function serializeFilter(filter: Filter): URLSearchParams;
|
|
124
|
+
/**
|
|
125
|
+
* Serialize full SelectOpts into URLSearchParams (filter + modifiers).
|
|
126
|
+
*/
|
|
127
|
+
export declare function serializeSelectOpts(opts: SelectOpts): URLSearchParams;
|
|
128
|
+
export declare class PrestClient {
|
|
129
|
+
private readonly prestUrl;
|
|
130
|
+
private authHeader;
|
|
131
|
+
private readonly extraHeaders;
|
|
132
|
+
constructor(config: PrestConfig | string);
|
|
133
|
+
/**
|
|
134
|
+
* Forward-compat factory: validate a Kratos session and build a PrestClient.
|
|
135
|
+
*
|
|
136
|
+
* The vanilla pREST submodule does not yet wire Kratos (the planned
|
|
137
|
+
* `[auth.kratos]` middleware is documented in the parent project's CLAUDE.md
|
|
138
|
+
* but not merged into the submodule). This helper validates the session
|
|
139
|
+
* against Kratos and stores it as a Bearer header, so it will Just Work
|
|
140
|
+
* once the integration lands. Today it's useful if you're running a patched
|
|
141
|
+
* prest build with Kratos auth.
|
|
142
|
+
*
|
|
143
|
+
* @param kratosUrl - Kratos public URL, e.g. http://localhost:4433
|
|
144
|
+
* @param prestUrl - pREST base URL, defaults to http://localhost:3000
|
|
145
|
+
* @param sessionToken - Optional token. Reads `ory_kratos_session` cookie if omitted (browser).
|
|
146
|
+
* @returns PrestClient, or null if no valid session was found.
|
|
147
|
+
*/
|
|
148
|
+
static fromKratosSession(kratosUrl: string, prestUrl?: string, sessionToken?: string): Promise<PrestClient | null>;
|
|
149
|
+
/**
|
|
150
|
+
* Set or replace the auth token after construction.
|
|
151
|
+
* Useful after calling `login()`. Returns `this` for chaining.
|
|
152
|
+
*/
|
|
153
|
+
setAuthToken(token: string): this;
|
|
154
|
+
/**
|
|
155
|
+
* Escape hatch for endpoints not covered by the typed methods.
|
|
156
|
+
* Throws PrestApiError on non-2xx.
|
|
157
|
+
*/
|
|
158
|
+
request<T = unknown>(method: string, path: string, init?: {
|
|
159
|
+
body?: BodyInit | null;
|
|
160
|
+
headers?: Record<string, string>;
|
|
161
|
+
params?: URLSearchParams;
|
|
162
|
+
}): Promise<T>;
|
|
163
|
+
/** `GET /databases` — list database names. */
|
|
164
|
+
databases(): Promise<unknown>;
|
|
165
|
+
/** `GET /schemas` — list schema names. */
|
|
166
|
+
schemas(): Promise<unknown>;
|
|
167
|
+
/** `GET /tables` — list table names. */
|
|
168
|
+
tables(): Promise<unknown>;
|
|
169
|
+
/** `GET /{database}/{schema}` — list tables in a specific database/schema. */
|
|
170
|
+
tablesIn(database: string, schema: string): Promise<unknown>;
|
|
171
|
+
/** `GET /show/{database}/{schema}/{table}` — table description / columns. */
|
|
172
|
+
showTable(database: string, schema: string, table: string): Promise<Record<string, unknown>>;
|
|
173
|
+
/** `GET /{db}/{schema}/{table}` — SELECT with typed filter DSL. */
|
|
174
|
+
select<T = unknown>(database: string, schema: string, table: string, opts?: SelectOpts): Promise<T[]>;
|
|
175
|
+
/** `POST /{db}/{schema}/{table}` — insert a single row. */
|
|
176
|
+
insert<T = unknown>(database: string, schema: string, table: string, data: Record<string, unknown>): Promise<T[]>;
|
|
177
|
+
/** `POST /batch/{db}/{schema}/{table}` — insert multiple rows in one call. */
|
|
178
|
+
insertBatch<T = unknown>(database: string, schema: string, table: string, rows: Record<string, unknown>[]): Promise<T[]>;
|
|
179
|
+
/** `PUT /{db}/{schema}/{table}?filter` — update rows matching filter. */
|
|
180
|
+
update<T = unknown>(database: string, schema: string, table: string, where: Filter, data: Record<string, unknown>): Promise<T[]>;
|
|
181
|
+
/** `DELETE /{db}/{schema}/{table}?filter` — delete rows matching filter. */
|
|
182
|
+
delete<T = unknown>(database: string, schema: string, table: string, where: Filter): Promise<T[]>;
|
|
183
|
+
/**
|
|
184
|
+
* Execute a stored SQL script: `GET /_QUERIES/{location}/{script}`.
|
|
185
|
+
*
|
|
186
|
+
* Scripts live in `<prest queries dir>/<location>/<script>.sql` and can
|
|
187
|
+
* reference params as `{{ sqlVal "key" }}` / `{{ sqlList "key" }}` in
|
|
188
|
+
* pREST's template syntax.
|
|
189
|
+
*
|
|
190
|
+
* @param location - Folder name under prest's `queries` directory.
|
|
191
|
+
* @param script - `.sql` filename without the extension.
|
|
192
|
+
* @param params - Rendered as URL query params; accessible in the SQL template.
|
|
193
|
+
*/
|
|
194
|
+
query<T = unknown>(location: string, script: string, params?: Record<string, string | number | boolean>): Promise<T[]>;
|
|
195
|
+
/** `GET /_health` — returns true on 2xx, false otherwise. */
|
|
196
|
+
health(): Promise<boolean>;
|
|
197
|
+
/**
|
|
198
|
+
* `POST /auth` — exchange username/password for a JWT.
|
|
199
|
+
* Stores the returned token and attaches it as `Authorization: Bearer <token>`
|
|
200
|
+
* on subsequent requests.
|
|
201
|
+
*
|
|
202
|
+
* Only works when pREST's `[auth]` block is enabled in `prest.toml`.
|
|
203
|
+
* Returns the token string.
|
|
204
|
+
*/
|
|
205
|
+
login(username: string, password: string): Promise<string>;
|
|
206
|
+
}
|
|
207
|
+
//# sourceMappingURL=index.d.ts.map
|