[
  {
    "path": ".github/workflows/ci.yaml",
    "content": "name: \"CI\"\non:\n    push:\n        branches: [\"main\"]\n    pull_request:\n\njobs:\n    \"typecheck\":\n        name: \"Typecheck\"\n        runs-on: ubuntu-latest\n        timeout-minutes: 2\n        steps:\n            - name: \"Checkout this repo\"\n              uses: actions/checkout@v4\n            - name: \"Setup Node.js\"\n              uses: actions/setup-node@v4\n              with:\n                  node-version: \"18.x\"\n            - name: \"Install node dependencies and build\"\n              run: \"npm ci && npm run build\"\n            - name: \"Typecheck\"\n              run: \"npm run typecheck\"\n\n    \"format-check\":\n        name: \"Check formatting\"\n        runs-on: ubuntu-latest\n        timeout-minutes: 2\n        steps:\n            - name: \"Checkout this repo\"\n              uses: actions/checkout@v4\n            - name: \"Setup Node.js\"\n              uses: actions/setup-node@v4\n              with:\n                  node-version: \"18.x\"\n            - name: \"Install node dependencies\"\n              run: \"npm ci\"\n            - name: \"Check formatting\"\n              run: \"npm run format:check\"\n\n    \"test-against-sqld\":\n        name: \"Tests against sqld\"\n        runs-on: ubuntu-latest\n        timeout-minutes: 2\n        defaults:\n            run:\n                working-directory: ./packages/libsql-client\n        env: { \"NODE_OPTIONS\": \"--trace-warnings\" }\n        steps:\n            - name: \"Checkout this repo\"\n              uses: actions/checkout@v4\n            - name: \"Setup Node.js\"\n              uses: actions/setup-node@v4\n              with:\n                  node-version: \"18.x\"\n            - name: \"Build core\"\n              run: \"npm ci && npm run build\"\n              working-directory: ./packages/libsql-core\n            - name: \"Install npm dependencies\"\n              run: \"npm ci\"\n            - name: \"Build\"\n              run: \"npm run build\"\n            - name: Run Docker container in the background\n              run: docker run -d -p 8080:8080 -e SQLD_NODE=primary ghcr.io/tursodatabase/libsql-server:latest\n            - name: Verify container is running\n              run: docker ps\n            - name: \"Test against sqld\"\n              run: \"npm test\"\n              env: { \"URL\": \"http://localhost:8080\", \"SERVER\": \"sqld\" }\n\n    \"wasm-test\":\n        name: \"Build and test Wasm on Node.js\"\n        runs-on: ubuntu-latest\n        timeout-minutes: 2\n        defaults:\n            run:\n                working-directory: ./packages/libsql-client-wasm\n        env: { \"NODE_OPTIONS\": \"--trace-warnings\" }\n        steps:\n            - name: \"Checkout this repo\"\n              uses: actions/checkout@v4\n            - name: \"Setup Node.js\"\n              uses: actions/setup-node@v4\n              with:\n                  node-version: \"18.x\"\n                  cache-dependency-path: \"packages/libsql-client-wasm\"\n            - name: \"Build core\"\n              run: \"npm ci && npm run build\"\n              working-directory: ./packages/libsql-core\n            - name: \"Install npm dependencies\"\n              run: \"npm ci\"\n            - name: \"Build\"\n              run: \"npm run build\"\n            - name: \"Test example\"\n              run: \"cd examples/node && npm i && node index.js\"\n              env: { \"URL\": \"file:///tmp/example.db\" }\n\n    \"node-test\":\n        name: \"Build and test on Node.js\"\n        runs-on: ubuntu-latest\n        timeout-minutes: 2\n        defaults:\n            run:\n                working-directory: ./packages/libsql-client\n        env: { \"NODE_OPTIONS\": \"--trace-warnings\" }\n        steps:\n            - name: \"Checkout this repo\"\n              uses: actions/checkout@v4\n            - name: \"Setup Node.js\"\n              uses: actions/setup-node@v4\n              with:\n                  node-version: \"18.x\"\n            - name: \"Build core\"\n              run: \"npm ci && npm run build\"\n              working-directory: ./packages/libsql-core\n            - name: \"Install npm dependencies\"\n              run: \"npm ci\"\n            - name: \"Setup Python\"\n              uses: actions/setup-python@v4\n              with:\n                  python-version: \"3.10\"\n            - name: \"Install pip dependencies\"\n              run: \"pip install -r ../../testing/hrana-test-server/requirements.txt\"\n\n            - name: \"Build\"\n              run: \"npm run build\"\n\n            - name: \"Test Hrana 1 over WebSocket\"\n              run: \"python ../../testing/hrana-test-server/server_v1.py npm test\"\n              env: { \"URL\": \"ws://localhost:8080\", \"SERVER\": \"test_v1\" }\n            - name: \"Test Hrana 2 over WebSocket\"\n              run: \"python ../../testing/hrana-test-server/server_v2.py npm test\"\n              env: { \"URL\": \"ws://localhost:8080\", \"SERVER\": \"test_v2\" }\n            - name: \"Test Hrana 2 over HTTP\"\n              run: \"python ../../testing/hrana-test-server/server_v2.py npm test\"\n              env: { \"URL\": \"http://localhost:8080\", \"SERVER\": \"test_v2\" }\n            #    - name: \"Test Hrana 3 over WebSocket\"\n            #      run: \"python ../../testing/hrana-test-server/server_v3.py npm test\"\n            #      env: {\"URL\": \"ws://localhost:8080\", \"SERVER\": \"test_v3\"}\n            #    - name: \"Test Hrana 3 over HTTP\"\n            #      run: \"python ../../testing/hrana-test-server/server_v3.py npm test\"\n            #      env: {\"URL\": \"http://localhost:8080\", \"SERVER\": \"test_v3\"}\n            - name: \"Test local file\"\n              run: \"npm test\"\n              env: { \"URL\": \"file:///tmp/test.db\" }\n\n            - name: \"Test example\"\n              run: \"cd examples && npm i && node example.js\"\n              env: { \"URL\": \"file:///tmp/example.db\" }\n\n    \"workers-test\":\n        name: \"Build and test with Cloudflare Workers\"\n        if: false\n        runs-on: ubuntu-latest\n        timeout-minutes: 2\n        defaults:\n            run:\n                working-directory: ./packages/libsql-client\n        env:\n            \"CLOUDFLARE_API_TOKEN\": \"${{ secrets.CLOUDFLARE_API_TOKEN }}\"\n            \"CLOUDFLARE_ACCOUNT_ID\": \"${{ secrets.CLOUDFLARE_ACCOUNT_ID }}\"\n        steps:\n            - name: \"Checkout this repo\"\n              uses: actions/checkout@v4\n            - name: \"Setup Node.js\"\n              uses: actions/setup-node@v4\n              with:\n                  node-version: \"lts/Hydrogen\"\n            - name: \"Build core\"\n              run: \"npm ci && npm run build\"\n              working-directory: ./packages/libsql-core\n            - name: \"Install npm dependencies\"\n              run: \"npm ci\"\n\n            - name: \"Setup Python\"\n              uses: actions/setup-python@v4\n              with:\n                  python-version: \"3.10\"\n            - name: \"Install pip dependencies\"\n              run: \"pip install -r ../../testing/hrana-test-server/requirements.txt\"\n\n            - name: \"Build\"\n              run: \"npm run build\"\n            - name: \"Install npm dependencies of the Workers test\"\n              run: \"cd smoke_test/workers && npm link ../..\"\n\n            - name: \"Local test with Hrana 1 over WebSocket\"\n              run: \"cd smoke_test/workers && python ../../../../testing/hrana-test-server/server_v1.py node --dns-result-order=ipv4first test.js\"\n              env: { \"LOCAL\": \"1\", \"URL\": \"ws://localhost:8080\" }\n            - name: \"Local test with Hrana 2 over WebSocket\"\n              run: \"cd smoke_test/workers && python ../../../../testing/hrana-test-server/server_v2.py node --dns-result-order=ipv4first test.js\"\n              env: { \"LOCAL\": \"1\", \"URL\": \"ws://localhost:8080\" }\n            - name: \"Local test with Hrana 2 over HTTP\"\n              run: \"cd smoke_test/workers && python ../../../../testing/hrana-test-server/server_v2.py node --dns-result-order=ipv4first test.js\"\n              env: { \"LOCAL\": \"1\", \"URL\": \"http://localhost:8080\" }\n#    - name: \"Local test with Hrana 3 over WebSocket\"\n#      run: \"cd smoke_test/workers && python ../../../../testing/hrana-test-server/server_v3.py node --dns-result-order=ipv4first test.js\"\n#      env: {\"LOCAL\": \"1\", \"URL\": \"ws://localhost:8080\"}\n#    - name: \"Local test with Hrana 3 over HTTP\"\n#      run: \"cd smoke_test/workers && python ../../../../testing/hrana-test-server/server_v3.py node --dns-result-order=ipv4first test.js\"\n#      env: {\"LOCAL\": \"1\", \"URL\": \"http://localhost:8080\"}\n\n# - name: \"Non-local test with Hrana 1 over WebSocket\"\n#   run: \"cd smoke_test/workers && python ../../../../testing/hrana-test-server/server_v1.py node test.js\"\n#   env: {\"LOCAL\": \"0\", \"URL\": \"ws://localhost:8080\"}\n# - name: \"Non-local test with Hrana 2 over WebSocket\"\n#   run: \"cd smoke_test/workers && python ../../../../testing/hrana-test-server/server_v2.py node test.js\"\n#   env: {\"LOCAL\": \"0\", \"URL\": \"ws://localhost:8080\"}\n# - name: \"Non-local test with Hrana 2 over HTTP\"\n#   run: \"cd smoke_test/workers && python ../../../../testing/hrana-test-server/server_v2.py node test.js\"\n#   env: {\"LOCAL\": \"0\", \"URL\": \"http://localhost:8080\"}\n#    - name: \"Non-local test with Hrana 3 over WebSocket\"\n#      run: \"cd smoke_test/workers && python ../../../../testing/hrana-test-server/server_v3.py node test.js\"\n#      env: {\"LOCAL\": \"0\", \"URL\": \"ws://localhost:8080\"}\n#    - name: \"Non-local test with Hrana 3 over HTTP\"\n#      run: \"cd smoke_test/workers && python ../../../../testing/hrana-test-server/server_v3.py node test.js\"\n#      env: {\"LOCAL\": \"0\", \"URL\": \"http://localhost:8080\"}\n\n# \"vercel-test\":\n#   name: \"Build and test with Vercel Edge Functions\"\n#   runs-on: ubuntu-latest\n#   env:\n#     VERCEL_TOKEN: \"${{ secrets.VERCEL_TOKEN }}\"\n#     VERCEL_PROJECT_NAME: \"smoke-test\"\n#   steps:\n#   - name: \"Checkout this repo\"\n#     uses: actions/checkout@v4\n#   - name: \"Setup Node.js\"\n#     uses: actions/setup-node@v4\n#     with:\n#       node-version: \"lts/Hydrogen\"\n#       cache: \"npm\"\n#   - name: \"Install npm dependencies\"\n#     run: \"npm ci\"\n\n#   - name: \"Checkout hrana-test-server\"\n#     uses: actions/checkout@v4\n#     with:\n#       repository: \"libsql/hrana-test-server\"\n#       path: \"hrana-test-server\"\n#   - name: \"Setup Python\"\n#     uses: actions/setup-python@v4\n#     with:\n#       python-version: \"3.10\"\n#       cache: \"pip\"\n#   - name: \"Install pip dependencies\"\n#     run: \"pip install -r hrana-test-server/requirements.txt\"\n\n#   - name: \"Build\"\n#     run: \"npm run build\"\n#   - name: \"Install npm dependencies of the Vercel test\"\n#     run: \"cd smoke_test/vercel && npm install\"\n\n#   - name: \"Test with Hrana 1 over WebSocket\"\n#     run: \"cd smoke_test/vercel && python ../../hrana-test-server/server_v1.py node test.js\"\n#     env: {\"URL\": \"ws://localhost:8080\"}\n#   - name: \"Test with Hrana 2 over WebSocket\"\n#     run: \"cd smoke_test/vercel && python ../../hrana-test-server/server_v2.py node test.js\"\n#     env: {\"URL\": \"ws://localhost:8080\"}\n#   - name: \"Test with Hrana 2 over HTTP\"\n#     run: \"cd smoke_test/vercel && python ../../hrana-test-server/server_v2.py node test.js\"\n#     env: {\"URL\": \"http://localhost:8080\"}\n#    - name: \"Test with Hrana 3 over WebSocket\"\n#      run: \"cd smoke_test/vercel && python ../../hrana-test-server/server_v3.py node test.js\"\n#      env: {\"URL\": \"ws://localhost:8080\"}\n#    - name: \"Test with Hrana 3 over HTTP\"\n#      run: \"cd smoke_test/vercel && python ../../hrana-test-server/server_v3.py node test.js\"\n#      env: {\"URL\": \"http://localhost:8080\"}\n"
  },
  {
    "path": ".github/workflows/pages.yaml",
    "content": "name: \"GitHub Pages\"\non:\n    push:\n        branches: [\"main\"]\n\njobs:\n    \"build\":\n        name: \"Build the docs\"\n        runs-on: ubuntu-latest\n        defaults:\n            run:\n                working-directory: ./packages/libsql-client\n        steps:\n            - name: \"Checkout this repo\"\n              uses: actions/checkout@v4\n            - name: \"Setup Node.js\"\n              uses: actions/setup-node@v4\n              with:\n                  node-version: \"${{ matrix.node-version }}\"\n                  cache: \"npm\"\n            - name: \"Build core\"\n              run: \"npm ci && npm run build\"\n              working-directory: ./packages/libsql-core\n            - name: \"Install npm dependencies\"\n              run: \"npm ci\"\n            - name: \"Build\"\n              run: \"npm run typedoc\"\n            - name: \"Upload GitHub Pages artifact\"\n              uses: actions/upload-pages-artifact@v3\n              id: deployment\n              with:\n                  path: \"./packages/libsql-client/docs\"\n\n    \"deploy\":\n        name: \"Deploy the docs to GitHub Pages\"\n        needs: \"build\"\n        permissions:\n            pages: write\n            id-token: write\n\n        environment:\n            name: github-pages\n            url: ${{ steps.deployment.outputs.page_url }}\n\n        runs-on: ubuntu-latest\n        steps:\n            - name: \"Deploy to GitHub Pages\"\n              id: deployment\n              uses: actions/deploy-pages@v4\n"
  },
  {
    "path": ".github/workflows/publish.yml",
    "content": "name: publish\n\npermissions:\n    contents: read\n    id-token: write\n\non:\n    push:\n        tags:\n            - v*\n\njobs:\n    publish-to-npm:\n        name: \"Publish new version to NPM\"\n        runs-on: ubuntu-latest\n        timeout-minutes: 5\n        env:\n            NODE_OPTIONS: \"--trace-warnings\"\n        steps:\n            - name: \"Checkout this repo\"\n              uses: actions/checkout@v4\n\n            - name: \"Setup Node.js\"\n              uses: actions/setup-node@v4\n              with:\n                  node-version: \"20.x\"\n\n            - name: \"Update npm\"\n              run: npm install -g npm@11\n\n            - name: \"Build core\"\n              run: \"npm ci && npm run build\"\n              working-directory: ./packages/libsql-core\n\n            - name: \"Publish core (pre-release)\"\n              if: contains(github.ref, '-pre')\n              run: npm publish --tag next --provenance --access public\n              working-directory: ./packages/libsql-core\n\n            - name: \"Publish core (latest)\"\n              if: \"!contains(github.ref, '-pre')\"\n              run: npm publish --provenance --access public\n              working-directory: ./packages/libsql-core\n\n            - name: \"Install npm dependencies (client)\"\n              run: \"npm ci\"\n              working-directory: ./packages/libsql-client\n\n            - name: \"Publish client (pre-release)\"\n              if: contains(github.ref, '-pre')\n              run: npm publish --tag next --provenance --access public\n              working-directory: ./packages/libsql-client\n\n            - name: \"Publish client (latest)\"\n              if: \"!contains(github.ref, '-pre')\"\n              run: npm publish --provenance --access public\n              working-directory: ./packages/libsql-client\n\n            - name: \"Install npm dependencies (client wasm)\"\n              run: \"npm ci\"\n              working-directory: ./packages/libsql-client-wasm\n\n            - name: \"Publish client-wasm (pre-release)\"\n              if: contains(github.ref, '-pre')\n              run: npm publish --tag next --provenance --access public\n              working-directory: ./packages/libsql-client-wasm\n\n            - name: \"Publish client-wasm (latest)\"\n              if: \"!contains(github.ref, '-pre')\"\n              run: npm publish --provenance --access public\n              working-directory: ./packages/libsql-client-wasm\n"
  },
  {
    "path": ".gitignore",
    "content": "node_modules\n/packages/*/lib-esm\n/packages/*/lib-cjs\n/docs\n*.tsbuildinfo\nSession.vim\npackages/libsql-client/hrana-test-server\n"
  },
  {
    "path": ".husky/install.mjs",
    "content": "// See https://typicode.github.io/husky/how-to.html#ci-server-and-docker\n// Skip Husky install in production and CI\nif (process.env.NODE_ENV === \"production\" || process.env.CI === \"true\") {\n    process.exit(0);\n}\nconst husky = (await import(\"husky\")).default;\nconsole.log(husky());\n"
  },
  {
    "path": ".husky/pre-commit",
    "content": "lint-staged"
  },
  {
    "path": ".lintstagedrc.json",
    "content": "{ \"*.{js,ts,json,md,yaml,yml}\": \"prettier --write\" }\n"
  },
  {
    "path": ".npmrc",
    "content": "//registry.npmjs.org/:_authToken=${NPM_TOKEN}\n"
  },
  {
    "path": ".prettierignore",
    "content": "lib-cjs\nlib-esm\n"
  },
  {
    "path": ".prettierrc",
    "content": "{\n    \"tabWidth\": 4\n}\n"
  },
  {
    "path": "CHANGELOG.md",
    "content": "# Changelog\n\n## 0.15.10 -- 2025-07-16\n\n-   Bump to latest `libsql` package.\n\n## 0.15.9 -- 2025-06-09\n\n-   Bump to latest `libsql` package.\n\n## 0.15.7 -- 2025-05-20\n\n-   Bump to latest `libsql` package.\n\n## 0.15.6 -- 2025-05-14\n\n-   Bump to latest `libsql` package.\n\n## 0.15.5 -- 2025-05-11\n\n-   Bump to latest `libsql` package.\n\n## 0.15.4 -- 2025-04-15\n\n-   Bump to latest `libsql` package.\n\n## 0.15.3 -- 2025-04-11\n\n-   Bump to latest `libsql` package.\n\n## 0.15.2 -- 2025-04-01\n\n-   Bump to latest `libsql` package.\n\n## 0.15.1 -- 2025-03-24\n\n-   Bump to latest `libsql` package.\n\n## 0.15.0 -- 2025-03-17\n\n-   Bump to latest `libsql` package.\n\n## 0.15.0-pre.3 -- 2025-03-11\n\n-   Fix Bun complaint about duplicate \"prepare\" key in `package.json`\n\n## 0.15.0-pre.2 -- 2024-02-11\n\n-   Bump to latest `libsql` package.\n\n## 0.15.0-pre.1 -- 2024-11-15\n\n-   Initial support for offline writes.\n\n## 0.12.0 -- 2024-09-16\n\n-   Upgrade `hrana-client-ts` to latest 0.7.0 version which has stable `isomorphic-fetch` implementation (see https://github.com/libsql/hrana-client-ts/pull/19)\n\n## 0.11.0 -- 2024-09-13\n\n-   Upgrade `libsql-js` to latest 0.4.4 version which brings full vector search support for embedded replicas (see vector search documentation here: https://docs.turso.tech/features/ai-and-embeddings)\n\n## 0.10.0 -- 2024-08-26\n\n-   Add a migrate() API that can be used to do migrations on both schema databases and regular databases. It is mostly dedicated to schema migration tools.\n\n## 0.8.1 -- 2024-08-03\n\n-   Fix embedded replica sync WAL index path name , which caused \"No such file or directory\" for local sync in some cases ([#244](https://github.com/tursodatabase/libsql-client-ts/issues/244)).\n\n## 0.8.0 -- 2024-07-30\n\n-   No changes from 0.8.0-pre.1.\n\n## 0.8.0-pre.1 -- 2024-07-18\n\n-   Bump hrana client to 0.6.2.\n-   Support `cache=private|shared` [query parameter](https://www.sqlite.org/uri.html#recognized_query_parameters) in the connection string to local SQLite (https://github.com/tursodatabase/libsql-client-ts/pull/220)\n-   Fix bug in wasm experimental client which appears when transaction are used in local mode (https://github.com/tursodatabase/libsql-client-ts/pull/231)\n-   Add `execute(sql, args)` overload to make the API similar to other SQLite SDKs\n\n## 0.7.0 -- 2024-06-25\n\n-   Add configurable concurrency limit for parallel query execution\n    (defaults to 20) to address socket hangup errors.\n\n## 0.6.2 -- 2024-06-01\n\n-   Fix compatibility issue with libSQL server versions that don't have migrations endpoint.\n\n## 0.6.1 -- 2024-05-30\n\n-   Add an option to `batch()` to wait for schema changes to finish when using shared schema.\n\n## 0.6.0 -- 2024-04-28\n\n-   Bump hrana client to 0.6.0, which uses native Node fetch(). Note that\n    `@libsql/client` now requires Node 18 or later.\n\n## 0.5.6 -- 2024-03-12\n\n-   Bump `libsql` package dependency to 0.3.10 that adds `wasm32` as\n    supported CPU, which is needed for StackBlitz compatibility.\n\n## 0.5.5 -- 2024-03-11\n\n-   Bump `@libsql/libsql-wasm-experimental\"` dependency to 0.0.2, which\n    fixes a broken sqlite3_get_autocommit() export.\n\n## 0.5.4 -- 2024-03-11\n\n-   Bump `libsql` dependency to 0.3.9, which fixes symbol not found errors on Alpine.\n\n## 0.5.3 -- 2024-03-06\n\n-   Add `syncInterval` config option to enable periodic sync.\n-   Bump `libsql` dependency to 0.3.7, which switches default encryption cipher to aes256cbs.\n\n## 0.5.2 -- 2024-02-24\n\n-   Disable SQL statemen tracing in Wasm.\n\n## 0.5.1 -- 2024-02-19\n\n-   Update `libsql` package to 0.3.2, add `encryptionCipher` option, and switch default cipher to SQLCipher.\n\n## 0.5.0 -- 2024-02-15\n\n-   Add a `encryptionKey` config option, which enables encryption at rest for local database files.\n\n## 0.4.0 -- 2024-01-26\n\n-   Update hrana-client package to 0.5.6.\n-   Add a `@libsql/client-wasm` package.\n-   Fix Bun on Linux/arm64.\n\n## 0.3.6 -- 2023-10-20\n\n-   Fix import problems on Cloudflare Workers.\n-   Add `rawCode` property to errors for local databases.\n-   Update the `libsql` package to version 0.1.28.\n\n## 0.3.5 -- 2023-09-25\n\n-   Performance improvements for local database access by reusing connection in `Client`.\n-   Embedded replica support.\n-   Column introspection support via ResultSet.columnTypes property.\n\n## 0.3.4 -- 2023-09-11\n\n-   Switch to Hrana 2 by default to let Hrana 3 cook some more.\n\n## 0.3.3 -- 2023-09-11\n\n-   Updated `@libsql/hrana-client` to version 0.5.1, which has Bun support.\n\n-   Switched to `libsql` package as a replacement for `better-sqlite3`.\n\n## 0.3.2 -- 2023-07-29\n\n-   Updated `@libsql/hrana-client` to version 0.5.0, which implements Hrana 3\n    -   Dropped workarounds for broken WebSocket support in Miniflare 2\n-   Added a `@libsql/client/node` import for explicit Node.js-specific module\n\n## 0.3.1 -- 2023-07-20\n\n-   Added `ResultSet.toJSON()` to provide better JSON serialization. ([#61](https://github.com/libsql/libsql-client-ts/pull/61))\n-   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))\n-   Added `Config.fetch` to support overriding the `fetch` implementation from `@libsql/isomorphic-fetch`. ([#66](https://github.com/libsql/libsql-client-ts/pull/66))\n\n## 0.3.0 -- 2023-07-07\n\n-   **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))\n-   **Changed the default transaction mode to `\"deferred\"`**. ([#57](https://github.com/libsql/libsql-client-ts/pull/57))\n-   Added `Client.protocol` property to find out which protocol the client uses ([#54](https://github.com/libsql/libsql-client-ts/pull/54)).\n\n## 0.2.2 -- 2023-06-22\n\n-   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)).\n\n## 0.2.1 -- 2023-06-13\n\n-   Added `TransactionMode` argument to `batch()` and `transaction()` ([#46](https://github.com/libsql/libsql-client-ts/pull/46))\n-   Added `Client.executeMultiple()` and `Transaction.executeMultiple()` ([#49](https://github.com/libsql/libsql-client-ts/pull/49))\n-   Added `Transaction.batch()` ([#49](https://github.com/libsql/libsql-client-ts/pull/49))\n-   **Changed the default transaction mode** from `BEGIN DEFERRED` to `BEGIN IMMEDIATE`\n\n## 0.2.0 -- 2023-06-07\n\n-   **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))\n-   Added `?tls=0` query parameter to turn off TLS for `libsql:` URLs\n-   Changed the `libsql:` URL to use HTTP instead of WebSockets\n-   Changed the `Value` type to include `bigint` (so that we can add support for reading integers as bigints in the future, without breaking compatibility)\n-   Removed the `./hrana` import, added `./ws` to import the WebSocket-only client\n"
  },
  {
    "path": "CONTRIBUTING.md",
    "content": "# Prerequisites\n\n-   Have `npm` installed and in PATH\n-   Have `git` installed and in PATH\n\n# Setting up the repository for contribution\n\n-   Clone this repository: `git clone https://github.com/tursodatabase/libsql-client-ts`\n-   Change the current working directory to the cloned repository: `cd libsql-client-ts`\n-   Install dependencies: `npm i`\n-   Change the current working directory to `libsql-core`'s workspace: `cd packages/libsql-core`\n-   Built the core package: `npm run build`\n-   Go back to the root directory to start making changes: `cd ../..`\n"
  },
  {
    "path": "LICENSE",
    "content": "MIT License\n\nCopyright (c) 2023 libSQL\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "examples/batch/.gitignore",
    "content": "local.db\n"
  },
  {
    "path": "examples/batch/README.md",
    "content": "# Batch\n\nThis example demonstrates how to use libSQL to execute a batch of SQL statements.\n\n## Install Dependencies\n\n```bash\nnpm i\n```\n\n## Running\n\nExecute the example:\n\n```bash\nnode index.mjs\n```\n\nThis will setup a SQLite database, execute a batch of SQL statements, and then query the results.\n"
  },
  {
    "path": "examples/batch/index.mjs",
    "content": "import { createClient } from \"@libsql/client\";\n\nconst client = createClient({\n    url: \"file:local.db\",\n});\n\nawait client.batch(\n    [\n        \"CREATE TABLE IF NOT EXISTS users (email TEXT)\",\n        {\n            sql: \"INSERT INTO users VALUES (?)\",\n            args: [\"first@example.com\"],\n        },\n        {\n            sql: \"INSERT INTO users VALUES (?)\",\n            args: [\"second@example.com\"],\n        },\n        {\n            sql: \"INSERT INTO users VALUES (?)\",\n            args: [\"third@example.com\"],\n        },\n    ],\n    \"write\",\n);\n\nconst result = await client.execute(\"SELECT * FROM users\");\n\nconsole.log(\"Users:\", result.rows);\n"
  },
  {
    "path": "examples/batch/package.json",
    "content": "{\n  \"name\": \"batch\",\n  \"version\": \"1.0.0\",\n  \"main\": \"index.mjs\",\n  \"author\": \"Giovanni Benussi\",\n  \"license\": \"MIT\",\n  \"dependencies\": {\n    \"@libsql/client\": \"^0.14.0\"\n  }\n}\n"
  },
  {
    "path": "examples/cloud-encryption/.gitignore",
    "content": "local.db\n"
  },
  {
    "path": "examples/cloud-encryption/README.md",
    "content": "# Cloud Encryption\n\nThese examples demonstrates how to use Turso Cloud encryption.\n\nVisit the documentation here - [Cloud Encryption](https://docs.turso.tech/cloud/encryption)\n\n## Install Dependencies\n\n```bash\nnpm i\n```\n\n## Running\n\nExecute the example which operates over remotely encrypted database:\n\n```bash\nnode remote.mjs\n```\n\nCloud encryption also supports sync:\n\n```bash\nnode sync.mjs\n```\n"
  },
  {
    "path": "examples/cloud-encryption/package.json",
    "content": "{\n    \"name\": \"batch\",\n    \"version\": \"1.0.0\",\n    \"main\": \"remote.mjs\",\n    \"author\": \"Turso Authors\",\n    \"license\": \"MIT\",\n    \"dependencies\": {\n        \"@libsql/client\": \"^0.16.0\"\n    }\n}\n"
  },
  {
    "path": "examples/cloud-encryption/remote.mjs",
    "content": "import { createClient } from \"@libsql/client\";\n\nconst client = createClient({\n    url: process.env.TURSO_DATABASE_URL,\n    authToken: process.env.TURSO_AUTH_TOKEN,\n    remoteEncryptionKey: process.env.TURSO_REMOTE_ENCRYPTION_KEY,\n});\n\nawait client.batch(\n    [\n        \"CREATE TABLE IF NOT EXISTS users (email TEXT)\",\n        \"INSERT INTO users VALUES ('first@example.com')\",\n        \"INSERT INTO users VALUES ('second@example.com')\",\n        \"INSERT INTO users VALUES ('third@example.com')\",\n    ],\n    \"write\",\n);\n\nconst result = await client.execute(\"SELECT * FROM users\");\n\nconsole.log(\"Users:\", result.rows);\n"
  },
  {
    "path": "examples/cloud-encryption/sync.mjs",
    "content": "import { createClient } from \"@libsql/client\";\n\nconst client = createClient({\n    url: \"file:local.db\",\n    syncUrl: process.env.TURSO_DATABASE_URL,\n    authToken: process.env.TURSO_AUTH_TOKEN,\n    remoteEncryptionKey: process.env.TURSO_REMOTE_ENCRYPTION_KEY,\n});\n\nawait client.batch(\n    [\n        \"CREATE TABLE IF NOT EXISTS users (email TEXT)\",\n        \"INSERT INTO users VALUES ('first@example.com')\",\n        \"INSERT INTO users VALUES ('second@example.com')\",\n        \"INSERT INTO users VALUES ('third@example.com')\",\n    ],\n    \"write\",\n);\n\nconst result = await client.execute(\"SELECT * FROM users\");\n\nconsole.log(\"Users:\", result.rows);\n"
  },
  {
    "path": "examples/encryption/.gitignore",
    "content": "encrypted.db\n"
  },
  {
    "path": "examples/encryption/README.md",
    "content": "# Encryption\n\nThis example demonstrates how to create and use an encrypted SQLite database with libSQL.\n\n## Install Dependencies\n\n```bash\nnpm i\n```\n\n## Running\n\nExecute the example:\n\n```bash\nnode index.mjs\n```\n\nThis will setup an encrypted SQLite database, execute a batch of SQL statements, and then query the results.\n"
  },
  {
    "path": "examples/encryption/index.mjs",
    "content": "import { createClient } from \"@libsql/client\";\n\n// You should set the ENCRYPTION_KEY in a environment variable\n// For demo purposes, we're using a fixed key\nconst encryptionKey = \"my-safe-encryption-key\";\n\nconst client = createClient({\n    url: \"file:encrypted.db\",\n    encryptionKey,\n});\n\nawait client.batch(\n    [\n        \"CREATE TABLE IF NOT EXISTS users (email TEXT)\",\n        \"INSERT INTO users VALUES ('first@example.com')\",\n        \"INSERT INTO users VALUES ('second@example.com')\",\n        \"INSERT INTO users VALUES ('third@example.com')\",\n    ],\n    \"write\",\n);\n\nconst result = await client.execute(\"SELECT * FROM users\");\n\nconsole.log(\"Users:\", result.rows);\n"
  },
  {
    "path": "examples/encryption/package.json",
    "content": "{\n  \"name\": \"batch\",\n  \"version\": \"1.0.0\",\n  \"main\": \"index.mjs\",\n  \"author\": \"Giovanni Benussi\",\n  \"license\": \"MIT\",\n  \"dependencies\": {\n    \"@libsql/client\": \"^0.14.0\"\n  }\n}\n"
  },
  {
    "path": "examples/local/.gitignore",
    "content": "local.db\n"
  },
  {
    "path": "examples/local/README.md",
    "content": "# Local\n\nThis example demonstrates how to use libSQL with a local SQLite file.\n\n## Install Dependencies\n\n```bash\nnpm i\n```\n\n## Running\n\nExecute the example:\n\n```bash\nnode index.mjs\n```\n\nThis will setup a local SQLite database, insert some data, and then query the results.\n"
  },
  {
    "path": "examples/local/index.mjs",
    "content": "import { createClient } from \"@libsql/client\";\n\nconst client = createClient({\n    url: \"file:local.db\",\n});\n\nawait client.batch(\n    [\n        \"CREATE TABLE IF NOT EXISTS users (email TEXT)\",\n        \"INSERT INTO users VALUES ('first@example.com')\",\n        \"INSERT INTO users VALUES ('second@example.com')\",\n        \"INSERT INTO users VALUES ('third@example.com')\",\n    ],\n    \"write\",\n);\n\nconst result = await client.execute(\"SELECT * FROM users\");\n\nconsole.log(\"Users:\", result.rows);\n"
  },
  {
    "path": "examples/local/package.json",
    "content": "{\n  \"name\": \"batch\",\n  \"version\": \"1.0.0\",\n  \"main\": \"index.mjs\",\n  \"author\": \"Giovanni Benussi\",\n  \"license\": \"MIT\",\n  \"dependencies\": {\n    \"@libsql/client\": \"^0.14.0\"\n  }\n}\n"
  },
  {
    "path": "examples/memory/.gitignore",
    "content": "local.db\n"
  },
  {
    "path": "examples/memory/README.md",
    "content": "# Memory\n\nThis example demonstrates how to use libsql with an in-memory SQLite database.\n\n## Install Dependencies\n\n```bash\nnpm i\n```\n\n## Running\n\nExecute the example:\n\n```bash\nnode index.mjs\n```\n\nThis will create an in-memory SQLite database, insert some data, and then query the results.\n"
  },
  {
    "path": "examples/memory/index.mjs",
    "content": "import { createClient } from \"@libsql/client\";\n\nconst client = createClient({\n    url: \":memory:\",\n});\n\nawait client.batch(\n    [\n        \"CREATE TABLE users (email TEXT)\",\n        \"INSERT INTO users VALUES ('first@example.com')\",\n        \"INSERT INTO users VALUES ('second@example.com')\",\n        \"INSERT INTO users VALUES ('third@example.com')\",\n    ],\n    \"write\",\n);\n\nconst result = await client.execute(\"SELECT * FROM users\");\n\nconsole.log(\"Users:\", result.rows);\n"
  },
  {
    "path": "examples/memory/package.json",
    "content": "{\n  \"name\": \"batch\",\n  \"version\": \"1.0.0\",\n  \"main\": \"index.mjs\",\n  \"author\": \"Giovanni Benussi\",\n  \"license\": \"MIT\",\n  \"dependencies\": {\n    \"@libsql/client\": \"^0.14.0\"\n  }\n}\n"
  },
  {
    "path": "examples/ollama/.gitignore",
    "content": "local.db\n"
  },
  {
    "path": "examples/ollama/README.md",
    "content": "# Ollama + Vector Search Example\n\nThis example demonstrates how to use libSQL vector search with a local database and Ollama.\n\n## Install Dependencies\n\n```bash\nnpm i\n```\n\n## Install Ollama\n\n[Download Ollama](https://ollama.com/download) and install it.\n\n## Running\n\nMake sure Ollama is running with the model `mistral`:\n\n```bash\nollama run mistral\n```\n\nExecute the example:\n\n```bash\nnode index.mjs\n```\n\nThis will setup a local SQLite database, generate embeddings using Ollama, and insert the data with embeddings, and then query the results using the vector similarity search function.\n"
  },
  {
    "path": "examples/ollama/index.mjs",
    "content": "import { createClient } from \"@libsql/client\";\nimport ollama from \"ollama\";\n\nconst client = createClient({\n    url: \"file:local.db\",\n});\n\nawait client.batch(\n    [\n        \"CREATE TABLE IF NOT EXISTS movies (id INTEGER PRIMARY KEY, title TEXT NOT NULL, description TEXT NOT NULL, embedding F32_BLOB(4096))\",\n        \"CREATE INDEX IF NOT EXISTS movies_embedding_idx ON movies(libsql_vector_idx(embedding))\",\n    ],\n    \"write\",\n);\n\nasync function getEmbedding(prompt) {\n    const response = await ollama.embeddings({\n        model: \"mistral\",\n        prompt,\n    });\n\n    return response.embedding;\n}\n\nasync function insertMovie(id, title, description) {\n    const embedding = await getEmbedding(description);\n\n    await client.execute({\n        sql: `INSERT OR REPLACE INTO movies (id, title, description, embedding) VALUES (?, ?, ?, vector(?))`,\n        args: [id, title, description, JSON.stringify(embedding)],\n    });\n}\n\nasync function insertMovieIfNotExists(id, title, description) {\n    const existing = await client.execute({\n        sql: \"SELECT id FROM movies WHERE id = ?\",\n        args: [id],\n    });\n\n    if (existing.rows.length === 0) {\n        await insertMovie(id, title, description);\n        console.log(`Inserted: ${title} (ID: ${id})`);\n    } else {\n        console.log(`Movie already exists: ${title} (ID: ${id})`);\n    }\n}\n\nasync function findSimilarMovies(description, limit = 3) {\n    const queryEmbedding = await getEmbedding(description);\n\n    const results = await client.execute({\n        sql: `\n          WITH vector_scores AS (\n            SELECT DISTINCT\n              id,\n              title,\n              description,\n              1 - vector_distance_cos(embedding, vector32(?)) AS similarity\n            FROM movies\n            ORDER BY similarity DESC\n            LIMIT ?\n          )\n          SELECT id, title, description, similarity FROM vector_scores\n        `,\n        args: [JSON.stringify(queryEmbedding), limit],\n    });\n\n    return results.rows;\n}\n\ntry {\n    const sampleMovies = [\n        {\n            id: 1,\n            title: \"Inception\",\n            description:\n                \"A thief who enters the dreams of others to steal secrets from their subconscious.\",\n        },\n        {\n            id: 2,\n            title: \"The Matrix\",\n            description:\n                \"A computer programmer discovers that reality as he knows it is a simulation created by machines.\",\n        },\n        {\n            id: 3,\n            title: \"Interstellar\",\n            description:\n                \"Astronauts travel through a wormhole in search of a new habitable planet for humanity.\",\n        },\n    ];\n\n    for (const movie of sampleMovies) {\n        await insertMovieIfNotExists(movie.id, movie.title, movie.description);\n    }\n\n    const query =\n        \"A sci-fi movie about virtual reality and artificial intelligence\";\n    console.log(\"\\nSearching for movies similar to:\", query);\n\n    const similarMovies = await findSimilarMovies(query);\n    console.log(\"\\nSimilar movies found:\");\n    similarMovies.forEach((movie) => {\n        console.log(`\\nTitle: ${movie.title}`);\n        console.log(`Description: ${movie.description}`);\n        console.log(`Similarity: ${movie.similarity.toFixed(4)}`);\n    });\n} catch (error) {\n    console.error(\"Error:\", error);\n}\n"
  },
  {
    "path": "examples/ollama/package.json",
    "content": "{\n    \"name\": \"ollama\",\n    \"private\": true,\n    \"main\": \"index.mjs\",\n    \"dependencies\": {\n        \"@libsql/client\": \"^0.14.0\",\n        \"ollama\": \"^0.5.11\"\n    }\n}\n"
  },
  {
    "path": "examples/read-your-writes/package.json",
    "content": "{\n    \"name\": \"read-your-writes\",\n    \"version\": \"1.0.0\",\n    \"main\": \"index.mjs\",\n    \"author\": \"Levy Albuquerque\",\n    \"license\": \"MIT\",\n    \"dependencies\": {\n        \"@libsql/client\": \"^0.14.0\"\n    }\n}\n"
  },
  {
    "path": "examples/read-your-writes/read_your_writes.js",
    "content": "import { createClient } from \"@libsql/client\";\n\nconst client = createClient({\n    url: \"file:local.db\",\n    syncUrl: process.env.TURSO_DATABASE_URL,\n    authToken: process.env.TURSO_AUTH_TOKEN,\n    readYourWrites: false,\n});\n\nawait client.execute(\"DROP TABLE users\");\nawait client.execute(\"CREATE TABLE IF NOT EXISTS users (email TEXT)\");\nawait client.sync();\n\nawait client.execute(\"INSERT INTO users VALUES ('first@example.com')\");\nawait client.execute(\"INSERT INTO users VALUES ('second@example.com')\");\nawait client.execute(\"INSERT INTO users VALUES ('third@example.com')\");\n\n{\n    // No users, sinc no sync has happend since inserts\n    const result = await client.execute(\"SELECT * FROM users\");\n\n    console.log(\"Users:\", result.rows);\n}\n\n{\n    await client.sync();\n\n    // No users, sinc no sync has happend since inserts\n    const result = await client.execute(\"SELECT * FROM users\");\n\n    console.log(\"Users:\", result.rows);\n}\n"
  },
  {
    "path": "examples/remote/.gitignore",
    "content": "local.db\n"
  },
  {
    "path": "examples/remote/README.md",
    "content": "# Remote\n\nThis example demonstrates how to use libSQL with a remote database.\n\n## Install Dependencies\n\n```bash\nnpm i\n```\n\n## Running\n\nExecute the example:\n\n```bash\nTURSO_DATABASE_URL=\"...\" TURSO_AUTH_TOKEN=\"...\" node index.mjs\n```\n\nThis will connect to a remote SQLite database, insert some data, and then query the results.\n"
  },
  {
    "path": "examples/remote/index.mjs",
    "content": "import { createClient } from \"@libsql/client\";\n\nconst client = createClient({\n    url: process.env.TURSO_DATABASE_URL,\n    authToken: process.env.TURSO_AUTH_TOKEN,\n});\n\nawait client.batch(\n    [\n        \"CREATE TABLE IF NOT EXISTS users (email TEXT)\",\n        \"INSERT INTO users VALUES ('first@example.com')\",\n        \"INSERT INTO users VALUES ('second@example.com')\",\n        \"INSERT INTO users VALUES ('third@example.com')\",\n    ],\n    \"write\",\n);\n\nconst result = await client.execute(\"SELECT * FROM users\");\n\nconsole.log(\"Users:\", result.rows);\n"
  },
  {
    "path": "examples/remote/package.json",
    "content": "{\n  \"name\": \"batch\",\n  \"version\": \"1.0.0\",\n  \"main\": \"index.mjs\",\n  \"author\": \"Giovanni Benussi\",\n  \"license\": \"MIT\",\n  \"dependencies\": {\n    \"@libsql/client\": \"^0.14.0\"\n  }\n}\n"
  },
  {
    "path": "examples/sync/.gitignore",
    "content": "local.db\nlocal.db-client_wal_index\n"
  },
  {
    "path": "examples/sync/README.md",
    "content": "# Sync\n\nThis example demonstrates how to use libSQL with a synced database (local file synced with a remote database).\n\n## Install Dependencies\n\n```bash\nnpm i\n```\n\n## Running\n\nExecute the example:\n\n```bash\nTURSO_DATABASE_URL=\"...\" TURSO_AUTH_TOKEN=\"...\" node index.mjs\n```\n\nThis will connect to a remote SQLite database, insert some data, and then query the results.\n"
  },
  {
    "path": "examples/sync/index.mjs",
    "content": "import { createClient } from \"@libsql/client\";\n\nconst client = createClient({\n    url: \"file:local.db\",\n    syncUrl: process.env.TURSO_DATABASE_URL,\n    authToken: process.env.TURSO_AUTH_TOKEN,\n});\n\nawait client.batch(\n    [\n        \"CREATE TABLE IF NOT EXISTS users (email TEXT)\",\n        \"INSERT INTO users VALUES ('first@example.com')\",\n        \"INSERT INTO users VALUES ('second@example.com')\",\n        \"INSERT INTO users VALUES ('third@example.com')\",\n    ],\n    \"write\",\n);\n\nconst result = await client.execute(\"SELECT * FROM users\");\n\nconsole.log(\"Users:\", result.rows);\n"
  },
  {
    "path": "examples/sync/package.json",
    "content": "{\n  \"name\": \"batch\",\n  \"version\": \"1.0.0\",\n  \"main\": \"index.mjs\",\n  \"author\": \"Giovanni Benussi\",\n  \"license\": \"MIT\",\n  \"dependencies\": {\n    \"@libsql/client\": \"^0.14.0\"\n  }\n}\n"
  },
  {
    "path": "examples/transactions/.gitignore",
    "content": "local.db\n"
  },
  {
    "path": "examples/transactions/README.md",
    "content": "# Local\n\nThis example demonstrates how to use transactions with libSQL.\n\n## Install Dependencies\n\n```bash\nnpm i\n```\n\n## Running\n\nExecute the example:\n\n```bash\nnode index.mjs\n```\n\nThis example will:\n\n1. Create a new table called `users`.\n2. Start a transaction.\n3. Insert multiple users within the transaction.\n4. Demonstrate how to rollback a transaction.\n5. Start another transaction.\n6. Insert more users and commit the transaction.\n7. Query and display the final state of the `users` table.\n"
  },
  {
    "path": "examples/transactions/index.mjs",
    "content": "import { createClient } from \"@libsql/client\";\n\nconst client = createClient({\n    url: \"file:local.db\",\n});\n\nawait client.batch(\n    [\n        \"DROP TABLE IF EXISTS users\",\n        \"CREATE TABLE users (id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT)\",\n        \"INSERT INTO users (name) VALUES ('Iku Turso')\",\n    ],\n    \"write\",\n);\n\nconst names = [\"John Doe\", \"Mary Smith\", \"Alice Jones\", \"Mark Taylor\"];\n\nlet transaction, secondTransaction;\ntry {\n    transaction = await client.transaction(\"write\");\n\n    for (const name of names) {\n        await transaction.execute({\n            sql: \"INSERT INTO users (name) VALUES (?)\",\n            args: [name],\n        });\n    }\n    await transaction.rollback();\n\n    secondTransaction = await client.transaction(\"write\");\n\n    for (const name of names) {\n        await secondTransaction.execute({\n            sql: \"INSERT INTO users (name) VALUES (?)\",\n            args: [name],\n        });\n    }\n\n    await secondTransaction.commit();\n} catch (e) {\n    console.error(e);\n    await transaction?.rollback();\n    await secondTransaction?.rollback();\n}\n\nconst result = await client.execute(\"SELECT * FROM users\");\n\nconsole.log(\"Users:\", result.rows);\n"
  },
  {
    "path": "examples/transactions/package.json",
    "content": "{\n  \"name\": \"batch\",\n  \"version\": \"1.0.0\",\n  \"main\": \"index.mjs\",\n  \"author\": \"Giovanni Benussi\",\n  \"license\": \"MIT\",\n  \"dependencies\": {\n    \"@libsql/client\": \"^0.14.0\"\n  }\n}\n"
  },
  {
    "path": "examples/vector/.gitignore",
    "content": "local.db\n"
  },
  {
    "path": "examples/vector/README.md",
    "content": "# Local\n\nThis example demonstrates how to use libSQL vector search with a local database.\n\n## Install Dependencies\n\n```bash\nnpm i\n```\n\n## Running\n\nExecute the example:\n\n```bash\nnode index.mjs\n```\n\nThis will setup a local SQLite database, insert some data, and then query the results using the vector similarity search function.\n"
  },
  {
    "path": "examples/vector/index.mjs",
    "content": "import { createClient } from \"@libsql/client\";\n\nconst client = createClient({\n    url: \"file:local.db\",\n});\n\nawait client.batch(\n    [\n        \"DROP TABLE IF EXISTS movies\",\n        \"CREATE TABLE IF NOT EXISTS movies (title TEXT, year INT, embedding F32_BLOB(3))\",\n        \"CREATE INDEX movies_idx ON movies (libsql_vector_idx(embedding))\",\n        \"INSERT INTO movies (title, year, embedding) VALUES ('Napoleon', 2023, vector32('[1,2,3]')), ('Black Hawk Down', 2001, vector32('[10,11,12]')), ('Gladiator', 2000, vector32('[7,8,9]')), ('Blade Runner', 1982, vector32('[4,5,6]'))\",\n    ],\n    \"write\",\n);\n\nconst result = await client.execute(\n    \"SELECT title, year FROM vector_top_k('movies_idx', '[4,5,6]', 3) JOIN movies ON movies.rowid = id\",\n);\n\nconsole.log(\"Movies:\", result.rows);\n"
  },
  {
    "path": "examples/vector/package.json",
    "content": "{\n  \"name\": \"batch\",\n  \"version\": \"1.0.0\",\n  \"main\": \"index.mjs\",\n  \"author\": \"Giovanni Benussi\",\n  \"license\": \"MIT\",\n  \"dependencies\": {\n    \"@libsql/client\": \"^0.14.0\"\n  }\n}\n"
  },
  {
    "path": "package.json",
    "content": "{\n    \"workspaces\": [\n        \"packages/libsql-core\",\n        \"packages/libsql-client\",\n        \"packages/libsql-client-wasm\"\n    ],\n    \"dependencies\": {},\n    \"scripts\": {\n        \"prepare\": \"node .husky/install.mjs\",\n        \"build\": \"npm run build --workspaces\",\n        \"test\": \"./testing/test.sh\",\n        \"typecheck\": \"npm run typecheck --workspaces\",\n        \"format:check\": \"npm run format:check --workspaces\",\n        \"lint-staged\": \"lint-staged\"\n    },\n    \"devDependencies\": {\n        \"lint-staged\": \"^15.2.2\",\n        \"husky\": \"^9.1.5\"\n    }\n}\n"
  },
  {
    "path": "packages/libsql-client/README.md",
    "content": "<p align=\"center\">\n  <a href=\"https://tur.so/turso-ts\">\n    <picture>\n      <img src=\"/.github/cover.png\" alt=\"libSQL TypeScript\" />\n    </picture>\n  </a>\n  <h1 align=\"center\">libSQL TypeScript</h1>\n</p>\n\n<p align=\"center\">\n  Databases for all TypeScript and JS multi-tenant apps.\n</p>\n\n<p align=\"center\">\n  <a href=\"https://tur.so/turso-ts\"><strong>Turso</strong></a> ·\n  <a href=\"https://docs.turso.tech\"><strong>Docs</strong></a> ·\n  <a href=\"https://docs.turso.tech/sdk/ts/quickstart\"><strong>Quickstart</strong></a> ·\n  <a href=\"https://docs.turso.tech/sdk/ts/reference\"><strong>SDK Reference</strong></a> ·\n  <a href=\"https://turso.tech/blog\"><strong>Blog &amp; Tutorials</strong></a>\n</p>\n\n<p align=\"center\">\n  <a href=\"LICENSE\">\n    <picture>\n      <img src=\"https://img.shields.io/github/license/tursodatabase/libsql-client-ts?color=0F624B\" alt=\"MIT License\" />\n    </picture>\n  </a>\n  <a href=\"https://tur.so/discord-ts\">\n    <picture>\n      <img src=\"https://img.shields.io/discord/933071162680958986?color=0F624B\" alt=\"Discord\" />\n    </picture>\n  </a>\n  <a href=\"#contributors\">\n    <picture>\n      <img src=\"https://img.shields.io/github/contributors/tursodatabase/libsql-client-ts?color=0F624B\" alt=\"Contributors\" />\n    </picture>\n  </a>\n  <a href=\"https://www.npmjs.com/package/@libsql/client\">\n    <picture>\n      <img src=\"https://img.shields.io/npm/dw/%40libsql%2Fclient?color=0F624B\" alt=\"Weekly downloads\" />\n    </picture>\n  </a>\n  <a href=\"/examples\">\n    <picture>\n      <img src=\"https://img.shields.io/badge/browse-examples-0F624B\" alt=\"Examples\" />\n    </picture>\n  </a>\n</p>\n\n> **Looking for the Turso serverless package?** Check out [`@tursodatabase/serverless`](https://www.npmjs.com/package/@tursodatabase/serverless) — the lightest option with zero native dependencies, and will be the driver to later support concurrent writes. Use `@libsql/client` if you need a battle-tested driver today with ORM integration.\n\n## Features\n\n-   🔌 Works offline with [Embedded Replicas](https://docs.turso.tech/features/embedded-replicas/introduction)\n-   🌎 Works with remote Turso databases\n-   ✨ Works with Turso [AI & Vector Search](https://docs.turso.tech/features/ai-and-embeddings)\n-   🔐 Supports [encryption at rest](https://docs.turso.tech/libsql#encryption-at-rest)\n\n## Install\n\n```bash\nnpm install @libsql/client\n```\n\n## Quickstart\n\nThe example below uses Embedded Replicas and syncs every minute from Turso.\n\n```ts\nimport { createClient } from \"@libsql/client\";\n\nexport const turso = createClient({\n    url: \"file:local.db\",\n    syncUrl: process.env.TURSO_DATABASE_URL,\n    authToken: process.env.TURSO_AUTH_TOKEN,\n    syncInterval: 60000,\n});\n\nawait turso.batch(\n    [\n        \"CREATE TABLE IF NOT EXISTS users (id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT)\",\n        {\n            sql: \"INSERT INTO users(name) VALUES (?)\",\n            args: [\"Iku\"],\n        },\n    ],\n    \"write\",\n);\n\nawait turso.execute({\n    sql: \"SELECT * FROM users WHERE id = ?\",\n    args: [1],\n});\n```\n\n## Examples\n\n| Example                               | Description                                                                             |\n| ------------------------------------- | --------------------------------------------------------------------------------------- |\n| [local](examples/local)               | Uses libsql with a local SQLite file. Creates database, inserts data, and queries.      |\n| [remote](examples/remote)             | Connects to a remote database. Requires environment variables for URL and auth token.   |\n| [sync](examples/sync)                 | Demonstrates synchronization between local and remote databases.                        |\n| [batch](examples/batch)               | Executes multiple SQL statements in a single batch operation.                           |\n| [transactions](examples/transactions) | Shows transaction usage: starting, performing operations, and committing/rolling back.  |\n| [memory](examples/memory)             | Uses an in-memory SQLite database for temporary storage or fast access.                 |\n| [vector](examples/vector)             | Works with vector embeddings, storing and querying for similarity search.               |\n| [encryption](examples/encryption)     | Creates and uses an encrypted SQLite database, demonstrating setup and data operations. |\n| [ollama](examples/ollama)             | Similarity search with Ollama and Mistral.                                              |\n\n## Documentation\n\nVisit our [official documentation](https://docs.turso.tech/sdk/ts).\n\n## Support\n\nJoin us [on Discord](https://tur.so/discord-ts) to get help using this SDK. Report security issues [via email](mailto:security@turso.tech).\n\n## Contributors\n\nSee the [contributing guide](CONTRIBUTING.md) to learn how to get involved.\n\n![Contributors](https://contrib.nn.ci/api?repo=tursodatabase/libsql-client-ts)\n\n<a href=\"https://github.com/tursodatabase/libsql-client-ts/issues?q=is%3Aopen+is%3Aissue+label%3A%22good+first+issue%22\">\n  <picture>\n    <img src=\"https://img.shields.io/github/issues-search/tursodatabase/libsql-client-ts?label=good%20first%20issue&query=label%3A%22good%20first%20issue%22%20&color=0F624B\" alt=\"good first issue\" />\n  </picture>\n</a>\n"
  },
  {
    "path": "packages/libsql-client/examples/example.js",
    "content": "import { createClient } from \"@libsql/client\";\n\nasync function example() {\n    const config = {\n        url: process.env.URL ?? \"file:local.db\",\n        encryptionKey: process.env.ENCRYPTION_KEY,\n    };\n    const db = createClient(config);\n    await db.batch(\n        [\n            \"CREATE TABLE IF NOT EXISTS users (email TEXT)\",\n            \"INSERT INTO users (email) VALUES ('alice@example.com')\",\n            \"INSERT INTO users (email) VALUES ('bob@example.com')\",\n        ],\n        \"write\",\n    );\n\n    await db.batch(\n        [\n            {\n                sql: \"INSERT INTO users (email) VALUES (?)\",\n                args: [\"alice@example.com\"],\n            },\n            [\"INSERT INTO users (email) VALUES (?)\", [\"bob@example.com\"]],\n            {\n                sql: \"INSERT INTO users (email) VALUES (:email)\",\n                args: { email: \"charlie@example.com\" },\n            },\n        ],\n        \"write\",\n    );\n\n    const rs = await db.execute(\"SELECT * FROM users\");\n    console.log(rs);\n}\n\nawait example();\n"
  },
  {
    "path": "packages/libsql-client/examples/package.json",
    "content": "{\n    \"name\": \"libsql-examples\",\n    \"type\": \"module\",\n    \"private\": true,\n    \"dependencies\": {\n        \"@libsql/client\": \"..\",\n        \"@libsql/core\": \"../../libsql-core\",\n        \"readline-sync\": \"^1.4.10\"\n    }\n}\n"
  },
  {
    "path": "packages/libsql-client/examples/shell.js",
    "content": "import * as readline from \"node:readline/promises\";\nimport { stdin, stdout, argv } from \"node:process\";\nimport * as libsql from \"@libsql/client\";\n\nasync function main() {\n    const url = argv[2];\n    if (!url) {\n        console.error(\"Please specify database URL as command-line argument\");\n        return;\n    }\n\n    const client = libsql.createClient({ url });\n    const rl = readline.createInterface({ input: stdin, output: stdout });\n\n    for (;;) {\n        const sql = await rl.question(\"> \");\n\n        let rs;\n        try {\n            rs = await client.execute(sql);\n        } catch (e) {\n            if (e instanceof libsql.LibsqlError) {\n                console.error(e);\n                continue;\n            }\n            throw e;\n        }\n\n        console.log(JSON.stringify(rs.columns));\n        for (const row of rs.rows) {\n            console.log(JSON.stringify(Array.from(row)));\n        }\n    }\n}\n\nawait main();\n"
  },
  {
    "path": "packages/libsql-client/examples/sync.js",
    "content": "import { createClient } from \"@libsql/client\";\nimport reader from \"readline-sync\";\n\nasync function example() {\n    const config = {\n        url: process.env.URL ?? \"file:local.db\",\n        syncUrl: process.env.SYNC_URL,\n        authToken: process.env.AUTH_TOKEN,\n    };\n    const db = createClient(config);\n    await db.sync();\n    await db.execute(\n        \"CREATE TABLE IF NOT EXISTS guest_book_entries (comment TEXT)\",\n    );\n    const rep = await db.sync();\n\n    console.log(\"frames_synced: \" + rep.frames_synced);\n\n    const comment = reader.question(\"Enter your comment: \");\n\n    await db.execute({\n        sql: \"INSERT INTO guest_book_entries (comment) VALUES (?)\",\n        args: [comment],\n    });\n\n    const rep2 = await db.sync();\n\n    console.log(\"frames_synced: \" + rep2.frames_synced);\n\n    console.log(\"Guest book entries:\");\n    const rs = await db.execute(\"SELECT * FROM guest_book_entries\");\n    for (const row of rs.rows) {\n        console.log(\" - \" + row.comment);\n    }\n}\n\nexample();\n"
  },
  {
    "path": "packages/libsql-client/examples/sync_offline.js",
    "content": "import { createClient } from \"@libsql/client\";\nimport reader from \"readline-sync\";\n\nasync function example() {\n    const config = {\n        url: process.env.URL ?? \"file:local.db\",\n        syncUrl: process.env.SYNC_URL,\n        authToken: process.env.AUTH_TOKEN,\n        offline: true,\n    };\n\n    const db = createClient(config);\n\n    console.log(\"Syncing database ...\");\n    await db.sync();\n\n    await db.execute(\n        \"CREATE TABLE IF NOT EXISTS guest_book_entries (comment TEXT)\",\n    );\n\n    const comment = reader.question(\"Enter your comment: \");\n\n    await db.execute({\n        sql: \"INSERT INTO guest_book_entries (comment) VALUES (?)\",\n        args: [comment],\n    });\n\n    console.log(\"Syncing database ...\");\n    const rep2 = await db.sync();\n\n    console.log(\"frames_synced: \" + rep2.frames_synced);\n\n    console.log(\"Guest book entries:\");\n    const rs = await db.execute(\"SELECT * FROM guest_book_entries\");\n    for (const row of rs.rows) {\n        console.log(\" - \" + row.comment);\n    }\n}\n\nexample();\n"
  },
  {
    "path": "packages/libsql-client/examples/sync_vector.js",
    "content": "import { createClient } from \"@libsql/client\";\nimport reader from \"readline-sync\";\n\nasync function example() {\n    const config = {\n        url: process.env.URL ?? \"file:local.db\",\n        syncUrl: process.env.SYNC_URL,\n        authToken: process.env.AUTH_TOKEN,\n    };\n    const db = createClient(config);\n    await db.sync();\n    await db.execute(\n        \"CREATE TABLE IF NOT EXISTS movies (title TEXT, embedding FLOAT32(4))\",\n    );\n    await db.execute(\n        \"CREATE INDEX IF NOT EXISTS movies_idx ON movies (libsql_vector_idx(embedding))\",\n    );\n    await db.sync();\n\n    const title = reader.question(\"Add movie (title): \");\n    const embedding = reader.question(\n        \"Add movie (embedding, e.g. [1,2,3,4]): \",\n    );\n\n    await db.execute({\n        sql: \"INSERT INTO movies (title, embedding) VALUES (?, vector32(?))\",\n        args: [title, embedding],\n    });\n\n    await db.sync();\n\n    const all = await db.execute(\n        \"SELECT title, vector_extract(embedding) as embedding FROM movies\",\n    );\n    console.info(\"all movies:\");\n    for (const row of all.rows) {\n        console.log(\" - \" + row.title + \": \" + row.embedding);\n    }\n\n    const query = reader.question(\"KNN query (e.g. [1,2,3,4]): \");\n    const nn = await db.execute({\n        sql: \"SELECT title, vector_extract(embedding) as embedding FROM vector_top_k('movies_idx', vector32(?), 2) as knn JOIN movies ON knn.id = movies.rowid\",\n        args: [query],\n    });\n    console.info(\"nearest neighbors:\");\n    for (const row of nn.rows) {\n        console.log(\" - \" + row.title + \": \" + row.embedding);\n    }\n}\n\nexample();\n"
  },
  {
    "path": "packages/libsql-client/jest.config.js",
    "content": "export default {\n    preset: \"ts-jest/presets/default-esm\",\n    moduleNameMapper: {\n        \"^(\\\\.{1,2}/.*)\\\\.js$\": \"$1\",\n    },\n    testMatch: [\"**/__tests__/*.test.[jt]s\"],\n};\n"
  },
  {
    "path": "packages/libsql-client/package-cjs.json",
    "content": "{\n    \"type\": \"commonjs\"\n}\n"
  },
  {
    "path": "packages/libsql-client/package.json",
    "content": "{\n    \"name\": \"@libsql/client\",\n    \"version\": \"0.17.2\",\n    \"keywords\": [\n        \"libsql\",\n        \"database\",\n        \"sqlite\",\n        \"serverless\",\n        \"vercel\",\n        \"netlify\",\n        \"lambda\"\n    ],\n    \"description\": \"libSQL driver for TypeScript and JavaScript\",\n    \"repository\": {\n        \"type\": \"git\",\n        \"url\": \"git+https://github.com/tursodatabase/libsql-client-ts\",\n        \"directory\": \"packages/libsql-client\"\n    },\n    \"authors\": [\n        \"Jan Špaček <honza@chiselstrike.com>\",\n        \"Pekka Enberg <penberg@chiselstrike.com>\",\n        \"Jan Plhak <jp@chiselstrike.com>\"\n    ],\n    \"license\": \"MIT\",\n    \"type\": \"module\",\n    \"main\": \"lib-cjs/node.js\",\n    \"types\": \"lib-esm/node.d.ts\",\n    \"exports\": {\n        \".\": {\n            \"types\": \"./lib-esm/node.d.ts\",\n            \"import\": {\n                \"workerd\": \"./lib-esm/web.js\",\n                \"deno\": \"./lib-esm/node.js\",\n                \"edge-light\": \"./lib-esm/web.js\",\n                \"netlify\": \"./lib-esm/web.js\",\n                \"node\": \"./lib-esm/node.js\",\n                \"browser\": \"./lib-esm/web.js\",\n                \"default\": \"./lib-esm/node.js\"\n            },\n            \"require\": \"./lib-cjs/node.js\"\n        },\n        \"./node\": {\n            \"types\": \"./lib-esm/node.d.ts\",\n            \"import\": \"./lib-esm/node.js\",\n            \"require\": \"./lib-cjs/node.js\"\n        },\n        \"./http\": {\n            \"types\": \"./lib-esm/http.d.ts\",\n            \"import\": \"./lib-esm/http.js\",\n            \"require\": \"./lib-cjs/http.js\"\n        },\n        \"./ws\": {\n            \"types\": \"./lib-esm/ws.d.ts\",\n            \"import\": \"./lib-esm/ws.js\",\n            \"require\": \"./lib-cjs/ws.js\"\n        },\n        \"./sqlite3\": {\n            \"types\": \"./lib-esm/sqlite3.d.ts\",\n            \"import\": \"./lib-esm/sqlite3.js\",\n            \"require\": \"./lib-cjs/sqlite3.js\"\n        },\n        \"./web\": {\n            \"types\": \"./lib-esm/web.d.ts\",\n            \"import\": \"./lib-esm/web.js\",\n            \"require\": \"./lib-cjs/web.js\"\n        }\n    },\n    \"typesVersions\": {\n        \"*\": {\n            \".\": [\n                \"./lib-esm/node.d.ts\"\n            ],\n            \"http\": [\n                \"./lib-esm/http.d.ts\"\n            ],\n            \"hrana\": [\n                \"./lib-esm/hrana.d.ts\"\n            ],\n            \"sqlite3\": [\n                \"./lib-esm/sqlite3.d.ts\"\n            ],\n            \"web\": [\n                \"./lib-esm/web.d.ts\"\n            ]\n        }\n    },\n    \"files\": [\n        \"lib-cjs/**\",\n        \"lib-esm/**\",\n        \"README.md\"\n    ],\n    \"scripts\": {\n        \"prepublishOnly\": \"npm run build\",\n        \"prebuild\": \"rm -rf ./lib-cjs ./lib-esm\",\n        \"build\": \"npm run build:cjs && npm run build:esm\",\n        \"build:cjs\": \"tsc -p tsconfig.build-cjs.json\",\n        \"build:esm\": \"tsc -p tsconfig.build-esm.json\",\n        \"format:check\": \"prettier --check .\",\n        \"postbuild\": \"cp package-cjs.json ./lib-cjs/package.json\",\n        \"test\": \"jest --runInBand\",\n        \"typecheck\": \"tsc --noEmit\",\n        \"typedoc\": \"rm -rf ./docs && typedoc\",\n        \"lint-staged\": \"lint-staged\"\n    },\n    \"dependencies\": {\n        \"@libsql/core\": \"^0.17.2\",\n        \"@libsql/hrana-client\": \"^0.9.0\",\n        \"js-base64\": \"^3.7.5\",\n        \"libsql\": \"^0.5.28\",\n        \"promise-limit\": \"^2.7.0\"\n    },\n    \"devDependencies\": {\n        \"@types/jest\": \"^29.2.5\",\n        \"@types/node\": \"^18.15.5\",\n        \"jest\": \"^29.3.1\",\n        \"lint-staged\": \"^15.2.2\",\n        \"msw\": \"^2.3.0\",\n        \"prettier\": \"3.2.5\",\n        \"ts-jest\": \"^29.0.5\",\n        \"typedoc\": \"^0.23.28\",\n        \"typescript\": \"^4.9.4\"\n    }\n}\n"
  },
  {
    "path": "packages/libsql-client/smoke_test/vercel/.gitignore",
    "content": "app/package.json\npackage-lock.json\n*.tgz\n"
  },
  {
    "path": "packages/libsql-client/smoke_test/vercel/app/.gitignore",
    "content": ".vercel\n"
  },
  {
    "path": "packages/libsql-client/smoke_test/vercel/app/api/function.ts",
    "content": "import * as libsql from \"@libsql/client\";\n\nexport const config = {\n    runtime: \"edge\",\n};\n\nexport default async function (request: Request) {\n    function respond(status: number, responseBody: string) {\n        return new Response(responseBody, {\n            status,\n            headers: [[\"content-type\", \"text/plain\"]],\n        });\n    }\n\n    if (request.method !== \"GET\") {\n        return respond(405, \"Only GET method is supported\");\n    }\n\n    const url = new URL(request.url);\n    const testCase = url.searchParams.get(\"test\");\n    if (testCase === null) {\n        return respond(\n            400,\n            \"Please specify the test case using the 'test' query parameter\",\n        );\n    }\n\n    const testCaseFn = testCases[testCase];\n    if (testCaseFn === undefined) {\n        return respond(404, \"Unknown test case\");\n    }\n\n    let client;\n    try {\n        client = libsql.createClient({ url: process.env.CLIENT_URL! });\n        await testCaseFn(client);\n        return respond(200, \"Test passed\");\n    } catch (e) {\n        return respond(500, `Test failed\\n${(e as Error).stack}`);\n    } finally {\n        if (client !== undefined) {\n            client.close();\n        }\n    }\n}\n\nconst testCases: Record<string, (client: libsql.Client) => Promise<void>> = {\n    execute: async (client: libsql.Client): Promise<void> => {\n        const rs = await client.execute(\"SELECT 1+1 AS two\");\n        assert(rs.columns.length === 1);\n        assert(rs.columns[0] === \"two\");\n        assert(rs.rows.length === 1);\n        assert(rs.rows[0].length === 1);\n        assert(rs.rows[0][0] === 2.0);\n    },\n\n    batch: async (client: libsql.Client): Promise<void> => {\n        const rss = await client.batch([\n            \"DROP TABLE IF EXISTS t\",\n            \"CREATE TABLE t (a, b)\",\n            \"INSERT INTO t VALUES (1, 'one'), (2, 'two'), (3, 'three')\",\n            \"SELECT * FROM t ORDER BY a\",\n        ]);\n\n        assert(rss[0].columns.length === 0);\n        assert(rss[0].rows.length === 0);\n\n        assert(rss[1].columns.length === 0);\n        assert(rss[1].rows.length === 0);\n\n        assert(rss[2].columns.length === 0);\n        assert(rss[2].rows.length === 0);\n\n        assert(rss[3].columns.length === 2);\n        assert(rss[3].columns[0] === \"a\");\n        assert(rss[3].columns[1] === \"b\");\n        assert(rss[3].rows.length === 3);\n        assert(rss[3].rows[0][0] === 1);\n        assert(rss[3].rows[0][1] === \"one\");\n        assert(rss[3].rows[1][0] === 2);\n        assert(rss[3].rows[1][1] === \"two\");\n        assert(rss[3].rows[2][0] === 3);\n        assert(rss[3].rows[2][1] === \"three\");\n    },\n\n    transaction: async (client: libsql.Client): Promise<void> => {\n        await client.batch([\n            \"DROP TABLE IF EXISTS t\",\n            \"CREATE TABLE t (a, b)\",\n            \"INSERT INTO t VALUES (1, 'one'), (2, 'two'), (3, 'three')\",\n        ]);\n\n        const txn = await client.transaction();\n        try {\n            await txn.execute(\"INSERT INTO t VALUES (4, 'four')\");\n            await txn.execute(\"DELETE FROM t WHERE a <= 2\");\n            await txn.commit();\n        } finally {\n            txn.close();\n        }\n\n        const rs = await client.execute(\"SELECT COUNT(*) FROM t\");\n        assert(rs.rows[0][0] === 2);\n    },\n};\n\nfunction assert(value: unknown, message?: string) {\n    if (!value) {\n        throw new Error(message ?? \"Assertion failed\");\n    }\n}\n"
  },
  {
    "path": "packages/libsql-client/smoke_test/vercel/app/public/index.html",
    "content": "<html>\n    <body>\n        <p>This is a smoke-test Vercel app for <code>@libsql/client</code>.</p>\n    </body>\n</html>\n"
  },
  {
    "path": "packages/libsql-client/smoke_test/vercel/package.json",
    "content": "{\n    \"name\": \"smoke-test\",\n    \"dependencies\": {\n        \"@types/node\": \"20.4.2\",\n        \"localtunnel\": \"^2.0.2\",\n        \"vercel\": \"^31.0.3\",\n        \"typescript\": \"^4.9.4\"\n    }\n}\n"
  },
  {
    "path": "packages/libsql-client/smoke_test/vercel/test.js",
    "content": "\"use strict\";\nconst { spawn } = require(\"node:child_process\");\nconst fs = require(\"node:fs\");\nconst fetch = require(\"node-fetch\");\nconst localtunnel = require(\"localtunnel\");\n\nfunction getEnv(name) {\n    const value = process.env[name] ?? \"\";\n    if (!value) {\n        throw new Error(`Please set the env variable ${name}`);\n    }\n    return value;\n}\n\nconst vercelToken = getEnv(\"VERCEL_TOKEN\");\nconst projectName = getEnv(\"VERCEL_PROJECT_NAME\");\n\nasync function npm(\n    subcommand,\n    args,\n    hiddenArgs = [],\n    { capture = false } = {},\n) {\n    console.info(`$ npm ${subcommand} ${args.join(\" \")}`);\n\n    const proc = spawn(\"npm\", [subcommand, ...args, ...hiddenArgs], {\n        stdio: [\"ignore\", capture ? \"pipe\" : \"inherit\", \"inherit\"],\n    });\n\n    const exitPromise = new Promise((resolve, reject) => {\n        proc.on(\"exit\", (code, signal) => {\n            if (signal !== null) {\n                reject(\n                    new Error(\n                        `vercel command terminated due to signal: ${signal}`,\n                    ),\n                );\n            } else if (code !== 0) {\n                reject(new Error(`vercel command exited with code: ${code}`));\n            } else {\n                resolve();\n            }\n        });\n    });\n\n    const dataPromise = new Promise((resolve, reject) => {\n        if (!capture) {\n            return resolve();\n        }\n\n        const stream = proc.stdout;\n        stream.setEncoding(\"utf-8\");\n\n        const chunks = [];\n        stream.on(\"data\", (chunk) => chunks.push(chunk));\n        stream.on(\"end\", () => resolve(chunks.join(\"\")));\n        stream.on(\"error\", (e) => reject(e));\n    });\n\n    return exitPromise.then(() => dataPromise);\n}\n\nasync function deployToVercel(clientUrlInsideVercel) {\n    console.info(\"Building and deploying to Vercel...\");\n\n    let tarballName = await npm(\"pack\", [\"../..\"], [], { capture: true });\n    tarballName = tarballName.trim();\n\n    const appPackageJson = {\n        dependencies: {\n            \"@libsql/client\": `../${tarballName}`,\n        },\n    };\n    fs.writeFileSync(\n        \"app/package.json\",\n        JSON.stringify(appPackageJson, null, 4),\n    );\n\n    await npm(\n        \"exec\",\n        [\n            \"--\",\n            \"vercel\",\n            \"link\",\n            \"--yes\",\n            \"--project\",\n            projectName,\n            \"--cwd\",\n            \"app/\",\n        ],\n        [\"--token\", vercelToken],\n    );\n    await npm(\n        \"exec\",\n        [\n            \"--\",\n            \"vercel\",\n            \"pull\",\n            \"--yes\",\n            \"--environment=preview\",\n            \"--cwd\",\n            \"app/\",\n        ],\n        [\"--token\", vercelToken],\n    );\n    await npm(\"exec\", [\"--\", \"vercel\", \"build\", \"--cwd\", \"app/\"]);\n\n    const deployUrl = await npm(\n        \"exec\",\n        [\n            \"--\",\n            \"vercel\",\n            \"deploy\",\n            \"--prebuilt\",\n            \"--env\",\n            `CLIENT_URL=${clientUrlInsideVercel}`,\n            \"--cwd\",\n            \"app/\",\n        ],\n        [\"--token\", vercelToken, \"--cwd\", \"app/\"],\n        { capture: true },\n    );\n\n    console.info(`Deployed Vercel project on ${deployUrl}`);\n    return deployUrl;\n}\n\nconst testCases = [\"execute\", \"batch\", \"transaction\"];\n\nasync function runTests(functionUrl) {\n    let ok = true;\n    for (const testCase of testCases) {\n        if (!(await runTest(functionUrl, testCase))) {\n            ok = false;\n        }\n    }\n    return ok;\n}\n\nasync function runTest(functionUrl, testCase) {\n    const resp = await fetch(`${functionUrl}?test=${testCase}`);\n    const respText = await resp.text();\n    const ok = resp.status === 200 && respText === \"Test passed\";\n    if (ok) {\n        console.info(`TEST ${testCase}: passed`);\n    } else {\n        console.warn(\n            `\\nTEST ${testCase}: failed with status ${resp.status}\\n${respText}\\n`,\n        );\n    }\n    return ok;\n}\n\nasync function main() {\n    const url = new URL(process.env.URL ?? \"ws://localhost:8080\");\n\n    console.info(`Creating a tunnel to ${url}...`);\n    const tunnel = await localtunnel({\n        port: url.port,\n        // NOTE: if we specify `local_host`, `localtunnel` will try to rewrite the `Host` header in the\n        // tunnelled HTTP requests. Unfortunately, they do it in a very silly way by converting the\n        // tunnelled data to a string, thus corrupting the request body.\n        //local_host: url.hostname,\n    });\n\n    let clientUrlInsideVercel = new URL(tunnel.url);\n    if (url.protocol === \"http:\") {\n        clientUrlInsideVercel.protocol = \"https:\";\n    } else if (url.protocol === \"ws:\") {\n        clientUrlInsideVercel.protocol = \"wss:\";\n    } else {\n        clientUrlInsideVercel.protocol = url.protocol;\n    }\n\n    console.info(`Established a tunnel on ${clientUrlInsideVercel}`);\n\n    let ok = false;\n    try {\n        const deployUrl = await deployToVercel(clientUrlInsideVercel);\n        const functionUrl = new URL(\"api/function\", deployUrl);\n        ok = await runTests(functionUrl);\n        if (ok) {\n            console.log(\"All tests passed\");\n        } else {\n            console.error(\"Some tests failed\");\n        }\n    } finally {\n        console.info(\"Closing the tunnel...\");\n        await tunnel.close();\n    }\n\n    process.exit(ok ? 0 : 1);\n}\n\nmain();\n"
  },
  {
    "path": "packages/libsql-client/smoke_test/vercel/tsconfig.json",
    "content": "{\n    \"compilerOptions\": {\n        \"target\": \"es5\",\n        \"lib\": [\"dom\", \"esnext\"],\n        \"module\": \"esnext\",\n        \"moduleResolution\": \"node\",\n        \"allowJs\": true,\n        \"skipLibCheck\": true,\n        \"strict\": true,\n        \"noEmit\": true,\n        \"esModuleInterop\": true,\n        \"isolatedModules\": true\n    }\n}\n"
  },
  {
    "path": "packages/libsql-client/smoke_test/workers/.gitignore",
    "content": "package-lock.json\n"
  },
  {
    "path": "packages/libsql-client/smoke_test/workers/package.json",
    "content": "{\n    \"devDependencies\": {\n        \"localtunnel\": \"^2.0.2\",\n        \"wrangler\": \"^3.5.1\"\n    }\n}\n"
  },
  {
    "path": "packages/libsql-client/smoke_test/workers/test.js",
    "content": "\"use strict\";\nconst localtunnel = require(\"localtunnel\");\nconst wrangler = require(\"wrangler\");\n\nconst testCases = [\"/execute\", \"/batch\", \"/transaction\"];\n\nasync function main() {\n    const local = !!parseInt(process.env.LOCAL ?? \"1\");\n    const url = new URL(process.env.URL ?? \"ws://localhost:8080\");\n\n    let clientUrlInsideWorker;\n    let tunnel = undefined;\n    if (local) {\n        clientUrlInsideWorker = url;\n    } else {\n        console.info(`Creating an tunnel to ${url}...`);\n        tunnel = await localtunnel({\n            port: url.port,\n            // NOTE: if we specify `local_host`, `localtunnel` will try to rewrite the `Host` header in the\n            // tunnelled HTTP requests. Unfortunately, they do it in a very silly way by converting the\n            // tunnelled data to a string, thus corrupting the request body.\n            //local_host: url.hostname,\n        });\n\n        clientUrlInsideWorker = new URL(tunnel.url);\n        if (url.protocol === \"http:\") {\n            clientUrlInsideWorker.protocol = \"https:\";\n        } else if (url.protocol === \"ws:\") {\n            clientUrlInsideWorker.protocol = \"wss:\";\n        } else {\n            clientUrlInsideWorker.protocol = url.protocol;\n        }\n\n        console.info(`Established a tunnel on ${clientUrlInsideWorker}`);\n    }\n\n    let ok = false;\n    try {\n        ok = await runWorker(local, clientUrlInsideWorker);\n        if (ok) {\n            console.log(\"All tests passed\");\n        } else {\n            console.error(\"Some tests failed\");\n        }\n    } finally {\n        if (tunnel !== undefined) {\n            console.info(\"Closing tunnel...\");\n            await tunnel.close();\n        }\n\n        // TODO: wrangler keeps the program running:\n        // https://github.com/cloudflare/workers-sdk/issues/2892\n        setTimeout(() => process.exit(ok ? 0 : 1), 200);\n    }\n}\n\nasync function runWorker(local, clientUrlInsideWorker) {\n    console.info(`Creating a ${local ? \"local\" : \"nonlocal\"} Worker...`);\n    const worker = await wrangler.unstable_dev(\"worker.js\", {\n        config: \"wrangler.toml\",\n        logLevel: \"info\",\n        local,\n        vars: {\n            CLIENT_URL: clientUrlInsideWorker.toString(),\n        },\n        experimental: {\n            disableExperimentalWarning: true,\n        },\n    });\n    console.info(`Worker created on ${worker.address}:${worker.port}`);\n\n    try {\n        let ok = true;\n        for (const testCase of testCases) {\n            if (!(await runTest(worker, testCase))) {\n                ok = false;\n            }\n        }\n        return ok;\n    } finally {\n        console.info(\"Stopping Worker...\");\n        await worker.stop();\n    }\n}\n\nasync function runTest(worker, testCase) {\n    const resp = await worker.fetch(testCase);\n    const respText = await resp.text();\n    const ok = resp.status === 200 && respText === \"Test passed\";\n    if (ok) {\n        console.info(`TEST ${testCase}: passed`);\n    } else {\n        console.warn(\n            `\\nTEST ${testCase}: failed with status ${resp.status}\\n${respText}\\n`,\n        );\n    }\n    return ok;\n}\n\nmain();\n"
  },
  {
    "path": "packages/libsql-client/smoke_test/workers/worker.js",
    "content": "import * as libsql from \"@libsql/client\";\n\nexport default {\n    async fetch(request, env, ctx) {\n        function respond(status, responseBody) {\n            return new Response(responseBody, {\n                status,\n                headers: [[\"content-type\", \"text/plain\"]],\n            });\n        }\n\n        if (request.method !== \"GET\") {\n            return respond(405, \"Only GET method is supported\");\n        }\n\n        const url = new URL(request.url);\n        if (url.pathname === \"/\") {\n            return respond(\n                200,\n                \"This is a smoke-test Worker for @libsql/client\",\n            );\n        }\n\n        const testCaseFn = testCases[url.pathname];\n        if (testCaseFn === undefined) {\n            return respond(404, \"Unknown test case\");\n        }\n\n        let client;\n        try {\n            client = libsql.createClient({ url: env.CLIENT_URL });\n            await testCaseFn(client);\n            return respond(200, \"Test passed\");\n        } catch (e) {\n            return respond(500, `Test failed\\n${e.stack}`);\n        } finally {\n            if (client !== undefined) {\n                client.close();\n            }\n        }\n    },\n};\n\nconst testCases = {\n    \"/execute\": async (client) => {\n        const rs = await client.execute(\"SELECT 1+1 AS two\");\n        assert(rs.columns.length === 1);\n        assert(rs.columns[0] === \"two\");\n        assert(rs.rows.length === 1);\n        assert(rs.rows[0].length === 1);\n        assert(rs.rows[0][0] === 2.0);\n    },\n\n    \"/batch\": async (client) => {\n        const rss = await client.batch([\n            \"DROP TABLE IF EXISTS t\",\n            \"CREATE TABLE t (a, b)\",\n            \"INSERT INTO t VALUES (1, 'one'), (2, 'two'), (3, 'three')\",\n            \"SELECT * FROM t ORDER BY a\",\n        ]);\n\n        assert(rss[0].columns.length === 0);\n        assert(rss[0].rows.length === 0);\n\n        assert(rss[1].columns.length === 0);\n        assert(rss[1].rows.length === 0);\n\n        assert(rss[2].columns.length === 0);\n        assert(rss[2].rows.length === 0);\n\n        assert(rss[3].columns.length === 2);\n        assert(rss[3].columns[0] === \"a\");\n        assert(rss[3].columns[1] === \"b\");\n        assert(rss[3].rows.length === 3);\n        assert(rss[3].rows[0][0] === 1);\n        assert(rss[3].rows[0][1] === \"one\");\n        assert(rss[3].rows[1][0] === 2);\n        assert(rss[3].rows[1][1] === \"two\");\n        assert(rss[3].rows[2][0] === 3);\n        assert(rss[3].rows[2][1] === \"three\");\n    },\n\n    \"/transaction\": async (client) => {\n        await client.batch([\n            \"DROP TABLE IF EXISTS t\",\n            \"CREATE TABLE t (a, b)\",\n            \"INSERT INTO t VALUES (1, 'one'), (2, 'two'), (3, 'three')\",\n        ]);\n\n        const txn = await client.transaction();\n        try {\n            await txn.execute(\"INSERT INTO t VALUES (4, 'four')\");\n            await txn.execute(\"DELETE FROM t WHERE a <= 2\");\n            await txn.commit();\n        } finally {\n            txn.close();\n        }\n\n        const rs = await client.execute(\"SELECT COUNT(*) FROM t\");\n        assert(rs.rows[0][0] === 2);\n    },\n};\n\nfunction assert(value, message) {\n    if (!value) {\n        throw new Error(message ?? \"Assertion failed\");\n    }\n}\n"
  },
  {
    "path": "packages/libsql-client/smoke_test/workers/wrangler.toml",
    "content": "main = \"worker.js\"\ncompatibility_date = \"2023-05-15\"\n\n[vars]\nCLIENT_URL = \"ws://localhost:8080\"\n"
  },
  {
    "path": "packages/libsql-client/src/__tests__/client.test.ts",
    "content": "import console from \"node:console\";\nimport { expect } from \"@jest/globals\";\nimport type { MatcherFunction } from \"expect\";\n\nimport type { Request, Response } from \"@libsql/hrana-client\";\nimport { fetch } from \"@libsql/hrana-client\";\n\nimport \"./helpers.js\";\n\nimport type * as libsql from \"../node.js\";\nimport { createClient } from \"../node.js\";\n\nconst config = {\n    url: process.env.URL ?? \"ws://localhost:8080\",\n    syncUrl: process.env.SYNC_URL,\n    authToken: process.env.AUTH_TOKEN,\n};\n\nconst isWs =\n    config.url.startsWith(\"ws:\") ||\n    config.url.startsWith(\"wss:\") ||\n    config.url.startsWith(\"libsql:\");\nconst isHttp =\n    config.url.startsWith(\"http:\") || config.url.startsWith(\"https:\");\nconst isFile = config.url.startsWith(\"file:\");\n\n// This allows us to skip tests based on the Hrana server that we are targeting:\n// - \"test_v3\" is the v3 test server in Python\n// - \"test_v2\" is the v2 test server in Python\n// - \"test_v1\" is the v1 test server in Python\n// - \"sqld\" is sqld\nconst server = process.env.SERVER ?? \"test_v3\";\n\nconst isSqld = server === \"sqld\";\nconst hasHrana2 = server !== \"test_v1\";\nconst hasHrana3 =\n    server !== \"test_v1\" && server !== \"test_v2\" && server !== \"sqld\";\nconst hasNetworkErrors =\n    isWs &&\n    (server === \"test_v1\" || server === \"test_v2\" || server === \"test_v3\");\n\nfunction withClient(\n    f: (c: libsql.Client) => Promise<void>,\n    extraConfig: Partial<libsql.Config> = {},\n): () => Promise<void> {\n    return async () => {\n        const c = createClient({ ...config, ...extraConfig });\n        try {\n            await f(c);\n        } finally {\n            c.close();\n        }\n    };\n}\n\nfunction withInMemoryClient(\n    f: (c: libsql.Client) => Promise<void>,\n): () => Promise<void> {\n    return async () => {\n        const c = createClient({ url: \":memory:\" });\n        try {\n            await f(c);\n        } finally {\n            c.close();\n        }\n    };\n}\n\ndescribe(\"createClient()\", () => {\n    test(\"URL scheme not supported\", () => {\n        expect(() => createClient({ url: \"ftp://localhost\" })).toThrow(\n            expect.toBeLibsqlError(\"URL_SCHEME_NOT_SUPPORTED\", /\"ftp:\"/),\n        );\n    });\n\n    test(\"URL param not supported\", () => {\n        expect(() => createClient({ url: \"ws://localhost?foo=bar\" })).toThrow(\n            expect.toBeLibsqlError(\"URL_PARAM_NOT_SUPPORTED\", /\"foo\"/),\n        );\n    });\n\n    test(\"URL scheme incompatible with ?tls\", () => {\n        const urls = [\n            \"ws://localhost?tls=1\",\n            \"wss://localhost?tls=0\",\n            \"http://localhost?tls=1\",\n            \"https://localhost?tls=0\",\n        ];\n        for (const url of urls) {\n            expect(() => createClient({ url })).toThrow(\n                expect.toBeLibsqlError(\"URL_INVALID\", /TLS/),\n            );\n        }\n    });\n\n    test(\"missing port in libsql URL with tls=0\", () => {\n        expect(() => createClient({ url: \"libsql://localhost?tls=0\" })).toThrow(\n            expect.toBeLibsqlError(\"URL_INVALID\", /port/),\n        );\n    });\n\n    test(\"invalid value of tls query param\", () => {\n        expect(() =>\n            createClient({ url: \"libsql://localhost?tls=yes\" }),\n        ).toThrow(expect.toBeLibsqlError(\"URL_INVALID\", /\"tls\".*\"yes\"/));\n    });\n\n    test(\"passing URL instead of config object\", () => {\n        // @ts-expect-error\n        expect(() => createClient(\"ws://localhost\")).toThrow(\n            /as object, got string/,\n        );\n    });\n\n    test(\"invalid value for `intMode`\", () => {\n        // @ts-expect-error\n        expect(() => createClient({ ...config, intMode: \"foo\" })).toThrow(\n            /\"foo\"/,\n        );\n    });\n\n    test(\"supports in-memory database\", () => {\n        expect(() => createClient({ url: \":memory:\" })).not.toThrow();\n    });\n});\n\ndescribe(\"execute()\", () => {\n    test(\n        \"query a single value\",\n        withClient(async (c) => {\n            const rs = await c.execute(\"SELECT 42\");\n            expect(rs.columns.length).toStrictEqual(1);\n            expect(rs.columnTypes.length).toStrictEqual(1);\n            expect(rs.rows.length).toStrictEqual(1);\n            expect(rs.rows[0].length).toStrictEqual(1);\n            expect(rs.rows[0][0]).toStrictEqual(42);\n        }),\n    );\n\n    test(\n        \"query a single row\",\n        withClient(async (c) => {\n            const rs = await c.execute(\n                \"SELECT 1 AS one, 'two' AS two, 0.5 AS three\",\n            );\n            expect(rs.columns).toStrictEqual([\"one\", \"two\", \"three\"]);\n            expect(rs.columnTypes).toStrictEqual([\"\", \"\", \"\"]);\n            expect(rs.rows.length).toStrictEqual(1);\n\n            const r = rs.rows[0];\n            expect(r.length).toStrictEqual(3);\n            expect(Array.from(r)).toStrictEqual([1, \"two\", 0.5]);\n            expect(Object.entries(r)).toStrictEqual([\n                [\"one\", 1],\n                [\"two\", \"two\"],\n                [\"three\", 0.5],\n            ]);\n        }),\n    );\n\n    test(\n        \"query multiple rows\",\n        withClient(async (c) => {\n            const rs = await c.execute(\n                \"VALUES (1, 'one'), (2, 'two'), (3, 'three')\",\n            );\n            expect(rs.columns.length).toStrictEqual(2);\n            expect(rs.columnTypes.length).toStrictEqual(2);\n            expect(rs.rows.length).toStrictEqual(3);\n\n            expect(Array.from(rs.rows[0])).toStrictEqual([1, \"one\"]);\n            expect(Array.from(rs.rows[1])).toStrictEqual([2, \"two\"]);\n            expect(Array.from(rs.rows[2])).toStrictEqual([3, \"three\"]);\n        }),\n    );\n\n    test(\n        \"statement that produces error\",\n        withClient(async (c) => {\n            await expect(c.execute(\"SELECT foobar\")).rejects.toBeLibsqlError();\n        }),\n    );\n\n    test(\n        \"rowsAffected with INSERT\",\n        withClient(async (c) => {\n            await c.batch(\n                [\"DROP TABLE IF EXISTS t\", \"CREATE TABLE t (a)\"],\n                \"write\",\n            );\n            const rs = await c.execute(\"INSERT INTO t VALUES (1), (2)\");\n            expect(rs.rowsAffected).toStrictEqual(2);\n        }),\n    );\n\n    test(\n        \"rowsAffected with DELETE\",\n        withClient(async (c) => {\n            await c.batch(\n                [\n                    \"DROP TABLE IF EXISTS t\",\n                    \"CREATE TABLE t (a)\",\n                    \"INSERT INTO t VALUES (1), (2), (3), (4), (5)\",\n                ],\n                \"write\",\n            );\n            const rs = await c.execute(\"DELETE FROM t WHERE a >= 3\");\n            expect(rs.rowsAffected).toStrictEqual(3);\n        }),\n    );\n\n    test(\n        \"lastInsertRowid with INSERT\",\n        withClient(async (c) => {\n            await c.batch(\n                [\n                    \"DROP TABLE IF EXISTS t\",\n                    \"CREATE TABLE t (a)\",\n                    \"INSERT INTO t VALUES ('one'), ('two')\",\n                ],\n                \"write\",\n            );\n            const insertRs = await c.execute(\"INSERT INTO t VALUES ('three')\");\n            expect(insertRs.lastInsertRowid).not.toBeUndefined();\n            const selectRs = await c.execute({\n                sql: \"SELECT a FROM t WHERE ROWID = ?\",\n                args: [insertRs.lastInsertRowid!],\n            });\n            expect(Array.from(selectRs.rows[0])).toStrictEqual([\"three\"]);\n        }),\n    );\n\n    test(\n        \"rows from INSERT RETURNING\",\n        withClient(async (c) => {\n            await c.batch(\n                [\"DROP TABLE IF EXISTS t\", \"CREATE TABLE t (a)\"],\n                \"write\",\n            );\n\n            const rs = await c.execute(\n                \"INSERT INTO t VALUES (1) RETURNING 42 AS x, 'foo' AS y\",\n            );\n            expect(rs.columns).toStrictEqual([\"x\", \"y\"]);\n            expect(rs.columnTypes).toStrictEqual([\"\", \"\"]);\n            expect(rs.rows.length).toStrictEqual(1);\n            expect(Array.from(rs.rows[0])).toStrictEqual([42, \"foo\"]);\n        }),\n    );\n\n    (hasHrana2 ? test : test.skip)(\n        \"rowsAffected with WITH INSERT\",\n        withClient(async (c) => {\n            await c.batch(\n                [\n                    \"DROP TABLE IF EXISTS t\",\n                    \"CREATE TABLE t (a)\",\n                    \"INSERT INTO t VALUES (1), (2), (3)\",\n                ],\n                \"write\",\n            );\n\n            const rs = await c.execute(`\n            WITH x(a) AS (SELECT 2*a FROM t)\n            INSERT INTO t SELECT a+1 FROM x\n        `);\n            expect(rs.rowsAffected).toStrictEqual(3);\n        }),\n    );\n\n    test(\n        \"query a single value using an in memory database\",\n        withInMemoryClient(async (c) => {\n            await c.batch(\n                [\n                    \"DROP TABLE IF EXISTS t\",\n                    \"CREATE TABLE t (a)\",\n                    \"INSERT INTO t VALUES ('one'), ('two')\",\n                ],\n                \"write\",\n            );\n            const insertRs = await c.execute(\"INSERT INTO t VALUES ('three')\");\n            expect(insertRs.lastInsertRowid).not.toBeUndefined();\n            const selectRs = await c.execute({\n                sql: \"SELECT a FROM t WHERE ROWID = ?\",\n                args: [insertRs.lastInsertRowid!],\n            });\n            expect(Array.from(selectRs.rows[0])).toStrictEqual([\"three\"]);\n        }),\n    );\n\n    // see issue https://github.com/tursodatabase/libsql/issues/1411\n    test(\n        \"execute transaction against in memory database with shared cache\",\n        withClient(\n            async (c) => {\n                await c.execute(\"CREATE TABLE t (a)\");\n                const transaction = await c.transaction();\n                transaction.close();\n                await c.execute(\"SELECT * FROM t\");\n            },\n            { url: \"file::memory:?cache=shared\" },\n        ),\n    );\n    test(\n        \"execute transaction against in memory database with private cache\",\n        withClient(\n            async (c) => {\n                await c.execute(\"CREATE TABLE t (a)\");\n                const transaction = await c.transaction();\n                transaction.close();\n                expect(() => c.execute(\"SELECT * FROM t\")).rejects.toThrow();\n            },\n            { url: \"file::memory:?cache=private\" },\n        ),\n    );\n    test(\n        \"execute transaction against in memory database with default cache\",\n        withClient(\n            async (c) => {\n                await c.execute(\"CREATE TABLE t (a)\");\n                const transaction = await c.transaction();\n                transaction.close();\n                expect(() => c.execute(\"SELECT * FROM t\")).rejects.toThrow();\n            },\n            { url: \":memory:\" },\n        ),\n    );\n});\n\ndescribe(\"values\", () => {\n    function testRoundtrip(\n        name: string,\n        passed: libsql.InValue,\n        expected: libsql.Value,\n        intMode?: libsql.IntMode,\n    ): void {\n        test(\n            name,\n            withClient(\n                async (c) => {\n                    const rs = await c.execute({\n                        sql: \"SELECT ?\",\n                        args: [passed],\n                    });\n                    expect(rs.rows[0][0]).toStrictEqual(expected);\n                },\n                { intMode },\n            ),\n        );\n    }\n\n    function testRoundtripError(\n        name: string,\n        passed: libsql.InValue,\n        expectedError: unknown,\n        intMode?: libsql.IntMode,\n    ): void {\n        test(\n            name,\n            withClient(\n                async (c) => {\n                    await expect(\n                        c.execute({\n                            sql: \"SELECT ?\",\n                            args: [passed],\n                        }),\n                    ).rejects.toBeInstanceOf(expectedError);\n                },\n                { intMode },\n            ),\n        );\n    }\n\n    testRoundtrip(\"string\", \"boomerang\", \"boomerang\");\n    testRoundtrip(\"string with weird characters\", \"a\\n\\r\\t \", \"a\\n\\r\\t \");\n    testRoundtrip(\n        \"string with unicode\",\n        \"žluťoučký kůň úpěl ďábelské ódy\",\n        \"žluťoučký kůň úpěl ďábelské ódy\",\n    );\n\n    describe(\"number\", () => {\n        const intModes: Array<libsql.IntMode> = [\"number\", \"bigint\", \"string\"];\n        for (const intMode of intModes) {\n            testRoundtrip(\"zero\", 0, 0, intMode);\n            testRoundtrip(\"integer\", -2023, -2023, intMode);\n            testRoundtrip(\"float\", 12.345, 12.345, intMode);\n            testRoundtrip(\"large positive float\", 1e18, 1e18, intMode);\n            testRoundtrip(\"large negative float\", -1e18, -1e18, intMode);\n            testRoundtrip(\n                \"MAX_VALUE\",\n                Number.MAX_VALUE,\n                Number.MAX_VALUE,\n                intMode,\n            );\n            testRoundtrip(\n                \"-MAX_VALUE\",\n                -Number.MAX_VALUE,\n                -Number.MAX_VALUE,\n                intMode,\n            );\n            testRoundtrip(\n                \"MIN_VALUE\",\n                Number.MIN_VALUE,\n                Number.MIN_VALUE,\n                intMode,\n            );\n        }\n    });\n\n    describe(\"bigint\", () => {\n        describe(\"'number' int mode\", () => {\n            testRoundtrip(\"zero integer\", 0n, 0, \"number\");\n            testRoundtrip(\"small integer\", -42n, -42, \"number\");\n            testRoundtrip(\n                \"largest safe integer\",\n                9007199254740991n,\n                9007199254740991,\n                \"number\",\n            );\n            testRoundtripError(\n                \"smallest unsafe integer\",\n                9007199254740992n,\n                RangeError,\n                \"number\",\n            );\n            testRoundtripError(\n                \"large unsafe integer\",\n                -1152921504594532842n,\n                RangeError,\n                \"number\",\n            );\n        });\n\n        describe(\"'bigint' int mode\", () => {\n            testRoundtrip(\"zero integer\", 0n, 0n, \"bigint\");\n            testRoundtrip(\"small integer\", -42n, -42n, \"bigint\");\n            testRoundtrip(\n                \"large positive integer\",\n                1152921504608088318n,\n                1152921504608088318n,\n                \"bigint\",\n            );\n            testRoundtrip(\n                \"large negative integer\",\n                -1152921504594532842n,\n                -1152921504594532842n,\n                \"bigint\",\n            );\n            testRoundtrip(\n                \"largest positive integer\",\n                9223372036854775807n,\n                9223372036854775807n,\n                \"bigint\",\n            );\n            testRoundtrip(\n                \"largest negative integer\",\n                -9223372036854775808n,\n                -9223372036854775808n,\n                \"bigint\",\n            );\n        });\n\n        describe(\"'string' int mode\", () => {\n            testRoundtrip(\"zero integer\", 0n, \"0\", \"string\");\n            testRoundtrip(\"small integer\", -42n, \"-42\", \"string\");\n            testRoundtrip(\n                \"large positive integer\",\n                1152921504608088318n,\n                \"1152921504608088318\",\n                \"string\",\n            );\n            testRoundtrip(\n                \"large negative integer\",\n                -1152921504594532842n,\n                \"-1152921504594532842\",\n                \"string\",\n            );\n            testRoundtrip(\n                \"largest positive integer\",\n                9223372036854775807n,\n                \"9223372036854775807\",\n                \"string\",\n            );\n            testRoundtrip(\n                \"largest negative integer\",\n                -9223372036854775808n,\n                \"-9223372036854775808\",\n                \"string\",\n            );\n        });\n    });\n\n    const buf = new ArrayBuffer(256);\n    const array = new Uint8Array(buf);\n    for (let i = 0; i < 256; ++i) {\n        array[i] = i ^ 0xab;\n    }\n    testRoundtrip(\"ArrayBuffer\", buf, buf);\n    testRoundtrip(\"Uint8Array\", array, buf);\n\n    testRoundtrip(\"null\", null, null);\n    testRoundtrip(\"true\", true, 1n, \"bigint\");\n    testRoundtrip(\"false\", false, 0n, \"bigint\");\n    testRoundtrip(\"true\", true, 1, \"number\");\n    testRoundtrip(\"false\", false, 0, \"number\");\n    testRoundtrip(\"true\", true, \"1\", \"string\");\n    testRoundtrip(\"false\", false, \"0\", \"string\");\n    testRoundtrip(\"true\", true, 1);\n    testRoundtrip(\"false\", false, 0);\n\n    testRoundtrip(\n        \"Date\",\n        new Date(\"2023-01-02T12:34:56Z\"),\n        1672662896000,\n        \"bigint\",\n    );\n\n    // @ts-expect-error\n    testRoundtripError(\"undefined produces error\", undefined, TypeError);\n    testRoundtripError(\"NaN produces error\", NaN, RangeError);\n    testRoundtripError(\"Infinity produces error\", Infinity, RangeError);\n    testRoundtripError(\n        \"large bigint produces error\",\n        -1267650600228229401496703205376n,\n        RangeError,\n    );\n\n    test(\n        \"max 64-bit bigint\",\n        withClient(async (c) => {\n            const rs = await c.execute({\n                sql: \"SELECT ?||''\",\n                args: [9223372036854775807n],\n            });\n            expect(rs.rows[0][0]).toStrictEqual(\"9223372036854775807\");\n        }),\n    );\n\n    test(\n        \"min 64-bit bigint\",\n        withClient(async (c) => {\n            const rs = await c.execute({\n                sql: \"SELECT ?||''\",\n                args: [-9223372036854775808n],\n            });\n            expect(rs.rows[0][0]).toStrictEqual(\"-9223372036854775808\");\n        }),\n    );\n});\n\ndescribe(\"ResultSet.toJSON()\", () => {\n    test(\n        \"simple result set\",\n        withClient(async (c) => {\n            const rs = await c.execute(\"SELECT 1 AS a\");\n            const json = rs.toJSON();\n            expect(\n                json[\"lastInsertRowid\"] === null ||\n                    json[\"lastInsertRowid\"] === \"0\",\n            ).toBe(true);\n            expect(json[\"columns\"]).toStrictEqual([\"a\"]);\n            expect(json[\"columnTypes\"]).toStrictEqual([\"\"]);\n            expect(json[\"rows\"]).toStrictEqual([[1]]);\n            expect(json[\"rowsAffected\"]).toStrictEqual(0);\n\n            const str = JSON.stringify(rs);\n            expect(\n                str ===\n                    '{\"columns\":[\"a\"],\"columnTypes\":[\"\"],\"rows\":[[1]],\"rowsAffected\":0,\"lastInsertRowid\":null}' ||\n                    str ===\n                        '{\"columns\":[\"a\"],\"columnTypes\":[\"\"],\"rows\":[[1]],\"rowsAffected\":0,\"lastInsertRowid\":\"0\"}',\n            ).toBe(true);\n        }),\n    );\n\n    test(\n        \"lastInsertRowid\",\n        withClient(async (c) => {\n            await c.execute(\"DROP TABLE IF EXISTS t\");\n            await c.execute(\"CREATE TABLE t (id INTEGER PRIMARY KEY NOT NULL)\");\n            const rs = await c.execute(\"INSERT INTO t VALUES (12345)\");\n            expect(rs.toJSON()).toStrictEqual({\n                columns: [],\n                columnTypes: [],\n                rows: [],\n                rowsAffected: 1,\n                lastInsertRowid: \"12345\",\n            });\n        }),\n    );\n\n    test(\n        \"computed values\",\n        withClient(async (c) => {\n            const rs = await c.execute(\n                \"SELECT 42 AS integer, 0.5 AS float, NULL AS \\\"null\\\", 'foo' AS text, X'626172' AS blob\",\n            );\n            const json = rs.toJSON();\n            expect(json[\"columns\"]).toStrictEqual([\n                \"integer\",\n                \"float\",\n                \"null\",\n                \"text\",\n                \"blob\",\n            ]);\n            expect(json[\"columnTypes\"]).toStrictEqual([\"\", \"\", \"\", \"\", \"\"]);\n            expect(json[\"rows\"]).toStrictEqual([\n                [42, 0.5, null, \"foo\", \"YmFy\"],\n            ]);\n        }),\n    );\n\n    (hasHrana2 ? test : test.skip)(\n        \"row values\",\n        withClient(async (c) => {\n            await c.execute(\"DROP TABLE IF EXISTS t\");\n            await c.execute(\n                \"CREATE TABLE t (i INTEGER, f FLOAT, t TEXT, b BLOB)\",\n            );\n            await c.execute(\"INSERT INTO t VALUES (42, 0.5, 'foo', X'626172')\");\n            const rs = await c.execute(\"SELECT i, f, t, b FROM t LIMIT 1\");\n            const json = rs.toJSON();\n            expect(json[\"columns\"]).toStrictEqual([\"i\", \"f\", \"t\", \"b\"]);\n            expect(json[\"columnTypes\"]).toStrictEqual([\n                \"INTEGER\",\n                \"FLOAT\",\n                \"TEXT\",\n                \"BLOB\",\n            ]);\n            expect(json[\"rows\"]).toStrictEqual([[42, 0.5, \"foo\", \"YmFy\"]]);\n        }),\n    );\n\n    test(\n        \"bigint row value\",\n        withClient(\n            async (c) => {\n                const rs = await c.execute(\"SELECT 42\");\n                const json = rs.toJSON();\n                expect(json[\"rows\"]).toStrictEqual([[\"42\"]]);\n            },\n            { intMode: \"bigint\" },\n        ),\n    );\n});\n\ndescribe(\"arguments\", () => {\n    test(\n        \"? arguments\",\n        withClient(async (c) => {\n            const rs = await c.execute({\n                sql: \"SELECT ?, ?\",\n                args: [\"one\", \"two\"],\n            });\n            expect(Array.from(rs.rows[0])).toStrictEqual([\"one\", \"two\"]);\n        }),\n    );\n\n    (!isFile ? test : test.skip)(\n        \"?NNN arguments\",\n        withClient(async (c) => {\n            const rs = await c.execute({\n                sql: \"SELECT ?2, ?3, ?1\",\n                args: [\"one\", \"two\", \"three\"],\n            });\n            expect(Array.from(rs.rows[0])).toStrictEqual([\n                \"two\",\n                \"three\",\n                \"one\",\n            ]);\n        }),\n    );\n\n    (!isFile ? test : test.skip)(\n        \"?NNN arguments with holes\",\n        withClient(async (c) => {\n            const rs = await c.execute({\n                sql: \"SELECT ?3, ?1\",\n                args: [\"one\", \"two\", \"three\"],\n            });\n            expect(Array.from(rs.rows[0])).toStrictEqual([\"three\", \"one\"]);\n        }),\n    );\n\n    (!isFile ? test : test.skip)(\n        \"?NNN and ? arguments\",\n        withClient(async (c) => {\n            const rs = await c.execute({\n                sql: \"SELECT ?2, ?, ?3\",\n                args: [\"one\", \"two\", \"three\"],\n            });\n            expect(Array.from(rs.rows[0])).toStrictEqual([\n                \"two\",\n                \"three\",\n                \"three\",\n            ]);\n        }),\n    );\n\n    for (const sign of [\":\", \"@\", \"$\"]) {\n        test(\n            `${sign}AAAA arguments`,\n            withClient(async (c) => {\n                const rs = await c.execute({\n                    sql: `SELECT ${sign}b, ${sign}a`,\n                    args: { a: \"one\", [`${sign}b`]: \"two\" },\n                });\n                expect(Array.from(rs.rows[0])).toStrictEqual([\"two\", \"one\"]);\n            }),\n        );\n\n        test(\n            `${sign}AAAA arguments used multiple times`,\n            withClient(async (c) => {\n                const rs = await c.execute({\n                    sql: `SELECT ${sign}b, ${sign}a, ${sign}b || ${sign}a`,\n                    args: { a: \"one\", [`${sign}b`]: \"two\" },\n                });\n                expect(Array.from(rs.rows[0])).toStrictEqual([\n                    \"two\",\n                    \"one\",\n                    \"twoone\",\n                ]);\n            }),\n        );\n\n        test(\n            `${sign}AAAA arguments and ?NNN arguments`,\n            withClient(async (c) => {\n                const rs = await c.execute({\n                    sql: `SELECT ${sign}b, ${sign}a, ?1`,\n                    args: { a: \"one\", [`${sign}b`]: \"two\" },\n                });\n                expect(Array.from(rs.rows[0])).toStrictEqual([\n                    \"two\",\n                    \"one\",\n                    \"two\",\n                ]);\n            }),\n        );\n    }\n});\n\ndescribe(\"batch()\", () => {\n    test(\n        \"multiple queries\",\n        withClient(async (c) => {\n            const rss = await c.batch(\n                [\n                    \"SELECT 1+1\",\n                    \"SELECT 1 AS one, 2 AS two\",\n                    { sql: \"SELECT ?\", args: [\"boomerang\"] },\n                    { sql: \"VALUES (?), (?)\", args: [\"big\", \"ben\"] },\n                ],\n                \"read\",\n            );\n\n            expect(rss.length).toStrictEqual(4);\n            const [rs0, rs1, rs2, rs3] = rss;\n\n            expect(rs0.rows.length).toStrictEqual(1);\n            expect(Array.from(rs0.rows[0])).toStrictEqual([2]);\n\n            expect(rs1.rows.length).toStrictEqual(1);\n            expect(Array.from(rs1.rows[0])).toStrictEqual([1, 2]);\n\n            expect(rs2.rows.length).toStrictEqual(1);\n            expect(Array.from(rs2.rows[0])).toStrictEqual([\"boomerang\"]);\n\n            expect(rs3.rows.length).toStrictEqual(2);\n            expect(Array.from(rs3.rows[0])).toStrictEqual([\"big\"]);\n            expect(Array.from(rs3.rows[1])).toStrictEqual([\"ben\"]);\n        }),\n    );\n\n    test(\n        \"statements are executed sequentially\",\n        withClient(async (c) => {\n            const rss = await c.batch(\n                [\n                    /* 0 */ \"DROP TABLE IF EXISTS t\",\n                    /* 1 */ \"CREATE TABLE t (a, b)\",\n                    /* 2 */ \"INSERT INTO t VALUES (1, 'one')\",\n                    /* 3 */ \"SELECT * FROM t ORDER BY a\",\n                    /* 4 */ \"INSERT INTO t VALUES (2, 'two')\",\n                    /* 5 */ \"SELECT * FROM t ORDER BY a\",\n                    /* 6 */ \"DROP TABLE t\",\n                ],\n                \"write\",\n            );\n\n            expect(rss.length).toStrictEqual(7);\n            expect(rss[3].rows).toEqual([{ a: 1, b: \"one\" }]);\n            expect(rss[5].rows).toEqual([\n                { a: 1, b: \"one\" },\n                { a: 2, b: \"two\" },\n            ]);\n        }),\n    );\n\n    test(\n        \"statements are executed in a transaction\",\n        withClient(async (c) => {\n            await c.batch(\n                [\n                    \"DROP TABLE IF EXISTS t1\",\n                    \"DROP TABLE IF EXISTS t2\",\n                    \"CREATE TABLE t1 (a)\",\n                    \"CREATE TABLE t2 (a)\",\n                ],\n                \"write\",\n            );\n\n            const n = 100;\n            const promises = [] as Array<any>;\n            for (let i = 0; i < n; ++i) {\n                const ii = i;\n                promises.push(\n                    (async () => {\n                        const rss = await c.batch(\n                            [\n                                {\n                                    sql: \"INSERT INTO t1 VALUES (?)\",\n                                    args: [ii],\n                                },\n                                {\n                                    sql: \"INSERT INTO t2 VALUES (?)\",\n                                    args: [ii * 10],\n                                },\n                                \"SELECT SUM(a) FROM t1\",\n                                \"SELECT SUM(a) FROM t2\",\n                            ],\n                            \"write\",\n                        );\n\n                        const sum1 = rss[2].rows[0][0] as number;\n                        const sum2 = rss[3].rows[0][0] as number;\n                        expect(sum2).toStrictEqual(sum1 * 10);\n                    })(),\n                );\n            }\n            await Promise.all(promises);\n\n            const rs1 = await c.execute(\"SELECT SUM(a) FROM t1\");\n            expect(rs1.rows[0][0]).toStrictEqual((n * (n - 1)) / 2);\n            const rs2 = await c.execute(\"SELECT SUM(a) FROM t2\");\n            expect(rs2.rows[0][0]).toStrictEqual(((n * (n - 1)) / 2) * 10);\n        }),\n        10000,\n    );\n\n    test(\n        \"error in batch\",\n        withClient(async (c) => {\n            await expect(\n                c.batch([\"SELECT 1+1\", \"SELECT foobar\"], \"read\"),\n            ).rejects.toBeLibsqlError();\n        }),\n    );\n\n    test(\n        \"error in batch rolls back transaction\",\n        withClient(async (c) => {\n            await c.execute(\"DROP TABLE IF EXISTS t\");\n            await c.execute(\"CREATE TABLE t (a)\");\n            await c.execute(\"INSERT INTO t VALUES ('one')\");\n            await expect(\n                c.batch(\n                    [\n                        \"INSERT INTO t VALUES ('two')\",\n                        \"SELECT foobar\",\n                        \"INSERT INTO t VALUES ('three')\",\n                    ],\n                    \"write\",\n                ),\n            ).rejects.toBeLibsqlError();\n\n            const rs = await c.execute(\"SELECT COUNT(*) FROM t\");\n            expect(rs.rows[0][0]).toStrictEqual(1);\n        }),\n    );\n\n    test(\n        \"batch error reports statement index - error at index 0\",\n        withClient(async (c) => {\n            try {\n                await c.batch(\n                    [\"SELECT invalid_column\", \"SELECT 1\", \"SELECT 2\"],\n                    \"read\",\n                );\n                throw new Error(\"Expected batch to fail\");\n            } catch (e: any) {\n                expect(e.name).toBe(\"LibsqlBatchError\");\n                expect(e.statementIndex).toBe(0);\n                expect(e.code).toBeDefined();\n            }\n        }),\n    );\n\n    test(\n        \"batch error reports statement index - error at index 1\",\n        withClient(async (c) => {\n            try {\n                await c.batch(\n                    [\"SELECT 1\", \"SELECT invalid_column\", \"SELECT 2\"],\n                    \"read\",\n                );\n                throw new Error(\"Expected batch to fail\");\n            } catch (e: any) {\n                expect(e.name).toBe(\"LibsqlBatchError\");\n                expect(e.statementIndex).toBe(1);\n                expect(e.code).toBeDefined();\n            }\n        }),\n    );\n\n    test(\n        \"batch error reports statement index - error at index 2\",\n        withClient(async (c) => {\n            try {\n                await c.batch(\n                    [\"SELECT 1\", \"SELECT 2\", \"SELECT invalid_column\"],\n                    \"read\",\n                );\n                throw new Error(\"Expected batch to fail\");\n            } catch (e: any) {\n                expect(e.name).toBe(\"LibsqlBatchError\");\n                expect(e.statementIndex).toBe(2);\n                expect(e.code).toBeDefined();\n            }\n        }),\n    );\n\n    test(\n        \"batch error with write mode reports statement index\",\n        withClient(async (c) => {\n            await c.execute(\"DROP TABLE IF EXISTS t\");\n            await c.execute(\"CREATE TABLE t (a UNIQUE)\");\n            await c.execute(\"INSERT INTO t VALUES (1)\");\n\n            try {\n                await c.batch(\n                    [\n                        \"INSERT INTO t VALUES (2)\",\n                        \"INSERT INTO t VALUES (3)\",\n                        \"INSERT INTO t VALUES (1)\", // Duplicate, will fail\n                        \"INSERT INTO t VALUES (4)\",\n                    ],\n                    \"write\",\n                );\n                throw new Error(\"Expected batch to fail\");\n            } catch (e: any) {\n                expect(e.name).toBe(\"LibsqlBatchError\");\n                expect(e.statementIndex).toBe(2);\n                expect(e.code).toBeDefined();\n            }\n\n            // Verify rollback happened\n            const rs = await c.execute(\"SELECT COUNT(*) FROM t\");\n            expect(rs.rows[0][0]).toBe(1);\n        }),\n    );\n\n    test(\n        \"batch error in in-memory database reports statement index\",\n        withInMemoryClient(async (c) => {\n            await c.execute(\"CREATE TABLE t (a)\");\n\n            try {\n                await c.batch(\n                    [\n                        \"INSERT INTO t VALUES (1)\",\n                        \"SELECT invalid_column FROM t\",\n                        \"INSERT INTO t VALUES (2)\",\n                    ],\n                    \"write\",\n                );\n                throw new Error(\"Expected batch to fail\");\n            } catch (e: any) {\n                expect(e.name).toBe(\"LibsqlBatchError\");\n                expect(e.statementIndex).toBe(1);\n                expect(e.code).toBeDefined();\n            }\n        }),\n    );\n\n    test(\n        \"batch with a lot of different statements\",\n        withClient(async (c) => {\n            const stmts = [] as Array<any>;\n            for (let i = 0; i < 1000; ++i) {\n                stmts.push(`SELECT ${i}`);\n            }\n            const rss = await c.batch(stmts, \"read\");\n            for (let i = 0; i < stmts.length; ++i) {\n                expect(rss[i].rows[0][0]).toStrictEqual(i);\n            }\n        }),\n    );\n\n    test(\n        \"batch with a lot of the same statements\",\n        withClient(async (c) => {\n            const n = 20;\n            const m = 200;\n\n            const stmts = [] as Array<any>;\n            for (let i = 0; i < n; ++i) {\n                for (let j = 0; j < m; ++j) {\n                    stmts.push({ sql: `SELECT ?, ${j}`, args: [i] });\n                }\n            }\n\n            const rss = await c.batch(stmts, \"read\");\n            for (let i = 0; i < n; ++i) {\n                for (let j = 0; j < m; ++j) {\n                    const rs = rss[i * m + j];\n                    expect(rs.rows[0][0]).toStrictEqual(i);\n                    expect(rs.rows[0][1]).toStrictEqual(j);\n                }\n            }\n        }),\n    );\n\n    test(\n        \"deferred batch\",\n        withClient(async (c) => {\n            const rss = await c.batch(\n                [\n                    \"SELECT 1+1\",\n                    \"DROP TABLE IF EXISTS t\",\n                    \"CREATE TABLE t (a)\",\n                    \"INSERT INTO t VALUES (21) RETURNING 2*a\",\n                ],\n                \"deferred\",\n            );\n\n            expect(rss.length).toStrictEqual(4);\n            const [rs0, _rs1, _rs2, rs3] = rss;\n\n            expect(rs0.rows.length).toStrictEqual(1);\n            expect(Array.from(rs0.rows[0])).toStrictEqual([2]);\n\n            expect(rs3.rows.length).toStrictEqual(1);\n            expect(Array.from(rs3.rows[0])).toStrictEqual([42]);\n        }),\n    );\n\n    (hasHrana3 ? test : test.skip)(\n        \"ROLLBACK statement stops execution of batch\",\n        withClient(async (c) => {\n            await c.execute(\"DROP TABLE IF EXISTS t\");\n            await c.execute(\"CREATE TABLE t (a)\");\n\n            await expect(\n                c.batch(\n                    [\n                        \"INSERT INTO t VALUES (1), (2), (3)\",\n                        \"ROLLBACK\",\n                        \"INSERT INTO t VALUES (4), (5)\",\n                    ],\n                    \"write\",\n                ),\n            ).rejects.toBeLibsqlError(\"TRANSACTION_CLOSED\");\n\n            const rs = await c.execute(\"SELECT COUNT(*) FROM t\");\n            expect(rs.rows[0][0]).toStrictEqual(0);\n        }),\n    );\n});\n\ndescribe(\"transaction()\", () => {\n    test(\n        \"query multiple rows\",\n        withClient(async (c) => {\n            const txn = await c.transaction(\"read\");\n\n            const rs = await txn.execute(\n                \"VALUES (1, 'one'), (2, 'two'), (3, 'three')\",\n            );\n            expect(rs.columns.length).toStrictEqual(2);\n            expect(rs.columnTypes.length).toStrictEqual(2);\n            expect(rs.rows.length).toStrictEqual(3);\n\n            expect(Array.from(rs.rows[0])).toStrictEqual([1, \"one\"]);\n            expect(Array.from(rs.rows[1])).toStrictEqual([2, \"two\"]);\n            expect(Array.from(rs.rows[2])).toStrictEqual([3, \"three\"]);\n\n            txn.close();\n        }),\n    );\n\n    test(\n        \"commit()\",\n        withClient(async (c) => {\n            await c.batch(\n                [\"DROP TABLE IF EXISTS t\", \"CREATE TABLE t (a)\"],\n                \"write\",\n            );\n\n            const txn = await c.transaction(\"write\");\n            await txn.execute(\"INSERT INTO t VALUES ('one')\");\n            await txn.execute(\"INSERT INTO t VALUES ('two')\");\n            expect(txn.closed).toStrictEqual(false);\n            await txn.commit();\n            expect(txn.closed).toStrictEqual(true);\n\n            const rs = await c.execute(\"SELECT COUNT(*) FROM t\");\n            expect(rs.rows[0][0]).toStrictEqual(2);\n            await expect(txn.execute(\"SELECT 1\")).rejects.toBeLibsqlError(\n                \"TRANSACTION_CLOSED\",\n            );\n        }),\n    );\n\n    test(\n        \"rollback()\",\n        withClient(async (c) => {\n            await c.batch(\n                [\"DROP TABLE IF EXISTS t\", \"CREATE TABLE t (a)\"],\n                \"write\",\n            );\n\n            const txn = await c.transaction(\"write\");\n            await txn.execute(\"INSERT INTO t VALUES ('one')\");\n            await txn.execute(\"INSERT INTO t VALUES ('two')\");\n            expect(txn.closed).toStrictEqual(false);\n            await txn.rollback();\n            expect(txn.closed).toStrictEqual(true);\n\n            const rs = await c.execute(\"SELECT COUNT(*) FROM t\");\n            expect(rs.rows[0][0]).toStrictEqual(0);\n            await expect(txn.execute(\"SELECT 1\")).rejects.toBeLibsqlError(\n                \"TRANSACTION_CLOSED\",\n            );\n        }),\n    );\n\n    test(\n        \"close()\",\n        withClient(async (c) => {\n            await c.batch(\n                [\"DROP TABLE IF EXISTS t\", \"CREATE TABLE t (a)\"],\n                \"write\",\n            );\n\n            const txn = await c.transaction(\"write\");\n            await txn.execute(\"INSERT INTO t VALUES ('one')\");\n            expect(txn.closed).toStrictEqual(false);\n            txn.close();\n            expect(txn.closed).toStrictEqual(true);\n\n            const rs = await c.execute(\"SELECT COUNT(*) FROM t\");\n            expect(rs.rows[0][0]).toStrictEqual(0);\n            await expect(txn.execute(\"SELECT 1\")).rejects.toBeLibsqlError(\n                \"TRANSACTION_CLOSED\",\n            );\n        }),\n    );\n\n    test(\n        \"error does not rollback\",\n        withClient(async (c) => {\n            await c.batch(\n                [\"DROP TABLE IF EXISTS t\", \"CREATE TABLE t (a)\"],\n                \"write\",\n            );\n\n            const txn = await c.transaction(\"write\");\n            await expect(txn.execute(\"SELECT foo\")).rejects.toBeLibsqlError();\n            await txn.execute(\"INSERT INTO t VALUES ('one')\");\n            await expect(txn.execute(\"SELECT bar\")).rejects.toBeLibsqlError();\n            await txn.commit();\n\n            const rs = await c.execute(\"SELECT COUNT(*) FROM t\");\n            expect(rs.rows[0][0]).toStrictEqual(1);\n        }),\n    );\n\n    (hasHrana3 ? test : test.skip)(\n        \"ROLLBACK statement stops execution of transaction\",\n        withClient(async (c) => {\n            await c.execute(\"DROP TABLE IF EXISTS t\");\n            await c.execute(\"CREATE TABLE t (a)\");\n\n            const txn = await c.transaction(\"write\");\n            const prom1 = txn.execute(\"INSERT INTO t VALUES (1), (2), (3)\");\n            const prom2 = txn.execute(\"ROLLBACK\");\n            const prom3 = txn.execute(\"INSERT INTO t VALUES (4), (5)\");\n\n            await prom1;\n            await prom2;\n            await expect(prom3).rejects.toBeLibsqlError(\"TRANSACTION_CLOSED\");\n            await expect(txn.commit()).rejects.toBeLibsqlError();\n            txn.close();\n\n            const rs = await c.execute(\"SELECT COUNT(*) FROM t\");\n            expect(rs.rows[0][0]).toStrictEqual(0);\n        }),\n    );\n\n    (hasHrana3 ? test : test.skip)(\n        \"OR ROLLBACK statement stops execution of transaction\",\n        withClient(async (c) => {\n            await c.execute(\"DROP TABLE IF EXISTS t\");\n            await c.execute(\"CREATE TABLE t (a UNIQUE)\");\n\n            const txn = await c.transaction(\"write\");\n            const prom1 = txn.execute(\"INSERT INTO t VALUES (1), (2), (3)\");\n            const prom2 = txn.execute(\"INSERT OR ROLLBACK INTO t VALUES (1)\");\n            const prom3 = txn.execute(\"INSERT INTO t VALUES (4), (5)\");\n\n            await prom1;\n            await expect(prom2).rejects.toBeLibsqlError();\n            await expect(prom3).rejects.toBeLibsqlError(\"TRANSACTION_CLOSED\");\n            await expect(txn.commit()).rejects.toBeLibsqlError();\n            txn.close();\n\n            const rs = await c.execute(\"SELECT COUNT(*) FROM t\");\n            expect(rs.rows[0][0]).toStrictEqual(0);\n        }),\n    );\n\n    (hasHrana3 ? test : test.skip)(\n        \"OR ROLLBACK as the first statement stops execution of transaction\",\n        withClient(async (c) => {\n            await c.execute(\"DROP TABLE IF EXISTS t\");\n            await c.execute(\"CREATE TABLE t (a UNIQUE)\");\n            await c.execute(\"INSERT INTO t VALUES (1), (2), (3)\");\n\n            const txn = await c.transaction(\"write\");\n            const prom1 = txn.execute(\"INSERT OR ROLLBACK INTO t VALUES (1)\");\n            const prom2 = txn.execute(\"INSERT INTO t VALUES (4), (5)\");\n\n            await expect(prom1).rejects.toBeLibsqlError();\n            await expect(prom2).rejects.toBeLibsqlError(\"TRANSACTION_CLOSED\");\n            await expect(txn.commit()).rejects.toBeLibsqlError();\n            txn.close();\n\n            const rs = await c.execute(\"SELECT COUNT(*) FROM t\");\n            expect(rs.rows[0][0]).toStrictEqual(3);\n        }),\n    );\n\n    test(\n        \"commit empty\",\n        withClient(async (c) => {\n            const txn = await c.transaction(\"read\");\n            await txn.commit();\n        }),\n    );\n\n    test(\n        \"rollback empty\",\n        withClient(async (c) => {\n            const txn = await c.transaction(\"read\");\n            await txn.rollback();\n        }),\n    );\n\n    describe(\"batch()\", () => {\n        test(\n            \"as the first operation on transaction\",\n            withClient(async (c) => {\n                const txn = await c.transaction(\"write\");\n\n                await txn.batch([\n                    \"DROP TABLE IF EXISTS t\",\n                    \"CREATE TABLE t (a)\",\n                    { sql: \"INSERT INTO t VALUES (?)\", args: [1] },\n                    { sql: \"INSERT INTO t VALUES (?)\", args: [2] },\n                    { sql: \"INSERT INTO t VALUES (?)\", args: [4] },\n                ]);\n\n                const rs = await txn.execute(\"SELECT SUM(a) FROM t\");\n                expect(rs.rows[0][0]).toStrictEqual(7);\n                txn.close();\n            }),\n        );\n\n        test(\n            \"as the second operation on transaction\",\n            withClient(async (c) => {\n                const txn = await c.transaction(\"write\");\n\n                await txn.execute(\"DROP TABLE IF EXISTS t\");\n                await txn.batch([\n                    \"CREATE TABLE t (a)\",\n                    { sql: \"INSERT INTO t VALUES (?)\", args: [1] },\n                    { sql: \"INSERT INTO t VALUES (?)\", args: [2] },\n                    { sql: \"INSERT INTO t VALUES (?)\", args: [4] },\n                ]);\n\n                const rs = await txn.execute(\"SELECT SUM(a) FROM t\");\n                expect(rs.rows[0][0]).toStrictEqual(7);\n                txn.close();\n            }),\n        );\n\n        test(\n            \"after error, further statements are not executed\",\n            withClient(async (c) => {\n                const txn = await c.transaction(\"write\");\n\n                await expect(\n                    txn.batch([\n                        \"DROP TABLE IF EXISTS t\",\n                        \"CREATE TABLE t (a UNIQUE)\",\n                        \"INSERT INTO t VALUES (1), (2), (4)\",\n                        \"INSERT INTO t VALUES (1)\",\n                        \"INSERT INTO t VALUES (8), (16)\",\n                    ]),\n                ).rejects.toBeLibsqlError();\n\n                const rs = await txn.execute(\"SELECT SUM(a) FROM t\");\n                expect(rs.rows[0][0]).toStrictEqual(7);\n\n                await txn.commit();\n            }),\n        );\n\n        test(\n            \"batch error reports statement index in transaction\",\n            withClient(async (c) => {\n                const txn = await c.transaction(\"write\");\n\n                try {\n                    await txn.batch([\n                        \"DROP TABLE IF EXISTS t\",\n                        \"CREATE TABLE t (a UNIQUE)\",\n                        \"INSERT INTO t VALUES (1), (2), (3)\",\n                        \"INSERT INTO t VALUES (1)\", // Duplicate, will fail at index 3\n                        \"INSERT INTO t VALUES (4), (5)\",\n                    ]);\n                    throw new Error(\"Expected batch to fail\");\n                } catch (e: any) {\n                    expect(e.name).toBe(\"LibsqlBatchError\");\n                    expect(e.statementIndex).toBe(3);\n                    expect(e.code).toBeDefined();\n                }\n\n                // Transaction should still be usable after batch error\n                const rs = await txn.execute(\"SELECT SUM(a) FROM t\");\n                expect(rs.rows[0][0]).toBe(6);\n\n                await txn.commit();\n            }),\n        );\n\n        test(\n            \"batch error reports statement index - error at first statement in transaction\",\n            withClient(async (c) => {\n                const txn = await c.transaction(\"read\");\n\n                try {\n                    await txn.batch([\n                        \"SELECT invalid_column\",\n                        \"SELECT 1\",\n                        \"SELECT 2\",\n                    ]);\n                    throw new Error(\"Expected batch to fail\");\n                } catch (e: any) {\n                    expect(e.name).toBe(\"LibsqlBatchError\");\n                    expect(e.statementIndex).toBe(0);\n                    expect(e.code).toBeDefined();\n                }\n\n                txn.close();\n            }),\n        );\n\n        test(\n            \"batch error reports statement index - error at middle statement in transaction\",\n            withClient(async (c) => {\n                const txn = await c.transaction(\"read\");\n\n                try {\n                    await txn.batch([\n                        \"SELECT 1\",\n                        \"SELECT 2\",\n                        \"SELECT invalid_column\",\n                        \"SELECT 3\",\n                    ]);\n                    throw new Error(\"Expected batch to fail\");\n                } catch (e: any) {\n                    expect(e.name).toBe(\"LibsqlBatchError\");\n                    expect(e.statementIndex).toBe(2);\n                    expect(e.code).toBeDefined();\n                }\n\n                txn.close();\n            }),\n        );\n    });\n\n    (hasHrana2 ? describe : describe.skip)(\"executeMultiple()\", () => {\n        test(\n            \"as the first operation on transaction\",\n            withClient(async (c) => {\n                const txn = await c.transaction(\"write\");\n\n                await txn.executeMultiple(`\n                DROP TABLE IF EXISTS t;\n                CREATE TABLE t (a);\n                INSERT INTO t VALUES (1), (2), (4), (8);\n            `);\n\n                const rs = await txn.execute(\"SELECT SUM(a) FROM t\");\n                expect(rs.rows[0][0]).toStrictEqual(15);\n                txn.close();\n            }),\n        );\n\n        test(\n            \"as the second operation on transaction\",\n            withClient(async (c) => {\n                const txn = await c.transaction(\"write\");\n                await txn.execute(\"DROP TABLE IF EXISTS t\");\n                await txn.executeMultiple(`\n                CREATE TABLE t (a);\n                INSERT INTO t VALUES (1), (2), (4), (8);\n            `);\n\n                const rs = await txn.execute(\"SELECT SUM(a) FROM t\");\n                expect(rs.rows[0][0]).toStrictEqual(15);\n                txn.close();\n            }),\n        );\n\n        test(\n            \"after error, further statements are not executed\",\n            withClient(async (c) => {\n                const txn = await c.transaction(\"write\");\n\n                await expect(\n                    txn.executeMultiple(`\n                DROP TABLE IF EXISTS t;\n                CREATE TABLE t (a UNIQUE);\n                INSERT INTO t VALUES (1), (2), (4);\n                INSERT INTO t VALUES (1);\n                INSERT INTO t VALUES (8), (16);\n            `),\n                ).rejects.toBeLibsqlError();\n\n                const rs = await txn.execute(\"SELECT SUM(a) FROM t\");\n                expect(rs.rows[0][0]).toStrictEqual(7);\n\n                await txn.commit();\n            }),\n        );\n    });\n});\n\n(hasHrana2 ? describe : describe.skip)(\"executeMultiple()\", () => {\n    test(\n        \"multiple statements\",\n        withClient(async (c) => {\n            await c.executeMultiple(`\n            DROP TABLE IF EXISTS t;\n            CREATE TABLE t (a);\n            INSERT INTO t VALUES (1), (2), (4), (8);\n        `);\n\n            const rs = await c.execute(\"SELECT SUM(a) FROM t\");\n            expect(rs.rows[0][0]).toStrictEqual(15);\n        }),\n    );\n\n    test(\n        \"after an error, statements are not executed\",\n        withClient(async (c) => {\n            await expect(\n                c.executeMultiple(`\n            DROP TABLE IF EXISTS t;\n            CREATE TABLE t (a);\n            INSERT INTO t VALUES (1), (2), (4);\n            INSERT INTO t VALUES (foo());\n            INSERT INTO t VALUES (8), (16);\n        `),\n            ).rejects.toBeLibsqlError();\n\n            const rs = await c.execute(\"SELECT SUM(a) FROM t\");\n            expect(rs.rows[0][0]).toStrictEqual(7);\n        }),\n    );\n\n    test(\n        \"manual transaction control statements\",\n        withClient(async (c) => {\n            await c.executeMultiple(`\n            DROP TABLE IF EXISTS t;\n            CREATE TABLE t (a);\n            BEGIN;\n            INSERT INTO t VALUES (1), (2), (4);\n            INSERT INTO t VALUES (8), (16);\n            COMMIT;\n        `);\n\n            const rs = await c.execute(\"SELECT SUM(a) FROM t\");\n            expect(rs.rows[0][0]).toStrictEqual(31);\n        }),\n    );\n\n    test(\n        \"error rolls back a manual transaction\",\n        withClient(async (c) => {\n            await expect(\n                c.executeMultiple(`\n            DROP TABLE IF EXISTS t;\n            CREATE TABLE t (a);\n            INSERT INTO t VALUES (0);\n            BEGIN;\n            INSERT INTO t VALUES (1), (2), (4);\n            INSERT INTO t VALUES (foo());\n            INSERT INTO t VALUES (8), (16);\n            COMMIT;\n        `),\n            ).rejects.toBeLibsqlError();\n\n            const rs = await c.execute(\"SELECT SUM(a) FROM t\");\n            expect(rs.rows[0][0]).toStrictEqual(0);\n        }),\n    );\n});\n\n(hasNetworkErrors ? describe : describe.skip)(\"network errors\", () => {\n    const testCases = [\n        { title: \"WebSocket close\", sql: \".close_ws\" },\n        { title: \"TCP close\", sql: \".close_tcp\" },\n    ];\n\n    for (const { title, sql } of testCases) {\n        test(\n            `${title} in execute()`,\n            withClient(async (c) => {\n                await expect(c.execute(sql)).rejects.toBeLibsqlError(\n                    \"HRANA_WEBSOCKET_ERROR\",\n                );\n\n                expect((await c.execute(\"SELECT 42\")).rows[0][0]).toStrictEqual(\n                    42,\n                );\n            }),\n        );\n\n        test(\n            `${title} in transaction()`,\n            withClient(async (c) => {\n                const txn = await c.transaction(\"read\");\n                await expect(txn.execute(sql)).rejects.toBeLibsqlError(\n                    \"HRANA_WEBSOCKET_ERROR\",\n                );\n                await expect(txn.commit()).rejects.toBeLibsqlError(\n                    \"TRANSACTION_CLOSED\",\n                );\n                txn.close();\n\n                expect((await c.execute(\"SELECT 42\")).rows[0][0]).toStrictEqual(\n                    42,\n                );\n            }),\n        );\n\n        test(\n            `${title} in batch()`,\n            withClient(async (c) => {\n                await expect(\n                    c.batch([\"SELECT 42\", sql, \"SELECT 24\"], \"read\"),\n                ).rejects.toBeLibsqlError(\"HRANA_WEBSOCKET_ERROR\");\n\n                expect((await c.execute(\"SELECT 42\")).rows[0][0]).toStrictEqual(\n                    42,\n                );\n            }),\n        );\n    }\n});\n\n(isHttp ? test : test.skip)(\"custom fetch\", async () => {\n    let fetchCalledCount = 0;\n    function customFetch(request: Request): Promise<Response> {\n        fetchCalledCount += 1;\n        return fetch(request);\n    }\n\n    const c = createClient({ ...config, fetch: customFetch });\n    try {\n        const rs = await c.execute(\"SELECT 42\");\n        expect(rs.rows[0][0]).toStrictEqual(42);\n        expect(fetchCalledCount).toBeGreaterThan(0);\n    } finally {\n        c.close();\n    }\n});\n\n(isFile ? test : test.skip)(\"raw error codes\", async () => {\n    const c = createClient(config);\n    try {\n        await expect(c.execute(\"NOT A VALID SQL\")).rejects.toThrow(\n            expect.toBeLibsqlError({ code: \"SQLITE_ERROR\", rawCode: 1 }),\n        );\n    } finally {\n        c.close();\n    }\n});\n\n// Test to verify constraint error codes\n// - code: base error code (e.g., SQLITE_CONSTRAINT) - consistent across local and remote\n// - extendedCode: extended error code (e.g., SQLITE_CONSTRAINT_PRIMARYKEY) - available when supported\n(server !== \"test_v1\" ? describe : describe.skip)(\n    \"constraint error codes\",\n    () => {\n        test(\n            \"PRIMARY KEY constraint violation\",\n            withClient(async (c) => {\n                await c.execute(\"DROP TABLE IF EXISTS t_pk_test\");\n                await c.execute(\n                    \"CREATE TABLE t_pk_test (id INTEGER PRIMARY KEY, name TEXT)\",\n                );\n                await c.execute(\"INSERT INTO t_pk_test VALUES (1, 'first')\");\n\n                try {\n                    await c.execute(\n                        \"INSERT INTO t_pk_test VALUES (1, 'duplicate')\",\n                    );\n                    throw new Error(\"Expected PRIMARY KEY constraint error\");\n                } catch (e: any) {\n                    expect(e.code).toBe(\"SQLITE_CONSTRAINT\");\n                    if (e.extendedCode !== undefined) {\n                        expect(e.extendedCode).toBe(\n                            \"SQLITE_CONSTRAINT_PRIMARYKEY\",\n                        );\n                    }\n                }\n            }),\n        );\n\n        test(\n            \"UNIQUE constraint violation\",\n            withClient(async (c) => {\n                await c.execute(\"DROP TABLE IF EXISTS t_unique_test\");\n                await c.execute(\n                    \"CREATE TABLE t_unique_test (id INTEGER, name TEXT UNIQUE)\",\n                );\n                await c.execute(\n                    \"INSERT INTO t_unique_test VALUES (1, 'unique_name')\",\n                );\n\n                try {\n                    await c.execute(\n                        \"INSERT INTO t_unique_test VALUES (2, 'unique_name')\",\n                    );\n                    throw new Error(\"Expected UNIQUE constraint error\");\n                } catch (e: any) {\n                    expect(e.code).toBe(\"SQLITE_CONSTRAINT\");\n                    if (e.extendedCode !== undefined) {\n                        expect(e.extendedCode).toBe(\"SQLITE_CONSTRAINT_UNIQUE\");\n                    }\n                }\n            }),\n        );\n    },\n);\n\n(isSqld ? test : test.skip)(\"embedded replica test\", async () => {\n    const remote = createClient(config);\n    const embedded = createClient({\n        ...config,\n        url: \"file:///tmp/local.db\",\n        syncUrl: config.url,\n    });\n    await remote.execute(\"CREATE TABLE embedded(a)\");\n    await embedded.sync();\n\n    let embedded1 = await embedded.execute(\"SELECT * FROM embedded\");\n    expect(embedded1.columns).toStrictEqual([\"a\"]);\n    expect(embedded1.rows.length).toStrictEqual(0);\n\n    await remote.execute(\"INSERT INTO embedded VALUES (1), (2), (3)\");\n    let embedded2 = await embedded.execute(\"SELECT * FROM embedded\");\n    expect(embedded2.columns).toStrictEqual([\"a\"]);\n    expect(embedded2.rows.length).toStrictEqual(0);\n\n    let remote1 = await remote.execute(\"SELECT * FROM embedded\");\n    expect(remote1.columns).toStrictEqual([\"a\"]);\n    expect(remote1.rows.length).toStrictEqual(3);\n\n    await embedded.sync();\n    let embedded3 = await embedded.execute(\"SELECT * FROM embedded\");\n    expect(embedded3.columns).toStrictEqual([\"a\"]);\n    expect(embedded3.rows.length).toStrictEqual(3);\n});\n"
  },
  {
    "path": "packages/libsql-client/src/__tests__/config.test.ts",
    "content": "import { expect } from \"@jest/globals\";\n\nimport \"./helpers.js\";\n\nimport { expandConfig } from \"@libsql/core/config\";\nimport { IntMode } from \"@libsql/hrana-client\";\n\ndescribe(\"expandConfig - default tls values\", () => {\n    const cases = [\n        {\n            name: \"file\",\n            preferHttp: true,\n            config: { url: \"file://local.db\" },\n            tls: true,\n        },\n        {\n            name: \"http\",\n            preferHttp: true,\n            config: { url: \"http://localhost\" },\n            tls: false,\n        },\n        {\n            name: \"http (tls in config)\",\n            preferHttp: true,\n            config: { url: \"http://localhost\", tls: true },\n            tls: true,\n        },\n        {\n            name: \"http (tls in query)\",\n            preferHttp: true,\n            config: { url: \"http://localhost?tls=1\", tls: false },\n            tls: true,\n        },\n        {\n            name: \"http (no tls in query)\",\n            preferHttp: true,\n            config: { url: \"http://localhost?tls=0\", tls: true },\n            tls: false,\n        },\n        {\n            name: \"http (no tls in query)\",\n            preferHttp: true,\n            config: { url: \"http://localhost?tls=0\", tls: true },\n            tls: false,\n        },\n    ];\n    for (const { name, config, preferHttp, tls } of cases) {\n        test(name, () => {\n            expect(expandConfig(config, preferHttp).tls).toEqual(tls);\n        });\n    }\n});\n\ndescribe(\"expandConfig - invalid arguments\", () => {\n    const cases = [\n        {\n            name: \"in-memory with unsupported query params\",\n            config: { url: \"file::memory:?mode=memory\" },\n            error: 'Unsupported URL query parameter \"mode\"',\n        },\n        {\n            name: \"in-memory with tls param\",\n            config: { url: \"file::memory:?tls=0\" },\n            error: 'Unsupported URL query parameter \"tls\"',\n        },\n        {\n            name: \"in-memory with authToken param\",\n            config: { url: \"file::memory:?authToken=0\" },\n            error: 'Unsupported URL query parameter \"authToken\"',\n        },\n        {\n            name: \"invalid tls param value\",\n            config: { url: \"libsql://localhost?tls=2\" },\n            error: 'Unknown value for the \"tls\" query argument: \"2\". Supported values are: [\"0\", \"1\"]',\n        },\n        {\n            name: \"invalid scheme\",\n            config: { url: \"ftp://localhost\" },\n            error: /The client supports only.*got \"ftp:\"/g,\n        },\n        {\n            name: \"invalid intMode\",\n            config: { url: \"file://localhost\", intMode: \"decimal\" as IntMode },\n            error: /Invalid value for intMode.*got \"decimal\"/g,\n        },\n        {\n            name: \"fragment in uri\",\n            config: { url: \"file://localhost#fragment\" },\n            error: \"URL fragments are not supported\",\n        },\n        {\n            name: \"libsql, no tls, no port\",\n            config: { url: \"libsql://localhost?tls=0\" },\n            error: \"must specify an explicit port\",\n        },\n    ];\n    for (const { name, config, error } of cases) {\n        test(name, () => {\n            try {\n                expandConfig(config, false);\n                throw new Error(\"expand command must fail\");\n            } catch (e: any) {\n                expect(e.message).toMatch(error);\n            }\n        });\n    }\n});\n\ndescribe(\"expandConfig - parsing of valid arguments\", () => {\n    const cases = [\n        {\n            name: \"in-memory\",\n            config: { url: \":memory:\" },\n            expanded: {\n                scheme: \"file\",\n                tls: false,\n                intMode: \"number\",\n                path: \":memory:\",\n                concurrency: 20,\n            },\n        },\n        {\n            name: \"in-memory with params\",\n            config: { url: \"file::memory:?cache=shared\" },\n            expanded: {\n                scheme: \"file\",\n                tls: false,\n                intMode: \"number\",\n                path: \":memory:?cache=shared\",\n                concurrency: 20,\n            },\n        },\n        {\n            name: \"simple local file\",\n            config: { url: \"file://local.db\" },\n            expanded: {\n                scheme: \"file\",\n                authority: { host: \"local.db\" },\n                tls: true,\n                intMode: \"number\",\n                path: \"\",\n                concurrency: 20,\n            },\n        },\n        {\n            name: \"wss with path & port\",\n            config: { url: \"wss://localhost:8888/libsql/connect\" },\n            expanded: {\n                scheme: \"wss\",\n                authority: { host: \"localhost\", port: 8888 },\n                tls: true,\n                intMode: \"number\",\n                path: \"/libsql/connect\",\n                concurrency: 20,\n            },\n        },\n        {\n            name: \"wss with user info\",\n            config: {\n                url: \"wss://user:password@localhost:8888/libsql/connect\",\n                concurrency: 20,\n            },\n            expanded: {\n                scheme: \"wss\",\n                authority: {\n                    host: \"localhost\",\n                    port: 8888,\n                    userinfo: { username: \"user\", password: \"password\" },\n                },\n                tls: true,\n                intMode: \"number\",\n                path: \"/libsql/connect\",\n                concurrency: 20,\n            },\n        },\n        {\n            name: \"override tls=0\",\n            config: { url: \"wss://localhost/libsql/connect?tls=0\", tls: true },\n            expanded: {\n                scheme: \"wss\",\n                authority: { host: \"localhost\" },\n                tls: false,\n                intMode: \"number\",\n                path: \"/libsql/connect\",\n                concurrency: 20,\n            },\n        },\n        {\n            name: \"override tls=1\",\n            config: { url: \"wss://localhost/libsql/connect?tls=1\", tls: false },\n            expanded: {\n                scheme: \"wss\",\n                authority: { host: \"localhost\" },\n                tls: true,\n                intMode: \"number\",\n                path: \"/libsql/connect\",\n                concurrency: 20,\n            },\n        },\n        {\n            name: \"override auth token\",\n            config: {\n                url: \"wss://localhost/libsql/connect?authToken=new\",\n                authToken: \"old\",\n            },\n            expanded: {\n                authToken: \"new\",\n                scheme: \"wss\",\n                authority: { host: \"localhost\" },\n                tls: true,\n                intMode: \"number\",\n                path: \"/libsql/connect\",\n                concurrency: 20,\n            },\n        },\n    ];\n    for (const { name, config, expanded } of cases) {\n        test(name, () => {\n            expect(expandConfig(config, false)).toEqual(expanded);\n        });\n    }\n});\n"
  },
  {
    "path": "packages/libsql-client/src/__tests__/helpers.ts",
    "content": "import { expect } from \"@jest/globals\";\nimport type { MatcherFunction } from \"expect\";\n\nimport { LibsqlError } from \"../node.js\";\n\ntype CodeMatch = {\n    code: string;\n    rawCode: number;\n};\n\nconst toBeLibsqlError: MatcherFunction<\n    [code?: string | CodeMatch, message?: RegExp]\n> = function (actual, code?, messageRe?) {\n    const pass =\n        actual instanceof LibsqlError &&\n        isValidCode(actual, code) &&\n        (messageRe === undefined || actual.message.match(messageRe) !== null);\n\n    const message = (): string => {\n        const parts = [];\n        parts.push(\"expected \");\n        parts.push(this.utils.printReceived(actual));\n        parts.push(pass ? \" not to be \" : \" to be \");\n        parts.push(\"an instance of LibsqlError\");\n        if (code !== undefined) {\n            parts.push(\" with error code \");\n            parts.push(this.utils.printExpected(code));\n        }\n        if (messageRe !== undefined) {\n            parts.push(\" with error message matching \");\n            parts.push(this.utils.printExpected(messageRe));\n        }\n        return parts.join(\"\");\n    };\n\n    return { pass, message };\n};\n\nconst isValidCode = (error: LibsqlError, code?: string | CodeMatch) => {\n    if (code === undefined) {\n        return true;\n    }\n    if (typeof code === \"string\") {\n        return error.code === code;\n    }\n    return error.code === code.code && error.rawCode === code.rawCode;\n};\nexpect.extend({ toBeLibsqlError });\ndeclare module \"expect\" {\n    interface AsymmetricMatchers {\n        toBeLibsqlError(code?: string | CodeMatch, messageRe?: RegExp): void;\n    }\n    interface Matchers<R> {\n        toBeLibsqlError(code?: string | CodeMatch, messageRe?: RegExp): R;\n    }\n}\n"
  },
  {
    "path": "packages/libsql-client/src/__tests__/mocks/handlers.ts",
    "content": "import { http, HttpResponse } from \"msw\";\n\nexport const handlers = [\n    http.get(\"http://fake-base-url.example.com/v1/jobs\", () => {\n        return HttpResponse.json({\n            schema_version: 4,\n            migrations: [\n                { job_id: 4, status: \"WaitingDryRun\" },\n                { job_id: 3, status: \"RunSuccess\" },\n                { job_id: 2, status: \"RunSuccess\" },\n                { job_id: 1, status: \"RunSuccess\" },\n            ],\n        });\n    }),\n\n    http.get(\n        \"http://fake-base-url.example.com/v1/jobs/:job_id\",\n        ({ params }) => {\n            const { job_id } = params;\n\n            return HttpResponse.json({\n                job_id,\n                status: \"RunSuccess\",\n                progress: [\n                    {\n                        namespace: \"b2ab4a64-402c-4bdf-a1e8-27ef33518cbd\",\n                        status: \"RunSuccess\",\n                        error: null,\n                    },\n                ],\n            });\n        },\n    ),\n];\n"
  },
  {
    "path": "packages/libsql-client/src/__tests__/mocks/node.ts",
    "content": "import { setupServer } from \"msw/node\";\nimport { handlers } from \"./handlers\";\n\nexport const server = setupServer(...handlers);\n"
  },
  {
    "path": "packages/libsql-client/src/__tests__/uri.test.ts",
    "content": "import { expect } from \"@jest/globals\";\n\nimport \"./helpers.js\";\n\nimport { parseUri, encodeBaseUrl } from \"@libsql/core/uri\";\n\ndescribe(\"parseUri()\", () => {\n    test(\":memory: uri\", () => {\n        const cases = [\n            { text: \"file::memory:\", path: \":memory:\", query: undefined },\n            {\n                text: \"file::memory:?cache=shared\",\n                path: \":memory:\",\n                query: { pairs: [{ key: \"cache\", value: \"shared\" }] },\n            },\n        ];\n        for (const { text, path, query } of cases) {\n            expect(parseUri(text)).toEqual({ scheme: \"file\", path, query });\n        }\n    });\n    test(\"authority and path\", () => {\n        const cases = [\n            { text: \"file://localhost\", path: \"\" },\n            { text: \"file://localhost/\", path: \"/\" },\n            { text: \"file://localhost/absolute/path\", path: \"/absolute/path\" },\n            { text: \"file://localhost/k%C5%AF%C5%88\", path: \"/kůň\" },\n        ];\n        for (const { text, path } of cases) {\n            expect(parseUri(text)).toEqual({\n                scheme: \"file\",\n                authority: { host: \"localhost\" },\n                path,\n            });\n        }\n    });\n\n    test(\"empty authority and path\", () => {\n        const cases = [\n            { text: \"file:///absolute/path\", path: \"/absolute/path\" },\n            { text: \"file://\", path: \"\" },\n            { text: \"file:///k%C5%AF%C5%88\", path: \"/kůň\" },\n        ];\n        for (const { text, path } of cases) {\n            expect(parseUri(text)).toEqual({\n                scheme: \"file\",\n                authority: { host: \"\" },\n                path,\n            });\n        }\n    });\n\n    test(\"no authority and path\", () => {\n        const cases = [\n            { text: \"file:/absolute/path\", path: \"/absolute/path\" },\n            { text: \"file:relative/path\", path: \"relative/path\" },\n            { text: \"file:\", path: \"\" },\n            { text: \"file:C:/path/to/file\", path: \"C:/path/to/file\" },\n            { text: \"file:k%C5%AF%C5%88\", path: \"kůň\" },\n        ];\n        for (const { text, path } of cases) {\n            expect(parseUri(text)).toEqual({\n                scheme: \"file\",\n                path,\n            });\n        }\n    });\n\n    test(\"authority\", () => {\n        const hosts = [\n            { text: \"localhost\", host: \"localhost\" },\n            { text: \"domain.name\", host: \"domain.name\" },\n            { text: \"some$weird.%20!name\", host: \"some$weird. !name\" },\n            { text: \"1.20.255.99\", host: \"1.20.255.99\" },\n            { text: \"[2001:4860:4802:32::a]\", host: \"2001:4860:4802:32::a\" },\n            { text: \"%61\", host: \"a\" },\n            { text: \"100%2e100%2e100%2e100\", host: \"100.100.100.100\" },\n            { text: \"k%C5%AF%C5%88\", host: \"kůň\" },\n        ];\n        const ports = [\n            { text: \"\", port: undefined },\n            { text: \":\", port: undefined },\n            { text: \":0\", port: 0 },\n            { text: \":99\", port: 99 },\n            { text: \":65535\", port: 65535 },\n        ];\n        const userinfos = [\n            { text: \"\", userinfo: undefined },\n            { text: \"@\", userinfo: { username: \"\" } },\n            { text: \"alice@\", userinfo: { username: \"alice\" } },\n            {\n                text: \"alice:secret@\",\n                userinfo: { username: \"alice\", password: \"secret\" },\n            },\n            {\n                text: \"alice:sec:et@\",\n                userinfo: { username: \"alice\", password: \"sec:et\" },\n            },\n            { text: \"alice%3Asecret@\", userinfo: { username: \"alice:secret\" } },\n            {\n                text: \"alice:s%65cret@\",\n                userinfo: { username: \"alice\", password: \"secret\" },\n            },\n        ];\n\n        for (const { text: hostText, host } of hosts) {\n            for (const { text: portText, port } of ports) {\n                for (const { text: userText, userinfo } of userinfos) {\n                    const text = `http://${userText}${hostText}${portText}`;\n                    expect(parseUri(text)).toEqual({\n                        scheme: \"http\",\n                        authority: { host, port, userinfo },\n                        path: \"\",\n                    });\n                }\n            }\n        }\n    });\n\n    test(\"query\", () => {\n        const cases = [\n            { text: \"?\", pairs: [] },\n            { text: \"?key=value\", pairs: [{ key: \"key\", value: \"value\" }] },\n            { text: \"?&key=value\", pairs: [{ key: \"key\", value: \"value\" }] },\n            { text: \"?key=value&&\", pairs: [{ key: \"key\", value: \"value\" }] },\n            { text: \"?a\", pairs: [{ key: \"a\", value: \"\" }] },\n            { text: \"?a=\", pairs: [{ key: \"a\", value: \"\" }] },\n            { text: \"?=a\", pairs: [{ key: \"\", value: \"a\" }] },\n            { text: \"?=\", pairs: [{ key: \"\", value: \"\" }] },\n            { text: \"?a=b=c\", pairs: [{ key: \"a\", value: \"b=c\" }] },\n            {\n                text: \"?a=b&c=d\",\n                pairs: [\n                    { key: \"a\", value: \"b\" },\n                    { key: \"c\", value: \"d\" },\n                ],\n            },\n            { text: \"?a+b=c\", pairs: [{ key: \"a b\", value: \"c\" }] },\n            { text: \"?a=b+c\", pairs: [{ key: \"a\", value: \"b c\" }] },\n            { text: \"?a?b\", pairs: [{ key: \"a?b\", value: \"\" }] },\n            { text: \"?%61=%62\", pairs: [{ key: \"a\", value: \"b\" }] },\n            { text: \"?a%3db\", pairs: [{ key: \"a=b\", value: \"\" }] },\n            { text: \"?a=%2b\", pairs: [{ key: \"a\", value: \"+\" }] },\n            { text: \"?%2b=b\", pairs: [{ key: \"+\", value: \"b\" }] },\n            { text: \"?a=b%26c\", pairs: [{ key: \"a\", value: \"b&c\" }] },\n            { text: \"?a=k%C5%AF%C5%88\", pairs: [{ key: \"a\", value: \"kůň\" }] },\n        ];\n        for (const { text: queryText, pairs } of cases) {\n            const text = `file:${queryText}`;\n            expect(parseUri(text)).toEqual({\n                scheme: \"file\",\n                path: \"\",\n                query: { pairs },\n            });\n        }\n    });\n\n    test(\"fragment\", () => {\n        const cases = [\n            { text: \"\", fragment: undefined },\n            { text: \"#a\", fragment: \"a\" },\n            { text: \"#a?b\", fragment: \"a?b\" },\n            { text: \"#%61\", fragment: \"a\" },\n            { text: \"#k%C5%AF%C5%88\", fragment: \"kůň\" },\n        ];\n        for (const { text: fragmentText, fragment } of cases) {\n            const text = `file:${fragmentText}`;\n            expect(parseUri(text)).toEqual({\n                scheme: \"file\",\n                path: \"\",\n                fragment,\n            });\n        }\n    });\n\n    test(\"parse errors\", () => {\n        const cases = [\n            { text: \"\", message: /format/ },\n            { text: \"foo\", message: /format/ },\n            { text: \"foo.bar.com\", message: /format/ },\n            { text: \"h$$p://localhost\", message: /format/ },\n            { text: \"h%74%74p://localhost\", message: /format/ },\n            { text: \"http://localhost:%38%38\", message: /authority/ },\n            { text: \"file:k%C5%C5%88\", message: /percent encoding/ },\n        ];\n\n        for (const { text, message } of cases) {\n            expect(() => parseUri(text)).toThrow(\n                expect.toBeLibsqlError(\"URL_INVALID\", message),\n            );\n        }\n    });\n});\n\ntest(\"encodeBaseUrl()\", () => {\n    const cases = [\n        {\n            scheme: \"http\",\n            host: \"localhost\",\n            path: \"\",\n            url: \"http://localhost\",\n        },\n        {\n            scheme: \"http\",\n            host: \"localhost\",\n            path: \"/\",\n            url: \"http://localhost/\",\n        },\n        {\n            scheme: \"http\",\n            host: \"localhost\",\n            port: 8080,\n            path: \"\",\n            url: \"http://localhost:8080\",\n        },\n        {\n            scheme: \"http\",\n            host: \"localhost\",\n            path: \"/foo/bar\",\n            url: \"http://localhost/foo/bar\",\n        },\n        {\n            scheme: \"http\",\n            host: \"localhost\",\n            path: \"foo/bar\",\n            url: \"http://localhost/foo/bar\",\n        },\n        {\n            scheme: \"http\",\n            host: \"some.long.domain.name\",\n            path: \"\",\n            url: \"http://some.long.domain.name\",\n        },\n        {\n            scheme: \"http\",\n            host: \"1.2.3.4\",\n            path: \"\",\n            url: \"http://1.2.3.4\",\n        },\n        {\n            scheme: \"http\",\n            host: \"2001:4860:4802:32::a\",\n            path: \"\",\n            url: \"http://[2001:4860:4802:32::a]\",\n        },\n        {\n            scheme: \"http\",\n            host: \"localhost\",\n            userinfo: { username: \"alice\", password: undefined },\n            path: \"\",\n            url: \"http://alice@localhost\",\n        },\n        {\n            scheme: \"http\",\n            host: \"localhost\",\n            userinfo: { username: \"alice\", password: \"secr:t\" },\n            path: \"\",\n            url: \"http://alice:secr%3At@localhost\",\n        },\n        {\n            scheme: \"https\",\n            host: \"localhost\",\n            userinfo: { username: \"alice\", password: \"secret\" },\n            port: 8080,\n            path: \"/some/path\",\n            url: \"https://alice:secret@localhost:8080/some/path\",\n        },\n    ];\n\n    for (const { scheme, host, port, userinfo, path, url } of cases) {\n        expect(\n            encodeBaseUrl(scheme, { host, port, userinfo }, path),\n        ).toStrictEqual(new URL(url));\n    }\n});\n"
  },
  {
    "path": "packages/libsql-client/src/hrana.ts",
    "content": "import * as hrana from \"@libsql/hrana-client\";\nimport type {\n    InStatement,\n    ResultSet,\n    Transaction,\n    TransactionMode,\n    InArgs,\n} from \"@libsql/core/api\";\nimport { LibsqlError, LibsqlBatchError } from \"@libsql/core/api\";\nimport type { SqlCache } from \"./sql_cache.js\";\nimport { transactionModeToBegin, ResultSetImpl } from \"@libsql/core/util\";\n\nexport abstract class HranaTransaction implements Transaction {\n    #mode: TransactionMode;\n    #version: hrana.ProtocolVersion;\n    // Promise that is resolved when the BEGIN statement completes, or `undefined` if we haven't executed the\n    // BEGIN statement yet.\n    #started: Promise<void> | undefined;\n\n    /** @private */\n    constructor(mode: TransactionMode, version: hrana.ProtocolVersion) {\n        this.#mode = mode;\n        this.#version = version;\n        this.#started = undefined;\n    }\n\n    /** @private */\n    abstract _getStream(): hrana.Stream;\n    /** @private */\n    abstract _getSqlCache(): SqlCache;\n\n    abstract close(): void;\n    abstract get closed(): boolean;\n\n    execute(stmt: InStatement): Promise<ResultSet> {\n        return this.batch([stmt]).then((results) => results[0]);\n    }\n\n    async batch(stmts: Array<InStatement>): Promise<Array<ResultSet>> {\n        const stream = this._getStream();\n        if (stream.closed) {\n            throw new LibsqlError(\n                \"Cannot execute statements because the transaction is closed\",\n                \"TRANSACTION_CLOSED\",\n            );\n        }\n\n        try {\n            const hranaStmts = stmts.map(stmtToHrana);\n\n            let rowsPromises: Array<Promise<hrana.RowsResult | undefined>>;\n            if (this.#started === undefined) {\n                // The transaction hasn't started yet, so we need to send the BEGIN statement in a batch with\n                // `hranaStmts`.\n\n                this._getSqlCache().apply(hranaStmts);\n                const batch = stream.batch(this.#version >= 3);\n                const beginStep = batch.step();\n                const beginPromise = beginStep.run(\n                    transactionModeToBegin(this.#mode),\n                );\n\n                // Execute the `hranaStmts` only if the BEGIN succeeded, to make sure that we don't execute it\n                // outside of a transaction.\n                let lastStep = beginStep;\n                rowsPromises = hranaStmts.map((hranaStmt) => {\n                    const stmtStep = batch\n                        .step()\n                        .condition(hrana.BatchCond.ok(lastStep));\n                    if (this.#version >= 3) {\n                        // If the Hrana version supports it, make sure that we are still in a transaction\n                        stmtStep.condition(\n                            hrana.BatchCond.not(\n                                hrana.BatchCond.isAutocommit(batch),\n                            ),\n                        );\n                    }\n\n                    const rowsPromise = stmtStep.query(hranaStmt);\n                    rowsPromise.catch(() => undefined); // silence Node warning\n                    lastStep = stmtStep;\n                    return rowsPromise;\n                });\n\n                // `this.#started` is resolved successfully only if the batch and the BEGIN statement inside\n                // of the batch are both successful.\n                this.#started = batch\n                    .execute()\n                    .then(() => beginPromise)\n                    .then(() => undefined);\n\n                try {\n                    await this.#started;\n                } catch (e) {\n                    // If the BEGIN failed, the transaction is unusable and we must close it. However, if the\n                    // BEGIN suceeds and `hranaStmts` fail, the transaction is _not_ closed.\n                    this.close();\n                    throw e;\n                }\n            } else {\n                if (this.#version < 3) {\n                    // The transaction has started, so we must wait until the BEGIN statement completed to make\n                    // sure that we don't execute `hranaStmts` outside of a transaction.\n                    await this.#started;\n                } else {\n                    // The transaction has started, but we will use `hrana.BatchCond.isAutocommit()` to make\n                    // sure that we don't execute `hranaStmts` outside of a transaction, so we don't have to\n                    // wait for `this.#started`\n                }\n\n                this._getSqlCache().apply(hranaStmts);\n                const batch = stream.batch(this.#version >= 3);\n\n                let lastStep: hrana.BatchStep | undefined = undefined;\n                rowsPromises = hranaStmts.map((hranaStmt) => {\n                    const stmtStep = batch.step();\n                    if (lastStep !== undefined) {\n                        stmtStep.condition(hrana.BatchCond.ok(lastStep));\n                    }\n                    if (this.#version >= 3) {\n                        stmtStep.condition(\n                            hrana.BatchCond.not(\n                                hrana.BatchCond.isAutocommit(batch),\n                            ),\n                        );\n                    }\n                    const rowsPromise = stmtStep.query(hranaStmt);\n                    rowsPromise.catch(() => undefined); // silence Node warning\n                    lastStep = stmtStep;\n                    return rowsPromise;\n                });\n\n                await batch.execute();\n            }\n\n            const resultSets = [];\n            for (let i = 0; i < rowsPromises.length; i++) {\n                try {\n                    const rows = await rowsPromises[i];\n                    if (rows === undefined) {\n                        throw new LibsqlBatchError(\n                            \"Statement in a transaction was not executed, \" +\n                                \"probably because the transaction has been rolled back\",\n                            i,\n                            \"TRANSACTION_CLOSED\",\n                        );\n                    }\n                    resultSets.push(resultSetFromHrana(rows));\n                } catch (e) {\n                    if (e instanceof LibsqlBatchError) {\n                        throw e;\n                    }\n                    // Map hrana errors to LibsqlError first, then wrap in LibsqlBatchError\n                    const mappedError = mapHranaError(e);\n                    if (mappedError instanceof LibsqlError) {\n                        throw new LibsqlBatchError(\n                            mappedError.message,\n                            i,\n                            mappedError.code,\n                            mappedError.extendedCode,\n                            mappedError.rawCode,\n                            mappedError.cause instanceof Error\n                                ? mappedError.cause\n                                : undefined,\n                        );\n                    }\n                    throw mappedError;\n                }\n            }\n            return resultSets;\n        } catch (e) {\n            throw mapHranaError(e);\n        }\n    }\n\n    async executeMultiple(sql: string): Promise<void> {\n        const stream = this._getStream();\n        if (stream.closed) {\n            throw new LibsqlError(\n                \"Cannot execute statements because the transaction is closed\",\n                \"TRANSACTION_CLOSED\",\n            );\n        }\n\n        try {\n            if (this.#started === undefined) {\n                // If the transaction hasn't started yet, start it now\n                this.#started = stream\n                    .run(transactionModeToBegin(this.#mode))\n                    .then(() => undefined);\n                try {\n                    await this.#started;\n                } catch (e) {\n                    this.close();\n                    throw e;\n                }\n            } else {\n                // Wait until the transaction has started\n                await this.#started;\n            }\n\n            await stream.sequence(sql);\n        } catch (e) {\n            throw mapHranaError(e);\n        }\n    }\n\n    async rollback(): Promise<void> {\n        try {\n            const stream = this._getStream();\n            if (stream.closed) {\n                return;\n            }\n\n            if (this.#started !== undefined) {\n                // We don't have to wait for the BEGIN statement to complete. If the BEGIN fails, we will\n                // execute a ROLLBACK outside of an active transaction, which should be harmless.\n            } else {\n                // We did nothing in the transaction, so there is nothing to rollback.\n                return;\n            }\n\n            // Pipeline the ROLLBACK statement and the stream close.\n            const promise = stream.run(\"ROLLBACK\").catch((e) => {\n                throw mapHranaError(e);\n            });\n            stream.closeGracefully();\n\n            await promise;\n        } catch (e) {\n            throw mapHranaError(e);\n        } finally {\n            // `this.close()` may close the `hrana.Client`, which aborts all pending stream requests, so we\n            // must call it _after_ we receive the ROLLBACK response.\n            // Also note that the current stream should already be closed, but we need to call `this.close()`\n            // anyway, because it may need to do more cleanup.\n            this.close();\n        }\n    }\n\n    async commit(): Promise<void> {\n        // (this method is analogous to `rollback()`)\n        try {\n            const stream = this._getStream();\n            if (stream.closed) {\n                throw new LibsqlError(\n                    \"Cannot commit the transaction because it is already closed\",\n                    \"TRANSACTION_CLOSED\",\n                );\n            }\n\n            if (this.#started !== undefined) {\n                // Make sure to execute the COMMIT only if the BEGIN was successful.\n                await this.#started;\n            } else {\n                return;\n            }\n\n            const promise = stream.run(\"COMMIT\").catch((e) => {\n                throw mapHranaError(e);\n            });\n            stream.closeGracefully();\n\n            await promise;\n        } catch (e) {\n            throw mapHranaError(e);\n        } finally {\n            this.close();\n        }\n    }\n}\n\nexport async function executeHranaBatch(\n    mode: TransactionMode,\n    version: hrana.ProtocolVersion,\n    batch: hrana.Batch,\n    hranaStmts: Array<hrana.Stmt>,\n    disableForeignKeys: boolean = false,\n): Promise<Array<ResultSet>> {\n    if (disableForeignKeys) {\n        batch.step().run(\"PRAGMA foreign_keys=off\");\n    }\n    const beginStep = batch.step();\n    const beginPromise = beginStep.run(transactionModeToBegin(mode));\n\n    let lastStep = beginStep;\n    const stmtPromises = hranaStmts.map((hranaStmt) => {\n        const stmtStep = batch.step().condition(hrana.BatchCond.ok(lastStep));\n        if (version >= 3) {\n            stmtStep.condition(\n                hrana.BatchCond.not(hrana.BatchCond.isAutocommit(batch)),\n            );\n        }\n\n        const stmtPromise = stmtStep.query(hranaStmt);\n        lastStep = stmtStep;\n        return stmtPromise;\n    });\n\n    const commitStep = batch.step().condition(hrana.BatchCond.ok(lastStep));\n    if (version >= 3) {\n        commitStep.condition(\n            hrana.BatchCond.not(hrana.BatchCond.isAutocommit(batch)),\n        );\n    }\n    const commitPromise = commitStep.run(\"COMMIT\");\n\n    const rollbackStep = batch\n        .step()\n        .condition(hrana.BatchCond.not(hrana.BatchCond.ok(commitStep)));\n    rollbackStep.run(\"ROLLBACK\").catch((_) => undefined);\n    if (disableForeignKeys) {\n        batch.step().run(\"PRAGMA foreign_keys=on\");\n    }\n\n    await batch.execute();\n\n    const resultSets = [];\n    await beginPromise;\n    for (let i = 0; i < stmtPromises.length; i++) {\n        try {\n            const hranaRows = await stmtPromises[i];\n            if (hranaRows === undefined) {\n                throw new LibsqlBatchError(\n                    \"Statement in a batch was not executed, probably because the transaction has been rolled back\",\n                    i,\n                    \"TRANSACTION_CLOSED\",\n                );\n            }\n            resultSets.push(resultSetFromHrana(hranaRows));\n        } catch (e) {\n            if (e instanceof LibsqlBatchError) {\n                throw e;\n            }\n            // Map hrana errors to LibsqlError first, then wrap in LibsqlBatchError\n            const mappedError = mapHranaError(e);\n            if (mappedError instanceof LibsqlError) {\n                throw new LibsqlBatchError(\n                    mappedError.message,\n                    i,\n                    mappedError.code,\n                    mappedError.extendedCode,\n                    mappedError.rawCode,\n                    mappedError.cause instanceof Error\n                        ? mappedError.cause\n                        : undefined,\n                );\n            }\n            throw mappedError;\n        }\n    }\n    await commitPromise;\n\n    return resultSets;\n}\n\nexport function stmtToHrana(stmt: InStatement | [string, InArgs?]): hrana.Stmt {\n    let sql: string;\n    let args: InArgs | undefined;\n\n    if (Array.isArray(stmt)) {\n        [sql, args] = stmt;\n    } else if (typeof stmt === \"string\") {\n        sql = stmt;\n    } else {\n        sql = stmt.sql;\n        args = stmt.args;\n    }\n\n    const hranaStmt = new hrana.Stmt(sql);\n    if (args) {\n        if (Array.isArray(args)) {\n            hranaStmt.bindIndexes(args);\n        } else {\n            for (const [key, value] of Object.entries(args)) {\n                hranaStmt.bindName(key, value);\n            }\n        }\n    }\n\n    return hranaStmt;\n}\n\nexport function resultSetFromHrana(hranaRows: hrana.RowsResult): ResultSet {\n    const columns = hranaRows.columnNames.map((c) => c ?? \"\");\n    const columnTypes = hranaRows.columnDecltypes.map((c) => c ?? \"\");\n    const rows = hranaRows.rows;\n    const rowsAffected = hranaRows.affectedRowCount;\n    const lastInsertRowid =\n        hranaRows.lastInsertRowid !== undefined\n            ? hranaRows.lastInsertRowid\n            : undefined;\n    return new ResultSetImpl(\n        columns,\n        columnTypes,\n        rows,\n        rowsAffected,\n        lastInsertRowid,\n    );\n}\n\nexport function mapHranaError(e: unknown): unknown {\n    if (e instanceof hrana.ClientError) {\n        const code = mapHranaErrorCode(e);\n        // TODO: Parse extendedCode once the SQL over HTTP protocol supports it\n        return new LibsqlError(e.message, code, undefined, undefined, e);\n    }\n    return e;\n}\n\nfunction mapHranaErrorCode(e: hrana.ClientError): string {\n    if (e instanceof hrana.ResponseError && e.code !== undefined) {\n        return e.code;\n    } else if (e instanceof hrana.ProtoError) {\n        return \"HRANA_PROTO_ERROR\";\n    } else if (e instanceof hrana.ClosedError) {\n        return e.cause instanceof hrana.ClientError\n            ? mapHranaErrorCode(e.cause)\n            : \"HRANA_CLOSED_ERROR\";\n    } else if (e instanceof hrana.WebSocketError) {\n        return \"HRANA_WEBSOCKET_ERROR\";\n    } else if (e instanceof hrana.HttpServerError) {\n        return \"SERVER_ERROR\";\n    } else if (e instanceof hrana.ProtocolVersionError) {\n        return \"PROTOCOL_VERSION_ERROR\";\n    } else if (e instanceof hrana.InternalError) {\n        return \"INTERNAL_ERROR\";\n    } else {\n        return \"UNKNOWN\";\n    }\n}\n"
  },
  {
    "path": "packages/libsql-client/src/http.ts",
    "content": "import * as hrana from \"@libsql/hrana-client\";\n\nimport type { Config, Client } from \"@libsql/core/api\";\nimport type {\n    InStatement,\n    ResultSet,\n    Transaction,\n    IntMode,\n    InArgs,\n    Replicated,\n} from \"@libsql/core/api\";\nimport { TransactionMode, LibsqlError } from \"@libsql/core/api\";\nimport type { ExpandedConfig } from \"@libsql/core/config\";\nimport { expandConfig } from \"@libsql/core/config\";\nimport {\n    HranaTransaction,\n    executeHranaBatch,\n    stmtToHrana,\n    resultSetFromHrana,\n    mapHranaError,\n} from \"./hrana.js\";\nimport { SqlCache } from \"./sql_cache.js\";\nimport { encodeBaseUrl } from \"@libsql/core/uri\";\nimport { supportedUrlLink } from \"@libsql/core/util\";\nimport promiseLimit from \"promise-limit\";\n\nexport * from \"@libsql/core/api\";\n\nexport function createClient(config: Config): Client {\n    return _createClient(expandConfig(config, true));\n}\n\n/** @private */\nexport function _createClient(config: ExpandedConfig): Client {\n    if (config.scheme !== \"https\" && config.scheme !== \"http\") {\n        throw new LibsqlError(\n            'The HTTP client supports only \"libsql:\", \"https:\" and \"http:\" URLs, ' +\n                `got ${JSON.stringify(config.scheme + \":\")}. For more information, please read ${supportedUrlLink}`,\n            \"URL_SCHEME_NOT_SUPPORTED\",\n        );\n    }\n\n    if (config.encryptionKey !== undefined) {\n        throw new LibsqlError(\n            \"Encryption key is not supported by the remote client.\",\n            \"ENCRYPTION_KEY_NOT_SUPPORTED\",\n        );\n    }\n\n    if (config.scheme === \"http\" && config.tls) {\n        throw new LibsqlError(\n            `A \"http:\" URL cannot opt into TLS by using ?tls=1`,\n            \"URL_INVALID\",\n        );\n    } else if (config.scheme === \"https\" && !config.tls) {\n        throw new LibsqlError(\n            `A \"https:\" URL cannot opt out of TLS by using ?tls=0`,\n            \"URL_INVALID\",\n        );\n    }\n\n    const url = encodeBaseUrl(config.scheme, config.authority, config.path);\n    return new HttpClient(\n        url,\n        config.authToken,\n        config.intMode,\n        config.fetch,\n        config.concurrency,\n        config.remoteEncryptionKey,\n    );\n}\n\nconst sqlCacheCapacity = 30;\n\nexport class HttpClient implements Client {\n    #client: hrana.HttpClient;\n    protocol: \"http\";\n    #url: URL;\n    #intMode: IntMode;\n    #customFetch: Function | undefined;\n    #concurrency: number;\n    #authToken: string | undefined;\n    #remoteEncryptionKey: string | undefined;\n    #promiseLimitFunction: ReturnType<typeof promiseLimit<any>>;\n\n    /** @private */\n    constructor(\n        url: URL,\n        authToken: string | undefined,\n        intMode: IntMode,\n        customFetch: Function | undefined,\n        concurrency: number,\n        remoteEncryptionKey: string | undefined,\n    ) {\n        this.#url = url;\n        this.#authToken = authToken;\n        this.#intMode = intMode;\n        this.#customFetch = customFetch;\n        this.#concurrency = concurrency;\n        this.#remoteEncryptionKey = remoteEncryptionKey;\n\n        this.#client = hrana.openHttp(\n            this.#url,\n            this.#authToken,\n            this.#customFetch,\n            remoteEncryptionKey,\n        );\n        this.#client.intMode = this.#intMode;\n        this.protocol = \"http\";\n        this.#promiseLimitFunction = promiseLimit<any>(this.#concurrency);\n    }\n\n    private async limit<T>(fn: () => Promise<T>): Promise<T> {\n        return this.#promiseLimitFunction(fn);\n    }\n\n    async execute(\n        stmtOrSql: InStatement | string,\n        args?: InArgs,\n    ): Promise<ResultSet> {\n        let stmt: InStatement;\n\n        if (typeof stmtOrSql === \"string\") {\n            stmt = {\n                sql: stmtOrSql,\n                args: args || [],\n            };\n        } else {\n            stmt = stmtOrSql;\n        }\n\n        return this.limit<ResultSet>(async () => {\n            try {\n                const hranaStmt = stmtToHrana(stmt);\n\n                // Pipeline all operations, so `hrana.HttpClient` can open the stream, execute the statement and\n                // close the stream in a single HTTP request.\n                let rowsPromise: Promise<hrana.RowsResult>;\n                const stream = this.#client.openStream();\n                try {\n                    rowsPromise = stream.query(hranaStmt);\n                } finally {\n                    stream.closeGracefully();\n                }\n\n                const rowsResult = await rowsPromise;\n\n                return resultSetFromHrana(rowsResult);\n            } catch (e) {\n                throw mapHranaError(e);\n            }\n        });\n    }\n\n    async batch(\n        stmts: Array<InStatement | [string, InArgs?]>,\n        mode: TransactionMode = \"deferred\",\n    ): Promise<Array<ResultSet>> {\n        return this.limit<Array<ResultSet>>(async () => {\n            try {\n                const normalizedStmts = stmts.map((stmt) => {\n                    if (Array.isArray(stmt)) {\n                        return {\n                            sql: stmt[0],\n                            args: stmt[1] || [],\n                        };\n                    }\n                    return stmt;\n                });\n\n                const hranaStmts = normalizedStmts.map(stmtToHrana);\n                const version = await this.#client.getVersion();\n\n                // Pipeline all operations, so `hrana.HttpClient` can open the stream, execute the batch and\n                // close the stream in a single HTTP request.\n                let resultsPromise: Promise<Array<ResultSet>>;\n                const stream = this.#client.openStream();\n                try {\n                    // It makes sense to use a SQL cache even for a single batch, because it may contain the same\n                    // statement repeated multiple times.\n                    const sqlCache = new SqlCache(stream, sqlCacheCapacity);\n                    sqlCache.apply(hranaStmts);\n\n                    // TODO: we do not use a cursor here, because it would cause three roundtrips:\n                    // 1. pipeline request to store SQL texts\n                    // 2. cursor request\n                    // 3. pipeline request to close the stream\n                    const batch = stream.batch(false);\n                    resultsPromise = executeHranaBatch(\n                        mode,\n                        version,\n                        batch,\n                        hranaStmts,\n                    );\n                } finally {\n                    stream.closeGracefully();\n                }\n\n                const results = await resultsPromise;\n\n                return results;\n            } catch (e) {\n                throw mapHranaError(e);\n            }\n        });\n    }\n\n    async migrate(stmts: Array<InStatement>): Promise<Array<ResultSet>> {\n        return this.limit<Array<ResultSet>>(async () => {\n            try {\n                const hranaStmts = stmts.map(stmtToHrana);\n                const version = await this.#client.getVersion();\n\n                // Pipeline all operations, so `hrana.HttpClient` can open the stream, execute the batch and\n                // close the stream in a single HTTP request.\n                let resultsPromise: Promise<Array<ResultSet>>;\n                const stream = this.#client.openStream();\n                try {\n                    const batch = stream.batch(false);\n                    resultsPromise = executeHranaBatch(\n                        \"deferred\",\n                        version,\n                        batch,\n                        hranaStmts,\n                        true,\n                    );\n                } finally {\n                    stream.closeGracefully();\n                }\n\n                const results = await resultsPromise;\n\n                return results;\n            } catch (e) {\n                throw mapHranaError(e);\n            }\n        });\n    }\n\n    async transaction(\n        mode: TransactionMode = \"write\",\n    ): Promise<HttpTransaction> {\n        return this.limit<HttpTransaction>(async () => {\n            try {\n                const version = await this.#client.getVersion();\n                return new HttpTransaction(\n                    this.#client.openStream(),\n                    mode,\n                    version,\n                );\n            } catch (e) {\n                throw mapHranaError(e);\n            }\n        });\n    }\n\n    async executeMultiple(sql: string): Promise<void> {\n        return this.limit<void>(async () => {\n            try {\n                // Pipeline all operations, so `hrana.HttpClient` can open the stream, execute the sequence and\n                // close the stream in a single HTTP request.\n                let promise: Promise<void>;\n                const stream = this.#client.openStream();\n                try {\n                    promise = stream.sequence(sql);\n                } finally {\n                    stream.closeGracefully();\n                }\n\n                await promise;\n            } catch (e) {\n                throw mapHranaError(e);\n            }\n        });\n    }\n\n    sync(): Promise<Replicated> {\n        throw new LibsqlError(\n            \"sync not supported in http mode\",\n            \"SYNC_NOT_SUPPORTED\",\n        );\n    }\n\n    close(): void {\n        this.#client.close();\n    }\n\n    async reconnect(): Promise<void> {\n        try {\n            if (!this.closed) {\n                // Abort in-flight ops and free resources\n                this.#client.close();\n            }\n        } finally {\n            // Recreate the underlying hrana client\n            this.#client = hrana.openHttp(\n                this.#url,\n                this.#authToken,\n                this.#customFetch,\n                this.#remoteEncryptionKey,\n            );\n            this.#client.intMode = this.#intMode;\n        }\n    }\n\n    get closed(): boolean {\n        return this.#client.closed;\n    }\n}\n\nexport class HttpTransaction extends HranaTransaction implements Transaction {\n    #stream: hrana.HttpStream;\n    #sqlCache: SqlCache;\n\n    /** @private */\n    constructor(\n        stream: hrana.HttpStream,\n        mode: TransactionMode,\n        version: hrana.ProtocolVersion,\n    ) {\n        super(mode, version);\n        this.#stream = stream;\n        this.#sqlCache = new SqlCache(stream, sqlCacheCapacity);\n    }\n\n    /** @private */\n    override _getStream(): hrana.Stream {\n        return this.#stream;\n    }\n\n    /** @private */\n    override _getSqlCache(): SqlCache {\n        return this.#sqlCache;\n    }\n\n    override close(): void {\n        this.#stream.close();\n    }\n\n    override get closed(): boolean {\n        return this.#stream.closed;\n    }\n}\n"
  },
  {
    "path": "packages/libsql-client/src/node.ts",
    "content": "import type { Config, Client } from \"@libsql/core/api\";\nimport { LibsqlError } from \"@libsql/core/api\";\nimport type { ExpandedConfig } from \"@libsql/core/config\";\nimport { expandConfig } from \"@libsql/core/config\";\nimport { _createClient as _createSqlite3Client } from \"./sqlite3.js\";\nimport { _createClient as _createWsClient } from \"./ws.js\";\nimport { _createClient as _createHttpClient } from \"./http.js\";\n\nexport * from \"@libsql/core/api\";\n\n/** Creates a {@link Client} object.\n *\n * You must pass at least an `url` in the {@link Config} object.\n */\nexport function createClient(config: Config): Client {\n    return _createClient(expandConfig(config, true));\n}\n\nfunction _createClient(config: ExpandedConfig) {\n    if (config.scheme === \"wss\" || config.scheme === \"ws\") {\n        return _createWsClient(config);\n    } else if (config.scheme === \"https\" || config.scheme === \"http\") {\n        return _createHttpClient(config);\n    } else {\n        return _createSqlite3Client(config);\n    }\n}\n"
  },
  {
    "path": "packages/libsql-client/src/sql_cache.ts",
    "content": "import type * as hrana from \"@libsql/hrana-client\";\n\nexport class SqlCache {\n    #owner: hrana.SqlOwner;\n    #sqls: Lru<string, hrana.Sql>;\n    capacity: number;\n\n    constructor(owner: hrana.SqlOwner, capacity: number) {\n        this.#owner = owner;\n        this.#sqls = new Lru();\n        this.capacity = capacity;\n    }\n\n    // Replaces SQL strings with cached `hrana.Sql` objects in the statements in `hranaStmts`. After this\n    // function returns, we guarantee that all `hranaStmts` refer to valid (not closed) `hrana.Sql` objects,\n    // but _we may invalidate any other `hrana.Sql` objects_ (by closing them, thus removing them from the\n    // server).\n    //\n    // In practice, this means that after calling this function, you can use the statements only up to the\n    // first `await`, because concurrent code may also use the cache and invalidate those statements.\n    apply(hranaStmts: Array<hrana.Stmt>): void {\n        if (this.capacity <= 0) {\n            return;\n        }\n\n        const usedSqlObjs: Set<hrana.Sql> = new Set();\n\n        for (const hranaStmt of hranaStmts) {\n            if (typeof hranaStmt.sql !== \"string\") {\n                continue;\n            }\n            const sqlText = hranaStmt.sql;\n\n            // Stored SQL cannot exceed 5kb.\n            // https://github.com/tursodatabase/libsql/blob/e9d637e051685f92b0da43849507b5ef4232fbeb/libsql-server/src/hrana/http/request.rs#L10\n            if (sqlText.length >= 5000) {\n                continue;\n            }\n\n            let sqlObj = this.#sqls.get(sqlText);\n            if (sqlObj === undefined) {\n                while (this.#sqls.size + 1 > this.capacity) {\n                    const [evictSqlText, evictSqlObj] = this.#sqls.peekLru()!;\n                    if (usedSqlObjs.has(evictSqlObj)) {\n                        // The SQL object that we are trying to evict is already in use in this batch, so we\n                        // must not evict and close it.\n                        break;\n                    }\n                    evictSqlObj.close();\n                    this.#sqls.delete(evictSqlText);\n                }\n\n                if (this.#sqls.size + 1 <= this.capacity) {\n                    sqlObj = this.#owner.storeSql(sqlText);\n                    this.#sqls.set(sqlText, sqlObj);\n                }\n            }\n\n            if (sqlObj !== undefined) {\n                hranaStmt.sql = sqlObj;\n                usedSqlObjs.add(sqlObj);\n            }\n        }\n    }\n}\n\nclass Lru<K, V> {\n    // This maps keys to the cache values. The entries are ordered by their last use (entires that were used\n    // most recently are at the end).\n    #cache: Map<K, V>;\n\n    constructor() {\n        this.#cache = new Map();\n    }\n\n    get(key: K): V | undefined {\n        const value = this.#cache.get(key);\n        if (value !== undefined) {\n            // move the entry to the back of the Map\n            this.#cache.delete(key);\n            this.#cache.set(key, value);\n        }\n        return value;\n    }\n\n    set(key: K, value: V): void {\n        this.#cache.set(key, value);\n    }\n\n    peekLru(): [K, V] | undefined {\n        for (const entry of this.#cache.entries()) {\n            return entry;\n        }\n        return undefined;\n    }\n\n    delete(key: K): void {\n        this.#cache.delete(key);\n    }\n\n    get size(): number {\n        return this.#cache.size;\n    }\n}\n"
  },
  {
    "path": "packages/libsql-client/src/sqlite3.ts",
    "content": "import Database from \"libsql\";\nimport { Buffer } from \"node:buffer\";\n\nimport type {\n    Config,\n    IntMode,\n    Client,\n    Transaction,\n    TransactionMode,\n    ResultSet,\n    Row,\n    Value,\n    InValue,\n    InStatement,\n    InArgs,\n    Replicated,\n} from \"@libsql/core/api\";\nimport { LibsqlError, LibsqlBatchError } from \"@libsql/core/api\";\nimport type { ExpandedConfig } from \"@libsql/core/config\";\nimport { expandConfig, isInMemoryConfig } from \"@libsql/core/config\";\nimport {\n    supportedUrlLink,\n    transactionModeToBegin,\n    ResultSetImpl,\n} from \"@libsql/core/util\";\n\nexport * from \"@libsql/core/api\";\n\nexport function createClient(config: Config): Client {\n    return _createClient(expandConfig(config, true));\n}\n\n/** @private */\nexport function _createClient(config: ExpandedConfig): Client {\n    if (config.scheme !== \"file\") {\n        throw new LibsqlError(\n            `URL scheme ${JSON.stringify(config.scheme + \":\")} is not supported by the local sqlite3 client. ` +\n                `For more information, please read ${supportedUrlLink}`,\n            \"URL_SCHEME_NOT_SUPPORTED\",\n        );\n    }\n\n    const authority = config.authority;\n    if (authority !== undefined) {\n        const host = authority.host.toLowerCase();\n        if (host !== \"\" && host !== \"localhost\") {\n            throw new LibsqlError(\n                `Invalid host in file URL: ${JSON.stringify(authority.host)}. ` +\n                    'A \"file:\" URL with an absolute path should start with one slash (\"file:/absolute/path.db\") ' +\n                    'or with three slashes (\"file:///absolute/path.db\"). ' +\n                    `For more information, please read ${supportedUrlLink}`,\n                \"URL_INVALID\",\n            );\n        }\n\n        if (authority.port !== undefined) {\n            throw new LibsqlError(\"File URL cannot have a port\", \"URL_INVALID\");\n        }\n        if (authority.userinfo !== undefined) {\n            throw new LibsqlError(\n                \"File URL cannot have username and password\",\n                \"URL_INVALID\",\n            );\n        }\n    }\n\n    let isInMemory = isInMemoryConfig(config);\n    if (isInMemory && config.syncUrl) {\n        throw new LibsqlError(\n            `Embedded replica must use file for local db but URI with in-memory mode were provided instead: ${config.path}`,\n            \"URL_INVALID\",\n        );\n    }\n\n    let path = config.path;\n    if (isInMemory) {\n        // note: we should prepend file scheme in order for SQLite3 to recognize :memory: connection query parameters\n        path = `${config.scheme}:${config.path}`;\n    }\n\n    const options = {\n        authToken: config.authToken,\n        encryptionKey: config.encryptionKey,\n        remoteEncryptionKey: config.remoteEncryptionKey,\n        syncUrl: config.syncUrl,\n        syncPeriod: config.syncInterval,\n        readYourWrites: config.readYourWrites,\n        offline: config.offline,\n    };\n\n    const db = new Database(path, options);\n\n    executeStmt(\n        db,\n        \"SELECT 1 AS checkThatTheDatabaseCanBeOpened\",\n        config.intMode,\n    );\n\n    return new Sqlite3Client(path, options, db, config.intMode);\n}\n\nexport class Sqlite3Client implements Client {\n    #path: string;\n    #options: Database.Options;\n    #db: Database.Database | null;\n    #intMode: IntMode;\n    closed: boolean;\n    protocol: \"file\";\n\n    /** @private */\n    constructor(\n        path: string,\n        options: Database.Options,\n        db: Database.Database,\n        intMode: IntMode,\n    ) {\n        this.#path = path;\n        this.#options = options;\n        this.#db = db;\n        this.#intMode = intMode;\n        this.closed = false;\n        this.protocol = \"file\";\n    }\n\n    async execute(\n        stmtOrSql: InStatement | string,\n        args?: InArgs,\n    ): Promise<ResultSet> {\n        let stmt: InStatement;\n\n        if (typeof stmtOrSql === \"string\") {\n            stmt = {\n                sql: stmtOrSql,\n                args: args || [],\n            };\n        } else {\n            stmt = stmtOrSql;\n        }\n\n        this.#checkNotClosed();\n        return executeStmt(this.#getDb(), stmt, this.#intMode);\n    }\n\n    async batch(\n        stmts: Array<InStatement | [string, InArgs?]>,\n        mode: TransactionMode = \"deferred\",\n    ): Promise<Array<ResultSet>> {\n        this.#checkNotClosed();\n        const db = this.#getDb();\n        try {\n            executeStmt(db, transactionModeToBegin(mode), this.#intMode);\n            const resultSets = [];\n            for (let i = 0; i < stmts.length; i++) {\n                try {\n                    if (!db.inTransaction) {\n                        throw new LibsqlBatchError(\n                            \"The transaction has been rolled back\",\n                            i,\n                            \"TRANSACTION_CLOSED\",\n                        );\n                    }\n                    const stmt = stmts[i];\n                    const normalizedStmt: InStatement = Array.isArray(stmt)\n                        ? { sql: stmt[0], args: stmt[1] || [] }\n                        : stmt;\n                    resultSets.push(\n                        executeStmt(db, normalizedStmt, this.#intMode),\n                    );\n                } catch (e) {\n                    if (e instanceof LibsqlBatchError) {\n                        throw e;\n                    }\n                    if (e instanceof LibsqlError) {\n                        throw new LibsqlBatchError(\n                            e.message,\n                            i,\n                            e.code,\n                            e.extendedCode,\n                            e.rawCode,\n                            e.cause instanceof Error ? e.cause : undefined,\n                        );\n                    }\n                    throw e;\n                }\n            }\n            executeStmt(db, \"COMMIT\", this.#intMode);\n            return resultSets;\n        } finally {\n            if (db.inTransaction) {\n                executeStmt(db, \"ROLLBACK\", this.#intMode);\n            }\n        }\n    }\n\n    async migrate(stmts: Array<InStatement>): Promise<Array<ResultSet>> {\n        this.#checkNotClosed();\n        const db = this.#getDb();\n        try {\n            executeStmt(db, \"PRAGMA foreign_keys=off\", this.#intMode);\n            executeStmt(db, transactionModeToBegin(\"deferred\"), this.#intMode);\n            const resultSets = [];\n            for (let i = 0; i < stmts.length; i++) {\n                try {\n                    if (!db.inTransaction) {\n                        throw new LibsqlBatchError(\n                            \"The transaction has been rolled back\",\n                            i,\n                            \"TRANSACTION_CLOSED\",\n                        );\n                    }\n                    resultSets.push(executeStmt(db, stmts[i], this.#intMode));\n                } catch (e) {\n                    if (e instanceof LibsqlBatchError) {\n                        throw e;\n                    }\n                    if (e instanceof LibsqlError) {\n                        throw new LibsqlBatchError(\n                            e.message,\n                            i,\n                            e.code,\n                            e.extendedCode,\n                            e.rawCode,\n                            e.cause instanceof Error ? e.cause : undefined,\n                        );\n                    }\n                    throw e;\n                }\n            }\n            executeStmt(db, \"COMMIT\", this.#intMode);\n            return resultSets;\n        } finally {\n            if (db.inTransaction) {\n                executeStmt(db, \"ROLLBACK\", this.#intMode);\n            }\n            executeStmt(db, \"PRAGMA foreign_keys=on\", this.#intMode);\n        }\n    }\n\n    async transaction(mode: TransactionMode = \"write\"): Promise<Transaction> {\n        const db = this.#getDb();\n        executeStmt(db, transactionModeToBegin(mode), this.#intMode);\n        this.#db = null; // A new connection will be lazily created on next use\n        return new Sqlite3Transaction(db, this.#intMode);\n    }\n\n    async executeMultiple(sql: string): Promise<void> {\n        this.#checkNotClosed();\n        const db = this.#getDb();\n        try {\n            return executeMultiple(db, sql);\n        } finally {\n            if (db.inTransaction) {\n                executeStmt(db, \"ROLLBACK\", this.#intMode);\n            }\n        }\n    }\n\n    async sync(): Promise<Replicated> {\n        this.#checkNotClosed();\n        const rep = await this.#getDb().sync();\n        return {\n            frames_synced: rep.frames_synced,\n            frame_no: rep.frame_no,\n        } as Replicated;\n    }\n\n    async reconnect(): Promise<void> {\n        try {\n            if (!this.closed && this.#db !== null) {\n                this.#db.close();\n            }\n        } finally {\n            this.#db = new Database(this.#path, this.#options);\n            this.closed = false;\n        }\n    }\n\n    close(): void {\n        this.closed = true;\n        if (this.#db !== null) {\n            this.#db.close();\n            this.#db = null;\n        }\n    }\n\n    #checkNotClosed(): void {\n        if (this.closed) {\n            throw new LibsqlError(\"The client is closed\", \"CLIENT_CLOSED\");\n        }\n    }\n\n    // Lazily creates the database connection and returns it\n    #getDb(): Database.Database {\n        if (this.#db === null) {\n            this.#db = new Database(this.#path, this.#options);\n        }\n        return this.#db;\n    }\n}\n\nexport class Sqlite3Transaction implements Transaction {\n    #database: Database.Database;\n    #intMode: IntMode;\n\n    /** @private */\n    constructor(database: Database.Database, intMode: IntMode) {\n        this.#database = database;\n        this.#intMode = intMode;\n    }\n\n    async execute(stmt: InStatement): Promise<ResultSet>;\n    async execute(sql: string, args?: InArgs): Promise<ResultSet>;\n\n    async execute(\n        stmtOrSql: InStatement | string,\n        args?: InArgs,\n    ): Promise<ResultSet> {\n        let stmt: InStatement;\n\n        if (typeof stmtOrSql === \"string\") {\n            stmt = {\n                sql: stmtOrSql,\n                args: args || [],\n            };\n        } else {\n            stmt = stmtOrSql;\n        }\n\n        this.#checkNotClosed();\n        return executeStmt(this.#database, stmt, this.#intMode);\n    }\n\n    async batch(\n        stmts: Array<InStatement | [string, InArgs?]>,\n    ): Promise<Array<ResultSet>> {\n        const resultSets = [];\n        for (let i = 0; i < stmts.length; i++) {\n            try {\n                this.#checkNotClosed();\n                const stmt = stmts[i];\n                const normalizedStmt: InStatement = Array.isArray(stmt)\n                    ? { sql: stmt[0], args: stmt[1] || [] }\n                    : stmt;\n                resultSets.push(\n                    executeStmt(this.#database, normalizedStmt, this.#intMode),\n                );\n            } catch (e) {\n                if (e instanceof LibsqlBatchError) {\n                    throw e;\n                }\n                if (e instanceof LibsqlError) {\n                    throw new LibsqlBatchError(\n                        e.message,\n                        i,\n                        e.code,\n                        e.extendedCode,\n                        e.rawCode,\n                        e.cause instanceof Error ? e.cause : undefined,\n                    );\n                }\n                throw e;\n            }\n        }\n        return resultSets;\n    }\n\n    async executeMultiple(sql: string): Promise<void> {\n        this.#checkNotClosed();\n        return executeMultiple(this.#database, sql);\n    }\n\n    async rollback(): Promise<void> {\n        if (!this.#database.open) {\n            return;\n        }\n        this.#checkNotClosed();\n        executeStmt(this.#database, \"ROLLBACK\", this.#intMode);\n    }\n\n    async commit(): Promise<void> {\n        this.#checkNotClosed();\n        executeStmt(this.#database, \"COMMIT\", this.#intMode);\n    }\n\n    close(): void {\n        if (this.#database.inTransaction) {\n            executeStmt(this.#database, \"ROLLBACK\", this.#intMode);\n        }\n    }\n\n    get closed(): boolean {\n        return !this.#database.inTransaction;\n    }\n\n    #checkNotClosed(): void {\n        if (this.closed) {\n            throw new LibsqlError(\n                \"The transaction is closed\",\n                \"TRANSACTION_CLOSED\",\n            );\n        }\n    }\n}\n\nfunction executeStmt(\n    db: Database.Database,\n    stmt: InStatement,\n    intMode: IntMode,\n): ResultSet {\n    let sql: string;\n    let args: Array<unknown> | Record<string, unknown>;\n    if (typeof stmt === \"string\") {\n        sql = stmt;\n        args = [];\n    } else {\n        sql = stmt.sql;\n        if (Array.isArray(stmt.args)) {\n            args = stmt.args.map((value) => valueToSql(value, intMode));\n        } else {\n            args = {};\n            for (const name in stmt.args) {\n                const argName =\n                    name[0] === \"@\" || name[0] === \"$\" || name[0] === \":\"\n                        ? name.substring(1)\n                        : name;\n                args[argName] = valueToSql(stmt.args[name], intMode);\n            }\n        }\n    }\n\n    try {\n        const sqlStmt = db.prepare(sql);\n        sqlStmt.safeIntegers(true);\n\n        let returnsData = true;\n        try {\n            sqlStmt.raw(true);\n        } catch {\n            // raw() throws an exception if the statement does not return data\n            returnsData = false;\n        }\n\n        if (returnsData) {\n            const columns = Array.from(\n                sqlStmt.columns().map((col) => col.name),\n            );\n            const columnTypes = Array.from(\n                sqlStmt.columns().map((col) => col.type ?? \"\"),\n            );\n            const rows = sqlStmt.all(args).map((sqlRow) => {\n                return rowFromSql(sqlRow as Array<unknown>, columns, intMode);\n            });\n            // TODO: can we get this info from better-sqlite3?\n            const rowsAffected = 0;\n            const lastInsertRowid = undefined;\n            return new ResultSetImpl(\n                columns,\n                columnTypes,\n                rows,\n                rowsAffected,\n                lastInsertRowid,\n            );\n        } else {\n            const info = sqlStmt.run(args);\n            const rowsAffected = info.changes;\n            const lastInsertRowid = BigInt(info.lastInsertRowid);\n            return new ResultSetImpl([], [], [], rowsAffected, lastInsertRowid);\n        }\n    } catch (e) {\n        throw mapSqliteError(e);\n    }\n}\n\nfunction rowFromSql(\n    sqlRow: Array<unknown>,\n    columns: Array<string>,\n    intMode: IntMode,\n): Row {\n    const row = {};\n    // make sure that the \"length\" property is not enumerable\n    Object.defineProperty(row, \"length\", { value: sqlRow.length });\n    for (let i = 0; i < sqlRow.length; ++i) {\n        const value = valueFromSql(sqlRow[i], intMode);\n        Object.defineProperty(row, i, { value });\n\n        const column = columns[i];\n        if (!Object.hasOwn(row, column)) {\n            Object.defineProperty(row, column, {\n                value,\n                enumerable: true,\n                configurable: true,\n                writable: true,\n            });\n        }\n    }\n    return row as Row;\n}\n\nfunction valueFromSql(sqlValue: unknown, intMode: IntMode): Value {\n    if (typeof sqlValue === \"bigint\") {\n        if (intMode === \"number\") {\n            if (sqlValue < minSafeBigint || sqlValue > maxSafeBigint) {\n                throw new RangeError(\n                    \"Received integer which cannot be safely represented as a JavaScript number\",\n                );\n            }\n            return Number(sqlValue);\n        } else if (intMode === \"bigint\") {\n            return sqlValue;\n        } else if (intMode === \"string\") {\n            return \"\" + sqlValue;\n        } else {\n            throw new Error(\"Invalid value for IntMode\");\n        }\n    } else if (sqlValue instanceof Buffer) {\n        return sqlValue.buffer;\n    }\n    return sqlValue as Value;\n}\n\nconst minSafeBigint = -9007199254740991n;\nconst maxSafeBigint = 9007199254740991n;\n\nfunction valueToSql(value: InValue, intMode: IntMode): unknown {\n    if (typeof value === \"number\") {\n        if (!Number.isFinite(value)) {\n            throw new RangeError(\n                \"Only finite numbers (not Infinity or NaN) can be passed as arguments\",\n            );\n        }\n        return value;\n    } else if (typeof value === \"bigint\") {\n        if (value < minInteger || value > maxInteger) {\n            throw new RangeError(\n                \"bigint is too large to be represented as a 64-bit integer and passed as argument\",\n            );\n        }\n        return value;\n    } else if (typeof value === \"boolean\") {\n        switch (intMode) {\n            case \"bigint\":\n                return value ? 1n : 0n;\n            case \"string\":\n                return value ? \"1\" : \"0\";\n            default:\n                return value ? 1 : 0;\n        }\n    } else if (value instanceof ArrayBuffer) {\n        return Buffer.from(value);\n    } else if (value instanceof Date) {\n        return value.valueOf();\n    } else if (value === undefined) {\n        throw new TypeError(\n            \"undefined cannot be passed as argument to the database\",\n        );\n    } else {\n        return value;\n    }\n}\n\nconst minInteger = -9223372036854775808n;\nconst maxInteger = 9223372036854775807n;\n\nfunction executeMultiple(db: Database.Database, sql: string): void {\n    try {\n        db.exec(sql);\n    } catch (e) {\n        throw mapSqliteError(e);\n    }\n}\n\nfunction mapSqliteError(e: unknown): unknown {\n    if (e instanceof Database.SqliteError) {\n        const extendedCode = e.code;\n        const code = mapToBaseCode(e.rawCode);\n        return new LibsqlError(e.message, code, extendedCode, e.rawCode, e);\n    }\n    return e;\n}\n\n// Map SQLite raw error code to base error code string.\n// Extended error codes are (base | (extended << 8)), so base = rawCode & 0xFF\nfunction mapToBaseCode(rawCode: number | undefined): string {\n    if (rawCode === undefined) {\n        return \"SQLITE_UNKNOWN\";\n    }\n    const baseCode = rawCode & 0xff;\n    return (\n        sqliteErrorCodes[baseCode] ?? `SQLITE_UNKNOWN_${baseCode.toString()}`\n    );\n}\n\nconst sqliteErrorCodes: Record<number, string> = {\n    1: \"SQLITE_ERROR\",\n    2: \"SQLITE_INTERNAL\",\n    3: \"SQLITE_PERM\",\n    4: \"SQLITE_ABORT\",\n    5: \"SQLITE_BUSY\",\n    6: \"SQLITE_LOCKED\",\n    7: \"SQLITE_NOMEM\",\n    8: \"SQLITE_READONLY\",\n    9: \"SQLITE_INTERRUPT\",\n    10: \"SQLITE_IOERR\",\n    11: \"SQLITE_CORRUPT\",\n    12: \"SQLITE_NOTFOUND\",\n    13: \"SQLITE_FULL\",\n    14: \"SQLITE_CANTOPEN\",\n    15: \"SQLITE_PROTOCOL\",\n    16: \"SQLITE_EMPTY\",\n    17: \"SQLITE_SCHEMA\",\n    18: \"SQLITE_TOOBIG\",\n    19: \"SQLITE_CONSTRAINT\",\n    20: \"SQLITE_MISMATCH\",\n    21: \"SQLITE_MISUSE\",\n    22: \"SQLITE_NOLFS\",\n    23: \"SQLITE_AUTH\",\n    24: \"SQLITE_FORMAT\",\n    25: \"SQLITE_RANGE\",\n    26: \"SQLITE_NOTADB\",\n    27: \"SQLITE_NOTICE\",\n    28: \"SQLITE_WARNING\",\n};\n"
  },
  {
    "path": "packages/libsql-client/src/web.ts",
    "content": "import type { Config, Client } from \"@libsql/core/api\";\nimport { LibsqlError } from \"@libsql/core/api\";\nimport type { ExpandedConfig } from \"@libsql/core/config\";\nimport { expandConfig } from \"@libsql/core/config\";\nimport { supportedUrlLink } from \"@libsql/core/util\";\n\nimport { _createClient as _createWsClient } from \"./ws.js\";\nimport { _createClient as _createHttpClient } from \"./http.js\";\n\nexport * from \"@libsql/core/api\";\n\nexport function createClient(config: Config): Client {\n    return _createClient(expandConfig(config, true));\n}\n\n/** @private */\nexport function _createClient(config: ExpandedConfig): Client {\n    if (config.scheme === \"ws\" || config.scheme === \"wss\") {\n        return _createWsClient(config);\n    } else if (config.scheme === \"http\" || config.scheme === \"https\") {\n        return _createHttpClient(config);\n    } else {\n        throw new LibsqlError(\n            'The client that uses Web standard APIs supports only \"libsql:\", \"wss:\", \"ws:\", \"https:\" and \"http:\" URLs, ' +\n                `got ${JSON.stringify(config.scheme + \":\")}. For more information, please read ${supportedUrlLink}`,\n            \"URL_SCHEME_NOT_SUPPORTED\",\n        );\n    }\n}\n"
  },
  {
    "path": "packages/libsql-client/src/ws.ts",
    "content": "import * as hrana from \"@libsql/hrana-client\";\n\nimport type {\n    Config,\n    IntMode,\n    Client,\n    Transaction,\n    ResultSet,\n    InStatement,\n    InArgs,\n    Replicated,\n} from \"@libsql/core/api\";\nimport { TransactionMode, LibsqlError } from \"@libsql/core/api\";\nimport type { ExpandedConfig } from \"@libsql/core/config\";\nimport { expandConfig } from \"@libsql/core/config\";\nimport {\n    HranaTransaction,\n    executeHranaBatch,\n    stmtToHrana,\n    resultSetFromHrana,\n    mapHranaError,\n} from \"./hrana.js\";\nimport { SqlCache } from \"./sql_cache.js\";\nimport { encodeBaseUrl } from \"@libsql/core/uri\";\nimport { supportedUrlLink } from \"@libsql/core/util\";\nimport promiseLimit from \"promise-limit\";\n\nexport * from \"@libsql/core/api\";\n\nexport function createClient(config: Config): WsClient {\n    return _createClient(expandConfig(config, false));\n}\n\n/** @private */\nexport function _createClient(config: ExpandedConfig): WsClient {\n    if (config.scheme !== \"wss\" && config.scheme !== \"ws\") {\n        throw new LibsqlError(\n            'The WebSocket client supports only \"libsql:\", \"wss:\" and \"ws:\" URLs, ' +\n                `got ${JSON.stringify(config.scheme + \":\")}. For more information, please read ${supportedUrlLink}`,\n            \"URL_SCHEME_NOT_SUPPORTED\",\n        );\n    }\n\n    if (config.encryptionKey !== undefined) {\n        throw new LibsqlError(\n            \"Encryption key is not supported by the remote client.\",\n            \"ENCRYPTION_KEY_NOT_SUPPORTED\",\n        );\n    }\n\n    if (config.scheme === \"ws\" && config.tls) {\n        throw new LibsqlError(\n            `A \"ws:\" URL cannot opt into TLS by using ?tls=1`,\n            \"URL_INVALID\",\n        );\n    } else if (config.scheme === \"wss\" && !config.tls) {\n        throw new LibsqlError(\n            `A \"wss:\" URL cannot opt out of TLS by using ?tls=0`,\n            \"URL_INVALID\",\n        );\n    }\n\n    const url = encodeBaseUrl(config.scheme, config.authority, config.path);\n\n    let client: hrana.WsClient;\n    try {\n        client = hrana.openWs(url, config.authToken);\n    } catch (e) {\n        if (e instanceof hrana.WebSocketUnsupportedError) {\n            const suggestedScheme = config.scheme === \"wss\" ? \"https\" : \"http\";\n            const suggestedUrl = encodeBaseUrl(\n                suggestedScheme,\n                config.authority,\n                config.path,\n            );\n            throw new LibsqlError(\n                \"This environment does not support WebSockets, please switch to the HTTP client by using \" +\n                    `a \"${suggestedScheme}:\" URL (${JSON.stringify(suggestedUrl)}). ` +\n                    `For more information, please read ${supportedUrlLink}`,\n                \"WEBSOCKETS_NOT_SUPPORTED\",\n            );\n        }\n        throw mapHranaError(e);\n    }\n\n    return new WsClient(\n        client,\n        url,\n        config.authToken,\n        config.intMode,\n        config.concurrency,\n    );\n}\n\n// This object maintains state for a single WebSocket connection.\ninterface ConnState {\n    // The Hrana client (which corresponds to a single WebSocket).\n    client: hrana.WsClient;\n    // We can cache SQL texts on the server only if the server supports Hrana 2. But to get the server\n    // version, we need to wait for the WebSocket handshake to complete, so this value is initially\n    // `undefined`, until we find out the version.\n    useSqlCache: boolean | undefined;\n    // The cache of SQL texts stored on the server. Initially has capacity 0, but it is set to\n    // `sqlCacheCapacity` when `useSqlCache` is set to `true`.\n    sqlCache: SqlCache;\n    // The time when the connection was opened.\n    openTime: Date;\n    // Set of all `StreamState`-s that were opened from this connection. We can safely close the connection\n    // only when this is empty.\n    streamStates: Set<StreamState>;\n}\n\ninterface StreamState {\n    conn: ConnState;\n    stream: hrana.WsStream;\n}\n\nconst maxConnAgeMillis = 60 * 1000;\nconst sqlCacheCapacity = 100;\n\nexport class WsClient implements Client {\n    #url: URL;\n    #authToken: string | undefined;\n    #intMode: IntMode;\n    // State of the current connection. The `hrana.WsClient` inside may be closed at any moment due to an\n    // asynchronous error.\n    #connState: ConnState;\n    // If defined, this is a connection that will be used in the future, once it is ready.\n    #futureConnState: ConnState | undefined;\n    closed: boolean;\n    protocol: \"ws\";\n    #isSchemaDatabase: Promise<boolean> | undefined;\n    #promiseLimitFunction: ReturnType<typeof promiseLimit<any>>;\n\n    /** @private */\n    constructor(\n        client: hrana.WsClient,\n        url: URL,\n        authToken: string | undefined,\n        intMode: IntMode,\n        concurrency: number | undefined,\n    ) {\n        this.#url = url;\n        this.#authToken = authToken;\n        this.#intMode = intMode;\n        this.#connState = this.#openConn(client);\n        this.#futureConnState = undefined;\n        this.closed = false;\n        this.protocol = \"ws\";\n        this.#promiseLimitFunction = promiseLimit<any>(concurrency);\n    }\n\n    private async limit<T>(fn: () => Promise<T>): Promise<T> {\n        return this.#promiseLimitFunction(fn);\n    }\n\n    async execute(\n        stmtOrSql: InStatement | string,\n        args?: InArgs,\n    ): Promise<ResultSet> {\n        let stmt: InStatement;\n\n        if (typeof stmtOrSql === \"string\") {\n            stmt = {\n                sql: stmtOrSql,\n                args: args || [],\n            };\n        } else {\n            stmt = stmtOrSql;\n        }\n\n        return this.limit<ResultSet>(async () => {\n            const streamState = await this.#openStream();\n            try {\n                const hranaStmt = stmtToHrana(stmt);\n\n                // Schedule all operations synchronously, so they will be pipelined and executed in a single\n                // network roundtrip.\n                streamState.conn.sqlCache.apply([hranaStmt]);\n                const hranaRowsPromise = streamState.stream.query(hranaStmt);\n                streamState.stream.closeGracefully();\n\n                const hranaRowsResult = await hranaRowsPromise;\n\n                return resultSetFromHrana(hranaRowsResult);\n            } catch (e) {\n                throw mapHranaError(e);\n            } finally {\n                this._closeStream(streamState);\n            }\n        });\n    }\n\n    async batch(\n        stmts: Array<InStatement | [string, InArgs?]>,\n        mode: TransactionMode = \"deferred\",\n    ): Promise<Array<ResultSet>> {\n        return this.limit<Array<ResultSet>>(async () => {\n            const streamState = await this.#openStream();\n            try {\n                const normalizedStmts = stmts.map((stmt) => {\n                    if (Array.isArray(stmt)) {\n                        return {\n                            sql: stmt[0],\n                            args: stmt[1] || [],\n                        };\n                    }\n                    return stmt;\n                });\n\n                const hranaStmts = normalizedStmts.map(stmtToHrana);\n                const version = await streamState.conn.client.getVersion();\n\n                // Schedule all operations synchronously, so they will be pipelined and executed in a single\n                // network roundtrip.\n                streamState.conn.sqlCache.apply(hranaStmts);\n                const batch = streamState.stream.batch(version >= 3);\n                const resultsPromise = executeHranaBatch(\n                    mode,\n                    version,\n                    batch,\n                    hranaStmts,\n                );\n\n                const results = await resultsPromise;\n\n                return results;\n            } catch (e) {\n                throw mapHranaError(e);\n            } finally {\n                this._closeStream(streamState);\n            }\n        });\n    }\n\n    async migrate(stmts: Array<InStatement>): Promise<Array<ResultSet>> {\n        return this.limit<Array<ResultSet>>(async () => {\n            const streamState = await this.#openStream();\n            try {\n                const hranaStmts = stmts.map(stmtToHrana);\n                const version = await streamState.conn.client.getVersion();\n\n                // Schedule all operations synchronously, so they will be pipelined and executed in a single\n                // network roundtrip.\n                const batch = streamState.stream.batch(version >= 3);\n                const resultsPromise = executeHranaBatch(\n                    \"deferred\",\n                    version,\n                    batch,\n                    hranaStmts,\n                    true,\n                );\n\n                const results = await resultsPromise;\n\n                return results;\n            } catch (e) {\n                throw mapHranaError(e);\n            } finally {\n                this._closeStream(streamState);\n            }\n        });\n    }\n\n    async transaction(mode: TransactionMode = \"write\"): Promise<WsTransaction> {\n        return this.limit<WsTransaction>(async () => {\n            const streamState = await this.#openStream();\n            try {\n                const version = await streamState.conn.client.getVersion();\n                // the BEGIN statement will be batched with the first statement on the transaction to save a\n                // network roundtrip\n                return new WsTransaction(this, streamState, mode, version);\n            } catch (e) {\n                this._closeStream(streamState);\n                throw mapHranaError(e);\n            }\n        });\n    }\n\n    async executeMultiple(sql: string): Promise<void> {\n        return this.limit<void>(async () => {\n            const streamState = await this.#openStream();\n            try {\n                // Schedule all operations synchronously, so they will be pipelined and executed in a single\n                // network roundtrip.\n                const promise = streamState.stream.sequence(sql);\n                streamState.stream.closeGracefully();\n\n                await promise;\n            } catch (e) {\n                throw mapHranaError(e);\n            } finally {\n                this._closeStream(streamState);\n            }\n        });\n    }\n\n    sync(): Promise<Replicated> {\n        throw new LibsqlError(\n            \"sync not supported in ws mode\",\n            \"SYNC_NOT_SUPPORTED\",\n        );\n    }\n\n    async #openStream(): Promise<StreamState> {\n        if (this.closed) {\n            throw new LibsqlError(\"The client is closed\", \"CLIENT_CLOSED\");\n        }\n\n        const now = new Date();\n\n        const ageMillis = now.valueOf() - this.#connState.openTime.valueOf();\n        if (\n            ageMillis > maxConnAgeMillis &&\n            this.#futureConnState === undefined\n        ) {\n            // The existing connection is too old, let's open a new one.\n            const futureConnState = this.#openConn();\n            this.#futureConnState = futureConnState;\n\n            // However, if we used `futureConnState` immediately, we would introduce additional latency,\n            // because we would have to wait for the WebSocket handshake to complete, even though we may a\n            // have perfectly good existing connection in `this.#connState`!\n            //\n            // So we wait until the `hrana.Client.getVersion()` operation completes (which happens when the\n            // WebSocket hanshake completes), and only then we replace `this.#connState` with\n            // `futureConnState`, which is stored in `this.#futureConnState` in the meantime.\n            futureConnState.client.getVersion().then(\n                (_version) => {\n                    if (this.#connState !== futureConnState) {\n                        // We need to close `this.#connState` before we replace it. However, it is possible\n                        // that `this.#connState` has already been replaced: see the code below.\n                        if (this.#connState.streamStates.size === 0) {\n                            this.#connState.client.close();\n                        } else {\n                            // If there are existing streams on the connection, we must not close it, because\n                            // these streams would be broken. The last stream to be closed will also close the\n                            // connection in `_closeStream()`.\n                        }\n                    }\n\n                    this.#connState = futureConnState;\n                    this.#futureConnState = undefined;\n                },\n                (_e) => {\n                    // If the new connection could not be established, let's just ignore the error and keep\n                    // using the existing connection.\n                    this.#futureConnState = undefined;\n                },\n            );\n        }\n\n        if (this.#connState.client.closed) {\n            // An error happened on this connection and it has been closed. Let's try to seamlessly reconnect.\n            try {\n                if (this.#futureConnState !== undefined) {\n                    // We are already in the process of opening a new connection, so let's just use it\n                    // immediately.\n                    this.#connState = this.#futureConnState;\n                } else {\n                    this.#connState = this.#openConn();\n                }\n            } catch (e) {\n                throw mapHranaError(e);\n            }\n        }\n\n        const connState = this.#connState;\n        try {\n            // Now we wait for the WebSocket handshake to complete (if it hasn't completed yet). Note that\n            // this does not increase latency, because any messages that we would send on the WebSocket before\n            // the handshake would be queued until the handshake is completed anyway.\n            if (connState.useSqlCache === undefined) {\n                connState.useSqlCache =\n                    (await connState.client.getVersion()) >= 2;\n                if (connState.useSqlCache) {\n                    connState.sqlCache.capacity = sqlCacheCapacity;\n                }\n            }\n\n            const stream = connState.client.openStream();\n            stream.intMode = this.#intMode;\n            const streamState = { conn: connState, stream };\n            connState.streamStates.add(streamState);\n            return streamState;\n        } catch (e) {\n            throw mapHranaError(e);\n        }\n    }\n\n    #openConn(client?: hrana.WsClient): ConnState {\n        try {\n            client ??= hrana.openWs(this.#url, this.#authToken);\n            return {\n                client,\n                useSqlCache: undefined,\n                sqlCache: new SqlCache(client, 0),\n                openTime: new Date(),\n                streamStates: new Set(),\n            };\n        } catch (e) {\n            throw mapHranaError(e);\n        }\n    }\n\n    async reconnect(): Promise<void> {\n        try {\n            for (const st of Array.from(this.#connState.streamStates)) {\n                try {\n                    st.stream.close();\n                } catch {}\n            }\n            this.#connState.client.close();\n        } catch {}\n\n        if (this.#futureConnState) {\n            try {\n                this.#futureConnState.client.close();\n            } catch {}\n            this.#futureConnState = undefined;\n        }\n\n        const next = this.#openConn();\n        const version = await next.client.getVersion();\n\n        next.useSqlCache = version >= 2;\n        if (next.useSqlCache) {\n            next.sqlCache.capacity = sqlCacheCapacity;\n        }\n\n        this.#connState = next;\n        this.closed = false;\n    }\n\n    _closeStream(streamState: StreamState): void {\n        streamState.stream.close();\n\n        const connState = streamState.conn;\n        connState.streamStates.delete(streamState);\n        if (\n            connState.streamStates.size === 0 &&\n            connState !== this.#connState\n        ) {\n            // We are not using this connection anymore and this is the last stream that was using it, so we\n            // must close it now.\n            connState.client.close();\n        }\n    }\n\n    close(): void {\n        this.#connState.client.close();\n        this.closed = true;\n        if (this.#futureConnState) {\n            try {\n                this.#futureConnState.client.close();\n            } catch {}\n            this.#futureConnState = undefined;\n        }\n        this.closed = true;\n    }\n}\n\nexport class WsTransaction extends HranaTransaction implements Transaction {\n    #client: WsClient;\n    #streamState: StreamState;\n\n    /** @private */\n    constructor(\n        client: WsClient,\n        state: StreamState,\n        mode: TransactionMode,\n        version: hrana.ProtocolVersion,\n    ) {\n        super(mode, version);\n        this.#client = client;\n        this.#streamState = state;\n    }\n\n    /** @private */\n    override _getStream(): hrana.Stream {\n        return this.#streamState.stream;\n    }\n\n    /** @private */\n    override _getSqlCache(): SqlCache {\n        return this.#streamState.conn.sqlCache;\n    }\n\n    override close(): void {\n        this.#client._closeStream(this.#streamState);\n    }\n\n    override get closed(): boolean {\n        return this.#streamState.stream.closed;\n    }\n}\n"
  },
  {
    "path": "packages/libsql-client/tsconfig.base.json",
    "content": "{\n    \"compilerOptions\": {\n        \"moduleResolution\": \"node\",\n        \"lib\": [\"esnext\"],\n        \"target\": \"esnext\",\n        \"esModuleInterop\": true,\n        \"isolatedModules\": true,\n        \"rootDir\": \"src/\",\n        \"strict\": true\n    },\n    \"include\": [\"src/\"],\n    \"exclude\": [\"**/__tests__\"]\n}\n"
  },
  {
    "path": "packages/libsql-client/tsconfig.build-cjs.json",
    "content": "{\n    \"extends\": \"./tsconfig.base.json\",\n    \"compilerOptions\": {\n        \"module\": \"commonjs\",\n        \"declaration\": false,\n        \"outDir\": \"./lib-cjs/\"\n    }\n}\n"
  },
  {
    "path": "packages/libsql-client/tsconfig.build-esm.json",
    "content": "{\n    \"extends\": \"./tsconfig.base.json\",\n    \"compilerOptions\": {\n        \"module\": \"esnext\",\n        \"declaration\": true,\n        \"outDir\": \"./lib-esm/\"\n    }\n}\n"
  },
  {
    "path": "packages/libsql-client/tsconfig.json",
    "content": "{\n    \"extends\": \"./tsconfig.base.json\",\n    \"compilerOptions\": {\n        \"noEmit\": true,\n        \"incremental\": true\n    }\n}\n"
  },
  {
    "path": "packages/libsql-client/typedoc.json",
    "content": "{\n    \"entryPoints\": [\"src/node.ts\"],\n    \"out\": \"docs\",\n    \"excludePrivate\": true,\n    \"excludeInternal\": true,\n    \"visibilityFilters\": {\n        \"inherited\": true,\n        \"external\": true\n    },\n    \"includeVersion\": true\n}\n"
  },
  {
    "path": "packages/libsql-client-wasm/LICENSE",
    "content": "MIT License\n\nCopyright (c) 2023 libSQL\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "packages/libsql-client-wasm/examples/browser/README.md",
    "content": "# libSQL Wasm example for browsers\n\n## Building\n\nRun the following in the `packages/libsql-client-wasm` directory:\n\n```\nnpm run build\n```\n\nRun the following in this directory:\n\n```\nnpm i\n./node_modules/.bin/esbuild --target=safari16 index.js --bundle --outfile=dist/out.js --format=esm\ncp ../../../../node_modules/@libsql/libsql-wasm-experimental/sqlite-wasm/jswasm/sqlite3.wasm dist\n```\n\nand open the app in browser:\n\n```\nnpx http-server -o\n```\n"
  },
  {
    "path": "packages/libsql-client-wasm/examples/browser/index.html",
    "content": "<html>\n    <head>\n        <meta charset=\"utf-8\" />\n        <title>libSQL SDK Wasm Demo</title>\n        <script type=\"module\" src=\"dist/out.js\"></script>\n    </head>\n    <body>\n        <h1>Hello libSQL and Wasm!</h1>\n    </body>\n</html>\n"
  },
  {
    "path": "packages/libsql-client-wasm/examples/browser/index.js",
    "content": "import { createClient } from \"@libsql/client-wasm\";\n\nasync function main() {\n    const config = {\n        url: \"file:local.db\",\n    };\n    const db = await createClient(config);\n    const rs = await db.execute(\"SELECT * FROM users\");\n    console.log(rs);\n}\n\nmain().catch((error) => {\n    console.log(error);\n});\n"
  },
  {
    "path": "packages/libsql-client-wasm/examples/browser/package.json",
    "content": "{\n    \"name\": \"browser\",\n    \"version\": \"1.0.0\",\n    \"description\": \"\",\n    \"main\": \"index.js\",\n    \"type\": \"module\",\n    \"scripts\": {\n        \"test\": \"echo \\\"Error: no test specified\\\" && exit 1\"\n    },\n    \"author\": \"\",\n    \"license\": \"MIT\",\n    \"dependencies\": {\n        \"@libsql/client-wasm\": \"../..\"\n    },\n    \"devDependencies\": {\n        \"esbuild\": \"0.19.11\"\n    }\n}\n"
  },
  {
    "path": "packages/libsql-client-wasm/examples/node/index.js",
    "content": "import { createClient } from \"@libsql/client-wasm\";\n\nasync function main() {\n    const config = {\n        url: \"file:local.db\",\n    };\n    const db = await createClient(config);\n    await db.execute(\"CREATE TABLE users (id INT PRIMARY KEY, username TEXT)\");\n    await db.execute(\"INSERT INTO users VALUES (1, 'penberg')\");\n    const rs = await db.execute(\"SELECT * FROM users\");\n    console.log(rs);\n}\n\nmain().catch((error) => {\n    console.log(error);\n});\n"
  },
  {
    "path": "packages/libsql-client-wasm/examples/node/package.json",
    "content": "{\n    \"name\": \"nodejs\",\n    \"version\": \"1.0.0\",\n    \"description\": \"\",\n    \"main\": \"index.js\",\n    \"type\": \"module\",\n    \"scripts\": {\n        \"test\": \"echo \\\"Error: no test specified\\\" && exit 1\"\n    },\n    \"author\": \"\",\n    \"license\": \"MIT\",\n    \"dependencies\": {\n        \"@libsql/client-wasm\": \"../../\"\n    }\n}\n"
  },
  {
    "path": "packages/libsql-client-wasm/jest.config.js",
    "content": "export default {\n    preset: \"ts-jest/presets/default-esm\",\n    moduleNameMapper: {\n        \"^(\\\\.{1,2}/.*)\\\\.js$\": \"$1\",\n    },\n    testMatch: [\"**/__tests__/*.test.[jt]s\"],\n};\n"
  },
  {
    "path": "packages/libsql-client-wasm/package-cjs.json",
    "content": "{\n    \"type\": \"commonjs\"\n}\n"
  },
  {
    "path": "packages/libsql-client-wasm/package.json",
    "content": "{\n    \"name\": \"@libsql/client-wasm\",\n    \"version\": \"0.17.2\",\n    \"keywords\": [\n        \"libsql\",\n        \"database\",\n        \"sqlite\",\n        \"serverless\",\n        \"vercel\",\n        \"netlify\",\n        \"lambda\"\n    ],\n    \"description\": \"libSQL driver for TypeScript and JavaScript\",\n    \"repository\": {\n        \"type\": \"git\",\n        \"url\": \"git+https://github.com/tursodatabase/libsql-client-ts\",\n        \"directory\": \"packages/libsql-client-wasm\"\n    },\n    \"authors\": [\n        \"Jan Špaček <honza@chiselstrike.com>\",\n        \"Pekka Enberg <penberg@chiselstrike.com>\",\n        \"Jan Plhak <jp@chiselstrike.com>\"\n    ],\n    \"license\": \"MIT\",\n    \"type\": \"module\",\n    \"bundledDependencies\": [\n        \"@libsql/libsql-wasm-experimental\"\n    ],\n    \"main\": \"lib-esm/wasm.js\",\n    \"types\": \"lib-esm/wasm.d.ts\",\n    \"exports\": {\n        \".\": {\n            \"types\": \"./lib-esm/wasm.d.ts\",\n            \"import\": {\n                \"default\": \"./lib-esm/wasm.js\"\n            }\n        }\n    },\n    \"typesVersions\": {\n        \"*\": {\n            \".\": [\n                \"./lib-esm/wasm.d.ts\"\n            ]\n        }\n    },\n    \"files\": [\n        \"lib-esm/**\"\n    ],\n    \"scripts\": {\n        \"prepublishOnly\": \"npm run build\",\n        \"prebuild\": \"rm -rf ./lib-esm && npm run bundle\",\n        \"build\": \"npm run build:esm\",\n        \"build:esm\": \"tsc -p tsconfig.build-esm.json\",\n        \"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\",\n        \"format:check\": \"prettier --check .\",\n        \"test\": \"jest --runInBand\",\n        \"typecheck\": \"tsc --noEmit\",\n        \"typedoc\": \"rm -rf ./docs && typedoc\"\n    },\n    \"dependencies\": {\n        \"@libsql/core\": \"^0.17.0\",\n        \"@libsql/libsql-wasm-experimental\": \"^0.0.2\",\n        \"js-base64\": \"^3.7.5\"\n    },\n    \"devDependencies\": {\n        \"@types/jest\": \"^29.2.5\",\n        \"jest\": \"^29.3.1\",\n        \"ts-jest\": \"^29.0.5\",\n        \"typedoc\": \"^0.23.28\",\n        \"typescript\": \"^4.9.4\"\n    }\n}\n"
  },
  {
    "path": "packages/libsql-client-wasm/src/wasm.ts",
    "content": "import sqlite3InitModule from \"@libsql/libsql-wasm-experimental\";\n\nimport type {\n    Database,\n    SqlValue,\n    Sqlite3Static,\n} from \"@libsql/libsql-wasm-experimental\";\n\nimport type {\n    Config,\n    IntMode,\n    Client,\n    Transaction,\n    TransactionMode,\n    ResultSet,\n    Row,\n    Value,\n    InValue,\n    InStatement,\n    InArgs,\n    Replicated,\n} from \"@libsql/core/api\";\nimport { LibsqlError } from \"@libsql/core/api\";\nimport type { ExpandedConfig } from \"@libsql/core/config\";\nimport { expandConfig } from \"@libsql/core/config\";\nimport {\n    supportedUrlLink,\n    transactionModeToBegin,\n    ResultSetImpl,\n} from \"@libsql/core/util\";\n\nexport * from \"@libsql/core/api\";\n\nconst sqlite3 = await sqlite3InitModule();\n\nexport function createClient(config: Config): Client {\n    return _createClient(expandConfig(config, true));\n}\n\n/** @private */\nexport function _createClient(config: ExpandedConfig): Client {\n    if (config.scheme !== \"file\") {\n        throw new LibsqlError(\n            `URL scheme ${JSON.stringify(config.scheme + \":\")} is not supported by the local sqlite3 client. ` +\n                `For more information, please read ${supportedUrlLink}`,\n            \"URL_SCHEME_NOT_SUPPORTED\",\n        );\n    }\n\n    if (config.encryptionKey !== undefined) {\n        throw new LibsqlError(\n            \"Encryption key is not supported by the Wasm client.\",\n            \"ENCRYPTION_KEY_NOT_SUPPORTED\",\n        );\n    }\n\n    const authority = config.authority;\n    if (authority !== undefined) {\n        const host = authority.host.toLowerCase();\n        if (host !== \"\" && host !== \"localhost\") {\n            throw new LibsqlError(\n                `Invalid host in file URL: ${JSON.stringify(authority.host)}. ` +\n                    'A \"file:\" URL with an absolute path should start with one slash (\"file:/absolute/path.db\") ' +\n                    'or with three slashes (\"file:///absolute/path.db\"). ' +\n                    `For more information, please read ${supportedUrlLink}`,\n                \"URL_INVALID\",\n            );\n        }\n\n        if (authority.port !== undefined) {\n            throw new LibsqlError(\"File URL cannot have a port\", \"URL_INVALID\");\n        }\n        if (authority.userinfo !== undefined) {\n            throw new LibsqlError(\n                \"File URL cannot have username and password\",\n                \"URL_INVALID\",\n            );\n        }\n    }\n\n    const path = config.path;\n    const options = {\n        authToken: config.authToken,\n        syncUrl: config.syncUrl,\n    };\n\n    const db: Database = new sqlite3.oo1.DB(path, \"c\");\n\n    executeStmt(\n        db,\n        \"SELECT 1 AS checkThatTheDatabaseCanBeOpened\",\n        config.intMode,\n    );\n\n    return new Sqlite3Client(sqlite3, path, /*options,*/ db, config.intMode);\n}\n\nfunction inTransaction(db: Database): boolean {\n    return db.getAutocommit() == 0;\n}\n\nexport class Sqlite3Client implements Client {\n    #sqlite3: Sqlite3Static;\n    #path: string;\n    #db: Database | null;\n    #intMode: IntMode;\n    closed: boolean;\n    protocol: \"file\";\n\n    /** @private */\n    constructor(\n        sqlite3: Sqlite3Static,\n        path: string,\n        /*options: Database.Options,*/ db: Database,\n        intMode: IntMode,\n    ) {\n        this.#sqlite3 = sqlite3;\n        this.#path = path;\n        //this.#options = options;\n        this.#db = db;\n        this.#intMode = intMode;\n        this.closed = false;\n        this.protocol = \"file\";\n    }\n\n    async execute(\n        stmtOrSql: InStatement | string,\n        args?: InArgs,\n    ): Promise<ResultSet> {\n        let stmt: InStatement;\n\n        if (typeof stmtOrSql === \"string\") {\n            stmt = {\n                sql: stmtOrSql,\n                args: args || [],\n            };\n        } else {\n            stmt = stmtOrSql;\n        }\n\n        this.#checkNotClosed();\n        return executeStmt(this.#getDb(), stmt, this.#intMode);\n    }\n\n    async batch(\n        stmts: Array<InStatement>,\n        mode: TransactionMode = \"deferred\",\n    ): Promise<Array<ResultSet>> {\n        this.#checkNotClosed();\n        const db = this.#getDb();\n        try {\n            executeStmt(db, transactionModeToBegin(mode), this.#intMode);\n            const resultSets = stmts.map((stmt) => {\n                if (!inTransaction(db)) {\n                    throw new LibsqlError(\n                        \"The transaction has been rolled back\",\n                        \"TRANSACTION_CLOSED\",\n                    );\n                }\n                return executeStmt(db, stmt, this.#intMode);\n            });\n            executeStmt(db, \"COMMIT\", this.#intMode);\n            return resultSets;\n        } finally {\n            if (inTransaction(db)) {\n                executeStmt(db, \"ROLLBACK\", this.#intMode);\n            }\n        }\n    }\n\n    async migrate(stmts: Array<InStatement>): Promise<Array<ResultSet>> {\n        this.#checkNotClosed();\n        const db = this.#getDb();\n        try {\n            executeStmt(db, \"PRAGMA foreign_keys=off\", this.#intMode);\n            executeStmt(db, transactionModeToBegin(\"deferred\"), this.#intMode);\n            const resultSets = stmts.map((stmt) => {\n                if (!inTransaction(db)) {\n                    throw new LibsqlError(\n                        \"The transaction has been rolled back\",\n                        \"TRANSACTION_CLOSED\",\n                    );\n                }\n                return executeStmt(db, stmt, this.#intMode);\n            });\n            executeStmt(db, \"COMMIT\", this.#intMode);\n            return resultSets;\n        } finally {\n            if (inTransaction(db)) {\n                executeStmt(db, \"ROLLBACK\", this.#intMode);\n            }\n            executeStmt(db, \"PRAGMA foreign_keys=on\", this.#intMode);\n        }\n    }\n\n    async transaction(mode: TransactionMode = \"write\"): Promise<Transaction> {\n        const db = this.#getDb();\n        executeStmt(db, transactionModeToBegin(mode), this.#intMode);\n        this.#db = null; // A new connection will be lazily created on next use\n        return new Sqlite3Transaction(db, this.#intMode);\n    }\n\n    async executeMultiple(sql: string): Promise<void> {\n        this.#checkNotClosed();\n        const db = this.#getDb();\n        try {\n            return executeMultiple(db, sql);\n        } finally {\n            if (inTransaction(db)) {\n                executeStmt(db, \"ROLLBACK\", this.#intMode);\n            }\n        }\n    }\n\n    async sync(): Promise<Replicated> {\n        throw new LibsqlError(\n            \"sync not supported in wasm mode\",\n            \"SYNC_NOT_SUPPORTED\",\n        );\n    }\n\n    async reconnect(): Promise<void> {\n        try {\n            if (!this.closed && this.#db !== null) {\n                this.#db.close();\n            }\n        } finally {\n            this.#db = new this.#sqlite3.oo1.DB(this.#path, \"c\");\n            this.closed = false;\n        }\n    }\n\n    close(): void {\n        this.closed = true;\n        if (this.#db !== null) {\n            this.#db.close();\n            this.#db = null;\n        }\n    }\n\n    #checkNotClosed(): void {\n        if (this.closed) {\n            throw new LibsqlError(\"The client is closed\", \"CLIENT_CLOSED\");\n        }\n    }\n\n    // Lazily creates the database connection and returns it\n    #getDb(): Database {\n        if (this.#db === null) {\n            this.#db = new sqlite3.oo1.DB(this.#path, \"c\");\n        }\n        return this.#db;\n    }\n}\n\nexport class Sqlite3Transaction implements Transaction {\n    #database: Database;\n    #intMode: IntMode;\n\n    /** @private */\n    constructor(database: Database, intMode: IntMode) {\n        this.#database = database;\n        this.#intMode = intMode;\n    }\n\n    async execute(stmt: InStatement): Promise<ResultSet> {\n        this.#checkNotClosed();\n        return executeStmt(this.#database, stmt, this.#intMode);\n    }\n\n    async batch(stmts: Array<InStatement>): Promise<Array<ResultSet>> {\n        return stmts.map((stmt) => {\n            this.#checkNotClosed();\n            return executeStmt(this.#database, stmt, this.#intMode);\n        });\n    }\n\n    async executeMultiple(sql: string): Promise<void> {\n        this.#checkNotClosed();\n        return executeMultiple(this.#database, sql);\n    }\n\n    async rollback(): Promise<void> {\n        if (!this.#database.isOpen()) {\n            return;\n        }\n        this.#checkNotClosed();\n        executeStmt(this.#database, \"ROLLBACK\", this.#intMode);\n    }\n\n    async commit(): Promise<void> {\n        this.#checkNotClosed();\n        executeStmt(this.#database, \"COMMIT\", this.#intMode);\n    }\n\n    close(): void {\n        if (inTransaction(this.#database)) {\n            executeStmt(this.#database, \"ROLLBACK\", this.#intMode);\n        }\n    }\n\n    get closed(): boolean {\n        return !inTransaction(this.#database);\n    }\n\n    #checkNotClosed(): void {\n        if (this.closed) {\n            throw new LibsqlError(\n                \"The transaction is closed\",\n                \"TRANSACTION_CLOSED\",\n            );\n        }\n    }\n}\n\nfunction executeStmt(\n    db: Database,\n    stmt: InStatement,\n    intMode: IntMode,\n): ResultSet {\n    let sql: string;\n    let args: Array<SqlValue> | Record<string, SqlValue>;\n    if (typeof stmt === \"string\") {\n        sql = stmt;\n        args = [];\n    } else {\n        sql = stmt.sql;\n        if (Array.isArray(stmt.args)) {\n            args = stmt.args.map((value) => valueToSql(value, intMode));\n        } else {\n            args = {};\n            for (const name in stmt.args) {\n                const argName =\n                    name[0] === \"@\" || name[0] === \"$\" || name[0] === \":\"\n                        ? name.substring(1)\n                        : name;\n                args[argName] = valueToSql(stmt.args[name], intMode);\n            }\n        }\n    }\n\n    try {\n        const sqlStmt = db.prepare(sql);\n\n        // TODO: sqlStmt.safeIntegers(true);\n\n        let returnsData = sqlStmt.columnCount > 0;\n\n        if (Array.isArray(args)) {\n            for (let i = 0; i < args.length; ++i) {\n                const value = args[i];\n                sqlStmt.bind(i + 1, value);\n            }\n        } else {\n            for (const argName in args) {\n                const idx = sqlStmt.getParamIndex(argName)!;\n                const value = args[argName];\n                sqlStmt.bind(idx, value);\n            }\n        }\n        if (returnsData) {\n            let columns: string[] = sqlStmt.getColumnNames();\n            let columnTypes: string[] = [];\n            let rows: Row[] = [];\n            for (;;) {\n                if (!sqlStmt.step()) {\n                    break;\n                }\n                const values: unknown[] = sqlStmt.get([]);\n                rows.push(rowFromSql(values, columns, intMode));\n            }\n            const rowsAffected = 0;\n            const lastInsertRowid = undefined;\n            return new ResultSetImpl(\n                columns,\n                columnTypes,\n                rows,\n                rowsAffected,\n                lastInsertRowid,\n            );\n        } else {\n            sqlStmt.step(); // TODO: check return value\n            const rowsAffected = db.changes();\n            const lastInsertRowid = BigInt(db.lastInsertRowid());\n            return new ResultSetImpl([], [], [], rowsAffected, lastInsertRowid);\n        }\n    } catch (e) {\n        throw mapSqliteError(e);\n    }\n}\n\nfunction rowFromSql(\n    sqlRow: Array<unknown>,\n    columns: Array<string>,\n    intMode: IntMode,\n): Row {\n    const row = {};\n    // make sure that the \"length\" property is not enumerable\n    Object.defineProperty(row, \"length\", { value: sqlRow.length });\n    for (let i = 0; i < sqlRow.length; ++i) {\n        const value = valueFromSql(sqlRow[i], intMode);\n        Object.defineProperty(row, i, { value });\n\n        const column = columns[i];\n        if (!Object.hasOwn(row, column)) {\n            Object.defineProperty(row, column, {\n                value,\n                enumerable: true,\n                configurable: true,\n                writable: true,\n            });\n        }\n    }\n    return row as Row;\n}\n\nfunction valueFromSql(sqlValue: unknown, intMode: IntMode): Value {\n    if (typeof sqlValue === \"bigint\") {\n        if (intMode === \"number\") {\n            if (sqlValue < minSafeBigint || sqlValue > maxSafeBigint) {\n                throw new RangeError(\n                    \"Received integer which cannot be safely represented as a JavaScript number\",\n                );\n            }\n            return Number(sqlValue);\n        } else if (intMode === \"bigint\") {\n            return sqlValue;\n        } else if (intMode === \"string\") {\n            return \"\" + sqlValue;\n        } else {\n            throw new Error(\"Invalid value for IntMode\");\n        }\n    }\n    return sqlValue as Value;\n}\n\nconst minSafeBigint = -9007199254740991n;\nconst maxSafeBigint = 9007199254740991n;\n\nfunction valueToSql(value: InValue, intMode: IntMode): SqlValue {\n    if (typeof value === \"number\") {\n        if (!Number.isFinite(value)) {\n            throw new RangeError(\n                \"Only finite numbers (not Infinity or NaN) can be passed as arguments\",\n            );\n        }\n        return value;\n    } else if (typeof value === \"bigint\") {\n        if (value < minInteger || value > maxInteger) {\n            throw new RangeError(\n                \"bigint is too large to be represented as a 64-bit integer and passed as argument\",\n            );\n        }\n        return value;\n    } else if (typeof value === \"boolean\") {\n        switch (intMode) {\n            case \"bigint\":\n                return value ? 1n : 0n;\n            case \"string\":\n                return value ? \"1\" : \"0\";\n            default:\n                return value ? 1 : 0;\n        }\n    } else if (value instanceof Date) {\n        return value.valueOf();\n    } else if (value === undefined) {\n        throw new TypeError(\n            \"undefined cannot be passed as argument to the database\",\n        );\n    } else {\n        return value;\n    }\n}\n\nconst minInteger = -9223372036854775808n;\nconst maxInteger = 9223372036854775807n;\n\nfunction executeMultiple(db: Database, sql: string): void {\n    try {\n        db.exec(sql);\n    } catch (e) {\n        throw mapSqliteError(e);\n    }\n}\n\nfunction mapSqliteError(e: unknown): unknown {\n    // TODO: Map to LibsqlError\n    return e;\n}\n"
  },
  {
    "path": "packages/libsql-client-wasm/tsconfig.base.json",
    "content": "{\n    \"compilerOptions\": {\n        \"module\": \"es2022\",\n        \"moduleResolution\": \"node\",\n        \"lib\": [\"esnext\", \"dom\"],\n        \"target\": \"es2022\",\n        \"esModuleInterop\": true,\n        \"isolatedModules\": true,\n        \"rootDir\": \"src/\",\n        \"strict\": true\n    },\n    \"include\": [\"src/\"],\n    \"exclude\": [\"**/__tests__\"]\n}\n"
  },
  {
    "path": "packages/libsql-client-wasm/tsconfig.build-esm.json",
    "content": "{\n    \"extends\": \"./tsconfig.base.json\",\n    \"compilerOptions\": {\n        \"module\": \"esnext\",\n        \"declaration\": true,\n        \"outDir\": \"./lib-esm/\"\n    }\n}\n"
  },
  {
    "path": "packages/libsql-client-wasm/tsconfig.json",
    "content": "{\n    \"extends\": \"./tsconfig.base.json\",\n    \"compilerOptions\": {\n        \"noEmit\": true,\n        \"incremental\": true\n    }\n}\n"
  },
  {
    "path": "packages/libsql-client-wasm/typedoc.json",
    "content": "{\n    \"entryPoints\": [\"src/node.ts\"],\n    \"out\": \"docs\",\n    \"excludePrivate\": true,\n    \"excludeInternal\": true,\n    \"visibilityFilters\": {\n        \"inherited\": true,\n        \"external\": true\n    },\n    \"includeVersion\": true\n}\n"
  },
  {
    "path": "packages/libsql-core/jest.config.js",
    "content": "export default {\n    preset: \"ts-jest/presets/default-esm\",\n    moduleNameMapper: {\n        \"^(\\\\.{1,2}/.*)\\\\.js$\": \"$1\",\n    },\n    testMatch: [\"**/__tests__/*.test.[jt]s\"],\n};\n"
  },
  {
    "path": "packages/libsql-core/package-cjs.json",
    "content": "{\n    \"type\": \"commonjs\"\n}\n"
  },
  {
    "path": "packages/libsql-core/package.json",
    "content": "{\n    \"name\": \"@libsql/core\",\n    \"version\": \"0.17.2\",\n    \"keywords\": [\n        \"libsql\",\n        \"database\",\n        \"sqlite\",\n        \"serverless\",\n        \"vercel\",\n        \"netlify\",\n        \"lambda\"\n    ],\n    \"description\": \"libSQL driver for TypeScript and JavaScript\",\n    \"repository\": {\n        \"type\": \"git\",\n        \"url\": \"git+https://github.com/tursodatabase/libsql-client-ts\",\n        \"directory\": \"packages/libsql-core\"\n    },\n    \"authors\": [\n        \"Jan Špaček <honza@chiselstrike.com>\",\n        \"Pekka Enberg <penberg@chiselstrike.com>\",\n        \"Jan Plhak <jp@chiselstrike.com>\"\n    ],\n    \"license\": \"MIT\",\n    \"type\": \"module\",\n    \"exports\": {\n        \"./api\": {\n            \"types\": \"./lib-esm/api.d.ts\",\n            \"import\": \"./lib-esm/api.js\",\n            \"require\": \"./lib-cjs/api.js\"\n        },\n        \"./config\": {\n            \"types\": \"./lib-esm/config.d.ts\",\n            \"import\": \"./lib-esm/config.js\",\n            \"require\": \"./lib-cjs/config.js\"\n        },\n        \"./uri\": {\n            \"types\": \"./lib-esm/uri.d.ts\",\n            \"import\": \"./lib-esm/uri.js\",\n            \"require\": \"./lib-cjs/uri.js\"\n        },\n        \"./util\": {\n            \"types\": \"./lib-esm/util.d.ts\",\n            \"import\": \"./lib-esm/util.js\",\n            \"require\": \"./lib-cjs/util.js\"\n        }\n    },\n    \"typesVersions\": {\n        \"*\": {\n            \"api\": [\n                \"./lib-esm/api.d.ts\"\n            ],\n            \"config\": [\n                \"./lib-esm/config.d.ts\"\n            ],\n            \"uri\": [\n                \"./lib-esm/uri.d.ts\"\n            ],\n            \"util\": [\n                \"./lib-esm/util.d.ts\"\n            ]\n        }\n    },\n    \"files\": [\n        \"lib-cjs/**\",\n        \"lib-esm/**\"\n    ],\n    \"scripts\": {\n        \"prepublishOnly\": \"npm run build\",\n        \"prebuild\": \"rm -rf ./lib-cjs ./lib-esm\",\n        \"build\": \"npm run build:cjs && npm run build:esm\",\n        \"build:cjs\": \"tsc -p tsconfig.build-cjs.json\",\n        \"build:esm\": \"tsc -p tsconfig.build-esm.json\",\n        \"format:check\": \"prettier --check .\",\n        \"postbuild\": \"cp package-cjs.json ./lib-cjs/package.json\",\n        \"test\": \"jest --runInBand\",\n        \"typecheck\": \"tsc --noEmit\",\n        \"typedoc\": \"rm -rf ./docs && typedoc\"\n    },\n    \"dependencies\": {\n        \"js-base64\": \"^3.7.5\"\n    },\n    \"devDependencies\": {\n        \"@types/jest\": \"^29.2.5\",\n        \"@types/node\": \"^18.15.5\",\n        \"jest\": \"^29.3.1\",\n        \"ts-jest\": \"^29.0.5\",\n        \"typedoc\": \"^0.23.28\",\n        \"typescript\": \"^4.9.4\"\n    }\n}\n"
  },
  {
    "path": "packages/libsql-core/src/api.ts",
    "content": "/** Configuration object for {@link createClient}. */\nexport interface Config {\n    /** The database URL.\n     *\n     * The client supports `libsql:`, `http:`/`https:`, `ws:`/`wss:` and `file:` URL. For more infomation,\n     * please refer to the project README:\n     *\n     * https://github.com/libsql/libsql-client-ts#supported-urls\n     */\n    url: string;\n\n    /** Authentication token for the database. */\n    authToken?: string;\n\n    /** Encryption key for the database. */\n    encryptionKey?: string;\n\n    /** Encryption key for encryption in Turso Cloud. */\n    remoteEncryptionKey?: string;\n\n    /** URL of a remote server to synchronize database with. */\n    syncUrl?: string;\n\n    /** Sync interval in seconds. */\n    syncInterval?: number;\n\n    /** Read your writes */\n    readYourWrites?: boolean;\n\n    /** Enable offline writes */\n    offline?: boolean;\n\n    /** Enables or disables TLS for `libsql:` URLs.\n     *\n     * By default, `libsql:` URLs use TLS. You can set this option to `false` to disable TLS.\n     */\n    tls?: boolean;\n\n    /** How to convert SQLite integers to JavaScript values:\n     *\n     * - `\"number\"` (default): returns SQLite integers as JavaScript `number`-s (double precision floats).\n     * `number` cannot precisely represent integers larger than 2^53-1 in absolute value, so attempting to read\n     * larger integers will throw a `RangeError`.\n     * - `\"bigint\"`: returns SQLite integers as JavaScript `bigint`-s (arbitrary precision integers). Bigints can\n     * precisely represent all SQLite integers.\n     * - `\"string\"`: returns SQLite integers as strings.\n     */\n    intMode?: IntMode;\n\n    /** Custom `fetch` function to use for the HTTP client.\n     *\n     * By default, the HTTP client uses `fetch` from the `@libsql/isomorphic-fetch` package, but you can pass\n     * your own function here. The argument to this function will be `Request` from\n     * `@libsql/isomorphic-fetch`, and it must return a promise that resolves to an object that is compatible\n     * with the Web `Response`.\n     */\n    fetch?: Function;\n\n    /** Concurrency limit.\n     *\n     * By default, the client performs up to 20 concurrent requests. You can set this option to a higher\n     * number to increase the concurrency limit or set it to 0 to disable concurrency limits completely.\n     */\n    concurrency?: number | undefined;\n}\n\n/** Representation of integers from database as JavaScript values. See {@link Config.intMode}. */\nexport type IntMode = \"number\" | \"bigint\" | \"string\";\n\n/** Client object for a remote or local database.\n *\n * After you are done with the client, you **should** close it by calling {@link close}.\n */\nexport interface Client {\n    /** Execute a single SQL statement.\n     *\n     * Every statement executed with this method is executed in its own logical database connection. If you\n     * want to execute a group of statements in a transaction, use the {@link batch} or the {@link\n     * transaction} methods.\n     *\n     * ```javascript\n     * // execute a statement without arguments\n     * const rs = await client.execute(\"SELECT * FROM books\");\n     *\n     * // execute a statement with positional arguments\n     * const rs = await client.execute({\n     *     sql: \"SELECT * FROM books WHERE author = ?\",\n     *     args: [\"Jane Austen\"],\n     * });\n     *\n     * // execute a statement with named arguments\n     * const rs = await client.execute({\n     *     sql: \"SELECT * FROM books WHERE published_at > $year\",\n     *     args: {year: 1719},\n     * });\n     * ```\n     */\n    execute(stmt: InStatement): Promise<ResultSet>;\n    execute(sql: string, args?: InArgs): Promise<ResultSet>;\n\n    /** Execute a batch of SQL statements in a transaction.\n     *\n     * The batch is executed in its own logical database connection and the statements are wrapped in a\n     * transaction. This ensures that the batch is applied atomically: either all or no changes are applied.\n     *\n     * The `mode` parameter selects the transaction mode for the batch; please see {@link TransactionMode} for\n     * details. The default transaction mode is `\"deferred\"`.\n     *\n     * If any of the statements in the batch fails with an error, the batch is aborted, the transaction is\n     * rolled back and the returned promise is rejected.\n     *\n     * This method provides non-interactive transactions. If you need interactive transactions, please use the\n     * {@link transaction} method.\n     *\n     * ```javascript\n     * const rss = await client.batch([\n     *     // batch statement without arguments\n     *     \"DELETE FROM books WHERE name LIKE '%Crusoe'\",\n     *\n     *     // batch statement with positional arguments\n     *     {\n     *         sql: \"INSERT INTO books (name, author, published_at) VALUES (?, ?, ?)\",\n     *         args: [\"First Impressions\", \"Jane Austen\", 1813],\n     *     },\n     *\n     *     // batch statement with named arguments\n     *     {\n     *         sql: \"UPDATE books SET name = $new WHERE name = $old\",\n     *         args: {old: \"First Impressions\", new: \"Pride and Prejudice\"},\n     *     },\n     * ], \"write\");\n     * ```\n     */\n    batch(\n        stmts: Array<InStatement | [string, InArgs?]>,\n        mode?: TransactionMode,\n    ): Promise<Array<ResultSet>>;\n\n    /** Execute a batch of SQL statements in a transaction with PRAGMA foreign_keys=off; before and PRAGMA foreign_keys=on; after.\n     *\n     * The batch is executed in its own logical database connection and the statements are wrapped in a\n     * transaction. This ensures that the batch is applied atomically: either all or no changes are applied.\n     *\n     * The transaction mode is `\"deferred\"`.\n     *\n     * If any of the statements in the batch fails with an error, the batch is aborted, the transaction is\n     * rolled back and the returned promise is rejected.\n     *\n     * ```javascript\n     * const rss = await client.migrate([\n     *     // statement without arguments\n     *     \"CREATE TABLE test (a INT)\",\n     *\n     *     // statement with positional arguments\n     *     {\n     *         sql: \"INSERT INTO books (name, author, published_at) VALUES (?, ?, ?)\",\n     *         args: [\"First Impressions\", \"Jane Austen\", 1813],\n     *     },\n     *\n     *     // statement with named arguments\n     *     {\n     *         sql: \"UPDATE books SET name = $new WHERE name = $old\",\n     *         args: {old: \"First Impressions\", new: \"Pride and Prejudice\"},\n     *     },\n     * ]);\n     * ```\n     */\n    migrate(stmts: Array<InStatement>): Promise<Array<ResultSet>>;\n\n    /** Start an interactive transaction.\n     *\n     * Interactive transactions allow you to interleave execution of SQL statements with your application\n     * logic. They can be used if the {@link batch} method is too restrictive, but please note that\n     * interactive transactions have higher latency.\n     *\n     * The `mode` parameter selects the transaction mode for the interactive transaction; please see {@link\n     * TransactionMode} for details. The default transaction mode is `\"deferred\"`.\n     *\n     * You **must** make sure that the returned {@link Transaction} object is closed, by calling {@link\n     * Transaction.close}, {@link Transaction.commit} or {@link Transaction.rollback}. The best practice is\n     * to call {@link Transaction.close} in a `finally` block, as follows:\n     *\n     * ```javascript\n     * const transaction = client.transaction(\"write\");\n     * try {\n     *     // do some operations with the transaction here\n     *     await transaction.execute({\n     *         sql: \"INSERT INTO books (name, author) VALUES (?, ?)\",\n     *         args: [\"First Impressions\", \"Jane Austen\"],\n     *     });\n     *     await transaction.execute({\n     *         sql: \"UPDATE books SET name = ? WHERE name = ?\",\n     *         args: [\"Pride and Prejudice\", \"First Impressions\"],\n     *     });\n     *\n     *     // if all went well, commit the transaction\n     *     await transaction.commit();\n     * } finally {\n     *     // make sure to close the transaction, even if an exception was thrown\n     *     transaction.close();\n     * }\n     * ```\n     */\n    transaction(mode?: TransactionMode): Promise<Transaction>;\n\n    /** Start an interactive transaction in `\"write\"` mode.\n     *\n     * Please see {@link transaction} for details.\n     *\n     * @deprecated Please specify the `mode` explicitly. The default `\"write\"` will be removed in the next\n     * major release.\n     */\n    transaction(): Promise<Transaction>;\n\n    /** Execute a sequence of SQL statements separated by semicolons.\n     *\n     * The statements are executed sequentially on a new logical database connection. If a statement fails,\n     * further statements are not executed and this method throws an error. All results from the statements\n     * are ignored.\n     *\n     * We do not wrap the statements in a transaction, but the SQL can contain explicit transaction-control\n     * statements such as `BEGIN` and `COMMIT`.\n     *\n     * This method is intended to be used with existing SQL scripts, such as migrations or small database\n     * dumps. If you want to execute a sequence of statements programmatically, please use {@link batch}\n     * instead.\n     *\n     * ```javascript\n     * await client.executeMultiple(`\n     *     CREATE TABLE books (id INTEGER PRIMARY KEY, title TEXT NOT NULL, author_id INTEGER NOT NULL);\n     *     CREATE TABLE authors (id INTEGER PRIMARY KEY, name TEXT NOT NULL);\n     * `);\n     * ```\n     */\n    executeMultiple(sql: string): Promise<void>;\n\n    sync(): Promise<Replicated>;\n\n    /** Close the client and release resources.\n     *\n     * This method closes the client (aborting any operations that are currently in progress) and releases any\n     * resources associated with the client (such as a WebSocket connection).\n     */\n    close(): void;\n\n    /** Reconnect after the client has been closed.\n     */\n    reconnect(): void;\n\n    /** Is the client closed?\n     *\n     * This is set to `true` after a call to {@link close} or if the client encounters an unrecoverable\n     * error.\n     */\n    closed: boolean;\n\n    /** Which protocol does the client use?\n     *\n     * - `\"http\"` if the client connects over HTTP\n     * - `\"ws\"` if the client connects over WebSockets\n     * - `\"file\"` if the client works with a local file\n     */\n    protocol: string;\n}\n\n/** Interactive transaction.\n *\n * A transaction groups multiple SQL statements together, so that they are applied atomically: either all\n * changes are applied, or none are. Other SQL statements on the database (including statements executed on\n * the same {@link Client} object outside of this transaction) will not see any changes from the transaction\n * until the transaction is committed by calling {@link commit}. You can also use {@link rollback} to abort\n * the transaction and roll back the changes.\n *\n * You **must** make sure that the {@link Transaction} object is closed, by calling {@link close}, {@link\n * commit} or {@link rollback}. The best practice is to call {@link close} in a `finally` block, as follows:\n *\n * ```javascript\n * const transaction = client.transaction(\"write\");\n * try {\n *     // do some operations with the transaction here\n *     await transaction.execute({\n *         sql: \"INSERT INTO books (name, author) VALUES (?, ?)\",\n *         args: [\"First Impressions\", \"Jane Austen\"],\n *     });\n *     await transaction.execute({\n *         sql: \"UPDATE books SET name = ? WHERE name = ?\",\n *         args: [\"Pride and Prejudice\", \"First Impressions\"],\n *     });\n *\n *     // if all went well, commit the transaction\n *     await transaction.commit();\n * } finally {\n *     // make sure to close the transaction, even if an exception was thrown\n *     transaction.close();\n * }\n * ```\n */\nexport interface Transaction {\n    /** Execute an SQL statement in this transaction.\n     *\n     * If the statement makes any changes to the database, these changes won't be visible to statements\n     * outside of this transaction until you call {@link rollback}.\n     *\n     * ```javascript\n     * await transaction.execute({\n     *     sql: \"INSERT INTO books (name, author) VALUES (?, ?)\",\n     *     args: [\"First Impressions\", \"Jane Austen\"],\n     * });\n     * ```\n     */\n    execute(stmt: InStatement): Promise<ResultSet>;\n\n    /** Execute a batch of SQL statements in this transaction.\n     *\n     * If any of the statements in the batch fails with an error, further statements are not executed and the\n     * returned promise is rejected with an error, but the transaction is not rolled back.\n     */\n    batch(stmts: Array<InStatement>): Promise<Array<ResultSet>>;\n\n    /** Execute a sequence of SQL statements separated by semicolons.\n     *\n     * The statements are executed sequentially in the transaction. If a statement fails, further statements\n     * are not executed and this method throws an error, but the transaction won't be rolled back. All results\n     * from the statements are ignored.\n     *\n     * This method is intended to be used with existing SQL scripts, such as migrations or small database\n     * dumps. If you want to execute statements programmatically, please use {@link batch} instead.\n     */\n    executeMultiple(sql: string): Promise<void>;\n\n    /** Roll back any changes from this transaction.\n     *\n     * This method closes the transaction and undoes any changes done by the previous SQL statements on this\n     * transaction. You cannot call this method after calling {@link commit}, though.\n     */\n    rollback(): Promise<void>;\n\n    /** Commit changes from this transaction to the database.\n     *\n     * This method closes the transaction and applies all changes done by the previous SQL statement on this\n     * transaction. Once the returned promise is resolved successfully, the database guarantees that the\n     * changes were applied.\n     */\n    commit(): Promise<void>;\n\n    /** Close the transaction.\n     *\n     * This method closes the transaction and releases any resources associated with the transaction. If the\n     * transaction is already closed (perhaps by a previous call to {@link commit} or {@link rollback}), then\n     * this method does nothing.\n     *\n     * If the transaction wasn't already committed by calling {@link commit}, the transaction is rolled\n     * back.\n     */\n    close(): void;\n\n    /** Is the transaction closed?\n     *\n     * This is set to `true` after a call to {@link close}, {@link commit} or {@link rollback}, or if we\n     * encounter an unrecoverable error.\n     */\n    closed: boolean;\n}\n\n/** Transaction mode.\n *\n * The client supports multiple modes for transactions:\n *\n * - `\"write\"` is a read-write transaction, started with `BEGIN IMMEDIATE`. This transaction mode supports\n * both read statements (`SELECT`) and write statements (`INSERT`, `UPDATE`, `CREATE TABLE`, etc). The libSQL\n * server cannot process multiple write transactions concurrently, so if there is another write transaction\n * already started, our transaction will wait in a queue before it can begin.\n *\n * - `\"read\"` is a read-only transaction, started with `BEGIN TRANSACTION READONLY` (a libSQL extension). This\n * transaction mode supports only reads (`SELECT`) and will not accept write statements. The libSQL server can\n * handle multiple read transactions at the same time, so we don't need to wait for other transactions to\n * complete. A read-only transaction can also be executed on a local replica, so it provides lower latency.\n *\n * - `\"deferred\"` is a transaction started with `BEGIN DEFERRED`, which starts as a read transaction, but the\n * first write statement will try to upgrade it to a write transaction. However, this upgrade may fail if\n * there already is a write transaction executing on the server, so you should be ready to handle these\n * failures.\n *\n * If your transaction includes only read statements, `\"read\"` is always preferred over `\"deferred\"` or\n * `\"write\"`, because `\"read\"` transactions can be executed more efficiently and don't block other\n * transactions.\n *\n * If your transaction includes both read and write statements, you should be using the `\"write\"` mode most of\n * the time. Use the `\"deferred\"` mode only if you prefer to fail the write transaction instead of waiting for\n * the previous write transactions to complete.\n */\nexport type TransactionMode = \"write\" | \"read\" | \"deferred\";\n\n/** Result of executing an SQL statement.\n *\n * ```javascript\n * const rs = await client.execute(\"SELECT name, title FROM books\");\n * console.log(`Found ${rs.rows.length} books`);\n * for (const row in rs.rows) {\n *     console.log(`Book ${row[0]} by ${row[1]}`);\n * }\n *\n * const rs = await client.execute(\"DELETE FROM books WHERE author = 'Jane Austen'\");\n * console.log(`Deleted ${rs.rowsAffected} books`);\n * ```\n */\nexport interface ResultSet {\n    /** Names of columns.\n     *\n     * Names of columns can be defined using the `AS` keyword in SQL:\n     *\n     * ```sql\n     * SELECT author AS author, COUNT(*) AS count FROM books GROUP BY author\n     * ```\n     */\n    columns: Array<string>;\n\n    /** Types of columns.\n     *\n     * The types are currently shown for types declared in a SQL table. For\n     * column types of function calls, for example, an empty string is\n     * returned.\n     */\n    columnTypes: Array<string>;\n\n    /** Rows produced by the statement. */\n    rows: Array<Row>;\n\n    /** Number of rows that were affected by an UPDATE, INSERT or DELETE operation.\n     *\n     * This value is not specified for other SQL statements.\n     */\n    rowsAffected: number;\n\n    /** ROWID of the last inserted row.\n     *\n     * This value is not specified if the SQL statement was not an INSERT or if the table was not a ROWID\n     * table.\n     */\n    lastInsertRowid: bigint | undefined;\n\n    /** Converts the result set to JSON.\n     *\n     * This is used automatically by `JSON.stringify()`, but you can also call it explicitly.\n     */\n    toJSON(): any;\n}\n\n/** Row returned from an SQL statement.\n *\n * The row object can be used as an `Array` or as an object:\n *\n * ```javascript\n * const rs = await client.execute(\"SELECT name, title FROM books\");\n * for (const row in rs.rows) {\n *     // Get the value from column `name`\n *     console.log(row.name);\n *     // Get the value from second column (`title`)\n *     console.log(row[1]);\n * }\n * ```\n */\nexport interface Row {\n    /** Number of columns in this row.\n     *\n     * All rows in one {@link ResultSet} have the same number and names of columns.\n     */\n    length: number;\n\n    /** Columns can be accessed like an array by numeric indexes. */\n    [index: number]: Value;\n\n    /** Columns can be accessed like an object by column names. */\n    [name: string]: Value;\n}\n\nexport type Replicated =\n    | { frame_no: number; frames_synced: number }\n    | undefined;\n\nexport type Value = null | string | number | bigint | ArrayBuffer;\n\nexport type InValue = Value | boolean | Uint8Array | Date;\n\nexport type InStatement = { sql: string; args?: InArgs } | string;\nexport type InArgs = Array<InValue> | Record<string, InValue>;\n\n/** Error thrown by the client. */\nexport class LibsqlError extends Error {\n    /** Machine-readable error code. */\n    code: string;\n    /** Extended error code with more specific information (e.g., SQLITE_CONSTRAINT_PRIMARYKEY). */\n    extendedCode?: string;\n    /** Raw numeric error code */\n    rawCode?: number;\n\n    constructor(\n        message: string,\n        code: string,\n        extendedCode?: string,\n        rawCode?: number,\n        cause?: Error,\n    ) {\n        if (code !== undefined) {\n            message = `${code}: ${message}`;\n        }\n        super(message, { cause });\n        this.code = code;\n        this.extendedCode = extendedCode;\n        this.rawCode = rawCode;\n        this.name = \"LibsqlError\";\n    }\n}\n\n/** Error thrown by the client during batch operations. */\nexport class LibsqlBatchError extends LibsqlError {\n    /** The zero-based index of the statement that failed in the batch. */\n    statementIndex: number;\n\n    constructor(\n        message: string,\n        statementIndex: number,\n        code: string,\n        extendedCode?: string,\n        rawCode?: number,\n        cause?: Error,\n    ) {\n        super(message, code, extendedCode, rawCode, cause);\n        this.statementIndex = statementIndex;\n        this.name = \"LibsqlBatchError\";\n    }\n}\n"
  },
  {
    "path": "packages/libsql-core/src/config.ts",
    "content": "import type { Config, IntMode } from \"./api.js\";\nimport { LibsqlError } from \"./api.js\";\nimport type { Authority } from \"./uri.js\";\nimport { parseUri } from \"./uri.js\";\nimport { supportedUrlLink } from \"./util.js\";\n\nexport interface ExpandedConfig {\n    scheme: ExpandedScheme;\n    tls: boolean;\n    authority: Authority | undefined;\n    path: string;\n    authToken: string | undefined;\n    encryptionKey: string | undefined;\n    remoteEncryptionKey: string | undefined;\n    syncUrl: string | undefined;\n    syncInterval: number | undefined;\n    readYourWrites: boolean | undefined;\n    offline: boolean | undefined;\n    intMode: IntMode;\n    fetch: Function | undefined;\n    concurrency: number;\n}\n\nexport type ExpandedScheme = \"wss\" | \"ws\" | \"https\" | \"http\" | \"file\";\n\ntype queryParamDef = {\n    values?: string[];\n    update?: (key: string, value: string) => void;\n};\ntype queryParamsDef = { [key: string]: queryParamDef };\n\nconst inMemoryMode = \":memory:\";\n\nexport function isInMemoryConfig(config: ExpandedConfig): boolean {\n    return (\n        config.scheme === \"file\" &&\n        (config.path === \":memory:\" || config.path.startsWith(\":memory:?\"))\n    );\n}\n\nexport function expandConfig(\n    config: Readonly<Config>,\n    preferHttp: boolean,\n): ExpandedConfig {\n    if (typeof config !== \"object\") {\n        // produce a reasonable error message in the common case where users type\n        // `createClient(\"libsql://...\")` instead of `createClient({url: \"libsql://...\"})`\n        throw new TypeError(\n            `Expected client configuration as object, got ${typeof config}`,\n        );\n    }\n\n    let { url, authToken, tls, intMode, concurrency } = config;\n    // fill simple defaults right here\n    concurrency = Math.max(0, concurrency || 20);\n    intMode ??= \"number\";\n\n    let connectionQueryParams: string[] = []; // recognized query parameters which we sanitize through white list of valid key-value pairs\n\n    // convert plain :memory: url to URI format to make logic more uniform\n    if (url === inMemoryMode) {\n        url = \"file::memory:\";\n    }\n\n    // parse url parameters first and override config with update values\n    const uri = parseUri(url);\n    const originalUriScheme = uri.scheme.toLowerCase();\n    const isInMemoryMode =\n        originalUriScheme === \"file\" &&\n        uri.path === inMemoryMode &&\n        uri.authority === undefined;\n\n    let queryParamsDef: queryParamsDef;\n    if (isInMemoryMode) {\n        queryParamsDef = {\n            cache: {\n                values: [\"shared\", \"private\"],\n                update: (key, value) =>\n                    connectionQueryParams.push(`${key}=${value}`),\n            },\n        };\n    } else {\n        queryParamsDef = {\n            tls: {\n                values: [\"0\", \"1\"],\n                update: (_, value) => (tls = value === \"1\"),\n            },\n            authToken: {\n                update: (_, value) => (authToken = value),\n            },\n        };\n    }\n\n    for (const { key, value } of uri.query?.pairs ?? []) {\n        if (!Object.hasOwn(queryParamsDef, key)) {\n            throw new LibsqlError(\n                `Unsupported URL query parameter ${JSON.stringify(key)}`,\n                \"URL_PARAM_NOT_SUPPORTED\",\n            );\n        }\n        const queryParamDef = queryParamsDef[key];\n        if (\n            queryParamDef.values !== undefined &&\n            !queryParamDef.values.includes(value)\n        ) {\n            throw new LibsqlError(\n                `Unknown value for the \"${key}\" query argument: ${JSON.stringify(value)}. Supported values are: [${queryParamDef.values.map((x) => '\"' + x + '\"').join(\", \")}]`,\n                \"URL_INVALID\",\n            );\n        }\n        if (queryParamDef.update !== undefined) {\n            queryParamDef?.update(key, value);\n        }\n    }\n\n    // fill complex defaults & validate config\n    const connectionQueryParamsString =\n        connectionQueryParams.length === 0\n            ? \"\"\n            : `?${connectionQueryParams.join(\"&\")}`;\n    const path = uri.path + connectionQueryParamsString;\n\n    let scheme: string;\n    if (originalUriScheme === \"libsql\") {\n        if (tls === false) {\n            if (uri.authority?.port === undefined) {\n                throw new LibsqlError(\n                    'A \"libsql:\" URL with ?tls=0 must specify an explicit port',\n                    \"URL_INVALID\",\n                );\n            }\n            scheme = preferHttp ? \"http\" : \"ws\";\n        } else {\n            scheme = preferHttp ? \"https\" : \"wss\";\n        }\n    } else {\n        scheme = originalUriScheme;\n    }\n    if (scheme === \"http\" || scheme === \"ws\") {\n        tls ??= false;\n    } else {\n        tls ??= true;\n    }\n\n    if (\n        scheme !== \"http\" &&\n        scheme !== \"ws\" &&\n        scheme !== \"https\" &&\n        scheme !== \"wss\" &&\n        scheme !== \"file\"\n    ) {\n        throw new LibsqlError(\n            'The client supports only \"libsql:\", \"wss:\", \"ws:\", \"https:\", \"http:\" and \"file:\" URLs, ' +\n                `got ${JSON.stringify(uri.scheme + \":\")}. ` +\n                `For more information, please read ${supportedUrlLink}`,\n            \"URL_SCHEME_NOT_SUPPORTED\",\n        );\n    }\n    if (intMode !== \"number\" && intMode !== \"bigint\" && intMode !== \"string\") {\n        throw new TypeError(\n            `Invalid value for intMode, expected \"number\", \"bigint\" or \"string\", got ${JSON.stringify(intMode)}`,\n        );\n    }\n    if (uri.fragment !== undefined) {\n        throw new LibsqlError(\n            `URL fragments are not supported: ${JSON.stringify(\"#\" + uri.fragment)}`,\n            \"URL_INVALID\",\n        );\n    }\n\n    if (isInMemoryMode) {\n        return {\n            scheme: \"file\",\n            tls: false,\n            path,\n            intMode,\n            concurrency,\n            syncUrl: config.syncUrl,\n            syncInterval: config.syncInterval,\n            readYourWrites: config.readYourWrites,\n            offline: config.offline,\n            fetch: config.fetch,\n            authToken: undefined,\n            encryptionKey: undefined,\n            remoteEncryptionKey: undefined,\n            authority: undefined,\n        };\n    }\n\n    return {\n        scheme,\n        tls,\n        authority: uri.authority,\n        path,\n        authToken,\n        intMode,\n        concurrency,\n        encryptionKey: config.encryptionKey,\n        remoteEncryptionKey: config.remoteEncryptionKey,\n        syncUrl: config.syncUrl,\n        syncInterval: config.syncInterval,\n        readYourWrites: config.readYourWrites,\n        offline: config.offline,\n        fetch: config.fetch,\n    };\n}\n"
  },
  {
    "path": "packages/libsql-core/src/uri.ts",
    "content": "// URI parser based on RFC 3986\n// We can't use the standard `URL` object, because we want to support relative `file:` URLs like\n// `file:relative/path/database.db`, which are not correct according to RFC 8089, which standardizes the\n// `file` scheme.\n\nimport { LibsqlError } from \"./api.js\";\n\nexport interface Uri {\n    scheme: string;\n    authority: Authority | undefined;\n    path: string;\n    query: Query | undefined;\n    fragment: string | undefined;\n}\n\nexport interface HierPart {\n    authority: Authority | undefined;\n    path: string;\n}\n\nexport interface Authority {\n    host: string;\n    port: number | undefined;\n    userinfo: Userinfo | undefined;\n}\n\nexport interface Userinfo {\n    username: string;\n    password: string | undefined;\n}\n\nexport interface Query {\n    pairs: Array<KeyValue>;\n}\n\nexport interface KeyValue {\n    key: string;\n    value: string;\n}\n\nexport function parseUri(text: string): Uri {\n    const match = URI_RE.exec(text);\n    if (match === null) {\n        throw new LibsqlError(\n            `The URL '${text}' is not in a valid format`,\n            \"URL_INVALID\",\n        );\n    }\n\n    const groups = match.groups!;\n    const scheme = groups[\"scheme\"]!;\n    const authority =\n        groups[\"authority\"] !== undefined\n            ? parseAuthority(groups[\"authority\"])\n            : undefined;\n    const path = percentDecode(groups[\"path\"]!);\n    const query =\n        groups[\"query\"] !== undefined ? parseQuery(groups[\"query\"]) : undefined;\n    const fragment =\n        groups[\"fragment\"] !== undefined\n            ? percentDecode(groups[\"fragment\"])\n            : undefined;\n    return { scheme, authority, path, query, fragment };\n}\n\nconst URI_RE = (() => {\n    const SCHEME = \"(?<scheme>[A-Za-z][A-Za-z.+-]*)\";\n    const AUTHORITY = \"(?<authority>[^/?#]*)\";\n    const PATH = \"(?<path>[^?#]*)\";\n    const QUERY = \"(?<query>[^#]*)\";\n    const FRAGMENT = \"(?<fragment>.*)\";\n    return new RegExp(\n        `^${SCHEME}:(//${AUTHORITY})?${PATH}(\\\\?${QUERY})?(#${FRAGMENT})?$`,\n        \"su\",\n    );\n})();\n\nfunction parseAuthority(text: string): Authority {\n    const match = AUTHORITY_RE.exec(text);\n    if (match === null) {\n        throw new LibsqlError(\n            \"The authority part of the URL is not in a valid format\",\n            \"URL_INVALID\",\n        );\n    }\n\n    const groups = match.groups!;\n    const host = percentDecode(groups[\"host_br\"] ?? groups[\"host\"]);\n    const port = groups[\"port\"] ? parseInt(groups[\"port\"], 10) : undefined;\n    const userinfo =\n        groups[\"username\"] !== undefined\n            ? {\n                  username: percentDecode(groups[\"username\"]),\n                  password:\n                      groups[\"password\"] !== undefined\n                          ? percentDecode(groups[\"password\"])\n                          : undefined,\n              }\n            : undefined;\n    return { host, port, userinfo };\n}\n\nconst AUTHORITY_RE = (() => {\n    return new RegExp(\n        `^((?<username>[^:]*)(:(?<password>.*))?@)?((?<host>[^:\\\\[\\\\]]*)|(\\\\[(?<host_br>[^\\\\[\\\\]]*)\\\\]))(:(?<port>[0-9]*))?$`,\n        \"su\",\n    );\n})();\n\n// Query string is parsed as application/x-www-form-urlencoded according to the Web URL standard:\n// https://url.spec.whatwg.org/#urlencoded-parsing\nfunction parseQuery(text: string): Query {\n    const sequences = text.split(\"&\");\n    const pairs = [];\n    for (const sequence of sequences) {\n        if (sequence === \"\") {\n            continue;\n        }\n\n        let key: string;\n        let value: string;\n        const splitIdx = sequence.indexOf(\"=\");\n        if (splitIdx < 0) {\n            key = sequence;\n            value = \"\";\n        } else {\n            key = sequence.substring(0, splitIdx);\n            value = sequence.substring(splitIdx + 1);\n        }\n\n        pairs.push({\n            key: percentDecode(key.replaceAll(\"+\", \" \")),\n            value: percentDecode(value.replaceAll(\"+\", \" \")),\n        });\n    }\n    return { pairs };\n}\n\nfunction percentDecode(text: string): string {\n    try {\n        return decodeURIComponent(text);\n    } catch (e) {\n        if (e instanceof URIError) {\n            throw new LibsqlError(\n                `URL component has invalid percent encoding: ${e}`,\n                \"URL_INVALID\",\n                undefined,\n                undefined,\n                e,\n            );\n        }\n        throw e;\n    }\n}\n\nexport function encodeBaseUrl(\n    scheme: string,\n    authority: Authority | undefined,\n    path: string,\n): URL {\n    if (authority === undefined) {\n        throw new LibsqlError(\n            `URL with scheme ${JSON.stringify(scheme + \":\")} requires authority (the \"//\" part)`,\n            \"URL_INVALID\",\n        );\n    }\n\n    const schemeText = `${scheme}:`;\n\n    const hostText = encodeHost(authority.host);\n    const portText = encodePort(authority.port);\n    const userinfoText = encodeUserinfo(authority.userinfo);\n    const authorityText = `//${userinfoText}${hostText}${portText}`;\n\n    let pathText = path.split(\"/\").map(encodeURIComponent).join(\"/\");\n    if (pathText !== \"\" && !pathText.startsWith(\"/\")) {\n        pathText = \"/\" + pathText;\n    }\n\n    return new URL(`${schemeText}${authorityText}${pathText}`);\n}\n\nfunction encodeHost(host: string): string {\n    return host.includes(\":\") ? `[${encodeURI(host)}]` : encodeURI(host);\n}\n\nfunction encodePort(port: number | undefined): string {\n    return port !== undefined ? `:${port}` : \"\";\n}\n\nfunction encodeUserinfo(userinfo: Userinfo | undefined): string {\n    if (userinfo === undefined) {\n        return \"\";\n    }\n\n    const usernameText = encodeURIComponent(userinfo.username);\n    const passwordText =\n        userinfo.password !== undefined\n            ? `:${encodeURIComponent(userinfo.password)}`\n            : \"\";\n    return `${usernameText}${passwordText}@`;\n}\n"
  },
  {
    "path": "packages/libsql-core/src/util.ts",
    "content": "import { Base64 } from \"js-base64\";\nimport {\n    ResultSet,\n    Row,\n    Value,\n    TransactionMode,\n    InStatement,\n    LibsqlError,\n} from \"./api\";\n\nexport const supportedUrlLink =\n    \"https://github.com/libsql/libsql-client-ts#supported-urls\";\n\nexport function transactionModeToBegin(mode: TransactionMode): string {\n    if (mode === \"write\") {\n        return \"BEGIN IMMEDIATE\";\n    } else if (mode === \"read\") {\n        return \"BEGIN TRANSACTION READONLY\";\n    } else if (mode === \"deferred\") {\n        return \"BEGIN DEFERRED\";\n    } else {\n        throw RangeError(\n            'Unknown transaction mode, supported values are \"write\", \"read\" and \"deferred\"',\n        );\n    }\n}\n\nexport class ResultSetImpl implements ResultSet {\n    columns: Array<string>;\n    columnTypes: Array<string>;\n    rows: Array<Row>;\n    rowsAffected: number;\n    lastInsertRowid: bigint | undefined;\n\n    constructor(\n        columns: Array<string>,\n        columnTypes: Array<string>,\n        rows: Array<Row>,\n        rowsAffected: number,\n        lastInsertRowid: bigint | undefined,\n    ) {\n        this.columns = columns;\n        this.columnTypes = columnTypes;\n        this.rows = rows;\n        this.rowsAffected = rowsAffected;\n        this.lastInsertRowid = lastInsertRowid;\n    }\n\n    toJSON(): any {\n        return {\n            columns: this.columns,\n            columnTypes: this.columnTypes,\n            rows: this.rows.map(rowToJson),\n            rowsAffected: this.rowsAffected,\n            lastInsertRowid:\n                this.lastInsertRowid !== undefined\n                    ? \"\" + this.lastInsertRowid\n                    : null,\n        };\n    }\n}\n\nfunction rowToJson(row: Row): unknown {\n    return Array.prototype.map.call(row, valueToJson);\n}\n\nfunction valueToJson(value: Value): unknown {\n    if (typeof value === \"bigint\") {\n        return \"\" + value;\n    } else if (value instanceof ArrayBuffer) {\n        return Base64.fromUint8Array(new Uint8Array(value));\n    } else {\n        return value;\n    }\n}\n"
  },
  {
    "path": "packages/libsql-core/tsconfig.base.json",
    "content": "{\n    \"compilerOptions\": {\n        \"moduleResolution\": \"node\",\n        \"lib\": [\"esnext\"],\n        \"target\": \"esnext\",\n        \"esModuleInterop\": true,\n        \"isolatedModules\": true,\n        \"rootDir\": \"src/\",\n        \"strict\": true\n    },\n    \"include\": [\"src/\"],\n    \"exclude\": [\"**/__tests__\"]\n}\n"
  },
  {
    "path": "packages/libsql-core/tsconfig.build-cjs.json",
    "content": "{\n    \"extends\": \"./tsconfig.base.json\",\n    \"compilerOptions\": {\n        \"module\": \"commonjs\",\n        \"declaration\": false,\n        \"outDir\": \"./lib-cjs/\"\n    }\n}\n"
  },
  {
    "path": "packages/libsql-core/tsconfig.build-esm.json",
    "content": "{\n    \"extends\": \"./tsconfig.base.json\",\n    \"compilerOptions\": {\n        \"module\": \"esnext\",\n        \"declaration\": true,\n        \"outDir\": \"./lib-esm/\"\n    }\n}\n"
  },
  {
    "path": "packages/libsql-core/tsconfig.json",
    "content": "{\n    \"extends\": \"./tsconfig.base.json\",\n    \"compilerOptions\": {\n        \"noEmit\": true,\n        \"incremental\": true\n    }\n}\n"
  },
  {
    "path": "packages/libsql-core/typedoc.json",
    "content": "{\n    \"entryPoints\": [\"src/node.ts\"],\n    \"out\": \"docs\",\n    \"excludePrivate\": true,\n    \"excludeInternal\": true,\n    \"visibilityFilters\": {\n        \"inherited\": true,\n        \"external\": true\n    },\n    \"includeVersion\": true\n}\n"
  },
  {
    "path": "testing/hrana-test-server/.gitignore",
    "content": "Session.vim\n*.pyc\n"
  },
  {
    "path": "testing/hrana-test-server/LICENSE",
    "content": "MIT License\n\nCopyright 2023 the sqld authors\n\nPermission is hereby granted, free of charge, to any person obtaining a copy of\nthis software and associated documentation files (the \"Software\"), to deal in\nthe Software without restriction, including without limitation the rights to\nuse, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of\nthe Software, and to permit persons to whom the Software is furnished to do so,\nsubject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS\nFOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR\nCOPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER\nIN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN\nCONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n"
  },
  {
    "path": "testing/hrana-test-server/README.md",
    "content": "# Test servers for Hrana\n\nThis repository contains simple Hrana servers implemented in Python, one for each version of the Hrana protocol. These servers are useful for testing our various Hrana libraries.\n\nBy default, the server creates a single temporary database for all HTTP requests and a new temporary database for every WebSocket connection, so multiple streams in the same WebSocket connection share the database, but are isolated from other WebSocket connections. However, if you pass environment variable `PERSISTENT_DB`, all HTTP requests and WebSocket connections will use that as the database file.\n\nIf you pass any arguments to the server, they will be interpreted as a command. After the server starts up, it spawns the command, waits for it to terminate, and returns its exit code.\n"
  },
  {
    "path": "testing/hrana-test-server/c3.py",
    "content": "import logging\nimport platform\nfrom ctypes import (\n    CDLL, POINTER, CFUNCTYPE,\n    pointer, byref, string_at, cast,\n    c_void_p, c_char_p,\n    c_int, c_int64, c_uint64, c_double, c_char,\n)\n\nfrom sqlite3_error_map import sqlite_error_code_to_name\n\nlogger = logging.getLogger(\"server\")\n\nc_sqlite3_p = c_void_p\nc_sqlite3_stmt_p = c_void_p\nc_exec_callback_fn = CFUNCTYPE(c_int, c_void_p, c_int, POINTER(c_char_p), POINTER(c_char_p))\nc_destructor_fn = CFUNCTYPE(None, c_void_p)\n\nlibfile_platform = {\n    \"Linux\": \"libsqlite3.so\",\n    \"Darwin\": \"libsqlite3.dylib\",\n}\n\nplatform_name = platform.system()\nlibfile = libfile_platform[platform_name]\nlib = CDLL(libfile)\nlib.sqlite3_open_v2.argtypes = (c_char_p, POINTER(c_sqlite3_p), c_int, c_char_p,)\nlib.sqlite3_open_v2.restype = c_int\nlib.sqlite3_close_v2.argtypes = (c_sqlite3_p,)\nlib.sqlite3_close_v2.restype = c_int\nlib.sqlite3_extended_result_codes.argtypes = (c_sqlite3_p, c_int,)\nlib.sqlite3_extended_result_codes.restype = c_int\nlib.sqlite3_errmsg.argtypes = (c_sqlite3_p,)\nlib.sqlite3_errmsg.restype = c_char_p\nlib.sqlite3_errstr.argtypes = (c_int,)\nlib.sqlite3_errstr.restype = c_char_p\nlib.sqlite3_exec.argtypes = (c_sqlite3_p, c_char_p, c_exec_callback_fn, c_void_p, POINTER(c_char_p),)\nlib.sqlite3_exec.restype = c_int\nlib.sqlite3_txn_state.argtypes = (c_sqlite3_p, c_char_p,)\nlib.sqlite3_txn_state.restype = c_int\nlib.sqlite3_changes64.argtypes = (c_sqlite3_p,)\nlib.sqlite3_changes64.restype = c_int64\nlib.sqlite3_total_changes64.argtypes = (c_sqlite3_p,)\nlib.sqlite3_total_changes64.restype = c_int64\nlib.sqlite3_last_insert_rowid.argtypes = (c_sqlite3_p,)\nlib.sqlite3_last_insert_rowid.restype = c_int64\nlib.sqlite3_limit.argtypes = (c_sqlite3_p, c_int, c_int,)\nlib.sqlite3_limit.restype = c_int\nlib.sqlite3_busy_timeout.argtypes = (c_sqlite3_p, c_int,)\nlib.sqlite3_busy_timeout.restype = c_int\nlib.sqlite3_get_autocommit.argtypes = (c_sqlite3_p,)\nlib.sqlite3_get_autocommit.restype = c_int\n\nlib.sqlite3_prepare_v2.argtypes = (\n    c_sqlite3_p, c_void_p, c_int, POINTER(c_sqlite3_stmt_p), POINTER(c_void_p),)\nlib.sqlite3_prepare_v2.restype = c_int\nlib.sqlite3_finalize.argtypes = (c_sqlite3_stmt_p,)\nlib.sqlite3_finalize.restype = c_int\nlib.sqlite3_step.argtypes = (c_sqlite3_stmt_p,)\nlib.sqlite3_step.restype = c_int\nlib.sqlite3_bind_parameter_count.argtypes = (c_sqlite3_stmt_p,)\nlib.sqlite3_bind_parameter_count.restype = c_int\nlib.sqlite3_bind_parameter_index.argtypes = (c_sqlite3_stmt_p, c_char_p,)\nlib.sqlite3_bind_parameter_index.restype = c_int\nlib.sqlite3_bind_parameter_name.argtypes = (c_sqlite3_stmt_p, c_int,)\nlib.sqlite3_bind_parameter_name.restype = c_char_p\nlib.sqlite3_bind_blob64.argtypes = (c_sqlite3_stmt_p, c_int, c_void_p, c_uint64, c_destructor_fn,)\nlib.sqlite3_bind_blob64.restype = c_int\nlib.sqlite3_bind_text.argtypes = (c_sqlite3_stmt_p, c_int, POINTER(c_char), c_int, c_destructor_fn,)\nlib.sqlite3_bind_text.restype = c_int\nlib.sqlite3_bind_double.argtypes = (c_sqlite3_stmt_p, c_int, c_double,)\nlib.sqlite3_bind_double.restype = c_int\nlib.sqlite3_bind_int64.argtypes = (c_sqlite3_stmt_p, c_int, c_int64,)\nlib.sqlite3_bind_int64.restype = c_int\nlib.sqlite3_bind_null.argtypes = (c_sqlite3_stmt_p, c_int,)\nlib.sqlite3_bind_null.restype = c_int\nlib.sqlite3_column_count.argtypes = (c_sqlite3_stmt_p,)\nlib.sqlite3_column_count.restype = c_int\nlib.sqlite3_column_name.argtypes = (c_sqlite3_stmt_p, c_int,)\nlib.sqlite3_column_name.restype = c_char_p\nlib.sqlite3_column_decltype.argtypes = (c_sqlite3_stmt_p, c_int,)\nlib.sqlite3_column_decltype.restype = c_char_p\nlib.sqlite3_column_type.argtypes = (c_sqlite3_stmt_p, c_int,)\nlib.sqlite3_column_type.restype = c_int\nlib.sqlite3_column_blob.argtypes = (c_sqlite3_stmt_p, c_int,)\nlib.sqlite3_column_blob.restype = c_void_p\nlib.sqlite3_column_text.argtypes = (c_sqlite3_stmt_p, c_int,)\nlib.sqlite3_column_text.restype = c_void_p\nlib.sqlite3_column_bytes.argtypes = (c_sqlite3_stmt_p, c_int,)\nlib.sqlite3_column_bytes.restype = c_int\nlib.sqlite3_column_double.argtypes = (c_sqlite3_stmt_p, c_int,)\nlib.sqlite3_column_double.restype = c_double\nlib.sqlite3_column_int64.argtypes = (c_sqlite3_stmt_p, c_int,)\nlib.sqlite3_column_int64.restype = c_int64\nlib.sqlite3_stmt_readonly.argtypes = (c_sqlite3_stmt_p,)\nlib.sqlite3_stmt_readonly.restype = c_int\nlib.sqlite3_stmt_isexplain.argtypes = (c_sqlite3_stmt_p,)\nlib.sqlite3_stmt_isexplain.restype = c_int\n\nSQLITE_OPEN_READWRITE = 0x00000002\nSQLITE_OPEN_CREATE = 0x00000004\nSQLITE_TRANSIENT = c_destructor_fn(-1)\nSQLITE_ROW = 100\nSQLITE_DONE = 101\n\nSQLITE_INTEGER = 1\nSQLITE_FLOAT = 2\nSQLITE_BLOB = 4\nSQLITE_NULL = 5\nSQLITE_TEXT = 3\n\nSQLITE_LIMIT_LENGTH = 0\nSQLITE_LIMIT_SQL_LENGTH = 1\nSQLITE_LIMIT_COLUMN = 2\nSQLITE_LIMIT_EXPR_DEPTH = 3\nSQLITE_LIMIT_COMPOUND_SELECT = 4\nSQLITE_LIMIT_VDBE_OP = 5\nSQLITE_LIMIT_FUNCTION_ARG = 6\nSQLITE_LIMIT_ATTACHED = 7\nSQLITE_LIMIT_LIKE_PATTERN_LENGTH = 8\nSQLITE_LIMIT_VARIABLE_NUMBER = 9\nSQLITE_LIMIT_TRIGGER_DEPTH = 10\nSQLITE_LIMIT_WORKER_THREADS = 11\n\n\nclass Conn:\n    def __init__(self, db_ptr):\n        self.db_ptr = db_ptr\n\n    @classmethod\n    def open(cls, filename):\n        filename_ptr = c_char_p(filename.encode())\n        db_ptr = c_sqlite3_p()\n        flags = SQLITE_OPEN_READWRITE|SQLITE_OPEN_CREATE\n        vfs_ptr = c_char_p()\n        _try(lib.sqlite3_open_v2(filename_ptr, byref(db_ptr), flags, vfs_ptr))\n        return cls(db_ptr)\n\n    def close(self):\n        if self.db_ptr is not None:\n            lib.sqlite3_close_v2(self.db_ptr)\n            self.db_ptr = None\n\n    def __del__(self):\n        self.close()\n\n    def extended_result_codes(self, onoff):\n        assert self.db_ptr is not None\n        lib.sqlite3_extended_result_codes(self.db_ptr, onoff)\n\n    def errmsg(self):\n        assert self.db_ptr is not None\n        return str(lib.sqlite3_errmsg(self.db_ptr).decode())\n\n    @classmethod\n    def errstr(cls, code):\n        return str(lib.sqlite3_errstr(code).decode())\n\n    def exec(self, sql):\n        assert self.db_ptr is not None\n        sql_ptr = c_char_p(sql.encode())\n        callback_ptr = c_exec_callback_fn()\n        arg_ptr = c_void_p()\n        errmsg_ptr_ptr = pointer(c_char_p())\n        _try(lib.sqlite3_exec(self.db_ptr, sql_ptr, callback_ptr, arg_ptr, errmsg_ptr_ptr), self)\n\n    def txn_state(self):\n        assert self.db_ptr is not None\n        schema_ptr = c_char_p()\n        return lib.sqlite3_txn_state(self.db_ptr, schema_ptr)\n\n    def prepare(self, sql):\n        assert self.db_ptr is not None\n        sql = sql.encode()\n        sql_data = c_char_p(sql)\n        sql_ptr = cast(sql_data, c_void_p)\n        sql_len = c_int(len(sql) + 1)\n        stmt_ptr = c_sqlite3_stmt_p()\n        tail_ptr = c_void_p()\n        _try(lib.sqlite3_prepare_v2(self.db_ptr, sql_ptr, sql_len, byref(stmt_ptr), byref(tail_ptr)), self)\n        if stmt_ptr.value is None:\n            return None, b\"\"\n        tail = sql[tail_ptr.value - sql_ptr.value:]\n        return Stmt(self, stmt_ptr), tail.decode()\n\n    def changes(self):\n        assert self.db_ptr is not None\n        return lib.sqlite3_changes64(self.db_ptr)\n\n    def total_changes(self):\n        assert self.db_ptr is not None\n        return lib.sqlite3_total_changes64(self.db_ptr)\n\n    def last_insert_rowid(self):\n        assert self.db_ptr is not None\n        return lib.sqlite3_last_insert_rowid(self.db_ptr)\n\n    def limit(self, id, new_val):\n        assert self.db_ptr is not None\n        return lib.sqlite3_limit(self.db_ptr, id, new_val)\n\n    def busy_timeout(self, ms):\n        assert self.db_ptr is not None\n        lib.sqlite3_busy_timeout(self.db_ptr, ms)\n\n    def get_autocommit(self):\n        assert self.db_ptr is not None\n        return lib.sqlite3_get_autocommit(self.db_ptr) != 0\n\nclass Stmt:\n    def __init__(self, conn, stmt_ptr):\n        self.conn = conn\n        self.stmt_ptr = stmt_ptr\n\n    def close(self):\n        if self.stmt_ptr is not None:\n            lib.sqlite3_finalize(self.stmt_ptr)\n            self.stmt_ptr = None\n\n    def __del__(self):\n        self.close()\n\n    def param_count(self):\n        assert self.stmt_ptr is not None\n        return lib.sqlite3_bind_parameter_count(self.stmt_ptr)\n\n    def param_index(self, name):\n        assert self.stmt_ptr is not None\n        name_ptr = c_char_p(name.encode())\n        return lib.sqlite3_bind_parameter_index(self.stmt_ptr, name_ptr)\n\n    def param_name(self, param_i):\n        assert self.stmt_ptr is not None\n        name = lib.sqlite3_bind_parameter_name(self.stmt_ptr, param_i)\n        return name.decode() if name is not None else None\n\n    def bind(self, param_i, value):\n        assert self.stmt_ptr is not None\n        if isinstance(value, str):\n            value = value.encode()\n            value_ptr, value_len = c_char_p(value), c_int(len(value))\n            _try(lib.sqlite3_bind_text(self.stmt_ptr, param_i, value_ptr, value_len, SQLITE_TRANSIENT), self.conn)\n        elif isinstance(value, bytes):\n            value_ptr, value_len = c_char_p(value), c_uint64(len(value))\n            _try(lib.sqlite3_bind_blob64(self.stmt_ptr, param_i, value_ptr, value_len, SQLITE_TRANSIENT), self.conn)\n        elif isinstance(value, int):\n            _try(lib.sqlite3_bind_int64(self.stmt_ptr, param_i, c_int64(value)), self.conn)\n        elif isinstance(value, float):\n            _try(lib.sqlite3_bind_double(self.stmt_ptr, param_i, c_double(value)), self.conn)\n        elif value is None:\n            _try(lib.sqlite3_bind_null(self.stmt_ptr, param_i), self.conn)\n        else:\n            raise ValueError(f\"Cannot bind {type(value)!r}\")\n\n    def step(self):\n        assert self.stmt_ptr is not None\n        res = lib.sqlite3_step(self.stmt_ptr)\n        if res == SQLITE_DONE:\n            return False\n        elif res == SQLITE_ROW:\n            return True\n        _try(res, self.conn)\n\n    def column_count(self):\n        assert self.stmt_ptr is not None\n        return lib.sqlite3_column_count(self.stmt_ptr)\n\n    def column_name(self, column_i):\n        assert self.stmt_ptr is not None\n        return lib.sqlite3_column_name(self.stmt_ptr, column_i).decode()\n\n    def column_decltype(self, column_i):\n        assert self.stmt_ptr is not None\n        name = lib.sqlite3_column_decltype(self.stmt_ptr, column_i)\n        return name.decode() if name is not None else name\n\n    def column(self, column_i):\n        assert self.stmt_ptr is not None\n        typ = lib.sqlite3_column_type(self.stmt_ptr, column_i)\n        if typ == SQLITE_INTEGER:\n            return lib.sqlite3_column_int64(self.stmt_ptr, column_i)\n        elif typ == SQLITE_FLOAT:\n            return lib.sqlite3_column_double(self.stmt_ptr, column_i)\n        elif typ == SQLITE_BLOB:\n            data_ptr = lib.sqlite3_column_blob(self.stmt_ptr, column_i)\n            data_len = lib.sqlite3_column_bytes(self.stmt_ptr, column_i)\n            return bytes(string_at(data_ptr, data_len))\n        elif typ == SQLITE_TEXT:\n            data_ptr = lib.sqlite3_column_text(self.stmt_ptr, column_i)\n            data_len = lib.sqlite3_column_bytes(self.stmt_ptr, column_i)\n            b = bytes(string_at(data_ptr, data_len))\n            try:\n                return b.decode()\n            except UnicodeDecodeError:\n                logger.debug(\"Could not decode column %s, bytes %s\", column_i, b, exc_info=True)\n                raise\n        elif typ == SQLITE_NULL:\n            return None\n        else:\n            raise ValueError(f\"Unknown SQLite type {typ}\")\n\n    def readonly(self):\n        assert self.stmt_ptr is not None\n        return lib.sqlite3_stmt_readonly(self.stmt_ptr) != 0\n\n    def isexplain(self):\n        assert self.stmt_ptr is not None\n        return lib.sqlite3_stmt_isexplain(self.stmt_ptr)\n\n\nclass SqliteError(RuntimeError):\n    def __init__(self, message, error_code=None) -> None:\n        super().__init__(message)\n        self.error_code = error_code\n        self.error_name = sqlite_error_code_to_name.get(error_code)\n\n\ndef _try(error_code, conn=None):\n    if error_code == 0:\n        return\n\n    error_str = Conn.errstr(error_code)\n    if conn is not None:\n        details = f\": {conn.errmsg()}\"\n\n    message = f\"SQLite function returned error code {error_code} ({error_str}){details}\"\n    raise SqliteError(message, error_code)\n"
  },
  {
    "path": "testing/hrana-test-server/from_proto.py",
    "content": "import base64\n\ndef ws_client_msg(p):\n    ty = p.WhichOneof(\"msg\")\n    if ty == \"hello\":\n        return ws_hello_msg(p.hello)\n    elif ty == \"request\":\n        return ws_request_msg(p.request)\n    else:\n        raise RuntimeError(\"Unknown type of ClientMsg\")\n\ndef ws_hello_msg(p):\n    return {\n        \"type\": \"hello\",\n        \"jwt\": p.jwt if p.HasField(\"jwt\") else None,\n    }\n\ndef ws_request_msg(p):\n    ty = p.WhichOneof(\"request\")\n    if ty == \"open_stream\":\n        request = ws_open_stream_req(p.open_stream)\n    elif ty == \"close_stream\":\n        request = ws_close_stream_req(p.close_stream)\n    elif ty == \"execute\":\n        request = ws_execute_req(p.execute)\n    elif ty == \"batch\":\n        request = ws_batch_req(p.batch)\n    elif ty == \"open_cursor\":\n        request = ws_open_cursor_req(p.open_cursor)\n    elif ty == \"close_cursor\":\n        request = ws_close_cursor_req(p.close_cursor)\n    elif ty == \"fetch_cursor\":\n        request = ws_fetch_cursor_req(p.fetch_cursor)\n    elif ty == \"sequence\":\n        request = ws_sequence_req(p.sequence)\n    elif ty == \"describe\":\n        request = ws_describe_req(p.describe)\n    elif ty == \"store_sql\":\n        request = ws_store_sql_req(p.store_sql)\n    elif ty == \"close_sql\":\n        request = ws_close_sql_req(p.close_sql)\n    elif ty == \"get_autocommit\":\n        request = ws_get_autocommit_req(p.get_autocommit)\n    else:\n        raise RuntimeError(\"Unknown type of RequestMsg\")\n    return {\"type\": \"request\", \"request_id\": p.request_id, \"request\": request}\n\ndef ws_open_stream_req(p):\n    return {\"type\": \"open_stream\", \"stream_id\": p.stream_id}\n\ndef ws_close_stream_req(p):\n    return {\"type\": \"close_stream\", \"stream_id\": p.stream_id}\n\ndef ws_execute_req(p):\n    return {\n        \"type\": \"execute\",\n        \"stream_id\": p.stream_id,\n        \"stmt\": stmt(p.stmt),\n    }\n\ndef ws_batch_req(p):\n    return {\n        \"type\": \"batch\",\n        \"stream_id\": p.stream_id,\n        \"batch\": batch(p.batch),\n    }\n\ndef ws_open_cursor_req(p):\n    return {\n        \"type\": \"open_cursor\",\n        \"stream_id\": p.stream_id,\n        \"cursor_id\": p.cursor_id,\n        \"batch\": batch(p.batch),\n    }\n\ndef ws_close_cursor_req(p):\n    return {\n        \"type\": \"close_cursor\",\n        \"cursor_id\": p.cursor_id,\n    }\n\ndef ws_fetch_cursor_req(p):\n    return {\n        \"type\": \"fetch_cursor\",\n        \"cursor_id\": p.cursor_id,\n        \"max_count\": p.max_count,\n    }\n\ndef ws_sequence_req(p):\n    return {\n        \"type\": \"sequence\",\n        \"stream_id\": p.stream_id,\n        \"sql\": p.sql if p.HasField(\"sql\") else None,\n        \"sql_id\": p.sql_id if p.HasField(\"sql_id\") else None,\n    }\n\ndef ws_describe_req(p):\n    return {\n        \"type\": \"describe\",\n        \"stream_id\": p.stream_id,\n        \"sql\": p.sql if p.HasField(\"sql\") else None,\n        \"sql_id\": p.sql_id if p.HasField(\"sql_id\") else None,\n    }\n\ndef ws_store_sql_req(p):\n    return {\n        \"type\": \"store_sql\",\n        \"sql_id\": p.sql_id,\n        \"sql\": p.sql,\n    }\n\ndef ws_close_sql_req(p):\n    return {\n        \"type\": \"close_sql\",\n        \"sql_id\": p.sql_id,\n    }\n\ndef ws_get_autocommit_req(p):\n    return {\n        \"type\": \"get_autocommit\",\n        \"stream_id\": p.stream_id,\n    }\n\n\n\ndef http_pipeline_req_body(p):\n    return {\n        \"baton\": p.baton if p.HasField(\"baton\") else None,\n        \"requests\": [http_stream_request(p) for p in p.requests],\n    }\n\ndef http_stream_request(p):\n    ty = p.WhichOneof(\"request\")\n    if ty == \"close\":\n        return {\"type\": \"close\"}\n    if ty == \"execute\":\n        return http_execute_stream_req(p.execute)\n    elif ty == \"batch\":\n        return http_batch_stream_req(p.batch)\n    elif ty == \"sequence\":\n        return http_sequence_stream_req(p.sequence)\n    elif ty == \"describe\":\n        return http_describe_stream_req(p.describe)\n    elif ty == \"store_sql\":\n        return http_store_sql_stream_req(p.store_sql)\n    elif ty == \"close_sql\":\n        return http_close_sql_stream_req(p.close_sql)\n    elif ty == \"get_autocommit\":\n        return {\"type\": \"get_autocommit\"}\n    else:\n        raise RuntimeError(\"Unknown type of StreamRequest\")\n\ndef http_execute_stream_req(p):\n    return {\"type\": \"execute\", \"stmt\": stmt(p.stmt)}\n\ndef http_batch_stream_req(p):\n    return {\"type\": \"batch\", \"batch\": batch(p.batch)}\n\ndef http_sequence_stream_req(p):\n    return {\n        \"type\": \"sequence\",\n        \"sql\": p.sql if p.HasField(\"sql\") else None,\n        \"sql_id\": p.sql_id if p.HasField(\"sql_id\") else None,\n    }\n\ndef http_describe_stream_req(p):\n    return {\n        \"type\": \"describe\",\n        \"sql\": p.sql if p.HasField(\"sql\") else None,\n        \"sql_id\": p.sql_id if p.HasField(\"sql_id\") else None,\n    }\n\ndef http_store_sql_stream_req(p):\n    return {\"type\": \"store_sql\", \"sql_id\": p.sql_id, \"sql\": p.sql}\n\ndef http_close_sql_stream_req(p):\n    return {\"type\": \"close_sql\", \"sql_id\": p.sql_id}\n\ndef http_cursor_req_body(p):\n    return {\n        \"baton\": p.baton if p.HasField(\"baton\") else None,\n        \"batch\": batch(p.batch),\n    }\n\n\n\ndef batch(p):\n    return {\"steps\": [batch_step(p) for p in p.steps]}\n\ndef batch_step(p):\n    return {\n        \"condition\": batch_cond(p.condition) if p.HasField(\"condition\") else None,\n        \"stmt\": stmt(p.stmt),\n    }\n\ndef batch_cond(p):\n    ty = p.WhichOneof(\"cond\")\n    if ty == \"step_ok\":\n        return {\"type\": \"ok\", \"step\": p.step_ok}\n    elif ty == \"step_error\":\n        return {\"type\": \"error\", \"step\": p.step_error}\n    elif ty == \"not\":\n        return {\"type\": \"not\", \"cond\": batch_cond(getattr(p, \"not\"))}\n    elif ty == \"and\":\n        return {\"type\": \"and\", \"conds\": [batch_cond(p) for p in getattr(p, \"and\").conds]}\n    elif ty == \"or\":\n        return {\"type\": \"or\", \"conds\": [batch_cond(p) for p in getattr(p, \"or\").conds]}\n    elif ty == \"is_autocommit\":\n        return {\"type\": \"is_autocommit\"}\n    else:\n        raise RuntimeError(\"Unknown type of BatchCond\")\n\ndef stmt(p):\n    return {\n        \"sql\": p.sql if p.HasField(\"sql\") else None,\n        \"sql_id\": p.sql_id if p.HasField(\"sql_id\") else None,\n        \"args\": [value(p) for p in p.args],\n        \"named_args\": [named_arg(p) for p in p.named_args],\n        \"want_rows\": p.want_rows if p.HasField(\"want_rows\") else None,\n    }\n\ndef named_arg(p):\n    return {\n        \"name\": p.name,\n        \"value\": value(p.value),\n    }\n\ndef value(p):\n    ty = p.WhichOneof(\"value\")\n    if ty == \"null\":\n        return {\"type\": \"null\"}\n    elif ty == \"integer\":\n        return {\"type\": \"integer\", \"value\": str(p.integer)}\n    elif ty == \"float\":\n        return {\"type\": \"float\", \"value\": p.float}\n    elif ty == \"text\":\n        return {\"type\": \"text\", \"value\": p.text}\n    elif ty == \"blob\":\n        return {\"type\": \"blob\", \"base64\": base64.b64encode(p.blob)}\n    else:\n        raise RuntimeError(\"Unknown type of Value\")\n"
  },
  {
    "path": "testing/hrana-test-server/gen_sqlite3_error_map.py",
    "content": "import re\n\n# usage:\n#    python gen_sqlite3_error_map.py > sqlite3_error_map.py\n\n# https://www.sqlite.org/c3ref/c_abort.html\n_sqlite_h_ = \"\"\"\"\n#define SQLITE_OK           0   /* Successful result */\n/* beginning-of-error-codes */\n#define SQLITE_ERROR        1   /* Generic error */\n#define SQLITE_INTERNAL     2   /* Internal logic error in SQLite */\n#define SQLITE_PERM         3   /* Access permission denied */\n#define SQLITE_ABORT        4   /* Callback routine requested an abort */\n#define SQLITE_BUSY         5   /* The database file is locked */\n#define SQLITE_LOCKED       6   /* A table in the database is locked */\n#define SQLITE_NOMEM        7   /* A malloc() failed */\n#define SQLITE_READONLY     8   /* Attempt to write a readonly database */\n#define SQLITE_INTERRUPT    9   /* Operation terminated by sqlite3_interrupt()*/\n#define SQLITE_IOERR       10   /* Some kind of disk I/O error occurred */\n#define SQLITE_CORRUPT     11   /* The database disk image is malformed */\n#define SQLITE_NOTFOUND    12   /* Unknown opcode in sqlite3_file_control() */\n#define SQLITE_FULL        13   /* Insertion failed because database is full */\n#define SQLITE_CANTOPEN    14   /* Unable to open the database file */\n#define SQLITE_PROTOCOL    15   /* Database lock protocol error */\n#define SQLITE_EMPTY       16   /* Internal use only */\n#define SQLITE_SCHEMA      17   /* The database schema changed */\n#define SQLITE_TOOBIG      18   /* String or BLOB exceeds size limit */\n#define SQLITE_CONSTRAINT  19   /* Abort due to constraint violation */\n#define SQLITE_MISMATCH    20   /* Data type mismatch */\n#define SQLITE_MISUSE      21   /* Library used incorrectly */\n#define SQLITE_NOLFS       22   /* Uses OS features not supported on host */\n#define SQLITE_AUTH        23   /* Authorization denied */\n#define SQLITE_FORMAT      24   /* Not used */\n#define SQLITE_RANGE       25   /* 2nd parameter to sqlite3_bind out of range */\n#define SQLITE_NOTADB      26   /* File opened that is not a database file */\n#define SQLITE_NOTICE      27   /* Notifications from sqlite3_log() */\n#define SQLITE_WARNING     28   /* Warnings from sqlite3_log() */\n#define SQLITE_ROW         100  /* sqlite3_step() has another row ready */\n#define SQLITE_DONE        101  /* sqlite3_step() has finished executing */\n/* end-of-error-codes */\n\"\"\"\n_sqlite_h_parse_re = re.compile(\n    r\"^\\s*#define\\s+(?P<name>\\w+)\\s+(?P<code>\\d+).*$\",\n    re.MULTILINE | re.ASCII,\n)\n_sqlite_error_code_to_name = {}\n_sqlite_error_name_to_code = {}\nfor m in _sqlite_h_parse_re.finditer(_sqlite_h_):\n    code = int(m.group(\"code\"))\n    name = m.group(\"name\")\n    _sqlite_error_code_to_name[code] = name\n    _sqlite_error_name_to_code[name] = code\n\n# https://www.sqlite.org/c3ref/c_abort_rollback.html\n_sqlite_h_extended = \"\"\"\n#define SQLITE_ERROR_MISSING_COLLSEQ   (SQLITE_ERROR | (1<<8))\n#define SQLITE_ERROR_RETRY             (SQLITE_ERROR | (2<<8))\n#define SQLITE_ERROR_SNAPSHOT          (SQLITE_ERROR | (3<<8))\n#define SQLITE_IOERR_READ              (SQLITE_IOERR | (1<<8))\n#define SQLITE_IOERR_SHORT_READ        (SQLITE_IOERR | (2<<8))\n#define SQLITE_IOERR_WRITE             (SQLITE_IOERR | (3<<8))\n#define SQLITE_IOERR_FSYNC             (SQLITE_IOERR | (4<<8))\n#define SQLITE_IOERR_DIR_FSYNC         (SQLITE_IOERR | (5<<8))\n#define SQLITE_IOERR_TRUNCATE          (SQLITE_IOERR | (6<<8))\n#define SQLITE_IOERR_FSTAT             (SQLITE_IOERR | (7<<8))\n#define SQLITE_IOERR_UNLOCK            (SQLITE_IOERR | (8<<8))\n#define SQLITE_IOERR_RDLOCK            (SQLITE_IOERR | (9<<8))\n#define SQLITE_IOERR_DELETE            (SQLITE_IOERR | (10<<8))\n#define SQLITE_IOERR_BLOCKED           (SQLITE_IOERR | (11<<8))\n#define SQLITE_IOERR_NOMEM             (SQLITE_IOERR | (12<<8))\n#define SQLITE_IOERR_ACCESS            (SQLITE_IOERR | (13<<8))\n#define SQLITE_IOERR_CHECKRESERVEDLOCK (SQLITE_IOERR | (14<<8))\n#define SQLITE_IOERR_LOCK              (SQLITE_IOERR | (15<<8))\n#define SQLITE_IOERR_CLOSE             (SQLITE_IOERR | (16<<8))\n#define SQLITE_IOERR_DIR_CLOSE         (SQLITE_IOERR | (17<<8))\n#define SQLITE_IOERR_SHMOPEN           (SQLITE_IOERR | (18<<8))\n#define SQLITE_IOERR_SHMSIZE           (SQLITE_IOERR | (19<<8))\n#define SQLITE_IOERR_SHMLOCK           (SQLITE_IOERR | (20<<8))\n#define SQLITE_IOERR_SHMMAP            (SQLITE_IOERR | (21<<8))\n#define SQLITE_IOERR_SEEK              (SQLITE_IOERR | (22<<8))\n#define SQLITE_IOERR_DELETE_NOENT      (SQLITE_IOERR | (23<<8))\n#define SQLITE_IOERR_MMAP              (SQLITE_IOERR | (24<<8))\n#define SQLITE_IOERR_GETTEMPPATH       (SQLITE_IOERR | (25<<8))\n#define SQLITE_IOERR_CONVPATH          (SQLITE_IOERR | (26<<8))\n#define SQLITE_IOERR_VNODE             (SQLITE_IOERR | (27<<8))\n#define SQLITE_IOERR_AUTH              (SQLITE_IOERR | (28<<8))\n#define SQLITE_IOERR_BEGIN_ATOMIC      (SQLITE_IOERR | (29<<8))\n#define SQLITE_IOERR_COMMIT_ATOMIC     (SQLITE_IOERR | (30<<8))\n#define SQLITE_IOERR_ROLLBACK_ATOMIC   (SQLITE_IOERR | (31<<8))\n#define SQLITE_IOERR_DATA              (SQLITE_IOERR | (32<<8))\n#define SQLITE_IOERR_CORRUPTFS         (SQLITE_IOERR | (33<<8))\n#define SQLITE_LOCKED_SHAREDCACHE      (SQLITE_LOCKED |  (1<<8))\n#define SQLITE_LOCKED_VTAB             (SQLITE_LOCKED |  (2<<8))\n#define SQLITE_BUSY_RECOVERY           (SQLITE_BUSY   |  (1<<8))\n#define SQLITE_BUSY_SNAPSHOT           (SQLITE_BUSY   |  (2<<8))\n#define SQLITE_BUSY_TIMEOUT            (SQLITE_BUSY   |  (3<<8))\n#define SQLITE_CANTOPEN_NOTEMPDIR      (SQLITE_CANTOPEN | (1<<8))\n#define SQLITE_CANTOPEN_ISDIR          (SQLITE_CANTOPEN | (2<<8))\n#define SQLITE_CANTOPEN_FULLPATH       (SQLITE_CANTOPEN | (3<<8))\n#define SQLITE_CANTOPEN_CONVPATH       (SQLITE_CANTOPEN | (4<<8))\n#define SQLITE_CANTOPEN_DIRTYWAL       (SQLITE_CANTOPEN | (5<<8)) /* Not Used */\n#define SQLITE_CANTOPEN_SYMLINK        (SQLITE_CANTOPEN | (6<<8))\n#define SQLITE_CORRUPT_VTAB            (SQLITE_CORRUPT | (1<<8))\n#define SQLITE_CORRUPT_SEQUENCE        (SQLITE_CORRUPT | (2<<8))\n#define SQLITE_CORRUPT_INDEX           (SQLITE_CORRUPT | (3<<8))\n#define SQLITE_READONLY_RECOVERY       (SQLITE_READONLY | (1<<8))\n#define SQLITE_READONLY_CANTLOCK       (SQLITE_READONLY | (2<<8))\n#define SQLITE_READONLY_ROLLBACK       (SQLITE_READONLY | (3<<8))\n#define SQLITE_READONLY_DBMOVED        (SQLITE_READONLY | (4<<8))\n#define SQLITE_READONLY_CANTINIT       (SQLITE_READONLY | (5<<8))\n#define SQLITE_READONLY_DIRECTORY      (SQLITE_READONLY | (6<<8))\n#define SQLITE_ABORT_ROLLBACK          (SQLITE_ABORT | (2<<8))\n#define SQLITE_CONSTRAINT_CHECK        (SQLITE_CONSTRAINT | (1<<8))\n#define SQLITE_CONSTRAINT_COMMITHOOK   (SQLITE_CONSTRAINT | (2<<8))\n#define SQLITE_CONSTRAINT_FOREIGNKEY   (SQLITE_CONSTRAINT | (3<<8))\n#define SQLITE_CONSTRAINT_FUNCTION     (SQLITE_CONSTRAINT | (4<<8))\n#define SQLITE_CONSTRAINT_NOTNULL      (SQLITE_CONSTRAINT | (5<<8))\n#define SQLITE_CONSTRAINT_PRIMARYKEY   (SQLITE_CONSTRAINT | (6<<8))\n#define SQLITE_CONSTRAINT_TRIGGER      (SQLITE_CONSTRAINT | (7<<8))\n#define SQLITE_CONSTRAINT_UNIQUE       (SQLITE_CONSTRAINT | (8<<8))\n#define SQLITE_CONSTRAINT_VTAB         (SQLITE_CONSTRAINT | (9<<8))\n#define SQLITE_CONSTRAINT_ROWID        (SQLITE_CONSTRAINT |(10<<8))\n#define SQLITE_CONSTRAINT_PINNED       (SQLITE_CONSTRAINT |(11<<8))\n#define SQLITE_CONSTRAINT_DATATYPE     (SQLITE_CONSTRAINT |(12<<8))\n#define SQLITE_NOTICE_RECOVER_WAL      (SQLITE_NOTICE | (1<<8))\n#define SQLITE_NOTICE_RECOVER_ROLLBACK (SQLITE_NOTICE | (2<<8))\n#define SQLITE_NOTICE_RBU              (SQLITE_NOTICE | (3<<8))\n#define SQLITE_WARNING_AUTOINDEX       (SQLITE_WARNING | (1<<8))\n#define SQLITE_AUTH_USER               (SQLITE_AUTH | (1<<8))\n#define SQLITE_OK_LOAD_PERMANENTLY     (SQLITE_OK | (1<<8))\n#define SQLITE_OK_SYMLINK              (SQLITE_OK | (2<<8)) /* internal use only */\n\"\"\"\n_sqlite_h_extended_parse_re = re.compile(\n    r\"^\\s*#define\\s+(?P<name>\\w+)\\s+[(]\\s*\" +\n    r\"(?P<dep>\\w+)\\s*[|]\\s*\" +\n    r\"[(]\\s*(?P<bit>\\d+)\\s*<<\\s*(?P<shift>\\d+)\\s*[)]\" +\n    r\"\\s*[)].*$\",\n    re.MULTILINE | re.ASCII,\n)\nfor m in _sqlite_h_extended_parse_re.finditer(_sqlite_h_extended):\n    dep = _sqlite_error_name_to_code[m.group(\"dep\")]\n    bit = int(m.group(\"bit\"))\n    shift = int(m.group(\"shift\"))\n    code = dep | (bit << shift)\n    name = m.group(\"name\")\n    _sqlite_error_code_to_name[code] = name\n    _sqlite_error_name_to_code[name] = code\n\nprint(\"sqlite_error_code_to_name = {\")\nfor k, v in sorted(_sqlite_error_code_to_name.items()):\n    print(f\"    {k}: \\\"{v}\\\",\")\nprint(\"}\")\n"
  },
  {
    "path": "testing/hrana-test-server/proto/generate.sh",
    "content": "#!/bin/sh\ncd $(dirname \"$0\")\nprotoc -I. *.proto --python_out=. --experimental_allow_proto3_optional\nsed -i 's/^import hrana_pb2 /from .. import hrana_pb2 /' hrana/*_pb2.py\n"
  },
  {
    "path": "testing/hrana-test-server/proto/hrana/http_pb2.py",
    "content": "# -*- coding: utf-8 -*-\n# Generated by the protocol buffer compiler.  DO NOT EDIT!\n# source: hrana.http.proto\n\"\"\"Generated protocol buffer code.\"\"\"\nfrom google.protobuf import descriptor as _descriptor\nfrom google.protobuf import descriptor_pool as _descriptor_pool\nfrom google.protobuf import symbol_database as _symbol_database\nfrom google.protobuf.internal import builder as _builder\n# @@protoc_insertion_point(imports)\n\n_sym_db = _symbol_database.Default()\n\n\nfrom .. import hrana_pb2 as hrana__pb2\n\n\nDESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\\n\\x10hrana.http.proto\\x12\\nhrana.http\\x1a\\x0bhrana.proto\\\"\\\\\\n\\x0fPipelineReqBody\\x12\\x12\\n\\x05\\x62\\x61ton\\x18\\x01 \\x01(\\tH\\x00\\x88\\x01\\x01\\x12+\\n\\x08requests\\x18\\x02 \\x03(\\x0b\\x32\\x19.hrana.http.StreamRequestB\\x08\\n\\x06_baton\\\"\\x7f\\n\\x10PipelineRespBody\\x12\\x12\\n\\x05\\x62\\x61ton\\x18\\x01 \\x01(\\tH\\x00\\x88\\x01\\x01\\x12\\x15\\n\\x08\\x62\\x61se_url\\x18\\x02 \\x01(\\tH\\x01\\x88\\x01\\x01\\x12)\\n\\x07results\\x18\\x03 \\x03(\\x0b\\x32\\x18.hrana.http.StreamResultB\\x08\\n\\x06_batonB\\x0b\\n\\t_base_url\\\"a\\n\\x0cStreamResult\\x12(\\n\\x02ok\\x18\\x01 \\x01(\\x0b\\x32\\x1a.hrana.http.StreamResponseH\\x00\\x12\\x1d\\n\\x05\\x65rror\\x18\\x02 \\x01(\\x0b\\x32\\x0c.hrana.ErrorH\\x00\\x42\\x08\\n\\x06result\\\"J\\n\\rCursorReqBody\\x12\\x12\\n\\x05\\x62\\x61ton\\x18\\x01 \\x01(\\tH\\x00\\x88\\x01\\x01\\x12\\x1b\\n\\x05\\x62\\x61tch\\x18\\x02 \\x01(\\x0b\\x32\\x0c.hrana.BatchB\\x08\\n\\x06_baton\\\"R\\n\\x0e\\x43ursorRespBody\\x12\\x12\\n\\x05\\x62\\x61ton\\x18\\x01 \\x01(\\tH\\x00\\x88\\x01\\x01\\x12\\x15\\n\\x08\\x62\\x61se_url\\x18\\x02 \\x01(\\tH\\x01\\x88\\x01\\x01\\x42\\x08\\n\\x06_batonB\\x0b\\n\\t_base_url\\\"\\xb1\\x03\\n\\rStreamRequest\\x12+\\n\\x05\\x63lose\\x18\\x01 \\x01(\\x0b\\x32\\x1a.hrana.http.CloseStreamReqH\\x00\\x12/\\n\\x07\\x65xecute\\x18\\x02 \\x01(\\x0b\\x32\\x1c.hrana.http.ExecuteStreamReqH\\x00\\x12+\\n\\x05\\x62\\x61tch\\x18\\x03 \\x01(\\x0b\\x32\\x1a.hrana.http.BatchStreamReqH\\x00\\x12\\x31\\n\\x08sequence\\x18\\x04 \\x01(\\x0b\\x32\\x1d.hrana.http.SequenceStreamReqH\\x00\\x12\\x31\\n\\x08\\x64\\x65scribe\\x18\\x05 \\x01(\\x0b\\x32\\x1d.hrana.http.DescribeStreamReqH\\x00\\x12\\x32\\n\\tstore_sql\\x18\\x06 \\x01(\\x0b\\x32\\x1d.hrana.http.StoreSqlStreamReqH\\x00\\x12\\x32\\n\\tclose_sql\\x18\\x07 \\x01(\\x0b\\x32\\x1d.hrana.http.CloseSqlStreamReqH\\x00\\x12<\\n\\x0eget_autocommit\\x18\\x08 \\x01(\\x0b\\x32\\\".hrana.http.GetAutocommitStreamReqH\\x00\\x42\\t\\n\\x07request\\\"\\xbb\\x03\\n\\x0eStreamResponse\\x12,\\n\\x05\\x63lose\\x18\\x01 \\x01(\\x0b\\x32\\x1b.hrana.http.CloseStreamRespH\\x00\\x12\\x30\\n\\x07\\x65xecute\\x18\\x02 \\x01(\\x0b\\x32\\x1d.hrana.http.ExecuteStreamRespH\\x00\\x12,\\n\\x05\\x62\\x61tch\\x18\\x03 \\x01(\\x0b\\x32\\x1b.hrana.http.BatchStreamRespH\\x00\\x12\\x32\\n\\x08sequence\\x18\\x04 \\x01(\\x0b\\x32\\x1e.hrana.http.SequenceStreamRespH\\x00\\x12\\x32\\n\\x08\\x64\\x65scribe\\x18\\x05 \\x01(\\x0b\\x32\\x1e.hrana.http.DescribeStreamRespH\\x00\\x12\\x33\\n\\tstore_sql\\x18\\x06 \\x01(\\x0b\\x32\\x1e.hrana.http.StoreSqlStreamRespH\\x00\\x12\\x33\\n\\tclose_sql\\x18\\x07 \\x01(\\x0b\\x32\\x1e.hrana.http.CloseSqlStreamRespH\\x00\\x12=\\n\\x0eget_autocommit\\x18\\x08 \\x01(\\x0b\\x32#.hrana.http.GetAutocommitStreamRespH\\x00\\x42\\n\\n\\x08response\\\"\\x10\\n\\x0e\\x43loseStreamReq\\\"\\x11\\n\\x0f\\x43loseStreamResp\\\"-\\n\\x10\\x45xecuteStreamReq\\x12\\x19\\n\\x04stmt\\x18\\x01 \\x01(\\x0b\\x32\\x0b.hrana.Stmt\\\"6\\n\\x11\\x45xecuteStreamResp\\x12!\\n\\x06result\\x18\\x01 \\x01(\\x0b\\x32\\x11.hrana.StmtResult\\\"-\\n\\x0e\\x42\\x61tchStreamReq\\x12\\x1b\\n\\x05\\x62\\x61tch\\x18\\x01 \\x01(\\x0b\\x32\\x0c.hrana.Batch\\\"5\\n\\x0f\\x42\\x61tchStreamResp\\x12\\\"\\n\\x06result\\x18\\x01 \\x01(\\x0b\\x32\\x12.hrana.BatchResult\\\"M\\n\\x11SequenceStreamReq\\x12\\x10\\n\\x03sql\\x18\\x01 \\x01(\\tH\\x00\\x88\\x01\\x01\\x12\\x13\\n\\x06sql_id\\x18\\x02 \\x01(\\x05H\\x01\\x88\\x01\\x01\\x42\\x06\\n\\x04_sqlB\\t\\n\\x07_sql_id\\\"\\x14\\n\\x12SequenceStreamResp\\\"M\\n\\x11\\x44\\x65scribeStreamReq\\x12\\x10\\n\\x03sql\\x18\\x01 \\x01(\\tH\\x00\\x88\\x01\\x01\\x12\\x13\\n\\x06sql_id\\x18\\x02 \\x01(\\x05H\\x01\\x88\\x01\\x01\\x42\\x06\\n\\x04_sqlB\\t\\n\\x07_sql_id\\\";\\n\\x12\\x44\\x65scribeStreamResp\\x12%\\n\\x06result\\x18\\x01 \\x01(\\x0b\\x32\\x15.hrana.DescribeResult\\\"0\\n\\x11StoreSqlStreamReq\\x12\\x0e\\n\\x06sql_id\\x18\\x01 \\x01(\\x05\\x12\\x0b\\n\\x03sql\\x18\\x02 \\x01(\\t\\\"\\x14\\n\\x12StoreSqlStreamResp\\\"#\\n\\x11\\x43loseSqlStreamReq\\x12\\x0e\\n\\x06sql_id\\x18\\x01 \\x01(\\x05\\\"\\x14\\n\\x12\\x43loseSqlStreamResp\\\"\\x18\\n\\x16GetAutocommitStreamReq\\\"0\\n\\x17GetAutocommitStreamResp\\x12\\x15\\n\\ris_autocommit\\x18\\x01 \\x01(\\x08\\x62\\x06proto3')\n\n_globals = globals()\n_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)\n_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'hrana.http_pb2', _globals)\nif _descriptor._USE_C_DESCRIPTORS == False:\n  DESCRIPTOR._options = None\n  _globals['_PIPELINEREQBODY']._serialized_start=45\n  _globals['_PIPELINEREQBODY']._serialized_end=137\n  _globals['_PIPELINERESPBODY']._serialized_start=139\n  _globals['_PIPELINERESPBODY']._serialized_end=266\n  _globals['_STREAMRESULT']._serialized_start=268\n  _globals['_STREAMRESULT']._serialized_end=365\n  _globals['_CURSORREQBODY']._serialized_start=367\n  _globals['_CURSORREQBODY']._serialized_end=441\n  _globals['_CURSORRESPBODY']._serialized_start=443\n  _globals['_CURSORRESPBODY']._serialized_end=525\n  _globals['_STREAMREQUEST']._serialized_start=528\n  _globals['_STREAMREQUEST']._serialized_end=961\n  _globals['_STREAMRESPONSE']._serialized_start=964\n  _globals['_STREAMRESPONSE']._serialized_end=1407\n  _globals['_CLOSESTREAMREQ']._serialized_start=1409\n  _globals['_CLOSESTREAMREQ']._serialized_end=1425\n  _globals['_CLOSESTREAMRESP']._serialized_start=1427\n  _globals['_CLOSESTREAMRESP']._serialized_end=1444\n  _globals['_EXECUTESTREAMREQ']._serialized_start=1446\n  _globals['_EXECUTESTREAMREQ']._serialized_end=1491\n  _globals['_EXECUTESTREAMRESP']._serialized_start=1493\n  _globals['_EXECUTESTREAMRESP']._serialized_end=1547\n  _globals['_BATCHSTREAMREQ']._serialized_start=1549\n  _globals['_BATCHSTREAMREQ']._serialized_end=1594\n  _globals['_BATCHSTREAMRESP']._serialized_start=1596\n  _globals['_BATCHSTREAMRESP']._serialized_end=1649\n  _globals['_SEQUENCESTREAMREQ']._serialized_start=1651\n  _globals['_SEQUENCESTREAMREQ']._serialized_end=1728\n  _globals['_SEQUENCESTREAMRESP']._serialized_start=1730\n  _globals['_SEQUENCESTREAMRESP']._serialized_end=1750\n  _globals['_DESCRIBESTREAMREQ']._serialized_start=1752\n  _globals['_DESCRIBESTREAMREQ']._serialized_end=1829\n  _globals['_DESCRIBESTREAMRESP']._serialized_start=1831\n  _globals['_DESCRIBESTREAMRESP']._serialized_end=1890\n  _globals['_STORESQLSTREAMREQ']._serialized_start=1892\n  _globals['_STORESQLSTREAMREQ']._serialized_end=1940\n  _globals['_STORESQLSTREAMRESP']._serialized_start=1942\n  _globals['_STORESQLSTREAMRESP']._serialized_end=1962\n  _globals['_CLOSESQLSTREAMREQ']._serialized_start=1964\n  _globals['_CLOSESQLSTREAMREQ']._serialized_end=1999\n  _globals['_CLOSESQLSTREAMRESP']._serialized_start=2001\n  _globals['_CLOSESQLSTREAMRESP']._serialized_end=2021\n  _globals['_GETAUTOCOMMITSTREAMREQ']._serialized_start=2023\n  _globals['_GETAUTOCOMMITSTREAMREQ']._serialized_end=2047\n  _globals['_GETAUTOCOMMITSTREAMRESP']._serialized_start=2049\n  _globals['_GETAUTOCOMMITSTREAMRESP']._serialized_end=2097\n# @@protoc_insertion_point(module_scope)\n"
  },
  {
    "path": "testing/hrana-test-server/proto/hrana/ws_pb2.py",
    "content": "# -*- coding: utf-8 -*-\n# Generated by the protocol buffer compiler.  DO NOT EDIT!\n# source: hrana.ws.proto\n\"\"\"Generated protocol buffer code.\"\"\"\nfrom google.protobuf import descriptor as _descriptor\nfrom google.protobuf import descriptor_pool as _descriptor_pool\nfrom google.protobuf import symbol_database as _symbol_database\nfrom google.protobuf.internal import builder as _builder\n# @@protoc_insertion_point(imports)\n\n_sym_db = _symbol_database.Default()\n\n\nfrom .. import hrana_pb2 as hrana__pb2\n\n\nDESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\\n\\x0ehrana.ws.proto\\x12\\x08hrana.ws\\x1a\\x0bhrana.proto\\\"`\\n\\tClientMsg\\x12#\\n\\x05hello\\x18\\x01 \\x01(\\x0b\\x32\\x12.hrana.ws.HelloMsgH\\x00\\x12\\'\\n\\x07request\\x18\\x02 \\x01(\\x0b\\x32\\x14.hrana.ws.RequestMsgH\\x00\\x42\\x05\\n\\x03msg\\\"\\xd2\\x01\\n\\tServerMsg\\x12(\\n\\x08hello_ok\\x18\\x01 \\x01(\\x0b\\x32\\x14.hrana.ws.HelloOkMsgH\\x00\\x12.\\n\\x0bhello_error\\x18\\x02 \\x01(\\x0b\\x32\\x17.hrana.ws.HelloErrorMsgH\\x00\\x12.\\n\\x0bresponse_ok\\x18\\x03 \\x01(\\x0b\\x32\\x17.hrana.ws.ResponseOkMsgH\\x00\\x12\\x34\\n\\x0eresponse_error\\x18\\x04 \\x01(\\x0b\\x32\\x1a.hrana.ws.ResponseErrorMsgH\\x00\\x42\\x05\\n\\x03msg\\\"$\\n\\x08HelloMsg\\x12\\x10\\n\\x03jwt\\x18\\x01 \\x01(\\tH\\x00\\x88\\x01\\x01\\x42\\x06\\n\\x04_jwt\\\"\\x0c\\n\\nHelloOkMsg\\\",\\n\\rHelloErrorMsg\\x12\\x1b\\n\\x05\\x65rror\\x18\\x01 \\x01(\\x0b\\x32\\x0c.hrana.Error\\\"\\xd3\\x04\\n\\nRequestMsg\\x12\\x12\\n\\nrequest_id\\x18\\x01 \\x01(\\x05\\x12.\\n\\x0bopen_stream\\x18\\x02 \\x01(\\x0b\\x32\\x17.hrana.ws.OpenStreamReqH\\x00\\x12\\x30\\n\\x0c\\x63lose_stream\\x18\\x03 \\x01(\\x0b\\x32\\x18.hrana.ws.CloseStreamReqH\\x00\\x12\\'\\n\\x07\\x65xecute\\x18\\x04 \\x01(\\x0b\\x32\\x14.hrana.ws.ExecuteReqH\\x00\\x12#\\n\\x05\\x62\\x61tch\\x18\\x05 \\x01(\\x0b\\x32\\x12.hrana.ws.BatchReqH\\x00\\x12.\\n\\x0bopen_cursor\\x18\\x06 \\x01(\\x0b\\x32\\x17.hrana.ws.OpenCursorReqH\\x00\\x12\\x30\\n\\x0c\\x63lose_cursor\\x18\\x07 \\x01(\\x0b\\x32\\x18.hrana.ws.CloseCursorReqH\\x00\\x12\\x30\\n\\x0c\\x66\\x65tch_cursor\\x18\\x08 \\x01(\\x0b\\x32\\x18.hrana.ws.FetchCursorReqH\\x00\\x12)\\n\\x08sequence\\x18\\t \\x01(\\x0b\\x32\\x15.hrana.ws.SequenceReqH\\x00\\x12)\\n\\x08\\x64\\x65scribe\\x18\\n \\x01(\\x0b\\x32\\x15.hrana.ws.DescribeReqH\\x00\\x12*\\n\\tstore_sql\\x18\\x0b \\x01(\\x0b\\x32\\x15.hrana.ws.StoreSqlReqH\\x00\\x12*\\n\\tclose_sql\\x18\\x0c \\x01(\\x0b\\x32\\x15.hrana.ws.CloseSqlReqH\\x00\\x12\\x34\\n\\x0eget_autocommit\\x18\\r \\x01(\\x0b\\x32\\x1a.hrana.ws.GetAutocommitReqH\\x00\\x42\\t\\n\\x07request\\\"\\xe3\\x04\\n\\rResponseOkMsg\\x12\\x12\\n\\nrequest_id\\x18\\x01 \\x01(\\x05\\x12/\\n\\x0bopen_stream\\x18\\x02 \\x01(\\x0b\\x32\\x18.hrana.ws.OpenStreamRespH\\x00\\x12\\x31\\n\\x0c\\x63lose_stream\\x18\\x03 \\x01(\\x0b\\x32\\x19.hrana.ws.CloseStreamRespH\\x00\\x12(\\n\\x07\\x65xecute\\x18\\x04 \\x01(\\x0b\\x32\\x15.hrana.ws.ExecuteRespH\\x00\\x12$\\n\\x05\\x62\\x61tch\\x18\\x05 \\x01(\\x0b\\x32\\x13.hrana.ws.BatchRespH\\x00\\x12/\\n\\x0bopen_cursor\\x18\\x06 \\x01(\\x0b\\x32\\x18.hrana.ws.OpenCursorRespH\\x00\\x12\\x31\\n\\x0c\\x63lose_cursor\\x18\\x07 \\x01(\\x0b\\x32\\x19.hrana.ws.CloseCursorRespH\\x00\\x12\\x31\\n\\x0c\\x66\\x65tch_cursor\\x18\\x08 \\x01(\\x0b\\x32\\x19.hrana.ws.FetchCursorRespH\\x00\\x12*\\n\\x08sequence\\x18\\t \\x01(\\x0b\\x32\\x16.hrana.ws.SequenceRespH\\x00\\x12*\\n\\x08\\x64\\x65scribe\\x18\\n \\x01(\\x0b\\x32\\x16.hrana.ws.DescribeRespH\\x00\\x12+\\n\\tstore_sql\\x18\\x0b \\x01(\\x0b\\x32\\x16.hrana.ws.StoreSqlRespH\\x00\\x12+\\n\\tclose_sql\\x18\\x0c \\x01(\\x0b\\x32\\x16.hrana.ws.CloseSqlRespH\\x00\\x12\\x35\\n\\x0eget_autocommit\\x18\\r \\x01(\\x0b\\x32\\x1b.hrana.ws.GetAutocommitRespH\\x00\\x42\\n\\n\\x08response\\\"C\\n\\x10ResponseErrorMsg\\x12\\x12\\n\\nrequest_id\\x18\\x01 \\x01(\\x05\\x12\\x1b\\n\\x05\\x65rror\\x18\\x02 \\x01(\\x0b\\x32\\x0c.hrana.Error\\\"\\\"\\n\\rOpenStreamReq\\x12\\x11\\n\\tstream_id\\x18\\x01 \\x01(\\x05\\\"\\x10\\n\\x0eOpenStreamResp\\\"#\\n\\x0e\\x43loseStreamReq\\x12\\x11\\n\\tstream_id\\x18\\x01 \\x01(\\x05\\\"\\x11\\n\\x0f\\x43loseStreamResp\\\":\\n\\nExecuteReq\\x12\\x11\\n\\tstream_id\\x18\\x01 \\x01(\\x05\\x12\\x19\\n\\x04stmt\\x18\\x02 \\x01(\\x0b\\x32\\x0b.hrana.Stmt\\\"0\\n\\x0b\\x45xecuteResp\\x12!\\n\\x06result\\x18\\x01 \\x01(\\x0b\\x32\\x11.hrana.StmtResult\\\":\\n\\x08\\x42\\x61tchReq\\x12\\x11\\n\\tstream_id\\x18\\x01 \\x01(\\x05\\x12\\x1b\\n\\x05\\x62\\x61tch\\x18\\x02 \\x01(\\x0b\\x32\\x0c.hrana.Batch\\\"/\\n\\tBatchResp\\x12\\\"\\n\\x06result\\x18\\x01 \\x01(\\x0b\\x32\\x12.hrana.BatchResult\\\"R\\n\\rOpenCursorReq\\x12\\x11\\n\\tstream_id\\x18\\x01 \\x01(\\x05\\x12\\x11\\n\\tcursor_id\\x18\\x02 \\x01(\\x05\\x12\\x1b\\n\\x05\\x62\\x61tch\\x18\\x03 \\x01(\\x0b\\x32\\x0c.hrana.Batch\\\"\\x10\\n\\x0eOpenCursorResp\\\"#\\n\\x0e\\x43loseCursorReq\\x12\\x11\\n\\tcursor_id\\x18\\x01 \\x01(\\x05\\\"\\x11\\n\\x0f\\x43loseCursorResp\\\"6\\n\\x0e\\x46\\x65tchCursorReq\\x12\\x11\\n\\tcursor_id\\x18\\x01 \\x01(\\x05\\x12\\x11\\n\\tmax_count\\x18\\x02 \\x01(\\r\\\"D\\n\\x0f\\x46\\x65tchCursorResp\\x12#\\n\\x07\\x65ntries\\x18\\x01 \\x03(\\x0b\\x32\\x12.hrana.CursorEntry\\x12\\x0c\\n\\x04\\x64one\\x18\\x02 \\x01(\\x08\\\"*\\n\\x0bStoreSqlReq\\x12\\x0e\\n\\x06sql_id\\x18\\x01 \\x01(\\x05\\x12\\x0b\\n\\x03sql\\x18\\x02 \\x01(\\t\\\"\\x0e\\n\\x0cStoreSqlResp\\\"\\x1d\\n\\x0b\\x43loseSqlReq\\x12\\x0e\\n\\x06sql_id\\x18\\x01 \\x01(\\x05\\\"\\x0e\\n\\x0c\\x43loseSqlResp\\\"Z\\n\\x0bSequenceReq\\x12\\x11\\n\\tstream_id\\x18\\x01 \\x01(\\x05\\x12\\x10\\n\\x03sql\\x18\\x02 \\x01(\\tH\\x00\\x88\\x01\\x01\\x12\\x13\\n\\x06sql_id\\x18\\x03 \\x01(\\x05H\\x01\\x88\\x01\\x01\\x42\\x06\\n\\x04_sqlB\\t\\n\\x07_sql_id\\\"\\x0e\\n\\x0cSequenceResp\\\"Z\\n\\x0b\\x44\\x65scribeReq\\x12\\x11\\n\\tstream_id\\x18\\x01 \\x01(\\x05\\x12\\x10\\n\\x03sql\\x18\\x02 \\x01(\\tH\\x00\\x88\\x01\\x01\\x12\\x13\\n\\x06sql_id\\x18\\x03 \\x01(\\x05H\\x01\\x88\\x01\\x01\\x42\\x06\\n\\x04_sqlB\\t\\n\\x07_sql_id\\\"5\\n\\x0c\\x44\\x65scribeResp\\x12%\\n\\x06result\\x18\\x01 \\x01(\\x0b\\x32\\x15.hrana.DescribeResult\\\"%\\n\\x10GetAutocommitReq\\x12\\x11\\n\\tstream_id\\x18\\x01 \\x01(\\x05\\\"*\\n\\x11GetAutocommitResp\\x12\\x15\\n\\ris_autocommit\\x18\\x01 \\x01(\\x08\\x62\\x06proto3')\n\n_globals = globals()\n_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)\n_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'hrana.ws_pb2', _globals)\nif _descriptor._USE_C_DESCRIPTORS == False:\n  DESCRIPTOR._options = None\n  _globals['_CLIENTMSG']._serialized_start=41\n  _globals['_CLIENTMSG']._serialized_end=137\n  _globals['_SERVERMSG']._serialized_start=140\n  _globals['_SERVERMSG']._serialized_end=350\n  _globals['_HELLOMSG']._serialized_start=352\n  _globals['_HELLOMSG']._serialized_end=388\n  _globals['_HELLOOKMSG']._serialized_start=390\n  _globals['_HELLOOKMSG']._serialized_end=402\n  _globals['_HELLOERRORMSG']._serialized_start=404\n  _globals['_HELLOERRORMSG']._serialized_end=448\n  _globals['_REQUESTMSG']._serialized_start=451\n  _globals['_REQUESTMSG']._serialized_end=1046\n  _globals['_RESPONSEOKMSG']._serialized_start=1049\n  _globals['_RESPONSEOKMSG']._serialized_end=1660\n  _globals['_RESPONSEERRORMSG']._serialized_start=1662\n  _globals['_RESPONSEERRORMSG']._serialized_end=1729\n  _globals['_OPENSTREAMREQ']._serialized_start=1731\n  _globals['_OPENSTREAMREQ']._serialized_end=1765\n  _globals['_OPENSTREAMRESP']._serialized_start=1767\n  _globals['_OPENSTREAMRESP']._serialized_end=1783\n  _globals['_CLOSESTREAMREQ']._serialized_start=1785\n  _globals['_CLOSESTREAMREQ']._serialized_end=1820\n  _globals['_CLOSESTREAMRESP']._serialized_start=1822\n  _globals['_CLOSESTREAMRESP']._serialized_end=1839\n  _globals['_EXECUTEREQ']._serialized_start=1841\n  _globals['_EXECUTEREQ']._serialized_end=1899\n  _globals['_EXECUTERESP']._serialized_start=1901\n  _globals['_EXECUTERESP']._serialized_end=1949\n  _globals['_BATCHREQ']._serialized_start=1951\n  _globals['_BATCHREQ']._serialized_end=2009\n  _globals['_BATCHRESP']._serialized_start=2011\n  _globals['_BATCHRESP']._serialized_end=2058\n  _globals['_OPENCURSORREQ']._serialized_start=2060\n  _globals['_OPENCURSORREQ']._serialized_end=2142\n  _globals['_OPENCURSORRESP']._serialized_start=2144\n  _globals['_OPENCURSORRESP']._serialized_end=2160\n  _globals['_CLOSECURSORREQ']._serialized_start=2162\n  _globals['_CLOSECURSORREQ']._serialized_end=2197\n  _globals['_CLOSECURSORRESP']._serialized_start=2199\n  _globals['_CLOSECURSORRESP']._serialized_end=2216\n  _globals['_FETCHCURSORREQ']._serialized_start=2218\n  _globals['_FETCHCURSORREQ']._serialized_end=2272\n  _globals['_FETCHCURSORRESP']._serialized_start=2274\n  _globals['_FETCHCURSORRESP']._serialized_end=2342\n  _globals['_STORESQLREQ']._serialized_start=2344\n  _globals['_STORESQLREQ']._serialized_end=2386\n  _globals['_STORESQLRESP']._serialized_start=2388\n  _globals['_STORESQLRESP']._serialized_end=2402\n  _globals['_CLOSESQLREQ']._serialized_start=2404\n  _globals['_CLOSESQLREQ']._serialized_end=2433\n  _globals['_CLOSESQLRESP']._serialized_start=2435\n  _globals['_CLOSESQLRESP']._serialized_end=2449\n  _globals['_SEQUENCEREQ']._serialized_start=2451\n  _globals['_SEQUENCEREQ']._serialized_end=2541\n  _globals['_SEQUENCERESP']._serialized_start=2543\n  _globals['_SEQUENCERESP']._serialized_end=2557\n  _globals['_DESCRIBEREQ']._serialized_start=2559\n  _globals['_DESCRIBEREQ']._serialized_end=2649\n  _globals['_DESCRIBERESP']._serialized_start=2651\n  _globals['_DESCRIBERESP']._serialized_end=2704\n  _globals['_GETAUTOCOMMITREQ']._serialized_start=2706\n  _globals['_GETAUTOCOMMITREQ']._serialized_end=2743\n  _globals['_GETAUTOCOMMITRESP']._serialized_start=2745\n  _globals['_GETAUTOCOMMITRESP']._serialized_end=2787\n# @@protoc_insertion_point(module_scope)\n"
  },
  {
    "path": "testing/hrana-test-server/proto/hrana.http.proto",
    "content": "syntax = \"proto3\";\npackage hrana.http;\nimport \"hrana.proto\";\n\nmessage PipelineReqBody {\n  optional string baton = 1;\n  repeated StreamRequest requests = 2;\n}\n\nmessage PipelineRespBody {\n  optional string baton = 1;\n  optional string base_url = 2;\n  repeated StreamResult results = 3;\n}\n\nmessage StreamResult {\n  oneof result {\n    StreamResponse ok = 1;\n    Error error = 2;\n  }\n}\n\nmessage CursorReqBody {\n  optional string baton = 1;\n  Batch batch = 2;\n}\n\nmessage CursorRespBody {\n  optional string baton = 1;\n  optional string base_url = 2;\n}\n\nmessage StreamRequest {\n  oneof request {\n    CloseStreamReq close = 1;\n    ExecuteStreamReq execute = 2;\n    BatchStreamReq batch = 3;\n    SequenceStreamReq sequence = 4;\n    DescribeStreamReq describe = 5;\n    StoreSqlStreamReq store_sql = 6;\n    CloseSqlStreamReq close_sql = 7;\n    GetAutocommitStreamReq get_autocommit = 8;\n  }\n}\n\nmessage StreamResponse {\n  oneof response {\n    CloseStreamResp close = 1;\n    ExecuteStreamResp execute = 2;\n    BatchStreamResp batch = 3;\n    SequenceStreamResp sequence = 4;\n    DescribeStreamResp describe = 5;\n    StoreSqlStreamResp store_sql = 6;\n    CloseSqlStreamResp close_sql = 7;\n    GetAutocommitStreamResp get_autocommit = 8;\n  }\n}\n\nmessage CloseStreamReq {\n}\n\nmessage CloseStreamResp {\n}\n\nmessage ExecuteStreamReq {\n  Stmt stmt = 1;\n}\n\nmessage ExecuteStreamResp {\n  StmtResult result = 1;\n}\n\nmessage BatchStreamReq {\n  Batch batch = 1;\n}\n\nmessage BatchStreamResp {\n  BatchResult result = 1;\n}\n\nmessage SequenceStreamReq {\n  optional string sql = 1;\n  optional int32 sql_id = 2;\n}\n\nmessage SequenceStreamResp {\n}\n\nmessage DescribeStreamReq {\n  optional string sql = 1;\n  optional int32 sql_id = 2;\n}\n\nmessage DescribeStreamResp {\n  DescribeResult result = 1;\n}\n\nmessage StoreSqlStreamReq {\n  int32 sql_id = 1;\n  string sql = 2;\n}\n\nmessage StoreSqlStreamResp {\n}\n\nmessage CloseSqlStreamReq {\n  int32 sql_id = 1;\n}\n\nmessage CloseSqlStreamResp {\n}\n\nmessage GetAutocommitStreamReq {\n}\n\nmessage GetAutocommitStreamResp {\n  bool is_autocommit = 1;\n}\n"
  },
  {
    "path": "testing/hrana-test-server/proto/hrana.proto",
    "content": "syntax = \"proto3\";\npackage hrana;\n\nmessage Error {\n  string message = 1;\n  optional string code = 2;\n}\n\nmessage Stmt {\n  optional string sql = 1;\n  optional int32 sql_id = 2;\n  repeated Value args = 3;\n  repeated NamedArg named_args = 4;\n  optional bool want_rows = 5;\n}\n\nmessage NamedArg {\n  string name = 1;\n  Value value = 2;\n}\n\nmessage StmtResult {\n  repeated Col cols = 1;\n  repeated Row rows = 2;\n  uint64 affected_row_count = 3;\n  optional sint64 last_insert_rowid = 4;\n}\n\nmessage Col {\n  optional string name = 1;\n  optional string decltype = 2;\n}\n\nmessage Row {\n  repeated Value values = 1;\n}\n\nmessage Batch {\n  repeated BatchStep steps = 1;\n}\n\nmessage BatchStep {\n  optional BatchCond condition = 1;\n  Stmt stmt = 2;\n}\n\nmessage BatchCond {\n  oneof cond {\n    uint32 step_ok = 1;\n    uint32 step_error = 2;\n    BatchCond not = 3;\n    CondList and = 4;\n    CondList or = 5;\n    IsAutocommit is_autocommit = 6;\n  }\n\n  message CondList {\n    repeated BatchCond conds = 1;\n  }\n\n  message IsAutocommit {\n  }\n}\n\nmessage BatchResult {\n  map<uint32, StmtResult> step_results = 1;\n  map<uint32, Error> step_errors = 2;\n}\n\nmessage CursorEntry {\n  oneof entry {\n    StepBeginEntry step_begin = 1;\n    StepEndEntry step_end = 2;\n    StepErrorEntry step_error = 3;\n    Row row = 4;\n    Error error = 5;\n  }\n}\n\nmessage StepBeginEntry {\n  uint32 step = 1;\n  repeated Col cols = 2;\n}\n\nmessage StepEndEntry {\n  uint64 affected_row_count = 1;\n  optional sint64 last_insert_rowid = 2;\n}\n\nmessage StepErrorEntry {\n  uint32 step = 1;\n  Error error = 2;\n}\n\nmessage DescribeResult {\n  repeated DescribeParam params = 1;\n  repeated DescribeCol cols = 2;\n  bool is_explain = 3;\n  bool is_readonly = 4;\n}\n\nmessage DescribeParam {\n  optional string name = 1;\n}\n\nmessage DescribeCol {\n  string name = 1;\n  optional string decltype = 2;\n}\n\nmessage Value {\n  oneof value {\n    Null null = 1;\n    sint64 integer = 2;\n    double float = 3;\n    string text = 4;\n    bytes blob = 5;\n  }\n\n  message Null {}\n}\n"
  },
  {
    "path": "testing/hrana-test-server/proto/hrana.ws.proto",
    "content": "syntax = \"proto3\";\npackage hrana.ws;\nimport \"hrana.proto\";\n\nmessage ClientMsg {\n  oneof msg {\n    HelloMsg hello = 1;\n    RequestMsg request = 2;\n  }\n}\n\nmessage ServerMsg {\n  oneof msg {\n    HelloOkMsg hello_ok = 1;\n    HelloErrorMsg hello_error = 2;\n    ResponseOkMsg response_ok = 3;\n    ResponseErrorMsg response_error = 4;\n  }\n}\n\nmessage HelloMsg {\n  optional string jwt = 1;\n}\n\nmessage HelloOkMsg {\n}\n\nmessage HelloErrorMsg {\n  Error error = 1;\n}\n\nmessage RequestMsg {\n  int32 request_id = 1;\n  oneof request {\n    OpenStreamReq open_stream = 2;\n    CloseStreamReq close_stream = 3;\n    ExecuteReq execute = 4;\n    BatchReq batch = 5;\n    OpenCursorReq open_cursor = 6;\n    CloseCursorReq close_cursor = 7;\n    FetchCursorReq fetch_cursor = 8;\n    SequenceReq sequence = 9;\n    DescribeReq describe = 10;\n    StoreSqlReq store_sql = 11;\n    CloseSqlReq close_sql = 12;\n    GetAutocommitReq get_autocommit = 13;\n  }\n}\n\nmessage ResponseOkMsg {\n  int32 request_id = 1;\n  oneof response {\n    OpenStreamResp open_stream = 2;\n    CloseStreamResp close_stream = 3;\n    ExecuteResp execute = 4;\n    BatchResp batch = 5;\n    OpenCursorResp open_cursor = 6;\n    CloseCursorResp close_cursor = 7;\n    FetchCursorResp fetch_cursor = 8;\n    SequenceResp sequence = 9;\n    DescribeResp describe = 10;\n    StoreSqlResp store_sql = 11;\n    CloseSqlResp close_sql = 12;\n    GetAutocommitResp get_autocommit = 13;\n  }\n}\n\nmessage ResponseErrorMsg {\n  int32 request_id = 1;\n  Error error = 2;\n}\n\nmessage OpenStreamReq {\n  int32 stream_id = 1;\n}\n\nmessage OpenStreamResp {\n}\n\nmessage CloseStreamReq {\n  int32 stream_id = 1;\n}\n\nmessage CloseStreamResp {\n}\n\nmessage ExecuteReq {\n  int32 stream_id = 1;\n  Stmt stmt = 2;\n}\n\nmessage ExecuteResp {\n  StmtResult result = 1;\n}\n\nmessage BatchReq {\n  int32 stream_id = 1;\n  Batch batch = 2;\n}\n\nmessage BatchResp {\n  BatchResult result = 1;\n}\n\nmessage OpenCursorReq {\n  int32 stream_id = 1;\n  int32 cursor_id = 2;\n  Batch batch = 3;\n}\n\nmessage OpenCursorResp {\n}\n\nmessage CloseCursorReq {\n  int32 cursor_id = 1;\n}\n\nmessage CloseCursorResp {\n}\n\nmessage FetchCursorReq {\n  int32 cursor_id = 1;\n  uint32 max_count = 2;\n}\n\nmessage FetchCursorResp {\n  repeated CursorEntry entries = 1;\n  bool done = 2;\n}\n\nmessage StoreSqlReq {\n  int32 sql_id = 1;\n  string sql = 2;\n}\n\nmessage StoreSqlResp {\n}\n\nmessage CloseSqlReq {\n  int32 sql_id = 1;\n}\n\nmessage CloseSqlResp {\n}\n\nmessage SequenceReq {\n  int32 stream_id = 1;\n  optional string sql = 2;\n  optional int32 sql_id = 3;\n}\n\nmessage SequenceResp {\n}\n\nmessage DescribeReq {\n  int32 stream_id = 1;\n  optional string sql = 2;\n  optional int32 sql_id = 3;\n}\n\nmessage DescribeResp {\n  DescribeResult result = 1;\n}\n\nmessage GetAutocommitReq {\n  int32 stream_id = 1;\n}\n\nmessage GetAutocommitResp {\n  bool is_autocommit = 1;\n}\n"
  },
  {
    "path": "testing/hrana-test-server/proto/hrana_pb2.py",
    "content": "# -*- coding: utf-8 -*-\n# Generated by the protocol buffer compiler.  DO NOT EDIT!\n# source: hrana.proto\n\"\"\"Generated protocol buffer code.\"\"\"\nfrom google.protobuf import descriptor as _descriptor\nfrom google.protobuf import descriptor_pool as _descriptor_pool\nfrom google.protobuf import symbol_database as _symbol_database\nfrom google.protobuf.internal import builder as _builder\n# @@protoc_insertion_point(imports)\n\n_sym_db = _symbol_database.Default()\n\n\n\n\nDESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\\n\\x0bhrana.proto\\x12\\x05hrana\\\"4\\n\\x05\\x45rror\\x12\\x0f\\n\\x07message\\x18\\x01 \\x01(\\t\\x12\\x11\\n\\x04\\x63ode\\x18\\x02 \\x01(\\tH\\x00\\x88\\x01\\x01\\x42\\x07\\n\\x05_code\\\"\\xa7\\x01\\n\\x04Stmt\\x12\\x10\\n\\x03sql\\x18\\x01 \\x01(\\tH\\x00\\x88\\x01\\x01\\x12\\x13\\n\\x06sql_id\\x18\\x02 \\x01(\\x05H\\x01\\x88\\x01\\x01\\x12\\x1a\\n\\x04\\x61rgs\\x18\\x03 \\x03(\\x0b\\x32\\x0c.hrana.Value\\x12#\\n\\nnamed_args\\x18\\x04 \\x03(\\x0b\\x32\\x0f.hrana.NamedArg\\x12\\x16\\n\\twant_rows\\x18\\x05 \\x01(\\x08H\\x02\\x88\\x01\\x01\\x42\\x06\\n\\x04_sqlB\\t\\n\\x07_sql_idB\\x0c\\n\\n_want_rows\\\"5\\n\\x08NamedArg\\x12\\x0c\\n\\x04name\\x18\\x01 \\x01(\\t\\x12\\x1b\\n\\x05value\\x18\\x02 \\x01(\\x0b\\x32\\x0c.hrana.Value\\\"\\x92\\x01\\n\\nStmtResult\\x12\\x18\\n\\x04\\x63ols\\x18\\x01 \\x03(\\x0b\\x32\\n.hrana.Col\\x12\\x18\\n\\x04rows\\x18\\x02 \\x03(\\x0b\\x32\\n.hrana.Row\\x12\\x1a\\n\\x12\\x61\\x66\\x66\\x65\\x63ted_row_count\\x18\\x03 \\x01(\\x04\\x12\\x1e\\n\\x11last_insert_rowid\\x18\\x04 \\x01(\\x12H\\x00\\x88\\x01\\x01\\x42\\x14\\n\\x12_last_insert_rowid\\\"E\\n\\x03\\x43ol\\x12\\x11\\n\\x04name\\x18\\x01 \\x01(\\tH\\x00\\x88\\x01\\x01\\x12\\x15\\n\\x08\\x64\\x65\\x63ltype\\x18\\x02 \\x01(\\tH\\x01\\x88\\x01\\x01\\x42\\x07\\n\\x05_nameB\\x0b\\n\\t_decltype\\\"#\\n\\x03Row\\x12\\x1c\\n\\x06values\\x18\\x01 \\x03(\\x0b\\x32\\x0c.hrana.Value\\\"(\\n\\x05\\x42\\x61tch\\x12\\x1f\\n\\x05steps\\x18\\x01 \\x03(\\x0b\\x32\\x10.hrana.BatchStep\\\"^\\n\\tBatchStep\\x12(\\n\\tcondition\\x18\\x01 \\x01(\\x0b\\x32\\x10.hrana.BatchCondH\\x00\\x88\\x01\\x01\\x12\\x19\\n\\x04stmt\\x18\\x02 \\x01(\\x0b\\x32\\x0b.hrana.StmtB\\x0c\\n\\n_condition\\\"\\xa5\\x02\\n\\tBatchCond\\x12\\x11\\n\\x07step_ok\\x18\\x01 \\x01(\\rH\\x00\\x12\\x14\\n\\nstep_error\\x18\\x02 \\x01(\\rH\\x00\\x12\\x1f\\n\\x03not\\x18\\x03 \\x01(\\x0b\\x32\\x10.hrana.BatchCondH\\x00\\x12(\\n\\x03\\x61nd\\x18\\x04 \\x01(\\x0b\\x32\\x19.hrana.BatchCond.CondListH\\x00\\x12\\'\\n\\x02or\\x18\\x05 \\x01(\\x0b\\x32\\x19.hrana.BatchCond.CondListH\\x00\\x12\\x36\\n\\ris_autocommit\\x18\\x06 \\x01(\\x0b\\x32\\x1d.hrana.BatchCond.IsAutocommitH\\x00\\x1a+\\n\\x08\\x43ondList\\x12\\x1f\\n\\x05\\x63onds\\x18\\x01 \\x03(\\x0b\\x32\\x10.hrana.BatchCond\\x1a\\x0e\\n\\x0cIsAutocommitB\\x06\\n\\x04\\x63ond\\\"\\x89\\x02\\n\\x0b\\x42\\x61tchResult\\x12\\x39\\n\\x0cstep_results\\x18\\x01 \\x03(\\x0b\\x32#.hrana.BatchResult.StepResultsEntry\\x12\\x37\\n\\x0bstep_errors\\x18\\x02 \\x03(\\x0b\\x32\\\".hrana.BatchResult.StepErrorsEntry\\x1a\\x45\\n\\x10StepResultsEntry\\x12\\x0b\\n\\x03key\\x18\\x01 \\x01(\\r\\x12 \\n\\x05value\\x18\\x02 \\x01(\\x0b\\x32\\x11.hrana.StmtResult:\\x02\\x38\\x01\\x1a?\\n\\x0fStepErrorsEntry\\x12\\x0b\\n\\x03key\\x18\\x01 \\x01(\\r\\x12\\x1b\\n\\x05value\\x18\\x02 \\x01(\\x0b\\x32\\x0c.hrana.Error:\\x02\\x38\\x01\\\"\\xd3\\x01\\n\\x0b\\x43ursorEntry\\x12+\\n\\nstep_begin\\x18\\x01 \\x01(\\x0b\\x32\\x15.hrana.StepBeginEntryH\\x00\\x12\\'\\n\\x08step_end\\x18\\x02 \\x01(\\x0b\\x32\\x13.hrana.StepEndEntryH\\x00\\x12+\\n\\nstep_error\\x18\\x03 \\x01(\\x0b\\x32\\x15.hrana.StepErrorEntryH\\x00\\x12\\x19\\n\\x03row\\x18\\x04 \\x01(\\x0b\\x32\\n.hrana.RowH\\x00\\x12\\x1d\\n\\x05\\x65rror\\x18\\x05 \\x01(\\x0b\\x32\\x0c.hrana.ErrorH\\x00\\x42\\x07\\n\\x05\\x65ntry\\\"8\\n\\x0eStepBeginEntry\\x12\\x0c\\n\\x04step\\x18\\x01 \\x01(\\r\\x12\\x18\\n\\x04\\x63ols\\x18\\x02 \\x03(\\x0b\\x32\\n.hrana.Col\\\"`\\n\\x0cStepEndEntry\\x12\\x1a\\n\\x12\\x61\\x66\\x66\\x65\\x63ted_row_count\\x18\\x01 \\x01(\\x04\\x12\\x1e\\n\\x11last_insert_rowid\\x18\\x02 \\x01(\\x12H\\x00\\x88\\x01\\x01\\x42\\x14\\n\\x12_last_insert_rowid\\\";\\n\\x0eStepErrorEntry\\x12\\x0c\\n\\x04step\\x18\\x01 \\x01(\\r\\x12\\x1b\\n\\x05\\x65rror\\x18\\x02 \\x01(\\x0b\\x32\\x0c.hrana.Error\\\"\\x81\\x01\\n\\x0e\\x44\\x65scribeResult\\x12$\\n\\x06params\\x18\\x01 \\x03(\\x0b\\x32\\x14.hrana.DescribeParam\\x12 \\n\\x04\\x63ols\\x18\\x02 \\x03(\\x0b\\x32\\x12.hrana.DescribeCol\\x12\\x12\\n\\nis_explain\\x18\\x03 \\x01(\\x08\\x12\\x13\\n\\x0bis_readonly\\x18\\x04 \\x01(\\x08\\\"+\\n\\rDescribeParam\\x12\\x11\\n\\x04name\\x18\\x01 \\x01(\\tH\\x00\\x88\\x01\\x01\\x42\\x07\\n\\x05_name\\\"?\\n\\x0b\\x44\\x65scribeCol\\x12\\x0c\\n\\x04name\\x18\\x01 \\x01(\\t\\x12\\x15\\n\\x08\\x64\\x65\\x63ltype\\x18\\x02 \\x01(\\tH\\x00\\x88\\x01\\x01\\x42\\x0b\\n\\t_decltype\\\"\\x7f\\n\\x05Value\\x12!\\n\\x04null\\x18\\x01 \\x01(\\x0b\\x32\\x11.hrana.Value.NullH\\x00\\x12\\x11\\n\\x07integer\\x18\\x02 \\x01(\\x12H\\x00\\x12\\x0f\\n\\x05\\x66loat\\x18\\x03 \\x01(\\x01H\\x00\\x12\\x0e\\n\\x04text\\x18\\x04 \\x01(\\tH\\x00\\x12\\x0e\\n\\x04\\x62lob\\x18\\x05 \\x01(\\x0cH\\x00\\x1a\\x06\\n\\x04NullB\\x07\\n\\x05valueb\\x06proto3')\n\n_globals = globals()\n_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)\n_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'hrana_pb2', _globals)\nif _descriptor._USE_C_DESCRIPTORS == False:\n  DESCRIPTOR._options = None\n  _BATCHRESULT_STEPRESULTSENTRY._options = None\n  _BATCHRESULT_STEPRESULTSENTRY._serialized_options = b'8\\001'\n  _BATCHRESULT_STEPERRORSENTRY._options = None\n  _BATCHRESULT_STEPERRORSENTRY._serialized_options = b'8\\001'\n  _globals['_ERROR']._serialized_start=22\n  _globals['_ERROR']._serialized_end=74\n  _globals['_STMT']._serialized_start=77\n  _globals['_STMT']._serialized_end=244\n  _globals['_NAMEDARG']._serialized_start=246\n  _globals['_NAMEDARG']._serialized_end=299\n  _globals['_STMTRESULT']._serialized_start=302\n  _globals['_STMTRESULT']._serialized_end=448\n  _globals['_COL']._serialized_start=450\n  _globals['_COL']._serialized_end=519\n  _globals['_ROW']._serialized_start=521\n  _globals['_ROW']._serialized_end=556\n  _globals['_BATCH']._serialized_start=558\n  _globals['_BATCH']._serialized_end=598\n  _globals['_BATCHSTEP']._serialized_start=600\n  _globals['_BATCHSTEP']._serialized_end=694\n  _globals['_BATCHCOND']._serialized_start=697\n  _globals['_BATCHCOND']._serialized_end=990\n  _globals['_BATCHCOND_CONDLIST']._serialized_start=923\n  _globals['_BATCHCOND_CONDLIST']._serialized_end=966\n  _globals['_BATCHCOND_ISAUTOCOMMIT']._serialized_start=968\n  _globals['_BATCHCOND_ISAUTOCOMMIT']._serialized_end=982\n  _globals['_BATCHRESULT']._serialized_start=993\n  _globals['_BATCHRESULT']._serialized_end=1258\n  _globals['_BATCHRESULT_STEPRESULTSENTRY']._serialized_start=1124\n  _globals['_BATCHRESULT_STEPRESULTSENTRY']._serialized_end=1193\n  _globals['_BATCHRESULT_STEPERRORSENTRY']._serialized_start=1195\n  _globals['_BATCHRESULT_STEPERRORSENTRY']._serialized_end=1258\n  _globals['_CURSORENTRY']._serialized_start=1261\n  _globals['_CURSORENTRY']._serialized_end=1472\n  _globals['_STEPBEGINENTRY']._serialized_start=1474\n  _globals['_STEPBEGINENTRY']._serialized_end=1530\n  _globals['_STEPENDENTRY']._serialized_start=1532\n  _globals['_STEPENDENTRY']._serialized_end=1628\n  _globals['_STEPERRORENTRY']._serialized_start=1630\n  _globals['_STEPERRORENTRY']._serialized_end=1689\n  _globals['_DESCRIBERESULT']._serialized_start=1692\n  _globals['_DESCRIBERESULT']._serialized_end=1821\n  _globals['_DESCRIBEPARAM']._serialized_start=1823\n  _globals['_DESCRIBEPARAM']._serialized_end=1866\n  _globals['_DESCRIBECOL']._serialized_start=1868\n  _globals['_DESCRIBECOL']._serialized_end=1931\n  _globals['_VALUE']._serialized_start=1933\n  _globals['_VALUE']._serialized_end=2060\n  _globals['_VALUE_NULL']._serialized_start=2045\n  _globals['_VALUE_NULL']._serialized_end=2051\n# @@protoc_insertion_point(module_scope)\n"
  },
  {
    "path": "testing/hrana-test-server/requirements.txt",
    "content": "aiohttp==3.8.4\nprotobuf==4.24.0\n"
  },
  {
    "path": "testing/hrana-test-server/server_v1.py",
    "content": "import asyncio\nimport base64\nimport collections\nimport json\nimport logging\nimport os\nimport sqlite3\nimport sys\nimport tempfile\n\nimport aiohttp.web\n\nlogger = logging.getLogger(\"server\")\npersistent_db_file = os.getenv(\"PERSISTENT_DB\")\n\nasync def main(command):\n    logging.basicConfig(level=os.getenv(\"LOG_LEVEL\", \"INFO\"))\n\n    app = aiohttp.web.Application()\n    app.add_routes([\n        aiohttp.web.get(\"/\", handle_get_index),\n        aiohttp.web.post(\"/v1/execute\", handle_post_execute),\n        aiohttp.web.post(\"/v1/batch\", handle_post_batch),\n    ])\n\n    if persistent_db_file is None:\n        http_db_fd, http_db_file = tempfile.mkstemp(suffix=\".db\", prefix=\"hrana_test_\")\n        os.close(http_db_fd)\n    else:\n        http_db_file = persistent_db_file\n    app[\"http_db_file\"] = http_db_file\n    app[\"db_lock\"] = asyncio.Lock()\n\n    async def on_shutdown(app):\n        if http_db_file != persistent_db_file:\n            os.unlink(http_db_file)\n    app.on_shutdown.append(on_shutdown)\n\n    runner = aiohttp.web.AppRunner(app)\n    await runner.setup()\n    site = aiohttp.web.TCPSite(runner, \"localhost\", 8080)\n    await site.start()\n    logger.info(\"Server is ready\")\n\n    if len(command) > 0:\n        proc = await asyncio.create_subprocess_exec(*command)\n        code = await proc.wait()\n    else:\n        while True:\n            await asyncio.sleep(10)\n\n    await runner.cleanup()\n    return code\n\nasync def handle_get_index(req):\n    ws = aiohttp.web.WebSocketResponse(protocols=(\"hrana1\",))\n    if ws.can_prepare(req):\n        await ws.prepare(req)\n        try:\n            await handle_websocket(req.app, ws)\n        finally:\n            await ws.close()\n        return ws\n\n    return aiohttp.web.Response(text=\"This is a Hrana test server\")\n\nasync def handle_websocket(app, ws):\n    async def recv_msg():\n        ws_msg = await ws.receive()\n        if ws_msg.type == aiohttp.WSMsgType.TEXT:\n            msg = json.loads(ws_msg.data)\n            return msg\n        elif ws_msg.type in (aiohttp.WSMsgType.CLOSE, aiohttp.WSMsgType.CLOSED):\n            return None\n        else:\n            raise RuntimeError(f\"Unknown websocket message: {msg!r}\")\n\n    async def send_msg(msg):\n        msg_str = json.dumps(msg)\n        await ws.send_str(msg_str)\n\n    Stream = collections.namedtuple(\"Stream\", [\"conn\"])\n    streams = {}\n\n    if persistent_db_file is None:\n        db_fd, db_file = tempfile.mkstemp(suffix=\".db\", prefix=\"hrana_test_\")\n        os.close(db_fd)\n    else:\n        db_file = persistent_db_file\n\n    async def handle_request(req):\n        if req[\"type\"] == \"open_stream\":\n            conn = await to_thread(lambda: connect(db_file))\n            streams[int(req[\"stream_id\"])] = Stream(conn)\n            return {\"type\": \"open_stream\"}\n        elif req[\"type\"] == \"close_stream\":\n            stream = streams.pop(int(req[\"stream_id\"]), None)\n            if stream is not None:\n                await to_thread(lambda: stream.conn.close())\n            return {\"type\": \"close_stream\"}\n        elif req[\"type\"] == \"execute\":\n            stream = streams[int(req[\"stream_id\"])]\n            async with app[\"db_lock\"]:\n                result = await to_thread(lambda: execute_stmt(stream.conn, req[\"stmt\"]))\n            return {\"type\": \"execute\", \"result\": result}\n        elif req[\"type\"] == \"batch\":\n            stream = streams[int(req[\"stream_id\"])]\n            async with app[\"db_lock\"]:\n                result = await to_thread(lambda: execute_batch(stream.conn, req[\"batch\"]))\n            return {\"type\": \"batch\", \"result\": result}\n        else:\n            raise RuntimeError(f\"Unknown req: {req!r}\")\n\n    async def handle_msg(msg):\n        if msg[\"type\"] == \"request\":\n            try:\n                response = await handle_request(msg[\"request\"])\n                await send_msg({\n                    \"type\": \"response_ok\",\n                    \"request_id\": msg[\"request_id\"],\n                    \"response\": response,\n                })\n            except ResponseError as e:\n                await send_msg({\n                    \"type\": \"response_error\",\n                    \"request_id\": msg[\"request_id\"],\n                    \"error\": {\"message\": str(e)},\n                })\n        else:\n            raise RuntimeError(f\"Unknown msg: {msg!r}\")\n\n    try:\n        hello_msg = await recv_msg()\n        if hello_msg is None:\n            return\n        assert hello_msg.get(\"type\") == \"hello\"\n        await send_msg({\"type\": \"hello_ok\"})\n\n        jwt = hello_msg.get(\"jwt\")\n        if jwt is not None:\n            logger.info(f\"Authenticated with JWT: {jwt[:20]}...\")\n\n        while True:\n            msg = await recv_msg()\n            if msg is None:\n                break\n            await handle_msg(msg)\n    except CloseWebSocket:\n        await ws.close()\n    except CloseTcpSocket:\n        ws._writer.transport.close()\n    finally:\n        for stream in streams.values():\n            stream.conn.close()\n        if db_file != persistent_db_file:\n            os.unlink(db_file)\n\nasync def handle_post_execute(req):\n    req_body = await req.json()\n    conn = await to_thread(lambda: connect(req.app[\"http_db_file\"]))\n    try:\n        async with req.app[\"db_lock\"]:\n            result = await to_thread(lambda: execute_stmt(conn, req_body[\"stmt\"]))\n        return aiohttp.web.json_response({\"result\": result})\n    except ResponseError as e:\n        return aiohttp.web.json_response({\"message\": str(e)}, status=400)\n    finally:\n        conn.close()\n\nasync def handle_post_batch(req):\n    req_body = await req.json()\n    conn = await to_thread(lambda: connect(req.app[\"http_db_file\"]))\n    try:\n        async with req.app[\"db_lock\"]:\n            result = await to_thread(lambda: execute_batch(conn, req_body[\"batch\"]))\n        return aiohttp.web.json_response({\"result\": result})\n    except ResponseError as e:\n        return aiohttp.web.json_response({\"message\": str(e)}, status=400)\n    finally:\n        conn.close()\n\ndef connect(db_file):\n    conn = sqlite3.connect(db_file, check_same_thread=False, isolation_level=None, timeout=1)\n    conn.execute(\"PRAGMA journal_mode = WAL\")\n    return conn\n\nclass CloseWebSocket(BaseException):\n    pass\n\nclass CloseTcpSocket(BaseException):\n    pass\n\ndef execute_stmt(conn, stmt):\n    if stmt[\"sql\"] == \".close_ws\":\n        raise CloseWebSocket()\n    elif stmt[\"sql\"] == \".close_tcp\":\n        raise CloseTcpSocket()\n\n    args = stmt.get(\"args\", [])\n    named_args = stmt.get(\"named_args\", [])\n    if len(named_args) == 0:\n        sql_args = [value_to_sqlite(arg) for arg in args]\n    elif len(args) == 0:\n        sql_args = {}\n        for arg in named_args:\n            value = value_to_sqlite(arg[\"value\"])\n            if arg[\"name\"][0] in (\":\", \"@\", \"$\"):\n                key = arg[\"name\"][1:]\n            else:\n                key = arg[\"name\"]\n            sql_args[key] = value\n    else:\n        raise RuntimeError(f\"Using both positional and named arguments is not supported\")\n\n    try:\n        cursor = conn.execute(stmt[\"sql\"], sql_args)\n    except sqlite3.Error as e:\n        raise ResponseError(str(e))\n    except OverflowError as e:\n        raise ResponseError(str(e))\n    except sqlite3.Warning as e:\n        raise ResponseError(str(e))\n\n    cols = [{\"name\": name} for name, *_ in cursor.description or []]\n\n    rows = []\n    for row in cursor:\n        if stmt[\"want_rows\"]:\n            rows.append([value_from_sqlite(val) for val in row])\n\n    if cursor.rowcount >= 0:\n        affected_row_count = cursor.rowcount\n    else:\n        affected_row_count = 0\n\n    if cursor.lastrowid is not None:\n        last_insert_rowid = str(cursor.lastrowid)\n    else:\n        last_insert_rowid = None\n\n    return {\n        \"cols\": cols,\n        \"rows\": rows,\n        \"affected_row_count\": affected_row_count,\n        \"last_insert_rowid\": last_insert_rowid,\n    }\n\ndef execute_batch(conn, batch):\n    step_results = []\n    step_errors = []\n    for step in batch[\"steps\"]:\n        condition = step.get(\"condition\")\n        if condition is not None:\n            enabled = eval_cond(step_results, step_errors, condition)\n        else:\n            enabled = True\n\n        step_result = None\n        step_error = None\n        if enabled:\n            try:\n                step_result = execute_stmt(conn, step[\"stmt\"])\n            except ResponseError as e:\n                step_error = {\"message\": str(e)}\n\n        step_results.append(step_result)\n        step_errors.append(step_error)\n\n    return {\n        \"step_results\": step_results,\n        \"step_errors\": step_errors,\n    }\n\ndef eval_cond(step_results, step_errors, cond):\n    if cond[\"type\"] == \"ok\":\n        return step_results[cond[\"step\"]] is not None\n    elif cond[\"type\"] == \"error\":\n        return step_errors[cond[\"step\"]] is not None\n    elif cond[\"type\"] == \"not\":\n        return not eval_cond(step_results, step_errors, cond[\"cond\"])\n    elif cond[\"type\"] == \"and\":\n        return all(eval_cond(step_results, step_errors, c) for c in cond[\"conds\"])\n    elif cond[\"type\"] == \"or\":\n        return any(eval_cond(step_results, step_errors, c) for c in cond[\"conds\"])\n    else:\n        raise RuntimeError(f\"Unknown cond: {cond!r}\")\n\ndef value_to_sqlite(value):\n    if value[\"type\"] == \"null\":\n        return None\n    elif value[\"type\"] == \"integer\":\n        return int(value[\"value\"])\n    elif value[\"type\"] == \"float\":\n        return float(value[\"value\"])\n    elif value[\"type\"] == \"text\":\n        return str(value[\"value\"])\n    elif value[\"type\"] == \"blob\":\n        return base64.b64decode(value[\"base64\"])\n    else:\n        raise RuntimeError(f\"Unknown value: {value!r}\")\n\ndef value_from_sqlite(value):\n    if value is None:\n        return {\"type\": \"null\"}\n    elif isinstance(value, int):\n        return {\"type\": \"integer\", \"value\": str(value)}\n    elif isinstance(value, float):\n        return {\"type\": \"float\", \"value\": value}\n    elif isinstance(value, str):\n        return {\"type\": \"text\", \"value\": value}\n    elif isinstance(value, bytes):\n        return {\"type\": \"blob\", \"base64\": base64.b64encode(value).decode()}\n    else:\n        raise RuntimeError(f\"Unknown SQLite value: {value!r}\")\n\nclass ResponseError(RuntimeError):\n    pass\n\nasync def to_thread(func):\n    return await asyncio.get_running_loop().run_in_executor(None, func)\n\nif __name__ == \"__main__\":\n    try:\n        sys.exit(asyncio.run(main(sys.argv[1:])))\n    except KeyboardInterrupt:\n        print()\n"
  },
  {
    "path": "testing/hrana-test-server/server_v2.py",
    "content": "import asyncio\nimport base64\nimport collections\nimport dataclasses\nimport json\nimport logging\nimport os\nimport random\nimport sys\nimport tempfile\n\nimport aiohttp.web\n\nimport c3\nfrom sqlite3_error_map import sqlite_error_code_to_name\n\nlogger = logging.getLogger(\"server\")\npersistent_db_file = os.getenv(\"PERSISTENT_DB\")\n\n@dataclasses.dataclass\nclass HttpStream:\n    conn: c3.Conn\n    sqls: dict\n    baton: str\n\nasync def main(command):\n    logging.basicConfig(level=os.getenv(\"LOG_LEVEL\", \"INFO\"))\n\n    app = aiohttp.web.Application()\n    app.add_routes([\n        aiohttp.web.get(\"/\", handle_get_index),\n        aiohttp.web.post(\"/v1/execute\", handle_post_execute),\n        aiohttp.web.post(\"/v1/batch\", handle_post_batch),\n        aiohttp.web.get(\"/v2\", handle_get_index),\n        aiohttp.web.post(\"/v2/pipeline\", handle_post_pipeline),\n    ])\n\n    app[\"http_streams\"] = {}\n\n    if persistent_db_file is None:\n        http_db_fd, http_db_file = tempfile.mkstemp(suffix=\".db\", prefix=\"hrana_test_\")\n        os.close(http_db_fd)\n    else:\n        http_db_file = persistent_db_file\n    app[\"http_db_file\"] = http_db_file\n    app[\"db_lock\"] = asyncio.Lock()\n\n    async def on_shutdown(app):\n        if http_db_file != persistent_db_file:\n            os.unlink(http_db_file)\n    app.on_shutdown.append(on_shutdown)\n\n    runner = aiohttp.web.AppRunner(app)\n    await runner.setup()\n    site = aiohttp.web.TCPSite(runner, \"localhost\", 8080)\n    await site.start()\n    logger.info(\"Server is ready\")\n\n    if len(command) > 0:\n        proc = await asyncio.create_subprocess_exec(*command)\n        code = await proc.wait()\n    else:\n        while True:\n            await asyncio.sleep(10)\n\n    await runner.cleanup()\n    return code\n\nasync def handle_get_index(req):\n    ws = aiohttp.web.WebSocketResponse(protocols=(\"hrana2\",))\n    if ws.can_prepare(req):\n        await ws.prepare(req)\n        try:\n            await handle_websocket(req.app, ws)\n        finally:\n            await ws.close()\n        return ws\n\n    return aiohttp.web.Response(text=\"This is a Hrana test server\")\n\nasync def handle_websocket(app, ws):\n    async def recv_msg():\n        ws_msg = await ws.receive()\n        if ws_msg.type == aiohttp.WSMsgType.TEXT:\n            msg = json.loads(ws_msg.data)\n            return msg\n        elif ws_msg.type in (aiohttp.WSMsgType.CLOSE, aiohttp.WSMsgType.CLOSED):\n            return None\n        else:\n            raise RuntimeError(f\"Unknown websocket message: {msg!r}\")\n\n    async def send_msg(msg):\n        msg_str = json.dumps(msg)\n        await ws.send_str(msg_str)\n\n    WsStream = collections.namedtuple(\"WsStream\", [\"conn\"])\n    streams = {}\n    sqls = {}\n\n    if persistent_db_file is None:\n        db_fd, db_file = tempfile.mkstemp(suffix=\".db\", prefix=\"hrana_test_\")\n        os.close(db_fd)\n    else:\n        db_file = persistent_db_file\n\n    async def handle_request(req):\n        if req[\"type\"] == \"open_stream\":\n            conn = await to_thread(lambda: connect(db_file))\n            stream_id = int(req[\"stream_id\"])\n            assert stream_id not in streams\n            streams[stream_id] = WsStream(conn)\n            return {\"type\": \"open_stream\"}\n        elif req[\"type\"] == \"close_stream\":\n            stream = streams.pop(int(req[\"stream_id\"]), None)\n            if stream is not None:\n                await to_thread(lambda: stream.conn.close())\n            return {\"type\": \"close_stream\"}\n        elif req[\"type\"] == \"execute\":\n            stream = streams[int(req[\"stream_id\"])]\n            async with app[\"db_lock\"]:\n                result = await to_thread(lambda: execute_stmt(stream.conn, sqls, req[\"stmt\"]))\n            return {\"type\": \"execute\", \"result\": result}\n        elif req[\"type\"] == \"batch\":\n            stream = streams[int(req[\"stream_id\"])]\n            async with app[\"db_lock\"]:\n                result = await to_thread(lambda: execute_batch(stream.conn, sqls, req[\"batch\"]))\n            return {\"type\": \"batch\", \"result\": result}\n        elif req[\"type\"] == \"sequence\":\n            stream = streams[int(req[\"stream_id\"])]\n            sql = get_sql(sqls, req)\n            async with app[\"db_lock\"]:\n                await to_thread(lambda: execute_sequence(stream.conn, sql))\n            return {\"type\": \"sequence\"}\n        elif req[\"type\"] == \"describe\":\n            stream = streams[int(req[\"stream_id\"])]\n            sql = get_sql(sqls, req)\n            async with app[\"db_lock\"]:\n                result = await to_thread(lambda: describe_stmt(stream.conn, sql))\n            return {\"type\": \"describe\", \"result\": result}\n        elif req[\"type\"] == \"store_sql\":\n            sql_id = int(req[\"sql_id\"])\n            assert sql_id not in sqls\n            sqls[sql_id] = req[\"sql\"]\n            assert len(sqls) <= 250\n            return {\"type\": \"store_sql\"}\n        elif req[\"type\"] == \"close_sql\":\n            sqls.pop(int(req[\"sql_id\"]))\n            return {\"type\": \"close_sql\"}\n        else:\n            raise RuntimeError(f\"Unknown req: {req!r}\")\n\n    hello_recvd = False\n\n    async def handle_msg(msg):\n        nonlocal hello_recvd\n        if msg[\"type\"] == \"request\":\n            assert hello_recvd\n            try:\n                response = await handle_request(msg[\"request\"])\n                await send_msg({\n                    \"type\": \"response_ok\",\n                    \"request_id\": msg[\"request_id\"],\n                    \"response\": response,\n                })\n            except ResponseError as e:\n                await send_msg({\n                    \"type\": \"response_error\",\n                    \"request_id\": msg[\"request_id\"],\n                    \"error\": e.tojson(),\n                })\n        elif msg[\"type\"] == \"hello\":\n            jwt = msg.get(\"jwt\")\n            if jwt is not None:\n                logger.info(f\"Reauthenticated with JWT: {jwt[:20]}...\")\n            hello_recvd = True\n            await send_msg({\"type\": \"hello_ok\"})\n        else:\n            raise RuntimeError(f\"Unknown msg: {msg!r}\")\n\n    try:\n        while True:\n            msg = await recv_msg()\n            if msg is None:\n                break\n            await handle_msg(msg)\n    except CloseWebSocket:\n        await ws.close()\n    except CloseTcpSocket:\n        ws._writer.transport.close()\n    finally:\n        for stream in streams.values():\n            stream.conn.close()\n        if db_file != persistent_db_file:\n            os.unlink(db_file)\n\nasync def handle_post_execute(req):\n    req_body = await req.json()\n    conn = await to_thread(lambda: connect(req.app[\"http_db_file\"]))\n    try:\n        async with req.app[\"db_lock\"]:\n            result = await to_thread(lambda: execute_stmt(conn, {}, req_body[\"stmt\"]))\n        return aiohttp.web.json_response({\"result\": result})\n    except ResponseError as e:\n        return aiohttp.web.json_response(e.tojson(), status=400)\n    finally:\n        conn.close()\n\nasync def handle_post_batch(req):\n    req_body = await req.json()\n    conn = await to_thread(lambda: connect(req.app[\"http_db_file\"]))\n    try:\n        async with req.app[\"db_lock\"]:\n            result = await to_thread(lambda: execute_batch(conn, {}, req_body[\"batch\"]))\n        return aiohttp.web.json_response({\"result\": result})\n    except ResponseError as e:\n        return aiohttp.web.json_response(e.tojson(), status=400)\n    finally:\n        conn.close()\n\nasync def handle_post_pipeline(req):\n    req_body = await req.json()\n    app = req.app\n\n    if req_body.get(\"baton\") is not None:\n        baton = req_body[\"baton\"]\n        stream_id, _, _ = baton.partition(\".\")\n        stream = req.app[\"http_streams\"][stream_id]\n        assert stream.baton == baton\n    else:\n        conn = await to_thread(lambda: connect(req.app[\"http_db_file\"]))\n        stream_id = random.randbytes(16).hex()\n        stream = HttpStream(conn, sqls={}, baton=None)\n        req.app[\"http_streams\"][stream_id] = stream\n    stream.baton = f\"{stream_id}.{random.randbytes(8).hex()}\"\n\n    async def handle_request(req):\n        if req[\"type\"] == \"execute\":\n            async with app[\"db_lock\"]:\n                result = await to_thread(lambda: execute_stmt(stream.conn, stream.sqls, req[\"stmt\"]))\n            return {\"type\": \"execute\", \"result\": result}\n        elif req[\"type\"] == \"batch\":\n            async with app[\"db_lock\"]:\n                result = await to_thread(lambda: execute_batch(stream.conn, stream.sqls, req[\"batch\"]))\n            return {\"type\": \"batch\", \"result\": result}\n        elif req[\"type\"] == \"sequence\":\n            sql = get_sql(stream.sqls, req)\n            async with app[\"db_lock\"]:\n                await to_thread(lambda: execute_sequence(stream.conn, sql))\n            return {\"type\": \"sequence\"}\n        elif req[\"type\"] == \"describe\":\n            sql = get_sql(stream.sqls, req)\n            async with app[\"db_lock\"]:\n                result = await to_thread(lambda: describe_stmt(stream.conn, sql))\n            return {\"type\": \"describe\", \"result\": result}\n        elif req[\"type\"] == \"store_sql\":\n            sql_id = int(req[\"sql_id\"])\n            assert sql_id not in stream.sqls\n            stream.sqls[sql_id] = req[\"sql\"]\n            assert len(stream.sqls) <= 50\n            return {\"type\": \"store_sql\"}\n        elif req[\"type\"] == \"close_sql\":\n            stream.sqls.pop(int(req[\"sql_id\"]))\n            return {\"type\": \"close_sql\"}\n        elif req[\"type\"] == \"close\":\n            stream.conn.close()\n            stream.conn = None\n            return {\"type\": \"close\"}\n        else:\n            raise RuntimeError(f\"Unknown req: {req!r}\")\n\n    try:\n        results = []\n        for request in req_body[\"requests\"]:\n            try:\n                response = await handle_request(request)\n                result = {\"type\": \"ok\", \"response\": response}\n            except ResponseError as e:\n                result = {\"type\": \"error\", \"error\": e.tojson()}\n            results.append(result)\n    except Exception:\n        if stream.conn is not None:\n            stream.conn.close()\n        stream.conn = None\n        raise\n    finally:\n        if stream.conn is None:\n            stream.baton = None\n            del app[\"http_streams\"][stream_id]\n\n    return aiohttp.web.json_response({\n        \"baton\": stream.baton,\n        \"results\": results,\n    })\n\ndef connect(db_file):\n    conn = c3.Conn.open(db_file)\n    conn.extended_result_codes(True)\n    conn.limit(c3.SQLITE_LIMIT_ATTACHED, 0)\n    conn.busy_timeout(1000)\n    conn.exec(\"PRAGMA journal_mode = WAL\")\n    return conn\n\ndef get_sql(sqls, obj):\n    sql, sql_id = obj.get(\"sql\"), obj.get(\"sql_id\")\n    assert sql is None or sql_id is None\n    if sql is not None:\n        return sql\n    elif sql_id is not None:\n        return sqls[sql_id]\n    else:\n        raise RuntimeError(\"Expected 'sql' or 'sql_id'\")\n\nclass CloseWebSocket(BaseException):\n    pass\n\nclass CloseTcpSocket(BaseException):\n    pass\n\ndef execute_stmt(conn, sqls, stmt):\n    sql = get_sql(sqls, stmt)\n\n    if sql == \".close_ws\":\n        raise CloseWebSocket()\n    elif sql == \".close_tcp\":\n        raise CloseTcpSocket()\n\n    try:\n        changes_before = conn.total_changes()\n        prepared, sql_rest = conn.prepare(sql)\n        if not prepared:\n            raise ResponseError(f\"SQL string does not contain a valid statement\", \"SQL_NO_STATEMENT\")\n\n        param_count = prepared.param_count()\n\n        if len(sql_rest.strip()) != 0:\n            raise ResponseError(f\"SQL string contains more than one statement\")\n\n        args = stmt.get(\"args\", [])\n        named_args = stmt.get(\"named_args\", [])\n        provided_params_count = len(args) + len(named_args)\n        if provided_params_count != param_count:\n            raise ResponseError(f\"Required {param_count} but {provided_params_count} were provided\", \"ARGS_INVALID\")\n\n        for param_i, arg_value in enumerate(args, 1):\n            prepared.bind(param_i, value_to_sqlite(arg_value))\n\n        for arg in named_args:\n            arg_name = arg[\"name\"]\n            if arg_name[0] in (\":\", \"@\", \"$\"):\n                param_i = prepared.param_index(arg_name)\n            else:\n                for prefix in (\":\", \"@\", \"$\"):\n                    param_i = prepared.param_index(prefix + arg_name)\n                    if param_i != 0: break\n\n            if param_i == 0:\n                raise ResponseError(f\"Parameter with name {arg_name!r} was not found\", \"ARGS_INVALID\")\n            prepared.bind(param_i, value_to_sqlite(arg[\"value\"]))\n\n        col_count = prepared.column_count()\n        cols = [\n            {\n                \"name\": prepared.column_name(col_i),\n                \"decltype\": prepared.column_decltype(col_i),\n            }\n            for col_i in range(col_count)\n        ]\n\n        want_rows = stmt.get(\"want_rows\", True)\n        rows = []\n        while prepared.step():\n            if not want_rows:\n                continue\n\n            cells = []\n            for col_i in range(col_count):\n                try:\n                    val = prepared.column(col_i)\n                except ValueError as e:\n                    name = cols[col_i].get(\"name\") or col_i\n                    if isinstance(e, UnicodeDecodeError):\n                        # NOTE: formatting msg like this to match Python's dbapi\n                        # error, but it could be anything. However this way\n                        # allows the hrana test server to be used against\n                        # Python's test suite\n                        obj = e.object.decode(errors=\"replace\")\n                        msg = f\"Could not decode to UTF-8 column {name!r} with text {obj!r}\"\n                        code = \"UNICODE_ERROR\"\n                    else:\n                        msg = f\"Could not get column {name!r}: {e}\"\n                        code = \"VALUE_ERROR\"\n                    raise ResponseError(msg, code) from e\n\n                cells.append(value_from_sqlite(val))\n\n            rows.append(cells)\n\n        affected_row_count = conn.total_changes() - changes_before\n        last_insert_rowid = conn.last_insert_rowid()\n    except c3.SqliteError as e:\n        raise ResponseError(e) from e\n\n    return {\n        \"cols\": cols,\n        \"rows\": rows,\n        \"affected_row_count\": affected_row_count,\n        \"last_insert_rowid\": str(last_insert_rowid),\n    }\n\ndef describe_stmt(conn, sql):\n    try:\n        prepared, _ = conn.prepare(sql)\n\n        param_count = prepared.param_count()\n        params = [\n            {\"name\": prepared.param_name(param_i)}\n            for param_i in range(1, param_count+1)\n        ]\n\n        col_count = prepared.column_count()\n        cols = [\n            {\n                \"name\": prepared.column_name(col_i),\n                \"decltype\": prepared.column_decltype(col_i)\n            }\n            for col_i in range(col_count)\n        ]\n\n        is_explain = prepared.isexplain() > 0\n        is_readonly = prepared.readonly()\n    except c3.SqliteError as e:\n        raise ResponseError(e) from e\n\n    return {\n        \"params\": params,\n        \"cols\": cols,\n        \"is_explain\": is_explain,\n        \"is_readonly\": is_readonly,\n    }\n\ndef execute_sequence(conn, sql):\n    try:\n        while len(sql) > 0:\n            prepared, sql = conn.prepare(sql)\n            if prepared is None:\n                break\n            while prepared.step():\n                pass\n    except c3.SqliteError as e:\n        raise ResponseError(e) from e\n\ndef execute_batch(conn, sqls, batch):\n    step_results = []\n    step_errors = []\n    for step in batch[\"steps\"]:\n        condition = step.get(\"condition\")\n        if condition is not None:\n            enabled = eval_cond(step_results, step_errors, condition)\n        else:\n            enabled = True\n\n        step_result = None\n        step_error = None\n        if enabled:\n            try:\n                step_result = execute_stmt(conn, sqls, step[\"stmt\"])\n            except ResponseError as e:\n                step_error = e.tojson()\n\n        step_results.append(step_result)\n        step_errors.append(step_error)\n\n    return {\n        \"step_results\": step_results,\n        \"step_errors\": step_errors,\n    }\n\ndef eval_cond(step_results, step_errors, cond):\n    if cond[\"type\"] == \"ok\":\n        return step_results[cond[\"step\"]] is not None\n    elif cond[\"type\"] == \"error\":\n        return step_errors[cond[\"step\"]] is not None\n    elif cond[\"type\"] == \"not\":\n        return not eval_cond(step_results, step_errors, cond[\"cond\"])\n    elif cond[\"type\"] == \"and\":\n        return all(eval_cond(step_results, step_errors, c) for c in cond[\"conds\"])\n    elif cond[\"type\"] == \"or\":\n        return any(eval_cond(step_results, step_errors, c) for c in cond[\"conds\"])\n    else:\n        raise RuntimeError(f\"Unknown cond: {cond!r}\")\n\ndef value_to_sqlite(value):\n    if value[\"type\"] == \"null\":\n        return None\n    elif value[\"type\"] == \"integer\":\n        return int(value[\"value\"])\n    elif value[\"type\"] == \"float\":\n        return float(value[\"value\"])\n    elif value[\"type\"] == \"text\":\n        return str(value[\"value\"])\n    elif value[\"type\"] == \"blob\":\n        return base64.b64decode(value[\"base64\"])\n    else:\n        raise RuntimeError(f\"Unknown value: {value!r}\")\n\ndef value_from_sqlite(value):\n    if value is None:\n        return {\"type\": \"null\"}\n    elif isinstance(value, int):\n        return {\"type\": \"integer\", \"value\": str(value)}\n    elif isinstance(value, float):\n        return {\"type\": \"float\", \"value\": value}\n    elif isinstance(value, str):\n        return {\"type\": \"text\", \"value\": value}\n    elif isinstance(value, bytes):\n        return {\"type\": \"blob\", \"base64\": base64.b64encode(value).decode()}\n    else:\n        raise RuntimeError(f\"Unknown SQLite value: {value!r}\")\n\n\nclass ResponseError(RuntimeError):\n    def __init__(self, message, code=None):\n        if isinstance(message, c3.SqliteError):\n            if code is None:\n                # Use base error code (error_code & 0xFF) instead of extended code\n                base_code = message.error_code & 0xFF if message.error_code else None\n                code = sqlite_error_code_to_name.get(base_code)\n            message = str(message)\n        super().__init__(message)\n        self.code = code\n\n    def tojson(self):\n        message = str(self)\n        if self.code:\n            return {\"message\": message, \"code\": self.code}\n        return {\"message\": message}\n\n\nasync def to_thread(func):\n    return await asyncio.get_running_loop().run_in_executor(None, func)\n\nif __name__ == \"__main__\":\n    try:\n        sys.exit(asyncio.run(main(sys.argv[1:])))\n    except KeyboardInterrupt:\n        print()\n"
  },
  {
    "path": "testing/hrana-test-server/server_v3.py",
    "content": "import asyncio\nimport base64\nimport collections\nimport dataclasses\nimport json\nimport logging\nimport os\nimport random\nimport sys\nimport tempfile\n\nimport aiohttp.web\n\nimport c3\nimport from_proto\nimport to_proto\nfrom sqlite3_error_map import sqlite_error_code_to_name\nimport proto.hrana.http_pb2\nimport proto.hrana.ws_pb2\n\nlogger = logging.getLogger(\"server\")\npersistent_db_file = os.getenv(\"PERSISTENT_DB\")\nencoding = os.getenv(\"ENCODING\", \"protobuf\")\nassert encoding in (\"json\", \"protobuf\")\n\n@dataclasses.dataclass\nclass HttpStream:\n    conn: c3.Conn\n    sqls: dict\n    baton: str\n\nasync def main(command):\n    logging.basicConfig(level=os.getenv(\"LOG_LEVEL\", \"INFO\"))\n\n    http_dir = {\n        \"json\": \"v3\",\n        \"protobuf\": \"v3-protobuf\",\n    }[encoding]\n\n    app = aiohttp.web.Application()\n    app.add_routes([\n        aiohttp.web.get(\"/\", handle_get_index),\n        aiohttp.web.get(f\"/{http_dir}\", handle_get_index),\n        aiohttp.web.post(f\"/{http_dir}/pipeline\", handle_post_pipeline),\n        aiohttp.web.post(f\"/{http_dir}/cursor\", handle_post_cursor),\n    ])\n\n    app[\"http_streams\"] = {}\n\n    if persistent_db_file is None:\n        http_db_fd, http_db_file = tempfile.mkstemp(suffix=\".db\", prefix=\"hrana_test_\")\n        os.close(http_db_fd)\n    else:\n        http_db_file = persistent_db_file\n    app[\"http_db_file\"] = http_db_file\n    app[\"db_lock\"] = asyncio.Lock()\n\n    async def on_shutdown(app):\n        if http_db_file != persistent_db_file:\n            os.unlink(http_db_file)\n    app.on_shutdown.append(on_shutdown)\n\n    runner = aiohttp.web.AppRunner(app)\n    await runner.setup()\n    site = aiohttp.web.TCPSite(runner, \"localhost\", 8080)\n    await site.start()\n    logger.info(\"Server is ready\")\n\n    if len(command) > 0:\n        proc = await asyncio.create_subprocess_exec(*command)\n        code = await proc.wait()\n    else:\n        while True:\n            await asyncio.sleep(10)\n\n    await runner.cleanup()\n    return code\n\nasync def handle_get_index(req):\n    protocol = {\n        \"json\": \"hrana3\",\n        \"protobuf\": \"hrana3-protobuf\",\n    }[encoding];\n    ws = aiohttp.web.WebSocketResponse(protocols=(protocol,))\n    if ws.can_prepare(req):\n        await ws.prepare(req)\n        try:\n            await handle_websocket(req.app, ws)\n        finally:\n            await ws.close()\n        return ws\n\n    return aiohttp.web.Response(text=\"This is a Hrana test server\")\n\nasync def handle_websocket(app, ws):\n    async def recv_msg():\n        ws_msg = await ws.receive()\n        if ws_msg.type == aiohttp.WSMsgType.TEXT:\n            assert encoding == \"json\"\n            msg = json.loads(ws_msg.data)\n            return msg\n        elif ws_msg.type == aiohttp.WSMsgType.BINARY:\n            assert encoding == \"protobuf\"\n            msg_proto = proto.hrana.ws_pb2.ClientMsg()\n            msg_proto.ParseFromString(ws_msg.data)\n            msg = from_proto.ws_client_msg(msg_proto)\n            return msg\n        elif ws_msg.type in (aiohttp.WSMsgType.CLOSE, aiohttp.WSMsgType.CLOSED):\n            return None\n        else:\n            raise RuntimeError(f\"Unknown websocket message: {msg!r}\")\n\n    async def send_msg(msg):\n        if encoding == \"json\":\n            await ws.send_str(json.dumps(msg))\n        elif encoding == \"protobuf\":\n            msg_proto = proto.hrana.ws_pb2.ServerMsg()\n            to_proto.ws_server_msg(msg_proto, msg)\n            await ws.send_bytes(msg_proto.SerializeToString())\n        else:\n            assert False\n\n    WsStream = collections.namedtuple(\"WsStream\", [\"conn\"])\n    streams = {}\n    cursors = {}\n    sqls = {}\n\n    if persistent_db_file is None:\n        db_fd, db_file = tempfile.mkstemp(suffix=\".db\", prefix=\"hrana_test_\")\n        os.close(db_fd)\n    else:\n        db_file = persistent_db_file\n\n    async def handle_request(req):\n        if req[\"type\"] == \"open_stream\":\n            conn = await to_thread(lambda: connect(db_file))\n            stream_id = int(req[\"stream_id\"])\n            assert stream_id not in streams\n            streams[stream_id] = WsStream(conn)\n            return {\"type\": \"open_stream\"}\n        elif req[\"type\"] == \"close_stream\":\n            stream = streams.pop(int(req[\"stream_id\"]), None)\n            if stream is not None:\n                await to_thread(lambda: stream.conn.close())\n            return {\"type\": \"close_stream\"}\n        elif req[\"type\"] == \"execute\":\n            stream = streams[int(req[\"stream_id\"])]\n            async with app[\"db_lock\"]:\n                result = await to_thread(lambda: execute_stmt(stream.conn, sqls, req[\"stmt\"]))\n            return {\"type\": \"execute\", \"result\": result}\n        elif req[\"type\"] == \"batch\":\n            stream = streams[int(req[\"stream_id\"])]\n            async with app[\"db_lock\"]:\n                result = await to_thread(lambda: execute_batch(stream.conn, sqls, req[\"batch\"]))\n            return {\"type\": \"batch\", \"result\": result}\n        elif req[\"type\"] == \"open_cursor\":\n            stream = streams[int(req[\"stream_id\"])]\n            cursor_id = int(req[\"cursor_id\"])\n            assert cursor_id not in cursors\n            async with app[\"db_lock\"]:\n                entries = await to_thread(lambda: execute_cursor(stream.conn, sqls, req[\"batch\"]))\n            cursors[cursor_id] = collections.deque(entries)\n            return {\"type\": \"open_cursor\"}\n        elif req[\"type\"] == \"close_cursor\":\n            cursors.pop(int(req[\"cursor_id\"]), None)\n            return {\"type\": \"close_cursor\"}\n        elif req[\"type\"] == \"fetch_cursor\":\n            cursor = cursors[int(req[\"cursor_id\"])]\n            entries = []\n            while len(cursor) > 0 and len(entries) < req[\"max_count\"]:\n                entries.append(cursor.popleft())\n            return {\"type\": \"fetch_cursor\", \"entries\": entries, \"done\": len(cursor) == 0}\n        elif req[\"type\"] == \"sequence\":\n            stream = streams[int(req[\"stream_id\"])]\n            sql = get_sql(sqls, req)\n            async with app[\"db_lock\"]:\n                await to_thread(lambda: execute_sequence(stream.conn, sql))\n            return {\"type\": \"sequence\"}\n        elif req[\"type\"] == \"describe\":\n            stream = streams[int(req[\"stream_id\"])]\n            sql = get_sql(sqls, req)\n            async with app[\"db_lock\"]:\n                result = await to_thread(lambda: describe_stmt(stream.conn, sql))\n            return {\"type\": \"describe\", \"result\": result}\n        elif req[\"type\"] == \"store_sql\":\n            sql_id = int(req[\"sql_id\"])\n            assert sql_id not in sqls\n            sqls[sql_id] = req[\"sql\"]\n            assert len(sqls) <= 250\n            return {\"type\": \"store_sql\"}\n        elif req[\"type\"] == \"close_sql\":\n            sqls.pop(int(req[\"sql_id\"]))\n            return {\"type\": \"close_sql\"}\n        elif req[\"type\"] == \"get_autocommit\":\n            stream = streams[int(req[\"stream_id\"])]\n            is_autocommit = stream.conn.get_autocommit()\n            return {\"type\": \"get_autocommit\", \"is_autocommit\": is_autocommit}\n        else:\n            raise RuntimeError(f\"Unknown req: {req!r}\")\n\n    hello_recvd = False\n\n    async def handle_msg(msg):\n        nonlocal hello_recvd\n        if msg[\"type\"] == \"request\":\n            assert hello_recvd\n            try:\n                response = await handle_request(msg[\"request\"])\n                await send_msg({\n                    \"type\": \"response_ok\",\n                    \"request_id\": msg[\"request_id\"],\n                    \"response\": response,\n                })\n            except ResponseError as e:\n                await send_msg({\n                    \"type\": \"response_error\",\n                    \"request_id\": msg[\"request_id\"],\n                    \"error\": e.tojson(),\n                })\n        elif msg[\"type\"] == \"hello\":\n            jwt = msg.get(\"jwt\")\n            if jwt is not None:\n                logger.info(f\"Reauthenticated with JWT: {jwt[:20]}...\")\n            hello_recvd = True\n            await send_msg({\"type\": \"hello_ok\"})\n        else:\n            raise RuntimeError(f\"Unknown msg: {msg!r}\")\n\n    try:\n        while True:\n            msg = await recv_msg()\n            if msg is None:\n                break\n            await handle_msg(msg)\n    except CloseWebSocket:\n        await ws.close()\n    except CloseTcpSocket:\n        ws._writer.transport.close()\n    finally:\n        for stream in streams.values():\n            stream.conn.close()\n        if db_file != persistent_db_file:\n            os.unlink(db_file)\n\nasync def handle_post_pipeline(req):\n    if encoding == \"json\":\n        req_body = await req.json()\n    elif encoding == \"protobuf\":\n        msg_proto = proto.hrana.http_pb2.PipelineReqBody()\n        msg_proto.ParseFromString(await req.read())\n        req_body = from_proto.http_pipeline_req_body(msg_proto)\n\n    app = req.app\n    stream_id, stream = await handle_baton(app, req_body.get(\"baton\"))\n\n    async def handle_request(req):\n        if req[\"type\"] == \"execute\":\n            async with app[\"db_lock\"]:\n                result = await to_thread(lambda: execute_stmt(stream.conn, stream.sqls, req[\"stmt\"]))\n            return {\"type\": \"execute\", \"result\": result}\n        elif req[\"type\"] == \"batch\":\n            async with app[\"db_lock\"]:\n                result = await to_thread(lambda: execute_batch(stream.conn, stream.sqls, req[\"batch\"]))\n            return {\"type\": \"batch\", \"result\": result}\n        elif req[\"type\"] == \"sequence\":\n            sql = get_sql(stream.sqls, req)\n            async with app[\"db_lock\"]:\n                await to_thread(lambda: execute_sequence(stream.conn, sql))\n            return {\"type\": \"sequence\"}\n        elif req[\"type\"] == \"describe\":\n            sql = get_sql(stream.sqls, req)\n            async with app[\"db_lock\"]:\n                result = await to_thread(lambda: describe_stmt(stream.conn, sql))\n            return {\"type\": \"describe\", \"result\": result}\n        elif req[\"type\"] == \"store_sql\":\n            sql_id = int(req[\"sql_id\"])\n            assert sql_id not in stream.sqls\n            stream.sqls[sql_id] = req[\"sql\"]\n            assert len(stream.sqls) <= 50\n            return {\"type\": \"store_sql\"}\n        elif req[\"type\"] == \"close_sql\":\n            stream.sqls.pop(int(req[\"sql_id\"]))\n            return {\"type\": \"close_sql\"}\n        elif req[\"type\"] == \"close\":\n            stream.conn.close()\n            stream.conn = None\n            return {\"type\": \"close\"}\n        elif req[\"type\"] == \"get_autocommit\":\n            is_autocommit = stream.conn.get_autocommit()\n            return {\"type\": \"get_autocommit\", \"is_autocommit\": is_autocommit}\n        else:\n            raise RuntimeError(f\"Unknown req: {req!r}\")\n\n    try:\n        results = []\n        for request in req_body[\"requests\"]:\n            try:\n                response = await handle_request(request)\n                result = {\"type\": \"ok\", \"response\": response}\n            except ResponseError as e:\n                result = {\"type\": \"error\", \"error\": e.tojson()}\n            results.append(result)\n    except Exception:\n        if stream.conn is not None:\n            stream.conn.close()\n        stream.conn = None\n        raise\n    finally:\n        if stream.conn is None:\n            stream.baton = None\n            del app[\"http_streams\"][stream_id]\n\n    resp_body = {\n        \"baton\": stream.baton,\n        \"results\": results,\n    }\n    if encoding == \"json\":\n        return aiohttp.web.json_response(resp_body)\n    elif encoding == \"protobuf\":\n        msg_proto = proto.hrana.http_pb2.PipelineRespBody()\n        to_proto.http_pipeline_resp_body(msg_proto, resp_body)\n        return aiohttp.web.Response(\n            body=msg_proto.SerializeToString(),\n            content_type=\"application/x-protobuf\",\n        )\n\nasync def handle_post_cursor(req):\n    if encoding == \"json\":\n        req_body = await req.json()\n    elif encoding == \"protobuf\":\n        msg_proto = proto.hrana.http_pb2.CursorReqBody()\n        msg_proto.ParseFromString(await req.read())\n        req_body = from_proto.http_cursor_req_body(msg_proto)\n\n    app = req.app\n    stream_id, stream = await handle_baton(app, req_body.get(\"baton\"))\n\n    resp = aiohttp.web.StreamResponse()\n    resp.headers[\"content-type\"] = {\n        \"json\": \"text/plain\",\n        \"protobuf\": \"application/octet-stream\",\n    }[encoding]\n    await resp.prepare(req)\n\n    async def send_item(item, proto_class, to_proto_fun):\n        if encoding == \"json\":\n            await resp.write(json.dumps(item).encode())\n            await resp.write(b\"\\n\")\n        elif encoding == \"protobuf\":\n            msg_proto = proto_class()\n            to_proto_fun(msg_proto, item)\n            msg_bytes = msg_proto.SerializeToString()\n            await resp.write(encode_varint(len(msg_bytes)))\n            await resp.write(msg_bytes)\n\n    resp_body = {\"baton\": stream.baton}\n    await send_item(resp_body, proto.hrana.http_pb2.CursorRespBody, to_proto.http_cursor_resp_body)\n\n    async with app[\"db_lock\"]:\n        entries = await to_thread(lambda: execute_cursor(stream.conn, stream.sqls, req_body[\"batch\"]))\n    for entry in entries:\n        await send_item(entry, proto.hrana_pb2.CursorEntry, to_proto.cursor_entry)\n\n    await resp.write_eof()\n    return resp\n\nasync def handle_baton(app, baton):\n    if baton is not None:\n        stream_id, _, _ = baton.partition(\".\")\n        stream = app[\"http_streams\"][stream_id]\n        assert stream.baton == baton\n    else:\n        conn = await to_thread(lambda: connect(app[\"http_db_file\"]))\n        stream_id = random.randbytes(16).hex()\n        stream = HttpStream(conn, sqls={}, baton=None)\n        app[\"http_streams\"][stream_id] = stream\n    stream.baton = f\"{stream_id}.{random.randbytes(8).hex()}\"\n    return stream_id, stream\n\ndef encode_varint(num):\n    bs = []\n    while True:\n        b = num & 0x7f\n        num = num >> 7\n        if num == 0:\n            bs.append(b)\n            break\n        else:\n            bs.append(0x80 | b)\n    return bytes(bs)\n\ndef connect(db_file):\n    conn = c3.Conn.open(db_file)\n    conn.extended_result_codes(True)\n    conn.limit(c3.SQLITE_LIMIT_ATTACHED, 0)\n    conn.busy_timeout(1000)\n    conn.exec(\"PRAGMA journal_mode = WAL\")\n    return conn\n\ndef get_sql(sqls, obj):\n    sql, sql_id = obj.get(\"sql\"), obj.get(\"sql_id\")\n    assert sql is None or sql_id is None\n    if sql is not None:\n        return sql\n    elif sql_id is not None:\n        return sqls[sql_id]\n    else:\n        raise RuntimeError(\"Expected 'sql' or 'sql_id'\")\n\nclass CloseWebSocket(BaseException):\n    pass\n\nclass CloseTcpSocket(BaseException):\n    pass\n\ndef execute_stmt(conn, sqls, stmt):\n    sql = get_sql(sqls, stmt)\n\n    if sql == \".close_ws\":\n        raise CloseWebSocket()\n    elif sql == \".close_tcp\":\n        raise CloseTcpSocket()\n\n    try:\n        changes_before = conn.total_changes()\n        prepared, sql_rest = conn.prepare(sql)\n        if not prepared:\n            raise ResponseError(\"SQL string does not contain a valid statement\", \"SQL_NO_STATEMENT\")\n\n        param_count = prepared.param_count()\n\n        if len(sql_rest.strip()) != 0:\n            raise ResponseError(\"SQL string contains more than one statement\")\n\n        args = stmt.get(\"args\", [])\n        named_args = stmt.get(\"named_args\", [])\n        provided_params_count = len(args) + len(named_args)\n        if provided_params_count != param_count:\n            raise ResponseError(f\"Required {param_count} but {provided_params_count} were provided\", \"ARGS_INVALID\")\n\n        for param_i, arg_value in enumerate(args, 1):\n            prepared.bind(param_i, value_to_sqlite(arg_value))\n\n        for arg in named_args:\n            arg_name = arg[\"name\"]\n            if arg_name[0] in (\":\", \"@\", \"$\"):\n                param_i = prepared.param_index(arg_name)\n            else:\n                for prefix in (\":\", \"@\", \"$\"):\n                    param_i = prepared.param_index(prefix + arg_name)\n                    if param_i != 0: break\n\n            if param_i == 0:\n                raise ResponseError(f\"Parameter with name {arg_name!r} was not found\", \"ARGS_INVALID\")\n            prepared.bind(param_i, value_to_sqlite(arg[\"value\"]))\n\n        col_count = prepared.column_count()\n        cols = [\n            {\n                \"name\": prepared.column_name(col_i),\n                \"decltype\": prepared.column_decltype(col_i),\n            }\n            for col_i in range(col_count)\n        ]\n\n        want_rows = stmt.get(\"want_rows\", True)\n        rows = []\n        while prepared.step():\n            if not want_rows:\n                continue\n\n            cells = []\n            for col_i in range(col_count):\n                try:\n                    val = prepared.column(col_i)\n                except ValueError as e:\n                    name = cols[col_i].get(\"name\") or col_i\n                    if isinstance(e, UnicodeDecodeError):\n                        # NOTE: formatting msg like this to match Python's dbapi\n                        # error, but it could be anything. However this way\n                        # allows the hrana test server to be used against\n                        # Python's test suite\n                        obj = e.object.decode(errors=\"replace\")\n                        msg = f\"Could not decode to UTF-8 column {name!r} with text {obj!r}\"\n                        code = \"UNICODE_ERROR\"\n                    else:\n                        msg = f\"Could not get column {name!r}: {e}\"\n                        code = \"VALUE_ERROR\"\n                    raise ResponseError(msg, code) from e\n\n                cells.append(value_from_sqlite(val))\n\n            rows.append(cells)\n\n        affected_row_count = conn.total_changes() - changes_before\n        last_insert_rowid = conn.last_insert_rowid()\n    except c3.SqliteError as e:\n        raise ResponseError(e) from e\n\n    return {\n        \"cols\": cols,\n        \"rows\": rows,\n        \"affected_row_count\": affected_row_count,\n        \"last_insert_rowid\": str(last_insert_rowid),\n    }\n\ndef describe_stmt(conn, sql):\n    try:\n        prepared, _ = conn.prepare(sql)\n\n        param_count = prepared.param_count()\n        params = [\n            {\"name\": prepared.param_name(param_i)}\n            for param_i in range(1, param_count+1)\n        ]\n\n        col_count = prepared.column_count()\n        cols = [\n            {\n                \"name\": prepared.column_name(col_i),\n                \"decltype\": prepared.column_decltype(col_i)\n            }\n            for col_i in range(col_count)\n        ]\n\n        is_explain = prepared.isexplain() > 0\n        is_readonly = prepared.readonly()\n    except c3.SqliteError as e:\n        raise ResponseError(e) from e\n\n    return {\n        \"params\": params,\n        \"cols\": cols,\n        \"is_explain\": is_explain,\n        \"is_readonly\": is_readonly,\n    }\n\ndef execute_sequence(conn, sql):\n    try:\n        while len(sql) > 0:\n            prepared, sql = conn.prepare(sql)\n            if prepared is None:\n                break\n            while prepared.step():\n                pass\n    except c3.SqliteError as e:\n        raise ResponseError(e) from e\n\ndef execute_cursor(conn, sqls, batch):\n    batch_result = execute_batch(conn, sqls, batch)\n\n    entries = []\n    for step_i in range(len(batch[\"steps\"])):\n        step_result = batch_result[\"step_results\"][step_i]\n        step_error = batch_result[\"step_errors\"][step_i]\n\n        if step_result is not None:\n            entries.append({\n                \"type\": \"step_begin\",\n                \"step\": step_i,\n                \"cols\": step_result[\"cols\"],\n            })\n            for row in step_result[\"rows\"]:\n                entries.append({\"type\": \"row\", \"row\": row})\n            entries.append({\n                \"type\": \"step_end\",\n                \"affected_row_count\": step_result[\"affected_row_count\"],\n                \"last_insert_rowid\": step_result[\"last_insert_rowid\"],\n            })\n        elif step_error is not None:\n            entries.append({\n                \"type\": \"step_error\",\n                \"step\": step_i,\n                \"error\": step_error,\n            })\n    return entries\n\ndef execute_batch(conn, sqls, batch):\n    step_results = []\n    step_errors = []\n    for step in batch[\"steps\"]:\n        condition = step.get(\"condition\")\n        if condition is not None:\n            enabled = eval_cond(conn, step_results, step_errors, condition)\n        else:\n            enabled = True\n\n        step_result = None\n        step_error = None\n        if enabled:\n            try:\n                step_result = execute_stmt(conn, sqls, step[\"stmt\"])\n            except ResponseError as e:\n                step_error = e.tojson()\n\n        step_results.append(step_result)\n        step_errors.append(step_error)\n\n    return {\n        \"step_results\": step_results,\n        \"step_errors\": step_errors,\n    }\n\ndef eval_cond(conn, step_results, step_errors, cond):\n    if cond[\"type\"] == \"ok\":\n        return step_results[cond[\"step\"]] is not None\n    elif cond[\"type\"] == \"error\":\n        return step_errors[cond[\"step\"]] is not None\n    elif cond[\"type\"] == \"not\":\n        return not eval_cond(conn, step_results, step_errors, cond[\"cond\"])\n    elif cond[\"type\"] == \"and\":\n        return all(eval_cond(conn, step_results, step_errors, c) for c in cond[\"conds\"])\n    elif cond[\"type\"] == \"or\":\n        return any(eval_cond(conn, step_results, step_errors, c) for c in cond[\"conds\"])\n    elif cond[\"type\"] == \"is_autocommit\":\n        return conn.get_autocommit()\n    else:\n        raise RuntimeError(f\"Unknown cond: {cond!r}\")\n\ndef value_to_sqlite(value):\n    if value[\"type\"] == \"null\":\n        return None\n    elif value[\"type\"] == \"integer\":\n        return int(value[\"value\"])\n    elif value[\"type\"] == \"float\":\n        return float(value[\"value\"])\n    elif value[\"type\"] == \"text\":\n        return str(value[\"value\"])\n    elif value[\"type\"] == \"blob\":\n        return base64.b64decode(value[\"base64\"])\n    else:\n        raise RuntimeError(f\"Unknown value: {value!r}\")\n\ndef value_from_sqlite(value):\n    if value is None:\n        return {\"type\": \"null\"}\n    elif isinstance(value, int):\n        return {\"type\": \"integer\", \"value\": str(value)}\n    elif isinstance(value, float):\n        return {\"type\": \"float\", \"value\": value}\n    elif isinstance(value, str):\n        return {\"type\": \"text\", \"value\": value}\n    elif isinstance(value, bytes):\n        return {\"type\": \"blob\", \"base64\": base64.b64encode(value).decode()}\n    else:\n        raise RuntimeError(f\"Unknown SQLite value: {value!r}\")\n\n\nclass ResponseError(RuntimeError):\n    def __init__(self, message, code=None):\n        if isinstance(message, c3.SqliteError):\n            if code is None:\n                # Use base error code (error_code & 0xFF) instead of extended code\n                base_code = message.error_code & 0xFF if message.error_code else None\n                code = sqlite_error_code_to_name.get(base_code)\n            message = str(message)\n        super().__init__(message)\n        self.code = code\n\n    def tojson(self):\n        message = str(self)\n        if self.code:\n            return {\"message\": message, \"code\": self.code}\n        return {\"message\": message}\n\n\nasync def to_thread(func):\n    return await asyncio.get_running_loop().run_in_executor(None, func)\n\nif __name__ == \"__main__\":\n    try:\n        sys.exit(asyncio.run(main(sys.argv[1:])))\n    except KeyboardInterrupt:\n        print()\n"
  },
  {
    "path": "testing/hrana-test-server/sqlite3_error_map.py",
    "content": "sqlite_error_code_to_name = {\n    0: \"SQLITE_OK\",\n    1: \"SQLITE_ERROR\",\n    2: \"SQLITE_INTERNAL\",\n    3: \"SQLITE_PERM\",\n    4: \"SQLITE_ABORT\",\n    5: \"SQLITE_BUSY\",\n    6: \"SQLITE_LOCKED\",\n    7: \"SQLITE_NOMEM\",\n    8: \"SQLITE_READONLY\",\n    9: \"SQLITE_INTERRUPT\",\n    10: \"SQLITE_IOERR\",\n    11: \"SQLITE_CORRUPT\",\n    12: \"SQLITE_NOTFOUND\",\n    13: \"SQLITE_FULL\",\n    14: \"SQLITE_CANTOPEN\",\n    15: \"SQLITE_PROTOCOL\",\n    16: \"SQLITE_EMPTY\",\n    17: \"SQLITE_SCHEMA\",\n    18: \"SQLITE_TOOBIG\",\n    19: \"SQLITE_CONSTRAINT\",\n    20: \"SQLITE_MISMATCH\",\n    21: \"SQLITE_MISUSE\",\n    22: \"SQLITE_NOLFS\",\n    23: \"SQLITE_AUTH\",\n    24: \"SQLITE_FORMAT\",\n    25: \"SQLITE_RANGE\",\n    26: \"SQLITE_NOTADB\",\n    27: \"SQLITE_NOTICE\",\n    28: \"SQLITE_WARNING\",\n    100: \"SQLITE_ROW\",\n    101: \"SQLITE_DONE\",\n    256: \"SQLITE_OK_LOAD_PERMANENTLY\",\n    257: \"SQLITE_ERROR_MISSING_COLLSEQ\",\n    261: \"SQLITE_BUSY_RECOVERY\",\n    262: \"SQLITE_LOCKED_SHAREDCACHE\",\n    264: \"SQLITE_READONLY_RECOVERY\",\n    266: \"SQLITE_IOERR_READ\",\n    267: \"SQLITE_CORRUPT_VTAB\",\n    270: \"SQLITE_CANTOPEN_NOTEMPDIR\",\n    275: \"SQLITE_CONSTRAINT_CHECK\",\n    279: \"SQLITE_AUTH_USER\",\n    283: \"SQLITE_NOTICE_RECOVER_WAL\",\n    284: \"SQLITE_WARNING_AUTOINDEX\",\n    512: \"SQLITE_OK_SYMLINK\",\n    513: \"SQLITE_ERROR_RETRY\",\n    516: \"SQLITE_ABORT_ROLLBACK\",\n    517: \"SQLITE_BUSY_SNAPSHOT\",\n    518: \"SQLITE_LOCKED_VTAB\",\n    520: \"SQLITE_READONLY_CANTLOCK\",\n    522: \"SQLITE_IOERR_SHORT_READ\",\n    523: \"SQLITE_CORRUPT_SEQUENCE\",\n    526: \"SQLITE_CANTOPEN_ISDIR\",\n    531: \"SQLITE_CONSTRAINT_COMMITHOOK\",\n    539: \"SQLITE_NOTICE_RECOVER_ROLLBACK\",\n    769: \"SQLITE_ERROR_SNAPSHOT\",\n    773: \"SQLITE_BUSY_TIMEOUT\",\n    776: \"SQLITE_READONLY_ROLLBACK\",\n    778: \"SQLITE_IOERR_WRITE\",\n    779: \"SQLITE_CORRUPT_INDEX\",\n    782: \"SQLITE_CANTOPEN_FULLPATH\",\n    787: \"SQLITE_CONSTRAINT_FOREIGNKEY\",\n    795: \"SQLITE_NOTICE_RBU\",\n    1032: \"SQLITE_READONLY_DBMOVED\",\n    1034: \"SQLITE_IOERR_FSYNC\",\n    1038: \"SQLITE_CANTOPEN_CONVPATH\",\n    1043: \"SQLITE_CONSTRAINT_FUNCTION\",\n    1288: \"SQLITE_READONLY_CANTINIT\",\n    1290: \"SQLITE_IOERR_DIR_FSYNC\",\n    1294: \"SQLITE_CANTOPEN_DIRTYWAL\",\n    1299: \"SQLITE_CONSTRAINT_NOTNULL\",\n    1544: \"SQLITE_READONLY_DIRECTORY\",\n    1546: \"SQLITE_IOERR_TRUNCATE\",\n    1550: \"SQLITE_CANTOPEN_SYMLINK\",\n    1555: \"SQLITE_CONSTRAINT_PRIMARYKEY\",\n    1802: \"SQLITE_IOERR_FSTAT\",\n    1811: \"SQLITE_CONSTRAINT_TRIGGER\",\n    2058: \"SQLITE_IOERR_UNLOCK\",\n    2067: \"SQLITE_CONSTRAINT_UNIQUE\",\n    2314: \"SQLITE_IOERR_RDLOCK\",\n    2323: \"SQLITE_CONSTRAINT_VTAB\",\n    2570: \"SQLITE_IOERR_DELETE\",\n    2579: \"SQLITE_CONSTRAINT_ROWID\",\n    2826: \"SQLITE_IOERR_BLOCKED\",\n    2835: \"SQLITE_CONSTRAINT_PINNED\",\n    3082: \"SQLITE_IOERR_NOMEM\",\n    3091: \"SQLITE_CONSTRAINT_DATATYPE\",\n    3338: \"SQLITE_IOERR_ACCESS\",\n    3594: \"SQLITE_IOERR_CHECKRESERVEDLOCK\",\n    3850: \"SQLITE_IOERR_LOCK\",\n    4106: \"SQLITE_IOERR_CLOSE\",\n    4362: \"SQLITE_IOERR_DIR_CLOSE\",\n    4618: \"SQLITE_IOERR_SHMOPEN\",\n    4874: \"SQLITE_IOERR_SHMSIZE\",\n    5130: \"SQLITE_IOERR_SHMLOCK\",\n    5386: \"SQLITE_IOERR_SHMMAP\",\n    5642: \"SQLITE_IOERR_SEEK\",\n    5898: \"SQLITE_IOERR_DELETE_NOENT\",\n    6154: \"SQLITE_IOERR_MMAP\",\n    6410: \"SQLITE_IOERR_GETTEMPPATH\",\n    6666: \"SQLITE_IOERR_CONVPATH\",\n    6922: \"SQLITE_IOERR_VNODE\",\n    7178: \"SQLITE_IOERR_AUTH\",\n    7434: \"SQLITE_IOERR_BEGIN_ATOMIC\",\n    7690: \"SQLITE_IOERR_COMMIT_ATOMIC\",\n    7946: \"SQLITE_IOERR_ROLLBACK_ATOMIC\",\n    8202: \"SQLITE_IOERR_DATA\",\n    8458: \"SQLITE_IOERR_CORRUPTFS\",\n}\n"
  },
  {
    "path": "testing/hrana-test-server/to_proto.py",
    "content": "import base64\n\nimport proto.hrana.ws_pb2\n\ndef ws_server_msg(p, m):\n    if m[\"type\"] == \"hello_ok\":\n        p.hello_ok.SetInParent()\n    elif m[\"type\"] == \"hello_error\":\n        error(p.hello_error.error, m[\"error\"])\n    elif m[\"type\"] == \"response_ok\":\n        p.response_ok.request_id = m[\"request_id\"]\n        if m[\"response\"][\"type\"] == \"open_stream\":\n            p.response_ok.open_stream.SetInParent()\n        elif m[\"response\"][\"type\"] == \"close_stream\":\n            p.response_ok.close_stream.SetInParent()\n        elif m[\"response\"][\"type\"] == \"execute\":\n            ws_execute_resp(p.response_ok.execute, m[\"response\"])\n        elif m[\"response\"][\"type\"] == \"batch\":\n            ws_batch_resp(p.response_ok.batch, m[\"response\"])\n        elif m[\"response\"][\"type\"] == \"open_cursor\":\n            p.response_ok.open_cursor.SetInParent()\n        elif m[\"response\"][\"type\"] == \"close_cursor\":\n            p.response_ok.close_cursor.SetInParent()\n        elif m[\"response\"][\"type\"] == \"fetch_cursor\":\n            ws_fetch_cursor_resp(p.response_ok.fetch_cursor, m[\"response\"])\n        elif m[\"response\"][\"type\"] == \"sequence\":\n            p.response_ok.sequence.SetInParent()\n        elif m[\"response\"][\"type\"] == \"describe\":\n            ws_describe_resp(p.response_ok.describe, m[\"response\"])\n        elif m[\"response\"][\"type\"] == \"store_sql\":\n            p.response_ok.store_sql.SetInParent()\n        elif m[\"response\"][\"type\"] == \"close_sql\":\n            p.response_ok.close_sql.SetInParent()\n        elif m[\"response\"][\"type\"] == \"get_autocommit\":\n            ws_get_autocommit_resp(p.response_ok.get_autocommit, m[\"response\"])\n    elif m[\"type\"] == \"response_error\":\n        p.response_error.request_id = m[\"request_id\"]\n        error(p.response_error.error, m[\"error\"])\n\ndef ws_execute_resp(p, m):\n    stmt_result(p.result, m[\"result\"])\n\ndef ws_batch_resp(p, m):\n    batch_result(p.result, m[\"result\"])\n\ndef ws_fetch_cursor_resp(p, m):\n    for mm in m[\"entries\"]:\n        cursor_entry(p.entries.add(), mm)\n    p.done = m[\"done\"]\n\ndef ws_describe_resp(p, m):\n    describe_result(p.result, m[\"result\"])\n\ndef ws_get_autocommit_resp(p, m):\n    p.is_autocommit = m[\"is_autocommit\"]\n\n\n\ndef http_pipeline_resp_body(p, m):\n    if m[\"baton\"] is not None:\n        p.baton = m[\"baton\"]\n    if m.get(\"base_url\") is not None:\n        p.base_url = m[\"base_url\"]\n    for mm in m[\"results\"]:\n        http_stream_result(p.results.add(), mm)\n\ndef http_stream_result(p, m):\n    if m[\"type\"] == \"ok\":\n        http_stream_response(p.ok, m[\"response\"])\n    elif m[\"type\"] == \"error\":\n        error(p.error, m[\"error\"])\n\ndef http_stream_response(p, m):\n    if m[\"type\"] == \"close\":\n        p.close.SetInParent()\n    elif m[\"type\"] == \"execute\":\n        stmt_result(p.execute.result, m[\"result\"])\n    elif m[\"type\"] == \"batch\":\n        batch_result(p.batch.result, m[\"result\"])\n    elif m[\"type\"] == \"sequence\":\n        p.sequence.SetInParent()\n    elif m[\"type\"] == \"describe\":\n        describe_result(p.describe.result, m[\"result\"])\n    elif m[\"type\"] == \"store_sql\":\n        p.store_sql.SetInParent()\n    elif m[\"type\"] == \"close_sql\":\n        p.close_sql.SetInParent()\n    elif m[\"type\"] == \"get_autocommit\":\n        p.get_autocommit.is_autocommit = m[\"is_autocommit\"]\n\ndef http_cursor_resp_body(p, m):\n    if m[\"baton\"] is not None:\n        p.baton = m[\"baton\"]\n    if m.get(\"base_url\") is not None:\n        p.base_url = m[\"base_url\"]\n\n\n\ndef error(p, m):\n    p.message = m[\"message\"]\n    if m[\"code\"] is not None:\n        p.code = m[\"code\"]\n\ndef cursor_entry(p, m):\n    if m[\"type\"] == \"step_begin\":\n        p.step_begin.step = m[\"step\"]\n        for mm in m[\"cols\"]:\n            col(p.step_begin.cols.add(), mm)\n    elif m[\"type\"] == \"step_end\":\n        p.step_end.affected_row_count = m[\"affected_row_count\"]\n        if m[\"last_insert_rowid\"] is not None:\n            p.step_end.last_insert_rowid = int(m[\"last_insert_rowid\"])\n    elif m[\"type\"] == \"step_error\":\n        p.step_error.step = m[\"step\"]\n        error(p.step_error.error, m[\"error\"])\n    elif m[\"type\"] == \"row\":\n        row(p.row, m[\"row\"])\n    elif m[\"type\"] == \"error\":\n        error(p.error, m[\"error\"])\n    return p\n\ndef stmt_result(p, m):\n    for mm in m[\"cols\"]:\n        col(p.cols.add(), mm)\n    for mm in m[\"rows\"]:\n        row(p.rows.add(), mm)\n    p.affected_row_count = m[\"affected_row_count\"]\n    if m[\"last_insert_rowid\"] is not None:\n        p.last_insert_rowid = int(m[\"last_insert_rowid\"])\n\ndef col(p, m):\n    p.name = m[\"name\"]\n    if m[\"decltype\"] is not None:\n        p.decltype = m[\"decltype\"]\n\ndef row(p, m):\n    for mm in m:\n        value(p.values.add(), mm)\n\ndef batch_result(p, m):\n    p.SetInParent()\n    for i, mm in enumerate(m[\"step_results\"]):\n        if mm is not None:\n            stmt_result(p.step_results[i], mm)\n    for i, mm in enumerate(m[\"step_errors\"]):\n        if mm is not None:\n            error(p.step_errors[i], mm)\n\ndef describe_result(p, m):\n    for mm in m[\"params\"]:\n        describe_param(p.params.add(), mm)\n    for mm in m[\"cols\"]:\n        describe_col(p.cols.add(), mm)\n    p.is_explain = m[\"is_explain\"]\n    p.is_readonly = m[\"is_readonly\"]\n\ndef describe_param(p, m):\n    if m[\"name\"] is not None:\n        p.name = m[\"name\"]\n\ndef describe_col(p, m):\n    p.name = m[\"name\"]\n    if m[\"decltype\"] is not None:\n        p.decltype = m[\"decltype\"]\n\ndef value(p, m):\n    if m[\"type\"] == \"null\":\n        p.null.SetInParent()\n    elif m[\"type\"] == \"integer\":\n        p.integer = int(m[\"value\"])\n    elif m[\"type\"] == \"float\":\n        p.float = m[\"value\"]\n    elif m[\"type\"] == \"text\":\n        p.text = m[\"value\"]\n    elif m[\"type\"] == \"blob\":\n        p.blob = base64.b64decode(m[\"base64\"])\n"
  },
  {
    "path": "testing/test.sh",
    "content": "#!/bin/sh\n\npython3 -m venv .venv\nsource .venv/bin/activate\npip3 install aiohttp protobuf\n\nnpm run build && SERVER=test_v2 python3 testing/hrana-test-server/server_v2.py npm test --prefix packages/libsql-client\n"
  }
]