From 3879dc85c8568c2f40fb4493b3bfcafe3441267e Mon Sep 17 00:00:00 2001 From: Giovanni Date: Fri, 17 May 2024 06:27:20 -0400 Subject: [PATCH 1/2] Run prettier in all files --- .github/workflows/ci.yaml | 230 +- .github/workflows/pages.yaml | 36 +- .husky/install.mjs | 8 +- CHANGELOG.md | 38 +- .../examples/browser/index.js | 7 +- .../libsql-client-wasm/examples/node/index.js | 7 +- packages/libsql-client-wasm/jest.config.js | 12 +- packages/libsql-client-wasm/package-cjs.json | 2 +- packages/libsql-client-wasm/package.json | 134 +- packages/libsql-client-wasm/src/wasm.ts | 641 +++--- .../libsql-client-wasm/tsconfig.base.json | 24 +- .../tsconfig.build-esm.json | 13 +- packages/libsql-client-wasm/tsconfig.json | 10 +- packages/libsql-client-wasm/typedoc.json | 18 +- packages/libsql-client/examples/example.js | 13 +- packages/libsql-client/examples/shell.js | 46 +- packages/libsql-client/examples/sync.js | 11 +- packages/libsql-client/jest.config.js | 12 +- packages/libsql-client/package-cjs.json | 2 +- packages/libsql-client/package.json | 226 +- .../smoke_test/vercel/app/api/function.ts | 189 +- .../smoke_test/vercel/app/public/index.html | 6 +- .../smoke_test/vercel/package.json | 14 +- .../libsql-client/smoke_test/vercel/test.js | 265 ++- .../smoke_test/workers/package.json | 8 +- .../libsql-client/smoke_test/workers/test.js | 150 +- .../smoke_test/workers/worker.js | 168 +- .../src/__tests__/client.test.ts | 1986 ++++++++++------- .../libsql-client/src/__tests__/helpers.ts | 84 +- .../libsql-client/src/__tests__/uri.test.ts | 498 ++--- packages/libsql-client/src/hrana.ts | 600 ++--- packages/libsql-client/src/http.ts | 337 +-- packages/libsql-client/src/node.ts | 16 +- packages/libsql-client/src/sql_cache.ts | 150 +- packages/libsql-client/src/sqlite3.ts | 614 ++--- packages/libsql-client/src/web.ts | 24 +- packages/libsql-client/src/ws.ts | 573 ++--- packages/libsql-client/tsconfig.base.json | 22 +- .../libsql-client/tsconfig.build-cjs.json | 13 +- .../libsql-client/tsconfig.build-esm.json | 13 +- packages/libsql-client/tsconfig.json | 10 +- packages/libsql-client/typedoc.json | 18 +- packages/libsql-core/jest.config.js | 12 +- packages/libsql-core/package-cjs.json | 2 +- packages/libsql-core/package.json | 166 +- packages/libsql-core/src/api.ts | 660 +++--- packages/libsql-core/src/config.ts | 220 +- packages/libsql-core/src/uri.ts | 253 ++- packages/libsql-core/src/util.ts | 103 +- packages/libsql-core/tsconfig.base.json | 22 +- packages/libsql-core/tsconfig.build-cjs.json | 13 +- packages/libsql-core/tsconfig.build-esm.json | 13 +- packages/libsql-core/tsconfig.json | 10 +- packages/libsql-core/typedoc.json | 18 +- 54 files changed, 4662 insertions(+), 4078 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 518d7e10..fba9886f 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -11,26 +11,26 @@ jobs: defaults: run: working-directory: ./packages/libsql-client-wasm - env: {"NODE_OPTIONS": "--trace-warnings"} + env: { "NODE_OPTIONS": "--trace-warnings" } steps: - - name: "Checkout this repo" - uses: actions/checkout@v3 - - name: "Setup Node.js" - uses: actions/setup-node@v3 - with: - node-version: "18.x" - cache: "npm" - cache-dependency-path: "packages/libsql-client-wasm" - - name: "Build core" - run: "npm ci && npm run build" - working-directory: ./packages/libsql-core - - name: "Install npm dependencies" - run: "npm ci" - - name: "Build" - run: "npm run build" - - name: "Test example" - run: "cd examples/node && npm i && node index.js" - env: {"URL": "file:///tmp/example.db"} + - name: "Checkout this repo" + uses: actions/checkout@v3 + - name: "Setup Node.js" + uses: actions/setup-node@v3 + with: + node-version: "18.x" + cache: "npm" + cache-dependency-path: "packages/libsql-client-wasm" + - name: "Build core" + run: "npm ci && npm run build" + working-directory: ./packages/libsql-core + - name: "Install npm dependencies" + run: "npm ci" + - name: "Build" + run: "npm run build" + - name: "Test example" + run: "cd examples/node && npm i && node index.js" + env: { "URL": "file:///tmp/example.db" } "node-test": name: "Build and test on Node.js" @@ -38,59 +38,59 @@ jobs: defaults: run: working-directory: ./packages/libsql-client - env: {"NODE_OPTIONS": "--trace-warnings"} + env: { "NODE_OPTIONS": "--trace-warnings" } steps: - - name: "Checkout this repo" - uses: actions/checkout@v3 - - name: "Setup Node.js" - uses: actions/setup-node@v3 - with: - node-version: "18.x" - cache: "npm" - cache-dependency-path: "packages/libsql-client" - - name: "Build core" - run: "npm ci && npm run build" - working-directory: ./packages/libsql-core - - name: "Install npm dependencies" - run: "npm ci" - - name: "Checkout hrana-test-server" - uses: actions/checkout@v3 - with: - repository: "libsql/hrana-test-server" - path: "packages/libsql-client/hrana-test-server" - - name: "Setup Python" - uses: actions/setup-python@v4 - with: - python-version: "3.10" - cache: "pip" - - name: "Install pip dependencies" - run: "pip install -r hrana-test-server/requirements.txt" + - name: "Checkout this repo" + uses: actions/checkout@v3 + - name: "Setup Node.js" + uses: actions/setup-node@v3 + with: + node-version: "18.x" + cache: "npm" + cache-dependency-path: "packages/libsql-client" + - name: "Build core" + run: "npm ci && npm run build" + working-directory: ./packages/libsql-core + - name: "Install npm dependencies" + run: "npm ci" + - name: "Checkout hrana-test-server" + uses: actions/checkout@v3 + with: + repository: "libsql/hrana-test-server" + path: "packages/libsql-client/hrana-test-server" + - name: "Setup Python" + uses: actions/setup-python@v4 + with: + python-version: "3.10" + cache: "pip" + - name: "Install pip dependencies" + run: "pip install -r hrana-test-server/requirements.txt" - - name: "Build" - run: "npm run build" + - name: "Build" + run: "npm run build" - - name: "Test Hrana 1 over WebSocket" - run: "python hrana-test-server/server_v1.py npm test" - env: {"URL": "ws://localhost:8080", "SERVER": "test_v1"} - - name: "Test Hrana 2 over WebSocket" - run: "python hrana-test-server/server_v2.py npm test" - env: {"URL": "ws://localhost:8080", "SERVER": "test_v2"} - - name: "Test Hrana 2 over HTTP" - run: "python hrana-test-server/server_v2.py npm test" - env: {"URL": "http://localhost:8080", "SERVER": "test_v2"} -# - name: "Test Hrana 3 over WebSocket" -# run: "python hrana-test-server/server_v3.py npm test" -# env: {"URL": "ws://localhost:8080", "SERVER": "test_v3"} -# - name: "Test Hrana 3 over HTTP" -# run: "python hrana-test-server/server_v3.py npm test" -# env: {"URL": "http://localhost:8080", "SERVER": "test_v3"} - - name: "Test local file" - run: "npm test" - env: {"URL": "file:///tmp/test.db"} + - name: "Test Hrana 1 over WebSocket" + run: "python hrana-test-server/server_v1.py npm test" + env: { "URL": "ws://localhost:8080", "SERVER": "test_v1" } + - name: "Test Hrana 2 over WebSocket" + run: "python hrana-test-server/server_v2.py npm test" + env: { "URL": "ws://localhost:8080", "SERVER": "test_v2" } + - name: "Test Hrana 2 over HTTP" + run: "python hrana-test-server/server_v2.py npm test" + env: { "URL": "http://localhost:8080", "SERVER": "test_v2" } + # - name: "Test Hrana 3 over WebSocket" + # run: "python hrana-test-server/server_v3.py npm test" + # env: {"URL": "ws://localhost:8080", "SERVER": "test_v3"} + # - name: "Test Hrana 3 over HTTP" + # run: "python hrana-test-server/server_v3.py npm test" + # env: {"URL": "http://localhost:8080", "SERVER": "test_v3"} + - name: "Test local file" + run: "npm test" + env: { "URL": "file:///tmp/test.db" } - - name: "Test example" - run: "cd examples && npm i && node example.js" - env: {"URL": "file:///tmp/example.db"} + - name: "Test example" + run: "cd examples && npm i && node example.js" + env: { "URL": "file:///tmp/example.db" } "workers-test": name: "Build and test with Cloudflare Workers" @@ -102,47 +102,47 @@ jobs: "CLOUDFLARE_API_TOKEN": "${{ secrets.CLOUDFLARE_API_TOKEN }}" "CLOUDFLARE_ACCOUNT_ID": "${{ secrets.CLOUDFLARE_ACCOUNT_ID }}" steps: - - name: "Checkout this repo" - uses: actions/checkout@v3 - - name: "Setup Node.js" - uses: actions/setup-node@v3 - with: - node-version: "lts/Hydrogen" - cache: "npm" - cache-dependency-path: "packages/libsql-client" - - name: "Build core" - run: "npm ci && npm run build" - working-directory: ./packages/libsql-core - - name: "Install npm dependencies" - run: "npm ci" + - name: "Checkout this repo" + uses: actions/checkout@v3 + - name: "Setup Node.js" + uses: actions/setup-node@v3 + with: + node-version: "lts/Hydrogen" + cache: "npm" + cache-dependency-path: "packages/libsql-client" + - name: "Build core" + run: "npm ci && npm run build" + working-directory: ./packages/libsql-core + - name: "Install npm dependencies" + run: "npm ci" - - name: "Checkout hrana-test-server" - uses: actions/checkout@v3 - with: - repository: "libsql/hrana-test-server" - path: "packages/libsql-client/hrana-test-server" - - name: "Setup Python" - uses: actions/setup-python@v4 - with: - python-version: "3.10" - cache: "pip" - - name: "Install pip dependencies" - run: "pip install -r hrana-test-server/requirements.txt" + - name: "Checkout hrana-test-server" + uses: actions/checkout@v3 + with: + repository: "libsql/hrana-test-server" + path: "packages/libsql-client/hrana-test-server" + - name: "Setup Python" + uses: actions/setup-python@v4 + with: + python-version: "3.10" + cache: "pip" + - name: "Install pip dependencies" + run: "pip install -r hrana-test-server/requirements.txt" - - name: "Build" - run: "npm run build" - - name: "Install npm dependencies of the Workers test" - run: "cd smoke_test/workers && npm link ../.." + - name: "Build" + run: "npm run build" + - name: "Install npm dependencies of the Workers test" + run: "cd smoke_test/workers && npm link ../.." - - name: "Local test with Hrana 1 over WebSocket" - run: "cd smoke_test/workers && python ../../hrana-test-server/server_v1.py node --dns-result-order=ipv4first test.js" - env: {"LOCAL": "1", "URL": "ws://localhost:8080"} - - name: "Local test with Hrana 2 over WebSocket" - run: "cd smoke_test/workers && python ../../hrana-test-server/server_v2.py node --dns-result-order=ipv4first test.js" - env: {"LOCAL": "1", "URL": "ws://localhost:8080"} - - name: "Local test with Hrana 2 over HTTP" - run: "cd smoke_test/workers && python ../../hrana-test-server/server_v2.py node --dns-result-order=ipv4first test.js" - env: {"LOCAL": "1", "URL": "http://localhost:8080"} + - name: "Local test with Hrana 1 over WebSocket" + run: "cd smoke_test/workers && python ../../hrana-test-server/server_v1.py node --dns-result-order=ipv4first test.js" + env: { "LOCAL": "1", "URL": "ws://localhost:8080" } + - name: "Local test with Hrana 2 over WebSocket" + run: "cd smoke_test/workers && python ../../hrana-test-server/server_v2.py node --dns-result-order=ipv4first test.js" + env: { "LOCAL": "1", "URL": "ws://localhost:8080" } + - name: "Local test with Hrana 2 over HTTP" + run: "cd smoke_test/workers && python ../../hrana-test-server/server_v2.py node --dns-result-order=ipv4first test.js" + env: { "LOCAL": "1", "URL": "http://localhost:8080" } # - name: "Local test with Hrana 3 over WebSocket" # run: "cd smoke_test/workers && python ../../hrana-test-server/server_v3.py node --dns-result-order=ipv4first test.js" # env: {"LOCAL": "1", "URL": "ws://localhost:8080"} @@ -150,15 +150,15 @@ jobs: # run: "cd smoke_test/workers && python ../../hrana-test-server/server_v3.py node --dns-result-order=ipv4first test.js" # env: {"LOCAL": "1", "URL": "http://localhost:8080"} - # - name: "Non-local test with Hrana 1 over WebSocket" - # run: "cd smoke_test/workers && python ../../hrana-test-server/server_v1.py node test.js" - # env: {"LOCAL": "0", "URL": "ws://localhost:8080"} - # - name: "Non-local test with Hrana 2 over WebSocket" - # run: "cd smoke_test/workers && python ../../hrana-test-server/server_v2.py node test.js" - # env: {"LOCAL": "0", "URL": "ws://localhost:8080"} - # - name: "Non-local test with Hrana 2 over HTTP" - # run: "cd smoke_test/workers && python ../../hrana-test-server/server_v2.py node test.js" - # env: {"LOCAL": "0", "URL": "http://localhost:8080"} +# - name: "Non-local test with Hrana 1 over WebSocket" +# run: "cd smoke_test/workers && python ../../hrana-test-server/server_v1.py node test.js" +# env: {"LOCAL": "0", "URL": "ws://localhost:8080"} +# - name: "Non-local test with Hrana 2 over WebSocket" +# run: "cd smoke_test/workers && python ../../hrana-test-server/server_v2.py node test.js" +# env: {"LOCAL": "0", "URL": "ws://localhost:8080"} +# - name: "Non-local test with Hrana 2 over HTTP" +# run: "cd smoke_test/workers && python ../../hrana-test-server/server_v2.py node test.js" +# env: {"LOCAL": "0", "URL": "http://localhost:8080"} # - name: "Non-local test with Hrana 3 over WebSocket" # run: "cd smoke_test/workers && python ../../hrana-test-server/server_v3.py node test.js" # env: {"LOCAL": "0", "URL": "ws://localhost:8080"} diff --git a/.github/workflows/pages.yaml b/.github/workflows/pages.yaml index 82a17f38..a54b52c1 100644 --- a/.github/workflows/pages.yaml +++ b/.github/workflows/pages.yaml @@ -11,28 +11,28 @@ jobs: run: working-directory: ./packages/libsql-client steps: - - name: "Checkout this repo" - uses: actions/checkout@v3 - - name: "Setup Node.js" - uses: actions/setup-node@v3 - with: - node-version: "${{ matrix.node-version }}" - cache: "npm" - - name: "Install npm dependencies" - run: "npm ci" - - name: "Build" - run: "npm run typedoc" - - name: "Upload GitHub Pages artifact" - uses: actions/upload-pages-artifact@v1 - with: - path: "./docs" + - name: "Checkout this repo" + uses: actions/checkout@v3 + - name: "Setup Node.js" + uses: actions/setup-node@v3 + with: + node-version: "${{ matrix.node-version }}" + cache: "npm" + - name: "Install npm dependencies" + run: "npm ci" + - name: "Build" + run: "npm run typedoc" + - name: "Upload GitHub Pages artifact" + uses: actions/upload-pages-artifact@v1 + with: + path: "./docs" "deploy": name: "Deploy the docs to GitHub Pages" needs: "build" permissions: - pages: write - id-token: write + pages: write + id-token: write environment: name: github-pages @@ -43,5 +43,3 @@ jobs: - name: "Deploy to GitHub Pages" id: deployment uses: actions/deploy-pages@v1 - - diff --git a/.husky/install.mjs b/.husky/install.mjs index ba8e33dd..920bd555 100644 --- a/.husky/install.mjs +++ b/.husky/install.mjs @@ -1,6 +1,6 @@ // Skip Husky install in production and CI -if (process.env.NODE_ENV === 'production' || process.env.CI === 'true') { - process.exit(0) +if (process.env.NODE_ENV === "production" || process.env.CI === "true") { + process.exit(0); } -const husky = (await import('husky')).default -console.log(husky()) +const husky = (await import("husky")).default; +console.log(husky()); diff --git a/CHANGELOG.md b/CHANGELOG.md index bb6d33b6..f79e8391 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,57 +2,57 @@ ## 0.6.0 -- 2024-04-28 -* Bump hrana client to 0.6.0, which uses native Node fetch(). Note that +- Bump hrana client to 0.6.0, which uses native Node fetch(). Note that `@libsql/client` now requires Node 18 or later. ## 0.5.6 -- 2024-03-12 -* Bump `libsql` package dependency to 0.3.10 that adds `wasm32` as +- Bump `libsql` package dependency to 0.3.10 that adds `wasm32` as supported CPU, which is needed for StackBlitz compatibility. ## 0.5.5 -- 2024-03-11 -* Bump `@libsql/libsql-wasm-experimental"` dependency to 0.0.2, which +- Bump `@libsql/libsql-wasm-experimental"` dependency to 0.0.2, which fixes a broken sqlite3_get_autocommit() export. ## 0.5.4 -- 2024-03-11 -* Bump `libsql` dependency to 0.3.9, which fixes symbol not found errors on Alpine. +- Bump `libsql` dependency to 0.3.9, which fixes symbol not found errors on Alpine. ## 0.5.3 -- 2024-03-06 -* Add `syncInterval` config option to enable periodic sync. -* Bump `libsql` dependency to 0.3.7, which switches default encryption cipher to aes256cbs. +- Add `syncInterval` config option to enable periodic sync. +- Bump `libsql` dependency to 0.3.7, which switches default encryption cipher to aes256cbs. ## 0.5.2 -- 2024-02-24 -* Disable SQL statemen tracing in Wasm. +- Disable SQL statemen tracing in Wasm. ## 0.5.1 -- 2024-02-19 -* Update `libsql` package to 0.3.2, add `encryptionCipher` option, and switch default cipher to SQLCipher. +- Update `libsql` package to 0.3.2, add `encryptionCipher` option, and switch default cipher to SQLCipher. ## 0.5.0 -- 2024-02-15 -* Add a `encryptionKey` config option, which enables encryption at rest for local database files. +- Add a `encryptionKey` config option, which enables encryption at rest for local database files. ## 0.4.0 -- 2024-01-26 -* Update hrana-client package to 0.5.6. -* Add a `@libsql/client-wasm` package. -* Fix Bun on Linux/arm64. +- Update hrana-client package to 0.5.6. +- Add a `@libsql/client-wasm` package. +- Fix Bun on Linux/arm64. ## 0.3.6 -- 2023-10-20 -* Fix import problems on Cloudflare Workers. -* Add `rawCode` property to errors for local databases. -* Update the `libsql` package to version 0.1.28. +- Fix import problems on Cloudflare Workers. +- Add `rawCode` property to errors for local databases. +- Update the `libsql` package to version 0.1.28. ## 0.3.5 -- 2023-09-25 -* Performance improvements for local database access by reusing connection in `Client`. -* Embedded replica support. -* Column introspection support via ResultSet.columnTypes property. +- Performance improvements for local database access by reusing connection in `Client`. +- Embedded replica support. +- Column introspection support via ResultSet.columnTypes property. ## 0.3.4 -- 2023-09-11 @@ -67,7 +67,7 @@ ## 0.3.2 -- 2023-07-29 - Updated `@libsql/hrana-client` to version 0.5.0, which implements Hrana 3 - - Dropped workarounds for broken WebSocket support in Miniflare 2 + - Dropped workarounds for broken WebSocket support in Miniflare 2 - Added a `@libsql/client/node` import for explicit Node.js-specific module ## 0.3.1 -- 2023-07-20 diff --git a/packages/libsql-client-wasm/examples/browser/index.js b/packages/libsql-client-wasm/examples/browser/index.js index 2a28909f..b20d414e 100644 --- a/packages/libsql-client-wasm/examples/browser/index.js +++ b/packages/libsql-client-wasm/examples/browser/index.js @@ -9,7 +9,6 @@ async function main() { console.log(rs); } -main() - .catch((error) => { - console.log(error); - }); +main().catch((error) => { + console.log(error); +}); diff --git a/packages/libsql-client-wasm/examples/node/index.js b/packages/libsql-client-wasm/examples/node/index.js index 2db18c82..8fa15273 100644 --- a/packages/libsql-client-wasm/examples/node/index.js +++ b/packages/libsql-client-wasm/examples/node/index.js @@ -11,7 +11,6 @@ async function main() { console.log(rs); } -main() - .catch((error) => { - console.log(error); - }); +main().catch((error) => { + console.log(error); +}); diff --git a/packages/libsql-client-wasm/jest.config.js b/packages/libsql-client-wasm/jest.config.js index 65716e3c..c558e7d7 100644 --- a/packages/libsql-client-wasm/jest.config.js +++ b/packages/libsql-client-wasm/jest.config.js @@ -1,7 +1,7 @@ export default { - preset: "ts-jest/presets/default-esm", - moduleNameMapper: { - '^(\\.{1,2}/.*)\\.js$': '$1', - }, - testMatch: ["**/__tests__/*.test.[jt]s"], -} + preset: "ts-jest/presets/default-esm", + moduleNameMapper: { + "^(\\.{1,2}/.*)\\.js$": "$1", + }, + testMatch: ["**/__tests__/*.test.[jt]s"], +}; diff --git a/packages/libsql-client-wasm/package-cjs.json b/packages/libsql-client-wasm/package-cjs.json index 1cd945a3..5bbefffb 100644 --- a/packages/libsql-client-wasm/package-cjs.json +++ b/packages/libsql-client-wasm/package-cjs.json @@ -1,3 +1,3 @@ { - "type": "commonjs" + "type": "commonjs" } diff --git a/packages/libsql-client-wasm/package.json b/packages/libsql-client-wasm/package.json index 68cacd63..cf7a32ea 100644 --- a/packages/libsql-client-wasm/package.json +++ b/packages/libsql-client-wasm/package.json @@ -1,70 +1,70 @@ { - "name": "@libsql/client-wasm", - "version": "0.6.0", - "keywords": [ - "libsql", - "database", - "sqlite", - "serverless", - "vercel", - "netlify", - "lambda" - ], - "description": "libSQL driver for TypeScript and JavaScript", - "repository": { - "type": "git", - "url": "https://github.com/libsql/libsql-client-ts" - }, - "authors": [ - "Jan Špaček ", - "Pekka Enberg ", - "Jan Plhak " - ], - "license": "MIT", - "type": "module", - "bundledDependencies": [ - "@libsql/libsql-wasm-experimental" - ], - "main": "lib-esm/wasm.js", - "types": "lib-esm/wasm.d.ts", - "exports": { - ".": { - "types": "./lib-esm/wasm.d.ts", - "import": { - "default": "./lib-esm/wasm.js" - } - } - }, - "typesVersions": { - "*": { - ".": [ - "./lib-esm/wasm.d.ts" - ] - } - }, - "files": [ - "lib-esm/**" - ], - "scripts": { - "prepublishOnly": "npm run build", - "prebuild": "rm -rf ./lib-esm && npm run bundle", - "build": "npm run build:esm", - "build:esm": "tsc -p tsconfig.build-esm.json", - "bundle": "rm -rf node_modules && mkdir -p node_modules/@libsql/libsql-wasm-experimental && cp -R ../../node_modules/@libsql/libsql-wasm-experimental/* node_modules/@libsql/libsql-wasm-experimental", - "test": "jest --runInBand", - "typecheck": "tsc --noEmit", - "typedoc": "rm -rf ./docs && typedoc" - }, - "dependencies": { - "@libsql/core": "0.6.0", - "@libsql/libsql-wasm-experimental": "^0.0.2", - "js-base64": "^3.7.5" - }, - "devDependencies": { - "@types/jest": "^29.2.5", - "jest": "^29.3.1", - "ts-jest": "^29.0.5", - "typedoc": "^0.23.28", - "typescript": "^4.9.4" + "name": "@libsql/client-wasm", + "version": "0.6.0", + "keywords": [ + "libsql", + "database", + "sqlite", + "serverless", + "vercel", + "netlify", + "lambda" + ], + "description": "libSQL driver for TypeScript and JavaScript", + "repository": { + "type": "git", + "url": "https://github.com/libsql/libsql-client-ts" + }, + "authors": [ + "Jan Špaček ", + "Pekka Enberg ", + "Jan Plhak " + ], + "license": "MIT", + "type": "module", + "bundledDependencies": [ + "@libsql/libsql-wasm-experimental" + ], + "main": "lib-esm/wasm.js", + "types": "lib-esm/wasm.d.ts", + "exports": { + ".": { + "types": "./lib-esm/wasm.d.ts", + "import": { + "default": "./lib-esm/wasm.js" + } } + }, + "typesVersions": { + "*": { + ".": [ + "./lib-esm/wasm.d.ts" + ] + } + }, + "files": [ + "lib-esm/**" + ], + "scripts": { + "prepublishOnly": "npm run build", + "prebuild": "rm -rf ./lib-esm && npm run bundle", + "build": "npm run build:esm", + "build:esm": "tsc -p tsconfig.build-esm.json", + "bundle": "rm -rf node_modules && mkdir -p node_modules/@libsql/libsql-wasm-experimental && cp -R ../../node_modules/@libsql/libsql-wasm-experimental/* node_modules/@libsql/libsql-wasm-experimental", + "test": "jest --runInBand", + "typecheck": "tsc --noEmit", + "typedoc": "rm -rf ./docs && typedoc" + }, + "dependencies": { + "@libsql/core": "0.6.0", + "@libsql/libsql-wasm-experimental": "^0.0.2", + "js-base64": "^3.7.5" + }, + "devDependencies": { + "@types/jest": "^29.2.5", + "jest": "^29.3.1", + "ts-jest": "^29.0.5", + "typedoc": "^0.23.28", + "typescript": "^4.9.4" + } } diff --git a/packages/libsql-client-wasm/src/wasm.ts b/packages/libsql-client-wasm/src/wasm.ts index 5b85407c..7066e355 100644 --- a/packages/libsql-client-wasm/src/wasm.ts +++ b/packages/libsql-client-wasm/src/wasm.ts @@ -1,369 +1,434 @@ -import sqlite3InitModule from '@libsql/libsql-wasm-experimental'; +import sqlite3InitModule from "@libsql/libsql-wasm-experimental"; -import type { Database, InitOptions, SqlValue, Sqlite3Static } from '@libsql/libsql-wasm-experimental'; +import type { + Database, + InitOptions, + SqlValue, + Sqlite3Static, +} from "@libsql/libsql-wasm-experimental"; import type { - Config, IntMode, Client, Transaction, TransactionMode, - ResultSet, Row, Value, InValue, InStatement, + Config, + IntMode, + Client, + Transaction, + TransactionMode, + ResultSet, + Row, + Value, + InValue, + InStatement, } from "@libsql/core/api"; import { LibsqlError } from "@libsql/core/api"; import type { ExpandedConfig } from "@libsql/core/config"; import { expandConfig } from "@libsql/core/config"; -import { supportedUrlLink, transactionModeToBegin, ResultSetImpl } from "@libsql/core/util"; +import { + supportedUrlLink, + transactionModeToBegin, + ResultSetImpl, +} from "@libsql/core/util"; export * from "@libsql/core/api"; const sqlite3 = await sqlite3InitModule(); export function createClient(config: Config): Client { - return _createClient(expandConfig(config, true)); + return _createClient(expandConfig(config, true)); } /** @private */ export function _createClient(config: ExpandedConfig): Client { - if (config.scheme !== "file") { - throw new LibsqlError( - `URL scheme ${JSON.stringify(config.scheme + ":")} is not supported by the local sqlite3 client. ` + - `For more information, please read ${supportedUrlLink}`, - "URL_SCHEME_NOT_SUPPORTED", - ); + if (config.scheme !== "file") { + throw new LibsqlError( + `URL scheme ${JSON.stringify(config.scheme + ":")} is not supported by the local sqlite3 client. ` + + `For more information, please read ${supportedUrlLink}`, + "URL_SCHEME_NOT_SUPPORTED", + ); + } + + if (config.encryptionKey !== undefined) { + throw new LibsqlError( + "Encryption key is not supported by the Wasm client.", + "ENCRYPTION_KEY_NOT_SUPPORTED", + ); + } + + const authority = config.authority; + if (authority !== undefined) { + const host = authority.host.toLowerCase(); + if (host !== "" && host !== "localhost") { + throw new LibsqlError( + `Invalid host in file URL: ${JSON.stringify(authority.host)}. ` + + 'A "file:" URL with an absolute path should start with one slash ("file:/absolute/path.db") ' + + 'or with three slashes ("file:///absolute/path.db"). ' + + `For more information, please read ${supportedUrlLink}`, + "URL_INVALID", + ); } - if (config.encryptionKey !== undefined) { - throw new LibsqlError("Encryption key is not supported by the Wasm client.", "ENCRYPTION_KEY_NOT_SUPPORTED"); + if (authority.port !== undefined) { + throw new LibsqlError("File URL cannot have a port", "URL_INVALID"); } - - const authority = config.authority; - if (authority !== undefined) { - const host = authority.host.toLowerCase(); - if (host !== "" && host !== "localhost") { - throw new LibsqlError( - `Invalid host in file URL: ${JSON.stringify(authority.host)}. ` + - 'A "file:" URL with an absolute path should start with one slash ("file:/absolute/path.db") ' + - 'or with three slashes ("file:///absolute/path.db"). ' + - `For more information, please read ${supportedUrlLink}`, - "URL_INVALID", - ); - } - - if (authority.port !== undefined) { - throw new LibsqlError("File URL cannot have a port", "URL_INVALID"); - } - if (authority.userinfo !== undefined) { - throw new LibsqlError("File URL cannot have username and password", "URL_INVALID"); - } + if (authority.userinfo !== undefined) { + throw new LibsqlError( + "File URL cannot have username and password", + "URL_INVALID", + ); } + } - const path = config.path; - const options = { - authToken: config.authToken, - syncUrl: config.syncUrl, - }; + const path = config.path; + const options = { + authToken: config.authToken, + syncUrl: config.syncUrl, + }; - const db: Database = new sqlite3.oo1.DB(path, 'c'); + const db: Database = new sqlite3.oo1.DB(path, "c"); - executeStmt(db, "SELECT 1 AS checkThatTheDatabaseCanBeOpened", config.intMode); + executeStmt( + db, + "SELECT 1 AS checkThatTheDatabaseCanBeOpened", + config.intMode, + ); - return new Sqlite3Client(sqlite3, path, /*options,*/ db, config.intMode); + return new Sqlite3Client(sqlite3, path, /*options,*/ db, config.intMode); } function inTransaction(db: Database): boolean { - return db.getAutocommit() == 0; + return db.getAutocommit() == 0; } export class Sqlite3Client implements Client { - #sqlite3: Sqlite3Static; - #path: string; - #db: Database | null; - #intMode: IntMode; - closed: boolean; - protocol: "file"; - - /** @private */ - constructor(sqlite3: Sqlite3Static, path: string, /*options: Database.Options,*/ db: Database, intMode: IntMode) { - this.#sqlite3 = sqlite3; - this.#path = path; - //this.#options = options; - this.#db = db; - this.#intMode = intMode; - this.closed = false; - this.protocol = "file"; - } - - async execute(stmt: InStatement): Promise { - this.#checkNotClosed(); - return executeStmt(this.#getDb(), stmt, this.#intMode); - } - - async batch(stmts: Array, mode: TransactionMode = "deferred"): Promise> { - this.#checkNotClosed(); - const db = this.#getDb(); - try { - executeStmt(db, transactionModeToBegin(mode), this.#intMode); - const resultSets = stmts.map((stmt) => { - if (!inTransaction(db)) { - throw new LibsqlError("The transaction has been rolled back", "TRANSACTION_CLOSED"); - } - return executeStmt(db, stmt, this.#intMode); - }); - executeStmt(db, "COMMIT", this.#intMode) - return resultSets; - } finally { - if (inTransaction(db)) { - executeStmt(db, "ROLLBACK", this.#intMode); - } - } - } - - async transaction(mode: TransactionMode = "write"): Promise { - const db = this.#getDb(); - executeStmt(db, transactionModeToBegin(mode), this.#intMode); - this.#db = null; // A new connection will be lazily created on next use - return new Sqlite3Transaction(db, this.#intMode); - } - - async executeMultiple(sql: string): Promise { - this.#checkNotClosed(); - const db = this.#getDb(); - try { - return executeMultiple(db, sql); - } finally { - if (inTransaction(db)) { - executeStmt(db, "ROLLBACK", this.#intMode); - } + #sqlite3: Sqlite3Static; + #path: string; + #db: Database | null; + #intMode: IntMode; + closed: boolean; + protocol: "file"; + + /** @private */ + constructor( + sqlite3: Sqlite3Static, + path: string, + /*options: Database.Options,*/ db: Database, + intMode: IntMode, + ) { + this.#sqlite3 = sqlite3; + this.#path = path; + //this.#options = options; + this.#db = db; + this.#intMode = intMode; + this.closed = false; + this.protocol = "file"; + } + + async execute(stmt: InStatement): Promise { + this.#checkNotClosed(); + return executeStmt(this.#getDb(), stmt, this.#intMode); + } + + async batch( + stmts: Array, + mode: TransactionMode = "deferred", + ): Promise> { + this.#checkNotClosed(); + const db = this.#getDb(); + try { + executeStmt(db, transactionModeToBegin(mode), this.#intMode); + const resultSets = stmts.map((stmt) => { + if (!inTransaction(db)) { + throw new LibsqlError( + "The transaction has been rolled back", + "TRANSACTION_CLOSED", + ); } + return executeStmt(db, stmt, this.#intMode); + }); + executeStmt(db, "COMMIT", this.#intMode); + return resultSets; + } finally { + if (inTransaction(db)) { + executeStmt(db, "ROLLBACK", this.#intMode); + } } - - - async sync(): Promise { - throw new LibsqlError("sync not supported in wasm mode", "SYNC_NOT_SUPPORTED"); + } + + async transaction(mode: TransactionMode = "write"): Promise { + const db = this.#getDb(); + executeStmt(db, transactionModeToBegin(mode), this.#intMode); + this.#db = null; // A new connection will be lazily created on next use + return new Sqlite3Transaction(db, this.#intMode); + } + + async executeMultiple(sql: string): Promise { + this.#checkNotClosed(); + const db = this.#getDb(); + try { + return executeMultiple(db, sql); + } finally { + if (inTransaction(db)) { + executeStmt(db, "ROLLBACK", this.#intMode); + } } - - close(): void { - this.closed = true; - if (this.#db !== null) { - this.#db.close(); - } + } + + async sync(): Promise { + throw new LibsqlError( + "sync not supported in wasm mode", + "SYNC_NOT_SUPPORTED", + ); + } + + close(): void { + this.closed = true; + if (this.#db !== null) { + this.#db.close(); } + } - #checkNotClosed(): void { - if (this.closed) { - throw new LibsqlError("The client is closed", "CLIENT_CLOSED"); - } + #checkNotClosed(): void { + if (this.closed) { + throw new LibsqlError("The client is closed", "CLIENT_CLOSED"); } + } - // Lazily creates the database connection and returns it - #getDb(): Database { - if (this.#db === null) { - this.#db = new this.#sqlite3.oo1.DB('/mydb.sqlite3', 'ct'); - } - return this.#db; + // Lazily creates the database connection and returns it + #getDb(): Database { + if (this.#db === null) { + this.#db = new this.#sqlite3.oo1.DB("/mydb.sqlite3", "ct"); } + return this.#db; + } } export class Sqlite3Transaction implements Transaction { - #database: Database; - #intMode: IntMode; - - /** @private */ - constructor(database: Database, intMode: IntMode) { - this.#database = database; - this.#intMode = intMode; + #database: Database; + #intMode: IntMode; + + /** @private */ + constructor(database: Database, intMode: IntMode) { + this.#database = database; + this.#intMode = intMode; + } + + async execute(stmt: InStatement): Promise { + this.#checkNotClosed(); + return executeStmt(this.#database, stmt, this.#intMode); + } + + async batch(stmts: Array): Promise> { + return stmts.map((stmt) => { + this.#checkNotClosed(); + return executeStmt(this.#database, stmt, this.#intMode); + }); + } + + async executeMultiple(sql: string): Promise { + this.#checkNotClosed(); + return executeMultiple(this.#database, sql); + } + + async rollback(): Promise { + if (!this.#database.isOpen()) { + return; } - - async execute(stmt: InStatement): Promise { - this.#checkNotClosed(); - return executeStmt(this.#database, stmt, this.#intMode); + this.#checkNotClosed(); + executeStmt(this.#database, "ROLLBACK", this.#intMode); + } + + async commit(): Promise { + this.#checkNotClosed(); + executeStmt(this.#database, "COMMIT", this.#intMode); + } + + close(): void { + if (inTransaction(this.#database)) { + executeStmt(this.#database, "ROLLBACK", this.#intMode); } + } - async batch(stmts: Array): Promise> { - return stmts.map((stmt) => { - this.#checkNotClosed(); - return executeStmt(this.#database, stmt, this.#intMode); - }); - } - - async executeMultiple(sql: string): Promise { - this.#checkNotClosed(); - return executeMultiple(this.#database, sql); - } + get closed(): boolean { + return !inTransaction(this.#database); + } - async rollback(): Promise { - if (!this.#database.isOpen()) { - return; - } - this.#checkNotClosed(); - executeStmt(this.#database, "ROLLBACK", this.#intMode); + #checkNotClosed(): void { + if (this.closed) { + throw new LibsqlError("The transaction is closed", "TRANSACTION_CLOSED"); } + } +} - async commit(): Promise { - this.#checkNotClosed(); - executeStmt(this.#database, "COMMIT", this.#intMode); +function executeStmt( + db: Database, + stmt: InStatement, + intMode: IntMode, +): ResultSet { + let sql: string; + let args: Array | Record; + if (typeof stmt === "string") { + sql = stmt; + args = []; + } else { + sql = stmt.sql; + if (Array.isArray(stmt.args)) { + args = stmt.args.map((value) => valueToSql(value, intMode)); + } else { + args = {}; + for (const name in stmt.args) { + const argName = + name[0] === "@" || name[0] === "$" || name[0] === ":" + ? name.substring(1) + : name; + args[argName] = valueToSql(stmt.args[name], intMode); + } } + } - close(): void { - if (inTransaction(this.#database)) { - executeStmt(this.#database, "ROLLBACK", this.#intMode); - } - } + try { + const sqlStmt = db.prepare(sql); - get closed(): boolean { - return !inTransaction(this.#database); - } + // TODO: sqlStmt.safeIntegers(true); - #checkNotClosed(): void { - if (this.closed) { - throw new LibsqlError("The transaction is closed", "TRANSACTION_CLOSED"); - } - } -} + let returnsData = sqlStmt.columnCount > 0; -function executeStmt(db: Database, stmt: InStatement, intMode: IntMode): ResultSet { - let sql: string; - let args: Array | Record; - if (typeof stmt === "string") { - sql = stmt; - args = []; + if (Array.isArray(args)) { + for (let i = 0; i < args.length; ++i) { + const value = args[i]; + sqlStmt.bind(i + 1, value); + } } else { - sql = stmt.sql; - if (Array.isArray(stmt.args)) { - args = stmt.args.map((value) => valueToSql(value, intMode)); - } else { - args = {}; - for (const name in stmt.args) { - const argName = (name[0] === "@" || name[0] === "$" || name[0] === ":") - ? name.substring(1) : name; - args[argName] = valueToSql(stmt.args[name], intMode); - } - } + for (const argName in args) { + const idx = sqlStmt.getParamIndex(argName)!; + const value = args[argName]; + sqlStmt.bind(idx, value); + } } - - try { - const sqlStmt = db.prepare(sql); - - // TODO: sqlStmt.safeIntegers(true); - - let returnsData = sqlStmt.columnCount > 0; - - if (Array.isArray(args)) { - for (let i = 0; i < args.length; ++i) { - const value = args[i]; - sqlStmt.bind(i + 1, value); - } - } else { - for (const argName in args) { - const idx = sqlStmt.getParamIndex(argName)!; - const value = args[argName]; - sqlStmt.bind(idx, value); - } - } - if (returnsData) { - let columns: string[] = sqlStmt.getColumnNames(); - let columnTypes: string[] = []; - let rows: Row[] = []; - for (;;) { - if (!sqlStmt.step()) { - break; - } - const values: unknown[] = sqlStmt.get([]); - rows.push(rowFromSql(values, columns, intMode)); - } - const rowsAffected = 0; - const lastInsertRowid = undefined; - return new ResultSetImpl(columns, columnTypes, rows, rowsAffected, lastInsertRowid); - } else { - sqlStmt.step(); // TODO: check return value - const rowsAffected = db.changes(); - const lastInsertRowid = BigInt(db.lastInsertRowid()); - return new ResultSetImpl([], [], [], rowsAffected, lastInsertRowid); + if (returnsData) { + let columns: string[] = sqlStmt.getColumnNames(); + let columnTypes: string[] = []; + let rows: Row[] = []; + for (;;) { + if (!sqlStmt.step()) { + break; } - } catch (e) { - throw mapSqliteError(e); + const values: unknown[] = sqlStmt.get([]); + rows.push(rowFromSql(values, columns, intMode)); + } + const rowsAffected = 0; + const lastInsertRowid = undefined; + return new ResultSetImpl( + columns, + columnTypes, + rows, + rowsAffected, + lastInsertRowid, + ); + } else { + sqlStmt.step(); // TODO: check return value + const rowsAffected = db.changes(); + const lastInsertRowid = BigInt(db.lastInsertRowid()); + return new ResultSetImpl([], [], [], rowsAffected, lastInsertRowid); } + } catch (e) { + throw mapSqliteError(e); + } } -function rowFromSql(sqlRow: Array, columns: Array, intMode: IntMode): Row { - const row = {}; - // make sure that the "length" property is not enumerable - Object.defineProperty(row, "length", { value: sqlRow.length }); - for (let i = 0; i < sqlRow.length; ++i) { - const value = valueFromSql(sqlRow[i], intMode); - Object.defineProperty(row, i, { value }); - - const column = columns[i]; - if (!Object.hasOwn(row, column)) { - Object.defineProperty(row, column, { value, enumerable: true, configurable: true, writable: true }); - } +function rowFromSql( + sqlRow: Array, + columns: Array, + intMode: IntMode, +): Row { + const row = {}; + // make sure that the "length" property is not enumerable + Object.defineProperty(row, "length", { value: sqlRow.length }); + for (let i = 0; i < sqlRow.length; ++i) { + const value = valueFromSql(sqlRow[i], intMode); + Object.defineProperty(row, i, { value }); + + const column = columns[i]; + if (!Object.hasOwn(row, column)) { + Object.defineProperty(row, column, { + value, + enumerable: true, + configurable: true, + writable: true, + }); } - return row as Row; + } + return row as Row; } function valueFromSql(sqlValue: unknown, intMode: IntMode): Value { - if (typeof sqlValue === "bigint") { - if (intMode === "number") { - if (sqlValue < minSafeBigint || sqlValue > maxSafeBigint) { - throw new RangeError( - "Received integer which cannot be safely represented as a JavaScript number" - ); - } - return Number(sqlValue); - } else if (intMode === "bigint") { - return sqlValue; - } else if (intMode === "string") { - return ""+sqlValue; - } else { - throw new Error("Invalid value for IntMode"); - } + if (typeof sqlValue === "bigint") { + if (intMode === "number") { + if (sqlValue < minSafeBigint || sqlValue > maxSafeBigint) { + throw new RangeError( + "Received integer which cannot be safely represented as a JavaScript number", + ); + } + return Number(sqlValue); + } else if (intMode === "bigint") { + return sqlValue; + } else if (intMode === "string") { + return "" + sqlValue; + } else { + throw new Error("Invalid value for IntMode"); } - return sqlValue as Value; + } + return sqlValue as Value; } const minSafeBigint = -9007199254740991n; const maxSafeBigint = 9007199254740991n; function valueToSql(value: InValue, intMode: IntMode): SqlValue { - if (typeof value === "number") { - if (!Number.isFinite(value)) { - throw new RangeError("Only finite numbers (not Infinity or NaN) can be passed as arguments"); - } - return value; - } else if (typeof value === "bigint") { - if (value < minInteger || value > maxInteger) { - throw new RangeError( - "bigint is too large to be represented as a 64-bit integer and passed as argument" - ); - } - return value; - } else if (typeof value === "boolean") { - switch(intMode) { - case "bigint": - return value ? 1n : 0n; - case "string": - return value ? "1" : "0"; - default: - return value ? 1 : 0; - } - } else if (value instanceof Date) { - return value.valueOf(); - } else if (value === undefined) { - throw new TypeError("undefined cannot be passed as argument to the database"); - } else { - return value; + if (typeof value === "number") { + if (!Number.isFinite(value)) { + throw new RangeError( + "Only finite numbers (not Infinity or NaN) can be passed as arguments", + ); + } + return value; + } else if (typeof value === "bigint") { + if (value < minInteger || value > maxInteger) { + throw new RangeError( + "bigint is too large to be represented as a 64-bit integer and passed as argument", + ); } + return value; + } else if (typeof value === "boolean") { + switch (intMode) { + case "bigint": + return value ? 1n : 0n; + case "string": + return value ? "1" : "0"; + default: + return value ? 1 : 0; + } + } else if (value instanceof Date) { + return value.valueOf(); + } else if (value === undefined) { + throw new TypeError( + "undefined cannot be passed as argument to the database", + ); + } else { + return value; + } } const minInteger = -9223372036854775808n; const maxInteger = 9223372036854775807n; function executeMultiple(db: Database, sql: string): void { - try { - db.exec(sql); - } catch (e) { - throw mapSqliteError(e); - } + try { + db.exec(sql); + } catch (e) { + throw mapSqliteError(e); + } } function mapSqliteError(e: unknown): unknown { - // TODO: Map to LibsqlError - return e; + // TODO: Map to LibsqlError + return e; } diff --git a/packages/libsql-client-wasm/tsconfig.base.json b/packages/libsql-client-wasm/tsconfig.base.json index 27fac27a..9793c026 100644 --- a/packages/libsql-client-wasm/tsconfig.base.json +++ b/packages/libsql-client-wasm/tsconfig.base.json @@ -1,14 +1,14 @@ { - "compilerOptions": { - "module": "es2022", - "moduleResolution": "node", - "lib": ["esnext", "dom"], - "target": "es2022", - "esModuleInterop": true, - "isolatedModules": true, - "rootDir": "src/", - "strict": true - }, - "include": ["src/"], - "exclude": ["**/__tests__"] + "compilerOptions": { + "module": "es2022", + "moduleResolution": "node", + "lib": ["esnext", "dom"], + "target": "es2022", + "esModuleInterop": true, + "isolatedModules": true, + "rootDir": "src/", + "strict": true + }, + "include": ["src/"], + "exclude": ["**/__tests__"] } diff --git a/packages/libsql-client-wasm/tsconfig.build-esm.json b/packages/libsql-client-wasm/tsconfig.build-esm.json index 9a01705b..a015a304 100644 --- a/packages/libsql-client-wasm/tsconfig.build-esm.json +++ b/packages/libsql-client-wasm/tsconfig.build-esm.json @@ -1,9 +1,8 @@ { - "extends": "./tsconfig.base.json", - "compilerOptions": { - "module": "esnext", - "declaration": true, - "outDir": "./lib-esm/" - } + "extends": "./tsconfig.base.json", + "compilerOptions": { + "module": "esnext", + "declaration": true, + "outDir": "./lib-esm/" + } } - diff --git a/packages/libsql-client-wasm/tsconfig.json b/packages/libsql-client-wasm/tsconfig.json index bc064274..23f862fb 100644 --- a/packages/libsql-client-wasm/tsconfig.json +++ b/packages/libsql-client-wasm/tsconfig.json @@ -1,7 +1,7 @@ { - "extends": "./tsconfig.base.json", - "compilerOptions": { - "noEmit": true, - "incremental": true - } + "extends": "./tsconfig.base.json", + "compilerOptions": { + "noEmit": true, + "incremental": true + } } diff --git a/packages/libsql-client-wasm/typedoc.json b/packages/libsql-client-wasm/typedoc.json index 4c5154ac..de52c9c4 100644 --- a/packages/libsql-client-wasm/typedoc.json +++ b/packages/libsql-client-wasm/typedoc.json @@ -1,11 +1,11 @@ { - "entryPoints": ["src/node.ts"], - "out": "docs", - "excludePrivate": true, - "excludeInternal": true, - "visibilityFilters": { - "inherited": true, - "external": true - }, - "includeVersion": true + "entryPoints": ["src/node.ts"], + "out": "docs", + "excludePrivate": true, + "excludeInternal": true, + "visibilityFilters": { + "inherited": true, + "external": true + }, + "includeVersion": true } diff --git a/packages/libsql-client/examples/example.js b/packages/libsql-client/examples/example.js index 2d696789..b446aeb6 100644 --- a/packages/libsql-client/examples/example.js +++ b/packages/libsql-client/examples/example.js @@ -6,11 +6,14 @@ async function example() { encryptionKey: process.env.ENCRYPTION_KEY, }; const db = createClient(config); - await db.batch([ - "CREATE TABLE IF NOT EXISTS users (email TEXT)", - "INSERT INTO users (email) VALUES ('alice@example.com')", - "INSERT INTO users (email) VALUES ('bob@example.com')" - ], "write"); + await db.batch( + [ + "CREATE TABLE IF NOT EXISTS users (email TEXT)", + "INSERT INTO users (email) VALUES ('alice@example.com')", + "INSERT INTO users (email) VALUES ('bob@example.com')", + ], + "write", + ); const rs = await db.execute("SELECT * FROM users"); console.log(rs); } diff --git a/packages/libsql-client/examples/shell.js b/packages/libsql-client/examples/shell.js index 4351cab3..237f5730 100644 --- a/packages/libsql-client/examples/shell.js +++ b/packages/libsql-client/examples/shell.js @@ -3,34 +3,34 @@ import { stdin, stdout, argv } from "node:process"; import * as libsql from "@libsql/client"; async function main() { - const url = argv[2]; - if (!url) { - console.error("Please specify database URL as command-line argument"); - return; - } + const url = argv[2]; + if (!url) { + console.error("Please specify database URL as command-line argument"); + return; + } - const client = libsql.createClient({url}); - const rl = readline.createInterface({input: stdin, output: stdout}); + const client = libsql.createClient({ url }); + const rl = readline.createInterface({ input: stdin, output: stdout }); - for (;;) { - const sql = await rl.question("> "); + for (;;) { + const sql = await rl.question("> "); - let rs; - try { - rs = await client.execute(sql); - } catch (e) { - if (e instanceof libsql.LibsqlError) { - console.error(e); - continue; - } - throw e; - } + let rs; + try { + rs = await client.execute(sql); + } catch (e) { + if (e instanceof libsql.LibsqlError) { + console.error(e); + continue; + } + throw e; + } - console.log(JSON.stringify(rs.columns)); - for (const row of rs.rows) { - console.log(JSON.stringify(Array.from(row))); - } + console.log(JSON.stringify(rs.columns)); + for (const row of rs.rows) { + console.log(JSON.stringify(Array.from(row))); } + } } await main(); diff --git a/packages/libsql-client/examples/sync.js b/packages/libsql-client/examples/sync.js index 21a87356..0852b9cb 100644 --- a/packages/libsql-client/examples/sync.js +++ b/packages/libsql-client/examples/sync.js @@ -9,12 +9,17 @@ async function example() { }; const db = createClient(config); await db.sync(); - await db.execute("CREATE TABLE IF NOT EXISTS guest_book_entries (comment TEXT)"); + await db.execute( + "CREATE TABLE IF NOT EXISTS guest_book_entries (comment TEXT)", + ); await db.sync(); const comment = reader.question("Enter your comment: "); - await db.execute({ sql: "INSERT INTO guest_book_entries (comment) VALUES (?)", args: [comment]}); + await db.execute({ + sql: "INSERT INTO guest_book_entries (comment) VALUES (?)", + args: [comment], + }); await db.sync(); console.log("Guest book entries:"); @@ -24,4 +29,4 @@ async function example() { } } -example() +example(); diff --git a/packages/libsql-client/jest.config.js b/packages/libsql-client/jest.config.js index 65716e3c..c558e7d7 100644 --- a/packages/libsql-client/jest.config.js +++ b/packages/libsql-client/jest.config.js @@ -1,7 +1,7 @@ export default { - preset: "ts-jest/presets/default-esm", - moduleNameMapper: { - '^(\\.{1,2}/.*)\\.js$': '$1', - }, - testMatch: ["**/__tests__/*.test.[jt]s"], -} + preset: "ts-jest/presets/default-esm", + moduleNameMapper: { + "^(\\.{1,2}/.*)\\.js$": "$1", + }, + testMatch: ["**/__tests__/*.test.[jt]s"], +}; diff --git a/packages/libsql-client/package-cjs.json b/packages/libsql-client/package-cjs.json index 1cd945a3..5bbefffb 100644 --- a/packages/libsql-client/package-cjs.json +++ b/packages/libsql-client/package-cjs.json @@ -1,3 +1,3 @@ { - "type": "commonjs" + "type": "commonjs" } diff --git a/packages/libsql-client/package.json b/packages/libsql-client/package.json index 259a9a3f..55a12234 100644 --- a/packages/libsql-client/package.json +++ b/packages/libsql-client/package.json @@ -1,121 +1,121 @@ { - "name": "@libsql/client", - "version": "0.6.0", - "keywords": [ - "libsql", - "database", - "sqlite", - "serverless", - "vercel", - "netlify", - "lambda" - ], - "description": "libSQL driver for TypeScript and JavaScript", - "repository": { - "type": "git", - "url": "https://github.com/libsql/libsql-client-ts" + "name": "@libsql/client", + "version": "0.6.0", + "keywords": [ + "libsql", + "database", + "sqlite", + "serverless", + "vercel", + "netlify", + "lambda" + ], + "description": "libSQL driver for TypeScript and JavaScript", + "repository": { + "type": "git", + "url": "https://github.com/libsql/libsql-client-ts" + }, + "authors": [ + "Jan Špaček ", + "Pekka Enberg ", + "Jan Plhak " + ], + "license": "MIT", + "type": "module", + "main": "lib-cjs/node.js", + "types": "lib-esm/node.d.ts", + "exports": { + ".": { + "types": "./lib-esm/node.d.ts", + "import": { + "workerd": "./lib-esm/web.js", + "deno": "./lib-esm/web.js", + "edge-light": "./lib-esm/web.js", + "netlify": "./lib-esm/web.js", + "node": "./lib-esm/node.js", + "browser": "./lib-esm/web.js", + "default": "./lib-esm/node.js" + }, + "require": "./lib-cjs/node.js" }, - "authors": [ - "Jan Špaček ", - "Pekka Enberg ", - "Jan Plhak " - ], - "license": "MIT", - "type": "module", - "main": "lib-cjs/node.js", - "types": "lib-esm/node.d.ts", - "exports": { - ".": { - "types": "./lib-esm/node.d.ts", - "import": { - "workerd": "./lib-esm/web.js", - "deno": "./lib-esm/web.js", - "edge-light": "./lib-esm/web.js", - "netlify": "./lib-esm/web.js", - "node": "./lib-esm/node.js", - "browser": "./lib-esm/web.js", - "default": "./lib-esm/node.js" - }, - "require": "./lib-cjs/node.js" - }, - "./node": { - "types": "./lib-esm/node.d.ts", - "import": "./lib-esm/node.js", - "require": "./lib-cjs/node.js" - }, - "./http": { - "types": "./lib-esm/http.d.ts", - "import": "./lib-esm/http.js", - "require": "./lib-cjs/http.js" - }, - "./ws": { - "types": "./lib-esm/ws.d.ts", - "import": "./lib-esm/ws.js", - "require": "./lib-cjs/ws.js" - }, - "./sqlite3": { - "types": "./lib-esm/sqlite3.d.ts", - "import": "./lib-esm/sqlite3.js", - "require": "./lib-cjs/sqlite3.js" - }, - "./web": { - "types": "./lib-esm/web.d.ts", - "import": "./lib-esm/web.js", - "require": "./lib-cjs/web.js" - } + "./node": { + "types": "./lib-esm/node.d.ts", + "import": "./lib-esm/node.js", + "require": "./lib-cjs/node.js" }, - "typesVersions": { - "*": { - ".": [ - "./lib-esm/node.d.ts" - ], - "http": [ - "./lib-esm/http.d.ts" - ], - "hrana": [ - "./lib-esm/hrana.d.ts" - ], - "sqlite3": [ - "./lib-esm/sqlite3.d.ts" - ], - "web": [ - "./lib-esm/web.d.ts" - ] - } + "./http": { + "types": "./lib-esm/http.d.ts", + "import": "./lib-esm/http.js", + "require": "./lib-cjs/http.js" }, - "files": [ - "lib-cjs/**", - "lib-esm/**", - "README.md" - ], - "scripts": { - "prepublishOnly": "npm run build", - "prebuild": "rm -rf ./lib-cjs ./lib-esm", - "build": "npm run build:cjs && npm run build:esm", - "build:cjs": "tsc -p tsconfig.build-cjs.json", - "build:esm": "tsc -p tsconfig.build-esm.json", - "postbuild": "cp package-cjs.json ./lib-cjs/package.json", - "test": "jest --runInBand", - "typecheck": "tsc --noEmit", - "typedoc": "rm -rf ./docs && typedoc", - "prepare": "husky install", - "lint-staged": "lint-staged" + "./ws": { + "types": "./lib-esm/ws.d.ts", + "import": "./lib-esm/ws.js", + "require": "./lib-cjs/ws.js" }, - "dependencies": { - "@libsql/core": "^0.6.0", - "@libsql/hrana-client": "^0.6.0", - "js-base64": "^3.7.5", - "libsql": "^0.3.10" + "./sqlite3": { + "types": "./lib-esm/sqlite3.d.ts", + "import": "./lib-esm/sqlite3.js", + "require": "./lib-cjs/sqlite3.js" }, - "devDependencies": { - "@types/jest": "^29.2.5", - "@types/node": "^18.15.5", - "husky": "^9.0.11", - "jest": "^29.3.1", - "lint-staged": "^15.2.2", - "prettier": "3.2.5", - "ts-jest": "^29.0.5", - "typedoc": "^0.23.28", - "typescript": "^4.9.4" + "./web": { + "types": "./lib-esm/web.d.ts", + "import": "./lib-esm/web.js", + "require": "./lib-cjs/web.js" } + }, + "typesVersions": { + "*": { + ".": [ + "./lib-esm/node.d.ts" + ], + "http": [ + "./lib-esm/http.d.ts" + ], + "hrana": [ + "./lib-esm/hrana.d.ts" + ], + "sqlite3": [ + "./lib-esm/sqlite3.d.ts" + ], + "web": [ + "./lib-esm/web.d.ts" + ] + } + }, + "files": [ + "lib-cjs/**", + "lib-esm/**", + "README.md" + ], + "scripts": { + "prepublishOnly": "npm run build", + "prebuild": "rm -rf ./lib-cjs ./lib-esm", + "build": "npm run build:cjs && npm run build:esm", + "build:cjs": "tsc -p tsconfig.build-cjs.json", + "build:esm": "tsc -p tsconfig.build-esm.json", + "postbuild": "cp package-cjs.json ./lib-cjs/package.json", + "test": "jest --runInBand", + "typecheck": "tsc --noEmit", + "typedoc": "rm -rf ./docs && typedoc", + "prepare": "husky install", + "lint-staged": "lint-staged" + }, + "dependencies": { + "@libsql/core": "^0.6.0", + "@libsql/hrana-client": "^0.6.0", + "js-base64": "^3.7.5", + "libsql": "^0.3.10" + }, + "devDependencies": { + "@types/jest": "^29.2.5", + "@types/node": "^18.15.5", + "husky": "^9.0.11", + "jest": "^29.3.1", + "lint-staged": "^15.2.2", + "prettier": "3.2.5", + "ts-jest": "^29.0.5", + "typedoc": "^0.23.28", + "typescript": "^4.9.4" + } } diff --git a/packages/libsql-client/smoke_test/vercel/app/api/function.ts b/packages/libsql-client/smoke_test/vercel/app/api/function.ts index 22d7a7e8..9cc0ef1c 100644 --- a/packages/libsql-client/smoke_test/vercel/app/api/function.ts +++ b/packages/libsql-client/smoke_test/vercel/app/api/function.ts @@ -1,110 +1,111 @@ import * as libsql from "@libsql/client"; export const config = { - runtime: "edge", + runtime: "edge", }; export default async function (request: Request) { - function respond(status: number, responseBody: string) { - return new Response(responseBody, { - status, - headers: [ - ["content-type", "text/plain"], - ], - }); - } - - if (request.method !== "GET") { - return respond(405, "Only GET method is supported"); - } - - const url = new URL(request.url); - const testCase = url.searchParams.get("test"); - if (testCase === null) { - return respond(400, "Please specify the test case using the 'test' query parameter"); - } - - const testCaseFn = testCases[testCase]; - if (testCaseFn === undefined) { - return respond(404, "Unknown test case"); + function respond(status: number, responseBody: string) { + return new Response(responseBody, { + status, + headers: [["content-type", "text/plain"]], + }); + } + + if (request.method !== "GET") { + return respond(405, "Only GET method is supported"); + } + + const url = new URL(request.url); + const testCase = url.searchParams.get("test"); + if (testCase === null) { + return respond( + 400, + "Please specify the test case using the 'test' query parameter", + ); + } + + const testCaseFn = testCases[testCase]; + if (testCaseFn === undefined) { + return respond(404, "Unknown test case"); + } + + let client; + try { + client = libsql.createClient({ url: process.env.CLIENT_URL! }); + await testCaseFn(client); + return respond(200, "Test passed"); + } catch (e) { + return respond(500, `Test failed\n${(e as Error).stack}`); + } finally { + if (client !== undefined) { + client.close(); } + } +} - let client; +const testCases: Record Promise> = { + execute: async (client: libsql.Client): Promise => { + const rs = await client.execute("SELECT 1+1 AS two"); + assert(rs.columns.length === 1); + assert(rs.columns[0] === "two"); + assert(rs.rows.length === 1); + assert(rs.rows[0].length === 1); + assert(rs.rows[0][0] === 2.0); + }, + + batch: async (client: libsql.Client): Promise => { + const rss = await client.batch([ + "DROP TABLE IF EXISTS t", + "CREATE TABLE t (a, b)", + "INSERT INTO t VALUES (1, 'one'), (2, 'two'), (3, 'three')", + "SELECT * FROM t ORDER BY a", + ]); + + assert(rss[0].columns.length === 0); + assert(rss[0].rows.length === 0); + + assert(rss[1].columns.length === 0); + assert(rss[1].rows.length === 0); + + assert(rss[2].columns.length === 0); + assert(rss[2].rows.length === 0); + + assert(rss[3].columns.length === 2); + assert(rss[3].columns[0] === "a"); + assert(rss[3].columns[1] === "b"); + assert(rss[3].rows.length === 3); + assert(rss[3].rows[0][0] === 1); + assert(rss[3].rows[0][1] === "one"); + assert(rss[3].rows[1][0] === 2); + assert(rss[3].rows[1][1] === "two"); + assert(rss[3].rows[2][0] === 3); + assert(rss[3].rows[2][1] === "three"); + }, + + transaction: async (client: libsql.Client): Promise => { + await client.batch([ + "DROP TABLE IF EXISTS t", + "CREATE TABLE t (a, b)", + "INSERT INTO t VALUES (1, 'one'), (2, 'two'), (3, 'three')", + ]); + + const txn = await client.transaction(); try { - client = libsql.createClient({url: process.env.CLIENT_URL!}); - await testCaseFn(client); - return respond(200, "Test passed"); - } catch (e) { - return respond(500, `Test failed\n${(e as Error).stack}`); + await txn.execute("INSERT INTO t VALUES (4, 'four')"); + await txn.execute("DELETE FROM t WHERE a <= 2"); + await txn.commit(); } finally { - if (client !== undefined) { - client.close(); - } + txn.close(); } -}; -const testCases: Record Promise> = { - "execute": async (client: libsql.Client): Promise => { - const rs = await client.execute("SELECT 1+1 AS two"); - assert(rs.columns.length === 1); - assert(rs.columns[0] === "two"); - assert(rs.rows.length === 1); - assert(rs.rows[0].length === 1); - assert(rs.rows[0][0] === 2.0); - }, - - "batch": async (client: libsql.Client): Promise => { - const rss = await client.batch([ - "DROP TABLE IF EXISTS t", - "CREATE TABLE t (a, b)", - "INSERT INTO t VALUES (1, 'one'), (2, 'two'), (3, 'three')", - "SELECT * FROM t ORDER BY a", - ]); - - assert(rss[0].columns.length === 0); - assert(rss[0].rows.length === 0); - - assert(rss[1].columns.length === 0); - assert(rss[1].rows.length === 0); - - assert(rss[2].columns.length === 0); - assert(rss[2].rows.length === 0); - - assert(rss[3].columns.length === 2); - assert(rss[3].columns[0] === "a"); - assert(rss[3].columns[1] === "b"); - assert(rss[3].rows.length === 3); - assert(rss[3].rows[0][0] === 1); - assert(rss[3].rows[0][1] === "one"); - assert(rss[3].rows[1][0] === 2); - assert(rss[3].rows[1][1] === "two"); - assert(rss[3].rows[2][0] === 3); - assert(rss[3].rows[2][1] === "three"); - }, - - "transaction": async (client: libsql.Client): Promise => { - await client.batch([ - "DROP TABLE IF EXISTS t", - "CREATE TABLE t (a, b)", - "INSERT INTO t VALUES (1, 'one'), (2, 'two'), (3, 'three')", - ]); - - const txn = await client.transaction(); - try { - await txn.execute("INSERT INTO t VALUES (4, 'four')"); - await txn.execute("DELETE FROM t WHERE a <= 2"); - await txn.commit(); - } finally { - txn.close(); - } - - const rs = await client.execute("SELECT COUNT(*) FROM t"); - assert(rs.rows[0][0] === 2); - }, + const rs = await client.execute("SELECT COUNT(*) FROM t"); + assert(rs.rows[0][0] === 2); + }, }; function assert(value: unknown, message?: string) { - if (!value) { - throw new Error(message ?? "Assertion failed"); - } + if (!value) { + throw new Error(message ?? "Assertion failed"); + } } diff --git a/packages/libsql-client/smoke_test/vercel/app/public/index.html b/packages/libsql-client/smoke_test/vercel/app/public/index.html index e3d18d45..53021736 100644 --- a/packages/libsql-client/smoke_test/vercel/app/public/index.html +++ b/packages/libsql-client/smoke_test/vercel/app/public/index.html @@ -1,3 +1,5 @@ - -

This is a smoke-test Vercel app for @libsql/client.

+ +

This is a smoke-test Vercel app for @libsql/client.

+ + diff --git a/packages/libsql-client/smoke_test/vercel/package.json b/packages/libsql-client/smoke_test/vercel/package.json index f21a0056..07a0a823 100644 --- a/packages/libsql-client/smoke_test/vercel/package.json +++ b/packages/libsql-client/smoke_test/vercel/package.json @@ -1,9 +1,9 @@ { - "name": "smoke-test", - "dependencies": { - "@types/node": "20.4.2", - "localtunnel": "^2.0.2", - "vercel": "^31.0.3", - "typescript": "^4.9.4" - } + "name": "smoke-test", + "dependencies": { + "@types/node": "20.4.2", + "localtunnel": "^2.0.2", + "vercel": "^31.0.3", + "typescript": "^4.9.4" + } } diff --git a/packages/libsql-client/smoke_test/vercel/test.js b/packages/libsql-client/smoke_test/vercel/test.js index d6c22189..f1d28bdc 100644 --- a/packages/libsql-client/smoke_test/vercel/test.js +++ b/packages/libsql-client/smoke_test/vercel/test.js @@ -5,157 +5,176 @@ const fetch = require("node-fetch"); const localtunnel = require("localtunnel"); function getEnv(name) { - const value = process.env[name] ?? ""; - if (!value) { - throw new Error(`Please set the env variable ${name}`); - } - return value; + const value = process.env[name] ?? ""; + if (!value) { + throw new Error(`Please set the env variable ${name}`); + } + return value; } const vercelToken = getEnv("VERCEL_TOKEN"); const projectName = getEnv("VERCEL_PROJECT_NAME"); -async function npm(subcommand, args, hiddenArgs = [], {capture = false} = {}) { - console.info(`$ npm ${subcommand} ${args.join(' ')}`); - - const proc = spawn("npm", [subcommand, ...args, ...hiddenArgs], { - stdio: ["ignore", capture ? "pipe" : "inherit", "inherit"], - }); - - const exitPromise = new Promise((resolve, reject) => { - proc.on("exit", (code, signal) => { - if (signal !== null) { - reject(new Error(`vercel command terminated due to signal: ${signal}`)); - } else if (code !== 0) { - reject(new Error(`vercel command exited with code: ${code}`)); - } else { - resolve(); - } - }); +async function npm( + subcommand, + args, + hiddenArgs = [], + { capture = false } = {}, +) { + console.info(`$ npm ${subcommand} ${args.join(" ")}`); + + const proc = spawn("npm", [subcommand, ...args, ...hiddenArgs], { + stdio: ["ignore", capture ? "pipe" : "inherit", "inherit"], + }); + + const exitPromise = new Promise((resolve, reject) => { + proc.on("exit", (code, signal) => { + if (signal !== null) { + reject(new Error(`vercel command terminated due to signal: ${signal}`)); + } else if (code !== 0) { + reject(new Error(`vercel command exited with code: ${code}`)); + } else { + resolve(); + } }); + }); - const dataPromise = new Promise((resolve, reject) => { - if (!capture) { - return resolve(); - } + const dataPromise = new Promise((resolve, reject) => { + if (!capture) { + return resolve(); + } - const stream = proc.stdout; - stream.setEncoding("utf-8"); + const stream = proc.stdout; + stream.setEncoding("utf-8"); - const chunks = []; - stream.on("data", (chunk) => chunks.push(chunk)); - stream.on("end", () => resolve(chunks.join(""))); - stream.on("error", (e) => reject(e)); - }); + const chunks = []; + stream.on("data", (chunk) => chunks.push(chunk)); + stream.on("end", () => resolve(chunks.join(""))); + stream.on("error", (e) => reject(e)); + }); - return exitPromise.then(() => dataPromise); + return exitPromise.then(() => dataPromise); } async function deployToVercel(clientUrlInsideVercel) { - console.info("Building and deploying to Vercel..."); - - let tarballName = await npm("pack", ["../.."], [], {capture: true}); - tarballName = tarballName.trim(); - - const appPackageJson = { - "dependencies": { - "@libsql/client": `../${tarballName}`, - }, - }; - fs.writeFileSync("app/package.json", JSON.stringify(appPackageJson, null, 4)); - - await npm( - "exec", - ["--", "vercel", "link", "--yes", "--project", projectName, "--cwd", "app/"], - ["--token", vercelToken], - ); - await npm( - "exec", - ["--", "vercel", "pull", "--yes", "--environment=preview", "--cwd", "app/"], - ["--token", vercelToken], - ); - await npm( - "exec", - ["--", "vercel", "build", "--cwd", "app/"], - ); - - const deployUrl = await npm( - "exec", - [ - "--", "vercel", "deploy", "--prebuilt", - "--env", `CLIENT_URL=${clientUrlInsideVercel}`, "--cwd", "app/", - ], - ["--token", vercelToken, "--cwd", "app/"], - {capture: true}, - ); - - console.info(`Deployed Vercel project on ${deployUrl}`); - return deployUrl; + console.info("Building and deploying to Vercel..."); + + let tarballName = await npm("pack", ["../.."], [], { capture: true }); + tarballName = tarballName.trim(); + + const appPackageJson = { + dependencies: { + "@libsql/client": `../${tarballName}`, + }, + }; + fs.writeFileSync("app/package.json", JSON.stringify(appPackageJson, null, 4)); + + await npm( + "exec", + [ + "--", + "vercel", + "link", + "--yes", + "--project", + projectName, + "--cwd", + "app/", + ], + ["--token", vercelToken], + ); + await npm( + "exec", + ["--", "vercel", "pull", "--yes", "--environment=preview", "--cwd", "app/"], + ["--token", vercelToken], + ); + await npm("exec", ["--", "vercel", "build", "--cwd", "app/"]); + + const deployUrl = await npm( + "exec", + [ + "--", + "vercel", + "deploy", + "--prebuilt", + "--env", + `CLIENT_URL=${clientUrlInsideVercel}`, + "--cwd", + "app/", + ], + ["--token", vercelToken, "--cwd", "app/"], + { capture: true }, + ); + + console.info(`Deployed Vercel project on ${deployUrl}`); + return deployUrl; } const testCases = ["execute", "batch", "transaction"]; async function runTests(functionUrl) { - let ok = true; - for (const testCase of testCases) { - if (!await runTest(functionUrl, testCase)) { - ok = false; - } + let ok = true; + for (const testCase of testCases) { + if (!(await runTest(functionUrl, testCase))) { + ok = false; } - return ok; + } + return ok; } async function runTest(functionUrl, testCase) { - const resp = await fetch(`${functionUrl}?test=${testCase}`); - const respText = await resp.text(); - const ok = resp.status === 200 && respText === "Test passed"; - if (ok) { - console.info(`TEST ${testCase}: passed`); - } else { - console.warn(`\nTEST ${testCase}: failed with status ${resp.status}\n${respText}\n`); - } - return ok; + const resp = await fetch(`${functionUrl}?test=${testCase}`); + const respText = await resp.text(); + const ok = resp.status === 200 && respText === "Test passed"; + if (ok) { + console.info(`TEST ${testCase}: passed`); + } else { + console.warn( + `\nTEST ${testCase}: failed with status ${resp.status}\n${respText}\n`, + ); + } + return ok; } async function main() { - const url = new URL(process.env.URL ?? "ws://localhost:8080"); - - console.info(`Creating a tunnel to ${url}...`); - const tunnel = await localtunnel({ - port: url.port, - // NOTE: if we specify `local_host`, `localtunnel` will try to rewrite the `Host` header in the - // tunnelled HTTP requests. Unfortunately, they do it in a very silly way by converting the - // tunnelled data to a string, thus corrupting the request body. - //local_host: url.hostname, - }); - - let clientUrlInsideVercel = new URL(tunnel.url); - if (url.protocol === "http:") { - clientUrlInsideVercel.protocol = "https:"; - } else if (url.protocol === "ws:") { - clientUrlInsideVercel.protocol = "wss:"; + const url = new URL(process.env.URL ?? "ws://localhost:8080"); + + console.info(`Creating a tunnel to ${url}...`); + const tunnel = await localtunnel({ + port: url.port, + // NOTE: if we specify `local_host`, `localtunnel` will try to rewrite the `Host` header in the + // tunnelled HTTP requests. Unfortunately, they do it in a very silly way by converting the + // tunnelled data to a string, thus corrupting the request body. + //local_host: url.hostname, + }); + + let clientUrlInsideVercel = new URL(tunnel.url); + if (url.protocol === "http:") { + clientUrlInsideVercel.protocol = "https:"; + } else if (url.protocol === "ws:") { + clientUrlInsideVercel.protocol = "wss:"; + } else { + clientUrlInsideVercel.protocol = url.protocol; + } + + console.info(`Established a tunnel on ${clientUrlInsideVercel}`); + + let ok = false; + try { + const deployUrl = await deployToVercel(clientUrlInsideVercel); + const functionUrl = new URL("api/function", deployUrl); + ok = await runTests(functionUrl); + if (ok) { + console.log("All tests passed"); } else { - clientUrlInsideVercel.protocol = url.protocol; - } - - console.info(`Established a tunnel on ${clientUrlInsideVercel}`); - - let ok = false; - try { - const deployUrl = await deployToVercel(clientUrlInsideVercel); - const functionUrl = new URL("api/function", deployUrl); - ok = await runTests(functionUrl); - if (ok) { - console.log("All tests passed"); - } else { - console.error("Some tests failed"); - } - } finally { - console.info("Closing the tunnel..."); - await tunnel.close(); + console.error("Some tests failed"); } + } finally { + console.info("Closing the tunnel..."); + await tunnel.close(); + } - process.exit(ok ? 0 : 1); + process.exit(ok ? 0 : 1); } main(); diff --git a/packages/libsql-client/smoke_test/workers/package.json b/packages/libsql-client/smoke_test/workers/package.json index 5b9d41c5..1ced1b79 100644 --- a/packages/libsql-client/smoke_test/workers/package.json +++ b/packages/libsql-client/smoke_test/workers/package.json @@ -1,6 +1,6 @@ { - "devDependencies": { - "localtunnel": "^2.0.2", - "wrangler": "^3.5.1" - } + "devDependencies": { + "localtunnel": "^2.0.2", + "wrangler": "^3.5.1" + } } diff --git a/packages/libsql-client/smoke_test/workers/test.js b/packages/libsql-client/smoke_test/workers/test.js index 5d5d567a..c0490274 100644 --- a/packages/libsql-client/smoke_test/workers/test.js +++ b/packages/libsql-client/smoke_test/workers/test.js @@ -5,94 +5,96 @@ const wrangler = require("wrangler"); const testCases = ["/execute", "/batch", "/transaction"]; async function main() { - const local = !!parseInt(process.env.LOCAL ?? "1"); - const url = new URL(process.env.URL ?? "ws://localhost:8080"); + const local = !!parseInt(process.env.LOCAL ?? "1"); + const url = new URL(process.env.URL ?? "ws://localhost:8080"); - let clientUrlInsideWorker; - let tunnel = undefined; - if (local) { - clientUrlInsideWorker = url; - } else { - console.info(`Creating an tunnel to ${url}...`); - tunnel = await localtunnel({ - port: url.port, - // NOTE: if we specify `local_host`, `localtunnel` will try to rewrite the `Host` header in the - // tunnelled HTTP requests. Unfortunately, they do it in a very silly way by converting the - // tunnelled data to a string, thus corrupting the request body. - //local_host: url.hostname, - }); - - clientUrlInsideWorker = new URL(tunnel.url); - if (url.protocol === "http:") { - clientUrlInsideWorker.protocol = "https:"; - } else if (url.protocol === "ws:") { - clientUrlInsideWorker.protocol = "wss:"; - } else { - clientUrlInsideWorker.protocol = url.protocol; - } + let clientUrlInsideWorker; + let tunnel = undefined; + if (local) { + clientUrlInsideWorker = url; + } else { + console.info(`Creating an tunnel to ${url}...`); + tunnel = await localtunnel({ + port: url.port, + // NOTE: if we specify `local_host`, `localtunnel` will try to rewrite the `Host` header in the + // tunnelled HTTP requests. Unfortunately, they do it in a very silly way by converting the + // tunnelled data to a string, thus corrupting the request body. + //local_host: url.hostname, + }); - console.info(`Established a tunnel on ${clientUrlInsideWorker}`); + clientUrlInsideWorker = new URL(tunnel.url); + if (url.protocol === "http:") { + clientUrlInsideWorker.protocol = "https:"; + } else if (url.protocol === "ws:") { + clientUrlInsideWorker.protocol = "wss:"; + } else { + clientUrlInsideWorker.protocol = url.protocol; } - let ok = false; - try { - ok = await runWorker(local, clientUrlInsideWorker); - if (ok) { - console.log("All tests passed"); - } else { - console.error("Some tests failed"); - } - } finally { - if (tunnel !== undefined) { - console.info("Closing tunnel..."); - await tunnel.close(); - } + console.info(`Established a tunnel on ${clientUrlInsideWorker}`); + } - // TODO: wrangler keeps the program running: - // https://github.com/cloudflare/workers-sdk/issues/2892 - setTimeout(() => process.exit(ok ? 0 : 1), 200); + let ok = false; + try { + ok = await runWorker(local, clientUrlInsideWorker); + if (ok) { + console.log("All tests passed"); + } else { + console.error("Some tests failed"); + } + } finally { + if (tunnel !== undefined) { + console.info("Closing tunnel..."); + await tunnel.close(); } + + // TODO: wrangler keeps the program running: + // https://github.com/cloudflare/workers-sdk/issues/2892 + setTimeout(() => process.exit(ok ? 0 : 1), 200); + } } async function runWorker(local, clientUrlInsideWorker) { - console.info(`Creating a ${local ? 'local' : 'nonlocal'} Worker...`); - const worker = await wrangler.unstable_dev("worker.js", { - config: "wrangler.toml", - logLevel: "info", - local, - vars: { - "CLIENT_URL": clientUrlInsideWorker.toString(), - }, - experimental: { - disableExperimentalWarning: true, - } - }); - console.info(`Worker created on ${worker.address}:${worker.port}`); + console.info(`Creating a ${local ? "local" : "nonlocal"} Worker...`); + const worker = await wrangler.unstable_dev("worker.js", { + config: "wrangler.toml", + logLevel: "info", + local, + vars: { + CLIENT_URL: clientUrlInsideWorker.toString(), + }, + experimental: { + disableExperimentalWarning: true, + }, + }); + console.info(`Worker created on ${worker.address}:${worker.port}`); - try { - let ok = true; - for (const testCase of testCases) { - if (!await runTest(worker, testCase)) { - ok = false; - } - } - return ok; - } finally { - console.info("Stopping Worker..."); - await worker.stop(); + try { + let ok = true; + for (const testCase of testCases) { + if (!(await runTest(worker, testCase))) { + ok = false; + } } + return ok; + } finally { + console.info("Stopping Worker..."); + await worker.stop(); + } } async function runTest(worker, testCase) { - const resp = await worker.fetch(testCase); - const respText = await resp.text(); - const ok = resp.status === 200 && respText === "Test passed"; - if (ok) { - console.info(`TEST ${testCase}: passed`); - } else { - console.warn(`\nTEST ${testCase}: failed with status ${resp.status}\n${respText}\n`); - } - return ok; + const resp = await worker.fetch(testCase); + const respText = await resp.text(); + const ok = resp.status === 200 && respText === "Test passed"; + if (ok) { + console.info(`TEST ${testCase}: passed`); + } else { + console.warn( + `\nTEST ${testCase}: failed with status ${resp.status}\n${respText}\n`, + ); + } + return ok; } main(); diff --git a/packages/libsql-client/smoke_test/workers/worker.js b/packages/libsql-client/smoke_test/workers/worker.js index 62a6492b..23e8fec8 100644 --- a/packages/libsql-client/smoke_test/workers/worker.js +++ b/packages/libsql-client/smoke_test/workers/worker.js @@ -1,107 +1,105 @@ import * as libsql from "@libsql/client"; export default { - async fetch(request, env, ctx) { - function respond(status, responseBody) { - return new Response(responseBody, { - status, - headers: [ - ["content-type", "text/plain"], - ], - }); - } + async fetch(request, env, ctx) { + function respond(status, responseBody) { + return new Response(responseBody, { + status, + headers: [["content-type", "text/plain"]], + }); + } - if (request.method !== "GET") { - return respond(405, "Only GET method is supported"); - } + if (request.method !== "GET") { + return respond(405, "Only GET method is supported"); + } - const url = new URL(request.url); - if (url.pathname === "/") { - return respond(200, "This is a smoke-test Worker for @libsql/client"); - } + const url = new URL(request.url); + if (url.pathname === "/") { + return respond(200, "This is a smoke-test Worker for @libsql/client"); + } - const testCaseFn = testCases[url.pathname]; - if (testCaseFn === undefined) { - return respond(404, "Unknown test case"); - } + const testCaseFn = testCases[url.pathname]; + if (testCaseFn === undefined) { + return respond(404, "Unknown test case"); + } - let client; - try { - client = libsql.createClient({url: env.CLIENT_URL}); - await testCaseFn(client); - return respond(200, "Test passed"); - } catch (e) { - return respond(500, `Test failed\n${e.stack}`); - } finally { - if (client !== undefined) { - client.close(); - } - } - }, + let client; + try { + client = libsql.createClient({ url: env.CLIENT_URL }); + await testCaseFn(client); + return respond(200, "Test passed"); + } catch (e) { + return respond(500, `Test failed\n${e.stack}`); + } finally { + if (client !== undefined) { + client.close(); + } + } + }, }; const testCases = { - "/execute": async (client) => { - const rs = await client.execute("SELECT 1+1 AS two"); - assert(rs.columns.length === 1); - assert(rs.columns[0] === "two"); - assert(rs.rows.length === 1); - assert(rs.rows[0].length === 1); - assert(rs.rows[0][0] === 2.0); - }, + "/execute": async (client) => { + const rs = await client.execute("SELECT 1+1 AS two"); + assert(rs.columns.length === 1); + assert(rs.columns[0] === "two"); + assert(rs.rows.length === 1); + assert(rs.rows[0].length === 1); + assert(rs.rows[0][0] === 2.0); + }, - "/batch": async (client) => { - const rss = await client.batch([ - "DROP TABLE IF EXISTS t", - "CREATE TABLE t (a, b)", - "INSERT INTO t VALUES (1, 'one'), (2, 'two'), (3, 'three')", - "SELECT * FROM t ORDER BY a", - ]); + "/batch": async (client) => { + const rss = await client.batch([ + "DROP TABLE IF EXISTS t", + "CREATE TABLE t (a, b)", + "INSERT INTO t VALUES (1, 'one'), (2, 'two'), (3, 'three')", + "SELECT * FROM t ORDER BY a", + ]); - assert(rss[0].columns.length === 0); - assert(rss[0].rows.length === 0); + assert(rss[0].columns.length === 0); + assert(rss[0].rows.length === 0); - assert(rss[1].columns.length === 0); - assert(rss[1].rows.length === 0); + assert(rss[1].columns.length === 0); + assert(rss[1].rows.length === 0); - assert(rss[2].columns.length === 0); - assert(rss[2].rows.length === 0); + assert(rss[2].columns.length === 0); + assert(rss[2].rows.length === 0); - assert(rss[3].columns.length === 2); - assert(rss[3].columns[0] === "a"); - assert(rss[3].columns[1] === "b"); - assert(rss[3].rows.length === 3); - assert(rss[3].rows[0][0] === 1); - assert(rss[3].rows[0][1] === "one"); - assert(rss[3].rows[1][0] === 2); - assert(rss[3].rows[1][1] === "two"); - assert(rss[3].rows[2][0] === 3); - assert(rss[3].rows[2][1] === "three"); - }, + assert(rss[3].columns.length === 2); + assert(rss[3].columns[0] === "a"); + assert(rss[3].columns[1] === "b"); + assert(rss[3].rows.length === 3); + assert(rss[3].rows[0][0] === 1); + assert(rss[3].rows[0][1] === "one"); + assert(rss[3].rows[1][0] === 2); + assert(rss[3].rows[1][1] === "two"); + assert(rss[3].rows[2][0] === 3); + assert(rss[3].rows[2][1] === "three"); + }, - "/transaction": async (client) => { - await client.batch([ - "DROP TABLE IF EXISTS t", - "CREATE TABLE t (a, b)", - "INSERT INTO t VALUES (1, 'one'), (2, 'two'), (3, 'three')", - ]); + "/transaction": async (client) => { + await client.batch([ + "DROP TABLE IF EXISTS t", + "CREATE TABLE t (a, b)", + "INSERT INTO t VALUES (1, 'one'), (2, 'two'), (3, 'three')", + ]); - const txn = await client.transaction(); - try { - await txn.execute("INSERT INTO t VALUES (4, 'four')"); - await txn.execute("DELETE FROM t WHERE a <= 2"); - await txn.commit(); - } finally { - txn.close(); - } + const txn = await client.transaction(); + try { + await txn.execute("INSERT INTO t VALUES (4, 'four')"); + await txn.execute("DELETE FROM t WHERE a <= 2"); + await txn.commit(); + } finally { + txn.close(); + } - const rs = await client.execute("SELECT COUNT(*) FROM t"); - assert(rs.rows[0][0] === 2); - }, + const rs = await client.execute("SELECT COUNT(*) FROM t"); + assert(rs.rows[0][0] === 2); + }, }; function assert(value, message) { - if (!value) { - throw new Error(message ?? "Assertion failed"); - } + if (!value) { + throw new Error(message ?? "Assertion failed"); + } } diff --git a/packages/libsql-client/src/__tests__/client.test.ts b/packages/libsql-client/src/__tests__/client.test.ts index 6ae29488..8607ad24 100644 --- a/packages/libsql-client/src/__tests__/client.test.ts +++ b/packages/libsql-client/src/__tests__/client.test.ts @@ -11,13 +11,17 @@ import type * as libsql from "../node.js"; import { createClient } from "../node.js"; const config = { - url: process.env.URL ?? "ws://localhost:8080", - syncUrl: process.env.SYNC_URL, - authToken: process.env.AUTH_TOKEN, + url: process.env.URL ?? "ws://localhost:8080", + syncUrl: process.env.SYNC_URL, + authToken: process.env.AUTH_TOKEN, }; -const isWs = config.url.startsWith("ws:") || config.url.startsWith("wss:") || config.url.startsWith("libsql:"); -const isHttp = config.url.startsWith("http:") || config.url.startsWith("https:"); +const isWs = + config.url.startsWith("ws:") || + config.url.startsWith("wss:") || + config.url.startsWith("libsql:"); +const isHttp = + config.url.startsWith("http:") || config.url.startsWith("https:"); const isFile = config.url.startsWith("file:"); // This allows us to skip tests based on the Hrana server that we are targeting: @@ -29,880 +33,1182 @@ const server = process.env.SERVER ?? "test_v3"; const hasHrana2 = server !== "test_v1"; const hasHrana3 = server !== "test_v1" && server !== "test_v2"; -const hasNetworkErrors = isWs && (server === "test_v1" || server === "test_v2" || server === "test_v3"); +const hasNetworkErrors = + isWs && + (server === "test_v1" || server === "test_v2" || server === "test_v3"); function withClient( - f: (c: libsql.Client) => Promise, - extraConfig: Partial = {}, + f: (c: libsql.Client) => Promise, + extraConfig: Partial = {}, ): () => Promise { - return async () => { - const c = createClient({...config, ...extraConfig}); - try { - await f(c); - } finally { - c.close(); - } - }; + return async () => { + const c = createClient({ ...config, ...extraConfig }); + try { + await f(c); + } finally { + c.close(); + } + }; } function withInMemoryClient( - f: (c: libsql.Client) => Promise, + f: (c: libsql.Client) => Promise, ): () => Promise { - return async () => { - const c = createClient({ url: ":memory:" }); - try { - await f(c); - } finally { - c.close(); - } - }; + return async () => { + const c = createClient({ url: ":memory:" }); + try { + await f(c); + } finally { + c.close(); + } + }; } describe("createClient()", () => { - test("URL scheme not supported", () => { - expect(() => createClient({url: "ftp://localhost"})) - .toThrow(expect.toBeLibsqlError("URL_SCHEME_NOT_SUPPORTED", /"ftp:"/)); - }); - - test("URL param not supported", () => { - expect(() => createClient({url: "ws://localhost?foo=bar"})) - .toThrow(expect.toBeLibsqlError("URL_PARAM_NOT_SUPPORTED", /"foo"/)); - }); - - test("URL scheme incompatible with ?tls", () => { - const urls = [ - "ws://localhost?tls=1", - "wss://localhost?tls=0", - "http://localhost?tls=1", - "https://localhost?tls=0", - ]; - for (const url of urls) { - expect(() => createClient({url})) - .toThrow(expect.toBeLibsqlError("URL_INVALID", /TLS/)); - } - }); + test("URL scheme not supported", () => { + expect(() => createClient({ url: "ftp://localhost" })).toThrow( + expect.toBeLibsqlError("URL_SCHEME_NOT_SUPPORTED", /"ftp:"/), + ); + }); + + test("URL param not supported", () => { + expect(() => createClient({ url: "ws://localhost?foo=bar" })).toThrow( + expect.toBeLibsqlError("URL_PARAM_NOT_SUPPORTED", /"foo"/), + ); + }); + + test("URL scheme incompatible with ?tls", () => { + const urls = [ + "ws://localhost?tls=1", + "wss://localhost?tls=0", + "http://localhost?tls=1", + "https://localhost?tls=0", + ]; + for (const url of urls) { + expect(() => createClient({ url })).toThrow( + expect.toBeLibsqlError("URL_INVALID", /TLS/), + ); + } + }); - test("missing port in libsql URL with tls=0", () => { - expect(() => createClient({url: "libsql://localhost?tls=0"})) - .toThrow(expect.toBeLibsqlError("URL_INVALID", /port/)); - }); + test("missing port in libsql URL with tls=0", () => { + expect(() => createClient({ url: "libsql://localhost?tls=0" })).toThrow( + expect.toBeLibsqlError("URL_INVALID", /port/), + ); + }); - test("invalid value of tls query param", () => { - expect(() => createClient({url: "libsql://localhost?tls=yes"})) - .toThrow(expect.toBeLibsqlError("URL_INVALID", /"tls".*"yes"/)); - }); + test("invalid value of tls query param", () => { + expect(() => createClient({ url: "libsql://localhost?tls=yes" })).toThrow( + expect.toBeLibsqlError("URL_INVALID", /"tls".*"yes"/), + ); + }); - test("passing URL instead of config object", () => { - // @ts-expect-error - expect(() => createClient("ws://localhost")).toThrow(/as object, got string/); - }); + test("passing URL instead of config object", () => { + // @ts-expect-error + expect(() => createClient("ws://localhost")).toThrow( + /as object, got string/, + ); + }); - test("invalid value for `intMode`", () => { - // @ts-expect-error - expect(() => createClient({...config, intMode: "foo"})).toThrow(/"foo"/); - }); + test("invalid value for `intMode`", () => { + // @ts-expect-error + expect(() => createClient({ ...config, intMode: "foo" })).toThrow(/"foo"/); + }); - test("supports in-memory database", () => { - expect(() => createClient({url: ":memory:"})).not.toThrow(); - }); + test("supports in-memory database", () => { + expect(() => createClient({ url: ":memory:" })).not.toThrow(); + }); }); describe("execute()", () => { - test("query a single value", withClient(async (c) => { - const rs = await c.execute("SELECT 42"); - expect(rs.columns.length).toStrictEqual(1); - expect(rs.columnTypes.length).toStrictEqual(1); - expect(rs.rows.length).toStrictEqual(1); - expect(rs.rows[0].length).toStrictEqual(1); - expect(rs.rows[0][0]).toStrictEqual(42); - })); - - test("query a single row", withClient(async (c) => { - const rs = await c.execute("SELECT 1 AS one, 'two' AS two, 0.5 AS three"); - expect(rs.columns).toStrictEqual(["one", "two", "three"]); - expect(rs.columnTypes).toStrictEqual(["", "", ""]); - expect(rs.rows.length).toStrictEqual(1); - - const r = rs.rows[0]; - expect(r.length).toStrictEqual(3); - expect(Array.from(r)).toStrictEqual([1, "two", 0.5]); - expect(Object.entries(r)).toStrictEqual([["one", 1], ["two", "two"], ["three", 0.5]]); - })); - - test("query multiple rows", withClient(async (c) => { - const rs = await c.execute("VALUES (1, 'one'), (2, 'two'), (3, 'three')"); - expect(rs.columns.length).toStrictEqual(2); - expect(rs.columnTypes.length).toStrictEqual(2); - expect(rs.rows.length).toStrictEqual(3); - - expect(Array.from(rs.rows[0])).toStrictEqual([1, "one"]); - expect(Array.from(rs.rows[1])).toStrictEqual([2, "two"]); - expect(Array.from(rs.rows[2])).toStrictEqual([3, "three"]); - })); - - test("statement that produces error", withClient(async (c) => { - await expect(c.execute("SELECT foobar")).rejects.toBeLibsqlError(); - })); - - test("rowsAffected with INSERT", withClient(async (c) => { - await c.batch([ - "DROP TABLE IF EXISTS t", - "CREATE TABLE t (a)", - ], "write"); - const rs = await c.execute("INSERT INTO t VALUES (1), (2)"); - expect(rs.rowsAffected).toStrictEqual(2); - })); - - test("rowsAffected with DELETE", withClient(async (c) => { - await c.batch([ - "DROP TABLE IF EXISTS t", - "CREATE TABLE t (a)", - "INSERT INTO t VALUES (1), (2), (3), (4), (5)", - ], "write"); - const rs = await c.execute("DELETE FROM t WHERE a >= 3"); - expect(rs.rowsAffected).toStrictEqual(3); - })); - - test("lastInsertRowid with INSERT", withClient(async (c) => { - await c.batch([ - "DROP TABLE IF EXISTS t", - "CREATE TABLE t (a)", - "INSERT INTO t VALUES ('one'), ('two')", - ], "write"); - const insertRs = await c.execute("INSERT INTO t VALUES ('three')"); - expect(insertRs.lastInsertRowid).not.toBeUndefined(); - const selectRs = await c.execute({ - sql: "SELECT a FROM t WHERE ROWID = ?", - args: [insertRs.lastInsertRowid!], - }); - expect(Array.from(selectRs.rows[0])).toStrictEqual(["three"]); - })); - - test("rows from INSERT RETURNING", withClient(async (c) => { - await c.batch([ - "DROP TABLE IF EXISTS t", - "CREATE TABLE t (a)", - ], "write"); - - const rs = await c.execute("INSERT INTO t VALUES (1) RETURNING 42 AS x, 'foo' AS y"); - expect(rs.columns).toStrictEqual(["x", "y"]); - expect(rs.columnTypes).toStrictEqual(["", ""]); - expect(rs.rows.length).toStrictEqual(1); - expect(Array.from(rs.rows[0])).toStrictEqual([42, "foo"]); - })); - - (hasHrana2 ? test : test.skip)("rowsAffected with WITH INSERT", withClient(async (c) => { - await c.batch([ - "DROP TABLE IF EXISTS t", - "CREATE TABLE t (a)", - "INSERT INTO t VALUES (1), (2), (3)", - ], "write"); - - const rs = await c.execute(` + test( + "query a single value", + withClient(async (c) => { + const rs = await c.execute("SELECT 42"); + expect(rs.columns.length).toStrictEqual(1); + expect(rs.columnTypes.length).toStrictEqual(1); + expect(rs.rows.length).toStrictEqual(1); + expect(rs.rows[0].length).toStrictEqual(1); + expect(rs.rows[0][0]).toStrictEqual(42); + }), + ); + + test( + "query a single row", + withClient(async (c) => { + const rs = await c.execute("SELECT 1 AS one, 'two' AS two, 0.5 AS three"); + expect(rs.columns).toStrictEqual(["one", "two", "three"]); + expect(rs.columnTypes).toStrictEqual(["", "", ""]); + expect(rs.rows.length).toStrictEqual(1); + + const r = rs.rows[0]; + expect(r.length).toStrictEqual(3); + expect(Array.from(r)).toStrictEqual([1, "two", 0.5]); + expect(Object.entries(r)).toStrictEqual([ + ["one", 1], + ["two", "two"], + ["three", 0.5], + ]); + }), + ); + + test( + "query multiple rows", + withClient(async (c) => { + const rs = await c.execute("VALUES (1, 'one'), (2, 'two'), (3, 'three')"); + expect(rs.columns.length).toStrictEqual(2); + expect(rs.columnTypes.length).toStrictEqual(2); + expect(rs.rows.length).toStrictEqual(3); + + expect(Array.from(rs.rows[0])).toStrictEqual([1, "one"]); + expect(Array.from(rs.rows[1])).toStrictEqual([2, "two"]); + expect(Array.from(rs.rows[2])).toStrictEqual([3, "three"]); + }), + ); + + test( + "statement that produces error", + withClient(async (c) => { + await expect(c.execute("SELECT foobar")).rejects.toBeLibsqlError(); + }), + ); + + test( + "rowsAffected with INSERT", + withClient(async (c) => { + await c.batch(["DROP TABLE IF EXISTS t", "CREATE TABLE t (a)"], "write"); + const rs = await c.execute("INSERT INTO t VALUES (1), (2)"); + expect(rs.rowsAffected).toStrictEqual(2); + }), + ); + + test( + "rowsAffected with DELETE", + withClient(async (c) => { + await c.batch( + [ + "DROP TABLE IF EXISTS t", + "CREATE TABLE t (a)", + "INSERT INTO t VALUES (1), (2), (3), (4), (5)", + ], + "write", + ); + const rs = await c.execute("DELETE FROM t WHERE a >= 3"); + expect(rs.rowsAffected).toStrictEqual(3); + }), + ); + + test( + "lastInsertRowid with INSERT", + withClient(async (c) => { + await c.batch( + [ + "DROP TABLE IF EXISTS t", + "CREATE TABLE t (a)", + "INSERT INTO t VALUES ('one'), ('two')", + ], + "write", + ); + const insertRs = await c.execute("INSERT INTO t VALUES ('three')"); + expect(insertRs.lastInsertRowid).not.toBeUndefined(); + const selectRs = await c.execute({ + sql: "SELECT a FROM t WHERE ROWID = ?", + args: [insertRs.lastInsertRowid!], + }); + expect(Array.from(selectRs.rows[0])).toStrictEqual(["three"]); + }), + ); + + test( + "rows from INSERT RETURNING", + withClient(async (c) => { + await c.batch(["DROP TABLE IF EXISTS t", "CREATE TABLE t (a)"], "write"); + + const rs = await c.execute( + "INSERT INTO t VALUES (1) RETURNING 42 AS x, 'foo' AS y", + ); + expect(rs.columns).toStrictEqual(["x", "y"]); + expect(rs.columnTypes).toStrictEqual(["", ""]); + expect(rs.rows.length).toStrictEqual(1); + expect(Array.from(rs.rows[0])).toStrictEqual([42, "foo"]); + }), + ); + + (hasHrana2 ? test : test.skip)( + "rowsAffected with WITH INSERT", + withClient(async (c) => { + await c.batch( + [ + "DROP TABLE IF EXISTS t", + "CREATE TABLE t (a)", + "INSERT INTO t VALUES (1), (2), (3)", + ], + "write", + ); + + const rs = await c.execute(` WITH x(a) AS (SELECT 2*a FROM t) INSERT INTO t SELECT a+1 FROM x `); - expect(rs.rowsAffected).toStrictEqual(3); - })); - - test("query a single value using an in memory database", withInMemoryClient(async (c) => { - await c.batch([ - "DROP TABLE IF EXISTS t", - "CREATE TABLE t (a)", - "INSERT INTO t VALUES ('one'), ('two')", - ], "write"); - const insertRs = await c.execute("INSERT INTO t VALUES ('three')"); - expect(insertRs.lastInsertRowid).not.toBeUndefined(); - const selectRs = await c.execute({ - sql: "SELECT a FROM t WHERE ROWID = ?", - args: [insertRs.lastInsertRowid!], - }); - expect(Array.from(selectRs.rows[0])).toStrictEqual(["three"]); - })); + expect(rs.rowsAffected).toStrictEqual(3); + }), + ); + + test( + "query a single value using an in memory database", + withInMemoryClient(async (c) => { + await c.batch( + [ + "DROP TABLE IF EXISTS t", + "CREATE TABLE t (a)", + "INSERT INTO t VALUES ('one'), ('two')", + ], + "write", + ); + const insertRs = await c.execute("INSERT INTO t VALUES ('three')"); + expect(insertRs.lastInsertRowid).not.toBeUndefined(); + const selectRs = await c.execute({ + sql: "SELECT a FROM t WHERE ROWID = ?", + args: [insertRs.lastInsertRowid!], + }); + expect(Array.from(selectRs.rows[0])).toStrictEqual(["three"]); + }), + ); }); describe("values", () => { - function testRoundtrip( - name: string, - passed: libsql.InValue, - expected: libsql.Value, - intMode?: libsql.IntMode, - ): void { - test(name, withClient(async (c) => { - const rs = await c.execute({sql: "SELECT ?", args: [passed]}); - expect(rs.rows[0][0]).toStrictEqual(expected); - }, {intMode})); - } - - function testRoundtripError( - name: string, - passed: libsql.InValue, - expectedError: unknown, - intMode?: libsql.IntMode, - ): void { - test(name, withClient(async (c) => { - await expect(c.execute({ - sql: "SELECT ?", - args: [passed], - })).rejects.toBeInstanceOf(expectedError); - }, {intMode})); + function testRoundtrip( + name: string, + passed: libsql.InValue, + expected: libsql.Value, + intMode?: libsql.IntMode, + ): void { + test( + name, + withClient( + async (c) => { + const rs = await c.execute({ sql: "SELECT ?", args: [passed] }); + expect(rs.rows[0][0]).toStrictEqual(expected); + }, + { intMode }, + ), + ); + } + + function testRoundtripError( + name: string, + passed: libsql.InValue, + expectedError: unknown, + intMode?: libsql.IntMode, + ): void { + test( + name, + withClient( + async (c) => { + await expect( + c.execute({ + sql: "SELECT ?", + args: [passed], + }), + ).rejects.toBeInstanceOf(expectedError); + }, + { intMode }, + ), + ); + } + + testRoundtrip("string", "boomerang", "boomerang"); + testRoundtrip("string with weird characters", "a\n\r\t ", "a\n\r\t "); + testRoundtrip( + "string with unicode", + "žluťoučký kůň úpěl ďábelské ódy", + "žluťoučký kůň úpěl ďábelské ódy", + ); + + describe("number", () => { + const intModes: Array = ["number", "bigint", "string"]; + for (const intMode of intModes) { + testRoundtrip("zero", 0, 0, intMode); + testRoundtrip("integer", -2023, -2023, intMode); + testRoundtrip("float", 12.345, 12.345, intMode); + testRoundtrip("large positive float", 1e18, 1e18, intMode); + testRoundtrip("large negative float", -1e18, -1e18, intMode); + testRoundtrip("MAX_VALUE", Number.MAX_VALUE, Number.MAX_VALUE, intMode); + testRoundtrip( + "-MAX_VALUE", + -Number.MAX_VALUE, + -Number.MAX_VALUE, + intMode, + ); + testRoundtrip("MIN_VALUE", Number.MIN_VALUE, Number.MIN_VALUE, intMode); } - - testRoundtrip("string", "boomerang", "boomerang"); - testRoundtrip("string with weird characters", "a\n\r\t ", "a\n\r\t "); - testRoundtrip("string with unicode", - "žluťoučký kůň úpěl ďábelské ódy", "žluťoučký kůň úpěl ďábelské ódy"); - - describe("number", () => { - const intModes: Array = ["number", "bigint", "string"]; - for (const intMode of intModes) { - testRoundtrip("zero", 0, 0, intMode); - testRoundtrip("integer", -2023, -2023, intMode); - testRoundtrip("float", 12.345, 12.345, intMode); - testRoundtrip("large positive float", 1e18, 1e18, intMode); - testRoundtrip("large negative float", -1e18, -1e18, intMode); - testRoundtrip("MAX_VALUE", Number.MAX_VALUE, Number.MAX_VALUE, intMode); - testRoundtrip("-MAX_VALUE", -Number.MAX_VALUE, -Number.MAX_VALUE, intMode); - testRoundtrip("MIN_VALUE", Number.MIN_VALUE, Number.MIN_VALUE, intMode); - } + }); + + describe("bigint", () => { + describe("'number' int mode", () => { + testRoundtrip("zero integer", 0n, 0, "number"); + testRoundtrip("small integer", -42n, -42, "number"); + testRoundtrip( + "largest safe integer", + 9007199254740991n, + 9007199254740991, + "number", + ); + testRoundtripError( + "smallest unsafe integer", + 9007199254740992n, + RangeError, + "number", + ); + testRoundtripError( + "large unsafe integer", + -1152921504594532842n, + RangeError, + "number", + ); }); - describe("bigint", () => { - describe("'number' int mode", () => { - testRoundtrip("zero integer", 0n, 0, "number"); - testRoundtrip("small integer", -42n, -42, "number"); - testRoundtrip("largest safe integer", 9007199254740991n, 9007199254740991, "number"); - testRoundtripError("smallest unsafe integer", 9007199254740992n, RangeError, "number"); - testRoundtripError("large unsafe integer", -1152921504594532842n, RangeError, "number"); - }); - - describe("'bigint' int mode", () => { - testRoundtrip("zero integer", 0n, 0n, "bigint"); - testRoundtrip("small integer", -42n, -42n, "bigint"); - testRoundtrip("large positive integer", 1152921504608088318n, 1152921504608088318n, "bigint"); - testRoundtrip("large negative integer", -1152921504594532842n, -1152921504594532842n, "bigint"); - testRoundtrip("largest positive integer", 9223372036854775807n, 9223372036854775807n, "bigint"); - testRoundtrip("largest negative integer", -9223372036854775808n, -9223372036854775808n, "bigint"); - }); - - describe("'string' int mode", () => { - testRoundtrip("zero integer", 0n, "0", "string"); - testRoundtrip("small integer", -42n, "-42", "string"); - testRoundtrip("large positive integer", 1152921504608088318n, "1152921504608088318", "string"); - testRoundtrip("large negative integer", -1152921504594532842n, "-1152921504594532842", "string"); - testRoundtrip("largest positive integer", 9223372036854775807n, "9223372036854775807", "string"); - testRoundtrip("largest negative integer", -9223372036854775808n, "-9223372036854775808", "string"); - }); + describe("'bigint' int mode", () => { + testRoundtrip("zero integer", 0n, 0n, "bigint"); + testRoundtrip("small integer", -42n, -42n, "bigint"); + testRoundtrip( + "large positive integer", + 1152921504608088318n, + 1152921504608088318n, + "bigint", + ); + testRoundtrip( + "large negative integer", + -1152921504594532842n, + -1152921504594532842n, + "bigint", + ); + testRoundtrip( + "largest positive integer", + 9223372036854775807n, + 9223372036854775807n, + "bigint", + ); + testRoundtrip( + "largest negative integer", + -9223372036854775808n, + -9223372036854775808n, + "bigint", + ); }); - const buf = new ArrayBuffer(256); - const array = new Uint8Array(buf); - for (let i = 0; i < 256; ++i) { - array[i] = i ^ 0xab; - } - testRoundtrip("ArrayBuffer", buf, buf); - testRoundtrip("Uint8Array", array, buf); - - testRoundtrip("null", null, null); - testRoundtrip("true", true, 1n, "bigint"); - testRoundtrip("false", false, 0n, "bigint"); - testRoundtrip("true", true, 1, "number"); - testRoundtrip("false", false, 0, "number"); - testRoundtrip("true", true, "1", "string"); - testRoundtrip("false", false, "0", "string"); - testRoundtrip("true", true, 1); - testRoundtrip("false", false, 0); - - testRoundtrip("Date", new Date("2023-01-02T12:34:56Z"), 1672662896000, "bigint"); - - // @ts-expect-error - testRoundtripError("undefined produces error", undefined, TypeError); - testRoundtripError("NaN produces error", NaN, RangeError); - testRoundtripError("Infinity produces error", Infinity, RangeError); - testRoundtripError("large bigint produces error", -1267650600228229401496703205376n, RangeError); - - test("max 64-bit bigint", withClient(async (c) => { - const rs = await c.execute({sql: "SELECT ?||''", args: [9223372036854775807n]}); - expect(rs.rows[0][0]).toStrictEqual("9223372036854775807"); - })); - - test("min 64-bit bigint", withClient(async (c) => { - const rs = await c.execute({sql: "SELECT ?||''", args: [-9223372036854775808n]}); - expect(rs.rows[0][0]).toStrictEqual("-9223372036854775808"); - })); + describe("'string' int mode", () => { + testRoundtrip("zero integer", 0n, "0", "string"); + testRoundtrip("small integer", -42n, "-42", "string"); + testRoundtrip( + "large positive integer", + 1152921504608088318n, + "1152921504608088318", + "string", + ); + testRoundtrip( + "large negative integer", + -1152921504594532842n, + "-1152921504594532842", + "string", + ); + testRoundtrip( + "largest positive integer", + 9223372036854775807n, + "9223372036854775807", + "string", + ); + testRoundtrip( + "largest negative integer", + -9223372036854775808n, + "-9223372036854775808", + "string", + ); + }); + }); + + const buf = new ArrayBuffer(256); + const array = new Uint8Array(buf); + for (let i = 0; i < 256; ++i) { + array[i] = i ^ 0xab; + } + testRoundtrip("ArrayBuffer", buf, buf); + testRoundtrip("Uint8Array", array, buf); + + testRoundtrip("null", null, null); + testRoundtrip("true", true, 1n, "bigint"); + testRoundtrip("false", false, 0n, "bigint"); + testRoundtrip("true", true, 1, "number"); + testRoundtrip("false", false, 0, "number"); + testRoundtrip("true", true, "1", "string"); + testRoundtrip("false", false, "0", "string"); + testRoundtrip("true", true, 1); + testRoundtrip("false", false, 0); + + testRoundtrip( + "Date", + new Date("2023-01-02T12:34:56Z"), + 1672662896000, + "bigint", + ); + + // @ts-expect-error + testRoundtripError("undefined produces error", undefined, TypeError); + testRoundtripError("NaN produces error", NaN, RangeError); + testRoundtripError("Infinity produces error", Infinity, RangeError); + testRoundtripError( + "large bigint produces error", + -1267650600228229401496703205376n, + RangeError, + ); + + test( + "max 64-bit bigint", + withClient(async (c) => { + const rs = await c.execute({ + sql: "SELECT ?||''", + args: [9223372036854775807n], + }); + expect(rs.rows[0][0]).toStrictEqual("9223372036854775807"); + }), + ); + + test( + "min 64-bit bigint", + withClient(async (c) => { + const rs = await c.execute({ + sql: "SELECT ?||''", + args: [-9223372036854775808n], + }); + expect(rs.rows[0][0]).toStrictEqual("-9223372036854775808"); + }), + ); }); describe("ResultSet.toJSON()", () => { - test("simple result set", withClient(async (c) => { - const rs = await c.execute("SELECT 1 AS a"); - const json = rs.toJSON(); - expect(json["lastInsertRowid"] === null || json["lastInsertRowid"] === "0").toBe(true); - expect(json["columns"]).toStrictEqual(["a"]); - expect(json["columnTypes"]).toStrictEqual([""]); - expect(json["rows"]).toStrictEqual([[1]]); - expect(json["rowsAffected"]).toStrictEqual(0); - - const str = JSON.stringify(rs); - expect( - str === '{"columns":["a"],"columnTypes":[""],"rows":[[1]],"rowsAffected":0,"lastInsertRowid":null}' || - str === '{"columns":["a"],"columnTypes":[""],"rows":[[1]],"rowsAffected":0,"lastInsertRowid":"0"}' - ).toBe(true); - })); - - test("lastInsertRowid", withClient(async (c) => { - await c.execute("DROP TABLE IF EXISTS t"); - await c.execute("CREATE TABLE t (id INTEGER PRIMARY KEY NOT NULL)"); - const rs = await c.execute("INSERT INTO t VALUES (12345)"); - expect(rs.toJSON()).toStrictEqual({ - "columns": [], - "columnTypes": [], - "rows": [], - "rowsAffected": 1, - "lastInsertRowid": "12345", - }); - })); - - test("computed values", withClient(async (c) => { - const rs = await c.execute( - "SELECT 42 AS integer, 0.5 AS float, NULL AS \"null\", 'foo' AS text, X'626172' AS blob", - ); - const json = rs.toJSON(); - expect(json["columns"]).toStrictEqual(["integer", "float", "null", "text", "blob"]); - expect(json["columnTypes"]).toStrictEqual(["", "", "", "", ""]); - expect(json["rows"]).toStrictEqual([[42, 0.5, null, "foo", "YmFy"]]); - })); - - (hasHrana2 ? test : test.skip)("row values", withClient(async (c) => { - await c.execute("DROP TABLE IF EXISTS t"); - await c.execute("CREATE TABLE t (i INTEGER, f FLOAT, t TEXT, b BLOB)"); - await c.execute("INSERT INTO t VALUES (42, 0.5, 'foo', X'626172')"); - const rs = await c.execute( - "SELECT i, f, t, b FROM t LIMIT 1", - ); - const json = rs.toJSON(); - expect(json["columns"]).toStrictEqual(["i", "f", "t", "b"]); - expect(json["columnTypes"]).toStrictEqual(["INTEGER", "FLOAT", "TEXT", "BLOB"]); - expect(json["rows"]).toStrictEqual([[42, 0.5, "foo", "YmFy"]]); - })); - - test("bigint row value", withClient(async (c) => { + test( + "simple result set", + withClient(async (c) => { + const rs = await c.execute("SELECT 1 AS a"); + const json = rs.toJSON(); + expect( + json["lastInsertRowid"] === null || json["lastInsertRowid"] === "0", + ).toBe(true); + expect(json["columns"]).toStrictEqual(["a"]); + expect(json["columnTypes"]).toStrictEqual([""]); + expect(json["rows"]).toStrictEqual([[1]]); + expect(json["rowsAffected"]).toStrictEqual(0); + + const str = JSON.stringify(rs); + expect( + str === + '{"columns":["a"],"columnTypes":[""],"rows":[[1]],"rowsAffected":0,"lastInsertRowid":null}' || + str === + '{"columns":["a"],"columnTypes":[""],"rows":[[1]],"rowsAffected":0,"lastInsertRowid":"0"}', + ).toBe(true); + }), + ); + + test( + "lastInsertRowid", + withClient(async (c) => { + await c.execute("DROP TABLE IF EXISTS t"); + await c.execute("CREATE TABLE t (id INTEGER PRIMARY KEY NOT NULL)"); + const rs = await c.execute("INSERT INTO t VALUES (12345)"); + expect(rs.toJSON()).toStrictEqual({ + columns: [], + columnTypes: [], + rows: [], + rowsAffected: 1, + lastInsertRowid: "12345", + }); + }), + ); + + test( + "computed values", + withClient(async (c) => { + const rs = await c.execute( + "SELECT 42 AS integer, 0.5 AS float, NULL AS \"null\", 'foo' AS text, X'626172' AS blob", + ); + const json = rs.toJSON(); + expect(json["columns"]).toStrictEqual([ + "integer", + "float", + "null", + "text", + "blob", + ]); + expect(json["columnTypes"]).toStrictEqual(["", "", "", "", ""]); + expect(json["rows"]).toStrictEqual([[42, 0.5, null, "foo", "YmFy"]]); + }), + ); + + (hasHrana2 ? test : test.skip)( + "row values", + withClient(async (c) => { + await c.execute("DROP TABLE IF EXISTS t"); + await c.execute("CREATE TABLE t (i INTEGER, f FLOAT, t TEXT, b BLOB)"); + await c.execute("INSERT INTO t VALUES (42, 0.5, 'foo', X'626172')"); + const rs = await c.execute("SELECT i, f, t, b FROM t LIMIT 1"); + const json = rs.toJSON(); + expect(json["columns"]).toStrictEqual(["i", "f", "t", "b"]); + expect(json["columnTypes"]).toStrictEqual([ + "INTEGER", + "FLOAT", + "TEXT", + "BLOB", + ]); + expect(json["rows"]).toStrictEqual([[42, 0.5, "foo", "YmFy"]]); + }), + ); + + test( + "bigint row value", + withClient( + async (c) => { const rs = await c.execute("SELECT 42"); const json = rs.toJSON(); expect(json["rows"]).toStrictEqual([["42"]]); - }, {intMode: "bigint"})); + }, + { intMode: "bigint" }, + ), + ); }); describe("arguments", () => { - test("? arguments", withClient(async (c) => { - const rs = await c.execute({ - sql: "SELECT ?, ?", - args: ["one", "two"], - }); - expect(Array.from(rs.rows[0])).toStrictEqual(["one", "two"]); - })); - - (!isFile ? test : test.skip)("?NNN arguments", withClient(async (c) => { + test( + "? arguments", + withClient(async (c) => { + const rs = await c.execute({ + sql: "SELECT ?, ?", + args: ["one", "two"], + }); + expect(Array.from(rs.rows[0])).toStrictEqual(["one", "two"]); + }), + ); + + (!isFile ? test : test.skip)( + "?NNN arguments", + withClient(async (c) => { + const rs = await c.execute({ + sql: "SELECT ?2, ?3, ?1", + args: ["one", "two", "three"], + }); + expect(Array.from(rs.rows[0])).toStrictEqual(["two", "three", "one"]); + }), + ); + + (!isFile ? test : test.skip)( + "?NNN arguments with holes", + withClient(async (c) => { + const rs = await c.execute({ + sql: "SELECT ?3, ?1", + args: ["one", "two", "three"], + }); + expect(Array.from(rs.rows[0])).toStrictEqual(["three", "one"]); + }), + ); + + (!isFile ? test : test.skip)( + "?NNN and ? arguments", + withClient(async (c) => { + const rs = await c.execute({ + sql: "SELECT ?2, ?, ?3", + args: ["one", "two", "three"], + }); + expect(Array.from(rs.rows[0])).toStrictEqual(["two", "three", "three"]); + }), + ); + + for (const sign of [":", "@", "$"]) { + test( + `${sign}AAAA arguments`, + withClient(async (c) => { const rs = await c.execute({ - sql: "SELECT ?2, ?3, ?1", - args: ["one", "two", "three"], + sql: `SELECT ${sign}b, ${sign}a`, + args: { a: "one", [`${sign}b`]: "two" }, }); - expect(Array.from(rs.rows[0])).toStrictEqual(["two", "three", "one"]); - })); + expect(Array.from(rs.rows[0])).toStrictEqual(["two", "one"]); + }), + ); - (!isFile ? test : test.skip)("?NNN arguments with holes", withClient(async (c) => { + test( + `${sign}AAAA arguments used multiple times`, + withClient(async (c) => { const rs = await c.execute({ - sql: "SELECT ?3, ?1", - args: ["one", "two", "three"], + sql: `SELECT ${sign}b, ${sign}a, ${sign}b || ${sign}a`, + args: { a: "one", [`${sign}b`]: "two" }, }); - expect(Array.from(rs.rows[0])).toStrictEqual(["three", "one"]); - })); + expect(Array.from(rs.rows[0])).toStrictEqual(["two", "one", "twoone"]); + }), + ); - (!isFile ? test : test.skip)("?NNN and ? arguments", withClient(async (c) => { + test( + `${sign}AAAA arguments and ?NNN arguments`, + withClient(async (c) => { const rs = await c.execute({ - sql: "SELECT ?2, ?, ?3", - args: ["one", "two", "three"], + sql: `SELECT ${sign}b, ${sign}a, ?1`, + args: { a: "one", [`${sign}b`]: "two" }, }); - expect(Array.from(rs.rows[0])).toStrictEqual(["two", "three", "three"]); - })); - - for (const sign of [":", "@", "$"]) { - test(`${sign}AAAA arguments`, withClient(async (c) => { - const rs = await c.execute({ - sql: `SELECT ${sign}b, ${sign}a`, - args: {"a": "one", [`${sign}b`]: "two"}, - }); - expect(Array.from(rs.rows[0])).toStrictEqual(["two", "one"]); - })); - - test(`${sign}AAAA arguments used multiple times`, withClient(async (c) => { - const rs = await c.execute({ - sql: `SELECT ${sign}b, ${sign}a, ${sign}b || ${sign}a`, - args: {"a": "one", [`${sign}b`]: "two"}, - }); - expect(Array.from(rs.rows[0])).toStrictEqual(["two", "one", "twoone"]); - })); - - test(`${sign}AAAA arguments and ?NNN arguments`, withClient(async (c) => { - const rs = await c.execute({ - sql: `SELECT ${sign}b, ${sign}a, ?1`, - args: {"a": "one", [`${sign}b`]: "two"}, - }); - expect(Array.from(rs.rows[0])).toStrictEqual(["two", "one", "two"]); - })); - } + expect(Array.from(rs.rows[0])).toStrictEqual(["two", "one", "two"]); + }), + ); + } }); describe("batch()", () => { - test("multiple queries", withClient(async (c) => { - const rss = await c.batch([ - "SELECT 1+1", - "SELECT 1 AS one, 2 AS two", - {sql: "SELECT ?", args: ["boomerang"]}, - {sql: "VALUES (?), (?)", args: ["big", "ben"]}, - ], "read"); - - expect(rss.length).toStrictEqual(4); - const [rs0, rs1, rs2, rs3] = rss; - - expect(rs0.rows.length).toStrictEqual(1); - expect(Array.from(rs0.rows[0])).toStrictEqual([2]); - - expect(rs1.rows.length).toStrictEqual(1); - expect(Array.from(rs1.rows[0])).toStrictEqual([1, 2]); - - expect(rs2.rows.length).toStrictEqual(1); - expect(Array.from(rs2.rows[0])).toStrictEqual(["boomerang"]); - - expect(rs3.rows.length).toStrictEqual(2); - expect(Array.from(rs3.rows[0])).toStrictEqual(["big"]); - expect(Array.from(rs3.rows[1])).toStrictEqual(["ben"]); - })); - - test("statements are executed sequentially", withClient(async (c) => { - const rss = await c.batch([ - /* 0 */ "DROP TABLE IF EXISTS t", - /* 1 */ "CREATE TABLE t (a, b)", - /* 2 */ "INSERT INTO t VALUES (1, 'one')", - /* 3 */ "SELECT * FROM t ORDER BY a", - /* 4 */ "INSERT INTO t VALUES (2, 'two')", - /* 5 */ "SELECT * FROM t ORDER BY a", - /* 6 */ "DROP TABLE t", - ], "write"); - - expect(rss.length).toStrictEqual(7); - expect(rss[3].rows).toEqual([ - {a: 1, b: "one"}, - ]); - expect(rss[5].rows).toEqual([ - {a: 1, b: "one"}, - {a: 2, b: "two"}, - ]); - })); - - test("statements are executed in a transaction", withClient(async (c) => { - await c.batch([ - "DROP TABLE IF EXISTS t1", - "DROP TABLE IF EXISTS t2", - "CREATE TABLE t1 (a)", - "CREATE TABLE t2 (a)", - ], "write"); - - const n = 100; - const promises = []; - for (let i = 0; i < n; ++i) { - const ii = i; - promises.push((async () => { - const rss = await c.batch([ - {sql: "INSERT INTO t1 VALUES (?)", args: [ii]}, - {sql: "INSERT INTO t2 VALUES (?)", args: [ii * 10]}, - "SELECT SUM(a) FROM t1", - "SELECT SUM(a) FROM t2", - ], "write"); - - const sum1 = rss[2].rows[0][0] as number; - const sum2 = rss[3].rows[0][0] as number; - expect(sum2).toStrictEqual(sum1 * 10); - })()); - } - await Promise.all(promises); - - const rs1 = await c.execute("SELECT SUM(a) FROM t1"); - expect(rs1.rows[0][0]).toStrictEqual(n*(n-1)/2); - const rs2 = await c.execute("SELECT SUM(a) FROM t2"); - expect(rs2.rows[0][0]).toStrictEqual(n*(n-1)/2*10); - }), 10000); - - test("error in batch", withClient(async (c) => { - await expect(c.batch([ - "SELECT 1+1", - "SELECT foobar", - ], "read")).rejects.toBeLibsqlError(); - })); - - test("error in batch rolls back transaction", withClient(async (c) => { - await c.execute("DROP TABLE IF EXISTS t"); - await c.execute("CREATE TABLE t (a)"); - await c.execute("INSERT INTO t VALUES ('one')"); - await expect(c.batch([ + test( + "multiple queries", + withClient(async (c) => { + const rss = await c.batch( + [ + "SELECT 1+1", + "SELECT 1 AS one, 2 AS two", + { sql: "SELECT ?", args: ["boomerang"] }, + { sql: "VALUES (?), (?)", args: ["big", "ben"] }, + ], + "read", + ); + + expect(rss.length).toStrictEqual(4); + const [rs0, rs1, rs2, rs3] = rss; + + expect(rs0.rows.length).toStrictEqual(1); + expect(Array.from(rs0.rows[0])).toStrictEqual([2]); + + expect(rs1.rows.length).toStrictEqual(1); + expect(Array.from(rs1.rows[0])).toStrictEqual([1, 2]); + + expect(rs2.rows.length).toStrictEqual(1); + expect(Array.from(rs2.rows[0])).toStrictEqual(["boomerang"]); + + expect(rs3.rows.length).toStrictEqual(2); + expect(Array.from(rs3.rows[0])).toStrictEqual(["big"]); + expect(Array.from(rs3.rows[1])).toStrictEqual(["ben"]); + }), + ); + + test( + "statements are executed sequentially", + withClient(async (c) => { + const rss = await c.batch( + [ + /* 0 */ "DROP TABLE IF EXISTS t", + /* 1 */ "CREATE TABLE t (a, b)", + /* 2 */ "INSERT INTO t VALUES (1, 'one')", + /* 3 */ "SELECT * FROM t ORDER BY a", + /* 4 */ "INSERT INTO t VALUES (2, 'two')", + /* 5 */ "SELECT * FROM t ORDER BY a", + /* 6 */ "DROP TABLE t", + ], + "write", + ); + + expect(rss.length).toStrictEqual(7); + expect(rss[3].rows).toEqual([{ a: 1, b: "one" }]); + expect(rss[5].rows).toEqual([ + { a: 1, b: "one" }, + { a: 2, b: "two" }, + ]); + }), + ); + + test( + "statements are executed in a transaction", + withClient(async (c) => { + await c.batch( + [ + "DROP TABLE IF EXISTS t1", + "DROP TABLE IF EXISTS t2", + "CREATE TABLE t1 (a)", + "CREATE TABLE t2 (a)", + ], + "write", + ); + + const n = 100; + const promises = []; + for (let i = 0; i < n; ++i) { + const ii = i; + promises.push( + (async () => { + const rss = await c.batch( + [ + { sql: "INSERT INTO t1 VALUES (?)", args: [ii] }, + { sql: "INSERT INTO t2 VALUES (?)", args: [ii * 10] }, + "SELECT SUM(a) FROM t1", + "SELECT SUM(a) FROM t2", + ], + "write", + ); + + const sum1 = rss[2].rows[0][0] as number; + const sum2 = rss[3].rows[0][0] as number; + expect(sum2).toStrictEqual(sum1 * 10); + })(), + ); + } + await Promise.all(promises); + + const rs1 = await c.execute("SELECT SUM(a) FROM t1"); + expect(rs1.rows[0][0]).toStrictEqual((n * (n - 1)) / 2); + const rs2 = await c.execute("SELECT SUM(a) FROM t2"); + expect(rs2.rows[0][0]).toStrictEqual(((n * (n - 1)) / 2) * 10); + }), + 10000, + ); + + test( + "error in batch", + withClient(async (c) => { + await expect( + c.batch(["SELECT 1+1", "SELECT foobar"], "read"), + ).rejects.toBeLibsqlError(); + }), + ); + + test( + "error in batch rolls back transaction", + withClient(async (c) => { + await c.execute("DROP TABLE IF EXISTS t"); + await c.execute("CREATE TABLE t (a)"); + await c.execute("INSERT INTO t VALUES ('one')"); + await expect( + c.batch( + [ "INSERT INTO t VALUES ('two')", "SELECT foobar", "INSERT INTO t VALUES ('three')", - ], "write")).rejects.toBeLibsqlError(); - - const rs = await c.execute("SELECT COUNT(*) FROM t"); - expect(rs.rows[0][0]).toStrictEqual(1); - })); - - test("batch with a lot of different statements", withClient(async (c) => { - const stmts = []; - for (let i = 0; i < 1000; ++i) { - stmts.push(`SELECT ${i}`); + ], + "write", + ), + ).rejects.toBeLibsqlError(); + + const rs = await c.execute("SELECT COUNT(*) FROM t"); + expect(rs.rows[0][0]).toStrictEqual(1); + }), + ); + + test( + "batch with a lot of different statements", + withClient(async (c) => { + const stmts = []; + for (let i = 0; i < 1000; ++i) { + stmts.push(`SELECT ${i}`); + } + const rss = await c.batch(stmts, "read"); + for (let i = 0; i < stmts.length; ++i) { + expect(rss[i].rows[0][0]).toStrictEqual(i); + } + }), + ); + + test( + "batch with a lot of the same statements", + withClient(async (c) => { + const n = 20; + const m = 200; + + const stmts = []; + for (let i = 0; i < n; ++i) { + for (let j = 0; j < m; ++j) { + stmts.push({ sql: `SELECT ?, ${j}`, args: [i] }); } - const rss = await c.batch(stmts, "read"); - for (let i = 0; i < stmts.length; ++i) { - expect(rss[i].rows[0][0]).toStrictEqual(i); + } + + const rss = await c.batch(stmts, "read"); + for (let i = 0; i < n; ++i) { + for (let j = 0; j < m; ++j) { + const rs = rss[i * m + j]; + expect(rs.rows[0][0]).toStrictEqual(i); + expect(rs.rows[0][1]).toStrictEqual(j); } - })); - - test("batch with a lot of the same statements", withClient(async (c) => { - const n = 20; - const m = 200; - - const stmts = []; - for (let i = 0; i < n; ++i) { - for (let j = 0; j < m; ++j) { - stmts.push({sql: `SELECT ?, ${j}`, args: [i]}); - } - } - - const rss = await c.batch(stmts, "read"); - for (let i = 0; i < n; ++i) { - for (let j = 0; j < m; ++j) { - const rs = rss[i*m + j]; - expect(rs.rows[0][0]).toStrictEqual(i); - expect(rs.rows[0][1]).toStrictEqual(j); - } - } - })); - - test("deferred batch", withClient(async (c) => { - const rss = await c.batch([ - "SELECT 1+1", - "DROP TABLE IF EXISTS t", - "CREATE TABLE t (a)", - "INSERT INTO t VALUES (21) RETURNING 2*a", - ], "deferred"); - - expect(rss.length).toStrictEqual(4); - const [rs0, _rs1, _rs2, rs3] = rss; - - expect(rs0.rows.length).toStrictEqual(1); - expect(Array.from(rs0.rows[0])).toStrictEqual([2]); - - expect(rs3.rows.length).toStrictEqual(1); - expect(Array.from(rs3.rows[0])).toStrictEqual([42]); - })); - - (hasHrana3 ? test : test.skip)("ROLLBACK statement stops execution of batch", withClient(async (c) => { - await c.execute("DROP TABLE IF EXISTS t"); - await c.execute("CREATE TABLE t (a)"); - - await expect(c.batch([ + } + }), + ); + + test( + "deferred batch", + withClient(async (c) => { + const rss = await c.batch( + [ + "SELECT 1+1", + "DROP TABLE IF EXISTS t", + "CREATE TABLE t (a)", + "INSERT INTO t VALUES (21) RETURNING 2*a", + ], + "deferred", + ); + + expect(rss.length).toStrictEqual(4); + const [rs0, _rs1, _rs2, rs3] = rss; + + expect(rs0.rows.length).toStrictEqual(1); + expect(Array.from(rs0.rows[0])).toStrictEqual([2]); + + expect(rs3.rows.length).toStrictEqual(1); + expect(Array.from(rs3.rows[0])).toStrictEqual([42]); + }), + ); + + (hasHrana3 ? test : test.skip)( + "ROLLBACK statement stops execution of batch", + withClient(async (c) => { + await c.execute("DROP TABLE IF EXISTS t"); + await c.execute("CREATE TABLE t (a)"); + + await expect( + c.batch( + [ "INSERT INTO t VALUES (1), (2), (3)", "ROLLBACK", "INSERT INTO t VALUES (4), (5)", - ], "write")).rejects.toBeLibsqlError("TRANSACTION_CLOSED"); - - const rs = await c.execute("SELECT COUNT(*) FROM t"); - expect(rs.rows[0][0]).toStrictEqual(0); - })); + ], + "write", + ), + ).rejects.toBeLibsqlError("TRANSACTION_CLOSED"); + + const rs = await c.execute("SELECT COUNT(*) FROM t"); + expect(rs.rows[0][0]).toStrictEqual(0); + }), + ); }); describe("transaction()", () => { - test("query multiple rows", withClient(async (c) => { - const txn = await c.transaction("read"); - - const rs = await txn.execute("VALUES (1, 'one'), (2, 'two'), (3, 'three')"); - expect(rs.columns.length).toStrictEqual(2); - expect(rs.columnTypes.length).toStrictEqual(2); - expect(rs.rows.length).toStrictEqual(3); - - expect(Array.from(rs.rows[0])).toStrictEqual([1, "one"]); - expect(Array.from(rs.rows[1])).toStrictEqual([2, "two"]); - expect(Array.from(rs.rows[2])).toStrictEqual([3, "three"]); - - txn.close(); - })); - - test("commit()", withClient(async (c) => { - await c.batch([ - "DROP TABLE IF EXISTS t", - "CREATE TABLE t (a)", - ], "write"); - + test( + "query multiple rows", + withClient(async (c) => { + const txn = await c.transaction("read"); + + const rs = await txn.execute( + "VALUES (1, 'one'), (2, 'two'), (3, 'three')", + ); + expect(rs.columns.length).toStrictEqual(2); + expect(rs.columnTypes.length).toStrictEqual(2); + expect(rs.rows.length).toStrictEqual(3); + + expect(Array.from(rs.rows[0])).toStrictEqual([1, "one"]); + expect(Array.from(rs.rows[1])).toStrictEqual([2, "two"]); + expect(Array.from(rs.rows[2])).toStrictEqual([3, "three"]); + + txn.close(); + }), + ); + + test( + "commit()", + withClient(async (c) => { + await c.batch(["DROP TABLE IF EXISTS t", "CREATE TABLE t (a)"], "write"); + + const txn = await c.transaction("write"); + await txn.execute("INSERT INTO t VALUES ('one')"); + await txn.execute("INSERT INTO t VALUES ('two')"); + expect(txn.closed).toStrictEqual(false); + await txn.commit(); + expect(txn.closed).toStrictEqual(true); + + const rs = await c.execute("SELECT COUNT(*) FROM t"); + expect(rs.rows[0][0]).toStrictEqual(2); + await expect(txn.execute("SELECT 1")).rejects.toBeLibsqlError( + "TRANSACTION_CLOSED", + ); + }), + ); + + test( + "rollback()", + withClient(async (c) => { + await c.batch(["DROP TABLE IF EXISTS t", "CREATE TABLE t (a)"], "write"); + + const txn = await c.transaction("write"); + await txn.execute("INSERT INTO t VALUES ('one')"); + await txn.execute("INSERT INTO t VALUES ('two')"); + expect(txn.closed).toStrictEqual(false); + await txn.rollback(); + expect(txn.closed).toStrictEqual(true); + + const rs = await c.execute("SELECT COUNT(*) FROM t"); + expect(rs.rows[0][0]).toStrictEqual(0); + await expect(txn.execute("SELECT 1")).rejects.toBeLibsqlError( + "TRANSACTION_CLOSED", + ); + }), + ); + + test( + "close()", + withClient(async (c) => { + await c.batch(["DROP TABLE IF EXISTS t", "CREATE TABLE t (a)"], "write"); + + const txn = await c.transaction("write"); + await txn.execute("INSERT INTO t VALUES ('one')"); + expect(txn.closed).toStrictEqual(false); + txn.close(); + expect(txn.closed).toStrictEqual(true); + + const rs = await c.execute("SELECT COUNT(*) FROM t"); + expect(rs.rows[0][0]).toStrictEqual(0); + await expect(txn.execute("SELECT 1")).rejects.toBeLibsqlError( + "TRANSACTION_CLOSED", + ); + }), + ); + + test( + "error does not rollback", + withClient(async (c) => { + await c.batch(["DROP TABLE IF EXISTS t", "CREATE TABLE t (a)"], "write"); + + const txn = await c.transaction("write"); + await expect(txn.execute("SELECT foo")).rejects.toBeLibsqlError(); + await txn.execute("INSERT INTO t VALUES ('one')"); + await expect(txn.execute("SELECT bar")).rejects.toBeLibsqlError(); + await txn.commit(); + + const rs = await c.execute("SELECT COUNT(*) FROM t"); + expect(rs.rows[0][0]).toStrictEqual(1); + }), + ); + + (hasHrana3 ? test : test.skip)( + "ROLLBACK statement stops execution of transaction", + withClient(async (c) => { + await c.execute("DROP TABLE IF EXISTS t"); + await c.execute("CREATE TABLE t (a)"); + + const txn = await c.transaction("write"); + const prom1 = txn.execute("INSERT INTO t VALUES (1), (2), (3)"); + const prom2 = txn.execute("ROLLBACK"); + const prom3 = txn.execute("INSERT INTO t VALUES (4), (5)"); + + await prom1; + await prom2; + await expect(prom3).rejects.toBeLibsqlError("TRANSACTION_CLOSED"); + await expect(txn.commit()).rejects.toBeLibsqlError(); + txn.close(); + + const rs = await c.execute("SELECT COUNT(*) FROM t"); + expect(rs.rows[0][0]).toStrictEqual(0); + }), + ); + + (hasHrana3 ? test : test.skip)( + "OR ROLLBACK statement stops execution of transaction", + withClient(async (c) => { + await c.execute("DROP TABLE IF EXISTS t"); + await c.execute("CREATE TABLE t (a UNIQUE)"); + + const txn = await c.transaction("write"); + const prom1 = txn.execute("INSERT INTO t VALUES (1), (2), (3)"); + const prom2 = txn.execute("INSERT OR ROLLBACK INTO t VALUES (1)"); + const prom3 = txn.execute("INSERT INTO t VALUES (4), (5)"); + + await prom1; + await expect(prom2).rejects.toBeLibsqlError(); + await expect(prom3).rejects.toBeLibsqlError("TRANSACTION_CLOSED"); + await expect(txn.commit()).rejects.toBeLibsqlError(); + txn.close(); + + const rs = await c.execute("SELECT COUNT(*) FROM t"); + expect(rs.rows[0][0]).toStrictEqual(0); + }), + ); + + (hasHrana3 ? test : test.skip)( + "OR ROLLBACK as the first statement stops execution of transaction", + withClient(async (c) => { + await c.execute("DROP TABLE IF EXISTS t"); + await c.execute("CREATE TABLE t (a UNIQUE)"); + await c.execute("INSERT INTO t VALUES (1), (2), (3)"); + + const txn = await c.transaction("write"); + const prom1 = txn.execute("INSERT OR ROLLBACK INTO t VALUES (1)"); + const prom2 = txn.execute("INSERT INTO t VALUES (4), (5)"); + + await expect(prom1).rejects.toBeLibsqlError(); + await expect(prom2).rejects.toBeLibsqlError("TRANSACTION_CLOSED"); + await expect(txn.commit()).rejects.toBeLibsqlError(); + txn.close(); + + const rs = await c.execute("SELECT COUNT(*) FROM t"); + expect(rs.rows[0][0]).toStrictEqual(3); + }), + ); + + test( + "commit empty", + withClient(async (c) => { + const txn = await c.transaction("read"); + await txn.commit(); + }), + ); + + test( + "rollback empty", + withClient(async (c) => { + const txn = await c.transaction("read"); + await txn.rollback(); + }), + ); + + describe("batch()", () => { + test( + "as the first operation on transaction", + withClient(async (c) => { const txn = await c.transaction("write"); - await txn.execute("INSERT INTO t VALUES ('one')"); - await txn.execute("INSERT INTO t VALUES ('two')"); - expect(txn.closed).toStrictEqual(false); - await txn.commit(); - expect(txn.closed).toStrictEqual(true); - - const rs = await c.execute("SELECT COUNT(*) FROM t"); - expect(rs.rows[0][0]).toStrictEqual(2); - await expect(txn.execute("SELECT 1")).rejects.toBeLibsqlError("TRANSACTION_CLOSED"); - })); - - test("rollback()", withClient(async (c) => { - await c.batch([ - "DROP TABLE IF EXISTS t", - "CREATE TABLE t (a)", - ], "write"); - const txn = await c.transaction("write"); - await txn.execute("INSERT INTO t VALUES ('one')"); - await txn.execute("INSERT INTO t VALUES ('two')"); - expect(txn.closed).toStrictEqual(false); - await txn.rollback(); - expect(txn.closed).toStrictEqual(true); - - const rs = await c.execute("SELECT COUNT(*) FROM t"); - expect(rs.rows[0][0]).toStrictEqual(0); - await expect(txn.execute("SELECT 1")).rejects.toBeLibsqlError("TRANSACTION_CLOSED"); - })); - - test("close()", withClient(async (c) => { - await c.batch([ - "DROP TABLE IF EXISTS t", - "CREATE TABLE t (a)", - ], "write"); + await txn.batch([ + "DROP TABLE IF EXISTS t", + "CREATE TABLE t (a)", + { sql: "INSERT INTO t VALUES (?)", args: [1] }, + { sql: "INSERT INTO t VALUES (?)", args: [2] }, + { sql: "INSERT INTO t VALUES (?)", args: [4] }, + ]); - const txn = await c.transaction("write"); - await txn.execute("INSERT INTO t VALUES ('one')"); - expect(txn.closed).toStrictEqual(false); + const rs = await txn.execute("SELECT SUM(a) FROM t"); + expect(rs.rows[0][0]).toStrictEqual(7); txn.close(); - expect(txn.closed).toStrictEqual(true); - - const rs = await c.execute("SELECT COUNT(*) FROM t"); - expect(rs.rows[0][0]).toStrictEqual(0); - await expect(txn.execute("SELECT 1")).rejects.toBeLibsqlError("TRANSACTION_CLOSED"); - })); - - test("error does not rollback", withClient(async (c) => { - await c.batch([ - "DROP TABLE IF EXISTS t", - "CREATE TABLE t (a)", - ], "write"); - - const txn = await c.transaction("write"); - await expect(txn.execute("SELECT foo")).rejects.toBeLibsqlError(); - await txn.execute("INSERT INTO t VALUES ('one')"); - await expect(txn.execute("SELECT bar")).rejects.toBeLibsqlError(); - await txn.commit(); - - const rs = await c.execute("SELECT COUNT(*) FROM t"); - expect(rs.rows[0][0]).toStrictEqual(1); - })); - - (hasHrana3 ? test : test.skip)( - "ROLLBACK statement stops execution of transaction", - withClient(async (c) => - { - await c.execute("DROP TABLE IF EXISTS t"); - await c.execute("CREATE TABLE t (a)"); + }), + ); + test( + "as the second operation on transaction", + withClient(async (c) => { const txn = await c.transaction("write"); - const prom1 = txn.execute("INSERT INTO t VALUES (1), (2), (3)"); - const prom2 = txn.execute("ROLLBACK"); - const prom3 = txn.execute("INSERT INTO t VALUES (4), (5)"); - - await prom1; - await prom2; - await expect(prom3).rejects.toBeLibsqlError("TRANSACTION_CLOSED"); - await expect(txn.commit()).rejects.toBeLibsqlError(); - txn.close(); - - const rs = await c.execute("SELECT COUNT(*) FROM t"); - expect(rs.rows[0][0]).toStrictEqual(0); - })); - (hasHrana3 ? test : test.skip)( - "OR ROLLBACK statement stops execution of transaction", - withClient(async (c) => - { - await c.execute("DROP TABLE IF EXISTS t"); - await c.execute("CREATE TABLE t (a UNIQUE)"); + await txn.execute("DROP TABLE IF EXISTS t"); + await txn.batch([ + "CREATE TABLE t (a)", + { sql: "INSERT INTO t VALUES (?)", args: [1] }, + { sql: "INSERT INTO t VALUES (?)", args: [2] }, + { sql: "INSERT INTO t VALUES (?)", args: [4] }, + ]); - const txn = await c.transaction("write"); - const prom1 = txn.execute("INSERT INTO t VALUES (1), (2), (3)"); - const prom2 = txn.execute("INSERT OR ROLLBACK INTO t VALUES (1)"); - const prom3 = txn.execute("INSERT INTO t VALUES (4), (5)"); - - await prom1; - await expect(prom2).rejects.toBeLibsqlError(); - await expect(prom3).rejects.toBeLibsqlError("TRANSACTION_CLOSED"); - await expect(txn.commit()).rejects.toBeLibsqlError(); + const rs = await txn.execute("SELECT SUM(a) FROM t"); + expect(rs.rows[0][0]).toStrictEqual(7); txn.close(); + }), + ); - const rs = await c.execute("SELECT COUNT(*) FROM t"); - expect(rs.rows[0][0]).toStrictEqual(0); - })); - - (hasHrana3 ? test : test.skip)( - "OR ROLLBACK as the first statement stops execution of transaction", - withClient(async (c) => - { - await c.execute("DROP TABLE IF EXISTS t"); - await c.execute("CREATE TABLE t (a UNIQUE)"); - await c.execute("INSERT INTO t VALUES (1), (2), (3)"); - + test( + "after error, further statements are not executed", + withClient(async (c) => { const txn = await c.transaction("write"); - const prom1 = txn.execute("INSERT OR ROLLBACK INTO t VALUES (1)"); - const prom2 = txn.execute("INSERT INTO t VALUES (4), (5)"); - await expect(prom1).rejects.toBeLibsqlError(); - await expect(prom2).rejects.toBeLibsqlError("TRANSACTION_CLOSED"); - await expect(txn.commit()).rejects.toBeLibsqlError(); - txn.close(); - - const rs = await c.execute("SELECT COUNT(*) FROM t"); - expect(rs.rows[0][0]).toStrictEqual(3); - })); + await expect( + txn.batch([ + "DROP TABLE IF EXISTS t", + "CREATE TABLE t (a UNIQUE)", + "INSERT INTO t VALUES (1), (2), (4)", + "INSERT INTO t VALUES (1)", + "INSERT INTO t VALUES (8), (16)", + ]), + ).rejects.toBeLibsqlError(); + + const rs = await txn.execute("SELECT SUM(a) FROM t"); + expect(rs.rows[0][0]).toStrictEqual(7); - test("commit empty", withClient(async (c) => { - const txn = await c.transaction("read"); await txn.commit(); - })); - - test("rollback empty", withClient(async (c) => { - const txn = await c.transaction("read"); - await txn.rollback(); - })); - - describe("batch()", () => { - test("as the first operation on transaction", withClient(async (c) => { - const txn = await c.transaction("write"); - - await txn.batch([ - "DROP TABLE IF EXISTS t", - "CREATE TABLE t (a)", - {sql: "INSERT INTO t VALUES (?)", args: [1]}, - {sql: "INSERT INTO t VALUES (?)", args: [2]}, - {sql: "INSERT INTO t VALUES (?)", args: [4]}, - ]); - - const rs = await txn.execute("SELECT SUM(a) FROM t"); - expect(rs.rows[0][0]).toStrictEqual(7); - txn.close(); - })); - - test("as the second operation on transaction", withClient(async (c) => { - const txn = await c.transaction("write"); - - await txn.execute("DROP TABLE IF EXISTS t"); - await txn.batch([ - "CREATE TABLE t (a)", - {sql: "INSERT INTO t VALUES (?)", args: [1]}, - {sql: "INSERT INTO t VALUES (?)", args: [2]}, - {sql: "INSERT INTO t VALUES (?)", args: [4]}, - ]); - - const rs = await txn.execute("SELECT SUM(a) FROM t"); - expect(rs.rows[0][0]).toStrictEqual(7); - txn.close(); - })); - - test("after error, further statements are not executed", withClient(async (c) => { - const txn = await c.transaction("write"); - - await expect(txn.batch([ - "DROP TABLE IF EXISTS t", - "CREATE TABLE t (a UNIQUE)", - "INSERT INTO t VALUES (1), (2), (4)", - "INSERT INTO t VALUES (1)", - "INSERT INTO t VALUES (8), (16)", - ])).rejects.toBeLibsqlError(); - - const rs = await txn.execute("SELECT SUM(a) FROM t"); - expect(rs.rows[0][0]).toStrictEqual(7); - - await txn.commit(); - })); - }); - - (hasHrana2 ? describe : describe.skip)("executeMultiple()", () => { - test("as the first operation on transaction", withClient(async (c) => { - const txn = await c.transaction("write"); + }), + ); + }); + + (hasHrana2 ? describe : describe.skip)("executeMultiple()", () => { + test( + "as the first operation on transaction", + withClient(async (c) => { + const txn = await c.transaction("write"); - await txn.executeMultiple(` + await txn.executeMultiple(` DROP TABLE IF EXISTS t; CREATE TABLE t (a); INSERT INTO t VALUES (1), (2), (4), (8); `); - const rs = await txn.execute("SELECT SUM(a) FROM t"); - expect(rs.rows[0][0]).toStrictEqual(15); - txn.close(); - })); + const rs = await txn.execute("SELECT SUM(a) FROM t"); + expect(rs.rows[0][0]).toStrictEqual(15); + txn.close(); + }), + ); - test("as the second operation on transaction", withClient(async (c) => { - const txn = await c.transaction("write"); - await txn.execute("DROP TABLE IF EXISTS t"); - await txn.executeMultiple(` + test( + "as the second operation on transaction", + withClient(async (c) => { + const txn = await c.transaction("write"); + await txn.execute("DROP TABLE IF EXISTS t"); + await txn.executeMultiple(` CREATE TABLE t (a); INSERT INTO t VALUES (1), (2), (4), (8); `); - const rs = await txn.execute("SELECT SUM(a) FROM t"); - expect(rs.rows[0][0]).toStrictEqual(15); - txn.close(); - })); + const rs = await txn.execute("SELECT SUM(a) FROM t"); + expect(rs.rows[0][0]).toStrictEqual(15); + txn.close(); + }), + ); - test("after error, further statements are not executed", withClient(async (c) => { - const txn = await c.transaction("write"); + test( + "after error, further statements are not executed", + withClient(async (c) => { + const txn = await c.transaction("write"); - await expect(txn.executeMultiple(` + await expect( + txn.executeMultiple(` DROP TABLE IF EXISTS t; CREATE TABLE t (a UNIQUE); INSERT INTO t VALUES (1), (2), (4); INSERT INTO t VALUES (1); INSERT INTO t VALUES (8), (16); - `)).rejects.toBeLibsqlError(); + `), + ).rejects.toBeLibsqlError(); - const rs = await txn.execute("SELECT SUM(a) FROM t"); - expect(rs.rows[0][0]).toStrictEqual(7); + const rs = await txn.execute("SELECT SUM(a) FROM t"); + expect(rs.rows[0][0]).toStrictEqual(7); - await txn.commit(); - })); - }); + await txn.commit(); + }), + ); + }); }); (hasHrana2 ? describe : describe.skip)("executeMultiple()", () => { - test("multiple statements", withClient(async (c) => { - await c.executeMultiple(` + test( + "multiple statements", + withClient(async (c) => { + await c.executeMultiple(` DROP TABLE IF EXISTS t; CREATE TABLE t (a); INSERT INTO t VALUES (1), (2), (4), (8); `); - const rs = await c.execute("SELECT SUM(a) FROM t"); - expect(rs.rows[0][0]).toStrictEqual(15); - })); + const rs = await c.execute("SELECT SUM(a) FROM t"); + expect(rs.rows[0][0]).toStrictEqual(15); + }), + ); - test("after an error, statements are not executed", withClient(async (c) => { - await expect(c.executeMultiple(` + test( + "after an error, statements are not executed", + withClient(async (c) => { + await expect( + c.executeMultiple(` DROP TABLE IF EXISTS t; CREATE TABLE t (a); INSERT INTO t VALUES (1), (2), (4); INSERT INTO t VALUES (foo()); INSERT INTO t VALUES (8), (16); - `)).rejects.toBeLibsqlError(); - - const rs = await c.execute("SELECT SUM(a) FROM t"); - expect(rs.rows[0][0]).toStrictEqual(7); - })); - - test("manual transaction control statements", withClient(async (c) => { - await c.executeMultiple(` + `), + ).rejects.toBeLibsqlError(); + + const rs = await c.execute("SELECT SUM(a) FROM t"); + expect(rs.rows[0][0]).toStrictEqual(7); + }), + ); + + test( + "manual transaction control statements", + withClient(async (c) => { + await c.executeMultiple(` DROP TABLE IF EXISTS t; CREATE TABLE t (a); BEGIN; @@ -911,12 +1217,16 @@ describe("transaction()", () => { COMMIT; `); - const rs = await c.execute("SELECT SUM(a) FROM t"); - expect(rs.rows[0][0]).toStrictEqual(31); - })); + const rs = await c.execute("SELECT SUM(a) FROM t"); + expect(rs.rows[0][0]).toStrictEqual(31); + }), + ); - test("error rolls back a manual transaction", withClient(async (c) => { - await expect(c.executeMultiple(` + test( + "error rolls back a manual transaction", + withClient(async (c) => { + await expect( + c.executeMultiple(` DROP TABLE IF EXISTS t; CREATE TABLE t (a); INSERT INTO t VALUES (0); @@ -925,68 +1235,86 @@ describe("transaction()", () => { INSERT INTO t VALUES (foo()); INSERT INTO t VALUES (8), (16); COMMIT; - `)).rejects.toBeLibsqlError(); + `), + ).rejects.toBeLibsqlError(); - const rs = await c.execute("SELECT SUM(a) FROM t"); - expect(rs.rows[0][0]).toStrictEqual(0); - })); + const rs = await c.execute("SELECT SUM(a) FROM t"); + expect(rs.rows[0][0]).toStrictEqual(0); + }), + ); }); (hasNetworkErrors ? describe : describe.skip)("network errors", () => { - const testCases = [ - {title: "WebSocket close", sql: ".close_ws"}, - {title: "TCP close", sql: ".close_tcp"}, - ]; - - for (const {title, sql} of testCases) { - test(`${title} in execute()`, withClient(async (c) => { - await expect(c.execute(sql)).rejects.toBeLibsqlError("HRANA_WEBSOCKET_ERROR"); - - expect((await c.execute("SELECT 42")).rows[0][0]).toStrictEqual(42); - })); - - test(`${title} in transaction()`, withClient(async (c) => { - const txn = await c.transaction("read"); - await expect(txn.execute(sql)).rejects.toBeLibsqlError("HRANA_WEBSOCKET_ERROR"); - await expect(txn.commit()).rejects.toBeLibsqlError("TRANSACTION_CLOSED"); - txn.close(); + const testCases = [ + { title: "WebSocket close", sql: ".close_ws" }, + { title: "TCP close", sql: ".close_tcp" }, + ]; + + for (const { title, sql } of testCases) { + test( + `${title} in execute()`, + withClient(async (c) => { + await expect(c.execute(sql)).rejects.toBeLibsqlError( + "HRANA_WEBSOCKET_ERROR", + ); - expect((await c.execute("SELECT 42")).rows[0][0]).toStrictEqual(42); - })); + expect((await c.execute("SELECT 42")).rows[0][0]).toStrictEqual(42); + }), + ); - test(`${title} in batch()`, withClient(async (c) => { - await expect(c.batch(["SELECT 42", sql, "SELECT 24"], "read")) - .rejects.toBeLibsqlError("HRANA_WEBSOCKET_ERROR"); + test( + `${title} in transaction()`, + withClient(async (c) => { + const txn = await c.transaction("read"); + await expect(txn.execute(sql)).rejects.toBeLibsqlError( + "HRANA_WEBSOCKET_ERROR", + ); + await expect(txn.commit()).rejects.toBeLibsqlError( + "TRANSACTION_CLOSED", + ); + txn.close(); - expect((await c.execute("SELECT 42")).rows[0][0]).toStrictEqual(42); - })); - } + expect((await c.execute("SELECT 42")).rows[0][0]).toStrictEqual(42); + }), + ); + + test( + `${title} in batch()`, + withClient(async (c) => { + await expect( + c.batch(["SELECT 42", sql, "SELECT 24"], "read"), + ).rejects.toBeLibsqlError("HRANA_WEBSOCKET_ERROR"); + + expect((await c.execute("SELECT 42")).rows[0][0]).toStrictEqual(42); + }), + ); + } }); (isHttp ? test : test.skip)("custom fetch", async () => { - let fetchCalledCount = 0; - function customFetch(request: Request): Promise { - fetchCalledCount += 1; - return fetch(request); - } - - const c = createClient({...config, fetch: customFetch}); - try { - const rs = await c.execute("SELECT 42"); - expect(rs.rows[0][0]).toStrictEqual(42); - expect(fetchCalledCount).toBeGreaterThan(0); - } finally { - c.close(); - } + let fetchCalledCount = 0; + function customFetch(request: Request): Promise { + fetchCalledCount += 1; + return fetch(request); + } + + const c = createClient({ ...config, fetch: customFetch }); + try { + const rs = await c.execute("SELECT 42"); + expect(rs.rows[0][0]).toStrictEqual(42); + expect(fetchCalledCount).toBeGreaterThan(0); + } finally { + c.close(); + } }); -(isFile ? test : test.skip)('raw error codes', async () => { - const c = createClient(config) - try { - await expect(c.execute('NOT A VALID SQL')).rejects.toThrow( - expect.toBeLibsqlError({ code: 'SQLITE_ERROR', rawCode: 1}) - ) - } finally { - c.close(); - } -}) +(isFile ? test : test.skip)("raw error codes", async () => { + const c = createClient(config); + try { + await expect(c.execute("NOT A VALID SQL")).rejects.toThrow( + expect.toBeLibsqlError({ code: "SQLITE_ERROR", rawCode: 1 }), + ); + } finally { + c.close(); + } +}); diff --git a/packages/libsql-client/src/__tests__/helpers.ts b/packages/libsql-client/src/__tests__/helpers.ts index 1a0a5185..baf26e34 100644 --- a/packages/libsql-client/src/__tests__/helpers.ts +++ b/packages/libsql-client/src/__tests__/helpers.ts @@ -4,51 +4,53 @@ import type { MatcherFunction } from "expect"; import { LibsqlError } from "../node.js"; type CodeMatch = { - code: string, - rawCode: number -} + code: string; + rawCode: number; +}; -const toBeLibsqlError: MatcherFunction<[code?: string | CodeMatch, message?: RegExp]> = - function (actual, code?, messageRe?) { - const pass = actual instanceof LibsqlError - && isValidCode(actual, code) - && (messageRe === undefined || actual.message.match(messageRe) !== null); +const toBeLibsqlError: MatcherFunction< + [code?: string | CodeMatch, message?: RegExp] +> = function (actual, code?, messageRe?) { + const pass = + actual instanceof LibsqlError && + isValidCode(actual, code) && + (messageRe === undefined || actual.message.match(messageRe) !== null); - const message = (): string => { - const parts = []; - parts.push("expected "); - parts.push(this.utils.printReceived(actual)); - parts.push(pass ? " not to be " : " to be "); - parts.push("an instance of LibsqlError"); - if (code !== undefined) { - parts.push(" with error code "); - parts.push(this.utils.printExpected(code)); - } - if (messageRe !== undefined) { - parts.push(" with error message matching "); - parts.push(this.utils.printExpected(messageRe)); - } - return parts.join(""); - }; + const message = (): string => { + const parts = []; + parts.push("expected "); + parts.push(this.utils.printReceived(actual)); + parts.push(pass ? " not to be " : " to be "); + parts.push("an instance of LibsqlError"); + if (code !== undefined) { + parts.push(" with error code "); + parts.push(this.utils.printExpected(code)); + } + if (messageRe !== undefined) { + parts.push(" with error message matching "); + parts.push(this.utils.printExpected(messageRe)); + } + return parts.join(""); + }; - return {pass, message}; - }; + return { pass, message }; +}; const isValidCode = (error: LibsqlError, code?: string | CodeMatch) => { - if (code === undefined) { - return true - } - if (typeof code === 'string') { - return error.code === code - } - return error.code === code.code && error.rawCode === code.rawCode -} -expect.extend({toBeLibsqlError}); + if (code === undefined) { + return true; + } + if (typeof code === "string") { + return error.code === code; + } + return error.code === code.code && error.rawCode === code.rawCode; +}; +expect.extend({ toBeLibsqlError }); declare module "expect" { - interface AsymmetricMatchers { - toBeLibsqlError(code?: string | CodeMatch, messageRe?: RegExp): void; - } - interface Matchers { - toBeLibsqlError(code?: string | CodeMatch, messageRe?: RegExp): R; - } + interface AsymmetricMatchers { + toBeLibsqlError(code?: string | CodeMatch, messageRe?: RegExp): void; + } + interface Matchers { + toBeLibsqlError(code?: string | CodeMatch, messageRe?: RegExp): R; + } } diff --git a/packages/libsql-client/src/__tests__/uri.test.ts b/packages/libsql-client/src/__tests__/uri.test.ts index a0bae4ac..4088c808 100644 --- a/packages/libsql-client/src/__tests__/uri.test.ts +++ b/packages/libsql-client/src/__tests__/uri.test.ts @@ -6,275 +6,257 @@ import "./helpers.js"; import { parseUri, encodeBaseUrl } from "@libsql/core/uri"; describe("parseUri()", () => { - test("authority and path", () => { - const cases = [ - {text: "file://localhost", path: ""}, - {text: "file://localhost/", path: "/"}, - {text: "file://localhost/absolute/path", path: "/absolute/path"}, - {text: "file://localhost/k%C5%AF%C5%88", path: "/kůň"}, - ]; - for (const {text, path} of cases) { - expect(parseUri(text)).toEqual({ - scheme: "file", - authority: {host: "localhost"}, - path, - }); - } - }); + test("authority and path", () => { + const cases = [ + { text: "file://localhost", path: "" }, + { text: "file://localhost/", path: "/" }, + { text: "file://localhost/absolute/path", path: "/absolute/path" }, + { text: "file://localhost/k%C5%AF%C5%88", path: "/kůň" }, + ]; + for (const { text, path } of cases) { + expect(parseUri(text)).toEqual({ + scheme: "file", + authority: { host: "localhost" }, + path, + }); + } + }); - test("empty authority and path", () => { - const cases = [ - {text: "file:///absolute/path", path: "/absolute/path"}, - {text: "file://", path: ""}, - {text: "file:///k%C5%AF%C5%88", path: "/kůň"}, - ]; - for (const {text, path} of cases) { - expect(parseUri(text)).toEqual({ - scheme: "file", - authority: {host: ""}, - path, - }); - } - }); + test("empty authority and path", () => { + const cases = [ + { text: "file:///absolute/path", path: "/absolute/path" }, + { text: "file://", path: "" }, + { text: "file:///k%C5%AF%C5%88", path: "/kůň" }, + ]; + for (const { text, path } of cases) { + expect(parseUri(text)).toEqual({ + scheme: "file", + authority: { host: "" }, + path, + }); + } + }); - test("no authority and path", () => { - const cases = [ - {text: "file:/absolute/path", path: "/absolute/path"}, - {text: "file:relative/path", path: "relative/path"}, - {text: "file:", path: ""}, - {text: "file:C:/path/to/file", path: "C:/path/to/file"}, - {text: "file:k%C5%AF%C5%88", path: "kůň"}, - ]; - for (const {text, path} of cases) { - expect(parseUri(text)).toEqual({ - scheme: "file", - path, - }); - } - }); + test("no authority and path", () => { + const cases = [ + { text: "file:/absolute/path", path: "/absolute/path" }, + { text: "file:relative/path", path: "relative/path" }, + { text: "file:", path: "" }, + { text: "file:C:/path/to/file", path: "C:/path/to/file" }, + { text: "file:k%C5%AF%C5%88", path: "kůň" }, + ]; + for (const { text, path } of cases) { + expect(parseUri(text)).toEqual({ + scheme: "file", + path, + }); + } + }); - test("authority", () => { - const hosts = [ - {text: "localhost", host: "localhost"}, - {text: "domain.name", host: "domain.name"}, - {text: "some$weird.%20!name", host: "some$weird. !name"}, - {text: "1.20.255.99", host: "1.20.255.99"}, - {text: "[2001:4860:4802:32::a]", host: "2001:4860:4802:32::a"}, - {text: "%61", host: "a"}, - {text: "100%2e100%2e100%2e100", host: "100.100.100.100"}, - {text: "k%C5%AF%C5%88", host: "kůň"}, - ]; - const ports = [ - {text: "", port: undefined}, - {text: ":", port: undefined}, - {text: ":0", port: 0}, - {text: ":99", port: 99}, - {text: ":65535", port: 65535}, - ]; - const userinfos = [ - {text: "", userinfo: undefined}, - {text: "@", userinfo: {username: ""}}, - {text: "alice@", userinfo: {username: "alice"}}, - {text: "alice:secret@", userinfo: {username: "alice", password: "secret"}}, - {text: "alice:sec:et@", userinfo: {username: "alice", password: "sec:et"}}, - {text: "alice%3Asecret@", userinfo: {username: "alice:secret"}}, - {text: "alice:s%65cret@", userinfo: {username: "alice", password: "secret"}}, - ]; + test("authority", () => { + const hosts = [ + { text: "localhost", host: "localhost" }, + { text: "domain.name", host: "domain.name" }, + { text: "some$weird.%20!name", host: "some$weird. !name" }, + { text: "1.20.255.99", host: "1.20.255.99" }, + { text: "[2001:4860:4802:32::a]", host: "2001:4860:4802:32::a" }, + { text: "%61", host: "a" }, + { text: "100%2e100%2e100%2e100", host: "100.100.100.100" }, + { text: "k%C5%AF%C5%88", host: "kůň" }, + ]; + const ports = [ + { text: "", port: undefined }, + { text: ":", port: undefined }, + { text: ":0", port: 0 }, + { text: ":99", port: 99 }, + { text: ":65535", port: 65535 }, + ]; + const userinfos = [ + { text: "", userinfo: undefined }, + { text: "@", userinfo: { username: "" } }, + { text: "alice@", userinfo: { username: "alice" } }, + { + text: "alice:secret@", + userinfo: { username: "alice", password: "secret" }, + }, + { + text: "alice:sec:et@", + userinfo: { username: "alice", password: "sec:et" }, + }, + { text: "alice%3Asecret@", userinfo: { username: "alice:secret" } }, + { + text: "alice:s%65cret@", + userinfo: { username: "alice", password: "secret" }, + }, + ]; - for (const {text: hostText, host} of hosts) { - for (const {text: portText, port} of ports) { - for (const {text: userText, userinfo} of userinfos) { - const text = `http://${userText}${hostText}${portText}`; - expect(parseUri(text)).toEqual({ - scheme: "http", - authority: {host, port, userinfo}, - path: "", - }); - } - } + for (const { text: hostText, host } of hosts) { + for (const { text: portText, port } of ports) { + for (const { text: userText, userinfo } of userinfos) { + const text = `http://${userText}${hostText}${portText}`; + expect(parseUri(text)).toEqual({ + scheme: "http", + authority: { host, port, userinfo }, + path: "", + }); } - }); + } + } + }); - test("query", () => { - const cases = [ - {text: "?", pairs: []}, - {text: "?key=value", pairs: [ - {key: "key", value: "value"}, - ]}, - {text: "?&key=value", pairs: [ - {key: "key", value: "value"}, - ]}, - {text: "?key=value&&", pairs: [ - {key: "key", value: "value"}, - ]}, - {text: "?a", pairs: [ - {key: "a", value: ""}, - ]}, - {text: "?a=", pairs: [ - {key: "a", value: ""}, - ]}, - {text: "?=a", pairs: [ - {key: "", value: "a"}, - ]}, - {text: "?=", pairs: [ - {key: "", value: ""}, - ]}, - {text: "?a=b=c", pairs: [ - {key: "a", value: "b=c"}, - ]}, - {text: "?a=b&c=d", pairs: [ - {key: "a", value: "b"}, - {key: "c", value: "d"}, - ]}, - {text: "?a+b=c", pairs: [ - {key: "a b", value: "c"}, - ]}, - {text: "?a=b+c", pairs: [ - {key: "a", value: "b c"}, - ]}, - {text: "?a?b", pairs: [ - {key: "a?b", value: ""}, - ]}, - {text: "?%61=%62", pairs: [ - {key: "a", value: "b"}, - ]}, - {text: "?a%3db", pairs: [ - {key: "a=b", value: ""}, - ]}, - {text: "?a=%2b", pairs: [ - {key: "a", value: "+"}, - ]}, - {text: "?%2b=b", pairs: [ - {key: "+", value: "b"}, - ]}, - {text: "?a=b%26c", pairs: [ - {key: "a", value: "b&c"}, - ]}, - {text: "?a=k%C5%AF%C5%88", pairs: [ - {key: "a", value: "kůň"}, - ]}, - ]; - for (const {text: queryText, pairs} of cases) { - const text = `file:${queryText}`; - expect(parseUri(text)).toEqual({ - scheme: "file", - path: "", - query: {pairs}, - }); - } - }); + test("query", () => { + const cases = [ + { text: "?", pairs: [] }, + { text: "?key=value", pairs: [{ key: "key", value: "value" }] }, + { text: "?&key=value", pairs: [{ key: "key", value: "value" }] }, + { text: "?key=value&&", pairs: [{ key: "key", value: "value" }] }, + { text: "?a", pairs: [{ key: "a", value: "" }] }, + { text: "?a=", pairs: [{ key: "a", value: "" }] }, + { text: "?=a", pairs: [{ key: "", value: "a" }] }, + { text: "?=", pairs: [{ key: "", value: "" }] }, + { text: "?a=b=c", pairs: [{ key: "a", value: "b=c" }] }, + { + text: "?a=b&c=d", + pairs: [ + { key: "a", value: "b" }, + { key: "c", value: "d" }, + ], + }, + { text: "?a+b=c", pairs: [{ key: "a b", value: "c" }] }, + { text: "?a=b+c", pairs: [{ key: "a", value: "b c" }] }, + { text: "?a?b", pairs: [{ key: "a?b", value: "" }] }, + { text: "?%61=%62", pairs: [{ key: "a", value: "b" }] }, + { text: "?a%3db", pairs: [{ key: "a=b", value: "" }] }, + { text: "?a=%2b", pairs: [{ key: "a", value: "+" }] }, + { text: "?%2b=b", pairs: [{ key: "+", value: "b" }] }, + { text: "?a=b%26c", pairs: [{ key: "a", value: "b&c" }] }, + { text: "?a=k%C5%AF%C5%88", pairs: [{ key: "a", value: "kůň" }] }, + ]; + for (const { text: queryText, pairs } of cases) { + const text = `file:${queryText}`; + expect(parseUri(text)).toEqual({ + scheme: "file", + path: "", + query: { pairs }, + }); + } + }); - test("fragment", () => { - const cases = [ - {text: "", fragment: undefined}, - {text: "#a", fragment: "a"}, - {text: "#a?b", fragment: "a?b"}, - {text: "#%61", fragment: "a"}, - {text: "#k%C5%AF%C5%88", fragment: "kůň"}, - ]; - for (const {text: fragmentText, fragment} of cases) { - const text = `file:${fragmentText}`; - expect(parseUri(text)).toEqual({ - scheme: "file", - path: "", - fragment, - }); - } - }); + test("fragment", () => { + const cases = [ + { text: "", fragment: undefined }, + { text: "#a", fragment: "a" }, + { text: "#a?b", fragment: "a?b" }, + { text: "#%61", fragment: "a" }, + { text: "#k%C5%AF%C5%88", fragment: "kůň" }, + ]; + for (const { text: fragmentText, fragment } of cases) { + const text = `file:${fragmentText}`; + expect(parseUri(text)).toEqual({ + scheme: "file", + path: "", + fragment, + }); + } + }); - test("parse errors", () => { - const cases = [ - {text: "", message: /format/}, - {text: "foo", message: /format/}, - {text: "foo.bar.com", message: /format/}, - {text: "h$$p://localhost", message: /format/}, - {text: "h%74%74p://localhost", message: /format/}, - {text: "http://localhost:%38%38", message: /authority/}, - {text: "file:k%C5%C5%88", message: /percent encoding/}, - ]; + test("parse errors", () => { + const cases = [ + { text: "", message: /format/ }, + { text: "foo", message: /format/ }, + { text: "foo.bar.com", message: /format/ }, + { text: "h$$p://localhost", message: /format/ }, + { text: "h%74%74p://localhost", message: /format/ }, + { text: "http://localhost:%38%38", message: /authority/ }, + { text: "file:k%C5%C5%88", message: /percent encoding/ }, + ]; - for (const {text, message} of cases) { - expect(() => parseUri(text)).toThrow(expect.toBeLibsqlError("URL_INVALID", message)); - } - }); + for (const { text, message } of cases) { + expect(() => parseUri(text)).toThrow( + expect.toBeLibsqlError("URL_INVALID", message), + ); + } + }); }); test("encodeBaseUrl()", () => { - const cases = [ - { - scheme: "http", - host: "localhost", - path: "", - url: "http://localhost", - }, - { - scheme: "http", - host: "localhost", - path: "/", - url: "http://localhost/", - }, - { - scheme: "http", - host: "localhost", - port: 8080, - path: "", - url: "http://localhost:8080", - }, - { - scheme: "http", - host: "localhost", - path: "/foo/bar", - url: "http://localhost/foo/bar", - }, - { - scheme: "http", - host: "localhost", - path: "foo/bar", - url: "http://localhost/foo/bar", - }, - { - scheme: "http", - host: "some.long.domain.name", - path: "", - url: "http://some.long.domain.name", - }, - { - scheme: "http", - host: "1.2.3.4", - path: "", - url: "http://1.2.3.4", - }, - { - scheme: "http", - host: "2001:4860:4802:32::a", - path: "", - url: "http://[2001:4860:4802:32::a]", - }, - { - scheme: "http", - host: "localhost", - userinfo: {username: "alice", password: undefined}, - path: "", - url: "http://alice@localhost", - }, - { - scheme: "http", - host: "localhost", - userinfo: {username: "alice", password: "secr:t"}, - path: "", - url: "http://alice:secr%3At@localhost", - }, - { - scheme: "https", - host: "localhost", - userinfo: {username: "alice", password: "secret"}, - port: 8080, - path: "/some/path", - url: "https://alice:secret@localhost:8080/some/path", - }, - ]; + const cases = [ + { + scheme: "http", + host: "localhost", + path: "", + url: "http://localhost", + }, + { + scheme: "http", + host: "localhost", + path: "/", + url: "http://localhost/", + }, + { + scheme: "http", + host: "localhost", + port: 8080, + path: "", + url: "http://localhost:8080", + }, + { + scheme: "http", + host: "localhost", + path: "/foo/bar", + url: "http://localhost/foo/bar", + }, + { + scheme: "http", + host: "localhost", + path: "foo/bar", + url: "http://localhost/foo/bar", + }, + { + scheme: "http", + host: "some.long.domain.name", + path: "", + url: "http://some.long.domain.name", + }, + { + scheme: "http", + host: "1.2.3.4", + path: "", + url: "http://1.2.3.4", + }, + { + scheme: "http", + host: "2001:4860:4802:32::a", + path: "", + url: "http://[2001:4860:4802:32::a]", + }, + { + scheme: "http", + host: "localhost", + userinfo: { username: "alice", password: undefined }, + path: "", + url: "http://alice@localhost", + }, + { + scheme: "http", + host: "localhost", + userinfo: { username: "alice", password: "secr:t" }, + path: "", + url: "http://alice:secr%3At@localhost", + }, + { + scheme: "https", + host: "localhost", + userinfo: { username: "alice", password: "secret" }, + port: 8080, + path: "/some/path", + url: "https://alice:secret@localhost:8080/some/path", + }, + ]; - for (const {scheme, host, port, userinfo, path, url} of cases) { - expect(encodeBaseUrl(scheme, {host, port, userinfo}, path)).toStrictEqual(new URL(url)); - } + for (const { scheme, host, port, userinfo, path, url } of cases) { + expect(encodeBaseUrl(scheme, { host, port, userinfo }, path)).toStrictEqual( + new URL(url), + ); + } }); diff --git a/packages/libsql-client/src/hrana.ts b/packages/libsql-client/src/hrana.ts index 2796baa7..e9058864 100644 --- a/packages/libsql-client/src/hrana.ts +++ b/packages/libsql-client/src/hrana.ts @@ -1,336 +1,360 @@ import * as hrana from "@libsql/hrana-client"; -import type { InStatement, ResultSet, Transaction, TransactionMode } from "@libsql/core/api"; +import type { + InStatement, + ResultSet, + Transaction, + TransactionMode, +} from "@libsql/core/api"; import { LibsqlError } from "@libsql/core/api"; import type { SqlCache } from "./sql_cache.js"; import { transactionModeToBegin, ResultSetImpl } from "@libsql/core/util"; export abstract class HranaTransaction implements Transaction { - #mode: TransactionMode; - #version: hrana.ProtocolVersion; - // Promise that is resolved when the BEGIN statement completes, or `undefined` if we haven't executed the - // BEGIN statement yet. - #started: Promise | undefined; - - /** @private */ - constructor(mode: TransactionMode, version: hrana.ProtocolVersion) { - this.#mode = mode; - this.#version = version; - this.#started = undefined; + #mode: TransactionMode; + #version: hrana.ProtocolVersion; + // Promise that is resolved when the BEGIN statement completes, or `undefined` if we haven't executed the + // BEGIN statement yet. + #started: Promise | undefined; + + /** @private */ + constructor(mode: TransactionMode, version: hrana.ProtocolVersion) { + this.#mode = mode; + this.#version = version; + this.#started = undefined; + } + + /** @private */ + abstract _getStream(): hrana.Stream; + /** @private */ + abstract _getSqlCache(): SqlCache; + + abstract close(): void; + abstract get closed(): boolean; + + execute(stmt: InStatement): Promise { + return this.batch([stmt]).then((results) => results[0]); + } + + async batch(stmts: Array): Promise> { + const stream = this._getStream(); + if (stream.closed) { + throw new LibsqlError( + "Cannot execute statements because the transaction is closed", + "TRANSACTION_CLOSED", + ); } - /** @private */ - abstract _getStream(): hrana.Stream; - /** @private */ - abstract _getSqlCache(): SqlCache; + try { + const hranaStmts = stmts.map(stmtToHrana); + + let rowsPromises: Array>; + if (this.#started === undefined) { + // The transaction hasn't started yet, so we need to send the BEGIN statement in a batch with + // `hranaStmts`. + + this._getSqlCache().apply(hranaStmts); + const batch = stream.batch(this.#version >= 3); + const beginStep = batch.step(); + const beginPromise = beginStep.run(transactionModeToBegin(this.#mode)); + + // Execute the `hranaStmts` only if the BEGIN succeeded, to make sure that we don't execute it + // outside of a transaction. + let lastStep = beginStep; + rowsPromises = hranaStmts.map((hranaStmt) => { + const stmtStep = batch.step().condition(hrana.BatchCond.ok(lastStep)); + if (this.#version >= 3) { + // If the Hrana version supports it, make sure that we are still in a transaction + stmtStep.condition( + hrana.BatchCond.not(hrana.BatchCond.isAutocommit(batch)), + ); + } - abstract close(): void; - abstract get closed(): boolean; + const rowsPromise = stmtStep.query(hranaStmt); + rowsPromise.catch(() => undefined); // silence Node warning + lastStep = stmtStep; + return rowsPromise; + }); - execute(stmt: InStatement): Promise { - return this.batch([stmt]).then((results) => results[0]); - } - - async batch(stmts: Array): Promise> { - const stream = this._getStream(); - if (stream.closed) { - throw new LibsqlError( - "Cannot execute statements because the transaction is closed", - "TRANSACTION_CLOSED", - ); - } + // `this.#started` is resolved successfully only if the batch and the BEGIN statement inside + // of the batch are both successful. + this.#started = batch + .execute() + .then(() => beginPromise) + .then(() => undefined); try { - const hranaStmts = stmts.map(stmtToHrana); - - let rowsPromises: Array>; - if (this.#started === undefined) { - // The transaction hasn't started yet, so we need to send the BEGIN statement in a batch with - // `hranaStmts`. - - this._getSqlCache().apply(hranaStmts); - const batch = stream.batch(this.#version >= 3); - const beginStep = batch.step(); - const beginPromise = beginStep.run(transactionModeToBegin(this.#mode)); - - // Execute the `hranaStmts` only if the BEGIN succeeded, to make sure that we don't execute it - // outside of a transaction. - let lastStep = beginStep; - rowsPromises = hranaStmts.map((hranaStmt) => { - const stmtStep = batch.step() - .condition(hrana.BatchCond.ok(lastStep)); - if (this.#version >= 3) { - // If the Hrana version supports it, make sure that we are still in a transaction - stmtStep.condition(hrana.BatchCond.not(hrana.BatchCond.isAutocommit(batch))); - } - - const rowsPromise = stmtStep.query(hranaStmt); - rowsPromise.catch(() => undefined); // silence Node warning - lastStep = stmtStep; - return rowsPromise; - }); - - // `this.#started` is resolved successfully only if the batch and the BEGIN statement inside - // of the batch are both successful. - this.#started = batch.execute() - .then(() => beginPromise) - .then(() => undefined); - - try { - await this.#started; - } catch (e) { - // If the BEGIN failed, the transaction is unusable and we must close it. However, if the - // BEGIN suceeds and `hranaStmts` fail, the transaction is _not_ closed. - this.close(); - throw e; - } - } else { - if (this.#version < 3) { - // The transaction has started, so we must wait until the BEGIN statement completed to make - // sure that we don't execute `hranaStmts` outside of a transaction. - await this.#started; - } else { - // The transaction has started, but we will use `hrana.BatchCond.isAutocommit()` to make - // sure that we don't execute `hranaStmts` outside of a transaction, so we don't have to - // wait for `this.#started` - } - - this._getSqlCache().apply(hranaStmts); - const batch = stream.batch(this.#version >= 3); - - let lastStep: hrana.BatchStep | undefined = undefined; - rowsPromises = hranaStmts.map((hranaStmt) => { - const stmtStep = batch.step(); - if (lastStep !== undefined) { - stmtStep.condition(hrana.BatchCond.ok(lastStep)); - } - if (this.#version >= 3) { - stmtStep.condition(hrana.BatchCond.not(hrana.BatchCond.isAutocommit(batch))); - } - const rowsPromise = stmtStep.query(hranaStmt); - rowsPromise.catch(() => undefined); // silence Node warning - lastStep = stmtStep; - return rowsPromise; - }); - - await batch.execute(); - } - - const resultSets = []; - for (const rowsPromise of rowsPromises) { - const rows = await rowsPromise; - if (rows === undefined) { - throw new LibsqlError( - "Statement in a transaction was not executed, " + - "probably because the transaction has been rolled back", - "TRANSACTION_CLOSED", - ); - } - resultSets.push(resultSetFromHrana(rows)); - } - return resultSets; + await this.#started; } catch (e) { - throw mapHranaError(e); + // If the BEGIN failed, the transaction is unusable and we must close it. However, if the + // BEGIN suceeds and `hranaStmts` fail, the transaction is _not_ closed. + this.close(); + throw e; } - } - - async executeMultiple(sql: string): Promise { - const stream = this._getStream(); - if (stream.closed) { - throw new LibsqlError( - "Cannot execute statements because the transaction is closed", - "TRANSACTION_CLOSED", - ); + } else { + if (this.#version < 3) { + // The transaction has started, so we must wait until the BEGIN statement completed to make + // sure that we don't execute `hranaStmts` outside of a transaction. + await this.#started; + } else { + // The transaction has started, but we will use `hrana.BatchCond.isAutocommit()` to make + // sure that we don't execute `hranaStmts` outside of a transaction, so we don't have to + // wait for `this.#started` } - try { - if (this.#started === undefined) { - // If the transaction hasn't started yet, start it now - this.#started = stream.run(transactionModeToBegin(this.#mode)) - .then(() => undefined); - try { - await this.#started; - } catch (e) { - this.close(); - throw e; - } - } else { - // Wait until the transaction has started - await this.#started; - } - - await stream.sequence(sql); - } catch (e) { - throw mapHranaError(e); + this._getSqlCache().apply(hranaStmts); + const batch = stream.batch(this.#version >= 3); + + let lastStep: hrana.BatchStep | undefined = undefined; + rowsPromises = hranaStmts.map((hranaStmt) => { + const stmtStep = batch.step(); + if (lastStep !== undefined) { + stmtStep.condition(hrana.BatchCond.ok(lastStep)); + } + if (this.#version >= 3) { + stmtStep.condition( + hrana.BatchCond.not(hrana.BatchCond.isAutocommit(batch)), + ); + } + const rowsPromise = stmtStep.query(hranaStmt); + rowsPromise.catch(() => undefined); // silence Node warning + lastStep = stmtStep; + return rowsPromise; + }); + + await batch.execute(); + } + + const resultSets = []; + for (const rowsPromise of rowsPromises) { + const rows = await rowsPromise; + if (rows === undefined) { + throw new LibsqlError( + "Statement in a transaction was not executed, " + + "probably because the transaction has been rolled back", + "TRANSACTION_CLOSED", + ); } + resultSets.push(resultSetFromHrana(rows)); + } + return resultSets; + } catch (e) { + throw mapHranaError(e); } - - async rollback(): Promise { - try { - const stream = this._getStream(); - if (stream.closed) { - return; - } - - if (this.#started !== undefined) { - // We don't have to wait for the BEGIN statement to complete. If the BEGIN fails, we will - // execute a ROLLBACK outside of an active transaction, which should be harmless. - } else { - // We did nothing in the transaction, so there is nothing to rollback. - return; - } - - // Pipeline the ROLLBACK statement and the stream close. - const promise = stream.run("ROLLBACK") - .catch(e => { throw mapHranaError(e); }); - stream.closeGracefully(); - - await promise; - } catch (e) { - throw mapHranaError(e); - } finally { - // `this.close()` may close the `hrana.Client`, which aborts all pending stream requests, so we - // must call it _after_ we receive the ROLLBACK response. - // Also note that the current stream should already be closed, but we need to call `this.close()` - // anyway, because it may need to do more cleanup. - this.close(); - } + } + + async executeMultiple(sql: string): Promise { + const stream = this._getStream(); + if (stream.closed) { + throw new LibsqlError( + "Cannot execute statements because the transaction is closed", + "TRANSACTION_CLOSED", + ); } - async commit(): Promise { - // (this method is analogous to `rollback()`) + try { + if (this.#started === undefined) { + // If the transaction hasn't started yet, start it now + this.#started = stream + .run(transactionModeToBegin(this.#mode)) + .then(() => undefined); try { - const stream = this._getStream(); - if (stream.closed) { - throw new LibsqlError( - "Cannot commit the transaction because it is already closed", - "TRANSACTION_CLOSED", - ); - } - - if (this.#started !== undefined) { - // Make sure to execute the COMMIT only if the BEGIN was successful. - await this.#started; - } else { - return; - } - - const promise = stream.run("COMMIT") - .catch(e => { throw mapHranaError(e); }); - stream.closeGracefully(); - - await promise; + await this.#started; } catch (e) { - throw mapHranaError(e); - } finally { - this.close(); + this.close(); + throw e; } + } else { + // Wait until the transaction has started + await this.#started; + } + + await stream.sequence(sql); + } catch (e) { + throw mapHranaError(e); + } + } + + async rollback(): Promise { + try { + const stream = this._getStream(); + if (stream.closed) { + return; + } + + if (this.#started !== undefined) { + // We don't have to wait for the BEGIN statement to complete. If the BEGIN fails, we will + // execute a ROLLBACK outside of an active transaction, which should be harmless. + } else { + // We did nothing in the transaction, so there is nothing to rollback. + return; + } + + // Pipeline the ROLLBACK statement and the stream close. + const promise = stream.run("ROLLBACK").catch((e) => { + throw mapHranaError(e); + }); + stream.closeGracefully(); + + await promise; + } catch (e) { + throw mapHranaError(e); + } finally { + // `this.close()` may close the `hrana.Client`, which aborts all pending stream requests, so we + // must call it _after_ we receive the ROLLBACK response. + // Also note that the current stream should already be closed, but we need to call `this.close()` + // anyway, because it may need to do more cleanup. + this.close(); } + } + + async commit(): Promise { + // (this method is analogous to `rollback()`) + try { + const stream = this._getStream(); + if (stream.closed) { + throw new LibsqlError( + "Cannot commit the transaction because it is already closed", + "TRANSACTION_CLOSED", + ); + } + + if (this.#started !== undefined) { + // Make sure to execute the COMMIT only if the BEGIN was successful. + await this.#started; + } else { + return; + } + + const promise = stream.run("COMMIT").catch((e) => { + throw mapHranaError(e); + }); + stream.closeGracefully(); + + await promise; + } catch (e) { + throw mapHranaError(e); + } finally { + this.close(); + } + } } export async function executeHranaBatch( - mode: TransactionMode, - version: hrana.ProtocolVersion, - batch: hrana.Batch, - hranaStmts: Array, + mode: TransactionMode, + version: hrana.ProtocolVersion, + batch: hrana.Batch, + hranaStmts: Array, ): Promise> { - const beginStep = batch.step(); - const beginPromise = beginStep.run(transactionModeToBegin(mode)); - - let lastStep = beginStep; - const stmtPromises = hranaStmts.map((hranaStmt) => { - const stmtStep = batch.step() - .condition(hrana.BatchCond.ok(lastStep)); - if (version >= 3) { - stmtStep.condition(hrana.BatchCond.not(hrana.BatchCond.isAutocommit(batch))); - } + const beginStep = batch.step(); + const beginPromise = beginStep.run(transactionModeToBegin(mode)); - const stmtPromise = stmtStep.query(hranaStmt); - lastStep = stmtStep; - return stmtPromise; - }); - - const commitStep = batch.step() - .condition(hrana.BatchCond.ok(lastStep)); + let lastStep = beginStep; + const stmtPromises = hranaStmts.map((hranaStmt) => { + const stmtStep = batch.step().condition(hrana.BatchCond.ok(lastStep)); if (version >= 3) { - commitStep.condition(hrana.BatchCond.not(hrana.BatchCond.isAutocommit(batch))); + stmtStep.condition( + hrana.BatchCond.not(hrana.BatchCond.isAutocommit(batch)), + ); } - const commitPromise = commitStep.run("COMMIT"); - - const rollbackStep = batch.step() - .condition(hrana.BatchCond.not(hrana.BatchCond.ok(commitStep))); - rollbackStep.run("ROLLBACK").catch(_ => undefined); - - await batch.execute(); - - const resultSets = []; - await beginPromise; - for (const stmtPromise of stmtPromises) { - const hranaRows = await stmtPromise; - if (hranaRows === undefined) { - throw new LibsqlError( - "Statement in a batch was not executed, probably because the transaction has been rolled back", - "TRANSACTION_CLOSED", - ); - } - resultSets.push(resultSetFromHrana(hranaRows)); + + const stmtPromise = stmtStep.query(hranaStmt); + lastStep = stmtStep; + return stmtPromise; + }); + + const commitStep = batch.step().condition(hrana.BatchCond.ok(lastStep)); + if (version >= 3) { + commitStep.condition( + hrana.BatchCond.not(hrana.BatchCond.isAutocommit(batch)), + ); + } + const commitPromise = commitStep.run("COMMIT"); + + const rollbackStep = batch + .step() + .condition(hrana.BatchCond.not(hrana.BatchCond.ok(commitStep))); + rollbackStep.run("ROLLBACK").catch((_) => undefined); + + await batch.execute(); + + const resultSets = []; + await beginPromise; + for (const stmtPromise of stmtPromises) { + const hranaRows = await stmtPromise; + if (hranaRows === undefined) { + throw new LibsqlError( + "Statement in a batch was not executed, probably because the transaction has been rolled back", + "TRANSACTION_CLOSED", + ); } - await commitPromise; + resultSets.push(resultSetFromHrana(hranaRows)); + } + await commitPromise; - return resultSets; + return resultSets; } export function stmtToHrana(stmt: InStatement): hrana.Stmt { - if (typeof stmt === "string") { - return new hrana.Stmt(stmt); - } - - const hranaStmt = new hrana.Stmt(stmt.sql); - if (Array.isArray(stmt.args)) { - hranaStmt.bindIndexes(stmt.args); - } else { - for (const [key, value] of Object.entries(stmt.args)) { - hranaStmt.bindName(key, value); - } + if (typeof stmt === "string") { + return new hrana.Stmt(stmt); + } + + const hranaStmt = new hrana.Stmt(stmt.sql); + if (Array.isArray(stmt.args)) { + hranaStmt.bindIndexes(stmt.args); + } else { + for (const [key, value] of Object.entries(stmt.args)) { + hranaStmt.bindName(key, value); } + } - return hranaStmt; + return hranaStmt; } export function resultSetFromHrana(hranaRows: hrana.RowsResult): ResultSet { - const columns = hranaRows.columnNames.map(c => c ?? ""); - const columnTypes = hranaRows.columnDecltypes.map(c => c ?? ""); - const rows = hranaRows.rows; - const rowsAffected = hranaRows.affectedRowCount; - const lastInsertRowid = hranaRows.lastInsertRowid !== undefined - ? hranaRows.lastInsertRowid : undefined; - return new ResultSetImpl(columns, columnTypes, rows, rowsAffected, lastInsertRowid); + const columns = hranaRows.columnNames.map((c) => c ?? ""); + const columnTypes = hranaRows.columnDecltypes.map((c) => c ?? ""); + const rows = hranaRows.rows; + const rowsAffected = hranaRows.affectedRowCount; + const lastInsertRowid = + hranaRows.lastInsertRowid !== undefined + ? hranaRows.lastInsertRowid + : undefined; + return new ResultSetImpl( + columns, + columnTypes, + rows, + rowsAffected, + lastInsertRowid, + ); } export function mapHranaError(e: unknown): unknown { - if (e instanceof hrana.ClientError) { - const code = mapHranaErrorCode(e); - return new LibsqlError(e.message, code, undefined, e); - } - return e; + if (e instanceof hrana.ClientError) { + const code = mapHranaErrorCode(e); + return new LibsqlError(e.message, code, undefined, e); + } + return e; } function mapHranaErrorCode(e: hrana.ClientError): string { - if (e instanceof hrana.ResponseError && e.code !== undefined) { - return e.code; - } else if (e instanceof hrana.ProtoError) { - return "HRANA_PROTO_ERROR"; - } else if (e instanceof hrana.ClosedError) { - return e.cause instanceof hrana.ClientError - ? mapHranaErrorCode(e.cause) : "HRANA_CLOSED_ERROR"; - } else if (e instanceof hrana.WebSocketError) { - return "HRANA_WEBSOCKET_ERROR"; - } else if (e instanceof hrana.HttpServerError) { - return "SERVER_ERROR"; - } else if (e instanceof hrana.ProtocolVersionError) { - return "PROTOCOL_VERSION_ERROR"; - } else if (e instanceof hrana.InternalError) { - return "INTERNAL_ERROR"; - } else { - return "UNKNOWN"; - } + if (e instanceof hrana.ResponseError && e.code !== undefined) { + return e.code; + } else if (e instanceof hrana.ProtoError) { + return "HRANA_PROTO_ERROR"; + } else if (e instanceof hrana.ClosedError) { + return e.cause instanceof hrana.ClientError + ? mapHranaErrorCode(e.cause) + : "HRANA_CLOSED_ERROR"; + } else if (e instanceof hrana.WebSocketError) { + return "HRANA_WEBSOCKET_ERROR"; + } else if (e instanceof hrana.HttpServerError) { + return "SERVER_ERROR"; + } else if (e instanceof hrana.ProtocolVersionError) { + return "PROTOCOL_VERSION_ERROR"; + } else if (e instanceof hrana.InternalError) { + return "INTERNAL_ERROR"; + } else { + return "UNKNOWN"; + } } diff --git a/packages/libsql-client/src/http.ts b/packages/libsql-client/src/http.ts index 425812d2..49d06b17 100644 --- a/packages/libsql-client/src/http.ts +++ b/packages/libsql-client/src/http.ts @@ -1,13 +1,21 @@ import * as hrana from "@libsql/hrana-client"; import type { Config, Client } from "@libsql/core/api"; -import type { InStatement, ResultSet, Transaction, IntMode } from "@libsql/core/api"; +import type { + InStatement, + ResultSet, + Transaction, + IntMode, +} from "@libsql/core/api"; import { TransactionMode, LibsqlError } from "@libsql/core/api"; import type { ExpandedConfig } from "@libsql/core/config"; import { expandConfig } from "@libsql/core/config"; import { - HranaTransaction, executeHranaBatch, - stmtToHrana, resultSetFromHrana, mapHranaError, + HranaTransaction, + executeHranaBatch, + stmtToHrana, + resultSetFromHrana, + mapHranaError, } from "./hrana.js"; import { SqlCache } from "./sql_cache.js"; import { encodeBaseUrl } from "@libsql/core/uri"; @@ -16,168 +24,187 @@ import { supportedUrlLink } from "@libsql/core/util"; export * from "@libsql/core/api"; export function createClient(config: Config): Client { - return _createClient(expandConfig(config, true)); + return _createClient(expandConfig(config, true)); } /** @private */ export function _createClient(config: ExpandedConfig): Client { - if (config.scheme !== "https" && config.scheme !== "http") { - throw new LibsqlError( - 'The HTTP client supports only "libsql:", "https:" and "http:" URLs, ' + - `got ${JSON.stringify(config.scheme + ":")}. For more information, please read ${supportedUrlLink}`, - "URL_SCHEME_NOT_SUPPORTED", - ); - } - - if (config.encryptionKey !== undefined) { - throw new LibsqlError("Encryption key is not supported by the remote client.", "ENCRYPTION_KEY_NOT_SUPPORTED"); - } - - if (config.scheme === "http" && config.tls) { - throw new LibsqlError(`A "http:" URL cannot opt into TLS by using ?tls=1`, "URL_INVALID"); - } else if (config.scheme === "https" && !config.tls) { - throw new LibsqlError(`A "https:" URL cannot opt out of TLS by using ?tls=0`, "URL_INVALID"); - } - - const url = encodeBaseUrl(config.scheme, config.authority, config.path); - return new HttpClient(url, config.authToken, config.intMode, config.fetch); + if (config.scheme !== "https" && config.scheme !== "http") { + throw new LibsqlError( + 'The HTTP client supports only "libsql:", "https:" and "http:" URLs, ' + + `got ${JSON.stringify(config.scheme + ":")}. For more information, please read ${supportedUrlLink}`, + "URL_SCHEME_NOT_SUPPORTED", + ); + } + + if (config.encryptionKey !== undefined) { + throw new LibsqlError( + "Encryption key is not supported by the remote client.", + "ENCRYPTION_KEY_NOT_SUPPORTED", + ); + } + + if (config.scheme === "http" && config.tls) { + throw new LibsqlError( + `A "http:" URL cannot opt into TLS by using ?tls=1`, + "URL_INVALID", + ); + } else if (config.scheme === "https" && !config.tls) { + throw new LibsqlError( + `A "https:" URL cannot opt out of TLS by using ?tls=0`, + "URL_INVALID", + ); + } + + const url = encodeBaseUrl(config.scheme, config.authority, config.path); + return new HttpClient(url, config.authToken, config.intMode, config.fetch); } const sqlCacheCapacity = 30; export class HttpClient implements Client { - #client: hrana.HttpClient; - protocol: "http"; - - /** @private */ - constructor( - url: URL, - authToken: string | undefined, - intMode: IntMode, - customFetch: Function | undefined, - ) { - this.#client = hrana.openHttp(url, authToken, customFetch); - this.#client.intMode = intMode; - this.protocol = "http"; - } - - async execute(stmt: InStatement): Promise { - try { - const hranaStmt = stmtToHrana(stmt); - - // Pipeline all operations, so `hrana.HttpClient` can open the stream, execute the statement and - // close the stream in a single HTTP request. - let rowsPromise: Promise; - const stream = this.#client.openStream(); - try { - rowsPromise = stream.query(hranaStmt); - } finally { - stream.closeGracefully(); - } - - return resultSetFromHrana(await rowsPromise); - } catch (e) { - throw mapHranaError(e); - } - } - - async batch(stmts: Array, mode: TransactionMode = "deferred"): Promise> { - try { - const hranaStmts = stmts.map(stmtToHrana); - const version = await this.#client.getVersion(); - - // Pipeline all operations, so `hrana.HttpClient` can open the stream, execute the batch and - // close the stream in a single HTTP request. - let resultsPromise: Promise>; - const stream = this.#client.openStream(); - try { - // It makes sense to use a SQL cache even for a single batch, because it may contain the same - // statement repeated multiple times. - const sqlCache = new SqlCache(stream, sqlCacheCapacity); - sqlCache.apply(hranaStmts); - - // TODO: we do not use a cursor here, because it would cause three roundtrips: - // 1. pipeline request to store SQL texts - // 2. cursor request - // 3. pipeline request to close the stream - const batch = stream.batch(false); - resultsPromise = executeHranaBatch(mode, version, batch, hranaStmts); - } finally { - stream.closeGracefully(); - } - - return await resultsPromise; - } catch (e) { - throw mapHranaError(e); - } - } - - async transaction(mode: TransactionMode = "write"): Promise { - try { - const version = await this.#client.getVersion(); - return new HttpTransaction(this.#client.openStream(), mode, version); - } catch (e) { - throw mapHranaError(e); - } - } - - async executeMultiple(sql: string): Promise { - try { - // Pipeline all operations, so `hrana.HttpClient` can open the stream, execute the sequence and - // close the stream in a single HTTP request. - let promise: Promise; - const stream = this.#client.openStream(); - try { - promise = stream.sequence(sql); - } finally { - stream.closeGracefully(); - } - - await promise; - } catch (e) { - throw mapHranaError(e); - } - } - - sync(): Promise { - throw new LibsqlError("sync not supported in http mode", "SYNC_NOT_SUPPORTED"); - } - - close(): void { - this.#client.close(); - } - - get closed(): boolean { - return this.#client.closed; - } + #client: hrana.HttpClient; + protocol: "http"; + + /** @private */ + constructor( + url: URL, + authToken: string | undefined, + intMode: IntMode, + customFetch: Function | undefined, + ) { + this.#client = hrana.openHttp(url, authToken, customFetch); + this.#client.intMode = intMode; + this.protocol = "http"; + } + + async execute(stmt: InStatement): Promise { + try { + const hranaStmt = stmtToHrana(stmt); + + // Pipeline all operations, so `hrana.HttpClient` can open the stream, execute the statement and + // close the stream in a single HTTP request. + let rowsPromise: Promise; + const stream = this.#client.openStream(); + try { + rowsPromise = stream.query(hranaStmt); + } finally { + stream.closeGracefully(); + } + + return resultSetFromHrana(await rowsPromise); + } catch (e) { + throw mapHranaError(e); + } + } + + async batch( + stmts: Array, + mode: TransactionMode = "deferred", + ): Promise> { + try { + const hranaStmts = stmts.map(stmtToHrana); + const version = await this.#client.getVersion(); + + // Pipeline all operations, so `hrana.HttpClient` can open the stream, execute the batch and + // close the stream in a single HTTP request. + let resultsPromise: Promise>; + const stream = this.#client.openStream(); + try { + // It makes sense to use a SQL cache even for a single batch, because it may contain the same + // statement repeated multiple times. + const sqlCache = new SqlCache(stream, sqlCacheCapacity); + sqlCache.apply(hranaStmts); + + // TODO: we do not use a cursor here, because it would cause three roundtrips: + // 1. pipeline request to store SQL texts + // 2. cursor request + // 3. pipeline request to close the stream + const batch = stream.batch(false); + resultsPromise = executeHranaBatch(mode, version, batch, hranaStmts); + } finally { + stream.closeGracefully(); + } + + return await resultsPromise; + } catch (e) { + throw mapHranaError(e); + } + } + + async transaction(mode: TransactionMode = "write"): Promise { + try { + const version = await this.#client.getVersion(); + return new HttpTransaction(this.#client.openStream(), mode, version); + } catch (e) { + throw mapHranaError(e); + } + } + + async executeMultiple(sql: string): Promise { + try { + // Pipeline all operations, so `hrana.HttpClient` can open the stream, execute the sequence and + // close the stream in a single HTTP request. + let promise: Promise; + const stream = this.#client.openStream(); + try { + promise = stream.sequence(sql); + } finally { + stream.closeGracefully(); + } + + await promise; + } catch (e) { + throw mapHranaError(e); + } + } + + sync(): Promise { + throw new LibsqlError( + "sync not supported in http mode", + "SYNC_NOT_SUPPORTED", + ); + } + + close(): void { + this.#client.close(); + } + + get closed(): boolean { + return this.#client.closed; + } } export class HttpTransaction extends HranaTransaction implements Transaction { - #stream: hrana.HttpStream; - #sqlCache: SqlCache; - - /** @private */ - constructor(stream: hrana.HttpStream, mode: TransactionMode, version: hrana.ProtocolVersion) { - super(mode, version); - this.#stream = stream; - this.#sqlCache = new SqlCache(stream, sqlCacheCapacity); - } - - /** @private */ - override _getStream(): hrana.Stream { - return this.#stream; - } - - /** @private */ - override _getSqlCache(): SqlCache { - return this.#sqlCache; - } - - override close(): void { - this.#stream.close(); - } - - override get closed(): boolean { - return this.#stream.closed; - } + #stream: hrana.HttpStream; + #sqlCache: SqlCache; + + /** @private */ + constructor( + stream: hrana.HttpStream, + mode: TransactionMode, + version: hrana.ProtocolVersion, + ) { + super(mode, version); + this.#stream = stream; + this.#sqlCache = new SqlCache(stream, sqlCacheCapacity); + } + + /** @private */ + override _getStream(): hrana.Stream { + return this.#stream; + } + + /** @private */ + override _getSqlCache(): SqlCache { + return this.#sqlCache; + } + + override close(): void { + this.#stream.close(); + } + + override get closed(): boolean { + return this.#stream.closed; + } } diff --git a/packages/libsql-client/src/node.ts b/packages/libsql-client/src/node.ts index 8284ca25..b9cb6a17 100644 --- a/packages/libsql-client/src/node.ts +++ b/packages/libsql-client/src/node.ts @@ -13,15 +13,15 @@ export * from "@libsql/core/api"; * You must pass at least an `url` in the {@link Config} object. */ export function createClient(config: Config): Client { - return _createClient(expandConfig(config, true)); + return _createClient(expandConfig(config, true)); } function _createClient(config: ExpandedConfig) { - if (config.scheme === "wss" || config.scheme === "ws") { - return _createWsClient(config); - } else if (config.scheme === "https" || config.scheme === "http") { - return _createHttpClient(config); - } else { - return _createSqlite3Client(config); - } + if (config.scheme === "wss" || config.scheme === "ws") { + return _createWsClient(config); + } else if (config.scheme === "https" || config.scheme === "http") { + return _createHttpClient(config); + } else { + return _createSqlite3Client(config); + } } diff --git a/packages/libsql-client/src/sql_cache.ts b/packages/libsql-client/src/sql_cache.ts index aa8a79cc..7509a569 100644 --- a/packages/libsql-client/src/sql_cache.ts +++ b/packages/libsql-client/src/sql_cache.ts @@ -1,98 +1,98 @@ import type * as hrana from "@libsql/hrana-client"; export class SqlCache { - #owner: hrana.SqlOwner; - #sqls: Lru; - capacity: number; + #owner: hrana.SqlOwner; + #sqls: Lru; + capacity: number; - constructor(owner: hrana.SqlOwner, capacity: number) { - this.#owner = owner; - this.#sqls = new Lru(); - this.capacity = capacity; - } - - // Replaces SQL strings with cached `hrana.Sql` objects in the statements in `hranaStmts`. After this - // function returns, we guarantee that all `hranaStmts` refer to valid (not closed) `hrana.Sql` objects, - // but _we may invalidate any other `hrana.Sql` objects_ (by closing them, thus removing them from the - // server). - // - // In practice, this means that after calling this function, you can use the statements only up to the - // first `await`, because concurrent code may also use the cache and invalidate those statements. - apply(hranaStmts: Array): void { - if (this.capacity <= 0) { - return; - } + constructor(owner: hrana.SqlOwner, capacity: number) { + this.#owner = owner; + this.#sqls = new Lru(); + this.capacity = capacity; + } - const usedSqlObjs: Set = new Set(); + // Replaces SQL strings with cached `hrana.Sql` objects in the statements in `hranaStmts`. After this + // function returns, we guarantee that all `hranaStmts` refer to valid (not closed) `hrana.Sql` objects, + // but _we may invalidate any other `hrana.Sql` objects_ (by closing them, thus removing them from the + // server). + // + // In practice, this means that after calling this function, you can use the statements only up to the + // first `await`, because concurrent code may also use the cache and invalidate those statements. + apply(hranaStmts: Array): void { + if (this.capacity <= 0) { + return; + } - for (const hranaStmt of hranaStmts) { - if (typeof hranaStmt.sql !== "string") { - continue; - } - const sqlText = hranaStmt.sql; + const usedSqlObjs: Set = new Set(); - let sqlObj = this.#sqls.get(sqlText); - if (sqlObj === undefined) { - while (this.#sqls.size + 1 > this.capacity) { - const [evictSqlText, evictSqlObj] = this.#sqls.peekLru()!; - if (usedSqlObjs.has(evictSqlObj)) { - // The SQL object that we are trying to evict is already in use in this batch, so we - // must not evict and close it. - break; - } - evictSqlObj.close(); - this.#sqls.delete(evictSqlText); - } + for (const hranaStmt of hranaStmts) { + if (typeof hranaStmt.sql !== "string") { + continue; + } + const sqlText = hranaStmt.sql; - if (this.#sqls.size + 1 <= this.capacity) { - sqlObj = this.#owner.storeSql(sqlText); - this.#sqls.set(sqlText, sqlObj); - } - } + let sqlObj = this.#sqls.get(sqlText); + if (sqlObj === undefined) { + while (this.#sqls.size + 1 > this.capacity) { + const [evictSqlText, evictSqlObj] = this.#sqls.peekLru()!; + if (usedSqlObjs.has(evictSqlObj)) { + // The SQL object that we are trying to evict is already in use in this batch, so we + // must not evict and close it. + break; + } + evictSqlObj.close(); + this.#sqls.delete(evictSqlText); + } - if (sqlObj !== undefined) { - hranaStmt.sql = sqlObj; - usedSqlObjs.add(sqlObj); - } + if (this.#sqls.size + 1 <= this.capacity) { + sqlObj = this.#owner.storeSql(sqlText); + this.#sqls.set(sqlText, sqlObj); } + } + + if (sqlObj !== undefined) { + hranaStmt.sql = sqlObj; + usedSqlObjs.add(sqlObj); + } } + } } class Lru { - // This maps keys to the cache values. The entries are ordered by their last use (entires that were used - // most recently are at the end). - #cache: Map; + // This maps keys to the cache values. The entries are ordered by their last use (entires that were used + // most recently are at the end). + #cache: Map; - constructor() { - this.#cache = new Map(); - } + constructor() { + this.#cache = new Map(); + } - get(key: K): V | undefined { - const value = this.#cache.get(key); - if (value !== undefined) { - // move the entry to the back of the Map - this.#cache.delete(key); - this.#cache.set(key, value); - } - return value; + get(key: K): V | undefined { + const value = this.#cache.get(key); + if (value !== undefined) { + // move the entry to the back of the Map + this.#cache.delete(key); + this.#cache.set(key, value); } + return value; + } - set(key: K, value: V): void { - this.#cache.set(key, value); - } + set(key: K, value: V): void { + this.#cache.set(key, value); + } - peekLru(): [K, V] | undefined { - for (const entry of this.#cache.entries()) { - return entry; - } - return undefined; + peekLru(): [K, V] | undefined { + for (const entry of this.#cache.entries()) { + return entry; } + return undefined; + } - delete(key: K): void { - this.#cache.delete(key); - } + delete(key: K): void { + this.#cache.delete(key); + } - get size(): number { - return this.#cache.size; - } + get size(): number { + return this.#cache.size; + } } diff --git a/packages/libsql-client/src/sqlite3.ts b/packages/libsql-client/src/sqlite3.ts index f5d23936..f2833a6b 100644 --- a/packages/libsql-client/src/sqlite3.ts +++ b/packages/libsql-client/src/sqlite3.ts @@ -2,354 +2,410 @@ import Database from "libsql"; import { Buffer } from "node:buffer"; import type { - Config, IntMode, Client, Transaction, TransactionMode, - ResultSet, Row, Value, InValue, InStatement, + Config, + IntMode, + Client, + Transaction, + TransactionMode, + ResultSet, + Row, + Value, + InValue, + InStatement, } from "@libsql/core/api"; import { LibsqlError } from "@libsql/core/api"; import type { ExpandedConfig } from "@libsql/core/config"; import { expandConfig } from "@libsql/core/config"; -import { supportedUrlLink, transactionModeToBegin, ResultSetImpl } from "@libsql/core/util"; +import { + supportedUrlLink, + transactionModeToBegin, + ResultSetImpl, +} from "@libsql/core/util"; export * from "@libsql/core/api"; export function createClient(config: Config): Client { - return _createClient(expandConfig(config, true)); + return _createClient(expandConfig(config, true)); } /** @private */ export function _createClient(config: ExpandedConfig): Client { - if (config.scheme !== "file") { - throw new LibsqlError( - `URL scheme ${JSON.stringify(config.scheme + ":")} is not supported by the local sqlite3 client. ` + - `For more information, please read ${supportedUrlLink}`, - "URL_SCHEME_NOT_SUPPORTED", - ); + if (config.scheme !== "file") { + throw new LibsqlError( + `URL scheme ${JSON.stringify(config.scheme + ":")} is not supported by the local sqlite3 client. ` + + `For more information, please read ${supportedUrlLink}`, + "URL_SCHEME_NOT_SUPPORTED", + ); + } + + const authority = config.authority; + if (authority !== undefined) { + const host = authority.host.toLowerCase(); + if (host !== "" && host !== "localhost") { + throw new LibsqlError( + `Invalid host in file URL: ${JSON.stringify(authority.host)}. ` + + 'A "file:" URL with an absolute path should start with one slash ("file:/absolute/path.db") ' + + 'or with three slashes ("file:///absolute/path.db"). ' + + `For more information, please read ${supportedUrlLink}`, + "URL_INVALID", + ); } - const authority = config.authority; - if (authority !== undefined) { - const host = authority.host.toLowerCase(); - if (host !== "" && host !== "localhost") { - throw new LibsqlError( - `Invalid host in file URL: ${JSON.stringify(authority.host)}. ` + - 'A "file:" URL with an absolute path should start with one slash ("file:/absolute/path.db") ' + - 'or with three slashes ("file:///absolute/path.db"). ' + - `For more information, please read ${supportedUrlLink}`, - "URL_INVALID", - ); - } - - if (authority.port !== undefined) { - throw new LibsqlError("File URL cannot have a port", "URL_INVALID"); - } - if (authority.userinfo !== undefined) { - throw new LibsqlError("File URL cannot have username and password", "URL_INVALID"); - } + if (authority.port !== undefined) { + throw new LibsqlError("File URL cannot have a port", "URL_INVALID"); + } + if (authority.userinfo !== undefined) { + throw new LibsqlError( + "File URL cannot have username and password", + "URL_INVALID", + ); } + } - const path = config.path; - const options = { - authToken: config.authToken, - encryptionKey: config.encryptionKey, - syncUrl: config.syncUrl, - syncInterval: config.syncInterval, - }; + const path = config.path; + const options = { + authToken: config.authToken, + encryptionKey: config.encryptionKey, + syncUrl: config.syncUrl, + syncInterval: config.syncInterval, + }; - const db = new Database(path, options); + const db = new Database(path, options); - executeStmt(db, "SELECT 1 AS checkThatTheDatabaseCanBeOpened", config.intMode); + executeStmt( + db, + "SELECT 1 AS checkThatTheDatabaseCanBeOpened", + config.intMode, + ); - return new Sqlite3Client(path, options, db, config.intMode); + return new Sqlite3Client(path, options, db, config.intMode); } export class Sqlite3Client implements Client { - #path: string; - #options: Database.Options; - #db: Database.Database | null; - #intMode: IntMode; - closed: boolean; - protocol: "file"; - - /** @private */ - constructor(path: string, options: Database.Options, db: Database.Database, intMode: IntMode) { - this.#path = path; - this.#options = options; - this.#db = db; - this.#intMode = intMode; - this.closed = false; - this.protocol = "file"; - } - - async execute(stmt: InStatement): Promise { - this.#checkNotClosed(); - return executeStmt(this.#getDb(), stmt, this.#intMode); - } - - async batch(stmts: Array, mode: TransactionMode = "deferred"): Promise> { - this.#checkNotClosed(); - const db = this.#getDb(); - try { - executeStmt(db, transactionModeToBegin(mode), this.#intMode); - const resultSets = stmts.map((stmt) => { - if (!db.inTransaction) { - throw new LibsqlError("The transaction has been rolled back", "TRANSACTION_CLOSED"); - } - return executeStmt(db, stmt, this.#intMode); - }); - executeStmt(db, "COMMIT", this.#intMode) - return resultSets; - } finally { - if (db.inTransaction) { - executeStmt(db, "ROLLBACK", this.#intMode); - } + #path: string; + #options: Database.Options; + #db: Database.Database | null; + #intMode: IntMode; + closed: boolean; + protocol: "file"; + + /** @private */ + constructor( + path: string, + options: Database.Options, + db: Database.Database, + intMode: IntMode, + ) { + this.#path = path; + this.#options = options; + this.#db = db; + this.#intMode = intMode; + this.closed = false; + this.protocol = "file"; + } + + async execute(stmt: InStatement): Promise { + this.#checkNotClosed(); + return executeStmt(this.#getDb(), stmt, this.#intMode); + } + + async batch( + stmts: Array, + mode: TransactionMode = "deferred", + ): Promise> { + this.#checkNotClosed(); + const db = this.#getDb(); + try { + executeStmt(db, transactionModeToBegin(mode), this.#intMode); + const resultSets = stmts.map((stmt) => { + if (!db.inTransaction) { + throw new LibsqlError( + "The transaction has been rolled back", + "TRANSACTION_CLOSED", + ); } + return executeStmt(db, stmt, this.#intMode); + }); + executeStmt(db, "COMMIT", this.#intMode); + return resultSets; + } finally { + if (db.inTransaction) { + executeStmt(db, "ROLLBACK", this.#intMode); + } } - - async transaction(mode: TransactionMode = "write"): Promise { - const db = this.#getDb(); - executeStmt(db, transactionModeToBegin(mode), this.#intMode); - this.#db = null; // A new connection will be lazily created on next use - return new Sqlite3Transaction(db, this.#intMode); - } - - async executeMultiple(sql: string): Promise { - this.#checkNotClosed(); - const db = this.#getDb(); - try { - return executeMultiple(db, sql); - } finally { - if (db.inTransaction) { - executeStmt(db, "ROLLBACK", this.#intMode); - } - } + } + + async transaction(mode: TransactionMode = "write"): Promise { + const db = this.#getDb(); + executeStmt(db, transactionModeToBegin(mode), this.#intMode); + this.#db = null; // A new connection will be lazily created on next use + return new Sqlite3Transaction(db, this.#intMode); + } + + async executeMultiple(sql: string): Promise { + this.#checkNotClosed(); + const db = this.#getDb(); + try { + return executeMultiple(db, sql); + } finally { + if (db.inTransaction) { + executeStmt(db, "ROLLBACK", this.#intMode); + } } + } + async sync(): Promise { + this.#checkNotClosed(); + await this.#getDb().sync(); + } - async sync(): Promise { - this.#checkNotClosed(); - await this.#getDb().sync(); - } - - close(): void { - this.closed = true; - if (this.#db !== null) { - this.#db.close(); - } + close(): void { + this.closed = true; + if (this.#db !== null) { + this.#db.close(); } + } - #checkNotClosed(): void { - if (this.closed) { - throw new LibsqlError("The client is closed", "CLIENT_CLOSED"); - } + #checkNotClosed(): void { + if (this.closed) { + throw new LibsqlError("The client is closed", "CLIENT_CLOSED"); } + } - // Lazily creates the database connection and returns it - #getDb(): Database.Database { - if (this.#db === null) { - this.#db = new Database(this.#path, this.#options); - } - return this.#db; + // Lazily creates the database connection and returns it + #getDb(): Database.Database { + if (this.#db === null) { + this.#db = new Database(this.#path, this.#options); } + return this.#db; + } } export class Sqlite3Transaction implements Transaction { - #database: Database.Database; - #intMode: IntMode; - - /** @private */ - constructor(database: Database.Database, intMode: IntMode) { - this.#database = database; - this.#intMode = intMode; - } - - async execute(stmt: InStatement): Promise { - this.#checkNotClosed(); - return executeStmt(this.#database, stmt, this.#intMode); - } - - async batch(stmts: Array): Promise> { - return stmts.map((stmt) => { - this.#checkNotClosed(); - return executeStmt(this.#database, stmt, this.#intMode); - }); - } - - async executeMultiple(sql: string): Promise { - this.#checkNotClosed(); - return executeMultiple(this.#database, sql); + #database: Database.Database; + #intMode: IntMode; + + /** @private */ + constructor(database: Database.Database, intMode: IntMode) { + this.#database = database; + this.#intMode = intMode; + } + + async execute(stmt: InStatement): Promise { + this.#checkNotClosed(); + return executeStmt(this.#database, stmt, this.#intMode); + } + + async batch(stmts: Array): Promise> { + return stmts.map((stmt) => { + this.#checkNotClosed(); + return executeStmt(this.#database, stmt, this.#intMode); + }); + } + + async executeMultiple(sql: string): Promise { + this.#checkNotClosed(); + return executeMultiple(this.#database, sql); + } + + async rollback(): Promise { + if (!this.#database.open) { + return; } - - async rollback(): Promise { - if (!this.#database.open) { - return; - } - this.#checkNotClosed(); - executeStmt(this.#database, "ROLLBACK", this.#intMode); - } - - async commit(): Promise { - this.#checkNotClosed(); - executeStmt(this.#database, "COMMIT", this.#intMode); + this.#checkNotClosed(); + executeStmt(this.#database, "ROLLBACK", this.#intMode); + } + + async commit(): Promise { + this.#checkNotClosed(); + executeStmt(this.#database, "COMMIT", this.#intMode); + } + + close(): void { + if (this.#database.inTransaction) { + executeStmt(this.#database, "ROLLBACK", this.#intMode); } + } - close(): void { - if (this.#database.inTransaction) { - executeStmt(this.#database, "ROLLBACK", this.#intMode); - } - } + get closed(): boolean { + return !this.#database.inTransaction; + } - get closed(): boolean { - return !this.#database.inTransaction; - } - - #checkNotClosed(): void { - if (this.closed) { - throw new LibsqlError("The transaction is closed", "TRANSACTION_CLOSED"); - } + #checkNotClosed(): void { + if (this.closed) { + throw new LibsqlError("The transaction is closed", "TRANSACTION_CLOSED"); } + } } -function executeStmt(db: Database.Database, stmt: InStatement, intMode: IntMode): ResultSet { - let sql: string; - let args: Array | Record; - if (typeof stmt === "string") { - sql = stmt; - args = []; +function executeStmt( + db: Database.Database, + stmt: InStatement, + intMode: IntMode, +): ResultSet { + let sql: string; + let args: Array | Record; + if (typeof stmt === "string") { + sql = stmt; + args = []; + } else { + sql = stmt.sql; + if (Array.isArray(stmt.args)) { + args = stmt.args.map((value) => valueToSql(value, intMode)); } else { - sql = stmt.sql; - if (Array.isArray(stmt.args)) { - args = stmt.args.map((value) => valueToSql(value, intMode)); - } else { - args = {}; - for (const name in stmt.args) { - const argName = (name[0] === "@" || name[0] === "$" || name[0] === ":") - ? name.substring(1) : name; - args[argName] = valueToSql(stmt.args[name], intMode); - } - } + args = {}; + for (const name in stmt.args) { + const argName = + name[0] === "@" || name[0] === "$" || name[0] === ":" + ? name.substring(1) + : name; + args[argName] = valueToSql(stmt.args[name], intMode); + } } + } + + try { + const sqlStmt = db.prepare(sql); + sqlStmt.safeIntegers(true); + let returnsData = true; try { - const sqlStmt = db.prepare(sql); - sqlStmt.safeIntegers(true); - - let returnsData = true; - try { - sqlStmt.raw(true); - } catch { - // raw() throws an exception if the statement does not return data - returnsData = false; - } + sqlStmt.raw(true); + } catch { + // raw() throws an exception if the statement does not return data + returnsData = false; + } - if (returnsData) { - const columns = Array.from(sqlStmt.columns().map(col => col.name)); - const columnTypes = Array.from(sqlStmt.columns().map(col => col.type ?? "")); - const rows = sqlStmt.all(args).map((sqlRow) => { - return rowFromSql(sqlRow as Array, columns, intMode); - }); - // TODO: can we get this info from better-sqlite3? - const rowsAffected = 0; - const lastInsertRowid = undefined; - return new ResultSetImpl(columns, columnTypes, rows, rowsAffected, lastInsertRowid); - } else { - const info = sqlStmt.run(args); - const rowsAffected = info.changes; - const lastInsertRowid = BigInt(info.lastInsertRowid); - return new ResultSetImpl([], [], [], rowsAffected, lastInsertRowid); - } - } catch (e) { - throw mapSqliteError(e); + if (returnsData) { + const columns = Array.from(sqlStmt.columns().map((col) => col.name)); + const columnTypes = Array.from( + sqlStmt.columns().map((col) => col.type ?? ""), + ); + const rows = sqlStmt.all(args).map((sqlRow) => { + return rowFromSql(sqlRow as Array, columns, intMode); + }); + // TODO: can we get this info from better-sqlite3? + const rowsAffected = 0; + const lastInsertRowid = undefined; + return new ResultSetImpl( + columns, + columnTypes, + rows, + rowsAffected, + lastInsertRowid, + ); + } else { + const info = sqlStmt.run(args); + const rowsAffected = info.changes; + const lastInsertRowid = BigInt(info.lastInsertRowid); + return new ResultSetImpl([], [], [], rowsAffected, lastInsertRowid); } + } catch (e) { + throw mapSqliteError(e); + } } -function rowFromSql(sqlRow: Array, columns: Array, intMode: IntMode): Row { - const row = {}; - // make sure that the "length" property is not enumerable - Object.defineProperty(row, "length", { value: sqlRow.length }); - for (let i = 0; i < sqlRow.length; ++i) { - const value = valueFromSql(sqlRow[i], intMode); - Object.defineProperty(row, i, { value }); - - const column = columns[i]; - if (!Object.hasOwn(row, column)) { - Object.defineProperty(row, column, { value, enumerable: true, configurable: true, writable: true }); - } +function rowFromSql( + sqlRow: Array, + columns: Array, + intMode: IntMode, +): Row { + const row = {}; + // make sure that the "length" property is not enumerable + Object.defineProperty(row, "length", { value: sqlRow.length }); + for (let i = 0; i < sqlRow.length; ++i) { + const value = valueFromSql(sqlRow[i], intMode); + Object.defineProperty(row, i, { value }); + + const column = columns[i]; + if (!Object.hasOwn(row, column)) { + Object.defineProperty(row, column, { + value, + enumerable: true, + configurable: true, + writable: true, + }); } - return row as Row; + } + return row as Row; } function valueFromSql(sqlValue: unknown, intMode: IntMode): Value { - if (typeof sqlValue === "bigint") { - if (intMode === "number") { - if (sqlValue < minSafeBigint || sqlValue > maxSafeBigint) { - throw new RangeError( - "Received integer which cannot be safely represented as a JavaScript number" - ); - } - return Number(sqlValue); - } else if (intMode === "bigint") { - return sqlValue; - } else if (intMode === "string") { - return ""+sqlValue; - } else { - throw new Error("Invalid value for IntMode"); - } - } else if (sqlValue instanceof Buffer) { - return sqlValue.buffer; + if (typeof sqlValue === "bigint") { + if (intMode === "number") { + if (sqlValue < minSafeBigint || sqlValue > maxSafeBigint) { + throw new RangeError( + "Received integer which cannot be safely represented as a JavaScript number", + ); + } + return Number(sqlValue); + } else if (intMode === "bigint") { + return sqlValue; + } else if (intMode === "string") { + return "" + sqlValue; + } else { + throw new Error("Invalid value for IntMode"); } - return sqlValue as Value; + } else if (sqlValue instanceof Buffer) { + return sqlValue.buffer; + } + return sqlValue as Value; } const minSafeBigint = -9007199254740991n; const maxSafeBigint = 9007199254740991n; function valueToSql(value: InValue, intMode: IntMode): unknown { - if (typeof value === "number") { - if (!Number.isFinite(value)) { - throw new RangeError("Only finite numbers (not Infinity or NaN) can be passed as arguments"); - } - return value; - } else if (typeof value === "bigint") { - if (value < minInteger || value > maxInteger) { - throw new RangeError( - "bigint is too large to be represented as a 64-bit integer and passed as argument" - ); - } - return value; - } else if (typeof value === "boolean") { - switch(intMode) { - case "bigint": - return value ? 1n : 0n; - case "string": - return value ? "1" : "0"; - default: - return value ? 1 : 0; - } - } else if (value instanceof ArrayBuffer) { - return Buffer.from(value); - } else if (value instanceof Date) { - return value.valueOf(); - } else if (value === undefined) { - throw new TypeError("undefined cannot be passed as argument to the database"); - } else { - return value; + if (typeof value === "number") { + if (!Number.isFinite(value)) { + throw new RangeError( + "Only finite numbers (not Infinity or NaN) can be passed as arguments", + ); + } + return value; + } else if (typeof value === "bigint") { + if (value < minInteger || value > maxInteger) { + throw new RangeError( + "bigint is too large to be represented as a 64-bit integer and passed as argument", + ); + } + return value; + } else if (typeof value === "boolean") { + switch (intMode) { + case "bigint": + return value ? 1n : 0n; + case "string": + return value ? "1" : "0"; + default: + return value ? 1 : 0; } + } else if (value instanceof ArrayBuffer) { + return Buffer.from(value); + } else if (value instanceof Date) { + return value.valueOf(); + } else if (value === undefined) { + throw new TypeError( + "undefined cannot be passed as argument to the database", + ); + } else { + return value; + } } const minInteger = -9223372036854775808n; const maxInteger = 9223372036854775807n; function executeMultiple(db: Database.Database, sql: string): void { - try { - db.exec(sql); - } catch (e) { - throw mapSqliteError(e); - } + try { + db.exec(sql); + } catch (e) { + throw mapSqliteError(e); + } } function mapSqliteError(e: unknown): unknown { - if (e instanceof Database.SqliteError) { - return new LibsqlError(e.message, e.code, e.rawCode, e); - } - return e; + if (e instanceof Database.SqliteError) { + return new LibsqlError(e.message, e.code, e.rawCode, e); + } + return e; } diff --git a/packages/libsql-client/src/web.ts b/packages/libsql-client/src/web.ts index 3e4d2c03..59bced8b 100644 --- a/packages/libsql-client/src/web.ts +++ b/packages/libsql-client/src/web.ts @@ -10,20 +10,20 @@ import { _createClient as _createHttpClient } from "./http.js"; export * from "@libsql/core/api"; export function createClient(config: Config): Client { - return _createClient(expandConfig(config, true)); + return _createClient(expandConfig(config, true)); } /** @private */ export function _createClient(config: ExpandedConfig): Client { - if (config.scheme === "ws" || config.scheme === "wss") { - return _createWsClient(config); - } else if (config.scheme === "http" || config.scheme === "https") { - return _createHttpClient(config); - } else { - throw new LibsqlError( - 'The client that uses Web standard APIs supports only "libsql:", "wss:", "ws:", "https:" and "http:" URLs, ' + - `got ${JSON.stringify(config.scheme + ":")}. For more information, please read ${supportedUrlLink}`, - "URL_SCHEME_NOT_SUPPORTED", - ); - } + if (config.scheme === "ws" || config.scheme === "wss") { + return _createWsClient(config); + } else if (config.scheme === "http" || config.scheme === "https") { + return _createHttpClient(config); + } else { + throw new LibsqlError( + 'The client that uses Web standard APIs supports only "libsql:", "wss:", "ws:", "https:" and "http:" URLs, ' + + `got ${JSON.stringify(config.scheme + ":")}. For more information, please read ${supportedUrlLink}`, + "URL_SCHEME_NOT_SUPPORTED", + ); + } } diff --git a/packages/libsql-client/src/ws.ts b/packages/libsql-client/src/ws.ts index 5b88c4d3..2c47c1e7 100644 --- a/packages/libsql-client/src/ws.ts +++ b/packages/libsql-client/src/ws.ts @@ -1,12 +1,22 @@ import * as hrana from "@libsql/hrana-client"; -import type { Config, IntMode, Client, Transaction, ResultSet, InStatement } from "@libsql/core/api"; +import type { + Config, + IntMode, + Client, + Transaction, + ResultSet, + InStatement, +} from "@libsql/core/api"; import { TransactionMode, LibsqlError } from "@libsql/core/api"; import type { ExpandedConfig } from "@libsql/core/config"; import { expandConfig } from "@libsql/core/config"; import { - HranaTransaction, executeHranaBatch, - stmtToHrana, resultSetFromHrana, mapHranaError, + HranaTransaction, + executeHranaBatch, + stmtToHrana, + resultSetFromHrana, + mapHranaError, } from "./hrana.js"; import { SqlCache } from "./sql_cache.js"; import { encodeBaseUrl } from "@libsql/core/uri"; @@ -15,313 +25,344 @@ import { supportedUrlLink } from "@libsql/core/util"; export * from "@libsql/core/api"; export function createClient(config: Config): WsClient { - return _createClient(expandConfig(config, false)); + return _createClient(expandConfig(config, false)); } /** @private */ export function _createClient(config: ExpandedConfig): WsClient { - if (config.scheme !== "wss" && config.scheme !== "ws") { - throw new LibsqlError( - 'The WebSocket client supports only "libsql:", "wss:" and "ws:" URLs, ' + - `got ${JSON.stringify(config.scheme + ":")}. For more information, please read ${supportedUrlLink}`, - "URL_SCHEME_NOT_SUPPORTED", - ); + if (config.scheme !== "wss" && config.scheme !== "ws") { + throw new LibsqlError( + 'The WebSocket client supports only "libsql:", "wss:" and "ws:" URLs, ' + + `got ${JSON.stringify(config.scheme + ":")}. For more information, please read ${supportedUrlLink}`, + "URL_SCHEME_NOT_SUPPORTED", + ); + } + + if (config.encryptionKey !== undefined) { + throw new LibsqlError( + "Encryption key is not supported by the remote client.", + "ENCRYPTION_KEY_NOT_SUPPORTED", + ); + } + + if (config.scheme === "ws" && config.tls) { + throw new LibsqlError( + `A "ws:" URL cannot opt into TLS by using ?tls=1`, + "URL_INVALID", + ); + } else if (config.scheme === "wss" && !config.tls) { + throw new LibsqlError( + `A "wss:" URL cannot opt out of TLS by using ?tls=0`, + "URL_INVALID", + ); + } + + const url = encodeBaseUrl(config.scheme, config.authority, config.path); + + let client: hrana.WsClient; + try { + client = hrana.openWs(url, config.authToken); + } catch (e) { + if (e instanceof hrana.WebSocketUnsupportedError) { + const suggestedScheme = config.scheme === "wss" ? "https" : "http"; + const suggestedUrl = encodeBaseUrl( + suggestedScheme, + config.authority, + config.path, + ); + throw new LibsqlError( + "This environment does not support WebSockets, please switch to the HTTP client by using " + + `a "${suggestedScheme}:" URL (${JSON.stringify(suggestedUrl)}). ` + + `For more information, please read ${supportedUrlLink}`, + "WEBSOCKETS_NOT_SUPPORTED", + ); } + throw mapHranaError(e); + } - if (config.encryptionKey !== undefined) { - throw new LibsqlError("Encryption key is not supported by the remote client.", "ENCRYPTION_KEY_NOT_SUPPORTED"); - } - - if (config.scheme === "ws" && config.tls) { - throw new LibsqlError(`A "ws:" URL cannot opt into TLS by using ?tls=1`, "URL_INVALID"); - } else if (config.scheme === "wss" && !config.tls) { - throw new LibsqlError(`A "wss:" URL cannot opt out of TLS by using ?tls=0`, "URL_INVALID"); - } - - const url = encodeBaseUrl(config.scheme, config.authority, config.path); - - let client: hrana.WsClient; - try { - client = hrana.openWs(url, config.authToken); - } catch (e) { - if (e instanceof hrana.WebSocketUnsupportedError) { - const suggestedScheme = config.scheme === "wss" ? "https" : "http"; - const suggestedUrl = encodeBaseUrl(suggestedScheme, config.authority, config.path); - throw new LibsqlError( - "This environment does not support WebSockets, please switch to the HTTP client by using " + - `a "${suggestedScheme}:" URL (${JSON.stringify(suggestedUrl)}). ` + - `For more information, please read ${supportedUrlLink}`, - "WEBSOCKETS_NOT_SUPPORTED", - ); - } - throw mapHranaError(e); - } - - return new WsClient(client, url, config.authToken, config.intMode); + return new WsClient(client, url, config.authToken, config.intMode); } // This object maintains state for a single WebSocket connection. interface ConnState { - // The Hrana client (which corresponds to a single WebSocket). - client: hrana.WsClient; - // We can cache SQL texts on the server only if the server supports Hrana 2. But to get the server - // version, we need to wait for the WebSocket handshake to complete, so this value is initially - // `undefined`, until we find out the version. - useSqlCache: boolean | undefined; - // The cache of SQL texts stored on the server. Initially has capacity 0, but it is set to - // `sqlCacheCapacity` when `useSqlCache` is set to `true`. - sqlCache: SqlCache, - // The time when the connection was opened. - openTime: Date; - // Set of all `StreamState`-s that were opened from this connection. We can safely close the connection - // only when this is empty. - streamStates: Set; + // The Hrana client (which corresponds to a single WebSocket). + client: hrana.WsClient; + // We can cache SQL texts on the server only if the server supports Hrana 2. But to get the server + // version, we need to wait for the WebSocket handshake to complete, so this value is initially + // `undefined`, until we find out the version. + useSqlCache: boolean | undefined; + // The cache of SQL texts stored on the server. Initially has capacity 0, but it is set to + // `sqlCacheCapacity` when `useSqlCache` is set to `true`. + sqlCache: SqlCache; + // The time when the connection was opened. + openTime: Date; + // Set of all `StreamState`-s that were opened from this connection. We can safely close the connection + // only when this is empty. + streamStates: Set; } interface StreamState { - conn: ConnState; - stream: hrana.WsStream; + conn: ConnState; + stream: hrana.WsStream; } -const maxConnAgeMillis = 60*1000; +const maxConnAgeMillis = 60 * 1000; const sqlCacheCapacity = 100; export class WsClient implements Client { - #url: URL; - #authToken: string | undefined; - #intMode: IntMode; - // State of the current connection. The `hrana.WsClient` inside may be closed at any moment due to an - // asynchronous error. - #connState: ConnState; - // If defined, this is a connection that will be used in the future, once it is ready. - #futureConnState: ConnState | undefined; - closed: boolean; - protocol: "ws"; - - /** @private */ - constructor(client: hrana.WsClient, url: URL, authToken: string | undefined, intMode: IntMode) { - this.#url = url; - this.#authToken = authToken; - this.#intMode = intMode; - this.#connState = this.#openConn(client); - this.#futureConnState = undefined; - this.closed = false; - this.protocol = "ws"; - } + #url: URL; + #authToken: string | undefined; + #intMode: IntMode; + // State of the current connection. The `hrana.WsClient` inside may be closed at any moment due to an + // asynchronous error. + #connState: ConnState; + // If defined, this is a connection that will be used in the future, once it is ready. + #futureConnState: ConnState | undefined; + closed: boolean; + protocol: "ws"; + + /** @private */ + constructor( + client: hrana.WsClient, + url: URL, + authToken: string | undefined, + intMode: IntMode, + ) { + this.#url = url; + this.#authToken = authToken; + this.#intMode = intMode; + this.#connState = this.#openConn(client); + this.#futureConnState = undefined; + this.closed = false; + this.protocol = "ws"; + } + + async execute(stmt: InStatement): Promise { + const streamState = await this.#openStream(); + try { + const hranaStmt = stmtToHrana(stmt); - async execute(stmt: InStatement): Promise { - const streamState = await this.#openStream(); - try { - const hranaStmt = stmtToHrana(stmt); - - // Schedule all operations synchronously, so they will be pipelined and executed in a single - // network roundtrip. - streamState.conn.sqlCache.apply([hranaStmt]); - const hranaRowsPromise = streamState.stream.query(hranaStmt); - streamState.stream.closeGracefully(); - - return resultSetFromHrana(await hranaRowsPromise); - } catch (e) { - throw mapHranaError(e); - } finally { - this._closeStream(streamState); - } - } + // Schedule all operations synchronously, so they will be pipelined and executed in a single + // network roundtrip. + streamState.conn.sqlCache.apply([hranaStmt]); + const hranaRowsPromise = streamState.stream.query(hranaStmt); + streamState.stream.closeGracefully(); - async batch(stmts: Array, mode: TransactionMode = "deferred"): Promise> { - const streamState = await this.#openStream(); - try { - const hranaStmts = stmts.map(stmtToHrana); - const version = await streamState.conn.client.getVersion(); - - // Schedule all operations synchronously, so they will be pipelined and executed in a single - // network roundtrip. - streamState.conn.sqlCache.apply(hranaStmts); - const batch = streamState.stream.batch(version >= 3); - const resultsPromise = executeHranaBatch(mode, version, batch, hranaStmts); - - return await resultsPromise; - } catch (e) { - throw mapHranaError(e); - } finally { - this._closeStream(streamState); - } + return resultSetFromHrana(await hranaRowsPromise); + } catch (e) { + throw mapHranaError(e); + } finally { + this._closeStream(streamState); } + } - async transaction(mode: TransactionMode = "write"): Promise { - const streamState = await this.#openStream(); - try { - const version = await streamState.conn.client.getVersion(); - // the BEGIN statement will be batched with the first statement on the transaction to save a - // network roundtrip - return new WsTransaction(this, streamState, mode, version); - } catch (e) { - this._closeStream(streamState); - throw mapHranaError(e); - } + async batch( + stmts: Array, + mode: TransactionMode = "deferred", + ): Promise> { + const streamState = await this.#openStream(); + try { + const hranaStmts = stmts.map(stmtToHrana); + const version = await streamState.conn.client.getVersion(); + + // Schedule all operations synchronously, so they will be pipelined and executed in a single + // network roundtrip. + streamState.conn.sqlCache.apply(hranaStmts); + const batch = streamState.stream.batch(version >= 3); + const resultsPromise = executeHranaBatch( + mode, + version, + batch, + hranaStmts, + ); + + return await resultsPromise; + } catch (e) { + throw mapHranaError(e); + } finally { + this._closeStream(streamState); } + } - async executeMultiple(sql: string): Promise { - const streamState = await this.#openStream(); - try { - // Schedule all operations synchronously, so they will be pipelined and executed in a single - // network roundtrip. - const promise = streamState.stream.sequence(sql); - streamState.stream.closeGracefully(); - - await promise; - } catch (e) { - throw mapHranaError(e); - } finally { - this._closeStream(streamState); - } + async transaction(mode: TransactionMode = "write"): Promise { + const streamState = await this.#openStream(); + try { + const version = await streamState.conn.client.getVersion(); + // the BEGIN statement will be batched with the first statement on the transaction to save a + // network roundtrip + return new WsTransaction(this, streamState, mode, version); + } catch (e) { + this._closeStream(streamState); + throw mapHranaError(e); } + } - sync(): Promise { - return Promise.resolve(); - } + async executeMultiple(sql: string): Promise { + const streamState = await this.#openStream(); + try { + // Schedule all operations synchronously, so they will be pipelined and executed in a single + // network roundtrip. + const promise = streamState.stream.sequence(sql); + streamState.stream.closeGracefully(); - async #openStream(): Promise { - if (this.closed) { - throw new LibsqlError("The client is closed", "CLIENT_CLOSED"); - } + await promise; + } catch (e) { + throw mapHranaError(e); + } finally { + this._closeStream(streamState); + } + } - const now = new Date(); - - const ageMillis = now.valueOf() - this.#connState.openTime.valueOf(); - if (ageMillis > maxConnAgeMillis && this.#futureConnState === undefined) { - // The existing connection is too old, let's open a new one. - const futureConnState = this.#openConn(); - this.#futureConnState = futureConnState; - - // However, if we used `futureConnState` immediately, we would introduce additional latency, - // because we would have to wait for the WebSocket handshake to complete, even though we may a - // have perfectly good existing connection in `this.#connState`! - // - // So we wait until the `hrana.Client.getVersion()` operation completes (which happens when the - // WebSocket hanshake completes), and only then we replace `this.#connState` with - // `futureConnState`, which is stored in `this.#futureConnState` in the meantime. - futureConnState.client.getVersion().then( - (_version) => { - if (this.#connState !== futureConnState) { - // We need to close `this.#connState` before we replace it. However, it is possible - // that `this.#connState` has already been replaced: see the code below. - if (this.#connState.streamStates.size === 0) { - this.#connState.client.close(); - } else { - // If there are existing streams on the connection, we must not close it, because - // these streams would be broken. The last stream to be closed will also close the - // connection in `_closeStream()`. - } - } - - this.#connState = futureConnState; - this.#futureConnState = undefined; - }, - (_e) => { - // If the new connection could not be established, let's just ignore the error and keep - // using the existing connection. - this.#futureConnState = undefined; - }, - ); - } + sync(): Promise { + return Promise.resolve(); + } - if (this.#connState.client.closed) { - // An error happened on this connection and it has been closed. Let's try to seamlessly reconnect. - try { - if (this.#futureConnState !== undefined) { - // We are already in the process of opening a new connection, so let's just use it - // immediately. - this.#connState = this.#futureConnState; - } else { - this.#connState = this.#openConn(); - } - } catch (e) { - throw mapHranaError(e); - } - } + async #openStream(): Promise { + if (this.closed) { + throw new LibsqlError("The client is closed", "CLIENT_CLOSED"); + } - const connState = this.#connState; - try { - // Now we wait for the WebSocket handshake to complete (if it hasn't completed yet). Note that - // this does not increase latency, because any messages that we would send on the WebSocket before - // the handshake would be queued until the handshake is completed anyway. - if (connState.useSqlCache === undefined) { - connState.useSqlCache = await connState.client.getVersion() >= 2; - if (connState.useSqlCache) { - connState.sqlCache.capacity = sqlCacheCapacity; - } + const now = new Date(); + + const ageMillis = now.valueOf() - this.#connState.openTime.valueOf(); + if (ageMillis > maxConnAgeMillis && this.#futureConnState === undefined) { + // The existing connection is too old, let's open a new one. + const futureConnState = this.#openConn(); + this.#futureConnState = futureConnState; + + // However, if we used `futureConnState` immediately, we would introduce additional latency, + // because we would have to wait for the WebSocket handshake to complete, even though we may a + // have perfectly good existing connection in `this.#connState`! + // + // So we wait until the `hrana.Client.getVersion()` operation completes (which happens when the + // WebSocket hanshake completes), and only then we replace `this.#connState` with + // `futureConnState`, which is stored in `this.#futureConnState` in the meantime. + futureConnState.client.getVersion().then( + (_version) => { + if (this.#connState !== futureConnState) { + // We need to close `this.#connState` before we replace it. However, it is possible + // that `this.#connState` has already been replaced: see the code below. + if (this.#connState.streamStates.size === 0) { + this.#connState.client.close(); + } else { + // If there are existing streams on the connection, we must not close it, because + // these streams would be broken. The last stream to be closed will also close the + // connection in `_closeStream()`. } - - const stream = connState.client.openStream(); - stream.intMode = this.#intMode; - const streamState = {conn: connState, stream}; - connState.streamStates.add(streamState); - return streamState; - } catch (e) { - throw mapHranaError(e); - } + } + + this.#connState = futureConnState; + this.#futureConnState = undefined; + }, + (_e) => { + // If the new connection could not be established, let's just ignore the error and keep + // using the existing connection. + this.#futureConnState = undefined; + }, + ); } - #openConn(client?: hrana.WsClient): ConnState { - try { - client ??= hrana.openWs(this.#url, this.#authToken); - return { - client, - useSqlCache: undefined, - sqlCache: new SqlCache(client, 0), - openTime: new Date(), - streamStates: new Set(), - }; - } catch (e) { - throw mapHranaError(e); + if (this.#connState.client.closed) { + // An error happened on this connection and it has been closed. Let's try to seamlessly reconnect. + try { + if (this.#futureConnState !== undefined) { + // We are already in the process of opening a new connection, so let's just use it + // immediately. + this.#connState = this.#futureConnState; + } else { + this.#connState = this.#openConn(); } + } catch (e) { + throw mapHranaError(e); + } } - _closeStream(streamState: StreamState): void { - streamState.stream.close(); - - const connState = streamState.conn; - connState.streamStates.delete(streamState); - if (connState.streamStates.size === 0 && connState !== this.#connState) { - // We are not using this connection anymore and this is the last stream that was using it, so we - // must close it now. - connState.client.close(); + const connState = this.#connState; + try { + // Now we wait for the WebSocket handshake to complete (if it hasn't completed yet). Note that + // this does not increase latency, because any messages that we would send on the WebSocket before + // the handshake would be queued until the handshake is completed anyway. + if (connState.useSqlCache === undefined) { + connState.useSqlCache = (await connState.client.getVersion()) >= 2; + if (connState.useSqlCache) { + connState.sqlCache.capacity = sqlCacheCapacity; } - } + } - close(): void { - this.#connState.client.close(); - this.closed = true; + const stream = connState.client.openStream(); + stream.intMode = this.#intMode; + const streamState = { conn: connState, stream }; + connState.streamStates.add(streamState); + return streamState; + } catch (e) { + throw mapHranaError(e); } -} + } -export class WsTransaction extends HranaTransaction implements Transaction { - #client: WsClient; - #streamState: StreamState; - - /** @private */ - constructor(client: WsClient, state: StreamState, mode: TransactionMode, version: hrana.ProtocolVersion) { - super(mode, version); - this.#client = client; - this.#streamState = state; + #openConn(client?: hrana.WsClient): ConnState { + try { + client ??= hrana.openWs(this.#url, this.#authToken); + return { + client, + useSqlCache: undefined, + sqlCache: new SqlCache(client, 0), + openTime: new Date(), + streamStates: new Set(), + }; + } catch (e) { + throw mapHranaError(e); } + } - /** @private */ - override _getStream(): hrana.Stream { - return this.#streamState.stream; - } + _closeStream(streamState: StreamState): void { + streamState.stream.close(); - /** @private */ - override _getSqlCache(): SqlCache { - return this.#streamState.conn.sqlCache; + const connState = streamState.conn; + connState.streamStates.delete(streamState); + if (connState.streamStates.size === 0 && connState !== this.#connState) { + // We are not using this connection anymore and this is the last stream that was using it, so we + // must close it now. + connState.client.close(); } + } - override close(): void { - this.#client._closeStream(this.#streamState); - } + close(): void { + this.#connState.client.close(); + this.closed = true; + } +} - override get closed(): boolean { - return this.#streamState.stream.closed; - } +export class WsTransaction extends HranaTransaction implements Transaction { + #client: WsClient; + #streamState: StreamState; + + /** @private */ + constructor( + client: WsClient, + state: StreamState, + mode: TransactionMode, + version: hrana.ProtocolVersion, + ) { + super(mode, version); + this.#client = client; + this.#streamState = state; + } + + /** @private */ + override _getStream(): hrana.Stream { + return this.#streamState.stream; + } + + /** @private */ + override _getSqlCache(): SqlCache { + return this.#streamState.conn.sqlCache; + } + + override close(): void { + this.#client._closeStream(this.#streamState); + } + + override get closed(): boolean { + return this.#streamState.stream.closed; + } } diff --git a/packages/libsql-client/tsconfig.base.json b/packages/libsql-client/tsconfig.base.json index e6d67e71..bfdf9532 100644 --- a/packages/libsql-client/tsconfig.base.json +++ b/packages/libsql-client/tsconfig.base.json @@ -1,13 +1,13 @@ { - "compilerOptions": { - "moduleResolution": "node", - "lib": ["esnext"], - "target": "esnext", - "esModuleInterop": true, - "isolatedModules": true, - "rootDir": "src/", - "strict": true - }, - "include": ["src/"], - "exclude": ["**/__tests__"] + "compilerOptions": { + "moduleResolution": "node", + "lib": ["esnext"], + "target": "esnext", + "esModuleInterop": true, + "isolatedModules": true, + "rootDir": "src/", + "strict": true + }, + "include": ["src/"], + "exclude": ["**/__tests__"] } diff --git a/packages/libsql-client/tsconfig.build-cjs.json b/packages/libsql-client/tsconfig.build-cjs.json index 857027a0..fa26814d 100644 --- a/packages/libsql-client/tsconfig.build-cjs.json +++ b/packages/libsql-client/tsconfig.build-cjs.json @@ -1,9 +1,8 @@ { - "extends": "./tsconfig.base.json", - "compilerOptions": { - "module": "commonjs", - "declaration": false, - "outDir": "./lib-cjs/" - } + "extends": "./tsconfig.base.json", + "compilerOptions": { + "module": "commonjs", + "declaration": false, + "outDir": "./lib-cjs/" + } } - diff --git a/packages/libsql-client/tsconfig.build-esm.json b/packages/libsql-client/tsconfig.build-esm.json index 9a01705b..a015a304 100644 --- a/packages/libsql-client/tsconfig.build-esm.json +++ b/packages/libsql-client/tsconfig.build-esm.json @@ -1,9 +1,8 @@ { - "extends": "./tsconfig.base.json", - "compilerOptions": { - "module": "esnext", - "declaration": true, - "outDir": "./lib-esm/" - } + "extends": "./tsconfig.base.json", + "compilerOptions": { + "module": "esnext", + "declaration": true, + "outDir": "./lib-esm/" + } } - diff --git a/packages/libsql-client/tsconfig.json b/packages/libsql-client/tsconfig.json index bc064274..23f862fb 100644 --- a/packages/libsql-client/tsconfig.json +++ b/packages/libsql-client/tsconfig.json @@ -1,7 +1,7 @@ { - "extends": "./tsconfig.base.json", - "compilerOptions": { - "noEmit": true, - "incremental": true - } + "extends": "./tsconfig.base.json", + "compilerOptions": { + "noEmit": true, + "incremental": true + } } diff --git a/packages/libsql-client/typedoc.json b/packages/libsql-client/typedoc.json index 4c5154ac..de52c9c4 100644 --- a/packages/libsql-client/typedoc.json +++ b/packages/libsql-client/typedoc.json @@ -1,11 +1,11 @@ { - "entryPoints": ["src/node.ts"], - "out": "docs", - "excludePrivate": true, - "excludeInternal": true, - "visibilityFilters": { - "inherited": true, - "external": true - }, - "includeVersion": true + "entryPoints": ["src/node.ts"], + "out": "docs", + "excludePrivate": true, + "excludeInternal": true, + "visibilityFilters": { + "inherited": true, + "external": true + }, + "includeVersion": true } diff --git a/packages/libsql-core/jest.config.js b/packages/libsql-core/jest.config.js index 65716e3c..c558e7d7 100644 --- a/packages/libsql-core/jest.config.js +++ b/packages/libsql-core/jest.config.js @@ -1,7 +1,7 @@ export default { - preset: "ts-jest/presets/default-esm", - moduleNameMapper: { - '^(\\.{1,2}/.*)\\.js$': '$1', - }, - testMatch: ["**/__tests__/*.test.[jt]s"], -} + preset: "ts-jest/presets/default-esm", + moduleNameMapper: { + "^(\\.{1,2}/.*)\\.js$": "$1", + }, + testMatch: ["**/__tests__/*.test.[jt]s"], +}; diff --git a/packages/libsql-core/package-cjs.json b/packages/libsql-core/package-cjs.json index 1cd945a3..5bbefffb 100644 --- a/packages/libsql-core/package-cjs.json +++ b/packages/libsql-core/package-cjs.json @@ -1,3 +1,3 @@ { - "type": "commonjs" + "type": "commonjs" } diff --git a/packages/libsql-core/package.json b/packages/libsql-core/package.json index 1362015f..de3a0e5b 100644 --- a/packages/libsql-core/package.json +++ b/packages/libsql-core/package.json @@ -1,89 +1,89 @@ { - "name": "@libsql/core", - "version": "0.6.0", - "keywords": [ - "libsql", - "database", - "sqlite", - "serverless", - "vercel", - "netlify", - "lambda" - ], - "description": "libSQL driver for TypeScript and JavaScript", - "repository": { - "type": "git", - "url": "https://github.com/libsql/libsql-client-ts" + "name": "@libsql/core", + "version": "0.6.0", + "keywords": [ + "libsql", + "database", + "sqlite", + "serverless", + "vercel", + "netlify", + "lambda" + ], + "description": "libSQL driver for TypeScript and JavaScript", + "repository": { + "type": "git", + "url": "https://github.com/libsql/libsql-client-ts" + }, + "authors": [ + "Jan Špaček ", + "Pekka Enberg ", + "Jan Plhak " + ], + "license": "MIT", + "type": "module", + "exports": { + "./api": { + "types": "./lib-esm/api.d.ts", + "import": "./lib-esm/api.js", + "require": "./lib-cjs/api.js" }, - "authors": [ - "Jan Špaček ", - "Pekka Enberg ", - "Jan Plhak " - ], - "license": "MIT", - "type": "module", - "exports": { - "./api": { - "types": "./lib-esm/api.d.ts", - "import": "./lib-esm/api.js", - "require": "./lib-cjs/api.js" - }, - "./config": { - "types": "./lib-esm/config.d.ts", - "import": "./lib-esm/config.js", - "require": "./lib-cjs/config.js" - }, - "./uri": { - "types": "./lib-esm/uri.d.ts", - "import": "./lib-esm/uri.js", - "require": "./lib-cjs/uri.js" - }, - "./util": { - "types": "./lib-esm/util.d.ts", - "import": "./lib-esm/util.js", - "require": "./lib-cjs/util.js" - } + "./config": { + "types": "./lib-esm/config.d.ts", + "import": "./lib-esm/config.js", + "require": "./lib-cjs/config.js" }, - "typesVersions": { - "*": { - "api": [ - "./lib-esm/api.d.ts" - ], - "config": [ - "./lib-esm/config.d.ts" - ], - "uri": [ - "./lib-esm/uri.d.ts" - ], - "util": [ - "./lib-esm/util.d.ts" - ] - } + "./uri": { + "types": "./lib-esm/uri.d.ts", + "import": "./lib-esm/uri.js", + "require": "./lib-cjs/uri.js" }, - "files": [ - "lib-cjs/**", - "lib-esm/**" - ], - "scripts": { - "prepublishOnly": "npm run build", - "prebuild": "rm -rf ./lib-cjs ./lib-esm", - "build": "npm run build:cjs && npm run build:esm", - "build:cjs": "tsc -p tsconfig.build-cjs.json", - "build:esm": "tsc -p tsconfig.build-esm.json", - "postbuild": "cp package-cjs.json ./lib-cjs/package.json", - "test": "jest --runInBand", - "typecheck": "tsc --noEmit", - "typedoc": "rm -rf ./docs && typedoc" - }, - "dependencies": { - "js-base64": "^3.7.5" - }, - "devDependencies": { - "@types/jest": "^29.2.5", - "@types/node": "^18.15.5", - "jest": "^29.3.1", - "ts-jest": "^29.0.5", - "typedoc": "^0.23.28", - "typescript": "^4.9.4" + "./util": { + "types": "./lib-esm/util.d.ts", + "import": "./lib-esm/util.js", + "require": "./lib-cjs/util.js" + } + }, + "typesVersions": { + "*": { + "api": [ + "./lib-esm/api.d.ts" + ], + "config": [ + "./lib-esm/config.d.ts" + ], + "uri": [ + "./lib-esm/uri.d.ts" + ], + "util": [ + "./lib-esm/util.d.ts" + ] } + }, + "files": [ + "lib-cjs/**", + "lib-esm/**" + ], + "scripts": { + "prepublishOnly": "npm run build", + "prebuild": "rm -rf ./lib-cjs ./lib-esm", + "build": "npm run build:cjs && npm run build:esm", + "build:cjs": "tsc -p tsconfig.build-cjs.json", + "build:esm": "tsc -p tsconfig.build-esm.json", + "postbuild": "cp package-cjs.json ./lib-cjs/package.json", + "test": "jest --runInBand", + "typecheck": "tsc --noEmit", + "typedoc": "rm -rf ./docs && typedoc" + }, + "dependencies": { + "js-base64": "^3.7.5" + }, + "devDependencies": { + "@types/jest": "^29.2.5", + "@types/node": "^18.15.5", + "jest": "^29.3.1", + "ts-jest": "^29.0.5", + "typedoc": "^0.23.28", + "typescript": "^4.9.4" + } } diff --git a/packages/libsql-core/src/api.ts b/packages/libsql-core/src/api.ts index 1424bc7f..1a463ac8 100644 --- a/packages/libsql-core/src/api.ts +++ b/packages/libsql-core/src/api.ts @@ -1,51 +1,51 @@ /** Configuration object for {@link createClient}. */ export interface Config { - /** The database URL. - * - * The client supports `libsql:`, `http:`/`https:`, `ws:`/`wss:` and `file:` URL. For more infomation, - * please refer to the project README: - * - * https://github.com/libsql/libsql-client-ts#supported-urls - */ - url: string; - - /** Authentication token for the database. */ - authToken?: string; - - /** Encryption key for the database. */ - encryptionKey?: string; - - /** URL of a remote server to synchronize database with. */ - syncUrl?: string; - - /** Sync interval in seconds. */ - syncInterval?: number; - - /** Enables or disables TLS for `libsql:` URLs. - * - * By default, `libsql:` URLs use TLS. You can set this option to `false` to disable TLS. - */ - tls?: boolean; - - /** How to convert SQLite integers to JavaScript values: - * - * - `"number"` (default): returns SQLite integers as JavaScript `number`-s (double precision floats). - * `number` cannot precisely represent integers larger than 2^53-1 in absolute value, so attempting to read - * larger integers will throw a `RangeError`. - * - `"bigint"`: returns SQLite integers as JavaScript `bigint`-s (arbitrary precision integers). Bigints can - * precisely represent all SQLite integers. - * - `"string"`: returns SQLite integers as strings. - */ - intMode?: IntMode; - - /** Custom `fetch` function to use for the HTTP client. - * - * By default, the HTTP client uses `fetch` from the `@libsql/isomorphic-fetch` package, but you can pass - * your own function here. The argument to this function will be `Request` from - * `@libsql/isomorphic-fetch`, and it must return a promise that resolves to an object that is compatible - * with the Web `Response`. - */ - fetch?: Function; + /** The database URL. + * + * The client supports `libsql:`, `http:`/`https:`, `ws:`/`wss:` and `file:` URL. For more infomation, + * please refer to the project README: + * + * https://github.com/libsql/libsql-client-ts#supported-urls + */ + url: string; + + /** Authentication token for the database. */ + authToken?: string; + + /** Encryption key for the database. */ + encryptionKey?: string; + + /** URL of a remote server to synchronize database with. */ + syncUrl?: string; + + /** Sync interval in seconds. */ + syncInterval?: number; + + /** Enables or disables TLS for `libsql:` URLs. + * + * By default, `libsql:` URLs use TLS. You can set this option to `false` to disable TLS. + */ + tls?: boolean; + + /** How to convert SQLite integers to JavaScript values: + * + * - `"number"` (default): returns SQLite integers as JavaScript `number`-s (double precision floats). + * `number` cannot precisely represent integers larger than 2^53-1 in absolute value, so attempting to read + * larger integers will throw a `RangeError`. + * - `"bigint"`: returns SQLite integers as JavaScript `bigint`-s (arbitrary precision integers). Bigints can + * precisely represent all SQLite integers. + * - `"string"`: returns SQLite integers as strings. + */ + intMode?: IntMode; + + /** Custom `fetch` function to use for the HTTP client. + * + * By default, the HTTP client uses `fetch` from the `@libsql/isomorphic-fetch` package, but you can pass + * your own function here. The argument to this function will be `Request` from + * `@libsql/isomorphic-fetch`, and it must return a promise that resolves to an object that is compatible + * with the Web `Response`. + */ + fetch?: Function; } /** Representation of integers from database as JavaScript values. See {@link Config.intMode}. */ @@ -56,156 +56,159 @@ export type IntMode = "number" | "bigint" | "string"; * After you are done with the client, you **should** close it by calling {@link close}. */ export interface Client { - /** Execute a single SQL statement. - * - * Every statement executed with this method is executed in its own logical database connection. If you - * want to execute a group of statements in a transaction, use the {@link batch} or the {@link - * transaction} methods. - * - * ```javascript - * // execute a statement without arguments - * const rs = await client.execute("SELECT * FROM books"); - * - * // execute a statement with positional arguments - * const rs = await client.execute({ - * sql: "SELECT * FROM books WHERE author = ?", - * args: ["Jane Austen"], - * }); - * - * // execute a statement with named arguments - * const rs = await client.execute({ - * sql: "SELECT * FROM books WHERE published_at > $year", - * args: {year: 1719}, - * }); - * ``` - */ - execute(stmt: InStatement): Promise; - - /** Execute a batch of SQL statements in a transaction. - * - * The batch is executed in its own logical database connection and the statements are wrapped in a - * transaction. This ensures that the batch is applied atomically: either all or no changes are applied. - * - * The `mode` parameter selects the transaction mode for the batch; please see {@link TransactionMode} for - * details. The default transaction mode is `"deferred"`. - * - * If any of the statements in the batch fails with an error, the batch is aborted, the transaction is - * rolled back and the returned promise is rejected. - * - * This method provides non-interactive transactions. If you need interactive transactions, please use the - * {@link transaction} method. - * - * ```javascript - * const rss = await client.batch([ - * // batch statement without arguments - * "DELETE FROM books WHERE name LIKE '%Crusoe'", - * - * // batch statement with positional arguments - * { - * sql: "INSERT INTO books (name, author, published_at) VALUES (?, ?, ?)", - * args: ["First Impressions", "Jane Austen", 1813], - * }, - * - * // batch statement with named arguments - * { - * sql: "UPDATE books SET name = $new WHERE name = $old", - * args: {old: "First Impressions", new: "Pride and Prejudice"}, - * }, - * ], "write"); - * ``` - */ - batch(stmts: Array, mode?: TransactionMode): Promise>; - - /** Start an interactive transaction. - * - * Interactive transactions allow you to interleave execution of SQL statements with your application - * logic. They can be used if the {@link batch} method is too restrictive, but please note that - * interactive transactions have higher latency. - * - * The `mode` parameter selects the transaction mode for the interactive transaction; please see {@link - * TransactionMode} for details. The default transaction mode is `"deferred"`. - * - * You **must** make sure that the returned {@link Transaction} object is closed, by calling {@link - * Transaction.close}, {@link Transaction.commit} or {@link Transaction.rollback}. The best practice is - * to call {@link Transaction.close} in a `finally` block, as follows: - * - * ```javascript - * const transaction = client.transaction("write"); - * try { - * // do some operations with the transaction here - * await transaction.execute({ - * sql: "INSERT INTO books (name, author) VALUES (?, ?)", - * args: ["First Impressions", "Jane Austen"], - * }); - * await transaction.execute({ - * sql: "UPDATE books SET name = ? WHERE name = ?", - * args: ["Pride and Prejudice", "First Impressions"], - * }); - * - * // if all went well, commit the transaction - * await transaction.commit(); - * } finally { - * // make sure to close the transaction, even if an exception was thrown - * transaction.close(); - * } - * ``` - */ - transaction(mode?: TransactionMode): Promise; - - /** Start an interactive transaction in `"write"` mode. - * - * Please see {@link transaction} for details. - * - * @deprecated Please specify the `mode` explicitly. The default `"write"` will be removed in the next - * major release. - */ - transaction(): Promise; - - /** Execute a sequence of SQL statements separated by semicolons. - * - * The statements are executed sequentially on a new logical database connection. If a statement fails, - * further statements are not executed and this method throws an error. All results from the statements - * are ignored. - * - * We do not wrap the statements in a transaction, but the SQL can contain explicit transaction-control - * statements such as `BEGIN` and `COMMIT`. - * - * This method is intended to be used with existing SQL scripts, such as migrations or small database - * dumps. If you want to execute a sequence of statements programmatically, please use {@link batch} - * instead. - * - * ```javascript - * await client.executeMultiple(` - * CREATE TABLE books (id INTEGER PRIMARY KEY, title TEXT NOT NULL, author_id INTEGER NOT NULL); - * CREATE TABLE authors (id INTEGER PRIMARY KEY, name TEXT NOT NULL); - * `); - * ``` - */ - executeMultiple(sql: string): Promise; - - sync(): Promise; - - /** Close the client and release resources. - * - * This method closes the client (aborting any operations that are currently in progress) and releases any - * resources associated with the client (such as a WebSocket connection). - */ - close(): void; - - /** Is the client closed? - * - * This is set to `true` after a call to {@link close} or if the client encounters an unrecoverable - * error. - */ - closed: boolean; - - /** Which protocol does the client use? - * - * - `"http"` if the client connects over HTTP - * - `"ws"` if the client connects over WebSockets - * - `"file"` if the client works with a local file - */ - protocol: string; + /** Execute a single SQL statement. + * + * Every statement executed with this method is executed in its own logical database connection. If you + * want to execute a group of statements in a transaction, use the {@link batch} or the {@link + * transaction} methods. + * + * ```javascript + * // execute a statement without arguments + * const rs = await client.execute("SELECT * FROM books"); + * + * // execute a statement with positional arguments + * const rs = await client.execute({ + * sql: "SELECT * FROM books WHERE author = ?", + * args: ["Jane Austen"], + * }); + * + * // execute a statement with named arguments + * const rs = await client.execute({ + * sql: "SELECT * FROM books WHERE published_at > $year", + * args: {year: 1719}, + * }); + * ``` + */ + execute(stmt: InStatement): Promise; + + /** Execute a batch of SQL statements in a transaction. + * + * The batch is executed in its own logical database connection and the statements are wrapped in a + * transaction. This ensures that the batch is applied atomically: either all or no changes are applied. + * + * The `mode` parameter selects the transaction mode for the batch; please see {@link TransactionMode} for + * details. The default transaction mode is `"deferred"`. + * + * If any of the statements in the batch fails with an error, the batch is aborted, the transaction is + * rolled back and the returned promise is rejected. + * + * This method provides non-interactive transactions. If you need interactive transactions, please use the + * {@link transaction} method. + * + * ```javascript + * const rss = await client.batch([ + * // batch statement without arguments + * "DELETE FROM books WHERE name LIKE '%Crusoe'", + * + * // batch statement with positional arguments + * { + * sql: "INSERT INTO books (name, author, published_at) VALUES (?, ?, ?)", + * args: ["First Impressions", "Jane Austen", 1813], + * }, + * + * // batch statement with named arguments + * { + * sql: "UPDATE books SET name = $new WHERE name = $old", + * args: {old: "First Impressions", new: "Pride and Prejudice"}, + * }, + * ], "write"); + * ``` + */ + batch( + stmts: Array, + mode?: TransactionMode, + ): Promise>; + + /** Start an interactive transaction. + * + * Interactive transactions allow you to interleave execution of SQL statements with your application + * logic. They can be used if the {@link batch} method is too restrictive, but please note that + * interactive transactions have higher latency. + * + * The `mode` parameter selects the transaction mode for the interactive transaction; please see {@link + * TransactionMode} for details. The default transaction mode is `"deferred"`. + * + * You **must** make sure that the returned {@link Transaction} object is closed, by calling {@link + * Transaction.close}, {@link Transaction.commit} or {@link Transaction.rollback}. The best practice is + * to call {@link Transaction.close} in a `finally` block, as follows: + * + * ```javascript + * const transaction = client.transaction("write"); + * try { + * // do some operations with the transaction here + * await transaction.execute({ + * sql: "INSERT INTO books (name, author) VALUES (?, ?)", + * args: ["First Impressions", "Jane Austen"], + * }); + * await transaction.execute({ + * sql: "UPDATE books SET name = ? WHERE name = ?", + * args: ["Pride and Prejudice", "First Impressions"], + * }); + * + * // if all went well, commit the transaction + * await transaction.commit(); + * } finally { + * // make sure to close the transaction, even if an exception was thrown + * transaction.close(); + * } + * ``` + */ + transaction(mode?: TransactionMode): Promise; + + /** Start an interactive transaction in `"write"` mode. + * + * Please see {@link transaction} for details. + * + * @deprecated Please specify the `mode` explicitly. The default `"write"` will be removed in the next + * major release. + */ + transaction(): Promise; + + /** Execute a sequence of SQL statements separated by semicolons. + * + * The statements are executed sequentially on a new logical database connection. If a statement fails, + * further statements are not executed and this method throws an error. All results from the statements + * are ignored. + * + * We do not wrap the statements in a transaction, but the SQL can contain explicit transaction-control + * statements such as `BEGIN` and `COMMIT`. + * + * This method is intended to be used with existing SQL scripts, such as migrations or small database + * dumps. If you want to execute a sequence of statements programmatically, please use {@link batch} + * instead. + * + * ```javascript + * await client.executeMultiple(` + * CREATE TABLE books (id INTEGER PRIMARY KEY, title TEXT NOT NULL, author_id INTEGER NOT NULL); + * CREATE TABLE authors (id INTEGER PRIMARY KEY, name TEXT NOT NULL); + * `); + * ``` + */ + executeMultiple(sql: string): Promise; + + sync(): Promise; + + /** Close the client and release resources. + * + * This method closes the client (aborting any operations that are currently in progress) and releases any + * resources associated with the client (such as a WebSocket connection). + */ + close(): void; + + /** Is the client closed? + * + * This is set to `true` after a call to {@link close} or if the client encounters an unrecoverable + * error. + */ + closed: boolean; + + /** Which protocol does the client use? + * + * - `"http"` if the client connects over HTTP + * - `"ws"` if the client connects over WebSockets + * - `"file"` if the client works with a local file + */ + protocol: string; } /** Interactive transaction. @@ -241,70 +244,70 @@ export interface Client { * ``` */ export interface Transaction { - /** Execute an SQL statement in this transaction. - * - * If the statement makes any changes to the database, these changes won't be visible to statements - * outside of this transaction until you call {@link rollback}. - * - * ```javascript - * await transaction.execute({ - * sql: "INSERT INTO books (name, author) VALUES (?, ?)", - * args: ["First Impressions", "Jane Austen"], - * }); - * ``` - */ - execute(stmt: InStatement): Promise; - - /** Execute a batch of SQL statements in this transaction. - * - * If any of the statements in the batch fails with an error, further statements are not executed and the - * returned promise is rejected with an error, but the transaction is not rolled back. - */ - batch(stmts: Array): Promise>; - - /** Execute a sequence of SQL statements separated by semicolons. - * - * The statements are executed sequentially in the transaction. If a statement fails, further statements - * are not executed and this method throws an error, but the transaction won't be rolled back. All results - * from the statements are ignored. - * - * This method is intended to be used with existing SQL scripts, such as migrations or small database - * dumps. If you want to execute statements programmatically, please use {@link batch} instead. - */ - executeMultiple(sql: string): Promise; - - /** Roll back any changes from this transaction. - * - * This method closes the transaction and undoes any changes done by the previous SQL statements on this - * transaction. You cannot call this method after calling {@link commit}, though. - */ - rollback(): Promise; - - /** Commit changes from this transaction to the database. - * - * This method closes the transaction and applies all changes done by the previous SQL statement on this - * transaction. Once the returned promise is resolved successfully, the database guarantees that the - * changes were applied. - */ - commit(): Promise; - - /** Close the transaction. - * - * This method closes the transaction and releases any resources associated with the transaction. If the - * transaction is already closed (perhaps by a previous call to {@link commit} or {@link rollback}), then - * this method does nothing. - * - * If the transaction wasn't already committed by calling {@link commit}, the transaction is rolled - * back. - */ - close(): void; - - /** Is the transaction closed? - * - * This is set to `true` after a call to {@link close}, {@link commit} or {@link rollback}, or if we - * encounter an unrecoverable error. - */ - closed: boolean; + /** Execute an SQL statement in this transaction. + * + * If the statement makes any changes to the database, these changes won't be visible to statements + * outside of this transaction until you call {@link rollback}. + * + * ```javascript + * await transaction.execute({ + * sql: "INSERT INTO books (name, author) VALUES (?, ?)", + * args: ["First Impressions", "Jane Austen"], + * }); + * ``` + */ + execute(stmt: InStatement): Promise; + + /** Execute a batch of SQL statements in this transaction. + * + * If any of the statements in the batch fails with an error, further statements are not executed and the + * returned promise is rejected with an error, but the transaction is not rolled back. + */ + batch(stmts: Array): Promise>; + + /** Execute a sequence of SQL statements separated by semicolons. + * + * The statements are executed sequentially in the transaction. If a statement fails, further statements + * are not executed and this method throws an error, but the transaction won't be rolled back. All results + * from the statements are ignored. + * + * This method is intended to be used with existing SQL scripts, such as migrations or small database + * dumps. If you want to execute statements programmatically, please use {@link batch} instead. + */ + executeMultiple(sql: string): Promise; + + /** Roll back any changes from this transaction. + * + * This method closes the transaction and undoes any changes done by the previous SQL statements on this + * transaction. You cannot call this method after calling {@link commit}, though. + */ + rollback(): Promise; + + /** Commit changes from this transaction to the database. + * + * This method closes the transaction and applies all changes done by the previous SQL statement on this + * transaction. Once the returned promise is resolved successfully, the database guarantees that the + * changes were applied. + */ + commit(): Promise; + + /** Close the transaction. + * + * This method closes the transaction and releases any resources associated with the transaction. If the + * transaction is already closed (perhaps by a previous call to {@link commit} or {@link rollback}), then + * this method does nothing. + * + * If the transaction wasn't already committed by calling {@link commit}, the transaction is rolled + * back. + */ + close(): void; + + /** Is the transaction closed? + * + * This is set to `true` after a call to {@link close}, {@link commit} or {@link rollback}, or if we + * encounter an unrecoverable error. + */ + closed: boolean; } /** Transaction mode. @@ -350,45 +353,45 @@ export type TransactionMode = "write" | "read" | "deferred"; * ``` */ export interface ResultSet { - /** Names of columns. - * - * Names of columns can be defined using the `AS` keyword in SQL: - * - * ```sql - * SELECT author AS author, COUNT(*) AS count FROM books GROUP BY author - * ``` - */ - columns: Array; - - /** Types of columns. - * - * The types are currently shown for types declared in a SQL table. For - * column types of function calls, for example, an empty string is - * returned. - */ - columnTypes: Array; - - /** Rows produced by the statement. */ - rows: Array; - - /** Number of rows that were affected by an UPDATE, INSERT or DELETE operation. - * - * This value is not specified for other SQL statements. - */ - rowsAffected: number; - - /** ROWID of the last inserted row. - * - * This value is not specified if the SQL statement was not an INSERT or if the table was not a ROWID - * table. - */ - lastInsertRowid: bigint | undefined; - - /** Converts the result set to JSON. - * - * This is used automatically by `JSON.stringify()`, but you can also call it explicitly. - */ - toJSON(): any; + /** Names of columns. + * + * Names of columns can be defined using the `AS` keyword in SQL: + * + * ```sql + * SELECT author AS author, COUNT(*) AS count FROM books GROUP BY author + * ``` + */ + columns: Array; + + /** Types of columns. + * + * The types are currently shown for types declared in a SQL table. For + * column types of function calls, for example, an empty string is + * returned. + */ + columnTypes: Array; + + /** Rows produced by the statement. */ + rows: Array; + + /** Number of rows that were affected by an UPDATE, INSERT or DELETE operation. + * + * This value is not specified for other SQL statements. + */ + rowsAffected: number; + + /** ROWID of the last inserted row. + * + * This value is not specified if the SQL statement was not an INSERT or if the table was not a ROWID + * table. + */ + lastInsertRowid: bigint | undefined; + + /** Converts the result set to JSON. + * + * This is used automatically by `JSON.stringify()`, but you can also call it explicitly. + */ + toJSON(): any; } /** Row returned from an SQL statement. @@ -406,49 +409,40 @@ export interface ResultSet { * ``` */ export interface Row { - /** Number of columns in this row. - * - * All rows in one {@link ResultSet} have the same number and names of columns. - */ - length: number; + /** Number of columns in this row. + * + * All rows in one {@link ResultSet} have the same number and names of columns. + */ + length: number; - /** Columns can be accessed like an array by numeric indexes. */ - [index: number]: Value; + /** Columns can be accessed like an array by numeric indexes. */ + [index: number]: Value; - /** Columns can be accessed like an object by column names. */ - [name: string]: Value; + /** Columns can be accessed like an object by column names. */ + [name: string]: Value; } -export type Value = - | null - | string - | number - | bigint - | ArrayBuffer +export type Value = null | string | number | bigint | ArrayBuffer; -export type InValue = - | Value - | boolean - | Uint8Array - | Date +export type InValue = Value | boolean | Uint8Array | Date; -export type InStatement = { sql: string, args: InArgs } | string; +export type InStatement = { sql: string; args: InArgs } | string; export type InArgs = Array | Record; /** Error thrown by the client. */ export class LibsqlError extends Error { - /** Machine-readable error code. */ - code: string; - /** Raw numeric error code */ - rawCode?: number; - - constructor(message: string, code: string, rawCode?: number, cause?: Error) { - if (code !== undefined) { - message = `${code}: ${message}`; - } - super(message, { cause }); - this.code = code; - this.rawCode = rawCode - this.name = "LibsqlError"; + /** Machine-readable error code. */ + code: string; + /** Raw numeric error code */ + rawCode?: number; + + constructor(message: string, code: string, rawCode?: number, cause?: Error) { + if (code !== undefined) { + message = `${code}: ${message}`; } + super(message, { cause }); + this.code = code; + this.rawCode = rawCode; + this.name = "LibsqlError"; + } } diff --git a/packages/libsql-core/src/config.ts b/packages/libsql-core/src/config.ts index a1d63745..0f135f79 100644 --- a/packages/libsql-core/src/config.ts +++ b/packages/libsql-core/src/config.ts @@ -5,125 +5,133 @@ import { parseUri } from "./uri.js"; import { supportedUrlLink } from "./util.js"; export interface ExpandedConfig { - scheme: ExpandedScheme; - tls: boolean; - authority: Authority | undefined; - path: string; - authToken: string | undefined; - encryptionKey: string | undefined; - syncUrl: string | undefined; - syncInterval: number | undefined; - intMode: IntMode; - fetch: Function | undefined; + scheme: ExpandedScheme; + tls: boolean; + authority: Authority | undefined; + path: string; + authToken: string | undefined; + encryptionKey: string | undefined; + syncUrl: string | undefined; + syncInterval: number | undefined; + intMode: IntMode; + fetch: Function | undefined; } export type ExpandedScheme = "wss" | "ws" | "https" | "http" | "file"; -export function expandConfig(config: Config, preferHttp: boolean): ExpandedConfig { - if (typeof config !== "object") { - // produce a reasonable error message in the common case where users type - // `createClient("libsql://...")` instead of `createClient({url: "libsql://..."})` - throw new TypeError(`Expected client configuration as object, got ${typeof config}`); - } - - let tls: boolean | undefined = config.tls; - let authToken = config.authToken; - let encryptionKey = config.encryptionKey; - let syncUrl = config.syncUrl; - let syncInterval = config.syncInterval; - const intMode = ""+(config.intMode ?? "number"); - if (intMode !== "number" && intMode !== "bigint" && intMode !== "string") { - throw new TypeError( - `Invalid value for intMode, expected "number", "bigint" or "string", \ - got ${JSON.stringify(intMode)}` - ); - } +export function expandConfig( + config: Config, + preferHttp: boolean, +): ExpandedConfig { + if (typeof config !== "object") { + // produce a reasonable error message in the common case where users type + // `createClient("libsql://...")` instead of `createClient({url: "libsql://..."})` + throw new TypeError( + `Expected client configuration as object, got ${typeof config}`, + ); + } + let tls: boolean | undefined = config.tls; + let authToken = config.authToken; + let encryptionKey = config.encryptionKey; + let syncUrl = config.syncUrl; + let syncInterval = config.syncInterval; + const intMode = "" + (config.intMode ?? "number"); + if (intMode !== "number" && intMode !== "bigint" && intMode !== "string") { + throw new TypeError( + `Invalid value for intMode, expected "number", "bigint" or "string", \ + got ${JSON.stringify(intMode)}`, + ); + } - if(config.url === ':memory:') { - return { - path: ':memory:', - scheme: 'file', - syncUrl, - syncInterval, - intMode, - fetch: config.fetch, - tls: false, - authToken: undefined, - encryptionKey: undefined, - authority: undefined, - }; - } - - const uri = parseUri(config.url); - for (const {key, value} of uri.query?.pairs ?? []) { - if (key === "authToken") { - authToken = value ? value : undefined; - } else if (key === "tls") { - if (value === "0") { - tls = false; - } else if (value === "1") { - tls = true; - } else { - throw new LibsqlError( - `Unknown value for the "tls" query argument: ${JSON.stringify(value)}. ` + - 'Supported values are "0" and "1"', - "URL_INVALID", - ); - } - } else { - throw new LibsqlError( - `Unknown URL query parameter ${JSON.stringify(key)}`, - "URL_PARAM_NOT_SUPPORTED", - ); - } - } + if (config.url === ":memory:") { + return { + path: ":memory:", + scheme: "file", + syncUrl, + syncInterval, + intMode, + fetch: config.fetch, + tls: false, + authToken: undefined, + encryptionKey: undefined, + authority: undefined, + }; + } - const uriScheme = uri.scheme.toLowerCase(); - let scheme: ExpandedScheme; - if (uriScheme === "libsql") { - if (tls === false) { - if (uri.authority?.port === undefined) { - throw new LibsqlError( - 'A "libsql:" URL with ?tls=0 must specify an explicit port', - "URL_INVALID", - ); - } - scheme = preferHttp ? "http" : "ws"; - } else { - scheme = preferHttp ? "https" : "wss"; - } - } else if (uriScheme === "http" || uriScheme === "ws") { - scheme = uriScheme; - tls ??= false; - } else if (uriScheme === "https" || uriScheme === "wss" || uriScheme === "file") { - scheme = uriScheme; - } else { + const uri = parseUri(config.url); + for (const { key, value } of uri.query?.pairs ?? []) { + if (key === "authToken") { + authToken = value ? value : undefined; + } else if (key === "tls") { + if (value === "0") { + tls = false; + } else if (value === "1") { + tls = true; + } else { throw new LibsqlError( - 'The client supports only "libsql:", "wss:", "ws:", "https:", "http:" and "file:" URLs, ' + - `got ${JSON.stringify(uri.scheme + ":")}. ` + - `For more information, please read ${supportedUrlLink}`, - "URL_SCHEME_NOT_SUPPORTED", + `Unknown value for the "tls" query argument: ${JSON.stringify(value)}. ` + + 'Supported values are "0" and "1"', + "URL_INVALID", ); + } + } else { + throw new LibsqlError( + `Unknown URL query parameter ${JSON.stringify(key)}`, + "URL_PARAM_NOT_SUPPORTED", + ); } + } - if (uri.fragment !== undefined) { + const uriScheme = uri.scheme.toLowerCase(); + let scheme: ExpandedScheme; + if (uriScheme === "libsql") { + if (tls === false) { + if (uri.authority?.port === undefined) { throw new LibsqlError( - `URL fragments are not supported: ${JSON.stringify("#" + uri.fragment)}`, - "URL_INVALID", + 'A "libsql:" URL with ?tls=0 must specify an explicit port', + "URL_INVALID", ); + } + scheme = preferHttp ? "http" : "ws"; + } else { + scheme = preferHttp ? "https" : "wss"; } + } else if (uriScheme === "http" || uriScheme === "ws") { + scheme = uriScheme; + tls ??= false; + } else if ( + uriScheme === "https" || + uriScheme === "wss" || + uriScheme === "file" + ) { + scheme = uriScheme; + } else { + throw new LibsqlError( + 'The client supports only "libsql:", "wss:", "ws:", "https:", "http:" and "file:" URLs, ' + + `got ${JSON.stringify(uri.scheme + ":")}. ` + + `For more information, please read ${supportedUrlLink}`, + "URL_SCHEME_NOT_SUPPORTED", + ); + } - return { - scheme, - tls: tls ?? true, - authority: uri.authority, - path: uri.path, - authToken, - encryptionKey, - syncUrl, - syncInterval, - intMode, - fetch: config.fetch, - }; + if (uri.fragment !== undefined) { + throw new LibsqlError( + `URL fragments are not supported: ${JSON.stringify("#" + uri.fragment)}`, + "URL_INVALID", + ); + } + + return { + scheme, + tls: tls ?? true, + authority: uri.authority, + path: uri.path, + authToken, + encryptionKey, + syncUrl, + syncInterval, + intMode, + fetch: config.fetch, + }; } diff --git a/packages/libsql-core/src/uri.ts b/packages/libsql-core/src/uri.ts index 3117f2f4..83dd44cb 100644 --- a/packages/libsql-core/src/uri.ts +++ b/packages/libsql-core/src/uri.ts @@ -6,168 +6,193 @@ import { LibsqlError } from "./api.js"; export interface Uri { - scheme: string; - authority: Authority | undefined; - path: string; - query: Query | undefined; - fragment: string | undefined; + scheme: string; + authority: Authority | undefined; + path: string; + query: Query | undefined; + fragment: string | undefined; } export interface HierPart { - authority: Authority | undefined; - path: string; + authority: Authority | undefined; + path: string; } export interface Authority { - host: string; - port: number | undefined; - userinfo: Userinfo | undefined; + host: string; + port: number | undefined; + userinfo: Userinfo | undefined; } export interface Userinfo { - username: string; - password: string | undefined; + username: string; + password: string | undefined; } export interface Query { - pairs: Array, + pairs: Array; } export interface KeyValue { - key: string; - value: string; + key: string; + value: string; } export function parseUri(text: string): Uri { - const match = URI_RE.exec(text); - if (match === null) { - throw new LibsqlError("The URL is not in a valid format", "URL_INVALID"); - } - - const groups = match.groups!; - const scheme = groups["scheme"]!; - const authority = groups["authority"] !== undefined - ? parseAuthority(groups["authority"]) : undefined; - const path = percentDecode(groups["path"]!); - const query = groups["query"] !== undefined - ? parseQuery(groups["query"]) : undefined; - const fragment = groups["fragment"] !== undefined - ? percentDecode(groups["fragment"]) : undefined; - return {scheme, authority, path, query, fragment}; + const match = URI_RE.exec(text); + if (match === null) { + throw new LibsqlError("The URL is not in a valid format", "URL_INVALID"); + } + + const groups = match.groups!; + const scheme = groups["scheme"]!; + const authority = + groups["authority"] !== undefined + ? parseAuthority(groups["authority"]) + : undefined; + const path = percentDecode(groups["path"]!); + const query = + groups["query"] !== undefined ? parseQuery(groups["query"]) : undefined; + const fragment = + groups["fragment"] !== undefined + ? percentDecode(groups["fragment"]) + : undefined; + return { scheme, authority, path, query, fragment }; } const URI_RE = (() => { - const SCHEME = '(?[A-Za-z][A-Za-z.+-]*)'; - const AUTHORITY = '(?[^/?#]*)'; - const PATH = '(?[^?#]*)'; - const QUERY = '(?[^#]*)'; - const FRAGMENT = '(?.*)' - return new RegExp(`^${SCHEME}:(//${AUTHORITY})?${PATH}(\\?${QUERY})?(#${FRAGMENT})?$`, "su"); + const SCHEME = "(?[A-Za-z][A-Za-z.+-]*)"; + const AUTHORITY = "(?[^/?#]*)"; + const PATH = "(?[^?#]*)"; + const QUERY = "(?[^#]*)"; + const FRAGMENT = "(?.*)"; + return new RegExp( + `^${SCHEME}:(//${AUTHORITY})?${PATH}(\\?${QUERY})?(#${FRAGMENT})?$`, + "su", + ); })(); function parseAuthority(text: string): Authority { - const match = AUTHORITY_RE.exec(text); - if (match === null) { - throw new LibsqlError("The authority part of the URL is not in a valid format", "URL_INVALID"); - } - - const groups = match.groups!; - const host = percentDecode(groups["host_br"] ?? groups["host"]); - const port = groups["port"] - ? parseInt(groups["port"], 10) - : undefined; - const userinfo = groups["username"] !== undefined - ? { - username: percentDecode(groups["username"]), - password: groups["password"] !== undefined - ? percentDecode(groups["password"]) : undefined, + const match = AUTHORITY_RE.exec(text); + if (match === null) { + throw new LibsqlError( + "The authority part of the URL is not in a valid format", + "URL_INVALID", + ); + } + + const groups = match.groups!; + const host = percentDecode(groups["host_br"] ?? groups["host"]); + const port = groups["port"] ? parseInt(groups["port"], 10) : undefined; + const userinfo = + groups["username"] !== undefined + ? { + username: percentDecode(groups["username"]), + password: + groups["password"] !== undefined + ? percentDecode(groups["password"]) + : undefined, } - : undefined; - return {host, port, userinfo}; + : undefined; + return { host, port, userinfo }; } const AUTHORITY_RE = (() => { - return new RegExp(`^((?[^:]*)(:(?.*))?@)?((?[^:\\[\\]]*)|(\\[(?[^\\[\\]]*)\\]))(:(?[0-9]*))?$`, "su"); + return new RegExp( + `^((?[^:]*)(:(?.*))?@)?((?[^:\\[\\]]*)|(\\[(?[^\\[\\]]*)\\]))(:(?[0-9]*))?$`, + "su", + ); })(); // Query string is parsed as application/x-www-form-urlencoded according to the Web URL standard: // https://url.spec.whatwg.org/#urlencoded-parsing function parseQuery(text: string): Query { - const sequences = text.split("&"); - const pairs = []; - for (const sequence of sequences) { - if (sequence === "") { - continue; - } - - let key: string; - let value: string; - const splitIdx = sequence.indexOf("="); - if (splitIdx < 0) { - key = sequence; - value = ""; - } else { - key = sequence.substring(0, splitIdx); - value = sequence.substring(splitIdx+1); - } + const sequences = text.split("&"); + const pairs = []; + for (const sequence of sequences) { + if (sequence === "") { + continue; + } - pairs.push({ - key: percentDecode(key.replaceAll("+", " ")), - value: percentDecode(value.replaceAll("+", " ")), - }); + let key: string; + let value: string; + const splitIdx = sequence.indexOf("="); + if (splitIdx < 0) { + key = sequence; + value = ""; + } else { + key = sequence.substring(0, splitIdx); + value = sequence.substring(splitIdx + 1); } - return {pairs}; + + pairs.push({ + key: percentDecode(key.replaceAll("+", " ")), + value: percentDecode(value.replaceAll("+", " ")), + }); + } + return { pairs }; } function percentDecode(text: string): string { - try { - return decodeURIComponent(text); - } catch (e) { - if (e instanceof URIError) { - throw new LibsqlError(`URL component has invalid percent encoding: ${e}`, "URL_INVALID", undefined, e); - } - throw e; + try { + return decodeURIComponent(text); + } catch (e) { + if (e instanceof URIError) { + throw new LibsqlError( + `URL component has invalid percent encoding: ${e}`, + "URL_INVALID", + undefined, + e, + ); } + throw e; + } } -export function encodeBaseUrl(scheme: string, authority: Authority | undefined, path: string): URL { - if (authority === undefined) { - throw new LibsqlError( - `URL with scheme ${JSON.stringify(scheme + ":")} requires authority (the "//" part)`, - "URL_INVALID", - ); - } - - const schemeText = `${scheme}:`; - - const hostText = encodeHost(authority.host); - const portText = encodePort(authority.port); - const userinfoText = encodeUserinfo(authority.userinfo); - const authorityText = `//${userinfoText}${hostText}${portText}`; - - let pathText = path.split("/").map(encodeURIComponent).join("/"); - if (pathText !== "" && !pathText.startsWith("/")) { - pathText = "/" + pathText; - } - - return new URL(`${schemeText}${authorityText}${pathText}`); +export function encodeBaseUrl( + scheme: string, + authority: Authority | undefined, + path: string, +): URL { + if (authority === undefined) { + throw new LibsqlError( + `URL with scheme ${JSON.stringify(scheme + ":")} requires authority (the "//" part)`, + "URL_INVALID", + ); + } + + const schemeText = `${scheme}:`; + + const hostText = encodeHost(authority.host); + const portText = encodePort(authority.port); + const userinfoText = encodeUserinfo(authority.userinfo); + const authorityText = `//${userinfoText}${hostText}${portText}`; + + let pathText = path.split("/").map(encodeURIComponent).join("/"); + if (pathText !== "" && !pathText.startsWith("/")) { + pathText = "/" + pathText; + } + + return new URL(`${schemeText}${authorityText}${pathText}`); } function encodeHost(host: string): string { - return host.includes(":") ? `[${encodeURI(host)}]` : encodeURI(host); + return host.includes(":") ? `[${encodeURI(host)}]` : encodeURI(host); } function encodePort(port: number | undefined): string { - return port !== undefined ? `:${port}` : ""; + return port !== undefined ? `:${port}` : ""; } function encodeUserinfo(userinfo: Userinfo | undefined): string { - if (userinfo === undefined) { - return ""; - } - - const usernameText = encodeURIComponent(userinfo.username); - const passwordText = userinfo.password !== undefined - ? `:${encodeURIComponent(userinfo.password)}` : ""; - return `${usernameText}${passwordText}@`; + if (userinfo === undefined) { + return ""; + } + + const usernameText = encodeURIComponent(userinfo.username); + const passwordText = + userinfo.password !== undefined + ? `:${encodeURIComponent(userinfo.password)}` + : ""; + return `${usernameText}${passwordText}@`; } diff --git a/packages/libsql-core/src/util.ts b/packages/libsql-core/src/util.ts index 285cc86c..a4bcfe4c 100644 --- a/packages/libsql-core/src/util.ts +++ b/packages/libsql-core/src/util.ts @@ -1,62 +1,73 @@ import { Base64 } from "js-base64"; -import { ResultSet, Row, Value, TransactionMode, InStatement, LibsqlError } from "./api"; +import { + ResultSet, + Row, + Value, + TransactionMode, + InStatement, + LibsqlError, +} from "./api"; -export const supportedUrlLink = "https://github.com/libsql/libsql-client-ts#supported-urls"; +export const supportedUrlLink = + "https://github.com/libsql/libsql-client-ts#supported-urls"; export function transactionModeToBegin(mode: TransactionMode): string { - if (mode === "write") { - return "BEGIN IMMEDIATE"; - } else if (mode === "read") { - return "BEGIN TRANSACTION READONLY"; - } else if (mode === "deferred") { - return "BEGIN DEFERRED"; - } else { - throw RangeError('Unknown transaction mode, supported values are "write", "read" and "deferred"'); - } + if (mode === "write") { + return "BEGIN IMMEDIATE"; + } else if (mode === "read") { + return "BEGIN TRANSACTION READONLY"; + } else if (mode === "deferred") { + return "BEGIN DEFERRED"; + } else { + throw RangeError( + 'Unknown transaction mode, supported values are "write", "read" and "deferred"', + ); + } } export class ResultSetImpl implements ResultSet { - columns: Array; - columnTypes: Array; - rows: Array; - rowsAffected: number; - lastInsertRowid: bigint | undefined; + columns: Array; + columnTypes: Array; + rows: Array; + rowsAffected: number; + lastInsertRowid: bigint | undefined; - constructor( - columns: Array, - columnTypes: Array, - rows: Array, - rowsAffected: number, - lastInsertRowid: bigint | undefined, - ) { - this.columns = columns; - this.columnTypes = columnTypes; - this.rows = rows; - this.rowsAffected = rowsAffected; - this.lastInsertRowid = lastInsertRowid; - } + constructor( + columns: Array, + columnTypes: Array, + rows: Array, + rowsAffected: number, + lastInsertRowid: bigint | undefined, + ) { + this.columns = columns; + this.columnTypes = columnTypes; + this.rows = rows; + this.rowsAffected = rowsAffected; + this.lastInsertRowid = lastInsertRowid; + } - toJSON(): any { - return { - "columns": this.columns, - "columnTypes": this.columnTypes, - "rows": this.rows.map(rowToJson), - "rowsAffected": this.rowsAffected, - "lastInsertRowid": this.lastInsertRowid !== undefined ? ""+this.lastInsertRowid : null, - }; - } + toJSON(): any { + return { + columns: this.columns, + columnTypes: this.columnTypes, + rows: this.rows.map(rowToJson), + rowsAffected: this.rowsAffected, + lastInsertRowid: + this.lastInsertRowid !== undefined ? "" + this.lastInsertRowid : null, + }; + } } function rowToJson(row: Row): unknown { - return Array.prototype.map.call(row, valueToJson); + return Array.prototype.map.call(row, valueToJson); } function valueToJson(value: Value): unknown { - if (typeof value === "bigint") { - return ""+value; - } else if (value instanceof ArrayBuffer) { - return Base64.fromUint8Array(new Uint8Array(value)); - } else { - return value; - } + if (typeof value === "bigint") { + return "" + value; + } else if (value instanceof ArrayBuffer) { + return Base64.fromUint8Array(new Uint8Array(value)); + } else { + return value; + } } diff --git a/packages/libsql-core/tsconfig.base.json b/packages/libsql-core/tsconfig.base.json index e6d67e71..bfdf9532 100644 --- a/packages/libsql-core/tsconfig.base.json +++ b/packages/libsql-core/tsconfig.base.json @@ -1,13 +1,13 @@ { - "compilerOptions": { - "moduleResolution": "node", - "lib": ["esnext"], - "target": "esnext", - "esModuleInterop": true, - "isolatedModules": true, - "rootDir": "src/", - "strict": true - }, - "include": ["src/"], - "exclude": ["**/__tests__"] + "compilerOptions": { + "moduleResolution": "node", + "lib": ["esnext"], + "target": "esnext", + "esModuleInterop": true, + "isolatedModules": true, + "rootDir": "src/", + "strict": true + }, + "include": ["src/"], + "exclude": ["**/__tests__"] } diff --git a/packages/libsql-core/tsconfig.build-cjs.json b/packages/libsql-core/tsconfig.build-cjs.json index 857027a0..fa26814d 100644 --- a/packages/libsql-core/tsconfig.build-cjs.json +++ b/packages/libsql-core/tsconfig.build-cjs.json @@ -1,9 +1,8 @@ { - "extends": "./tsconfig.base.json", - "compilerOptions": { - "module": "commonjs", - "declaration": false, - "outDir": "./lib-cjs/" - } + "extends": "./tsconfig.base.json", + "compilerOptions": { + "module": "commonjs", + "declaration": false, + "outDir": "./lib-cjs/" + } } - diff --git a/packages/libsql-core/tsconfig.build-esm.json b/packages/libsql-core/tsconfig.build-esm.json index 9a01705b..a015a304 100644 --- a/packages/libsql-core/tsconfig.build-esm.json +++ b/packages/libsql-core/tsconfig.build-esm.json @@ -1,9 +1,8 @@ { - "extends": "./tsconfig.base.json", - "compilerOptions": { - "module": "esnext", - "declaration": true, - "outDir": "./lib-esm/" - } + "extends": "./tsconfig.base.json", + "compilerOptions": { + "module": "esnext", + "declaration": true, + "outDir": "./lib-esm/" + } } - diff --git a/packages/libsql-core/tsconfig.json b/packages/libsql-core/tsconfig.json index bc064274..23f862fb 100644 --- a/packages/libsql-core/tsconfig.json +++ b/packages/libsql-core/tsconfig.json @@ -1,7 +1,7 @@ { - "extends": "./tsconfig.base.json", - "compilerOptions": { - "noEmit": true, - "incremental": true - } + "extends": "./tsconfig.base.json", + "compilerOptions": { + "noEmit": true, + "incremental": true + } } diff --git a/packages/libsql-core/typedoc.json b/packages/libsql-core/typedoc.json index 4c5154ac..de52c9c4 100644 --- a/packages/libsql-core/typedoc.json +++ b/packages/libsql-core/typedoc.json @@ -1,11 +1,11 @@ { - "entryPoints": ["src/node.ts"], - "out": "docs", - "excludePrivate": true, - "excludeInternal": true, - "visibilityFilters": { - "inherited": true, - "external": true - }, - "includeVersion": true + "entryPoints": ["src/node.ts"], + "out": "docs", + "excludePrivate": true, + "excludeInternal": true, + "visibilityFilters": { + "inherited": true, + "external": true + }, + "includeVersion": true } From f840d060dddb64b45b943729011c9f3ce0f0b61b Mon Sep 17 00:00:00 2001 From: Giovanni Date: Mon, 20 May 2024 07:52:35 -0400 Subject: [PATCH 2/2] Prettify project --- .github/workflows/ci.yaml | 266 +- .github/workflows/pages.yaml | 76 +- .husky/install.mjs | 2 +- .prettierrc | 3 + CHANGELOG.md | 86 +- package-lock.json | 8874 ++++++++--------- package.json | 26 +- .../examples/browser/index.html | 16 +- .../examples/browser/index.js | 14 +- .../examples/browser/package-lock.json | 1300 +-- .../examples/browser/package.json | 32 +- .../libsql-client-wasm/examples/node/index.js | 18 +- .../examples/node/package-lock.json | 68 +- .../examples/node/package.json | 26 +- packages/libsql-client-wasm/jest.config.js | 10 +- packages/libsql-client-wasm/package-cjs.json | 2 +- packages/libsql-client-wasm/package.json | 134 +- packages/libsql-client-wasm/src/wasm.ts | 691 +- .../libsql-client-wasm/tsconfig.base.json | 24 +- .../tsconfig.build-esm.json | 12 +- packages/libsql-client-wasm/tsconfig.json | 10 +- packages/libsql-client-wasm/typedoc.json | 18 +- packages/libsql-client/.prettierrc | 1 - packages/libsql-client/README.md | 12 +- packages/libsql-client/examples/example.js | 30 +- packages/libsql-client/examples/package.json | 16 +- packages/libsql-client/examples/shell.js | 46 +- packages/libsql-client/examples/sync.js | 44 +- packages/libsql-client/jest.config.js | 10 +- packages/libsql-client/package-cjs.json | 2 +- packages/libsql-client/package.json | 226 +- .../smoke_test/vercel/app/api/function.ts | 190 +- .../smoke_test/vercel/app/public/index.html | 6 +- .../smoke_test/vercel/package.json | 14 +- .../libsql-client/smoke_test/vercel/test.js | 295 +- .../smoke_test/vercel/tsconfig.json | 24 +- .../smoke_test/workers/package.json | 8 +- .../libsql-client/smoke_test/workers/test.js | 152 +- .../smoke_test/workers/worker.js | 169 +- .../src/__tests__/client.test.ts | 2376 ++--- .../libsql-client/src/__tests__/helpers.ts | 74 +- .../libsql-client/src/__tests__/uri.test.ts | 480 +- packages/libsql-client/src/hrana.ts | 628 +- packages/libsql-client/src/http.ts | 371 +- packages/libsql-client/src/node.ts | 16 +- packages/libsql-client/src/sql_cache.ts | 150 +- packages/libsql-client/src/sqlite3.ts | 663 +- packages/libsql-client/src/web.ts | 24 +- packages/libsql-client/src/ws.ts | 615 +- packages/libsql-client/tsconfig.base.json | 22 +- .../libsql-client/tsconfig.build-cjs.json | 12 +- .../libsql-client/tsconfig.build-esm.json | 12 +- packages/libsql-client/tsconfig.json | 10 +- packages/libsql-client/typedoc.json | 18 +- packages/libsql-core/jest.config.js | 10 +- packages/libsql-core/package-cjs.json | 2 +- packages/libsql-core/package.json | 166 +- packages/libsql-core/src/api.ts | 653 +- packages/libsql-core/src/config.ts | 222 +- packages/libsql-core/src/uri.ts | 271 +- packages/libsql-core/src/util.ts | 110 +- packages/libsql-core/tsconfig.base.json | 22 +- packages/libsql-core/tsconfig.build-cjs.json | 12 +- packages/libsql-core/tsconfig.build-esm.json | 12 +- packages/libsql-core/tsconfig.json | 10 +- packages/libsql-core/typedoc.json | 18 +- 66 files changed, 10033 insertions(+), 9899 deletions(-) create mode 100644 .prettierrc delete mode 100644 packages/libsql-client/.prettierrc diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index fba9886f..90762c82 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -1,148 +1,148 @@ name: "CI" on: - push: - branches: ["main"] - pull_request: + push: + branches: ["main"] + pull_request: jobs: - "wasm-test": - name: "Build and test Wasm on Node.js" - runs-on: ubuntu-latest - defaults: - run: - working-directory: ./packages/libsql-client-wasm - env: { "NODE_OPTIONS": "--trace-warnings" } - steps: - - name: "Checkout this repo" - uses: actions/checkout@v3 - - name: "Setup Node.js" - uses: actions/setup-node@v3 - with: - node-version: "18.x" - cache: "npm" - cache-dependency-path: "packages/libsql-client-wasm" - - name: "Build core" - run: "npm ci && npm run build" - working-directory: ./packages/libsql-core - - name: "Install npm dependencies" - run: "npm ci" - - name: "Build" - run: "npm run build" - - name: "Test example" - run: "cd examples/node && npm i && node index.js" - env: { "URL": "file:///tmp/example.db" } + "wasm-test": + name: "Build and test Wasm on Node.js" + runs-on: ubuntu-latest + defaults: + run: + working-directory: ./packages/libsql-client-wasm + env: { "NODE_OPTIONS": "--trace-warnings" } + steps: + - name: "Checkout this repo" + uses: actions/checkout@v3 + - name: "Setup Node.js" + uses: actions/setup-node@v3 + with: + node-version: "18.x" + cache: "npm" + cache-dependency-path: "packages/libsql-client-wasm" + - name: "Build core" + run: "npm ci && npm run build" + working-directory: ./packages/libsql-core + - name: "Install npm dependencies" + run: "npm ci" + - name: "Build" + run: "npm run build" + - name: "Test example" + run: "cd examples/node && npm i && node index.js" + env: { "URL": "file:///tmp/example.db" } - "node-test": - name: "Build and test on Node.js" - runs-on: ubuntu-latest - defaults: - run: - working-directory: ./packages/libsql-client - env: { "NODE_OPTIONS": "--trace-warnings" } - steps: - - name: "Checkout this repo" - uses: actions/checkout@v3 - - name: "Setup Node.js" - uses: actions/setup-node@v3 - with: - node-version: "18.x" - cache: "npm" - cache-dependency-path: "packages/libsql-client" - - name: "Build core" - run: "npm ci && npm run build" - working-directory: ./packages/libsql-core - - name: "Install npm dependencies" - run: "npm ci" - - name: "Checkout hrana-test-server" - uses: actions/checkout@v3 - with: - repository: "libsql/hrana-test-server" - path: "packages/libsql-client/hrana-test-server" - - name: "Setup Python" - uses: actions/setup-python@v4 - with: - python-version: "3.10" - cache: "pip" - - name: "Install pip dependencies" - run: "pip install -r hrana-test-server/requirements.txt" + "node-test": + name: "Build and test on Node.js" + runs-on: ubuntu-latest + defaults: + run: + working-directory: ./packages/libsql-client + env: { "NODE_OPTIONS": "--trace-warnings" } + steps: + - name: "Checkout this repo" + uses: actions/checkout@v3 + - name: "Setup Node.js" + uses: actions/setup-node@v3 + with: + node-version: "18.x" + cache: "npm" + cache-dependency-path: "packages/libsql-client" + - name: "Build core" + run: "npm ci && npm run build" + working-directory: ./packages/libsql-core + - name: "Install npm dependencies" + run: "npm ci" + - name: "Checkout hrana-test-server" + uses: actions/checkout@v3 + with: + repository: "libsql/hrana-test-server" + path: "packages/libsql-client/hrana-test-server" + - name: "Setup Python" + uses: actions/setup-python@v4 + with: + python-version: "3.10" + cache: "pip" + - name: "Install pip dependencies" + run: "pip install -r hrana-test-server/requirements.txt" - - name: "Build" - run: "npm run build" + - name: "Build" + run: "npm run build" - - name: "Test Hrana 1 over WebSocket" - run: "python hrana-test-server/server_v1.py npm test" - env: { "URL": "ws://localhost:8080", "SERVER": "test_v1" } - - name: "Test Hrana 2 over WebSocket" - run: "python hrana-test-server/server_v2.py npm test" - env: { "URL": "ws://localhost:8080", "SERVER": "test_v2" } - - name: "Test Hrana 2 over HTTP" - run: "python hrana-test-server/server_v2.py npm test" - env: { "URL": "http://localhost:8080", "SERVER": "test_v2" } - # - name: "Test Hrana 3 over WebSocket" - # run: "python hrana-test-server/server_v3.py npm test" - # env: {"URL": "ws://localhost:8080", "SERVER": "test_v3"} - # - name: "Test Hrana 3 over HTTP" - # run: "python hrana-test-server/server_v3.py npm test" - # env: {"URL": "http://localhost:8080", "SERVER": "test_v3"} - - name: "Test local file" - run: "npm test" - env: { "URL": "file:///tmp/test.db" } + - name: "Test Hrana 1 over WebSocket" + run: "python hrana-test-server/server_v1.py npm test" + env: { "URL": "ws://localhost:8080", "SERVER": "test_v1" } + - name: "Test Hrana 2 over WebSocket" + run: "python hrana-test-server/server_v2.py npm test" + env: { "URL": "ws://localhost:8080", "SERVER": "test_v2" } + - name: "Test Hrana 2 over HTTP" + run: "python hrana-test-server/server_v2.py npm test" + env: { "URL": "http://localhost:8080", "SERVER": "test_v2" } + # - name: "Test Hrana 3 over WebSocket" + # run: "python hrana-test-server/server_v3.py npm test" + # env: {"URL": "ws://localhost:8080", "SERVER": "test_v3"} + # - name: "Test Hrana 3 over HTTP" + # run: "python hrana-test-server/server_v3.py npm test" + # env: {"URL": "http://localhost:8080", "SERVER": "test_v3"} + - name: "Test local file" + run: "npm test" + env: { "URL": "file:///tmp/test.db" } - - name: "Test example" - run: "cd examples && npm i && node example.js" - env: { "URL": "file:///tmp/example.db" } + - name: "Test example" + run: "cd examples && npm i && node example.js" + env: { "URL": "file:///tmp/example.db" } - "workers-test": - name: "Build and test with Cloudflare Workers" - runs-on: ubuntu-latest - defaults: - run: - working-directory: ./packages/libsql-client - env: - "CLOUDFLARE_API_TOKEN": "${{ secrets.CLOUDFLARE_API_TOKEN }}" - "CLOUDFLARE_ACCOUNT_ID": "${{ secrets.CLOUDFLARE_ACCOUNT_ID }}" - steps: - - name: "Checkout this repo" - uses: actions/checkout@v3 - - name: "Setup Node.js" - uses: actions/setup-node@v3 - with: - node-version: "lts/Hydrogen" - cache: "npm" - cache-dependency-path: "packages/libsql-client" - - name: "Build core" - run: "npm ci && npm run build" - working-directory: ./packages/libsql-core - - name: "Install npm dependencies" - run: "npm ci" + "workers-test": + name: "Build and test with Cloudflare Workers" + runs-on: ubuntu-latest + defaults: + run: + working-directory: ./packages/libsql-client + env: + "CLOUDFLARE_API_TOKEN": "${{ secrets.CLOUDFLARE_API_TOKEN }}" + "CLOUDFLARE_ACCOUNT_ID": "${{ secrets.CLOUDFLARE_ACCOUNT_ID }}" + steps: + - name: "Checkout this repo" + uses: actions/checkout@v3 + - name: "Setup Node.js" + uses: actions/setup-node@v3 + with: + node-version: "lts/Hydrogen" + cache: "npm" + cache-dependency-path: "packages/libsql-client" + - name: "Build core" + run: "npm ci && npm run build" + working-directory: ./packages/libsql-core + - name: "Install npm dependencies" + run: "npm ci" - - name: "Checkout hrana-test-server" - uses: actions/checkout@v3 - with: - repository: "libsql/hrana-test-server" - path: "packages/libsql-client/hrana-test-server" - - name: "Setup Python" - uses: actions/setup-python@v4 - with: - python-version: "3.10" - cache: "pip" - - name: "Install pip dependencies" - run: "pip install -r hrana-test-server/requirements.txt" + - name: "Checkout hrana-test-server" + uses: actions/checkout@v3 + with: + repository: "libsql/hrana-test-server" + path: "packages/libsql-client/hrana-test-server" + - name: "Setup Python" + uses: actions/setup-python@v4 + with: + python-version: "3.10" + cache: "pip" + - name: "Install pip dependencies" + run: "pip install -r hrana-test-server/requirements.txt" - - name: "Build" - run: "npm run build" - - name: "Install npm dependencies of the Workers test" - run: "cd smoke_test/workers && npm link ../.." + - name: "Build" + run: "npm run build" + - name: "Install npm dependencies of the Workers test" + run: "cd smoke_test/workers && npm link ../.." - - name: "Local test with Hrana 1 over WebSocket" - run: "cd smoke_test/workers && python ../../hrana-test-server/server_v1.py node --dns-result-order=ipv4first test.js" - env: { "LOCAL": "1", "URL": "ws://localhost:8080" } - - name: "Local test with Hrana 2 over WebSocket" - run: "cd smoke_test/workers && python ../../hrana-test-server/server_v2.py node --dns-result-order=ipv4first test.js" - env: { "LOCAL": "1", "URL": "ws://localhost:8080" } - - name: "Local test with Hrana 2 over HTTP" - run: "cd smoke_test/workers && python ../../hrana-test-server/server_v2.py node --dns-result-order=ipv4first test.js" - env: { "LOCAL": "1", "URL": "http://localhost:8080" } + - name: "Local test with Hrana 1 over WebSocket" + run: "cd smoke_test/workers && python ../../hrana-test-server/server_v1.py node --dns-result-order=ipv4first test.js" + env: { "LOCAL": "1", "URL": "ws://localhost:8080" } + - name: "Local test with Hrana 2 over WebSocket" + run: "cd smoke_test/workers && python ../../hrana-test-server/server_v2.py node --dns-result-order=ipv4first test.js" + env: { "LOCAL": "1", "URL": "ws://localhost:8080" } + - name: "Local test with Hrana 2 over HTTP" + run: "cd smoke_test/workers && python ../../hrana-test-server/server_v2.py node --dns-result-order=ipv4first test.js" + env: { "LOCAL": "1", "URL": "http://localhost:8080" } # - name: "Local test with Hrana 3 over WebSocket" # run: "cd smoke_test/workers && python ../../hrana-test-server/server_v3.py node --dns-result-order=ipv4first test.js" # env: {"LOCAL": "1", "URL": "ws://localhost:8080"} diff --git a/.github/workflows/pages.yaml b/.github/workflows/pages.yaml index a54b52c1..d02d3aca 100644 --- a/.github/workflows/pages.yaml +++ b/.github/workflows/pages.yaml @@ -1,45 +1,45 @@ name: "GitHub Pages" on: - push: - branches: ["main"] + push: + branches: ["main"] jobs: - "build": - name: "Build the docs" - runs-on: ubuntu-latest - defaults: - run: - working-directory: ./packages/libsql-client - steps: - - name: "Checkout this repo" - uses: actions/checkout@v3 - - name: "Setup Node.js" - uses: actions/setup-node@v3 - with: - node-version: "${{ matrix.node-version }}" - cache: "npm" - - name: "Install npm dependencies" - run: "npm ci" - - name: "Build" - run: "npm run typedoc" - - name: "Upload GitHub Pages artifact" - uses: actions/upload-pages-artifact@v1 - with: - path: "./docs" + "build": + name: "Build the docs" + runs-on: ubuntu-latest + defaults: + run: + working-directory: ./packages/libsql-client + steps: + - name: "Checkout this repo" + uses: actions/checkout@v3 + - name: "Setup Node.js" + uses: actions/setup-node@v3 + with: + node-version: "${{ matrix.node-version }}" + cache: "npm" + - name: "Install npm dependencies" + run: "npm ci" + - name: "Build" + run: "npm run typedoc" + - name: "Upload GitHub Pages artifact" + uses: actions/upload-pages-artifact@v1 + with: + path: "./docs" - "deploy": - name: "Deploy the docs to GitHub Pages" - needs: "build" - permissions: - pages: write - id-token: write + "deploy": + name: "Deploy the docs to GitHub Pages" + needs: "build" + permissions: + pages: write + id-token: write - environment: - name: github-pages - url: ${{ steps.deployment.outputs.page_url }} + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} - runs-on: ubuntu-latest - steps: - - name: "Deploy to GitHub Pages" - id: deployment - uses: actions/deploy-pages@v1 + runs-on: ubuntu-latest + steps: + - name: "Deploy to GitHub Pages" + id: deployment + uses: actions/deploy-pages@v1 diff --git a/.husky/install.mjs b/.husky/install.mjs index 920bd555..941d817e 100644 --- a/.husky/install.mjs +++ b/.husky/install.mjs @@ -1,6 +1,6 @@ // Skip Husky install in production and CI if (process.env.NODE_ENV === "production" || process.env.CI === "true") { - process.exit(0); + process.exit(0); } const husky = (await import("husky")).default; console.log(husky()); diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 00000000..0a02bcef --- /dev/null +++ b/.prettierrc @@ -0,0 +1,3 @@ +{ + "tabWidth": 4 +} diff --git a/CHANGELOG.md b/CHANGELOG.md index f79e8391..604cafe7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,101 +2,101 @@ ## 0.6.0 -- 2024-04-28 -- Bump hrana client to 0.6.0, which uses native Node fetch(). Note that - `@libsql/client` now requires Node 18 or later. +- Bump hrana client to 0.6.0, which uses native Node fetch(). Note that + `@libsql/client` now requires Node 18 or later. ## 0.5.6 -- 2024-03-12 -- Bump `libsql` package dependency to 0.3.10 that adds `wasm32` as - supported CPU, which is needed for StackBlitz compatibility. +- Bump `libsql` package dependency to 0.3.10 that adds `wasm32` as + supported CPU, which is needed for StackBlitz compatibility. ## 0.5.5 -- 2024-03-11 -- Bump `@libsql/libsql-wasm-experimental"` dependency to 0.0.2, which - fixes a broken sqlite3_get_autocommit() export. +- Bump `@libsql/libsql-wasm-experimental"` dependency to 0.0.2, which + fixes a broken sqlite3_get_autocommit() export. ## 0.5.4 -- 2024-03-11 -- Bump `libsql` dependency to 0.3.9, which fixes symbol not found errors on Alpine. +- Bump `libsql` dependency to 0.3.9, which fixes symbol not found errors on Alpine. ## 0.5.3 -- 2024-03-06 -- Add `syncInterval` config option to enable periodic sync. -- Bump `libsql` dependency to 0.3.7, which switches default encryption cipher to aes256cbs. +- Add `syncInterval` config option to enable periodic sync. +- Bump `libsql` dependency to 0.3.7, which switches default encryption cipher to aes256cbs. ## 0.5.2 -- 2024-02-24 -- Disable SQL statemen tracing in Wasm. +- Disable SQL statemen tracing in Wasm. ## 0.5.1 -- 2024-02-19 -- Update `libsql` package to 0.3.2, add `encryptionCipher` option, and switch default cipher to SQLCipher. +- Update `libsql` package to 0.3.2, add `encryptionCipher` option, and switch default cipher to SQLCipher. ## 0.5.0 -- 2024-02-15 -- Add a `encryptionKey` config option, which enables encryption at rest for local database files. +- Add a `encryptionKey` config option, which enables encryption at rest for local database files. ## 0.4.0 -- 2024-01-26 -- Update hrana-client package to 0.5.6. -- Add a `@libsql/client-wasm` package. -- Fix Bun on Linux/arm64. +- Update hrana-client package to 0.5.6. +- Add a `@libsql/client-wasm` package. +- Fix Bun on Linux/arm64. ## 0.3.6 -- 2023-10-20 -- Fix import problems on Cloudflare Workers. -- Add `rawCode` property to errors for local databases. -- Update the `libsql` package to version 0.1.28. +- Fix import problems on Cloudflare Workers. +- Add `rawCode` property to errors for local databases. +- Update the `libsql` package to version 0.1.28. ## 0.3.5 -- 2023-09-25 -- Performance improvements for local database access by reusing connection in `Client`. -- Embedded replica support. -- Column introspection support via ResultSet.columnTypes property. +- Performance improvements for local database access by reusing connection in `Client`. +- Embedded replica support. +- Column introspection support via ResultSet.columnTypes property. ## 0.3.4 -- 2023-09-11 -- Switch to Hrana 2 by default to let Hrana 3 cook some more. +- Switch to Hrana 2 by default to let Hrana 3 cook some more. ## 0.3.3 -- 2023-09-11 -- Updated `@libsql/hrana-client` to version 0.5.1, which has Bun support. +- Updated `@libsql/hrana-client` to version 0.5.1, which has Bun support. -- Switched to `libsql` package as a replacement for `better-sqlite3`. +- Switched to `libsql` package as a replacement for `better-sqlite3`. ## 0.3.2 -- 2023-07-29 -- Updated `@libsql/hrana-client` to version 0.5.0, which implements Hrana 3 - - Dropped workarounds for broken WebSocket support in Miniflare 2 -- Added a `@libsql/client/node` import for explicit Node.js-specific module +- Updated `@libsql/hrana-client` to version 0.5.0, which implements Hrana 3 + - Dropped workarounds for broken WebSocket support in Miniflare 2 +- Added a `@libsql/client/node` import for explicit Node.js-specific module ## 0.3.1 -- 2023-07-20 -- Added `ResultSet.toJSON()` to provide better JSON serialization. ([#61](https://github.com/libsql/libsql-client-ts/pull/61)) -- Added conditional exports to `package.json` that redirect the default import of `@libsql/client` to `@libsql/client/web` on a few supported edge platforms. ([#65](https://github.com/libsql/libsql-client-ts/pull/65)) -- Added `Config.fetch` to support overriding the `fetch` implementation from `@libsql/isomorphic-fetch`. ([#66](https://github.com/libsql/libsql-client-ts/pull/66)) +- Added `ResultSet.toJSON()` to provide better JSON serialization. ([#61](https://github.com/libsql/libsql-client-ts/pull/61)) +- Added conditional exports to `package.json` that redirect the default import of `@libsql/client` to `@libsql/client/web` on a few supported edge platforms. ([#65](https://github.com/libsql/libsql-client-ts/pull/65)) +- Added `Config.fetch` to support overriding the `fetch` implementation from `@libsql/isomorphic-fetch`. ([#66](https://github.com/libsql/libsql-client-ts/pull/66)) ## 0.3.0 -- 2023-07-07 -- **Changed the order of parameters to `batch()`**, so that the transaction mode is passed as the second parameter. ([#57](https://github.com/libsql/libsql-client-ts/pull/57)) -- **Changed the default transaction mode to `"deferred"`**. ([#57](https://github.com/libsql/libsql-client-ts/pull/57)) -- Added `Client.protocol` property to find out which protocol the client uses ([#54](https://github.com/libsql/libsql-client-ts/pull/54)). +- **Changed the order of parameters to `batch()`**, so that the transaction mode is passed as the second parameter. ([#57](https://github.com/libsql/libsql-client-ts/pull/57)) +- **Changed the default transaction mode to `"deferred"`**. ([#57](https://github.com/libsql/libsql-client-ts/pull/57)) +- Added `Client.protocol` property to find out which protocol the client uses ([#54](https://github.com/libsql/libsql-client-ts/pull/54)). ## 0.2.2 -- 2023-06-22 -- Added `intMode` field to the `Config`, which chooses whether SQLite integers are represented as numbers, bigints or strings in JavaScript ([#51](https://github.com/libsql/libsql-client-ts/pull/51)). +- Added `intMode` field to the `Config`, which chooses whether SQLite integers are represented as numbers, bigints or strings in JavaScript ([#51](https://github.com/libsql/libsql-client-ts/pull/51)). ## 0.2.1 -- 2023-06-13 -- Added `TransactionMode` argument to `batch()` and `transaction()` ([#46](https://github.com/libsql/libsql-client-ts/pull/46)) -- Added `Client.executeMultiple()` and `Transaction.executeMultiple()` ([#49](https://github.com/libsql/libsql-client-ts/pull/49)) -- Added `Transaction.batch()` ([#49](https://github.com/libsql/libsql-client-ts/pull/49)) -- **Changed the default transaction mode** from `BEGIN DEFERRED` to `BEGIN IMMEDIATE` +- Added `TransactionMode` argument to `batch()` and `transaction()` ([#46](https://github.com/libsql/libsql-client-ts/pull/46)) +- Added `Client.executeMultiple()` and `Transaction.executeMultiple()` ([#49](https://github.com/libsql/libsql-client-ts/pull/49)) +- Added `Transaction.batch()` ([#49](https://github.com/libsql/libsql-client-ts/pull/49)) +- **Changed the default transaction mode** from `BEGIN DEFERRED` to `BEGIN IMMEDIATE` ## 0.2.0 -- 2023-06-07 -- **Added support for interactive transactions over HTTP** by using `@libsql/hrana-client` version 0.4 ([#44](https://github.com/libsql/libsql-client-ts/pull/44)) -- Added `?tls=0` query parameter to turn off TLS for `libsql:` URLs -- Changed the `libsql:` URL to use HTTP instead of WebSockets -- Changed the `Value` type to include `bigint` (so that we can add support for reading integers as bigints in the future, without breaking compatibility) -- Removed the `./hrana` import, added `./ws` to import the WebSocket-only client +- **Added support for interactive transactions over HTTP** by using `@libsql/hrana-client` version 0.4 ([#44](https://github.com/libsql/libsql-client-ts/pull/44)) +- Added `?tls=0` query parameter to turn off TLS for `libsql:` URLs +- Changed the `libsql:` URL to use HTTP instead of WebSockets +- Changed the `Value` type to include `bigint` (so that we can add support for reading integers as bigints in the future, without breaking compatibility) +- Removed the `./hrana` import, added `./ws` to import the WebSocket-only client diff --git a/package-lock.json b/package-lock.json index 91735f44..8c9098fb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,4441 +1,4441 @@ { - "name": "libsql-client-ts", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "workspaces": [ - "packages/libsql-core", - "packages/libsql-client", - "packages/libsql-client-wasm" - ], - "dependencies": { - "husky": "^9.0.11", - "lint-staged": "^15.2.2" - } - }, - "node_modules/@ampproject/remapping": { - "version": "2.2.1", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@jridgewell/gen-mapping": "^0.3.0", - "@jridgewell/trace-mapping": "^0.3.9" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@babel/code-frame": { - "version": "7.23.5", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/highlight": "^7.23.4", - "chalk": "^2.4.2" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/code-frame/node_modules/ansi-styles": { - "version": "3.2.1", - "dev": true, - "license": "MIT", - "dependencies": { - "color-convert": "^1.9.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/code-frame/node_modules/chalk": { - "version": "2.4.2", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/code-frame/node_modules/color-convert": { - "version": "1.9.3", - "dev": true, - "license": "MIT", - "dependencies": { - "color-name": "1.1.3" - } - }, - "node_modules/@babel/code-frame/node_modules/color-name": { - "version": "1.1.3", - "dev": true, - "license": "MIT" - }, - "node_modules/@babel/code-frame/node_modules/escape-string-regexp": { - "version": "1.0.5", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/@babel/code-frame/node_modules/has-flag": { - "version": "3.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/code-frame/node_modules/supports-color": { - "version": "5.5.0", - "dev": true, - "license": "MIT", - "dependencies": { - "has-flag": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/compat-data": { - "version": "7.23.5", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/core": { - "version": "7.23.7", - "dev": true, - "license": "MIT", - "dependencies": { - "@ampproject/remapping": "^2.2.0", - "@babel/code-frame": "^7.23.5", - "@babel/generator": "^7.23.6", - "@babel/helper-compilation-targets": "^7.23.6", - "@babel/helper-module-transforms": "^7.23.3", - "@babel/helpers": "^7.23.7", - "@babel/parser": "^7.23.6", - "@babel/template": "^7.22.15", - "@babel/traverse": "^7.23.7", - "@babel/types": "^7.23.6", - "convert-source-map": "^2.0.0", - "debug": "^4.1.0", - "gensync": "^1.0.0-beta.2", - "json5": "^2.2.3", - "semver": "^6.3.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/babel" - } - }, - "node_modules/@babel/generator": { - "version": "7.23.6", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/types": "^7.23.6", - "@jridgewell/gen-mapping": "^0.3.2", - "@jridgewell/trace-mapping": "^0.3.17", - "jsesc": "^2.5.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-compilation-targets": { - "version": "7.23.6", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/compat-data": "^7.23.5", - "@babel/helper-validator-option": "^7.23.5", - "browserslist": "^4.22.2", - "lru-cache": "^5.1.1", - "semver": "^6.3.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-environment-visitor": { - "version": "7.22.20", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-function-name": { - "version": "7.23.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/template": "^7.22.15", - "@babel/types": "^7.23.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-hoist-variables": { - "version": "7.22.5", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/types": "^7.22.5" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-module-imports": { - "version": "7.22.15", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/types": "^7.22.15" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-module-transforms": { - "version": "7.23.3", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-environment-visitor": "^7.22.20", - "@babel/helper-module-imports": "^7.22.15", - "@babel/helper-simple-access": "^7.22.5", - "@babel/helper-split-export-declaration": "^7.22.6", - "@babel/helper-validator-identifier": "^7.22.20" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/helper-plugin-utils": { - "version": "7.22.5", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-simple-access": { - "version": "7.22.5", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/types": "^7.22.5" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-split-export-declaration": { - "version": "7.22.6", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/types": "^7.22.5" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-string-parser": { - "version": "7.23.4", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-validator-identifier": { - "version": "7.22.20", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-validator-option": { - "version": "7.23.5", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helpers": { - "version": "7.23.8", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/template": "^7.22.15", - "@babel/traverse": "^7.23.7", - "@babel/types": "^7.23.6" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/highlight": { - "version": "7.23.4", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-validator-identifier": "^7.22.20", - "chalk": "^2.4.2", - "js-tokens": "^4.0.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/highlight/node_modules/ansi-styles": { - "version": "3.2.1", - "dev": true, - "license": "MIT", - "dependencies": { - "color-convert": "^1.9.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/highlight/node_modules/chalk": { - "version": "2.4.2", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/highlight/node_modules/color-convert": { - "version": "1.9.3", - "dev": true, - "license": "MIT", - "dependencies": { - "color-name": "1.1.3" - } - }, - "node_modules/@babel/highlight/node_modules/color-name": { - "version": "1.1.3", - "dev": true, - "license": "MIT" - }, - "node_modules/@babel/highlight/node_modules/escape-string-regexp": { - "version": "1.0.5", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/@babel/highlight/node_modules/has-flag": { - "version": "3.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/highlight/node_modules/supports-color": { - "version": "5.5.0", - "dev": true, - "license": "MIT", - "dependencies": { - "has-flag": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/parser": { - "version": "7.23.6", - "dev": true, - "license": "MIT", - "bin": { - "parser": "bin/babel-parser.js" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@babel/plugin-syntax-async-generators": { - "version": "7.8.4", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-bigint": { - "version": "7.8.3", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-class-properties": { - "version": "7.12.13", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.12.13" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-import-meta": { - "version": "7.10.4", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-json-strings": { - "version": "7.8.3", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-jsx": { - "version": "7.23.3", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-logical-assignment-operators": { - "version": "7.10.4", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-nullish-coalescing-operator": { - "version": "7.8.3", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-numeric-separator": { - "version": "7.10.4", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-object-rest-spread": { - "version": "7.8.3", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-optional-catch-binding": { - "version": "7.8.3", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-optional-chaining": { - "version": "7.8.3", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-top-level-await": { - "version": "7.14.5", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.14.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-typescript": { - "version": "7.23.3", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/template": { - "version": "7.22.15", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.22.13", - "@babel/parser": "^7.22.15", - "@babel/types": "^7.22.15" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/traverse": { - "version": "7.23.7", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.23.5", - "@babel/generator": "^7.23.6", - "@babel/helper-environment-visitor": "^7.22.20", - "@babel/helper-function-name": "^7.23.0", - "@babel/helper-hoist-variables": "^7.22.5", - "@babel/helper-split-export-declaration": "^7.22.6", - "@babel/parser": "^7.23.6", - "@babel/types": "^7.23.6", - "debug": "^4.3.1", - "globals": "^11.1.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/types": { - "version": "7.23.6", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-string-parser": "^7.23.4", - "@babel/helper-validator-identifier": "^7.22.20", - "to-fast-properties": "^2.0.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@bcoe/v8-coverage": { - "version": "0.2.3", - "dev": true, - "license": "MIT" - }, - "node_modules/@istanbuljs/load-nyc-config": { - "version": "1.1.0", - "dev": true, - "license": "ISC", - "dependencies": { - "camelcase": "^5.3.1", - "find-up": "^4.1.0", - "get-package-type": "^0.1.0", - "js-yaml": "^3.13.1", - "resolve-from": "^5.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@istanbuljs/schema": { - "version": "0.1.3", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/@jest/console": { - "version": "29.7.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/types": "^29.6.3", - "@types/node": "*", - "chalk": "^4.0.0", - "jest-message-util": "^29.7.0", - "jest-util": "^29.7.0", - "slash": "^3.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/core": { - "version": "29.7.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/console": "^29.7.0", - "@jest/reporters": "^29.7.0", - "@jest/test-result": "^29.7.0", - "@jest/transform": "^29.7.0", - "@jest/types": "^29.6.3", - "@types/node": "*", - "ansi-escapes": "^4.2.1", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "exit": "^0.1.2", - "graceful-fs": "^4.2.9", - "jest-changed-files": "^29.7.0", - "jest-config": "^29.7.0", - "jest-haste-map": "^29.7.0", - "jest-message-util": "^29.7.0", - "jest-regex-util": "^29.6.3", - "jest-resolve": "^29.7.0", - "jest-resolve-dependencies": "^29.7.0", - "jest-runner": "^29.7.0", - "jest-runtime": "^29.7.0", - "jest-snapshot": "^29.7.0", - "jest-util": "^29.7.0", - "jest-validate": "^29.7.0", - "jest-watcher": "^29.7.0", - "micromatch": "^4.0.4", - "pretty-format": "^29.7.0", - "slash": "^3.0.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - }, - "peerDependencies": { - "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" - }, - "peerDependenciesMeta": { - "node-notifier": { - "optional": true - } - } - }, - "node_modules/@jest/environment": { - "version": "29.7.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/fake-timers": "^29.7.0", - "@jest/types": "^29.6.3", - "@types/node": "*", - "jest-mock": "^29.7.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/expect": { - "version": "29.7.0", - "dev": true, - "license": "MIT", - "dependencies": { - "expect": "^29.7.0", - "jest-snapshot": "^29.7.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/expect-utils": { - "version": "29.7.0", - "dev": true, - "license": "MIT", - "dependencies": { - "jest-get-type": "^29.6.3" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/fake-timers": { - "version": "29.7.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/types": "^29.6.3", - "@sinonjs/fake-timers": "^10.0.2", - "@types/node": "*", - "jest-message-util": "^29.7.0", - "jest-mock": "^29.7.0", - "jest-util": "^29.7.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/globals": { - "version": "29.7.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/environment": "^29.7.0", - "@jest/expect": "^29.7.0", - "@jest/types": "^29.6.3", - "jest-mock": "^29.7.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/reporters": { - "version": "29.7.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@bcoe/v8-coverage": "^0.2.3", - "@jest/console": "^29.7.0", - "@jest/test-result": "^29.7.0", - "@jest/transform": "^29.7.0", - "@jest/types": "^29.6.3", - "@jridgewell/trace-mapping": "^0.3.18", - "@types/node": "*", - "chalk": "^4.0.0", - "collect-v8-coverage": "^1.0.0", - "exit": "^0.1.2", - "glob": "^7.1.3", - "graceful-fs": "^4.2.9", - "istanbul-lib-coverage": "^3.0.0", - "istanbul-lib-instrument": "^6.0.0", - "istanbul-lib-report": "^3.0.0", - "istanbul-lib-source-maps": "^4.0.0", - "istanbul-reports": "^3.1.3", - "jest-message-util": "^29.7.0", - "jest-util": "^29.7.0", - "jest-worker": "^29.7.0", - "slash": "^3.0.0", - "string-length": "^4.0.1", - "strip-ansi": "^6.0.0", - "v8-to-istanbul": "^9.0.1" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - }, - "peerDependencies": { - "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" - }, - "peerDependenciesMeta": { - "node-notifier": { - "optional": true - } - } - }, - "node_modules/@jest/schemas": { - "version": "29.6.3", - "dev": true, - "license": "MIT", - "dependencies": { - "@sinclair/typebox": "^0.27.8" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/source-map": { - "version": "29.6.3", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/trace-mapping": "^0.3.18", - "callsites": "^3.0.0", - "graceful-fs": "^4.2.9" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/test-result": { - "version": "29.7.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/console": "^29.7.0", - "@jest/types": "^29.6.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "collect-v8-coverage": "^1.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/test-sequencer": { - "version": "29.7.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/test-result": "^29.7.0", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^29.7.0", - "slash": "^3.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/transform": { - "version": "29.7.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/core": "^7.11.6", - "@jest/types": "^29.6.3", - "@jridgewell/trace-mapping": "^0.3.18", - "babel-plugin-istanbul": "^6.1.1", - "chalk": "^4.0.0", - "convert-source-map": "^2.0.0", - "fast-json-stable-stringify": "^2.1.0", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^29.7.0", - "jest-regex-util": "^29.6.3", - "jest-util": "^29.7.0", - "micromatch": "^4.0.4", - "pirates": "^4.0.4", - "slash": "^3.0.0", - "write-file-atomic": "^4.0.2" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/types": { - "version": "29.6.3", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/schemas": "^29.6.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.3", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/set-array": "^1.0.1", - "@jridgewell/sourcemap-codec": "^1.4.10", - "@jridgewell/trace-mapping": "^0.3.9" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/set-array": { - "version": "1.1.2", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.4.15", - "dev": true, - "license": "MIT" - }, - "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.21", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/resolve-uri": "^3.1.0", - "@jridgewell/sourcemap-codec": "^1.4.14" - } - }, - "node_modules/@libsql/client": { - "resolved": "packages/libsql-client", - "link": true - }, - "node_modules/@libsql/client-wasm": { - "resolved": "packages/libsql-client-wasm", - "link": true - }, - "node_modules/@libsql/core": { - "resolved": "packages/libsql-core", - "link": true - }, - "node_modules/@libsql/darwin-arm64": { - "version": "0.3.10", - "resolved": "https://registry.npmjs.org/@libsql/darwin-arm64/-/darwin-arm64-0.3.10.tgz", - "integrity": "sha512-RaexEFfPAFogd6dJlqkpCkTxdr6K14Z0286lodIJ8Ny77mWuWyBkWKxf70OYWXXAMxMJFUW+6al1F3/Osf/pTg==", - "cpu": [ - "arm64" - ], - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@libsql/darwin-x64": { - "version": "0.3.10", - "resolved": "https://registry.npmjs.org/@libsql/darwin-x64/-/darwin-x64-0.3.10.tgz", - "integrity": "sha512-SNVN6n4qNUdMW1fJMFmx4qn4n5RnXsxjFbczpkzG/V7m/5VeTFt1chhGcrahTHCr3+K6eRJWJUEQHRGqjBwPkw==", - "cpu": [ - "x64" - ], - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@libsql/hrana-client": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/@libsql/hrana-client/-/hrana-client-0.6.0.tgz", - "integrity": "sha512-k+fqzdjqg3IvWfKmVJK5StsbjeTcyNAXFelUbXbGNz3yH1gEVT9mZ6kmhsIXP30ZSyVV0AE1Gi25p82mxC9hwg==", - "dependencies": { - "@libsql/isomorphic-fetch": "^0.2.1", - "@libsql/isomorphic-ws": "^0.1.5", - "js-base64": "^3.7.5", - "node-fetch": "^3.3.2" - } - }, - "node_modules/@libsql/isomorphic-fetch": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/@libsql/isomorphic-fetch/-/isomorphic-fetch-0.2.1.tgz", - "integrity": "sha512-Sv07QP1Aw8A5OOrmKgRUBKe2fFhF2hpGJhtHe3d1aRnTESZCGkn//0zDycMKTGamVWb3oLYRroOsCV8Ukes9GA==" - }, - "node_modules/@libsql/isomorphic-ws": { - "version": "0.1.5", - "license": "MIT", - "dependencies": { - "@types/ws": "^8.5.4", - "ws": "^8.13.0" - } - }, - "node_modules/@libsql/libsql-wasm-experimental": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/@libsql/libsql-wasm-experimental/-/libsql-wasm-experimental-0.0.2.tgz", - "integrity": "sha512-xkiu88QwozGr3KEt9h0zeHLYUIWkeDchXmuOUW4/Wh/mRZkDlNtIIePAR0FiLl1j0o4OyTEOtPnvmaXQ5MNTKQ==", - "bin": { - "sqlite-wasm": "bin/index.js" - } - }, - "node_modules/@libsql/linux-arm64-gnu": { - "version": "0.3.10", - "resolved": "https://registry.npmjs.org/@libsql/linux-arm64-gnu/-/linux-arm64-gnu-0.3.10.tgz", - "integrity": "sha512-2uXpi9d8qtyIOr7pyG4a88j6YXgemyIHEs2Wbp+PPletlCIPsFS+E7IQHbz8VwTohchOzcokGUm1Bc5QC+A7wg==", - "cpu": [ - "arm64" - ], - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@libsql/linux-arm64-musl": { - "version": "0.3.10", - "resolved": "https://registry.npmjs.org/@libsql/linux-arm64-musl/-/linux-arm64-musl-0.3.10.tgz", - "integrity": "sha512-72SN1FUavLvzHddCS861ynSpQndcW5oLGKA3U8CyMfgIZIwJAPc7+48Uj1plW00htXBx4GBpcntFp68KKIx3YQ==", - "cpu": [ - "arm64" - ], - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@libsql/linux-x64-gnu": { - "version": "0.3.10", - "resolved": "https://registry.npmjs.org/@libsql/linux-x64-gnu/-/linux-x64-gnu-0.3.10.tgz", - "integrity": "sha512-hXyNqVRi7ONuyWZ1SX6setxL0QaQ7InyS3bHLupsi9s7NpOGD5vcpTaYicJOqmIIm+6kt8vJfmo7ZxlarIHy7Q==", - "cpu": [ - "x64" - ], - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@libsql/linux-x64-musl": { - "version": "0.3.10", - "resolved": "https://registry.npmjs.org/@libsql/linux-x64-musl/-/linux-x64-musl-0.3.10.tgz", - "integrity": "sha512-kNmIRxomVwt9S+cLyYS497F/3gXFF4r8wW12YSBQgxG75JYft07AHVd8J7HINg+oqRkLzT0s+mVX5dM6nk68EQ==", - "cpu": [ - "x64" - ], - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@libsql/win32-x64-msvc": { - "version": "0.3.10", - "resolved": "https://registry.npmjs.org/@libsql/win32-x64-msvc/-/win32-x64-msvc-0.3.10.tgz", - "integrity": "sha512-c/6rjdtGULKrJkLgfLobFefObfOtxjXGmCfPxv6pr0epPCeUEssfDbDIeEH9fQUgzogIMWEHwT8so52UJ/iT1Q==", - "cpu": [ - "x64" - ], - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@neon-rs/load": { - "version": "0.0.4", - "resolved": "https://registry.npmjs.org/@neon-rs/load/-/load-0.0.4.tgz", - "integrity": "sha512-kTPhdZyTQxB+2wpiRcFWrDcejc4JI6tkPuS7UZCG4l6Zvc5kU/gGQ/ozvHTh1XR5tS+UlfAfGuPajjzQjCiHCw==" - }, - "node_modules/@sinclair/typebox": { - "version": "0.27.8", - "dev": true, - "license": "MIT" - }, - "node_modules/@sinonjs/commons": { - "version": "3.0.0", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "type-detect": "4.0.8" - } - }, - "node_modules/@sinonjs/fake-timers": { - "version": "10.3.0", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "@sinonjs/commons": "^3.0.0" - } - }, - "node_modules/@types/babel__core": { - "version": "7.20.5", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/parser": "^7.20.7", - "@babel/types": "^7.20.7", - "@types/babel__generator": "*", - "@types/babel__template": "*", - "@types/babel__traverse": "*" - } - }, - "node_modules/@types/babel__generator": { - "version": "7.6.8", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/types": "^7.0.0" - } - }, - "node_modules/@types/babel__template": { - "version": "7.4.4", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/parser": "^7.1.0", - "@babel/types": "^7.0.0" - } - }, - "node_modules/@types/babel__traverse": { - "version": "7.20.5", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/types": "^7.20.7" - } - }, - "node_modules/@types/graceful-fs": { - "version": "4.1.9", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/istanbul-lib-coverage": { - "version": "2.0.6", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/istanbul-lib-report": { - "version": "3.0.3", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/istanbul-lib-coverage": "*" - } - }, - "node_modules/@types/istanbul-reports": { - "version": "3.0.4", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/istanbul-lib-report": "*" - } - }, - "node_modules/@types/jest": { - "version": "29.5.11", - "dev": true, - "license": "MIT", - "dependencies": { - "expect": "^29.0.0", - "pretty-format": "^29.0.0" - } - }, - "node_modules/@types/node": { - "version": "18.19.8", - "license": "MIT", - "dependencies": { - "undici-types": "~5.26.4" - } - }, - "node_modules/@types/stack-utils": { - "version": "2.0.3", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/ws": { - "version": "8.5.10", - "license": "MIT", - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/yargs": { - "version": "17.0.32", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/yargs-parser": "*" - } - }, - "node_modules/@types/yargs-parser": { - "version": "21.0.3", - "dev": true, - "license": "MIT" - }, - "node_modules/ansi-escapes": { - "version": "4.3.2", - "dev": true, - "license": "MIT", - "dependencies": { - "type-fest": "^0.21.3" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/ansi-regex": { - "version": "5.0.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/ansi-sequence-parser": { - "version": "1.1.1", - "dev": true, - "license": "MIT" - }, - "node_modules/ansi-styles": { - "version": "4.3.0", - "dev": true, - "license": "MIT", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/anymatch": { - "version": "3.1.3", - "dev": true, - "license": "ISC", - "dependencies": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/argparse": { - "version": "1.0.10", - "dev": true, - "license": "MIT", - "dependencies": { - "sprintf-js": "~1.0.2" - } - }, - "node_modules/babel-jest": { - "version": "29.7.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/transform": "^29.7.0", - "@types/babel__core": "^7.1.14", - "babel-plugin-istanbul": "^6.1.1", - "babel-preset-jest": "^29.6.3", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", - "slash": "^3.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - }, - "peerDependencies": { - "@babel/core": "^7.8.0" - } - }, - "node_modules/babel-plugin-istanbul": { - "version": "6.1.1", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "@babel/helper-plugin-utils": "^7.0.0", - "@istanbuljs/load-nyc-config": "^1.0.0", - "@istanbuljs/schema": "^0.1.2", - "istanbul-lib-instrument": "^5.0.4", - "test-exclude": "^6.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/babel-plugin-istanbul/node_modules/istanbul-lib-instrument": { - "version": "5.2.1", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "@babel/core": "^7.12.3", - "@babel/parser": "^7.14.7", - "@istanbuljs/schema": "^0.1.2", - "istanbul-lib-coverage": "^3.2.0", - "semver": "^6.3.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/babel-plugin-jest-hoist": { - "version": "29.6.3", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/template": "^7.3.3", - "@babel/types": "^7.3.3", - "@types/babel__core": "^7.1.14", - "@types/babel__traverse": "^7.0.6" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/babel-preset-current-node-syntax": { - "version": "1.0.1", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/plugin-syntax-async-generators": "^7.8.4", - "@babel/plugin-syntax-bigint": "^7.8.3", - "@babel/plugin-syntax-class-properties": "^7.8.3", - "@babel/plugin-syntax-import-meta": "^7.8.3", - "@babel/plugin-syntax-json-strings": "^7.8.3", - "@babel/plugin-syntax-logical-assignment-operators": "^7.8.3", - "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", - "@babel/plugin-syntax-numeric-separator": "^7.8.3", - "@babel/plugin-syntax-object-rest-spread": "^7.8.3", - "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", - "@babel/plugin-syntax-optional-chaining": "^7.8.3", - "@babel/plugin-syntax-top-level-await": "^7.8.3" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/babel-preset-jest": { - "version": "29.6.3", - "dev": true, - "license": "MIT", - "dependencies": { - "babel-plugin-jest-hoist": "^29.6.3", - "babel-preset-current-node-syntax": "^1.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/balanced-match": { - "version": "1.0.2", - "dev": true, - "license": "MIT" - }, - "node_modules/brace-expansion": { - "version": "1.1.11", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/braces": { - "version": "3.0.2", - "license": "MIT", - "dependencies": { - "fill-range": "^7.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/browserslist": { - "version": "4.22.2", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "caniuse-lite": "^1.0.30001565", - "electron-to-chromium": "^1.4.601", - "node-releases": "^2.0.14", - "update-browserslist-db": "^1.0.13" - }, - "bin": { - "browserslist": "cli.js" - }, - "engines": { - "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" - } - }, - "node_modules/bs-logger": { - "version": "0.2.6", - "dev": true, - "license": "MIT", - "dependencies": { - "fast-json-stable-stringify": "2.x" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/bser": { - "version": "2.1.1", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "node-int64": "^0.4.0" - } - }, - "node_modules/buffer-from": { - "version": "1.1.2", - "dev": true, - "license": "MIT" - }, - "node_modules/callsites": { - "version": "3.1.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/camelcase": { - "version": "5.3.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/caniuse-lite": { - "version": "1.0.30001579", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/caniuse-lite" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "CC-BY-4.0" - }, - "node_modules/chalk": { - "version": "4.1.2", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/char-regex": { - "version": "1.0.2", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - } - }, - "node_modules/ci-info": { - "version": "3.9.0", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/sibiraj-s" - } - ], - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/cjs-module-lexer": { - "version": "1.2.3", - "dev": true, - "license": "MIT" - }, - "node_modules/cli-cursor": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-4.0.0.tgz", - "integrity": "sha512-VGtlMu3x/4DOtIUwEkRezxUZ2lBacNJCHash0N0WeZDBS+7Ux1dm3XWAgWYxLJFMMdOeXMHXorshEFhbMSGelg==", - "dependencies": { - "restore-cursor": "^4.0.0" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/cli-truncate": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-4.0.0.tgz", - "integrity": "sha512-nPdaFdQ0h/GEigbPClz11D0v/ZJEwxmeVZGeMo3Z5StPtUTkA9o1lD6QwoirYiSDzbcwn2XcjwmCp68W1IS4TA==", - "dependencies": { - "slice-ansi": "^5.0.0", - "string-width": "^7.0.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/cli-truncate/node_modules/ansi-regex": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", - "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" - } - }, - "node_modules/cli-truncate/node_modules/emoji-regex": { - "version": "10.3.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.3.0.tgz", - "integrity": "sha512-QpLs9D9v9kArv4lfDEgg1X/gN5XLnf/A6l9cs8SPZLRZR3ZkY9+kwIQTxm+fsSej5UMYGE8fdoaZVIBlqG0XTw==" - }, - "node_modules/cli-truncate/node_modules/string-width": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.1.0.tgz", - "integrity": "sha512-SEIJCWiX7Kg4c129n48aDRwLbFb2LJmXXFrWBG4NGaRtMQ3myKPKbwrD1BKqQn74oCoNMBVrfDEr5M9YxCsrkw==", - "dependencies": { - "emoji-regex": "^10.3.0", - "get-east-asian-width": "^1.0.0", - "strip-ansi": "^7.1.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/cli-truncate/node_modules/strip-ansi": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", - "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", - "dependencies": { - "ansi-regex": "^6.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" - } - }, - "node_modules/cliui": { - "version": "8.0.1", - "dev": true, - "license": "ISC", - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.1", - "wrap-ansi": "^7.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/co": { - "version": "4.6.0", - "dev": true, - "license": "MIT", - "engines": { - "iojs": ">= 1.0.0", - "node": ">= 0.12.0" - } - }, - "node_modules/collect-v8-coverage": { - "version": "1.0.2", - "dev": true, - "license": "MIT" - }, - "node_modules/color-convert": { - "version": "2.0.1", - "dev": true, - "license": "MIT", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/color-name": { - "version": "1.1.4", - "dev": true, - "license": "MIT" - }, - "node_modules/colorette": { - "version": "2.0.20", - "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", - "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==" - }, - "node_modules/commander": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-11.1.0.tgz", - "integrity": "sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==", - "engines": { - "node": ">=16" - } - }, - "node_modules/concat-map": { - "version": "0.0.1", - "dev": true, - "license": "MIT" - }, - "node_modules/convert-source-map": { - "version": "2.0.0", - "dev": true, - "license": "MIT" - }, - "node_modules/create-jest": { - "version": "29.7.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/types": "^29.6.3", - "chalk": "^4.0.0", - "exit": "^0.1.2", - "graceful-fs": "^4.2.9", - "jest-config": "^29.7.0", - "jest-util": "^29.7.0", - "prompts": "^2.0.1" - }, - "bin": { - "create-jest": "bin/create-jest.js" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/cross-spawn": { - "version": "7.0.3", - "license": "MIT", - "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/data-uri-to-buffer": { - "version": "4.0.1", - "license": "MIT", - "engines": { - "node": ">= 12" - } - }, - "node_modules/debug": { - "version": "4.3.4", - "license": "MIT", - "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/dedent": { - "version": "1.5.1", - "dev": true, - "license": "MIT", - "peerDependencies": { - "babel-plugin-macros": "^3.1.0" - }, - "peerDependenciesMeta": { - "babel-plugin-macros": { - "optional": true - } - } - }, - "node_modules/deepmerge": { - "version": "4.3.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/detect-libc": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.2.tgz", - "integrity": "sha512-UX6sGumvvqSaXgdKGUsgZWqcUyIXZ/vZTrlRT/iobiKhGL0zL4d3osHj3uqllWJK+i+sixDS/3COVEOFbupFyw==", - "engines": { - "node": ">=8" - } - }, - "node_modules/detect-newline": { - "version": "3.1.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/diff-sequences": { - "version": "29.6.3", - "dev": true, - "license": "MIT", - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/electron-to-chromium": { - "version": "1.4.637", - "dev": true, - "license": "ISC" - }, - "node_modules/emittery": { - "version": "0.13.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sindresorhus/emittery?sponsor=1" - } - }, - "node_modules/emoji-regex": { - "version": "8.0.0", - "dev": true, - "license": "MIT" - }, - "node_modules/error-ex": { - "version": "1.3.2", - "dev": true, - "license": "MIT", - "dependencies": { - "is-arrayish": "^0.2.1" - } - }, - "node_modules/escalade": { - "version": "3.1.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/escape-string-regexp": { - "version": "2.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/esprima": { - "version": "4.0.1", - "dev": true, - "license": "BSD-2-Clause", - "bin": { - "esparse": "bin/esparse.js", - "esvalidate": "bin/esvalidate.js" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/eventemitter3": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz", - "integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==" - }, - "node_modules/execa": { - "version": "5.1.1", - "dev": true, - "license": "MIT", - "dependencies": { - "cross-spawn": "^7.0.3", - "get-stream": "^6.0.0", - "human-signals": "^2.1.0", - "is-stream": "^2.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^4.0.1", - "onetime": "^5.1.2", - "signal-exit": "^3.0.3", - "strip-final-newline": "^2.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sindresorhus/execa?sponsor=1" - } - }, - "node_modules/exit": { - "version": "0.1.2", - "dev": true, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/expect": { - "version": "29.7.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/expect-utils": "^29.7.0", - "jest-get-type": "^29.6.3", - "jest-matcher-utils": "^29.7.0", - "jest-message-util": "^29.7.0", - "jest-util": "^29.7.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/fast-json-stable-stringify": { - "version": "2.1.0", - "dev": true, - "license": "MIT" - }, - "node_modules/fb-watchman": { - "version": "2.0.2", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "bser": "2.1.1" - } - }, - "node_modules/fetch-blob": { - "version": "3.2.0", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/jimmywarting" - }, - { - "type": "paypal", - "url": "https://paypal.me/jimmywarting" - } - ], - "license": "MIT", - "dependencies": { - "node-domexception": "^1.0.0", - "web-streams-polyfill": "^3.0.3" - }, - "engines": { - "node": "^12.20 || >= 14.13" - } - }, - "node_modules/fill-range": { - "version": "7.0.1", - "license": "MIT", - "dependencies": { - "to-regex-range": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/find-up": { - "version": "4.1.0", - "dev": true, - "license": "MIT", - "dependencies": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/formdata-polyfill": { - "version": "4.0.10", - "license": "MIT", - "dependencies": { - "fetch-blob": "^3.1.2" - }, - "engines": { - "node": ">=12.20.0" - } - }, - "node_modules/fs.realpath": { - "version": "1.0.0", - "dev": true, - "license": "ISC" - }, - "node_modules/function-bind": { - "version": "1.1.2", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/gensync": { - "version": "1.0.0-beta.2", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/get-caller-file": { - "version": "2.0.5", - "dev": true, - "license": "ISC", - "engines": { - "node": "6.* || 8.* || >= 10.*" - } - }, - "node_modules/get-east-asian-width": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.2.0.tgz", - "integrity": "sha512-2nk+7SIVb14QrgXFHcm84tD4bKQz0RxPuMT8Ag5KPOq7J5fEmAg0UbXdTOSHqNuHSU28k55qnceesxXRZGzKWA==", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/get-package-type": { - "version": "0.1.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/get-stream": { - "version": "6.0.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/glob": { - "version": "7.2.3", - "dev": true, - "license": "ISC", - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/globals": { - "version": "11.12.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/graceful-fs": { - "version": "4.2.11", - "dev": true, - "license": "ISC" - }, - "node_modules/has-flag": { - "version": "4.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/hasown": { - "version": "2.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "function-bind": "^1.1.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/html-escaper": { - "version": "2.0.2", - "dev": true, - "license": "MIT" - }, - "node_modules/human-signals": { - "version": "2.1.0", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=10.17.0" - } - }, - "node_modules/husky": { - "version": "9.0.11", - "resolved": "https://registry.npmjs.org/husky/-/husky-9.0.11.tgz", - "integrity": "sha512-AB6lFlbwwyIqMdHYhwPe+kjOC3Oc5P3nThEoW/AaO2BX3vJDjWPFxYLxokUZOo6RNX20He3AaT8sESs9NJcmEw==", - "bin": { - "husky": "bin.mjs" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/typicode" - } - }, - "node_modules/import-local": { - "version": "3.1.0", - "dev": true, - "license": "MIT", - "dependencies": { - "pkg-dir": "^4.2.0", - "resolve-cwd": "^3.0.0" - }, - "bin": { - "import-local-fixture": "fixtures/cli.js" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/imurmurhash": { - "version": "0.1.4", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.8.19" - } - }, - "node_modules/inflight": { - "version": "1.0.6", - "dev": true, - "license": "ISC", - "dependencies": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "node_modules/inherits": { - "version": "2.0.4", - "dev": true, - "license": "ISC" - }, - "node_modules/is-arrayish": { - "version": "0.2.1", - "dev": true, - "license": "MIT" - }, - "node_modules/is-core-module": { - "version": "2.13.1", - "dev": true, - "license": "MIT", - "dependencies": { - "hasown": "^2.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/is-generator-fn": { - "version": "2.1.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/is-number": { - "version": "7.0.0", - "license": "MIT", - "engines": { - "node": ">=0.12.0" - } - }, - "node_modules/is-stream": { - "version": "2.0.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/isexe": { - "version": "2.0.0", - "license": "ISC" - }, - "node_modules/istanbul-lib-coverage": { - "version": "3.2.2", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=8" - } - }, - "node_modules/istanbul-lib-instrument": { - "version": "6.0.1", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "@babel/core": "^7.12.3", - "@babel/parser": "^7.14.7", - "@istanbuljs/schema": "^0.1.2", - "istanbul-lib-coverage": "^3.2.0", - "semver": "^7.5.4" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/istanbul-lib-instrument/node_modules/lru-cache": { - "version": "6.0.0", - "dev": true, - "license": "ISC", - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/istanbul-lib-instrument/node_modules/semver": { - "version": "7.5.4", - "dev": true, - "license": "ISC", - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/istanbul-lib-instrument/node_modules/yallist": { - "version": "4.0.0", - "dev": true, - "license": "ISC" - }, - "node_modules/istanbul-lib-report": { - "version": "3.0.1", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "istanbul-lib-coverage": "^3.0.0", - "make-dir": "^4.0.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/istanbul-lib-source-maps": { - "version": "4.0.1", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "debug": "^4.1.1", - "istanbul-lib-coverage": "^3.0.0", - "source-map": "^0.6.1" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/istanbul-reports": { - "version": "3.1.6", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "html-escaper": "^2.0.0", - "istanbul-lib-report": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest": { - "version": "29.7.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/core": "^29.7.0", - "@jest/types": "^29.6.3", - "import-local": "^3.0.2", - "jest-cli": "^29.7.0" - }, - "bin": { - "jest": "bin/jest.js" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - }, - "peerDependencies": { - "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" - }, - "peerDependenciesMeta": { - "node-notifier": { - "optional": true - } - } - }, - "node_modules/jest-changed-files": { - "version": "29.7.0", - "dev": true, - "license": "MIT", - "dependencies": { - "execa": "^5.0.0", - "jest-util": "^29.7.0", - "p-limit": "^3.1.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-circus": { - "version": "29.7.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/environment": "^29.7.0", - "@jest/expect": "^29.7.0", - "@jest/test-result": "^29.7.0", - "@jest/types": "^29.6.3", - "@types/node": "*", - "chalk": "^4.0.0", - "co": "^4.6.0", - "dedent": "^1.0.0", - "is-generator-fn": "^2.0.0", - "jest-each": "^29.7.0", - "jest-matcher-utils": "^29.7.0", - "jest-message-util": "^29.7.0", - "jest-runtime": "^29.7.0", - "jest-snapshot": "^29.7.0", - "jest-util": "^29.7.0", - "p-limit": "^3.1.0", - "pretty-format": "^29.7.0", - "pure-rand": "^6.0.0", - "slash": "^3.0.0", - "stack-utils": "^2.0.3" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-cli": { - "version": "29.7.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/core": "^29.7.0", - "@jest/test-result": "^29.7.0", - "@jest/types": "^29.6.3", - "chalk": "^4.0.0", - "create-jest": "^29.7.0", - "exit": "^0.1.2", - "import-local": "^3.0.2", - "jest-config": "^29.7.0", - "jest-util": "^29.7.0", - "jest-validate": "^29.7.0", - "yargs": "^17.3.1" - }, - "bin": { - "jest": "bin/jest.js" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - }, - "peerDependencies": { - "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" - }, - "peerDependenciesMeta": { - "node-notifier": { - "optional": true - } - } - }, - "node_modules/jest-config": { - "version": "29.7.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/core": "^7.11.6", - "@jest/test-sequencer": "^29.7.0", - "@jest/types": "^29.6.3", - "babel-jest": "^29.7.0", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "deepmerge": "^4.2.2", - "glob": "^7.1.3", - "graceful-fs": "^4.2.9", - "jest-circus": "^29.7.0", - "jest-environment-node": "^29.7.0", - "jest-get-type": "^29.6.3", - "jest-regex-util": "^29.6.3", - "jest-resolve": "^29.7.0", - "jest-runner": "^29.7.0", - "jest-util": "^29.7.0", - "jest-validate": "^29.7.0", - "micromatch": "^4.0.4", - "parse-json": "^5.2.0", - "pretty-format": "^29.7.0", - "slash": "^3.0.0", - "strip-json-comments": "^3.1.1" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - }, - "peerDependencies": { - "@types/node": "*", - "ts-node": ">=9.0.0" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - }, - "ts-node": { - "optional": true - } - } - }, - "node_modules/jest-diff": { - "version": "29.7.0", - "dev": true, - "license": "MIT", - "dependencies": { - "chalk": "^4.0.0", - "diff-sequences": "^29.6.3", - "jest-get-type": "^29.6.3", - "pretty-format": "^29.7.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-docblock": { - "version": "29.7.0", - "dev": true, - "license": "MIT", - "dependencies": { - "detect-newline": "^3.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-each": { - "version": "29.7.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/types": "^29.6.3", - "chalk": "^4.0.0", - "jest-get-type": "^29.6.3", - "jest-util": "^29.7.0", - "pretty-format": "^29.7.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-environment-node": { - "version": "29.7.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/environment": "^29.7.0", - "@jest/fake-timers": "^29.7.0", - "@jest/types": "^29.6.3", - "@types/node": "*", - "jest-mock": "^29.7.0", - "jest-util": "^29.7.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-get-type": { - "version": "29.6.3", - "dev": true, - "license": "MIT", - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-haste-map": { - "version": "29.7.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/types": "^29.6.3", - "@types/graceful-fs": "^4.1.3", - "@types/node": "*", - "anymatch": "^3.0.3", - "fb-watchman": "^2.0.0", - "graceful-fs": "^4.2.9", - "jest-regex-util": "^29.6.3", - "jest-util": "^29.7.0", - "jest-worker": "^29.7.0", - "micromatch": "^4.0.4", - "walker": "^1.0.8" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - }, - "optionalDependencies": { - "fsevents": "^2.3.2" - } - }, - "node_modules/jest-leak-detector": { - "version": "29.7.0", - "dev": true, - "license": "MIT", - "dependencies": { - "jest-get-type": "^29.6.3", - "pretty-format": "^29.7.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-matcher-utils": { - "version": "29.7.0", - "dev": true, - "license": "MIT", - "dependencies": { - "chalk": "^4.0.0", - "jest-diff": "^29.7.0", - "jest-get-type": "^29.6.3", - "pretty-format": "^29.7.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-message-util": { - "version": "29.7.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.12.13", - "@jest/types": "^29.6.3", - "@types/stack-utils": "^2.0.0", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", - "micromatch": "^4.0.4", - "pretty-format": "^29.7.0", - "slash": "^3.0.0", - "stack-utils": "^2.0.3" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-mock": { - "version": "29.7.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/types": "^29.6.3", - "@types/node": "*", - "jest-util": "^29.7.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-pnp-resolver": { - "version": "1.2.3", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - }, - "peerDependencies": { - "jest-resolve": "*" - }, - "peerDependenciesMeta": { - "jest-resolve": { - "optional": true - } - } - }, - "node_modules/jest-regex-util": { - "version": "29.6.3", - "dev": true, - "license": "MIT", - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-resolve": { - "version": "29.7.0", - "dev": true, - "license": "MIT", - "dependencies": { - "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^29.7.0", - "jest-pnp-resolver": "^1.2.2", - "jest-util": "^29.7.0", - "jest-validate": "^29.7.0", - "resolve": "^1.20.0", - "resolve.exports": "^2.0.0", - "slash": "^3.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-resolve-dependencies": { - "version": "29.7.0", - "dev": true, - "license": "MIT", - "dependencies": { - "jest-regex-util": "^29.6.3", - "jest-snapshot": "^29.7.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-runner": { - "version": "29.7.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/console": "^29.7.0", - "@jest/environment": "^29.7.0", - "@jest/test-result": "^29.7.0", - "@jest/transform": "^29.7.0", - "@jest/types": "^29.6.3", - "@types/node": "*", - "chalk": "^4.0.0", - "emittery": "^0.13.1", - "graceful-fs": "^4.2.9", - "jest-docblock": "^29.7.0", - "jest-environment-node": "^29.7.0", - "jest-haste-map": "^29.7.0", - "jest-leak-detector": "^29.7.0", - "jest-message-util": "^29.7.0", - "jest-resolve": "^29.7.0", - "jest-runtime": "^29.7.0", - "jest-util": "^29.7.0", - "jest-watcher": "^29.7.0", - "jest-worker": "^29.7.0", - "p-limit": "^3.1.0", - "source-map-support": "0.5.13" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-runtime": { - "version": "29.7.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/environment": "^29.7.0", - "@jest/fake-timers": "^29.7.0", - "@jest/globals": "^29.7.0", - "@jest/source-map": "^29.6.3", - "@jest/test-result": "^29.7.0", - "@jest/transform": "^29.7.0", - "@jest/types": "^29.6.3", - "@types/node": "*", - "chalk": "^4.0.0", - "cjs-module-lexer": "^1.0.0", - "collect-v8-coverage": "^1.0.0", - "glob": "^7.1.3", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^29.7.0", - "jest-message-util": "^29.7.0", - "jest-mock": "^29.7.0", - "jest-regex-util": "^29.6.3", - "jest-resolve": "^29.7.0", - "jest-snapshot": "^29.7.0", - "jest-util": "^29.7.0", - "slash": "^3.0.0", - "strip-bom": "^4.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-snapshot": { - "version": "29.7.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/core": "^7.11.6", - "@babel/generator": "^7.7.2", - "@babel/plugin-syntax-jsx": "^7.7.2", - "@babel/plugin-syntax-typescript": "^7.7.2", - "@babel/types": "^7.3.3", - "@jest/expect-utils": "^29.7.0", - "@jest/transform": "^29.7.0", - "@jest/types": "^29.6.3", - "babel-preset-current-node-syntax": "^1.0.0", - "chalk": "^4.0.0", - "expect": "^29.7.0", - "graceful-fs": "^4.2.9", - "jest-diff": "^29.7.0", - "jest-get-type": "^29.6.3", - "jest-matcher-utils": "^29.7.0", - "jest-message-util": "^29.7.0", - "jest-util": "^29.7.0", - "natural-compare": "^1.4.0", - "pretty-format": "^29.7.0", - "semver": "^7.5.3" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-snapshot/node_modules/lru-cache": { - "version": "6.0.0", - "dev": true, - "license": "ISC", - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/jest-snapshot/node_modules/semver": { - "version": "7.5.4", - "dev": true, - "license": "ISC", - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/jest-snapshot/node_modules/yallist": { - "version": "4.0.0", - "dev": true, - "license": "ISC" - }, - "node_modules/jest-util": { - "version": "29.7.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/types": "^29.6.3", - "@types/node": "*", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "graceful-fs": "^4.2.9", - "picomatch": "^2.2.3" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-validate": { - "version": "29.7.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/types": "^29.6.3", - "camelcase": "^6.2.0", - "chalk": "^4.0.0", - "jest-get-type": "^29.6.3", - "leven": "^3.1.0", - "pretty-format": "^29.7.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-validate/node_modules/camelcase": { - "version": "6.3.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/jest-watcher": { - "version": "29.7.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/test-result": "^29.7.0", - "@jest/types": "^29.6.3", - "@types/node": "*", - "ansi-escapes": "^4.2.1", - "chalk": "^4.0.0", - "emittery": "^0.13.1", - "jest-util": "^29.7.0", - "string-length": "^4.0.1" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-worker": { - "version": "29.7.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "*", - "jest-util": "^29.7.0", - "merge-stream": "^2.0.0", - "supports-color": "^8.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-worker/node_modules/supports-color": { - "version": "8.1.1", - "dev": true, - "license": "MIT", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/supports-color?sponsor=1" - } - }, - "node_modules/js-base64": { - "version": "3.7.5", - "license": "BSD-3-Clause" - }, - "node_modules/js-tokens": { - "version": "4.0.0", - "dev": true, - "license": "MIT" - }, - "node_modules/js-yaml": { - "version": "3.14.1", - "dev": true, - "license": "MIT", - "dependencies": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/jsesc": { - "version": "2.5.2", - "dev": true, - "license": "MIT", - "bin": { - "jsesc": "bin/jsesc" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/json-parse-even-better-errors": { - "version": "2.3.1", - "dev": true, - "license": "MIT" - }, - "node_modules/json5": { - "version": "2.2.3", - "dev": true, - "license": "MIT", - "bin": { - "json5": "lib/cli.js" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/jsonc-parser": { - "version": "3.2.0", - "dev": true, - "license": "MIT" - }, - "node_modules/kleur": { - "version": "3.0.3", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/leven": { - "version": "3.1.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/libsql": { - "version": "0.3.10", - "resolved": "https://registry.npmjs.org/libsql/-/libsql-0.3.10.tgz", - "integrity": "sha512-/8YMTbwWFPmrDWY+YFK3kYqVPFkMgQre0DGmBaOmjogMdSe+7GHm1/q9AZ61AWkEub/vHmi+bA4tqIzVhKnqzg==", - "cpu": [ - "x64", - "arm64", - "wasm32" - ], - "os": [ - "darwin", - "linux", - "win32" - ], - "dependencies": { - "@neon-rs/load": "^0.0.4", - "detect-libc": "2.0.2" - }, - "optionalDependencies": { - "@libsql/darwin-arm64": "0.3.10", - "@libsql/darwin-x64": "0.3.10", - "@libsql/linux-arm64-gnu": "0.3.10", - "@libsql/linux-arm64-musl": "0.3.10", - "@libsql/linux-x64-gnu": "0.3.10", - "@libsql/linux-x64-musl": "0.3.10", - "@libsql/win32-x64-msvc": "0.3.10" - } - }, - "node_modules/lilconfig": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.0.0.tgz", - "integrity": "sha512-K2U4W2Ff5ibV7j7ydLr+zLAkIg5JJ4lPn1Ltsdt+Tz/IjQ8buJ55pZAxoP34lqIiwtF9iAvtLv3JGv7CAyAg+g==", - "engines": { - "node": ">=14" - } - }, - "node_modules/lines-and-columns": { - "version": "1.2.4", - "dev": true, - "license": "MIT" - }, - "node_modules/lint-staged": { - "version": "15.2.2", - "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-15.2.2.tgz", - "integrity": "sha512-TiTt93OPh1OZOsb5B7k96A/ATl2AjIZo+vnzFZ6oHK5FuTk63ByDtxGQpHm+kFETjEWqgkF95M8FRXKR/LEBcw==", - "dependencies": { - "chalk": "5.3.0", - "commander": "11.1.0", - "debug": "4.3.4", - "execa": "8.0.1", - "lilconfig": "3.0.0", - "listr2": "8.0.1", - "micromatch": "4.0.5", - "pidtree": "0.6.0", - "string-argv": "0.3.2", - "yaml": "2.3.4" - }, - "bin": { - "lint-staged": "bin/lint-staged.js" - }, - "engines": { - "node": ">=18.12.0" - }, - "funding": { - "url": "https://opencollective.com/lint-staged" - } - }, - "node_modules/lint-staged/node_modules/chalk": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz", - "integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==", - "engines": { - "node": "^12.17.0 || ^14.13 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/lint-staged/node_modules/execa": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/execa/-/execa-8.0.1.tgz", - "integrity": "sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==", - "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.1.0", - "onetime": "^6.0.0", - "signal-exit": "^4.1.0", - "strip-final-newline": "^3.0.0" - }, - "engines": { - "node": ">=16.17" - }, - "funding": { - "url": "https://github.com/sindresorhus/execa?sponsor=1" - } - }, - "node_modules/lint-staged/node_modules/get-stream": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-8.0.1.tgz", - "integrity": "sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==", - "engines": { - "node": ">=16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/lint-staged/node_modules/human-signals": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-5.0.0.tgz", - "integrity": "sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==", - "engines": { - "node": ">=16.17.0" - } - }, - "node_modules/lint-staged/node_modules/is-stream": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-3.0.0.tgz", - "integrity": "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==", - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/lint-staged/node_modules/mimic-fn": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz", - "integrity": "sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/lint-staged/node_modules/npm-run-path": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.3.0.tgz", - "integrity": "sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==", - "dependencies": { - "path-key": "^4.0.0" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/lint-staged/node_modules/onetime": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-6.0.0.tgz", - "integrity": "sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==", - "dependencies": { - "mimic-fn": "^4.0.0" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/lint-staged/node_modules/path-key": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz", - "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/lint-staged/node_modules/signal-exit": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", - "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/lint-staged/node_modules/strip-final-newline": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-3.0.0.tgz", - "integrity": "sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/listr2": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/listr2/-/listr2-8.0.1.tgz", - "integrity": "sha512-ovJXBXkKGfq+CwmKTjluEqFi3p4h8xvkxGQQAQan22YCgef4KZ1mKGjzfGh6PL6AW5Csw0QiQPNuQyH+6Xk3hA==", - "dependencies": { - "cli-truncate": "^4.0.0", - "colorette": "^2.0.20", - "eventemitter3": "^5.0.1", - "log-update": "^6.0.0", - "rfdc": "^1.3.0", - "wrap-ansi": "^9.0.0" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/listr2/node_modules/ansi-regex": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", - "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" - } - }, - "node_modules/listr2/node_modules/ansi-styles": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", - "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/listr2/node_modules/emoji-regex": { - "version": "10.3.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.3.0.tgz", - "integrity": "sha512-QpLs9D9v9kArv4lfDEgg1X/gN5XLnf/A6l9cs8SPZLRZR3ZkY9+kwIQTxm+fsSej5UMYGE8fdoaZVIBlqG0XTw==" - }, - "node_modules/listr2/node_modules/string-width": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.1.0.tgz", - "integrity": "sha512-SEIJCWiX7Kg4c129n48aDRwLbFb2LJmXXFrWBG4NGaRtMQ3myKPKbwrD1BKqQn74oCoNMBVrfDEr5M9YxCsrkw==", - "dependencies": { - "emoji-regex": "^10.3.0", - "get-east-asian-width": "^1.0.0", - "strip-ansi": "^7.1.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/listr2/node_modules/strip-ansi": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", - "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", - "dependencies": { - "ansi-regex": "^6.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" - } - }, - "node_modules/listr2/node_modules/wrap-ansi": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.0.tgz", - "integrity": "sha512-G8ura3S+3Z2G+mkgNRq8dqaFZAuxfsxpBB8OCTGRTCtp+l/v9nbFNmCUP1BZMts3G1142MsZfn6eeUKrr4PD1Q==", - "dependencies": { - "ansi-styles": "^6.2.1", - "string-width": "^7.0.0", - "strip-ansi": "^7.1.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/locate-path": { - "version": "5.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "p-locate": "^4.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/lodash.memoize": { - "version": "4.1.2", - "dev": true, - "license": "MIT" - }, - "node_modules/log-update": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/log-update/-/log-update-6.0.0.tgz", - "integrity": "sha512-niTvB4gqvtof056rRIrTZvjNYE4rCUzO6X/X+kYjd7WFxXeJ0NwEFnRxX6ehkvv3jTwrXnNdtAak5XYZuIyPFw==", - "dependencies": { - "ansi-escapes": "^6.2.0", - "cli-cursor": "^4.0.0", - "slice-ansi": "^7.0.0", - "strip-ansi": "^7.1.0", - "wrap-ansi": "^9.0.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/log-update/node_modules/ansi-escapes": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-6.2.1.tgz", - "integrity": "sha512-4nJ3yixlEthEJ9Rk4vPcdBRkZvQZlYyu8j4/Mqz5sgIkddmEnH2Yj2ZrnP9S3tQOvSNRUIgVNF/1yPpRAGNRig==", - "engines": { - "node": ">=14.16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/log-update/node_modules/ansi-regex": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", - "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" - } - }, - "node_modules/log-update/node_modules/ansi-styles": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", - "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/log-update/node_modules/emoji-regex": { - "version": "10.3.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.3.0.tgz", - "integrity": "sha512-QpLs9D9v9kArv4lfDEgg1X/gN5XLnf/A6l9cs8SPZLRZR3ZkY9+kwIQTxm+fsSej5UMYGE8fdoaZVIBlqG0XTw==" - }, - "node_modules/log-update/node_modules/is-fullwidth-code-point": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-5.0.0.tgz", - "integrity": "sha512-OVa3u9kkBbw7b8Xw5F9P+D/T9X+Z4+JruYVNapTjPYZYUznQ5YfWeFkOj606XYYW8yugTfC8Pj0hYqvi4ryAhA==", - "dependencies": { - "get-east-asian-width": "^1.0.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/log-update/node_modules/slice-ansi": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-7.1.0.tgz", - "integrity": "sha512-bSiSngZ/jWeX93BqeIAbImyTbEihizcwNjFoRUIY/T1wWQsfsm2Vw1agPKylXvQTU7iASGdHhyqRlqQzfz+Htg==", - "dependencies": { - "ansi-styles": "^6.2.1", - "is-fullwidth-code-point": "^5.0.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/chalk/slice-ansi?sponsor=1" - } - }, - "node_modules/log-update/node_modules/string-width": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.1.0.tgz", - "integrity": "sha512-SEIJCWiX7Kg4c129n48aDRwLbFb2LJmXXFrWBG4NGaRtMQ3myKPKbwrD1BKqQn74oCoNMBVrfDEr5M9YxCsrkw==", - "dependencies": { - "emoji-regex": "^10.3.0", - "get-east-asian-width": "^1.0.0", - "strip-ansi": "^7.1.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/log-update/node_modules/strip-ansi": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", - "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", - "dependencies": { - "ansi-regex": "^6.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" - } - }, - "node_modules/log-update/node_modules/wrap-ansi": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.0.tgz", - "integrity": "sha512-G8ura3S+3Z2G+mkgNRq8dqaFZAuxfsxpBB8OCTGRTCtp+l/v9nbFNmCUP1BZMts3G1142MsZfn6eeUKrr4PD1Q==", - "dependencies": { - "ansi-styles": "^6.2.1", - "string-width": "^7.0.0", - "strip-ansi": "^7.1.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/lru-cache": { - "version": "5.1.1", - "dev": true, - "license": "ISC", - "dependencies": { - "yallist": "^3.0.2" - } - }, - "node_modules/lunr": { - "version": "2.3.9", - "dev": true, - "license": "MIT" - }, - "node_modules/make-dir": { - "version": "4.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "semver": "^7.5.3" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/make-dir/node_modules/lru-cache": { - "version": "6.0.0", - "dev": true, - "license": "ISC", - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/make-dir/node_modules/semver": { - "version": "7.5.4", - "dev": true, - "license": "ISC", - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/make-dir/node_modules/yallist": { - "version": "4.0.0", - "dev": true, - "license": "ISC" - }, - "node_modules/make-error": { - "version": "1.3.6", - "dev": true, - "license": "ISC" - }, - "node_modules/makeerror": { - "version": "1.0.12", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "tmpl": "1.0.5" - } - }, - "node_modules/marked": { - "version": "4.3.0", - "dev": true, - "license": "MIT", - "bin": { - "marked": "bin/marked.js" - }, - "engines": { - "node": ">= 12" - } - }, - "node_modules/merge-stream": { - "version": "2.0.0", - "license": "MIT" - }, - "node_modules/micromatch": { - "version": "4.0.5", - "license": "MIT", - "dependencies": { - "braces": "^3.0.2", - "picomatch": "^2.3.1" - }, - "engines": { - "node": ">=8.6" - } - }, - "node_modules/mimic-fn": { - "version": "2.1.0", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/minimatch": { - "version": "3.1.2", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/ms": { - "version": "2.1.2", - "license": "MIT" - }, - "node_modules/natural-compare": { - "version": "1.4.0", - "dev": true, - "license": "MIT" - }, - "node_modules/node-domexception": { - "version": "1.0.0", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/jimmywarting" - }, - { - "type": "github", - "url": "https://paypal.me/jimmywarting" - } - ], - "license": "MIT", - "engines": { - "node": ">=10.5.0" - } - }, - "node_modules/node-fetch": { - "version": "3.3.2", - "license": "MIT", - "dependencies": { - "data-uri-to-buffer": "^4.0.0", - "fetch-blob": "^3.1.4", - "formdata-polyfill": "^4.0.10" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/node-fetch" - } - }, - "node_modules/node-int64": { - "version": "0.4.0", - "dev": true, - "license": "MIT" - }, - "node_modules/node-releases": { - "version": "2.0.14", - "dev": true, - "license": "MIT" - }, - "node_modules/normalize-path": { - "version": "3.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/npm-run-path": { - "version": "4.0.1", - "dev": true, - "license": "MIT", - "dependencies": { - "path-key": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/once": { - "version": "1.4.0", - "dev": true, - "license": "ISC", - "dependencies": { - "wrappy": "1" - } - }, - "node_modules/onetime": { - "version": "5.1.2", - "license": "MIT", - "dependencies": { - "mimic-fn": "^2.1.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-limit": { - "version": "3.1.0", - "dev": true, - "license": "MIT", - "dependencies": { - "yocto-queue": "^0.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-locate": { - "version": "4.1.0", - "dev": true, - "license": "MIT", - "dependencies": { - "p-limit": "^2.2.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/p-locate/node_modules/p-limit": { - "version": "2.3.0", - "dev": true, - "license": "MIT", - "dependencies": { - "p-try": "^2.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-try": { - "version": "2.2.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/parse-json": { - "version": "5.2.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.0.0", - "error-ex": "^1.3.1", - "json-parse-even-better-errors": "^2.3.0", - "lines-and-columns": "^1.1.6" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/path-exists": { - "version": "4.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/path-is-absolute": { - "version": "1.0.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/path-key": { - "version": "3.1.1", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/path-parse": { - "version": "1.0.7", - "dev": true, - "license": "MIT" - }, - "node_modules/picocolors": { - "version": "1.0.0", - "dev": true, - "license": "ISC" - }, - "node_modules/picomatch": { - "version": "2.3.1", - "license": "MIT", - "engines": { - "node": ">=8.6" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/pidtree": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/pidtree/-/pidtree-0.6.0.tgz", - "integrity": "sha512-eG2dWTVw5bzqGRztnHExczNxt5VGsE6OwTeCG3fdUf9KBsZzO3R5OIIIzWR+iZA0NtZ+RDVdaoE2dK1cn6jH4g==", - "bin": { - "pidtree": "bin/pidtree.js" - }, - "engines": { - "node": ">=0.10" - } - }, - "node_modules/pirates": { - "version": "4.0.6", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 6" - } - }, - "node_modules/pkg-dir": { - "version": "4.2.0", - "dev": true, - "license": "MIT", - "dependencies": { - "find-up": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/prettier": { - "version": "3.2.5", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.2.5.tgz", - "integrity": "sha512-3/GWa9aOC0YeD7LUfvOG2NiDyhOWRvt1k+rcKhOuYnMY24iiCphgneUfJDyFXd6rZCAnuLBv6UeAULtrhT/F4A==", - "dev": true, - "bin": { - "prettier": "bin/prettier.cjs" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/prettier/prettier?sponsor=1" - } - }, - "node_modules/pretty-format": { - "version": "29.7.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/schemas": "^29.6.3", - "ansi-styles": "^5.0.0", - "react-is": "^18.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/pretty-format/node_modules/ansi-styles": { - "version": "5.2.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/prompts": { - "version": "2.4.2", - "dev": true, - "license": "MIT", - "dependencies": { - "kleur": "^3.0.3", - "sisteransi": "^1.0.5" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/pure-rand": { - "version": "6.0.4", - "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://github.com/sponsors/dubzzz" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/fast-check" - } - ], - "license": "MIT" - }, - "node_modules/react-is": { - "version": "18.2.0", - "dev": true, - "license": "MIT" - }, - "node_modules/require-directory": { - "version": "2.1.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/resolve": { - "version": "1.22.8", - "dev": true, - "license": "MIT", - "dependencies": { - "is-core-module": "^2.13.0", - "path-parse": "^1.0.7", - "supports-preserve-symlinks-flag": "^1.0.0" - }, - "bin": { - "resolve": "bin/resolve" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/resolve-cwd": { - "version": "3.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "resolve-from": "^5.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/resolve-from": { - "version": "5.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/resolve.exports": { - "version": "2.0.2", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - } - }, - "node_modules/restore-cursor": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-4.0.0.tgz", - "integrity": "sha512-I9fPXU9geO9bHOt9pHHOhOkYerIMsmVaWB0rA2AI9ERh/+x/i7MV5HKBNrg+ljO5eoPVgCcnFuRjJ9uH6I/3eg==", - "dependencies": { - "onetime": "^5.1.0", - "signal-exit": "^3.0.2" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/rfdc": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.3.1.tgz", - "integrity": "sha512-r5a3l5HzYlIC68TpmYKlxWjmOP6wiPJ1vWv2HeLhNsRZMrCkxeqxiHlQ21oXmQ4F3SiryXBHhAD7JZqvOJjFmg==" - }, - "node_modules/semver": { - "version": "6.3.1", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/shebang-command": { - "version": "2.0.0", - "license": "MIT", - "dependencies": { - "shebang-regex": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/shebang-regex": { - "version": "3.0.0", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/shiki": { - "version": "0.14.7", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-sequence-parser": "^1.1.0", - "jsonc-parser": "^3.2.0", - "vscode-oniguruma": "^1.7.0", - "vscode-textmate": "^8.0.0" - } - }, - "node_modules/signal-exit": { - "version": "3.0.7", - "license": "ISC" - }, - "node_modules/sisteransi": { - "version": "1.0.5", - "dev": true, - "license": "MIT" - }, - "node_modules/slash": { - "version": "3.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/slice-ansi": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-5.0.0.tgz", - "integrity": "sha512-FC+lgizVPfie0kkhqUScwRu1O/lF6NOgJmlCgK+/LYxDCTk8sGelYaHDhFcDN+Sn3Cv+3VSa4Byeo+IMCzpMgQ==", - "dependencies": { - "ansi-styles": "^6.0.0", - "is-fullwidth-code-point": "^4.0.0" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/slice-ansi?sponsor=1" - } - }, - "node_modules/slice-ansi/node_modules/ansi-styles": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", - "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/slice-ansi/node_modules/is-fullwidth-code-point": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-4.0.0.tgz", - "integrity": "sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ==", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/source-map": { - "version": "0.6.1", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/source-map-support": { - "version": "0.5.13", - "dev": true, - "license": "MIT", - "dependencies": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" - } - }, - "node_modules/sprintf-js": { - "version": "1.0.3", - "dev": true, - "license": "BSD-3-Clause" - }, - "node_modules/stack-utils": { - "version": "2.0.6", - "dev": true, - "license": "MIT", - "dependencies": { - "escape-string-regexp": "^2.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/string-argv": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/string-argv/-/string-argv-0.3.2.tgz", - "integrity": "sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q==", - "engines": { - "node": ">=0.6.19" - } - }, - "node_modules/string-length": { - "version": "4.0.2", - "dev": true, - "license": "MIT", - "dependencies": { - "char-regex": "^1.0.2", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/string-width": { - "version": "4.2.3", - "dev": true, - "license": "MIT", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-ansi": { - "version": "6.0.1", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-bom": { - "version": "4.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-final-newline": { - "version": "2.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/strip-json-comments": { - "version": "3.1.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/supports-color": { - "version": "7.2.0", - "dev": true, - "license": "MIT", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/supports-preserve-symlinks-flag": { - "version": "1.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/test-exclude": { - "version": "6.0.0", - "dev": true, - "license": "ISC", - "dependencies": { - "@istanbuljs/schema": "^0.1.2", - "glob": "^7.1.4", - "minimatch": "^3.0.4" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/tmpl": { - "version": "1.0.5", - "dev": true, - "license": "BSD-3-Clause" - }, - "node_modules/to-fast-properties": { - "version": "2.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/to-regex-range": { - "version": "5.0.1", - "license": "MIT", - "dependencies": { - "is-number": "^7.0.0" - }, - "engines": { - "node": ">=8.0" - } - }, - "node_modules/ts-jest": { - "version": "29.1.1", - "dev": true, - "license": "MIT", - "dependencies": { - "bs-logger": "0.x", - "fast-json-stable-stringify": "2.x", - "jest-util": "^29.0.0", - "json5": "^2.2.3", - "lodash.memoize": "4.x", - "make-error": "1.x", - "semver": "^7.5.3", - "yargs-parser": "^21.0.1" - }, - "bin": { - "ts-jest": "cli.js" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - }, - "peerDependencies": { - "@babel/core": ">=7.0.0-beta.0 <8", - "@jest/types": "^29.0.0", - "babel-jest": "^29.0.0", - "jest": "^29.0.0", - "typescript": ">=4.3 <6" - }, - "peerDependenciesMeta": { - "@babel/core": { - "optional": true - }, - "@jest/types": { - "optional": true - }, - "babel-jest": { - "optional": true - }, - "esbuild": { - "optional": true - } - } - }, - "node_modules/ts-jest/node_modules/lru-cache": { - "version": "6.0.0", - "dev": true, - "license": "ISC", - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/ts-jest/node_modules/semver": { - "version": "7.5.4", - "dev": true, - "license": "ISC", - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/ts-jest/node_modules/yallist": { - "version": "4.0.0", - "dev": true, - "license": "ISC" - }, - "node_modules/type-detect": { - "version": "4.0.8", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/type-fest": { - "version": "0.21.3", - "dev": true, - "license": "(MIT OR CC0-1.0)", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/typedoc": { - "version": "0.23.28", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "lunr": "^2.3.9", - "marked": "^4.2.12", - "minimatch": "^7.1.3", - "shiki": "^0.14.1" - }, - "bin": { - "typedoc": "bin/typedoc" - }, - "engines": { - "node": ">= 14.14" - }, - "peerDependencies": { - "typescript": "4.6.x || 4.7.x || 4.8.x || 4.9.x || 5.0.x" - } - }, - "node_modules/typedoc/node_modules/brace-expansion": { - "version": "2.0.1", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "node_modules/typedoc/node_modules/minimatch": { - "version": "7.4.6", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/typescript": { - "version": "4.9.5", - "dev": true, - "license": "Apache-2.0", - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=4.2.0" - } - }, - "node_modules/undici-types": { - "version": "5.26.5", - "license": "MIT" - }, - "node_modules/update-browserslist-db": { - "version": "1.0.13", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "escalade": "^3.1.1", - "picocolors": "^1.0.0" - }, - "bin": { - "update-browserslist-db": "cli.js" - }, - "peerDependencies": { - "browserslist": ">= 4.21.0" - } - }, - "node_modules/v8-to-istanbul": { - "version": "9.2.0", - "dev": true, - "license": "ISC", - "dependencies": { - "@jridgewell/trace-mapping": "^0.3.12", - "@types/istanbul-lib-coverage": "^2.0.1", - "convert-source-map": "^2.0.0" - }, - "engines": { - "node": ">=10.12.0" - } - }, - "node_modules/vscode-oniguruma": { - "version": "1.7.0", - "dev": true, - "license": "MIT" - }, - "node_modules/vscode-textmate": { - "version": "8.0.0", - "dev": true, - "license": "MIT" - }, - "node_modules/walker": { - "version": "1.0.8", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "makeerror": "1.0.12" - } - }, - "node_modules/web-streams-polyfill": { - "version": "3.3.2", - "license": "MIT", - "engines": { - "node": ">= 8" - } - }, - "node_modules/which": { - "version": "2.0.2", - "license": "ISC", - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/node-which" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/wrap-ansi": { - "version": "7.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/wrappy": { - "version": "1.0.2", - "dev": true, - "license": "ISC" - }, - "node_modules/write-file-atomic": { - "version": "4.0.2", - "dev": true, - "license": "ISC", - "dependencies": { - "imurmurhash": "^0.1.4", - "signal-exit": "^3.0.7" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" - } - }, - "node_modules/ws": { - "version": "8.16.0", - "license": "MIT", - "engines": { - "node": ">=10.0.0" - }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": ">=5.0.2" - }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true + "name": "libsql-client-ts", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "workspaces": [ + "packages/libsql-core", + "packages/libsql-client", + "packages/libsql-client-wasm" + ], + "dependencies": { + "husky": "^9.0.11", + "lint-staged": "^15.2.2" + } + }, + "node_modules/@ampproject/remapping": { + "version": "2.2.1", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.0", + "@jridgewell/trace-mapping": "^0.3.9" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.23.5", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/highlight": "^7.23.4", + "chalk": "^2.4.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/code-frame/node_modules/ansi-styles": { + "version": "3.2.1", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/code-frame/node_modules/chalk": { + "version": "2.4.2", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/code-frame/node_modules/color-convert": { + "version": "1.9.3", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/@babel/code-frame/node_modules/color-name": { + "version": "1.1.3", + "dev": true, + "license": "MIT" + }, + "node_modules/@babel/code-frame/node_modules/escape-string-regexp": { + "version": "1.0.5", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/@babel/code-frame/node_modules/has-flag": { + "version": "3.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/code-frame/node_modules/supports-color": { + "version": "5.5.0", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.23.5", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.23.7", + "dev": true, + "license": "MIT", + "dependencies": { + "@ampproject/remapping": "^2.2.0", + "@babel/code-frame": "^7.23.5", + "@babel/generator": "^7.23.6", + "@babel/helper-compilation-targets": "^7.23.6", + "@babel/helper-module-transforms": "^7.23.3", + "@babel/helpers": "^7.23.7", + "@babel/parser": "^7.23.6", + "@babel/template": "^7.22.15", + "@babel/traverse": "^7.23.7", + "@babel/types": "^7.23.6", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/generator": { + "version": "7.23.6", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.23.6", + "@jridgewell/gen-mapping": "^0.3.2", + "@jridgewell/trace-mapping": "^0.3.17", + "jsesc": "^2.5.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.23.6", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.23.5", + "@babel/helper-validator-option": "^7.23.5", + "browserslist": "^4.22.2", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-environment-visitor": { + "version": "7.22.20", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-function-name": { + "version": "7.23.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.22.15", + "@babel/types": "^7.23.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-hoist-variables": { + "version": "7.22.5", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.22.15", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.22.15" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.23.3", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-module-imports": "^7.22.15", + "@babel/helper-simple-access": "^7.22.5", + "@babel/helper-split-export-declaration": "^7.22.6", + "@babel/helper-validator-identifier": "^7.22.20" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.22.5", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-simple-access": { + "version": "7.22.5", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-split-export-declaration": { + "version": "7.22.6", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.23.4", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.22.20", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.23.5", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.23.8", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.22.15", + "@babel/traverse": "^7.23.7", + "@babel/types": "^7.23.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/highlight": { + "version": "7.23.4", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.22.20", + "chalk": "^2.4.2", + "js-tokens": "^4.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/highlight/node_modules/ansi-styles": { + "version": "3.2.1", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/highlight/node_modules/chalk": { + "version": "2.4.2", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/highlight/node_modules/color-convert": { + "version": "1.9.3", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/@babel/highlight/node_modules/color-name": { + "version": "1.1.3", + "dev": true, + "license": "MIT" + }, + "node_modules/@babel/highlight/node_modules/escape-string-regexp": { + "version": "1.0.5", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/@babel/highlight/node_modules/has-flag": { + "version": "3.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/highlight/node_modules/supports-color": { + "version": "5.5.0", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/parser": { + "version": "7.23.6", + "dev": true, + "license": "MIT", + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-syntax-async-generators": { + "version": "7.8.4", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-bigint": { + "version": "7.8.3", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-class-properties": { + "version": "7.12.13", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.12.13" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-meta": { + "version": "7.10.4", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-json-strings": { + "version": "7.8.3", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-jsx": { + "version": "7.23.3", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-logical-assignment-operators": { + "version": "7.10.4", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-nullish-coalescing-operator": { + "version": "7.8.3", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-numeric-separator": { + "version": "7.10.4", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-object-rest-spread": { + "version": "7.8.3", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-optional-catch-binding": { + "version": "7.8.3", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-optional-chaining": { + "version": "7.8.3", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-top-level-await": { + "version": "7.14.5", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-typescript": { + "version": "7.23.3", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/template": { + "version": "7.22.15", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.22.13", + "@babel/parser": "^7.22.15", + "@babel/types": "^7.22.15" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.23.7", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.23.5", + "@babel/generator": "^7.23.6", + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-function-name": "^7.23.0", + "@babel/helper-hoist-variables": "^7.22.5", + "@babel/helper-split-export-declaration": "^7.22.6", + "@babel/parser": "^7.23.6", + "@babel/types": "^7.23.6", + "debug": "^4.3.1", + "globals": "^11.1.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.23.6", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.23.4", + "@babel/helper-validator-identifier": "^7.22.20", + "to-fast-properties": "^2.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@bcoe/v8-coverage": { + "version": "0.2.3", + "dev": true, + "license": "MIT" + }, + "node_modules/@istanbuljs/load-nyc-config": { + "version": "1.1.0", + "dev": true, + "license": "ISC", + "dependencies": { + "camelcase": "^5.3.1", + "find-up": "^4.1.0", + "get-package-type": "^0.1.0", + "js-yaml": "^3.13.1", + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/schema": { + "version": "0.1.3", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/@jest/console": { + "version": "29.7.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/core": { + "version": "29.7.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/console": "^29.7.0", + "@jest/reporters": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "ansi-escapes": "^4.2.1", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "exit": "^0.1.2", + "graceful-fs": "^4.2.9", + "jest-changed-files": "^29.7.0", + "jest-config": "^29.7.0", + "jest-haste-map": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-regex-util": "^29.6.3", + "jest-resolve": "^29.7.0", + "jest-resolve-dependencies": "^29.7.0", + "jest-runner": "^29.7.0", + "jest-runtime": "^29.7.0", + "jest-snapshot": "^29.7.0", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "jest-watcher": "^29.7.0", + "micromatch": "^4.0.4", + "pretty-format": "^29.7.0", + "slash": "^3.0.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/@jest/environment": { + "version": "29.7.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/fake-timers": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "jest-mock": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/expect": { + "version": "29.7.0", + "dev": true, + "license": "MIT", + "dependencies": { + "expect": "^29.7.0", + "jest-snapshot": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/expect-utils": { + "version": "29.7.0", + "dev": true, + "license": "MIT", + "dependencies": { + "jest-get-type": "^29.6.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/fake-timers": { + "version": "29.7.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "@sinonjs/fake-timers": "^10.0.2", + "@types/node": "*", + "jest-message-util": "^29.7.0", + "jest-mock": "^29.7.0", + "jest-util": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/globals": { + "version": "29.7.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/environment": "^29.7.0", + "@jest/expect": "^29.7.0", + "@jest/types": "^29.6.3", + "jest-mock": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/reporters": { + "version": "29.7.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@bcoe/v8-coverage": "^0.2.3", + "@jest/console": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@jridgewell/trace-mapping": "^0.3.18", + "@types/node": "*", + "chalk": "^4.0.0", + "collect-v8-coverage": "^1.0.0", + "exit": "^0.1.2", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "istanbul-lib-coverage": "^3.0.0", + "istanbul-lib-instrument": "^6.0.0", + "istanbul-lib-report": "^3.0.0", + "istanbul-lib-source-maps": "^4.0.0", + "istanbul-reports": "^3.1.3", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0", + "jest-worker": "^29.7.0", + "slash": "^3.0.0", + "string-length": "^4.0.1", + "strip-ansi": "^6.0.0", + "v8-to-istanbul": "^9.0.1" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/@jest/schemas": { + "version": "29.6.3", + "dev": true, + "license": "MIT", + "dependencies": { + "@sinclair/typebox": "^0.27.8" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/source-map": { + "version": "29.6.3", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.18", + "callsites": "^3.0.0", + "graceful-fs": "^4.2.9" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/test-result": { + "version": "29.7.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/console": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/istanbul-lib-coverage": "^2.0.0", + "collect-v8-coverage": "^1.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/test-sequencer": { + "version": "29.7.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/test-result": "^29.7.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/transform": { + "version": "29.7.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.11.6", + "@jest/types": "^29.6.3", + "@jridgewell/trace-mapping": "^0.3.18", + "babel-plugin-istanbul": "^6.1.1", + "chalk": "^4.0.0", + "convert-source-map": "^2.0.0", + "fast-json-stable-stringify": "^2.1.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "jest-regex-util": "^29.6.3", + "jest-util": "^29.7.0", + "micromatch": "^4.0.4", + "pirates": "^4.0.4", + "slash": "^3.0.0", + "write-file-atomic": "^4.0.2" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/types": { + "version": "29.6.3", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/schemas": "^29.6.3", + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^17.0.8", + "chalk": "^4.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.3", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/set-array": "^1.0.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.9" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/set-array": { + "version": "1.1.2", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.4.15", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.21", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@libsql/client": { + "resolved": "packages/libsql-client", + "link": true + }, + "node_modules/@libsql/client-wasm": { + "resolved": "packages/libsql-client-wasm", + "link": true + }, + "node_modules/@libsql/core": { + "resolved": "packages/libsql-core", + "link": true + }, + "node_modules/@libsql/darwin-arm64": { + "version": "0.3.10", + "resolved": "https://registry.npmjs.org/@libsql/darwin-arm64/-/darwin-arm64-0.3.10.tgz", + "integrity": "sha512-RaexEFfPAFogd6dJlqkpCkTxdr6K14Z0286lodIJ8Ny77mWuWyBkWKxf70OYWXXAMxMJFUW+6al1F3/Osf/pTg==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@libsql/darwin-x64": { + "version": "0.3.10", + "resolved": "https://registry.npmjs.org/@libsql/darwin-x64/-/darwin-x64-0.3.10.tgz", + "integrity": "sha512-SNVN6n4qNUdMW1fJMFmx4qn4n5RnXsxjFbczpkzG/V7m/5VeTFt1chhGcrahTHCr3+K6eRJWJUEQHRGqjBwPkw==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@libsql/hrana-client": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/@libsql/hrana-client/-/hrana-client-0.6.0.tgz", + "integrity": "sha512-k+fqzdjqg3IvWfKmVJK5StsbjeTcyNAXFelUbXbGNz3yH1gEVT9mZ6kmhsIXP30ZSyVV0AE1Gi25p82mxC9hwg==", + "dependencies": { + "@libsql/isomorphic-fetch": "^0.2.1", + "@libsql/isomorphic-ws": "^0.1.5", + "js-base64": "^3.7.5", + "node-fetch": "^3.3.2" + } + }, + "node_modules/@libsql/isomorphic-fetch": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/@libsql/isomorphic-fetch/-/isomorphic-fetch-0.2.1.tgz", + "integrity": "sha512-Sv07QP1Aw8A5OOrmKgRUBKe2fFhF2hpGJhtHe3d1aRnTESZCGkn//0zDycMKTGamVWb3oLYRroOsCV8Ukes9GA==" + }, + "node_modules/@libsql/isomorphic-ws": { + "version": "0.1.5", + "license": "MIT", + "dependencies": { + "@types/ws": "^8.5.4", + "ws": "^8.13.0" + } + }, + "node_modules/@libsql/libsql-wasm-experimental": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/@libsql/libsql-wasm-experimental/-/libsql-wasm-experimental-0.0.2.tgz", + "integrity": "sha512-xkiu88QwozGr3KEt9h0zeHLYUIWkeDchXmuOUW4/Wh/mRZkDlNtIIePAR0FiLl1j0o4OyTEOtPnvmaXQ5MNTKQ==", + "bin": { + "sqlite-wasm": "bin/index.js" + } + }, + "node_modules/@libsql/linux-arm64-gnu": { + "version": "0.3.10", + "resolved": "https://registry.npmjs.org/@libsql/linux-arm64-gnu/-/linux-arm64-gnu-0.3.10.tgz", + "integrity": "sha512-2uXpi9d8qtyIOr7pyG4a88j6YXgemyIHEs2Wbp+PPletlCIPsFS+E7IQHbz8VwTohchOzcokGUm1Bc5QC+A7wg==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@libsql/linux-arm64-musl": { + "version": "0.3.10", + "resolved": "https://registry.npmjs.org/@libsql/linux-arm64-musl/-/linux-arm64-musl-0.3.10.tgz", + "integrity": "sha512-72SN1FUavLvzHddCS861ynSpQndcW5oLGKA3U8CyMfgIZIwJAPc7+48Uj1plW00htXBx4GBpcntFp68KKIx3YQ==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@libsql/linux-x64-gnu": { + "version": "0.3.10", + "resolved": "https://registry.npmjs.org/@libsql/linux-x64-gnu/-/linux-x64-gnu-0.3.10.tgz", + "integrity": "sha512-hXyNqVRi7ONuyWZ1SX6setxL0QaQ7InyS3bHLupsi9s7NpOGD5vcpTaYicJOqmIIm+6kt8vJfmo7ZxlarIHy7Q==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@libsql/linux-x64-musl": { + "version": "0.3.10", + "resolved": "https://registry.npmjs.org/@libsql/linux-x64-musl/-/linux-x64-musl-0.3.10.tgz", + "integrity": "sha512-kNmIRxomVwt9S+cLyYS497F/3gXFF4r8wW12YSBQgxG75JYft07AHVd8J7HINg+oqRkLzT0s+mVX5dM6nk68EQ==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@libsql/win32-x64-msvc": { + "version": "0.3.10", + "resolved": "https://registry.npmjs.org/@libsql/win32-x64-msvc/-/win32-x64-msvc-0.3.10.tgz", + "integrity": "sha512-c/6rjdtGULKrJkLgfLobFefObfOtxjXGmCfPxv6pr0epPCeUEssfDbDIeEH9fQUgzogIMWEHwT8so52UJ/iT1Q==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@neon-rs/load": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/@neon-rs/load/-/load-0.0.4.tgz", + "integrity": "sha512-kTPhdZyTQxB+2wpiRcFWrDcejc4JI6tkPuS7UZCG4l6Zvc5kU/gGQ/ozvHTh1XR5tS+UlfAfGuPajjzQjCiHCw==" + }, + "node_modules/@sinclair/typebox": { + "version": "0.27.8", + "dev": true, + "license": "MIT" + }, + "node_modules/@sinonjs/commons": { + "version": "3.0.0", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "type-detect": "4.0.8" + } + }, + "node_modules/@sinonjs/fake-timers": { + "version": "10.3.0", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@sinonjs/commons": "^3.0.0" + } + }, + "node_modules/@types/babel__core": { + "version": "7.20.5", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@types/babel__generator": { + "version": "7.6.8", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.4", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.20.5", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.20.7" + } + }, + "node_modules/@types/graceful-fs": { + "version": "4.1.9", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/istanbul-lib-coverage": { + "version": "2.0.6", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/istanbul-lib-report": { + "version": "3.0.3", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/istanbul-lib-coverage": "*" + } + }, + "node_modules/@types/istanbul-reports": { + "version": "3.0.4", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/istanbul-lib-report": "*" + } + }, + "node_modules/@types/jest": { + "version": "29.5.11", + "dev": true, + "license": "MIT", + "dependencies": { + "expect": "^29.0.0", + "pretty-format": "^29.0.0" + } + }, + "node_modules/@types/node": { + "version": "18.19.8", + "license": "MIT", + "dependencies": { + "undici-types": "~5.26.4" + } + }, + "node_modules/@types/stack-utils": { + "version": "2.0.3", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/ws": { + "version": "8.5.10", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/yargs": { + "version": "17.0.32", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/yargs-parser": "*" + } + }, + "node_modules/@types/yargs-parser": { + "version": "21.0.3", + "dev": true, + "license": "MIT" + }, + "node_modules/ansi-escapes": { + "version": "4.3.2", + "dev": true, + "license": "MIT", + "dependencies": { + "type-fest": "^0.21.3" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-sequence-parser": { + "version": "1.1.1", + "dev": true, + "license": "MIT" + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/anymatch": { + "version": "3.1.3", + "dev": true, + "license": "ISC", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/argparse": { + "version": "1.0.10", + "dev": true, + "license": "MIT", + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "node_modules/babel-jest": { + "version": "29.7.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/transform": "^29.7.0", + "@types/babel__core": "^7.1.14", + "babel-plugin-istanbul": "^6.1.1", + "babel-preset-jest": "^29.6.3", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.8.0" + } + }, + "node_modules/babel-plugin-istanbul": { + "version": "6.1.1", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@babel/helper-plugin-utils": "^7.0.0", + "@istanbuljs/load-nyc-config": "^1.0.0", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-instrument": "^5.0.4", + "test-exclude": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/babel-plugin-istanbul/node_modules/istanbul-lib-instrument": { + "version": "5.2.1", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@babel/core": "^7.12.3", + "@babel/parser": "^7.14.7", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-coverage": "^3.2.0", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/babel-plugin-jest-hoist": { + "version": "29.6.3", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.3.3", + "@babel/types": "^7.3.3", + "@types/babel__core": "^7.1.14", + "@types/babel__traverse": "^7.0.6" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/babel-preset-current-node-syntax": { + "version": "1.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/plugin-syntax-async-generators": "^7.8.4", + "@babel/plugin-syntax-bigint": "^7.8.3", + "@babel/plugin-syntax-class-properties": "^7.8.3", + "@babel/plugin-syntax-import-meta": "^7.8.3", + "@babel/plugin-syntax-json-strings": "^7.8.3", + "@babel/plugin-syntax-logical-assignment-operators": "^7.8.3", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", + "@babel/plugin-syntax-numeric-separator": "^7.8.3", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", + "@babel/plugin-syntax-optional-chaining": "^7.8.3", + "@babel/plugin-syntax-top-level-await": "^7.8.3" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/babel-preset-jest": { + "version": "29.6.3", + "dev": true, + "license": "MIT", + "dependencies": { + "babel-plugin-jest-hoist": "^29.6.3", + "babel-preset-current-node-syntax": "^1.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "dev": true, + "license": "MIT" + }, + "node_modules/brace-expansion": { + "version": "1.1.11", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.2", + "license": "MIT", + "dependencies": { + "fill-range": "^7.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browserslist": { + "version": "4.22.2", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "caniuse-lite": "^1.0.30001565", + "electron-to-chromium": "^1.4.601", + "node-releases": "^2.0.14", + "update-browserslist-db": "^1.0.13" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/bs-logger": { + "version": "0.2.6", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-json-stable-stringify": "2.x" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/bser": { + "version": "2.1.1", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "node-int64": "^0.4.0" + } + }, + "node_modules/buffer-from": { + "version": "1.1.2", + "dev": true, + "license": "MIT" + }, + "node_modules/callsites": { + "version": "3.1.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/camelcase": { + "version": "5.3.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001579", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/chalk": { + "version": "4.1.2", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/char-regex": { + "version": "1.0.2", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/ci-info": { + "version": "3.9.0", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/cjs-module-lexer": { + "version": "1.2.3", + "dev": true, + "license": "MIT" + }, + "node_modules/cli-cursor": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-4.0.0.tgz", + "integrity": "sha512-VGtlMu3x/4DOtIUwEkRezxUZ2lBacNJCHash0N0WeZDBS+7Ux1dm3XWAgWYxLJFMMdOeXMHXorshEFhbMSGelg==", + "dependencies": { + "restore-cursor": "^4.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-truncate": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-4.0.0.tgz", + "integrity": "sha512-nPdaFdQ0h/GEigbPClz11D0v/ZJEwxmeVZGeMo3Z5StPtUTkA9o1lD6QwoirYiSDzbcwn2XcjwmCp68W1IS4TA==", + "dependencies": { + "slice-ansi": "^5.0.0", + "string-width": "^7.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-truncate/node_modules/ansi-regex": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", + "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/cli-truncate/node_modules/emoji-regex": { + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.3.0.tgz", + "integrity": "sha512-QpLs9D9v9kArv4lfDEgg1X/gN5XLnf/A6l9cs8SPZLRZR3ZkY9+kwIQTxm+fsSej5UMYGE8fdoaZVIBlqG0XTw==" + }, + "node_modules/cli-truncate/node_modules/string-width": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.1.0.tgz", + "integrity": "sha512-SEIJCWiX7Kg4c129n48aDRwLbFb2LJmXXFrWBG4NGaRtMQ3myKPKbwrD1BKqQn74oCoNMBVrfDEr5M9YxCsrkw==", + "dependencies": { + "emoji-regex": "^10.3.0", + "get-east-asian-width": "^1.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-truncate/node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/cliui": { + "version": "8.0.1", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/co": { + "version": "4.6.0", + "dev": true, + "license": "MIT", + "engines": { + "iojs": ">= 1.0.0", + "node": ">= 0.12.0" + } + }, + "node_modules/collect-v8-coverage": { + "version": "1.0.2", + "dev": true, + "license": "MIT" + }, + "node_modules/color-convert": { + "version": "2.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "dev": true, + "license": "MIT" + }, + "node_modules/colorette": { + "version": "2.0.20", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", + "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==" + }, + "node_modules/commander": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-11.1.0.tgz", + "integrity": "sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==", + "engines": { + "node": ">=16" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "dev": true, + "license": "MIT" + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "dev": true, + "license": "MIT" + }, + "node_modules/create-jest": { + "version": "29.7.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "chalk": "^4.0.0", + "exit": "^0.1.2", + "graceful-fs": "^4.2.9", + "jest-config": "^29.7.0", + "jest-util": "^29.7.0", + "prompts": "^2.0.1" + }, + "bin": { + "create-jest": "bin/create-jest.js" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.3", + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/data-uri-to-buffer": { + "version": "4.0.1", + "license": "MIT", + "engines": { + "node": ">= 12" + } + }, + "node_modules/debug": { + "version": "4.3.4", + "license": "MIT", + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/dedent": { + "version": "1.5.1", + "dev": true, + "license": "MIT", + "peerDependencies": { + "babel-plugin-macros": "^3.1.0" + }, + "peerDependenciesMeta": { + "babel-plugin-macros": { + "optional": true + } + } + }, + "node_modules/deepmerge": { + "version": "4.3.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/detect-libc": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.2.tgz", + "integrity": "sha512-UX6sGumvvqSaXgdKGUsgZWqcUyIXZ/vZTrlRT/iobiKhGL0zL4d3osHj3uqllWJK+i+sixDS/3COVEOFbupFyw==", + "engines": { + "node": ">=8" + } + }, + "node_modules/detect-newline": { + "version": "3.1.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/diff-sequences": { + "version": "29.6.3", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/electron-to-chromium": { + "version": "1.4.637", + "dev": true, + "license": "ISC" + }, + "node_modules/emittery": { + "version": "0.13.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sindresorhus/emittery?sponsor=1" + } + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "dev": true, + "license": "MIT" + }, + "node_modules/error-ex": { + "version": "1.3.2", + "dev": true, + "license": "MIT", + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, + "node_modules/escalade": { + "version": "3.1.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-string-regexp": { + "version": "2.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/esprima": { + "version": "4.0.1", + "dev": true, + "license": "BSD-2-Clause", + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/eventemitter3": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz", + "integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==" + }, + "node_modules/execa": { + "version": "5.1.1", + "dev": true, + "license": "MIT", + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.0", + "human-signals": "^2.1.0", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.1", + "onetime": "^5.1.2", + "signal-exit": "^3.0.3", + "strip-final-newline": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/exit": { + "version": "0.1.2", + "dev": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/expect": { + "version": "29.7.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/expect-utils": "^29.7.0", + "jest-get-type": "^29.6.3", + "jest-matcher-utils": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "dev": true, + "license": "MIT" + }, + "node_modules/fb-watchman": { + "version": "2.0.2", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "bser": "2.1.1" + } + }, + "node_modules/fetch-blob": { + "version": "3.2.0", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "paypal", + "url": "https://paypal.me/jimmywarting" + } + ], + "license": "MIT", + "dependencies": { + "node-domexception": "^1.0.0", + "web-streams-polyfill": "^3.0.3" + }, + "engines": { + "node": "^12.20 || >= 14.13" + } + }, + "node_modules/fill-range": { + "version": "7.0.1", + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-up": { + "version": "4.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/formdata-polyfill": { + "version": "4.0.10", + "license": "MIT", + "dependencies": { + "fetch-blob": "^3.1.2" + }, + "engines": { + "node": ">=12.20.0" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "dev": true, + "license": "ISC" + }, + "node_modules/function-bind": { + "version": "1.1.2", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "dev": true, + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-east-asian-width": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.2.0.tgz", + "integrity": "sha512-2nk+7SIVb14QrgXFHcm84tD4bKQz0RxPuMT8Ag5KPOq7J5fEmAg0UbXdTOSHqNuHSU28k55qnceesxXRZGzKWA==", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/get-package-type": { + "version": "0.1.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/get-stream": { + "version": "6.0.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/glob": { + "version": "7.2.3", + "dev": true, + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/globals": { + "version": "11.12.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "dev": true, + "license": "ISC" + }, + "node_modules/has-flag": { + "version": "4.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/hasown": { + "version": "2.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/html-escaper": { + "version": "2.0.2", + "dev": true, + "license": "MIT" + }, + "node_modules/human-signals": { + "version": "2.1.0", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=10.17.0" + } + }, + "node_modules/husky": { + "version": "9.0.11", + "resolved": "https://registry.npmjs.org/husky/-/husky-9.0.11.tgz", + "integrity": "sha512-AB6lFlbwwyIqMdHYhwPe+kjOC3Oc5P3nThEoW/AaO2BX3vJDjWPFxYLxokUZOo6RNX20He3AaT8sESs9NJcmEw==", + "bin": { + "husky": "bin.mjs" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/typicode" + } + }, + "node_modules/import-local": { + "version": "3.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "pkg-dir": "^4.2.0", + "resolve-cwd": "^3.0.0" + }, + "bin": { + "import-local-fixture": "fixtures/cli.js" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "dev": true, + "license": "ISC", + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "dev": true, + "license": "ISC" + }, + "node_modules/is-arrayish": { + "version": "0.2.1", + "dev": true, + "license": "MIT" + }, + "node_modules/is-core-module": { + "version": "2.13.1", + "dev": true, + "license": "MIT", + "dependencies": { + "hasown": "^2.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-generator-fn": { + "version": "2.1.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-stream": { + "version": "2.0.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "license": "ISC" + }, + "node_modules/istanbul-lib-coverage": { + "version": "3.2.2", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-instrument": { + "version": "6.0.1", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@babel/core": "^7.12.3", + "@babel/parser": "^7.14.7", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-coverage": "^3.2.0", + "semver": "^7.5.4" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-instrument/node_modules/lru-cache": { + "version": "6.0.0", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-instrument/node_modules/semver": { + "version": "7.5.4", + "dev": true, + "license": "ISC", + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-instrument/node_modules/yallist": { + "version": "4.0.0", + "dev": true, + "license": "ISC" + }, + "node_modules/istanbul-lib-report": { + "version": "3.0.1", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^4.0.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-source-maps": { + "version": "4.0.1", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "debug": "^4.1.1", + "istanbul-lib-coverage": "^3.0.0", + "source-map": "^0.6.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-reports": { + "version": "3.1.6", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest": { + "version": "29.7.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/core": "^29.7.0", + "@jest/types": "^29.6.3", + "import-local": "^3.0.2", + "jest-cli": "^29.7.0" + }, + "bin": { + "jest": "bin/jest.js" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/jest-changed-files": { + "version": "29.7.0", + "dev": true, + "license": "MIT", + "dependencies": { + "execa": "^5.0.0", + "jest-util": "^29.7.0", + "p-limit": "^3.1.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-circus": { + "version": "29.7.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/environment": "^29.7.0", + "@jest/expect": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "co": "^4.6.0", + "dedent": "^1.0.0", + "is-generator-fn": "^2.0.0", + "jest-each": "^29.7.0", + "jest-matcher-utils": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-runtime": "^29.7.0", + "jest-snapshot": "^29.7.0", + "jest-util": "^29.7.0", + "p-limit": "^3.1.0", + "pretty-format": "^29.7.0", + "pure-rand": "^6.0.0", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-cli": { + "version": "29.7.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/core": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/types": "^29.6.3", + "chalk": "^4.0.0", + "create-jest": "^29.7.0", + "exit": "^0.1.2", + "import-local": "^3.0.2", + "jest-config": "^29.7.0", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "yargs": "^17.3.1" + }, + "bin": { + "jest": "bin/jest.js" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/jest-config": { + "version": "29.7.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.11.6", + "@jest/test-sequencer": "^29.7.0", + "@jest/types": "^29.6.3", + "babel-jest": "^29.7.0", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "deepmerge": "^4.2.2", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "jest-circus": "^29.7.0", + "jest-environment-node": "^29.7.0", + "jest-get-type": "^29.6.3", + "jest-regex-util": "^29.6.3", + "jest-resolve": "^29.7.0", + "jest-runner": "^29.7.0", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "micromatch": "^4.0.4", + "parse-json": "^5.2.0", + "pretty-format": "^29.7.0", + "slash": "^3.0.0", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "@types/node": "*", + "ts-node": ">=9.0.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "ts-node": { + "optional": true + } + } + }, + "node_modules/jest-diff": { + "version": "29.7.0", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.0.0", + "diff-sequences": "^29.6.3", + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-docblock": { + "version": "29.7.0", + "dev": true, + "license": "MIT", + "dependencies": { + "detect-newline": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-each": { + "version": "29.7.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "chalk": "^4.0.0", + "jest-get-type": "^29.6.3", + "jest-util": "^29.7.0", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-environment-node": { + "version": "29.7.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/environment": "^29.7.0", + "@jest/fake-timers": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "jest-mock": "^29.7.0", + "jest-util": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-get-type": { + "version": "29.6.3", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-haste-map": { + "version": "29.7.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "@types/graceful-fs": "^4.1.3", + "@types/node": "*", + "anymatch": "^3.0.3", + "fb-watchman": "^2.0.0", + "graceful-fs": "^4.2.9", + "jest-regex-util": "^29.6.3", + "jest-util": "^29.7.0", + "jest-worker": "^29.7.0", + "micromatch": "^4.0.4", + "walker": "^1.0.8" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "optionalDependencies": { + "fsevents": "^2.3.2" + } + }, + "node_modules/jest-leak-detector": { + "version": "29.7.0", + "dev": true, + "license": "MIT", + "dependencies": { + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-matcher-utils": { + "version": "29.7.0", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.0.0", + "jest-diff": "^29.7.0", + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-message-util": { + "version": "29.7.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.12.13", + "@jest/types": "^29.6.3", + "@types/stack-utils": "^2.0.0", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "micromatch": "^4.0.4", + "pretty-format": "^29.7.0", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-mock": { + "version": "29.7.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "@types/node": "*", + "jest-util": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-pnp-resolver": { + "version": "1.2.3", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + }, + "peerDependencies": { + "jest-resolve": "*" + }, + "peerDependenciesMeta": { + "jest-resolve": { + "optional": true + } + } + }, + "node_modules/jest-regex-util": { + "version": "29.6.3", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-resolve": { + "version": "29.7.0", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "jest-pnp-resolver": "^1.2.2", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "resolve": "^1.20.0", + "resolve.exports": "^2.0.0", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-resolve-dependencies": { + "version": "29.7.0", + "dev": true, + "license": "MIT", + "dependencies": { + "jest-regex-util": "^29.6.3", + "jest-snapshot": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-runner": { + "version": "29.7.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/console": "^29.7.0", + "@jest/environment": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "emittery": "^0.13.1", + "graceful-fs": "^4.2.9", + "jest-docblock": "^29.7.0", + "jest-environment-node": "^29.7.0", + "jest-haste-map": "^29.7.0", + "jest-leak-detector": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-resolve": "^29.7.0", + "jest-runtime": "^29.7.0", + "jest-util": "^29.7.0", + "jest-watcher": "^29.7.0", + "jest-worker": "^29.7.0", + "p-limit": "^3.1.0", + "source-map-support": "0.5.13" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-runtime": { + "version": "29.7.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/environment": "^29.7.0", + "@jest/fake-timers": "^29.7.0", + "@jest/globals": "^29.7.0", + "@jest/source-map": "^29.6.3", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "cjs-module-lexer": "^1.0.0", + "collect-v8-coverage": "^1.0.0", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-mock": "^29.7.0", + "jest-regex-util": "^29.6.3", + "jest-resolve": "^29.7.0", + "jest-snapshot": "^29.7.0", + "jest-util": "^29.7.0", + "slash": "^3.0.0", + "strip-bom": "^4.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-snapshot": { + "version": "29.7.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.11.6", + "@babel/generator": "^7.7.2", + "@babel/plugin-syntax-jsx": "^7.7.2", + "@babel/plugin-syntax-typescript": "^7.7.2", + "@babel/types": "^7.3.3", + "@jest/expect-utils": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "babel-preset-current-node-syntax": "^1.0.0", + "chalk": "^4.0.0", + "expect": "^29.7.0", + "graceful-fs": "^4.2.9", + "jest-diff": "^29.7.0", + "jest-get-type": "^29.6.3", + "jest-matcher-utils": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0", + "natural-compare": "^1.4.0", + "pretty-format": "^29.7.0", + "semver": "^7.5.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-snapshot/node_modules/lru-cache": { + "version": "6.0.0", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/jest-snapshot/node_modules/semver": { + "version": "7.5.4", + "dev": true, + "license": "ISC", + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/jest-snapshot/node_modules/yallist": { + "version": "4.0.0", + "dev": true, + "license": "ISC" + }, + "node_modules/jest-util": { + "version": "29.7.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-validate": { + "version": "29.7.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "camelcase": "^6.2.0", + "chalk": "^4.0.0", + "jest-get-type": "^29.6.3", + "leven": "^3.1.0", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-validate/node_modules/camelcase": { + "version": "6.3.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/jest-watcher": { + "version": "29.7.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/test-result": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "ansi-escapes": "^4.2.1", + "chalk": "^4.0.0", + "emittery": "^0.13.1", + "jest-util": "^29.7.0", + "string-length": "^4.0.1" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-worker": { + "version": "29.7.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "jest-util": "^29.7.0", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-worker/node_modules/supports-color": { + "version": "8.1.1", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/js-base64": { + "version": "3.7.5", + "license": "BSD-3-Clause" + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "dev": true, + "license": "MIT" + }, + "node_modules/js-yaml": { + "version": "3.14.1", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsesc": { + "version": "2.5.2", + "dev": true, + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "dev": true, + "license": "MIT" + }, + "node_modules/json5": { + "version": "2.2.3", + "dev": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/jsonc-parser": { + "version": "3.2.0", + "dev": true, + "license": "MIT" + }, + "node_modules/kleur": { + "version": "3.0.3", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/leven": { + "version": "3.1.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/libsql": { + "version": "0.3.10", + "resolved": "https://registry.npmjs.org/libsql/-/libsql-0.3.10.tgz", + "integrity": "sha512-/8YMTbwWFPmrDWY+YFK3kYqVPFkMgQre0DGmBaOmjogMdSe+7GHm1/q9AZ61AWkEub/vHmi+bA4tqIzVhKnqzg==", + "cpu": [ + "x64", + "arm64", + "wasm32" + ], + "os": [ + "darwin", + "linux", + "win32" + ], + "dependencies": { + "@neon-rs/load": "^0.0.4", + "detect-libc": "2.0.2" + }, + "optionalDependencies": { + "@libsql/darwin-arm64": "0.3.10", + "@libsql/darwin-x64": "0.3.10", + "@libsql/linux-arm64-gnu": "0.3.10", + "@libsql/linux-arm64-musl": "0.3.10", + "@libsql/linux-x64-gnu": "0.3.10", + "@libsql/linux-x64-musl": "0.3.10", + "@libsql/win32-x64-msvc": "0.3.10" + } + }, + "node_modules/lilconfig": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.0.0.tgz", + "integrity": "sha512-K2U4W2Ff5ibV7j7ydLr+zLAkIg5JJ4lPn1Ltsdt+Tz/IjQ8buJ55pZAxoP34lqIiwtF9iAvtLv3JGv7CAyAg+g==", + "engines": { + "node": ">=14" + } + }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "dev": true, + "license": "MIT" + }, + "node_modules/lint-staged": { + "version": "15.2.2", + "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-15.2.2.tgz", + "integrity": "sha512-TiTt93OPh1OZOsb5B7k96A/ATl2AjIZo+vnzFZ6oHK5FuTk63ByDtxGQpHm+kFETjEWqgkF95M8FRXKR/LEBcw==", + "dependencies": { + "chalk": "5.3.0", + "commander": "11.1.0", + "debug": "4.3.4", + "execa": "8.0.1", + "lilconfig": "3.0.0", + "listr2": "8.0.1", + "micromatch": "4.0.5", + "pidtree": "0.6.0", + "string-argv": "0.3.2", + "yaml": "2.3.4" + }, + "bin": { + "lint-staged": "bin/lint-staged.js" + }, + "engines": { + "node": ">=18.12.0" + }, + "funding": { + "url": "https://opencollective.com/lint-staged" + } + }, + "node_modules/lint-staged/node_modules/chalk": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz", + "integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/lint-staged/node_modules/execa": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-8.0.1.tgz", + "integrity": "sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==", + "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.1.0", + "onetime": "^6.0.0", + "signal-exit": "^4.1.0", + "strip-final-newline": "^3.0.0" + }, + "engines": { + "node": ">=16.17" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/lint-staged/node_modules/get-stream": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-8.0.1.tgz", + "integrity": "sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lint-staged/node_modules/human-signals": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-5.0.0.tgz", + "integrity": "sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==", + "engines": { + "node": ">=16.17.0" + } + }, + "node_modules/lint-staged/node_modules/is-stream": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-3.0.0.tgz", + "integrity": "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lint-staged/node_modules/mimic-fn": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz", + "integrity": "sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lint-staged/node_modules/npm-run-path": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.3.0.tgz", + "integrity": "sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==", + "dependencies": { + "path-key": "^4.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lint-staged/node_modules/onetime": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-6.0.0.tgz", + "integrity": "sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==", + "dependencies": { + "mimic-fn": "^4.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lint-staged/node_modules/path-key": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz", + "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lint-staged/node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/lint-staged/node_modules/strip-final-newline": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-3.0.0.tgz", + "integrity": "sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/listr2": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/listr2/-/listr2-8.0.1.tgz", + "integrity": "sha512-ovJXBXkKGfq+CwmKTjluEqFi3p4h8xvkxGQQAQan22YCgef4KZ1mKGjzfGh6PL6AW5Csw0QiQPNuQyH+6Xk3hA==", + "dependencies": { + "cli-truncate": "^4.0.0", + "colorette": "^2.0.20", + "eventemitter3": "^5.0.1", + "log-update": "^6.0.0", + "rfdc": "^1.3.0", + "wrap-ansi": "^9.0.0" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/listr2/node_modules/ansi-regex": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", + "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/listr2/node_modules/ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/listr2/node_modules/emoji-regex": { + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.3.0.tgz", + "integrity": "sha512-QpLs9D9v9kArv4lfDEgg1X/gN5XLnf/A6l9cs8SPZLRZR3ZkY9+kwIQTxm+fsSej5UMYGE8fdoaZVIBlqG0XTw==" + }, + "node_modules/listr2/node_modules/string-width": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.1.0.tgz", + "integrity": "sha512-SEIJCWiX7Kg4c129n48aDRwLbFb2LJmXXFrWBG4NGaRtMQ3myKPKbwrD1BKqQn74oCoNMBVrfDEr5M9YxCsrkw==", + "dependencies": { + "emoji-regex": "^10.3.0", + "get-east-asian-width": "^1.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/listr2/node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/listr2/node_modules/wrap-ansi": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.0.tgz", + "integrity": "sha512-G8ura3S+3Z2G+mkgNRq8dqaFZAuxfsxpBB8OCTGRTCtp+l/v9nbFNmCUP1BZMts3G1142MsZfn6eeUKrr4PD1Q==", + "dependencies": { + "ansi-styles": "^6.2.1", + "string-width": "^7.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/locate-path": { + "version": "5.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/lodash.memoize": { + "version": "4.1.2", + "dev": true, + "license": "MIT" + }, + "node_modules/log-update": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/log-update/-/log-update-6.0.0.tgz", + "integrity": "sha512-niTvB4gqvtof056rRIrTZvjNYE4rCUzO6X/X+kYjd7WFxXeJ0NwEFnRxX6ehkvv3jTwrXnNdtAak5XYZuIyPFw==", + "dependencies": { + "ansi-escapes": "^6.2.0", + "cli-cursor": "^4.0.0", + "slice-ansi": "^7.0.0", + "strip-ansi": "^7.1.0", + "wrap-ansi": "^9.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-update/node_modules/ansi-escapes": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-6.2.1.tgz", + "integrity": "sha512-4nJ3yixlEthEJ9Rk4vPcdBRkZvQZlYyu8j4/Mqz5sgIkddmEnH2Yj2ZrnP9S3tQOvSNRUIgVNF/1yPpRAGNRig==", + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-update/node_modules/ansi-regex": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", + "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/log-update/node_modules/ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/log-update/node_modules/emoji-regex": { + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.3.0.tgz", + "integrity": "sha512-QpLs9D9v9kArv4lfDEgg1X/gN5XLnf/A6l9cs8SPZLRZR3ZkY9+kwIQTxm+fsSej5UMYGE8fdoaZVIBlqG0XTw==" + }, + "node_modules/log-update/node_modules/is-fullwidth-code-point": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-5.0.0.tgz", + "integrity": "sha512-OVa3u9kkBbw7b8Xw5F9P+D/T9X+Z4+JruYVNapTjPYZYUznQ5YfWeFkOj606XYYW8yugTfC8Pj0hYqvi4ryAhA==", + "dependencies": { + "get-east-asian-width": "^1.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-update/node_modules/slice-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-7.1.0.tgz", + "integrity": "sha512-bSiSngZ/jWeX93BqeIAbImyTbEihizcwNjFoRUIY/T1wWQsfsm2Vw1agPKylXvQTU7iASGdHhyqRlqQzfz+Htg==", + "dependencies": { + "ansi-styles": "^6.2.1", + "is-fullwidth-code-point": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/chalk/slice-ansi?sponsor=1" + } + }, + "node_modules/log-update/node_modules/string-width": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.1.0.tgz", + "integrity": "sha512-SEIJCWiX7Kg4c129n48aDRwLbFb2LJmXXFrWBG4NGaRtMQ3myKPKbwrD1BKqQn74oCoNMBVrfDEr5M9YxCsrkw==", + "dependencies": { + "emoji-regex": "^10.3.0", + "get-east-asian-width": "^1.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-update/node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/log-update/node_modules/wrap-ansi": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.0.tgz", + "integrity": "sha512-G8ura3S+3Z2G+mkgNRq8dqaFZAuxfsxpBB8OCTGRTCtp+l/v9nbFNmCUP1BZMts3G1142MsZfn6eeUKrr4PD1Q==", + "dependencies": { + "ansi-styles": "^6.2.1", + "string-width": "^7.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/lunr": { + "version": "2.3.9", + "dev": true, + "license": "MIT" + }, + "node_modules/make-dir": { + "version": "4.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/make-dir/node_modules/lru-cache": { + "version": "6.0.0", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/make-dir/node_modules/semver": { + "version": "7.5.4", + "dev": true, + "license": "ISC", + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/make-dir/node_modules/yallist": { + "version": "4.0.0", + "dev": true, + "license": "ISC" + }, + "node_modules/make-error": { + "version": "1.3.6", + "dev": true, + "license": "ISC" + }, + "node_modules/makeerror": { + "version": "1.0.12", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "tmpl": "1.0.5" + } + }, + "node_modules/marked": { + "version": "4.3.0", + "dev": true, + "license": "MIT", + "bin": { + "marked": "bin/marked.js" + }, + "engines": { + "node": ">= 12" + } + }, + "node_modules/merge-stream": { + "version": "2.0.0", + "license": "MIT" + }, + "node_modules/micromatch": { + "version": "4.0.5", + "license": "MIT", + "dependencies": { + "braces": "^3.0.2", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/mimic-fn": { + "version": "2.1.0", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/minimatch": { + "version": "3.1.2", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/ms": { + "version": "2.1.2", + "license": "MIT" + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "dev": true, + "license": "MIT" + }, + "node_modules/node-domexception": { + "version": "1.0.0", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "github", + "url": "https://paypal.me/jimmywarting" + } + ], + "license": "MIT", + "engines": { + "node": ">=10.5.0" + } + }, + "node_modules/node-fetch": { + "version": "3.3.2", + "license": "MIT", + "dependencies": { + "data-uri-to-buffer": "^4.0.0", + "fetch-blob": "^3.1.4", + "formdata-polyfill": "^4.0.10" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/node-fetch" + } + }, + "node_modules/node-int64": { + "version": "0.4.0", + "dev": true, + "license": "MIT" + }, + "node_modules/node-releases": { + "version": "2.0.14", + "dev": true, + "license": "MIT" + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/npm-run-path": { + "version": "4.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/once": { + "version": "1.4.0", + "dev": true, + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/onetime": { + "version": "5.1.2", + "license": "MIT", + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "4.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/p-locate/node_modules/p-limit": { + "version": "2.3.0", + "dev": true, + "license": "MIT", + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-try": { + "version": "2.2.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/parse-json": { + "version": "5.2.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "dev": true, + "license": "MIT" + }, + "node_modules/picocolors": { + "version": "1.0.0", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "2.3.1", + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pidtree": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/pidtree/-/pidtree-0.6.0.tgz", + "integrity": "sha512-eG2dWTVw5bzqGRztnHExczNxt5VGsE6OwTeCG3fdUf9KBsZzO3R5OIIIzWR+iZA0NtZ+RDVdaoE2dK1cn6jH4g==", + "bin": { + "pidtree": "bin/pidtree.js" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/pirates": { + "version": "4.0.6", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/pkg-dir": { + "version": "4.2.0", + "dev": true, + "license": "MIT", + "dependencies": { + "find-up": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/prettier": { + "version": "3.2.5", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.2.5.tgz", + "integrity": "sha512-3/GWa9aOC0YeD7LUfvOG2NiDyhOWRvt1k+rcKhOuYnMY24iiCphgneUfJDyFXd6rZCAnuLBv6UeAULtrhT/F4A==", + "dev": true, + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/pretty-format": { + "version": "29.7.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/schemas": "^29.6.3", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/pretty-format/node_modules/ansi-styles": { + "version": "5.2.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/prompts": { + "version": "2.4.2", + "dev": true, + "license": "MIT", + "dependencies": { + "kleur": "^3.0.3", + "sisteransi": "^1.0.5" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/pure-rand": { + "version": "6.0.4", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/dubzzz" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fast-check" + } + ], + "license": "MIT" + }, + "node_modules/react-is": { + "version": "18.2.0", + "dev": true, + "license": "MIT" + }, + "node_modules/require-directory": { + "version": "2.1.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/resolve": { + "version": "1.22.8", + "dev": true, + "license": "MIT", + "dependencies": { + "is-core-module": "^2.13.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-cwd": { + "version": "3.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/resolve-from": { + "version": "5.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/resolve.exports": { + "version": "2.0.2", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/restore-cursor": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-4.0.0.tgz", + "integrity": "sha512-I9fPXU9geO9bHOt9pHHOhOkYerIMsmVaWB0rA2AI9ERh/+x/i7MV5HKBNrg+ljO5eoPVgCcnFuRjJ9uH6I/3eg==", + "dependencies": { + "onetime": "^5.1.0", + "signal-exit": "^3.0.2" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/rfdc": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.3.1.tgz", + "integrity": "sha512-r5a3l5HzYlIC68TpmYKlxWjmOP6wiPJ1vWv2HeLhNsRZMrCkxeqxiHlQ21oXmQ4F3SiryXBHhAD7JZqvOJjFmg==" + }, + "node_modules/semver": { + "version": "6.3.1", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/shiki": { + "version": "0.14.7", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-sequence-parser": "^1.1.0", + "jsonc-parser": "^3.2.0", + "vscode-oniguruma": "^1.7.0", + "vscode-textmate": "^8.0.0" + } + }, + "node_modules/signal-exit": { + "version": "3.0.7", + "license": "ISC" + }, + "node_modules/sisteransi": { + "version": "1.0.5", + "dev": true, + "license": "MIT" + }, + "node_modules/slash": { + "version": "3.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/slice-ansi": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-5.0.0.tgz", + "integrity": "sha512-FC+lgizVPfie0kkhqUScwRu1O/lF6NOgJmlCgK+/LYxDCTk8sGelYaHDhFcDN+Sn3Cv+3VSa4Byeo+IMCzpMgQ==", + "dependencies": { + "ansi-styles": "^6.0.0", + "is-fullwidth-code-point": "^4.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/slice-ansi?sponsor=1" + } + }, + "node_modules/slice-ansi/node_modules/ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/slice-ansi/node_modules/is-fullwidth-code-point": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-4.0.0.tgz", + "integrity": "sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/source-map": { + "version": "0.6.1", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-support": { + "version": "0.5.13", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/sprintf-js": { + "version": "1.0.3", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/stack-utils": { + "version": "2.0.6", + "dev": true, + "license": "MIT", + "dependencies": { + "escape-string-regexp": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/string-argv": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/string-argv/-/string-argv-0.3.2.tgz", + "integrity": "sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q==", + "engines": { + "node": ">=0.6.19" + } + }, + "node_modules/string-length": { + "version": "4.0.2", + "dev": true, + "license": "MIT", + "dependencies": { + "char-regex": "^1.0.2", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/string-width": { + "version": "4.2.3", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-bom": { + "version": "4.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-final-newline": { + "version": "2.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/test-exclude": { + "version": "6.0.0", + "dev": true, + "license": "ISC", + "dependencies": { + "@istanbuljs/schema": "^0.1.2", + "glob": "^7.1.4", + "minimatch": "^3.0.4" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/tmpl": { + "version": "1.0.5", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/to-fast-properties": { + "version": "2.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/ts-jest": { + "version": "29.1.1", + "dev": true, + "license": "MIT", + "dependencies": { + "bs-logger": "0.x", + "fast-json-stable-stringify": "2.x", + "jest-util": "^29.0.0", + "json5": "^2.2.3", + "lodash.memoize": "4.x", + "make-error": "1.x", + "semver": "^7.5.3", + "yargs-parser": "^21.0.1" + }, + "bin": { + "ts-jest": "cli.js" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "@babel/core": ">=7.0.0-beta.0 <8", + "@jest/types": "^29.0.0", + "babel-jest": "^29.0.0", + "jest": "^29.0.0", + "typescript": ">=4.3 <6" + }, + "peerDependenciesMeta": { + "@babel/core": { + "optional": true + }, + "@jest/types": { + "optional": true + }, + "babel-jest": { + "optional": true + }, + "esbuild": { + "optional": true + } + } + }, + "node_modules/ts-jest/node_modules/lru-cache": { + "version": "6.0.0", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/ts-jest/node_modules/semver": { + "version": "7.5.4", + "dev": true, + "license": "ISC", + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/ts-jest/node_modules/yallist": { + "version": "4.0.0", + "dev": true, + "license": "ISC" + }, + "node_modules/type-detect": { + "version": "4.0.8", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/type-fest": { + "version": "0.21.3", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/typedoc": { + "version": "0.23.28", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "lunr": "^2.3.9", + "marked": "^4.2.12", + "minimatch": "^7.1.3", + "shiki": "^0.14.1" + }, + "bin": { + "typedoc": "bin/typedoc" + }, + "engines": { + "node": ">= 14.14" + }, + "peerDependencies": { + "typescript": "4.6.x || 4.7.x || 4.8.x || 4.9.x || 5.0.x" + } + }, + "node_modules/typedoc/node_modules/brace-expansion": { + "version": "2.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/typedoc/node_modules/minimatch": { + "version": "7.4.6", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/typescript": { + "version": "4.9.5", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=4.2.0" + } + }, + "node_modules/undici-types": { + "version": "5.26.5", + "license": "MIT" + }, + "node_modules/update-browserslist-db": { + "version": "1.0.13", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.1.1", + "picocolors": "^1.0.0" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/v8-to-istanbul": { + "version": "9.2.0", + "dev": true, + "license": "ISC", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.12", + "@types/istanbul-lib-coverage": "^2.0.1", + "convert-source-map": "^2.0.0" + }, + "engines": { + "node": ">=10.12.0" + } + }, + "node_modules/vscode-oniguruma": { + "version": "1.7.0", + "dev": true, + "license": "MIT" + }, + "node_modules/vscode-textmate": { + "version": "8.0.0", + "dev": true, + "license": "MIT" + }, + "node_modules/walker": { + "version": "1.0.8", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "makeerror": "1.0.12" + } + }, + "node_modules/web-streams-polyfill": { + "version": "3.3.2", + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/which": { + "version": "2.0.2", + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "dev": true, + "license": "ISC" + }, + "node_modules/write-file-atomic": { + "version": "4.0.2", + "dev": true, + "license": "ISC", + "dependencies": { + "imurmurhash": "^0.1.4", + "signal-exit": "^3.0.7" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/ws": { + "version": "8.16.0", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/y18n": { + "version": "5.0.8", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "dev": true, + "license": "ISC" + }, + "node_modules/yaml": { + "version": "2.3.4", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.3.4.tgz", + "integrity": "sha512-8aAvwVUSHpfEqTQ4w/KMlf3HcRdt50E5ODIQJBw1fQ5RL34xabzxtUlzTXVqc4rkZsPbvrXKWnABCD7kWSmocA==", + "engines": { + "node": ">= 14" + } + }, + "node_modules/yargs": { + "version": "17.7.2", + "dev": true, + "license": "MIT", + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "packages/libsql-client": { + "name": "@libsql/client", + "version": "0.6.0", + "license": "MIT", + "dependencies": { + "@libsql/core": "^0.6.0", + "@libsql/hrana-client": "^0.6.0", + "js-base64": "^3.7.5", + "libsql": "^0.3.10" + }, + "devDependencies": { + "@types/jest": "^29.2.5", + "@types/node": "^18.15.5", + "husky": "^9.0.11", + "jest": "^29.3.1", + "lint-staged": "^15.2.2", + "prettier": "3.2.5", + "ts-jest": "^29.0.5", + "typedoc": "^0.23.28", + "typescript": "^4.9.4" + } + }, + "packages/libsql-client-wasm": { + "name": "@libsql/client-wasm", + "version": "0.6.0", + "bundleDependencies": [ + "@libsql/libsql-wasm-experimental" + ], + "license": "MIT", + "dependencies": { + "@libsql/core": "0.6.0", + "@libsql/libsql-wasm-experimental": "^0.0.2", + "js-base64": "^3.7.5" + }, + "devDependencies": { + "@types/jest": "^29.2.5", + "jest": "^29.3.1", + "ts-jest": "^29.0.5", + "typedoc": "^0.23.28", + "typescript": "^4.9.4" + } + }, + "packages/libsql-core": { + "name": "@libsql/core", + "version": "0.6.0", + "license": "MIT", + "dependencies": { + "js-base64": "^3.7.5" + }, + "devDependencies": { + "@types/jest": "^29.2.5", + "@types/node": "^18.15.5", + "jest": "^29.3.1", + "ts-jest": "^29.0.5", + "typedoc": "^0.23.28", + "typescript": "^4.9.4" + } } - } - }, - "node_modules/y18n": { - "version": "5.0.8", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=10" - } - }, - "node_modules/yallist": { - "version": "3.1.1", - "dev": true, - "license": "ISC" - }, - "node_modules/yaml": { - "version": "2.3.4", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.3.4.tgz", - "integrity": "sha512-8aAvwVUSHpfEqTQ4w/KMlf3HcRdt50E5ODIQJBw1fQ5RL34xabzxtUlzTXVqc4rkZsPbvrXKWnABCD7kWSmocA==", - "engines": { - "node": ">= 14" - } - }, - "node_modules/yargs": { - "version": "17.7.2", - "dev": true, - "license": "MIT", - "dependencies": { - "cliui": "^8.0.1", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.3", - "y18n": "^5.0.5", - "yargs-parser": "^21.1.1" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/yargs-parser": { - "version": "21.1.1", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=12" - } - }, - "node_modules/yocto-queue": { - "version": "0.1.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "packages/libsql-client": { - "name": "@libsql/client", - "version": "0.6.0", - "license": "MIT", - "dependencies": { - "@libsql/core": "^0.6.0", - "@libsql/hrana-client": "^0.6.0", - "js-base64": "^3.7.5", - "libsql": "^0.3.10" - }, - "devDependencies": { - "@types/jest": "^29.2.5", - "@types/node": "^18.15.5", - "husky": "^9.0.11", - "jest": "^29.3.1", - "lint-staged": "^15.2.2", - "prettier": "3.2.5", - "ts-jest": "^29.0.5", - "typedoc": "^0.23.28", - "typescript": "^4.9.4" - } - }, - "packages/libsql-client-wasm": { - "name": "@libsql/client-wasm", - "version": "0.6.0", - "bundleDependencies": [ - "@libsql/libsql-wasm-experimental" - ], - "license": "MIT", - "dependencies": { - "@libsql/core": "0.6.0", - "@libsql/libsql-wasm-experimental": "^0.0.2", - "js-base64": "^3.7.5" - }, - "devDependencies": { - "@types/jest": "^29.2.5", - "jest": "^29.3.1", - "ts-jest": "^29.0.5", - "typedoc": "^0.23.28", - "typescript": "^4.9.4" - } - }, - "packages/libsql-core": { - "name": "@libsql/core", - "version": "0.6.0", - "license": "MIT", - "dependencies": { - "js-base64": "^3.7.5" - }, - "devDependencies": { - "@types/jest": "^29.2.5", - "@types/node": "^18.15.5", - "jest": "^29.3.1", - "ts-jest": "^29.0.5", - "typedoc": "^0.23.28", - "typescript": "^4.9.4" - } } - } } diff --git a/package.json b/package.json index 5f688512..32decbbe 100644 --- a/package.json +++ b/package.json @@ -1,15 +1,15 @@ { - "workspaces": [ - "packages/libsql-core", - "packages/libsql-client", - "packages/libsql-client-wasm" - ], - "dependencies": { - "husky": "^9.0.11", - "lint-staged": "^15.2.2" - }, - "scripts": { - "prepare": "node .husky/install.mjs", - "lint-staged": "lint-staged" - } + "workspaces": [ + "packages/libsql-core", + "packages/libsql-client", + "packages/libsql-client-wasm" + ], + "dependencies": { + "husky": "^9.0.11", + "lint-staged": "^15.2.2" + }, + "scripts": { + "prepare": "node .husky/install.mjs", + "lint-staged": "lint-staged" + } } diff --git a/packages/libsql-client-wasm/examples/browser/index.html b/packages/libsql-client-wasm/examples/browser/index.html index aa1cafc1..0494cf2f 100644 --- a/packages/libsql-client-wasm/examples/browser/index.html +++ b/packages/libsql-client-wasm/examples/browser/index.html @@ -1,10 +1,10 @@ - - - libSQL SDK Wasm Demo - - - -

Hello libSQL and Wasm!

- + + + libSQL SDK Wasm Demo + + + +

Hello libSQL and Wasm!

+ diff --git a/packages/libsql-client-wasm/examples/browser/index.js b/packages/libsql-client-wasm/examples/browser/index.js index b20d414e..58798094 100644 --- a/packages/libsql-client-wasm/examples/browser/index.js +++ b/packages/libsql-client-wasm/examples/browser/index.js @@ -1,14 +1,14 @@ import { createClient } from "@libsql/client-wasm"; async function main() { - const config = { - url: "file:local.db", - }; - const db = await createClient(config); - const rs = await db.execute("SELECT * FROM users"); - console.log(rs); + const config = { + url: "file:local.db", + }; + const db = await createClient(config); + const rs = await db.execute("SELECT * FROM users"); + console.log(rs); } main().catch((error) => { - console.log(error); + console.log(error); }); diff --git a/packages/libsql-client-wasm/examples/browser/package-lock.json b/packages/libsql-client-wasm/examples/browser/package-lock.json index 285e2e12..7e65c807 100644 --- a/packages/libsql-client-wasm/examples/browser/package-lock.json +++ b/packages/libsql-client-wasm/examples/browser/package-lock.json @@ -1,653 +1,653 @@ { - "name": "browser", - "version": "1.0.0", - "lockfileVersion": 2, - "requires": true, - "packages": { - "": { - "name": "browser", - "version": "1.0.0", - "license": "MIT", - "dependencies": { - "@libsql/client-wasm": "*" - }, - "devDependencies": { - "esbuild": "0.19.11" - } - }, - "../..": { - "name": "@libsql/client-wasm", - "version": "0.4.0-pre.10", - "license": "MIT", - "dependencies": { - "@libsql/core": "0.4.0-pre.10", - "@libsql/libsql-wasm-experimental": "^0.0.1", - "js-base64": "^3.7.5" - }, - "devDependencies": { - "@types/jest": "^29.2.5", - "jest": "^29.3.1", - "ts-jest": "^29.0.5", - "typedoc": "^0.23.28", - "typescript": "^4.9.4" - } - }, - "node_modules/@esbuild/aix-ppc64": { - "version": "0.19.11", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.19.11.tgz", - "integrity": "sha512-FnzU0LyE3ySQk7UntJO4+qIiQgI7KoODnZg5xzXIrFJlKd2P2gwHsHY4927xj9y5PJmJSzULiUCWmv7iWnNa7g==", - "cpu": [ - "ppc64" - ], - "dev": true, - "optional": true, - "os": [ - "aix" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/android-arm": { - "version": "0.19.11", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.19.11.tgz", - "integrity": "sha512-5OVapq0ClabvKvQ58Bws8+wkLCV+Rxg7tUVbo9xu034Nm536QTII4YzhaFriQ7rMrorfnFKUsArD2lqKbFY4vw==", - "cpu": [ - "arm" - ], - "dev": true, - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/android-arm64": { - "version": "0.19.11", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.19.11.tgz", - "integrity": "sha512-aiu7K/5JnLj//KOnOfEZ0D90obUkRzDMyqd/wNAUQ34m4YUPVhRZpnqKV9uqDGxT7cToSDnIHsGooyIczu9T+Q==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/android-x64": { - "version": "0.19.11", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.19.11.tgz", - "integrity": "sha512-eccxjlfGw43WYoY9QgB82SgGgDbibcqyDTlk3l3C0jOVHKxrjdc9CTwDUQd0vkvYg5um0OH+GpxYvp39r+IPOg==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/darwin-arm64": { - "version": "0.19.11", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.19.11.tgz", - "integrity": "sha512-ETp87DRWuSt9KdDVkqSoKoLFHYTrkyz2+65fj9nfXsaV3bMhTCjtQfw3y+um88vGRKRiF7erPrh/ZuIdLUIVxQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/darwin-x64": { - "version": "0.19.11", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.19.11.tgz", - "integrity": "sha512-fkFUiS6IUK9WYUO/+22omwetaSNl5/A8giXvQlcinLIjVkxwTLSktbF5f/kJMftM2MJp9+fXqZ5ezS7+SALp4g==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/freebsd-arm64": { - "version": "0.19.11", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.19.11.tgz", - "integrity": "sha512-lhoSp5K6bxKRNdXUtHoNc5HhbXVCS8V0iZmDvyWvYq9S5WSfTIHU2UGjcGt7UeS6iEYp9eeymIl5mJBn0yiuxA==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/freebsd-x64": { - "version": "0.19.11", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.19.11.tgz", - "integrity": "sha512-JkUqn44AffGXitVI6/AbQdoYAq0TEullFdqcMY/PCUZ36xJ9ZJRtQabzMA+Vi7r78+25ZIBosLTOKnUXBSi1Kw==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-arm": { - "version": "0.19.11", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.19.11.tgz", - "integrity": "sha512-3CRkr9+vCV2XJbjwgzjPtO8T0SZUmRZla+UL1jw+XqHZPkPgZiyWvbDvl9rqAN8Zl7qJF0O/9ycMtjU67HN9/Q==", - "cpu": [ - "arm" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-arm64": { - "version": "0.19.11", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.19.11.tgz", - "integrity": "sha512-LneLg3ypEeveBSMuoa0kwMpCGmpu8XQUh+mL8XXwoYZ6Be2qBnVtcDI5azSvh7vioMDhoJFZzp9GWp9IWpYoUg==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-ia32": { - "version": "0.19.11", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.19.11.tgz", - "integrity": "sha512-caHy++CsD8Bgq2V5CodbJjFPEiDPq8JJmBdeyZ8GWVQMjRD0sU548nNdwPNvKjVpamYYVL40AORekgfIubwHoA==", - "cpu": [ - "ia32" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-loong64": { - "version": "0.19.11", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.19.11.tgz", - "integrity": "sha512-ppZSSLVpPrwHccvC6nQVZaSHlFsvCQyjnvirnVjbKSHuE5N24Yl8F3UwYUUR1UEPaFObGD2tSvVKbvR+uT1Nrg==", - "cpu": [ - "loong64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-mips64el": { - "version": "0.19.11", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.19.11.tgz", - "integrity": "sha512-B5x9j0OgjG+v1dF2DkH34lr+7Gmv0kzX6/V0afF41FkPMMqaQ77pH7CrhWeR22aEeHKaeZVtZ6yFwlxOKPVFyg==", - "cpu": [ - "mips64el" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-ppc64": { - "version": "0.19.11", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.19.11.tgz", - "integrity": "sha512-MHrZYLeCG8vXblMetWyttkdVRjQlQUb/oMgBNurVEnhj4YWOr4G5lmBfZjHYQHHN0g6yDmCAQRR8MUHldvvRDA==", - "cpu": [ - "ppc64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-riscv64": { - "version": "0.19.11", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.19.11.tgz", - "integrity": "sha512-f3DY++t94uVg141dozDu4CCUkYW+09rWtaWfnb3bqe4w5NqmZd6nPVBm+qbz7WaHZCoqXqHz5p6CM6qv3qnSSQ==", - "cpu": [ - "riscv64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-s390x": { - "version": "0.19.11", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.19.11.tgz", - "integrity": "sha512-A5xdUoyWJHMMlcSMcPGVLzYzpcY8QP1RtYzX5/bS4dvjBGVxdhuiYyFwp7z74ocV7WDc0n1harxmpq2ePOjI0Q==", - "cpu": [ - "s390x" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-x64": { - "version": "0.19.11", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.19.11.tgz", - "integrity": "sha512-grbyMlVCvJSfxFQUndw5mCtWs5LO1gUlwP4CDi4iJBbVpZcqLVT29FxgGuBJGSzyOxotFG4LoO5X+M1350zmPA==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/netbsd-x64": { - "version": "0.19.11", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.19.11.tgz", - "integrity": "sha512-13jvrQZJc3P230OhU8xgwUnDeuC/9egsjTkXN49b3GcS5BKvJqZn86aGM8W9pd14Kd+u7HuFBMVtrNGhh6fHEQ==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/openbsd-x64": { - "version": "0.19.11", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.19.11.tgz", - "integrity": "sha512-ysyOGZuTp6SNKPE11INDUeFVVQFrhcNDVUgSQVDzqsqX38DjhPEPATpid04LCoUr2WXhQTEZ8ct/EgJCUDpyNw==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/sunos-x64": { - "version": "0.19.11", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.19.11.tgz", - "integrity": "sha512-Hf+Sad9nVwvtxy4DXCZQqLpgmRTQqyFyhT3bZ4F2XlJCjxGmRFF0Shwn9rzhOYRB61w9VMXUkxlBy56dk9JJiQ==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "sunos" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/win32-arm64": { - "version": "0.19.11", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.19.11.tgz", - "integrity": "sha512-0P58Sbi0LctOMOQbpEOvOL44Ne0sqbS0XWHMvvrg6NE5jQ1xguCSSw9jQeUk2lfrXYsKDdOe6K+oZiwKPilYPQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/win32-ia32": { - "version": "0.19.11", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.19.11.tgz", - "integrity": "sha512-6YOrWS+sDJDmshdBIQU+Uoyh7pQKrdykdefC1avn76ss5c+RN6gut3LZA4E2cH5xUEp5/cA0+YxRaVtRAb0xBg==", - "cpu": [ - "ia32" - ], - "dev": true, - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/win32-x64": { - "version": "0.19.11", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.19.11.tgz", - "integrity": "sha512-vfkhltrjCAb603XaFhqhAF4LGDi2M4OrCRrFusyQ+iTLQ/o60QQXxc9cZC/FFpihBI9N1Grn6SMKVJ4KP7Fuiw==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@libsql/client-wasm": { - "resolved": "../..", - "link": true - }, - "node_modules/esbuild": { - "version": "0.19.11", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.19.11.tgz", - "integrity": "sha512-HJ96Hev2hX/6i5cDVwcqiJBBtuo9+FeIJOtZ9W1kA5M6AMJRHUZlpYZ1/SbEwtO0ioNAW8rUooVpC/WehY2SfA==", - "dev": true, - "hasInstallScript": true, - "bin": { - "esbuild": "bin/esbuild" - }, - "engines": { - "node": ">=12" - }, - "optionalDependencies": { - "@esbuild/aix-ppc64": "0.19.11", - "@esbuild/android-arm": "0.19.11", - "@esbuild/android-arm64": "0.19.11", - "@esbuild/android-x64": "0.19.11", - "@esbuild/darwin-arm64": "0.19.11", - "@esbuild/darwin-x64": "0.19.11", - "@esbuild/freebsd-arm64": "0.19.11", - "@esbuild/freebsd-x64": "0.19.11", - "@esbuild/linux-arm": "0.19.11", - "@esbuild/linux-arm64": "0.19.11", - "@esbuild/linux-ia32": "0.19.11", - "@esbuild/linux-loong64": "0.19.11", - "@esbuild/linux-mips64el": "0.19.11", - "@esbuild/linux-ppc64": "0.19.11", - "@esbuild/linux-riscv64": "0.19.11", - "@esbuild/linux-s390x": "0.19.11", - "@esbuild/linux-x64": "0.19.11", - "@esbuild/netbsd-x64": "0.19.11", - "@esbuild/openbsd-x64": "0.19.11", - "@esbuild/sunos-x64": "0.19.11", - "@esbuild/win32-arm64": "0.19.11", - "@esbuild/win32-ia32": "0.19.11", - "@esbuild/win32-x64": "0.19.11" - } - } - }, - "dependencies": { - "@esbuild/aix-ppc64": { - "version": "0.19.11", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.19.11.tgz", - "integrity": "sha512-FnzU0LyE3ySQk7UntJO4+qIiQgI7KoODnZg5xzXIrFJlKd2P2gwHsHY4927xj9y5PJmJSzULiUCWmv7iWnNa7g==", - "dev": true, - "optional": true - }, - "@esbuild/android-arm": { - "version": "0.19.11", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.19.11.tgz", - "integrity": "sha512-5OVapq0ClabvKvQ58Bws8+wkLCV+Rxg7tUVbo9xu034Nm536QTII4YzhaFriQ7rMrorfnFKUsArD2lqKbFY4vw==", - "dev": true, - "optional": true - }, - "@esbuild/android-arm64": { - "version": "0.19.11", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.19.11.tgz", - "integrity": "sha512-aiu7K/5JnLj//KOnOfEZ0D90obUkRzDMyqd/wNAUQ34m4YUPVhRZpnqKV9uqDGxT7cToSDnIHsGooyIczu9T+Q==", - "dev": true, - "optional": true - }, - "@esbuild/android-x64": { - "version": "0.19.11", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.19.11.tgz", - "integrity": "sha512-eccxjlfGw43WYoY9QgB82SgGgDbibcqyDTlk3l3C0jOVHKxrjdc9CTwDUQd0vkvYg5um0OH+GpxYvp39r+IPOg==", - "dev": true, - "optional": true - }, - "@esbuild/darwin-arm64": { - "version": "0.19.11", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.19.11.tgz", - "integrity": "sha512-ETp87DRWuSt9KdDVkqSoKoLFHYTrkyz2+65fj9nfXsaV3bMhTCjtQfw3y+um88vGRKRiF7erPrh/ZuIdLUIVxQ==", - "dev": true, - "optional": true - }, - "@esbuild/darwin-x64": { - "version": "0.19.11", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.19.11.tgz", - "integrity": "sha512-fkFUiS6IUK9WYUO/+22omwetaSNl5/A8giXvQlcinLIjVkxwTLSktbF5f/kJMftM2MJp9+fXqZ5ezS7+SALp4g==", - "dev": true, - "optional": true - }, - "@esbuild/freebsd-arm64": { - "version": "0.19.11", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.19.11.tgz", - "integrity": "sha512-lhoSp5K6bxKRNdXUtHoNc5HhbXVCS8V0iZmDvyWvYq9S5WSfTIHU2UGjcGt7UeS6iEYp9eeymIl5mJBn0yiuxA==", - "dev": true, - "optional": true - }, - "@esbuild/freebsd-x64": { - "version": "0.19.11", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.19.11.tgz", - "integrity": "sha512-JkUqn44AffGXitVI6/AbQdoYAq0TEullFdqcMY/PCUZ36xJ9ZJRtQabzMA+Vi7r78+25ZIBosLTOKnUXBSi1Kw==", - "dev": true, - "optional": true - }, - "@esbuild/linux-arm": { - "version": "0.19.11", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.19.11.tgz", - "integrity": "sha512-3CRkr9+vCV2XJbjwgzjPtO8T0SZUmRZla+UL1jw+XqHZPkPgZiyWvbDvl9rqAN8Zl7qJF0O/9ycMtjU67HN9/Q==", - "dev": true, - "optional": true - }, - "@esbuild/linux-arm64": { - "version": "0.19.11", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.19.11.tgz", - "integrity": "sha512-LneLg3ypEeveBSMuoa0kwMpCGmpu8XQUh+mL8XXwoYZ6Be2qBnVtcDI5azSvh7vioMDhoJFZzp9GWp9IWpYoUg==", - "dev": true, - "optional": true - }, - "@esbuild/linux-ia32": { - "version": "0.19.11", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.19.11.tgz", - "integrity": "sha512-caHy++CsD8Bgq2V5CodbJjFPEiDPq8JJmBdeyZ8GWVQMjRD0sU548nNdwPNvKjVpamYYVL40AORekgfIubwHoA==", - "dev": true, - "optional": true - }, - "@esbuild/linux-loong64": { - "version": "0.19.11", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.19.11.tgz", - "integrity": "sha512-ppZSSLVpPrwHccvC6nQVZaSHlFsvCQyjnvirnVjbKSHuE5N24Yl8F3UwYUUR1UEPaFObGD2tSvVKbvR+uT1Nrg==", - "dev": true, - "optional": true - }, - "@esbuild/linux-mips64el": { - "version": "0.19.11", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.19.11.tgz", - "integrity": "sha512-B5x9j0OgjG+v1dF2DkH34lr+7Gmv0kzX6/V0afF41FkPMMqaQ77pH7CrhWeR22aEeHKaeZVtZ6yFwlxOKPVFyg==", - "dev": true, - "optional": true - }, - "@esbuild/linux-ppc64": { - "version": "0.19.11", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.19.11.tgz", - "integrity": "sha512-MHrZYLeCG8vXblMetWyttkdVRjQlQUb/oMgBNurVEnhj4YWOr4G5lmBfZjHYQHHN0g6yDmCAQRR8MUHldvvRDA==", - "dev": true, - "optional": true - }, - "@esbuild/linux-riscv64": { - "version": "0.19.11", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.19.11.tgz", - "integrity": "sha512-f3DY++t94uVg141dozDu4CCUkYW+09rWtaWfnb3bqe4w5NqmZd6nPVBm+qbz7WaHZCoqXqHz5p6CM6qv3qnSSQ==", - "dev": true, - "optional": true - }, - "@esbuild/linux-s390x": { - "version": "0.19.11", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.19.11.tgz", - "integrity": "sha512-A5xdUoyWJHMMlcSMcPGVLzYzpcY8QP1RtYzX5/bS4dvjBGVxdhuiYyFwp7z74ocV7WDc0n1harxmpq2ePOjI0Q==", - "dev": true, - "optional": true - }, - "@esbuild/linux-x64": { - "version": "0.19.11", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.19.11.tgz", - "integrity": "sha512-grbyMlVCvJSfxFQUndw5mCtWs5LO1gUlwP4CDi4iJBbVpZcqLVT29FxgGuBJGSzyOxotFG4LoO5X+M1350zmPA==", - "dev": true, - "optional": true - }, - "@esbuild/netbsd-x64": { - "version": "0.19.11", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.19.11.tgz", - "integrity": "sha512-13jvrQZJc3P230OhU8xgwUnDeuC/9egsjTkXN49b3GcS5BKvJqZn86aGM8W9pd14Kd+u7HuFBMVtrNGhh6fHEQ==", - "dev": true, - "optional": true - }, - "@esbuild/openbsd-x64": { - "version": "0.19.11", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.19.11.tgz", - "integrity": "sha512-ysyOGZuTp6SNKPE11INDUeFVVQFrhcNDVUgSQVDzqsqX38DjhPEPATpid04LCoUr2WXhQTEZ8ct/EgJCUDpyNw==", - "dev": true, - "optional": true - }, - "@esbuild/sunos-x64": { - "version": "0.19.11", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.19.11.tgz", - "integrity": "sha512-Hf+Sad9nVwvtxy4DXCZQqLpgmRTQqyFyhT3bZ4F2XlJCjxGmRFF0Shwn9rzhOYRB61w9VMXUkxlBy56dk9JJiQ==", - "dev": true, - "optional": true - }, - "@esbuild/win32-arm64": { - "version": "0.19.11", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.19.11.tgz", - "integrity": "sha512-0P58Sbi0LctOMOQbpEOvOL44Ne0sqbS0XWHMvvrg6NE5jQ1xguCSSw9jQeUk2lfrXYsKDdOe6K+oZiwKPilYPQ==", - "dev": true, - "optional": true - }, - "@esbuild/win32-ia32": { - "version": "0.19.11", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.19.11.tgz", - "integrity": "sha512-6YOrWS+sDJDmshdBIQU+Uoyh7pQKrdykdefC1avn76ss5c+RN6gut3LZA4E2cH5xUEp5/cA0+YxRaVtRAb0xBg==", - "dev": true, - "optional": true - }, - "@esbuild/win32-x64": { - "version": "0.19.11", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.19.11.tgz", - "integrity": "sha512-vfkhltrjCAb603XaFhqhAF4LGDi2M4OrCRrFusyQ+iTLQ/o60QQXxc9cZC/FFpihBI9N1Grn6SMKVJ4KP7Fuiw==", - "dev": true, - "optional": true - }, - "@libsql/client-wasm": { - "version": "file:../..", - "requires": { - "@libsql/core": "0.4.0-pre.10", - "@libsql/libsql-wasm-experimental": "^0.0.1", - "@types/jest": "^29.2.5", - "jest": "^29.3.1", - "js-base64": "^3.7.5", - "ts-jest": "^29.0.5", - "typedoc": "^0.23.28", - "typescript": "^4.9.4" - } - }, - "esbuild": { - "version": "0.19.11", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.19.11.tgz", - "integrity": "sha512-HJ96Hev2hX/6i5cDVwcqiJBBtuo9+FeIJOtZ9W1kA5M6AMJRHUZlpYZ1/SbEwtO0ioNAW8rUooVpC/WehY2SfA==", - "dev": true, - "requires": { - "@esbuild/aix-ppc64": "0.19.11", - "@esbuild/android-arm": "0.19.11", - "@esbuild/android-arm64": "0.19.11", - "@esbuild/android-x64": "0.19.11", - "@esbuild/darwin-arm64": "0.19.11", - "@esbuild/darwin-x64": "0.19.11", - "@esbuild/freebsd-arm64": "0.19.11", - "@esbuild/freebsd-x64": "0.19.11", - "@esbuild/linux-arm": "0.19.11", - "@esbuild/linux-arm64": "0.19.11", - "@esbuild/linux-ia32": "0.19.11", - "@esbuild/linux-loong64": "0.19.11", - "@esbuild/linux-mips64el": "0.19.11", - "@esbuild/linux-ppc64": "0.19.11", - "@esbuild/linux-riscv64": "0.19.11", - "@esbuild/linux-s390x": "0.19.11", - "@esbuild/linux-x64": "0.19.11", - "@esbuild/netbsd-x64": "0.19.11", - "@esbuild/openbsd-x64": "0.19.11", - "@esbuild/sunos-x64": "0.19.11", - "@esbuild/win32-arm64": "0.19.11", - "@esbuild/win32-ia32": "0.19.11", - "@esbuild/win32-x64": "0.19.11" - } + "name": "browser", + "version": "1.0.0", + "lockfileVersion": 2, + "requires": true, + "packages": { + "": { + "name": "browser", + "version": "1.0.0", + "license": "MIT", + "dependencies": { + "@libsql/client-wasm": "*" + }, + "devDependencies": { + "esbuild": "0.19.11" + } + }, + "../..": { + "name": "@libsql/client-wasm", + "version": "0.4.0-pre.10", + "license": "MIT", + "dependencies": { + "@libsql/core": "0.4.0-pre.10", + "@libsql/libsql-wasm-experimental": "^0.0.1", + "js-base64": "^3.7.5" + }, + "devDependencies": { + "@types/jest": "^29.2.5", + "jest": "^29.3.1", + "ts-jest": "^29.0.5", + "typedoc": "^0.23.28", + "typescript": "^4.9.4" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.19.11", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.19.11.tgz", + "integrity": "sha512-FnzU0LyE3ySQk7UntJO4+qIiQgI7KoODnZg5xzXIrFJlKd2P2gwHsHY4927xj9y5PJmJSzULiUCWmv7iWnNa7g==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.19.11", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.19.11.tgz", + "integrity": "sha512-5OVapq0ClabvKvQ58Bws8+wkLCV+Rxg7tUVbo9xu034Nm536QTII4YzhaFriQ7rMrorfnFKUsArD2lqKbFY4vw==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.19.11", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.19.11.tgz", + "integrity": "sha512-aiu7K/5JnLj//KOnOfEZ0D90obUkRzDMyqd/wNAUQ34m4YUPVhRZpnqKV9uqDGxT7cToSDnIHsGooyIczu9T+Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.19.11", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.19.11.tgz", + "integrity": "sha512-eccxjlfGw43WYoY9QgB82SgGgDbibcqyDTlk3l3C0jOVHKxrjdc9CTwDUQd0vkvYg5um0OH+GpxYvp39r+IPOg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.19.11", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.19.11.tgz", + "integrity": "sha512-ETp87DRWuSt9KdDVkqSoKoLFHYTrkyz2+65fj9nfXsaV3bMhTCjtQfw3y+um88vGRKRiF7erPrh/ZuIdLUIVxQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.19.11", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.19.11.tgz", + "integrity": "sha512-fkFUiS6IUK9WYUO/+22omwetaSNl5/A8giXvQlcinLIjVkxwTLSktbF5f/kJMftM2MJp9+fXqZ5ezS7+SALp4g==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.19.11", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.19.11.tgz", + "integrity": "sha512-lhoSp5K6bxKRNdXUtHoNc5HhbXVCS8V0iZmDvyWvYq9S5WSfTIHU2UGjcGt7UeS6iEYp9eeymIl5mJBn0yiuxA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.19.11", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.19.11.tgz", + "integrity": "sha512-JkUqn44AffGXitVI6/AbQdoYAq0TEullFdqcMY/PCUZ36xJ9ZJRtQabzMA+Vi7r78+25ZIBosLTOKnUXBSi1Kw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.19.11", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.19.11.tgz", + "integrity": "sha512-3CRkr9+vCV2XJbjwgzjPtO8T0SZUmRZla+UL1jw+XqHZPkPgZiyWvbDvl9rqAN8Zl7qJF0O/9ycMtjU67HN9/Q==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.19.11", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.19.11.tgz", + "integrity": "sha512-LneLg3ypEeveBSMuoa0kwMpCGmpu8XQUh+mL8XXwoYZ6Be2qBnVtcDI5azSvh7vioMDhoJFZzp9GWp9IWpYoUg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.19.11", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.19.11.tgz", + "integrity": "sha512-caHy++CsD8Bgq2V5CodbJjFPEiDPq8JJmBdeyZ8GWVQMjRD0sU548nNdwPNvKjVpamYYVL40AORekgfIubwHoA==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.19.11", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.19.11.tgz", + "integrity": "sha512-ppZSSLVpPrwHccvC6nQVZaSHlFsvCQyjnvirnVjbKSHuE5N24Yl8F3UwYUUR1UEPaFObGD2tSvVKbvR+uT1Nrg==", + "cpu": [ + "loong64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.19.11", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.19.11.tgz", + "integrity": "sha512-B5x9j0OgjG+v1dF2DkH34lr+7Gmv0kzX6/V0afF41FkPMMqaQ77pH7CrhWeR22aEeHKaeZVtZ6yFwlxOKPVFyg==", + "cpu": [ + "mips64el" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.19.11", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.19.11.tgz", + "integrity": "sha512-MHrZYLeCG8vXblMetWyttkdVRjQlQUb/oMgBNurVEnhj4YWOr4G5lmBfZjHYQHHN0g6yDmCAQRR8MUHldvvRDA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.19.11", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.19.11.tgz", + "integrity": "sha512-f3DY++t94uVg141dozDu4CCUkYW+09rWtaWfnb3bqe4w5NqmZd6nPVBm+qbz7WaHZCoqXqHz5p6CM6qv3qnSSQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.19.11", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.19.11.tgz", + "integrity": "sha512-A5xdUoyWJHMMlcSMcPGVLzYzpcY8QP1RtYzX5/bS4dvjBGVxdhuiYyFwp7z74ocV7WDc0n1harxmpq2ePOjI0Q==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.19.11", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.19.11.tgz", + "integrity": "sha512-grbyMlVCvJSfxFQUndw5mCtWs5LO1gUlwP4CDi4iJBbVpZcqLVT29FxgGuBJGSzyOxotFG4LoO5X+M1350zmPA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.19.11", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.19.11.tgz", + "integrity": "sha512-13jvrQZJc3P230OhU8xgwUnDeuC/9egsjTkXN49b3GcS5BKvJqZn86aGM8W9pd14Kd+u7HuFBMVtrNGhh6fHEQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.19.11", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.19.11.tgz", + "integrity": "sha512-ysyOGZuTp6SNKPE11INDUeFVVQFrhcNDVUgSQVDzqsqX38DjhPEPATpid04LCoUr2WXhQTEZ8ct/EgJCUDpyNw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.19.11", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.19.11.tgz", + "integrity": "sha512-Hf+Sad9nVwvtxy4DXCZQqLpgmRTQqyFyhT3bZ4F2XlJCjxGmRFF0Shwn9rzhOYRB61w9VMXUkxlBy56dk9JJiQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.19.11", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.19.11.tgz", + "integrity": "sha512-0P58Sbi0LctOMOQbpEOvOL44Ne0sqbS0XWHMvvrg6NE5jQ1xguCSSw9jQeUk2lfrXYsKDdOe6K+oZiwKPilYPQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.19.11", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.19.11.tgz", + "integrity": "sha512-6YOrWS+sDJDmshdBIQU+Uoyh7pQKrdykdefC1avn76ss5c+RN6gut3LZA4E2cH5xUEp5/cA0+YxRaVtRAb0xBg==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.19.11", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.19.11.tgz", + "integrity": "sha512-vfkhltrjCAb603XaFhqhAF4LGDi2M4OrCRrFusyQ+iTLQ/o60QQXxc9cZC/FFpihBI9N1Grn6SMKVJ4KP7Fuiw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@libsql/client-wasm": { + "resolved": "../..", + "link": true + }, + "node_modules/esbuild": { + "version": "0.19.11", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.19.11.tgz", + "integrity": "sha512-HJ96Hev2hX/6i5cDVwcqiJBBtuo9+FeIJOtZ9W1kA5M6AMJRHUZlpYZ1/SbEwtO0ioNAW8rUooVpC/WehY2SfA==", + "dev": true, + "hasInstallScript": true, + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.19.11", + "@esbuild/android-arm": "0.19.11", + "@esbuild/android-arm64": "0.19.11", + "@esbuild/android-x64": "0.19.11", + "@esbuild/darwin-arm64": "0.19.11", + "@esbuild/darwin-x64": "0.19.11", + "@esbuild/freebsd-arm64": "0.19.11", + "@esbuild/freebsd-x64": "0.19.11", + "@esbuild/linux-arm": "0.19.11", + "@esbuild/linux-arm64": "0.19.11", + "@esbuild/linux-ia32": "0.19.11", + "@esbuild/linux-loong64": "0.19.11", + "@esbuild/linux-mips64el": "0.19.11", + "@esbuild/linux-ppc64": "0.19.11", + "@esbuild/linux-riscv64": "0.19.11", + "@esbuild/linux-s390x": "0.19.11", + "@esbuild/linux-x64": "0.19.11", + "@esbuild/netbsd-x64": "0.19.11", + "@esbuild/openbsd-x64": "0.19.11", + "@esbuild/sunos-x64": "0.19.11", + "@esbuild/win32-arm64": "0.19.11", + "@esbuild/win32-ia32": "0.19.11", + "@esbuild/win32-x64": "0.19.11" + } + } + }, + "dependencies": { + "@esbuild/aix-ppc64": { + "version": "0.19.11", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.19.11.tgz", + "integrity": "sha512-FnzU0LyE3ySQk7UntJO4+qIiQgI7KoODnZg5xzXIrFJlKd2P2gwHsHY4927xj9y5PJmJSzULiUCWmv7iWnNa7g==", + "dev": true, + "optional": true + }, + "@esbuild/android-arm": { + "version": "0.19.11", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.19.11.tgz", + "integrity": "sha512-5OVapq0ClabvKvQ58Bws8+wkLCV+Rxg7tUVbo9xu034Nm536QTII4YzhaFriQ7rMrorfnFKUsArD2lqKbFY4vw==", + "dev": true, + "optional": true + }, + "@esbuild/android-arm64": { + "version": "0.19.11", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.19.11.tgz", + "integrity": "sha512-aiu7K/5JnLj//KOnOfEZ0D90obUkRzDMyqd/wNAUQ34m4YUPVhRZpnqKV9uqDGxT7cToSDnIHsGooyIczu9T+Q==", + "dev": true, + "optional": true + }, + "@esbuild/android-x64": { + "version": "0.19.11", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.19.11.tgz", + "integrity": "sha512-eccxjlfGw43WYoY9QgB82SgGgDbibcqyDTlk3l3C0jOVHKxrjdc9CTwDUQd0vkvYg5um0OH+GpxYvp39r+IPOg==", + "dev": true, + "optional": true + }, + "@esbuild/darwin-arm64": { + "version": "0.19.11", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.19.11.tgz", + "integrity": "sha512-ETp87DRWuSt9KdDVkqSoKoLFHYTrkyz2+65fj9nfXsaV3bMhTCjtQfw3y+um88vGRKRiF7erPrh/ZuIdLUIVxQ==", + "dev": true, + "optional": true + }, + "@esbuild/darwin-x64": { + "version": "0.19.11", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.19.11.tgz", + "integrity": "sha512-fkFUiS6IUK9WYUO/+22omwetaSNl5/A8giXvQlcinLIjVkxwTLSktbF5f/kJMftM2MJp9+fXqZ5ezS7+SALp4g==", + "dev": true, + "optional": true + }, + "@esbuild/freebsd-arm64": { + "version": "0.19.11", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.19.11.tgz", + "integrity": "sha512-lhoSp5K6bxKRNdXUtHoNc5HhbXVCS8V0iZmDvyWvYq9S5WSfTIHU2UGjcGt7UeS6iEYp9eeymIl5mJBn0yiuxA==", + "dev": true, + "optional": true + }, + "@esbuild/freebsd-x64": { + "version": "0.19.11", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.19.11.tgz", + "integrity": "sha512-JkUqn44AffGXitVI6/AbQdoYAq0TEullFdqcMY/PCUZ36xJ9ZJRtQabzMA+Vi7r78+25ZIBosLTOKnUXBSi1Kw==", + "dev": true, + "optional": true + }, + "@esbuild/linux-arm": { + "version": "0.19.11", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.19.11.tgz", + "integrity": "sha512-3CRkr9+vCV2XJbjwgzjPtO8T0SZUmRZla+UL1jw+XqHZPkPgZiyWvbDvl9rqAN8Zl7qJF0O/9ycMtjU67HN9/Q==", + "dev": true, + "optional": true + }, + "@esbuild/linux-arm64": { + "version": "0.19.11", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.19.11.tgz", + "integrity": "sha512-LneLg3ypEeveBSMuoa0kwMpCGmpu8XQUh+mL8XXwoYZ6Be2qBnVtcDI5azSvh7vioMDhoJFZzp9GWp9IWpYoUg==", + "dev": true, + "optional": true + }, + "@esbuild/linux-ia32": { + "version": "0.19.11", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.19.11.tgz", + "integrity": "sha512-caHy++CsD8Bgq2V5CodbJjFPEiDPq8JJmBdeyZ8GWVQMjRD0sU548nNdwPNvKjVpamYYVL40AORekgfIubwHoA==", + "dev": true, + "optional": true + }, + "@esbuild/linux-loong64": { + "version": "0.19.11", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.19.11.tgz", + "integrity": "sha512-ppZSSLVpPrwHccvC6nQVZaSHlFsvCQyjnvirnVjbKSHuE5N24Yl8F3UwYUUR1UEPaFObGD2tSvVKbvR+uT1Nrg==", + "dev": true, + "optional": true + }, + "@esbuild/linux-mips64el": { + "version": "0.19.11", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.19.11.tgz", + "integrity": "sha512-B5x9j0OgjG+v1dF2DkH34lr+7Gmv0kzX6/V0afF41FkPMMqaQ77pH7CrhWeR22aEeHKaeZVtZ6yFwlxOKPVFyg==", + "dev": true, + "optional": true + }, + "@esbuild/linux-ppc64": { + "version": "0.19.11", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.19.11.tgz", + "integrity": "sha512-MHrZYLeCG8vXblMetWyttkdVRjQlQUb/oMgBNurVEnhj4YWOr4G5lmBfZjHYQHHN0g6yDmCAQRR8MUHldvvRDA==", + "dev": true, + "optional": true + }, + "@esbuild/linux-riscv64": { + "version": "0.19.11", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.19.11.tgz", + "integrity": "sha512-f3DY++t94uVg141dozDu4CCUkYW+09rWtaWfnb3bqe4w5NqmZd6nPVBm+qbz7WaHZCoqXqHz5p6CM6qv3qnSSQ==", + "dev": true, + "optional": true + }, + "@esbuild/linux-s390x": { + "version": "0.19.11", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.19.11.tgz", + "integrity": "sha512-A5xdUoyWJHMMlcSMcPGVLzYzpcY8QP1RtYzX5/bS4dvjBGVxdhuiYyFwp7z74ocV7WDc0n1harxmpq2ePOjI0Q==", + "dev": true, + "optional": true + }, + "@esbuild/linux-x64": { + "version": "0.19.11", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.19.11.tgz", + "integrity": "sha512-grbyMlVCvJSfxFQUndw5mCtWs5LO1gUlwP4CDi4iJBbVpZcqLVT29FxgGuBJGSzyOxotFG4LoO5X+M1350zmPA==", + "dev": true, + "optional": true + }, + "@esbuild/netbsd-x64": { + "version": "0.19.11", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.19.11.tgz", + "integrity": "sha512-13jvrQZJc3P230OhU8xgwUnDeuC/9egsjTkXN49b3GcS5BKvJqZn86aGM8W9pd14Kd+u7HuFBMVtrNGhh6fHEQ==", + "dev": true, + "optional": true + }, + "@esbuild/openbsd-x64": { + "version": "0.19.11", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.19.11.tgz", + "integrity": "sha512-ysyOGZuTp6SNKPE11INDUeFVVQFrhcNDVUgSQVDzqsqX38DjhPEPATpid04LCoUr2WXhQTEZ8ct/EgJCUDpyNw==", + "dev": true, + "optional": true + }, + "@esbuild/sunos-x64": { + "version": "0.19.11", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.19.11.tgz", + "integrity": "sha512-Hf+Sad9nVwvtxy4DXCZQqLpgmRTQqyFyhT3bZ4F2XlJCjxGmRFF0Shwn9rzhOYRB61w9VMXUkxlBy56dk9JJiQ==", + "dev": true, + "optional": true + }, + "@esbuild/win32-arm64": { + "version": "0.19.11", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.19.11.tgz", + "integrity": "sha512-0P58Sbi0LctOMOQbpEOvOL44Ne0sqbS0XWHMvvrg6NE5jQ1xguCSSw9jQeUk2lfrXYsKDdOe6K+oZiwKPilYPQ==", + "dev": true, + "optional": true + }, + "@esbuild/win32-ia32": { + "version": "0.19.11", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.19.11.tgz", + "integrity": "sha512-6YOrWS+sDJDmshdBIQU+Uoyh7pQKrdykdefC1avn76ss5c+RN6gut3LZA4E2cH5xUEp5/cA0+YxRaVtRAb0xBg==", + "dev": true, + "optional": true + }, + "@esbuild/win32-x64": { + "version": "0.19.11", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.19.11.tgz", + "integrity": "sha512-vfkhltrjCAb603XaFhqhAF4LGDi2M4OrCRrFusyQ+iTLQ/o60QQXxc9cZC/FFpihBI9N1Grn6SMKVJ4KP7Fuiw==", + "dev": true, + "optional": true + }, + "@libsql/client-wasm": { + "version": "file:../..", + "requires": { + "@libsql/core": "0.4.0-pre.10", + "@libsql/libsql-wasm-experimental": "^0.0.1", + "@types/jest": "^29.2.5", + "jest": "^29.3.1", + "js-base64": "^3.7.5", + "ts-jest": "^29.0.5", + "typedoc": "^0.23.28", + "typescript": "^4.9.4" + } + }, + "esbuild": { + "version": "0.19.11", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.19.11.tgz", + "integrity": "sha512-HJ96Hev2hX/6i5cDVwcqiJBBtuo9+FeIJOtZ9W1kA5M6AMJRHUZlpYZ1/SbEwtO0ioNAW8rUooVpC/WehY2SfA==", + "dev": true, + "requires": { + "@esbuild/aix-ppc64": "0.19.11", + "@esbuild/android-arm": "0.19.11", + "@esbuild/android-arm64": "0.19.11", + "@esbuild/android-x64": "0.19.11", + "@esbuild/darwin-arm64": "0.19.11", + "@esbuild/darwin-x64": "0.19.11", + "@esbuild/freebsd-arm64": "0.19.11", + "@esbuild/freebsd-x64": "0.19.11", + "@esbuild/linux-arm": "0.19.11", + "@esbuild/linux-arm64": "0.19.11", + "@esbuild/linux-ia32": "0.19.11", + "@esbuild/linux-loong64": "0.19.11", + "@esbuild/linux-mips64el": "0.19.11", + "@esbuild/linux-ppc64": "0.19.11", + "@esbuild/linux-riscv64": "0.19.11", + "@esbuild/linux-s390x": "0.19.11", + "@esbuild/linux-x64": "0.19.11", + "@esbuild/netbsd-x64": "0.19.11", + "@esbuild/openbsd-x64": "0.19.11", + "@esbuild/sunos-x64": "0.19.11", + "@esbuild/win32-arm64": "0.19.11", + "@esbuild/win32-ia32": "0.19.11", + "@esbuild/win32-x64": "0.19.11" + } + } } - } } diff --git a/packages/libsql-client-wasm/examples/browser/package.json b/packages/libsql-client-wasm/examples/browser/package.json index a544723e..a449aa87 100644 --- a/packages/libsql-client-wasm/examples/browser/package.json +++ b/packages/libsql-client-wasm/examples/browser/package.json @@ -1,18 +1,18 @@ { - "name": "browser", - "version": "1.0.0", - "description": "", - "main": "index.js", - "type": "module", - "scripts": { - "test": "echo \"Error: no test specified\" && exit 1" - }, - "author": "", - "license": "MIT", - "dependencies": { - "@libsql/client-wasm": "../.." - }, - "devDependencies": { - "esbuild": "0.19.11" - } + "name": "browser", + "version": "1.0.0", + "description": "", + "main": "index.js", + "type": "module", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "author": "", + "license": "MIT", + "dependencies": { + "@libsql/client-wasm": "../.." + }, + "devDependencies": { + "esbuild": "0.19.11" + } } diff --git a/packages/libsql-client-wasm/examples/node/index.js b/packages/libsql-client-wasm/examples/node/index.js index 8fa15273..634b9a98 100644 --- a/packages/libsql-client-wasm/examples/node/index.js +++ b/packages/libsql-client-wasm/examples/node/index.js @@ -1,16 +1,16 @@ import { createClient } from "@libsql/client-wasm"; async function main() { - const config = { - url: "file:local.db", - }; - const db = await createClient(config); - await db.execute("CREATE TABLE users (id INT PRIMARY KEY, username TEXT)"); - await db.execute("INSERT INTO users VALUES (1, 'penberg')"); - const rs = await db.execute("SELECT * FROM users"); - console.log(rs); + const config = { + url: "file:local.db", + }; + const db = await createClient(config); + await db.execute("CREATE TABLE users (id INT PRIMARY KEY, username TEXT)"); + await db.execute("INSERT INTO users VALUES (1, 'penberg')"); + const rs = await db.execute("SELECT * FROM users"); + console.log(rs); } main().catch((error) => { - console.log(error); + console.log(error); }); diff --git a/packages/libsql-client-wasm/examples/node/package-lock.json b/packages/libsql-client-wasm/examples/node/package-lock.json index e9a84336..6a110b76 100644 --- a/packages/libsql-client-wasm/examples/node/package-lock.json +++ b/packages/libsql-client-wasm/examples/node/package-lock.json @@ -1,37 +1,37 @@ { - "name": "nodejs", - "version": "1.0.0", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "nodejs", - "version": "1.0.0", - "license": "MIT", - "dependencies": { - "@libsql/client-wasm": "../../" - } - }, - "../..": { - "name": "@libsql/client-wasm", - "version": "0.4.0-pre.7", - "license": "MIT", - "dependencies": { - "@libsql/core": "0.4.0-pre.7", - "@libsql/libsql-wasm-experimental": "^0.0.1", - "js-base64": "^3.7.5" - }, - "devDependencies": { - "@types/jest": "^29.2.5", - "jest": "^29.3.1", - "ts-jest": "^29.0.5", - "typedoc": "^0.23.28", - "typescript": "^4.9.4" - } - }, - "node_modules/@libsql/client-wasm": { - "resolved": "../..", - "link": true + "name": "nodejs", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "nodejs", + "version": "1.0.0", + "license": "MIT", + "dependencies": { + "@libsql/client-wasm": "../../" + } + }, + "../..": { + "name": "@libsql/client-wasm", + "version": "0.4.0-pre.7", + "license": "MIT", + "dependencies": { + "@libsql/core": "0.4.0-pre.7", + "@libsql/libsql-wasm-experimental": "^0.0.1", + "js-base64": "^3.7.5" + }, + "devDependencies": { + "@types/jest": "^29.2.5", + "jest": "^29.3.1", + "ts-jest": "^29.0.5", + "typedoc": "^0.23.28", + "typescript": "^4.9.4" + } + }, + "node_modules/@libsql/client-wasm": { + "resolved": "../..", + "link": true + } } - } } diff --git a/packages/libsql-client-wasm/examples/node/package.json b/packages/libsql-client-wasm/examples/node/package.json index b3810808..964d78b6 100644 --- a/packages/libsql-client-wasm/examples/node/package.json +++ b/packages/libsql-client-wasm/examples/node/package.json @@ -1,15 +1,15 @@ { - "name": "nodejs", - "version": "1.0.0", - "description": "", - "main": "index.js", - "type": "module", - "scripts": { - "test": "echo \"Error: no test specified\" && exit 1" - }, - "author": "", - "license": "MIT", - "dependencies": { - "@libsql/client-wasm": "../../" - } + "name": "nodejs", + "version": "1.0.0", + "description": "", + "main": "index.js", + "type": "module", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "author": "", + "license": "MIT", + "dependencies": { + "@libsql/client-wasm": "../../" + } } diff --git a/packages/libsql-client-wasm/jest.config.js b/packages/libsql-client-wasm/jest.config.js index c558e7d7..044a5b86 100644 --- a/packages/libsql-client-wasm/jest.config.js +++ b/packages/libsql-client-wasm/jest.config.js @@ -1,7 +1,7 @@ export default { - preset: "ts-jest/presets/default-esm", - moduleNameMapper: { - "^(\\.{1,2}/.*)\\.js$": "$1", - }, - testMatch: ["**/__tests__/*.test.[jt]s"], + preset: "ts-jest/presets/default-esm", + moduleNameMapper: { + "^(\\.{1,2}/.*)\\.js$": "$1", + }, + testMatch: ["**/__tests__/*.test.[jt]s"], }; diff --git a/packages/libsql-client-wasm/package-cjs.json b/packages/libsql-client-wasm/package-cjs.json index 5bbefffb..1cd945a3 100644 --- a/packages/libsql-client-wasm/package-cjs.json +++ b/packages/libsql-client-wasm/package-cjs.json @@ -1,3 +1,3 @@ { - "type": "commonjs" + "type": "commonjs" } diff --git a/packages/libsql-client-wasm/package.json b/packages/libsql-client-wasm/package.json index cf7a32ea..68cacd63 100644 --- a/packages/libsql-client-wasm/package.json +++ b/packages/libsql-client-wasm/package.json @@ -1,70 +1,70 @@ { - "name": "@libsql/client-wasm", - "version": "0.6.0", - "keywords": [ - "libsql", - "database", - "sqlite", - "serverless", - "vercel", - "netlify", - "lambda" - ], - "description": "libSQL driver for TypeScript and JavaScript", - "repository": { - "type": "git", - "url": "https://github.com/libsql/libsql-client-ts" - }, - "authors": [ - "Jan Špaček ", - "Pekka Enberg ", - "Jan Plhak " - ], - "license": "MIT", - "type": "module", - "bundledDependencies": [ - "@libsql/libsql-wasm-experimental" - ], - "main": "lib-esm/wasm.js", - "types": "lib-esm/wasm.d.ts", - "exports": { - ".": { - "types": "./lib-esm/wasm.d.ts", - "import": { - "default": "./lib-esm/wasm.js" - } + "name": "@libsql/client-wasm", + "version": "0.6.0", + "keywords": [ + "libsql", + "database", + "sqlite", + "serverless", + "vercel", + "netlify", + "lambda" + ], + "description": "libSQL driver for TypeScript and JavaScript", + "repository": { + "type": "git", + "url": "https://github.com/libsql/libsql-client-ts" + }, + "authors": [ + "Jan Špaček ", + "Pekka Enberg ", + "Jan Plhak " + ], + "license": "MIT", + "type": "module", + "bundledDependencies": [ + "@libsql/libsql-wasm-experimental" + ], + "main": "lib-esm/wasm.js", + "types": "lib-esm/wasm.d.ts", + "exports": { + ".": { + "types": "./lib-esm/wasm.d.ts", + "import": { + "default": "./lib-esm/wasm.js" + } + } + }, + "typesVersions": { + "*": { + ".": [ + "./lib-esm/wasm.d.ts" + ] + } + }, + "files": [ + "lib-esm/**" + ], + "scripts": { + "prepublishOnly": "npm run build", + "prebuild": "rm -rf ./lib-esm && npm run bundle", + "build": "npm run build:esm", + "build:esm": "tsc -p tsconfig.build-esm.json", + "bundle": "rm -rf node_modules && mkdir -p node_modules/@libsql/libsql-wasm-experimental && cp -R ../../node_modules/@libsql/libsql-wasm-experimental/* node_modules/@libsql/libsql-wasm-experimental", + "test": "jest --runInBand", + "typecheck": "tsc --noEmit", + "typedoc": "rm -rf ./docs && typedoc" + }, + "dependencies": { + "@libsql/core": "0.6.0", + "@libsql/libsql-wasm-experimental": "^0.0.2", + "js-base64": "^3.7.5" + }, + "devDependencies": { + "@types/jest": "^29.2.5", + "jest": "^29.3.1", + "ts-jest": "^29.0.5", + "typedoc": "^0.23.28", + "typescript": "^4.9.4" } - }, - "typesVersions": { - "*": { - ".": [ - "./lib-esm/wasm.d.ts" - ] - } - }, - "files": [ - "lib-esm/**" - ], - "scripts": { - "prepublishOnly": "npm run build", - "prebuild": "rm -rf ./lib-esm && npm run bundle", - "build": "npm run build:esm", - "build:esm": "tsc -p tsconfig.build-esm.json", - "bundle": "rm -rf node_modules && mkdir -p node_modules/@libsql/libsql-wasm-experimental && cp -R ../../node_modules/@libsql/libsql-wasm-experimental/* node_modules/@libsql/libsql-wasm-experimental", - "test": "jest --runInBand", - "typecheck": "tsc --noEmit", - "typedoc": "rm -rf ./docs && typedoc" - }, - "dependencies": { - "@libsql/core": "0.6.0", - "@libsql/libsql-wasm-experimental": "^0.0.2", - "js-base64": "^3.7.5" - }, - "devDependencies": { - "@types/jest": "^29.2.5", - "jest": "^29.3.1", - "ts-jest": "^29.0.5", - "typedoc": "^0.23.28", - "typescript": "^4.9.4" - } } diff --git a/packages/libsql-client-wasm/src/wasm.ts b/packages/libsql-client-wasm/src/wasm.ts index 7066e355..694d94af 100644 --- a/packages/libsql-client-wasm/src/wasm.ts +++ b/packages/libsql-client-wasm/src/wasm.ts @@ -1,31 +1,31 @@ import sqlite3InitModule from "@libsql/libsql-wasm-experimental"; import type { - Database, - InitOptions, - SqlValue, - Sqlite3Static, + Database, + InitOptions, + SqlValue, + Sqlite3Static, } from "@libsql/libsql-wasm-experimental"; import type { - Config, - IntMode, - Client, - Transaction, - TransactionMode, - ResultSet, - Row, - Value, - InValue, - InStatement, + Config, + IntMode, + Client, + Transaction, + TransactionMode, + ResultSet, + Row, + Value, + InValue, + InStatement, } from "@libsql/core/api"; import { LibsqlError } from "@libsql/core/api"; import type { ExpandedConfig } from "@libsql/core/config"; import { expandConfig } from "@libsql/core/config"; import { - supportedUrlLink, - transactionModeToBegin, - ResultSetImpl, + supportedUrlLink, + transactionModeToBegin, + ResultSetImpl, } from "@libsql/core/util"; export * from "@libsql/core/api"; @@ -33,402 +33,405 @@ export * from "@libsql/core/api"; const sqlite3 = await sqlite3InitModule(); export function createClient(config: Config): Client { - return _createClient(expandConfig(config, true)); + return _createClient(expandConfig(config, true)); } /** @private */ export function _createClient(config: ExpandedConfig): Client { - if (config.scheme !== "file") { - throw new LibsqlError( - `URL scheme ${JSON.stringify(config.scheme + ":")} is not supported by the local sqlite3 client. ` + - `For more information, please read ${supportedUrlLink}`, - "URL_SCHEME_NOT_SUPPORTED", - ); - } - - if (config.encryptionKey !== undefined) { - throw new LibsqlError( - "Encryption key is not supported by the Wasm client.", - "ENCRYPTION_KEY_NOT_SUPPORTED", - ); - } - - const authority = config.authority; - if (authority !== undefined) { - const host = authority.host.toLowerCase(); - if (host !== "" && host !== "localhost") { - throw new LibsqlError( - `Invalid host in file URL: ${JSON.stringify(authority.host)}. ` + - 'A "file:" URL with an absolute path should start with one slash ("file:/absolute/path.db") ' + - 'or with three slashes ("file:///absolute/path.db"). ' + - `For more information, please read ${supportedUrlLink}`, - "URL_INVALID", - ); + if (config.scheme !== "file") { + throw new LibsqlError( + `URL scheme ${JSON.stringify(config.scheme + ":")} is not supported by the local sqlite3 client. ` + + `For more information, please read ${supportedUrlLink}`, + "URL_SCHEME_NOT_SUPPORTED", + ); } - if (authority.port !== undefined) { - throw new LibsqlError("File URL cannot have a port", "URL_INVALID"); + if (config.encryptionKey !== undefined) { + throw new LibsqlError( + "Encryption key is not supported by the Wasm client.", + "ENCRYPTION_KEY_NOT_SUPPORTED", + ); } - if (authority.userinfo !== undefined) { - throw new LibsqlError( - "File URL cannot have username and password", - "URL_INVALID", - ); + + const authority = config.authority; + if (authority !== undefined) { + const host = authority.host.toLowerCase(); + if (host !== "" && host !== "localhost") { + throw new LibsqlError( + `Invalid host in file URL: ${JSON.stringify(authority.host)}. ` + + 'A "file:" URL with an absolute path should start with one slash ("file:/absolute/path.db") ' + + 'or with three slashes ("file:///absolute/path.db"). ' + + `For more information, please read ${supportedUrlLink}`, + "URL_INVALID", + ); + } + + if (authority.port !== undefined) { + throw new LibsqlError("File URL cannot have a port", "URL_INVALID"); + } + if (authority.userinfo !== undefined) { + throw new LibsqlError( + "File URL cannot have username and password", + "URL_INVALID", + ); + } } - } - const path = config.path; - const options = { - authToken: config.authToken, - syncUrl: config.syncUrl, - }; + const path = config.path; + const options = { + authToken: config.authToken, + syncUrl: config.syncUrl, + }; - const db: Database = new sqlite3.oo1.DB(path, "c"); + const db: Database = new sqlite3.oo1.DB(path, "c"); - executeStmt( - db, - "SELECT 1 AS checkThatTheDatabaseCanBeOpened", - config.intMode, - ); + executeStmt( + db, + "SELECT 1 AS checkThatTheDatabaseCanBeOpened", + config.intMode, + ); - return new Sqlite3Client(sqlite3, path, /*options,*/ db, config.intMode); + return new Sqlite3Client(sqlite3, path, /*options,*/ db, config.intMode); } function inTransaction(db: Database): boolean { - return db.getAutocommit() == 0; + return db.getAutocommit() == 0; } export class Sqlite3Client implements Client { - #sqlite3: Sqlite3Static; - #path: string; - #db: Database | null; - #intMode: IntMode; - closed: boolean; - protocol: "file"; - - /** @private */ - constructor( - sqlite3: Sqlite3Static, - path: string, - /*options: Database.Options,*/ db: Database, - intMode: IntMode, - ) { - this.#sqlite3 = sqlite3; - this.#path = path; - //this.#options = options; - this.#db = db; - this.#intMode = intMode; - this.closed = false; - this.protocol = "file"; - } - - async execute(stmt: InStatement): Promise { - this.#checkNotClosed(); - return executeStmt(this.#getDb(), stmt, this.#intMode); - } - - async batch( - stmts: Array, - mode: TransactionMode = "deferred", - ): Promise> { - this.#checkNotClosed(); - const db = this.#getDb(); - try { - executeStmt(db, transactionModeToBegin(mode), this.#intMode); - const resultSets = stmts.map((stmt) => { - if (!inTransaction(db)) { - throw new LibsqlError( - "The transaction has been rolled back", - "TRANSACTION_CLOSED", - ); + #sqlite3: Sqlite3Static; + #path: string; + #db: Database | null; + #intMode: IntMode; + closed: boolean; + protocol: "file"; + + /** @private */ + constructor( + sqlite3: Sqlite3Static, + path: string, + /*options: Database.Options,*/ db: Database, + intMode: IntMode, + ) { + this.#sqlite3 = sqlite3; + this.#path = path; + //this.#options = options; + this.#db = db; + this.#intMode = intMode; + this.closed = false; + this.protocol = "file"; + } + + async execute(stmt: InStatement): Promise { + this.#checkNotClosed(); + return executeStmt(this.#getDb(), stmt, this.#intMode); + } + + async batch( + stmts: Array, + mode: TransactionMode = "deferred", + ): Promise> { + this.#checkNotClosed(); + const db = this.#getDb(); + try { + executeStmt(db, transactionModeToBegin(mode), this.#intMode); + const resultSets = stmts.map((stmt) => { + if (!inTransaction(db)) { + throw new LibsqlError( + "The transaction has been rolled back", + "TRANSACTION_CLOSED", + ); + } + return executeStmt(db, stmt, this.#intMode); + }); + executeStmt(db, "COMMIT", this.#intMode); + return resultSets; + } finally { + if (inTransaction(db)) { + executeStmt(db, "ROLLBACK", this.#intMode); + } } - return executeStmt(db, stmt, this.#intMode); - }); - executeStmt(db, "COMMIT", this.#intMode); - return resultSets; - } finally { - if (inTransaction(db)) { - executeStmt(db, "ROLLBACK", this.#intMode); - } } - } - - async transaction(mode: TransactionMode = "write"): Promise { - const db = this.#getDb(); - executeStmt(db, transactionModeToBegin(mode), this.#intMode); - this.#db = null; // A new connection will be lazily created on next use - return new Sqlite3Transaction(db, this.#intMode); - } - - async executeMultiple(sql: string): Promise { - this.#checkNotClosed(); - const db = this.#getDb(); - try { - return executeMultiple(db, sql); - } finally { - if (inTransaction(db)) { - executeStmt(db, "ROLLBACK", this.#intMode); - } + + async transaction(mode: TransactionMode = "write"): Promise { + const db = this.#getDb(); + executeStmt(db, transactionModeToBegin(mode), this.#intMode); + this.#db = null; // A new connection will be lazily created on next use + return new Sqlite3Transaction(db, this.#intMode); } - } - async sync(): Promise { - throw new LibsqlError( - "sync not supported in wasm mode", - "SYNC_NOT_SUPPORTED", - ); - } + async executeMultiple(sql: string): Promise { + this.#checkNotClosed(); + const db = this.#getDb(); + try { + return executeMultiple(db, sql); + } finally { + if (inTransaction(db)) { + executeStmt(db, "ROLLBACK", this.#intMode); + } + } + } - close(): void { - this.closed = true; - if (this.#db !== null) { - this.#db.close(); + async sync(): Promise { + throw new LibsqlError( + "sync not supported in wasm mode", + "SYNC_NOT_SUPPORTED", + ); } - } - #checkNotClosed(): void { - if (this.closed) { - throw new LibsqlError("The client is closed", "CLIENT_CLOSED"); + close(): void { + this.closed = true; + if (this.#db !== null) { + this.#db.close(); + } } - } - // Lazily creates the database connection and returns it - #getDb(): Database { - if (this.#db === null) { - this.#db = new this.#sqlite3.oo1.DB("/mydb.sqlite3", "ct"); + #checkNotClosed(): void { + if (this.closed) { + throw new LibsqlError("The client is closed", "CLIENT_CLOSED"); + } + } + + // Lazily creates the database connection and returns it + #getDb(): Database { + if (this.#db === null) { + this.#db = new this.#sqlite3.oo1.DB("/mydb.sqlite3", "ct"); + } + return this.#db; } - return this.#db; - } } export class Sqlite3Transaction implements Transaction { - #database: Database; - #intMode: IntMode; - - /** @private */ - constructor(database: Database, intMode: IntMode) { - this.#database = database; - this.#intMode = intMode; - } - - async execute(stmt: InStatement): Promise { - this.#checkNotClosed(); - return executeStmt(this.#database, stmt, this.#intMode); - } - - async batch(stmts: Array): Promise> { - return stmts.map((stmt) => { - this.#checkNotClosed(); - return executeStmt(this.#database, stmt, this.#intMode); - }); - } - - async executeMultiple(sql: string): Promise { - this.#checkNotClosed(); - return executeMultiple(this.#database, sql); - } - - async rollback(): Promise { - if (!this.#database.isOpen()) { - return; + #database: Database; + #intMode: IntMode; + + /** @private */ + constructor(database: Database, intMode: IntMode) { + this.#database = database; + this.#intMode = intMode; } - this.#checkNotClosed(); - executeStmt(this.#database, "ROLLBACK", this.#intMode); - } - - async commit(): Promise { - this.#checkNotClosed(); - executeStmt(this.#database, "COMMIT", this.#intMode); - } - - close(): void { - if (inTransaction(this.#database)) { - executeStmt(this.#database, "ROLLBACK", this.#intMode); + + async execute(stmt: InStatement): Promise { + this.#checkNotClosed(); + return executeStmt(this.#database, stmt, this.#intMode); } - } - get closed(): boolean { - return !inTransaction(this.#database); - } + async batch(stmts: Array): Promise> { + return stmts.map((stmt) => { + this.#checkNotClosed(); + return executeStmt(this.#database, stmt, this.#intMode); + }); + } - #checkNotClosed(): void { - if (this.closed) { - throw new LibsqlError("The transaction is closed", "TRANSACTION_CLOSED"); + async executeMultiple(sql: string): Promise { + this.#checkNotClosed(); + return executeMultiple(this.#database, sql); } - } -} -function executeStmt( - db: Database, - stmt: InStatement, - intMode: IntMode, -): ResultSet { - let sql: string; - let args: Array | Record; - if (typeof stmt === "string") { - sql = stmt; - args = []; - } else { - sql = stmt.sql; - if (Array.isArray(stmt.args)) { - args = stmt.args.map((value) => valueToSql(value, intMode)); - } else { - args = {}; - for (const name in stmt.args) { - const argName = - name[0] === "@" || name[0] === "$" || name[0] === ":" - ? name.substring(1) - : name; - args[argName] = valueToSql(stmt.args[name], intMode); - } + async rollback(): Promise { + if (!this.#database.isOpen()) { + return; + } + this.#checkNotClosed(); + executeStmt(this.#database, "ROLLBACK", this.#intMode); } - } - try { - const sqlStmt = db.prepare(sql); + async commit(): Promise { + this.#checkNotClosed(); + executeStmt(this.#database, "COMMIT", this.#intMode); + } - // TODO: sqlStmt.safeIntegers(true); + close(): void { + if (inTransaction(this.#database)) { + executeStmt(this.#database, "ROLLBACK", this.#intMode); + } + } - let returnsData = sqlStmt.columnCount > 0; + get closed(): boolean { + return !inTransaction(this.#database); + } - if (Array.isArray(args)) { - for (let i = 0; i < args.length; ++i) { - const value = args[i]; - sqlStmt.bind(i + 1, value); - } + #checkNotClosed(): void { + if (this.closed) { + throw new LibsqlError( + "The transaction is closed", + "TRANSACTION_CLOSED", + ); + } + } +} + +function executeStmt( + db: Database, + stmt: InStatement, + intMode: IntMode, +): ResultSet { + let sql: string; + let args: Array | Record; + if (typeof stmt === "string") { + sql = stmt; + args = []; } else { - for (const argName in args) { - const idx = sqlStmt.getParamIndex(argName)!; - const value = args[argName]; - sqlStmt.bind(idx, value); - } + sql = stmt.sql; + if (Array.isArray(stmt.args)) { + args = stmt.args.map((value) => valueToSql(value, intMode)); + } else { + args = {}; + for (const name in stmt.args) { + const argName = + name[0] === "@" || name[0] === "$" || name[0] === ":" + ? name.substring(1) + : name; + args[argName] = valueToSql(stmt.args[name], intMode); + } + } } - if (returnsData) { - let columns: string[] = sqlStmt.getColumnNames(); - let columnTypes: string[] = []; - let rows: Row[] = []; - for (;;) { - if (!sqlStmt.step()) { - break; + + try { + const sqlStmt = db.prepare(sql); + + // TODO: sqlStmt.safeIntegers(true); + + let returnsData = sqlStmt.columnCount > 0; + + if (Array.isArray(args)) { + for (let i = 0; i < args.length; ++i) { + const value = args[i]; + sqlStmt.bind(i + 1, value); + } + } else { + for (const argName in args) { + const idx = sqlStmt.getParamIndex(argName)!; + const value = args[argName]; + sqlStmt.bind(idx, value); + } } - const values: unknown[] = sqlStmt.get([]); - rows.push(rowFromSql(values, columns, intMode)); - } - const rowsAffected = 0; - const lastInsertRowid = undefined; - return new ResultSetImpl( - columns, - columnTypes, - rows, - rowsAffected, - lastInsertRowid, - ); - } else { - sqlStmt.step(); // TODO: check return value - const rowsAffected = db.changes(); - const lastInsertRowid = BigInt(db.lastInsertRowid()); - return new ResultSetImpl([], [], [], rowsAffected, lastInsertRowid); + if (returnsData) { + let columns: string[] = sqlStmt.getColumnNames(); + let columnTypes: string[] = []; + let rows: Row[] = []; + for (;;) { + if (!sqlStmt.step()) { + break; + } + const values: unknown[] = sqlStmt.get([]); + rows.push(rowFromSql(values, columns, intMode)); + } + const rowsAffected = 0; + const lastInsertRowid = undefined; + return new ResultSetImpl( + columns, + columnTypes, + rows, + rowsAffected, + lastInsertRowid, + ); + } else { + sqlStmt.step(); // TODO: check return value + const rowsAffected = db.changes(); + const lastInsertRowid = BigInt(db.lastInsertRowid()); + return new ResultSetImpl([], [], [], rowsAffected, lastInsertRowid); + } + } catch (e) { + throw mapSqliteError(e); } - } catch (e) { - throw mapSqliteError(e); - } } function rowFromSql( - sqlRow: Array, - columns: Array, - intMode: IntMode, + sqlRow: Array, + columns: Array, + intMode: IntMode, ): Row { - const row = {}; - // make sure that the "length" property is not enumerable - Object.defineProperty(row, "length", { value: sqlRow.length }); - for (let i = 0; i < sqlRow.length; ++i) { - const value = valueFromSql(sqlRow[i], intMode); - Object.defineProperty(row, i, { value }); - - const column = columns[i]; - if (!Object.hasOwn(row, column)) { - Object.defineProperty(row, column, { - value, - enumerable: true, - configurable: true, - writable: true, - }); + const row = {}; + // make sure that the "length" property is not enumerable + Object.defineProperty(row, "length", { value: sqlRow.length }); + for (let i = 0; i < sqlRow.length; ++i) { + const value = valueFromSql(sqlRow[i], intMode); + Object.defineProperty(row, i, { value }); + + const column = columns[i]; + if (!Object.hasOwn(row, column)) { + Object.defineProperty(row, column, { + value, + enumerable: true, + configurable: true, + writable: true, + }); + } } - } - return row as Row; + return row as Row; } function valueFromSql(sqlValue: unknown, intMode: IntMode): Value { - if (typeof sqlValue === "bigint") { - if (intMode === "number") { - if (sqlValue < minSafeBigint || sqlValue > maxSafeBigint) { - throw new RangeError( - "Received integer which cannot be safely represented as a JavaScript number", - ); - } - return Number(sqlValue); - } else if (intMode === "bigint") { - return sqlValue; - } else if (intMode === "string") { - return "" + sqlValue; - } else { - throw new Error("Invalid value for IntMode"); + if (typeof sqlValue === "bigint") { + if (intMode === "number") { + if (sqlValue < minSafeBigint || sqlValue > maxSafeBigint) { + throw new RangeError( + "Received integer which cannot be safely represented as a JavaScript number", + ); + } + return Number(sqlValue); + } else if (intMode === "bigint") { + return sqlValue; + } else if (intMode === "string") { + return "" + sqlValue; + } else { + throw new Error("Invalid value for IntMode"); + } } - } - return sqlValue as Value; + return sqlValue as Value; } const minSafeBigint = -9007199254740991n; const maxSafeBigint = 9007199254740991n; function valueToSql(value: InValue, intMode: IntMode): SqlValue { - if (typeof value === "number") { - if (!Number.isFinite(value)) { - throw new RangeError( - "Only finite numbers (not Infinity or NaN) can be passed as arguments", - ); - } - return value; - } else if (typeof value === "bigint") { - if (value < minInteger || value > maxInteger) { - throw new RangeError( - "bigint is too large to be represented as a 64-bit integer and passed as argument", - ); - } - return value; - } else if (typeof value === "boolean") { - switch (intMode) { - case "bigint": - return value ? 1n : 0n; - case "string": - return value ? "1" : "0"; - default: - return value ? 1 : 0; + if (typeof value === "number") { + if (!Number.isFinite(value)) { + throw new RangeError( + "Only finite numbers (not Infinity or NaN) can be passed as arguments", + ); + } + return value; + } else if (typeof value === "bigint") { + if (value < minInteger || value > maxInteger) { + throw new RangeError( + "bigint is too large to be represented as a 64-bit integer and passed as argument", + ); + } + return value; + } else if (typeof value === "boolean") { + switch (intMode) { + case "bigint": + return value ? 1n : 0n; + case "string": + return value ? "1" : "0"; + default: + return value ? 1 : 0; + } + } else if (value instanceof Date) { + return value.valueOf(); + } else if (value === undefined) { + throw new TypeError( + "undefined cannot be passed as argument to the database", + ); + } else { + return value; } - } else if (value instanceof Date) { - return value.valueOf(); - } else if (value === undefined) { - throw new TypeError( - "undefined cannot be passed as argument to the database", - ); - } else { - return value; - } } const minInteger = -9223372036854775808n; const maxInteger = 9223372036854775807n; function executeMultiple(db: Database, sql: string): void { - try { - db.exec(sql); - } catch (e) { - throw mapSqliteError(e); - } + try { + db.exec(sql); + } catch (e) { + throw mapSqliteError(e); + } } function mapSqliteError(e: unknown): unknown { - // TODO: Map to LibsqlError - return e; + // TODO: Map to LibsqlError + return e; } diff --git a/packages/libsql-client-wasm/tsconfig.base.json b/packages/libsql-client-wasm/tsconfig.base.json index 9793c026..27fac27a 100644 --- a/packages/libsql-client-wasm/tsconfig.base.json +++ b/packages/libsql-client-wasm/tsconfig.base.json @@ -1,14 +1,14 @@ { - "compilerOptions": { - "module": "es2022", - "moduleResolution": "node", - "lib": ["esnext", "dom"], - "target": "es2022", - "esModuleInterop": true, - "isolatedModules": true, - "rootDir": "src/", - "strict": true - }, - "include": ["src/"], - "exclude": ["**/__tests__"] + "compilerOptions": { + "module": "es2022", + "moduleResolution": "node", + "lib": ["esnext", "dom"], + "target": "es2022", + "esModuleInterop": true, + "isolatedModules": true, + "rootDir": "src/", + "strict": true + }, + "include": ["src/"], + "exclude": ["**/__tests__"] } diff --git a/packages/libsql-client-wasm/tsconfig.build-esm.json b/packages/libsql-client-wasm/tsconfig.build-esm.json index a015a304..2ba1706d 100644 --- a/packages/libsql-client-wasm/tsconfig.build-esm.json +++ b/packages/libsql-client-wasm/tsconfig.build-esm.json @@ -1,8 +1,8 @@ { - "extends": "./tsconfig.base.json", - "compilerOptions": { - "module": "esnext", - "declaration": true, - "outDir": "./lib-esm/" - } + "extends": "./tsconfig.base.json", + "compilerOptions": { + "module": "esnext", + "declaration": true, + "outDir": "./lib-esm/" + } } diff --git a/packages/libsql-client-wasm/tsconfig.json b/packages/libsql-client-wasm/tsconfig.json index 23f862fb..bc064274 100644 --- a/packages/libsql-client-wasm/tsconfig.json +++ b/packages/libsql-client-wasm/tsconfig.json @@ -1,7 +1,7 @@ { - "extends": "./tsconfig.base.json", - "compilerOptions": { - "noEmit": true, - "incremental": true - } + "extends": "./tsconfig.base.json", + "compilerOptions": { + "noEmit": true, + "incremental": true + } } diff --git a/packages/libsql-client-wasm/typedoc.json b/packages/libsql-client-wasm/typedoc.json index de52c9c4..4c5154ac 100644 --- a/packages/libsql-client-wasm/typedoc.json +++ b/packages/libsql-client-wasm/typedoc.json @@ -1,11 +1,11 @@ { - "entryPoints": ["src/node.ts"], - "out": "docs", - "excludePrivate": true, - "excludeInternal": true, - "visibilityFilters": { - "inherited": true, - "external": true - }, - "includeVersion": true + "entryPoints": ["src/node.ts"], + "out": "docs", + "excludePrivate": true, + "excludeInternal": true, + "visibilityFilters": { + "inherited": true, + "external": true + }, + "includeVersion": true } diff --git a/packages/libsql-client/.prettierrc b/packages/libsql-client/.prettierrc deleted file mode 100644 index 0967ef42..00000000 --- a/packages/libsql-client/.prettierrc +++ /dev/null @@ -1 +0,0 @@ -{} diff --git a/packages/libsql-client/README.md b/packages/libsql-client/README.md index eaf21450..0510fa47 100644 --- a/packages/libsql-client/README.md +++ b/packages/libsql-client/README.md @@ -43,9 +43,9 @@ Learn more about what you can do with Turso: -- [Embedded Replicas](https://docs.turso.tech/features/embedded-replicas) -- [Platform API](https://docs.turso.tech/features/platform-api) -- [Data Edge](https://docs.turso.tech/features/data-edge) -- [Branching](https://docs.turso.tech/features/branching) -- [Point-in-Time Recovery](https://docs.turso.tech/features/point-in-time-recovery) -- [Scale to Zero](https://docs.turso.tech/features/scale-to-zero) +- [Embedded Replicas](https://docs.turso.tech/features/embedded-replicas) +- [Platform API](https://docs.turso.tech/features/platform-api) +- [Data Edge](https://docs.turso.tech/features/data-edge) +- [Branching](https://docs.turso.tech/features/branching) +- [Point-in-Time Recovery](https://docs.turso.tech/features/point-in-time-recovery) +- [Scale to Zero](https://docs.turso.tech/features/scale-to-zero) diff --git a/packages/libsql-client/examples/example.js b/packages/libsql-client/examples/example.js index b446aeb6..ffb083e3 100644 --- a/packages/libsql-client/examples/example.js +++ b/packages/libsql-client/examples/example.js @@ -1,21 +1,21 @@ import { createClient } from "@libsql/client"; async function example() { - const config = { - url: process.env.URL ?? "file:local.db", - encryptionKey: process.env.ENCRYPTION_KEY, - }; - const db = createClient(config); - await db.batch( - [ - "CREATE TABLE IF NOT EXISTS users (email TEXT)", - "INSERT INTO users (email) VALUES ('alice@example.com')", - "INSERT INTO users (email) VALUES ('bob@example.com')", - ], - "write", - ); - const rs = await db.execute("SELECT * FROM users"); - console.log(rs); + const config = { + url: process.env.URL ?? "file:local.db", + encryptionKey: process.env.ENCRYPTION_KEY, + }; + const db = createClient(config); + await db.batch( + [ + "CREATE TABLE IF NOT EXISTS users (email TEXT)", + "INSERT INTO users (email) VALUES ('alice@example.com')", + "INSERT INTO users (email) VALUES ('bob@example.com')", + ], + "write", + ); + const rs = await db.execute("SELECT * FROM users"); + console.log(rs); } await example(); diff --git a/packages/libsql-client/examples/package.json b/packages/libsql-client/examples/package.json index 5d58af5f..64f619ee 100644 --- a/packages/libsql-client/examples/package.json +++ b/packages/libsql-client/examples/package.json @@ -1,10 +1,10 @@ { - "name": "libsql-examples", - "type": "module", - "private": true, - "dependencies": { - "@libsql/client": "..", - "@libsql/core": "../../libsql-core", - "readline-sync": "^1.4.10" - } + "name": "libsql-examples", + "type": "module", + "private": true, + "dependencies": { + "@libsql/client": "..", + "@libsql/core": "../../libsql-core", + "readline-sync": "^1.4.10" + } } diff --git a/packages/libsql-client/examples/shell.js b/packages/libsql-client/examples/shell.js index 237f5730..4d5384bc 100644 --- a/packages/libsql-client/examples/shell.js +++ b/packages/libsql-client/examples/shell.js @@ -3,34 +3,34 @@ import { stdin, stdout, argv } from "node:process"; import * as libsql from "@libsql/client"; async function main() { - const url = argv[2]; - if (!url) { - console.error("Please specify database URL as command-line argument"); - return; - } + const url = argv[2]; + if (!url) { + console.error("Please specify database URL as command-line argument"); + return; + } - const client = libsql.createClient({ url }); - const rl = readline.createInterface({ input: stdin, output: stdout }); + const client = libsql.createClient({ url }); + const rl = readline.createInterface({ input: stdin, output: stdout }); - for (;;) { - const sql = await rl.question("> "); + for (;;) { + const sql = await rl.question("> "); - let rs; - try { - rs = await client.execute(sql); - } catch (e) { - if (e instanceof libsql.LibsqlError) { - console.error(e); - continue; - } - throw e; - } + let rs; + try { + rs = await client.execute(sql); + } catch (e) { + if (e instanceof libsql.LibsqlError) { + console.error(e); + continue; + } + throw e; + } - console.log(JSON.stringify(rs.columns)); - for (const row of rs.rows) { - console.log(JSON.stringify(Array.from(row))); + console.log(JSON.stringify(rs.columns)); + for (const row of rs.rows) { + console.log(JSON.stringify(Array.from(row))); + } } - } } await main(); diff --git a/packages/libsql-client/examples/sync.js b/packages/libsql-client/examples/sync.js index 0852b9cb..15aaa4a6 100644 --- a/packages/libsql-client/examples/sync.js +++ b/packages/libsql-client/examples/sync.js @@ -2,31 +2,31 @@ import { createClient } from "@libsql/client"; import reader from "readline-sync"; async function example() { - const config = { - url: process.env.URL ?? "file:local.db", - syncUrl: process.env.SYNC_URL, - authToken: process.env.AUTH_TOKEN, - }; - const db = createClient(config); - await db.sync(); - await db.execute( - "CREATE TABLE IF NOT EXISTS guest_book_entries (comment TEXT)", - ); - await db.sync(); + const config = { + url: process.env.URL ?? "file:local.db", + syncUrl: process.env.SYNC_URL, + authToken: process.env.AUTH_TOKEN, + }; + const db = createClient(config); + await db.sync(); + await db.execute( + "CREATE TABLE IF NOT EXISTS guest_book_entries (comment TEXT)", + ); + await db.sync(); - const comment = reader.question("Enter your comment: "); + const comment = reader.question("Enter your comment: "); - await db.execute({ - sql: "INSERT INTO guest_book_entries (comment) VALUES (?)", - args: [comment], - }); - await db.sync(); + await db.execute({ + sql: "INSERT INTO guest_book_entries (comment) VALUES (?)", + args: [comment], + }); + await db.sync(); - console.log("Guest book entries:"); - const rs = await db.execute("SELECT * FROM guest_book_entries"); - for (const row of rs.rows) { - console.log(" - " + row.comment); - } + console.log("Guest book entries:"); + const rs = await db.execute("SELECT * FROM guest_book_entries"); + for (const row of rs.rows) { + console.log(" - " + row.comment); + } } example(); diff --git a/packages/libsql-client/jest.config.js b/packages/libsql-client/jest.config.js index c558e7d7..044a5b86 100644 --- a/packages/libsql-client/jest.config.js +++ b/packages/libsql-client/jest.config.js @@ -1,7 +1,7 @@ export default { - preset: "ts-jest/presets/default-esm", - moduleNameMapper: { - "^(\\.{1,2}/.*)\\.js$": "$1", - }, - testMatch: ["**/__tests__/*.test.[jt]s"], + preset: "ts-jest/presets/default-esm", + moduleNameMapper: { + "^(\\.{1,2}/.*)\\.js$": "$1", + }, + testMatch: ["**/__tests__/*.test.[jt]s"], }; diff --git a/packages/libsql-client/package-cjs.json b/packages/libsql-client/package-cjs.json index 5bbefffb..1cd945a3 100644 --- a/packages/libsql-client/package-cjs.json +++ b/packages/libsql-client/package-cjs.json @@ -1,3 +1,3 @@ { - "type": "commonjs" + "type": "commonjs" } diff --git a/packages/libsql-client/package.json b/packages/libsql-client/package.json index 55a12234..259a9a3f 100644 --- a/packages/libsql-client/package.json +++ b/packages/libsql-client/package.json @@ -1,121 +1,121 @@ { - "name": "@libsql/client", - "version": "0.6.0", - "keywords": [ - "libsql", - "database", - "sqlite", - "serverless", - "vercel", - "netlify", - "lambda" - ], - "description": "libSQL driver for TypeScript and JavaScript", - "repository": { - "type": "git", - "url": "https://github.com/libsql/libsql-client-ts" - }, - "authors": [ - "Jan Špaček ", - "Pekka Enberg ", - "Jan Plhak " - ], - "license": "MIT", - "type": "module", - "main": "lib-cjs/node.js", - "types": "lib-esm/node.d.ts", - "exports": { - ".": { - "types": "./lib-esm/node.d.ts", - "import": { - "workerd": "./lib-esm/web.js", - "deno": "./lib-esm/web.js", - "edge-light": "./lib-esm/web.js", - "netlify": "./lib-esm/web.js", - "node": "./lib-esm/node.js", - "browser": "./lib-esm/web.js", - "default": "./lib-esm/node.js" - }, - "require": "./lib-cjs/node.js" + "name": "@libsql/client", + "version": "0.6.0", + "keywords": [ + "libsql", + "database", + "sqlite", + "serverless", + "vercel", + "netlify", + "lambda" + ], + "description": "libSQL driver for TypeScript and JavaScript", + "repository": { + "type": "git", + "url": "https://github.com/libsql/libsql-client-ts" }, - "./node": { - "types": "./lib-esm/node.d.ts", - "import": "./lib-esm/node.js", - "require": "./lib-cjs/node.js" + "authors": [ + "Jan Špaček ", + "Pekka Enberg ", + "Jan Plhak " + ], + "license": "MIT", + "type": "module", + "main": "lib-cjs/node.js", + "types": "lib-esm/node.d.ts", + "exports": { + ".": { + "types": "./lib-esm/node.d.ts", + "import": { + "workerd": "./lib-esm/web.js", + "deno": "./lib-esm/web.js", + "edge-light": "./lib-esm/web.js", + "netlify": "./lib-esm/web.js", + "node": "./lib-esm/node.js", + "browser": "./lib-esm/web.js", + "default": "./lib-esm/node.js" + }, + "require": "./lib-cjs/node.js" + }, + "./node": { + "types": "./lib-esm/node.d.ts", + "import": "./lib-esm/node.js", + "require": "./lib-cjs/node.js" + }, + "./http": { + "types": "./lib-esm/http.d.ts", + "import": "./lib-esm/http.js", + "require": "./lib-cjs/http.js" + }, + "./ws": { + "types": "./lib-esm/ws.d.ts", + "import": "./lib-esm/ws.js", + "require": "./lib-cjs/ws.js" + }, + "./sqlite3": { + "types": "./lib-esm/sqlite3.d.ts", + "import": "./lib-esm/sqlite3.js", + "require": "./lib-cjs/sqlite3.js" + }, + "./web": { + "types": "./lib-esm/web.d.ts", + "import": "./lib-esm/web.js", + "require": "./lib-cjs/web.js" + } }, - "./http": { - "types": "./lib-esm/http.d.ts", - "import": "./lib-esm/http.js", - "require": "./lib-cjs/http.js" + "typesVersions": { + "*": { + ".": [ + "./lib-esm/node.d.ts" + ], + "http": [ + "./lib-esm/http.d.ts" + ], + "hrana": [ + "./lib-esm/hrana.d.ts" + ], + "sqlite3": [ + "./lib-esm/sqlite3.d.ts" + ], + "web": [ + "./lib-esm/web.d.ts" + ] + } }, - "./ws": { - "types": "./lib-esm/ws.d.ts", - "import": "./lib-esm/ws.js", - "require": "./lib-cjs/ws.js" + "files": [ + "lib-cjs/**", + "lib-esm/**", + "README.md" + ], + "scripts": { + "prepublishOnly": "npm run build", + "prebuild": "rm -rf ./lib-cjs ./lib-esm", + "build": "npm run build:cjs && npm run build:esm", + "build:cjs": "tsc -p tsconfig.build-cjs.json", + "build:esm": "tsc -p tsconfig.build-esm.json", + "postbuild": "cp package-cjs.json ./lib-cjs/package.json", + "test": "jest --runInBand", + "typecheck": "tsc --noEmit", + "typedoc": "rm -rf ./docs && typedoc", + "prepare": "husky install", + "lint-staged": "lint-staged" }, - "./sqlite3": { - "types": "./lib-esm/sqlite3.d.ts", - "import": "./lib-esm/sqlite3.js", - "require": "./lib-cjs/sqlite3.js" + "dependencies": { + "@libsql/core": "^0.6.0", + "@libsql/hrana-client": "^0.6.0", + "js-base64": "^3.7.5", + "libsql": "^0.3.10" }, - "./web": { - "types": "./lib-esm/web.d.ts", - "import": "./lib-esm/web.js", - "require": "./lib-cjs/web.js" + "devDependencies": { + "@types/jest": "^29.2.5", + "@types/node": "^18.15.5", + "husky": "^9.0.11", + "jest": "^29.3.1", + "lint-staged": "^15.2.2", + "prettier": "3.2.5", + "ts-jest": "^29.0.5", + "typedoc": "^0.23.28", + "typescript": "^4.9.4" } - }, - "typesVersions": { - "*": { - ".": [ - "./lib-esm/node.d.ts" - ], - "http": [ - "./lib-esm/http.d.ts" - ], - "hrana": [ - "./lib-esm/hrana.d.ts" - ], - "sqlite3": [ - "./lib-esm/sqlite3.d.ts" - ], - "web": [ - "./lib-esm/web.d.ts" - ] - } - }, - "files": [ - "lib-cjs/**", - "lib-esm/**", - "README.md" - ], - "scripts": { - "prepublishOnly": "npm run build", - "prebuild": "rm -rf ./lib-cjs ./lib-esm", - "build": "npm run build:cjs && npm run build:esm", - "build:cjs": "tsc -p tsconfig.build-cjs.json", - "build:esm": "tsc -p tsconfig.build-esm.json", - "postbuild": "cp package-cjs.json ./lib-cjs/package.json", - "test": "jest --runInBand", - "typecheck": "tsc --noEmit", - "typedoc": "rm -rf ./docs && typedoc", - "prepare": "husky install", - "lint-staged": "lint-staged" - }, - "dependencies": { - "@libsql/core": "^0.6.0", - "@libsql/hrana-client": "^0.6.0", - "js-base64": "^3.7.5", - "libsql": "^0.3.10" - }, - "devDependencies": { - "@types/jest": "^29.2.5", - "@types/node": "^18.15.5", - "husky": "^9.0.11", - "jest": "^29.3.1", - "lint-staged": "^15.2.2", - "prettier": "3.2.5", - "ts-jest": "^29.0.5", - "typedoc": "^0.23.28", - "typescript": "^4.9.4" - } } diff --git a/packages/libsql-client/smoke_test/vercel/app/api/function.ts b/packages/libsql-client/smoke_test/vercel/app/api/function.ts index 9cc0ef1c..2ed60507 100644 --- a/packages/libsql-client/smoke_test/vercel/app/api/function.ts +++ b/packages/libsql-client/smoke_test/vercel/app/api/function.ts @@ -1,111 +1,111 @@ import * as libsql from "@libsql/client"; export const config = { - runtime: "edge", + runtime: "edge", }; export default async function (request: Request) { - function respond(status: number, responseBody: string) { - return new Response(responseBody, { - status, - headers: [["content-type", "text/plain"]], - }); - } - - if (request.method !== "GET") { - return respond(405, "Only GET method is supported"); - } - - const url = new URL(request.url); - const testCase = url.searchParams.get("test"); - if (testCase === null) { - return respond( - 400, - "Please specify the test case using the 'test' query parameter", - ); - } - - const testCaseFn = testCases[testCase]; - if (testCaseFn === undefined) { - return respond(404, "Unknown test case"); - } - - let client; - try { - client = libsql.createClient({ url: process.env.CLIENT_URL! }); - await testCaseFn(client); - return respond(200, "Test passed"); - } catch (e) { - return respond(500, `Test failed\n${(e as Error).stack}`); - } finally { - if (client !== undefined) { - client.close(); + function respond(status: number, responseBody: string) { + return new Response(responseBody, { + status, + headers: [["content-type", "text/plain"]], + }); } - } -} -const testCases: Record Promise> = { - execute: async (client: libsql.Client): Promise => { - const rs = await client.execute("SELECT 1+1 AS two"); - assert(rs.columns.length === 1); - assert(rs.columns[0] === "two"); - assert(rs.rows.length === 1); - assert(rs.rows[0].length === 1); - assert(rs.rows[0][0] === 2.0); - }, - - batch: async (client: libsql.Client): Promise => { - const rss = await client.batch([ - "DROP TABLE IF EXISTS t", - "CREATE TABLE t (a, b)", - "INSERT INTO t VALUES (1, 'one'), (2, 'two'), (3, 'three')", - "SELECT * FROM t ORDER BY a", - ]); - - assert(rss[0].columns.length === 0); - assert(rss[0].rows.length === 0); - - assert(rss[1].columns.length === 0); - assert(rss[1].rows.length === 0); - - assert(rss[2].columns.length === 0); - assert(rss[2].rows.length === 0); - - assert(rss[3].columns.length === 2); - assert(rss[3].columns[0] === "a"); - assert(rss[3].columns[1] === "b"); - assert(rss[3].rows.length === 3); - assert(rss[3].rows[0][0] === 1); - assert(rss[3].rows[0][1] === "one"); - assert(rss[3].rows[1][0] === 2); - assert(rss[3].rows[1][1] === "two"); - assert(rss[3].rows[2][0] === 3); - assert(rss[3].rows[2][1] === "three"); - }, - - transaction: async (client: libsql.Client): Promise => { - await client.batch([ - "DROP TABLE IF EXISTS t", - "CREATE TABLE t (a, b)", - "INSERT INTO t VALUES (1, 'one'), (2, 'two'), (3, 'three')", - ]); - - const txn = await client.transaction(); + if (request.method !== "GET") { + return respond(405, "Only GET method is supported"); + } + + const url = new URL(request.url); + const testCase = url.searchParams.get("test"); + if (testCase === null) { + return respond( + 400, + "Please specify the test case using the 'test' query parameter", + ); + } + + const testCaseFn = testCases[testCase]; + if (testCaseFn === undefined) { + return respond(404, "Unknown test case"); + } + + let client; try { - await txn.execute("INSERT INTO t VALUES (4, 'four')"); - await txn.execute("DELETE FROM t WHERE a <= 2"); - await txn.commit(); + client = libsql.createClient({ url: process.env.CLIENT_URL! }); + await testCaseFn(client); + return respond(200, "Test passed"); + } catch (e) { + return respond(500, `Test failed\n${(e as Error).stack}`); } finally { - txn.close(); + if (client !== undefined) { + client.close(); + } } +} - const rs = await client.execute("SELECT COUNT(*) FROM t"); - assert(rs.rows[0][0] === 2); - }, +const testCases: Record Promise> = { + execute: async (client: libsql.Client): Promise => { + const rs = await client.execute("SELECT 1+1 AS two"); + assert(rs.columns.length === 1); + assert(rs.columns[0] === "two"); + assert(rs.rows.length === 1); + assert(rs.rows[0].length === 1); + assert(rs.rows[0][0] === 2.0); + }, + + batch: async (client: libsql.Client): Promise => { + const rss = await client.batch([ + "DROP TABLE IF EXISTS t", + "CREATE TABLE t (a, b)", + "INSERT INTO t VALUES (1, 'one'), (2, 'two'), (3, 'three')", + "SELECT * FROM t ORDER BY a", + ]); + + assert(rss[0].columns.length === 0); + assert(rss[0].rows.length === 0); + + assert(rss[1].columns.length === 0); + assert(rss[1].rows.length === 0); + + assert(rss[2].columns.length === 0); + assert(rss[2].rows.length === 0); + + assert(rss[3].columns.length === 2); + assert(rss[3].columns[0] === "a"); + assert(rss[3].columns[1] === "b"); + assert(rss[3].rows.length === 3); + assert(rss[3].rows[0][0] === 1); + assert(rss[3].rows[0][1] === "one"); + assert(rss[3].rows[1][0] === 2); + assert(rss[3].rows[1][1] === "two"); + assert(rss[3].rows[2][0] === 3); + assert(rss[3].rows[2][1] === "three"); + }, + + transaction: async (client: libsql.Client): Promise => { + await client.batch([ + "DROP TABLE IF EXISTS t", + "CREATE TABLE t (a, b)", + "INSERT INTO t VALUES (1, 'one'), (2, 'two'), (3, 'three')", + ]); + + const txn = await client.transaction(); + try { + await txn.execute("INSERT INTO t VALUES (4, 'four')"); + await txn.execute("DELETE FROM t WHERE a <= 2"); + await txn.commit(); + } finally { + txn.close(); + } + + const rs = await client.execute("SELECT COUNT(*) FROM t"); + assert(rs.rows[0][0] === 2); + }, }; function assert(value: unknown, message?: string) { - if (!value) { - throw new Error(message ?? "Assertion failed"); - } + if (!value) { + throw new Error(message ?? "Assertion failed"); + } } diff --git a/packages/libsql-client/smoke_test/vercel/app/public/index.html b/packages/libsql-client/smoke_test/vercel/app/public/index.html index 53021736..bc129de7 100644 --- a/packages/libsql-client/smoke_test/vercel/app/public/index.html +++ b/packages/libsql-client/smoke_test/vercel/app/public/index.html @@ -1,5 +1,5 @@ - -

This is a smoke-test Vercel app for @libsql/client.

- + +

This is a smoke-test Vercel app for @libsql/client.

+ diff --git a/packages/libsql-client/smoke_test/vercel/package.json b/packages/libsql-client/smoke_test/vercel/package.json index 07a0a823..f21a0056 100644 --- a/packages/libsql-client/smoke_test/vercel/package.json +++ b/packages/libsql-client/smoke_test/vercel/package.json @@ -1,9 +1,9 @@ { - "name": "smoke-test", - "dependencies": { - "@types/node": "20.4.2", - "localtunnel": "^2.0.2", - "vercel": "^31.0.3", - "typescript": "^4.9.4" - } + "name": "smoke-test", + "dependencies": { + "@types/node": "20.4.2", + "localtunnel": "^2.0.2", + "vercel": "^31.0.3", + "typescript": "^4.9.4" + } } diff --git a/packages/libsql-client/smoke_test/vercel/test.js b/packages/libsql-client/smoke_test/vercel/test.js index f1d28bdc..8cd1fbbd 100644 --- a/packages/libsql-client/smoke_test/vercel/test.js +++ b/packages/libsql-client/smoke_test/vercel/test.js @@ -5,176 +5,191 @@ const fetch = require("node-fetch"); const localtunnel = require("localtunnel"); function getEnv(name) { - const value = process.env[name] ?? ""; - if (!value) { - throw new Error(`Please set the env variable ${name}`); - } - return value; + const value = process.env[name] ?? ""; + if (!value) { + throw new Error(`Please set the env variable ${name}`); + } + return value; } const vercelToken = getEnv("VERCEL_TOKEN"); const projectName = getEnv("VERCEL_PROJECT_NAME"); async function npm( - subcommand, - args, - hiddenArgs = [], - { capture = false } = {}, + subcommand, + args, + hiddenArgs = [], + { capture = false } = {}, ) { - console.info(`$ npm ${subcommand} ${args.join(" ")}`); - - const proc = spawn("npm", [subcommand, ...args, ...hiddenArgs], { - stdio: ["ignore", capture ? "pipe" : "inherit", "inherit"], - }); - - const exitPromise = new Promise((resolve, reject) => { - proc.on("exit", (code, signal) => { - if (signal !== null) { - reject(new Error(`vercel command terminated due to signal: ${signal}`)); - } else if (code !== 0) { - reject(new Error(`vercel command exited with code: ${code}`)); - } else { - resolve(); - } + console.info(`$ npm ${subcommand} ${args.join(" ")}`); + + const proc = spawn("npm", [subcommand, ...args, ...hiddenArgs], { + stdio: ["ignore", capture ? "pipe" : "inherit", "inherit"], }); - }); - const dataPromise = new Promise((resolve, reject) => { - if (!capture) { - return resolve(); - } + const exitPromise = new Promise((resolve, reject) => { + proc.on("exit", (code, signal) => { + if (signal !== null) { + reject( + new Error( + `vercel command terminated due to signal: ${signal}`, + ), + ); + } else if (code !== 0) { + reject(new Error(`vercel command exited with code: ${code}`)); + } else { + resolve(); + } + }); + }); + + const dataPromise = new Promise((resolve, reject) => { + if (!capture) { + return resolve(); + } - const stream = proc.stdout; - stream.setEncoding("utf-8"); + const stream = proc.stdout; + stream.setEncoding("utf-8"); - const chunks = []; - stream.on("data", (chunk) => chunks.push(chunk)); - stream.on("end", () => resolve(chunks.join(""))); - stream.on("error", (e) => reject(e)); - }); + const chunks = []; + stream.on("data", (chunk) => chunks.push(chunk)); + stream.on("end", () => resolve(chunks.join(""))); + stream.on("error", (e) => reject(e)); + }); - return exitPromise.then(() => dataPromise); + return exitPromise.then(() => dataPromise); } async function deployToVercel(clientUrlInsideVercel) { - console.info("Building and deploying to Vercel..."); - - let tarballName = await npm("pack", ["../.."], [], { capture: true }); - tarballName = tarballName.trim(); - - const appPackageJson = { - dependencies: { - "@libsql/client": `../${tarballName}`, - }, - }; - fs.writeFileSync("app/package.json", JSON.stringify(appPackageJson, null, 4)); - - await npm( - "exec", - [ - "--", - "vercel", - "link", - "--yes", - "--project", - projectName, - "--cwd", - "app/", - ], - ["--token", vercelToken], - ); - await npm( - "exec", - ["--", "vercel", "pull", "--yes", "--environment=preview", "--cwd", "app/"], - ["--token", vercelToken], - ); - await npm("exec", ["--", "vercel", "build", "--cwd", "app/"]); - - const deployUrl = await npm( - "exec", - [ - "--", - "vercel", - "deploy", - "--prebuilt", - "--env", - `CLIENT_URL=${clientUrlInsideVercel}`, - "--cwd", - "app/", - ], - ["--token", vercelToken, "--cwd", "app/"], - { capture: true }, - ); - - console.info(`Deployed Vercel project on ${deployUrl}`); - return deployUrl; + console.info("Building and deploying to Vercel..."); + + let tarballName = await npm("pack", ["../.."], [], { capture: true }); + tarballName = tarballName.trim(); + + const appPackageJson = { + dependencies: { + "@libsql/client": `../${tarballName}`, + }, + }; + fs.writeFileSync( + "app/package.json", + JSON.stringify(appPackageJson, null, 4), + ); + + await npm( + "exec", + [ + "--", + "vercel", + "link", + "--yes", + "--project", + projectName, + "--cwd", + "app/", + ], + ["--token", vercelToken], + ); + await npm( + "exec", + [ + "--", + "vercel", + "pull", + "--yes", + "--environment=preview", + "--cwd", + "app/", + ], + ["--token", vercelToken], + ); + await npm("exec", ["--", "vercel", "build", "--cwd", "app/"]); + + const deployUrl = await npm( + "exec", + [ + "--", + "vercel", + "deploy", + "--prebuilt", + "--env", + `CLIENT_URL=${clientUrlInsideVercel}`, + "--cwd", + "app/", + ], + ["--token", vercelToken, "--cwd", "app/"], + { capture: true }, + ); + + console.info(`Deployed Vercel project on ${deployUrl}`); + return deployUrl; } const testCases = ["execute", "batch", "transaction"]; async function runTests(functionUrl) { - let ok = true; - for (const testCase of testCases) { - if (!(await runTest(functionUrl, testCase))) { - ok = false; + let ok = true; + for (const testCase of testCases) { + if (!(await runTest(functionUrl, testCase))) { + ok = false; + } } - } - return ok; + return ok; } async function runTest(functionUrl, testCase) { - const resp = await fetch(`${functionUrl}?test=${testCase}`); - const respText = await resp.text(); - const ok = resp.status === 200 && respText === "Test passed"; - if (ok) { - console.info(`TEST ${testCase}: passed`); - } else { - console.warn( - `\nTEST ${testCase}: failed with status ${resp.status}\n${respText}\n`, - ); - } - return ok; + const resp = await fetch(`${functionUrl}?test=${testCase}`); + const respText = await resp.text(); + const ok = resp.status === 200 && respText === "Test passed"; + if (ok) { + console.info(`TEST ${testCase}: passed`); + } else { + console.warn( + `\nTEST ${testCase}: failed with status ${resp.status}\n${respText}\n`, + ); + } + return ok; } async function main() { - const url = new URL(process.env.URL ?? "ws://localhost:8080"); - - console.info(`Creating a tunnel to ${url}...`); - const tunnel = await localtunnel({ - port: url.port, - // NOTE: if we specify `local_host`, `localtunnel` will try to rewrite the `Host` header in the - // tunnelled HTTP requests. Unfortunately, they do it in a very silly way by converting the - // tunnelled data to a string, thus corrupting the request body. - //local_host: url.hostname, - }); - - let clientUrlInsideVercel = new URL(tunnel.url); - if (url.protocol === "http:") { - clientUrlInsideVercel.protocol = "https:"; - } else if (url.protocol === "ws:") { - clientUrlInsideVercel.protocol = "wss:"; - } else { - clientUrlInsideVercel.protocol = url.protocol; - } - - console.info(`Established a tunnel on ${clientUrlInsideVercel}`); - - let ok = false; - try { - const deployUrl = await deployToVercel(clientUrlInsideVercel); - const functionUrl = new URL("api/function", deployUrl); - ok = await runTests(functionUrl); - if (ok) { - console.log("All tests passed"); + const url = new URL(process.env.URL ?? "ws://localhost:8080"); + + console.info(`Creating a tunnel to ${url}...`); + const tunnel = await localtunnel({ + port: url.port, + // NOTE: if we specify `local_host`, `localtunnel` will try to rewrite the `Host` header in the + // tunnelled HTTP requests. Unfortunately, they do it in a very silly way by converting the + // tunnelled data to a string, thus corrupting the request body. + //local_host: url.hostname, + }); + + let clientUrlInsideVercel = new URL(tunnel.url); + if (url.protocol === "http:") { + clientUrlInsideVercel.protocol = "https:"; + } else if (url.protocol === "ws:") { + clientUrlInsideVercel.protocol = "wss:"; } else { - console.error("Some tests failed"); + clientUrlInsideVercel.protocol = url.protocol; + } + + console.info(`Established a tunnel on ${clientUrlInsideVercel}`); + + let ok = false; + try { + const deployUrl = await deployToVercel(clientUrlInsideVercel); + const functionUrl = new URL("api/function", deployUrl); + ok = await runTests(functionUrl); + if (ok) { + console.log("All tests passed"); + } else { + console.error("Some tests failed"); + } + } finally { + console.info("Closing the tunnel..."); + await tunnel.close(); } - } finally { - console.info("Closing the tunnel..."); - await tunnel.close(); - } - process.exit(ok ? 0 : 1); + process.exit(ok ? 0 : 1); } main(); diff --git a/packages/libsql-client/smoke_test/vercel/tsconfig.json b/packages/libsql-client/smoke_test/vercel/tsconfig.json index 966e86fc..74ac41e2 100644 --- a/packages/libsql-client/smoke_test/vercel/tsconfig.json +++ b/packages/libsql-client/smoke_test/vercel/tsconfig.json @@ -1,14 +1,14 @@ { - "compilerOptions": { - "target": "es5", - "lib": ["dom", "esnext"], - "module": "esnext", - "moduleResolution": "node", - "allowJs": true, - "skipLibCheck": true, - "strict": true, - "noEmit": true, - "esModuleInterop": true, - "isolatedModules": true - } + "compilerOptions": { + "target": "es5", + "lib": ["dom", "esnext"], + "module": "esnext", + "moduleResolution": "node", + "allowJs": true, + "skipLibCheck": true, + "strict": true, + "noEmit": true, + "esModuleInterop": true, + "isolatedModules": true + } } diff --git a/packages/libsql-client/smoke_test/workers/package.json b/packages/libsql-client/smoke_test/workers/package.json index 1ced1b79..5b9d41c5 100644 --- a/packages/libsql-client/smoke_test/workers/package.json +++ b/packages/libsql-client/smoke_test/workers/package.json @@ -1,6 +1,6 @@ { - "devDependencies": { - "localtunnel": "^2.0.2", - "wrangler": "^3.5.1" - } + "devDependencies": { + "localtunnel": "^2.0.2", + "wrangler": "^3.5.1" + } } diff --git a/packages/libsql-client/smoke_test/workers/test.js b/packages/libsql-client/smoke_test/workers/test.js index c0490274..8d776ebf 100644 --- a/packages/libsql-client/smoke_test/workers/test.js +++ b/packages/libsql-client/smoke_test/workers/test.js @@ -5,96 +5,96 @@ const wrangler = require("wrangler"); const testCases = ["/execute", "/batch", "/transaction"]; async function main() { - const local = !!parseInt(process.env.LOCAL ?? "1"); - const url = new URL(process.env.URL ?? "ws://localhost:8080"); + const local = !!parseInt(process.env.LOCAL ?? "1"); + const url = new URL(process.env.URL ?? "ws://localhost:8080"); - let clientUrlInsideWorker; - let tunnel = undefined; - if (local) { - clientUrlInsideWorker = url; - } else { - console.info(`Creating an tunnel to ${url}...`); - tunnel = await localtunnel({ - port: url.port, - // NOTE: if we specify `local_host`, `localtunnel` will try to rewrite the `Host` header in the - // tunnelled HTTP requests. Unfortunately, they do it in a very silly way by converting the - // tunnelled data to a string, thus corrupting the request body. - //local_host: url.hostname, - }); - - clientUrlInsideWorker = new URL(tunnel.url); - if (url.protocol === "http:") { - clientUrlInsideWorker.protocol = "https:"; - } else if (url.protocol === "ws:") { - clientUrlInsideWorker.protocol = "wss:"; + let clientUrlInsideWorker; + let tunnel = undefined; + if (local) { + clientUrlInsideWorker = url; } else { - clientUrlInsideWorker.protocol = url.protocol; - } + console.info(`Creating an tunnel to ${url}...`); + tunnel = await localtunnel({ + port: url.port, + // NOTE: if we specify `local_host`, `localtunnel` will try to rewrite the `Host` header in the + // tunnelled HTTP requests. Unfortunately, they do it in a very silly way by converting the + // tunnelled data to a string, thus corrupting the request body. + //local_host: url.hostname, + }); - console.info(`Established a tunnel on ${clientUrlInsideWorker}`); - } + clientUrlInsideWorker = new URL(tunnel.url); + if (url.protocol === "http:") { + clientUrlInsideWorker.protocol = "https:"; + } else if (url.protocol === "ws:") { + clientUrlInsideWorker.protocol = "wss:"; + } else { + clientUrlInsideWorker.protocol = url.protocol; + } - let ok = false; - try { - ok = await runWorker(local, clientUrlInsideWorker); - if (ok) { - console.log("All tests passed"); - } else { - console.error("Some tests failed"); - } - } finally { - if (tunnel !== undefined) { - console.info("Closing tunnel..."); - await tunnel.close(); + console.info(`Established a tunnel on ${clientUrlInsideWorker}`); } - // TODO: wrangler keeps the program running: - // https://github.com/cloudflare/workers-sdk/issues/2892 - setTimeout(() => process.exit(ok ? 0 : 1), 200); - } + let ok = false; + try { + ok = await runWorker(local, clientUrlInsideWorker); + if (ok) { + console.log("All tests passed"); + } else { + console.error("Some tests failed"); + } + } finally { + if (tunnel !== undefined) { + console.info("Closing tunnel..."); + await tunnel.close(); + } + + // TODO: wrangler keeps the program running: + // https://github.com/cloudflare/workers-sdk/issues/2892 + setTimeout(() => process.exit(ok ? 0 : 1), 200); + } } async function runWorker(local, clientUrlInsideWorker) { - console.info(`Creating a ${local ? "local" : "nonlocal"} Worker...`); - const worker = await wrangler.unstable_dev("worker.js", { - config: "wrangler.toml", - logLevel: "info", - local, - vars: { - CLIENT_URL: clientUrlInsideWorker.toString(), - }, - experimental: { - disableExperimentalWarning: true, - }, - }); - console.info(`Worker created on ${worker.address}:${worker.port}`); + console.info(`Creating a ${local ? "local" : "nonlocal"} Worker...`); + const worker = await wrangler.unstable_dev("worker.js", { + config: "wrangler.toml", + logLevel: "info", + local, + vars: { + CLIENT_URL: clientUrlInsideWorker.toString(), + }, + experimental: { + disableExperimentalWarning: true, + }, + }); + console.info(`Worker created on ${worker.address}:${worker.port}`); - try { - let ok = true; - for (const testCase of testCases) { - if (!(await runTest(worker, testCase))) { - ok = false; - } + try { + let ok = true; + for (const testCase of testCases) { + if (!(await runTest(worker, testCase))) { + ok = false; + } + } + return ok; + } finally { + console.info("Stopping Worker..."); + await worker.stop(); } - return ok; - } finally { - console.info("Stopping Worker..."); - await worker.stop(); - } } async function runTest(worker, testCase) { - const resp = await worker.fetch(testCase); - const respText = await resp.text(); - const ok = resp.status === 200 && respText === "Test passed"; - if (ok) { - console.info(`TEST ${testCase}: passed`); - } else { - console.warn( - `\nTEST ${testCase}: failed with status ${resp.status}\n${respText}\n`, - ); - } - return ok; + const resp = await worker.fetch(testCase); + const respText = await resp.text(); + const ok = resp.status === 200 && respText === "Test passed"; + if (ok) { + console.info(`TEST ${testCase}: passed`); + } else { + console.warn( + `\nTEST ${testCase}: failed with status ${resp.status}\n${respText}\n`, + ); + } + return ok; } main(); diff --git a/packages/libsql-client/smoke_test/workers/worker.js b/packages/libsql-client/smoke_test/workers/worker.js index 23e8fec8..7af79718 100644 --- a/packages/libsql-client/smoke_test/workers/worker.js +++ b/packages/libsql-client/smoke_test/workers/worker.js @@ -1,105 +1,108 @@ import * as libsql from "@libsql/client"; export default { - async fetch(request, env, ctx) { - function respond(status, responseBody) { - return new Response(responseBody, { - status, - headers: [["content-type", "text/plain"]], - }); - } + async fetch(request, env, ctx) { + function respond(status, responseBody) { + return new Response(responseBody, { + status, + headers: [["content-type", "text/plain"]], + }); + } - if (request.method !== "GET") { - return respond(405, "Only GET method is supported"); - } + if (request.method !== "GET") { + return respond(405, "Only GET method is supported"); + } - const url = new URL(request.url); - if (url.pathname === "/") { - return respond(200, "This is a smoke-test Worker for @libsql/client"); - } + const url = new URL(request.url); + if (url.pathname === "/") { + return respond( + 200, + "This is a smoke-test Worker for @libsql/client", + ); + } - const testCaseFn = testCases[url.pathname]; - if (testCaseFn === undefined) { - return respond(404, "Unknown test case"); - } + const testCaseFn = testCases[url.pathname]; + if (testCaseFn === undefined) { + return respond(404, "Unknown test case"); + } - let client; - try { - client = libsql.createClient({ url: env.CLIENT_URL }); - await testCaseFn(client); - return respond(200, "Test passed"); - } catch (e) { - return respond(500, `Test failed\n${e.stack}`); - } finally { - if (client !== undefined) { - client.close(); - } - } - }, + let client; + try { + client = libsql.createClient({ url: env.CLIENT_URL }); + await testCaseFn(client); + return respond(200, "Test passed"); + } catch (e) { + return respond(500, `Test failed\n${e.stack}`); + } finally { + if (client !== undefined) { + client.close(); + } + } + }, }; const testCases = { - "/execute": async (client) => { - const rs = await client.execute("SELECT 1+1 AS two"); - assert(rs.columns.length === 1); - assert(rs.columns[0] === "two"); - assert(rs.rows.length === 1); - assert(rs.rows[0].length === 1); - assert(rs.rows[0][0] === 2.0); - }, + "/execute": async (client) => { + const rs = await client.execute("SELECT 1+1 AS two"); + assert(rs.columns.length === 1); + assert(rs.columns[0] === "two"); + assert(rs.rows.length === 1); + assert(rs.rows[0].length === 1); + assert(rs.rows[0][0] === 2.0); + }, - "/batch": async (client) => { - const rss = await client.batch([ - "DROP TABLE IF EXISTS t", - "CREATE TABLE t (a, b)", - "INSERT INTO t VALUES (1, 'one'), (2, 'two'), (3, 'three')", - "SELECT * FROM t ORDER BY a", - ]); + "/batch": async (client) => { + const rss = await client.batch([ + "DROP TABLE IF EXISTS t", + "CREATE TABLE t (a, b)", + "INSERT INTO t VALUES (1, 'one'), (2, 'two'), (3, 'three')", + "SELECT * FROM t ORDER BY a", + ]); - assert(rss[0].columns.length === 0); - assert(rss[0].rows.length === 0); + assert(rss[0].columns.length === 0); + assert(rss[0].rows.length === 0); - assert(rss[1].columns.length === 0); - assert(rss[1].rows.length === 0); + assert(rss[1].columns.length === 0); + assert(rss[1].rows.length === 0); - assert(rss[2].columns.length === 0); - assert(rss[2].rows.length === 0); + assert(rss[2].columns.length === 0); + assert(rss[2].rows.length === 0); - assert(rss[3].columns.length === 2); - assert(rss[3].columns[0] === "a"); - assert(rss[3].columns[1] === "b"); - assert(rss[3].rows.length === 3); - assert(rss[3].rows[0][0] === 1); - assert(rss[3].rows[0][1] === "one"); - assert(rss[3].rows[1][0] === 2); - assert(rss[3].rows[1][1] === "two"); - assert(rss[3].rows[2][0] === 3); - assert(rss[3].rows[2][1] === "three"); - }, + assert(rss[3].columns.length === 2); + assert(rss[3].columns[0] === "a"); + assert(rss[3].columns[1] === "b"); + assert(rss[3].rows.length === 3); + assert(rss[3].rows[0][0] === 1); + assert(rss[3].rows[0][1] === "one"); + assert(rss[3].rows[1][0] === 2); + assert(rss[3].rows[1][1] === "two"); + assert(rss[3].rows[2][0] === 3); + assert(rss[3].rows[2][1] === "three"); + }, - "/transaction": async (client) => { - await client.batch([ - "DROP TABLE IF EXISTS t", - "CREATE TABLE t (a, b)", - "INSERT INTO t VALUES (1, 'one'), (2, 'two'), (3, 'three')", - ]); + "/transaction": async (client) => { + await client.batch([ + "DROP TABLE IF EXISTS t", + "CREATE TABLE t (a, b)", + "INSERT INTO t VALUES (1, 'one'), (2, 'two'), (3, 'three')", + ]); - const txn = await client.transaction(); - try { - await txn.execute("INSERT INTO t VALUES (4, 'four')"); - await txn.execute("DELETE FROM t WHERE a <= 2"); - await txn.commit(); - } finally { - txn.close(); - } + const txn = await client.transaction(); + try { + await txn.execute("INSERT INTO t VALUES (4, 'four')"); + await txn.execute("DELETE FROM t WHERE a <= 2"); + await txn.commit(); + } finally { + txn.close(); + } - const rs = await client.execute("SELECT COUNT(*) FROM t"); - assert(rs.rows[0][0] === 2); - }, + const rs = await client.execute("SELECT COUNT(*) FROM t"); + assert(rs.rows[0][0] === 2); + }, }; function assert(value, message) { - if (!value) { - throw new Error(message ?? "Assertion failed"); - } + if (!value) { + throw new Error(message ?? "Assertion failed"); + } } diff --git a/packages/libsql-client/src/__tests__/client.test.ts b/packages/libsql-client/src/__tests__/client.test.ts index 8607ad24..7d230e96 100644 --- a/packages/libsql-client/src/__tests__/client.test.ts +++ b/packages/libsql-client/src/__tests__/client.test.ts @@ -11,17 +11,17 @@ import type * as libsql from "../node.js"; import { createClient } from "../node.js"; const config = { - url: process.env.URL ?? "ws://localhost:8080", - syncUrl: process.env.SYNC_URL, - authToken: process.env.AUTH_TOKEN, + url: process.env.URL ?? "ws://localhost:8080", + syncUrl: process.env.SYNC_URL, + authToken: process.env.AUTH_TOKEN, }; const isWs = - config.url.startsWith("ws:") || - config.url.startsWith("wss:") || - config.url.startsWith("libsql:"); + config.url.startsWith("ws:") || + config.url.startsWith("wss:") || + config.url.startsWith("libsql:"); const isHttp = - config.url.startsWith("http:") || config.url.startsWith("https:"); + config.url.startsWith("http:") || config.url.startsWith("https:"); const isFile = config.url.startsWith("file:"); // This allows us to skip tests based on the Hrana server that we are targeting: @@ -34,1181 +34,1245 @@ const server = process.env.SERVER ?? "test_v3"; const hasHrana2 = server !== "test_v1"; const hasHrana3 = server !== "test_v1" && server !== "test_v2"; const hasNetworkErrors = - isWs && - (server === "test_v1" || server === "test_v2" || server === "test_v3"); + isWs && + (server === "test_v1" || server === "test_v2" || server === "test_v3"); function withClient( - f: (c: libsql.Client) => Promise, - extraConfig: Partial = {}, + f: (c: libsql.Client) => Promise, + extraConfig: Partial = {}, ): () => Promise { - return async () => { - const c = createClient({ ...config, ...extraConfig }); - try { - await f(c); - } finally { - c.close(); - } - }; + return async () => { + const c = createClient({ ...config, ...extraConfig }); + try { + await f(c); + } finally { + c.close(); + } + }; } function withInMemoryClient( - f: (c: libsql.Client) => Promise, + f: (c: libsql.Client) => Promise, ): () => Promise { - return async () => { - const c = createClient({ url: ":memory:" }); - try { - await f(c); - } finally { - c.close(); - } - }; + return async () => { + const c = createClient({ url: ":memory:" }); + try { + await f(c); + } finally { + c.close(); + } + }; } describe("createClient()", () => { - test("URL scheme not supported", () => { - expect(() => createClient({ url: "ftp://localhost" })).toThrow( - expect.toBeLibsqlError("URL_SCHEME_NOT_SUPPORTED", /"ftp:"/), + test("URL scheme not supported", () => { + expect(() => createClient({ url: "ftp://localhost" })).toThrow( + expect.toBeLibsqlError("URL_SCHEME_NOT_SUPPORTED", /"ftp:"/), + ); + }); + + test("URL param not supported", () => { + expect(() => createClient({ url: "ws://localhost?foo=bar" })).toThrow( + expect.toBeLibsqlError("URL_PARAM_NOT_SUPPORTED", /"foo"/), + ); + }); + + test("URL scheme incompatible with ?tls", () => { + const urls = [ + "ws://localhost?tls=1", + "wss://localhost?tls=0", + "http://localhost?tls=1", + "https://localhost?tls=0", + ]; + for (const url of urls) { + expect(() => createClient({ url })).toThrow( + expect.toBeLibsqlError("URL_INVALID", /TLS/), + ); + } + }); + + test("missing port in libsql URL with tls=0", () => { + expect(() => createClient({ url: "libsql://localhost?tls=0" })).toThrow( + expect.toBeLibsqlError("URL_INVALID", /port/), + ); + }); + + test("invalid value of tls query param", () => { + expect(() => + createClient({ url: "libsql://localhost?tls=yes" }), + ).toThrow(expect.toBeLibsqlError("URL_INVALID", /"tls".*"yes"/)); + }); + + test("passing URL instead of config object", () => { + // @ts-expect-error + expect(() => createClient("ws://localhost")).toThrow( + /as object, got string/, + ); + }); + + test("invalid value for `intMode`", () => { + // @ts-expect-error + expect(() => createClient({ ...config, intMode: "foo" })).toThrow( + /"foo"/, + ); + }); + + test("supports in-memory database", () => { + expect(() => createClient({ url: ":memory:" })).not.toThrow(); + }); +}); + +describe("execute()", () => { + test( + "query a single value", + withClient(async (c) => { + const rs = await c.execute("SELECT 42"); + expect(rs.columns.length).toStrictEqual(1); + expect(rs.columnTypes.length).toStrictEqual(1); + expect(rs.rows.length).toStrictEqual(1); + expect(rs.rows[0].length).toStrictEqual(1); + expect(rs.rows[0][0]).toStrictEqual(42); + }), ); - }); - test("URL param not supported", () => { - expect(() => createClient({ url: "ws://localhost?foo=bar" })).toThrow( - expect.toBeLibsqlError("URL_PARAM_NOT_SUPPORTED", /"foo"/), + test( + "query a single row", + withClient(async (c) => { + const rs = await c.execute( + "SELECT 1 AS one, 'two' AS two, 0.5 AS three", + ); + expect(rs.columns).toStrictEqual(["one", "two", "three"]); + expect(rs.columnTypes).toStrictEqual(["", "", ""]); + expect(rs.rows.length).toStrictEqual(1); + + const r = rs.rows[0]; + expect(r.length).toStrictEqual(3); + expect(Array.from(r)).toStrictEqual([1, "two", 0.5]); + expect(Object.entries(r)).toStrictEqual([ + ["one", 1], + ["two", "two"], + ["three", 0.5], + ]); + }), ); - }); - - test("URL scheme incompatible with ?tls", () => { - const urls = [ - "ws://localhost?tls=1", - "wss://localhost?tls=0", - "http://localhost?tls=1", - "https://localhost?tls=0", - ]; - for (const url of urls) { - expect(() => createClient({ url })).toThrow( - expect.toBeLibsqlError("URL_INVALID", /TLS/), - ); - } - }); - test("missing port in libsql URL with tls=0", () => { - expect(() => createClient({ url: "libsql://localhost?tls=0" })).toThrow( - expect.toBeLibsqlError("URL_INVALID", /port/), + test( + "query multiple rows", + withClient(async (c) => { + const rs = await c.execute( + "VALUES (1, 'one'), (2, 'two'), (3, 'three')", + ); + expect(rs.columns.length).toStrictEqual(2); + expect(rs.columnTypes.length).toStrictEqual(2); + expect(rs.rows.length).toStrictEqual(3); + + expect(Array.from(rs.rows[0])).toStrictEqual([1, "one"]); + expect(Array.from(rs.rows[1])).toStrictEqual([2, "two"]); + expect(Array.from(rs.rows[2])).toStrictEqual([3, "three"]); + }), ); - }); - test("invalid value of tls query param", () => { - expect(() => createClient({ url: "libsql://localhost?tls=yes" })).toThrow( - expect.toBeLibsqlError("URL_INVALID", /"tls".*"yes"/), + test( + "statement that produces error", + withClient(async (c) => { + await expect(c.execute("SELECT foobar")).rejects.toBeLibsqlError(); + }), ); - }); - test("passing URL instead of config object", () => { - // @ts-expect-error - expect(() => createClient("ws://localhost")).toThrow( - /as object, got string/, + test( + "rowsAffected with INSERT", + withClient(async (c) => { + await c.batch( + ["DROP TABLE IF EXISTS t", "CREATE TABLE t (a)"], + "write", + ); + const rs = await c.execute("INSERT INTO t VALUES (1), (2)"); + expect(rs.rowsAffected).toStrictEqual(2); + }), ); - }); - test("invalid value for `intMode`", () => { - // @ts-expect-error - expect(() => createClient({ ...config, intMode: "foo" })).toThrow(/"foo"/); - }); + test( + "rowsAffected with DELETE", + withClient(async (c) => { + await c.batch( + [ + "DROP TABLE IF EXISTS t", + "CREATE TABLE t (a)", + "INSERT INTO t VALUES (1), (2), (3), (4), (5)", + ], + "write", + ); + const rs = await c.execute("DELETE FROM t WHERE a >= 3"); + expect(rs.rowsAffected).toStrictEqual(3); + }), + ); - test("supports in-memory database", () => { - expect(() => createClient({ url: ":memory:" })).not.toThrow(); - }); -}); + test( + "lastInsertRowid with INSERT", + withClient(async (c) => { + await c.batch( + [ + "DROP TABLE IF EXISTS t", + "CREATE TABLE t (a)", + "INSERT INTO t VALUES ('one'), ('two')", + ], + "write", + ); + const insertRs = await c.execute("INSERT INTO t VALUES ('three')"); + expect(insertRs.lastInsertRowid).not.toBeUndefined(); + const selectRs = await c.execute({ + sql: "SELECT a FROM t WHERE ROWID = ?", + args: [insertRs.lastInsertRowid!], + }); + expect(Array.from(selectRs.rows[0])).toStrictEqual(["three"]); + }), + ); -describe("execute()", () => { - test( - "query a single value", - withClient(async (c) => { - const rs = await c.execute("SELECT 42"); - expect(rs.columns.length).toStrictEqual(1); - expect(rs.columnTypes.length).toStrictEqual(1); - expect(rs.rows.length).toStrictEqual(1); - expect(rs.rows[0].length).toStrictEqual(1); - expect(rs.rows[0][0]).toStrictEqual(42); - }), - ); - - test( - "query a single row", - withClient(async (c) => { - const rs = await c.execute("SELECT 1 AS one, 'two' AS two, 0.5 AS three"); - expect(rs.columns).toStrictEqual(["one", "two", "three"]); - expect(rs.columnTypes).toStrictEqual(["", "", ""]); - expect(rs.rows.length).toStrictEqual(1); - - const r = rs.rows[0]; - expect(r.length).toStrictEqual(3); - expect(Array.from(r)).toStrictEqual([1, "two", 0.5]); - expect(Object.entries(r)).toStrictEqual([ - ["one", 1], - ["two", "two"], - ["three", 0.5], - ]); - }), - ); - - test( - "query multiple rows", - withClient(async (c) => { - const rs = await c.execute("VALUES (1, 'one'), (2, 'two'), (3, 'three')"); - expect(rs.columns.length).toStrictEqual(2); - expect(rs.columnTypes.length).toStrictEqual(2); - expect(rs.rows.length).toStrictEqual(3); - - expect(Array.from(rs.rows[0])).toStrictEqual([1, "one"]); - expect(Array.from(rs.rows[1])).toStrictEqual([2, "two"]); - expect(Array.from(rs.rows[2])).toStrictEqual([3, "three"]); - }), - ); - - test( - "statement that produces error", - withClient(async (c) => { - await expect(c.execute("SELECT foobar")).rejects.toBeLibsqlError(); - }), - ); - - test( - "rowsAffected with INSERT", - withClient(async (c) => { - await c.batch(["DROP TABLE IF EXISTS t", "CREATE TABLE t (a)"], "write"); - const rs = await c.execute("INSERT INTO t VALUES (1), (2)"); - expect(rs.rowsAffected).toStrictEqual(2); - }), - ); - - test( - "rowsAffected with DELETE", - withClient(async (c) => { - await c.batch( - [ - "DROP TABLE IF EXISTS t", - "CREATE TABLE t (a)", - "INSERT INTO t VALUES (1), (2), (3), (4), (5)", - ], - "write", - ); - const rs = await c.execute("DELETE FROM t WHERE a >= 3"); - expect(rs.rowsAffected).toStrictEqual(3); - }), - ); - - test( - "lastInsertRowid with INSERT", - withClient(async (c) => { - await c.batch( - [ - "DROP TABLE IF EXISTS t", - "CREATE TABLE t (a)", - "INSERT INTO t VALUES ('one'), ('two')", - ], - "write", - ); - const insertRs = await c.execute("INSERT INTO t VALUES ('three')"); - expect(insertRs.lastInsertRowid).not.toBeUndefined(); - const selectRs = await c.execute({ - sql: "SELECT a FROM t WHERE ROWID = ?", - args: [insertRs.lastInsertRowid!], - }); - expect(Array.from(selectRs.rows[0])).toStrictEqual(["three"]); - }), - ); - - test( - "rows from INSERT RETURNING", - withClient(async (c) => { - await c.batch(["DROP TABLE IF EXISTS t", "CREATE TABLE t (a)"], "write"); - - const rs = await c.execute( - "INSERT INTO t VALUES (1) RETURNING 42 AS x, 'foo' AS y", - ); - expect(rs.columns).toStrictEqual(["x", "y"]); - expect(rs.columnTypes).toStrictEqual(["", ""]); - expect(rs.rows.length).toStrictEqual(1); - expect(Array.from(rs.rows[0])).toStrictEqual([42, "foo"]); - }), - ); - - (hasHrana2 ? test : test.skip)( - "rowsAffected with WITH INSERT", - withClient(async (c) => { - await c.batch( - [ - "DROP TABLE IF EXISTS t", - "CREATE TABLE t (a)", - "INSERT INTO t VALUES (1), (2), (3)", - ], - "write", - ); - - const rs = await c.execute(` + test( + "rows from INSERT RETURNING", + withClient(async (c) => { + await c.batch( + ["DROP TABLE IF EXISTS t", "CREATE TABLE t (a)"], + "write", + ); + + const rs = await c.execute( + "INSERT INTO t VALUES (1) RETURNING 42 AS x, 'foo' AS y", + ); + expect(rs.columns).toStrictEqual(["x", "y"]); + expect(rs.columnTypes).toStrictEqual(["", ""]); + expect(rs.rows.length).toStrictEqual(1); + expect(Array.from(rs.rows[0])).toStrictEqual([42, "foo"]); + }), + ); + + (hasHrana2 ? test : test.skip)( + "rowsAffected with WITH INSERT", + withClient(async (c) => { + await c.batch( + [ + "DROP TABLE IF EXISTS t", + "CREATE TABLE t (a)", + "INSERT INTO t VALUES (1), (2), (3)", + ], + "write", + ); + + const rs = await c.execute(` WITH x(a) AS (SELECT 2*a FROM t) INSERT INTO t SELECT a+1 FROM x `); - expect(rs.rowsAffected).toStrictEqual(3); - }), - ); - - test( - "query a single value using an in memory database", - withInMemoryClient(async (c) => { - await c.batch( - [ - "DROP TABLE IF EXISTS t", - "CREATE TABLE t (a)", - "INSERT INTO t VALUES ('one'), ('two')", - ], - "write", - ); - const insertRs = await c.execute("INSERT INTO t VALUES ('three')"); - expect(insertRs.lastInsertRowid).not.toBeUndefined(); - const selectRs = await c.execute({ - sql: "SELECT a FROM t WHERE ROWID = ?", - args: [insertRs.lastInsertRowid!], - }); - expect(Array.from(selectRs.rows[0])).toStrictEqual(["three"]); - }), - ); -}); - -describe("values", () => { - function testRoundtrip( - name: string, - passed: libsql.InValue, - expected: libsql.Value, - intMode?: libsql.IntMode, - ): void { - test( - name, - withClient( - async (c) => { - const rs = await c.execute({ sql: "SELECT ?", args: [passed] }); - expect(rs.rows[0][0]).toStrictEqual(expected); - }, - { intMode }, - ), + expect(rs.rowsAffected).toStrictEqual(3); + }), ); - } - - function testRoundtripError( - name: string, - passed: libsql.InValue, - expectedError: unknown, - intMode?: libsql.IntMode, - ): void { + test( - name, - withClient( - async (c) => { - await expect( - c.execute({ - sql: "SELECT ?", - args: [passed], - }), - ).rejects.toBeInstanceOf(expectedError); - }, - { intMode }, - ), + "query a single value using an in memory database", + withInMemoryClient(async (c) => { + await c.batch( + [ + "DROP TABLE IF EXISTS t", + "CREATE TABLE t (a)", + "INSERT INTO t VALUES ('one'), ('two')", + ], + "write", + ); + const insertRs = await c.execute("INSERT INTO t VALUES ('three')"); + expect(insertRs.lastInsertRowid).not.toBeUndefined(); + const selectRs = await c.execute({ + sql: "SELECT a FROM t WHERE ROWID = ?", + args: [insertRs.lastInsertRowid!], + }); + expect(Array.from(selectRs.rows[0])).toStrictEqual(["three"]); + }), ); - } - - testRoundtrip("string", "boomerang", "boomerang"); - testRoundtrip("string with weird characters", "a\n\r\t ", "a\n\r\t "); - testRoundtrip( - "string with unicode", - "žluťoučký kůň úpěl ďábelské ódy", - "žluťoučký kůň úpěl ďábelské ódy", - ); - - describe("number", () => { - const intModes: Array = ["number", "bigint", "string"]; - for (const intMode of intModes) { - testRoundtrip("zero", 0, 0, intMode); - testRoundtrip("integer", -2023, -2023, intMode); - testRoundtrip("float", 12.345, 12.345, intMode); - testRoundtrip("large positive float", 1e18, 1e18, intMode); - testRoundtrip("large negative float", -1e18, -1e18, intMode); - testRoundtrip("MAX_VALUE", Number.MAX_VALUE, Number.MAX_VALUE, intMode); - testRoundtrip( - "-MAX_VALUE", - -Number.MAX_VALUE, - -Number.MAX_VALUE, - intMode, - ); - testRoundtrip("MIN_VALUE", Number.MIN_VALUE, Number.MIN_VALUE, intMode); +}); + +describe("values", () => { + function testRoundtrip( + name: string, + passed: libsql.InValue, + expected: libsql.Value, + intMode?: libsql.IntMode, + ): void { + test( + name, + withClient( + async (c) => { + const rs = await c.execute({ + sql: "SELECT ?", + args: [passed], + }); + expect(rs.rows[0][0]).toStrictEqual(expected); + }, + { intMode }, + ), + ); } - }); - - describe("bigint", () => { - describe("'number' int mode", () => { - testRoundtrip("zero integer", 0n, 0, "number"); - testRoundtrip("small integer", -42n, -42, "number"); - testRoundtrip( - "largest safe integer", - 9007199254740991n, - 9007199254740991, - "number", - ); - testRoundtripError( - "smallest unsafe integer", - 9007199254740992n, - RangeError, - "number", - ); - testRoundtripError( - "large unsafe integer", - -1152921504594532842n, - RangeError, - "number", - ); - }); - describe("'bigint' int mode", () => { - testRoundtrip("zero integer", 0n, 0n, "bigint"); - testRoundtrip("small integer", -42n, -42n, "bigint"); - testRoundtrip( - "large positive integer", - 1152921504608088318n, - 1152921504608088318n, - "bigint", - ); - testRoundtrip( - "large negative integer", - -1152921504594532842n, - -1152921504594532842n, - "bigint", - ); - testRoundtrip( - "largest positive integer", - 9223372036854775807n, - 9223372036854775807n, - "bigint", - ); - testRoundtrip( - "largest negative integer", - -9223372036854775808n, - -9223372036854775808n, - "bigint", - ); + function testRoundtripError( + name: string, + passed: libsql.InValue, + expectedError: unknown, + intMode?: libsql.IntMode, + ): void { + test( + name, + withClient( + async (c) => { + await expect( + c.execute({ + sql: "SELECT ?", + args: [passed], + }), + ).rejects.toBeInstanceOf(expectedError); + }, + { intMode }, + ), + ); + } + + testRoundtrip("string", "boomerang", "boomerang"); + testRoundtrip("string with weird characters", "a\n\r\t ", "a\n\r\t "); + testRoundtrip( + "string with unicode", + "žluťoučký kůň úpěl ďábelské ódy", + "žluťoučký kůň úpěl ďábelské ódy", + ); + + describe("number", () => { + const intModes: Array = ["number", "bigint", "string"]; + for (const intMode of intModes) { + testRoundtrip("zero", 0, 0, intMode); + testRoundtrip("integer", -2023, -2023, intMode); + testRoundtrip("float", 12.345, 12.345, intMode); + testRoundtrip("large positive float", 1e18, 1e18, intMode); + testRoundtrip("large negative float", -1e18, -1e18, intMode); + testRoundtrip( + "MAX_VALUE", + Number.MAX_VALUE, + Number.MAX_VALUE, + intMode, + ); + testRoundtrip( + "-MAX_VALUE", + -Number.MAX_VALUE, + -Number.MAX_VALUE, + intMode, + ); + testRoundtrip( + "MIN_VALUE", + Number.MIN_VALUE, + Number.MIN_VALUE, + intMode, + ); + } }); - describe("'string' int mode", () => { - testRoundtrip("zero integer", 0n, "0", "string"); - testRoundtrip("small integer", -42n, "-42", "string"); - testRoundtrip( - "large positive integer", - 1152921504608088318n, - "1152921504608088318", - "string", - ); - testRoundtrip( - "large negative integer", - -1152921504594532842n, - "-1152921504594532842", - "string", - ); - testRoundtrip( - "largest positive integer", - 9223372036854775807n, - "9223372036854775807", - "string", - ); - testRoundtrip( - "largest negative integer", - -9223372036854775808n, - "-9223372036854775808", - "string", - ); + describe("bigint", () => { + describe("'number' int mode", () => { + testRoundtrip("zero integer", 0n, 0, "number"); + testRoundtrip("small integer", -42n, -42, "number"); + testRoundtrip( + "largest safe integer", + 9007199254740991n, + 9007199254740991, + "number", + ); + testRoundtripError( + "smallest unsafe integer", + 9007199254740992n, + RangeError, + "number", + ); + testRoundtripError( + "large unsafe integer", + -1152921504594532842n, + RangeError, + "number", + ); + }); + + describe("'bigint' int mode", () => { + testRoundtrip("zero integer", 0n, 0n, "bigint"); + testRoundtrip("small integer", -42n, -42n, "bigint"); + testRoundtrip( + "large positive integer", + 1152921504608088318n, + 1152921504608088318n, + "bigint", + ); + testRoundtrip( + "large negative integer", + -1152921504594532842n, + -1152921504594532842n, + "bigint", + ); + testRoundtrip( + "largest positive integer", + 9223372036854775807n, + 9223372036854775807n, + "bigint", + ); + testRoundtrip( + "largest negative integer", + -9223372036854775808n, + -9223372036854775808n, + "bigint", + ); + }); + + describe("'string' int mode", () => { + testRoundtrip("zero integer", 0n, "0", "string"); + testRoundtrip("small integer", -42n, "-42", "string"); + testRoundtrip( + "large positive integer", + 1152921504608088318n, + "1152921504608088318", + "string", + ); + testRoundtrip( + "large negative integer", + -1152921504594532842n, + "-1152921504594532842", + "string", + ); + testRoundtrip( + "largest positive integer", + 9223372036854775807n, + "9223372036854775807", + "string", + ); + testRoundtrip( + "largest negative integer", + -9223372036854775808n, + "-9223372036854775808", + "string", + ); + }); }); - }); - - const buf = new ArrayBuffer(256); - const array = new Uint8Array(buf); - for (let i = 0; i < 256; ++i) { - array[i] = i ^ 0xab; - } - testRoundtrip("ArrayBuffer", buf, buf); - testRoundtrip("Uint8Array", array, buf); - - testRoundtrip("null", null, null); - testRoundtrip("true", true, 1n, "bigint"); - testRoundtrip("false", false, 0n, "bigint"); - testRoundtrip("true", true, 1, "number"); - testRoundtrip("false", false, 0, "number"); - testRoundtrip("true", true, "1", "string"); - testRoundtrip("false", false, "0", "string"); - testRoundtrip("true", true, 1); - testRoundtrip("false", false, 0); - - testRoundtrip( - "Date", - new Date("2023-01-02T12:34:56Z"), - 1672662896000, - "bigint", - ); - - // @ts-expect-error - testRoundtripError("undefined produces error", undefined, TypeError); - testRoundtripError("NaN produces error", NaN, RangeError); - testRoundtripError("Infinity produces error", Infinity, RangeError); - testRoundtripError( - "large bigint produces error", - -1267650600228229401496703205376n, - RangeError, - ); - - test( - "max 64-bit bigint", - withClient(async (c) => { - const rs = await c.execute({ - sql: "SELECT ?||''", - args: [9223372036854775807n], - }); - expect(rs.rows[0][0]).toStrictEqual("9223372036854775807"); - }), - ); - - test( - "min 64-bit bigint", - withClient(async (c) => { - const rs = await c.execute({ - sql: "SELECT ?||''", - args: [-9223372036854775808n], - }); - expect(rs.rows[0][0]).toStrictEqual("-9223372036854775808"); - }), - ); + + const buf = new ArrayBuffer(256); + const array = new Uint8Array(buf); + for (let i = 0; i < 256; ++i) { + array[i] = i ^ 0xab; + } + testRoundtrip("ArrayBuffer", buf, buf); + testRoundtrip("Uint8Array", array, buf); + + testRoundtrip("null", null, null); + testRoundtrip("true", true, 1n, "bigint"); + testRoundtrip("false", false, 0n, "bigint"); + testRoundtrip("true", true, 1, "number"); + testRoundtrip("false", false, 0, "number"); + testRoundtrip("true", true, "1", "string"); + testRoundtrip("false", false, "0", "string"); + testRoundtrip("true", true, 1); + testRoundtrip("false", false, 0); + + testRoundtrip( + "Date", + new Date("2023-01-02T12:34:56Z"), + 1672662896000, + "bigint", + ); + + // @ts-expect-error + testRoundtripError("undefined produces error", undefined, TypeError); + testRoundtripError("NaN produces error", NaN, RangeError); + testRoundtripError("Infinity produces error", Infinity, RangeError); + testRoundtripError( + "large bigint produces error", + -1267650600228229401496703205376n, + RangeError, + ); + + test( + "max 64-bit bigint", + withClient(async (c) => { + const rs = await c.execute({ + sql: "SELECT ?||''", + args: [9223372036854775807n], + }); + expect(rs.rows[0][0]).toStrictEqual("9223372036854775807"); + }), + ); + + test( + "min 64-bit bigint", + withClient(async (c) => { + const rs = await c.execute({ + sql: "SELECT ?||''", + args: [-9223372036854775808n], + }); + expect(rs.rows[0][0]).toStrictEqual("-9223372036854775808"); + }), + ); }); describe("ResultSet.toJSON()", () => { - test( - "simple result set", - withClient(async (c) => { - const rs = await c.execute("SELECT 1 AS a"); - const json = rs.toJSON(); - expect( - json["lastInsertRowid"] === null || json["lastInsertRowid"] === "0", - ).toBe(true); - expect(json["columns"]).toStrictEqual(["a"]); - expect(json["columnTypes"]).toStrictEqual([""]); - expect(json["rows"]).toStrictEqual([[1]]); - expect(json["rowsAffected"]).toStrictEqual(0); - - const str = JSON.stringify(rs); - expect( - str === - '{"columns":["a"],"columnTypes":[""],"rows":[[1]],"rowsAffected":0,"lastInsertRowid":null}' || - str === - '{"columns":["a"],"columnTypes":[""],"rows":[[1]],"rowsAffected":0,"lastInsertRowid":"0"}', - ).toBe(true); - }), - ); - - test( - "lastInsertRowid", - withClient(async (c) => { - await c.execute("DROP TABLE IF EXISTS t"); - await c.execute("CREATE TABLE t (id INTEGER PRIMARY KEY NOT NULL)"); - const rs = await c.execute("INSERT INTO t VALUES (12345)"); - expect(rs.toJSON()).toStrictEqual({ - columns: [], - columnTypes: [], - rows: [], - rowsAffected: 1, - lastInsertRowid: "12345", - }); - }), - ); - - test( - "computed values", - withClient(async (c) => { - const rs = await c.execute( - "SELECT 42 AS integer, 0.5 AS float, NULL AS \"null\", 'foo' AS text, X'626172' AS blob", - ); - const json = rs.toJSON(); - expect(json["columns"]).toStrictEqual([ - "integer", - "float", - "null", - "text", - "blob", - ]); - expect(json["columnTypes"]).toStrictEqual(["", "", "", "", ""]); - expect(json["rows"]).toStrictEqual([[42, 0.5, null, "foo", "YmFy"]]); - }), - ); - - (hasHrana2 ? test : test.skip)( - "row values", - withClient(async (c) => { - await c.execute("DROP TABLE IF EXISTS t"); - await c.execute("CREATE TABLE t (i INTEGER, f FLOAT, t TEXT, b BLOB)"); - await c.execute("INSERT INTO t VALUES (42, 0.5, 'foo', X'626172')"); - const rs = await c.execute("SELECT i, f, t, b FROM t LIMIT 1"); - const json = rs.toJSON(); - expect(json["columns"]).toStrictEqual(["i", "f", "t", "b"]); - expect(json["columnTypes"]).toStrictEqual([ - "INTEGER", - "FLOAT", - "TEXT", - "BLOB", - ]); - expect(json["rows"]).toStrictEqual([[42, 0.5, "foo", "YmFy"]]); - }), - ); - - test( - "bigint row value", - withClient( - async (c) => { - const rs = await c.execute("SELECT 42"); - const json = rs.toJSON(); - expect(json["rows"]).toStrictEqual([["42"]]); - }, - { intMode: "bigint" }, - ), - ); -}); + test( + "simple result set", + withClient(async (c) => { + const rs = await c.execute("SELECT 1 AS a"); + const json = rs.toJSON(); + expect( + json["lastInsertRowid"] === null || + json["lastInsertRowid"] === "0", + ).toBe(true); + expect(json["columns"]).toStrictEqual(["a"]); + expect(json["columnTypes"]).toStrictEqual([""]); + expect(json["rows"]).toStrictEqual([[1]]); + expect(json["rowsAffected"]).toStrictEqual(0); + + const str = JSON.stringify(rs); + expect( + str === + '{"columns":["a"],"columnTypes":[""],"rows":[[1]],"rowsAffected":0,"lastInsertRowid":null}' || + str === + '{"columns":["a"],"columnTypes":[""],"rows":[[1]],"rowsAffected":0,"lastInsertRowid":"0"}', + ).toBe(true); + }), + ); -describe("arguments", () => { - test( - "? arguments", - withClient(async (c) => { - const rs = await c.execute({ - sql: "SELECT ?, ?", - args: ["one", "two"], - }); - expect(Array.from(rs.rows[0])).toStrictEqual(["one", "two"]); - }), - ); - - (!isFile ? test : test.skip)( - "?NNN arguments", - withClient(async (c) => { - const rs = await c.execute({ - sql: "SELECT ?2, ?3, ?1", - args: ["one", "two", "three"], - }); - expect(Array.from(rs.rows[0])).toStrictEqual(["two", "three", "one"]); - }), - ); - - (!isFile ? test : test.skip)( - "?NNN arguments with holes", - withClient(async (c) => { - const rs = await c.execute({ - sql: "SELECT ?3, ?1", - args: ["one", "two", "three"], - }); - expect(Array.from(rs.rows[0])).toStrictEqual(["three", "one"]); - }), - ); - - (!isFile ? test : test.skip)( - "?NNN and ? arguments", - withClient(async (c) => { - const rs = await c.execute({ - sql: "SELECT ?2, ?, ?3", - args: ["one", "two", "three"], - }); - expect(Array.from(rs.rows[0])).toStrictEqual(["two", "three", "three"]); - }), - ); - - for (const sign of [":", "@", "$"]) { test( - `${sign}AAAA arguments`, - withClient(async (c) => { - const rs = await c.execute({ - sql: `SELECT ${sign}b, ${sign}a`, - args: { a: "one", [`${sign}b`]: "two" }, - }); - expect(Array.from(rs.rows[0])).toStrictEqual(["two", "one"]); - }), + "lastInsertRowid", + withClient(async (c) => { + await c.execute("DROP TABLE IF EXISTS t"); + await c.execute("CREATE TABLE t (id INTEGER PRIMARY KEY NOT NULL)"); + const rs = await c.execute("INSERT INTO t VALUES (12345)"); + expect(rs.toJSON()).toStrictEqual({ + columns: [], + columnTypes: [], + rows: [], + rowsAffected: 1, + lastInsertRowid: "12345", + }); + }), ); test( - `${sign}AAAA arguments used multiple times`, - withClient(async (c) => { - const rs = await c.execute({ - sql: `SELECT ${sign}b, ${sign}a, ${sign}b || ${sign}a`, - args: { a: "one", [`${sign}b`]: "two" }, - }); - expect(Array.from(rs.rows[0])).toStrictEqual(["two", "one", "twoone"]); - }), + "computed values", + withClient(async (c) => { + const rs = await c.execute( + "SELECT 42 AS integer, 0.5 AS float, NULL AS \"null\", 'foo' AS text, X'626172' AS blob", + ); + const json = rs.toJSON(); + expect(json["columns"]).toStrictEqual([ + "integer", + "float", + "null", + "text", + "blob", + ]); + expect(json["columnTypes"]).toStrictEqual(["", "", "", "", ""]); + expect(json["rows"]).toStrictEqual([ + [42, 0.5, null, "foo", "YmFy"], + ]); + }), + ); + + (hasHrana2 ? test : test.skip)( + "row values", + withClient(async (c) => { + await c.execute("DROP TABLE IF EXISTS t"); + await c.execute( + "CREATE TABLE t (i INTEGER, f FLOAT, t TEXT, b BLOB)", + ); + await c.execute("INSERT INTO t VALUES (42, 0.5, 'foo', X'626172')"); + const rs = await c.execute("SELECT i, f, t, b FROM t LIMIT 1"); + const json = rs.toJSON(); + expect(json["columns"]).toStrictEqual(["i", "f", "t", "b"]); + expect(json["columnTypes"]).toStrictEqual([ + "INTEGER", + "FLOAT", + "TEXT", + "BLOB", + ]); + expect(json["rows"]).toStrictEqual([[42, 0.5, "foo", "YmFy"]]); + }), ); test( - `${sign}AAAA arguments and ?NNN arguments`, - withClient(async (c) => { - const rs = await c.execute({ - sql: `SELECT ${sign}b, ${sign}a, ?1`, - args: { a: "one", [`${sign}b`]: "two" }, - }); - expect(Array.from(rs.rows[0])).toStrictEqual(["two", "one", "two"]); - }), + "bigint row value", + withClient( + async (c) => { + const rs = await c.execute("SELECT 42"); + const json = rs.toJSON(); + expect(json["rows"]).toStrictEqual([["42"]]); + }, + { intMode: "bigint" }, + ), + ); +}); + +describe("arguments", () => { + test( + "? arguments", + withClient(async (c) => { + const rs = await c.execute({ + sql: "SELECT ?, ?", + args: ["one", "two"], + }); + expect(Array.from(rs.rows[0])).toStrictEqual(["one", "two"]); + }), + ); + + (!isFile ? test : test.skip)( + "?NNN arguments", + withClient(async (c) => { + const rs = await c.execute({ + sql: "SELECT ?2, ?3, ?1", + args: ["one", "two", "three"], + }); + expect(Array.from(rs.rows[0])).toStrictEqual([ + "two", + "three", + "one", + ]); + }), + ); + + (!isFile ? test : test.skip)( + "?NNN arguments with holes", + withClient(async (c) => { + const rs = await c.execute({ + sql: "SELECT ?3, ?1", + args: ["one", "two", "three"], + }); + expect(Array.from(rs.rows[0])).toStrictEqual(["three", "one"]); + }), + ); + + (!isFile ? test : test.skip)( + "?NNN and ? arguments", + withClient(async (c) => { + const rs = await c.execute({ + sql: "SELECT ?2, ?, ?3", + args: ["one", "two", "three"], + }); + expect(Array.from(rs.rows[0])).toStrictEqual([ + "two", + "three", + "three", + ]); + }), ); - } + + for (const sign of [":", "@", "$"]) { + test( + `${sign}AAAA arguments`, + withClient(async (c) => { + const rs = await c.execute({ + sql: `SELECT ${sign}b, ${sign}a`, + args: { a: "one", [`${sign}b`]: "two" }, + }); + expect(Array.from(rs.rows[0])).toStrictEqual(["two", "one"]); + }), + ); + + test( + `${sign}AAAA arguments used multiple times`, + withClient(async (c) => { + const rs = await c.execute({ + sql: `SELECT ${sign}b, ${sign}a, ${sign}b || ${sign}a`, + args: { a: "one", [`${sign}b`]: "two" }, + }); + expect(Array.from(rs.rows[0])).toStrictEqual([ + "two", + "one", + "twoone", + ]); + }), + ); + + test( + `${sign}AAAA arguments and ?NNN arguments`, + withClient(async (c) => { + const rs = await c.execute({ + sql: `SELECT ${sign}b, ${sign}a, ?1`, + args: { a: "one", [`${sign}b`]: "two" }, + }); + expect(Array.from(rs.rows[0])).toStrictEqual([ + "two", + "one", + "two", + ]); + }), + ); + } }); describe("batch()", () => { - test( - "multiple queries", - withClient(async (c) => { - const rss = await c.batch( - [ - "SELECT 1+1", - "SELECT 1 AS one, 2 AS two", - { sql: "SELECT ?", args: ["boomerang"] }, - { sql: "VALUES (?), (?)", args: ["big", "ben"] }, - ], - "read", - ); - - expect(rss.length).toStrictEqual(4); - const [rs0, rs1, rs2, rs3] = rss; - - expect(rs0.rows.length).toStrictEqual(1); - expect(Array.from(rs0.rows[0])).toStrictEqual([2]); - - expect(rs1.rows.length).toStrictEqual(1); - expect(Array.from(rs1.rows[0])).toStrictEqual([1, 2]); - - expect(rs2.rows.length).toStrictEqual(1); - expect(Array.from(rs2.rows[0])).toStrictEqual(["boomerang"]); - - expect(rs3.rows.length).toStrictEqual(2); - expect(Array.from(rs3.rows[0])).toStrictEqual(["big"]); - expect(Array.from(rs3.rows[1])).toStrictEqual(["ben"]); - }), - ); - - test( - "statements are executed sequentially", - withClient(async (c) => { - const rss = await c.batch( - [ - /* 0 */ "DROP TABLE IF EXISTS t", - /* 1 */ "CREATE TABLE t (a, b)", - /* 2 */ "INSERT INTO t VALUES (1, 'one')", - /* 3 */ "SELECT * FROM t ORDER BY a", - /* 4 */ "INSERT INTO t VALUES (2, 'two')", - /* 5 */ "SELECT * FROM t ORDER BY a", - /* 6 */ "DROP TABLE t", - ], - "write", - ); - - expect(rss.length).toStrictEqual(7); - expect(rss[3].rows).toEqual([{ a: 1, b: "one" }]); - expect(rss[5].rows).toEqual([ - { a: 1, b: "one" }, - { a: 2, b: "two" }, - ]); - }), - ); - - test( - "statements are executed in a transaction", - withClient(async (c) => { - await c.batch( - [ - "DROP TABLE IF EXISTS t1", - "DROP TABLE IF EXISTS t2", - "CREATE TABLE t1 (a)", - "CREATE TABLE t2 (a)", - ], - "write", - ); - - const n = 100; - const promises = []; - for (let i = 0; i < n; ++i) { - const ii = i; - promises.push( - (async () => { + test( + "multiple queries", + withClient(async (c) => { const rss = await c.batch( - [ - { sql: "INSERT INTO t1 VALUES (?)", args: [ii] }, - { sql: "INSERT INTO t2 VALUES (?)", args: [ii * 10] }, - "SELECT SUM(a) FROM t1", - "SELECT SUM(a) FROM t2", - ], - "write", + [ + "SELECT 1+1", + "SELECT 1 AS one, 2 AS two", + { sql: "SELECT ?", args: ["boomerang"] }, + { sql: "VALUES (?), (?)", args: ["big", "ben"] }, + ], + "read", ); - const sum1 = rss[2].rows[0][0] as number; - const sum2 = rss[3].rows[0][0] as number; - expect(sum2).toStrictEqual(sum1 * 10); - })(), - ); - } - await Promise.all(promises); - - const rs1 = await c.execute("SELECT SUM(a) FROM t1"); - expect(rs1.rows[0][0]).toStrictEqual((n * (n - 1)) / 2); - const rs2 = await c.execute("SELECT SUM(a) FROM t2"); - expect(rs2.rows[0][0]).toStrictEqual(((n * (n - 1)) / 2) * 10); - }), - 10000, - ); - - test( - "error in batch", - withClient(async (c) => { - await expect( - c.batch(["SELECT 1+1", "SELECT foobar"], "read"), - ).rejects.toBeLibsqlError(); - }), - ); - - test( - "error in batch rolls back transaction", - withClient(async (c) => { - await c.execute("DROP TABLE IF EXISTS t"); - await c.execute("CREATE TABLE t (a)"); - await c.execute("INSERT INTO t VALUES ('one')"); - await expect( - c.batch( - [ - "INSERT INTO t VALUES ('two')", - "SELECT foobar", - "INSERT INTO t VALUES ('three')", - ], - "write", - ), - ).rejects.toBeLibsqlError(); - - const rs = await c.execute("SELECT COUNT(*) FROM t"); - expect(rs.rows[0][0]).toStrictEqual(1); - }), - ); - - test( - "batch with a lot of different statements", - withClient(async (c) => { - const stmts = []; - for (let i = 0; i < 1000; ++i) { - stmts.push(`SELECT ${i}`); - } - const rss = await c.batch(stmts, "read"); - for (let i = 0; i < stmts.length; ++i) { - expect(rss[i].rows[0][0]).toStrictEqual(i); - } - }), - ); - - test( - "batch with a lot of the same statements", - withClient(async (c) => { - const n = 20; - const m = 200; - - const stmts = []; - for (let i = 0; i < n; ++i) { - for (let j = 0; j < m; ++j) { - stmts.push({ sql: `SELECT ?, ${j}`, args: [i] }); - } - } - - const rss = await c.batch(stmts, "read"); - for (let i = 0; i < n; ++i) { - for (let j = 0; j < m; ++j) { - const rs = rss[i * m + j]; - expect(rs.rows[0][0]).toStrictEqual(i); - expect(rs.rows[0][1]).toStrictEqual(j); - } - } - }), - ); - - test( - "deferred batch", - withClient(async (c) => { - const rss = await c.batch( - [ - "SELECT 1+1", - "DROP TABLE IF EXISTS t", - "CREATE TABLE t (a)", - "INSERT INTO t VALUES (21) RETURNING 2*a", - ], - "deferred", - ); - - expect(rss.length).toStrictEqual(4); - const [rs0, _rs1, _rs2, rs3] = rss; - - expect(rs0.rows.length).toStrictEqual(1); - expect(Array.from(rs0.rows[0])).toStrictEqual([2]); - - expect(rs3.rows.length).toStrictEqual(1); - expect(Array.from(rs3.rows[0])).toStrictEqual([42]); - }), - ); - - (hasHrana3 ? test : test.skip)( - "ROLLBACK statement stops execution of batch", - withClient(async (c) => { - await c.execute("DROP TABLE IF EXISTS t"); - await c.execute("CREATE TABLE t (a)"); - - await expect( - c.batch( - [ - "INSERT INTO t VALUES (1), (2), (3)", - "ROLLBACK", - "INSERT INTO t VALUES (4), (5)", - ], - "write", - ), - ).rejects.toBeLibsqlError("TRANSACTION_CLOSED"); + expect(rss.length).toStrictEqual(4); + const [rs0, rs1, rs2, rs3] = rss; + + expect(rs0.rows.length).toStrictEqual(1); + expect(Array.from(rs0.rows[0])).toStrictEqual([2]); + + expect(rs1.rows.length).toStrictEqual(1); + expect(Array.from(rs1.rows[0])).toStrictEqual([1, 2]); - const rs = await c.execute("SELECT COUNT(*) FROM t"); - expect(rs.rows[0][0]).toStrictEqual(0); - }), - ); + expect(rs2.rows.length).toStrictEqual(1); + expect(Array.from(rs2.rows[0])).toStrictEqual(["boomerang"]); + + expect(rs3.rows.length).toStrictEqual(2); + expect(Array.from(rs3.rows[0])).toStrictEqual(["big"]); + expect(Array.from(rs3.rows[1])).toStrictEqual(["ben"]); + }), + ); + + test( + "statements are executed sequentially", + withClient(async (c) => { + const rss = await c.batch( + [ + /* 0 */ "DROP TABLE IF EXISTS t", + /* 1 */ "CREATE TABLE t (a, b)", + /* 2 */ "INSERT INTO t VALUES (1, 'one')", + /* 3 */ "SELECT * FROM t ORDER BY a", + /* 4 */ "INSERT INTO t VALUES (2, 'two')", + /* 5 */ "SELECT * FROM t ORDER BY a", + /* 6 */ "DROP TABLE t", + ], + "write", + ); + + expect(rss.length).toStrictEqual(7); + expect(rss[3].rows).toEqual([{ a: 1, b: "one" }]); + expect(rss[5].rows).toEqual([ + { a: 1, b: "one" }, + { a: 2, b: "two" }, + ]); + }), + ); + + test( + "statements are executed in a transaction", + withClient(async (c) => { + await c.batch( + [ + "DROP TABLE IF EXISTS t1", + "DROP TABLE IF EXISTS t2", + "CREATE TABLE t1 (a)", + "CREATE TABLE t2 (a)", + ], + "write", + ); + + const n = 100; + const promises = []; + for (let i = 0; i < n; ++i) { + const ii = i; + promises.push( + (async () => { + const rss = await c.batch( + [ + { + sql: "INSERT INTO t1 VALUES (?)", + args: [ii], + }, + { + sql: "INSERT INTO t2 VALUES (?)", + args: [ii * 10], + }, + "SELECT SUM(a) FROM t1", + "SELECT SUM(a) FROM t2", + ], + "write", + ); + + const sum1 = rss[2].rows[0][0] as number; + const sum2 = rss[3].rows[0][0] as number; + expect(sum2).toStrictEqual(sum1 * 10); + })(), + ); + } + await Promise.all(promises); + + const rs1 = await c.execute("SELECT SUM(a) FROM t1"); + expect(rs1.rows[0][0]).toStrictEqual((n * (n - 1)) / 2); + const rs2 = await c.execute("SELECT SUM(a) FROM t2"); + expect(rs2.rows[0][0]).toStrictEqual(((n * (n - 1)) / 2) * 10); + }), + 10000, + ); + + test( + "error in batch", + withClient(async (c) => { + await expect( + c.batch(["SELECT 1+1", "SELECT foobar"], "read"), + ).rejects.toBeLibsqlError(); + }), + ); + + test( + "error in batch rolls back transaction", + withClient(async (c) => { + await c.execute("DROP TABLE IF EXISTS t"); + await c.execute("CREATE TABLE t (a)"); + await c.execute("INSERT INTO t VALUES ('one')"); + await expect( + c.batch( + [ + "INSERT INTO t VALUES ('two')", + "SELECT foobar", + "INSERT INTO t VALUES ('three')", + ], + "write", + ), + ).rejects.toBeLibsqlError(); + + const rs = await c.execute("SELECT COUNT(*) FROM t"); + expect(rs.rows[0][0]).toStrictEqual(1); + }), + ); + + test( + "batch with a lot of different statements", + withClient(async (c) => { + const stmts = []; + for (let i = 0; i < 1000; ++i) { + stmts.push(`SELECT ${i}`); + } + const rss = await c.batch(stmts, "read"); + for (let i = 0; i < stmts.length; ++i) { + expect(rss[i].rows[0][0]).toStrictEqual(i); + } + }), + ); + + test( + "batch with a lot of the same statements", + withClient(async (c) => { + const n = 20; + const m = 200; + + const stmts = []; + for (let i = 0; i < n; ++i) { + for (let j = 0; j < m; ++j) { + stmts.push({ sql: `SELECT ?, ${j}`, args: [i] }); + } + } + + const rss = await c.batch(stmts, "read"); + for (let i = 0; i < n; ++i) { + for (let j = 0; j < m; ++j) { + const rs = rss[i * m + j]; + expect(rs.rows[0][0]).toStrictEqual(i); + expect(rs.rows[0][1]).toStrictEqual(j); + } + } + }), + ); + + test( + "deferred batch", + withClient(async (c) => { + const rss = await c.batch( + [ + "SELECT 1+1", + "DROP TABLE IF EXISTS t", + "CREATE TABLE t (a)", + "INSERT INTO t VALUES (21) RETURNING 2*a", + ], + "deferred", + ); + + expect(rss.length).toStrictEqual(4); + const [rs0, _rs1, _rs2, rs3] = rss; + + expect(rs0.rows.length).toStrictEqual(1); + expect(Array.from(rs0.rows[0])).toStrictEqual([2]); + + expect(rs3.rows.length).toStrictEqual(1); + expect(Array.from(rs3.rows[0])).toStrictEqual([42]); + }), + ); + + (hasHrana3 ? test : test.skip)( + "ROLLBACK statement stops execution of batch", + withClient(async (c) => { + await c.execute("DROP TABLE IF EXISTS t"); + await c.execute("CREATE TABLE t (a)"); + + await expect( + c.batch( + [ + "INSERT INTO t VALUES (1), (2), (3)", + "ROLLBACK", + "INSERT INTO t VALUES (4), (5)", + ], + "write", + ), + ).rejects.toBeLibsqlError("TRANSACTION_CLOSED"); + + const rs = await c.execute("SELECT COUNT(*) FROM t"); + expect(rs.rows[0][0]).toStrictEqual(0); + }), + ); }); describe("transaction()", () => { - test( - "query multiple rows", - withClient(async (c) => { - const txn = await c.transaction("read"); - - const rs = await txn.execute( - "VALUES (1, 'one'), (2, 'two'), (3, 'three')", - ); - expect(rs.columns.length).toStrictEqual(2); - expect(rs.columnTypes.length).toStrictEqual(2); - expect(rs.rows.length).toStrictEqual(3); - - expect(Array.from(rs.rows[0])).toStrictEqual([1, "one"]); - expect(Array.from(rs.rows[1])).toStrictEqual([2, "two"]); - expect(Array.from(rs.rows[2])).toStrictEqual([3, "three"]); - - txn.close(); - }), - ); - - test( - "commit()", - withClient(async (c) => { - await c.batch(["DROP TABLE IF EXISTS t", "CREATE TABLE t (a)"], "write"); - - const txn = await c.transaction("write"); - await txn.execute("INSERT INTO t VALUES ('one')"); - await txn.execute("INSERT INTO t VALUES ('two')"); - expect(txn.closed).toStrictEqual(false); - await txn.commit(); - expect(txn.closed).toStrictEqual(true); - - const rs = await c.execute("SELECT COUNT(*) FROM t"); - expect(rs.rows[0][0]).toStrictEqual(2); - await expect(txn.execute("SELECT 1")).rejects.toBeLibsqlError( - "TRANSACTION_CLOSED", - ); - }), - ); - - test( - "rollback()", - withClient(async (c) => { - await c.batch(["DROP TABLE IF EXISTS t", "CREATE TABLE t (a)"], "write"); - - const txn = await c.transaction("write"); - await txn.execute("INSERT INTO t VALUES ('one')"); - await txn.execute("INSERT INTO t VALUES ('two')"); - expect(txn.closed).toStrictEqual(false); - await txn.rollback(); - expect(txn.closed).toStrictEqual(true); - - const rs = await c.execute("SELECT COUNT(*) FROM t"); - expect(rs.rows[0][0]).toStrictEqual(0); - await expect(txn.execute("SELECT 1")).rejects.toBeLibsqlError( - "TRANSACTION_CLOSED", - ); - }), - ); - - test( - "close()", - withClient(async (c) => { - await c.batch(["DROP TABLE IF EXISTS t", "CREATE TABLE t (a)"], "write"); - - const txn = await c.transaction("write"); - await txn.execute("INSERT INTO t VALUES ('one')"); - expect(txn.closed).toStrictEqual(false); - txn.close(); - expect(txn.closed).toStrictEqual(true); - - const rs = await c.execute("SELECT COUNT(*) FROM t"); - expect(rs.rows[0][0]).toStrictEqual(0); - await expect(txn.execute("SELECT 1")).rejects.toBeLibsqlError( - "TRANSACTION_CLOSED", - ); - }), - ); - - test( - "error does not rollback", - withClient(async (c) => { - await c.batch(["DROP TABLE IF EXISTS t", "CREATE TABLE t (a)"], "write"); - - const txn = await c.transaction("write"); - await expect(txn.execute("SELECT foo")).rejects.toBeLibsqlError(); - await txn.execute("INSERT INTO t VALUES ('one')"); - await expect(txn.execute("SELECT bar")).rejects.toBeLibsqlError(); - await txn.commit(); - - const rs = await c.execute("SELECT COUNT(*) FROM t"); - expect(rs.rows[0][0]).toStrictEqual(1); - }), - ); - - (hasHrana3 ? test : test.skip)( - "ROLLBACK statement stops execution of transaction", - withClient(async (c) => { - await c.execute("DROP TABLE IF EXISTS t"); - await c.execute("CREATE TABLE t (a)"); - - const txn = await c.transaction("write"); - const prom1 = txn.execute("INSERT INTO t VALUES (1), (2), (3)"); - const prom2 = txn.execute("ROLLBACK"); - const prom3 = txn.execute("INSERT INTO t VALUES (4), (5)"); - - await prom1; - await prom2; - await expect(prom3).rejects.toBeLibsqlError("TRANSACTION_CLOSED"); - await expect(txn.commit()).rejects.toBeLibsqlError(); - txn.close(); - - const rs = await c.execute("SELECT COUNT(*) FROM t"); - expect(rs.rows[0][0]).toStrictEqual(0); - }), - ); - - (hasHrana3 ? test : test.skip)( - "OR ROLLBACK statement stops execution of transaction", - withClient(async (c) => { - await c.execute("DROP TABLE IF EXISTS t"); - await c.execute("CREATE TABLE t (a UNIQUE)"); - - const txn = await c.transaction("write"); - const prom1 = txn.execute("INSERT INTO t VALUES (1), (2), (3)"); - const prom2 = txn.execute("INSERT OR ROLLBACK INTO t VALUES (1)"); - const prom3 = txn.execute("INSERT INTO t VALUES (4), (5)"); - - await prom1; - await expect(prom2).rejects.toBeLibsqlError(); - await expect(prom3).rejects.toBeLibsqlError("TRANSACTION_CLOSED"); - await expect(txn.commit()).rejects.toBeLibsqlError(); - txn.close(); - - const rs = await c.execute("SELECT COUNT(*) FROM t"); - expect(rs.rows[0][0]).toStrictEqual(0); - }), - ); - - (hasHrana3 ? test : test.skip)( - "OR ROLLBACK as the first statement stops execution of transaction", - withClient(async (c) => { - await c.execute("DROP TABLE IF EXISTS t"); - await c.execute("CREATE TABLE t (a UNIQUE)"); - await c.execute("INSERT INTO t VALUES (1), (2), (3)"); - - const txn = await c.transaction("write"); - const prom1 = txn.execute("INSERT OR ROLLBACK INTO t VALUES (1)"); - const prom2 = txn.execute("INSERT INTO t VALUES (4), (5)"); - - await expect(prom1).rejects.toBeLibsqlError(); - await expect(prom2).rejects.toBeLibsqlError("TRANSACTION_CLOSED"); - await expect(txn.commit()).rejects.toBeLibsqlError(); - txn.close(); - - const rs = await c.execute("SELECT COUNT(*) FROM t"); - expect(rs.rows[0][0]).toStrictEqual(3); - }), - ); - - test( - "commit empty", - withClient(async (c) => { - const txn = await c.transaction("read"); - await txn.commit(); - }), - ); - - test( - "rollback empty", - withClient(async (c) => { - const txn = await c.transaction("read"); - await txn.rollback(); - }), - ); - - describe("batch()", () => { test( - "as the first operation on transaction", - withClient(async (c) => { - const txn = await c.transaction("write"); - - await txn.batch([ - "DROP TABLE IF EXISTS t", - "CREATE TABLE t (a)", - { sql: "INSERT INTO t VALUES (?)", args: [1] }, - { sql: "INSERT INTO t VALUES (?)", args: [2] }, - { sql: "INSERT INTO t VALUES (?)", args: [4] }, - ]); - - const rs = await txn.execute("SELECT SUM(a) FROM t"); - expect(rs.rows[0][0]).toStrictEqual(7); - txn.close(); - }), + "query multiple rows", + withClient(async (c) => { + const txn = await c.transaction("read"); + + const rs = await txn.execute( + "VALUES (1, 'one'), (2, 'two'), (3, 'three')", + ); + expect(rs.columns.length).toStrictEqual(2); + expect(rs.columnTypes.length).toStrictEqual(2); + expect(rs.rows.length).toStrictEqual(3); + + expect(Array.from(rs.rows[0])).toStrictEqual([1, "one"]); + expect(Array.from(rs.rows[1])).toStrictEqual([2, "two"]); + expect(Array.from(rs.rows[2])).toStrictEqual([3, "three"]); + + txn.close(); + }), + ); + + test( + "commit()", + withClient(async (c) => { + await c.batch( + ["DROP TABLE IF EXISTS t", "CREATE TABLE t (a)"], + "write", + ); + + const txn = await c.transaction("write"); + await txn.execute("INSERT INTO t VALUES ('one')"); + await txn.execute("INSERT INTO t VALUES ('two')"); + expect(txn.closed).toStrictEqual(false); + await txn.commit(); + expect(txn.closed).toStrictEqual(true); + + const rs = await c.execute("SELECT COUNT(*) FROM t"); + expect(rs.rows[0][0]).toStrictEqual(2); + await expect(txn.execute("SELECT 1")).rejects.toBeLibsqlError( + "TRANSACTION_CLOSED", + ); + }), + ); + + test( + "rollback()", + withClient(async (c) => { + await c.batch( + ["DROP TABLE IF EXISTS t", "CREATE TABLE t (a)"], + "write", + ); + + const txn = await c.transaction("write"); + await txn.execute("INSERT INTO t VALUES ('one')"); + await txn.execute("INSERT INTO t VALUES ('two')"); + expect(txn.closed).toStrictEqual(false); + await txn.rollback(); + expect(txn.closed).toStrictEqual(true); + + const rs = await c.execute("SELECT COUNT(*) FROM t"); + expect(rs.rows[0][0]).toStrictEqual(0); + await expect(txn.execute("SELECT 1")).rejects.toBeLibsqlError( + "TRANSACTION_CLOSED", + ); + }), + ); + + test( + "close()", + withClient(async (c) => { + await c.batch( + ["DROP TABLE IF EXISTS t", "CREATE TABLE t (a)"], + "write", + ); + + const txn = await c.transaction("write"); + await txn.execute("INSERT INTO t VALUES ('one')"); + expect(txn.closed).toStrictEqual(false); + txn.close(); + expect(txn.closed).toStrictEqual(true); + + const rs = await c.execute("SELECT COUNT(*) FROM t"); + expect(rs.rows[0][0]).toStrictEqual(0); + await expect(txn.execute("SELECT 1")).rejects.toBeLibsqlError( + "TRANSACTION_CLOSED", + ); + }), ); test( - "as the second operation on transaction", - withClient(async (c) => { - const txn = await c.transaction("write"); - - await txn.execute("DROP TABLE IF EXISTS t"); - await txn.batch([ - "CREATE TABLE t (a)", - { sql: "INSERT INTO t VALUES (?)", args: [1] }, - { sql: "INSERT INTO t VALUES (?)", args: [2] }, - { sql: "INSERT INTO t VALUES (?)", args: [4] }, - ]); - - const rs = await txn.execute("SELECT SUM(a) FROM t"); - expect(rs.rows[0][0]).toStrictEqual(7); - txn.close(); - }), + "error does not rollback", + withClient(async (c) => { + await c.batch( + ["DROP TABLE IF EXISTS t", "CREATE TABLE t (a)"], + "write", + ); + + const txn = await c.transaction("write"); + await expect(txn.execute("SELECT foo")).rejects.toBeLibsqlError(); + await txn.execute("INSERT INTO t VALUES ('one')"); + await expect(txn.execute("SELECT bar")).rejects.toBeLibsqlError(); + await txn.commit(); + + const rs = await c.execute("SELECT COUNT(*) FROM t"); + expect(rs.rows[0][0]).toStrictEqual(1); + }), + ); + + (hasHrana3 ? test : test.skip)( + "ROLLBACK statement stops execution of transaction", + withClient(async (c) => { + await c.execute("DROP TABLE IF EXISTS t"); + await c.execute("CREATE TABLE t (a)"); + + const txn = await c.transaction("write"); + const prom1 = txn.execute("INSERT INTO t VALUES (1), (2), (3)"); + const prom2 = txn.execute("ROLLBACK"); + const prom3 = txn.execute("INSERT INTO t VALUES (4), (5)"); + + await prom1; + await prom2; + await expect(prom3).rejects.toBeLibsqlError("TRANSACTION_CLOSED"); + await expect(txn.commit()).rejects.toBeLibsqlError(); + txn.close(); + + const rs = await c.execute("SELECT COUNT(*) FROM t"); + expect(rs.rows[0][0]).toStrictEqual(0); + }), + ); + + (hasHrana3 ? test : test.skip)( + "OR ROLLBACK statement stops execution of transaction", + withClient(async (c) => { + await c.execute("DROP TABLE IF EXISTS t"); + await c.execute("CREATE TABLE t (a UNIQUE)"); + + const txn = await c.transaction("write"); + const prom1 = txn.execute("INSERT INTO t VALUES (1), (2), (3)"); + const prom2 = txn.execute("INSERT OR ROLLBACK INTO t VALUES (1)"); + const prom3 = txn.execute("INSERT INTO t VALUES (4), (5)"); + + await prom1; + await expect(prom2).rejects.toBeLibsqlError(); + await expect(prom3).rejects.toBeLibsqlError("TRANSACTION_CLOSED"); + await expect(txn.commit()).rejects.toBeLibsqlError(); + txn.close(); + + const rs = await c.execute("SELECT COUNT(*) FROM t"); + expect(rs.rows[0][0]).toStrictEqual(0); + }), + ); + + (hasHrana3 ? test : test.skip)( + "OR ROLLBACK as the first statement stops execution of transaction", + withClient(async (c) => { + await c.execute("DROP TABLE IF EXISTS t"); + await c.execute("CREATE TABLE t (a UNIQUE)"); + await c.execute("INSERT INTO t VALUES (1), (2), (3)"); + + const txn = await c.transaction("write"); + const prom1 = txn.execute("INSERT OR ROLLBACK INTO t VALUES (1)"); + const prom2 = txn.execute("INSERT INTO t VALUES (4), (5)"); + + await expect(prom1).rejects.toBeLibsqlError(); + await expect(prom2).rejects.toBeLibsqlError("TRANSACTION_CLOSED"); + await expect(txn.commit()).rejects.toBeLibsqlError(); + txn.close(); + + const rs = await c.execute("SELECT COUNT(*) FROM t"); + expect(rs.rows[0][0]).toStrictEqual(3); + }), ); test( - "after error, further statements are not executed", - withClient(async (c) => { - const txn = await c.transaction("write"); - - await expect( - txn.batch([ - "DROP TABLE IF EXISTS t", - "CREATE TABLE t (a UNIQUE)", - "INSERT INTO t VALUES (1), (2), (4)", - "INSERT INTO t VALUES (1)", - "INSERT INTO t VALUES (8), (16)", - ]), - ).rejects.toBeLibsqlError(); - - const rs = await txn.execute("SELECT SUM(a) FROM t"); - expect(rs.rows[0][0]).toStrictEqual(7); - - await txn.commit(); - }), + "commit empty", + withClient(async (c) => { + const txn = await c.transaction("read"); + await txn.commit(); + }), ); - }); - (hasHrana2 ? describe : describe.skip)("executeMultiple()", () => { test( - "as the first operation on transaction", - withClient(async (c) => { - const txn = await c.transaction("write"); + "rollback empty", + withClient(async (c) => { + const txn = await c.transaction("read"); + await txn.rollback(); + }), + ); + + describe("batch()", () => { + test( + "as the first operation on transaction", + withClient(async (c) => { + const txn = await c.transaction("write"); + + await txn.batch([ + "DROP TABLE IF EXISTS t", + "CREATE TABLE t (a)", + { sql: "INSERT INTO t VALUES (?)", args: [1] }, + { sql: "INSERT INTO t VALUES (?)", args: [2] }, + { sql: "INSERT INTO t VALUES (?)", args: [4] }, + ]); + + const rs = await txn.execute("SELECT SUM(a) FROM t"); + expect(rs.rows[0][0]).toStrictEqual(7); + txn.close(); + }), + ); - await txn.executeMultiple(` + test( + "as the second operation on transaction", + withClient(async (c) => { + const txn = await c.transaction("write"); + + await txn.execute("DROP TABLE IF EXISTS t"); + await txn.batch([ + "CREATE TABLE t (a)", + { sql: "INSERT INTO t VALUES (?)", args: [1] }, + { sql: "INSERT INTO t VALUES (?)", args: [2] }, + { sql: "INSERT INTO t VALUES (?)", args: [4] }, + ]); + + const rs = await txn.execute("SELECT SUM(a) FROM t"); + expect(rs.rows[0][0]).toStrictEqual(7); + txn.close(); + }), + ); + + test( + "after error, further statements are not executed", + withClient(async (c) => { + const txn = await c.transaction("write"); + + await expect( + txn.batch([ + "DROP TABLE IF EXISTS t", + "CREATE TABLE t (a UNIQUE)", + "INSERT INTO t VALUES (1), (2), (4)", + "INSERT INTO t VALUES (1)", + "INSERT INTO t VALUES (8), (16)", + ]), + ).rejects.toBeLibsqlError(); + + const rs = await txn.execute("SELECT SUM(a) FROM t"); + expect(rs.rows[0][0]).toStrictEqual(7); + + await txn.commit(); + }), + ); + }); + + (hasHrana2 ? describe : describe.skip)("executeMultiple()", () => { + test( + "as the first operation on transaction", + withClient(async (c) => { + const txn = await c.transaction("write"); + + await txn.executeMultiple(` DROP TABLE IF EXISTS t; CREATE TABLE t (a); INSERT INTO t VALUES (1), (2), (4), (8); `); - const rs = await txn.execute("SELECT SUM(a) FROM t"); - expect(rs.rows[0][0]).toStrictEqual(15); - txn.close(); - }), - ); + const rs = await txn.execute("SELECT SUM(a) FROM t"); + expect(rs.rows[0][0]).toStrictEqual(15); + txn.close(); + }), + ); - test( - "as the second operation on transaction", - withClient(async (c) => { - const txn = await c.transaction("write"); - await txn.execute("DROP TABLE IF EXISTS t"); - await txn.executeMultiple(` + test( + "as the second operation on transaction", + withClient(async (c) => { + const txn = await c.transaction("write"); + await txn.execute("DROP TABLE IF EXISTS t"); + await txn.executeMultiple(` CREATE TABLE t (a); INSERT INTO t VALUES (1), (2), (4), (8); `); - const rs = await txn.execute("SELECT SUM(a) FROM t"); - expect(rs.rows[0][0]).toStrictEqual(15); - txn.close(); - }), - ); + const rs = await txn.execute("SELECT SUM(a) FROM t"); + expect(rs.rows[0][0]).toStrictEqual(15); + txn.close(); + }), + ); - test( - "after error, further statements are not executed", - withClient(async (c) => { - const txn = await c.transaction("write"); + test( + "after error, further statements are not executed", + withClient(async (c) => { + const txn = await c.transaction("write"); - await expect( - txn.executeMultiple(` + await expect( + txn.executeMultiple(` DROP TABLE IF EXISTS t; CREATE TABLE t (a UNIQUE); INSERT INTO t VALUES (1), (2), (4); INSERT INTO t VALUES (1); INSERT INTO t VALUES (8), (16); `), - ).rejects.toBeLibsqlError(); + ).rejects.toBeLibsqlError(); - const rs = await txn.execute("SELECT SUM(a) FROM t"); - expect(rs.rows[0][0]).toStrictEqual(7); + const rs = await txn.execute("SELECT SUM(a) FROM t"); + expect(rs.rows[0][0]).toStrictEqual(7); - await txn.commit(); - }), - ); - }); + await txn.commit(); + }), + ); + }); }); (hasHrana2 ? describe : describe.skip)("executeMultiple()", () => { - test( - "multiple statements", - withClient(async (c) => { - await c.executeMultiple(` + test( + "multiple statements", + withClient(async (c) => { + await c.executeMultiple(` DROP TABLE IF EXISTS t; CREATE TABLE t (a); INSERT INTO t VALUES (1), (2), (4), (8); `); - const rs = await c.execute("SELECT SUM(a) FROM t"); - expect(rs.rows[0][0]).toStrictEqual(15); - }), - ); + const rs = await c.execute("SELECT SUM(a) FROM t"); + expect(rs.rows[0][0]).toStrictEqual(15); + }), + ); - test( - "after an error, statements are not executed", - withClient(async (c) => { - await expect( - c.executeMultiple(` + test( + "after an error, statements are not executed", + withClient(async (c) => { + await expect( + c.executeMultiple(` DROP TABLE IF EXISTS t; CREATE TABLE t (a); INSERT INTO t VALUES (1), (2), (4); INSERT INTO t VALUES (foo()); INSERT INTO t VALUES (8), (16); `), - ).rejects.toBeLibsqlError(); + ).rejects.toBeLibsqlError(); - const rs = await c.execute("SELECT SUM(a) FROM t"); - expect(rs.rows[0][0]).toStrictEqual(7); - }), - ); + const rs = await c.execute("SELECT SUM(a) FROM t"); + expect(rs.rows[0][0]).toStrictEqual(7); + }), + ); - test( - "manual transaction control statements", - withClient(async (c) => { - await c.executeMultiple(` + test( + "manual transaction control statements", + withClient(async (c) => { + await c.executeMultiple(` DROP TABLE IF EXISTS t; CREATE TABLE t (a); BEGIN; @@ -1217,16 +1281,16 @@ describe("transaction()", () => { COMMIT; `); - const rs = await c.execute("SELECT SUM(a) FROM t"); - expect(rs.rows[0][0]).toStrictEqual(31); - }), - ); + const rs = await c.execute("SELECT SUM(a) FROM t"); + expect(rs.rows[0][0]).toStrictEqual(31); + }), + ); - test( - "error rolls back a manual transaction", - withClient(async (c) => { - await expect( - c.executeMultiple(` + test( + "error rolls back a manual transaction", + withClient(async (c) => { + await expect( + c.executeMultiple(` DROP TABLE IF EXISTS t; CREATE TABLE t (a); INSERT INTO t VALUES (0); @@ -1236,85 +1300,91 @@ describe("transaction()", () => { INSERT INTO t VALUES (8), (16); COMMIT; `), - ).rejects.toBeLibsqlError(); + ).rejects.toBeLibsqlError(); - const rs = await c.execute("SELECT SUM(a) FROM t"); - expect(rs.rows[0][0]).toStrictEqual(0); - }), - ); + const rs = await c.execute("SELECT SUM(a) FROM t"); + expect(rs.rows[0][0]).toStrictEqual(0); + }), + ); }); (hasNetworkErrors ? describe : describe.skip)("network errors", () => { - const testCases = [ - { title: "WebSocket close", sql: ".close_ws" }, - { title: "TCP close", sql: ".close_tcp" }, - ]; + const testCases = [ + { title: "WebSocket close", sql: ".close_ws" }, + { title: "TCP close", sql: ".close_tcp" }, + ]; - for (const { title, sql } of testCases) { - test( - `${title} in execute()`, - withClient(async (c) => { - await expect(c.execute(sql)).rejects.toBeLibsqlError( - "HRANA_WEBSOCKET_ERROR", + for (const { title, sql } of testCases) { + test( + `${title} in execute()`, + withClient(async (c) => { + await expect(c.execute(sql)).rejects.toBeLibsqlError( + "HRANA_WEBSOCKET_ERROR", + ); + + expect((await c.execute("SELECT 42")).rows[0][0]).toStrictEqual( + 42, + ); + }), ); - expect((await c.execute("SELECT 42")).rows[0][0]).toStrictEqual(42); - }), - ); - - test( - `${title} in transaction()`, - withClient(async (c) => { - const txn = await c.transaction("read"); - await expect(txn.execute(sql)).rejects.toBeLibsqlError( - "HRANA_WEBSOCKET_ERROR", - ); - await expect(txn.commit()).rejects.toBeLibsqlError( - "TRANSACTION_CLOSED", + test( + `${title} in transaction()`, + withClient(async (c) => { + const txn = await c.transaction("read"); + await expect(txn.execute(sql)).rejects.toBeLibsqlError( + "HRANA_WEBSOCKET_ERROR", + ); + await expect(txn.commit()).rejects.toBeLibsqlError( + "TRANSACTION_CLOSED", + ); + txn.close(); + + expect((await c.execute("SELECT 42")).rows[0][0]).toStrictEqual( + 42, + ); + }), ); - txn.close(); - expect((await c.execute("SELECT 42")).rows[0][0]).toStrictEqual(42); - }), - ); + test( + `${title} in batch()`, + withClient(async (c) => { + await expect( + c.batch(["SELECT 42", sql, "SELECT 24"], "read"), + ).rejects.toBeLibsqlError("HRANA_WEBSOCKET_ERROR"); - test( - `${title} in batch()`, - withClient(async (c) => { - await expect( - c.batch(["SELECT 42", sql, "SELECT 24"], "read"), - ).rejects.toBeLibsqlError("HRANA_WEBSOCKET_ERROR"); - - expect((await c.execute("SELECT 42")).rows[0][0]).toStrictEqual(42); - }), - ); - } + expect((await c.execute("SELECT 42")).rows[0][0]).toStrictEqual( + 42, + ); + }), + ); + } }); (isHttp ? test : test.skip)("custom fetch", async () => { - let fetchCalledCount = 0; - function customFetch(request: Request): Promise { - fetchCalledCount += 1; - return fetch(request); - } - - const c = createClient({ ...config, fetch: customFetch }); - try { - const rs = await c.execute("SELECT 42"); - expect(rs.rows[0][0]).toStrictEqual(42); - expect(fetchCalledCount).toBeGreaterThan(0); - } finally { - c.close(); - } + let fetchCalledCount = 0; + function customFetch(request: Request): Promise { + fetchCalledCount += 1; + return fetch(request); + } + + const c = createClient({ ...config, fetch: customFetch }); + try { + const rs = await c.execute("SELECT 42"); + expect(rs.rows[0][0]).toStrictEqual(42); + expect(fetchCalledCount).toBeGreaterThan(0); + } finally { + c.close(); + } }); (isFile ? test : test.skip)("raw error codes", async () => { - const c = createClient(config); - try { - await expect(c.execute("NOT A VALID SQL")).rejects.toThrow( - expect.toBeLibsqlError({ code: "SQLITE_ERROR", rawCode: 1 }), - ); - } finally { - c.close(); - } + const c = createClient(config); + try { + await expect(c.execute("NOT A VALID SQL")).rejects.toThrow( + expect.toBeLibsqlError({ code: "SQLITE_ERROR", rawCode: 1 }), + ); + } finally { + c.close(); + } }); diff --git a/packages/libsql-client/src/__tests__/helpers.ts b/packages/libsql-client/src/__tests__/helpers.ts index baf26e34..ad8b5207 100644 --- a/packages/libsql-client/src/__tests__/helpers.ts +++ b/packages/libsql-client/src/__tests__/helpers.ts @@ -4,53 +4,53 @@ import type { MatcherFunction } from "expect"; import { LibsqlError } from "../node.js"; type CodeMatch = { - code: string; - rawCode: number; + code: string; + rawCode: number; }; const toBeLibsqlError: MatcherFunction< - [code?: string | CodeMatch, message?: RegExp] + [code?: string | CodeMatch, message?: RegExp] > = function (actual, code?, messageRe?) { - const pass = - actual instanceof LibsqlError && - isValidCode(actual, code) && - (messageRe === undefined || actual.message.match(messageRe) !== null); + const pass = + actual instanceof LibsqlError && + isValidCode(actual, code) && + (messageRe === undefined || actual.message.match(messageRe) !== null); - const message = (): string => { - const parts = []; - parts.push("expected "); - parts.push(this.utils.printReceived(actual)); - parts.push(pass ? " not to be " : " to be "); - parts.push("an instance of LibsqlError"); - if (code !== undefined) { - parts.push(" with error code "); - parts.push(this.utils.printExpected(code)); - } - if (messageRe !== undefined) { - parts.push(" with error message matching "); - parts.push(this.utils.printExpected(messageRe)); - } - return parts.join(""); - }; + const message = (): string => { + const parts = []; + parts.push("expected "); + parts.push(this.utils.printReceived(actual)); + parts.push(pass ? " not to be " : " to be "); + parts.push("an instance of LibsqlError"); + if (code !== undefined) { + parts.push(" with error code "); + parts.push(this.utils.printExpected(code)); + } + if (messageRe !== undefined) { + parts.push(" with error message matching "); + parts.push(this.utils.printExpected(messageRe)); + } + return parts.join(""); + }; - return { pass, message }; + return { pass, message }; }; const isValidCode = (error: LibsqlError, code?: string | CodeMatch) => { - if (code === undefined) { - return true; - } - if (typeof code === "string") { - return error.code === code; - } - return error.code === code.code && error.rawCode === code.rawCode; + if (code === undefined) { + return true; + } + if (typeof code === "string") { + return error.code === code; + } + return error.code === code.code && error.rawCode === code.rawCode; }; expect.extend({ toBeLibsqlError }); declare module "expect" { - interface AsymmetricMatchers { - toBeLibsqlError(code?: string | CodeMatch, messageRe?: RegExp): void; - } - interface Matchers { - toBeLibsqlError(code?: string | CodeMatch, messageRe?: RegExp): R; - } + interface AsymmetricMatchers { + toBeLibsqlError(code?: string | CodeMatch, messageRe?: RegExp): void; + } + interface Matchers { + toBeLibsqlError(code?: string | CodeMatch, messageRe?: RegExp): R; + } } diff --git a/packages/libsql-client/src/__tests__/uri.test.ts b/packages/libsql-client/src/__tests__/uri.test.ts index 4088c808..a8af85a9 100644 --- a/packages/libsql-client/src/__tests__/uri.test.ts +++ b/packages/libsql-client/src/__tests__/uri.test.ts @@ -6,257 +6,257 @@ import "./helpers.js"; import { parseUri, encodeBaseUrl } from "@libsql/core/uri"; describe("parseUri()", () => { - test("authority and path", () => { - const cases = [ - { text: "file://localhost", path: "" }, - { text: "file://localhost/", path: "/" }, - { text: "file://localhost/absolute/path", path: "/absolute/path" }, - { text: "file://localhost/k%C5%AF%C5%88", path: "/kůň" }, - ]; - for (const { text, path } of cases) { - expect(parseUri(text)).toEqual({ - scheme: "file", - authority: { host: "localhost" }, - path, - }); - } - }); + test("authority and path", () => { + const cases = [ + { text: "file://localhost", path: "" }, + { text: "file://localhost/", path: "/" }, + { text: "file://localhost/absolute/path", path: "/absolute/path" }, + { text: "file://localhost/k%C5%AF%C5%88", path: "/kůň" }, + ]; + for (const { text, path } of cases) { + expect(parseUri(text)).toEqual({ + scheme: "file", + authority: { host: "localhost" }, + path, + }); + } + }); - test("empty authority and path", () => { - const cases = [ - { text: "file:///absolute/path", path: "/absolute/path" }, - { text: "file://", path: "" }, - { text: "file:///k%C5%AF%C5%88", path: "/kůň" }, - ]; - for (const { text, path } of cases) { - expect(parseUri(text)).toEqual({ - scheme: "file", - authority: { host: "" }, - path, - }); - } - }); + test("empty authority and path", () => { + const cases = [ + { text: "file:///absolute/path", path: "/absolute/path" }, + { text: "file://", path: "" }, + { text: "file:///k%C5%AF%C5%88", path: "/kůň" }, + ]; + for (const { text, path } of cases) { + expect(parseUri(text)).toEqual({ + scheme: "file", + authority: { host: "" }, + path, + }); + } + }); - test("no authority and path", () => { - const cases = [ - { text: "file:/absolute/path", path: "/absolute/path" }, - { text: "file:relative/path", path: "relative/path" }, - { text: "file:", path: "" }, - { text: "file:C:/path/to/file", path: "C:/path/to/file" }, - { text: "file:k%C5%AF%C5%88", path: "kůň" }, - ]; - for (const { text, path } of cases) { - expect(parseUri(text)).toEqual({ - scheme: "file", - path, - }); - } - }); + test("no authority and path", () => { + const cases = [ + { text: "file:/absolute/path", path: "/absolute/path" }, + { text: "file:relative/path", path: "relative/path" }, + { text: "file:", path: "" }, + { text: "file:C:/path/to/file", path: "C:/path/to/file" }, + { text: "file:k%C5%AF%C5%88", path: "kůň" }, + ]; + for (const { text, path } of cases) { + expect(parseUri(text)).toEqual({ + scheme: "file", + path, + }); + } + }); - test("authority", () => { - const hosts = [ - { text: "localhost", host: "localhost" }, - { text: "domain.name", host: "domain.name" }, - { text: "some$weird.%20!name", host: "some$weird. !name" }, - { text: "1.20.255.99", host: "1.20.255.99" }, - { text: "[2001:4860:4802:32::a]", host: "2001:4860:4802:32::a" }, - { text: "%61", host: "a" }, - { text: "100%2e100%2e100%2e100", host: "100.100.100.100" }, - { text: "k%C5%AF%C5%88", host: "kůň" }, - ]; - const ports = [ - { text: "", port: undefined }, - { text: ":", port: undefined }, - { text: ":0", port: 0 }, - { text: ":99", port: 99 }, - { text: ":65535", port: 65535 }, - ]; - const userinfos = [ - { text: "", userinfo: undefined }, - { text: "@", userinfo: { username: "" } }, - { text: "alice@", userinfo: { username: "alice" } }, - { - text: "alice:secret@", - userinfo: { username: "alice", password: "secret" }, - }, - { - text: "alice:sec:et@", - userinfo: { username: "alice", password: "sec:et" }, - }, - { text: "alice%3Asecret@", userinfo: { username: "alice:secret" } }, - { - text: "alice:s%65cret@", - userinfo: { username: "alice", password: "secret" }, - }, - ]; + test("authority", () => { + const hosts = [ + { text: "localhost", host: "localhost" }, + { text: "domain.name", host: "domain.name" }, + { text: "some$weird.%20!name", host: "some$weird. !name" }, + { text: "1.20.255.99", host: "1.20.255.99" }, + { text: "[2001:4860:4802:32::a]", host: "2001:4860:4802:32::a" }, + { text: "%61", host: "a" }, + { text: "100%2e100%2e100%2e100", host: "100.100.100.100" }, + { text: "k%C5%AF%C5%88", host: "kůň" }, + ]; + const ports = [ + { text: "", port: undefined }, + { text: ":", port: undefined }, + { text: ":0", port: 0 }, + { text: ":99", port: 99 }, + { text: ":65535", port: 65535 }, + ]; + const userinfos = [ + { text: "", userinfo: undefined }, + { text: "@", userinfo: { username: "" } }, + { text: "alice@", userinfo: { username: "alice" } }, + { + text: "alice:secret@", + userinfo: { username: "alice", password: "secret" }, + }, + { + text: "alice:sec:et@", + userinfo: { username: "alice", password: "sec:et" }, + }, + { text: "alice%3Asecret@", userinfo: { username: "alice:secret" } }, + { + text: "alice:s%65cret@", + userinfo: { username: "alice", password: "secret" }, + }, + ]; - for (const { text: hostText, host } of hosts) { - for (const { text: portText, port } of ports) { - for (const { text: userText, userinfo } of userinfos) { - const text = `http://${userText}${hostText}${portText}`; - expect(parseUri(text)).toEqual({ - scheme: "http", - authority: { host, port, userinfo }, - path: "", - }); + for (const { text: hostText, host } of hosts) { + for (const { text: portText, port } of ports) { + for (const { text: userText, userinfo } of userinfos) { + const text = `http://${userText}${hostText}${portText}`; + expect(parseUri(text)).toEqual({ + scheme: "http", + authority: { host, port, userinfo }, + path: "", + }); + } + } } - } - } - }); + }); - test("query", () => { - const cases = [ - { text: "?", pairs: [] }, - { text: "?key=value", pairs: [{ key: "key", value: "value" }] }, - { text: "?&key=value", pairs: [{ key: "key", value: "value" }] }, - { text: "?key=value&&", pairs: [{ key: "key", value: "value" }] }, - { text: "?a", pairs: [{ key: "a", value: "" }] }, - { text: "?a=", pairs: [{ key: "a", value: "" }] }, - { text: "?=a", pairs: [{ key: "", value: "a" }] }, - { text: "?=", pairs: [{ key: "", value: "" }] }, - { text: "?a=b=c", pairs: [{ key: "a", value: "b=c" }] }, - { - text: "?a=b&c=d", - pairs: [ - { key: "a", value: "b" }, - { key: "c", value: "d" }, - ], - }, - { text: "?a+b=c", pairs: [{ key: "a b", value: "c" }] }, - { text: "?a=b+c", pairs: [{ key: "a", value: "b c" }] }, - { text: "?a?b", pairs: [{ key: "a?b", value: "" }] }, - { text: "?%61=%62", pairs: [{ key: "a", value: "b" }] }, - { text: "?a%3db", pairs: [{ key: "a=b", value: "" }] }, - { text: "?a=%2b", pairs: [{ key: "a", value: "+" }] }, - { text: "?%2b=b", pairs: [{ key: "+", value: "b" }] }, - { text: "?a=b%26c", pairs: [{ key: "a", value: "b&c" }] }, - { text: "?a=k%C5%AF%C5%88", pairs: [{ key: "a", value: "kůň" }] }, - ]; - for (const { text: queryText, pairs } of cases) { - const text = `file:${queryText}`; - expect(parseUri(text)).toEqual({ - scheme: "file", - path: "", - query: { pairs }, - }); - } - }); + test("query", () => { + const cases = [ + { text: "?", pairs: [] }, + { text: "?key=value", pairs: [{ key: "key", value: "value" }] }, + { text: "?&key=value", pairs: [{ key: "key", value: "value" }] }, + { text: "?key=value&&", pairs: [{ key: "key", value: "value" }] }, + { text: "?a", pairs: [{ key: "a", value: "" }] }, + { text: "?a=", pairs: [{ key: "a", value: "" }] }, + { text: "?=a", pairs: [{ key: "", value: "a" }] }, + { text: "?=", pairs: [{ key: "", value: "" }] }, + { text: "?a=b=c", pairs: [{ key: "a", value: "b=c" }] }, + { + text: "?a=b&c=d", + pairs: [ + { key: "a", value: "b" }, + { key: "c", value: "d" }, + ], + }, + { text: "?a+b=c", pairs: [{ key: "a b", value: "c" }] }, + { text: "?a=b+c", pairs: [{ key: "a", value: "b c" }] }, + { text: "?a?b", pairs: [{ key: "a?b", value: "" }] }, + { text: "?%61=%62", pairs: [{ key: "a", value: "b" }] }, + { text: "?a%3db", pairs: [{ key: "a=b", value: "" }] }, + { text: "?a=%2b", pairs: [{ key: "a", value: "+" }] }, + { text: "?%2b=b", pairs: [{ key: "+", value: "b" }] }, + { text: "?a=b%26c", pairs: [{ key: "a", value: "b&c" }] }, + { text: "?a=k%C5%AF%C5%88", pairs: [{ key: "a", value: "kůň" }] }, + ]; + for (const { text: queryText, pairs } of cases) { + const text = `file:${queryText}`; + expect(parseUri(text)).toEqual({ + scheme: "file", + path: "", + query: { pairs }, + }); + } + }); - test("fragment", () => { - const cases = [ - { text: "", fragment: undefined }, - { text: "#a", fragment: "a" }, - { text: "#a?b", fragment: "a?b" }, - { text: "#%61", fragment: "a" }, - { text: "#k%C5%AF%C5%88", fragment: "kůň" }, - ]; - for (const { text: fragmentText, fragment } of cases) { - const text = `file:${fragmentText}`; - expect(parseUri(text)).toEqual({ - scheme: "file", - path: "", - fragment, - }); - } - }); + test("fragment", () => { + const cases = [ + { text: "", fragment: undefined }, + { text: "#a", fragment: "a" }, + { text: "#a?b", fragment: "a?b" }, + { text: "#%61", fragment: "a" }, + { text: "#k%C5%AF%C5%88", fragment: "kůň" }, + ]; + for (const { text: fragmentText, fragment } of cases) { + const text = `file:${fragmentText}`; + expect(parseUri(text)).toEqual({ + scheme: "file", + path: "", + fragment, + }); + } + }); - test("parse errors", () => { - const cases = [ - { text: "", message: /format/ }, - { text: "foo", message: /format/ }, - { text: "foo.bar.com", message: /format/ }, - { text: "h$$p://localhost", message: /format/ }, - { text: "h%74%74p://localhost", message: /format/ }, - { text: "http://localhost:%38%38", message: /authority/ }, - { text: "file:k%C5%C5%88", message: /percent encoding/ }, - ]; + test("parse errors", () => { + const cases = [ + { text: "", message: /format/ }, + { text: "foo", message: /format/ }, + { text: "foo.bar.com", message: /format/ }, + { text: "h$$p://localhost", message: /format/ }, + { text: "h%74%74p://localhost", message: /format/ }, + { text: "http://localhost:%38%38", message: /authority/ }, + { text: "file:k%C5%C5%88", message: /percent encoding/ }, + ]; - for (const { text, message } of cases) { - expect(() => parseUri(text)).toThrow( - expect.toBeLibsqlError("URL_INVALID", message), - ); - } - }); + for (const { text, message } of cases) { + expect(() => parseUri(text)).toThrow( + expect.toBeLibsqlError("URL_INVALID", message), + ); + } + }); }); test("encodeBaseUrl()", () => { - const cases = [ - { - scheme: "http", - host: "localhost", - path: "", - url: "http://localhost", - }, - { - scheme: "http", - host: "localhost", - path: "/", - url: "http://localhost/", - }, - { - scheme: "http", - host: "localhost", - port: 8080, - path: "", - url: "http://localhost:8080", - }, - { - scheme: "http", - host: "localhost", - path: "/foo/bar", - url: "http://localhost/foo/bar", - }, - { - scheme: "http", - host: "localhost", - path: "foo/bar", - url: "http://localhost/foo/bar", - }, - { - scheme: "http", - host: "some.long.domain.name", - path: "", - url: "http://some.long.domain.name", - }, - { - scheme: "http", - host: "1.2.3.4", - path: "", - url: "http://1.2.3.4", - }, - { - scheme: "http", - host: "2001:4860:4802:32::a", - path: "", - url: "http://[2001:4860:4802:32::a]", - }, - { - scheme: "http", - host: "localhost", - userinfo: { username: "alice", password: undefined }, - path: "", - url: "http://alice@localhost", - }, - { - scheme: "http", - host: "localhost", - userinfo: { username: "alice", password: "secr:t" }, - path: "", - url: "http://alice:secr%3At@localhost", - }, - { - scheme: "https", - host: "localhost", - userinfo: { username: "alice", password: "secret" }, - port: 8080, - path: "/some/path", - url: "https://alice:secret@localhost:8080/some/path", - }, - ]; + const cases = [ + { + scheme: "http", + host: "localhost", + path: "", + url: "http://localhost", + }, + { + scheme: "http", + host: "localhost", + path: "/", + url: "http://localhost/", + }, + { + scheme: "http", + host: "localhost", + port: 8080, + path: "", + url: "http://localhost:8080", + }, + { + scheme: "http", + host: "localhost", + path: "/foo/bar", + url: "http://localhost/foo/bar", + }, + { + scheme: "http", + host: "localhost", + path: "foo/bar", + url: "http://localhost/foo/bar", + }, + { + scheme: "http", + host: "some.long.domain.name", + path: "", + url: "http://some.long.domain.name", + }, + { + scheme: "http", + host: "1.2.3.4", + path: "", + url: "http://1.2.3.4", + }, + { + scheme: "http", + host: "2001:4860:4802:32::a", + path: "", + url: "http://[2001:4860:4802:32::a]", + }, + { + scheme: "http", + host: "localhost", + userinfo: { username: "alice", password: undefined }, + path: "", + url: "http://alice@localhost", + }, + { + scheme: "http", + host: "localhost", + userinfo: { username: "alice", password: "secr:t" }, + path: "", + url: "http://alice:secr%3At@localhost", + }, + { + scheme: "https", + host: "localhost", + userinfo: { username: "alice", password: "secret" }, + port: 8080, + path: "/some/path", + url: "https://alice:secret@localhost:8080/some/path", + }, + ]; - for (const { scheme, host, port, userinfo, path, url } of cases) { - expect(encodeBaseUrl(scheme, { host, port, userinfo }, path)).toStrictEqual( - new URL(url), - ); - } + for (const { scheme, host, port, userinfo, path, url } of cases) { + expect( + encodeBaseUrl(scheme, { host, port, userinfo }, path), + ).toStrictEqual(new URL(url)); + } }); diff --git a/packages/libsql-client/src/hrana.ts b/packages/libsql-client/src/hrana.ts index e9058864..628bc474 100644 --- a/packages/libsql-client/src/hrana.ts +++ b/packages/libsql-client/src/hrana.ts @@ -1,360 +1,368 @@ import * as hrana from "@libsql/hrana-client"; import type { - InStatement, - ResultSet, - Transaction, - TransactionMode, + InStatement, + ResultSet, + Transaction, + TransactionMode, } from "@libsql/core/api"; import { LibsqlError } from "@libsql/core/api"; import type { SqlCache } from "./sql_cache.js"; import { transactionModeToBegin, ResultSetImpl } from "@libsql/core/util"; export abstract class HranaTransaction implements Transaction { - #mode: TransactionMode; - #version: hrana.ProtocolVersion; - // Promise that is resolved when the BEGIN statement completes, or `undefined` if we haven't executed the - // BEGIN statement yet. - #started: Promise | undefined; - - /** @private */ - constructor(mode: TransactionMode, version: hrana.ProtocolVersion) { - this.#mode = mode; - this.#version = version; - this.#started = undefined; - } - - /** @private */ - abstract _getStream(): hrana.Stream; - /** @private */ - abstract _getSqlCache(): SqlCache; - - abstract close(): void; - abstract get closed(): boolean; - - execute(stmt: InStatement): Promise { - return this.batch([stmt]).then((results) => results[0]); - } - - async batch(stmts: Array): Promise> { - const stream = this._getStream(); - if (stream.closed) { - throw new LibsqlError( - "Cannot execute statements because the transaction is closed", - "TRANSACTION_CLOSED", - ); + #mode: TransactionMode; + #version: hrana.ProtocolVersion; + // Promise that is resolved when the BEGIN statement completes, or `undefined` if we haven't executed the + // BEGIN statement yet. + #started: Promise | undefined; + + /** @private */ + constructor(mode: TransactionMode, version: hrana.ProtocolVersion) { + this.#mode = mode; + this.#version = version; + this.#started = undefined; } - try { - const hranaStmts = stmts.map(stmtToHrana); - - let rowsPromises: Array>; - if (this.#started === undefined) { - // The transaction hasn't started yet, so we need to send the BEGIN statement in a batch with - // `hranaStmts`. - - this._getSqlCache().apply(hranaStmts); - const batch = stream.batch(this.#version >= 3); - const beginStep = batch.step(); - const beginPromise = beginStep.run(transactionModeToBegin(this.#mode)); - - // Execute the `hranaStmts` only if the BEGIN succeeded, to make sure that we don't execute it - // outside of a transaction. - let lastStep = beginStep; - rowsPromises = hranaStmts.map((hranaStmt) => { - const stmtStep = batch.step().condition(hrana.BatchCond.ok(lastStep)); - if (this.#version >= 3) { - // If the Hrana version supports it, make sure that we are still in a transaction - stmtStep.condition( - hrana.BatchCond.not(hrana.BatchCond.isAutocommit(batch)), - ); - } + /** @private */ + abstract _getStream(): hrana.Stream; + /** @private */ + abstract _getSqlCache(): SqlCache; - const rowsPromise = stmtStep.query(hranaStmt); - rowsPromise.catch(() => undefined); // silence Node warning - lastStep = stmtStep; - return rowsPromise; - }); + abstract close(): void; + abstract get closed(): boolean; - // `this.#started` is resolved successfully only if the batch and the BEGIN statement inside - // of the batch are both successful. - this.#started = batch - .execute() - .then(() => beginPromise) - .then(() => undefined); + execute(stmt: InStatement): Promise { + return this.batch([stmt]).then((results) => results[0]); + } + + async batch(stmts: Array): Promise> { + const stream = this._getStream(); + if (stream.closed) { + throw new LibsqlError( + "Cannot execute statements because the transaction is closed", + "TRANSACTION_CLOSED", + ); + } try { - await this.#started; + const hranaStmts = stmts.map(stmtToHrana); + + let rowsPromises: Array>; + if (this.#started === undefined) { + // The transaction hasn't started yet, so we need to send the BEGIN statement in a batch with + // `hranaStmts`. + + this._getSqlCache().apply(hranaStmts); + const batch = stream.batch(this.#version >= 3); + const beginStep = batch.step(); + const beginPromise = beginStep.run( + transactionModeToBegin(this.#mode), + ); + + // Execute the `hranaStmts` only if the BEGIN succeeded, to make sure that we don't execute it + // outside of a transaction. + let lastStep = beginStep; + rowsPromises = hranaStmts.map((hranaStmt) => { + const stmtStep = batch + .step() + .condition(hrana.BatchCond.ok(lastStep)); + if (this.#version >= 3) { + // If the Hrana version supports it, make sure that we are still in a transaction + stmtStep.condition( + hrana.BatchCond.not( + hrana.BatchCond.isAutocommit(batch), + ), + ); + } + + const rowsPromise = stmtStep.query(hranaStmt); + rowsPromise.catch(() => undefined); // silence Node warning + lastStep = stmtStep; + return rowsPromise; + }); + + // `this.#started` is resolved successfully only if the batch and the BEGIN statement inside + // of the batch are both successful. + this.#started = batch + .execute() + .then(() => beginPromise) + .then(() => undefined); + + try { + await this.#started; + } catch (e) { + // If the BEGIN failed, the transaction is unusable and we must close it. However, if the + // BEGIN suceeds and `hranaStmts` fail, the transaction is _not_ closed. + this.close(); + throw e; + } + } else { + if (this.#version < 3) { + // The transaction has started, so we must wait until the BEGIN statement completed to make + // sure that we don't execute `hranaStmts` outside of a transaction. + await this.#started; + } else { + // The transaction has started, but we will use `hrana.BatchCond.isAutocommit()` to make + // sure that we don't execute `hranaStmts` outside of a transaction, so we don't have to + // wait for `this.#started` + } + + this._getSqlCache().apply(hranaStmts); + const batch = stream.batch(this.#version >= 3); + + let lastStep: hrana.BatchStep | undefined = undefined; + rowsPromises = hranaStmts.map((hranaStmt) => { + const stmtStep = batch.step(); + if (lastStep !== undefined) { + stmtStep.condition(hrana.BatchCond.ok(lastStep)); + } + if (this.#version >= 3) { + stmtStep.condition( + hrana.BatchCond.not( + hrana.BatchCond.isAutocommit(batch), + ), + ); + } + const rowsPromise = stmtStep.query(hranaStmt); + rowsPromise.catch(() => undefined); // silence Node warning + lastStep = stmtStep; + return rowsPromise; + }); + + await batch.execute(); + } + + const resultSets = []; + for (const rowsPromise of rowsPromises) { + const rows = await rowsPromise; + if (rows === undefined) { + throw new LibsqlError( + "Statement in a transaction was not executed, " + + "probably because the transaction has been rolled back", + "TRANSACTION_CLOSED", + ); + } + resultSets.push(resultSetFromHrana(rows)); + } + return resultSets; } catch (e) { - // If the BEGIN failed, the transaction is unusable and we must close it. However, if the - // BEGIN suceeds and `hranaStmts` fail, the transaction is _not_ closed. - this.close(); - throw e; - } - } else { - if (this.#version < 3) { - // The transaction has started, so we must wait until the BEGIN statement completed to make - // sure that we don't execute `hranaStmts` outside of a transaction. - await this.#started; - } else { - // The transaction has started, but we will use `hrana.BatchCond.isAutocommit()` to make - // sure that we don't execute `hranaStmts` outside of a transaction, so we don't have to - // wait for `this.#started` + throw mapHranaError(e); } + } - this._getSqlCache().apply(hranaStmts); - const batch = stream.batch(this.#version >= 3); - - let lastStep: hrana.BatchStep | undefined = undefined; - rowsPromises = hranaStmts.map((hranaStmt) => { - const stmtStep = batch.step(); - if (lastStep !== undefined) { - stmtStep.condition(hrana.BatchCond.ok(lastStep)); - } - if (this.#version >= 3) { - stmtStep.condition( - hrana.BatchCond.not(hrana.BatchCond.isAutocommit(batch)), + async executeMultiple(sql: string): Promise { + const stream = this._getStream(); + if (stream.closed) { + throw new LibsqlError( + "Cannot execute statements because the transaction is closed", + "TRANSACTION_CLOSED", ); - } - const rowsPromise = stmtStep.query(hranaStmt); - rowsPromise.catch(() => undefined); // silence Node warning - lastStep = stmtStep; - return rowsPromise; - }); - - await batch.execute(); - } - - const resultSets = []; - for (const rowsPromise of rowsPromises) { - const rows = await rowsPromise; - if (rows === undefined) { - throw new LibsqlError( - "Statement in a transaction was not executed, " + - "probably because the transaction has been rolled back", - "TRANSACTION_CLOSED", - ); } - resultSets.push(resultSetFromHrana(rows)); - } - return resultSets; - } catch (e) { - throw mapHranaError(e); - } - } - - async executeMultiple(sql: string): Promise { - const stream = this._getStream(); - if (stream.closed) { - throw new LibsqlError( - "Cannot execute statements because the transaction is closed", - "TRANSACTION_CLOSED", - ); - } - try { - if (this.#started === undefined) { - // If the transaction hasn't started yet, start it now - this.#started = stream - .run(transactionModeToBegin(this.#mode)) - .then(() => undefined); try { - await this.#started; + if (this.#started === undefined) { + // If the transaction hasn't started yet, start it now + this.#started = stream + .run(transactionModeToBegin(this.#mode)) + .then(() => undefined); + try { + await this.#started; + } catch (e) { + this.close(); + throw e; + } + } else { + // Wait until the transaction has started + await this.#started; + } + + await stream.sequence(sql); } catch (e) { - this.close(); - throw e; + throw mapHranaError(e); } - } else { - // Wait until the transaction has started - await this.#started; - } - - await stream.sequence(sql); - } catch (e) { - throw mapHranaError(e); } - } - - async rollback(): Promise { - try { - const stream = this._getStream(); - if (stream.closed) { - return; - } - - if (this.#started !== undefined) { - // We don't have to wait for the BEGIN statement to complete. If the BEGIN fails, we will - // execute a ROLLBACK outside of an active transaction, which should be harmless. - } else { - // We did nothing in the transaction, so there is nothing to rollback. - return; - } - - // Pipeline the ROLLBACK statement and the stream close. - const promise = stream.run("ROLLBACK").catch((e) => { - throw mapHranaError(e); - }); - stream.closeGracefully(); - - await promise; - } catch (e) { - throw mapHranaError(e); - } finally { - // `this.close()` may close the `hrana.Client`, which aborts all pending stream requests, so we - // must call it _after_ we receive the ROLLBACK response. - // Also note that the current stream should already be closed, but we need to call `this.close()` - // anyway, because it may need to do more cleanup. - this.close(); + + async rollback(): Promise { + try { + const stream = this._getStream(); + if (stream.closed) { + return; + } + + if (this.#started !== undefined) { + // We don't have to wait for the BEGIN statement to complete. If the BEGIN fails, we will + // execute a ROLLBACK outside of an active transaction, which should be harmless. + } else { + // We did nothing in the transaction, so there is nothing to rollback. + return; + } + + // Pipeline the ROLLBACK statement and the stream close. + const promise = stream.run("ROLLBACK").catch((e) => { + throw mapHranaError(e); + }); + stream.closeGracefully(); + + await promise; + } catch (e) { + throw mapHranaError(e); + } finally { + // `this.close()` may close the `hrana.Client`, which aborts all pending stream requests, so we + // must call it _after_ we receive the ROLLBACK response. + // Also note that the current stream should already be closed, but we need to call `this.close()` + // anyway, because it may need to do more cleanup. + this.close(); + } } - } - - async commit(): Promise { - // (this method is analogous to `rollback()`) - try { - const stream = this._getStream(); - if (stream.closed) { - throw new LibsqlError( - "Cannot commit the transaction because it is already closed", - "TRANSACTION_CLOSED", - ); - } - - if (this.#started !== undefined) { - // Make sure to execute the COMMIT only if the BEGIN was successful. - await this.#started; - } else { - return; - } - - const promise = stream.run("COMMIT").catch((e) => { - throw mapHranaError(e); - }); - stream.closeGracefully(); - - await promise; - } catch (e) { - throw mapHranaError(e); - } finally { - this.close(); + + async commit(): Promise { + // (this method is analogous to `rollback()`) + try { + const stream = this._getStream(); + if (stream.closed) { + throw new LibsqlError( + "Cannot commit the transaction because it is already closed", + "TRANSACTION_CLOSED", + ); + } + + if (this.#started !== undefined) { + // Make sure to execute the COMMIT only if the BEGIN was successful. + await this.#started; + } else { + return; + } + + const promise = stream.run("COMMIT").catch((e) => { + throw mapHranaError(e); + }); + stream.closeGracefully(); + + await promise; + } catch (e) { + throw mapHranaError(e); + } finally { + this.close(); + } } - } } export async function executeHranaBatch( - mode: TransactionMode, - version: hrana.ProtocolVersion, - batch: hrana.Batch, - hranaStmts: Array, + mode: TransactionMode, + version: hrana.ProtocolVersion, + batch: hrana.Batch, + hranaStmts: Array, ): Promise> { - const beginStep = batch.step(); - const beginPromise = beginStep.run(transactionModeToBegin(mode)); + const beginStep = batch.step(); + const beginPromise = beginStep.run(transactionModeToBegin(mode)); - let lastStep = beginStep; - const stmtPromises = hranaStmts.map((hranaStmt) => { - const stmtStep = batch.step().condition(hrana.BatchCond.ok(lastStep)); - if (version >= 3) { - stmtStep.condition( - hrana.BatchCond.not(hrana.BatchCond.isAutocommit(batch)), - ); - } + let lastStep = beginStep; + const stmtPromises = hranaStmts.map((hranaStmt) => { + const stmtStep = batch.step().condition(hrana.BatchCond.ok(lastStep)); + if (version >= 3) { + stmtStep.condition( + hrana.BatchCond.not(hrana.BatchCond.isAutocommit(batch)), + ); + } - const stmtPromise = stmtStep.query(hranaStmt); - lastStep = stmtStep; - return stmtPromise; - }); + const stmtPromise = stmtStep.query(hranaStmt); + lastStep = stmtStep; + return stmtPromise; + }); - const commitStep = batch.step().condition(hrana.BatchCond.ok(lastStep)); - if (version >= 3) { - commitStep.condition( - hrana.BatchCond.not(hrana.BatchCond.isAutocommit(batch)), - ); - } - const commitPromise = commitStep.run("COMMIT"); - - const rollbackStep = batch - .step() - .condition(hrana.BatchCond.not(hrana.BatchCond.ok(commitStep))); - rollbackStep.run("ROLLBACK").catch((_) => undefined); - - await batch.execute(); - - const resultSets = []; - await beginPromise; - for (const stmtPromise of stmtPromises) { - const hranaRows = await stmtPromise; - if (hranaRows === undefined) { - throw new LibsqlError( - "Statement in a batch was not executed, probably because the transaction has been rolled back", - "TRANSACTION_CLOSED", - ); + const commitStep = batch.step().condition(hrana.BatchCond.ok(lastStep)); + if (version >= 3) { + commitStep.condition( + hrana.BatchCond.not(hrana.BatchCond.isAutocommit(batch)), + ); } - resultSets.push(resultSetFromHrana(hranaRows)); - } - await commitPromise; + const commitPromise = commitStep.run("COMMIT"); + + const rollbackStep = batch + .step() + .condition(hrana.BatchCond.not(hrana.BatchCond.ok(commitStep))); + rollbackStep.run("ROLLBACK").catch((_) => undefined); + + await batch.execute(); + + const resultSets = []; + await beginPromise; + for (const stmtPromise of stmtPromises) { + const hranaRows = await stmtPromise; + if (hranaRows === undefined) { + throw new LibsqlError( + "Statement in a batch was not executed, probably because the transaction has been rolled back", + "TRANSACTION_CLOSED", + ); + } + resultSets.push(resultSetFromHrana(hranaRows)); + } + await commitPromise; - return resultSets; + return resultSets; } export function stmtToHrana(stmt: InStatement): hrana.Stmt { - if (typeof stmt === "string") { - return new hrana.Stmt(stmt); - } - - const hranaStmt = new hrana.Stmt(stmt.sql); - if (Array.isArray(stmt.args)) { - hranaStmt.bindIndexes(stmt.args); - } else { - for (const [key, value] of Object.entries(stmt.args)) { - hranaStmt.bindName(key, value); + if (typeof stmt === "string") { + return new hrana.Stmt(stmt); } - } - return hranaStmt; + const hranaStmt = new hrana.Stmt(stmt.sql); + if (Array.isArray(stmt.args)) { + hranaStmt.bindIndexes(stmt.args); + } else { + for (const [key, value] of Object.entries(stmt.args)) { + hranaStmt.bindName(key, value); + } + } + + return hranaStmt; } export function resultSetFromHrana(hranaRows: hrana.RowsResult): ResultSet { - const columns = hranaRows.columnNames.map((c) => c ?? ""); - const columnTypes = hranaRows.columnDecltypes.map((c) => c ?? ""); - const rows = hranaRows.rows; - const rowsAffected = hranaRows.affectedRowCount; - const lastInsertRowid = - hranaRows.lastInsertRowid !== undefined - ? hranaRows.lastInsertRowid - : undefined; - return new ResultSetImpl( - columns, - columnTypes, - rows, - rowsAffected, - lastInsertRowid, - ); + const columns = hranaRows.columnNames.map((c) => c ?? ""); + const columnTypes = hranaRows.columnDecltypes.map((c) => c ?? ""); + const rows = hranaRows.rows; + const rowsAffected = hranaRows.affectedRowCount; + const lastInsertRowid = + hranaRows.lastInsertRowid !== undefined + ? hranaRows.lastInsertRowid + : undefined; + return new ResultSetImpl( + columns, + columnTypes, + rows, + rowsAffected, + lastInsertRowid, + ); } export function mapHranaError(e: unknown): unknown { - if (e instanceof hrana.ClientError) { - const code = mapHranaErrorCode(e); - return new LibsqlError(e.message, code, undefined, e); - } - return e; + if (e instanceof hrana.ClientError) { + const code = mapHranaErrorCode(e); + return new LibsqlError(e.message, code, undefined, e); + } + return e; } function mapHranaErrorCode(e: hrana.ClientError): string { - if (e instanceof hrana.ResponseError && e.code !== undefined) { - return e.code; - } else if (e instanceof hrana.ProtoError) { - return "HRANA_PROTO_ERROR"; - } else if (e instanceof hrana.ClosedError) { - return e.cause instanceof hrana.ClientError - ? mapHranaErrorCode(e.cause) - : "HRANA_CLOSED_ERROR"; - } else if (e instanceof hrana.WebSocketError) { - return "HRANA_WEBSOCKET_ERROR"; - } else if (e instanceof hrana.HttpServerError) { - return "SERVER_ERROR"; - } else if (e instanceof hrana.ProtocolVersionError) { - return "PROTOCOL_VERSION_ERROR"; - } else if (e instanceof hrana.InternalError) { - return "INTERNAL_ERROR"; - } else { - return "UNKNOWN"; - } + if (e instanceof hrana.ResponseError && e.code !== undefined) { + return e.code; + } else if (e instanceof hrana.ProtoError) { + return "HRANA_PROTO_ERROR"; + } else if (e instanceof hrana.ClosedError) { + return e.cause instanceof hrana.ClientError + ? mapHranaErrorCode(e.cause) + : "HRANA_CLOSED_ERROR"; + } else if (e instanceof hrana.WebSocketError) { + return "HRANA_WEBSOCKET_ERROR"; + } else if (e instanceof hrana.HttpServerError) { + return "SERVER_ERROR"; + } else if (e instanceof hrana.ProtocolVersionError) { + return "PROTOCOL_VERSION_ERROR"; + } else if (e instanceof hrana.InternalError) { + return "INTERNAL_ERROR"; + } else { + return "UNKNOWN"; + } } diff --git a/packages/libsql-client/src/http.ts b/packages/libsql-client/src/http.ts index 49d06b17..1cf64a57 100644 --- a/packages/libsql-client/src/http.ts +++ b/packages/libsql-client/src/http.ts @@ -2,20 +2,20 @@ import * as hrana from "@libsql/hrana-client"; import type { Config, Client } from "@libsql/core/api"; import type { - InStatement, - ResultSet, - Transaction, - IntMode, + InStatement, + ResultSet, + Transaction, + IntMode, } from "@libsql/core/api"; import { TransactionMode, LibsqlError } from "@libsql/core/api"; import type { ExpandedConfig } from "@libsql/core/config"; import { expandConfig } from "@libsql/core/config"; import { - HranaTransaction, - executeHranaBatch, - stmtToHrana, - resultSetFromHrana, - mapHranaError, + HranaTransaction, + executeHranaBatch, + stmtToHrana, + resultSetFromHrana, + mapHranaError, } from "./hrana.js"; import { SqlCache } from "./sql_cache.js"; import { encodeBaseUrl } from "@libsql/core/uri"; @@ -24,187 +24,198 @@ import { supportedUrlLink } from "@libsql/core/util"; export * from "@libsql/core/api"; export function createClient(config: Config): Client { - return _createClient(expandConfig(config, true)); + return _createClient(expandConfig(config, true)); } /** @private */ export function _createClient(config: ExpandedConfig): Client { - if (config.scheme !== "https" && config.scheme !== "http") { - throw new LibsqlError( - 'The HTTP client supports only "libsql:", "https:" and "http:" URLs, ' + - `got ${JSON.stringify(config.scheme + ":")}. For more information, please read ${supportedUrlLink}`, - "URL_SCHEME_NOT_SUPPORTED", - ); - } - - if (config.encryptionKey !== undefined) { - throw new LibsqlError( - "Encryption key is not supported by the remote client.", - "ENCRYPTION_KEY_NOT_SUPPORTED", - ); - } - - if (config.scheme === "http" && config.tls) { - throw new LibsqlError( - `A "http:" URL cannot opt into TLS by using ?tls=1`, - "URL_INVALID", - ); - } else if (config.scheme === "https" && !config.tls) { - throw new LibsqlError( - `A "https:" URL cannot opt out of TLS by using ?tls=0`, - "URL_INVALID", - ); - } - - const url = encodeBaseUrl(config.scheme, config.authority, config.path); - return new HttpClient(url, config.authToken, config.intMode, config.fetch); + if (config.scheme !== "https" && config.scheme !== "http") { + throw new LibsqlError( + 'The HTTP client supports only "libsql:", "https:" and "http:" URLs, ' + + `got ${JSON.stringify(config.scheme + ":")}. For more information, please read ${supportedUrlLink}`, + "URL_SCHEME_NOT_SUPPORTED", + ); + } + + if (config.encryptionKey !== undefined) { + throw new LibsqlError( + "Encryption key is not supported by the remote client.", + "ENCRYPTION_KEY_NOT_SUPPORTED", + ); + } + + if (config.scheme === "http" && config.tls) { + throw new LibsqlError( + `A "http:" URL cannot opt into TLS by using ?tls=1`, + "URL_INVALID", + ); + } else if (config.scheme === "https" && !config.tls) { + throw new LibsqlError( + `A "https:" URL cannot opt out of TLS by using ?tls=0`, + "URL_INVALID", + ); + } + + const url = encodeBaseUrl(config.scheme, config.authority, config.path); + return new HttpClient(url, config.authToken, config.intMode, config.fetch); } const sqlCacheCapacity = 30; export class HttpClient implements Client { - #client: hrana.HttpClient; - protocol: "http"; - - /** @private */ - constructor( - url: URL, - authToken: string | undefined, - intMode: IntMode, - customFetch: Function | undefined, - ) { - this.#client = hrana.openHttp(url, authToken, customFetch); - this.#client.intMode = intMode; - this.protocol = "http"; - } - - async execute(stmt: InStatement): Promise { - try { - const hranaStmt = stmtToHrana(stmt); - - // Pipeline all operations, so `hrana.HttpClient` can open the stream, execute the statement and - // close the stream in a single HTTP request. - let rowsPromise: Promise; - const stream = this.#client.openStream(); - try { - rowsPromise = stream.query(hranaStmt); - } finally { - stream.closeGracefully(); - } - - return resultSetFromHrana(await rowsPromise); - } catch (e) { - throw mapHranaError(e); - } - } - - async batch( - stmts: Array, - mode: TransactionMode = "deferred", - ): Promise> { - try { - const hranaStmts = stmts.map(stmtToHrana); - const version = await this.#client.getVersion(); - - // Pipeline all operations, so `hrana.HttpClient` can open the stream, execute the batch and - // close the stream in a single HTTP request. - let resultsPromise: Promise>; - const stream = this.#client.openStream(); - try { - // It makes sense to use a SQL cache even for a single batch, because it may contain the same - // statement repeated multiple times. - const sqlCache = new SqlCache(stream, sqlCacheCapacity); - sqlCache.apply(hranaStmts); - - // TODO: we do not use a cursor here, because it would cause three roundtrips: - // 1. pipeline request to store SQL texts - // 2. cursor request - // 3. pipeline request to close the stream - const batch = stream.batch(false); - resultsPromise = executeHranaBatch(mode, version, batch, hranaStmts); - } finally { - stream.closeGracefully(); - } - - return await resultsPromise; - } catch (e) { - throw mapHranaError(e); - } - } - - async transaction(mode: TransactionMode = "write"): Promise { - try { - const version = await this.#client.getVersion(); - return new HttpTransaction(this.#client.openStream(), mode, version); - } catch (e) { - throw mapHranaError(e); - } - } - - async executeMultiple(sql: string): Promise { - try { - // Pipeline all operations, so `hrana.HttpClient` can open the stream, execute the sequence and - // close the stream in a single HTTP request. - let promise: Promise; - const stream = this.#client.openStream(); - try { - promise = stream.sequence(sql); - } finally { - stream.closeGracefully(); - } - - await promise; - } catch (e) { - throw mapHranaError(e); - } - } - - sync(): Promise { - throw new LibsqlError( - "sync not supported in http mode", - "SYNC_NOT_SUPPORTED", - ); - } - - close(): void { - this.#client.close(); - } - - get closed(): boolean { - return this.#client.closed; - } + #client: hrana.HttpClient; + protocol: "http"; + + /** @private */ + constructor( + url: URL, + authToken: string | undefined, + intMode: IntMode, + customFetch: Function | undefined, + ) { + this.#client = hrana.openHttp(url, authToken, customFetch); + this.#client.intMode = intMode; + this.protocol = "http"; + } + + async execute(stmt: InStatement): Promise { + try { + const hranaStmt = stmtToHrana(stmt); + + // Pipeline all operations, so `hrana.HttpClient` can open the stream, execute the statement and + // close the stream in a single HTTP request. + let rowsPromise: Promise; + const stream = this.#client.openStream(); + try { + rowsPromise = stream.query(hranaStmt); + } finally { + stream.closeGracefully(); + } + + return resultSetFromHrana(await rowsPromise); + } catch (e) { + throw mapHranaError(e); + } + } + + async batch( + stmts: Array, + mode: TransactionMode = "deferred", + ): Promise> { + try { + const hranaStmts = stmts.map(stmtToHrana); + const version = await this.#client.getVersion(); + + // Pipeline all operations, so `hrana.HttpClient` can open the stream, execute the batch and + // close the stream in a single HTTP request. + let resultsPromise: Promise>; + const stream = this.#client.openStream(); + try { + // It makes sense to use a SQL cache even for a single batch, because it may contain the same + // statement repeated multiple times. + const sqlCache = new SqlCache(stream, sqlCacheCapacity); + sqlCache.apply(hranaStmts); + + // TODO: we do not use a cursor here, because it would cause three roundtrips: + // 1. pipeline request to store SQL texts + // 2. cursor request + // 3. pipeline request to close the stream + const batch = stream.batch(false); + resultsPromise = executeHranaBatch( + mode, + version, + batch, + hranaStmts, + ); + } finally { + stream.closeGracefully(); + } + + return await resultsPromise; + } catch (e) { + throw mapHranaError(e); + } + } + + async transaction( + mode: TransactionMode = "write", + ): Promise { + try { + const version = await this.#client.getVersion(); + return new HttpTransaction( + this.#client.openStream(), + mode, + version, + ); + } catch (e) { + throw mapHranaError(e); + } + } + + async executeMultiple(sql: string): Promise { + try { + // Pipeline all operations, so `hrana.HttpClient` can open the stream, execute the sequence and + // close the stream in a single HTTP request. + let promise: Promise; + const stream = this.#client.openStream(); + try { + promise = stream.sequence(sql); + } finally { + stream.closeGracefully(); + } + + await promise; + } catch (e) { + throw mapHranaError(e); + } + } + + sync(): Promise { + throw new LibsqlError( + "sync not supported in http mode", + "SYNC_NOT_SUPPORTED", + ); + } + + close(): void { + this.#client.close(); + } + + get closed(): boolean { + return this.#client.closed; + } } export class HttpTransaction extends HranaTransaction implements Transaction { - #stream: hrana.HttpStream; - #sqlCache: SqlCache; - - /** @private */ - constructor( - stream: hrana.HttpStream, - mode: TransactionMode, - version: hrana.ProtocolVersion, - ) { - super(mode, version); - this.#stream = stream; - this.#sqlCache = new SqlCache(stream, sqlCacheCapacity); - } - - /** @private */ - override _getStream(): hrana.Stream { - return this.#stream; - } - - /** @private */ - override _getSqlCache(): SqlCache { - return this.#sqlCache; - } - - override close(): void { - this.#stream.close(); - } - - override get closed(): boolean { - return this.#stream.closed; - } + #stream: hrana.HttpStream; + #sqlCache: SqlCache; + + /** @private */ + constructor( + stream: hrana.HttpStream, + mode: TransactionMode, + version: hrana.ProtocolVersion, + ) { + super(mode, version); + this.#stream = stream; + this.#sqlCache = new SqlCache(stream, sqlCacheCapacity); + } + + /** @private */ + override _getStream(): hrana.Stream { + return this.#stream; + } + + /** @private */ + override _getSqlCache(): SqlCache { + return this.#sqlCache; + } + + override close(): void { + this.#stream.close(); + } + + override get closed(): boolean { + return this.#stream.closed; + } } diff --git a/packages/libsql-client/src/node.ts b/packages/libsql-client/src/node.ts index b9cb6a17..8284ca25 100644 --- a/packages/libsql-client/src/node.ts +++ b/packages/libsql-client/src/node.ts @@ -13,15 +13,15 @@ export * from "@libsql/core/api"; * You must pass at least an `url` in the {@link Config} object. */ export function createClient(config: Config): Client { - return _createClient(expandConfig(config, true)); + return _createClient(expandConfig(config, true)); } function _createClient(config: ExpandedConfig) { - if (config.scheme === "wss" || config.scheme === "ws") { - return _createWsClient(config); - } else if (config.scheme === "https" || config.scheme === "http") { - return _createHttpClient(config); - } else { - return _createSqlite3Client(config); - } + if (config.scheme === "wss" || config.scheme === "ws") { + return _createWsClient(config); + } else if (config.scheme === "https" || config.scheme === "http") { + return _createHttpClient(config); + } else { + return _createSqlite3Client(config); + } } diff --git a/packages/libsql-client/src/sql_cache.ts b/packages/libsql-client/src/sql_cache.ts index 7509a569..aa8a79cc 100644 --- a/packages/libsql-client/src/sql_cache.ts +++ b/packages/libsql-client/src/sql_cache.ts @@ -1,98 +1,98 @@ import type * as hrana from "@libsql/hrana-client"; export class SqlCache { - #owner: hrana.SqlOwner; - #sqls: Lru; - capacity: number; + #owner: hrana.SqlOwner; + #sqls: Lru; + capacity: number; - constructor(owner: hrana.SqlOwner, capacity: number) { - this.#owner = owner; - this.#sqls = new Lru(); - this.capacity = capacity; - } - - // Replaces SQL strings with cached `hrana.Sql` objects in the statements in `hranaStmts`. After this - // function returns, we guarantee that all `hranaStmts` refer to valid (not closed) `hrana.Sql` objects, - // but _we may invalidate any other `hrana.Sql` objects_ (by closing them, thus removing them from the - // server). - // - // In practice, this means that after calling this function, you can use the statements only up to the - // first `await`, because concurrent code may also use the cache and invalidate those statements. - apply(hranaStmts: Array): void { - if (this.capacity <= 0) { - return; + constructor(owner: hrana.SqlOwner, capacity: number) { + this.#owner = owner; + this.#sqls = new Lru(); + this.capacity = capacity; } - const usedSqlObjs: Set = new Set(); + // Replaces SQL strings with cached `hrana.Sql` objects in the statements in `hranaStmts`. After this + // function returns, we guarantee that all `hranaStmts` refer to valid (not closed) `hrana.Sql` objects, + // but _we may invalidate any other `hrana.Sql` objects_ (by closing them, thus removing them from the + // server). + // + // In practice, this means that after calling this function, you can use the statements only up to the + // first `await`, because concurrent code may also use the cache and invalidate those statements. + apply(hranaStmts: Array): void { + if (this.capacity <= 0) { + return; + } - for (const hranaStmt of hranaStmts) { - if (typeof hranaStmt.sql !== "string") { - continue; - } - const sqlText = hranaStmt.sql; + const usedSqlObjs: Set = new Set(); - let sqlObj = this.#sqls.get(sqlText); - if (sqlObj === undefined) { - while (this.#sqls.size + 1 > this.capacity) { - const [evictSqlText, evictSqlObj] = this.#sqls.peekLru()!; - if (usedSqlObjs.has(evictSqlObj)) { - // The SQL object that we are trying to evict is already in use in this batch, so we - // must not evict and close it. - break; - } - evictSqlObj.close(); - this.#sqls.delete(evictSqlText); - } + for (const hranaStmt of hranaStmts) { + if (typeof hranaStmt.sql !== "string") { + continue; + } + const sqlText = hranaStmt.sql; - if (this.#sqls.size + 1 <= this.capacity) { - sqlObj = this.#owner.storeSql(sqlText); - this.#sqls.set(sqlText, sqlObj); - } - } + let sqlObj = this.#sqls.get(sqlText); + if (sqlObj === undefined) { + while (this.#sqls.size + 1 > this.capacity) { + const [evictSqlText, evictSqlObj] = this.#sqls.peekLru()!; + if (usedSqlObjs.has(evictSqlObj)) { + // The SQL object that we are trying to evict is already in use in this batch, so we + // must not evict and close it. + break; + } + evictSqlObj.close(); + this.#sqls.delete(evictSqlText); + } + + if (this.#sqls.size + 1 <= this.capacity) { + sqlObj = this.#owner.storeSql(sqlText); + this.#sqls.set(sqlText, sqlObj); + } + } - if (sqlObj !== undefined) { - hranaStmt.sql = sqlObj; - usedSqlObjs.add(sqlObj); - } + if (sqlObj !== undefined) { + hranaStmt.sql = sqlObj; + usedSqlObjs.add(sqlObj); + } + } } - } } class Lru { - // This maps keys to the cache values. The entries are ordered by their last use (entires that were used - // most recently are at the end). - #cache: Map; + // This maps keys to the cache values. The entries are ordered by their last use (entires that were used + // most recently are at the end). + #cache: Map; - constructor() { - this.#cache = new Map(); - } + constructor() { + this.#cache = new Map(); + } - get(key: K): V | undefined { - const value = this.#cache.get(key); - if (value !== undefined) { - // move the entry to the back of the Map - this.#cache.delete(key); - this.#cache.set(key, value); + get(key: K): V | undefined { + const value = this.#cache.get(key); + if (value !== undefined) { + // move the entry to the back of the Map + this.#cache.delete(key); + this.#cache.set(key, value); + } + return value; } - return value; - } - set(key: K, value: V): void { - this.#cache.set(key, value); - } + set(key: K, value: V): void { + this.#cache.set(key, value); + } - peekLru(): [K, V] | undefined { - for (const entry of this.#cache.entries()) { - return entry; + peekLru(): [K, V] | undefined { + for (const entry of this.#cache.entries()) { + return entry; + } + return undefined; } - return undefined; - } - delete(key: K): void { - this.#cache.delete(key); - } + delete(key: K): void { + this.#cache.delete(key); + } - get size(): number { - return this.#cache.size; - } + get size(): number { + return this.#cache.size; + } } diff --git a/packages/libsql-client/src/sqlite3.ts b/packages/libsql-client/src/sqlite3.ts index f2833a6b..8a9f35d3 100644 --- a/packages/libsql-client/src/sqlite3.ts +++ b/packages/libsql-client/src/sqlite3.ts @@ -2,410 +2,415 @@ import Database from "libsql"; import { Buffer } from "node:buffer"; import type { - Config, - IntMode, - Client, - Transaction, - TransactionMode, - ResultSet, - Row, - Value, - InValue, - InStatement, + Config, + IntMode, + Client, + Transaction, + TransactionMode, + ResultSet, + Row, + Value, + InValue, + InStatement, } from "@libsql/core/api"; import { LibsqlError } from "@libsql/core/api"; import type { ExpandedConfig } from "@libsql/core/config"; import { expandConfig } from "@libsql/core/config"; import { - supportedUrlLink, - transactionModeToBegin, - ResultSetImpl, + supportedUrlLink, + transactionModeToBegin, + ResultSetImpl, } from "@libsql/core/util"; export * from "@libsql/core/api"; export function createClient(config: Config): Client { - return _createClient(expandConfig(config, true)); + return _createClient(expandConfig(config, true)); } /** @private */ export function _createClient(config: ExpandedConfig): Client { - if (config.scheme !== "file") { - throw new LibsqlError( - `URL scheme ${JSON.stringify(config.scheme + ":")} is not supported by the local sqlite3 client. ` + - `For more information, please read ${supportedUrlLink}`, - "URL_SCHEME_NOT_SUPPORTED", - ); - } - - const authority = config.authority; - if (authority !== undefined) { - const host = authority.host.toLowerCase(); - if (host !== "" && host !== "localhost") { - throw new LibsqlError( - `Invalid host in file URL: ${JSON.stringify(authority.host)}. ` + - 'A "file:" URL with an absolute path should start with one slash ("file:/absolute/path.db") ' + - 'or with three slashes ("file:///absolute/path.db"). ' + - `For more information, please read ${supportedUrlLink}`, - "URL_INVALID", - ); + if (config.scheme !== "file") { + throw new LibsqlError( + `URL scheme ${JSON.stringify(config.scheme + ":")} is not supported by the local sqlite3 client. ` + + `For more information, please read ${supportedUrlLink}`, + "URL_SCHEME_NOT_SUPPORTED", + ); } - if (authority.port !== undefined) { - throw new LibsqlError("File URL cannot have a port", "URL_INVALID"); - } - if (authority.userinfo !== undefined) { - throw new LibsqlError( - "File URL cannot have username and password", - "URL_INVALID", - ); + const authority = config.authority; + if (authority !== undefined) { + const host = authority.host.toLowerCase(); + if (host !== "" && host !== "localhost") { + throw new LibsqlError( + `Invalid host in file URL: ${JSON.stringify(authority.host)}. ` + + 'A "file:" URL with an absolute path should start with one slash ("file:/absolute/path.db") ' + + 'or with three slashes ("file:///absolute/path.db"). ' + + `For more information, please read ${supportedUrlLink}`, + "URL_INVALID", + ); + } + + if (authority.port !== undefined) { + throw new LibsqlError("File URL cannot have a port", "URL_INVALID"); + } + if (authority.userinfo !== undefined) { + throw new LibsqlError( + "File URL cannot have username and password", + "URL_INVALID", + ); + } } - } - const path = config.path; - const options = { - authToken: config.authToken, - encryptionKey: config.encryptionKey, - syncUrl: config.syncUrl, - syncInterval: config.syncInterval, - }; + const path = config.path; + const options = { + authToken: config.authToken, + encryptionKey: config.encryptionKey, + syncUrl: config.syncUrl, + syncInterval: config.syncInterval, + }; - const db = new Database(path, options); + const db = new Database(path, options); - executeStmt( - db, - "SELECT 1 AS checkThatTheDatabaseCanBeOpened", - config.intMode, - ); + executeStmt( + db, + "SELECT 1 AS checkThatTheDatabaseCanBeOpened", + config.intMode, + ); - return new Sqlite3Client(path, options, db, config.intMode); + return new Sqlite3Client(path, options, db, config.intMode); } export class Sqlite3Client implements Client { - #path: string; - #options: Database.Options; - #db: Database.Database | null; - #intMode: IntMode; - closed: boolean; - protocol: "file"; - - /** @private */ - constructor( - path: string, - options: Database.Options, - db: Database.Database, - intMode: IntMode, - ) { - this.#path = path; - this.#options = options; - this.#db = db; - this.#intMode = intMode; - this.closed = false; - this.protocol = "file"; - } - - async execute(stmt: InStatement): Promise { - this.#checkNotClosed(); - return executeStmt(this.#getDb(), stmt, this.#intMode); - } - - async batch( - stmts: Array, - mode: TransactionMode = "deferred", - ): Promise> { - this.#checkNotClosed(); - const db = this.#getDb(); - try { - executeStmt(db, transactionModeToBegin(mode), this.#intMode); - const resultSets = stmts.map((stmt) => { - if (!db.inTransaction) { - throw new LibsqlError( - "The transaction has been rolled back", - "TRANSACTION_CLOSED", - ); + #path: string; + #options: Database.Options; + #db: Database.Database | null; + #intMode: IntMode; + closed: boolean; + protocol: "file"; + + /** @private */ + constructor( + path: string, + options: Database.Options, + db: Database.Database, + intMode: IntMode, + ) { + this.#path = path; + this.#options = options; + this.#db = db; + this.#intMode = intMode; + this.closed = false; + this.protocol = "file"; + } + + async execute(stmt: InStatement): Promise { + this.#checkNotClosed(); + return executeStmt(this.#getDb(), stmt, this.#intMode); + } + + async batch( + stmts: Array, + mode: TransactionMode = "deferred", + ): Promise> { + this.#checkNotClosed(); + const db = this.#getDb(); + try { + executeStmt(db, transactionModeToBegin(mode), this.#intMode); + const resultSets = stmts.map((stmt) => { + if (!db.inTransaction) { + throw new LibsqlError( + "The transaction has been rolled back", + "TRANSACTION_CLOSED", + ); + } + return executeStmt(db, stmt, this.#intMode); + }); + executeStmt(db, "COMMIT", this.#intMode); + return resultSets; + } finally { + if (db.inTransaction) { + executeStmt(db, "ROLLBACK", this.#intMode); + } } - return executeStmt(db, stmt, this.#intMode); - }); - executeStmt(db, "COMMIT", this.#intMode); - return resultSets; - } finally { - if (db.inTransaction) { - executeStmt(db, "ROLLBACK", this.#intMode); - } } - } - - async transaction(mode: TransactionMode = "write"): Promise { - const db = this.#getDb(); - executeStmt(db, transactionModeToBegin(mode), this.#intMode); - this.#db = null; // A new connection will be lazily created on next use - return new Sqlite3Transaction(db, this.#intMode); - } - - async executeMultiple(sql: string): Promise { - this.#checkNotClosed(); - const db = this.#getDb(); - try { - return executeMultiple(db, sql); - } finally { - if (db.inTransaction) { - executeStmt(db, "ROLLBACK", this.#intMode); - } + + async transaction(mode: TransactionMode = "write"): Promise { + const db = this.#getDb(); + executeStmt(db, transactionModeToBegin(mode), this.#intMode); + this.#db = null; // A new connection will be lazily created on next use + return new Sqlite3Transaction(db, this.#intMode); + } + + async executeMultiple(sql: string): Promise { + this.#checkNotClosed(); + const db = this.#getDb(); + try { + return executeMultiple(db, sql); + } finally { + if (db.inTransaction) { + executeStmt(db, "ROLLBACK", this.#intMode); + } + } } - } - async sync(): Promise { - this.#checkNotClosed(); - await this.#getDb().sync(); - } + async sync(): Promise { + this.#checkNotClosed(); + await this.#getDb().sync(); + } - close(): void { - this.closed = true; - if (this.#db !== null) { - this.#db.close(); + close(): void { + this.closed = true; + if (this.#db !== null) { + this.#db.close(); + } } - } - #checkNotClosed(): void { - if (this.closed) { - throw new LibsqlError("The client is closed", "CLIENT_CLOSED"); + #checkNotClosed(): void { + if (this.closed) { + throw new LibsqlError("The client is closed", "CLIENT_CLOSED"); + } } - } - // Lazily creates the database connection and returns it - #getDb(): Database.Database { - if (this.#db === null) { - this.#db = new Database(this.#path, this.#options); + // Lazily creates the database connection and returns it + #getDb(): Database.Database { + if (this.#db === null) { + this.#db = new Database(this.#path, this.#options); + } + return this.#db; } - return this.#db; - } } export class Sqlite3Transaction implements Transaction { - #database: Database.Database; - #intMode: IntMode; - - /** @private */ - constructor(database: Database.Database, intMode: IntMode) { - this.#database = database; - this.#intMode = intMode; - } - - async execute(stmt: InStatement): Promise { - this.#checkNotClosed(); - return executeStmt(this.#database, stmt, this.#intMode); - } - - async batch(stmts: Array): Promise> { - return stmts.map((stmt) => { - this.#checkNotClosed(); - return executeStmt(this.#database, stmt, this.#intMode); - }); - } - - async executeMultiple(sql: string): Promise { - this.#checkNotClosed(); - return executeMultiple(this.#database, sql); - } - - async rollback(): Promise { - if (!this.#database.open) { - return; + #database: Database.Database; + #intMode: IntMode; + + /** @private */ + constructor(database: Database.Database, intMode: IntMode) { + this.#database = database; + this.#intMode = intMode; + } + + async execute(stmt: InStatement): Promise { + this.#checkNotClosed(); + return executeStmt(this.#database, stmt, this.#intMode); + } + + async batch(stmts: Array): Promise> { + return stmts.map((stmt) => { + this.#checkNotClosed(); + return executeStmt(this.#database, stmt, this.#intMode); + }); + } + + async executeMultiple(sql: string): Promise { + this.#checkNotClosed(); + return executeMultiple(this.#database, sql); + } + + async rollback(): Promise { + if (!this.#database.open) { + return; + } + this.#checkNotClosed(); + executeStmt(this.#database, "ROLLBACK", this.#intMode); + } + + async commit(): Promise { + this.#checkNotClosed(); + executeStmt(this.#database, "COMMIT", this.#intMode); } - this.#checkNotClosed(); - executeStmt(this.#database, "ROLLBACK", this.#intMode); - } - - async commit(): Promise { - this.#checkNotClosed(); - executeStmt(this.#database, "COMMIT", this.#intMode); - } - - close(): void { - if (this.#database.inTransaction) { - executeStmt(this.#database, "ROLLBACK", this.#intMode); + + close(): void { + if (this.#database.inTransaction) { + executeStmt(this.#database, "ROLLBACK", this.#intMode); + } } - } - get closed(): boolean { - return !this.#database.inTransaction; - } + get closed(): boolean { + return !this.#database.inTransaction; + } - #checkNotClosed(): void { - if (this.closed) { - throw new LibsqlError("The transaction is closed", "TRANSACTION_CLOSED"); + #checkNotClosed(): void { + if (this.closed) { + throw new LibsqlError( + "The transaction is closed", + "TRANSACTION_CLOSED", + ); + } } - } } function executeStmt( - db: Database.Database, - stmt: InStatement, - intMode: IntMode, + db: Database.Database, + stmt: InStatement, + intMode: IntMode, ): ResultSet { - let sql: string; - let args: Array | Record; - if (typeof stmt === "string") { - sql = stmt; - args = []; - } else { - sql = stmt.sql; - if (Array.isArray(stmt.args)) { - args = stmt.args.map((value) => valueToSql(value, intMode)); + let sql: string; + let args: Array | Record; + if (typeof stmt === "string") { + sql = stmt; + args = []; } else { - args = {}; - for (const name in stmt.args) { - const argName = - name[0] === "@" || name[0] === "$" || name[0] === ":" - ? name.substring(1) - : name; - args[argName] = valueToSql(stmt.args[name], intMode); - } + sql = stmt.sql; + if (Array.isArray(stmt.args)) { + args = stmt.args.map((value) => valueToSql(value, intMode)); + } else { + args = {}; + for (const name in stmt.args) { + const argName = + name[0] === "@" || name[0] === "$" || name[0] === ":" + ? name.substring(1) + : name; + args[argName] = valueToSql(stmt.args[name], intMode); + } + } } - } - - try { - const sqlStmt = db.prepare(sql); - sqlStmt.safeIntegers(true); - let returnsData = true; try { - sqlStmt.raw(true); - } catch { - // raw() throws an exception if the statement does not return data - returnsData = false; - } + const sqlStmt = db.prepare(sql); + sqlStmt.safeIntegers(true); + + let returnsData = true; + try { + sqlStmt.raw(true); + } catch { + // raw() throws an exception if the statement does not return data + returnsData = false; + } - if (returnsData) { - const columns = Array.from(sqlStmt.columns().map((col) => col.name)); - const columnTypes = Array.from( - sqlStmt.columns().map((col) => col.type ?? ""), - ); - const rows = sqlStmt.all(args).map((sqlRow) => { - return rowFromSql(sqlRow as Array, columns, intMode); - }); - // TODO: can we get this info from better-sqlite3? - const rowsAffected = 0; - const lastInsertRowid = undefined; - return new ResultSetImpl( - columns, - columnTypes, - rows, - rowsAffected, - lastInsertRowid, - ); - } else { - const info = sqlStmt.run(args); - const rowsAffected = info.changes; - const lastInsertRowid = BigInt(info.lastInsertRowid); - return new ResultSetImpl([], [], [], rowsAffected, lastInsertRowid); + if (returnsData) { + const columns = Array.from( + sqlStmt.columns().map((col) => col.name), + ); + const columnTypes = Array.from( + sqlStmt.columns().map((col) => col.type ?? ""), + ); + const rows = sqlStmt.all(args).map((sqlRow) => { + return rowFromSql(sqlRow as Array, columns, intMode); + }); + // TODO: can we get this info from better-sqlite3? + const rowsAffected = 0; + const lastInsertRowid = undefined; + return new ResultSetImpl( + columns, + columnTypes, + rows, + rowsAffected, + lastInsertRowid, + ); + } else { + const info = sqlStmt.run(args); + const rowsAffected = info.changes; + const lastInsertRowid = BigInt(info.lastInsertRowid); + return new ResultSetImpl([], [], [], rowsAffected, lastInsertRowid); + } + } catch (e) { + throw mapSqliteError(e); } - } catch (e) { - throw mapSqliteError(e); - } } function rowFromSql( - sqlRow: Array, - columns: Array, - intMode: IntMode, + sqlRow: Array, + columns: Array, + intMode: IntMode, ): Row { - const row = {}; - // make sure that the "length" property is not enumerable - Object.defineProperty(row, "length", { value: sqlRow.length }); - for (let i = 0; i < sqlRow.length; ++i) { - const value = valueFromSql(sqlRow[i], intMode); - Object.defineProperty(row, i, { value }); - - const column = columns[i]; - if (!Object.hasOwn(row, column)) { - Object.defineProperty(row, column, { - value, - enumerable: true, - configurable: true, - writable: true, - }); + const row = {}; + // make sure that the "length" property is not enumerable + Object.defineProperty(row, "length", { value: sqlRow.length }); + for (let i = 0; i < sqlRow.length; ++i) { + const value = valueFromSql(sqlRow[i], intMode); + Object.defineProperty(row, i, { value }); + + const column = columns[i]; + if (!Object.hasOwn(row, column)) { + Object.defineProperty(row, column, { + value, + enumerable: true, + configurable: true, + writable: true, + }); + } } - } - return row as Row; + return row as Row; } function valueFromSql(sqlValue: unknown, intMode: IntMode): Value { - if (typeof sqlValue === "bigint") { - if (intMode === "number") { - if (sqlValue < minSafeBigint || sqlValue > maxSafeBigint) { - throw new RangeError( - "Received integer which cannot be safely represented as a JavaScript number", - ); - } - return Number(sqlValue); - } else if (intMode === "bigint") { - return sqlValue; - } else if (intMode === "string") { - return "" + sqlValue; - } else { - throw new Error("Invalid value for IntMode"); + if (typeof sqlValue === "bigint") { + if (intMode === "number") { + if (sqlValue < minSafeBigint || sqlValue > maxSafeBigint) { + throw new RangeError( + "Received integer which cannot be safely represented as a JavaScript number", + ); + } + return Number(sqlValue); + } else if (intMode === "bigint") { + return sqlValue; + } else if (intMode === "string") { + return "" + sqlValue; + } else { + throw new Error("Invalid value for IntMode"); + } + } else if (sqlValue instanceof Buffer) { + return sqlValue.buffer; } - } else if (sqlValue instanceof Buffer) { - return sqlValue.buffer; - } - return sqlValue as Value; + return sqlValue as Value; } const minSafeBigint = -9007199254740991n; const maxSafeBigint = 9007199254740991n; function valueToSql(value: InValue, intMode: IntMode): unknown { - if (typeof value === "number") { - if (!Number.isFinite(value)) { - throw new RangeError( - "Only finite numbers (not Infinity or NaN) can be passed as arguments", - ); - } - return value; - } else if (typeof value === "bigint") { - if (value < minInteger || value > maxInteger) { - throw new RangeError( - "bigint is too large to be represented as a 64-bit integer and passed as argument", - ); - } - return value; - } else if (typeof value === "boolean") { - switch (intMode) { - case "bigint": - return value ? 1n : 0n; - case "string": - return value ? "1" : "0"; - default: - return value ? 1 : 0; + if (typeof value === "number") { + if (!Number.isFinite(value)) { + throw new RangeError( + "Only finite numbers (not Infinity or NaN) can be passed as arguments", + ); + } + return value; + } else if (typeof value === "bigint") { + if (value < minInteger || value > maxInteger) { + throw new RangeError( + "bigint is too large to be represented as a 64-bit integer and passed as argument", + ); + } + return value; + } else if (typeof value === "boolean") { + switch (intMode) { + case "bigint": + return value ? 1n : 0n; + case "string": + return value ? "1" : "0"; + default: + return value ? 1 : 0; + } + } else if (value instanceof ArrayBuffer) { + return Buffer.from(value); + } else if (value instanceof Date) { + return value.valueOf(); + } else if (value === undefined) { + throw new TypeError( + "undefined cannot be passed as argument to the database", + ); + } else { + return value; } - } else if (value instanceof ArrayBuffer) { - return Buffer.from(value); - } else if (value instanceof Date) { - return value.valueOf(); - } else if (value === undefined) { - throw new TypeError( - "undefined cannot be passed as argument to the database", - ); - } else { - return value; - } } const minInteger = -9223372036854775808n; const maxInteger = 9223372036854775807n; function executeMultiple(db: Database.Database, sql: string): void { - try { - db.exec(sql); - } catch (e) { - throw mapSqliteError(e); - } + try { + db.exec(sql); + } catch (e) { + throw mapSqliteError(e); + } } function mapSqliteError(e: unknown): unknown { - if (e instanceof Database.SqliteError) { - return new LibsqlError(e.message, e.code, e.rawCode, e); - } - return e; + if (e instanceof Database.SqliteError) { + return new LibsqlError(e.message, e.code, e.rawCode, e); + } + return e; } diff --git a/packages/libsql-client/src/web.ts b/packages/libsql-client/src/web.ts index 59bced8b..3e4d2c03 100644 --- a/packages/libsql-client/src/web.ts +++ b/packages/libsql-client/src/web.ts @@ -10,20 +10,20 @@ import { _createClient as _createHttpClient } from "./http.js"; export * from "@libsql/core/api"; export function createClient(config: Config): Client { - return _createClient(expandConfig(config, true)); + return _createClient(expandConfig(config, true)); } /** @private */ export function _createClient(config: ExpandedConfig): Client { - if (config.scheme === "ws" || config.scheme === "wss") { - return _createWsClient(config); - } else if (config.scheme === "http" || config.scheme === "https") { - return _createHttpClient(config); - } else { - throw new LibsqlError( - 'The client that uses Web standard APIs supports only "libsql:", "wss:", "ws:", "https:" and "http:" URLs, ' + - `got ${JSON.stringify(config.scheme + ":")}. For more information, please read ${supportedUrlLink}`, - "URL_SCHEME_NOT_SUPPORTED", - ); - } + if (config.scheme === "ws" || config.scheme === "wss") { + return _createWsClient(config); + } else if (config.scheme === "http" || config.scheme === "https") { + return _createHttpClient(config); + } else { + throw new LibsqlError( + 'The client that uses Web standard APIs supports only "libsql:", "wss:", "ws:", "https:" and "http:" URLs, ' + + `got ${JSON.stringify(config.scheme + ":")}. For more information, please read ${supportedUrlLink}`, + "URL_SCHEME_NOT_SUPPORTED", + ); + } } diff --git a/packages/libsql-client/src/ws.ts b/packages/libsql-client/src/ws.ts index 2c47c1e7..8b5f0c00 100644 --- a/packages/libsql-client/src/ws.ts +++ b/packages/libsql-client/src/ws.ts @@ -1,22 +1,22 @@ import * as hrana from "@libsql/hrana-client"; import type { - Config, - IntMode, - Client, - Transaction, - ResultSet, - InStatement, + Config, + IntMode, + Client, + Transaction, + ResultSet, + InStatement, } from "@libsql/core/api"; import { TransactionMode, LibsqlError } from "@libsql/core/api"; import type { ExpandedConfig } from "@libsql/core/config"; import { expandConfig } from "@libsql/core/config"; import { - HranaTransaction, - executeHranaBatch, - stmtToHrana, - resultSetFromHrana, - mapHranaError, + HranaTransaction, + executeHranaBatch, + stmtToHrana, + resultSetFromHrana, + mapHranaError, } from "./hrana.js"; import { SqlCache } from "./sql_cache.js"; import { encodeBaseUrl } from "@libsql/core/uri"; @@ -25,344 +25,351 @@ import { supportedUrlLink } from "@libsql/core/util"; export * from "@libsql/core/api"; export function createClient(config: Config): WsClient { - return _createClient(expandConfig(config, false)); + return _createClient(expandConfig(config, false)); } /** @private */ export function _createClient(config: ExpandedConfig): WsClient { - if (config.scheme !== "wss" && config.scheme !== "ws") { - throw new LibsqlError( - 'The WebSocket client supports only "libsql:", "wss:" and "ws:" URLs, ' + - `got ${JSON.stringify(config.scheme + ":")}. For more information, please read ${supportedUrlLink}`, - "URL_SCHEME_NOT_SUPPORTED", - ); - } - - if (config.encryptionKey !== undefined) { - throw new LibsqlError( - "Encryption key is not supported by the remote client.", - "ENCRYPTION_KEY_NOT_SUPPORTED", - ); - } - - if (config.scheme === "ws" && config.tls) { - throw new LibsqlError( - `A "ws:" URL cannot opt into TLS by using ?tls=1`, - "URL_INVALID", - ); - } else if (config.scheme === "wss" && !config.tls) { - throw new LibsqlError( - `A "wss:" URL cannot opt out of TLS by using ?tls=0`, - "URL_INVALID", - ); - } - - const url = encodeBaseUrl(config.scheme, config.authority, config.path); - - let client: hrana.WsClient; - try { - client = hrana.openWs(url, config.authToken); - } catch (e) { - if (e instanceof hrana.WebSocketUnsupportedError) { - const suggestedScheme = config.scheme === "wss" ? "https" : "http"; - const suggestedUrl = encodeBaseUrl( - suggestedScheme, - config.authority, - config.path, - ); - throw new LibsqlError( - "This environment does not support WebSockets, please switch to the HTTP client by using " + - `a "${suggestedScheme}:" URL (${JSON.stringify(suggestedUrl)}). ` + - `For more information, please read ${supportedUrlLink}`, - "WEBSOCKETS_NOT_SUPPORTED", - ); + if (config.scheme !== "wss" && config.scheme !== "ws") { + throw new LibsqlError( + 'The WebSocket client supports only "libsql:", "wss:" and "ws:" URLs, ' + + `got ${JSON.stringify(config.scheme + ":")}. For more information, please read ${supportedUrlLink}`, + "URL_SCHEME_NOT_SUPPORTED", + ); } - throw mapHranaError(e); - } - return new WsClient(client, url, config.authToken, config.intMode); + if (config.encryptionKey !== undefined) { + throw new LibsqlError( + "Encryption key is not supported by the remote client.", + "ENCRYPTION_KEY_NOT_SUPPORTED", + ); + } + + if (config.scheme === "ws" && config.tls) { + throw new LibsqlError( + `A "ws:" URL cannot opt into TLS by using ?tls=1`, + "URL_INVALID", + ); + } else if (config.scheme === "wss" && !config.tls) { + throw new LibsqlError( + `A "wss:" URL cannot opt out of TLS by using ?tls=0`, + "URL_INVALID", + ); + } + + const url = encodeBaseUrl(config.scheme, config.authority, config.path); + + let client: hrana.WsClient; + try { + client = hrana.openWs(url, config.authToken); + } catch (e) { + if (e instanceof hrana.WebSocketUnsupportedError) { + const suggestedScheme = config.scheme === "wss" ? "https" : "http"; + const suggestedUrl = encodeBaseUrl( + suggestedScheme, + config.authority, + config.path, + ); + throw new LibsqlError( + "This environment does not support WebSockets, please switch to the HTTP client by using " + + `a "${suggestedScheme}:" URL (${JSON.stringify(suggestedUrl)}). ` + + `For more information, please read ${supportedUrlLink}`, + "WEBSOCKETS_NOT_SUPPORTED", + ); + } + throw mapHranaError(e); + } + + return new WsClient(client, url, config.authToken, config.intMode); } // This object maintains state for a single WebSocket connection. interface ConnState { - // The Hrana client (which corresponds to a single WebSocket). - client: hrana.WsClient; - // We can cache SQL texts on the server only if the server supports Hrana 2. But to get the server - // version, we need to wait for the WebSocket handshake to complete, so this value is initially - // `undefined`, until we find out the version. - useSqlCache: boolean | undefined; - // The cache of SQL texts stored on the server. Initially has capacity 0, but it is set to - // `sqlCacheCapacity` when `useSqlCache` is set to `true`. - sqlCache: SqlCache; - // The time when the connection was opened. - openTime: Date; - // Set of all `StreamState`-s that were opened from this connection. We can safely close the connection - // only when this is empty. - streamStates: Set; + // The Hrana client (which corresponds to a single WebSocket). + client: hrana.WsClient; + // We can cache SQL texts on the server only if the server supports Hrana 2. But to get the server + // version, we need to wait for the WebSocket handshake to complete, so this value is initially + // `undefined`, until we find out the version. + useSqlCache: boolean | undefined; + // The cache of SQL texts stored on the server. Initially has capacity 0, but it is set to + // `sqlCacheCapacity` when `useSqlCache` is set to `true`. + sqlCache: SqlCache; + // The time when the connection was opened. + openTime: Date; + // Set of all `StreamState`-s that were opened from this connection. We can safely close the connection + // only when this is empty. + streamStates: Set; } interface StreamState { - conn: ConnState; - stream: hrana.WsStream; + conn: ConnState; + stream: hrana.WsStream; } const maxConnAgeMillis = 60 * 1000; const sqlCacheCapacity = 100; export class WsClient implements Client { - #url: URL; - #authToken: string | undefined; - #intMode: IntMode; - // State of the current connection. The `hrana.WsClient` inside may be closed at any moment due to an - // asynchronous error. - #connState: ConnState; - // If defined, this is a connection that will be used in the future, once it is ready. - #futureConnState: ConnState | undefined; - closed: boolean; - protocol: "ws"; - - /** @private */ - constructor( - client: hrana.WsClient, - url: URL, - authToken: string | undefined, - intMode: IntMode, - ) { - this.#url = url; - this.#authToken = authToken; - this.#intMode = intMode; - this.#connState = this.#openConn(client); - this.#futureConnState = undefined; - this.closed = false; - this.protocol = "ws"; - } - - async execute(stmt: InStatement): Promise { - const streamState = await this.#openStream(); - try { - const hranaStmt = stmtToHrana(stmt); - - // Schedule all operations synchronously, so they will be pipelined and executed in a single - // network roundtrip. - streamState.conn.sqlCache.apply([hranaStmt]); - const hranaRowsPromise = streamState.stream.query(hranaStmt); - streamState.stream.closeGracefully(); + #url: URL; + #authToken: string | undefined; + #intMode: IntMode; + // State of the current connection. The `hrana.WsClient` inside may be closed at any moment due to an + // asynchronous error. + #connState: ConnState; + // If defined, this is a connection that will be used in the future, once it is ready. + #futureConnState: ConnState | undefined; + closed: boolean; + protocol: "ws"; + + /** @private */ + constructor( + client: hrana.WsClient, + url: URL, + authToken: string | undefined, + intMode: IntMode, + ) { + this.#url = url; + this.#authToken = authToken; + this.#intMode = intMode; + this.#connState = this.#openConn(client); + this.#futureConnState = undefined; + this.closed = false; + this.protocol = "ws"; + } - return resultSetFromHrana(await hranaRowsPromise); - } catch (e) { - throw mapHranaError(e); - } finally { - this._closeStream(streamState); + async execute(stmt: InStatement): Promise { + const streamState = await this.#openStream(); + try { + const hranaStmt = stmtToHrana(stmt); + + // Schedule all operations synchronously, so they will be pipelined and executed in a single + // network roundtrip. + streamState.conn.sqlCache.apply([hranaStmt]); + const hranaRowsPromise = streamState.stream.query(hranaStmt); + streamState.stream.closeGracefully(); + + return resultSetFromHrana(await hranaRowsPromise); + } catch (e) { + throw mapHranaError(e); + } finally { + this._closeStream(streamState); + } } - } - async batch( - stmts: Array, - mode: TransactionMode = "deferred", - ): Promise> { - const streamState = await this.#openStream(); - try { - const hranaStmts = stmts.map(stmtToHrana); - const version = await streamState.conn.client.getVersion(); - - // Schedule all operations synchronously, so they will be pipelined and executed in a single - // network roundtrip. - streamState.conn.sqlCache.apply(hranaStmts); - const batch = streamState.stream.batch(version >= 3); - const resultsPromise = executeHranaBatch( - mode, - version, - batch, - hranaStmts, - ); - - return await resultsPromise; - } catch (e) { - throw mapHranaError(e); - } finally { - this._closeStream(streamState); + async batch( + stmts: Array, + mode: TransactionMode = "deferred", + ): Promise> { + const streamState = await this.#openStream(); + try { + const hranaStmts = stmts.map(stmtToHrana); + const version = await streamState.conn.client.getVersion(); + + // Schedule all operations synchronously, so they will be pipelined and executed in a single + // network roundtrip. + streamState.conn.sqlCache.apply(hranaStmts); + const batch = streamState.stream.batch(version >= 3); + const resultsPromise = executeHranaBatch( + mode, + version, + batch, + hranaStmts, + ); + + return await resultsPromise; + } catch (e) { + throw mapHranaError(e); + } finally { + this._closeStream(streamState); + } } - } - async transaction(mode: TransactionMode = "write"): Promise { - const streamState = await this.#openStream(); - try { - const version = await streamState.conn.client.getVersion(); - // the BEGIN statement will be batched with the first statement on the transaction to save a - // network roundtrip - return new WsTransaction(this, streamState, mode, version); - } catch (e) { - this._closeStream(streamState); - throw mapHranaError(e); + async transaction(mode: TransactionMode = "write"): Promise { + const streamState = await this.#openStream(); + try { + const version = await streamState.conn.client.getVersion(); + // the BEGIN statement will be batched with the first statement on the transaction to save a + // network roundtrip + return new WsTransaction(this, streamState, mode, version); + } catch (e) { + this._closeStream(streamState); + throw mapHranaError(e); + } } - } - async executeMultiple(sql: string): Promise { - const streamState = await this.#openStream(); - try { - // Schedule all operations synchronously, so they will be pipelined and executed in a single - // network roundtrip. - const promise = streamState.stream.sequence(sql); - streamState.stream.closeGracefully(); + async executeMultiple(sql: string): Promise { + const streamState = await this.#openStream(); + try { + // Schedule all operations synchronously, so they will be pipelined and executed in a single + // network roundtrip. + const promise = streamState.stream.sequence(sql); + streamState.stream.closeGracefully(); + + await promise; + } catch (e) { + throw mapHranaError(e); + } finally { + this._closeStream(streamState); + } + } - await promise; - } catch (e) { - throw mapHranaError(e); - } finally { - this._closeStream(streamState); + sync(): Promise { + return Promise.resolve(); } - } - sync(): Promise { - return Promise.resolve(); - } + async #openStream(): Promise { + if (this.closed) { + throw new LibsqlError("The client is closed", "CLIENT_CLOSED"); + } + + const now = new Date(); + + const ageMillis = now.valueOf() - this.#connState.openTime.valueOf(); + if ( + ageMillis > maxConnAgeMillis && + this.#futureConnState === undefined + ) { + // The existing connection is too old, let's open a new one. + const futureConnState = this.#openConn(); + this.#futureConnState = futureConnState; + + // However, if we used `futureConnState` immediately, we would introduce additional latency, + // because we would have to wait for the WebSocket handshake to complete, even though we may a + // have perfectly good existing connection in `this.#connState`! + // + // So we wait until the `hrana.Client.getVersion()` operation completes (which happens when the + // WebSocket hanshake completes), and only then we replace `this.#connState` with + // `futureConnState`, which is stored in `this.#futureConnState` in the meantime. + futureConnState.client.getVersion().then( + (_version) => { + if (this.#connState !== futureConnState) { + // We need to close `this.#connState` before we replace it. However, it is possible + // that `this.#connState` has already been replaced: see the code below. + if (this.#connState.streamStates.size === 0) { + this.#connState.client.close(); + } else { + // If there are existing streams on the connection, we must not close it, because + // these streams would be broken. The last stream to be closed will also close the + // connection in `_closeStream()`. + } + } + + this.#connState = futureConnState; + this.#futureConnState = undefined; + }, + (_e) => { + // If the new connection could not be established, let's just ignore the error and keep + // using the existing connection. + this.#futureConnState = undefined; + }, + ); + } - async #openStream(): Promise { - if (this.closed) { - throw new LibsqlError("The client is closed", "CLIENT_CLOSED"); - } + if (this.#connState.client.closed) { + // An error happened on this connection and it has been closed. Let's try to seamlessly reconnect. + try { + if (this.#futureConnState !== undefined) { + // We are already in the process of opening a new connection, so let's just use it + // immediately. + this.#connState = this.#futureConnState; + } else { + this.#connState = this.#openConn(); + } + } catch (e) { + throw mapHranaError(e); + } + } - const now = new Date(); - - const ageMillis = now.valueOf() - this.#connState.openTime.valueOf(); - if (ageMillis > maxConnAgeMillis && this.#futureConnState === undefined) { - // The existing connection is too old, let's open a new one. - const futureConnState = this.#openConn(); - this.#futureConnState = futureConnState; - - // However, if we used `futureConnState` immediately, we would introduce additional latency, - // because we would have to wait for the WebSocket handshake to complete, even though we may a - // have perfectly good existing connection in `this.#connState`! - // - // So we wait until the `hrana.Client.getVersion()` operation completes (which happens when the - // WebSocket hanshake completes), and only then we replace `this.#connState` with - // `futureConnState`, which is stored in `this.#futureConnState` in the meantime. - futureConnState.client.getVersion().then( - (_version) => { - if (this.#connState !== futureConnState) { - // We need to close `this.#connState` before we replace it. However, it is possible - // that `this.#connState` has already been replaced: see the code below. - if (this.#connState.streamStates.size === 0) { - this.#connState.client.close(); - } else { - // If there are existing streams on the connection, we must not close it, because - // these streams would be broken. The last stream to be closed will also close the - // connection in `_closeStream()`. + const connState = this.#connState; + try { + // Now we wait for the WebSocket handshake to complete (if it hasn't completed yet). Note that + // this does not increase latency, because any messages that we would send on the WebSocket before + // the handshake would be queued until the handshake is completed anyway. + if (connState.useSqlCache === undefined) { + connState.useSqlCache = + (await connState.client.getVersion()) >= 2; + if (connState.useSqlCache) { + connState.sqlCache.capacity = sqlCacheCapacity; + } } - } - - this.#connState = futureConnState; - this.#futureConnState = undefined; - }, - (_e) => { - // If the new connection could not be established, let's just ignore the error and keep - // using the existing connection. - this.#futureConnState = undefined; - }, - ); + + const stream = connState.client.openStream(); + stream.intMode = this.#intMode; + const streamState = { conn: connState, stream }; + connState.streamStates.add(streamState); + return streamState; + } catch (e) { + throw mapHranaError(e); + } } - if (this.#connState.client.closed) { - // An error happened on this connection and it has been closed. Let's try to seamlessly reconnect. - try { - if (this.#futureConnState !== undefined) { - // We are already in the process of opening a new connection, so let's just use it - // immediately. - this.#connState = this.#futureConnState; - } else { - this.#connState = this.#openConn(); + #openConn(client?: hrana.WsClient): ConnState { + try { + client ??= hrana.openWs(this.#url, this.#authToken); + return { + client, + useSqlCache: undefined, + sqlCache: new SqlCache(client, 0), + openTime: new Date(), + streamStates: new Set(), + }; + } catch (e) { + throw mapHranaError(e); } - } catch (e) { - throw mapHranaError(e); - } } - const connState = this.#connState; - try { - // Now we wait for the WebSocket handshake to complete (if it hasn't completed yet). Note that - // this does not increase latency, because any messages that we would send on the WebSocket before - // the handshake would be queued until the handshake is completed anyway. - if (connState.useSqlCache === undefined) { - connState.useSqlCache = (await connState.client.getVersion()) >= 2; - if (connState.useSqlCache) { - connState.sqlCache.capacity = sqlCacheCapacity; + _closeStream(streamState: StreamState): void { + streamState.stream.close(); + + const connState = streamState.conn; + connState.streamStates.delete(streamState); + if ( + connState.streamStates.size === 0 && + connState !== this.#connState + ) { + // We are not using this connection anymore and this is the last stream that was using it, so we + // must close it now. + connState.client.close(); } - } + } - const stream = connState.client.openStream(); - stream.intMode = this.#intMode; - const streamState = { conn: connState, stream }; - connState.streamStates.add(streamState); - return streamState; - } catch (e) { - throw mapHranaError(e); + close(): void { + this.#connState.client.close(); + this.closed = true; } - } +} - #openConn(client?: hrana.WsClient): ConnState { - try { - client ??= hrana.openWs(this.#url, this.#authToken); - return { - client, - useSqlCache: undefined, - sqlCache: new SqlCache(client, 0), - openTime: new Date(), - streamStates: new Set(), - }; - } catch (e) { - throw mapHranaError(e); +export class WsTransaction extends HranaTransaction implements Transaction { + #client: WsClient; + #streamState: StreamState; + + /** @private */ + constructor( + client: WsClient, + state: StreamState, + mode: TransactionMode, + version: hrana.ProtocolVersion, + ) { + super(mode, version); + this.#client = client; + this.#streamState = state; } - } - _closeStream(streamState: StreamState): void { - streamState.stream.close(); + /** @private */ + override _getStream(): hrana.Stream { + return this.#streamState.stream; + } - const connState = streamState.conn; - connState.streamStates.delete(streamState); - if (connState.streamStates.size === 0 && connState !== this.#connState) { - // We are not using this connection anymore and this is the last stream that was using it, so we - // must close it now. - connState.client.close(); + /** @private */ + override _getSqlCache(): SqlCache { + return this.#streamState.conn.sqlCache; } - } - close(): void { - this.#connState.client.close(); - this.closed = true; - } -} + override close(): void { + this.#client._closeStream(this.#streamState); + } -export class WsTransaction extends HranaTransaction implements Transaction { - #client: WsClient; - #streamState: StreamState; - - /** @private */ - constructor( - client: WsClient, - state: StreamState, - mode: TransactionMode, - version: hrana.ProtocolVersion, - ) { - super(mode, version); - this.#client = client; - this.#streamState = state; - } - - /** @private */ - override _getStream(): hrana.Stream { - return this.#streamState.stream; - } - - /** @private */ - override _getSqlCache(): SqlCache { - return this.#streamState.conn.sqlCache; - } - - override close(): void { - this.#client._closeStream(this.#streamState); - } - - override get closed(): boolean { - return this.#streamState.stream.closed; - } + override get closed(): boolean { + return this.#streamState.stream.closed; + } } diff --git a/packages/libsql-client/tsconfig.base.json b/packages/libsql-client/tsconfig.base.json index bfdf9532..e6d67e71 100644 --- a/packages/libsql-client/tsconfig.base.json +++ b/packages/libsql-client/tsconfig.base.json @@ -1,13 +1,13 @@ { - "compilerOptions": { - "moduleResolution": "node", - "lib": ["esnext"], - "target": "esnext", - "esModuleInterop": true, - "isolatedModules": true, - "rootDir": "src/", - "strict": true - }, - "include": ["src/"], - "exclude": ["**/__tests__"] + "compilerOptions": { + "moduleResolution": "node", + "lib": ["esnext"], + "target": "esnext", + "esModuleInterop": true, + "isolatedModules": true, + "rootDir": "src/", + "strict": true + }, + "include": ["src/"], + "exclude": ["**/__tests__"] } diff --git a/packages/libsql-client/tsconfig.build-cjs.json b/packages/libsql-client/tsconfig.build-cjs.json index fa26814d..8feb91c5 100644 --- a/packages/libsql-client/tsconfig.build-cjs.json +++ b/packages/libsql-client/tsconfig.build-cjs.json @@ -1,8 +1,8 @@ { - "extends": "./tsconfig.base.json", - "compilerOptions": { - "module": "commonjs", - "declaration": false, - "outDir": "./lib-cjs/" - } + "extends": "./tsconfig.base.json", + "compilerOptions": { + "module": "commonjs", + "declaration": false, + "outDir": "./lib-cjs/" + } } diff --git a/packages/libsql-client/tsconfig.build-esm.json b/packages/libsql-client/tsconfig.build-esm.json index a015a304..2ba1706d 100644 --- a/packages/libsql-client/tsconfig.build-esm.json +++ b/packages/libsql-client/tsconfig.build-esm.json @@ -1,8 +1,8 @@ { - "extends": "./tsconfig.base.json", - "compilerOptions": { - "module": "esnext", - "declaration": true, - "outDir": "./lib-esm/" - } + "extends": "./tsconfig.base.json", + "compilerOptions": { + "module": "esnext", + "declaration": true, + "outDir": "./lib-esm/" + } } diff --git a/packages/libsql-client/tsconfig.json b/packages/libsql-client/tsconfig.json index 23f862fb..bc064274 100644 --- a/packages/libsql-client/tsconfig.json +++ b/packages/libsql-client/tsconfig.json @@ -1,7 +1,7 @@ { - "extends": "./tsconfig.base.json", - "compilerOptions": { - "noEmit": true, - "incremental": true - } + "extends": "./tsconfig.base.json", + "compilerOptions": { + "noEmit": true, + "incremental": true + } } diff --git a/packages/libsql-client/typedoc.json b/packages/libsql-client/typedoc.json index de52c9c4..4c5154ac 100644 --- a/packages/libsql-client/typedoc.json +++ b/packages/libsql-client/typedoc.json @@ -1,11 +1,11 @@ { - "entryPoints": ["src/node.ts"], - "out": "docs", - "excludePrivate": true, - "excludeInternal": true, - "visibilityFilters": { - "inherited": true, - "external": true - }, - "includeVersion": true + "entryPoints": ["src/node.ts"], + "out": "docs", + "excludePrivate": true, + "excludeInternal": true, + "visibilityFilters": { + "inherited": true, + "external": true + }, + "includeVersion": true } diff --git a/packages/libsql-core/jest.config.js b/packages/libsql-core/jest.config.js index c558e7d7..044a5b86 100644 --- a/packages/libsql-core/jest.config.js +++ b/packages/libsql-core/jest.config.js @@ -1,7 +1,7 @@ export default { - preset: "ts-jest/presets/default-esm", - moduleNameMapper: { - "^(\\.{1,2}/.*)\\.js$": "$1", - }, - testMatch: ["**/__tests__/*.test.[jt]s"], + preset: "ts-jest/presets/default-esm", + moduleNameMapper: { + "^(\\.{1,2}/.*)\\.js$": "$1", + }, + testMatch: ["**/__tests__/*.test.[jt]s"], }; diff --git a/packages/libsql-core/package-cjs.json b/packages/libsql-core/package-cjs.json index 5bbefffb..1cd945a3 100644 --- a/packages/libsql-core/package-cjs.json +++ b/packages/libsql-core/package-cjs.json @@ -1,3 +1,3 @@ { - "type": "commonjs" + "type": "commonjs" } diff --git a/packages/libsql-core/package.json b/packages/libsql-core/package.json index de3a0e5b..1362015f 100644 --- a/packages/libsql-core/package.json +++ b/packages/libsql-core/package.json @@ -1,89 +1,89 @@ { - "name": "@libsql/core", - "version": "0.6.0", - "keywords": [ - "libsql", - "database", - "sqlite", - "serverless", - "vercel", - "netlify", - "lambda" - ], - "description": "libSQL driver for TypeScript and JavaScript", - "repository": { - "type": "git", - "url": "https://github.com/libsql/libsql-client-ts" - }, - "authors": [ - "Jan Špaček ", - "Pekka Enberg ", - "Jan Plhak " - ], - "license": "MIT", - "type": "module", - "exports": { - "./api": { - "types": "./lib-esm/api.d.ts", - "import": "./lib-esm/api.js", - "require": "./lib-cjs/api.js" + "name": "@libsql/core", + "version": "0.6.0", + "keywords": [ + "libsql", + "database", + "sqlite", + "serverless", + "vercel", + "netlify", + "lambda" + ], + "description": "libSQL driver for TypeScript and JavaScript", + "repository": { + "type": "git", + "url": "https://github.com/libsql/libsql-client-ts" }, - "./config": { - "types": "./lib-esm/config.d.ts", - "import": "./lib-esm/config.js", - "require": "./lib-cjs/config.js" + "authors": [ + "Jan Špaček ", + "Pekka Enberg ", + "Jan Plhak " + ], + "license": "MIT", + "type": "module", + "exports": { + "./api": { + "types": "./lib-esm/api.d.ts", + "import": "./lib-esm/api.js", + "require": "./lib-cjs/api.js" + }, + "./config": { + "types": "./lib-esm/config.d.ts", + "import": "./lib-esm/config.js", + "require": "./lib-cjs/config.js" + }, + "./uri": { + "types": "./lib-esm/uri.d.ts", + "import": "./lib-esm/uri.js", + "require": "./lib-cjs/uri.js" + }, + "./util": { + "types": "./lib-esm/util.d.ts", + "import": "./lib-esm/util.js", + "require": "./lib-cjs/util.js" + } }, - "./uri": { - "types": "./lib-esm/uri.d.ts", - "import": "./lib-esm/uri.js", - "require": "./lib-cjs/uri.js" + "typesVersions": { + "*": { + "api": [ + "./lib-esm/api.d.ts" + ], + "config": [ + "./lib-esm/config.d.ts" + ], + "uri": [ + "./lib-esm/uri.d.ts" + ], + "util": [ + "./lib-esm/util.d.ts" + ] + } }, - "./util": { - "types": "./lib-esm/util.d.ts", - "import": "./lib-esm/util.js", - "require": "./lib-cjs/util.js" - } - }, - "typesVersions": { - "*": { - "api": [ - "./lib-esm/api.d.ts" - ], - "config": [ - "./lib-esm/config.d.ts" - ], - "uri": [ - "./lib-esm/uri.d.ts" - ], - "util": [ - "./lib-esm/util.d.ts" - ] + "files": [ + "lib-cjs/**", + "lib-esm/**" + ], + "scripts": { + "prepublishOnly": "npm run build", + "prebuild": "rm -rf ./lib-cjs ./lib-esm", + "build": "npm run build:cjs && npm run build:esm", + "build:cjs": "tsc -p tsconfig.build-cjs.json", + "build:esm": "tsc -p tsconfig.build-esm.json", + "postbuild": "cp package-cjs.json ./lib-cjs/package.json", + "test": "jest --runInBand", + "typecheck": "tsc --noEmit", + "typedoc": "rm -rf ./docs && typedoc" + }, + "dependencies": { + "js-base64": "^3.7.5" + }, + "devDependencies": { + "@types/jest": "^29.2.5", + "@types/node": "^18.15.5", + "jest": "^29.3.1", + "ts-jest": "^29.0.5", + "typedoc": "^0.23.28", + "typescript": "^4.9.4" } - }, - "files": [ - "lib-cjs/**", - "lib-esm/**" - ], - "scripts": { - "prepublishOnly": "npm run build", - "prebuild": "rm -rf ./lib-cjs ./lib-esm", - "build": "npm run build:cjs && npm run build:esm", - "build:cjs": "tsc -p tsconfig.build-cjs.json", - "build:esm": "tsc -p tsconfig.build-esm.json", - "postbuild": "cp package-cjs.json ./lib-cjs/package.json", - "test": "jest --runInBand", - "typecheck": "tsc --noEmit", - "typedoc": "rm -rf ./docs && typedoc" - }, - "dependencies": { - "js-base64": "^3.7.5" - }, - "devDependencies": { - "@types/jest": "^29.2.5", - "@types/node": "^18.15.5", - "jest": "^29.3.1", - "ts-jest": "^29.0.5", - "typedoc": "^0.23.28", - "typescript": "^4.9.4" - } } diff --git a/packages/libsql-core/src/api.ts b/packages/libsql-core/src/api.ts index 1a463ac8..6278fc03 100644 --- a/packages/libsql-core/src/api.ts +++ b/packages/libsql-core/src/api.ts @@ -1,51 +1,51 @@ /** Configuration object for {@link createClient}. */ export interface Config { - /** The database URL. - * - * The client supports `libsql:`, `http:`/`https:`, `ws:`/`wss:` and `file:` URL. For more infomation, - * please refer to the project README: - * - * https://github.com/libsql/libsql-client-ts#supported-urls - */ - url: string; - - /** Authentication token for the database. */ - authToken?: string; - - /** Encryption key for the database. */ - encryptionKey?: string; - - /** URL of a remote server to synchronize database with. */ - syncUrl?: string; - - /** Sync interval in seconds. */ - syncInterval?: number; - - /** Enables or disables TLS for `libsql:` URLs. - * - * By default, `libsql:` URLs use TLS. You can set this option to `false` to disable TLS. - */ - tls?: boolean; - - /** How to convert SQLite integers to JavaScript values: - * - * - `"number"` (default): returns SQLite integers as JavaScript `number`-s (double precision floats). - * `number` cannot precisely represent integers larger than 2^53-1 in absolute value, so attempting to read - * larger integers will throw a `RangeError`. - * - `"bigint"`: returns SQLite integers as JavaScript `bigint`-s (arbitrary precision integers). Bigints can - * precisely represent all SQLite integers. - * - `"string"`: returns SQLite integers as strings. - */ - intMode?: IntMode; - - /** Custom `fetch` function to use for the HTTP client. - * - * By default, the HTTP client uses `fetch` from the `@libsql/isomorphic-fetch` package, but you can pass - * your own function here. The argument to this function will be `Request` from - * `@libsql/isomorphic-fetch`, and it must return a promise that resolves to an object that is compatible - * with the Web `Response`. - */ - fetch?: Function; + /** The database URL. + * + * The client supports `libsql:`, `http:`/`https:`, `ws:`/`wss:` and `file:` URL. For more infomation, + * please refer to the project README: + * + * https://github.com/libsql/libsql-client-ts#supported-urls + */ + url: string; + + /** Authentication token for the database. */ + authToken?: string; + + /** Encryption key for the database. */ + encryptionKey?: string; + + /** URL of a remote server to synchronize database with. */ + syncUrl?: string; + + /** Sync interval in seconds. */ + syncInterval?: number; + + /** Enables or disables TLS for `libsql:` URLs. + * + * By default, `libsql:` URLs use TLS. You can set this option to `false` to disable TLS. + */ + tls?: boolean; + + /** How to convert SQLite integers to JavaScript values: + * + * - `"number"` (default): returns SQLite integers as JavaScript `number`-s (double precision floats). + * `number` cannot precisely represent integers larger than 2^53-1 in absolute value, so attempting to read + * larger integers will throw a `RangeError`. + * - `"bigint"`: returns SQLite integers as JavaScript `bigint`-s (arbitrary precision integers). Bigints can + * precisely represent all SQLite integers. + * - `"string"`: returns SQLite integers as strings. + */ + intMode?: IntMode; + + /** Custom `fetch` function to use for the HTTP client. + * + * By default, the HTTP client uses `fetch` from the `@libsql/isomorphic-fetch` package, but you can pass + * your own function here. The argument to this function will be `Request` from + * `@libsql/isomorphic-fetch`, and it must return a promise that resolves to an object that is compatible + * with the Web `Response`. + */ + fetch?: Function; } /** Representation of integers from database as JavaScript values. See {@link Config.intMode}. */ @@ -56,159 +56,159 @@ export type IntMode = "number" | "bigint" | "string"; * After you are done with the client, you **should** close it by calling {@link close}. */ export interface Client { - /** Execute a single SQL statement. - * - * Every statement executed with this method is executed in its own logical database connection. If you - * want to execute a group of statements in a transaction, use the {@link batch} or the {@link - * transaction} methods. - * - * ```javascript - * // execute a statement without arguments - * const rs = await client.execute("SELECT * FROM books"); - * - * // execute a statement with positional arguments - * const rs = await client.execute({ - * sql: "SELECT * FROM books WHERE author = ?", - * args: ["Jane Austen"], - * }); - * - * // execute a statement with named arguments - * const rs = await client.execute({ - * sql: "SELECT * FROM books WHERE published_at > $year", - * args: {year: 1719}, - * }); - * ``` - */ - execute(stmt: InStatement): Promise; - - /** Execute a batch of SQL statements in a transaction. - * - * The batch is executed in its own logical database connection and the statements are wrapped in a - * transaction. This ensures that the batch is applied atomically: either all or no changes are applied. - * - * The `mode` parameter selects the transaction mode for the batch; please see {@link TransactionMode} for - * details. The default transaction mode is `"deferred"`. - * - * If any of the statements in the batch fails with an error, the batch is aborted, the transaction is - * rolled back and the returned promise is rejected. - * - * This method provides non-interactive transactions. If you need interactive transactions, please use the - * {@link transaction} method. - * - * ```javascript - * const rss = await client.batch([ - * // batch statement without arguments - * "DELETE FROM books WHERE name LIKE '%Crusoe'", - * - * // batch statement with positional arguments - * { - * sql: "INSERT INTO books (name, author, published_at) VALUES (?, ?, ?)", - * args: ["First Impressions", "Jane Austen", 1813], - * }, - * - * // batch statement with named arguments - * { - * sql: "UPDATE books SET name = $new WHERE name = $old", - * args: {old: "First Impressions", new: "Pride and Prejudice"}, - * }, - * ], "write"); - * ``` - */ - batch( - stmts: Array, - mode?: TransactionMode, - ): Promise>; - - /** Start an interactive transaction. - * - * Interactive transactions allow you to interleave execution of SQL statements with your application - * logic. They can be used if the {@link batch} method is too restrictive, but please note that - * interactive transactions have higher latency. - * - * The `mode` parameter selects the transaction mode for the interactive transaction; please see {@link - * TransactionMode} for details. The default transaction mode is `"deferred"`. - * - * You **must** make sure that the returned {@link Transaction} object is closed, by calling {@link - * Transaction.close}, {@link Transaction.commit} or {@link Transaction.rollback}. The best practice is - * to call {@link Transaction.close} in a `finally` block, as follows: - * - * ```javascript - * const transaction = client.transaction("write"); - * try { - * // do some operations with the transaction here - * await transaction.execute({ - * sql: "INSERT INTO books (name, author) VALUES (?, ?)", - * args: ["First Impressions", "Jane Austen"], - * }); - * await transaction.execute({ - * sql: "UPDATE books SET name = ? WHERE name = ?", - * args: ["Pride and Prejudice", "First Impressions"], - * }); - * - * // if all went well, commit the transaction - * await transaction.commit(); - * } finally { - * // make sure to close the transaction, even if an exception was thrown - * transaction.close(); - * } - * ``` - */ - transaction(mode?: TransactionMode): Promise; - - /** Start an interactive transaction in `"write"` mode. - * - * Please see {@link transaction} for details. - * - * @deprecated Please specify the `mode` explicitly. The default `"write"` will be removed in the next - * major release. - */ - transaction(): Promise; - - /** Execute a sequence of SQL statements separated by semicolons. - * - * The statements are executed sequentially on a new logical database connection. If a statement fails, - * further statements are not executed and this method throws an error. All results from the statements - * are ignored. - * - * We do not wrap the statements in a transaction, but the SQL can contain explicit transaction-control - * statements such as `BEGIN` and `COMMIT`. - * - * This method is intended to be used with existing SQL scripts, such as migrations or small database - * dumps. If you want to execute a sequence of statements programmatically, please use {@link batch} - * instead. - * - * ```javascript - * await client.executeMultiple(` - * CREATE TABLE books (id INTEGER PRIMARY KEY, title TEXT NOT NULL, author_id INTEGER NOT NULL); - * CREATE TABLE authors (id INTEGER PRIMARY KEY, name TEXT NOT NULL); - * `); - * ``` - */ - executeMultiple(sql: string): Promise; - - sync(): Promise; - - /** Close the client and release resources. - * - * This method closes the client (aborting any operations that are currently in progress) and releases any - * resources associated with the client (such as a WebSocket connection). - */ - close(): void; - - /** Is the client closed? - * - * This is set to `true` after a call to {@link close} or if the client encounters an unrecoverable - * error. - */ - closed: boolean; - - /** Which protocol does the client use? - * - * - `"http"` if the client connects over HTTP - * - `"ws"` if the client connects over WebSockets - * - `"file"` if the client works with a local file - */ - protocol: string; + /** Execute a single SQL statement. + * + * Every statement executed with this method is executed in its own logical database connection. If you + * want to execute a group of statements in a transaction, use the {@link batch} or the {@link + * transaction} methods. + * + * ```javascript + * // execute a statement without arguments + * const rs = await client.execute("SELECT * FROM books"); + * + * // execute a statement with positional arguments + * const rs = await client.execute({ + * sql: "SELECT * FROM books WHERE author = ?", + * args: ["Jane Austen"], + * }); + * + * // execute a statement with named arguments + * const rs = await client.execute({ + * sql: "SELECT * FROM books WHERE published_at > $year", + * args: {year: 1719}, + * }); + * ``` + */ + execute(stmt: InStatement): Promise; + + /** Execute a batch of SQL statements in a transaction. + * + * The batch is executed in its own logical database connection and the statements are wrapped in a + * transaction. This ensures that the batch is applied atomically: either all or no changes are applied. + * + * The `mode` parameter selects the transaction mode for the batch; please see {@link TransactionMode} for + * details. The default transaction mode is `"deferred"`. + * + * If any of the statements in the batch fails with an error, the batch is aborted, the transaction is + * rolled back and the returned promise is rejected. + * + * This method provides non-interactive transactions. If you need interactive transactions, please use the + * {@link transaction} method. + * + * ```javascript + * const rss = await client.batch([ + * // batch statement without arguments + * "DELETE FROM books WHERE name LIKE '%Crusoe'", + * + * // batch statement with positional arguments + * { + * sql: "INSERT INTO books (name, author, published_at) VALUES (?, ?, ?)", + * args: ["First Impressions", "Jane Austen", 1813], + * }, + * + * // batch statement with named arguments + * { + * sql: "UPDATE books SET name = $new WHERE name = $old", + * args: {old: "First Impressions", new: "Pride and Prejudice"}, + * }, + * ], "write"); + * ``` + */ + batch( + stmts: Array, + mode?: TransactionMode, + ): Promise>; + + /** Start an interactive transaction. + * + * Interactive transactions allow you to interleave execution of SQL statements with your application + * logic. They can be used if the {@link batch} method is too restrictive, but please note that + * interactive transactions have higher latency. + * + * The `mode` parameter selects the transaction mode for the interactive transaction; please see {@link + * TransactionMode} for details. The default transaction mode is `"deferred"`. + * + * You **must** make sure that the returned {@link Transaction} object is closed, by calling {@link + * Transaction.close}, {@link Transaction.commit} or {@link Transaction.rollback}. The best practice is + * to call {@link Transaction.close} in a `finally` block, as follows: + * + * ```javascript + * const transaction = client.transaction("write"); + * try { + * // do some operations with the transaction here + * await transaction.execute({ + * sql: "INSERT INTO books (name, author) VALUES (?, ?)", + * args: ["First Impressions", "Jane Austen"], + * }); + * await transaction.execute({ + * sql: "UPDATE books SET name = ? WHERE name = ?", + * args: ["Pride and Prejudice", "First Impressions"], + * }); + * + * // if all went well, commit the transaction + * await transaction.commit(); + * } finally { + * // make sure to close the transaction, even if an exception was thrown + * transaction.close(); + * } + * ``` + */ + transaction(mode?: TransactionMode): Promise; + + /** Start an interactive transaction in `"write"` mode. + * + * Please see {@link transaction} for details. + * + * @deprecated Please specify the `mode` explicitly. The default `"write"` will be removed in the next + * major release. + */ + transaction(): Promise; + + /** Execute a sequence of SQL statements separated by semicolons. + * + * The statements are executed sequentially on a new logical database connection. If a statement fails, + * further statements are not executed and this method throws an error. All results from the statements + * are ignored. + * + * We do not wrap the statements in a transaction, but the SQL can contain explicit transaction-control + * statements such as `BEGIN` and `COMMIT`. + * + * This method is intended to be used with existing SQL scripts, such as migrations or small database + * dumps. If you want to execute a sequence of statements programmatically, please use {@link batch} + * instead. + * + * ```javascript + * await client.executeMultiple(` + * CREATE TABLE books (id INTEGER PRIMARY KEY, title TEXT NOT NULL, author_id INTEGER NOT NULL); + * CREATE TABLE authors (id INTEGER PRIMARY KEY, name TEXT NOT NULL); + * `); + * ``` + */ + executeMultiple(sql: string): Promise; + + sync(): Promise; + + /** Close the client and release resources. + * + * This method closes the client (aborting any operations that are currently in progress) and releases any + * resources associated with the client (such as a WebSocket connection). + */ + close(): void; + + /** Is the client closed? + * + * This is set to `true` after a call to {@link close} or if the client encounters an unrecoverable + * error. + */ + closed: boolean; + + /** Which protocol does the client use? + * + * - `"http"` if the client connects over HTTP + * - `"ws"` if the client connects over WebSockets + * - `"file"` if the client works with a local file + */ + protocol: string; } /** Interactive transaction. @@ -244,70 +244,70 @@ export interface Client { * ``` */ export interface Transaction { - /** Execute an SQL statement in this transaction. - * - * If the statement makes any changes to the database, these changes won't be visible to statements - * outside of this transaction until you call {@link rollback}. - * - * ```javascript - * await transaction.execute({ - * sql: "INSERT INTO books (name, author) VALUES (?, ?)", - * args: ["First Impressions", "Jane Austen"], - * }); - * ``` - */ - execute(stmt: InStatement): Promise; - - /** Execute a batch of SQL statements in this transaction. - * - * If any of the statements in the batch fails with an error, further statements are not executed and the - * returned promise is rejected with an error, but the transaction is not rolled back. - */ - batch(stmts: Array): Promise>; - - /** Execute a sequence of SQL statements separated by semicolons. - * - * The statements are executed sequentially in the transaction. If a statement fails, further statements - * are not executed and this method throws an error, but the transaction won't be rolled back. All results - * from the statements are ignored. - * - * This method is intended to be used with existing SQL scripts, such as migrations or small database - * dumps. If you want to execute statements programmatically, please use {@link batch} instead. - */ - executeMultiple(sql: string): Promise; - - /** Roll back any changes from this transaction. - * - * This method closes the transaction and undoes any changes done by the previous SQL statements on this - * transaction. You cannot call this method after calling {@link commit}, though. - */ - rollback(): Promise; - - /** Commit changes from this transaction to the database. - * - * This method closes the transaction and applies all changes done by the previous SQL statement on this - * transaction. Once the returned promise is resolved successfully, the database guarantees that the - * changes were applied. - */ - commit(): Promise; - - /** Close the transaction. - * - * This method closes the transaction and releases any resources associated with the transaction. If the - * transaction is already closed (perhaps by a previous call to {@link commit} or {@link rollback}), then - * this method does nothing. - * - * If the transaction wasn't already committed by calling {@link commit}, the transaction is rolled - * back. - */ - close(): void; - - /** Is the transaction closed? - * - * This is set to `true` after a call to {@link close}, {@link commit} or {@link rollback}, or if we - * encounter an unrecoverable error. - */ - closed: boolean; + /** Execute an SQL statement in this transaction. + * + * If the statement makes any changes to the database, these changes won't be visible to statements + * outside of this transaction until you call {@link rollback}. + * + * ```javascript + * await transaction.execute({ + * sql: "INSERT INTO books (name, author) VALUES (?, ?)", + * args: ["First Impressions", "Jane Austen"], + * }); + * ``` + */ + execute(stmt: InStatement): Promise; + + /** Execute a batch of SQL statements in this transaction. + * + * If any of the statements in the batch fails with an error, further statements are not executed and the + * returned promise is rejected with an error, but the transaction is not rolled back. + */ + batch(stmts: Array): Promise>; + + /** Execute a sequence of SQL statements separated by semicolons. + * + * The statements are executed sequentially in the transaction. If a statement fails, further statements + * are not executed and this method throws an error, but the transaction won't be rolled back. All results + * from the statements are ignored. + * + * This method is intended to be used with existing SQL scripts, such as migrations or small database + * dumps. If you want to execute statements programmatically, please use {@link batch} instead. + */ + executeMultiple(sql: string): Promise; + + /** Roll back any changes from this transaction. + * + * This method closes the transaction and undoes any changes done by the previous SQL statements on this + * transaction. You cannot call this method after calling {@link commit}, though. + */ + rollback(): Promise; + + /** Commit changes from this transaction to the database. + * + * This method closes the transaction and applies all changes done by the previous SQL statement on this + * transaction. Once the returned promise is resolved successfully, the database guarantees that the + * changes were applied. + */ + commit(): Promise; + + /** Close the transaction. + * + * This method closes the transaction and releases any resources associated with the transaction. If the + * transaction is already closed (perhaps by a previous call to {@link commit} or {@link rollback}), then + * this method does nothing. + * + * If the transaction wasn't already committed by calling {@link commit}, the transaction is rolled + * back. + */ + close(): void; + + /** Is the transaction closed? + * + * This is set to `true` after a call to {@link close}, {@link commit} or {@link rollback}, or if we + * encounter an unrecoverable error. + */ + closed: boolean; } /** Transaction mode. @@ -353,45 +353,45 @@ export type TransactionMode = "write" | "read" | "deferred"; * ``` */ export interface ResultSet { - /** Names of columns. - * - * Names of columns can be defined using the `AS` keyword in SQL: - * - * ```sql - * SELECT author AS author, COUNT(*) AS count FROM books GROUP BY author - * ``` - */ - columns: Array; - - /** Types of columns. - * - * The types are currently shown for types declared in a SQL table. For - * column types of function calls, for example, an empty string is - * returned. - */ - columnTypes: Array; - - /** Rows produced by the statement. */ - rows: Array; - - /** Number of rows that were affected by an UPDATE, INSERT or DELETE operation. - * - * This value is not specified for other SQL statements. - */ - rowsAffected: number; - - /** ROWID of the last inserted row. - * - * This value is not specified if the SQL statement was not an INSERT or if the table was not a ROWID - * table. - */ - lastInsertRowid: bigint | undefined; - - /** Converts the result set to JSON. - * - * This is used automatically by `JSON.stringify()`, but you can also call it explicitly. - */ - toJSON(): any; + /** Names of columns. + * + * Names of columns can be defined using the `AS` keyword in SQL: + * + * ```sql + * SELECT author AS author, COUNT(*) AS count FROM books GROUP BY author + * ``` + */ + columns: Array; + + /** Types of columns. + * + * The types are currently shown for types declared in a SQL table. For + * column types of function calls, for example, an empty string is + * returned. + */ + columnTypes: Array; + + /** Rows produced by the statement. */ + rows: Array; + + /** Number of rows that were affected by an UPDATE, INSERT or DELETE operation. + * + * This value is not specified for other SQL statements. + */ + rowsAffected: number; + + /** ROWID of the last inserted row. + * + * This value is not specified if the SQL statement was not an INSERT or if the table was not a ROWID + * table. + */ + lastInsertRowid: bigint | undefined; + + /** Converts the result set to JSON. + * + * This is used automatically by `JSON.stringify()`, but you can also call it explicitly. + */ + toJSON(): any; } /** Row returned from an SQL statement. @@ -409,17 +409,17 @@ export interface ResultSet { * ``` */ export interface Row { - /** Number of columns in this row. - * - * All rows in one {@link ResultSet} have the same number and names of columns. - */ - length: number; + /** Number of columns in this row. + * + * All rows in one {@link ResultSet} have the same number and names of columns. + */ + length: number; - /** Columns can be accessed like an array by numeric indexes. */ - [index: number]: Value; + /** Columns can be accessed like an array by numeric indexes. */ + [index: number]: Value; - /** Columns can be accessed like an object by column names. */ - [name: string]: Value; + /** Columns can be accessed like an object by column names. */ + [name: string]: Value; } export type Value = null | string | number | bigint | ArrayBuffer; @@ -431,18 +431,23 @@ export type InArgs = Array | Record; /** Error thrown by the client. */ export class LibsqlError extends Error { - /** Machine-readable error code. */ - code: string; - /** Raw numeric error code */ - rawCode?: number; - - constructor(message: string, code: string, rawCode?: number, cause?: Error) { - if (code !== undefined) { - message = `${code}: ${message}`; + /** Machine-readable error code. */ + code: string; + /** Raw numeric error code */ + rawCode?: number; + + constructor( + message: string, + code: string, + rawCode?: number, + cause?: Error, + ) { + if (code !== undefined) { + message = `${code}: ${message}`; + } + super(message, { cause }); + this.code = code; + this.rawCode = rawCode; + this.name = "LibsqlError"; } - super(message, { cause }); - this.code = code; - this.rawCode = rawCode; - this.name = "LibsqlError"; - } } diff --git a/packages/libsql-core/src/config.ts b/packages/libsql-core/src/config.ts index 0f135f79..2eb42672 100644 --- a/packages/libsql-core/src/config.ts +++ b/packages/libsql-core/src/config.ts @@ -5,133 +5,133 @@ import { parseUri } from "./uri.js"; import { supportedUrlLink } from "./util.js"; export interface ExpandedConfig { - scheme: ExpandedScheme; - tls: boolean; - authority: Authority | undefined; - path: string; - authToken: string | undefined; - encryptionKey: string | undefined; - syncUrl: string | undefined; - syncInterval: number | undefined; - intMode: IntMode; - fetch: Function | undefined; + scheme: ExpandedScheme; + tls: boolean; + authority: Authority | undefined; + path: string; + authToken: string | undefined; + encryptionKey: string | undefined; + syncUrl: string | undefined; + syncInterval: number | undefined; + intMode: IntMode; + fetch: Function | undefined; } export type ExpandedScheme = "wss" | "ws" | "https" | "http" | "file"; export function expandConfig( - config: Config, - preferHttp: boolean, + config: Config, + preferHttp: boolean, ): ExpandedConfig { - if (typeof config !== "object") { - // produce a reasonable error message in the common case where users type - // `createClient("libsql://...")` instead of `createClient({url: "libsql://..."})` - throw new TypeError( - `Expected client configuration as object, got ${typeof config}`, - ); - } + if (typeof config !== "object") { + // produce a reasonable error message in the common case where users type + // `createClient("libsql://...")` instead of `createClient({url: "libsql://..."})` + throw new TypeError( + `Expected client configuration as object, got ${typeof config}`, + ); + } - let tls: boolean | undefined = config.tls; - let authToken = config.authToken; - let encryptionKey = config.encryptionKey; - let syncUrl = config.syncUrl; - let syncInterval = config.syncInterval; - const intMode = "" + (config.intMode ?? "number"); - if (intMode !== "number" && intMode !== "bigint" && intMode !== "string") { - throw new TypeError( - `Invalid value for intMode, expected "number", "bigint" or "string", \ + let tls: boolean | undefined = config.tls; + let authToken = config.authToken; + let encryptionKey = config.encryptionKey; + let syncUrl = config.syncUrl; + let syncInterval = config.syncInterval; + const intMode = "" + (config.intMode ?? "number"); + if (intMode !== "number" && intMode !== "bigint" && intMode !== "string") { + throw new TypeError( + `Invalid value for intMode, expected "number", "bigint" or "string", \ got ${JSON.stringify(intMode)}`, - ); - } + ); + } - if (config.url === ":memory:") { - return { - path: ":memory:", - scheme: "file", - syncUrl, - syncInterval, - intMode, - fetch: config.fetch, - tls: false, - authToken: undefined, - encryptionKey: undefined, - authority: undefined, - }; - } + if (config.url === ":memory:") { + return { + path: ":memory:", + scheme: "file", + syncUrl, + syncInterval, + intMode, + fetch: config.fetch, + tls: false, + authToken: undefined, + encryptionKey: undefined, + authority: undefined, + }; + } - const uri = parseUri(config.url); - for (const { key, value } of uri.query?.pairs ?? []) { - if (key === "authToken") { - authToken = value ? value : undefined; - } else if (key === "tls") { - if (value === "0") { - tls = false; - } else if (value === "1") { - tls = true; - } else { + const uri = parseUri(config.url); + for (const { key, value } of uri.query?.pairs ?? []) { + if (key === "authToken") { + authToken = value ? value : undefined; + } else if (key === "tls") { + if (value === "0") { + tls = false; + } else if (value === "1") { + tls = true; + } else { + throw new LibsqlError( + `Unknown value for the "tls" query argument: ${JSON.stringify(value)}. ` + + 'Supported values are "0" and "1"', + "URL_INVALID", + ); + } + } else { + throw new LibsqlError( + `Unknown URL query parameter ${JSON.stringify(key)}`, + "URL_PARAM_NOT_SUPPORTED", + ); + } + } + + const uriScheme = uri.scheme.toLowerCase(); + let scheme: ExpandedScheme; + if (uriScheme === "libsql") { + if (tls === false) { + if (uri.authority?.port === undefined) { + throw new LibsqlError( + 'A "libsql:" URL with ?tls=0 must specify an explicit port', + "URL_INVALID", + ); + } + scheme = preferHttp ? "http" : "ws"; + } else { + scheme = preferHttp ? "https" : "wss"; + } + } else if (uriScheme === "http" || uriScheme === "ws") { + scheme = uriScheme; + tls ??= false; + } else if ( + uriScheme === "https" || + uriScheme === "wss" || + uriScheme === "file" + ) { + scheme = uriScheme; + } else { throw new LibsqlError( - `Unknown value for the "tls" query argument: ${JSON.stringify(value)}. ` + - 'Supported values are "0" and "1"', - "URL_INVALID", + 'The client supports only "libsql:", "wss:", "ws:", "https:", "http:" and "file:" URLs, ' + + `got ${JSON.stringify(uri.scheme + ":")}. ` + + `For more information, please read ${supportedUrlLink}`, + "URL_SCHEME_NOT_SUPPORTED", ); - } - } else { - throw new LibsqlError( - `Unknown URL query parameter ${JSON.stringify(key)}`, - "URL_PARAM_NOT_SUPPORTED", - ); } - } - const uriScheme = uri.scheme.toLowerCase(); - let scheme: ExpandedScheme; - if (uriScheme === "libsql") { - if (tls === false) { - if (uri.authority?.port === undefined) { + if (uri.fragment !== undefined) { throw new LibsqlError( - 'A "libsql:" URL with ?tls=0 must specify an explicit port', - "URL_INVALID", + `URL fragments are not supported: ${JSON.stringify("#" + uri.fragment)}`, + "URL_INVALID", ); - } - scheme = preferHttp ? "http" : "ws"; - } else { - scheme = preferHttp ? "https" : "wss"; } - } else if (uriScheme === "http" || uriScheme === "ws") { - scheme = uriScheme; - tls ??= false; - } else if ( - uriScheme === "https" || - uriScheme === "wss" || - uriScheme === "file" - ) { - scheme = uriScheme; - } else { - throw new LibsqlError( - 'The client supports only "libsql:", "wss:", "ws:", "https:", "http:" and "file:" URLs, ' + - `got ${JSON.stringify(uri.scheme + ":")}. ` + - `For more information, please read ${supportedUrlLink}`, - "URL_SCHEME_NOT_SUPPORTED", - ); - } - if (uri.fragment !== undefined) { - throw new LibsqlError( - `URL fragments are not supported: ${JSON.stringify("#" + uri.fragment)}`, - "URL_INVALID", - ); - } - - return { - scheme, - tls: tls ?? true, - authority: uri.authority, - path: uri.path, - authToken, - encryptionKey, - syncUrl, - syncInterval, - intMode, - fetch: config.fetch, - }; + return { + scheme, + tls: tls ?? true, + authority: uri.authority, + path: uri.path, + authToken, + encryptionKey, + syncUrl, + syncInterval, + intMode, + fetch: config.fetch, + }; } diff --git a/packages/libsql-core/src/uri.ts b/packages/libsql-core/src/uri.ts index 83dd44cb..abe46337 100644 --- a/packages/libsql-core/src/uri.ts +++ b/packages/libsql-core/src/uri.ts @@ -6,193 +6,196 @@ import { LibsqlError } from "./api.js"; export interface Uri { - scheme: string; - authority: Authority | undefined; - path: string; - query: Query | undefined; - fragment: string | undefined; + scheme: string; + authority: Authority | undefined; + path: string; + query: Query | undefined; + fragment: string | undefined; } export interface HierPart { - authority: Authority | undefined; - path: string; + authority: Authority | undefined; + path: string; } export interface Authority { - host: string; - port: number | undefined; - userinfo: Userinfo | undefined; + host: string; + port: number | undefined; + userinfo: Userinfo | undefined; } export interface Userinfo { - username: string; - password: string | undefined; + username: string; + password: string | undefined; } export interface Query { - pairs: Array; + pairs: Array; } export interface KeyValue { - key: string; - value: string; + key: string; + value: string; } export function parseUri(text: string): Uri { - const match = URI_RE.exec(text); - if (match === null) { - throw new LibsqlError("The URL is not in a valid format", "URL_INVALID"); - } - - const groups = match.groups!; - const scheme = groups["scheme"]!; - const authority = - groups["authority"] !== undefined - ? parseAuthority(groups["authority"]) - : undefined; - const path = percentDecode(groups["path"]!); - const query = - groups["query"] !== undefined ? parseQuery(groups["query"]) : undefined; - const fragment = - groups["fragment"] !== undefined - ? percentDecode(groups["fragment"]) - : undefined; - return { scheme, authority, path, query, fragment }; + const match = URI_RE.exec(text); + if (match === null) { + throw new LibsqlError( + "The URL is not in a valid format", + "URL_INVALID", + ); + } + + const groups = match.groups!; + const scheme = groups["scheme"]!; + const authority = + groups["authority"] !== undefined + ? parseAuthority(groups["authority"]) + : undefined; + const path = percentDecode(groups["path"]!); + const query = + groups["query"] !== undefined ? parseQuery(groups["query"]) : undefined; + const fragment = + groups["fragment"] !== undefined + ? percentDecode(groups["fragment"]) + : undefined; + return { scheme, authority, path, query, fragment }; } const URI_RE = (() => { - const SCHEME = "(?[A-Za-z][A-Za-z.+-]*)"; - const AUTHORITY = "(?[^/?#]*)"; - const PATH = "(?[^?#]*)"; - const QUERY = "(?[^#]*)"; - const FRAGMENT = "(?.*)"; - return new RegExp( - `^${SCHEME}:(//${AUTHORITY})?${PATH}(\\?${QUERY})?(#${FRAGMENT})?$`, - "su", - ); + const SCHEME = "(?[A-Za-z][A-Za-z.+-]*)"; + const AUTHORITY = "(?[^/?#]*)"; + const PATH = "(?[^?#]*)"; + const QUERY = "(?[^#]*)"; + const FRAGMENT = "(?.*)"; + return new RegExp( + `^${SCHEME}:(//${AUTHORITY})?${PATH}(\\?${QUERY})?(#${FRAGMENT})?$`, + "su", + ); })(); function parseAuthority(text: string): Authority { - const match = AUTHORITY_RE.exec(text); - if (match === null) { - throw new LibsqlError( - "The authority part of the URL is not in a valid format", - "URL_INVALID", - ); - } - - const groups = match.groups!; - const host = percentDecode(groups["host_br"] ?? groups["host"]); - const port = groups["port"] ? parseInt(groups["port"], 10) : undefined; - const userinfo = - groups["username"] !== undefined - ? { - username: percentDecode(groups["username"]), - password: - groups["password"] !== undefined - ? percentDecode(groups["password"]) - : undefined, - } - : undefined; - return { host, port, userinfo }; + const match = AUTHORITY_RE.exec(text); + if (match === null) { + throw new LibsqlError( + "The authority part of the URL is not in a valid format", + "URL_INVALID", + ); + } + + const groups = match.groups!; + const host = percentDecode(groups["host_br"] ?? groups["host"]); + const port = groups["port"] ? parseInt(groups["port"], 10) : undefined; + const userinfo = + groups["username"] !== undefined + ? { + username: percentDecode(groups["username"]), + password: + groups["password"] !== undefined + ? percentDecode(groups["password"]) + : undefined, + } + : undefined; + return { host, port, userinfo }; } const AUTHORITY_RE = (() => { - return new RegExp( - `^((?[^:]*)(:(?.*))?@)?((?[^:\\[\\]]*)|(\\[(?[^\\[\\]]*)\\]))(:(?[0-9]*))?$`, - "su", - ); + return new RegExp( + `^((?[^:]*)(:(?.*))?@)?((?[^:\\[\\]]*)|(\\[(?[^\\[\\]]*)\\]))(:(?[0-9]*))?$`, + "su", + ); })(); // Query string is parsed as application/x-www-form-urlencoded according to the Web URL standard: // https://url.spec.whatwg.org/#urlencoded-parsing function parseQuery(text: string): Query { - const sequences = text.split("&"); - const pairs = []; - for (const sequence of sequences) { - if (sequence === "") { - continue; - } + const sequences = text.split("&"); + const pairs = []; + for (const sequence of sequences) { + if (sequence === "") { + continue; + } - let key: string; - let value: string; - const splitIdx = sequence.indexOf("="); - if (splitIdx < 0) { - key = sequence; - value = ""; - } else { - key = sequence.substring(0, splitIdx); - value = sequence.substring(splitIdx + 1); - } + let key: string; + let value: string; + const splitIdx = sequence.indexOf("="); + if (splitIdx < 0) { + key = sequence; + value = ""; + } else { + key = sequence.substring(0, splitIdx); + value = sequence.substring(splitIdx + 1); + } - pairs.push({ - key: percentDecode(key.replaceAll("+", " ")), - value: percentDecode(value.replaceAll("+", " ")), - }); - } - return { pairs }; + pairs.push({ + key: percentDecode(key.replaceAll("+", " ")), + value: percentDecode(value.replaceAll("+", " ")), + }); + } + return { pairs }; } function percentDecode(text: string): string { - try { - return decodeURIComponent(text); - } catch (e) { - if (e instanceof URIError) { - throw new LibsqlError( - `URL component has invalid percent encoding: ${e}`, - "URL_INVALID", - undefined, - e, - ); + try { + return decodeURIComponent(text); + } catch (e) { + if (e instanceof URIError) { + throw new LibsqlError( + `URL component has invalid percent encoding: ${e}`, + "URL_INVALID", + undefined, + e, + ); + } + throw e; } - throw e; - } } export function encodeBaseUrl( - scheme: string, - authority: Authority | undefined, - path: string, + scheme: string, + authority: Authority | undefined, + path: string, ): URL { - if (authority === undefined) { - throw new LibsqlError( - `URL with scheme ${JSON.stringify(scheme + ":")} requires authority (the "//" part)`, - "URL_INVALID", - ); - } + if (authority === undefined) { + throw new LibsqlError( + `URL with scheme ${JSON.stringify(scheme + ":")} requires authority (the "//" part)`, + "URL_INVALID", + ); + } - const schemeText = `${scheme}:`; + const schemeText = `${scheme}:`; - const hostText = encodeHost(authority.host); - const portText = encodePort(authority.port); - const userinfoText = encodeUserinfo(authority.userinfo); - const authorityText = `//${userinfoText}${hostText}${portText}`; + const hostText = encodeHost(authority.host); + const portText = encodePort(authority.port); + const userinfoText = encodeUserinfo(authority.userinfo); + const authorityText = `//${userinfoText}${hostText}${portText}`; - let pathText = path.split("/").map(encodeURIComponent).join("/"); - if (pathText !== "" && !pathText.startsWith("/")) { - pathText = "/" + pathText; - } + let pathText = path.split("/").map(encodeURIComponent).join("/"); + if (pathText !== "" && !pathText.startsWith("/")) { + pathText = "/" + pathText; + } - return new URL(`${schemeText}${authorityText}${pathText}`); + return new URL(`${schemeText}${authorityText}${pathText}`); } function encodeHost(host: string): string { - return host.includes(":") ? `[${encodeURI(host)}]` : encodeURI(host); + return host.includes(":") ? `[${encodeURI(host)}]` : encodeURI(host); } function encodePort(port: number | undefined): string { - return port !== undefined ? `:${port}` : ""; + return port !== undefined ? `:${port}` : ""; } function encodeUserinfo(userinfo: Userinfo | undefined): string { - if (userinfo === undefined) { - return ""; - } - - const usernameText = encodeURIComponent(userinfo.username); - const passwordText = - userinfo.password !== undefined - ? `:${encodeURIComponent(userinfo.password)}` - : ""; - return `${usernameText}${passwordText}@`; + if (userinfo === undefined) { + return ""; + } + + const usernameText = encodeURIComponent(userinfo.username); + const passwordText = + userinfo.password !== undefined + ? `:${encodeURIComponent(userinfo.password)}` + : ""; + return `${usernameText}${passwordText}@`; } diff --git a/packages/libsql-core/src/util.ts b/packages/libsql-core/src/util.ts index a4bcfe4c..6d5bdbb6 100644 --- a/packages/libsql-core/src/util.ts +++ b/packages/libsql-core/src/util.ts @@ -1,73 +1,75 @@ import { Base64 } from "js-base64"; import { - ResultSet, - Row, - Value, - TransactionMode, - InStatement, - LibsqlError, + ResultSet, + Row, + Value, + TransactionMode, + InStatement, + LibsqlError, } from "./api"; export const supportedUrlLink = - "https://github.com/libsql/libsql-client-ts#supported-urls"; + "https://github.com/libsql/libsql-client-ts#supported-urls"; export function transactionModeToBegin(mode: TransactionMode): string { - if (mode === "write") { - return "BEGIN IMMEDIATE"; - } else if (mode === "read") { - return "BEGIN TRANSACTION READONLY"; - } else if (mode === "deferred") { - return "BEGIN DEFERRED"; - } else { - throw RangeError( - 'Unknown transaction mode, supported values are "write", "read" and "deferred"', - ); - } + if (mode === "write") { + return "BEGIN IMMEDIATE"; + } else if (mode === "read") { + return "BEGIN TRANSACTION READONLY"; + } else if (mode === "deferred") { + return "BEGIN DEFERRED"; + } else { + throw RangeError( + 'Unknown transaction mode, supported values are "write", "read" and "deferred"', + ); + } } export class ResultSetImpl implements ResultSet { - columns: Array; - columnTypes: Array; - rows: Array; - rowsAffected: number; - lastInsertRowid: bigint | undefined; + columns: Array; + columnTypes: Array; + rows: Array; + rowsAffected: number; + lastInsertRowid: bigint | undefined; - constructor( - columns: Array, - columnTypes: Array, - rows: Array, - rowsAffected: number, - lastInsertRowid: bigint | undefined, - ) { - this.columns = columns; - this.columnTypes = columnTypes; - this.rows = rows; - this.rowsAffected = rowsAffected; - this.lastInsertRowid = lastInsertRowid; - } + constructor( + columns: Array, + columnTypes: Array, + rows: Array, + rowsAffected: number, + lastInsertRowid: bigint | undefined, + ) { + this.columns = columns; + this.columnTypes = columnTypes; + this.rows = rows; + this.rowsAffected = rowsAffected; + this.lastInsertRowid = lastInsertRowid; + } - toJSON(): any { - return { - columns: this.columns, - columnTypes: this.columnTypes, - rows: this.rows.map(rowToJson), - rowsAffected: this.rowsAffected, - lastInsertRowid: - this.lastInsertRowid !== undefined ? "" + this.lastInsertRowid : null, - }; - } + toJSON(): any { + return { + columns: this.columns, + columnTypes: this.columnTypes, + rows: this.rows.map(rowToJson), + rowsAffected: this.rowsAffected, + lastInsertRowid: + this.lastInsertRowid !== undefined + ? "" + this.lastInsertRowid + : null, + }; + } } function rowToJson(row: Row): unknown { - return Array.prototype.map.call(row, valueToJson); + return Array.prototype.map.call(row, valueToJson); } function valueToJson(value: Value): unknown { - if (typeof value === "bigint") { - return "" + value; - } else if (value instanceof ArrayBuffer) { - return Base64.fromUint8Array(new Uint8Array(value)); - } else { - return value; - } + if (typeof value === "bigint") { + return "" + value; + } else if (value instanceof ArrayBuffer) { + return Base64.fromUint8Array(new Uint8Array(value)); + } else { + return value; + } } diff --git a/packages/libsql-core/tsconfig.base.json b/packages/libsql-core/tsconfig.base.json index bfdf9532..e6d67e71 100644 --- a/packages/libsql-core/tsconfig.base.json +++ b/packages/libsql-core/tsconfig.base.json @@ -1,13 +1,13 @@ { - "compilerOptions": { - "moduleResolution": "node", - "lib": ["esnext"], - "target": "esnext", - "esModuleInterop": true, - "isolatedModules": true, - "rootDir": "src/", - "strict": true - }, - "include": ["src/"], - "exclude": ["**/__tests__"] + "compilerOptions": { + "moduleResolution": "node", + "lib": ["esnext"], + "target": "esnext", + "esModuleInterop": true, + "isolatedModules": true, + "rootDir": "src/", + "strict": true + }, + "include": ["src/"], + "exclude": ["**/__tests__"] } diff --git a/packages/libsql-core/tsconfig.build-cjs.json b/packages/libsql-core/tsconfig.build-cjs.json index fa26814d..8feb91c5 100644 --- a/packages/libsql-core/tsconfig.build-cjs.json +++ b/packages/libsql-core/tsconfig.build-cjs.json @@ -1,8 +1,8 @@ { - "extends": "./tsconfig.base.json", - "compilerOptions": { - "module": "commonjs", - "declaration": false, - "outDir": "./lib-cjs/" - } + "extends": "./tsconfig.base.json", + "compilerOptions": { + "module": "commonjs", + "declaration": false, + "outDir": "./lib-cjs/" + } } diff --git a/packages/libsql-core/tsconfig.build-esm.json b/packages/libsql-core/tsconfig.build-esm.json index a015a304..2ba1706d 100644 --- a/packages/libsql-core/tsconfig.build-esm.json +++ b/packages/libsql-core/tsconfig.build-esm.json @@ -1,8 +1,8 @@ { - "extends": "./tsconfig.base.json", - "compilerOptions": { - "module": "esnext", - "declaration": true, - "outDir": "./lib-esm/" - } + "extends": "./tsconfig.base.json", + "compilerOptions": { + "module": "esnext", + "declaration": true, + "outDir": "./lib-esm/" + } } diff --git a/packages/libsql-core/tsconfig.json b/packages/libsql-core/tsconfig.json index 23f862fb..bc064274 100644 --- a/packages/libsql-core/tsconfig.json +++ b/packages/libsql-core/tsconfig.json @@ -1,7 +1,7 @@ { - "extends": "./tsconfig.base.json", - "compilerOptions": { - "noEmit": true, - "incremental": true - } + "extends": "./tsconfig.base.json", + "compilerOptions": { + "noEmit": true, + "incremental": true + } } diff --git a/packages/libsql-core/typedoc.json b/packages/libsql-core/typedoc.json index de52c9c4..4c5154ac 100644 --- a/packages/libsql-core/typedoc.json +++ b/packages/libsql-core/typedoc.json @@ -1,11 +1,11 @@ { - "entryPoints": ["src/node.ts"], - "out": "docs", - "excludePrivate": true, - "excludeInternal": true, - "visibilityFilters": { - "inherited": true, - "external": true - }, - "includeVersion": true + "entryPoints": ["src/node.ts"], + "out": "docs", + "excludePrivate": true, + "excludeInternal": true, + "visibilityFilters": { + "inherited": true, + "external": true + }, + "includeVersion": true }