From 9c733ac48082ec81ca6e0ffbf799e9fe982a34d6 Mon Sep 17 00:00:00 2001 From: will griffin Date: Sat, 17 Feb 2024 17:49:02 -0700 Subject: [PATCH] feat: initial commit --- .github/workflows/release.yml | 29 + .github/workflows/test.yml | 37 + .gitignore | 3 + .prettierrc | 4 + README.md | 140 ++++ docker-compose.yml | 10 + package.json | 23 + pnpm-lock.yaml | 1280 +++++++++++++++++++++++++++++++++ src/index.js | 27 + src/index.test.js | 23 + src/postgres.js | 221 ++++++ src/postgres.test.js | 111 +++ src/sqlite.js | 205 ++++++ src/sqlite.test.js | 73 ++ vitest.config.js | 10 + vitest.setup.js | 9 + 16 files changed, 2205 insertions(+) create mode 100644 .github/workflows/release.yml create mode 100644 .github/workflows/test.yml create mode 100644 .gitignore create mode 100644 .prettierrc create mode 100644 README.md create mode 100644 docker-compose.yml create mode 100644 package.json create mode 100644 pnpm-lock.yaml create mode 100644 src/index.js create mode 100644 src/index.test.js create mode 100644 src/postgres.js create mode 100644 src/postgres.test.js create mode 100644 src/sqlite.js create mode 100644 src/sqlite.test.js create mode 100644 vitest.config.js create mode 100644 vitest.setup.js diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..ad77551 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,29 @@ +name: Release +on: + push: + branches: + - master +jobs: + release-please: + runs-on: ubuntu-latest + steps: + - uses: google-github-actions/release-please-action@v4 + id: release + with: + release-type: node + # The logic below handles the npm publication: + # - uses: actions/checkout@v4 + # # these if statements ensure that a publication only occurs when + # # a new release is created: + # if: ${{ steps.release.outputs.release_created }} + # - uses: actions/setup-node@v4 + # with: + # node-version: "20.x" + # registry-url: "https://registry.npmjs.org" + # if: ${{ steps.release.outputs.release_created }} + # - run: npm ci + # if: ${{ steps.release.outputs.release_created }} + # - run: npm publish + # env: + # NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}} + # if: ${{ steps.release.outputs.release_created }} diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..36c8cd3 --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,37 @@ +name: Run Unit Tests +on: [push, pull_request] +jobs: + test: + runs-on: ubuntu-latest + services: + postgres: + image: postgres:latest + env: + POSTGRES_USER: sqloo + POSTGRES_PASSWORD: sqloo + POSTGRES_DB: sqloo + ports: + - 5432:5432 + # Wait for the database to be ready + options: >- + --health-cmd pg_isready + --health-interval 10s + --health-timeout 5s + --health-retries 5 + steps: + - uses: actions/checkout@v3 + - name: Set up Node.js + uses: actions/setup-node@v3 + with: + node-version: "20.x" + - name: Install pnpm + run: curl -f https://get.pnpm.io/v6.js | node - add --global pnpm + - name: Install dependencies + run: pnpm install + - name: Run tests + env: + PGDATABASE: sqloo + PGUSER: sqloo + PGPASSWORD: sqloo + PGPORT: 5432 + run: pnpm test diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ff1608a --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +node_modules/ +.envrc +.env diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 0000000..222861c --- /dev/null +++ b/.prettierrc @@ -0,0 +1,4 @@ +{ + "tabWidth": 2, + "useTabs": false +} diff --git a/README.md b/README.md new file mode 100644 index 0000000..9edec9d --- /dev/null +++ b/README.md @@ -0,0 +1,140 @@ +# sqloo + +a simple interface to sqlite and/or postgres for basic tasks + +```js +// get a postgres database +const db = await getDatabase({ + host: "localhost", + port: 5432, + database: "sqlooooo", + user: "oO", + password: "ackBillD.C@", +}); +``` + +```js +// get a sqlite database +const db = await getDatabase({ + file: "::memory::", +}); +``` + +```js +// fetch all the posts +const { oo } = db; +const posts = await oo` + select * from posts +`; +``` + +```js +// fetch a single post +const { oO } = db; +const post = await oO` + select * from posts where author = ${author} order by created_at desc limit 1 +`; +``` + +```js +// pluck the first value from the first row +const { ox } = db; +const id = await ox` + select id from posts where author = ${author} order by created_at desc limit 1 +`; +``` + +```js +// execute a statement +const { xx } = db; +await xx` + delete from posts where author = ${author} +`; +``` + +## Functions (db.\*) + +### `insert(table, data)` + +Inserts data into a table and returns the inserted rows. + +- `table` (string): The name of the table. +- `data` (Object | Object[]): The data to insert. +- Returns: `Promise`: The inserted rows, return type will match data. + +### `get(table, where)` + +Retrieves a row from a table based on a where clause. + +- `table` (string): The name of the table. +- `where` (Object): The where clause. +- Returns: `Promise`: The retrieved row. + +### `list(table, where)` + +Retrieves multiple rows from a table based on a where clause. + +- `table` (string): The name of the table. +- `where` (Object): The where clause. +- Returns: `Promise`: The retrieved rows. + +### `update(table, where, data)` + +Updates rows in a table based on a where clause and returns the updated rows. + +- `table` (string): The name of the table. +- `where` (Object): The where clause. +- `data` (Object): The data to update. +- Returns: `Promise`: The updated rows. + +### `table(table)` + +Returns an object with insert, get, and list methods for a specific table. + +- `table` (string): The name of the table. +- Returns: `Object`: An object with insert, get, and list methods. + +### `parseTemplate(strings, ...vars)` + +Parses a SQL template into a SQL string and an array of values. + +- `strings` (Array): The strings in the SQL template. +- `vars` (...any): The variables in the SQL template. +- Returns: `Object`: An object with a sql property and a values property. + +### `pluck(strings, ...vars)` + +Executes a SQL query and returns the first column of the first row. Handles templates with it's alias "xo" + +- `strings` (TemplateStringsArray): The strings in the SQL template. +- `vars` (...any): The variables in the SQL template. +- Returns: `Promise`: The first column of the first row. + +### `single(strings, ...vars)` + +Executes a SQL query and returns the first row. Handles templates with it's alias "oO" + +- `strings` (TemplateStringsArray): The strings in the SQL template. +- `vars` (...any): The variables in the SQL template. +- Returns: `Promise`: The first row. + +### `many(strings, ...vars)` + +Executes a SQL query and returns all rows. Handles templates with it's alias "oo" + +- `strings` (TemplateStringsArray): The strings in the SQL template. +- `vars` (...any): The variables in the SQL template. +- Returns: `Promise`: All rows. + +### `execute(strings, ...vars)` + +Executes a SQL query. Handles templates with it's alias "xx" + +- `strings` (TemplateStringsArray): The strings in the SQL template. +- `vars` (...any): The variables in the SQL template. +- Returns: `Promise`: The result of the query. + +## Warning + +- dont use variables for anything except values in template queries +- dont accept unsantized user input for table or column names diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..d3ba814 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,10 @@ +version: "3.1" +services: + postgres: + image: postgres:latest + environment: + POSTGRES_USER: sqloo + POSTGRES_PASSWORD: sqloo + POSTGRES_DB: sqloo + ports: + - "35432:5432" diff --git a/package.json b/package.json new file mode 100644 index 0000000..b7b454b --- /dev/null +++ b/package.json @@ -0,0 +1,23 @@ +{ + "name": "sqloo", + "version": "0.0.0", + "description": "", + "main": "index.js", + "scripts": { + "test": "vitest" + }, + "keywords": [], + "author": "willgriffin@gmail.com", + "license": "ISC", + "dependencies": { + "better-sqlite3": "^9.4.0", + "pg": "^8.11.3", + "sqlite-vss": "^0.1.2" + }, + "devDependencies": { + "vitest": "^1.2.2" + }, + "engines": { + "node": "20.x" + } +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml new file mode 100644 index 0000000..c7db8c9 --- /dev/null +++ b/pnpm-lock.yaml @@ -0,0 +1,1280 @@ +lockfileVersion: '6.0' + +dependencies: + better-sqlite3: + specifier: ^9.4.0 + version: 9.4.0 + pg: + specifier: ^8.11.3 + version: 8.11.3 + sqlite-vss: + specifier: ^0.1.2 + version: 0.1.2 + +devDependencies: + vitest: + specifier: ^1.2.2 + version: 1.2.2 + +packages: + + /@esbuild/aix-ppc64@0.19.12: + resolution: {integrity: sha512-bmoCYyWdEL3wDQIVbcyzRyeKLgk2WtWLTWz1ZIAZF/EGbNOwSA6ew3PftJ1PqMiOOGu0OyFMzG53L0zqIpPeNA==} + engines: {node: '>=12'} + cpu: [ppc64] + os: [aix] + requiresBuild: true + dev: true + optional: true + + /@esbuild/android-arm64@0.19.12: + resolution: {integrity: sha512-P0UVNGIienjZv3f5zq0DP3Nt2IE/3plFzuaS96vihvD0Hd6H/q4WXUGpCxD/E8YrSXfNyRPbpTq+T8ZQioSuPA==} + engines: {node: '>=12'} + cpu: [arm64] + os: [android] + requiresBuild: true + dev: true + optional: true + + /@esbuild/android-arm@0.19.12: + resolution: {integrity: sha512-qg/Lj1mu3CdQlDEEiWrlC4eaPZ1KztwGJ9B6J+/6G+/4ewxJg7gqj8eVYWvao1bXrqGiW2rsBZFSX3q2lcW05w==} + engines: {node: '>=12'} + cpu: [arm] + os: [android] + requiresBuild: true + dev: true + optional: true + + /@esbuild/android-x64@0.19.12: + resolution: {integrity: sha512-3k7ZoUW6Q6YqhdhIaq/WZ7HwBpnFBlW905Fa4s4qWJyiNOgT1dOqDiVAQFwBH7gBRZr17gLrlFCRzF6jFh7Kew==} + engines: {node: '>=12'} + cpu: [x64] + os: [android] + requiresBuild: true + dev: true + optional: true + + /@esbuild/darwin-arm64@0.19.12: + resolution: {integrity: sha512-B6IeSgZgtEzGC42jsI+YYu9Z3HKRxp8ZT3cqhvliEHovq8HSX2YX8lNocDn79gCKJXOSaEot9MVYky7AKjCs8g==} + engines: {node: '>=12'} + cpu: [arm64] + os: [darwin] + requiresBuild: true + dev: true + optional: true + + /@esbuild/darwin-x64@0.19.12: + resolution: {integrity: sha512-hKoVkKzFiToTgn+41qGhsUJXFlIjxI/jSYeZf3ugemDYZldIXIxhvwN6erJGlX4t5h417iFuheZ7l+YVn05N3A==} + engines: {node: '>=12'} + cpu: [x64] + os: [darwin] + requiresBuild: true + dev: true + optional: true + + /@esbuild/freebsd-arm64@0.19.12: + resolution: {integrity: sha512-4aRvFIXmwAcDBw9AueDQ2YnGmz5L6obe5kmPT8Vd+/+x/JMVKCgdcRwH6APrbpNXsPz+K653Qg8HB/oXvXVukA==} + engines: {node: '>=12'} + cpu: [arm64] + os: [freebsd] + requiresBuild: true + dev: true + optional: true + + /@esbuild/freebsd-x64@0.19.12: + resolution: {integrity: sha512-EYoXZ4d8xtBoVN7CEwWY2IN4ho76xjYXqSXMNccFSx2lgqOG/1TBPW0yPx1bJZk94qu3tX0fycJeeQsKovA8gg==} + engines: {node: '>=12'} + cpu: [x64] + os: [freebsd] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-arm64@0.19.12: + resolution: {integrity: sha512-EoTjyYyLuVPfdPLsGVVVC8a0p1BFFvtpQDB/YLEhaXyf/5bczaGeN15QkR+O4S5LeJ92Tqotve7i1jn35qwvdA==} + engines: {node: '>=12'} + cpu: [arm64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-arm@0.19.12: + resolution: {integrity: sha512-J5jPms//KhSNv+LO1S1TX1UWp1ucM6N6XuL6ITdKWElCu8wXP72l9MM0zDTzzeikVyqFE6U8YAV9/tFyj0ti+w==} + engines: {node: '>=12'} + cpu: [arm] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-ia32@0.19.12: + resolution: {integrity: sha512-Thsa42rrP1+UIGaWz47uydHSBOgTUnwBwNq59khgIwktK6x60Hivfbux9iNR0eHCHzOLjLMLfUMLCypBkZXMHA==} + engines: {node: '>=12'} + cpu: [ia32] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-loong64@0.19.12: + resolution: {integrity: sha512-LiXdXA0s3IqRRjm6rV6XaWATScKAXjI4R4LoDlvO7+yQqFdlr1Bax62sRwkVvRIrwXxvtYEHHI4dm50jAXkuAA==} + engines: {node: '>=12'} + cpu: [loong64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-mips64el@0.19.12: + resolution: {integrity: sha512-fEnAuj5VGTanfJ07ff0gOA6IPsvrVHLVb6Lyd1g2/ed67oU1eFzL0r9WL7ZzscD+/N6i3dWumGE1Un4f7Amf+w==} + engines: {node: '>=12'} + cpu: [mips64el] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-ppc64@0.19.12: + resolution: {integrity: sha512-nYJA2/QPimDQOh1rKWedNOe3Gfc8PabU7HT3iXWtNUbRzXS9+vgB0Fjaqr//XNbd82mCxHzik2qotuI89cfixg==} + engines: {node: '>=12'} + cpu: [ppc64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-riscv64@0.19.12: + resolution: {integrity: sha512-2MueBrlPQCw5dVJJpQdUYgeqIzDQgw3QtiAHUC4RBz9FXPrskyyU3VI1hw7C0BSKB9OduwSJ79FTCqtGMWqJHg==} + engines: {node: '>=12'} + cpu: [riscv64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-s390x@0.19.12: + resolution: {integrity: sha512-+Pil1Nv3Umes4m3AZKqA2anfhJiVmNCYkPchwFJNEJN5QxmTs1uzyy4TvmDrCRNT2ApwSari7ZIgrPeUx4UZDg==} + engines: {node: '>=12'} + cpu: [s390x] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-x64@0.19.12: + resolution: {integrity: sha512-B71g1QpxfwBvNrfyJdVDexenDIt1CiDN1TIXLbhOw0KhJzE78KIFGX6OJ9MrtC0oOqMWf+0xop4qEU8JrJTwCg==} + engines: {node: '>=12'} + cpu: [x64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/netbsd-x64@0.19.12: + resolution: {integrity: sha512-3ltjQ7n1owJgFbuC61Oj++XhtzmymoCihNFgT84UAmJnxJfm4sYCiSLTXZtE00VWYpPMYc+ZQmB6xbSdVh0JWA==} + engines: {node: '>=12'} + cpu: [x64] + os: [netbsd] + requiresBuild: true + dev: true + optional: true + + /@esbuild/openbsd-x64@0.19.12: + resolution: {integrity: sha512-RbrfTB9SWsr0kWmb9srfF+L933uMDdu9BIzdA7os2t0TXhCRjrQyCeOt6wVxr79CKD4c+p+YhCj31HBkYcXebw==} + engines: {node: '>=12'} + cpu: [x64] + os: [openbsd] + requiresBuild: true + dev: true + optional: true + + /@esbuild/sunos-x64@0.19.12: + resolution: {integrity: sha512-HKjJwRrW8uWtCQnQOz9qcU3mUZhTUQvi56Q8DPTLLB+DawoiQdjsYq+j+D3s9I8VFtDr+F9CjgXKKC4ss89IeA==} + engines: {node: '>=12'} + cpu: [x64] + os: [sunos] + requiresBuild: true + dev: true + optional: true + + /@esbuild/win32-arm64@0.19.12: + resolution: {integrity: sha512-URgtR1dJnmGvX864pn1B2YUYNzjmXkuJOIqG2HdU62MVS4EHpU2946OZoTMnRUHklGtJdJZ33QfzdjGACXhn1A==} + engines: {node: '>=12'} + cpu: [arm64] + os: [win32] + requiresBuild: true + dev: true + optional: true + + /@esbuild/win32-ia32@0.19.12: + resolution: {integrity: sha512-+ZOE6pUkMOJfmxmBZElNOx72NKpIa/HFOMGzu8fqzQJ5kgf6aTGrcJaFsNiVMH4JKpMipyK+7k0n2UXN7a8YKQ==} + engines: {node: '>=12'} + cpu: [ia32] + os: [win32] + requiresBuild: true + dev: true + optional: true + + /@esbuild/win32-x64@0.19.12: + resolution: {integrity: sha512-T1QyPSDCyMXaO3pzBkF96E8xMkiRYbUEZADd29SyPGabqxMViNoii+NcK7eWJAEoU6RZyEm5lVSIjTmcdoB9HA==} + engines: {node: '>=12'} + cpu: [x64] + os: [win32] + requiresBuild: true + dev: true + optional: true + + /@jest/schemas@29.6.3: + resolution: {integrity: sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dependencies: + '@sinclair/typebox': 0.27.8 + dev: true + + /@jridgewell/sourcemap-codec@1.4.15: + resolution: {integrity: sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==} + dev: true + + /@rollup/rollup-android-arm-eabi@4.10.0: + resolution: {integrity: sha512-/MeDQmcD96nVoRumKUljsYOLqfv1YFJps+0pTrb2Z9Nl/w5qNUysMaWQsrd1mvAlNT4yza1iVyIu4Q4AgF6V3A==} + cpu: [arm] + os: [android] + requiresBuild: true + dev: true + optional: true + + /@rollup/rollup-android-arm64@4.10.0: + resolution: {integrity: sha512-lvu0jK97mZDJdpZKDnZI93I0Om8lSDaiPx3OiCk0RXn3E8CMPJNS/wxjAvSJJzhhZpfjXsjLWL8LnS6qET4VNQ==} + cpu: [arm64] + os: [android] + requiresBuild: true + dev: true + optional: true + + /@rollup/rollup-darwin-arm64@4.10.0: + resolution: {integrity: sha512-uFpayx8I8tyOvDkD7X6n0PriDRWxcqEjqgtlxnUA/G9oS93ur9aZ8c8BEpzFmsed1TH5WZNG5IONB8IiW90TQg==} + cpu: [arm64] + os: [darwin] + requiresBuild: true + dev: true + optional: true + + /@rollup/rollup-darwin-x64@4.10.0: + resolution: {integrity: sha512-nIdCX03qFKoR/MwQegQBK+qZoSpO3LESurVAC6s6jazLA1Mpmgzo3Nj3H1vydXp/JM29bkCiuF7tDuToj4+U9Q==} + cpu: [x64] + os: [darwin] + requiresBuild: true + dev: true + optional: true + + /@rollup/rollup-linux-arm-gnueabihf@4.10.0: + resolution: {integrity: sha512-Fz7a+y5sYhYZMQFRkOyCs4PLhICAnxRX/GnWYReaAoruUzuRtcf+Qnw+T0CoAWbHCuz2gBUwmWnUgQ67fb3FYw==} + cpu: [arm] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@rollup/rollup-linux-arm64-gnu@4.10.0: + resolution: {integrity: sha512-yPtF9jIix88orwfTi0lJiqINnlWo6p93MtZEoaehZnmCzEmLL0eqjA3eGVeyQhMtxdV+Mlsgfwhh0+M/k1/V7Q==} + cpu: [arm64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@rollup/rollup-linux-arm64-musl@4.10.0: + resolution: {integrity: sha512-9GW9yA30ib+vfFiwjX+N7PnjTnCMiUffhWj4vkG4ukYv1kJ4T9gHNg8zw+ChsOccM27G9yXrEtMScf1LaCuoWQ==} + cpu: [arm64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@rollup/rollup-linux-riscv64-gnu@4.10.0: + resolution: {integrity: sha512-X1ES+V4bMq2ws5fF4zHornxebNxMXye0ZZjUrzOrf7UMx1d6wMQtfcchZ8SqUnQPPHdOyOLW6fTcUiFgHFadRA==} + cpu: [riscv64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@rollup/rollup-linux-x64-gnu@4.10.0: + resolution: {integrity: sha512-w/5OpT2EnI/Xvypw4FIhV34jmNqU5PZjZue2l2Y3ty1Ootm3SqhI+AmfhlUYGBTd9JnpneZCDnt3uNOiOBkMyw==} + cpu: [x64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@rollup/rollup-linux-x64-musl@4.10.0: + resolution: {integrity: sha512-q/meftEe3QlwQiGYxD9rWwB21DoKQ9Q8wA40of/of6yGHhZuGfZO0c3WYkN9dNlopHlNT3mf5BPsUSxoPuVQaw==} + cpu: [x64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@rollup/rollup-win32-arm64-msvc@4.10.0: + resolution: {integrity: sha512-NrR6667wlUfP0BHaEIKgYM/2va+Oj+RjZSASbBMnszM9k+1AmliRjHc3lJIiOehtSSjqYiO7R6KLNrWOX+YNSQ==} + cpu: [arm64] + os: [win32] + requiresBuild: true + dev: true + optional: true + + /@rollup/rollup-win32-ia32-msvc@4.10.0: + resolution: {integrity: sha512-FV0Tpt84LPYDduIDcXvEC7HKtyXxdvhdAOvOeWMWbQNulxViH2O07QXkT/FffX4FqEI02jEbCJbr+YcuKdyyMg==} + cpu: [ia32] + os: [win32] + requiresBuild: true + dev: true + optional: true + + /@rollup/rollup-win32-x64-msvc@4.10.0: + resolution: {integrity: sha512-OZoJd+o5TaTSQeFFQ6WjFCiltiYVjIdsXxwu/XZ8qRpsvMQr4UsVrE5UyT9RIvsnuF47DqkJKhhVZ2Q9YW9IpQ==} + cpu: [x64] + os: [win32] + requiresBuild: true + dev: true + optional: true + + /@sinclair/typebox@0.27.8: + resolution: {integrity: sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==} + dev: true + + /@types/estree@1.0.5: + resolution: {integrity: sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==} + dev: true + + /@vitest/expect@1.2.2: + resolution: {integrity: sha512-3jpcdPAD7LwHUUiT2pZTj2U82I2Tcgg2oVPvKxhn6mDI2On6tfvPQTjAI4628GUGDZrCm4Zna9iQHm5cEexOAg==} + dependencies: + '@vitest/spy': 1.2.2 + '@vitest/utils': 1.2.2 + chai: 4.4.1 + dev: true + + /@vitest/runner@1.2.2: + resolution: {integrity: sha512-JctG7QZ4LSDXr5CsUweFgcpEvrcxOV1Gft7uHrvkQ+fsAVylmWQvnaAr/HDp3LAH1fztGMQZugIheTWjaGzYIg==} + dependencies: + '@vitest/utils': 1.2.2 + p-limit: 5.0.0 + pathe: 1.1.2 + dev: true + + /@vitest/snapshot@1.2.2: + resolution: {integrity: sha512-SmGY4saEw1+bwE1th6S/cZmPxz/Q4JWsl7LvbQIky2tKE35US4gd0Mjzqfr84/4OD0tikGWaWdMja/nWL5NIPA==} + dependencies: + magic-string: 0.30.7 + pathe: 1.1.2 + pretty-format: 29.7.0 + dev: true + + /@vitest/spy@1.2.2: + resolution: {integrity: sha512-k9Gcahssw8d7X3pSLq3e3XEu/0L78mUkCjivUqCQeXJm9clfXR/Td8+AP+VC1O6fKPIDLcHDTAmBOINVuv6+7g==} + dependencies: + tinyspy: 2.2.1 + dev: true + + /@vitest/utils@1.2.2: + resolution: {integrity: sha512-WKITBHLsBHlpjnDQahr+XK6RE7MiAsgrIkr0pGhQ9ygoxBfUeG0lUG5iLlzqjmKSlBv3+j5EGsriBzh+C3Tq9g==} + dependencies: + diff-sequences: 29.6.3 + estree-walker: 3.0.3 + loupe: 2.3.7 + pretty-format: 29.7.0 + dev: true + + /acorn-walk@8.3.2: + resolution: {integrity: sha512-cjkyv4OtNCIeqhHrfS81QWXoCBPExR/J62oyEqepVw8WaQeSqpW2uhuLPh1m9eWhDuOo/jUXVTlifvesOWp/4A==} + engines: {node: '>=0.4.0'} + dev: true + + /acorn@8.11.3: + resolution: {integrity: sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==} + engines: {node: '>=0.4.0'} + hasBin: true + dev: true + + /ansi-styles@5.2.0: + resolution: {integrity: sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==} + engines: {node: '>=10'} + dev: true + + /assertion-error@1.1.0: + resolution: {integrity: sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==} + dev: true + + /base64-js@1.5.1: + resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} + dev: false + + /better-sqlite3@9.4.0: + resolution: {integrity: sha512-5kynxekMxSjCMiFyUBLHggFcJkCmiZi6fUkiGz/B5GZOvdRWQJD0klqCx5/Y+bm2AKP7I/DHbSFx26AxjruWNg==} + requiresBuild: true + dependencies: + bindings: 1.5.0 + prebuild-install: 7.1.1 + dev: false + + /bindings@1.5.0: + resolution: {integrity: sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==} + dependencies: + file-uri-to-path: 1.0.0 + dev: false + + /bl@4.1.0: + resolution: {integrity: sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==} + dependencies: + buffer: 5.7.1 + inherits: 2.0.4 + readable-stream: 3.6.2 + dev: false + + /buffer-writer@2.0.0: + resolution: {integrity: sha512-a7ZpuTZU1TRtnwyCNW3I5dc0wWNC3VR9S++Ewyk2HHZdrO3CQJqSpd+95Us590V6AL7JqUAH2IwZ/398PmNFgw==} + engines: {node: '>=4'} + dev: false + + /buffer@5.7.1: + resolution: {integrity: sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==} + dependencies: + base64-js: 1.5.1 + ieee754: 1.2.1 + dev: false + + /cac@6.7.14: + resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==} + engines: {node: '>=8'} + dev: true + + /chai@4.4.1: + resolution: {integrity: sha512-13sOfMv2+DWduEU+/xbun3LScLoqN17nBeTLUsmDfKdoiC1fr0n9PU4guu4AhRcOVFk/sW8LyZWHuhWtQZiF+g==} + engines: {node: '>=4'} + dependencies: + assertion-error: 1.1.0 + check-error: 1.0.3 + deep-eql: 4.1.3 + get-func-name: 2.0.2 + loupe: 2.3.7 + pathval: 1.1.1 + type-detect: 4.0.8 + dev: true + + /check-error@1.0.3: + resolution: {integrity: sha512-iKEoDYaRmd1mxM90a2OEfWhjsjPpYPuQ+lMYsoxB126+t8fw7ySEO48nmDg5COTjxDI65/Y2OWpeEHk3ZOe8zg==} + dependencies: + get-func-name: 2.0.2 + dev: true + + /chownr@1.1.4: + resolution: {integrity: sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==} + dev: false + + /cross-spawn@7.0.3: + resolution: {integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==} + engines: {node: '>= 8'} + dependencies: + path-key: 3.1.1 + shebang-command: 2.0.0 + which: 2.0.2 + dev: true + + /debug@4.3.4: + resolution: {integrity: sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + dependencies: + ms: 2.1.2 + dev: true + + /decompress-response@6.0.0: + resolution: {integrity: sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==} + engines: {node: '>=10'} + dependencies: + mimic-response: 3.1.0 + dev: false + + /deep-eql@4.1.3: + resolution: {integrity: sha512-WaEtAOpRA1MQ0eohqZjpGD8zdI0Ovsm8mmFhaDN8dvDZzyoUMcYDnf5Y6iu7HTXxf8JDS23qWa4a+hKCDyOPzw==} + engines: {node: '>=6'} + dependencies: + type-detect: 4.0.8 + dev: true + + /deep-extend@0.6.0: + resolution: {integrity: sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==} + engines: {node: '>=4.0.0'} + dev: false + + /detect-libc@2.0.2: + resolution: {integrity: sha512-UX6sGumvvqSaXgdKGUsgZWqcUyIXZ/vZTrlRT/iobiKhGL0zL4d3osHj3uqllWJK+i+sixDS/3COVEOFbupFyw==} + engines: {node: '>=8'} + dev: false + + /diff-sequences@29.6.3: + resolution: {integrity: sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dev: true + + /end-of-stream@1.4.4: + resolution: {integrity: sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==} + dependencies: + once: 1.4.0 + dev: false + + /esbuild@0.19.12: + resolution: {integrity: sha512-aARqgq8roFBj054KvQr5f1sFu0D65G+miZRCuJyJ0G13Zwx7vRar5Zhn2tkQNzIXcBrNVsv/8stehpj+GAjgbg==} + engines: {node: '>=12'} + hasBin: true + requiresBuild: true + optionalDependencies: + '@esbuild/aix-ppc64': 0.19.12 + '@esbuild/android-arm': 0.19.12 + '@esbuild/android-arm64': 0.19.12 + '@esbuild/android-x64': 0.19.12 + '@esbuild/darwin-arm64': 0.19.12 + '@esbuild/darwin-x64': 0.19.12 + '@esbuild/freebsd-arm64': 0.19.12 + '@esbuild/freebsd-x64': 0.19.12 + '@esbuild/linux-arm': 0.19.12 + '@esbuild/linux-arm64': 0.19.12 + '@esbuild/linux-ia32': 0.19.12 + '@esbuild/linux-loong64': 0.19.12 + '@esbuild/linux-mips64el': 0.19.12 + '@esbuild/linux-ppc64': 0.19.12 + '@esbuild/linux-riscv64': 0.19.12 + '@esbuild/linux-s390x': 0.19.12 + '@esbuild/linux-x64': 0.19.12 + '@esbuild/netbsd-x64': 0.19.12 + '@esbuild/openbsd-x64': 0.19.12 + '@esbuild/sunos-x64': 0.19.12 + '@esbuild/win32-arm64': 0.19.12 + '@esbuild/win32-ia32': 0.19.12 + '@esbuild/win32-x64': 0.19.12 + dev: true + + /estree-walker@3.0.3: + resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} + dependencies: + '@types/estree': 1.0.5 + dev: true + + /execa@8.0.1: + resolution: {integrity: sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==} + engines: {node: '>=16.17'} + dependencies: + cross-spawn: 7.0.3 + get-stream: 8.0.1 + human-signals: 5.0.0 + is-stream: 3.0.0 + merge-stream: 2.0.0 + npm-run-path: 5.2.0 + onetime: 6.0.0 + signal-exit: 4.1.0 + strip-final-newline: 3.0.0 + dev: true + + /expand-template@2.0.3: + resolution: {integrity: sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==} + engines: {node: '>=6'} + dev: false + + /file-uri-to-path@1.0.0: + resolution: {integrity: sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==} + dev: false + + /fs-constants@1.0.0: + resolution: {integrity: sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==} + dev: false + + /fsevents@2.3.3: + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + requiresBuild: true + dev: true + optional: true + + /get-func-name@2.0.2: + resolution: {integrity: sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==} + dev: true + + /get-stream@8.0.1: + resolution: {integrity: sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==} + engines: {node: '>=16'} + dev: true + + /github-from-package@0.0.0: + resolution: {integrity: sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==} + dev: false + + /human-signals@5.0.0: + resolution: {integrity: sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==} + engines: {node: '>=16.17.0'} + dev: true + + /ieee754@1.2.1: + resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} + dev: false + + /inherits@2.0.4: + resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} + dev: false + + /ini@1.3.8: + resolution: {integrity: sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==} + dev: false + + /is-stream@3.0.0: + resolution: {integrity: sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + dev: true + + /isexe@2.0.0: + resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + dev: true + + /jsonc-parser@3.2.1: + resolution: {integrity: sha512-AilxAyFOAcK5wA1+LeaySVBrHsGQvUFCDWXKpZjzaL0PqW+xfBOttn8GNtWKFWqneyMZj41MWF9Kl6iPWLwgOA==} + dev: true + + /local-pkg@0.5.0: + resolution: {integrity: sha512-ok6z3qlYyCDS4ZEU27HaU6x/xZa9Whf8jD4ptH5UZTQYZVYeb9bnZ3ojVhiJNLiXK1Hfc0GNbLXcmZ5plLDDBg==} + engines: {node: '>=14'} + dependencies: + mlly: 1.5.0 + pkg-types: 1.0.3 + dev: true + + /loupe@2.3.7: + resolution: {integrity: sha512-zSMINGVYkdpYSOBmLi0D1Uo7JU9nVdQKrHxC8eYlV+9YKK9WePqAlL7lSlorG/U2Fw1w0hTBmaa/jrQ3UbPHtA==} + dependencies: + get-func-name: 2.0.2 + dev: true + + /lru-cache@6.0.0: + resolution: {integrity: sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==} + engines: {node: '>=10'} + dependencies: + yallist: 4.0.0 + dev: false + + /magic-string@0.30.7: + resolution: {integrity: sha512-8vBuFF/I/+OSLRmdf2wwFCJCz+nSn0m6DPvGH1fS/KiQoSaR+sETbov0eIk9KhEKy8CYqIkIAnbohxT/4H0kuA==} + engines: {node: '>=12'} + dependencies: + '@jridgewell/sourcemap-codec': 1.4.15 + dev: true + + /merge-stream@2.0.0: + resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==} + dev: true + + /mimic-fn@4.0.0: + resolution: {integrity: sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==} + engines: {node: '>=12'} + dev: true + + /mimic-response@3.1.0: + resolution: {integrity: sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==} + engines: {node: '>=10'} + dev: false + + /minimist@1.2.8: + resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} + dev: false + + /mkdirp-classic@0.5.3: + resolution: {integrity: sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==} + dev: false + + /mlly@1.5.0: + resolution: {integrity: sha512-NPVQvAY1xr1QoVeG0cy8yUYC7FQcOx6evl/RjT1wL5FvzPnzOysoqB/jmx/DhssT2dYa8nxECLAaFI/+gVLhDQ==} + dependencies: + acorn: 8.11.3 + pathe: 1.1.2 + pkg-types: 1.0.3 + ufo: 1.4.0 + dev: true + + /ms@2.1.2: + resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==} + dev: true + + /nanoid@3.3.7: + resolution: {integrity: sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + dev: true + + /napi-build-utils@1.0.2: + resolution: {integrity: sha512-ONmRUqK7zj7DWX0D9ADe03wbwOBZxNAfF20PlGfCWQcD3+/MakShIHrMqx9YwPTfxDdF1zLeL+RGZiR9kGMLdg==} + dev: false + + /node-abi@3.54.0: + resolution: {integrity: sha512-p7eGEiQil0YUV3ItH4/tBb781L5impVmmx2E9FRKF7d18XXzp4PGT2tdYMFY6wQqgxD0IwNZOiSJ0/K0fSi/OA==} + engines: {node: '>=10'} + dependencies: + semver: 7.6.0 + dev: false + + /npm-run-path@5.2.0: + resolution: {integrity: sha512-W4/tgAXFqFA0iL7fk0+uQ3g7wkL8xJmx3XdK0VGb4cHW//eZTtKGvFBBoRKVTpY7n6ze4NL9ly7rgXcHufqXKg==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + dependencies: + path-key: 4.0.0 + dev: true + + /once@1.4.0: + resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} + dependencies: + wrappy: 1.0.2 + dev: false + + /onetime@6.0.0: + resolution: {integrity: sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==} + engines: {node: '>=12'} + dependencies: + mimic-fn: 4.0.0 + dev: true + + /p-limit@5.0.0: + resolution: {integrity: sha512-/Eaoq+QyLSiXQ4lyYV23f14mZRQcXnxfHrN0vCai+ak9G0pp9iEQukIIZq5NccEvwRB8PUnZT0KsOoDCINS1qQ==} + engines: {node: '>=18'} + dependencies: + yocto-queue: 1.0.0 + dev: true + + /packet-reader@1.0.0: + resolution: {integrity: sha512-HAKu/fG3HpHFO0AA8WE8q2g+gBJaZ9MG7fcKk+IJPLTGAD6Psw4443l+9DGRbOIh3/aXr7Phy0TjilYivJo5XQ==} + dev: false + + /path-key@3.1.1: + resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} + engines: {node: '>=8'} + dev: true + + /path-key@4.0.0: + resolution: {integrity: sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==} + engines: {node: '>=12'} + dev: true + + /pathe@1.1.2: + resolution: {integrity: sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==} + dev: true + + /pathval@1.1.1: + resolution: {integrity: sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==} + dev: true + + /pg-cloudflare@1.1.1: + resolution: {integrity: sha512-xWPagP/4B6BgFO+EKz3JONXv3YDgvkbVrGw2mTo3D6tVDQRh1e7cqVGvyR3BE+eQgAvx1XhW/iEASj4/jCWl3Q==} + requiresBuild: true + dev: false + optional: true + + /pg-connection-string@2.6.2: + resolution: {integrity: sha512-ch6OwaeaPYcova4kKZ15sbJ2hKb/VP48ZD2gE7i1J+L4MspCtBMAx8nMgz7bksc7IojCIIWuEhHibSMFH8m8oA==} + dev: false + + /pg-int8@1.0.1: + resolution: {integrity: sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==} + engines: {node: '>=4.0.0'} + dev: false + + /pg-pool@3.6.1(pg@8.11.3): + resolution: {integrity: sha512-jizsIzhkIitxCGfPRzJn1ZdcosIt3pz9Sh3V01fm1vZnbnCMgmGl5wvGGdNN2EL9Rmb0EcFoCkixH4Pu+sP9Og==} + peerDependencies: + pg: '>=8.0' + dependencies: + pg: 8.11.3 + dev: false + + /pg-protocol@1.6.0: + resolution: {integrity: sha512-M+PDm637OY5WM307051+bsDia5Xej6d9IR4GwJse1qA1DIhiKlksvrneZOYQq42OM+spubpcNYEo2FcKQrDk+Q==} + dev: false + + /pg-types@2.2.0: + resolution: {integrity: sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==} + engines: {node: '>=4'} + dependencies: + pg-int8: 1.0.1 + postgres-array: 2.0.0 + postgres-bytea: 1.0.0 + postgres-date: 1.0.7 + postgres-interval: 1.2.0 + dev: false + + /pg@8.11.3: + resolution: {integrity: sha512-+9iuvG8QfaaUrrph+kpF24cXkH1YOOUeArRNYIxq1viYHZagBxrTno7cecY1Fa44tJeZvaoG+Djpkc3JwehN5g==} + engines: {node: '>= 8.0.0'} + peerDependencies: + pg-native: '>=3.0.1' + peerDependenciesMeta: + pg-native: + optional: true + dependencies: + buffer-writer: 2.0.0 + packet-reader: 1.0.0 + pg-connection-string: 2.6.2 + pg-pool: 3.6.1(pg@8.11.3) + pg-protocol: 1.6.0 + pg-types: 2.2.0 + pgpass: 1.0.5 + optionalDependencies: + pg-cloudflare: 1.1.1 + dev: false + + /pgpass@1.0.5: + resolution: {integrity: sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug==} + dependencies: + split2: 4.2.0 + dev: false + + /picocolors@1.0.0: + resolution: {integrity: sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==} + dev: true + + /pkg-types@1.0.3: + resolution: {integrity: sha512-nN7pYi0AQqJnoLPC9eHFQ8AcyaixBUOwvqc5TDnIKCMEE6I0y8P7OKA7fPexsXGCGxQDl/cmrLAp26LhcwxZ4A==} + dependencies: + jsonc-parser: 3.2.1 + mlly: 1.5.0 + pathe: 1.1.2 + dev: true + + /postcss@8.4.35: + resolution: {integrity: sha512-u5U8qYpBCpN13BsiEB0CbR1Hhh4Gc0zLFuedrHJKMctHCHAGrMdG0PRM/KErzAL3CU6/eckEtmHNB3x6e3c0vA==} + engines: {node: ^10 || ^12 || >=14} + dependencies: + nanoid: 3.3.7 + picocolors: 1.0.0 + source-map-js: 1.0.2 + dev: true + + /postgres-array@2.0.0: + resolution: {integrity: sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==} + engines: {node: '>=4'} + dev: false + + /postgres-bytea@1.0.0: + resolution: {integrity: sha512-xy3pmLuQqRBZBXDULy7KbaitYqLcmxigw14Q5sj8QBVLqEwXfeybIKVWiqAXTlcvdvb0+xkOtDbfQMOf4lST1w==} + engines: {node: '>=0.10.0'} + dev: false + + /postgres-date@1.0.7: + resolution: {integrity: sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==} + engines: {node: '>=0.10.0'} + dev: false + + /postgres-interval@1.2.0: + resolution: {integrity: sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==} + engines: {node: '>=0.10.0'} + dependencies: + xtend: 4.0.2 + dev: false + + /prebuild-install@7.1.1: + resolution: {integrity: sha512-jAXscXWMcCK8GgCoHOfIr0ODh5ai8mj63L2nWrjuAgXE6tDyYGnx4/8o/rCgU+B4JSyZBKbeZqzhtwtC3ovxjw==} + engines: {node: '>=10'} + hasBin: true + dependencies: + detect-libc: 2.0.2 + expand-template: 2.0.3 + github-from-package: 0.0.0 + minimist: 1.2.8 + mkdirp-classic: 0.5.3 + napi-build-utils: 1.0.2 + node-abi: 3.54.0 + pump: 3.0.0 + rc: 1.2.8 + simple-get: 4.0.1 + tar-fs: 2.1.1 + tunnel-agent: 0.6.0 + dev: false + + /pretty-format@29.7.0: + resolution: {integrity: sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dependencies: + '@jest/schemas': 29.6.3 + ansi-styles: 5.2.0 + react-is: 18.2.0 + dev: true + + /pump@3.0.0: + resolution: {integrity: sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==} + dependencies: + end-of-stream: 1.4.4 + once: 1.4.0 + dev: false + + /rc@1.2.8: + resolution: {integrity: sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==} + hasBin: true + dependencies: + deep-extend: 0.6.0 + ini: 1.3.8 + minimist: 1.2.8 + strip-json-comments: 2.0.1 + dev: false + + /react-is@18.2.0: + resolution: {integrity: sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==} + dev: true + + /readable-stream@3.6.2: + resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==} + engines: {node: '>= 6'} + dependencies: + inherits: 2.0.4 + string_decoder: 1.3.0 + util-deprecate: 1.0.2 + dev: false + + /rollup@4.10.0: + resolution: {integrity: sha512-t2v9G2AKxcQ8yrG+WGxctBes1AomT0M4ND7jTFBCVPXQ/WFTvNSefIrNSmLKhIKBrvN8SG+CZslimJcT3W2u2g==} + engines: {node: '>=18.0.0', npm: '>=8.0.0'} + hasBin: true + dependencies: + '@types/estree': 1.0.5 + optionalDependencies: + '@rollup/rollup-android-arm-eabi': 4.10.0 + '@rollup/rollup-android-arm64': 4.10.0 + '@rollup/rollup-darwin-arm64': 4.10.0 + '@rollup/rollup-darwin-x64': 4.10.0 + '@rollup/rollup-linux-arm-gnueabihf': 4.10.0 + '@rollup/rollup-linux-arm64-gnu': 4.10.0 + '@rollup/rollup-linux-arm64-musl': 4.10.0 + '@rollup/rollup-linux-riscv64-gnu': 4.10.0 + '@rollup/rollup-linux-x64-gnu': 4.10.0 + '@rollup/rollup-linux-x64-musl': 4.10.0 + '@rollup/rollup-win32-arm64-msvc': 4.10.0 + '@rollup/rollup-win32-ia32-msvc': 4.10.0 + '@rollup/rollup-win32-x64-msvc': 4.10.0 + fsevents: 2.3.3 + dev: true + + /safe-buffer@5.2.1: + resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} + dev: false + + /semver@7.6.0: + resolution: {integrity: sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==} + engines: {node: '>=10'} + hasBin: true + dependencies: + lru-cache: 6.0.0 + dev: false + + /shebang-command@2.0.0: + resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} + engines: {node: '>=8'} + dependencies: + shebang-regex: 3.0.0 + dev: true + + /shebang-regex@3.0.0: + resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} + engines: {node: '>=8'} + dev: true + + /siginfo@2.0.0: + resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} + dev: true + + /signal-exit@4.1.0: + resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} + engines: {node: '>=14'} + dev: true + + /simple-concat@1.0.1: + resolution: {integrity: sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==} + dev: false + + /simple-get@4.0.1: + resolution: {integrity: sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==} + dependencies: + decompress-response: 6.0.0 + once: 1.4.0 + simple-concat: 1.0.1 + dev: false + + /source-map-js@1.0.2: + resolution: {integrity: sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==} + engines: {node: '>=0.10.0'} + dev: true + + /split2@4.2.0: + resolution: {integrity: sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==} + engines: {node: '>= 10.x'} + dev: false + + /sqlite-vss-darwin-arm64@0.1.2: + resolution: {integrity: sha512-zyDk9eg33nBABrUC4cqQ7el8KJaRPzsqp8Y/nGZ0CAt7o1PMqLoCOgREorill5MGiZEBmLqxdAgw0O2MFwq4mw==} + cpu: [arm64] + os: [darwin] + requiresBuild: true + dev: false + optional: true + + /sqlite-vss-darwin-x64@0.1.2: + resolution: {integrity: sha512-w+ODOH2dNkyO6UaGclwC0jwNf/FBsKaE53XKJ7dFmpOvlvO0/9sA1stkWXygykRVWwa3UD8ow0qbQpRwdOFyqg==} + cpu: [x64] + os: [darwin] + requiresBuild: true + dev: false + optional: true + + /sqlite-vss-linux-x64@0.1.2: + resolution: {integrity: sha512-y1qktcHAZcfN1nYMcF5os/cCRRyaisaNc2C9I3ceLKLPAqUWIocsOdD5nNK/dIeGPag/QeT2ZItJ6uYWciLiAg==} + cpu: [x64] + os: [linux] + requiresBuild: true + dev: false + optional: true + + /sqlite-vss@0.1.2: + resolution: {integrity: sha512-MgTz3GLT04ckv1kaesbrsUU6/kcVsA6vGeCS/HO5d/8zKqCuZFCD0QlJaQnS6zwaMyPG++BO/uu40MMrMa0cow==} + optionalDependencies: + sqlite-vss-darwin-arm64: 0.1.2 + sqlite-vss-darwin-x64: 0.1.2 + sqlite-vss-linux-x64: 0.1.2 + dev: false + + /stackback@0.0.2: + resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} + dev: true + + /std-env@3.7.0: + resolution: {integrity: sha512-JPbdCEQLj1w5GilpiHAx3qJvFndqybBysA3qUOnznweH4QbNYUsW/ea8QzSrnh0vNsezMMw5bcVool8lM0gwzg==} + dev: true + + /string_decoder@1.3.0: + resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==} + dependencies: + safe-buffer: 5.2.1 + dev: false + + /strip-final-newline@3.0.0: + resolution: {integrity: sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==} + engines: {node: '>=12'} + dev: true + + /strip-json-comments@2.0.1: + resolution: {integrity: sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==} + engines: {node: '>=0.10.0'} + dev: false + + /strip-literal@1.3.0: + resolution: {integrity: sha512-PugKzOsyXpArk0yWmUwqOZecSO0GH0bPoctLcqNDH9J04pVW3lflYE0ujElBGTloevcxF5MofAOZ7C5l2b+wLg==} + dependencies: + acorn: 8.11.3 + dev: true + + /tar-fs@2.1.1: + resolution: {integrity: sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng==} + dependencies: + chownr: 1.1.4 + mkdirp-classic: 0.5.3 + pump: 3.0.0 + tar-stream: 2.2.0 + dev: false + + /tar-stream@2.2.0: + resolution: {integrity: sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==} + engines: {node: '>=6'} + dependencies: + bl: 4.1.0 + end-of-stream: 1.4.4 + fs-constants: 1.0.0 + inherits: 2.0.4 + readable-stream: 3.6.2 + dev: false + + /tinybench@2.6.0: + resolution: {integrity: sha512-N8hW3PG/3aOoZAN5V/NSAEDz0ZixDSSt5b/a05iqtpgfLWMSVuCo7w0k2vVvEjdrIoeGqZzweX2WlyioNIHchA==} + dev: true + + /tinypool@0.8.2: + resolution: {integrity: sha512-SUszKYe5wgsxnNOVlBYO6IC+8VGWdVGZWAqUxp3UErNBtptZvWbwyUOyzNL59zigz2rCA92QiL3wvG+JDSdJdQ==} + engines: {node: '>=14.0.0'} + dev: true + + /tinyspy@2.2.1: + resolution: {integrity: sha512-KYad6Vy5VDWV4GH3fjpseMQ/XU2BhIYP7Vzd0LG44qRWm/Yt2WCOTicFdvmgo6gWaqooMQCawTtILVQJupKu7A==} + engines: {node: '>=14.0.0'} + dev: true + + /tunnel-agent@0.6.0: + resolution: {integrity: sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==} + dependencies: + safe-buffer: 5.2.1 + dev: false + + /type-detect@4.0.8: + resolution: {integrity: sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==} + engines: {node: '>=4'} + dev: true + + /ufo@1.4.0: + resolution: {integrity: sha512-Hhy+BhRBleFjpJ2vchUNN40qgkh0366FWJGqVLYBHev0vpHTrXSA0ryT+74UiW6KWsldNurQMKGqCm1M2zBciQ==} + dev: true + + /util-deprecate@1.0.2: + resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} + dev: false + + /vite-node@1.2.2: + resolution: {integrity: sha512-1as4rDTgVWJO3n1uHmUYqq7nsFgINQ9u+mRcXpjeOMJUmviqNKjcZB7UfRZrlM7MjYXMKpuWp5oGkjaFLnjawg==} + engines: {node: ^18.0.0 || >=20.0.0} + hasBin: true + dependencies: + cac: 6.7.14 + debug: 4.3.4 + pathe: 1.1.2 + picocolors: 1.0.0 + vite: 5.1.1 + transitivePeerDependencies: + - '@types/node' + - less + - lightningcss + - sass + - stylus + - sugarss + - supports-color + - terser + dev: true + + /vite@5.1.1: + resolution: {integrity: sha512-wclpAgY3F1tR7t9LL5CcHC41YPkQIpKUGeIuT8MdNwNZr6OqOTLs7JX5vIHAtzqLWXts0T+GDrh9pN2arneKqg==} + engines: {node: ^18.0.0 || >=20.0.0} + hasBin: true + peerDependencies: + '@types/node': ^18.0.0 || >=20.0.0 + less: '*' + lightningcss: ^1.21.0 + sass: '*' + stylus: '*' + sugarss: '*' + terser: ^5.4.0 + peerDependenciesMeta: + '@types/node': + optional: true + less: + optional: true + lightningcss: + optional: true + sass: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + dependencies: + esbuild: 0.19.12 + postcss: 8.4.35 + rollup: 4.10.0 + optionalDependencies: + fsevents: 2.3.3 + dev: true + + /vitest@1.2.2: + resolution: {integrity: sha512-d5Ouvrnms3GD9USIK36KG8OZ5bEvKEkITFtnGv56HFaSlbItJuYr7hv2Lkn903+AvRAgSixiamozUVfORUekjw==} + engines: {node: ^18.0.0 || >=20.0.0} + hasBin: true + peerDependencies: + '@edge-runtime/vm': '*' + '@types/node': ^18.0.0 || >=20.0.0 + '@vitest/browser': ^1.0.0 + '@vitest/ui': ^1.0.0 + happy-dom: '*' + jsdom: '*' + peerDependenciesMeta: + '@edge-runtime/vm': + optional: true + '@types/node': + optional: true + '@vitest/browser': + optional: true + '@vitest/ui': + optional: true + happy-dom: + optional: true + jsdom: + optional: true + dependencies: + '@vitest/expect': 1.2.2 + '@vitest/runner': 1.2.2 + '@vitest/snapshot': 1.2.2 + '@vitest/spy': 1.2.2 + '@vitest/utils': 1.2.2 + acorn-walk: 8.3.2 + cac: 6.7.14 + chai: 4.4.1 + debug: 4.3.4 + execa: 8.0.1 + local-pkg: 0.5.0 + magic-string: 0.30.7 + pathe: 1.1.2 + picocolors: 1.0.0 + std-env: 3.7.0 + strip-literal: 1.3.0 + tinybench: 2.6.0 + tinypool: 0.8.2 + vite: 5.1.1 + vite-node: 1.2.2 + why-is-node-running: 2.2.2 + transitivePeerDependencies: + - less + - lightningcss + - sass + - stylus + - sugarss + - supports-color + - terser + dev: true + + /which@2.0.2: + resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} + engines: {node: '>= 8'} + hasBin: true + dependencies: + isexe: 2.0.0 + dev: true + + /why-is-node-running@2.2.2: + resolution: {integrity: sha512-6tSwToZxTOcotxHeA+qGCq1mVzKR3CwcJGmVcY+QE8SHy6TnpFnh8PAvPNHYr7EcuVeG0QSMxtYCuO1ta/G/oA==} + engines: {node: '>=8'} + hasBin: true + dependencies: + siginfo: 2.0.0 + stackback: 0.0.2 + dev: true + + /wrappy@1.0.2: + resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} + dev: false + + /xtend@4.0.2: + resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==} + engines: {node: '>=0.4'} + dev: false + + /yallist@4.0.0: + resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==} + dev: false + + /yocto-queue@1.0.0: + resolution: {integrity: sha512-9bnSc/HEW2uRy67wc+T8UwauLuPJVn28jb+GtJY16iiKWyvmYJRXVT4UamsAEGQfPohgr2q4Tq0sQbQlxTfi1g==} + engines: {node: '>=12.20'} + dev: true diff --git a/src/index.js b/src/index.js new file mode 100644 index 0000000..dddcf5d --- /dev/null +++ b/src/index.js @@ -0,0 +1,27 @@ +/** + * @typedef {Object} DatabaseOptions + * @property {("postgres"|"sqlite")=} type + * @property {string=} file + * @property {string=} host + * @property {number=} port + * @property {string=} username + * @property {string=} password + * @property {string=} database + */ + +/** + * @param {DatabaseOptions=} options + * @returns {Promise} + */ +export async function getDatabase(options = {}) { + const type = options.type || options.host ? "postgres" : "sqlite"; + if (type === "sqlite") { + const sqlite = await import("./sqlite"); + return sqlite.getDatabase(options); + } else { + const postgres = await import("./postgres"); + return postgres.getDatabase(options); + } +} + +export default { getDatabase }; diff --git a/src/index.test.js b/src/index.test.js new file mode 100644 index 0000000..5de7074 --- /dev/null +++ b/src/index.test.js @@ -0,0 +1,23 @@ +import { it, describe, expect } from "vitest"; +import { getDatabase } from "./index.js"; +describe("sqloo tests", () => { + it("should be able to get the adapter for a postgres database", async () => { + const db = await getDatabase({ + type: "postgres", + name: process.env.SQLOO_NAME || "sqloo", + host: process.env.SQLOO_HOST || "localhost", + user: process.env.SQLOO_USER || "sqloo", + pass: process.env.SQLOO_PASS || "sqloo", + port: process.env.SQLOO_PORT || "5432", + }); + expect(db.client).toBeDefined(); + }); + + it("should be able to get the adapter for a sqlite database", async () => { + const db = await getDatabase({ + type: "sqlite", + file: "::memory::", + }); + expect(db.client).toBeDefined(); + }); +}); diff --git a/src/postgres.js b/src/postgres.js new file mode 100644 index 0000000..3a638a7 --- /dev/null +++ b/src/postgres.js @@ -0,0 +1,221 @@ +import { Pool } from "pg"; + +/** + * @typedef {Object} DatabaseOptions + * @property {string=} connectionString + */ + +/** + * @param {DatabaseOptions=} options + * @returns {{ + * client: PoolClient, + * insert: (table: string, data: Object) => any, + * get: (table: string, where: Object) => any + * }} + */ +export function getDatabase(options) { + const { + database = process.env.SQLOO_DATABASE, + host = process.env.SQLOO_HOST || "localhost", + user = process.env.SQLOO_USER, + password = process.env.SQLOO_PASSWORD, + port = process.env.SQLOO_PORT || "5432", + } = options; + + const client = new Pool({ + host, + user, + password, + port, + database, + }); + + /** + * Inserts data into a table and returns the inserted rows. + * @param {string} table - The name of the table. + * @param {Object | Object[]} data - The data to insert. + * @returns {Promise} The inserted rows, return type will match data + */ + const insert = async (table, data) => { + // If data is an array, we need to handle multiple rows + if (Array.isArray(data)) { + const keys = Object.keys(data[0]); + const placeholders = data + .map( + (_, i) => + `(${keys.map((_, j) => `$${i * keys.length + j + 1}`).join(", ")})` + ) + .join(", "); + const query = `INSERT INTO ${table} (${keys.join( + ", " + )}) VALUES ${placeholders}`; + const values = data.reduce( + (acc, row) => acc.concat(Object.values(row)), + [] + ); + const result = await client.query(query, values); + return { operation: "insert", affected: result.rowCount }; + } else { + // If data is an object, we handle a single row + const keys = Object.keys(data); + const values = Object.values(data); + const placeholders = keys.map((_, i) => `$${i + 1}`).join(", "); + const query = `INSERT INTO ${table} (${keys.join( + ", " + )}) VALUES (${placeholders})`; + const result = await client.query(query, values); + return { operation: "insert", affected: result.rowCount }; + } + }; + + /** + * Retrieves a row from a table based on a where clause. + * @param {string} table - The name of the table. + * @param {Object} where - The where clause. + * @returns {Promise} The retrieved row. + */ + const get = async (table, where) => { + const keys = Object.keys(where); + const values = Object.values(where); + const whereClause = keys + .map((key, i) => `${key} = $${i + 1}`) + .join(" AND "); + const query = `SELECT * FROM ${table} WHERE ${whereClause}`; + return client.query(query, values); + }; + + /** + * Retrieves multiple rows from a table based on a where clause. + * @param {string} table - The name of the table. + * @param {Object} where - The where clause. + * @returns {Promise} The retrieved rows. + */ + const list = async (table, where) => { + const keys = Object.keys(where); + const values = Object.values(where); + const whereClause = keys + .map((key, i) => `${key} = $${i + 1}`) + .join(" AND "); + const query = `SELECT * FROM ${table} WHERE ${whereClause}`; + return client.query(query, values); + }; + + /** + * Updates rows in a table based on a where clause and returns the updated rows. + * @param {string} table - The name of the table. + * @param {Object} where - The where clause. + * @param {Object} data - The data to update. + * @returns {Promise} The updated rows. + */ + const update = async (table, where, data) => { + const keys = Object.keys(data); + const values = Object.values(data); + const setClause = keys.map((key, i) => `${key} = $${i + 1}`).join(", "); + const whereKeys = Object.keys(where); + const whereValues = Object.values(where); + const whereClause = whereKeys + .map((key, i) => `${key} = $${i + 1 + values.length}`) + .join(" AND "); + + const sql = `UPDATE ${table} SET ${setClause} WHERE ${whereClause}`; + const result = await client.query(sql, [...values, ...whereValues]); + return { operation: "update", affected: result.rowCount }; + }; + + /** + * Returns an object with insert, get, and list methods for a specific table. + * @param {string} table - The name of the table. + * @returns {Object} An object with insert, get, and list methods. + */ + const table = (table) => { + const insert = (data) => insert(table, data); + const get = (data) => get(table, data); + const list = (data) => list(table, data); + return { insert, get, list }; + }; + + /** + * Parses a SQL template into a SQL string and an array of values. + * @param {Array} strings - The strings in the SQL template. + * @param {...any} vars - The variables in the SQL template. + * @returns {Object} An object with a sql property and a values property. + */ + const parseTemplate = (strings, ...vars) => { + let sql = strings[0]; + const values = []; + for (let i = 0; i < vars.length; i++) { + values.push(vars[i]); + sql += "$" + (i + 1) + strings[i + 1]; + } + return { sql, values }; + }; + /** + * Executes a SQL query and returns the first column of the first row. + * @param {TemplateStringsArray} strings - The strings in the SQL template. + * @param {...any} vars - The variables in the SQL template. + * @returns {Promise} The first column of the first row. + */ + const pluck = async (strings, ...vars) => { + const { sql, values } = parseTemplate(strings, ...vars); + const result = await client.query(sql, values); + return result.rows[0][0]; + }; + + /** + * Executes a SQL query and returns the first row. + * @param {TemplateStringsArray} strings - The strings in the SQL template. + * @param {...any} vars - The variables in the SQL template. + * @returns {Promise} The first row. + */ + const single = async (strings, ...vars) => { + const { sql, values } = parseTemplate(strings, ...vars); + const result = await client.query(sql, values); + return result.rows[0]; + }; + + /** + * Executes a SQL query and returns all rows. + * @param {TemplateStringsArray} strings - The strings in the SQL template. + * @param {...any} vars - The variables in the SQL template. + * @returns {Promise} All rows. + */ + const many = async (strings, ...vars) => { + const { sql, values } = parseTemplate(strings, ...vars); + const { rows } = await client.query(sql, values); + return rows; + }; + + /** + * Executes a SQL query. + * @param {TemplateStringsArray} strings - The strings in the SQL template. + * @param {...any} vars - The variables in the SQL template. + * @returns {Promise} The result of the query. + */ + const execute = async (strings, ...vars) => { + const { sql, values } = parseTemplate(strings, ...vars); + return client.query(sql, values); + }; + + // cute aliases + const oo = many; + const oO = single; + const ox = pluck; + const xx = execute; + + return { + client, + insert, + update, + get, + list, + table, + many, + single, + pluck, + execute, + oo, + oO, + ox, + xx, + }; +} diff --git a/src/postgres.test.js b/src/postgres.test.js new file mode 100644 index 0000000..6eeafc8 --- /dev/null +++ b/src/postgres.test.js @@ -0,0 +1,111 @@ +import { it, expect, describe, beforeEach, afterEach } from "vitest"; +import { randomUUID } from "crypto"; +import { getDatabase } from "./index.js"; +describe("postgres tests", () => { + let db; + beforeEach(async () => { + db = await getDatabase({ + type: "postgres", + database: process.env.SQLOO_NAME || "sqloo", + host: process.env.SQLOO_HOST || "localhost", + user: process.env.SQLOO_USER || "sqloo", + password: process.env.SQLOO_PASS || "sqloo", + port: process.env.SQLOO_PORT || "5432", + }); + + await db.execute` + create extension if not exists "uuid-ossp"; + drop table if exists contents; + create table contents ( + id uuid primary key not null default (uuid_generate_v4()), + title text, + body text + ) + `; + }); + + afterEach(async () => { + await db.execute`drop table contents`; + await db.client.end(); + }); + + it("should be able to perform a statement", async () => { + const result = await db.many` + select * from contents + `; + expect(result).toEqual(expect.arrayContaining([])); + }); + + it("should be able to insert data", async () => { + const inserted = await db.insert("contents", { + title: "hello", + body: "world", + }); + expect(inserted).toBeDefined(); + expect(inserted.affected).toBe(1); + }); + + it("should be able to insert multiple rows at a time", async () => { + const inserted = await db.insert("contents", [ + { + title: "hello", + body: "world", + }, + { + title: "hi", + body: "universe", + }, + ]); + expect(inserted.affected).toBe(2); + }); + + it("should be able to query data with a condition", async () => { + await db.insert("contents", { title: "hello", body: "world" }); + const result = await db.many` + select * from contents where title = ${"hello"} + `; + expect(result[0]).toEqual( + expect.objectContaining({ + id: expect.any(String), + title: "hello", + body: "world", + }) + ); + }); + + it("should be able to get a single row", async () => { + await db.insert("contents", { title: "hello", body: "world" }); + const result = await db.single` + select * from contents where title = ${"hello"} + `; + expect(result).toEqual( + expect.objectContaining({ + id: expect.any(String), + title: "hello", + body: "world", + }) + ); + }); + + it("should be able to update a row", async () => { + const id = randomUUID(); + const inserted = await db.insert("contents", { + id, + title: "hello", + body: "world", + }); + expect(inserted.affected).toBe(1); + const updated = await db.update( + "contents", + { id }, + { title: "hi", body: "universe" } + ); + expect(updated.affected).toBe(1); + const result = await db.oO` + select * from contents where id = ${id} + `; + expect(result.id).toEqual(id); + expect(result.title).toEqual("hi"); + expect(result.body).toEqual("universe"); + }); +}); diff --git a/src/sqlite.js b/src/sqlite.js new file mode 100644 index 0000000..1d9b8e4 --- /dev/null +++ b/src/sqlite.js @@ -0,0 +1,205 @@ +import Database from "better-sqlite3"; +import * as sqlite_vss from "sqlite-vss"; +import { randomUUID } from "crypto"; +import { readFileSync } from "fs"; +import { resolve } from "path"; + +/** + * @typedef {Object} DatabaseOptions + * @property {string=} file + */ + +/** + * @param {DatabaseOptions=} options + * @returns {{a + * client: any, + * insert: (table: string, data: Object) => any, + * get: (table: string, where: Object) => any + * }} + */ +export function getDatabase(options = {}) { + const { file = ":memory:" } = options; + + const client = new Database(file); + client.function("uuid_generate_v4", () => randomUUID()); + client.pragma("journal_mode = WAL"); + sqlite_vss.load(client); + + /** + * Inserts data into a table and returns the inserted rows. + * @param {string} table - The name of the table. + * @param {Object | Object[]} data - The data to insert. + * @returns {Promise} The inserted rows, return type will match data + */ + const insert = async (table, data) => { + let query; + let values; + if (Array.isArray(data)) { + const keys = Object.keys(data[0]); + const placeholders = data + .map(() => `(${keys.map(() => "?").join(", ")})`) + .join(", "); + query = `INSERT INTO ${table} (${keys.join( + ", " + )}) VALUES ${placeholders}`; + values = data.reduce((acc, row) => acc.concat(Object.values(row)), []); + } else { + const keys = Object.keys(data); + const placeholders = keys.map(() => "?").join(", "); + query = `INSERT INTO ${table} (${keys.join( + ", " + )}) VALUES (${placeholders})`; + values = Object.values(data); + } + + const statement = await client.prepare(query); + const result = await statement.run(...values); + return { operation: "insert", affected: result.changes }; + }; + + /** + * Fetches a single row from a table that matches the provided conditions. + * @param {string} table - The name of the table. + * @param {Object} where - An object representing the conditions. + * @returns {Promise} The fetched row. + */ + const get = async (table, where) => { + const keys = Object.keys(where); + const values = Object.values(where); + const whereClause = keys.map((key) => `${key} = ?`).join(" and "); + const query = `select * from ${table} where ${whereClause}`; + return client.prepare(query).get(...values); + }; + + /** + * Fetches all rows from a table that match the provided conditions. + * @param {string} table - The name of the table. + * @param {Object} where - An object representing the conditions. + * @returns {Promise} The fetched rows. + */ + const list = async (table, where) => { + const keys = Object.keys(where); + const values = Object.values(where); + const whereClause = keys.map((key) => `${key} = ?`).join(" and "); + const query = `select * from ${table} where ${whereClause}`; + return client.prepare(query).all(...values); + }; + + /** + * Updates rows in a table that match the provided conditions. + * @param {string} table - The name of the table. + * @param {Object} where - An object representing the conditions. + * @param {Object} data - An object representing the new data. + * @returns {Promise} No return value. + */ + const update = async (table, where, data) => { + const keys = Object.keys(data); + const values = Object.values(data); + const setClause = keys.map((key, index) => `${key} = ?`).join(", "); + const whereKeys = Object.keys(where); + const whereValues = Object.values(where); + const whereClause = whereKeys + .map((key, index) => `${key} = ?`) + .join(" AND "); + + const sql = `UPDATE ${table} SET ${setClause} WHERE ${whereClause}`; + const result = await client.prepare(sql).run([...values, ...whereValues]); + + return { operation: "update", affected: result.changes }; + }; + + /** + * Returns an object with methods to interact with a specific table. + * @param {string} table - The name of the table. + * @returns {Object} An object with methods to interact with the table. + */ + const table = (table) => { + const insert = (data) => insert(table, data); + const get = (data) => get(table, data); + const list = (data) => list(table, data); + return { insert, get, list }; + }; + + /** + * Parses a SQL template string into a SQL query and an array of values. + * @param {TemplateStringsArray} strings - The template strings. + * @param {...any} vars - The template variables. + * @returns {Object} An object with the SQL query and the array of values. + */ + const parseTemplate = (strings, ...vars) => { + let sql = strings[0]; + const values = []; + for (let i = 0; i < vars.length; i++) { + values.push(vars[i]); + sql += "?" + strings[i + 1]; + } + return { sql, values }; + }; + + /** + * Executes a SQL query and returns the first column of the first row. + * @param {TemplateStringsArray} strings - The template strings. + * @param {...any} vars - The template variables. + * @returns {Promise} The first column of the first row. + */ + const pluck = async (strings, ...vars) => { + const { sql, values } = parseTemplate(strings, ...vars); + return client.prepare(sql).pluck().get(values); + }; + + /** + * Executes a SQL query and returns the first row. + * @param {TemplateStringsArray} strings - The template strings. + * @param {...any} vars - The template variables. + * @returns {Promise} The first row. + */ + const single = async (strings, ...vars) => { + const { sql, values } = parseTemplate(strings, ...vars); + return client.prepare(sql).get(values); + }; + + /** + * Executes a SQL query and returns all rows. + * @param {TemplateStringsArray} strings - The template strings. + * @param {...any} vars - The template variables. + * @returns {Promise} All rows. + */ + const many = async (strings, ...vars) => { + const { sql, values } = parseTemplate(strings, ...vars); + return client.prepare(sql).all(values); + }; + + /** + * Executes a SQL query and does not return any rows. + * @param {TemplateStringsArray} strings - The template strings. + * @param {...any} vars - The template variables. + * @returns {Promise} No return value. + */ + const execute = async (strings, ...vars) => { + const { sql, values } = parseTemplate(strings, ...vars); + return client.prepare(sql).run(values); + }; + + // cute aliases + const oo = many; + const oO = single; + const ox = pluck; + const xx = execute; + + return { + client, + insert, + update, + get, + list, + table, + many, + single, + pluck, + execute, + oo, + oO, + ox, + xx, + }; +} diff --git a/src/sqlite.test.js b/src/sqlite.test.js new file mode 100644 index 0000000..b2c78e9 --- /dev/null +++ b/src/sqlite.test.js @@ -0,0 +1,73 @@ +import { it, expect, describe, beforeEach, afterEach } from "vitest"; +import { randomUUID } from "crypto"; +import { getDatabase } from "./index.js"; +describe("sqlite tests", () => { + let db; + beforeEach(async () => { + db = await getDatabase(); + await db.execute` + create table contents ( + id uuid primary key not null default (uuid_generate_v4()), + title text, + body text + ) + `; + }); + + afterEach(async () => { + await db.execute` + drop table contents + `; + }); + + it("should be able to perform a statement", async () => { + const result = await db.many` + select * from contents + `; + expect(result).toEqual([]); + }); + + it("should be able to insert data", async () => { + const data = { + title: "hello", + body: "world", + }; + const inserted = await db.insert("contents", data); + expect(inserted).toBeDefined(); + }); + + it("should be able to query data with a condition", async () => { + const data = { + id: randomUUID(), + title: "hello", + body: "world", + }; + await db.insert("contents", data); + const result = await db.single` + select * from contents where id = ${data.id} + `; + expect(result).toEqual({ + id: data.id, + title: data.title, + body: data.body, + }); + }); + + it("should be able to update a row", async () => { + const data = { + id: randomUUID(), + title: "hello", + body: "world", + }; + await db.insert("contents", data); + await db.update( + "contents", + { id: data.id }, + { title: "hi", body: "universe" } + ); + const result = await db.single` + select * from contents where id = ${data.id} + `; + expect(result).toEqual({ id: data.id, title: "hi", body: "universe" }); + }); +}); diff --git a/vitest.config.js b/vitest.config.js new file mode 100644 index 0000000..74f5148 --- /dev/null +++ b/vitest.config.js @@ -0,0 +1,10 @@ +import { defineConfig, mergeConfig } from "vitest/config"; + +export default defineConfig({ + test: { + watch: false, + exclude: [], + include: ["src/**/*.test.js"], + // globalSetup: "vitest.setup.js", + }, +}); diff --git a/vitest.setup.js b/vitest.setup.js new file mode 100644 index 0000000..191daa3 --- /dev/null +++ b/vitest.setup.js @@ -0,0 +1,9 @@ +export async function setup() { + try { + // to be continued + } catch (error) { + console.error("Error in test setup", error); + } finally { + // cleanup + } +}