feat: one install and one import per database; server dialects open their pool from a URL; nodeListener answers a throwing context; 0.5.0

This commit is contained in:
2026-11-03 13:06:04 +01:00
parent 1391c21c1f
commit e7637c3704
24 changed files with 194 additions and 227 deletions
+4 -6
View File
@@ -3,12 +3,10 @@
The core answers a plain request object, so it fits any framework:
```ts
import pg from 'pg';
import { createREST } from '@crudy/restloom';
import { postgres } from '@crudy/restloom-postgres';
import { createREST, postgres } from '@crudy/restloom-postgres';
const rest = createREST({
db: postgres(new pg.Pool({ connectionString: process.env.DATABASE_URL })),
db: postgres(process.env.DATABASE_URL),
expose: { schemas: ['app'] },
});
@@ -22,11 +20,11 @@ The examples below use this `rest`.
## Node, without a framework
`nodeListener` from the core serves `node:http` with nothing else installed. It mounts the API under `prefix` and answers `404 not_found` outside it, reads JSON bodies up to 1 MiB (`maxBodyBytes`), and builds each request's context with `context`:
`nodeListener` from the core serves `node:http` with nothing else installed. It mounts the API under `prefix` and answers `404 not_found` outside it, reads JSON bodies up to 1 MiB (`maxBodyBytes`), and builds each request's context with `context`. A `context` that throws a `RestError` refuses the request with it; any other error answers `500 internal`:
```ts
import http from 'node:http';
import { nodeListener } from '@crudy/restloom';
import { nodeListener } from '@crudy/restloom-postgres';
const api = nodeListener(rest, { prefix: '/api', context: (req) => userFrom(req) });
http.createServer(api).listen(3000);
+10 -8
View File
@@ -9,15 +9,16 @@ A dialect is the package that knows one database: how to read its catalog, how t
| `@crudy/restloom-mssql` | SQL Server 2022+ | `JSON_OBJECT`, `STRING_AGG` | `OUTPUT` | no | security policies | scalar and table functions |
| `@crudy/restloom-sqlite` | SQLite 3.44+ (better-sqlite3) | `json_object`, `json_group_array` | `RETURNING` | no | no | none |
The PostgreSQL, MySQL and SQL Server packages bring their driver and open their own connection from a URL. To tune it, pass the driver's pool options instead, or a pool you already have: a `pg` Pool, a `mysql2/promise` Pool or a connected `mssql` ConnectionPool. The SQLite package takes a database object, so it runs on `better-sqlite3` in Node or on sql.js in a browser; install the one you use.
Every dialect passes the same conformance suite, so a request answers the same rows on each. Regular expressions (`match`, `imatch`) need Postgres or MySQL, and array filters (`ov`, `cs`, `cd`) Postgres array columns; elsewhere they answer `unsupported`. Where else the dialects differ, the sections below say how.
## Postgres
```ts
import pg from 'pg';
import { postgres } from '@crudy/restloom-postgres';
const db = postgres(new pg.Pool({ connectionString: process.env.DATABASE_URL }));
const db = postgres(process.env.DATABASE_URL);
```
- **Transactions:** every request is one transaction, `READ ONLY` for reads, with `SET LOCAL statement_timeout` from `limits.timeoutMs`.
@@ -28,10 +29,9 @@ const db = postgres(new pg.Pool({ connectionString: process.env.DATABASE_URL }))
## MySQL
```ts
import { createPool } from 'mysql2/promise';
import { mysql } from '@crudy/restloom-mysql';
const db = mysql(createPool({ uri: process.env.DATABASE_URL }));
const db = mysql(process.env.DATABASE_URL);
```
- **Schemas:** MySQL's schemas are its databases: `expose: { schemas: ['shop'] }`.
@@ -44,10 +44,9 @@ const db = mysql(createPool({ uri: process.env.DATABASE_URL }));
## SQL Server
```ts
import sql from 'mssql';
import { mssql } from '@crudy/restloom-mssql';
const db = mssql(await new sql.ConnectionPool(process.env.DATABASE_URL).connect());
const db = mssql(process.env.DATABASE_URL);
```
- **Version:** SQL Server 2022 or later, for `JSON_OBJECT`.
@@ -59,6 +58,10 @@ const db = mssql(await new sql.ConnectionPool(process.env.DATABASE_URL).connect(
## SQLite
```sh
npm install @crudy/restloom-sqlite better-sqlite3
```
```ts
import Database from 'better-sqlite3';
import { sqlite } from '@crudy/restloom-sqlite';
@@ -77,7 +80,6 @@ const db = sqlite(new Database('app.db'));
Every piece of SQL comes from one overridable method of the dialect's renderer. To change one, subclass it and pass it in:
```ts
import pg from 'pg';
import { PostgresRenderer, postgres } from '@crudy/restloom-postgres';
class Renderer extends PostgresRenderer {
@@ -86,7 +88,7 @@ class Renderer extends PostgresRenderer {
}
}
const db = postgres(new pg.Pool({ connectionString: process.env.DATABASE_URL }), { renderer: new Renderer() });
const db = postgres(process.env.DATABASE_URL, { renderer: new Renderer() });
```
The pieces are listed in [writing a dialect](./writing-a-dialect.md).
+12 -12
View File
@@ -2,29 +2,29 @@
## Install
Install the core, the package for your database, and its driver:
Install the package for your database. It brings the core and the database driver with it, except for SQLite, which also needs `better-sqlite3`:
```sh
npm install @crudy/restloom @crudy/restloom-postgres pg
npm install @crudy/restloom-postgres
```
| Database | Package | Driver |
|---|---|---|
| PostgreSQL 13+ | `@crudy/restloom-postgres` | `pg` |
| MySQL 8.0+ | `@crudy/restloom-mysql` | `mysql2` |
| SQL Server 2022+ | `@crudy/restloom-mssql` | `mssql` |
| SQLite 3.44+ | `@crudy/restloom-sqlite` | `better-sqlite3` |
| Database | Package |
|---|---|
| PostgreSQL 13+ | `@crudy/restloom-postgres` |
| MySQL 8.0+ | `@crudy/restloom-mysql` |
| SQL Server 2022+ | `@crudy/restloom-mssql` |
| SQLite 3.44+ | `@crudy/restloom-sqlite` |
Each of them exports the whole core, so everything is imported from that one package. The examples in these pages use `@crudy/restloom-postgres`; with another database, import from its package instead.
## Serve a schema
```ts
import http from 'node:http';
import pg from 'pg';
import { createREST, nodeListener } from '@crudy/restloom';
import { postgres } from '@crudy/restloom-postgres';
import { createREST, nodeListener, postgres } from '@crudy/restloom-postgres';
const rest = createREST({
db: postgres(new pg.Pool({ connectionString: process.env.DATABASE_URL })),
db: postgres(process.env.DATABASE_URL),
expose: { schemas: ['app'] },
});
+2 -2
View File
@@ -52,7 +52,7 @@ resources: {
`sensitive` replaces the whole pattern, for every resource. Start from `DEFAULT_SENSITIVE` to add words rather than lose the defaults:
```ts
import { createREST, DEFAULT_SENSITIVE } from '@crudy/restloom';
import { createREST, DEFAULT_SENSITIVE } from '@crudy/restloom-postgres';
createREST({
db, // from a database package, see Dialects
@@ -64,7 +64,7 @@ createREST({
## Hooks
```ts
import { createREST, RestError } from '@crudy/restloom';
import { createREST, RestError } from '@crudy/restloom-postgres';
createREST({
db, // from a database package, see Dialects
+1 -1
View File
@@ -28,7 +28,7 @@ procedures: {
For an API that writes everywhere, set `write` once, beside `expose`:
```ts
import { createREST } from '@crudy/restloom';
import { createREST } from '@crudy/restloom-postgres';
createREST({
db, // from a database package, see Dialects