Compare commits

..
10 Commits
31 changed files with 445 additions and 228 deletions
+55
View File
@@ -0,0 +1,55 @@
# Stages every @crudy/restloom* package to npmjs on version tags
# (v<major>.<minor>.<patch>) through npm trusted publishing: GitHub's OIDC
# token stands in for an npm token, and npm attaches a provenance statement
# naming this repository and commit. A staged version goes live once a
# maintainer approves it with 2FA (npm stage approve, or npmjs.com). The
# organisation only allows its own actions, so checkout and Node setup are
# plain shell. The core goes first: every other package depends on it.
name: publish
on:
push:
tags: ['v*']
permissions:
contents: read
id-token: write
jobs:
publish:
runs-on: ubuntu-latest
env:
NODE_VERSION: v24.12.0
steps:
- name: Check out the tag
run: |
case "$GITHUB_REF_NAME" in v[0-9]*.[0-9]*.[0-9]*) ;; *) echo "not a version tag: $GITHUB_REF_NAME" >&2; exit 1 ;; esac
git init -q .
git fetch -q --depth 1 "https://github.com/$GITHUB_REPOSITORY" "refs/tags/$GITHUB_REF_NAME:refs/tags/$GITHUB_REF_NAME"
git checkout -q "refs/tags/$GITHUB_REF_NAME"
- name: Install Node
run: |
base="https://nodejs.org/dist/$NODE_VERSION"
file="node-$NODE_VERSION-linux-x64.tar.xz"
curl -fsSLO "$base/$file"
curl -fsSL "$base/SHASUMS256.txt" | grep " $file\$" | sha256sum -c -
mkdir -p "$RUNNER_TEMP/node"
tar -xJf "$file" -C "$RUNNER_TEMP/node" --strip-components=1
rm "$file"
echo "$RUNNER_TEMP/node/bin" >> "$GITHUB_PATH"
- name: Install npm with staged publishing
run: npm install -g npm@11.21.1
- run: npm ci --no-audit --no-fund
- run: npm test
- name: Publish
run: |
for dir in core postgres mysql mssql sqlite fastify express; do
NAME=$(node -p "require('./packages/$dir/package.json').name")
VERSION=$(node -p "require('./packages/$dir/package.json').version")
[ "v$VERSION" = "$GITHUB_REF_NAME" ] || { echo "tag $GITHUB_REF_NAME does not match $NAME $VERSION" >&2; exit 1; }
if npm view "$NAME@$VERSION" version >/dev/null 2>&1; then
echo "$NAME@$VERSION is already published"
else
(cd "packages/$dir" && npm stage publish)
fi
done
+18
View File
@@ -6,6 +6,24 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
## [Unreleased] ## [Unreleased]
## [0.5.0] - 2026-11-03
### Added
- Every database package exports the whole core, so one install and one import are enough: `import { createREST, nodeListener, postgres } from '@crudy/restloom-postgres'`.
- `postgres`, `mysql` and `mssql` take a connection URL or the driver's pool options and open the pool themselves; a pool you already have still works.
### Changed
- `pg`, `mysql2` and `mssql` are dependencies of their packages again, installed with them. `better-sqlite3` stays an optional peer of the SQLite package, which also runs on sql.js in a browser.
### Fixed
- A `nodeListener` context that throws answers the request, with the `RestError` it threw or `500 internal`, instead of stopping the process.
## [0.4.0] - 2026-11-03
### Added
- A top-level `write` option turns writes on for every exposed table at once: `write: true`, or `{ create, update, delete, bulk }`. A resource's own `write` replaces it, and views stay read-only unless their resource allows writes.
- Every write example in the docs states its status, and the security page describes sensitive columns and how to serve one.
## [0.3.1] - 2026-11-03 ## [0.3.1] - 2026-11-03
### Changed ### Changed
+5 -5
View File
@@ -37,19 +37,19 @@ Your backend stays in charge:
| `@crudy/restloom-express` | Express handler | | `@crudy/restloom-express` | Express handler |
```sh ```sh
npm install @crudy/restloom @crudy/restloom-postgres pg npm install @crudy/restloom-postgres
``` ```
A database package brings the core and its driver, and exports the whole core.
## Use ## Use
```ts ```ts
import http from 'node:http'; import http from 'node:http';
import pg from 'pg'; import { createREST, nodeListener, postgres } from '@crudy/restloom-postgres';
import { createREST, nodeListener } from '@crudy/restloom';
import { postgres } from '@crudy/restloom-postgres';
const rest = createREST({ const rest = createREST({
db: postgres(new pg.Pool()), db: postgres(process.env.DATABASE_URL),
expose: { schemas: ['app'] }, expose: { schemas: ['app'] },
resources: { todos: { write: true } }, resources: { todos: { write: true } },
}); });
+27
View File
@@ -0,0 +1,27 @@
import { test } from 'node:test';
import assert from 'node:assert/strict';
import * as pg from '../packages/postgres/dist/index.js';
import * as my from '../packages/mysql/dist/index.js';
import * as ms from '../packages/mssql/dist/index.js';
import * as lite from '../packages/sqlite/dist/index.js';
// One install and one import: each database package brings the core with it,
// and the server databases open their own connection from a URL.
test('every database package exports the core', () => {
for (const mod of [pg, my, ms, lite]) {
assert.equal(typeof mod.createREST, 'function');
assert.equal(typeof mod.nodeListener, 'function');
assert.equal(typeof mod.RestError, 'function');
}
});
test('postgres and mysql open a pool from a URL without connecting yet', async () => {
assert.equal(pg.postgres('postgres://user:secret@127.0.0.1:1/app').name, 'postgres');
assert.equal(my.mysql('mysql://user:secret@127.0.0.1:1/app').name, 'mysql');
});
test('postgres serves from a URL', { skip: !process.env.RESTLOOM_PG_URL && 'RESTLOOM_PG_URL is not set' }, async () => {
const db = pg.postgres(process.env.RESTLOOM_PG_URL);
const rows = await db.transaction((tx) => tx.query('SELECT 1 AS one', []), { timeoutMs: 5000, readOnly: true });
assert.deepEqual(rows, [{ one: 1 }]);
});
+3 -3
View File
@@ -9,9 +9,9 @@ import { sqlite } from '../packages/sqlite/dist/index.js';
// The table in docs/dialects.md is what readers compare databases by, on the // The table in docs/dialects.md is what readers compare databases by, on the
// site too. It must say what each dialect's capabilities say. // site too. It must say what each dialect's capabilities say.
const DIALECTS = { const DIALECTS = {
'@crudy/restloom-postgres': postgres({}), '@crudy/restloom-postgres': postgres({ connect() {} }),
'@crudy/restloom-mysql': mysql({}), '@crudy/restloom-mysql': mysql({ getConnection() {} }),
'@crudy/restloom-mssql': mssql({}), '@crudy/restloom-mssql': mssql({ request() {} }),
'@crudy/restloom-sqlite': sqlite({ pragma() {} }), '@crudy/restloom-sqlite': sqlite({ pragma() {} }),
}; };
+1 -1
View File
@@ -1,5 +1,5 @@
/** The docs pages whose examples are conformance cases, and the options every database runs them with. */ /** The docs pages whose examples are conformance cases, and the options every database runs them with. */
export const PAGES = ['query-grammar.md', 'writes.md']; export const PAGES = ['query-grammar.md', 'writes.md', 'security.md'];
const PROCEDURES = { const PROCEDURES = {
'todos/{todo}/complete': { function: 'demo.complete_todo' }, 'todos/{todo}/complete': { function: 'demo.complete_todo' },
+59 -3
View File
@@ -3,20 +3,43 @@
The core answers a plain request object, so it fits any framework: The core answers a plain request object, so it fits any framework:
```ts ```ts
import { createREST, postgres } from '@crudy/restloom-postgres';
const rest = createREST({
db: postgres(process.env.DATABASE_URL),
expose: { schemas: ['app'] },
});
const response = await rest.handle({ method, path, query, headers, body, context }); const response = await rest.handle({ method, path, query, headers, body, context });
// { status, headers, body } // { status, headers, body }
``` ```
The examples below use this `rest`.
`handle` never throws: every failure is a response. `path` is the path below where the API is mounted (`/todos/4`), `query` a `URLSearchParams`, `body` the parsed JSON. `handle` never throws: every failure is a response. `path` is the path below where the API is mounted (`/todos/4`), `query` a `URLSearchParams`, `body` the parsed JSON.
## Node ## Node, without a framework
`nodeListener` from the core serves `node:http` with no other dependency. It reads JSON bodies up to 1 MiB (`maxBodyBytes`). `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 ```ts
http.createServer(nodeListener(rest, { prefix: '/api', context: (req) => userFrom(req) })).listen(3000); import http from 'node:http';
import { nodeListener } from '@crudy/restloom-postgres';
const api = nodeListener(rest, { prefix: '/api', context: (req) => userFrom(req) });
http.createServer(api).listen(3000);
``` ```
To serve other pages from the same server, hand only the API's paths to it:
```ts
import http from 'node:http';
http.createServer((req, res) => (req.url?.startsWith('/api/') ? api(req, res) : site(req, res))).listen(3000);
```
Without HTTP at all, call `rest.handle` directly: [From your own code](#from-your-own-code).
## Fastify ## Fastify
```sh ```sh
@@ -24,8 +47,10 @@ npm install @crudy/restloom-fastify
``` ```
```ts ```ts
import Fastify from 'fastify';
import { restloom } from '@crudy/restloom-fastify'; import { restloom } from '@crudy/restloom-fastify';
const app = Fastify();
await app.register(restloom(rest, { context: (req) => req.user }), { prefix: '/api' }); await app.register(restloom(rest, { context: (req) => req.user }), { prefix: '/api' });
``` ```
@@ -38,11 +63,42 @@ npm install @crudy/restloom-express
``` ```
```ts ```ts
import express from 'express';
import { restloom } from '@crudy/restloom-express'; import { restloom } from '@crudy/restloom-express';
const app = express();
app.use('/api', express.json(), restloom(rest, { context: (req) => req.user })); app.use('/api', express.json(), restloom(rest, { context: (req) => req.user }));
``` ```
## From your own code
Your backend never needs to call its own API over HTTP. `rest.handle` runs a request in the same process, through the same checks, hooks and row security as a client's, and gives the answer back as data:
```ts
const { status, body: todos } = await rest.handle({
method: 'GET',
path: '/todos',
query: new URLSearchParams('select=id,name,owner(name)&status=eq.open'),
headers: {},
context: currentUser,
});
```
A write passes its body as an object, and header names in lower case:
```ts
const { status, body } = await rest.handle({
method: 'POST',
path: '/tags',
query: new URLSearchParams('select=id,name'),
headers: { prefer: 'return=representation' },
body: { id: 8, name: 'garden' },
context: currentUser,
});
```
A call from your code is not trusted more than a client's: `authorize` and `scope` receive its `context` like any other. Pass the user you act for, or a context your hooks recognise as a background job. Check `status` rather than catching: `handle` never throws, and an error answers its code in `body.error`.
## Your own routes ## Your own routes
A host that registers every route itself, to attach its own permissions or documentation to each, calls `rest.handle` from those routes. That is how crudy mounts restloom: each declared route checks its permission, then hands the request to restloom. A host that registers every route itself, to attach its own permissions or documentation to each, calls `rest.handle` from those routes. That is how crudy mounts restloom: each declared route checks its permission, then hands the request to restloom.
+10 -6
View File
@@ -9,6 +9,8 @@ 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-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 | | `@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. 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 ## Postgres
@@ -16,7 +18,7 @@ Every dialect passes the same conformance suite, so a request answers the same r
```ts ```ts
import { postgres } from '@crudy/restloom-postgres'; import { postgres } from '@crudy/restloom-postgres';
const db = postgres(pool); 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`. - **Transactions:** every request is one transaction, `READ ONLY` for reads, with `SET LOCAL statement_timeout` from `limits.timeoutMs`.
@@ -27,10 +29,9 @@ const db = postgres(pool);
## MySQL ## MySQL
```ts ```ts
import { createPool } from 'mysql2/promise';
import { mysql } from '@crudy/restloom-mysql'; 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'] }`. - **Schemas:** MySQL's schemas are its databases: `expose: { schemas: ['shop'] }`.
@@ -43,10 +44,9 @@ const db = mysql(createPool({ uri: process.env.DATABASE_URL }));
## SQL Server ## SQL Server
```ts ```ts
import sql from 'mssql';
import { mssql } from '@crudy/restloom-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`. - **Version:** SQL Server 2022 or later, for `JSON_OBJECT`.
@@ -58,6 +58,10 @@ const db = mssql(await new sql.ConnectionPool(process.env.DATABASE_URL).connect(
## SQLite ## SQLite
```sh
npm install @crudy/restloom-sqlite better-sqlite3
```
```ts ```ts
import Database from 'better-sqlite3'; import Database from 'better-sqlite3';
import { sqlite } from '@crudy/restloom-sqlite'; import { sqlite } from '@crudy/restloom-sqlite';
@@ -84,7 +88,7 @@ class Renderer extends PostgresRenderer {
} }
} }
const db = postgres(pool, { 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). The pieces are listed in [writing a dialect](./writing-a-dialect.md).
+15 -13
View File
@@ -2,41 +2,43 @@
## Install ## 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 ```sh
npm install @crudy/restloom @crudy/restloom-postgres pg npm install @crudy/restloom-postgres
``` ```
| Database | Package | Driver | | Database | Package |
|---|---|---| |---|---|
| PostgreSQL 13+ | `@crudy/restloom-postgres` | `pg` | | PostgreSQL 13+ | `@crudy/restloom-postgres` |
| MySQL 8.0+ | `@crudy/restloom-mysql` | `mysql2` | | MySQL 8.0+ | `@crudy/restloom-mysql` |
| SQL Server 2022+ | `@crudy/restloom-mssql` | `mssql` | | SQL Server 2022+ | `@crudy/restloom-mssql` |
| SQLite 3.44+ | `@crudy/restloom-sqlite` | `better-sqlite3` | | 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 ## Serve a schema
```ts ```ts
import http from 'node:http'; import http from 'node:http';
import pg from 'pg'; import { createREST, nodeListener, postgres } from '@crudy/restloom-postgres';
import { createREST, nodeListener } from '@crudy/restloom';
import { postgres } from '@crudy/restloom-postgres';
const rest = createREST({ const rest = createREST({
db: postgres(new pg.Pool({ connectionString: process.env.DATABASE_URL })), db: postgres(process.env.DATABASE_URL),
expose: { schemas: ['app'] }, expose: { schemas: ['app'] },
}); });
http.createServer(nodeListener(rest, { prefix: '/api' })).listen(3000); http.createServer(nodeListener(rest, { prefix: '/api' })).listen(3000);
``` ```
Every table and view of `app` is now a resource, read-only: `nodeListener` comes with the core, so no framework is needed. Every table and view of `app` is now a resource, read-only:
```sh ```sh
curl -g 'http://localhost:3000/api/todos?select=id,name,owner(name)&status=eq.open' curl -g 'http://localhost:3000/api/todos?select=id,name,owner(name)&status=eq.open'
``` ```
Add `write: true` beside `expose` to create, update and delete rows in every table too. Do it once your `authorize` hook decides who may write: [Writes](./writes.md#writes-on-every-table).
## Next ## Next
- [Discovery and overrides](./discovery.md): what becomes a resource, which columns are hidden, how relations are named. - [Discovery and overrides](./discovery.md): what becomes a resource, which columns are hidden, how relations are named.
+44 -2
View File
@@ -5,6 +5,7 @@ restloom decides what can be asked. Your backend decides who may ask it, through
## What restloom enforces ## What restloom enforces
- **Exposure:** only the schemas and tables you name are served, and only their visible columns. - **Exposure:** only the schemas and tables you name are served, and only their visible columns.
- **Sensitive columns:** a column whose name looks like a secret is hidden without being asked, see below.
- **Bound values:** every value is a bound parameter, checked against its column's type before any SQL runs. Names come from the catalog and are quoted; nothing from a request is written into SQL. - **Bound values:** every value is a bound parameter, checked against its column's type before any SQL runs. Names come from the catalog and are quoted; nothing from a request is written into SQL.
- **Bounded requests:** every request is limited: - **Bounded requests:** every request is limited:
@@ -21,11 +22,52 @@ A request over a limit is refused, never quietly cut down.
- **Errors:** they answer stable codes. A database message never reaches the client. Unexpected errors answer `internal` and reach your `onError` hook. - **Errors:** they answer stable codes. A database message never reaches the client. Unexpected errors answer `internal` and reach your `onError` hook.
## Sensitive columns
restloom reads every column name when it reads the catalog. A name made of a word such as `password`, `hash`, `salt`, `secret`, `token`, `api_key`, `private_key`, `otp`, `mfa` or `recovery`, alone or between underscores, marks the column as sensitive: `password_hash` and `reset_token` are hidden, `tokenizer` is not. The full list is in [Columns](./discovery.md#columns).
A hidden column is not served at all. It cannot be read, filtered, sorted or written, `select=*` leaves it out, and naming it answers as a column that does not exist, so a client cannot even learn it is there:
```http
GET /users?select=name,password_hash
```
Answer: 400
```json
{ "error": "unknown_column", "message": "password_hash" }
```
The check runs on names, so it guards against the column nobody thought about when the table grew. It does not know what a column holds: hide a sensitive column with a harmless name yourself, with `hide`.
To serve a column the check hides on purpose, name it in `show`. It then behaves as any other column, and is writable only if `write` lists it:
```ts
resources: {
files: { show: ['sha256_hash'] },
users: { hide: ['phone'] },
}
```
`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-postgres';
createREST({
db, // from a database package, see Dialects
expose: { schemas: ['app'] },
sensitive: new RegExp(`${DEFAULT_SENSITIVE.source}|(^|_)(iban|ssn)(_|$)`, 'i'),
});
```
## Hooks ## Hooks
```ts ```ts
import { createREST, RestError } from '@crudy/restloom-postgres';
createREST({ createREST({
db, db, // from a database package, see Dialects
expose: { schemas: ['app'] }, expose: { schemas: ['app'] },
hooks: { hooks: {
authorize(request, target) { /* throw a RestError to refuse */ }, authorize(request, target) { /* throw a RestError to refuse */ },
@@ -47,7 +89,7 @@ createREST({
## Row security ## Row security
With Postgres, row security is the strongest perimeter: the database itself hides the rows a session may not see. Connect as a role without `BYPASSRLS`, and set what your policies read in the `transaction` hook: Row security needs PostgreSQL, or SQL Server with security policies (see [Dialects](./dialects.md#sql-server)). With Postgres, it is the strongest perimeter: the database itself hides the rows a session may not see. Connect as a role without `BYPASSRLS`, and set what your policies read in the `transaction` hook:
```ts ```ts
async transaction(tx, context) { async transaction(tx, context) {
+45 -1
View File
@@ -1,6 +1,6 @@
# Writes and procedures # Writes and procedures
Writes are off until a resource enables them. The examples on this page run with these options, against the [demo schema](../conformance/fixtures/postgres.sql): Writes are off until you turn them on, for every table at once or one resource at a time. The examples on this page run with these options, against the [demo schema](../conformance/fixtures/postgres.sql):
```ts ```ts
resources: { resources: {
@@ -23,6 +23,28 @@ procedures: {
| DELETE | `/tags/4` | deletes one row | 204, or 200 with the row | | DELETE | `/tags/4` | deletes one row | 204, or 200 with the row |
| PATCH, DELETE | `/steps?priority=lt.3` | by filter, only where `bulk: true` | 204, or 200 with the rows | | PATCH, DELETE | `/steps?priority=lt.3` | by filter, only where `bulk: true` | 204, or 200 with the rows |
## Writes on every table
For an API that writes everywhere, set `write` once, beside `expose`:
```ts
import { createREST } from '@crudy/restloom-postgres';
createREST({
db, // from a database package, see Dialects
expose: { schemas: ['app'] },
write: true,
resources: {
audit_log: { write: false },
users: { write: { update: ['name'] } },
},
});
```
It applies to every exposed table; views stay read-only until their resource allows writes, since many views cannot be written. A resource that sets its own `write` uses it instead: `write: false` keeps one table read-only, a column list narrows another. `write: { create: true, update: true, delete: true, bulk: true }` adds updates and deletes by filter everywhere.
What is never writable stays so: hidden and sensitive columns, generated columns, and keys on update; a bulk request still needs a filter. Who may write is decided by your `authorize` hook, which receives every write with its action: see [Security](./security.md#hooks).
## Getting the rows back ## Getting the rows back
Without a `Prefer` header a write answers with no body. `Prefer: return=representation` answers the written rows, read back with the request's `select`, so embeds work too. Without a `Prefer` header a write answers with no body. `Prefer: return=representation` answers the written rows, read back with the request's `select`, so embeds work too.
@@ -56,6 +78,8 @@ POST /tags
[{ "id": 7, "name": "a" }, { "name": "b" }] [{ "id": 7, "name": "a" }, { "name": "b" }]
``` ```
Answer: 400
```json ```json
{ "error": "mixed_columns" } { "error": "mixed_columns" }
``` ```
@@ -68,6 +92,8 @@ POST /todos
{ "name": "book dentist", "status": "open", "pinned": false } { "name": "book dentist", "status": "open", "pinned": false }
``` ```
Answer: 400
```json ```json
{ "error": "not_writable", "message": "pinned" } { "error": "not_writable", "message": "pinned" }
``` ```
@@ -101,6 +127,8 @@ A row still referenced by another is not deleted:
DELETE /tags/3 DELETE /tags/3
``` ```
Answer: 409
```json ```json
{ "error": "still_referenced" } { "error": "still_referenced" }
``` ```
@@ -109,6 +137,8 @@ DELETE /tags/3
DELETE /todos/1 DELETE /todos/1
``` ```
Answer: 405
```json ```json
{ "error": "method_not_allowed" } { "error": "method_not_allowed" }
``` ```
@@ -130,6 +160,8 @@ Answer: 200
DELETE /steps DELETE /steps
``` ```
Answer: 400
```json ```json
{ "error": "filter_required" } { "error": "filter_required" }
``` ```
@@ -144,6 +176,8 @@ POST /tags
{ "id": 1, "name": "duplicate key" } { "id": 1, "name": "duplicate key" }
``` ```
Answer: 409
```json ```json
{ "error": "duplicate" } { "error": "duplicate" }
``` ```
@@ -154,12 +188,16 @@ POST /steps
{ "id": 9, "title": "Call the clinic", "todo_id": 99, "priority": 1, "due_on": "2026-04-01" } { "id": 9, "title": "Call the clinic", "todo_id": 99, "priority": 1, "due_on": "2026-04-01" }
``` ```
Answer: 409
```json ```json
{ "error": "reference_missing" } { "error": "reference_missing" }
``` ```
## Procedures ## Procedures
PostgreSQL, MySQL and SQL Server run procedures; SQLite has no functions to call. A MySQL function returns a single value, and a SQL Server function cannot change data.
A procedure calls a database function at a path you choose. A `{name}` in the path passes that segment as the argument of the same name; the body's keys pass the others. Arguments are checked by name and type, and one with a default may be left out. A procedure calls a database function at a path you choose. A `{name}` in the path passes that segment as the argument of the same name; the body's keys pass the others. Arguments are checked by name and type, and one with a default may be left out.
A procedure is a POST unless declared `method: 'GET'`. A GET procedure takes its arguments from the query string (`/search?q=milk&limit=10`), each once, and runs in a read-only transaction: use it for a function without side effects. A procedure is a POST unless declared `method: 'GET'`. A GET procedure takes its arguments from the query string (`/search?q=milk&limit=10`), each once, and runs in a read-only transaction: use it for a function without side effects.
@@ -170,6 +208,8 @@ POST /todos/3/complete
{ "note": "bought a new one" } { "note": "bought a new one" }
``` ```
Answer: 200
```json ```json
3 3
``` ```
@@ -180,6 +220,8 @@ A function returning a set answers an array of its rows. File taxes belongs to A
POST /tags/home/todos POST /tags/home/todos
``` ```
Answer: 200
```json ```json
[ [
{ "id": 2, "name": "file taxes", "status": "open", "owner_id": 1, "estimate": 1, "pinned": true, "created_at": "2026-01-05T09:30:00+00:00" }, { "id": 2, "name": "file taxes", "status": "open", "owner_id": 1, "estimate": 1, "pinned": true, "created_at": "2026-01-05T09:30:00+00:00" },
@@ -193,6 +235,8 @@ POST /todos/3/complete
{ "why": "too busy" } { "why": "too busy" }
``` ```
Answer: 400
```json ```json
{ "error": "unknown_argument", "message": "why" } { "error": "unknown_argument", "message": "why" }
``` ```
+2
View File
@@ -3,6 +3,8 @@
A dialect implements one interface: A dialect implements one interface:
```ts ```ts
import type { Catalog, RestError, SqlRenderer, Tx } from '@crudy/restloom';
interface Dialect { interface Dialect {
name: string; name: string;
capabilities: { returning: boolean; estimatedCount: boolean; rowSecurity: boolean; procedures: boolean }; capabilities: { returning: boolean; estimatedCount: boolean; rowSecurity: boolean; procedures: boolean };
+16 -136
View File
@@ -22,7 +22,6 @@
"version": "2.9.0", "version": "2.9.0",
"resolved": "https://registry.npmjs.org/@azure-rest/core-client/-/core-client-2.9.0.tgz", "resolved": "https://registry.npmjs.org/@azure-rest/core-client/-/core-client-2.9.0.tgz",
"integrity": "sha512-6933vNLqh06RR7rumnrq3UZIeBtRLeBHDPMrieDPIljiCaMMwt0nRw++nd3TOxiohtasNtm29rC+b/9ZVKCOqg==", "integrity": "sha512-6933vNLqh06RR7rumnrq3UZIeBtRLeBHDPMrieDPIljiCaMMwt0nRw++nd3TOxiohtasNtm29rC+b/9ZVKCOqg==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@azure/abort-controller": "^2.1.2", "@azure/abort-controller": "^2.1.2",
@@ -40,7 +39,6 @@
"version": "2.2.0", "version": "2.2.0",
"resolved": "https://registry.npmjs.org/@azure/abort-controller/-/abort-controller-2.2.0.tgz", "resolved": "https://registry.npmjs.org/@azure/abort-controller/-/abort-controller-2.2.0.tgz",
"integrity": "sha512-fNAjWnA/nZ2jz31kxR/AqRaUT8ewHBw/WuBIosK0moMy1C9e5ValbDfFdIxJzVOOYaYkV/b2F1S4H/aHiqfVQg==", "integrity": "sha512-fNAjWnA/nZ2jz31kxR/AqRaUT8ewHBw/WuBIosK0moMy1C9e5ValbDfFdIxJzVOOYaYkV/b2F1S4H/aHiqfVQg==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"tslib": "^2.6.2" "tslib": "^2.6.2"
@@ -53,7 +51,6 @@
"version": "1.11.0", "version": "1.11.0",
"resolved": "https://registry.npmjs.org/@azure/core-auth/-/core-auth-1.11.0.tgz", "resolved": "https://registry.npmjs.org/@azure/core-auth/-/core-auth-1.11.0.tgz",
"integrity": "sha512-IUZydyTUkDnYdstOW9pFOOUQlBjAepK5teihDE3x6yxsPJs/hsAaaYpeGxdxrgtOiJbBKSjKW7MDk7AEhb4LRg==", "integrity": "sha512-IUZydyTUkDnYdstOW9pFOOUQlBjAepK5teihDE3x6yxsPJs/hsAaaYpeGxdxrgtOiJbBKSjKW7MDk7AEhb4LRg==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@azure/abort-controller": "^2.1.2", "@azure/abort-controller": "^2.1.2",
@@ -68,7 +65,6 @@
"version": "1.11.1", "version": "1.11.1",
"resolved": "https://registry.npmjs.org/@azure/core-client/-/core-client-1.11.1.tgz", "resolved": "https://registry.npmjs.org/@azure/core-client/-/core-client-1.11.1.tgz",
"integrity": "sha512-2QygG2F76ZpMP2eMztiJvAiFMu71M9rDeU7vO/QKg5Css7MgM4frUOslFjhVjRhbGaCNPtz/S8M6y46/fFKVuQ==", "integrity": "sha512-2QygG2F76ZpMP2eMztiJvAiFMu71M9rDeU7vO/QKg5Css7MgM4frUOslFjhVjRhbGaCNPtz/S8M6y46/fFKVuQ==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@azure/abort-controller": "^2.1.2", "@azure/abort-controller": "^2.1.2",
@@ -87,7 +83,6 @@
"version": "2.7.2", "version": "2.7.2",
"resolved": "https://registry.npmjs.org/@azure/core-lro/-/core-lro-2.7.2.tgz", "resolved": "https://registry.npmjs.org/@azure/core-lro/-/core-lro-2.7.2.tgz",
"integrity": "sha512-0YIpccoX8m/k00O7mDDMdJpbr6mf1yWo2dfmxt5A8XVZVVMz2SSKaEbMCeJRvgQ0IaSlqhjT47p4hVIRRy90xw==", "integrity": "sha512-0YIpccoX8m/k00O7mDDMdJpbr6mf1yWo2dfmxt5A8XVZVVMz2SSKaEbMCeJRvgQ0IaSlqhjT47p4hVIRRy90xw==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@azure/abort-controller": "^2.0.0", "@azure/abort-controller": "^2.0.0",
@@ -103,7 +98,6 @@
"version": "1.7.0", "version": "1.7.0",
"resolved": "https://registry.npmjs.org/@azure/core-paging/-/core-paging-1.7.0.tgz", "resolved": "https://registry.npmjs.org/@azure/core-paging/-/core-paging-1.7.0.tgz",
"integrity": "sha512-7GEAoIsaoBr6KELNRb8nypowCqvk8dnCHFCYg4XD4lOQGY2GqjQg5IhkRjyBFRO18CGSMq05PaNqSOE9GQro3g==", "integrity": "sha512-7GEAoIsaoBr6KELNRb8nypowCqvk8dnCHFCYg4XD4lOQGY2GqjQg5IhkRjyBFRO18CGSMq05PaNqSOE9GQro3g==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"tslib": "^2.6.2" "tslib": "^2.6.2"
@@ -116,7 +110,6 @@
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/@azure/core-process/-/core-process-1.0.0.tgz", "resolved": "https://registry.npmjs.org/@azure/core-process/-/core-process-1.0.0.tgz",
"integrity": "sha512-/shnJ+ooO8WPxDhPEeI/2oRQuubn16gZ6CvlbpWbEswZfzwI9tI/sMAHmF3x1LuQ9yZYXfLW3TjzGMLEC5blKg==", "integrity": "sha512-/shnJ+ooO8WPxDhPEeI/2oRQuubn16gZ6CvlbpWbEswZfzwI9tI/sMAHmF3x1LuQ9yZYXfLW3TjzGMLEC5blKg==",
"dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": ">=22.0.0" "node": ">=22.0.0"
@@ -126,7 +119,6 @@
"version": "1.25.0", "version": "1.25.0",
"resolved": "https://registry.npmjs.org/@azure/core-rest-pipeline/-/core-rest-pipeline-1.25.0.tgz", "resolved": "https://registry.npmjs.org/@azure/core-rest-pipeline/-/core-rest-pipeline-1.25.0.tgz",
"integrity": "sha512-bMs8ekJLjX8wPV+9IPBges1SLPyuDtE9g5gLDWOpxzKcoOFQnpLGkbcT1tdw3FaAmDS1gnPmMmJ6y/T5B96kIA==", "integrity": "sha512-bMs8ekJLjX8wPV+9IPBges1SLPyuDtE9g5gLDWOpxzKcoOFQnpLGkbcT1tdw3FaAmDS1gnPmMmJ6y/T5B96kIA==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@azure/abort-controller": "^2.1.2", "@azure/abort-controller": "^2.1.2",
@@ -145,7 +137,6 @@
"version": "1.4.0", "version": "1.4.0",
"resolved": "https://registry.npmjs.org/@azure/core-tracing/-/core-tracing-1.4.0.tgz", "resolved": "https://registry.npmjs.org/@azure/core-tracing/-/core-tracing-1.4.0.tgz",
"integrity": "sha512-eGwxD0AtncrxeBM4tG8R55Pc3rdX1hNW2WibJAgYpCVA6E93mvvVH+LcssoVjOBrSKWS55yEIHsk0X8ctHmfOQ==", "integrity": "sha512-eGwxD0AtncrxeBM4tG8R55Pc3rdX1hNW2WibJAgYpCVA6E93mvvVH+LcssoVjOBrSKWS55yEIHsk0X8ctHmfOQ==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"tslib": "^2.6.2" "tslib": "^2.6.2"
@@ -158,7 +149,6 @@
"version": "1.14.0", "version": "1.14.0",
"resolved": "https://registry.npmjs.org/@azure/core-util/-/core-util-1.14.0.tgz", "resolved": "https://registry.npmjs.org/@azure/core-util/-/core-util-1.14.0.tgz",
"integrity": "sha512-9n2pWK61veAuN0V20t9lOuoV4CFMdyAZ1ygZzvBGk/pBBJRib/PjL9PLXa/aI2CcPpyHfqVsxxqLCYl6uZlfDw==", "integrity": "sha512-9n2pWK61veAuN0V20t9lOuoV4CFMdyAZ1ygZzvBGk/pBBJRib/PjL9PLXa/aI2CcPpyHfqVsxxqLCYl6uZlfDw==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@azure/abort-controller": "^2.1.2", "@azure/abort-controller": "^2.1.2",
@@ -173,7 +163,6 @@
"version": "4.14.1", "version": "4.14.1",
"resolved": "https://registry.npmjs.org/@azure/identity/-/identity-4.14.1.tgz", "resolved": "https://registry.npmjs.org/@azure/identity/-/identity-4.14.1.tgz",
"integrity": "sha512-HHuuFrmN1uuF9D5A6aAo5eHzNFNX4Q+P4SksZqDKse5YaJFFy/zDLG3wXHEV2FVGgetZ3QM8jGURNBtnQYf1NQ==", "integrity": "sha512-HHuuFrmN1uuF9D5A6aAo5eHzNFNX4Q+P4SksZqDKse5YaJFFy/zDLG3wXHEV2FVGgetZ3QM8jGURNBtnQYf1NQ==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@azure/abort-controller": "^2.0.0", "@azure/abort-controller": "^2.0.0",
@@ -197,7 +186,6 @@
"version": "2.1.1", "version": "2.1.1",
"resolved": "https://registry.npmjs.org/@azure/keyvault-common/-/keyvault-common-2.1.1.tgz", "resolved": "https://registry.npmjs.org/@azure/keyvault-common/-/keyvault-common-2.1.1.tgz",
"integrity": "sha512-SxpomJRTBfDTQnuii3w10WnZNQJGPNi15tRPdPUQz7BqyB3yk976knEueS7R0H9kSLtXbP0wJZXRh0EbRTQ0lw==", "integrity": "sha512-SxpomJRTBfDTQnuii3w10WnZNQJGPNi15tRPdPUQz7BqyB3yk976knEueS7R0H9kSLtXbP0wJZXRh0EbRTQ0lw==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@azure-rest/core-client": "^2.3.3", "@azure-rest/core-client": "^2.3.3",
@@ -217,7 +205,6 @@
"version": "4.10.2", "version": "4.10.2",
"resolved": "https://registry.npmjs.org/@azure/keyvault-keys/-/keyvault-keys-4.10.2.tgz", "resolved": "https://registry.npmjs.org/@azure/keyvault-keys/-/keyvault-keys-4.10.2.tgz",
"integrity": "sha512-VmUSLbXRAbSzDD8grXHGPaknYs0SKr3yuf6U+d4XMpX4XuVYskNqbTTwXce0zR1LyxfTZm9rWEBcvs3vdYwCmQ==", "integrity": "sha512-VmUSLbXRAbSzDD8grXHGPaknYs0SKr3yuf6U+d4XMpX4XuVYskNqbTTwXce0zR1LyxfTZm9rWEBcvs3vdYwCmQ==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@azure-rest/core-client": "^2.3.3", "@azure-rest/core-client": "^2.3.3",
@@ -240,7 +227,6 @@
"version": "1.4.0", "version": "1.4.0",
"resolved": "https://registry.npmjs.org/@azure/logger/-/logger-1.4.0.tgz", "resolved": "https://registry.npmjs.org/@azure/logger/-/logger-1.4.0.tgz",
"integrity": "sha512-rbAE25KUfjU/s3XHUdJgceoCP5dEOpMx85J04kF+QMdta73XkuG9JGHHinch+XIoKpBdqljin+KqURpJriSzLA==", "integrity": "sha512-rbAE25KUfjU/s3XHUdJgceoCP5dEOpMx85J04kF+QMdta73XkuG9JGHHinch+XIoKpBdqljin+KqURpJriSzLA==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@typespec/ts-http-runtime": "^0.3.0", "@typespec/ts-http-runtime": "^0.3.0",
@@ -254,7 +240,6 @@
"version": "5.27.0", "version": "5.27.0",
"resolved": "https://registry.npmjs.org/@azure/msal-browser/-/msal-browser-5.27.0.tgz", "resolved": "https://registry.npmjs.org/@azure/msal-browser/-/msal-browser-5.27.0.tgz",
"integrity": "sha512-i2CKKyuCuOZEBZJiAoFK/viYZAP6CRaqqlkeHl3ViteDHOUE/tkI/Slq1cdGnwhkIB+k1OE0g00QGl9O0gAFNQ==", "integrity": "sha512-i2CKKyuCuOZEBZJiAoFK/viYZAP6CRaqqlkeHl3ViteDHOUE/tkI/Slq1cdGnwhkIB+k1OE0g00QGl9O0gAFNQ==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@azure/msal-common": "16.15.0" "@azure/msal-common": "16.15.0"
@@ -267,7 +252,6 @@
"version": "16.15.0", "version": "16.15.0",
"resolved": "https://registry.npmjs.org/@azure/msal-common/-/msal-common-16.15.0.tgz", "resolved": "https://registry.npmjs.org/@azure/msal-common/-/msal-common-16.15.0.tgz",
"integrity": "sha512-lwdLb1B9Q5nEhPLVIKe78XH5wFMN15fCiDkY7ylPDSWZuWcD93G3jpJajrKhO3CQld8SLKz0sHiOi24mxdvtsg==", "integrity": "sha512-lwdLb1B9Q5nEhPLVIKe78XH5wFMN15fCiDkY7ylPDSWZuWcD93G3jpJajrKhO3CQld8SLKz0sHiOi24mxdvtsg==",
"dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": ">=0.8.0" "node": ">=0.8.0"
@@ -277,7 +261,6 @@
"version": "7.1.2", "version": "7.1.2",
"resolved": "https://registry.npmjs.org/@azure/msal-node/-/msal-node-7.1.2.tgz", "resolved": "https://registry.npmjs.org/@azure/msal-node/-/msal-node-7.1.2.tgz",
"integrity": "sha512-52hKQLGpEQzwNufIXG1SPVU+wQ3RIe7Rp2+g+VdAmZojCAZ6yCdGEffzQEQ6RnBZGBEs40nl14rWBmfJpCoHsg==", "integrity": "sha512-52hKQLGpEQzwNufIXG1SPVU+wQ3RIe7Rp2+g+VdAmZojCAZ6yCdGEffzQEQ6RnBZGBEs40nl14rWBmfJpCoHsg==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@azure/msal-common": "16.15.0", "@azure/msal-common": "16.15.0",
@@ -453,7 +436,6 @@
"version": "6.3.2", "version": "6.3.2",
"resolved": "https://registry.npmjs.org/@js-joda/core/-/core-6.3.2.tgz", "resolved": "https://registry.npmjs.org/@js-joda/core/-/core-6.3.2.tgz",
"integrity": "sha512-BVsYu84Gut8YTNR1v5/PnifmqXv4SVcfA/hVIM/mXADZfIocip/DfNppBtv+4am5hGH1XuH6FSoU8OHkBvZMeA==", "integrity": "sha512-BVsYu84Gut8YTNR1v5/PnifmqXv4SVcfA/hVIM/mXADZfIocip/DfNppBtv+4am5hGH1XuH6FSoU8OHkBvZMeA==",
"dev": true,
"license": "BSD-3-Clause" "license": "BSD-3-Clause"
}, },
"node_modules/@pinojs/redact": { "node_modules/@pinojs/redact": {
@@ -467,7 +449,6 @@
"version": "1.1.0", "version": "1.1.0",
"resolved": "https://registry.npmjs.org/@tediousjs/connection-string/-/connection-string-1.1.0.tgz", "resolved": "https://registry.npmjs.org/@tediousjs/connection-string/-/connection-string-1.1.0.tgz",
"integrity": "sha512-z9ZBWEG+8pIB5V1zYzlRPXx0oRJ5H7coPnMQK8EZOw03UTPI9Umn6viL36f5w+CuqkKsnCM50RVStpjZmR0Bng==", "integrity": "sha512-z9ZBWEG+8pIB5V1zYzlRPXx0oRJ5H7coPnMQK8EZOw03UTPI9Umn6viL36f5w+CuqkKsnCM50RVStpjZmR0Bng==",
"dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/@types/better-sqlite3": { "node_modules/@types/better-sqlite3": {
@@ -549,7 +530,6 @@
"version": "24.19.2", "version": "24.19.2",
"resolved": "https://registry.npmjs.org/@types/node/-/node-24.19.2.tgz", "resolved": "https://registry.npmjs.org/@types/node/-/node-24.19.2.tgz",
"integrity": "sha512-93d49wCWJkCyN87MxLpNN6KXPCwFw7bVUg79mGUV1OS40JGWwVCr8n61DXCSqz7CV6/VfPkb7xec7uP2M+QHWQ==", "integrity": "sha512-93d49wCWJkCyN87MxLpNN6KXPCwFw7bVUg79mGUV1OS40JGWwVCr8n61DXCSqz7CV6/VfPkb7xec7uP2M+QHWQ==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"undici-types": ">=7.24.0 <7.24.7" "undici-types": ">=7.24.0 <7.24.7"
@@ -585,7 +565,6 @@
"version": "4.0.25", "version": "4.0.25",
"resolved": "https://registry.npmjs.org/@types/readable-stream/-/readable-stream-4.0.25.tgz", "resolved": "https://registry.npmjs.org/@types/readable-stream/-/readable-stream-4.0.25.tgz",
"integrity": "sha512-7GsxL8Wd5X3jh73/qgW7WCnbBvGSdD8U3E4Jiuus3/8I+PN20DtlkJtwFhr/sImXfDDQ2Z03i/9wNiIF3TVSsw==", "integrity": "sha512-7GsxL8Wd5X3jh73/qgW7WCnbBvGSdD8U3E4Jiuus3/8I+PN20DtlkJtwFhr/sImXfDDQ2Z03i/9wNiIF3TVSsw==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@types/node": "*" "@types/node": "*"
@@ -616,7 +595,6 @@
"version": "0.3.9", "version": "0.3.9",
"resolved": "https://registry.npmjs.org/@typespec/ts-http-runtime/-/ts-http-runtime-0.3.9.tgz", "resolved": "https://registry.npmjs.org/@typespec/ts-http-runtime/-/ts-http-runtime-0.3.9.tgz",
"integrity": "sha512-edSdeAqkdxBVzA1yL1LrLCml1YjyCVvPMtMqJpbF+6K609tHe8V6sQUzFQSGcYNhcuhOceZtjvN32+mpIth30A==", "integrity": "sha512-edSdeAqkdxBVzA1yL1LrLCml1YjyCVvPMtMqJpbF+6K609tHe8V6sQUzFQSGcYNhcuhOceZtjvN32+mpIth30A==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"http-proxy-agent": "^7.0.0", "http-proxy-agent": "^7.0.0",
@@ -631,7 +609,6 @@
"version": "3.0.0", "version": "3.0.0",
"resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz",
"integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"event-target-shim": "^5.0.0" "event-target-shim": "^5.0.0"
@@ -665,7 +642,6 @@
"version": "7.1.4", "version": "7.1.4",
"resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz",
"integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==",
"dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": ">= 14" "node": ">= 14"
@@ -758,7 +734,6 @@
"version": "1.1.2", "version": "1.1.2",
"resolved": "https://registry.npmjs.org/aws-ssl-profiles/-/aws-ssl-profiles-1.1.2.tgz", "resolved": "https://registry.npmjs.org/aws-ssl-profiles/-/aws-ssl-profiles-1.1.2.tgz",
"integrity": "sha512-NZKeq9AfyQvEeNlN0zSYAaWrmBffJh3IELMZfRpJVWgrpEbtEpnjvzqBPf+mxoI287JohRDoa+/nsfqqiZmF6g==", "integrity": "sha512-NZKeq9AfyQvEeNlN0zSYAaWrmBffJh3IELMZfRpJVWgrpEbtEpnjvzqBPf+mxoI287JohRDoa+/nsfqqiZmF6g==",
"dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": ">= 6.0.0" "node": ">= 6.0.0"
@@ -768,7 +743,6 @@
"version": "1.5.1", "version": "1.5.1",
"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
"integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==",
"dev": true,
"funding": [ "funding": [
{ {
"type": "github", "type": "github",
@@ -876,14 +850,12 @@
"version": "1.0.1", "version": "1.0.1",
"resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz",
"integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==", "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==",
"dev": true,
"license": "BSD-3-Clause" "license": "BSD-3-Clause"
}, },
"node_modules/bundle-name": { "node_modules/bundle-name": {
"version": "4.1.1", "version": "4.1.1",
"resolved": "https://registry.npmjs.org/bundle-name/-/bundle-name-4.1.1.tgz", "resolved": "https://registry.npmjs.org/bundle-name/-/bundle-name-4.1.1.tgz",
"integrity": "sha512-DdH81/zPLVS11EUgWq3tEu/xn+EzljlMYooDNdzWEnFha3R3NBMpMV1UqYIpjYHV/SgpFKMIX1Oh7o06SjM/oA==", "integrity": "sha512-DdH81/zPLVS11EUgWq3tEu/xn+EzljlMYooDNdzWEnFha3R3NBMpMV1UqYIpjYHV/SgpFKMIX1Oh7o06SjM/oA==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"run-applescript": "^7.0.0" "run-applescript": "^7.0.0"
@@ -947,7 +919,6 @@
"version": "11.1.0", "version": "11.1.0",
"resolved": "https://registry.npmjs.org/commander/-/commander-11.1.0.tgz", "resolved": "https://registry.npmjs.org/commander/-/commander-11.1.0.tgz",
"integrity": "sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==", "integrity": "sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==",
"dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": ">=16" "node": ">=16"
@@ -1005,7 +976,6 @@
"version": "4.4.3", "version": "4.4.3",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
"integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"ms": "^2.1.3" "ms": "^2.1.3"
@@ -1049,7 +1019,6 @@
"version": "5.5.1", "version": "5.5.1",
"resolved": "https://registry.npmjs.org/default-browser/-/default-browser-5.5.1.tgz", "resolved": "https://registry.npmjs.org/default-browser/-/default-browser-5.5.1.tgz",
"integrity": "sha512-m1pAzaJgZ/gssEqlOhJkPJp8Xly7QyW6xcrkUa2KKcDeDSEMP7X8xipU3snUcfisTQx0w1AGae+9UtJSfVnXGw==", "integrity": "sha512-m1pAzaJgZ/gssEqlOhJkPJp8Xly7QyW6xcrkUa2KKcDeDSEMP7X8xipU3snUcfisTQx0w1AGae+9UtJSfVnXGw==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"bundle-name": "^4.1.0", "bundle-name": "^4.1.0",
@@ -1066,7 +1035,6 @@
"version": "5.0.1", "version": "5.0.1",
"resolved": "https://registry.npmjs.org/default-browser-id/-/default-browser-id-5.0.1.tgz", "resolved": "https://registry.npmjs.org/default-browser-id/-/default-browser-id-5.0.1.tgz",
"integrity": "sha512-x1VCxdX4t+8wVfd1so/9w+vQ4vx7lKd2Qp5tDRutErwmR85OgmfX7RlLRMWafRMY7hbEiXIbudNrjOAPa/hL8Q==", "integrity": "sha512-x1VCxdX4t+8wVfd1so/9w+vQ4vx7lKd2Qp5tDRutErwmR85OgmfX7RlLRMWafRMY7hbEiXIbudNrjOAPa/hL8Q==",
"dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": ">=18" "node": ">=18"
@@ -1079,7 +1047,6 @@
"version": "3.0.0", "version": "3.0.0",
"resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-3.0.0.tgz", "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-3.0.0.tgz",
"integrity": "sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==", "integrity": "sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==",
"dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": ">=12" "node": ">=12"
@@ -1137,7 +1104,6 @@
"version": "1.0.11", "version": "1.0.11",
"resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz",
"integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==",
"dev": true,
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
"safe-buffer": "^5.0.1" "safe-buffer": "^5.0.1"
@@ -1228,7 +1194,6 @@
"version": "5.0.1", "version": "5.0.1",
"resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz",
"integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==",
"dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": ">=6" "node": ">=6"
@@ -1238,7 +1203,6 @@
"version": "3.3.0", "version": "3.3.0",
"resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz",
"integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==",
"dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": ">=0.8.x" "node": ">=0.8.x"
@@ -1493,7 +1457,6 @@
"version": "2.3.1", "version": "2.3.1",
"resolved": "https://registry.npmjs.org/generate-function/-/generate-function-2.3.1.tgz", "resolved": "https://registry.npmjs.org/generate-function/-/generate-function-2.3.1.tgz",
"integrity": "sha512-eeB5GfMNeevm/GRYq20ShmsaGcmI81kIX2K9XQx5miC8KdHaC6Jm0qQ8ZNeGOi7wYB8OsdxKs+Y2oVuTFuVwKQ==", "integrity": "sha512-eeB5GfMNeevm/GRYq20ShmsaGcmI81kIX2K9XQx5miC8KdHaC6Jm0qQ8ZNeGOi7wYB8OsdxKs+Y2oVuTFuVwKQ==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"is-property": "^1.0.2" "is-property": "^1.0.2"
@@ -1609,7 +1572,6 @@
"version": "7.0.2", "version": "7.0.2",
"resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz",
"integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"agent-base": "^7.1.0", "agent-base": "^7.1.0",
@@ -1623,7 +1585,6 @@
"version": "7.0.6", "version": "7.0.6",
"resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz",
"integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"agent-base": "^7.1.2", "agent-base": "^7.1.2",
@@ -1637,7 +1598,6 @@
"version": "0.7.3", "version": "0.7.3",
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.3.tgz", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.3.tgz",
"integrity": "sha512-IKXpvIzjnC9XTAUbVBcMfGS0EPaIXtW6v+zr+RRp+hqULEpo0owZax6wyRwPOJbWbzjYspQwusTsfVr0ifh4uQ==", "integrity": "sha512-IKXpvIzjnC9XTAUbVBcMfGS0EPaIXtW6v+zr+RRp+hqULEpo0owZax6wyRwPOJbWbzjYspQwusTsfVr0ifh4uQ==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"safer-buffer": ">= 2.1.2 < 3.0.0" "safer-buffer": ">= 2.1.2 < 3.0.0"
@@ -1654,7 +1614,6 @@
"version": "1.2.1", "version": "1.2.1",
"resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
"integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==",
"dev": true,
"funding": [ "funding": [
{ {
"type": "github", "type": "github",
@@ -1675,7 +1634,6 @@
"version": "2.0.4", "version": "2.0.4",
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
"dev": true,
"license": "ISC" "license": "ISC"
}, },
"node_modules/ini": { "node_modules/ini": {
@@ -1699,7 +1657,6 @@
"version": "3.0.0", "version": "3.0.0",
"resolved": "https://registry.npmjs.org/is-docker/-/is-docker-3.0.0.tgz", "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-3.0.0.tgz",
"integrity": "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==", "integrity": "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==",
"dev": true,
"license": "MIT", "license": "MIT",
"bin": { "bin": {
"is-docker": "cli.js" "is-docker": "cli.js"
@@ -1715,7 +1672,6 @@
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/is-in-ssh/-/is-in-ssh-1.0.0.tgz", "resolved": "https://registry.npmjs.org/is-in-ssh/-/is-in-ssh-1.0.0.tgz",
"integrity": "sha512-jYa6Q9rH90kR1vKB6NM7qqd1mge3Fx4Dhw5TVlK1MUBqhEOuCagrEHMevNuCcbECmXZ0ThXkRm+Ymr51HwEPAw==", "integrity": "sha512-jYa6Q9rH90kR1vKB6NM7qqd1mge3Fx4Dhw5TVlK1MUBqhEOuCagrEHMevNuCcbECmXZ0ThXkRm+Ymr51HwEPAw==",
"dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": ">=20" "node": ">=20"
@@ -1728,7 +1684,6 @@
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/is-inside-container/-/is-inside-container-1.0.0.tgz", "resolved": "https://registry.npmjs.org/is-inside-container/-/is-inside-container-1.0.0.tgz",
"integrity": "sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==", "integrity": "sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"is-docker": "^3.0.0" "is-docker": "^3.0.0"
@@ -1754,14 +1709,12 @@
"version": "1.0.2", "version": "1.0.2",
"resolved": "https://registry.npmjs.org/is-property/-/is-property-1.0.2.tgz", "resolved": "https://registry.npmjs.org/is-property/-/is-property-1.0.2.tgz",
"integrity": "sha512-Ks/IoX00TtClbGQr4TWXemAnktAQvYB7HzcCxDGqEZU6oCmb2INHuOoKxbtR+HFkmYWBKv/dOZtGRiAjDhj92g==", "integrity": "sha512-Ks/IoX00TtClbGQr4TWXemAnktAQvYB7HzcCxDGqEZU6oCmb2INHuOoKxbtR+HFkmYWBKv/dOZtGRiAjDhj92g==",
"dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/is-wsl": { "node_modules/is-wsl": {
"version": "3.1.1", "version": "3.1.1",
"resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-3.1.1.tgz", "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-3.1.1.tgz",
"integrity": "sha512-e6rvdUCiQCAuumZslxRJWR/Doq4VpPR82kqclvcS0efgt430SlGIk05vdCN58+VrzgtIcfNODjozVielycD4Sw==", "integrity": "sha512-e6rvdUCiQCAuumZslxRJWR/Doq4VpPR82kqclvcS0efgt430SlGIk05vdCN58+VrzgtIcfNODjozVielycD4Sw==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"is-inside-container": "^1.0.0" "is-inside-container": "^1.0.0"
@@ -1777,7 +1730,6 @@
"version": "0.3.2", "version": "0.3.2",
"resolved": "https://registry.npmjs.org/js-md4/-/js-md4-0.3.2.tgz", "resolved": "https://registry.npmjs.org/js-md4/-/js-md4-0.3.2.tgz",
"integrity": "sha512-/GDnfQYsltsjRswQhN9fhv3EMw2sCpUdrdxyWDOUK7eyD++r3gRhzgiQgc/x4MAv2i1iuQ4lxO5mvqM3vj4bwA==", "integrity": "sha512-/GDnfQYsltsjRswQhN9fhv3EMw2sCpUdrdxyWDOUK7eyD++r3gRhzgiQgc/x4MAv2i1iuQ4lxO5mvqM3vj4bwA==",
"dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/json-schema-ref-resolver": { "node_modules/json-schema-ref-resolver": {
@@ -1811,7 +1763,6 @@
"version": "9.0.3", "version": "9.0.3",
"resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.3.tgz", "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.3.tgz",
"integrity": "sha512-MT/xP0CrubFRNLNKvxJ2BYfy53Zkm++5bX9dtuPbqAeQpTVe0MQTFhao8+Cp//EmJp244xt6Drw/GVEGCUj40g==", "integrity": "sha512-MT/xP0CrubFRNLNKvxJ2BYfy53Zkm++5bX9dtuPbqAeQpTVe0MQTFhao8+Cp//EmJp244xt6Drw/GVEGCUj40g==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"jws": "^4.0.1", "jws": "^4.0.1",
@@ -1834,7 +1785,6 @@
"version": "2.0.1", "version": "2.0.1",
"resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.1.tgz", "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.1.tgz",
"integrity": "sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg==", "integrity": "sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"buffer-equal-constant-time": "^1.0.1", "buffer-equal-constant-time": "^1.0.1",
@@ -1846,7 +1796,6 @@
"version": "4.0.1", "version": "4.0.1",
"resolved": "https://registry.npmjs.org/jws/-/jws-4.0.1.tgz", "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.1.tgz",
"integrity": "sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA==", "integrity": "sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"jwa": "^2.0.1", "jwa": "^2.0.1",
@@ -1894,63 +1843,54 @@
"version": "4.3.0", "version": "4.3.0",
"resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz",
"integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==", "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==",
"dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/lodash.isboolean": { "node_modules/lodash.isboolean": {
"version": "3.0.3", "version": "3.0.3",
"resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz",
"integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==", "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==",
"dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/lodash.isinteger": { "node_modules/lodash.isinteger": {
"version": "4.0.4", "version": "4.0.4",
"resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz",
"integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==", "integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==",
"dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/lodash.isnumber": { "node_modules/lodash.isnumber": {
"version": "3.0.3", "version": "3.0.3",
"resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz",
"integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==", "integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==",
"dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/lodash.isplainobject": { "node_modules/lodash.isplainobject": {
"version": "4.0.6", "version": "4.0.6",
"resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz",
"integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==", "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==",
"dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/lodash.isstring": { "node_modules/lodash.isstring": {
"version": "4.0.1", "version": "4.0.1",
"resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz",
"integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==", "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==",
"dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/lodash.once": { "node_modules/lodash.once": {
"version": "4.1.1", "version": "4.1.1",
"resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz",
"integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==", "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==",
"dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/long": { "node_modules/long": {
"version": "5.3.2", "version": "5.3.2",
"resolved": "https://registry.npmjs.org/long/-/long-5.3.2.tgz", "resolved": "https://registry.npmjs.org/long/-/long-5.3.2.tgz",
"integrity": "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==", "integrity": "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==",
"dev": true,
"license": "Apache-2.0" "license": "Apache-2.0"
}, },
"node_modules/lru.min": { "node_modules/lru.min": {
"version": "1.1.5", "version": "1.1.5",
"resolved": "https://registry.npmjs.org/lru.min/-/lru.min-1.1.5.tgz", "resolved": "https://registry.npmjs.org/lru.min/-/lru.min-1.1.5.tgz",
"integrity": "sha512-5J9ysMYUpYIg9RF2vJpy9SinEmSviFSe0GyPpCQ4L5QSkLAgeLXlTAOu2ZwWUU5m+0SBl6gUU1R1ZQB3aKypfA==", "integrity": "sha512-5J9ysMYUpYIg9RF2vJpy9SinEmSviFSe0GyPpCQ4L5QSkLAgeLXlTAOu2ZwWUU5m+0SBl6gUU1R1ZQB3aKypfA==",
"dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"bun": ">=1.0.0", "bun": ">=1.0.0",
@@ -2060,14 +2000,12 @@
"version": "2.1.3", "version": "2.1.3",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
"dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/mssql": { "node_modules/mssql": {
"version": "12.7.10", "version": "12.7.10",
"resolved": "https://registry.npmjs.org/mssql/-/mssql-12.7.10.tgz", "resolved": "https://registry.npmjs.org/mssql/-/mssql-12.7.10.tgz",
"integrity": "sha512-vTmKBsogfFhd8dMhFjwn2QiPZuUP2/zpqt19L8eEJpQFwRa19bR+n3pFBjRtGdz+YfuC5jgj4YixILSAAF3ovQ==", "integrity": "sha512-vTmKBsogfFhd8dMhFjwn2QiPZuUP2/zpqt19L8eEJpQFwRa19bR+n3pFBjRtGdz+YfuC5jgj4YixILSAAF3ovQ==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@tediousjs/connection-string": "^1.0.0", "@tediousjs/connection-string": "^1.0.0",
@@ -2087,7 +2025,6 @@
"version": "3.24.6", "version": "3.24.6",
"resolved": "https://registry.npmjs.org/mysql2/-/mysql2-3.24.6.tgz", "resolved": "https://registry.npmjs.org/mysql2/-/mysql2-3.24.6.tgz",
"integrity": "sha512-enu73KhbAua2L3dIxjc6qg6ZJ7H3pt1BImZASWNWY5xSsmJ1ZuUPULTDGXgmJbI61vb+nJAG19IZclIq19UidQ==", "integrity": "sha512-enu73KhbAua2L3dIxjc6qg6ZJ7H3pt1BImZASWNWY5xSsmJ1ZuUPULTDGXgmJbI61vb+nJAG19IZclIq19UidQ==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"aws-ssl-profiles": "^1.1.2", "aws-ssl-profiles": "^1.1.2",
@@ -2109,7 +2046,6 @@
"version": "1.1.6", "version": "1.1.6",
"resolved": "https://registry.npmjs.org/named-placeholders/-/named-placeholders-1.1.6.tgz", "resolved": "https://registry.npmjs.org/named-placeholders/-/named-placeholders-1.1.6.tgz",
"integrity": "sha512-Tz09sEL2EEuv5fFowm419c1+a/jSMiBjI9gHxVLrVdbUkkNUUfjsVYs9pVZu5oCon/kmRh9TfLEObFtkVxmY0w==", "integrity": "sha512-Tz09sEL2EEuv5fFowm419c1+a/jSMiBjI9gHxVLrVdbUkkNUUfjsVYs9pVZu5oCon/kmRh9TfLEObFtkVxmY0w==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"lru.min": "^1.1.0" "lru.min": "^1.1.0"
@@ -2129,7 +2065,6 @@
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/native-duplexpair/-/native-duplexpair-1.0.0.tgz", "resolved": "https://registry.npmjs.org/native-duplexpair/-/native-duplexpair-1.0.0.tgz",
"integrity": "sha512-E7QQoM+3jvNtlmyfqRZ0/U75VFgCls+fSkbml2MpgWkWyz3ox8Y58gNhfuziuQYGNNQAbFZJQck55LHCnCK6CA==", "integrity": "sha512-E7QQoM+3jvNtlmyfqRZ0/U75VFgCls+fSkbml2MpgWkWyz3ox8Y58gNhfuziuQYGNNQAbFZJQck55LHCnCK6CA==",
"dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/negotiator": { "node_modules/negotiator": {
@@ -2216,7 +2151,6 @@
"version": "11.0.4", "version": "11.0.4",
"resolved": "https://registry.npmjs.org/open/-/open-11.0.4.tgz", "resolved": "https://registry.npmjs.org/open/-/open-11.0.4.tgz",
"integrity": "sha512-++Zlftm0kVLPmzC06t6epuWmcRMDbI4z5P3NNX979WA/k23+NtSOynEGzsVfZwguKw2mi5umVgnBlJQMwRz4Pg==", "integrity": "sha512-++Zlftm0kVLPmzC06t6epuWmcRMDbI4z5P3NNX979WA/k23+NtSOynEGzsVfZwguKw2mi5umVgnBlJQMwRz4Pg==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"default-browser": "^5.5.1", "default-browser": "^5.5.1",
@@ -2258,7 +2192,6 @@
"version": "8.23.1", "version": "8.23.1",
"resolved": "https://registry.npmjs.org/pg/-/pg-8.23.1.tgz", "resolved": "https://registry.npmjs.org/pg/-/pg-8.23.1.tgz",
"integrity": "sha512-aL96AHANtWjPLDOLqnhx+ngp9+UK7ETEU8VJrDCGvsSSi/mGLcWYsS6Herg7lmaBJe4uwrfqsa7gTEFaSizDoQ==", "integrity": "sha512-aL96AHANtWjPLDOLqnhx+ngp9+UK7ETEU8VJrDCGvsSSi/mGLcWYsS6Herg7lmaBJe4uwrfqsa7gTEFaSizDoQ==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"pg-connection-string": "^2.14.1", "pg-connection-string": "^2.14.1",
@@ -2286,7 +2219,6 @@
"version": "1.4.1", "version": "1.4.1",
"resolved": "https://registry.npmjs.org/pg-cloudflare/-/pg-cloudflare-1.4.1.tgz", "resolved": "https://registry.npmjs.org/pg-cloudflare/-/pg-cloudflare-1.4.1.tgz",
"integrity": "sha512-6PQbsFWZcp9EmJEwy5cGQ2La+AMWpP46lgbb8X+U/XsHIUweYDNCpeuKck5RxL2MdVFi7krbbEi5nX4Zh7JhrQ==", "integrity": "sha512-6PQbsFWZcp9EmJEwy5cGQ2La+AMWpP46lgbb8X+U/XsHIUweYDNCpeuKck5RxL2MdVFi7krbbEi5nX4Zh7JhrQ==",
"dev": true,
"license": "MIT", "license": "MIT",
"optional": true "optional": true
}, },
@@ -2294,14 +2226,12 @@
"version": "2.14.1", "version": "2.14.1",
"resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.14.1.tgz", "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.14.1.tgz",
"integrity": "sha512-qR3kGNPBLpCNtz0evbKA0Y/MRFXwSSdT+pTJvYp/bXTcReZbvX1kzF0IyTc1QnxqF7AZbOeBhNL8R5mYQZV/MA==", "integrity": "sha512-qR3kGNPBLpCNtz0evbKA0Y/MRFXwSSdT+pTJvYp/bXTcReZbvX1kzF0IyTc1QnxqF7AZbOeBhNL8R5mYQZV/MA==",
"dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/pg-int8": { "node_modules/pg-int8": {
"version": "1.0.1", "version": "1.0.1",
"resolved": "https://registry.npmjs.org/pg-int8/-/pg-int8-1.0.1.tgz", "resolved": "https://registry.npmjs.org/pg-int8/-/pg-int8-1.0.1.tgz",
"integrity": "sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==", "integrity": "sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==",
"dev": true,
"license": "ISC", "license": "ISC",
"engines": { "engines": {
"node": ">=4.0.0" "node": ">=4.0.0"
@@ -2311,7 +2241,6 @@
"version": "3.14.0", "version": "3.14.0",
"resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-3.14.0.tgz", "resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-3.14.0.tgz",
"integrity": "sha512-gKtPkFdQPU3DksooVLi9LsjZxrsBUZIpa+7aVx+LV5pNh0KzP4Zleud2po+ConrxbuXGBJ6Hfer6hdgpIBpBaw==", "integrity": "sha512-gKtPkFdQPU3DksooVLi9LsjZxrsBUZIpa+7aVx+LV5pNh0KzP4Zleud2po+ConrxbuXGBJ6Hfer6hdgpIBpBaw==",
"dev": true,
"license": "MIT", "license": "MIT",
"peerDependencies": { "peerDependencies": {
"pg": ">=8.0" "pg": ">=8.0"
@@ -2321,14 +2250,12 @@
"version": "1.16.1", "version": "1.16.1",
"resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.16.1.tgz", "resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.16.1.tgz",
"integrity": "sha512-p9VOFMiHB/ZbJATetbg+99PxssTVSQRnyuPSQ67mN1+1KBOjZaZ83ZQzltnxPhJwSsC3nwVjJ10DVJlerbFzLg==", "integrity": "sha512-p9VOFMiHB/ZbJATetbg+99PxssTVSQRnyuPSQ67mN1+1KBOjZaZ83ZQzltnxPhJwSsC3nwVjJ10DVJlerbFzLg==",
"dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/pg-types": { "node_modules/pg-types": {
"version": "2.2.0", "version": "2.2.0",
"resolved": "https://registry.npmjs.org/pg-types/-/pg-types-2.2.0.tgz", "resolved": "https://registry.npmjs.org/pg-types/-/pg-types-2.2.0.tgz",
"integrity": "sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==", "integrity": "sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"pg-int8": "1.0.1", "pg-int8": "1.0.1",
@@ -2345,7 +2272,6 @@
"version": "1.0.5", "version": "1.0.5",
"resolved": "https://registry.npmjs.org/pgpass/-/pgpass-1.0.5.tgz", "resolved": "https://registry.npmjs.org/pgpass/-/pgpass-1.0.5.tgz",
"integrity": "sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug==", "integrity": "sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"split2": "^4.1.0" "split2": "^4.1.0"
@@ -2395,7 +2321,6 @@
"version": "2.0.0", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-2.0.0.tgz", "resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-2.0.0.tgz",
"integrity": "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==", "integrity": "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==",
"dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": ">=4" "node": ">=4"
@@ -2405,7 +2330,6 @@
"version": "1.0.1", "version": "1.0.1",
"resolved": "https://registry.npmjs.org/postgres-bytea/-/postgres-bytea-1.0.1.tgz", "resolved": "https://registry.npmjs.org/postgres-bytea/-/postgres-bytea-1.0.1.tgz",
"integrity": "sha512-5+5HqXnsZPE65IJZSMkZtURARZelel2oXUEO8rH83VS/hxH5vv1uHquPg5wZs8yMAfdv971IU+kcPUczi7NVBQ==", "integrity": "sha512-5+5HqXnsZPE65IJZSMkZtURARZelel2oXUEO8rH83VS/hxH5vv1uHquPg5wZs8yMAfdv971IU+kcPUczi7NVBQ==",
"dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": ">=0.10.0" "node": ">=0.10.0"
@@ -2415,7 +2339,6 @@
"version": "1.0.7", "version": "1.0.7",
"resolved": "https://registry.npmjs.org/postgres-date/-/postgres-date-1.0.7.tgz", "resolved": "https://registry.npmjs.org/postgres-date/-/postgres-date-1.0.7.tgz",
"integrity": "sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==", "integrity": "sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==",
"dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": ">=0.10.0" "node": ">=0.10.0"
@@ -2425,7 +2348,6 @@
"version": "1.2.0", "version": "1.2.0",
"resolved": "https://registry.npmjs.org/postgres-interval/-/postgres-interval-1.2.0.tgz", "resolved": "https://registry.npmjs.org/postgres-interval/-/postgres-interval-1.2.0.tgz",
"integrity": "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==", "integrity": "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"xtend": "^4.0.0" "xtend": "^4.0.0"
@@ -2438,7 +2360,6 @@
"version": "0.2.1", "version": "0.2.1",
"resolved": "https://registry.npmjs.org/powershell-utils/-/powershell-utils-0.2.1.tgz", "resolved": "https://registry.npmjs.org/powershell-utils/-/powershell-utils-0.2.1.tgz",
"integrity": "sha512-C+y9x90UElAddDZmV4qOx9W53B61PO7cIqWz2dQsWlwswuq4mr8NEwytdGKboYbQlGZ3awrkTeNvcZiZNHnQ8A==", "integrity": "sha512-C+y9x90UElAddDZmV4qOx9W53B61PO7cIqWz2dQsWlwswuq4mr8NEwytdGKboYbQlGZ3awrkTeNvcZiZNHnQ8A==",
"dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": ">=20" "node": ">=20"
@@ -2479,7 +2400,6 @@
"version": "0.11.10", "version": "0.11.10",
"resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz",
"integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==", "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==",
"dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": ">= 0.6.0" "node": ">= 0.6.0"
@@ -2692,7 +2612,6 @@
"version": "7.1.0", "version": "7.1.0",
"resolved": "https://registry.npmjs.org/run-applescript/-/run-applescript-7.1.0.tgz", "resolved": "https://registry.npmjs.org/run-applescript/-/run-applescript-7.1.0.tgz",
"integrity": "sha512-DPe5pVFaAsinSaV6QjQ6gdiedWDcRCbUuiQfQa2wmWV7+xC9bGulGI8+TdRmoFkAPaBXk8CrAbnlY2ISniJ47Q==", "integrity": "sha512-DPe5pVFaAsinSaV6QjQ6gdiedWDcRCbUuiQfQa2wmWV7+xC9bGulGI8+TdRmoFkAPaBXk8CrAbnlY2ISniJ47Q==",
"dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": ">=18" "node": ">=18"
@@ -2705,7 +2624,6 @@
"version": "5.2.1", "version": "5.2.1",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
"integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
"dev": true,
"funding": [ "funding": [
{ {
"type": "github", "type": "github",
@@ -2759,7 +2677,6 @@
"version": "2.1.2", "version": "2.1.2",
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
"dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/secure-json-parse": { "node_modules/secure-json-parse": {
@@ -2783,7 +2700,6 @@
"version": "7.8.5", "version": "7.8.5",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz",
"integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==",
"dev": true,
"license": "ISC", "license": "ISC",
"bin": { "bin": {
"semver": "bin/semver.js" "semver": "bin/semver.js"
@@ -2990,7 +2906,6 @@
"version": "4.2.0", "version": "4.2.0",
"resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz", "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz",
"integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==", "integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==",
"dev": true,
"license": "ISC", "license": "ISC",
"engines": { "engines": {
"node": ">= 10.x" "node": ">= 10.x"
@@ -3000,7 +2915,6 @@
"version": "1.5.2", "version": "1.5.2",
"resolved": "https://registry.npmjs.org/sql-escaper/-/sql-escaper-1.5.2.tgz", "resolved": "https://registry.npmjs.org/sql-escaper/-/sql-escaper-1.5.2.tgz",
"integrity": "sha512-6CKD38c31SENivxOADeMNLdukOnUxUcflKtzVWzace7Riv1v7cAEym5Cx9Q7gYZ3ezIJI7ZpqecQq8cqNeSSFg==", "integrity": "sha512-6CKD38c31SENivxOADeMNLdukOnUxUcflKtzVWzace7Riv1v7cAEym5Cx9Q7gYZ3ezIJI7ZpqecQq8cqNeSSFg==",
"dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"bun": ">=1.0.0", "bun": ">=1.0.0",
@@ -3026,7 +2940,6 @@
"version": "1.3.0", "version": "1.3.0",
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
"integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"safe-buffer": "~5.2.0" "safe-buffer": "~5.2.0"
@@ -3076,7 +2989,6 @@
"version": "3.1.2", "version": "3.1.2",
"resolved": "https://registry.npmjs.org/tarn/-/tarn-3.1.2.tgz", "resolved": "https://registry.npmjs.org/tarn/-/tarn-3.1.2.tgz",
"integrity": "sha512-3RTvqKZcK/17jnJ8rMKFXbyNogywTs1z0gVPPwFsJGX46rkmUHOdIaSQ/aVO1rS7nH+soiXiWk7rvUXxndm8Dg==", "integrity": "sha512-3RTvqKZcK/17jnJ8rMKFXbyNogywTs1z0gVPPwFsJGX46rkmUHOdIaSQ/aVO1rS7nH+soiXiWk7rvUXxndm8Dg==",
"dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": ">=8.0.0" "node": ">=8.0.0"
@@ -3086,7 +2998,6 @@
"version": "20.3.8", "version": "20.3.8",
"resolved": "https://registry.npmjs.org/tedious/-/tedious-20.3.8.tgz", "resolved": "https://registry.npmjs.org/tedious/-/tedious-20.3.8.tgz",
"integrity": "sha512-ZrJiwyGn340Sa0V0g37Xqj5bvmJ1riYwvEtg7zdm/lpw70f2ZNIQ2fvHmntU5fEOorWDa48Jo1oKYAuUP+rh7Q==", "integrity": "sha512-ZrJiwyGn340Sa0V0g37Xqj5bvmJ1riYwvEtg7zdm/lpw70f2ZNIQ2fvHmntU5fEOorWDa48Jo1oKYAuUP+rh7Q==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@azure/core-auth": "^1.10.1", "@azure/core-auth": "^1.10.1",
@@ -3107,7 +3018,6 @@
"version": "6.1.6", "version": "6.1.6",
"resolved": "https://registry.npmjs.org/bl/-/bl-6.1.6.tgz", "resolved": "https://registry.npmjs.org/bl/-/bl-6.1.6.tgz",
"integrity": "sha512-jLsPgN/YSvPUg9UX0Kd73CXpm2Psg9FxMeCSXnk3WBO3CMT10JMwijubhGfHCnFu6TPn1ei3b975dxv7K2pWVg==", "integrity": "sha512-jLsPgN/YSvPUg9UX0Kd73CXpm2Psg9FxMeCSXnk3WBO3CMT10JMwijubhGfHCnFu6TPn1ei3b975dxv7K2pWVg==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@types/readable-stream": "^4.0.0", "@types/readable-stream": "^4.0.0",
@@ -3120,7 +3030,6 @@
"version": "6.0.3", "version": "6.0.3",
"resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz",
"integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==",
"dev": true,
"funding": [ "funding": [
{ {
"type": "github", "type": "github",
@@ -3145,7 +3054,6 @@
"version": "4.8.0", "version": "4.8.0",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.8.0.tgz", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.8.0.tgz",
"integrity": "sha512-2jhTTU3Eilp/2DMT71gbcoqC35eQHasMJH0RHkYxwlxIv3+FR6Y5xAFZIwEGLxKkjZYEFlsiMNIdwUqjK2lLHQ==", "integrity": "sha512-2jhTTU3Eilp/2DMT71gbcoqC35eQHasMJH0RHkYxwlxIv3+FR6Y5xAFZIwEGLxKkjZYEFlsiMNIdwUqjK2lLHQ==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"abort-controller": "^3.0.0", "abort-controller": "^3.0.0",
@@ -3195,7 +3103,6 @@
"version": "2.9.1", "version": "2.9.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.9.1.tgz", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.9.1.tgz",
"integrity": "sha512-a4Ux9VKuIMHsOS7ENbEq6hK2ZGYIT27Rtl0E4eD9KqwxhKiQeMZE/9xb3IdYwWaSOyIBQLExhQgXDTYoiCxnWA==", "integrity": "sha512-a4Ux9VKuIMHsOS7ENbEq6hK2ZGYIT27Rtl0E4eD9KqwxhKiQeMZE/9xb3IdYwWaSOyIBQLExhQgXDTYoiCxnWA==",
"dev": true,
"license": "0BSD" "license": "0BSD"
}, },
"node_modules/tunnel-agent": { "node_modules/tunnel-agent": {
@@ -3248,7 +3155,6 @@
"version": "7.24.6", "version": "7.24.6",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.24.6.tgz", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.24.6.tgz",
"integrity": "sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg==", "integrity": "sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg==",
"dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/unpipe": { "node_modules/unpipe": {
@@ -3289,7 +3195,6 @@
"version": "1.0.1", "version": "1.0.1",
"resolved": "https://registry.npmjs.org/wsl-utils/-/wsl-utils-1.0.1.tgz", "resolved": "https://registry.npmjs.org/wsl-utils/-/wsl-utils-1.0.1.tgz",
"integrity": "sha512-c16PKLIRxBYpR3jSWsdS7SFp0COm5SOQdiFjY818dYFHJbMGuLBszpF07LBWmOS1uzrHa9VGdynsEgoeKy0oOQ==", "integrity": "sha512-c16PKLIRxBYpR3jSWsdS7SFp0COm5SOQdiFjY818dYFHJbMGuLBszpF07LBWmOS1uzrHa9VGdynsEgoeKy0oOQ==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"is-wsl": "^3.1.0", "is-wsl": "^3.1.0",
@@ -3306,7 +3211,6 @@
"version": "4.0.2", "version": "4.0.2",
"resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",
"integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==",
"dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": ">=0.4" "node": ">=0.4"
@@ -3314,7 +3218,7 @@
}, },
"packages/core": { "packages/core": {
"name": "@crudy/restloom", "name": "@crudy/restloom",
"version": "0.3.1", "version": "0.5.0",
"license": "Apache-2.0", "license": "Apache-2.0",
"devDependencies": { "devDependencies": {
"@types/node": "^24.0.0", "@types/node": "^24.0.0",
@@ -3326,10 +3230,10 @@
}, },
"packages/express": { "packages/express": {
"name": "@crudy/restloom-express", "name": "@crudy/restloom-express",
"version": "0.3.1", "version": "0.5.0",
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
"@crudy/restloom": "^0.3.0" "@crudy/restloom": "^0.5.0"
}, },
"devDependencies": { "devDependencies": {
"@types/express": "^5.0.0", "@types/express": "^5.0.0",
@@ -3346,10 +3250,10 @@
}, },
"packages/fastify": { "packages/fastify": {
"name": "@crudy/restloom-fastify", "name": "@crudy/restloom-fastify",
"version": "0.3.1", "version": "0.5.0",
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
"@crudy/restloom": "^0.3.0" "@crudy/restloom": "^0.5.0"
}, },
"devDependencies": { "devDependencies": {
"@types/node": "^24.0.0", "@types/node": "^24.0.0",
@@ -3365,84 +3269,60 @@
}, },
"packages/mssql": { "packages/mssql": {
"name": "@crudy/restloom-mssql", "name": "@crudy/restloom-mssql",
"version": "0.3.1", "version": "0.5.0",
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
"@crudy/restloom": "^0.3.0" "@crudy/restloom": "^0.5.0",
"mssql": "^12.7.10"
}, },
"devDependencies": { "devDependencies": {
"@types/mssql": "^12.0.0", "@types/mssql": "^12.0.0",
"@types/node": "^24.0.0", "@types/node": "^24.0.0",
"mssql": "12.7.10",
"typescript": "^5.9.0" "typescript": "^5.9.0"
}, },
"engines": { "engines": {
"node": ">=20" "node": ">=20"
},
"peerDependencies": {
"mssql": "^12.0.0"
},
"peerDependenciesMeta": {
"mssql": {
"optional": true
}
} }
}, },
"packages/mysql": { "packages/mysql": {
"name": "@crudy/restloom-mysql", "name": "@crudy/restloom-mysql",
"version": "0.3.1", "version": "0.5.0",
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
"@crudy/restloom": "^0.3.0" "@crudy/restloom": "^0.5.0",
"mysql2": "^3.24.5"
}, },
"devDependencies": { "devDependencies": {
"@types/node": "^24.0.0", "@types/node": "^24.0.0",
"mysql2": "^3.24.5",
"typescript": "^5.9.0" "typescript": "^5.9.0"
}, },
"engines": { "engines": {
"node": ">=20" "node": ">=20"
},
"peerDependencies": {
"mysql2": "^3.11.0"
},
"peerDependenciesMeta": {
"mysql2": {
"optional": true
}
} }
}, },
"packages/postgres": { "packages/postgres": {
"name": "@crudy/restloom-postgres", "name": "@crudy/restloom-postgres",
"version": "0.3.1", "version": "0.5.0",
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
"@crudy/restloom": "^0.3.0" "@crudy/restloom": "^0.5.0",
"pg": "^8.16.0"
}, },
"devDependencies": { "devDependencies": {
"@types/node": "^24.0.0", "@types/node": "^24.0.0",
"@types/pg": "^8.11.0", "@types/pg": "^8.11.0",
"pg": "^8.16.0",
"typescript": "^5.9.0" "typescript": "^5.9.0"
}, },
"engines": { "engines": {
"node": ">=20" "node": ">=20"
},
"peerDependencies": {
"pg": "^8.11.0"
},
"peerDependenciesMeta": {
"pg": {
"optional": true
}
} }
}, },
"packages/sqlite": { "packages/sqlite": {
"name": "@crudy/restloom-sqlite", "name": "@crudy/restloom-sqlite",
"version": "0.3.1", "version": "0.5.0",
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
"@crudy/restloom": "^0.3.0" "@crudy/restloom": "^0.5.0"
}, },
"devDependencies": { "devDependencies": {
"@types/better-sqlite3": "^7.6.0", "@types/better-sqlite3": "^7.6.0",
+5 -5
View File
@@ -32,19 +32,19 @@ Your backend stays in charge:
| `@crudy/restloom-express` | Express handler | | `@crudy/restloom-express` | Express handler |
```sh ```sh
npm install @crudy/restloom @crudy/restloom-postgres pg npm install @crudy/restloom-postgres
``` ```
A database package brings the core and its driver, and exports the whole core.
## Use ## Use
```ts ```ts
import http from 'node:http'; import http from 'node:http';
import pg from 'pg'; import { createREST, nodeListener, postgres } from '@crudy/restloom-postgres';
import { createREST, nodeListener } from '@crudy/restloom';
import { postgres } from '@crudy/restloom-postgres';
const rest = createREST({ const rest = createREST({
db: postgres(new pg.Pool()), db: postgres(process.env.DATABASE_URL),
expose: { schemas: ['app'] }, expose: { schemas: ['app'] },
resources: { todos: { write: true } }, resources: { todos: { write: true } },
}); });
+2 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@crudy/restloom", "name": "@crudy/restloom",
"version": "0.3.1", "version": "0.5.0",
"type": "module", "type": "module",
"description": "A REST API generated from your database's catalog: discovery, a curl-friendly query grammar, relations of every kind, safe defaults", "description": "A REST API generated from your database's catalog: discovery, a curl-friendly query grammar, relations of every kind, safe defaults",
"keywords": ["rest", "api", "postgrest", "sql", "database", "crud", "generator"], "keywords": ["rest", "api", "postgrest", "sql", "database", "crud", "generator"],
@@ -12,6 +12,7 @@
".": { "types": "./dist/index.d.ts", "default": "./dist/index.js" } ".": { "types": "./dist/index.d.ts", "default": "./dist/index.js" }
}, },
"files": ["dist", "LICENSE", "README.md"], "files": ["dist", "LICENSE", "README.md"],
"publishConfig": { "access": "public" },
"engines": { "node": ">=20" }, "engines": { "node": ">=20" },
"scripts": { "scripts": {
"build": "tsc -p .", "build": "tsc -p .",
+1 -1
View File
@@ -2,7 +2,7 @@ export { createREST, type Rest } from './rest.js';
export { nodeListener, type NodeListenerOptions } from './node.js'; export { nodeListener, type NodeListenerOptions } from './node.js';
export { RestError, badRequest, notFound } from './errors.js'; export { RestError, badRequest, notFound } from './errors.js';
export { DEFAULT_LIMITS, DEFAULT_SENSITIVE } from './options.js'; export { DEFAULT_LIMITS, DEFAULT_SENSITIVE } from './options.js';
export type { Hooks, Limits, ProcedureOptions, RelationOptions, ResourceOptions, RestOptions, RestRequest, RestResponse, WriteOptions } from './options.js'; export type { DefaultWriteOptions, Hooks, Limits, ProcedureOptions, RelationOptions, ResourceOptions, RestOptions, RestRequest, RestResponse, WriteOptions } from './options.js';
export type { Capabilities, Dialect, Tx } from './dialect.js'; export type { Capabilities, Dialect, Tx } from './dialect.js';
export type { Catalog, CatalogColumn, CatalogForeignKey, CatalogFunction, CatalogTable, LogicalType } from './catalog.js'; export type { Catalog, CatalogColumn, CatalogForeignKey, CatalogFunction, CatalogTable, LogicalType } from './catalog.js';
export type { Column, Junction, Relation, Resource } from './resources.js'; export type { Column, Junction, Relation, Resource } from './resources.js';
+10 -2
View File
@@ -1,9 +1,10 @@
import type { IncomingMessage, ServerResponse } from 'node:http'; import type { IncomingMessage, ServerResponse } from 'node:http';
import { RestError } from './errors.js';
import type { RestResponse } from './options.js'; import type { RestResponse } from './options.js';
import type { Rest } from './rest.js'; import type { Rest } from './rest.js';
export interface NodeListenerOptions<C> { export interface NodeListenerOptions<C> {
/** Builds the context hooks receive, e.g. the authenticated user. */ /** Builds the context hooks receive, e.g. the authenticated user. Throw a RestError to refuse the request. */
context?(req: IncomingMessage): C | Promise<C>; context?(req: IncomingMessage): C | Promise<C>;
/** Path the API is mounted under, e.g. /api. */ /** Path the API is mounted under, e.g. /api. */
prefix?: string; prefix?: string;
@@ -47,6 +48,13 @@ export function nodeListener<C>(rest: Rest<C>, options: NodeListenerOptions<C> =
const status = (err as { status?: number }).status ?? 400; const status = (err as { status?: number }).status ?? 400;
return send(res, { status, headers: {}, body: { error: status === 413 ? 'body_too_large' : 'bad_body' } }); return send(res, { status, headers: {}, body: { error: status === 413 ? 'body_too_large' : 'bad_body' } });
} }
let context: C;
try {
context = (await options.context?.(req)) as C;
} catch (err) {
const refusal = err instanceof RestError ? err : new RestError(500, 'internal');
return send(res, { status: refusal.status, headers: {}, body: { error: refusal.code, ...(refusal.detail ? { message: refusal.detail } : {}) } });
}
const headers = Object.fromEntries(Object.entries(req.headers).map(([k, v]) => [k, Array.isArray(v) ? v.join(', ') : v])); const headers = Object.fromEntries(Object.entries(req.headers).map(([k, v]) => [k, Array.isArray(v) ? v.join(', ') : v]));
send(res, await rest.handle({ send(res, await rest.handle({
method: req.method ?? 'GET', method: req.method ?? 'GET',
@@ -54,7 +62,7 @@ export function nodeListener<C>(rest: Rest<C>, options: NodeListenerOptions<C> =
query: url.searchParams, query: url.searchParams,
headers, headers,
body, body,
context: (await options.context?.(req)) as C, context,
})); }));
}; };
} }
+13
View File
@@ -34,6 +34,14 @@ export interface WriteOptions {
bulk?: boolean; bulk?: boolean;
} }
/** What a global `write` allows on each table: whole methods, since a column list belongs to one resource. */
export interface DefaultWriteOptions {
create?: boolean;
update?: boolean;
delete?: boolean;
bulk?: boolean;
}
export interface ResourceOptions { export interface ResourceOptions {
/** false removes a discovered table or view from the API. */ /** false removes a discovered table or view from the API. */
expose?: boolean; expose?: boolean;
@@ -92,6 +100,11 @@ export interface RestOptions<C = unknown> {
db: Dialect; db: Dialect;
/** Nothing is served until a schema or a table is named here. */ /** Nothing is served until a schema or a table is named here. */
expose: { schemas?: string[]; tables?: string[] }; expose: { schemas?: string[]; tables?: string[] };
/**
* Writes on every exposed table, unless its resource sets its own `write`.
* Views stay read-only until their resource allows writes.
*/
write?: boolean | DefaultWriteOptions;
resources?: Record<string, ResourceOptions>; resources?: Record<string, ResourceOptions>;
procedures?: Record<string, ProcedureOptions>; procedures?: Record<string, ProcedureOptions>;
/** Replaces the default list of sensitive name patterns. */ /** Replaces the default list of sensitive name patterns. */
+3 -2
View File
@@ -92,8 +92,9 @@ export function buildResources(catalog: Catalog, options: RestOptions<unknown>):
const resources = new Map<string, Resource>(); const resources = new Map<string, Resource>();
for (const t of tables) { for (const t of tables) {
const name = named.get(t)!; const name = named.get(t)!;
const o = config[name] ?? {}; const own = config[name] ?? {};
if (o.expose === false) continue; if (own.expose === false) continue;
const o = own.write === undefined && t.kind === 'table' ? { ...own, write: options.write } : own;
const key = o.key ?? (t.primaryKey.length ? t.primaryKey : t.columns.some((c) => c.name === 'id') ? ['id'] : []); const key = o.key ?? (t.primaryKey.length ? t.primaryKey : t.columns.some((c) => c.name === 'id') ? ['id'] : []);
const w = o.write === true ? { delete: true, bulk: false } : o.write || {}; const w = o.write === true ? { delete: true, bulk: false } : o.write || {};
const columns = columnsOf(t, o, sensitive); const columns = columnsOf(t, o, sensitive);
+24 -1
View File
@@ -1,7 +1,7 @@
import { test } from 'node:test'; import { test } from 'node:test';
import assert from 'node:assert/strict'; import assert from 'node:assert/strict';
import http from 'node:http'; import http from 'node:http';
import { createREST, nodeListener } from '../dist/index.js'; import { createREST, nodeListener, RestError } from '../dist/index.js';
import { fakeDialect } from './fake-dialect.mjs'; import { fakeDialect } from './fake-dialect.mjs';
test('the node listener serves under its prefix and refuses bad bodies', async () => { test('the node listener serves under its prefix and refuses bad bodies', async () => {
@@ -24,3 +24,26 @@ test('Prefer: count=exact counts as count=exact does', async () => {
const res = await rest.handle({ method: 'GET', path: '/items', query: new URLSearchParams(), headers: { prefer: 'count=exact' }, context: undefined }); const res = await rest.handle({ method: 'GET', path: '/items', query: new URLSearchParams(), headers: { prefer: 'count=exact' }, context: undefined });
assert.equal(res.headers['content-range'], '0-0/1'); assert.equal(res.headers['content-range'], '0-0/1');
}); });
test('a context that throws answers the request instead of stopping the process', async () => {
const rest = createREST({ db: fakeDialect(), expose: { schemas: ['app'] } });
const server = http.createServer(nodeListener(rest, {
context(req) {
if (req.headers.authorization === 'bad') throw new RestError(401, 'unauthenticated');
if (req.headers.authorization === 'broken') throw new Error('database down');
return {};
},
})).listen(0);
const base = `http://127.0.0.1:${server.address().port}`;
try {
const refused = await fetch(`${base}/items`, { headers: { authorization: 'bad' } });
assert.equal(refused.status, 401);
assert.deepEqual(await refused.json(), { error: 'unauthenticated' });
const broken = await fetch(`${base}/items`, { headers: { authorization: 'broken' } });
assert.equal(broken.status, 500);
assert.deepEqual(await broken.json(), { error: 'internal' });
assert.equal((await fetch(`${base}/items`)).status, 200);
} finally {
server.close();
}
});
+19 -1
View File
@@ -1,6 +1,7 @@
import { test } from 'node:test'; import { test } from 'node:test';
import assert from 'node:assert/strict'; import assert from 'node:assert/strict';
import { resources } from './fixture.mjs'; import { buildResources } from '../dist/resources.js';
import { catalog, resources } from './fixture.mjs';
test('only exposed schemas become resources', () => { test('only exposed schemas become resources', () => {
const r = resources(); const r = resources();
@@ -40,6 +41,23 @@ test('writes are off by default and never reach generated or key columns', () =>
assert.equal(a.write.delete, true); assert.equal(a.write.delete, true);
}); });
test('a global write applies to every table that does not set its own', () => {
const r = resources({ write: { create: true, update: true, delete: true }, resources: { tags: { write: false }, users: { write: { update: ['name'] } } } });
assert.deepEqual(r.get('todos').write, { create: true, update: true, delete: true, bulk: false });
assert.equal(r.get('todos').columns.get('id').update, false);
assert.deepEqual(r.get('tags').write, { create: false, update: false, delete: false, bulk: false });
assert.deepEqual(r.get('users').write, { create: false, update: true, delete: false, bulk: false });
assert.equal(r.get('users').columns.has('password_hash'), false);
});
test('a global write leaves views read-only', () => {
const c = catalog();
c.tables.push({ ...c.tables.find((t) => t.name === 'tags'), name: 'tag_view', kind: 'view', primaryKey: [] });
const r = buildResources(c, { db: null, expose: { schemas: ['demo'] }, write: true });
assert.equal(r.get('tag_view').write.create, false);
assert.equal(r.get('tags').write.create, true);
});
test('options naming unknown resources or columns fail at load', () => { test('options naming unknown resources or columns fail at load', () => {
assert.throws(() => resources({ resources: { nope: {} } }), /no exposed table/); assert.throws(() => resources({ resources: { nope: {} } }), /no exposed table/);
assert.throws(() => resources({ resources: { users: { hide: ['nope'] } } }), /no column nope/); assert.throws(() => resources({ resources: { users: { hide: ['nope'] } } }), /no column nope/);
+3 -2
View File
@@ -1,6 +1,6 @@
{ {
"name": "@crudy/restloom-express", "name": "@crudy/restloom-express",
"version": "0.3.1", "version": "0.5.0",
"type": "module", "type": "module",
"description": "Express adapter for restloom", "description": "Express adapter for restloom",
"keywords": [ "keywords": [
@@ -28,6 +28,7 @@
"LICENSE", "LICENSE",
"README.md" "README.md"
], ],
"publishConfig": { "access": "public" },
"engines": { "engines": {
"node": ">=20" "node": ">=20"
}, },
@@ -36,7 +37,7 @@
"prepack": "npm run build" "prepack": "npm run build"
}, },
"dependencies": { "dependencies": {
"@crudy/restloom": "^0.3.0" "@crudy/restloom": "^0.5.0"
}, },
"peerDependencies": { "peerDependencies": {
"express": "^5.0.0" "express": "^5.0.0"
+3 -2
View File
@@ -1,6 +1,6 @@
{ {
"name": "@crudy/restloom-fastify", "name": "@crudy/restloom-fastify",
"version": "0.3.1", "version": "0.5.0",
"type": "module", "type": "module",
"description": "Fastify adapter for restloom", "description": "Fastify adapter for restloom",
"keywords": [ "keywords": [
@@ -28,6 +28,7 @@
"LICENSE", "LICENSE",
"README.md" "README.md"
], ],
"publishConfig": { "access": "public" },
"engines": { "engines": {
"node": ">=20" "node": ">=20"
}, },
@@ -36,7 +37,7 @@
"prepack": "npm run build" "prepack": "npm run build"
}, },
"dependencies": { "dependencies": {
"@crudy/restloom": "^0.3.0" "@crudy/restloom": "^0.5.0"
}, },
"peerDependencies": { "peerDependencies": {
"fastify": "^5.0.0" "fastify": "^5.0.0"
+4 -9
View File
@@ -1,6 +1,6 @@
{ {
"name": "@crudy/restloom-mssql", "name": "@crudy/restloom-mssql",
"version": "0.3.1", "version": "0.5.0",
"type": "module", "type": "module",
"description": "SQL Server dialect for restloom", "description": "SQL Server dialect for restloom",
"keywords": [ "keywords": [
@@ -29,6 +29,7 @@
"LICENSE", "LICENSE",
"README.md" "README.md"
], ],
"publishConfig": { "access": "public" },
"engines": { "engines": {
"node": ">=20" "node": ">=20"
}, },
@@ -37,18 +38,12 @@
"prepack": "npm run build" "prepack": "npm run build"
}, },
"dependencies": { "dependencies": {
"@crudy/restloom": "^0.3.0" "@crudy/restloom": "^0.5.0",
}, "mssql": "^12.7.10"
"peerDependencies": {
"mssql": "^12.0.0"
},
"peerDependenciesMeta": {
"mssql": { "optional": true }
}, },
"devDependencies": { "devDependencies": {
"@types/node": "^24.0.0", "@types/node": "^24.0.0",
"@types/mssql": "^12.0.0", "@types/mssql": "^12.0.0",
"mssql": "12.7.10",
"typescript": "^5.9.0" "typescript": "^5.9.0"
} }
} }
+19 -5
View File
@@ -1,8 +1,21 @@
import { RestError, type Dialect, type SqlRenderer } from '@crudy/restloom'; import { RestError, type Dialect, type SqlRenderer } from '@crudy/restloom';
import mssqlDriver, { type ConnectionPool } from 'mssql'; import mssqlDriver, { type ConnectionPool, type config as MssqlConfig } from 'mssql';
import { introspect } from './introspect.js'; import { introspect } from './introspect.js';
import { MssqlRenderer } from './renderer.js'; import { MssqlRenderer } from './renderer.js';
export * from '@crudy/restloom';
/** A connected mssql ConnectionPool, or what opens one: a connection string or config. */
export type MssqlConnection = ConnectionPool | MssqlConfig | string;
function poolOf(c: MssqlConnection): Promise<ConnectionPool> {
if (typeof c !== 'string' && typeof (c as ConnectionPool).request === 'function') return Promise.resolve(c as ConnectionPool);
const ready = new mssqlDriver.ConnectionPool(c as MssqlConfig).connect();
// A failed connection surfaces on the first request, not as an unhandled rejection at startup.
ready.catch(() => undefined);
return ready;
}
export { MssqlRenderer } from './renderer.js'; export { MssqlRenderer } from './renderer.js';
const CODES: Record<number, [number, string]> = { const CODES: Record<number, [number, string]> = {
@@ -37,20 +50,21 @@ export interface MssqlOptions {
} }
/** /**
* The SQL Server 2022 dialect over an mssql ConnectionPool. Each request is one * The SQL Server 2022 dialect over an mssql ConnectionPool, given or opened. Each request is one
* transaction; the statement timeout is the pool's `requestTimeout`, since the * transaction; the statement timeout is the pool's `requestTimeout`, since the
* driver sets it per pool. Row security is SQL Server's security policies, * driver sets it per pool. Row security is SQL Server's security policies,
* which a `transaction` hook feeds through sp_set_session_context. * which a `transaction` hook feeds through sp_set_session_context.
*/ */
export function mssql(pool: ConnectionPool, options: MssqlOptions = {}): Dialect { export function mssql(connection: MssqlConnection, options: MssqlOptions = {}): Dialect {
const ready = poolOf(connection);
return { return {
name: 'mssql', name: 'mssql',
capabilities: { returning: true, estimatedCount: false, rowSecurity: true, procedures: true }, capabilities: { returning: true, estimatedCount: false, rowSecurity: true, procedures: true },
renderer: options.renderer ?? new MssqlRenderer(), renderer: options.renderer ?? new MssqlRenderer(),
decode: (value) => (typeof value === 'string' ? JSON.parse(value) : value), decode: (value) => (typeof value === 'string' ? JSON.parse(value) : value),
introspect: (scope) => introspect(pool, scope), introspect: async (scope) => introspect(await ready, scope),
async transaction(fn) { async transaction(fn) {
const tx = new mssqlDriver.Transaction(pool); const tx = new mssqlDriver.Transaction(await ready);
await tx.begin(); await tx.begin();
try { try {
const out = await fn({ const out = await fn({
+4 -9
View File
@@ -1,6 +1,6 @@
{ {
"name": "@crudy/restloom-mysql", "name": "@crudy/restloom-mysql",
"version": "0.3.1", "version": "0.5.0",
"type": "module", "type": "module",
"description": "MySQL dialect for restloom", "description": "MySQL dialect for restloom",
"keywords": [ "keywords": [
@@ -28,6 +28,7 @@
"LICENSE", "LICENSE",
"README.md" "README.md"
], ],
"publishConfig": { "access": "public" },
"engines": { "engines": {
"node": ">=20" "node": ">=20"
}, },
@@ -36,17 +37,11 @@
"prepack": "npm run build" "prepack": "npm run build"
}, },
"dependencies": { "dependencies": {
"@crudy/restloom": "^0.3.0" "@crudy/restloom": "^0.5.0",
}, "mysql2": "^3.24.5"
"peerDependencies": {
"mysql2": "^3.11.0"
},
"peerDependenciesMeta": {
"mysql2": { "optional": true }
}, },
"devDependencies": { "devDependencies": {
"@types/node": "^24.0.0", "@types/node": "^24.0.0",
"mysql2": "^3.24.5",
"typescript": "^5.9.0" "typescript": "^5.9.0"
} }
} }
+12 -3
View File
@@ -1,8 +1,16 @@
import { RestError, type Dialect, type SqlRenderer } from '@crudy/restloom'; import { RestError, type Dialect, type SqlRenderer } from '@crudy/restloom';
import type { Pool } from 'mysql2/promise'; import { createPool, type Pool, type PoolOptions } from 'mysql2/promise';
import { introspect } from './introspect.js'; import { introspect } from './introspect.js';
import { MysqlRenderer } from './renderer.js'; import { MysqlRenderer } from './renderer.js';
export * from '@crudy/restloom';
/** A mysql2/promise Pool, or what opens one: a connection URL or pool options. */
export type MysqlConnection = Pool | PoolOptions | string;
const poolOf = (c: MysqlConnection): Pool =>
typeof c === 'string' ? createPool({ uri: c }) : typeof (c as Pool).getConnection === 'function' ? (c as Pool) : createPool(c as PoolOptions);
export { MysqlRenderer } from './renderer.js'; export { MysqlRenderer } from './renderer.js';
const CODES: Record<number, [number, string]> = { const CODES: Record<number, [number, string]> = {
@@ -31,12 +39,13 @@ export interface MysqlOptions {
} }
/** /**
* The MySQL 8 dialect over a mysql2/promise Pool. Each request is one * The MySQL 8 dialect over a mysql2/promise Pool, given or opened. Each request is one
* transaction on one connection, in UTC, with MAX_EXECUTION_TIME for reads. * transaction on one connection, in UTC, with MAX_EXECUTION_TIME for reads.
* Text compares with each column's collation, so with a case-insensitive * Text compares with each column's collation, so with a case-insensitive
* collation `eq` ignores case. * collation `eq` ignores case.
*/ */
export function mysql(pool: Pool, options: MysqlOptions = {}): Dialect { export function mysql(connection: MysqlConnection, options: MysqlOptions = {}): Dialect {
const pool = poolOf(connection);
return { return {
name: 'mysql', name: 'mysql',
capabilities: { returning: false, estimatedCount: false, rowSecurity: false, procedures: true }, capabilities: { returning: false, estimatedCount: false, rowSecurity: false, procedures: true },
+4 -9
View File
@@ -1,6 +1,6 @@
{ {
"name": "@crudy/restloom-postgres", "name": "@crudy/restloom-postgres",
"version": "0.3.1", "version": "0.5.0",
"type": "module", "type": "module",
"description": "Postgres dialect for restloom", "description": "Postgres dialect for restloom",
"keywords": ["restloom", "postgres", "postgresql", "rest", "api"], "keywords": ["restloom", "postgres", "postgresql", "rest", "api"],
@@ -12,24 +12,19 @@
".": { "types": "./dist/index.d.ts", "default": "./dist/index.js" } ".": { "types": "./dist/index.d.ts", "default": "./dist/index.js" }
}, },
"files": ["dist", "LICENSE", "README.md"], "files": ["dist", "LICENSE", "README.md"],
"publishConfig": { "access": "public" },
"engines": { "node": ">=20" }, "engines": { "node": ">=20" },
"scripts": { "scripts": {
"build": "tsc -p .", "build": "tsc -p .",
"prepack": "npm run build" "prepack": "npm run build"
}, },
"dependencies": { "dependencies": {
"@crudy/restloom": "^0.3.0" "@crudy/restloom": "^0.5.0",
}, "pg": "^8.16.0"
"peerDependencies": {
"pg": "^8.11.0"
},
"peerDependenciesMeta": {
"pg": { "optional": true }
}, },
"devDependencies": { "devDependencies": {
"@types/node": "^24.0.0", "@types/node": "^24.0.0",
"@types/pg": "^8.11.0", "@types/pg": "^8.11.0",
"pg": "^8.16.0",
"typescript": "^5.9.0" "typescript": "^5.9.0"
} }
} }
+12 -3
View File
@@ -1,10 +1,17 @@
import { SqlRenderer, type Dialect, type Tx } from '@crudy/restloom'; import { SqlRenderer, type Dialect, type Tx } from '@crudy/restloom';
import type { Pool } from 'pg'; import pg, { type Pool, type PoolConfig } from 'pg';
import { mapError } from './errors.js'; import { mapError } from './errors.js';
import { introspect } from './introspect.js'; import { introspect } from './introspect.js';
export * from '@crudy/restloom';
export { mapError } from './errors.js'; export { mapError } from './errors.js';
/** A pg Pool, or what opens one: a connection string or a pool config. */
export type PostgresConnection = Pool | PoolConfig | string;
const poolOf = (c: PostgresConnection): Pool =>
typeof c === 'string' ? new pg.Pool({ connectionString: c }) : typeof (c as Pool).connect === 'function' ? (c as Pool) : new pg.Pool(c as PoolConfig);
/** Postgres writes every primitive the core's defaults assume; subclass it to change one. */ /** Postgres writes every primitive the core's defaults assume; subclass it to change one. */
export class PostgresRenderer extends SqlRenderer {} export class PostgresRenderer extends SqlRenderer {}
@@ -13,11 +20,13 @@ export interface PostgresOptions {
} }
/** /**
* The Postgres dialect over a `pg` Pool. Every request runs in one transaction * The Postgres dialect over a `pg` Pool, given or opened from a connection
* string or config. Every request runs in one transaction
* with a local statement timeout, read-only for reads, so a `transaction` hook * with a local statement timeout, read-only for reads, so a `transaction` hook
* can set the role and session settings that row security reads. * can set the role and session settings that row security reads.
*/ */
export function postgres(pool: Pool, options: PostgresOptions = {}): Dialect { export function postgres(connection: PostgresConnection, options: PostgresOptions = {}): Dialect {
const pool = poolOf(connection);
return { return {
name: 'postgres', name: 'postgres',
capabilities: { returning: true, estimatedCount: true, rowSecurity: true, procedures: true }, capabilities: { returning: true, estimatedCount: true, rowSecurity: true, procedures: true },
+3 -2
View File
@@ -1,6 +1,6 @@
{ {
"name": "@crudy/restloom-sqlite", "name": "@crudy/restloom-sqlite",
"version": "0.3.1", "version": "0.5.0",
"type": "module", "type": "module",
"description": "SQLite dialect for restloom", "description": "SQLite dialect for restloom",
"keywords": [ "keywords": [
@@ -28,6 +28,7 @@
"LICENSE", "LICENSE",
"README.md" "README.md"
], ],
"publishConfig": { "access": "public" },
"engines": { "engines": {
"node": ">=20" "node": ">=20"
}, },
@@ -36,7 +37,7 @@
"prepack": "npm run build" "prepack": "npm run build"
}, },
"dependencies": { "dependencies": {
"@crudy/restloom": "^0.3.0" "@crudy/restloom": "^0.5.0"
}, },
"peerDependencies": { "peerDependencies": {
"better-sqlite3": ">=12" "better-sqlite3": ">=12"
+4 -1
View File
@@ -3,6 +3,8 @@ import type { Database } from 'better-sqlite3';
import { introspect } from './introspect.js'; import { introspect } from './introspect.js';
import { SqliteRenderer } from './renderer.js'; import { SqliteRenderer } from './renderer.js';
export * from '@crudy/restloom';
export { SqliteRenderer } from './renderer.js'; export { SqliteRenderer } from './renderer.js';
const CODES: Record<string, [number, string]> = { const CODES: Record<string, [number, string]> = {
@@ -27,7 +29,8 @@ export interface SqliteOptions {
} }
/** /**
* The SQLite dialect over a better-sqlite3 Database. It turns on foreign keys * The SQLite dialect over a better-sqlite3 Database, or any object with its
* API, such as sql.js in a browser. It turns on foreign keys
* and case-sensitive LIKE for the connection. One connection runs one * and case-sensitive LIKE for the connection. One connection runs one
* transaction at a time, so requests are queued. SQLite cannot stop a running * transaction at a time, so requests are queued. SQLite cannot stop a running
* statement, so `limits.timeoutMs` does not apply, and it has no functions to * statement, so `limits.timeoutMs` does not apply, and it has no functions to