From e7637c37048ff333d2bb563a900db2ce58ce3a62 Mon Sep 17 00:00:00 2001 From: Marc Villain Date: Tue, 3 Nov 2026 13:06:04 +0100 Subject: [PATCH] feat: one install and one import per database; server dialects open their pool from a URL; nodeListener answers a throwing context; 0.5.0 --- CHANGELOG.md | 12 +++ README.md | 10 +- conformance/connections.test.mjs | 27 ++++++ conformance/dialects-doc.test.mjs | 6 +- docs/adapters.md | 10 +- docs/dialects.md | 18 ++-- docs/getting-started.md | 24 ++--- docs/security.md | 4 +- docs/writes.md | 2 +- package-lock.json | 152 ++++-------------------------- packages/core/README.md | 10 +- packages/core/package.json | 2 +- packages/core/src/node.ts | 12 ++- packages/core/test/node.test.mjs | 25 ++++- packages/express/package.json | 4 +- packages/fastify/package.json | 4 +- packages/mssql/package.json | 12 +-- packages/mssql/src/index.ts | 24 ++++- packages/mysql/package.json | 12 +-- packages/mysql/src/index.ts | 15 ++- packages/postgres/package.json | 12 +-- packages/postgres/src/index.ts | 15 ++- packages/sqlite/package.json | 4 +- packages/sqlite/src/index.ts | 5 +- 24 files changed, 194 insertions(+), 227 deletions(-) create mode 100644 conformance/connections.test.mjs diff --git a/CHANGELOG.md b/CHANGELOG.md index 314ab35..f34358b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,18 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), ## [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 diff --git a/README.md b/README.md index b00405f..9319b0b 100644 --- a/README.md +++ b/README.md @@ -37,19 +37,19 @@ Your backend stays in charge: | `@crudy/restloom-express` | Express handler | ```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 ```ts import http from 'node:http'; -import pg from 'pg'; -import { createREST, nodeListener } from '@crudy/restloom'; -import { postgres } from '@crudy/restloom-postgres'; +import { createREST, nodeListener, postgres } from '@crudy/restloom-postgres'; const rest = createREST({ - db: postgres(new pg.Pool()), + db: postgres(process.env.DATABASE_URL), expose: { schemas: ['app'] }, resources: { todos: { write: true } }, }); diff --git a/conformance/connections.test.mjs b/conformance/connections.test.mjs new file mode 100644 index 0000000..41fb814 --- /dev/null +++ b/conformance/connections.test.mjs @@ -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 }]); +}); diff --git a/conformance/dialects-doc.test.mjs b/conformance/dialects-doc.test.mjs index 8c449e5..e63c70f 100644 --- a/conformance/dialects-doc.test.mjs +++ b/conformance/dialects-doc.test.mjs @@ -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 // site too. It must say what each dialect's capabilities say. const DIALECTS = { - '@crudy/restloom-postgres': postgres({}), - '@crudy/restloom-mysql': mysql({}), - '@crudy/restloom-mssql': mssql({}), + '@crudy/restloom-postgres': postgres({ connect() {} }), + '@crudy/restloom-mysql': mysql({ getConnection() {} }), + '@crudy/restloom-mssql': mssql({ request() {} }), '@crudy/restloom-sqlite': sqlite({ pragma() {} }), }; diff --git a/docs/adapters.md b/docs/adapters.md index e18c681..f6f6479 100644 --- a/docs/adapters.md +++ b/docs/adapters.md @@ -3,12 +3,10 @@ The core answers a plain request object, so it fits any framework: ```ts -import pg from 'pg'; -import { createREST } from '@crudy/restloom'; -import { postgres } from '@crudy/restloom-postgres'; +import { createREST, postgres } from '@crudy/restloom-postgres'; const rest = createREST({ - db: postgres(new pg.Pool({ connectionString: process.env.DATABASE_URL })), + db: postgres(process.env.DATABASE_URL), expose: { schemas: ['app'] }, }); @@ -22,11 +20,11 @@ The examples below use this `rest`. ## Node, without a framework -`nodeListener` from the core serves `node:http` with nothing else installed. It mounts the API under `prefix` and answers `404 not_found` outside it, reads JSON bodies up to 1 MiB (`maxBodyBytes`), and builds each request's context with `context`: +`nodeListener` from the core serves `node:http` with nothing else installed. It mounts the API under `prefix` and answers `404 not_found` outside it, reads JSON bodies up to 1 MiB (`maxBodyBytes`), and builds each request's context with `context`. A `context` that throws a `RestError` refuses the request with it; any other error answers `500 internal`: ```ts import http from 'node:http'; -import { nodeListener } from '@crudy/restloom'; +import { nodeListener } from '@crudy/restloom-postgres'; const api = nodeListener(rest, { prefix: '/api', context: (req) => userFrom(req) }); http.createServer(api).listen(3000); diff --git a/docs/dialects.md b/docs/dialects.md index 54c27ff..4c9f295 100644 --- a/docs/dialects.md +++ b/docs/dialects.md @@ -9,15 +9,16 @@ A dialect is the package that knows one database: how to read its catalog, how t | `@crudy/restloom-mssql` | SQL Server 2022+ | `JSON_OBJECT`, `STRING_AGG` | `OUTPUT` | no | security policies | scalar and table functions | | `@crudy/restloom-sqlite` | SQLite 3.44+ (better-sqlite3) | `json_object`, `json_group_array` | `RETURNING` | no | no | none | +The PostgreSQL, MySQL and SQL Server packages bring their driver and open their own connection from a URL. To tune it, pass the driver's pool options instead, or a pool you already have: a `pg` Pool, a `mysql2/promise` Pool or a connected `mssql` ConnectionPool. The SQLite package takes a database object, so it runs on `better-sqlite3` in Node or on sql.js in a browser; install the one you use. + Every dialect passes the same conformance suite, so a request answers the same rows on each. Regular expressions (`match`, `imatch`) need Postgres or MySQL, and array filters (`ov`, `cs`, `cd`) Postgres array columns; elsewhere they answer `unsupported`. Where else the dialects differ, the sections below say how. ## Postgres ```ts -import pg from 'pg'; import { postgres } from '@crudy/restloom-postgres'; -const db = postgres(new pg.Pool({ connectionString: process.env.DATABASE_URL })); +const db = postgres(process.env.DATABASE_URL); ``` - **Transactions:** every request is one transaction, `READ ONLY` for reads, with `SET LOCAL statement_timeout` from `limits.timeoutMs`. @@ -28,10 +29,9 @@ const db = postgres(new pg.Pool({ connectionString: process.env.DATABASE_URL })) ## MySQL ```ts -import { createPool } from 'mysql2/promise'; import { mysql } from '@crudy/restloom-mysql'; -const db = mysql(createPool({ uri: process.env.DATABASE_URL })); +const db = mysql(process.env.DATABASE_URL); ``` - **Schemas:** MySQL's schemas are its databases: `expose: { schemas: ['shop'] }`. @@ -44,10 +44,9 @@ const db = mysql(createPool({ uri: process.env.DATABASE_URL })); ## SQL Server ```ts -import sql from 'mssql'; import { mssql } from '@crudy/restloom-mssql'; -const db = mssql(await new sql.ConnectionPool(process.env.DATABASE_URL).connect()); +const db = mssql(process.env.DATABASE_URL); ``` - **Version:** SQL Server 2022 or later, for `JSON_OBJECT`. @@ -59,6 +58,10 @@ const db = mssql(await new sql.ConnectionPool(process.env.DATABASE_URL).connect( ## SQLite +```sh +npm install @crudy/restloom-sqlite better-sqlite3 +``` + ```ts import Database from 'better-sqlite3'; import { sqlite } from '@crudy/restloom-sqlite'; @@ -77,7 +80,6 @@ const db = sqlite(new Database('app.db')); Every piece of SQL comes from one overridable method of the dialect's renderer. To change one, subclass it and pass it in: ```ts -import pg from 'pg'; import { PostgresRenderer, postgres } from '@crudy/restloom-postgres'; class Renderer extends PostgresRenderer { @@ -86,7 +88,7 @@ class Renderer extends PostgresRenderer { } } -const db = postgres(new pg.Pool({ connectionString: process.env.DATABASE_URL }), { renderer: new Renderer() }); +const db = postgres(process.env.DATABASE_URL, { renderer: new Renderer() }); ``` The pieces are listed in [writing a dialect](./writing-a-dialect.md). diff --git a/docs/getting-started.md b/docs/getting-started.md index 1e49519..fa4a284 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -2,29 +2,29 @@ ## Install -Install the core, the package for your database, and its driver: +Install the package for your database. It brings the core and the database driver with it, except for SQLite, which also needs `better-sqlite3`: ```sh -npm install @crudy/restloom @crudy/restloom-postgres pg +npm install @crudy/restloom-postgres ``` -| Database | Package | Driver | -|---|---|---| -| PostgreSQL 13+ | `@crudy/restloom-postgres` | `pg` | -| MySQL 8.0+ | `@crudy/restloom-mysql` | `mysql2` | -| SQL Server 2022+ | `@crudy/restloom-mssql` | `mssql` | -| SQLite 3.44+ | `@crudy/restloom-sqlite` | `better-sqlite3` | +| Database | Package | +|---|---| +| PostgreSQL 13+ | `@crudy/restloom-postgres` | +| MySQL 8.0+ | `@crudy/restloom-mysql` | +| SQL Server 2022+ | `@crudy/restloom-mssql` | +| SQLite 3.44+ | `@crudy/restloom-sqlite` | + +Each of them exports the whole core, so everything is imported from that one package. The examples in these pages use `@crudy/restloom-postgres`; with another database, import from its package instead. ## Serve a schema ```ts import http from 'node:http'; -import pg from 'pg'; -import { createREST, nodeListener } from '@crudy/restloom'; -import { postgres } from '@crudy/restloom-postgres'; +import { createREST, nodeListener, postgres } from '@crudy/restloom-postgres'; const rest = createREST({ - db: postgres(new pg.Pool({ connectionString: process.env.DATABASE_URL })), + db: postgres(process.env.DATABASE_URL), expose: { schemas: ['app'] }, }); diff --git a/docs/security.md b/docs/security.md index 46d9886..41f6772 100644 --- a/docs/security.md +++ b/docs/security.md @@ -52,7 +52,7 @@ resources: { `sensitive` replaces the whole pattern, for every resource. Start from `DEFAULT_SENSITIVE` to add words rather than lose the defaults: ```ts -import { createREST, DEFAULT_SENSITIVE } from '@crudy/restloom'; +import { createREST, DEFAULT_SENSITIVE } from '@crudy/restloom-postgres'; createREST({ db, // from a database package, see Dialects @@ -64,7 +64,7 @@ createREST({ ## Hooks ```ts -import { createREST, RestError } from '@crudy/restloom'; +import { createREST, RestError } from '@crudy/restloom-postgres'; createREST({ db, // from a database package, see Dialects diff --git a/docs/writes.md b/docs/writes.md index e36f4b4..c2a0bcc 100644 --- a/docs/writes.md +++ b/docs/writes.md @@ -28,7 +28,7 @@ procedures: { For an API that writes everywhere, set `write` once, beside `expose`: ```ts -import { createREST } from '@crudy/restloom'; +import { createREST } from '@crudy/restloom-postgres'; createREST({ db, // from a database package, see Dialects diff --git a/package-lock.json b/package-lock.json index a8a35f4..b74664f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -22,7 +22,6 @@ "version": "2.9.0", "resolved": "https://registry.npmjs.org/@azure-rest/core-client/-/core-client-2.9.0.tgz", "integrity": "sha512-6933vNLqh06RR7rumnrq3UZIeBtRLeBHDPMrieDPIljiCaMMwt0nRw++nd3TOxiohtasNtm29rC+b/9ZVKCOqg==", - "dev": true, "license": "MIT", "dependencies": { "@azure/abort-controller": "^2.1.2", @@ -40,7 +39,6 @@ "version": "2.2.0", "resolved": "https://registry.npmjs.org/@azure/abort-controller/-/abort-controller-2.2.0.tgz", "integrity": "sha512-fNAjWnA/nZ2jz31kxR/AqRaUT8ewHBw/WuBIosK0moMy1C9e5ValbDfFdIxJzVOOYaYkV/b2F1S4H/aHiqfVQg==", - "dev": true, "license": "MIT", "dependencies": { "tslib": "^2.6.2" @@ -53,7 +51,6 @@ "version": "1.11.0", "resolved": "https://registry.npmjs.org/@azure/core-auth/-/core-auth-1.11.0.tgz", "integrity": "sha512-IUZydyTUkDnYdstOW9pFOOUQlBjAepK5teihDE3x6yxsPJs/hsAaaYpeGxdxrgtOiJbBKSjKW7MDk7AEhb4LRg==", - "dev": true, "license": "MIT", "dependencies": { "@azure/abort-controller": "^2.1.2", @@ -68,7 +65,6 @@ "version": "1.11.1", "resolved": "https://registry.npmjs.org/@azure/core-client/-/core-client-1.11.1.tgz", "integrity": "sha512-2QygG2F76ZpMP2eMztiJvAiFMu71M9rDeU7vO/QKg5Css7MgM4frUOslFjhVjRhbGaCNPtz/S8M6y46/fFKVuQ==", - "dev": true, "license": "MIT", "dependencies": { "@azure/abort-controller": "^2.1.2", @@ -87,7 +83,6 @@ "version": "2.7.2", "resolved": "https://registry.npmjs.org/@azure/core-lro/-/core-lro-2.7.2.tgz", "integrity": "sha512-0YIpccoX8m/k00O7mDDMdJpbr6mf1yWo2dfmxt5A8XVZVVMz2SSKaEbMCeJRvgQ0IaSlqhjT47p4hVIRRy90xw==", - "dev": true, "license": "MIT", "dependencies": { "@azure/abort-controller": "^2.0.0", @@ -103,7 +98,6 @@ "version": "1.7.0", "resolved": "https://registry.npmjs.org/@azure/core-paging/-/core-paging-1.7.0.tgz", "integrity": "sha512-7GEAoIsaoBr6KELNRb8nypowCqvk8dnCHFCYg4XD4lOQGY2GqjQg5IhkRjyBFRO18CGSMq05PaNqSOE9GQro3g==", - "dev": true, "license": "MIT", "dependencies": { "tslib": "^2.6.2" @@ -116,7 +110,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/@azure/core-process/-/core-process-1.0.0.tgz", "integrity": "sha512-/shnJ+ooO8WPxDhPEeI/2oRQuubn16gZ6CvlbpWbEswZfzwI9tI/sMAHmF3x1LuQ9yZYXfLW3TjzGMLEC5blKg==", - "dev": true, "license": "MIT", "engines": { "node": ">=22.0.0" @@ -126,7 +119,6 @@ "version": "1.25.0", "resolved": "https://registry.npmjs.org/@azure/core-rest-pipeline/-/core-rest-pipeline-1.25.0.tgz", "integrity": "sha512-bMs8ekJLjX8wPV+9IPBges1SLPyuDtE9g5gLDWOpxzKcoOFQnpLGkbcT1tdw3FaAmDS1gnPmMmJ6y/T5B96kIA==", - "dev": true, "license": "MIT", "dependencies": { "@azure/abort-controller": "^2.1.2", @@ -145,7 +137,6 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/@azure/core-tracing/-/core-tracing-1.4.0.tgz", "integrity": "sha512-eGwxD0AtncrxeBM4tG8R55Pc3rdX1hNW2WibJAgYpCVA6E93mvvVH+LcssoVjOBrSKWS55yEIHsk0X8ctHmfOQ==", - "dev": true, "license": "MIT", "dependencies": { "tslib": "^2.6.2" @@ -158,7 +149,6 @@ "version": "1.14.0", "resolved": "https://registry.npmjs.org/@azure/core-util/-/core-util-1.14.0.tgz", "integrity": "sha512-9n2pWK61veAuN0V20t9lOuoV4CFMdyAZ1ygZzvBGk/pBBJRib/PjL9PLXa/aI2CcPpyHfqVsxxqLCYl6uZlfDw==", - "dev": true, "license": "MIT", "dependencies": { "@azure/abort-controller": "^2.1.2", @@ -173,7 +163,6 @@ "version": "4.14.1", "resolved": "https://registry.npmjs.org/@azure/identity/-/identity-4.14.1.tgz", "integrity": "sha512-HHuuFrmN1uuF9D5A6aAo5eHzNFNX4Q+P4SksZqDKse5YaJFFy/zDLG3wXHEV2FVGgetZ3QM8jGURNBtnQYf1NQ==", - "dev": true, "license": "MIT", "dependencies": { "@azure/abort-controller": "^2.0.0", @@ -197,7 +186,6 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/@azure/keyvault-common/-/keyvault-common-2.1.1.tgz", "integrity": "sha512-SxpomJRTBfDTQnuii3w10WnZNQJGPNi15tRPdPUQz7BqyB3yk976knEueS7R0H9kSLtXbP0wJZXRh0EbRTQ0lw==", - "dev": true, "license": "MIT", "dependencies": { "@azure-rest/core-client": "^2.3.3", @@ -217,7 +205,6 @@ "version": "4.10.2", "resolved": "https://registry.npmjs.org/@azure/keyvault-keys/-/keyvault-keys-4.10.2.tgz", "integrity": "sha512-VmUSLbXRAbSzDD8grXHGPaknYs0SKr3yuf6U+d4XMpX4XuVYskNqbTTwXce0zR1LyxfTZm9rWEBcvs3vdYwCmQ==", - "dev": true, "license": "MIT", "dependencies": { "@azure-rest/core-client": "^2.3.3", @@ -240,7 +227,6 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/@azure/logger/-/logger-1.4.0.tgz", "integrity": "sha512-rbAE25KUfjU/s3XHUdJgceoCP5dEOpMx85J04kF+QMdta73XkuG9JGHHinch+XIoKpBdqljin+KqURpJriSzLA==", - "dev": true, "license": "MIT", "dependencies": { "@typespec/ts-http-runtime": "^0.3.0", @@ -254,7 +240,6 @@ "version": "5.27.0", "resolved": "https://registry.npmjs.org/@azure/msal-browser/-/msal-browser-5.27.0.tgz", "integrity": "sha512-i2CKKyuCuOZEBZJiAoFK/viYZAP6CRaqqlkeHl3ViteDHOUE/tkI/Slq1cdGnwhkIB+k1OE0g00QGl9O0gAFNQ==", - "dev": true, "license": "MIT", "dependencies": { "@azure/msal-common": "16.15.0" @@ -267,7 +252,6 @@ "version": "16.15.0", "resolved": "https://registry.npmjs.org/@azure/msal-common/-/msal-common-16.15.0.tgz", "integrity": "sha512-lwdLb1B9Q5nEhPLVIKe78XH5wFMN15fCiDkY7ylPDSWZuWcD93G3jpJajrKhO3CQld8SLKz0sHiOi24mxdvtsg==", - "dev": true, "license": "MIT", "engines": { "node": ">=0.8.0" @@ -277,7 +261,6 @@ "version": "7.1.2", "resolved": "https://registry.npmjs.org/@azure/msal-node/-/msal-node-7.1.2.tgz", "integrity": "sha512-52hKQLGpEQzwNufIXG1SPVU+wQ3RIe7Rp2+g+VdAmZojCAZ6yCdGEffzQEQ6RnBZGBEs40nl14rWBmfJpCoHsg==", - "dev": true, "license": "MIT", "dependencies": { "@azure/msal-common": "16.15.0", @@ -453,7 +436,6 @@ "version": "6.3.2", "resolved": "https://registry.npmjs.org/@js-joda/core/-/core-6.3.2.tgz", "integrity": "sha512-BVsYu84Gut8YTNR1v5/PnifmqXv4SVcfA/hVIM/mXADZfIocip/DfNppBtv+4am5hGH1XuH6FSoU8OHkBvZMeA==", - "dev": true, "license": "BSD-3-Clause" }, "node_modules/@pinojs/redact": { @@ -467,7 +449,6 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/@tediousjs/connection-string/-/connection-string-1.1.0.tgz", "integrity": "sha512-z9ZBWEG+8pIB5V1zYzlRPXx0oRJ5H7coPnMQK8EZOw03UTPI9Umn6viL36f5w+CuqkKsnCM50RVStpjZmR0Bng==", - "dev": true, "license": "MIT" }, "node_modules/@types/better-sqlite3": { @@ -549,7 +530,6 @@ "version": "24.19.2", "resolved": "https://registry.npmjs.org/@types/node/-/node-24.19.2.tgz", "integrity": "sha512-93d49wCWJkCyN87MxLpNN6KXPCwFw7bVUg79mGUV1OS40JGWwVCr8n61DXCSqz7CV6/VfPkb7xec7uP2M+QHWQ==", - "dev": true, "license": "MIT", "dependencies": { "undici-types": ">=7.24.0 <7.24.7" @@ -585,7 +565,6 @@ "version": "4.0.25", "resolved": "https://registry.npmjs.org/@types/readable-stream/-/readable-stream-4.0.25.tgz", "integrity": "sha512-7GsxL8Wd5X3jh73/qgW7WCnbBvGSdD8U3E4Jiuus3/8I+PN20DtlkJtwFhr/sImXfDDQ2Z03i/9wNiIF3TVSsw==", - "dev": true, "license": "MIT", "dependencies": { "@types/node": "*" @@ -616,7 +595,6 @@ "version": "0.3.9", "resolved": "https://registry.npmjs.org/@typespec/ts-http-runtime/-/ts-http-runtime-0.3.9.tgz", "integrity": "sha512-edSdeAqkdxBVzA1yL1LrLCml1YjyCVvPMtMqJpbF+6K609tHe8V6sQUzFQSGcYNhcuhOceZtjvN32+mpIth30A==", - "dev": true, "license": "MIT", "dependencies": { "http-proxy-agent": "^7.0.0", @@ -631,7 +609,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", - "dev": true, "license": "MIT", "dependencies": { "event-target-shim": "^5.0.0" @@ -665,7 +642,6 @@ "version": "7.1.4", "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", - "dev": true, "license": "MIT", "engines": { "node": ">= 14" @@ -758,7 +734,6 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/aws-ssl-profiles/-/aws-ssl-profiles-1.1.2.tgz", "integrity": "sha512-NZKeq9AfyQvEeNlN0zSYAaWrmBffJh3IELMZfRpJVWgrpEbtEpnjvzqBPf+mxoI287JohRDoa+/nsfqqiZmF6g==", - "dev": true, "license": "MIT", "engines": { "node": ">= 6.0.0" @@ -768,7 +743,6 @@ "version": "1.5.1", "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", - "dev": true, "funding": [ { "type": "github", @@ -876,14 +850,12 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==", - "dev": true, "license": "BSD-3-Clause" }, "node_modules/bundle-name": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/bundle-name/-/bundle-name-4.1.1.tgz", "integrity": "sha512-DdH81/zPLVS11EUgWq3tEu/xn+EzljlMYooDNdzWEnFha3R3NBMpMV1UqYIpjYHV/SgpFKMIX1Oh7o06SjM/oA==", - "dev": true, "license": "MIT", "dependencies": { "run-applescript": "^7.0.0" @@ -947,7 +919,6 @@ "version": "11.1.0", "resolved": "https://registry.npmjs.org/commander/-/commander-11.1.0.tgz", "integrity": "sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==", - "dev": true, "license": "MIT", "engines": { "node": ">=16" @@ -1005,7 +976,6 @@ "version": "4.4.3", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", - "dev": true, "license": "MIT", "dependencies": { "ms": "^2.1.3" @@ -1049,7 +1019,6 @@ "version": "5.5.1", "resolved": "https://registry.npmjs.org/default-browser/-/default-browser-5.5.1.tgz", "integrity": "sha512-m1pAzaJgZ/gssEqlOhJkPJp8Xly7QyW6xcrkUa2KKcDeDSEMP7X8xipU3snUcfisTQx0w1AGae+9UtJSfVnXGw==", - "dev": true, "license": "MIT", "dependencies": { "bundle-name": "^4.1.0", @@ -1066,7 +1035,6 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/default-browser-id/-/default-browser-id-5.0.1.tgz", "integrity": "sha512-x1VCxdX4t+8wVfd1so/9w+vQ4vx7lKd2Qp5tDRutErwmR85OgmfX7RlLRMWafRMY7hbEiXIbudNrjOAPa/hL8Q==", - "dev": true, "license": "MIT", "engines": { "node": ">=18" @@ -1079,7 +1047,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-3.0.0.tgz", "integrity": "sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==", - "dev": true, "license": "MIT", "engines": { "node": ">=12" @@ -1137,7 +1104,6 @@ "version": "1.0.11", "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", - "dev": true, "license": "Apache-2.0", "dependencies": { "safe-buffer": "^5.0.1" @@ -1228,7 +1194,6 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", - "dev": true, "license": "MIT", "engines": { "node": ">=6" @@ -1238,7 +1203,6 @@ "version": "3.3.0", "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", - "dev": true, "license": "MIT", "engines": { "node": ">=0.8.x" @@ -1493,7 +1457,6 @@ "version": "2.3.1", "resolved": "https://registry.npmjs.org/generate-function/-/generate-function-2.3.1.tgz", "integrity": "sha512-eeB5GfMNeevm/GRYq20ShmsaGcmI81kIX2K9XQx5miC8KdHaC6Jm0qQ8ZNeGOi7wYB8OsdxKs+Y2oVuTFuVwKQ==", - "dev": true, "license": "MIT", "dependencies": { "is-property": "^1.0.2" @@ -1609,7 +1572,6 @@ "version": "7.0.2", "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", - "dev": true, "license": "MIT", "dependencies": { "agent-base": "^7.1.0", @@ -1623,7 +1585,6 @@ "version": "7.0.6", "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", - "dev": true, "license": "MIT", "dependencies": { "agent-base": "^7.1.2", @@ -1637,7 +1598,6 @@ "version": "0.7.3", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.3.tgz", "integrity": "sha512-IKXpvIzjnC9XTAUbVBcMfGS0EPaIXtW6v+zr+RRp+hqULEpo0owZax6wyRwPOJbWbzjYspQwusTsfVr0ifh4uQ==", - "dev": true, "license": "MIT", "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" @@ -1654,7 +1614,6 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", - "dev": true, "funding": [ { "type": "github", @@ -1675,7 +1634,6 @@ "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true, "license": "ISC" }, "node_modules/ini": { @@ -1699,7 +1657,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-3.0.0.tgz", "integrity": "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==", - "dev": true, "license": "MIT", "bin": { "is-docker": "cli.js" @@ -1715,7 +1672,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-in-ssh/-/is-in-ssh-1.0.0.tgz", "integrity": "sha512-jYa6Q9rH90kR1vKB6NM7qqd1mge3Fx4Dhw5TVlK1MUBqhEOuCagrEHMevNuCcbECmXZ0ThXkRm+Ymr51HwEPAw==", - "dev": true, "license": "MIT", "engines": { "node": ">=20" @@ -1728,7 +1684,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-inside-container/-/is-inside-container-1.0.0.tgz", "integrity": "sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==", - "dev": true, "license": "MIT", "dependencies": { "is-docker": "^3.0.0" @@ -1754,14 +1709,12 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-property/-/is-property-1.0.2.tgz", "integrity": "sha512-Ks/IoX00TtClbGQr4TWXemAnktAQvYB7HzcCxDGqEZU6oCmb2INHuOoKxbtR+HFkmYWBKv/dOZtGRiAjDhj92g==", - "dev": true, "license": "MIT" }, "node_modules/is-wsl": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-3.1.1.tgz", "integrity": "sha512-e6rvdUCiQCAuumZslxRJWR/Doq4VpPR82kqclvcS0efgt430SlGIk05vdCN58+VrzgtIcfNODjozVielycD4Sw==", - "dev": true, "license": "MIT", "dependencies": { "is-inside-container": "^1.0.0" @@ -1777,7 +1730,6 @@ "version": "0.3.2", "resolved": "https://registry.npmjs.org/js-md4/-/js-md4-0.3.2.tgz", "integrity": "sha512-/GDnfQYsltsjRswQhN9fhv3EMw2sCpUdrdxyWDOUK7eyD++r3gRhzgiQgc/x4MAv2i1iuQ4lxO5mvqM3vj4bwA==", - "dev": true, "license": "MIT" }, "node_modules/json-schema-ref-resolver": { @@ -1811,7 +1763,6 @@ "version": "9.0.3", "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.3.tgz", "integrity": "sha512-MT/xP0CrubFRNLNKvxJ2BYfy53Zkm++5bX9dtuPbqAeQpTVe0MQTFhao8+Cp//EmJp244xt6Drw/GVEGCUj40g==", - "dev": true, "license": "MIT", "dependencies": { "jws": "^4.0.1", @@ -1834,7 +1785,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.1.tgz", "integrity": "sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg==", - "dev": true, "license": "MIT", "dependencies": { "buffer-equal-constant-time": "^1.0.1", @@ -1846,7 +1796,6 @@ "version": "4.0.1", "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.1.tgz", "integrity": "sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA==", - "dev": true, "license": "MIT", "dependencies": { "jwa": "^2.0.1", @@ -1894,63 +1843,54 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==", - "dev": true, "license": "MIT" }, "node_modules/lodash.isboolean": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==", - "dev": true, "license": "MIT" }, "node_modules/lodash.isinteger": { "version": "4.0.4", "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", "integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==", - "dev": true, "license": "MIT" }, "node_modules/lodash.isnumber": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", "integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==", - "dev": true, "license": "MIT" }, "node_modules/lodash.isplainobject": { "version": "4.0.6", "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==", - "dev": true, "license": "MIT" }, "node_modules/lodash.isstring": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==", - "dev": true, "license": "MIT" }, "node_modules/lodash.once": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==", - "dev": true, "license": "MIT" }, "node_modules/long": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/long/-/long-5.3.2.tgz", "integrity": "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==", - "dev": true, "license": "Apache-2.0" }, "node_modules/lru.min": { "version": "1.1.5", "resolved": "https://registry.npmjs.org/lru.min/-/lru.min-1.1.5.tgz", "integrity": "sha512-5J9ysMYUpYIg9RF2vJpy9SinEmSviFSe0GyPpCQ4L5QSkLAgeLXlTAOu2ZwWUU5m+0SBl6gUU1R1ZQB3aKypfA==", - "dev": true, "license": "MIT", "engines": { "bun": ">=1.0.0", @@ -2060,14 +2000,12 @@ "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true, "license": "MIT" }, "node_modules/mssql": { "version": "12.7.10", "resolved": "https://registry.npmjs.org/mssql/-/mssql-12.7.10.tgz", "integrity": "sha512-vTmKBsogfFhd8dMhFjwn2QiPZuUP2/zpqt19L8eEJpQFwRa19bR+n3pFBjRtGdz+YfuC5jgj4YixILSAAF3ovQ==", - "dev": true, "license": "MIT", "dependencies": { "@tediousjs/connection-string": "^1.0.0", @@ -2087,7 +2025,6 @@ "version": "3.24.6", "resolved": "https://registry.npmjs.org/mysql2/-/mysql2-3.24.6.tgz", "integrity": "sha512-enu73KhbAua2L3dIxjc6qg6ZJ7H3pt1BImZASWNWY5xSsmJ1ZuUPULTDGXgmJbI61vb+nJAG19IZclIq19UidQ==", - "dev": true, "license": "MIT", "dependencies": { "aws-ssl-profiles": "^1.1.2", @@ -2109,7 +2046,6 @@ "version": "1.1.6", "resolved": "https://registry.npmjs.org/named-placeholders/-/named-placeholders-1.1.6.tgz", "integrity": "sha512-Tz09sEL2EEuv5fFowm419c1+a/jSMiBjI9gHxVLrVdbUkkNUUfjsVYs9pVZu5oCon/kmRh9TfLEObFtkVxmY0w==", - "dev": true, "license": "MIT", "dependencies": { "lru.min": "^1.1.0" @@ -2129,7 +2065,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/native-duplexpair/-/native-duplexpair-1.0.0.tgz", "integrity": "sha512-E7QQoM+3jvNtlmyfqRZ0/U75VFgCls+fSkbml2MpgWkWyz3ox8Y58gNhfuziuQYGNNQAbFZJQck55LHCnCK6CA==", - "dev": true, "license": "MIT" }, "node_modules/negotiator": { @@ -2216,7 +2151,6 @@ "version": "11.0.4", "resolved": "https://registry.npmjs.org/open/-/open-11.0.4.tgz", "integrity": "sha512-++Zlftm0kVLPmzC06t6epuWmcRMDbI4z5P3NNX979WA/k23+NtSOynEGzsVfZwguKw2mi5umVgnBlJQMwRz4Pg==", - "dev": true, "license": "MIT", "dependencies": { "default-browser": "^5.5.1", @@ -2258,7 +2192,6 @@ "version": "8.23.1", "resolved": "https://registry.npmjs.org/pg/-/pg-8.23.1.tgz", "integrity": "sha512-aL96AHANtWjPLDOLqnhx+ngp9+UK7ETEU8VJrDCGvsSSi/mGLcWYsS6Herg7lmaBJe4uwrfqsa7gTEFaSizDoQ==", - "dev": true, "license": "MIT", "dependencies": { "pg-connection-string": "^2.14.1", @@ -2286,7 +2219,6 @@ "version": "1.4.1", "resolved": "https://registry.npmjs.org/pg-cloudflare/-/pg-cloudflare-1.4.1.tgz", "integrity": "sha512-6PQbsFWZcp9EmJEwy5cGQ2La+AMWpP46lgbb8X+U/XsHIUweYDNCpeuKck5RxL2MdVFi7krbbEi5nX4Zh7JhrQ==", - "dev": true, "license": "MIT", "optional": true }, @@ -2294,14 +2226,12 @@ "version": "2.14.1", "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.14.1.tgz", "integrity": "sha512-qR3kGNPBLpCNtz0evbKA0Y/MRFXwSSdT+pTJvYp/bXTcReZbvX1kzF0IyTc1QnxqF7AZbOeBhNL8R5mYQZV/MA==", - "dev": true, "license": "MIT" }, "node_modules/pg-int8": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/pg-int8/-/pg-int8-1.0.1.tgz", "integrity": "sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==", - "dev": true, "license": "ISC", "engines": { "node": ">=4.0.0" @@ -2311,7 +2241,6 @@ "version": "3.14.0", "resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-3.14.0.tgz", "integrity": "sha512-gKtPkFdQPU3DksooVLi9LsjZxrsBUZIpa+7aVx+LV5pNh0KzP4Zleud2po+ConrxbuXGBJ6Hfer6hdgpIBpBaw==", - "dev": true, "license": "MIT", "peerDependencies": { "pg": ">=8.0" @@ -2321,14 +2250,12 @@ "version": "1.16.1", "resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.16.1.tgz", "integrity": "sha512-p9VOFMiHB/ZbJATetbg+99PxssTVSQRnyuPSQ67mN1+1KBOjZaZ83ZQzltnxPhJwSsC3nwVjJ10DVJlerbFzLg==", - "dev": true, "license": "MIT" }, "node_modules/pg-types": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/pg-types/-/pg-types-2.2.0.tgz", "integrity": "sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==", - "dev": true, "license": "MIT", "dependencies": { "pg-int8": "1.0.1", @@ -2345,7 +2272,6 @@ "version": "1.0.5", "resolved": "https://registry.npmjs.org/pgpass/-/pgpass-1.0.5.tgz", "integrity": "sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug==", - "dev": true, "license": "MIT", "dependencies": { "split2": "^4.1.0" @@ -2395,7 +2321,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-2.0.0.tgz", "integrity": "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==", - "dev": true, "license": "MIT", "engines": { "node": ">=4" @@ -2405,7 +2330,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/postgres-bytea/-/postgres-bytea-1.0.1.tgz", "integrity": "sha512-5+5HqXnsZPE65IJZSMkZtURARZelel2oXUEO8rH83VS/hxH5vv1uHquPg5wZs8yMAfdv971IU+kcPUczi7NVBQ==", - "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -2415,7 +2339,6 @@ "version": "1.0.7", "resolved": "https://registry.npmjs.org/postgres-date/-/postgres-date-1.0.7.tgz", "integrity": "sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==", - "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -2425,7 +2348,6 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/postgres-interval/-/postgres-interval-1.2.0.tgz", "integrity": "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==", - "dev": true, "license": "MIT", "dependencies": { "xtend": "^4.0.0" @@ -2438,7 +2360,6 @@ "version": "0.2.1", "resolved": "https://registry.npmjs.org/powershell-utils/-/powershell-utils-0.2.1.tgz", "integrity": "sha512-C+y9x90UElAddDZmV4qOx9W53B61PO7cIqWz2dQsWlwswuq4mr8NEwytdGKboYbQlGZ3awrkTeNvcZiZNHnQ8A==", - "dev": true, "license": "MIT", "engines": { "node": ">=20" @@ -2479,7 +2400,6 @@ "version": "0.11.10", "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.6.0" @@ -2692,7 +2612,6 @@ "version": "7.1.0", "resolved": "https://registry.npmjs.org/run-applescript/-/run-applescript-7.1.0.tgz", "integrity": "sha512-DPe5pVFaAsinSaV6QjQ6gdiedWDcRCbUuiQfQa2wmWV7+xC9bGulGI8+TdRmoFkAPaBXk8CrAbnlY2ISniJ47Q==", - "dev": true, "license": "MIT", "engines": { "node": ">=18" @@ -2705,7 +2624,6 @@ "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "dev": true, "funding": [ { "type": "github", @@ -2759,7 +2677,6 @@ "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", - "dev": true, "license": "MIT" }, "node_modules/secure-json-parse": { @@ -2783,7 +2700,6 @@ "version": "7.8.5", "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", - "dev": true, "license": "ISC", "bin": { "semver": "bin/semver.js" @@ -2990,7 +2906,6 @@ "version": "4.2.0", "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz", "integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==", - "dev": true, "license": "ISC", "engines": { "node": ">= 10.x" @@ -3000,7 +2915,6 @@ "version": "1.5.2", "resolved": "https://registry.npmjs.org/sql-escaper/-/sql-escaper-1.5.2.tgz", "integrity": "sha512-6CKD38c31SENivxOADeMNLdukOnUxUcflKtzVWzace7Riv1v7cAEym5Cx9Q7gYZ3ezIJI7ZpqecQq8cqNeSSFg==", - "dev": true, "license": "MIT", "engines": { "bun": ">=1.0.0", @@ -3026,7 +2940,6 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", - "dev": true, "license": "MIT", "dependencies": { "safe-buffer": "~5.2.0" @@ -3076,7 +2989,6 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/tarn/-/tarn-3.1.2.tgz", "integrity": "sha512-3RTvqKZcK/17jnJ8rMKFXbyNogywTs1z0gVPPwFsJGX46rkmUHOdIaSQ/aVO1rS7nH+soiXiWk7rvUXxndm8Dg==", - "dev": true, "license": "MIT", "engines": { "node": ">=8.0.0" @@ -3086,7 +2998,6 @@ "version": "20.3.8", "resolved": "https://registry.npmjs.org/tedious/-/tedious-20.3.8.tgz", "integrity": "sha512-ZrJiwyGn340Sa0V0g37Xqj5bvmJ1riYwvEtg7zdm/lpw70f2ZNIQ2fvHmntU5fEOorWDa48Jo1oKYAuUP+rh7Q==", - "dev": true, "license": "MIT", "dependencies": { "@azure/core-auth": "^1.10.1", @@ -3107,7 +3018,6 @@ "version": "6.1.6", "resolved": "https://registry.npmjs.org/bl/-/bl-6.1.6.tgz", "integrity": "sha512-jLsPgN/YSvPUg9UX0Kd73CXpm2Psg9FxMeCSXnk3WBO3CMT10JMwijubhGfHCnFu6TPn1ei3b975dxv7K2pWVg==", - "dev": true, "license": "MIT", "dependencies": { "@types/readable-stream": "^4.0.0", @@ -3120,7 +3030,6 @@ "version": "6.0.3", "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", - "dev": true, "funding": [ { "type": "github", @@ -3145,7 +3054,6 @@ "version": "4.8.0", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.8.0.tgz", "integrity": "sha512-2jhTTU3Eilp/2DMT71gbcoqC35eQHasMJH0RHkYxwlxIv3+FR6Y5xAFZIwEGLxKkjZYEFlsiMNIdwUqjK2lLHQ==", - "dev": true, "license": "MIT", "dependencies": { "abort-controller": "^3.0.0", @@ -3195,7 +3103,6 @@ "version": "2.9.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.9.1.tgz", "integrity": "sha512-a4Ux9VKuIMHsOS7ENbEq6hK2ZGYIT27Rtl0E4eD9KqwxhKiQeMZE/9xb3IdYwWaSOyIBQLExhQgXDTYoiCxnWA==", - "dev": true, "license": "0BSD" }, "node_modules/tunnel-agent": { @@ -3248,7 +3155,6 @@ "version": "7.24.6", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.24.6.tgz", "integrity": "sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg==", - "dev": true, "license": "MIT" }, "node_modules/unpipe": { @@ -3289,7 +3195,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/wsl-utils/-/wsl-utils-1.0.1.tgz", "integrity": "sha512-c16PKLIRxBYpR3jSWsdS7SFp0COm5SOQdiFjY818dYFHJbMGuLBszpF07LBWmOS1uzrHa9VGdynsEgoeKy0oOQ==", - "dev": true, "license": "MIT", "dependencies": { "is-wsl": "^3.1.0", @@ -3306,7 +3211,6 @@ "version": "4.0.2", "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", - "dev": true, "license": "MIT", "engines": { "node": ">=0.4" @@ -3314,7 +3218,7 @@ }, "packages/core": { "name": "@crudy/restloom", - "version": "0.4.0", + "version": "0.5.0", "license": "Apache-2.0", "devDependencies": { "@types/node": "^24.0.0", @@ -3326,10 +3230,10 @@ }, "packages/express": { "name": "@crudy/restloom-express", - "version": "0.4.0", + "version": "0.5.0", "license": "Apache-2.0", "dependencies": { - "@crudy/restloom": "^0.4.0" + "@crudy/restloom": "^0.5.0" }, "devDependencies": { "@types/express": "^5.0.0", @@ -3346,10 +3250,10 @@ }, "packages/fastify": { "name": "@crudy/restloom-fastify", - "version": "0.4.0", + "version": "0.5.0", "license": "Apache-2.0", "dependencies": { - "@crudy/restloom": "^0.4.0" + "@crudy/restloom": "^0.5.0" }, "devDependencies": { "@types/node": "^24.0.0", @@ -3365,84 +3269,60 @@ }, "packages/mssql": { "name": "@crudy/restloom-mssql", - "version": "0.4.0", + "version": "0.5.0", "license": "Apache-2.0", "dependencies": { - "@crudy/restloom": "^0.4.0" + "@crudy/restloom": "^0.5.0", + "mssql": "^12.7.10" }, "devDependencies": { "@types/mssql": "^12.0.0", "@types/node": "^24.0.0", - "mssql": "12.7.10", "typescript": "^5.9.0" }, "engines": { "node": ">=20" - }, - "peerDependencies": { - "mssql": "^12.0.0" - }, - "peerDependenciesMeta": { - "mssql": { - "optional": true - } } }, "packages/mysql": { "name": "@crudy/restloom-mysql", - "version": "0.4.0", + "version": "0.5.0", "license": "Apache-2.0", "dependencies": { - "@crudy/restloom": "^0.4.0" + "@crudy/restloom": "^0.5.0", + "mysql2": "^3.24.5" }, "devDependencies": { "@types/node": "^24.0.0", - "mysql2": "^3.24.5", "typescript": "^5.9.0" }, "engines": { "node": ">=20" - }, - "peerDependencies": { - "mysql2": "^3.11.0" - }, - "peerDependenciesMeta": { - "mysql2": { - "optional": true - } } }, "packages/postgres": { "name": "@crudy/restloom-postgres", - "version": "0.4.0", + "version": "0.5.0", "license": "Apache-2.0", "dependencies": { - "@crudy/restloom": "^0.4.0" + "@crudy/restloom": "^0.5.0", + "pg": "^8.16.0" }, "devDependencies": { "@types/node": "^24.0.0", "@types/pg": "^8.11.0", - "pg": "^8.16.0", "typescript": "^5.9.0" }, "engines": { "node": ">=20" - }, - "peerDependencies": { - "pg": "^8.11.0" - }, - "peerDependenciesMeta": { - "pg": { - "optional": true - } } }, "packages/sqlite": { "name": "@crudy/restloom-sqlite", - "version": "0.4.0", + "version": "0.5.0", "license": "Apache-2.0", "dependencies": { - "@crudy/restloom": "^0.4.0" + "@crudy/restloom": "^0.5.0" }, "devDependencies": { "@types/better-sqlite3": "^7.6.0", diff --git a/packages/core/README.md b/packages/core/README.md index 891a6c1..150bb33 100644 --- a/packages/core/README.md +++ b/packages/core/README.md @@ -32,19 +32,19 @@ Your backend stays in charge: | `@crudy/restloom-express` | Express handler | ```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 ```ts import http from 'node:http'; -import pg from 'pg'; -import { createREST, nodeListener } from '@crudy/restloom'; -import { postgres } from '@crudy/restloom-postgres'; +import { createREST, nodeListener, postgres } from '@crudy/restloom-postgres'; const rest = createREST({ - db: postgres(new pg.Pool()), + db: postgres(process.env.DATABASE_URL), expose: { schemas: ['app'] }, resources: { todos: { write: true } }, }); diff --git a/packages/core/package.json b/packages/core/package.json index 2a394c5..489215d 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -1,6 +1,6 @@ { "name": "@crudy/restloom", - "version": "0.4.0", + "version": "0.5.0", "type": "module", "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"], diff --git a/packages/core/src/node.ts b/packages/core/src/node.ts index 931a6ed..e382cee 100644 --- a/packages/core/src/node.ts +++ b/packages/core/src/node.ts @@ -1,9 +1,10 @@ import type { IncomingMessage, ServerResponse } from 'node:http'; +import { RestError } from './errors.js'; import type { RestResponse } from './options.js'; import type { Rest } from './rest.js'; export interface NodeListenerOptions { - /** 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; /** Path the API is mounted under, e.g. /api. */ prefix?: string; @@ -47,6 +48,13 @@ export function nodeListener(rest: Rest, options: NodeListenerOptions = const status = (err as { status?: number }).status ?? 400; 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])); send(res, await rest.handle({ method: req.method ?? 'GET', @@ -54,7 +62,7 @@ export function nodeListener(rest: Rest, options: NodeListenerOptions = query: url.searchParams, headers, body, - context: (await options.context?.(req)) as C, + context, })); }; } diff --git a/packages/core/test/node.test.mjs b/packages/core/test/node.test.mjs index 8de6be3..6830ecf 100644 --- a/packages/core/test/node.test.mjs +++ b/packages/core/test/node.test.mjs @@ -1,7 +1,7 @@ import { test } from 'node:test'; import assert from 'node:assert/strict'; 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'; 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 }); 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(); + } +}); diff --git a/packages/express/package.json b/packages/express/package.json index 43e103c..5a6c588 100644 --- a/packages/express/package.json +++ b/packages/express/package.json @@ -1,6 +1,6 @@ { "name": "@crudy/restloom-express", - "version": "0.4.0", + "version": "0.5.0", "type": "module", "description": "Express adapter for restloom", "keywords": [ @@ -36,7 +36,7 @@ "prepack": "npm run build" }, "dependencies": { - "@crudy/restloom": "^0.4.0" + "@crudy/restloom": "^0.5.0" }, "peerDependencies": { "express": "^5.0.0" diff --git a/packages/fastify/package.json b/packages/fastify/package.json index 894901d..de5ecf9 100644 --- a/packages/fastify/package.json +++ b/packages/fastify/package.json @@ -1,6 +1,6 @@ { "name": "@crudy/restloom-fastify", - "version": "0.4.0", + "version": "0.5.0", "type": "module", "description": "Fastify adapter for restloom", "keywords": [ @@ -36,7 +36,7 @@ "prepack": "npm run build" }, "dependencies": { - "@crudy/restloom": "^0.4.0" + "@crudy/restloom": "^0.5.0" }, "peerDependencies": { "fastify": "^5.0.0" diff --git a/packages/mssql/package.json b/packages/mssql/package.json index 9479660..89819b6 100644 --- a/packages/mssql/package.json +++ b/packages/mssql/package.json @@ -1,6 +1,6 @@ { "name": "@crudy/restloom-mssql", - "version": "0.4.0", + "version": "0.5.0", "type": "module", "description": "SQL Server dialect for restloom", "keywords": [ @@ -37,18 +37,12 @@ "prepack": "npm run build" }, "dependencies": { - "@crudy/restloom": "^0.4.0" - }, - "peerDependencies": { - "mssql": "^12.0.0" - }, - "peerDependenciesMeta": { - "mssql": { "optional": true } + "@crudy/restloom": "^0.5.0", + "mssql": "^12.7.10" }, "devDependencies": { "@types/node": "^24.0.0", "@types/mssql": "^12.0.0", - "mssql": "12.7.10", "typescript": "^5.9.0" } } diff --git a/packages/mssql/src/index.ts b/packages/mssql/src/index.ts index 8f64a79..f8b4208 100644 --- a/packages/mssql/src/index.ts +++ b/packages/mssql/src/index.ts @@ -1,8 +1,21 @@ 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 { 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 { + 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'; const CODES: Record = { @@ -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 * driver sets it per pool. Row security is SQL Server's security policies, * 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 { name: 'mssql', capabilities: { returning: true, estimatedCount: false, rowSecurity: true, procedures: true }, renderer: options.renderer ?? new MssqlRenderer(), decode: (value) => (typeof value === 'string' ? JSON.parse(value) : value), - introspect: (scope) => introspect(pool, scope), + introspect: async (scope) => introspect(await ready, scope), async transaction(fn) { - const tx = new mssqlDriver.Transaction(pool); + const tx = new mssqlDriver.Transaction(await ready); await tx.begin(); try { const out = await fn({ diff --git a/packages/mysql/package.json b/packages/mysql/package.json index 8afb10f..eb756b6 100644 --- a/packages/mysql/package.json +++ b/packages/mysql/package.json @@ -1,6 +1,6 @@ { "name": "@crudy/restloom-mysql", - "version": "0.4.0", + "version": "0.5.0", "type": "module", "description": "MySQL dialect for restloom", "keywords": [ @@ -36,17 +36,11 @@ "prepack": "npm run build" }, "dependencies": { - "@crudy/restloom": "^0.4.0" - }, - "peerDependencies": { - "mysql2": "^3.11.0" - }, - "peerDependenciesMeta": { - "mysql2": { "optional": true } + "@crudy/restloom": "^0.5.0", + "mysql2": "^3.24.5" }, "devDependencies": { "@types/node": "^24.0.0", - "mysql2": "^3.24.5", "typescript": "^5.9.0" } } diff --git a/packages/mysql/src/index.ts b/packages/mysql/src/index.ts index b841c0a..38053dc 100644 --- a/packages/mysql/src/index.ts +++ b/packages/mysql/src/index.ts @@ -1,8 +1,16 @@ 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 { 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'; const CODES: Record = { @@ -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. * Text compares with each column's collation, so with a case-insensitive * 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 { name: 'mysql', capabilities: { returning: false, estimatedCount: false, rowSecurity: false, procedures: true }, diff --git a/packages/postgres/package.json b/packages/postgres/package.json index 2818c72..b051108 100644 --- a/packages/postgres/package.json +++ b/packages/postgres/package.json @@ -1,6 +1,6 @@ { "name": "@crudy/restloom-postgres", - "version": "0.4.0", + "version": "0.5.0", "type": "module", "description": "Postgres dialect for restloom", "keywords": ["restloom", "postgres", "postgresql", "rest", "api"], @@ -18,18 +18,12 @@ "prepack": "npm run build" }, "dependencies": { - "@crudy/restloom": "^0.4.0" - }, - "peerDependencies": { - "pg": "^8.11.0" - }, - "peerDependenciesMeta": { - "pg": { "optional": true } + "@crudy/restloom": "^0.5.0", + "pg": "^8.16.0" }, "devDependencies": { "@types/node": "^24.0.0", "@types/pg": "^8.11.0", - "pg": "^8.16.0", "typescript": "^5.9.0" } } diff --git a/packages/postgres/src/index.ts b/packages/postgres/src/index.ts index 1afc695..e7ff723 100644 --- a/packages/postgres/src/index.ts +++ b/packages/postgres/src/index.ts @@ -1,10 +1,17 @@ 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 { introspect } from './introspect.js'; +export * from '@crudy/restloom'; 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. */ 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 * 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 { name: 'postgres', capabilities: { returning: true, estimatedCount: true, rowSecurity: true, procedures: true }, diff --git a/packages/sqlite/package.json b/packages/sqlite/package.json index c3c0cd7..75bc0f6 100644 --- a/packages/sqlite/package.json +++ b/packages/sqlite/package.json @@ -1,6 +1,6 @@ { "name": "@crudy/restloom-sqlite", - "version": "0.4.0", + "version": "0.5.0", "type": "module", "description": "SQLite dialect for restloom", "keywords": [ @@ -36,7 +36,7 @@ "prepack": "npm run build" }, "dependencies": { - "@crudy/restloom": "^0.4.0" + "@crudy/restloom": "^0.5.0" }, "peerDependencies": { "better-sqlite3": ">=12" diff --git a/packages/sqlite/src/index.ts b/packages/sqlite/src/index.ts index 79ef951..7f41335 100644 --- a/packages/sqlite/src/index.ts +++ b/packages/sqlite/src/index.ts @@ -3,6 +3,8 @@ import type { Database } from 'better-sqlite3'; import { introspect } from './introspect.js'; import { SqliteRenderer } from './renderer.js'; +export * from '@crudy/restloom'; + export { SqliteRenderer } from './renderer.js'; const CODES: Record = { @@ -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 * 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