@adrdp/pg-rest 0.1.3 → 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/README.md +89 -134
- package/dist/adapters/bun.d.ts +1 -1
- package/dist/adapters/node-postgres.d.ts +1 -1
- package/dist/adapters/pglite.d.ts +1 -1
- package/dist/adapters/postgres-js.d.ts +1 -1
- package/dist/index.d.ts +642 -130
- package/dist/index.js +380 -648
- package/dist/schema-BhF_TQdX.js +669 -0
- package/dist/types-DaNxiC7H.d.ts +146 -0
- package/dist/types.d.ts +2 -0
- package/dist/types.js +202 -0
- package/package.json +7 -1
- /package/dist/{adapter-SqPs6SuJ.d.ts → adapter-C2udR5IT.d.ts} +0 -0
package/README.md
CHANGED
|
@@ -1,22 +1,23 @@
|
|
|
1
1
|
# pg-rest
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
`handle(Request) => Promise<Response>`
|
|
3
|
+
**PostgREST v16, as a library.** The same URLs, the same negotiation, the same JSON, the same status codes and the same
|
|
4
|
+
`PGRSTxxx` error bodies — as a web-standard `handle(Request) => Promise<Response>` you mount in your own server.
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
Bun.sql, node-postgres, postgres.js and PGlite ship as separate entry points (`pg-rest/adapters/*`); the drivers are
|
|
10
|
-
_optional peer dependencies_, so you only install the one you use.
|
|
11
|
-
- **No JWT and no server.** You decide who the caller is (cookie, API key, session, mTLS…) and hand pg-rest a role, claims and
|
|
12
|
-
settings; they are applied per request with `set_config`, so Postgres Row Level Security sees them exactly as it would behind PostgREST.
|
|
6
|
+
Measured, not promised: every run replays **344 requests against the real PostgREST 16.5 binary** and compares status,
|
|
7
|
+
headers and body. One difference is accepted today, and asserted so it cannot quietly become two →
|
|
8
|
+
[`PARITY.md`](PARITY.md).
|
|
13
9
|
|
|
14
|
-
|
|
10
|
+
Being a library is the rest of it. **No JWT and no server:** you decide who the caller is — cookie, API key, session,
|
|
11
|
+
mTLS — and hand over a role and claims, applied with `set_config` so Row Level Security sees what it would behind
|
|
12
|
+
PostgREST. **No HTTP needed either:** the same queries run in the calling process, typed from your own schema. And no
|
|
13
|
+
runtime dependencies, nothing but web standards in the core, your driver staying yours.
|
|
15
14
|
|
|
16
15
|
```sh
|
|
17
|
-
npm i @adrdp/pg-rest pg
|
|
16
|
+
npm i @adrdp/pg-rest pg # or: bun add @adrdp/pg-rest (Bun.sql is built in), postgres, @electric-sql/pglite
|
|
18
17
|
```
|
|
19
18
|
|
|
19
|
+
## Over HTTP
|
|
20
|
+
|
|
20
21
|
```ts
|
|
21
22
|
import pg from "pg";
|
|
22
23
|
import { createPgRest } from "@adrdp/pg-rest";
|
|
@@ -26,160 +27,114 @@ const pool = new pg.Pool({ connectionString: process.env.DATABASE_URL });
|
|
|
26
27
|
|
|
27
28
|
const pgRest = await createPgRest({
|
|
28
29
|
db: nodePostgresAdapter(pool), // you create and close the driver
|
|
29
|
-
schemas: ["api"], // first one is the default schema
|
|
30
|
+
schemas: ["api"], // the first one is the default schema
|
|
30
31
|
anonymousRole: "anon",
|
|
31
32
|
resolveContext: async (req) => {
|
|
32
|
-
const user = await mySessionLookup(req);
|
|
33
|
-
return user ? { role: "authenticated", claims: { sub: user.id
|
|
33
|
+
const user = await mySessionLookup(req); // your auth, by any means
|
|
34
|
+
return user ? { role: "authenticated", claims: { sub: user.id } } : undefined;
|
|
34
35
|
},
|
|
35
36
|
});
|
|
36
37
|
|
|
37
38
|
// any host that speaks Request/Response:
|
|
38
|
-
//
|
|
39
|
-
|
|
40
|
-
// Hono: app.all("/api/*", (c) => pgRest.handle(c.req.raw))
|
|
41
|
-
```
|
|
42
|
-
|
|
43
|
-
## Examples
|
|
39
|
+
// Bun:
|
|
40
|
+
Bun.serve({ fetch: (req) => pgRest.handle(req) });
|
|
44
41
|
|
|
45
|
-
|
|
42
|
+
// Node:
|
|
43
|
+
serve({ fetch: (req) => pgRest.handle(req) }); // @hono/node-server
|
|
46
44
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
| [`with-bun`](examples/with-bun) | Bun | `Bun.sql` (built in) | yes (docker-compose included) |
|
|
50
|
-
| [`with-node-postgres`](examples/with-node-postgres) | Node.js ≥ 22.18 | `pg` | yes (docker-compose included) |
|
|
51
|
-
| [`with-postgres-js`](examples/with-postgres-js) | Node.js ≥ 22.18 | `postgres` | yes (docker-compose included) |
|
|
52
|
-
| [`with-pglite`](examples/with-pglite) | Node.js ≥ 22.18 | `@electric-sql/pglite` | no (Postgres in WebAssembly) |
|
|
53
|
-
| [`with-better-auth`](examples/with-better-auth) | Node.js ≥ 22.18 | `pg` + [Better Auth](https://better-auth.com) sessions | yes (docker-compose included) |
|
|
54
|
-
|
|
55
|
-
## Database adapters
|
|
56
|
-
|
|
57
|
-
`db` is a `DbAdapter` — the only thing pg-rest needs from a driver:
|
|
58
|
-
|
|
59
|
-
```ts
|
|
60
|
-
interface DbAdapter {
|
|
61
|
-
query(text: string, params?: (string | null)[]): Promise<Record<string, unknown>[]>;
|
|
62
|
-
transaction<A>(fn: (session: { query: DbAdapter["query"] }) => Promise<A>): Promise<A>; // one connection, BEGIN/COMMIT/ROLLBACK
|
|
63
|
-
close?(): Promise<void>;
|
|
64
|
-
}
|
|
45
|
+
// Hono:
|
|
46
|
+
app.all("/api/*", (c) => pgRest.handle(c.req.raw));
|
|
65
47
|
```
|
|
66
48
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
| `@adrdp/pg-rest/adapters/bun` → `bunSqlAdapter(sql)` | `new SQL(url)` from `"bun"` | none (built into Bun) |
|
|
70
|
-
| `@adrdp/pg-rest/adapters/node-postgres` → `nodePostgresAdapter(pool)` | `new pg.Pool(...)` | `pg` ≥ 8 |
|
|
71
|
-
| `@adrdp/pg-rest/adapters/postgres-js` → `postgresJsAdapter(sql)` | `postgres(url)` | `postgres` ≥ 3 |
|
|
72
|
-
| `@adrdp/pg-rest/adapters/pglite` → `pgliteAdapter(db)` | `new PGlite()` | `@electric-sql/pglite` |
|
|
73
|
-
|
|
74
|
-
The adapters are structural (they never import the driver; you pass the client in), so a project only loads the code it imports.
|
|
75
|
-
To support another driver, implement the interface above (about 20 lines) — the adapter sources in `src/adapters` are good templates.
|
|
76
|
-
|
|
77
|
-
Parameters are always text with an explicit `$n::text` cast in the SQL, so no type inference is needed. Database errors are recognised
|
|
78
|
-
by SQLSTATE (`code`, `errno` or `sqlState` on the thrown error), so adapters don't have to translate them. pg-rest never closes your
|
|
79
|
-
driver; `pgRest.close()` only stops the handler.
|
|
80
|
-
|
|
81
|
-
In SQL, policies read the same GUCs as with PostgREST:
|
|
82
|
-
|
|
83
|
-
```sql
|
|
84
|
-
CREATE POLICY own ON api.notes USING (owner = (current_setting('request.jwt.claims', true)::json ->> 'sub'));
|
|
49
|
+
```sh
|
|
50
|
+
curl 'localhost:3000/todos?select=id,title,tags(label)&done=is.false&order=priority.desc&limit=10'
|
|
85
51
|
```
|
|
86
52
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
[`
|
|
90
|
-
results. In a small 2-vCPU sandbox pg-rest on Bun was on par with or faster than PostgREST in every scenario, and slower on a few of the cheapest ones
|
|
91
|
-
when run on Node (it also uses more memory); the benchmark includes `bun build --compile`, `--bytecode` and `bun compile` variants; see [`bench/README.md`](bench/README.md) for the numbers, the caveats and how to run it on your own hardware.
|
|
92
|
-
|
|
93
|
-
### Which runtime?
|
|
53
|
+
Any PostgREST client works — the wire format is the whole contract. Including
|
|
54
|
+
[`@supabase/postgrest-js`](https://github.com/supabase/supabase-js/tree/master/packages/core/postgrest-js) and
|
|
55
|
+
[`@neondatabase/postgrest-js`](https://github.com/neondatabase/neon-js/tree/main/packages/postgrest-js).
|
|
94
56
|
|
|
95
|
-
|
|
57
|
+
## In the same process, with types from your schema
|
|
96
58
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
| Throughput | 1.2–1.5× on most reads, ~2× on writes | same as plain Bun (within noise) | 0.7–0.9× on the cheapest queries, ahead on heavier ones |
|
|
100
|
-
| Peak memory | ~80 MB | ~50 MB | ~125 MB |
|
|
101
|
-
| Setup | nothing extra | single executable; needs a recent (canary) Bun | Node (tested on 22) |
|
|
102
|
-
| Pick it when | you can run Bun (default choice) | memory or deployment size matters and you accept an experimental feature | Bun is not an option |
|
|
59
|
+
No HTTP, no loopback call: the same parser, the same SQL and the same `set_config` of the role and claims, so Row Level
|
|
60
|
+
Security applies as it would over the wire.
|
|
103
61
|
|
|
104
|
-
|
|
62
|
+
```ts
|
|
63
|
+
const { data } = await pgRest.query(
|
|
64
|
+
{
|
|
65
|
+
table: "todos",
|
|
66
|
+
select: ["id", "title", { tags: ["label"] }],
|
|
67
|
+
where: { done: false },
|
|
68
|
+
order: [{ column: "priority", desc: true }],
|
|
69
|
+
limit: 10,
|
|
70
|
+
},
|
|
71
|
+
{ role: "authenticated", claims: { sub: user.id } },
|
|
72
|
+
);
|
|
73
|
+
```
|
|
105
74
|
|
|
106
|
-
|
|
75
|
+
Errors throw a `QueryError` carrying the PostgREST status and body; `querySafe()` hands them back as `{ data, error }`
|
|
76
|
+
instead. → [`docs/query.md`](docs/query.md)
|
|
107
77
|
|
|
108
|
-
|
|
109
|
-
| --------------------- | --------------------------------------------------------------------------------------------------------- |
|
|
110
|
-
| `db` | a `DbAdapter` built from your driver (see [Database adapters](#database-adapters)) |
|
|
111
|
-
| `schemas` | exposed schemas; selected with `Accept-Profile` / `Content-Profile` |
|
|
112
|
-
| `anonymousRole` | role when no context is resolved (`PGRST302` if unset and none given); `false` refuses anonymous requests |
|
|
113
|
-
| `planEnabled` | allow `Accept: application/vnd.pgrst.plan` (EXPLAIN output); off by default, like `db-plan-enabled` |
|
|
114
|
-
| `resolveContext(req)` | return `{ role, claims, settings }`; throw `PgRestError` to reject (e.g. 401) |
|
|
115
|
-
| `handle(req, ctx?)` | an explicit context overrides `resolveContext` |
|
|
116
|
-
| `maxRows` | cap on rows returned |
|
|
117
|
-
| `basePath` | URL prefix to strip |
|
|
118
|
-
| `extraSearchPath` | extra schemas appended to `search_path` |
|
|
119
|
-
| `requestSettings` | set `request.method/path/headers/cookies` GUCs (default on) |
|
|
120
|
-
| `preRequest` | SQL function name or `(session, info) => void`, run inside the request transaction |
|
|
121
|
-
| `allowTxOverride` | honour `Prefer: tx=rollback` |
|
|
122
|
-
| `openApi` | serve the Swagger 2.0 document at `/` |
|
|
78
|
+
One command writes the types for your database, and one type argument puts them to work:
|
|
123
79
|
|
|
124
|
-
|
|
80
|
+
```ts
|
|
81
|
+
writeFileSync("src/db.types.ts", await pgRest.generateTypes({ name: "Database" }));
|
|
82
|
+
```
|
|
125
83
|
|
|
126
|
-
|
|
84
|
+
```ts
|
|
85
|
+
const pgRest = await createPgRest<Database>({ db: nodePostgresAdapter(pool), schemas: ["api"] });
|
|
127
86
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
87
|
+
const { data } = await pgRest.query({
|
|
88
|
+
table: "todos", // only a relation that exists
|
|
89
|
+
select: ["id", "title", { tags: ["label"] }], // only real columns and relationships
|
|
90
|
+
where: { done: false }, // only its own columns, with the right value type
|
|
91
|
+
});
|
|
92
|
+
// data: Array<{ id: number; title: string; tags: Array<{ label: string }> }>
|
|
93
|
+
```
|
|
135
94
|
|
|
136
|
-
|
|
95
|
+
They come from the catalog the library itself reads, so what they allow is what it accepts — down to your enums, and to
|
|
96
|
+
which embeds a foreign key actually permits. → [`docs/types.md`](docs/types.md)
|
|
137
97
|
|
|
138
|
-
|
|
98
|
+
## Documentation
|
|
139
99
|
|
|
140
|
-
|
|
100
|
+
| | |
|
|
101
|
+
| ---------------------------------------------- | ----------------------------------------------------------------- |
|
|
102
|
+
| [`docs/query.md`](docs/query.md) | `query()` and `querySafe()`, with an example for every field |
|
|
103
|
+
| [`docs/types.md`](docs/types.md) | generating types, the typed instance, and what it cannot describe |
|
|
104
|
+
| [`docs/options.md`](docs/options.md) | every `createPgRest` option, and what the instance gives back |
|
|
105
|
+
| [`docs/adapters.md`](docs/adapters.md) | `DbAdapter`, the four that ship, and writing your own |
|
|
106
|
+
| [`docs/design.md`](docs/design.md) | what it does, and what it will never do because it is a library |
|
|
107
|
+
| [`docs/architecture.md`](docs/architecture.md) | how a request becomes SQL, and where each piece lives |
|
|
108
|
+
| [`docs/security.md`](docs/security.md) | the role and RLS model, and how request values reach SQL |
|
|
109
|
+
| [`docs/performance.md`](docs/performance.md) | measured against PostgREST 16, and which runtime to pick |
|
|
110
|
+
| [`docs/development.md`](docs/development.md) | running the tests, the examples and the differential suite |
|
|
111
|
+
| [`PARITY.md`](PARITY.md) | PostgREST v16 feature by feature |
|
|
141
112
|
|
|
142
|
-
|
|
143
|
-
code is handled by _your_ application instead. These are not gaps: adding them would work against the goals of the project.
|
|
113
|
+
## Examples
|
|
144
114
|
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
| JWT validation, JWKS, `jwt-secret`, audience, role claim path | The point of the project is that **you** decide who the caller is, by any means: a session cookie, an API key, Better Auth, mTLS. Baking JWT in would force one scheme and add crypto and key-rotation code (and dependencies) to the library. | `resolveContext(req)` or `handle(req, ctx)` returns `{ role, claims, settings }`. pg-rest applies it with `set_config`, so RLS and `current_setting('request.jwt.claims')` work as usual. See `examples/with-better-auth`. |
|
|
148
|
-
| Standalone server, CLI, host/port, unix sockets, TLS, `log-level`, `server-timing` | `handle(Request) => Response` is web-standard and runs under `Bun.serve`, Hono (`@hono/node-server`), or any framework that gives you a `Request`... Owning the server would tie the library to one runtime and duplicate what your framework already does. | Mount `handle` in your own server. |
|
|
149
|
-
| Admin server (`/live`, `/ready`, `/metrics`) | Health checks and metrics belong to the host process. | Expose them in your app. |
|
|
150
|
-
| CORS headers | Policy depends on your deployment. | Set them in your server or middleware. |
|
|
151
|
-
| Config file, environment variables, `PGRST_*` settings | Configuration is the `createPgRest(options)` call, which is typed and lives with the code. | Pass options; read env vars yourself. |
|
|
152
|
-
| Connection pool and `db-uri` | The pool belongs to your driver, and the driver is your choice (`DbAdapter`). pg-rest never opens connections. | Create the pool, wrap it in an adapter. |
|
|
153
|
-
| In-database configuration (`db-pre-config`, per-role `ALTER ROLE ... SET pgrst.*`) | Settings are options in code, not rows in the database, so they are versioned and visible with the application. | Use the options; `preRequest` can still call a SQL function if you want database-side logic. |
|
|
154
|
-
| Schema cache refresh over SQL: `NOTIFY pgrst, 'reload schema'` / `db-channel`, `SIGUSR1`/`SIGUSR2`, DDL event triggers | They require a dedicated `LISTEN` connection and signal handling, which a library must not hold open or assume (serverless, PGlite and pooled connections can't `LISTEN`). The introspection itself (`pg_catalog`) is implemented. | Call `reloadSchema()` after migrations, or wire it to your own `LISTEN`. A failed reload keeps the old cache. |
|
|
115
|
+
Self-contained projects in [`examples/`](examples) — a todo API with Row Level Security, one per driver, each with its
|
|
116
|
+
own README, schema and `server.ts`:
|
|
155
117
|
|
|
156
|
-
|
|
118
|
+
| example | runtime | driver | needs a database server |
|
|
119
|
+
| ------------------------------------------------------- | --------------- | --------------------------------------------- | ----------------------------- |
|
|
120
|
+
| [`with-bun`](examples/with-bun) | Bun | `Bun.sql` (built in) | yes (docker-compose included) |
|
|
121
|
+
| [`with-node-postgres`](examples/with-node-postgres) | Node.js ≥ 22.18 | `pg` | yes (docker-compose included) |
|
|
122
|
+
| [`with-postgres-js`](examples/with-postgres-js) | Node.js ≥ 22.18 | `postgres` | yes (docker-compose included) |
|
|
123
|
+
| [`with-pglite`](examples/with-pglite) | Node.js ≥ 22.18 | `@electric-sql/pglite` | no (Postgres in WebAssembly) |
|
|
124
|
+
| [`with-better-auth`](examples/with-better-auth) | Node.js ≥ 22.18 | `pg` + [Better Auth](https://better-auth.com) | yes (docker-compose included) |
|
|
125
|
+
| [`with-generated-types`](examples/with-generated-types) | Bun | `@electric-sql/pglite` | no — and no server either |
|
|
157
126
|
|
|
158
|
-
|
|
159
|
-
|
|
127
|
+
[`with-generated-types`](examples/with-generated-types) is the shortest way to see the typed path: two tables, an enum,
|
|
128
|
+
`bun run gen:types && bun start`.
|
|
160
129
|
|
|
161
130
|
## Development
|
|
162
131
|
|
|
163
|
-
`mise.toml` pins the PostgREST binary the differential test needs and runs a throwaway Postgres 16 **with PostGIS**
|
|
164
|
-
(required: the `application/geo+json` cases fail without it). Nothing is installed system-wide.
|
|
165
|
-
|
|
166
132
|
```sh
|
|
167
133
|
bun install
|
|
168
|
-
mise install # PostgREST 16.5, the
|
|
169
|
-
mise run db:start # Postgres 16 + PostGIS under .cache/
|
|
170
|
-
mise run test # the whole suite
|
|
171
|
-
```
|
|
172
|
-
|
|
173
|
-
Or bring your own Postgres 16 and PostgREST:
|
|
174
|
-
|
|
175
|
-
```sh
|
|
176
|
-
TEST_DATABASE_URL=postgres://user@host:5432/postgres bun test # a server you can create databases on
|
|
177
|
-
POSTGREST_BIN=/path/to/postgrest TEST_DATABASE_URL=... bun run test:compare # see PARITY.md
|
|
178
|
-
bun run typecheck && bun run build
|
|
179
|
-
|
|
180
|
-
# run an example end to end; the second argument is a database URL with the schema loaded
|
|
181
|
-
scripts/check-example.sh with-node-postgres postgres://user@host:5432/mydb
|
|
134
|
+
mise install # PostgREST 16.5, for the differential test
|
|
135
|
+
mise run db:start # a throwaway Postgres 16 + PostGIS under .cache/
|
|
136
|
+
mise run test # the whole suite
|
|
182
137
|
```
|
|
183
138
|
|
|
184
|
-
|
|
185
|
-
|
|
139
|
+
→ [`docs/development.md`](docs/development.md) for bringing your own Postgres, running one example end to end, and what
|
|
140
|
+
each suite covers.
|
package/dist/adapters/bun.d.ts
CHANGED