Showing preview only (424K chars total). Download the full file or copy to clipboard to get everything.
Repository: tursodatabase/libsql-client-ts
Branch: main
Commit: 697ae59038dc
Files: 144
Total size: 387.5 KB
Directory structure:
gitextract_x14w5we3/
├── .github/
│ └── workflows/
│ ├── ci.yaml
│ ├── pages.yaml
│ └── publish.yml
├── .gitignore
├── .husky/
│ ├── install.mjs
│ └── pre-commit
├── .lintstagedrc.json
├── .npmrc
├── .prettierignore
├── .prettierrc
├── CHANGELOG.md
├── CONTRIBUTING.md
├── LICENSE
├── examples/
│ ├── batch/
│ │ ├── .gitignore
│ │ ├── README.md
│ │ ├── index.mjs
│ │ └── package.json
│ ├── cloud-encryption/
│ │ ├── .gitignore
│ │ ├── README.md
│ │ ├── package.json
│ │ ├── remote.mjs
│ │ └── sync.mjs
│ ├── encryption/
│ │ ├── .gitignore
│ │ ├── README.md
│ │ ├── index.mjs
│ │ └── package.json
│ ├── local/
│ │ ├── .gitignore
│ │ ├── README.md
│ │ ├── index.mjs
│ │ └── package.json
│ ├── memory/
│ │ ├── .gitignore
│ │ ├── README.md
│ │ ├── index.mjs
│ │ └── package.json
│ ├── ollama/
│ │ ├── .gitignore
│ │ ├── README.md
│ │ ├── index.mjs
│ │ └── package.json
│ ├── read-your-writes/
│ │ ├── package.json
│ │ └── read_your_writes.js
│ ├── remote/
│ │ ├── .gitignore
│ │ ├── README.md
│ │ ├── index.mjs
│ │ └── package.json
│ ├── sync/
│ │ ├── .gitignore
│ │ ├── README.md
│ │ ├── index.mjs
│ │ └── package.json
│ ├── transactions/
│ │ ├── .gitignore
│ │ ├── README.md
│ │ ├── index.mjs
│ │ └── package.json
│ └── vector/
│ ├── .gitignore
│ ├── README.md
│ ├── index.mjs
│ └── package.json
├── package.json
├── packages/
│ ├── libsql-client/
│ │ ├── README.md
│ │ ├── examples/
│ │ │ ├── example.js
│ │ │ ├── package.json
│ │ │ ├── shell.js
│ │ │ ├── sync.js
│ │ │ ├── sync_offline.js
│ │ │ └── sync_vector.js
│ │ ├── jest.config.js
│ │ ├── package-cjs.json
│ │ ├── package.json
│ │ ├── smoke_test/
│ │ │ ├── vercel/
│ │ │ │ ├── .gitignore
│ │ │ │ ├── app/
│ │ │ │ │ ├── .gitignore
│ │ │ │ │ ├── api/
│ │ │ │ │ │ └── function.ts
│ │ │ │ │ └── public/
│ │ │ │ │ └── index.html
│ │ │ │ ├── package.json
│ │ │ │ ├── test.js
│ │ │ │ └── tsconfig.json
│ │ │ └── workers/
│ │ │ ├── .gitignore
│ │ │ ├── package.json
│ │ │ ├── test.js
│ │ │ ├── worker.js
│ │ │ └── wrangler.toml
│ │ ├── src/
│ │ │ ├── __tests__/
│ │ │ │ ├── client.test.ts
│ │ │ │ ├── config.test.ts
│ │ │ │ ├── helpers.ts
│ │ │ │ ├── mocks/
│ │ │ │ │ ├── handlers.ts
│ │ │ │ │ └── node.ts
│ │ │ │ └── uri.test.ts
│ │ │ ├── hrana.ts
│ │ │ ├── http.ts
│ │ │ ├── node.ts
│ │ │ ├── sql_cache.ts
│ │ │ ├── sqlite3.ts
│ │ │ ├── web.ts
│ │ │ └── ws.ts
│ │ ├── tsconfig.base.json
│ │ ├── tsconfig.build-cjs.json
│ │ ├── tsconfig.build-esm.json
│ │ ├── tsconfig.json
│ │ └── typedoc.json
│ ├── libsql-client-wasm/
│ │ ├── LICENSE
│ │ ├── examples/
│ │ │ ├── browser/
│ │ │ │ ├── README.md
│ │ │ │ ├── index.html
│ │ │ │ ├── index.js
│ │ │ │ └── package.json
│ │ │ └── node/
│ │ │ ├── index.js
│ │ │ └── package.json
│ │ ├── jest.config.js
│ │ ├── package-cjs.json
│ │ ├── package.json
│ │ ├── src/
│ │ │ └── wasm.ts
│ │ ├── tsconfig.base.json
│ │ ├── tsconfig.build-esm.json
│ │ ├── tsconfig.json
│ │ └── typedoc.json
│ └── libsql-core/
│ ├── jest.config.js
│ ├── package-cjs.json
│ ├── package.json
│ ├── src/
│ │ ├── api.ts
│ │ ├── config.ts
│ │ ├── uri.ts
│ │ └── util.ts
│ ├── tsconfig.base.json
│ ├── tsconfig.build-cjs.json
│ ├── tsconfig.build-esm.json
│ ├── tsconfig.json
│ └── typedoc.json
└── testing/
├── hrana-test-server/
│ ├── .gitignore
│ ├── LICENSE
│ ├── README.md
│ ├── c3.py
│ ├── from_proto.py
│ ├── gen_sqlite3_error_map.py
│ ├── proto/
│ │ ├── generate.sh
│ │ ├── hrana/
│ │ │ ├── http_pb2.py
│ │ │ └── ws_pb2.py
│ │ ├── hrana.http.proto
│ │ ├── hrana.proto
│ │ ├── hrana.ws.proto
│ │ └── hrana_pb2.py
│ ├── requirements.txt
│ ├── server_v1.py
│ ├── server_v2.py
│ ├── server_v3.py
│ ├── sqlite3_error_map.py
│ └── to_proto.py
└── test.sh
================================================
FILE CONTENTS
================================================
================================================
FILE: .github/workflows/ci.yaml
================================================
name: "CI"
on:
push:
branches: ["main"]
pull_request:
jobs:
"typecheck":
name: "Typecheck"
runs-on: ubuntu-latest
timeout-minutes: 2
steps:
- name: "Checkout this repo"
uses: actions/checkout@v4
- name: "Setup Node.js"
uses: actions/setup-node@v4
with:
node-version: "18.x"
- name: "Install node dependencies and build"
run: "npm ci && npm run build"
- name: "Typecheck"
run: "npm run typecheck"
"format-check":
name: "Check formatting"
runs-on: ubuntu-latest
timeout-minutes: 2
steps:
- name: "Checkout this repo"
uses: actions/checkout@v4
- name: "Setup Node.js"
uses: actions/setup-node@v4
with:
node-version: "18.x"
- name: "Install node dependencies"
run: "npm ci"
- name: "Check formatting"
run: "npm run format:check"
"test-against-sqld":
name: "Tests against sqld"
runs-on: ubuntu-latest
timeout-minutes: 2
defaults:
run:
working-directory: ./packages/libsql-client
env: { "NODE_OPTIONS": "--trace-warnings" }
steps:
- name: "Checkout this repo"
uses: actions/checkout@v4
- name: "Setup Node.js"
uses: actions/setup-node@v4
with:
node-version: "18.x"
- name: "Build core"
run: "npm ci && npm run build"
working-directory: ./packages/libsql-core
- name: "Install npm dependencies"
run: "npm ci"
- name: "Build"
run: "npm run build"
- name: Run Docker container in the background
run: docker run -d -p 8080:8080 -e SQLD_NODE=primary ghcr.io/tursodatabase/libsql-server:latest
- name: Verify container is running
run: docker ps
- name: "Test against sqld"
run: "npm test"
env: { "URL": "http://localhost:8080", "SERVER": "sqld" }
"wasm-test":
name: "Build and test Wasm on Node.js"
runs-on: ubuntu-latest
timeout-minutes: 2
defaults:
run:
working-directory: ./packages/libsql-client-wasm
env: { "NODE_OPTIONS": "--trace-warnings" }
steps:
- name: "Checkout this repo"
uses: actions/checkout@v4
- name: "Setup Node.js"
uses: actions/setup-node@v4
with:
node-version: "18.x"
cache-dependency-path: "packages/libsql-client-wasm"
- name: "Build core"
run: "npm ci && npm run build"
working-directory: ./packages/libsql-core
- name: "Install npm dependencies"
run: "npm ci"
- name: "Build"
run: "npm run build"
- name: "Test example"
run: "cd examples/node && npm i && node index.js"
env: { "URL": "file:///tmp/example.db" }
"node-test":
name: "Build and test on Node.js"
runs-on: ubuntu-latest
timeout-minutes: 2
defaults:
run:
working-directory: ./packages/libsql-client
env: { "NODE_OPTIONS": "--trace-warnings" }
steps:
- name: "Checkout this repo"
uses: actions/checkout@v4
- name: "Setup Node.js"
uses: actions/setup-node@v4
with:
node-version: "18.x"
- name: "Build core"
run: "npm ci && npm run build"
working-directory: ./packages/libsql-core
- name: "Install npm dependencies"
run: "npm ci"
- name: "Setup Python"
uses: actions/setup-python@v4
with:
python-version: "3.10"
- name: "Install pip dependencies"
run: "pip install -r ../../testing/hrana-test-server/requirements.txt"
- name: "Build"
run: "npm run build"
- name: "Test Hrana 1 over WebSocket"
run: "python ../../testing/hrana-test-server/server_v1.py npm test"
env: { "URL": "ws://localhost:8080", "SERVER": "test_v1" }
- name: "Test Hrana 2 over WebSocket"
run: "python ../../testing/hrana-test-server/server_v2.py npm test"
env: { "URL": "ws://localhost:8080", "SERVER": "test_v2" }
- name: "Test Hrana 2 over HTTP"
run: "python ../../testing/hrana-test-server/server_v2.py npm test"
env: { "URL": "http://localhost:8080", "SERVER": "test_v2" }
# - name: "Test Hrana 3 over WebSocket"
# run: "python ../../testing/hrana-test-server/server_v3.py npm test"
# env: {"URL": "ws://localhost:8080", "SERVER": "test_v3"}
# - name: "Test Hrana 3 over HTTP"
# run: "python ../../testing/hrana-test-server/server_v3.py npm test"
# env: {"URL": "http://localhost:8080", "SERVER": "test_v3"}
- name: "Test local file"
run: "npm test"
env: { "URL": "file:///tmp/test.db" }
- name: "Test example"
run: "cd examples && npm i && node example.js"
env: { "URL": "file:///tmp/example.db" }
"workers-test":
name: "Build and test with Cloudflare Workers"
if: false
runs-on: ubuntu-latest
timeout-minutes: 2
defaults:
run:
working-directory: ./packages/libsql-client
env:
"CLOUDFLARE_API_TOKEN": "${{ secrets.CLOUDFLARE_API_TOKEN }}"
"CLOUDFLARE_ACCOUNT_ID": "${{ secrets.CLOUDFLARE_ACCOUNT_ID }}"
steps:
- name: "Checkout this repo"
uses: actions/checkout@v4
- name: "Setup Node.js"
uses: actions/setup-node@v4
with:
node-version: "lts/Hydrogen"
- name: "Build core"
run: "npm ci && npm run build"
working-directory: ./packages/libsql-core
- name: "Install npm dependencies"
run: "npm ci"
- name: "Setup Python"
uses: actions/setup-python@v4
with:
python-version: "3.10"
- name: "Install pip dependencies"
run: "pip install -r ../../testing/hrana-test-server/requirements.txt"
- name: "Build"
run: "npm run build"
- name: "Install npm dependencies of the Workers test"
run: "cd smoke_test/workers && npm link ../.."
- name: "Local test with Hrana 1 over WebSocket"
run: "cd smoke_test/workers && python ../../../../testing/hrana-test-server/server_v1.py node --dns-result-order=ipv4first test.js"
env: { "LOCAL": "1", "URL": "ws://localhost:8080" }
- name: "Local test with Hrana 2 over WebSocket"
run: "cd smoke_test/workers && python ../../../../testing/hrana-test-server/server_v2.py node --dns-result-order=ipv4first test.js"
env: { "LOCAL": "1", "URL": "ws://localhost:8080" }
- name: "Local test with Hrana 2 over HTTP"
run: "cd smoke_test/workers && python ../../../../testing/hrana-test-server/server_v2.py node --dns-result-order=ipv4first test.js"
env: { "LOCAL": "1", "URL": "http://localhost:8080" }
# - name: "Local test with Hrana 3 over WebSocket"
# run: "cd smoke_test/workers && python ../../../../testing/hrana-test-server/server_v3.py node --dns-result-order=ipv4first test.js"
# env: {"LOCAL": "1", "URL": "ws://localhost:8080"}
# - name: "Local test with Hrana 3 over HTTP"
# run: "cd smoke_test/workers && python ../../../../testing/hrana-test-server/server_v3.py node --dns-result-order=ipv4first test.js"
# env: {"LOCAL": "1", "URL": "http://localhost:8080"}
# - name: "Non-local test with Hrana 1 over WebSocket"
# run: "cd smoke_test/workers && python ../../../../testing/hrana-test-server/server_v1.py node test.js"
# env: {"LOCAL": "0", "URL": "ws://localhost:8080"}
# - name: "Non-local test with Hrana 2 over WebSocket"
# run: "cd smoke_test/workers && python ../../../../testing/hrana-test-server/server_v2.py node test.js"
# env: {"LOCAL": "0", "URL": "ws://localhost:8080"}
# - name: "Non-local test with Hrana 2 over HTTP"
# run: "cd smoke_test/workers && python ../../../../testing/hrana-test-server/server_v2.py node test.js"
# env: {"LOCAL": "0", "URL": "http://localhost:8080"}
# - name: "Non-local test with Hrana 3 over WebSocket"
# run: "cd smoke_test/workers && python ../../../../testing/hrana-test-server/server_v3.py node test.js"
# env: {"LOCAL": "0", "URL": "ws://localhost:8080"}
# - name: "Non-local test with Hrana 3 over HTTP"
# run: "cd smoke_test/workers && python ../../../../testing/hrana-test-server/server_v3.py node test.js"
# env: {"LOCAL": "0", "URL": "http://localhost:8080"}
# "vercel-test":
# name: "Build and test with Vercel Edge Functions"
# runs-on: ubuntu-latest
# env:
# VERCEL_TOKEN: "${{ secrets.VERCEL_TOKEN }}"
# VERCEL_PROJECT_NAME: "smoke-test"
# steps:
# - name: "Checkout this repo"
# uses: actions/checkout@v4
# - name: "Setup Node.js"
# uses: actions/setup-node@v4
# with:
# node-version: "lts/Hydrogen"
# cache: "npm"
# - name: "Install npm dependencies"
# run: "npm ci"
# - name: "Checkout hrana-test-server"
# uses: actions/checkout@v4
# with:
# repository: "libsql/hrana-test-server"
# path: "hrana-test-server"
# - name: "Setup Python"
# uses: actions/setup-python@v4
# with:
# python-version: "3.10"
# cache: "pip"
# - name: "Install pip dependencies"
# run: "pip install -r hrana-test-server/requirements.txt"
# - name: "Build"
# run: "npm run build"
# - name: "Install npm dependencies of the Vercel test"
# run: "cd smoke_test/vercel && npm install"
# - name: "Test with Hrana 1 over WebSocket"
# run: "cd smoke_test/vercel && python ../../hrana-test-server/server_v1.py node test.js"
# env: {"URL": "ws://localhost:8080"}
# - name: "Test with Hrana 2 over WebSocket"
# run: "cd smoke_test/vercel && python ../../hrana-test-server/server_v2.py node test.js"
# env: {"URL": "ws://localhost:8080"}
# - name: "Test with Hrana 2 over HTTP"
# run: "cd smoke_test/vercel && python ../../hrana-test-server/server_v2.py node test.js"
# env: {"URL": "http://localhost:8080"}
# - name: "Test with Hrana 3 over WebSocket"
# run: "cd smoke_test/vercel && python ../../hrana-test-server/server_v3.py node test.js"
# env: {"URL": "ws://localhost:8080"}
# - name: "Test with Hrana 3 over HTTP"
# run: "cd smoke_test/vercel && python ../../hrana-test-server/server_v3.py node test.js"
# env: {"URL": "http://localhost:8080"}
================================================
FILE: .github/workflows/pages.yaml
================================================
name: "GitHub Pages"
on:
push:
branches: ["main"]
jobs:
"build":
name: "Build the docs"
runs-on: ubuntu-latest
defaults:
run:
working-directory: ./packages/libsql-client
steps:
- name: "Checkout this repo"
uses: actions/checkout@v4
- name: "Setup Node.js"
uses: actions/setup-node@v4
with:
node-version: "${{ matrix.node-version }}"
cache: "npm"
- name: "Build core"
run: "npm ci && npm run build"
working-directory: ./packages/libsql-core
- name: "Install npm dependencies"
run: "npm ci"
- name: "Build"
run: "npm run typedoc"
- name: "Upload GitHub Pages artifact"
uses: actions/upload-pages-artifact@v3
id: deployment
with:
path: "./packages/libsql-client/docs"
"deploy":
name: "Deploy the docs to GitHub Pages"
needs: "build"
permissions:
pages: write
id-token: write
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
runs-on: ubuntu-latest
steps:
- name: "Deploy to GitHub Pages"
id: deployment
uses: actions/deploy-pages@v4
================================================
FILE: .github/workflows/publish.yml
================================================
name: publish
permissions:
contents: read
id-token: write
on:
push:
tags:
- v*
jobs:
publish-to-npm:
name: "Publish new version to NPM"
runs-on: ubuntu-latest
timeout-minutes: 5
env:
NODE_OPTIONS: "--trace-warnings"
steps:
- name: "Checkout this repo"
uses: actions/checkout@v4
- name: "Setup Node.js"
uses: actions/setup-node@v4
with:
node-version: "20.x"
- name: "Update npm"
run: npm install -g npm@11
- name: "Build core"
run: "npm ci && npm run build"
working-directory: ./packages/libsql-core
- name: "Publish core (pre-release)"
if: contains(github.ref, '-pre')
run: npm publish --tag next --provenance --access public
working-directory: ./packages/libsql-core
- name: "Publish core (latest)"
if: "!contains(github.ref, '-pre')"
run: npm publish --provenance --access public
working-directory: ./packages/libsql-core
- name: "Install npm dependencies (client)"
run: "npm ci"
working-directory: ./packages/libsql-client
- name: "Publish client (pre-release)"
if: contains(github.ref, '-pre')
run: npm publish --tag next --provenance --access public
working-directory: ./packages/libsql-client
- name: "Publish client (latest)"
if: "!contains(github.ref, '-pre')"
run: npm publish --provenance --access public
working-directory: ./packages/libsql-client
- name: "Install npm dependencies (client wasm)"
run: "npm ci"
working-directory: ./packages/libsql-client-wasm
- name: "Publish client-wasm (pre-release)"
if: contains(github.ref, '-pre')
run: npm publish --tag next --provenance --access public
working-directory: ./packages/libsql-client-wasm
- name: "Publish client-wasm (latest)"
if: "!contains(github.ref, '-pre')"
run: npm publish --provenance --access public
working-directory: ./packages/libsql-client-wasm
================================================
FILE: .gitignore
================================================
node_modules
/packages/*/lib-esm
/packages/*/lib-cjs
/docs
*.tsbuildinfo
Session.vim
packages/libsql-client/hrana-test-server
================================================
FILE: .husky/install.mjs
================================================
// See https://typicode.github.io/husky/how-to.html#ci-server-and-docker
// Skip Husky install in production and CI
if (process.env.NODE_ENV === "production" || process.env.CI === "true") {
process.exit(0);
}
const husky = (await import("husky")).default;
console.log(husky());
================================================
FILE: .husky/pre-commit
================================================
lint-staged
================================================
FILE: .lintstagedrc.json
================================================
{ "*.{js,ts,json,md,yaml,yml}": "prettier --write" }
================================================
FILE: .npmrc
================================================
//registry.npmjs.org/:_authToken=${NPM_TOKEN}
================================================
FILE: .prettierignore
================================================
lib-cjs
lib-esm
================================================
FILE: .prettierrc
================================================
{
"tabWidth": 4
}
================================================
FILE: CHANGELOG.md
================================================
# Changelog
## 0.15.10 -- 2025-07-16
- Bump to latest `libsql` package.
## 0.15.9 -- 2025-06-09
- Bump to latest `libsql` package.
## 0.15.7 -- 2025-05-20
- Bump to latest `libsql` package.
## 0.15.6 -- 2025-05-14
- Bump to latest `libsql` package.
## 0.15.5 -- 2025-05-11
- Bump to latest `libsql` package.
## 0.15.4 -- 2025-04-15
- Bump to latest `libsql` package.
## 0.15.3 -- 2025-04-11
- Bump to latest `libsql` package.
## 0.15.2 -- 2025-04-01
- Bump to latest `libsql` package.
## 0.15.1 -- 2025-03-24
- Bump to latest `libsql` package.
## 0.15.0 -- 2025-03-17
- Bump to latest `libsql` package.
## 0.15.0-pre.3 -- 2025-03-11
- Fix Bun complaint about duplicate "prepare" key in `package.json`
## 0.15.0-pre.2 -- 2024-02-11
- Bump to latest `libsql` package.
## 0.15.0-pre.1 -- 2024-11-15
- Initial support for offline writes.
## 0.12.0 -- 2024-09-16
- 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)
## 0.11.0 -- 2024-09-13
- 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)
## 0.10.0 -- 2024-08-26
- 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.
## 0.8.1 -- 2024-08-03
- 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)).
## 0.8.0 -- 2024-07-30
- No changes from 0.8.0-pre.1.
## 0.8.0-pre.1 -- 2024-07-18
- Bump hrana client to 0.6.2.
- 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)
- Fix bug in wasm experimental client which appears when transaction are used in local mode (https://github.com/tursodatabase/libsql-client-ts/pull/231)
- Add `execute(sql, args)` overload to make the API similar to other SQLite SDKs
## 0.7.0 -- 2024-06-25
- Add configurable concurrency limit for parallel query execution
(defaults to 20) to address socket hangup errors.
## 0.6.2 -- 2024-06-01
- Fix compatibility issue with libSQL server versions that don't have migrations endpoint.
## 0.6.1 -- 2024-05-30
- Add an option to `batch()` to wait for schema changes to finish when using shared schema.
## 0.6.0 -- 2024-04-28
- Bump hrana client to 0.6.0, which uses native Node fetch(). Note that
`@libsql/client` now requires Node 18 or later.
## 0.5.6 -- 2024-03-12
- Bump `libsql` package dependency to 0.3.10 that adds `wasm32` as
supported CPU, which is needed for StackBlitz compatibility.
## 0.5.5 -- 2024-03-11
- Bump `@libsql/libsql-wasm-experimental"` dependency to 0.0.2, which
fixes a broken sqlite3_get_autocommit() export.
## 0.5.4 -- 2024-03-11
- Bump `libsql` dependency to 0.3.9, which fixes symbol not found errors on Alpine.
## 0.5.3 -- 2024-03-06
- Add `syncInterval` config option to enable periodic sync.
- Bump `libsql` dependency to 0.3.7, which switches default encryption cipher to aes256cbs.
## 0.5.2 -- 2024-02-24
- Disable SQL statemen tracing in Wasm.
## 0.5.1 -- 2024-02-19
- Update `libsql` package to 0.3.2, add `encryptionCipher` option, and switch default cipher to SQLCipher.
## 0.5.0 -- 2024-02-15
- Add a `encryptionKey` config option, which enables encryption at rest for local database files.
## 0.4.0 -- 2024-01-26
- Update hrana-client package to 0.5.6.
- Add a `@libsql/client-wasm` package.
- Fix Bun on Linux/arm64.
## 0.3.6 -- 2023-10-20
- Fix import problems on Cloudflare Workers.
- Add `rawCode` property to errors for local databases.
- Update the `libsql` package to version 0.1.28.
## 0.3.5 -- 2023-09-25
- Performance improvements for local database access by reusing connection in `Client`.
- Embedded replica support.
- Column introspection support via ResultSet.columnTypes property.
## 0.3.4 -- 2023-09-11
- Switch to Hrana 2 by default to let Hrana 3 cook some more.
## 0.3.3 -- 2023-09-11
- Updated `@libsql/hrana-client` to version 0.5.1, which has Bun support.
- Switched to `libsql` package as a replacement for `better-sqlite3`.
## 0.3.2 -- 2023-07-29
- Updated `@libsql/hrana-client` to version 0.5.0, which implements Hrana 3
- Dropped workarounds for broken WebSocket support in Miniflare 2
- Added a `@libsql/client/node` import for explicit Node.js-specific module
## 0.3.1 -- 2023-07-20
- Added `ResultSet.toJSON()` to provide better JSON serialization. ([#61](https://github.com/libsql/libsql-client-ts/pull/61))
- Added conditional exports to `package.json` that redirect the default import of `@libsql/client` to `@libsql/client/web` on a few supported edge platforms. ([#65](https://github.com/libsql/libsql-client-ts/pull/65))
- Added `Config.fetch` to support overriding the `fetch` implementation from `@libsql/isomorphic-fetch`. ([#66](https://github.com/libsql/libsql-client-ts/pull/66))
## 0.3.0 -- 2023-07-07
- **Changed the order of parameters to `batch()`**, so that the transaction mode is passed as the second parameter. ([#57](https://github.com/libsql/libsql-client-ts/pull/57))
- **Changed the default transaction mode to `"deferred"`**. ([#57](https://github.com/libsql/libsql-client-ts/pull/57))
- Added `Client.protocol` property to find out which protocol the client uses ([#54](https://github.com/libsql/libsql-client-ts/pull/54)).
## 0.2.2 -- 2023-06-22
- Added `intMode` field to the `Config`, which chooses whether SQLite integers are represented as numbers, bigints or strings in JavaScript ([#51](https://github.com/libsql/libsql-client-ts/pull/51)).
## 0.2.1 -- 2023-06-13
- Added `TransactionMode` argument to `batch()` and `transaction()` ([#46](https://github.com/libsql/libsql-client-ts/pull/46))
- Added `Client.executeMultiple()` and `Transaction.executeMultiple()` ([#49](https://github.com/libsql/libsql-client-ts/pull/49))
- Added `Transaction.batch()` ([#49](https://github.com/libsql/libsql-client-ts/pull/49))
- **Changed the default transaction mode** from `BEGIN DEFERRED` to `BEGIN IMMEDIATE`
## 0.2.0 -- 2023-06-07
- **Added support for interactive transactions over HTTP** by using `@libsql/hrana-client` version 0.4 ([#44](https://github.com/libsql/libsql-client-ts/pull/44))
- Added `?tls=0` query parameter to turn off TLS for `libsql:` URLs
- Changed the `libsql:` URL to use HTTP instead of WebSockets
- Changed the `Value` type to include `bigint` (so that we can add support for reading integers as bigints in the future, without breaking compatibility)
- Removed the `./hrana` import, added `./ws` to import the WebSocket-only client
================================================
FILE: CONTRIBUTING.md
================================================
# Prerequisites
- Have `npm` installed and in PATH
- Have `git` installed and in PATH
# Setting up the repository for contribution
- Clone this repository: `git clone https://github.com/tursodatabase/libsql-client-ts`
- Change the current working directory to the cloned repository: `cd libsql-client-ts`
- Install dependencies: `npm i`
- Change the current working directory to `libsql-core`'s workspace: `cd packages/libsql-core`
- Built the core package: `npm run build`
- Go back to the root directory to start making changes: `cd ../..`
================================================
FILE: LICENSE
================================================
MIT License
Copyright (c) 2023 libSQL
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
================================================
FILE: examples/batch/.gitignore
================================================
local.db
================================================
FILE: examples/batch/README.md
================================================
# Batch
This example demonstrates how to use libSQL to execute a batch of SQL statements.
## Install Dependencies
```bash
npm i
```
## Running
Execute the example:
```bash
node index.mjs
```
This will setup a SQLite database, execute a batch of SQL statements, and then query the results.
================================================
FILE: examples/batch/index.mjs
================================================
import { createClient } from "@libsql/client";
const client = createClient({
url: "file:local.db",
});
await client.batch(
[
"CREATE TABLE IF NOT EXISTS users (email TEXT)",
{
sql: "INSERT INTO users VALUES (?)",
args: ["first@example.com"],
},
{
sql: "INSERT INTO users VALUES (?)",
args: ["second@example.com"],
},
{
sql: "INSERT INTO users VALUES (?)",
args: ["third@example.com"],
},
],
"write",
);
const result = await client.execute("SELECT * FROM users");
console.log("Users:", result.rows);
================================================
FILE: examples/batch/package.json
================================================
{
"name": "batch",
"version": "1.0.0",
"main": "index.mjs",
"author": "Giovanni Benussi",
"license": "MIT",
"dependencies": {
"@libsql/client": "^0.14.0"
}
}
================================================
FILE: examples/cloud-encryption/.gitignore
================================================
local.db
================================================
FILE: examples/cloud-encryption/README.md
================================================
# Cloud Encryption
These examples demonstrates how to use Turso Cloud encryption.
Visit the documentation here - [Cloud Encryption](https://docs.turso.tech/cloud/encryption)
## Install Dependencies
```bash
npm i
```
## Running
Execute the example which operates over remotely encrypted database:
```bash
node remote.mjs
```
Cloud encryption also supports sync:
```bash
node sync.mjs
```
================================================
FILE: examples/cloud-encryption/package.json
================================================
{
"name": "batch",
"version": "1.0.0",
"main": "remote.mjs",
"author": "Turso Authors",
"license": "MIT",
"dependencies": {
"@libsql/client": "^0.16.0"
}
}
================================================
FILE: examples/cloud-encryption/remote.mjs
================================================
import { createClient } from "@libsql/client";
const client = createClient({
url: process.env.TURSO_DATABASE_URL,
authToken: process.env.TURSO_AUTH_TOKEN,
remoteEncryptionKey: process.env.TURSO_REMOTE_ENCRYPTION_KEY,
});
await client.batch(
[
"CREATE TABLE IF NOT EXISTS users (email TEXT)",
"INSERT INTO users VALUES ('first@example.com')",
"INSERT INTO users VALUES ('second@example.com')",
"INSERT INTO users VALUES ('third@example.com')",
],
"write",
);
const result = await client.execute("SELECT * FROM users");
console.log("Users:", result.rows);
================================================
FILE: examples/cloud-encryption/sync.mjs
================================================
import { createClient } from "@libsql/client";
const client = createClient({
url: "file:local.db",
syncUrl: process.env.TURSO_DATABASE_URL,
authToken: process.env.TURSO_AUTH_TOKEN,
remoteEncryptionKey: process.env.TURSO_REMOTE_ENCRYPTION_KEY,
});
await client.batch(
[
"CREATE TABLE IF NOT EXISTS users (email TEXT)",
"INSERT INTO users VALUES ('first@example.com')",
"INSERT INTO users VALUES ('second@example.com')",
"INSERT INTO users VALUES ('third@example.com')",
],
"write",
);
const result = await client.execute("SELECT * FROM users");
console.log("Users:", result.rows);
================================================
FILE: examples/encryption/.gitignore
================================================
encrypted.db
================================================
FILE: examples/encryption/README.md
================================================
# Encryption
This example demonstrates how to create and use an encrypted SQLite database with libSQL.
## Install Dependencies
```bash
npm i
```
## Running
Execute the example:
```bash
node index.mjs
```
This will setup an encrypted SQLite database, execute a batch of SQL statements, and then query the results.
================================================
FILE: examples/encryption/index.mjs
================================================
import { createClient } from "@libsql/client";
// You should set the ENCRYPTION_KEY in a environment variable
// For demo purposes, we're using a fixed key
const encryptionKey = "my-safe-encryption-key";
const client = createClient({
url: "file:encrypted.db",
encryptionKey,
});
await client.batch(
[
"CREATE TABLE IF NOT EXISTS users (email TEXT)",
"INSERT INTO users VALUES ('first@example.com')",
"INSERT INTO users VALUES ('second@example.com')",
"INSERT INTO users VALUES ('third@example.com')",
],
"write",
);
const result = await client.execute("SELECT * FROM users");
console.log("Users:", result.rows);
================================================
FILE: examples/encryption/package.json
================================================
{
"name": "batch",
"version": "1.0.0",
"main": "index.mjs",
"author": "Giovanni Benussi",
"license": "MIT",
"dependencies": {
"@libsql/client": "^0.14.0"
}
}
================================================
FILE: examples/local/.gitignore
================================================
local.db
================================================
FILE: examples/local/README.md
================================================
# Local
This example demonstrates how to use libSQL with a local SQLite file.
## Install Dependencies
```bash
npm i
```
## Running
Execute the example:
```bash
node index.mjs
```
This will setup a local SQLite database, insert some data, and then query the results.
================================================
FILE: examples/local/index.mjs
================================================
import { createClient } from "@libsql/client";
const client = createClient({
url: "file:local.db",
});
await client.batch(
[
"CREATE TABLE IF NOT EXISTS users (email TEXT)",
"INSERT INTO users VALUES ('first@example.com')",
"INSERT INTO users VALUES ('second@example.com')",
"INSERT INTO users VALUES ('third@example.com')",
],
"write",
);
const result = await client.execute("SELECT * FROM users");
console.log("Users:", result.rows);
================================================
FILE: examples/local/package.json
================================================
{
"name": "batch",
"version": "1.0.0",
"main": "index.mjs",
"author": "Giovanni Benussi",
"license": "MIT",
"dependencies": {
"@libsql/client": "^0.14.0"
}
}
================================================
FILE: examples/memory/.gitignore
================================================
local.db
================================================
FILE: examples/memory/README.md
================================================
# Memory
This example demonstrates how to use libsql with an in-memory SQLite database.
## Install Dependencies
```bash
npm i
```
## Running
Execute the example:
```bash
node index.mjs
```
This will create an in-memory SQLite database, insert some data, and then query the results.
================================================
FILE: examples/memory/index.mjs
================================================
import { createClient } from "@libsql/client";
const client = createClient({
url: ":memory:",
});
await client.batch(
[
"CREATE TABLE users (email TEXT)",
"INSERT INTO users VALUES ('first@example.com')",
"INSERT INTO users VALUES ('second@example.com')",
"INSERT INTO users VALUES ('third@example.com')",
],
"write",
);
const result = await client.execute("SELECT * FROM users");
console.log("Users:", result.rows);
================================================
FILE: examples/memory/package.json
================================================
{
"name": "batch",
"version": "1.0.0",
"main": "index.mjs",
"author": "Giovanni Benussi",
"license": "MIT",
"dependencies": {
"@libsql/client": "^0.14.0"
}
}
================================================
FILE: examples/ollama/.gitignore
================================================
local.db
================================================
FILE: examples/ollama/README.md
================================================
# Ollama + Vector Search Example
This example demonstrates how to use libSQL vector search with a local database and Ollama.
## Install Dependencies
```bash
npm i
```
## Install Ollama
[Download Ollama](https://ollama.com/download) and install it.
## Running
Make sure Ollama is running with the model `mistral`:
```bash
ollama run mistral
```
Execute the example:
```bash
node index.mjs
```
This 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.
================================================
FILE: examples/ollama/index.mjs
================================================
import { createClient } from "@libsql/client";
import ollama from "ollama";
const client = createClient({
url: "file:local.db",
});
await client.batch(
[
"CREATE TABLE IF NOT EXISTS movies (id INTEGER PRIMARY KEY, title TEXT NOT NULL, description TEXT NOT NULL, embedding F32_BLOB(4096))",
"CREATE INDEX IF NOT EXISTS movies_embedding_idx ON movies(libsql_vector_idx(embedding))",
],
"write",
);
async function getEmbedding(prompt) {
const response = await ollama.embeddings({
model: "mistral",
prompt,
});
return response.embedding;
}
async function insertMovie(id, title, description) {
const embedding = await getEmbedding(description);
await client.execute({
sql: `INSERT OR REPLACE INTO movies (id, title, description, embedding) VALUES (?, ?, ?, vector(?))`,
args: [id, title, description, JSON.stringify(embedding)],
});
}
async function insertMovieIfNotExists(id, title, description) {
const existing = await client.execute({
sql: "SELECT id FROM movies WHERE id = ?",
args: [id],
});
if (existing.rows.length === 0) {
await insertMovie(id, title, description);
console.log(`Inserted: ${title} (ID: ${id})`);
} else {
console.log(`Movie already exists: ${title} (ID: ${id})`);
}
}
async function findSimilarMovies(description, limit = 3) {
const queryEmbedding = await getEmbedding(description);
const results = await client.execute({
sql: `
WITH vector_scores AS (
SELECT DISTINCT
id,
title,
description,
1 - vector_distance_cos(embedding, vector32(?)) AS similarity
FROM movies
ORDER BY similarity DESC
LIMIT ?
)
SELECT id, title, description, similarity FROM vector_scores
`,
args: [JSON.stringify(queryEmbedding), limit],
});
return results.rows;
}
try {
const sampleMovies = [
{
id: 1,
title: "Inception",
description:
"A thief who enters the dreams of others to steal secrets from their subconscious.",
},
{
id: 2,
title: "The Matrix",
description:
"A computer programmer discovers that reality as he knows it is a simulation created by machines.",
},
{
id: 3,
title: "Interstellar",
description:
"Astronauts travel through a wormhole in search of a new habitable planet for humanity.",
},
];
for (const movie of sampleMovies) {
await insertMovieIfNotExists(movie.id, movie.title, movie.description);
}
const query =
"A sci-fi movie about virtual reality and artificial intelligence";
console.log("\nSearching for movies similar to:", query);
const similarMovies = await findSimilarMovies(query);
console.log("\nSimilar movies found:");
similarMovies.forEach((movie) => {
console.log(`\nTitle: ${movie.title}`);
console.log(`Description: ${movie.description}`);
console.log(`Similarity: ${movie.similarity.toFixed(4)}`);
});
} catch (error) {
console.error("Error:", error);
}
================================================
FILE: examples/ollama/package.json
================================================
{
"name": "ollama",
"private": true,
"main": "index.mjs",
"dependencies": {
"@libsql/client": "^0.14.0",
"ollama": "^0.5.11"
}
}
================================================
FILE: examples/read-your-writes/package.json
================================================
{
"name": "read-your-writes",
"version": "1.0.0",
"main": "index.mjs",
"author": "Levy Albuquerque",
"license": "MIT",
"dependencies": {
"@libsql/client": "^0.14.0"
}
}
================================================
FILE: examples/read-your-writes/read_your_writes.js
================================================
import { createClient } from "@libsql/client";
const client = createClient({
url: "file:local.db",
syncUrl: process.env.TURSO_DATABASE_URL,
authToken: process.env.TURSO_AUTH_TOKEN,
readYourWrites: false,
});
await client.execute("DROP TABLE users");
await client.execute("CREATE TABLE IF NOT EXISTS users (email TEXT)");
await client.sync();
await client.execute("INSERT INTO users VALUES ('first@example.com')");
await client.execute("INSERT INTO users VALUES ('second@example.com')");
await client.execute("INSERT INTO users VALUES ('third@example.com')");
{
// No users, sinc no sync has happend since inserts
const result = await client.execute("SELECT * FROM users");
console.log("Users:", result.rows);
}
{
await client.sync();
// No users, sinc no sync has happend since inserts
const result = await client.execute("SELECT * FROM users");
console.log("Users:", result.rows);
}
================================================
FILE: examples/remote/.gitignore
================================================
local.db
================================================
FILE: examples/remote/README.md
================================================
# Remote
This example demonstrates how to use libSQL with a remote database.
## Install Dependencies
```bash
npm i
```
## Running
Execute the example:
```bash
TURSO_DATABASE_URL="..." TURSO_AUTH_TOKEN="..." node index.mjs
```
This will connect to a remote SQLite database, insert some data, and then query the results.
================================================
FILE: examples/remote/index.mjs
================================================
import { createClient } from "@libsql/client";
const client = createClient({
url: process.env.TURSO_DATABASE_URL,
authToken: process.env.TURSO_AUTH_TOKEN,
});
await client.batch(
[
"CREATE TABLE IF NOT EXISTS users (email TEXT)",
"INSERT INTO users VALUES ('first@example.com')",
"INSERT INTO users VALUES ('second@example.com')",
"INSERT INTO users VALUES ('third@example.com')",
],
"write",
);
const result = await client.execute("SELECT * FROM users");
console.log("Users:", result.rows);
================================================
FILE: examples/remote/package.json
================================================
{
"name": "batch",
"version": "1.0.0",
"main": "index.mjs",
"author": "Giovanni Benussi",
"license": "MIT",
"dependencies": {
"@libsql/client": "^0.14.0"
}
}
================================================
FILE: examples/sync/.gitignore
================================================
local.db
local.db-client_wal_index
================================================
FILE: examples/sync/README.md
================================================
# Sync
This example demonstrates how to use libSQL with a synced database (local file synced with a remote database).
## Install Dependencies
```bash
npm i
```
## Running
Execute the example:
```bash
TURSO_DATABASE_URL="..." TURSO_AUTH_TOKEN="..." node index.mjs
```
This will connect to a remote SQLite database, insert some data, and then query the results.
================================================
FILE: examples/sync/index.mjs
================================================
import { createClient } from "@libsql/client";
const client = createClient({
url: "file:local.db",
syncUrl: process.env.TURSO_DATABASE_URL,
authToken: process.env.TURSO_AUTH_TOKEN,
});
await client.batch(
[
"CREATE TABLE IF NOT EXISTS users (email TEXT)",
"INSERT INTO users VALUES ('first@example.com')",
"INSERT INTO users VALUES ('second@example.com')",
"INSERT INTO users VALUES ('third@example.com')",
],
"write",
);
const result = await client.execute("SELECT * FROM users");
console.log("Users:", result.rows);
================================================
FILE: examples/sync/package.json
================================================
{
"name": "batch",
"version": "1.0.0",
"main": "index.mjs",
"author": "Giovanni Benussi",
"license": "MIT",
"dependencies": {
"@libsql/client": "^0.14.0"
}
}
================================================
FILE: examples/transactions/.gitignore
================================================
local.db
================================================
FILE: examples/transactions/README.md
================================================
# Local
This example demonstrates how to use transactions with libSQL.
## Install Dependencies
```bash
npm i
```
## Running
Execute the example:
```bash
node index.mjs
```
This example will:
1. Create a new table called `users`.
2. Start a transaction.
3. Insert multiple users within the transaction.
4. Demonstrate how to rollback a transaction.
5. Start another transaction.
6. Insert more users and commit the transaction.
7. Query and display the final state of the `users` table.
================================================
FILE: examples/transactions/index.mjs
================================================
import { createClient } from "@libsql/client";
const client = createClient({
url: "file:local.db",
});
await client.batch(
[
"DROP TABLE IF EXISTS users",
"CREATE TABLE users (id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT)",
"INSERT INTO users (name) VALUES ('Iku Turso')",
],
"write",
);
const names = ["John Doe", "Mary Smith", "Alice Jones", "Mark Taylor"];
let transaction, secondTransaction;
try {
transaction = await client.transaction("write");
for (const name of names) {
await transaction.execute({
sql: "INSERT INTO users (name) VALUES (?)",
args: [name],
});
}
await transaction.rollback();
secondTransaction = await client.transaction("write");
for (const name of names) {
await secondTransaction.execute({
sql: "INSERT INTO users (name) VALUES (?)",
args: [name],
});
}
await secondTransaction.commit();
} catch (e) {
console.error(e);
await transaction?.rollback();
await secondTransaction?.rollback();
}
const result = await client.execute("SELECT * FROM users");
console.log("Users:", result.rows);
================================================
FILE: examples/transactions/package.json
================================================
{
"name": "batch",
"version": "1.0.0",
"main": "index.mjs",
"author": "Giovanni Benussi",
"license": "MIT",
"dependencies": {
"@libsql/client": "^0.14.0"
}
}
================================================
FILE: examples/vector/.gitignore
================================================
local.db
================================================
FILE: examples/vector/README.md
================================================
# Local
This example demonstrates how to use libSQL vector search with a local database.
## Install Dependencies
```bash
npm i
```
## Running
Execute the example:
```bash
node index.mjs
```
This will setup a local SQLite database, insert some data, and then query the results using the vector similarity search function.
================================================
FILE: examples/vector/index.mjs
================================================
import { createClient } from "@libsql/client";
const client = createClient({
url: "file:local.db",
});
await client.batch(
[
"DROP TABLE IF EXISTS movies",
"CREATE TABLE IF NOT EXISTS movies (title TEXT, year INT, embedding F32_BLOB(3))",
"CREATE INDEX movies_idx ON movies (libsql_vector_idx(embedding))",
"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]'))",
],
"write",
);
const result = await client.execute(
"SELECT title, year FROM vector_top_k('movies_idx', '[4,5,6]', 3) JOIN movies ON movies.rowid = id",
);
console.log("Movies:", result.rows);
================================================
FILE: examples/vector/package.json
================================================
{
"name": "batch",
"version": "1.0.0",
"main": "index.mjs",
"author": "Giovanni Benussi",
"license": "MIT",
"dependencies": {
"@libsql/client": "^0.14.0"
}
}
================================================
FILE: package.json
================================================
{
"workspaces": [
"packages/libsql-core",
"packages/libsql-client",
"packages/libsql-client-wasm"
],
"dependencies": {},
"scripts": {
"prepare": "node .husky/install.mjs",
"build": "npm run build --workspaces",
"test": "./testing/test.sh",
"typecheck": "npm run typecheck --workspaces",
"format:check": "npm run format:check --workspaces",
"lint-staged": "lint-staged"
},
"devDependencies": {
"lint-staged": "^15.2.2",
"husky": "^9.1.5"
}
}
================================================
FILE: packages/libsql-client/README.md
================================================
<p align="center">
<a href="https://tur.so/turso-ts">
<picture>
<img src="/.github/cover.png" alt="libSQL TypeScript" />
</picture>
</a>
<h1 align="center">libSQL TypeScript</h1>
</p>
<p align="center">
Databases for all TypeScript and JS multi-tenant apps.
</p>
<p align="center">
<a href="https://tur.so/turso-ts"><strong>Turso</strong></a> ·
<a href="https://docs.turso.tech"><strong>Docs</strong></a> ·
<a href="https://docs.turso.tech/sdk/ts/quickstart"><strong>Quickstart</strong></a> ·
<a href="https://docs.turso.tech/sdk/ts/reference"><strong>SDK Reference</strong></a> ·
<a href="https://turso.tech/blog"><strong>Blog & Tutorials</strong></a>
</p>
<p align="center">
<a href="LICENSE">
<picture>
<img src="https://img.shields.io/github/license/tursodatabase/libsql-client-ts?color=0F624B" alt="MIT License" />
</picture>
</a>
<a href="https://tur.so/discord-ts">
<picture>
<img src="https://img.shields.io/discord/933071162680958986?color=0F624B" alt="Discord" />
</picture>
</a>
<a href="#contributors">
<picture>
<img src="https://img.shields.io/github/contributors/tursodatabase/libsql-client-ts?color=0F624B" alt="Contributors" />
</picture>
</a>
<a href="https://www.npmjs.com/package/@libsql/client">
<picture>
<img src="https://img.shields.io/npm/dw/%40libsql%2Fclient?color=0F624B" alt="Weekly downloads" />
</picture>
</a>
<a href="/examples">
<picture>
<img src="https://img.shields.io/badge/browse-examples-0F624B" alt="Examples" />
</picture>
</a>
</p>
> **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.
## Features
- 🔌 Works offline with [Embedded Replicas](https://docs.turso.tech/features/embedded-replicas/introduction)
- 🌎 Works with remote Turso databases
- ✨ Works with Turso [AI & Vector Search](https://docs.turso.tech/features/ai-and-embeddings)
- 🔐 Supports [encryption at rest](https://docs.turso.tech/libsql#encryption-at-rest)
## Install
```bash
npm install @libsql/client
```
## Quickstart
The example below uses Embedded Replicas and syncs every minute from Turso.
```ts
import { createClient } from "@libsql/client";
export const turso = createClient({
url: "file:local.db",
syncUrl: process.env.TURSO_DATABASE_URL,
authToken: process.env.TURSO_AUTH_TOKEN,
syncInterval: 60000,
});
await turso.batch(
[
"CREATE TABLE IF NOT EXISTS users (id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT)",
{
sql: "INSERT INTO users(name) VALUES (?)",
args: ["Iku"],
},
],
"write",
);
await turso.execute({
sql: "SELECT * FROM users WHERE id = ?",
args: [1],
});
```
## Examples
| Example | Description |
| ------------------------------------- | --------------------------------------------------------------------------------------- |
| [local](examples/local) | Uses libsql with a local SQLite file. Creates database, inserts data, and queries. |
| [remote](examples/remote) | Connects to a remote database. Requires environment variables for URL and auth token. |
| [sync](examples/sync) | Demonstrates synchronization between local and remote databases. |
| [batch](examples/batch) | Executes multiple SQL statements in a single batch operation. |
| [transactions](examples/transactions) | Shows transaction usage: starting, performing operations, and committing/rolling back. |
| [memory](examples/memory) | Uses an in-memory SQLite database for temporary storage or fast access. |
| [vector](examples/vector) | Works with vector embeddings, storing and querying for similarity search. |
| [encryption](examples/encryption) | Creates and uses an encrypted SQLite database, demonstrating setup and data operations. |
| [ollama](examples/ollama) | Similarity search with Ollama and Mistral. |
## Documentation
Visit our [official documentation](https://docs.turso.tech/sdk/ts).
## Support
Join us [on Discord](https://tur.so/discord-ts) to get help using this SDK. Report security issues [via email](mailto:security@turso.tech).
## Contributors
See the [contributing guide](CONTRIBUTING.md) to learn how to get involved.

<a href="https://github.com/tursodatabase/libsql-client-ts/issues?q=is%3Aopen+is%3Aissue+label%3A%22good+first+issue%22">
<picture>
<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" />
</picture>
</a>
================================================
FILE: packages/libsql-client/examples/example.js
================================================
import { createClient } from "@libsql/client";
async function example() {
const config = {
url: process.env.URL ?? "file:local.db",
encryptionKey: process.env.ENCRYPTION_KEY,
};
const db = createClient(config);
await db.batch(
[
"CREATE TABLE IF NOT EXISTS users (email TEXT)",
"INSERT INTO users (email) VALUES ('alice@example.com')",
"INSERT INTO users (email) VALUES ('bob@example.com')",
],
"write",
);
await db.batch(
[
{
sql: "INSERT INTO users (email) VALUES (?)",
args: ["alice@example.com"],
},
["INSERT INTO users (email) VALUES (?)", ["bob@example.com"]],
{
sql: "INSERT INTO users (email) VALUES (:email)",
args: { email: "charlie@example.com" },
},
],
"write",
);
const rs = await db.execute("SELECT * FROM users");
console.log(rs);
}
await example();
================================================
FILE: packages/libsql-client/examples/package.json
================================================
{
"name": "libsql-examples",
"type": "module",
"private": true,
"dependencies": {
"@libsql/client": "..",
"@libsql/core": "../../libsql-core",
"readline-sync": "^1.4.10"
}
}
================================================
FILE: packages/libsql-client/examples/shell.js
================================================
import * as readline from "node:readline/promises";
import { stdin, stdout, argv } from "node:process";
import * as libsql from "@libsql/client";
async function main() {
const url = argv[2];
if (!url) {
console.error("Please specify database URL as command-line argument");
return;
}
const client = libsql.createClient({ url });
const rl = readline.createInterface({ input: stdin, output: stdout });
for (;;) {
const sql = await rl.question("> ");
let rs;
try {
rs = await client.execute(sql);
} catch (e) {
if (e instanceof libsql.LibsqlError) {
console.error(e);
continue;
}
throw e;
}
console.log(JSON.stringify(rs.columns));
for (const row of rs.rows) {
console.log(JSON.stringify(Array.from(row)));
}
}
}
await main();
================================================
FILE: packages/libsql-client/examples/sync.js
================================================
import { createClient } from "@libsql/client";
import reader from "readline-sync";
async function example() {
const config = {
url: process.env.URL ?? "file:local.db",
syncUrl: process.env.SYNC_URL,
authToken: process.env.AUTH_TOKEN,
};
const db = createClient(config);
await db.sync();
await db.execute(
"CREATE TABLE IF NOT EXISTS guest_book_entries (comment TEXT)",
);
const rep = await db.sync();
console.log("frames_synced: " + rep.frames_synced);
const comment = reader.question("Enter your comment: ");
await db.execute({
sql: "INSERT INTO guest_book_entries (comment) VALUES (?)",
args: [comment],
});
const rep2 = await db.sync();
console.log("frames_synced: " + rep2.frames_synced);
console.log("Guest book entries:");
const rs = await db.execute("SELECT * FROM guest_book_entries");
for (const row of rs.rows) {
console.log(" - " + row.comment);
}
}
example();
================================================
FILE: packages/libsql-client/examples/sync_offline.js
================================================
import { createClient } from "@libsql/client";
import reader from "readline-sync";
async function example() {
const config = {
url: process.env.URL ?? "file:local.db",
syncUrl: process.env.SYNC_URL,
authToken: process.env.AUTH_TOKEN,
offline: true,
};
const db = createClient(config);
console.log("Syncing database ...");
await db.sync();
await db.execute(
"CREATE TABLE IF NOT EXISTS guest_book_entries (comment TEXT)",
);
const comment = reader.question("Enter your comment: ");
await db.execute({
sql: "INSERT INTO guest_book_entries (comment) VALUES (?)",
args: [comment],
});
console.log("Syncing database ...");
const rep2 = await db.sync();
console.log("frames_synced: " + rep2.frames_synced);
console.log("Guest book entries:");
const rs = await db.execute("SELECT * FROM guest_book_entries");
for (const row of rs.rows) {
console.log(" - " + row.comment);
}
}
example();
================================================
FILE: packages/libsql-client/examples/sync_vector.js
================================================
import { createClient } from "@libsql/client";
import reader from "readline-sync";
async function example() {
const config = {
url: process.env.URL ?? "file:local.db",
syncUrl: process.env.SYNC_URL,
authToken: process.env.AUTH_TOKEN,
};
const db = createClient(config);
await db.sync();
await db.execute(
"CREATE TABLE IF NOT EXISTS movies (title TEXT, embedding FLOAT32(4))",
);
await db.execute(
"CREATE INDEX IF NOT EXISTS movies_idx ON movies (libsql_vector_idx(embedding))",
);
await db.sync();
const title = reader.question("Add movie (title): ");
const embedding = reader.question(
"Add movie (embedding, e.g. [1,2,3,4]): ",
);
await db.execute({
sql: "INSERT INTO movies (title, embedding) VALUES (?, vector32(?))",
args: [title, embedding],
});
await db.sync();
const all = await db.execute(
"SELECT title, vector_extract(embedding) as embedding FROM movies",
);
console.info("all movies:");
for (const row of all.rows) {
console.log(" - " + row.title + ": " + row.embedding);
}
const query = reader.question("KNN query (e.g. [1,2,3,4]): ");
const nn = await db.execute({
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",
args: [query],
});
console.info("nearest neighbors:");
for (const row of nn.rows) {
console.log(" - " + row.title + ": " + row.embedding);
}
}
example();
================================================
FILE: packages/libsql-client/jest.config.js
================================================
export default {
preset: "ts-jest/presets/default-esm",
moduleNameMapper: {
"^(\\.{1,2}/.*)\\.js$": "$1",
},
testMatch: ["**/__tests__/*.test.[jt]s"],
};
================================================
FILE: packages/libsql-client/package-cjs.json
================================================
{
"type": "commonjs"
}
================================================
FILE: packages/libsql-client/package.json
================================================
{
"name": "@libsql/client",
"version": "0.17.2",
"keywords": [
"libsql",
"database",
"sqlite",
"serverless",
"vercel",
"netlify",
"lambda"
],
"description": "libSQL driver for TypeScript and JavaScript",
"repository": {
"type": "git",
"url": "git+https://github.com/tursodatabase/libsql-client-ts",
"directory": "packages/libsql-client"
},
"authors": [
"Jan Špaček <honza@chiselstrike.com>",
"Pekka Enberg <penberg@chiselstrike.com>",
"Jan Plhak <jp@chiselstrike.com>"
],
"license": "MIT",
"type": "module",
"main": "lib-cjs/node.js",
"types": "lib-esm/node.d.ts",
"exports": {
".": {
"types": "./lib-esm/node.d.ts",
"import": {
"workerd": "./lib-esm/web.js",
"deno": "./lib-esm/node.js",
"edge-light": "./lib-esm/web.js",
"netlify": "./lib-esm/web.js",
"node": "./lib-esm/node.js",
"browser": "./lib-esm/web.js",
"default": "./lib-esm/node.js"
},
"require": "./lib-cjs/node.js"
},
"./node": {
"types": "./lib-esm/node.d.ts",
"import": "./lib-esm/node.js",
"require": "./lib-cjs/node.js"
},
"./http": {
"types": "./lib-esm/http.d.ts",
"import": "./lib-esm/http.js",
"require": "./lib-cjs/http.js"
},
"./ws": {
"types": "./lib-esm/ws.d.ts",
"import": "./lib-esm/ws.js",
"require": "./lib-cjs/ws.js"
},
"./sqlite3": {
"types": "./lib-esm/sqlite3.d.ts",
"import": "./lib-esm/sqlite3.js",
"require": "./lib-cjs/sqlite3.js"
},
"./web": {
"types": "./lib-esm/web.d.ts",
"import": "./lib-esm/web.js",
"require": "./lib-cjs/web.js"
}
},
"typesVersions": {
"*": {
".": [
"./lib-esm/node.d.ts"
],
"http": [
"./lib-esm/http.d.ts"
],
"hrana": [
"./lib-esm/hrana.d.ts"
],
"sqlite3": [
"./lib-esm/sqlite3.d.ts"
],
"web": [
"./lib-esm/web.d.ts"
]
}
},
"files": [
"lib-cjs/**",
"lib-esm/**",
"README.md"
],
"scripts": {
"prepublishOnly": "npm run build",
"prebuild": "rm -rf ./lib-cjs ./lib-esm",
"build": "npm run build:cjs && npm run build:esm",
"build:cjs": "tsc -p tsconfig.build-cjs.json",
"build:esm": "tsc -p tsconfig.build-esm.json",
"format:check": "prettier --check .",
"postbuild": "cp package-cjs.json ./lib-cjs/package.json",
"test": "jest --runInBand",
"typecheck": "tsc --noEmit",
"typedoc": "rm -rf ./docs && typedoc",
"lint-staged": "lint-staged"
},
"dependencies": {
"@libsql/core": "^0.17.2",
"@libsql/hrana-client": "^0.9.0",
"js-base64": "^3.7.5",
"libsql": "^0.5.28",
"promise-limit": "^2.7.0"
},
"devDependencies": {
"@types/jest": "^29.2.5",
"@types/node": "^18.15.5",
"jest": "^29.3.1",
"lint-staged": "^15.2.2",
"msw": "^2.3.0",
"prettier": "3.2.5",
"ts-jest": "^29.0.5",
"typedoc": "^0.23.28",
"typescript": "^4.9.4"
}
}
================================================
FILE: packages/libsql-client/smoke_test/vercel/.gitignore
================================================
app/package.json
package-lock.json
*.tgz
================================================
FILE: packages/libsql-client/smoke_test/vercel/app/.gitignore
================================================
.vercel
================================================
FILE: packages/libsql-client/smoke_test/vercel/app/api/function.ts
================================================
import * as libsql from "@libsql/client";
export const config = {
runtime: "edge",
};
export default async function (request: Request) {
function respond(status: number, responseBody: string) {
return new Response(responseBody, {
status,
headers: [["content-type", "text/plain"]],
});
}
if (request.method !== "GET") {
return respond(405, "Only GET method is supported");
}
const url = new URL(request.url);
const testCase = url.searchParams.get("test");
if (testCase === null) {
return respond(
400,
"Please specify the test case using the 'test' query parameter",
);
}
const testCaseFn = testCases[testCase];
if (testCaseFn === undefined) {
return respond(404, "Unknown test case");
}
let client;
try {
client = libsql.createClient({ url: process.env.CLIENT_URL! });
await testCaseFn(client);
return respond(200, "Test passed");
} catch (e) {
return respond(500, `Test failed\n${(e as Error).stack}`);
} finally {
if (client !== undefined) {
client.close();
}
}
}
const testCases: Record<string, (client: libsql.Client) => Promise<void>> = {
execute: async (client: libsql.Client): Promise<void> => {
const rs = await client.execute("SELECT 1+1 AS two");
assert(rs.columns.length === 1);
assert(rs.columns[0] === "two");
assert(rs.rows.length === 1);
assert(rs.rows[0].length === 1);
assert(rs.rows[0][0] === 2.0);
},
batch: async (client: libsql.Client): Promise<void> => {
const rss = await client.batch([
"DROP TABLE IF EXISTS t",
"CREATE TABLE t (a, b)",
"INSERT INTO t VALUES (1, 'one'), (2, 'two'), (3, 'three')",
"SELECT * FROM t ORDER BY a",
]);
assert(rss[0].columns.length === 0);
assert(rss[0].rows.length === 0);
assert(rss[1].columns.length === 0);
assert(rss[1].rows.length === 0);
assert(rss[2].columns.length === 0);
assert(rss[2].rows.length === 0);
assert(rss[3].columns.length === 2);
assert(rss[3].columns[0] === "a");
assert(rss[3].columns[1] === "b");
assert(rss[3].rows.length === 3);
assert(rss[3].rows[0][0] === 1);
assert(rss[3].rows[0][1] === "one");
assert(rss[3].rows[1][0] === 2);
assert(rss[3].rows[1][1] === "two");
assert(rss[3].rows[2][0] === 3);
assert(rss[3].rows[2][1] === "three");
},
transaction: async (client: libsql.Client): Promise<void> => {
await client.batch([
"DROP TABLE IF EXISTS t",
"CREATE TABLE t (a, b)",
"INSERT INTO t VALUES (1, 'one'), (2, 'two'), (3, 'three')",
]);
const txn = await client.transaction();
try {
await txn.execute("INSERT INTO t VALUES (4, 'four')");
await txn.execute("DELETE FROM t WHERE a <= 2");
await txn.commit();
} finally {
txn.close();
}
const rs = await client.execute("SELECT COUNT(*) FROM t");
assert(rs.rows[0][0] === 2);
},
};
function assert(value: unknown, message?: string) {
if (!value) {
throw new Error(message ?? "Assertion failed");
}
}
================================================
FILE: packages/libsql-client/smoke_test/vercel/app/public/index.html
================================================
<html>
<body>
<p>This is a smoke-test Vercel app for <code>@libsql/client</code>.</p>
</body>
</html>
================================================
FILE: packages/libsql-client/smoke_test/vercel/package.json
================================================
{
"name": "smoke-test",
"dependencies": {
"@types/node": "20.4.2",
"localtunnel": "^2.0.2",
"vercel": "^31.0.3",
"typescript": "^4.9.4"
}
}
================================================
FILE: packages/libsql-client/smoke_test/vercel/test.js
================================================
"use strict";
const { spawn } = require("node:child_process");
const fs = require("node:fs");
const fetch = require("node-fetch");
const localtunnel = require("localtunnel");
function getEnv(name) {
const value = process.env[name] ?? "";
if (!value) {
throw new Error(`Please set the env variable ${name}`);
}
return value;
}
const vercelToken = getEnv("VERCEL_TOKEN");
const projectName = getEnv("VERCEL_PROJECT_NAME");
async function npm(
subcommand,
args,
hiddenArgs = [],
{ capture = false } = {},
) {
console.info(`$ npm ${subcommand} ${args.join(" ")}`);
const proc = spawn("npm", [subcommand, ...args, ...hiddenArgs], {
stdio: ["ignore", capture ? "pipe" : "inherit", "inherit"],
});
const exitPromise = new Promise((resolve, reject) => {
proc.on("exit", (code, signal) => {
if (signal !== null) {
reject(
new Error(
`vercel command terminated due to signal: ${signal}`,
),
);
} else if (code !== 0) {
reject(new Error(`vercel command exited with code: ${code}`));
} else {
resolve();
}
});
});
const dataPromise = new Promise((resolve, reject) => {
if (!capture) {
return resolve();
}
const stream = proc.stdout;
stream.setEncoding("utf-8");
const chunks = [];
stream.on("data", (chunk) => chunks.push(chunk));
stream.on("end", () => resolve(chunks.join("")));
stream.on("error", (e) => reject(e));
});
return exitPromise.then(() => dataPromise);
}
async function deployToVercel(clientUrlInsideVercel) {
console.info("Building and deploying to Vercel...");
let tarballName = await npm("pack", ["../.."], [], { capture: true });
tarballName = tarballName.trim();
const appPackageJson = {
dependencies: {
"@libsql/client": `../${tarballName}`,
},
};
fs.writeFileSync(
"app/package.json",
JSON.stringify(appPackageJson, null, 4),
);
await npm(
"exec",
[
"--",
"vercel",
"link",
"--yes",
"--project",
projectName,
"--cwd",
"app/",
],
["--token", vercelToken],
);
await npm(
"exec",
[
"--",
"vercel",
"pull",
"--yes",
"--environment=preview",
"--cwd",
"app/",
],
["--token", vercelToken],
);
await npm("exec", ["--", "vercel", "build", "--cwd", "app/"]);
const deployUrl = await npm(
"exec",
[
"--",
"vercel",
"deploy",
"--prebuilt",
"--env",
`CLIENT_URL=${clientUrlInsideVercel}`,
"--cwd",
"app/",
],
["--token", vercelToken, "--cwd", "app/"],
{ capture: true },
);
console.info(`Deployed Vercel project on ${deployUrl}`);
return deployUrl;
}
const testCases = ["execute", "batch", "transaction"];
async function runTests(functionUrl) {
let ok = true;
for (const testCase of testCases) {
if (!(await runTest(functionUrl, testCase))) {
ok = false;
}
}
return ok;
}
async function runTest(functionUrl, testCase) {
const resp = await fetch(`${functionUrl}?test=${testCase}`);
const respText = await resp.text();
const ok = resp.status === 200 && respText === "Test passed";
if (ok) {
console.info(`TEST ${testCase}: passed`);
} else {
console.warn(
`\nTEST ${testCase}: failed with status ${resp.status}\n${respText}\n`,
);
}
return ok;
}
async function main() {
const url = new URL(process.env.URL ?? "ws://localhost:8080");
console.info(`Creating a tunnel to ${url}...`);
const tunnel = await localtunnel({
port: url.port,
// NOTE: if we specify `local_host`, `localtunnel` will try to rewrite the `Host` header in the
// tunnelled HTTP requests. Unfortunately, they do it in a very silly way by converting the
// tunnelled data to a string, thus corrupting the request body.
//local_host: url.hostname,
});
let clientUrlInsideVercel = new URL(tunnel.url);
if (url.protocol === "http:") {
clientUrlInsideVercel.protocol = "https:";
} else if (url.protocol === "ws:") {
clientUrlInsideVercel.protocol = "wss:";
} else {
clientUrlInsideVercel.protocol = url.protocol;
}
console.info(`Established a tunnel on ${clientUrlInsideVercel}`);
let ok = false;
try {
const deployUrl = await deployToVercel(clientUrlInsideVercel);
const functionUrl = new URL("api/function", deployUrl);
ok = await runTests(functionUrl);
if (ok) {
console.log("All tests passed");
} else {
console.error("Some tests failed");
}
} finally {
console.info("Closing the tunnel...");
await tunnel.close();
}
process.exit(ok ? 0 : 1);
}
main();
================================================
FILE: packages/libsql-client/smoke_test/vercel/tsconfig.json
================================================
{
"compilerOptions": {
"target": "es5",
"lib": ["dom", "esnext"],
"module": "esnext",
"moduleResolution": "node",
"allowJs": true,
"skipLibCheck": true,
"strict": true,
"noEmit": true,
"esModuleInterop": true,
"isolatedModules": true
}
}
================================================
FILE: packages/libsql-client/smoke_test/workers/.gitignore
================================================
package-lock.json
================================================
FILE: packages/libsql-client/smoke_test/workers/package.json
================================================
{
"devDependencies": {
"localtunnel": "^2.0.2",
"wrangler": "^3.5.1"
}
}
================================================
FILE: packages/libsql-client/smoke_test/workers/test.js
================================================
"use strict";
const localtunnel = require("localtunnel");
const wrangler = require("wrangler");
const testCases = ["/execute", "/batch", "/transaction"];
async function main() {
const local = !!parseInt(process.env.LOCAL ?? "1");
const url = new URL(process.env.URL ?? "ws://localhost:8080");
let clientUrlInsideWorker;
let tunnel = undefined;
if (local) {
clientUrlInsideWorker = url;
} else {
console.info(`Creating an tunnel to ${url}...`);
tunnel = await localtunnel({
port: url.port,
// NOTE: if we specify `local_host`, `localtunnel` will try to rewrite the `Host` header in the
// tunnelled HTTP requests. Unfortunately, they do it in a very silly way by converting the
// tunnelled data to a string, thus corrupting the request body.
//local_host: url.hostname,
});
clientUrlInsideWorker = new URL(tunnel.url);
if (url.protocol === "http:") {
clientUrlInsideWorker.protocol = "https:";
} else if (url.protocol === "ws:") {
clientUrlInsideWorker.protocol = "wss:";
} else {
clientUrlInsideWorker.protocol = url.protocol;
}
console.info(`Established a tunnel on ${clientUrlInsideWorker}`);
}
let ok = false;
try {
ok = await runWorker(local, clientUrlInsideWorker);
if (ok) {
console.log("All tests passed");
} else {
console.error("Some tests failed");
}
} finally {
if (tunnel !== undefined) {
console.info("Closing tunnel...");
await tunnel.close();
}
// TODO: wrangler keeps the program running:
// https://github.com/cloudflare/workers-sdk/issues/2892
setTimeout(() => process.exit(ok ? 0 : 1), 200);
}
}
async function runWorker(local, clientUrlInsideWorker) {
console.info(`Creating a ${local ? "local" : "nonlocal"} Worker...`);
const worker = await wrangler.unstable_dev("worker.js", {
config: "wrangler.toml",
logLevel: "info",
local,
vars: {
CLIENT_URL: clientUrlInsideWorker.toString(),
},
experimental: {
disableExperimentalWarning: true,
},
});
console.info(`Worker created on ${worker.address}:${worker.port}`);
try {
let ok = true;
for (const testCase of testCases) {
if (!(await runTest(worker, testCase))) {
ok = false;
}
}
return ok;
} finally {
console.info("Stopping Worker...");
await worker.stop();
}
}
async function runTest(worker, testCase) {
const resp = await worker.fetch(testCase);
const respText = await resp.text();
const ok = resp.status === 200 && respText === "Test passed";
if (ok) {
console.info(`TEST ${testCase}: passed`);
} else {
console.warn(
`\nTEST ${testCase}: failed with status ${resp.status}\n${respText}\n`,
);
}
return ok;
}
main();
================================================
FILE: packages/libsql-client/smoke_test/workers/worker.js
================================================
import * as libsql from "@libsql/client";
export default {
async fetch(request, env, ctx) {
function respond(status, responseBody) {
return new Response(responseBody, {
status,
headers: [["content-type", "text/plain"]],
});
}
if (request.method !== "GET") {
return respond(405, "Only GET method is supported");
}
const url = new URL(request.url);
if (url.pathname === "/") {
return respond(
200,
"This is a smoke-test Worker for @libsql/client",
);
}
const testCaseFn = testCases[url.pathname];
if (testCaseFn === undefined) {
return respond(404, "Unknown test case");
}
let client;
try {
client = libsql.createClient({ url: env.CLIENT_URL });
await testCaseFn(client);
return respond(200, "Test passed");
} catch (e) {
return respond(500, `Test failed\n${e.stack}`);
} finally {
if (client !== undefined) {
client.close();
}
}
},
};
const testCases = {
"/execute": async (client) => {
const rs = await client.execute("SELECT 1+1 AS two");
assert(rs.columns.length === 1);
assert(rs.columns[0] === "two");
assert(rs.rows.length === 1);
assert(rs.rows[0].length === 1);
assert(rs.rows[0][0] === 2.0);
},
"/batch": async (client) => {
const rss = await client.batch([
"DROP TABLE IF EXISTS t",
"CREATE TABLE t (a, b)",
"INSERT INTO t VALUES (1, 'one'), (2, 'two'), (3, 'three')",
"SELECT * FROM t ORDER BY a",
]);
assert(rss[0].columns.length === 0);
assert(rss[0].rows.length === 0);
assert(rss[1].columns.length === 0);
assert(rss[1].rows.length === 0);
assert(rss[2].columns.length === 0);
assert(rss[2].rows.length === 0);
assert(rss[3].columns.length === 2);
assert(rss[3].columns[0] === "a");
assert(rss[3].columns[1] === "b");
assert(rss[3].rows.length === 3);
assert(rss[3].rows[0][0] === 1);
assert(rss[3].rows[0][1] === "one");
assert(rss[3].rows[1][0] === 2);
assert(rss[3].rows[1][1] === "two");
assert(rss[3].rows[2][0] === 3);
assert(rss[3].rows[2][1] === "three");
},
"/transaction": async (client) => {
await client.batch([
"DROP TABLE IF EXISTS t",
"CREATE TABLE t (a, b)",
"INSERT INTO t VALUES (1, 'one'), (2, 'two'), (3, 'three')",
]);
const txn = await client.transaction();
try {
await txn.execute("INSERT INTO t VALUES (4, 'four')");
await txn.execute("DELETE FROM t WHERE a <= 2");
await txn.commit();
} finally {
txn.close();
}
const rs = await client.execute("SELECT COUNT(*) FROM t");
assert(rs.rows[0][0] === 2);
},
};
function assert(value, message) {
if (!value) {
throw new Error(message ?? "Assertion failed");
}
}
================================================
FILE: packages/libsql-client/smoke_test/workers/wrangler.toml
================================================
main = "worker.js"
compatibility_date = "2023-05-15"
[vars]
CLIENT_URL = "ws://localhost:8080"
================================================
FILE: packages/libsql-client/src/__tests__/client.test.ts
================================================
import console from "node:console";
import { expect } from "@jest/globals";
import type { MatcherFunction } from "expect";
import type { Request, Response } from "@libsql/hrana-client";
import { fetch } from "@libsql/hrana-client";
import "./helpers.js";
import type * as libsql from "../node.js";
import { createClient } from "../node.js";
const config = {
url: process.env.URL ?? "ws://localhost:8080",
syncUrl: process.env.SYNC_URL,
authToken: process.env.AUTH_TOKEN,
};
const isWs =
config.url.startsWith("ws:") ||
config.url.startsWith("wss:") ||
config.url.startsWith("libsql:");
const isHttp =
config.url.startsWith("http:") || config.url.startsWith("https:");
const isFile = config.url.startsWith("file:");
// This allows us to skip tests based on the Hrana server that we are targeting:
// - "test_v3" is the v3 test server in Python
// - "test_v2" is the v2 test server in Python
// - "test_v1" is the v1 test server in Python
// - "sqld" is sqld
const server = process.env.SERVER ?? "test_v3";
const isSqld = server === "sqld";
const hasHrana2 = server !== "test_v1";
const hasHrana3 =
server !== "test_v1" && server !== "test_v2" && server !== "sqld";
const hasNetworkErrors =
isWs &&
(server === "test_v1" || server === "test_v2" || server === "test_v3");
function withClient(
f: (c: libsql.Client) => Promise<void>,
extraConfig: Partial<libsql.Config> = {},
): () => Promise<void> {
return async () => {
const c = createClient({ ...config, ...extraConfig });
try {
await f(c);
} finally {
c.close();
}
};
}
function withInMemoryClient(
f: (c: libsql.Client) => Promise<void>,
): () => Promise<void> {
return async () => {
const c = createClient({ url: ":memory:" });
try {
await f(c);
} finally {
c.close();
}
};
}
describe("createClient()", () => {
test("URL scheme not supported", () => {
expect(() => createClient({ url: "ftp://localhost" })).toThrow(
expect.toBeLibsqlError("URL_SCHEME_NOT_SUPPORTED", /"ftp:"/),
);
});
test("URL param not supported", () => {
expect(() => createClient({ url: "ws://localhost?foo=bar" })).toThrow(
expect.toBeLibsqlError("URL_PARAM_NOT_SUPPORTED", /"foo"/),
);
});
test("URL scheme incompatible with ?tls", () => {
const urls = [
"ws://localhost?tls=1",
"wss://localhost?tls=0",
"http://localhost?tls=1",
"https://localhost?tls=0",
];
for (const url of urls) {
expect(() => createClient({ url })).toThrow(
expect.toBeLibsqlError("URL_INVALID", /TLS/),
);
}
});
test("missing port in libsql URL with tls=0", () => {
expect(() => createClient({ url: "libsql://localhost?tls=0" })).toThrow(
expect.toBeLibsqlError("URL_INVALID", /port/),
);
});
test("invalid value of tls query param", () => {
expect(() =>
createClient({ url: "libsql://localhost?tls=yes" }),
).toThrow(expect.toBeLibsqlError("URL_INVALID", /"tls".*"yes"/));
});
test("passing URL instead of config object", () => {
// @ts-expect-error
expect(() => createClient("ws://localhost")).toThrow(
/as object, got string/,
);
});
test("invalid value for `intMode`", () => {
// @ts-expect-error
expect(() => createClient({ ...config, intMode: "foo" })).toThrow(
/"foo"/,
);
});
test("supports in-memory database", () => {
expect(() => createClient({ url: ":memory:" })).not.toThrow();
});
});
describe("execute()", () => {
test(
"query a single value",
withClient(async (c) => {
const rs = await c.execute("SELECT 42");
expect(rs.columns.length).toStrictEqual(1);
expect(rs.columnTypes.length).toStrictEqual(1);
expect(rs.rows.length).toStrictEqual(1);
expect(rs.rows[0].length).toStrictEqual(1);
expect(rs.rows[0][0]).toStrictEqual(42);
}),
);
test(
"query a single row",
withClient(async (c) => {
const rs = await c.execute(
"SELECT 1 AS one, 'two' AS two, 0.5 AS three",
);
expect(rs.columns).toStrictEqual(["one", "two", "three"]);
expect(rs.columnTypes).toStrictEqual(["", "", ""]);
expect(rs.rows.length).toStrictEqual(1);
const r = rs.rows[0];
expect(r.length).toStrictEqual(3);
expect(Array.from(r)).toStrictEqual([1, "two", 0.5]);
expect(Object.entries(r)).toStrictEqual([
["one", 1],
["two", "two"],
["three", 0.5],
]);
}),
);
test(
"query multiple rows",
withClient(async (c) => {
const rs = await c.execute(
"VALUES (1, 'one'), (2, 'two'), (3, 'three')",
);
expect(rs.columns.length).toStrictEqual(2);
expect(rs.columnTypes.length).toStrictEqual(2);
expect(rs.rows.length).toStrictEqual(3);
expect(Array.from(rs.rows[0])).toStrictEqual([1, "one"]);
expect(Array.from(rs.rows[1])).toStrictEqual([2, "two"]);
expect(Array.from(rs.rows[2])).toStrictEqual([3, "three"]);
}),
);
test(
"statement that produces error",
withClient(async (c) => {
await expect(c.execute("SELECT foobar")).rejects.toBeLibsqlError();
}),
);
test(
"rowsAffected with INSERT",
withClient(async (c) => {
await c.batch(
["DROP TABLE IF EXISTS t", "CREATE TABLE t (a)"],
"write",
);
const rs = await c.execute("INSERT INTO t VALUES (1), (2)");
expect(rs.rowsAffected).toStrictEqual(2);
}),
);
test(
"rowsAffected with DELETE",
withClient(async (c) => {
await c.batch(
[
"DROP TABLE IF EXISTS t",
"CREATE TABLE t (a)",
"INSERT INTO t VALUES (1), (2), (3), (4), (5)",
],
"write",
);
const rs = await c.execute("DELETE FROM t WHERE a >= 3");
expect(rs.rowsAffected).toStrictEqual(3);
}),
);
test(
"lastInsertRowid with INSERT",
withClient(async (c) => {
await c.batch(
[
"DROP TABLE IF EXISTS t",
"CREATE TABLE t (a)",
"INSERT INTO t VALUES ('one'), ('two')",
],
"write",
);
const insertRs = await c.execute("INSERT INTO t VALUES ('three')");
expect(insertRs.lastInsertRowid).not.toBeUndefined();
const selectRs = await c.execute({
sql: "SELECT a FROM t WHERE ROWID = ?",
args: [insertRs.lastInsertRowid!],
});
expect(Array.from(selectRs.rows[0])).toStrictEqual(["three"]);
}),
);
test(
"rows from INSERT RETURNING",
withClient(async (c) => {
await c.batch(
["DROP TABLE IF EXISTS t", "CREATE TABLE t (a)"],
"write",
);
const rs = await c.execute(
"INSERT INTO t VALUES (1) RETURNING 42 AS x, 'foo' AS y",
);
expect(rs.columns).toStrictEqual(["x", "y"]);
expect(rs.columnTypes).toStrictEqual(["", ""]);
expect(rs.rows.length).toStrictEqual(1);
expect(Array.from(rs.rows[0])).toStrictEqual([42, "foo"]);
}),
);
(hasHrana2 ? test : test.skip)(
"rowsAffected with WITH INSERT",
withClient(async (c) => {
await c.batch(
[
"DROP TABLE IF EXISTS t",
"CREATE TABLE t (a)",
"INSERT INTO t VALUES (1), (2), (3)",
],
"write",
);
const rs = await c.execute(`
WITH x(a) AS (SELECT 2*a FROM t)
INSERT INTO t SELECT a+1 FROM x
`);
expect(rs.rowsAffected).toStrictEqual(3);
}),
);
test(
"query a single value using an in memory database",
withInMemoryClient(async (c) => {
await c.batch(
[
"DROP TABLE IF EXISTS t",
"CREATE TABLE t (a)",
"INSERT INTO t VALUES ('one'), ('two')",
],
"write",
);
const insertRs = await c.execute("INSERT INTO t VALUES ('three')");
expect(insertRs.lastInsertRowid).not.toBeUndefined();
const selectRs = await c.execute({
sql: "SELECT a FROM t WHERE ROWID = ?",
args: [insertRs.lastInsertRowid!],
});
expect(Array.from(selectRs.rows[0])).toStrictEqual(["three"]);
}),
);
// see issue https://github.com/tursodatabase/libsql/issues/1411
test(
"execute transaction against in memory database with shared cache",
withClient(
async (c) => {
await c.execute("CREATE TABLE t (a)");
const transaction = await c.transaction();
transaction.close();
await c.execute("SELECT * FROM t");
},
{ url: "file::memory:?cache=shared" },
),
);
test(
"execute transaction against in memory database with private cache",
withClient(
async (c) => {
await c.execute("CREATE TABLE t (a)");
const transaction = await c.transaction();
transaction.close();
expect(() => c.execute("SELECT * FROM t")).rejects.toThrow();
},
{ url: "file::memory:?cache=private" },
),
);
test(
"execute transaction against in memory database with default cache",
withClient(
async (c) => {
await c.execute("CREATE TABLE t (a)");
const transaction = await c.transaction();
transaction.close();
expect(() => c.execute("SELECT * FROM t")).rejects.toThrow();
},
{ url: ":memory:" },
),
);
});
describe("values", () => {
function testRoundtrip(
name: string,
passed: libsql.InValue,
expected: libsql.Value,
intMode?: libsql.IntMode,
): void {
test(
name,
withClient(
async (c) => {
const rs = await c.execute({
sql: "SELECT ?",
args: [passed],
});
expect(rs.rows[0][0]).toStrictEqual(expected);
},
{ intMode },
),
);
}
function testRoundtripError(
name: string,
passed: libsql.InValue,
expectedError: unknown,
intMode?: libsql.IntMode,
): void {
test(
name,
withClient(
async (c) => {
await expect(
c.execute({
sql: "SELECT ?",
args: [passed],
}),
).rejects.toBeInstanceOf(expectedError);
},
{ intMode },
),
);
}
testRoundtrip("string", "boomerang", "boomerang");
testRoundtrip("string with weird characters", "a\n\r\t ", "a\n\r\t ");
testRoundtrip(
"string with unicode",
"žluťoučký kůň úpěl ďábelské ódy",
"žluťoučký kůň úpěl ďábelské ódy",
);
describe("number", () => {
const intModes: Array<libsql.IntMode> = ["number", "bigint", "string"];
for (const intMode of intModes) {
testRoundtrip("zero", 0, 0, intMode);
testRoundtrip("integer", -2023, -2023, intMode);
testRoundtrip("float", 12.345, 12.345, intMode);
testRoundtrip("large positive float", 1e18, 1e18, intMode);
testRoundtrip("large negative float", -1e18, -1e18, intMode);
testRoundtrip(
"MAX_VALUE",
Number.MAX_VALUE,
Number.MAX_VALUE,
intMode,
);
testRoundtrip(
"-MAX_VALUE",
-Number.MAX_VALUE,
-Number.MAX_VALUE,
intMode,
);
testRoundtrip(
"MIN_VALUE",
Number.MIN_VALUE,
Number.MIN_VALUE,
intMode,
);
}
});
describe("bigint", () => {
describe("'number' int mode", () => {
testRoundtrip("zero integer", 0n, 0, "number");
testRoundtrip("small integer", -42n, -42, "number");
testRoundtrip(
"largest safe integer",
9007199254740991n,
9007199254740991,
"number",
);
testRoundtripError(
"smallest unsafe integer",
9007199254740992n,
RangeError,
"number",
);
testRoundtripError(
"large unsafe integer",
-1152921504594532842n,
RangeError,
"number",
);
});
describe("'bigint' int mode", () => {
testRoundtrip("zero integer", 0n, 0n, "bigint");
testRoundtrip("small integer", -42n, -42n, "bigint");
testRoundtrip(
"large positive integer",
1152921504608088318n,
1152921504608088318n,
"bigint",
);
testRoundtrip(
"large negative integer",
-1152921504594532842n,
-1152921504594532842n,
"bigint",
);
testRoundtrip(
"largest positive integer",
9223372036854775807n,
9223372036854775807n,
"bigint",
);
testRoundtrip(
"largest negative integer",
-9223372036854775808n,
-9223372036854775808n,
"bigint",
);
});
describe("'string' int mode", () => {
testRoundtrip("zero integer", 0n, "0", "string");
testRoundtrip("small integer", -42n, "-42", "string");
testRoundtrip(
"large positive integer",
1152921504608088318n,
"1152921504608088318",
"string",
);
testRoundtrip(
"large negative integer",
-1152921504594532842n,
"-1152921504594532842",
"string",
);
testRoundtrip(
"largest positive integer",
9223372036854775807n,
"9223372036854775807",
"string",
);
testRoundtrip(
"largest negative integer",
-9223372036854775808n,
"-9223372036854775808",
"string",
);
});
});
const buf = new ArrayBuffer(256);
const array = new Uint8Array(buf);
for (let i = 0; i < 256; ++i) {
array[i] = i ^ 0xab;
}
testRoundtrip("ArrayBuffer", buf, buf);
testRoundtrip("Uint8Array", array, buf);
testRoundtrip("null", null, null);
testRoundtrip("true", true, 1n, "bigint");
testRoundtrip("false", false, 0n, "bigint");
testRoundtrip("true", true, 1, "number");
testRoundtrip("false", false, 0, "number");
testRoundtrip("true", true, "1", "string");
testRoundtrip("false", false, "0", "string");
testRoundtrip("true", true, 1);
testRoundtrip("false", false, 0);
testRoundtrip(
"Date",
new Date("2023-01-02T12:34:56Z"),
1672662896000,
"bigint",
);
// @ts-expect-error
testRoundtripError("undefined produces error", undefined, TypeError);
testRoundtripError("NaN produces error", NaN, RangeError);
testRoundtripError("Infinity produces error", Infinity, RangeError);
testRoundtripError(
"large bigint produces error",
-1267650600228229401496703205376n,
RangeError,
);
test(
"max 64-bit bigint",
withClient(async (c) => {
const rs = await c.execute({
sql: "SELECT ?||''",
args: [9223372036854775807n],
});
expect(rs.rows[0][0]).toStrictEqual("9223372036854775807");
}),
);
test(
"min 64-bit bigint",
withClient(async (c) => {
const rs = await c.execute({
sql: "SELECT ?||''",
args: [-9223372036854775808n],
});
expect(rs.rows[0][0]).toStrictEqual("-9223372036854775808");
}),
);
});
describe("ResultSet.toJSON()", () => {
test(
"simple result set",
withClient(async (c) => {
const rs = await c.execute("SELECT 1 AS a");
const json = rs.toJSON();
expect(
json["lastInsertRowid"] === null ||
json["lastInsertRowid"] === "0",
).toBe(true);
expect(json["columns"]).toStrictEqual(["a"]);
expect(json["columnTypes"]).toStrictEqual([""]);
expect(json["rows"]).toStrictEqual([[1]]);
expect(json["rowsAffected"]).toStrictEqual(0);
const str = JSON.stringify(rs);
expect(
str ===
'{"columns":["a"],"columnTypes":[""],"rows":[[1]],"rowsAffected":0,"lastInsertRowid":null}' ||
str ===
'{"columns":["a"],"columnTypes":[""],"rows":[[1]],"rowsAffected":0,"lastInsertRowid":"0"}',
).toBe(true);
}),
);
test(
"lastInsertRowid",
withClient(async (c) => {
await c.execute("DROP TABLE IF EXISTS t");
await c.execute("CREATE TABLE t (id INTEGER PRIMARY KEY NOT NULL)");
const rs = await c.execute("INSERT INTO t VALUES (12345)");
expect(rs.toJSON()).toStrictEqual({
columns: [],
columnTypes: [],
rows: [],
rowsAffected: 1,
lastInsertRowid: "12345",
});
}),
);
test(
"computed values",
withClient(async (c) => {
const rs = await c.execute(
"SELECT 42 AS integer, 0.5 AS float, NULL AS \"null\", 'foo' AS text, X'626172' AS blob",
);
const json = rs.toJSON();
expect(json["columns"]).toStrictEqual([
"integer",
"float",
"null",
"text",
"blob",
]);
expect(json["columnTypes"]).toStrictEqual(["", "", "", "", ""]);
expect(json["rows"]).toStrictEqual([
[42, 0.5, null, "foo", "YmFy"],
]);
}),
);
(hasHrana2 ? test : test.skip)(
"row values",
withClient(async (c) => {
await c.execute("DROP TABLE IF EXISTS t");
await c.execute(
"CREATE TABLE t (i INTEGER, f FLOAT, t TEXT, b BLOB)",
);
await c.execute("INSERT INTO t VALUES (42, 0.5, 'foo', X'626172')");
const rs = await c.execute("SELECT i, f, t, b FROM t LIMIT 1");
const json = rs.toJSON();
expect(json["columns"]).toStrictEqual(["i", "f", "t", "b"]);
expect(json["columnTypes"]).toStrictEqual([
"INTEGER",
"FLOAT",
"TEXT",
"BLOB",
]);
expect(json["rows"]).toStrictEqual([[42, 0.5, "foo", "YmFy"]]);
}),
);
test(
"bigint row value",
withClient(
async (c) => {
const rs = await c.execute("SELECT 42");
const json = rs.toJSON();
expect(json["rows"]).toStrictEqual([["42"]]);
},
{ intMode: "bigint" },
),
);
});
describe("arguments", () => {
test(
"? arguments",
withClient(async (c) => {
const rs = await c.execute({
sql: "SELECT ?, ?",
args: ["one", "two"],
});
expect(Array.from(rs.rows[0])).toStrictEqual(["one", "two"]);
}),
);
(!isFile ? test : test.skip)(
"?NNN arguments",
withClient(async (c) => {
const rs = await c.execute({
sql: "SELECT ?2, ?3, ?1",
args: ["one", "two", "three"],
});
expect(Array.from(rs.rows[0])).toStrictEqual([
"two",
"three",
"one",
]);
}),
);
(!isFile ? test : test.skip)(
"?NNN arguments with holes",
withClient(async (c) => {
const rs = await c.execute({
sql: "SELECT ?3, ?1",
args: ["one", "two", "three"],
});
expect(Array.from(rs.rows[0])).toStrictEqual(["three", "one"]);
}),
);
(!isFile ? test : test.skip)(
"?NNN and ? arguments",
withClient(async (c) => {
const rs = await c.execute({
sql: "SELECT ?2, ?, ?3",
args: ["one", "two", "three"],
});
expect(Array.from(rs.rows[0])).toStrictEqual([
"two",
"three",
"three",
]);
}),
);
for (const sign of [":", "@", "$"]) {
test(
`${sign}AAAA arguments`,
withClient(async (c) => {
const rs = await c.execute({
sql: `SELECT ${sign}b, ${sign}a`,
args: { a: "one", [`${sign}b`]: "two" },
});
expect(Array.from(rs.rows[0])).toStrictEqual(["two", "one"]);
}),
);
test(
`${sign}AAAA arguments used multiple times`,
withClient(async (c) => {
const rs = await c.execute({
sql: `SELECT ${sign}b, ${sign}a, ${sign}b || ${sign}a`,
args: { a: "one", [`${sign}b`]: "two" },
});
expect(Array.from(rs.rows[0])).toStrictEqual([
"two",
"one",
"twoone",
]);
}),
);
test(
`${sign}AAAA arguments and ?NNN arguments`,
withClient(async (c) => {
const rs = await c.execute({
sql: `SELECT ${sign}b, ${sign}a, ?1`,
args: { a: "one", [`${sign}b`]: "two" },
});
expect(Array.from(rs.rows[0])).toStrictEqual([
"two",
"one",
"two",
]);
}),
);
}
});
describe("batch()", () => {
test(
"multiple queries",
withClient(async (c) => {
const rss = await c.batch(
[
"SELECT 1+1",
"SELECT 1 AS one, 2 AS two",
{ sql: "SELECT ?", args: ["boomerang"] },
{ sql: "VALUES (?), (?)", args: ["big", "ben"] },
],
"read",
);
expect(rss.length).toStrictEqual(4);
const [rs0, rs1, rs2, rs3] = rss;
expect(rs0.rows.length).toStrictEqual(1);
expect(Array.from(rs0.rows[0])).toStrictEqual([2]);
expect(rs1.rows.length).toStrictEqual(1);
expect(Array.from(rs1.rows[0])).toStrictEqual([1, 2]);
expect(rs2.rows.length).toStrictEqual(1);
expect(Array.from(rs2.rows[0])).toStrictEqual(["boomerang"]);
expect(rs3.rows.length).toStrictEqual(2);
expect(Array.from(rs3.rows[0])).toStrictEqual(["big"]);
expect(Array.from(rs3.rows[1])).toStrictEqual(["ben"]);
}),
);
test(
"statements are executed sequentially",
withClient(async (c) => {
const rss = await c.batch(
[
/* 0 */ "DROP TABLE IF EXISTS t",
/* 1 */ "CREATE TABLE t (a, b)",
/* 2 */ "INSERT INTO t VALUES (1, 'one')",
/* 3 */ "SELECT * FROM t ORDER BY a",
/* 4 */ "INSERT INTO t VALUES (2, 'two')",
/* 5 */ "SELECT * FROM t ORDER BY a",
/* 6 */ "DROP TABLE t",
],
"write",
);
expect(rss.length).toStrictEqual(7);
expect(rss[3].rows).toEqual([{ a: 1, b: "one" }]);
expect(rss[5].rows).toEqual([
{ a: 1, b: "one" },
{ a: 2, b: "two" },
]);
}),
);
test(
"statements are executed in a transaction",
withClient(async (c) => {
await c.batch(
[
"DROP TABLE IF EXISTS t1",
"DROP TABLE IF EXISTS t2",
"CREATE TABLE t1 (a)",
"CREATE TABLE t2 (a)",
],
"write",
);
const n = 100;
const promises = [] as Array<any>;
for (let i = 0; i < n; ++i) {
const ii = i;
promises.push(
(async () => {
const rss = await c.batch(
[
{
sql: "INSERT INTO t1 VALUES (?)",
args: [ii],
},
{
sql: "INSERT INTO t2 VALUES (?)",
args: [ii * 10],
},
"SELECT SUM(a) FROM t1",
"SELECT SUM(a) FROM t2",
],
"write",
);
const sum1 = rss[2].rows[0][0] as number;
const sum2 = rss[3].rows[0][0] as number;
expect(sum2).toStrictEqual(sum1 * 10);
})(),
);
}
await Promise.all(promises);
const rs1 = await c.execute("SELECT SUM(a) FROM t1");
expect(rs1.rows[0][0]).toStrictEqual((n * (n - 1)) / 2);
const rs2 = await c.execute("SELECT SUM(a) FROM t2");
expect(rs2.rows[0][0]).toStrictEqual(((n * (n - 1)) / 2) * 10);
}),
10000,
);
test(
"error in batch",
withClient(async (c) => {
await expect(
c.batch(["SELECT 1+1", "SELECT foobar"], "read"),
).rejects.toBeLibsqlError();
}),
);
test(
"error in batch rolls back transaction",
withClient(async (c) => {
await c.execute("DROP TABLE IF EXISTS t");
await c.execute("CREATE TABLE t (a)");
await c.execute("INSERT INTO t VALUES ('one')");
await expect(
c.batch(
[
"INSERT INTO t VALUES ('two')",
"SELECT foobar",
"INSERT INTO t VALUES ('three')",
],
"write",
),
).rejects.toBeLibsqlError();
const rs = await c.execute("SELECT COUNT(*) FROM t");
expect(rs.rows[0][0]).toStrictEqual(1);
}),
);
test(
"batch error reports statement index - error at index 0",
withClient(async (c) => {
try {
await c.batch(
["SELECT invalid_column", "SELECT 1", "SELECT 2"],
"read",
);
throw new Error("Expected batch to fail");
} catch (e: any) {
expect(e.name).toBe("LibsqlBatchError");
expect(e.statementIndex).toBe(0);
expect(e.code).toBeDefined();
}
}),
);
test(
"batch error reports statement index - error at index 1",
withClient(async (c) => {
try {
await c.batch(
["SELECT 1", "SELECT invalid_column", "SELECT 2"],
"read",
);
throw new Error("Expected batch to fail");
} catch (e: any) {
expect(e.name).toBe("LibsqlBatchError");
expect(e.statementIndex).toBe(1);
expect(e.code).toBeDefined();
}
}),
);
test(
"batch error reports statement index - error at index 2",
withClient(async (c) => {
try {
await c.batch(
["SELECT 1", "SELECT 2", "SELECT invalid_column"],
"read",
);
throw new Error("Expected batch to fail");
} catch (e: any) {
expect(e.name).toBe("LibsqlBatchError");
expect(e.statementIndex).toBe(2);
expect(e.code).toBeDefined();
}
}),
);
test(
"batch error with write mode reports statement index",
withClient(async (c) => {
await c.execute("DROP TABLE IF EXISTS t");
await c.execute("CREATE TABLE t (a UNIQUE)");
await c.execute("INSERT INTO t VALUES (1)");
try {
await c.batch(
[
"INSERT INTO t VALUES (2)",
"INSERT INTO t VALUES (3)",
"INSERT INTO t VALUES (1)", // Duplicate, will fail
"INSERT INTO t VALUES (4)",
],
"write",
);
throw new Error("Expected batch to fail");
} catch (e: any) {
expect(e.name).toBe("LibsqlBatchError");
expect(e.statementIndex).toBe(2);
expect(e.code).toBeDefined();
}
// Verify rollback happened
const rs = await c.execute("SELECT COUNT(*) FROM t");
expect(rs.rows[0][0]).toBe(1);
}),
);
test(
"batch error in in-memory database reports statement index",
withInMemoryClient(async (c) => {
await c.execute("CREATE TABLE t (a)");
try {
await c.batch(
[
"INSERT INTO t VALUES (1)",
"SELECT invalid_column FROM t",
"INSERT INTO t VALUES (2)",
],
"write",
);
throw new Error("Expected batch to fail");
} catch (e: any) {
expect(e.name).toBe("LibsqlBatchError");
expect(e.statementIndex).toBe(1);
expect(e.code).toBeDefined();
}
}),
);
test(
"batch with a lot of different statements",
withClient(async (c) => {
const stmts = [] as Array<any>;
for (let i = 0; i < 1000; ++i) {
stmts.push(`SELECT ${i}`);
}
const rss = await c.batch(stmts, "read");
for (let i = 0; i < stmts.length; ++i) {
expect(rss[i].rows[0][0]).toStrictEqual(i);
}
}),
);
test(
"batch with a lot of the same statements",
withClient(async (c) => {
const n = 20;
const m = 200;
const stmts = [] as Array<any>;
for (let i = 0; i < n; ++i) {
for (let j = 0; j < m; ++j) {
stmts.push({ sql: `SELECT ?, ${j}`, args: [i] });
}
}
const rss = await c.batch(stmts, "read");
for (let i = 0; i < n; ++i) {
for (let j = 0; j < m; ++j) {
const rs = rss[i * m + j];
expect(rs.rows[0][0]).toStrictEqual(i);
expect(rs.rows[0][1]).toStrictEqual(j);
}
}
}),
);
test(
"deferred batch",
withClient(async (c) => {
const rss = await c.batch(
[
"SELECT 1+1",
"DROP TABLE IF EXISTS t",
"CREATE TABLE t (a)",
"INSERT INTO t VALUES (21) RETURNING 2*a",
],
"deferred",
);
expect(rss.length).toStrictEqual(4);
const [rs0, _rs1, _rs2, rs3] = rss;
expect(rs0.rows.length).toStrictEqual(1);
expect(Array.from(rs0.rows[0])).toStrictEqual([2]);
expect(rs3.rows.length).toStrictEqual(1);
expect(Array.from(rs3.rows[0])).toStrictEqual([42]);
}),
);
(hasHrana3 ? test : test.skip)(
"ROLLBACK statement stops execution of batch",
withClient(async (c) => {
await c.execute("DROP TABLE IF EXISTS t");
await c.execute("CREATE TABLE t (a)");
await expect(
c.batch(
[
"INSERT INTO t VALUES (1), (2), (3)",
"ROLLBACK",
"INSERT INTO t VALUES (4), (5)",
],
"write",
),
).rejects.toBeLibsqlError("TRANSACTION_CLOSED");
const rs = await c.execute("SELECT COUNT(*) FROM t");
expect(rs.rows[0][0]).toStrictEqual(0);
}),
);
});
describe("transaction()", () => {
test(
"query multiple rows",
withClient(async (c) => {
const txn = await c.transaction("read");
const rs = await txn.execute(
"VALUES (1, 'one'), (2, 'two'), (3, 'three')",
);
expect(rs.columns.length).toStrictEqual(2);
expect(rs.columnTypes.length).toStrictEqual(2);
expect(rs.rows.length).toStrictEqual(3);
expect(Array.from(rs.rows[0])).toStrictEqual([1, "one"]);
expect(Array.from(rs.rows[1])).toStrictEqual([2, "two"]);
expect(Array.from(rs.rows[2])).toStrictEqual([3, "three"]);
txn.close();
}),
);
test(
"commit()",
withClient(async (c) => {
await c.batch(
["DROP TABLE IF EXISTS t", "CREATE TABLE t (a)"],
"write",
);
const txn = await c.transaction("write");
await txn.execute("INSERT INTO t VALUES ('one')");
await txn.execute("INSERT INTO t VALUES ('two')");
expect(txn.closed).toStrictEqual(false);
await txn.commit();
expect(txn.closed).toStrictEqual(true);
const rs = await c.execute("SELECT COUNT(*) FROM t");
expect(rs.rows[0][0]).toStrictEqual(2);
await expect(txn.execute("SELECT 1")).rejects.toBeLibsqlError(
"TRANSACTION_CLOSED",
);
}),
);
test(
"rollback()",
withClient(async (c) => {
await c.batch(
["DROP TABLE IF EXISTS t", "CREATE TABLE t (a)"],
"write",
);
const txn = await c.transaction("write");
await txn.execute("INSERT INTO t VALUES ('one')");
await txn.execute("INSERT INTO t VALUES ('two')");
expect(txn.closed).toStrictEqual(false);
await txn.rollback();
expect(txn.closed).toStrictEqual(true);
const rs = await c.execute("SELECT COUNT(*) FROM t");
expect(rs.rows[0][0]).toStrictEqual(0);
await expect(txn.execute("SELECT 1")).rejects.toBeLibsqlError(
"TRANSACTION_CLOSED",
);
}),
);
test(
"close()",
withClient(async (c) => {
await c.batch(
["DROP TABLE IF EXISTS t", "CREATE TABLE t (a)"],
"write",
);
const txn = await c.transaction("write");
await txn.execute("INSERT INTO t VALUES ('one')");
expect(txn.closed).toStrictEqual(false);
txn.close();
expect(txn.closed).toStrictEqual(true);
const rs = await c.execute("SELECT COUNT(*) FROM t");
expect(rs.rows[0][0]).toStrictEqual(0);
await expect(txn.execute("SELECT 1")).rejects.toBeLibsqlError(
"TRANSACTION_CLOSED",
);
}),
);
test(
"error does not rollback",
withClient(async (c) => {
await c.batch(
["DROP TABLE IF EXISTS t", "CREATE TABLE t (a)"],
"write",
);
const txn = await c.transaction("write");
await expect(txn.execute("SELECT foo")).rejects.toBeLibsqlError();
await txn.execute("INSERT INTO t VALUES ('one')");
await expect(txn.execute("SELECT bar")).rejects.toBeLibsqlError();
await txn.commit();
const rs = await c.execute("SELECT COUNT(*) FROM t");
expect(rs.rows[0][0]).toStrictEqual(1);
}),
);
(hasHrana3 ? test : test.skip)(
"ROLLBACK statement stops execution of transaction",
withClient(async (c) => {
await c.execute("DROP TABLE IF EXISTS t");
await c.execute("CREATE TABLE t (a)");
const txn = await c.transaction("write");
const prom1 = txn.execute("INSERT INTO t VALUES (1), (2), (3)");
const prom2 = txn.execute("ROLLBACK");
const prom3 = txn.execute("INSERT INTO t VALUES (4), (5)");
await prom1;
await prom2;
await expect(prom3).rejects.toBeLibsqlError("TRANSACTION_CLOSED");
await expect(txn.commit()).rejects.toBeLibsqlError();
txn.close();
const rs = await c.execute("SELECT COUNT(*) FROM t");
expect(rs.rows[0][0]).toStrictEqual(0);
}),
);
(hasHrana3 ? test : test.skip)(
"OR ROLLBACK statement stops execution of transaction",
withClient(async (c) => {
await c.execute("DROP TABLE IF EXISTS t");
await c.execute("CREATE TABLE t (a UNIQUE)");
const txn = await c.transaction("write");
const prom1 = txn.execute("INSERT INTO t VALUES (1), (2), (3)");
const prom2 = txn.execute("INSERT OR ROLLBACK INTO t VALUES (1)");
const prom3 = txn.execute("INSERT INTO t VALUES (4), (5)");
await prom1;
await expect(prom2).rejects.toBeLibsqlError();
await expect(prom3).rejects.toBeLibsqlError("TRANSACTION_CLOSED");
await expect(txn.commit()).rejects.toBeLibsqlError();
txn.close();
const rs = await c.execute("SELECT COUNT(*) FROM t");
expect(rs.rows[0][0]).toStrictEqual(0);
}),
);
(hasHrana3 ? test : test.skip)(
"OR ROLLBACK as the first statement stops execution of transaction",
withClient(async (c) => {
await c.execute("DROP TABLE IF EXISTS t");
await c.execute("CREATE TABLE t (a UNIQUE)");
await c.execute("INSERT INTO t VALUES (1), (2), (3)");
const txn = await c.transaction("write");
const prom1 = txn.execute("INSERT OR ROLLBACK INTO t VALUES (1)");
const prom2 = txn.execute("INSERT INTO t VALUES (4), (5)");
await expect(prom1).rejects.toBeLibsqlError();
await expect(prom2).rejects.toBeLibsqlError("TRANSACTION_CLOSED");
await expect(txn.commit()).rejects.toBeLibsqlError();
txn.close();
const rs = await c.execute("SELECT COUNT(*) FROM t");
expect(rs.rows[0][0]).toStrictEqual(3);
}),
);
test(
"commit empty",
withClient(async (c) => {
const txn = await c.transaction("read");
await txn.commit();
}),
);
test(
"rollback empty",
withClient(async (c) => {
const txn = await c.transaction("read");
await txn.rollback();
}),
);
describe("batch()", () => {
test(
"as the first operation on transaction",
withClient(async (c) => {
const txn = await c.transaction("write");
await txn.batch([
"DROP TABLE IF EXISTS t",
"CREATE TABLE t (a)",
{ sql: "INSERT INTO t VALUES (?)", args: [1] },
{ sql: "INSERT INTO t VALUES (?)", args: [2] },
{ sql: "INSERT INTO t VALUES (?)", args: [4] },
]);
const rs = await txn.execute("SELECT SUM(a) FROM t");
expect(rs.rows[0][0]).toStrictEqual(7);
txn.close();
}),
);
test(
"as the second operation on transaction",
withClient(async (c) => {
const txn = await c.transaction("write");
await txn.execute("DROP TABLE IF EXISTS t");
await txn.batch([
"CREATE TABLE t (a)",
{ sql: "INSERT INTO t VALUES (?)", args: [1] },
{ sql: "INSERT INTO t VALUES (?)", args: [2] },
{ sql: "INSERT INTO t VALUES (?)", args: [4] },
]);
const rs = await txn.execute("SELECT SUM(a) FROM t");
expect(rs.rows[0][0]).toStrictEqual(7);
txn.close();
}),
);
test(
"after error, further statements are not executed",
withClient(async (c) => {
const txn = await c.transaction("write");
await expect(
txn.batch([
"DROP TABLE IF EXISTS t",
"CREATE TABLE t (a UNIQUE)",
"INSERT INTO t VALUES (1), (2), (4)",
"INSERT INTO t VALUES (1)",
"INSERT INTO t VALUES (8), (16)",
]),
).rejects.toBeLibsqlError();
const rs = await txn.execute("SELECT SUM(a) FROM t");
expect(rs.rows[0][0]).toStrictEqual(7);
await txn.commit();
}),
);
test(
"batch error reports statement index in transaction",
withClient(async (c) => {
const txn = await c.transaction("write");
try {
await txn.batch([
"DROP TABLE IF EXISTS t",
"CREATE TABLE t (a UNIQUE)",
"INSERT INTO t VALUES (1), (2), (3)",
"INSERT INTO t VALUES (1)", // Duplicate, will fail at index 3
"INSERT INTO t VALUES (4), (5)",
]);
throw new Error("Expected batch to fail");
} catch (e: any) {
expect(e.name).toBe("LibsqlBatchError");
expect(e.statementIndex).toBe(3);
expect(e.code).toBeDefined();
}
// Transaction should still be usable after batch error
const rs = await txn.execute("SELECT SUM(a) FROM t");
expect(rs.rows[0][0]).toBe(6);
await txn.commit();
}),
);
test(
"batch error reports statement index - error at first statement in transaction",
withClient(async (c) => {
const txn = await c.transaction("read");
try {
await txn.batch([
"SELECT invalid_column",
"SELECT 1",
"SELECT 2",
]);
throw new Error("Expected batch to fail");
} catch (e: any) {
expect(e.name).toBe("LibsqlBatchError");
expect(e.statementIndex).toBe(0);
expect(e.code).toBeDefined();
}
txn.close();
}),
);
test(
"batch error reports statement index - error at middle statement in transaction",
withClient(async (c) => {
const txn = await c.transaction("read");
try {
await txn.batch([
"SELECT 1",
"SELECT 2",
"SELECT invalid_column",
"SELECT 3",
]);
throw new Error("Expected batch to fail");
} catch (e: any) {
expect(e.name).toBe("LibsqlBatchError");
expect(e.statementIndex).toBe(2);
expect(e.code).toBeDefined();
}
txn.close();
}),
);
});
(hasHrana2 ? describe : describe.skip)("executeMultiple()", () => {
test(
"as the first operation on transaction",
withClient(async (c) => {
const txn = await c.transaction("write");
await txn.executeMultiple(`
DROP TABLE IF EXISTS t;
CREATE TABLE t (a);
INSERT INTO t VALUES (1), (2), (4), (8);
`);
const rs = await txn.execute("SELECT SUM(a) FROM t");
expect(rs.rows[0][0]).toStrictEqual(15);
txn.close();
}),
);
test(
"as the second operation on transaction",
withClient(async (c) => {
const txn = await c.transaction("write");
await txn.execute("DROP TABLE IF EXISTS t");
await txn.executeMultiple(`
CREATE TABLE t (a);
INSERT INTO t VALUES (1), (2), (4), (8);
`);
const rs = await txn.execute("SELECT SUM(a) FROM t");
expect(rs.rows[0][0]).toStrictEqual(15);
txn.close();
}),
);
test(
"after error, further statements are not executed",
withClient(async (c) => {
const txn = await c.transaction("write");
await expect(
txn.executeMultiple(`
DROP TABLE IF EXISTS t;
CREATE TABLE t (a UNIQUE);
INSERT INTO t VALUES (1), (2), (4);
INSERT INTO t VALUES (1);
INSERT INTO t VALUES (8), (16);
`),
).rejects.toBeLibsqlError();
const rs = await txn.execute("SELECT SUM(a) FROM t");
expect(rs.rows[0][0]).toStrictEqual(7);
await txn.commit();
}),
);
});
});
(hasHrana2 ? describe : describe.skip)("executeMultiple()", () => {
test(
"multiple statements",
withClient(async (c) => {
await c.executeMultiple(`
DROP TABLE IF EXISTS t;
CREATE TABLE t (a);
INSERT INTO t VALUES (1), (2), (4), (8);
`);
const rs = await c.execute("SELECT SUM(a) FROM t");
expect(rs.rows[0][0]).toStrictEqual(15);
}),
);
test(
"after an error, statements are not executed",
withClient(async (c) => {
await expect(
c.executeMultiple(`
DROP TABLE IF EXISTS t;
CREATE TABLE t (a);
INSERT INTO t VALUES (1), (2), (4);
INSERT INTO t VALUES (foo());
INSERT INTO t VALUES (8), (16);
`),
).rejects.toBeLibsqlError();
const rs = await c.execute("SELECT SUM(a) FROM t");
expect(rs.rows[0][0]).toStrictEqual(7);
}),
);
test(
"manual transaction control statements",
withClient(async (c) => {
await c.executeMultiple(`
DROP TABLE IF EXISTS t;
CREATE TABLE t (a);
BEGIN;
INSERT INTO t VALUES (1), (2), (4);
INSERT INTO t VALUES (8), (16);
COMMIT;
`);
const rs = await c.execute("SELECT SUM(a) FROM t");
expect(rs.rows[0][0]).toStrictEqual(31);
}),
);
test(
"error rolls back a manual transaction",
withClient(async (c) => {
await expect(
c.executeMultiple(`
DROP TABLE IF EXISTS t;
CREATE TABLE t (a);
INSERT INTO t VALUES (0);
BEGIN;
INSERT INTO t VALUES (1), (2), (4);
INSERT INTO t VALUES (foo());
INSERT INTO t VALUES (8), (16);
COMMIT;
`),
).rejects.toBeLibsqlError();
const rs = await c.execute("SELECT SUM(a) FROM t");
expect(rs.rows[0][0]).toStrictEqual(0);
}),
);
});
(hasNetworkErrors ? describe : describe.skip)("network errors", () => {
const testCases = [
{ title: "WebSocket close", sql: ".close_ws" },
{ title: "TCP close", sql: ".close_tcp" },
];
for (const { title, sql } of testCases) {
test(
`${title} in execute()`,
withClient(async (c) => {
await expect(c.execute(sql)).rejects.toBeLibsqlError(
"HRANA_WEBSOCKET_ERROR",
);
expect((await c.execute("SELECT 42")).rows[0][0]).toStrictEqual(
42,
);
}),
);
test(
`${title} in transaction()`,
withClient(async (c) => {
const txn = await c.transaction("read");
await expect(txn.execute(sql)).rejects.toBeLibsqlError(
"HRANA_WEBSOCKET_ERROR",
);
await expect(txn.commit()).rejects.toBeLibsqlError(
"TRANSACTION_CLOSED",
);
txn.close();
expect((await c.execute("SELECT 42")).rows[0][0]).toStrictEqual(
42,
);
}),
);
test(
`${title} in batch()`,
withClient(async (c) => {
await expect(
c.batch(["SELECT 42", sql, "SELECT 24"], "read"),
).rejects.toBeLibsqlError("HRANA_WEBSOCKET_ERROR");
expect((await c.execute("SELECT 42")).rows[0][0]).toStrictEqual(
42,
);
}),
);
}
});
(isHttp ? test : test.skip)("custom fetch", async () => {
let fetchCalledCount = 0;
function customFetch(request: Request): Promise<Response> {
fetchCalledCount += 1;
return fetch(request);
}
const c = createClient({ ...config, fetch: customFetch });
try {
const rs = await c.execute("SELECT 42");
expect(rs.rows[0][0]).toStrictEqual(42);
expect(fetchCalledCount).toBeGreaterThan(0);
} finally {
c.close();
}
});
(isFile ? test : test.skip)("raw error codes", async () => {
const c = createClient(config);
try {
await expect(c.execute("NOT A VALID SQL")).rejects.toThrow(
expect.toBeLibsqlError({ code: "SQLITE_ERROR", rawCode: 1 }),
);
} finally {
c.close();
}
});
// Test to verify constraint error codes
// - code: base error code (e.g., SQLITE_CONSTRAINT) - consistent across local and remote
// - extendedCode: extended error code (e.g., SQLITE_CONSTRAINT_PRIMARYKEY) - available when supported
(server !== "test_v1" ? describe : describe.skip)(
"constraint error codes",
() => {
test(
"PRIMARY KEY constraint violation",
withClient(async (c) => {
await c.execute("DROP TABLE IF EXISTS t_pk_test");
await c.execute(
"CREATE TABLE t_pk_test (id INTEGER PRIMARY KEY, name TEXT)",
);
await c.execute("INSERT INTO t_pk_test VALUES (1, 'first')");
try {
await c.execute(
"INSERT INTO t_pk_test VALUES (1, 'duplicate')",
);
throw new Error("Expected PRIMARY KEY constraint error");
} catch (e: any) {
expect(e.code).toBe("SQLITE_CONSTRAINT");
if (e.extendedCode !== undefined) {
expect(e.extendedCode).toBe(
"SQLITE_CONSTRAINT_PRIMARYKEY",
);
}
}
}),
);
test(
"UNIQUE constraint violation",
withClient(async (c) => {
await c.execute("DROP TABLE IF EXISTS t_unique_test");
await c.execute(
"CREATE TABLE t_unique_test (id INTEGER, name TEXT UNIQUE)",
);
await c.execute(
"INSERT INTO t_unique_test VALUES (1, 'unique_name')",
);
try {
await c.execute(
"INSERT INTO t_unique_test VALUES (2, 'unique_name')",
);
throw new Error("Expected UNIQUE constraint error");
} catch (e: any) {
expect(e.code).toBe("SQLITE_CONSTRAINT");
if (e.extendedCode !== undefined) {
expect(e.extendedCode).toBe("SQLITE_CONSTRAINT_UNIQUE");
}
}
}),
);
},
);
(isSqld ? test : test.skip)("embedded replica test", async () => {
const remote = createClient(config);
const embedded = createClient({
...config,
url: "file:///tmp/local.db",
syncUrl: config.url,
});
await remote.execute("CREATE TABLE embedded(a)");
await embedded.sync();
let embedded1 = await embedded.execute("SELECT * FROM embedded");
expect(embedded1.columns).toStrictEqual(["a"]);
expect(embedded1.rows.length).toStrictEqual(0);
await remote.execute("INSERT INTO embedded VALUES (1), (2), (3)");
let embedded2 = await embedded.execute("SELECT * FROM embedded");
expect(embedded2.columns).toStrictEqual(["a"]);
expect(embedded2.rows.length).toStrictEqual(0);
let remote1 = await remote.execute("SELECT * FROM embedded");
expect(remote1.columns).toStrictEqual(["a"]);
expect(remote1.rows.length).toStrictEqual(3);
await embedded.sync();
let embedded3 = await embedded.execute("SELECT * FROM embedded");
expect(embedded3.columns).toStrictEqual(["a"]);
expect(embedded3.rows.length).toStrictEqual(3);
});
================================================
FILE: packages/libsql-client/src/__tests__/config.test.ts
================================================
import { expect } from "@jest/globals";
import "./helpers.js";
import { expandConfig } from "@libsql/core/config";
import { IntMode } from "@libsql/hrana-client";
describe("expandConfig - default tls values", () => {
const cases = [
{
name: "file",
preferHttp: true,
config: { url: "file://local.db" },
tls: true,
},
{
name: "http",
preferHttp: true,
config: { url: "http://localhost" },
tls: false,
},
{
name: "http (tls in config)",
preferHttp: true,
config: { url: "http://localhost", tls: true },
tls: true,
},
{
name: "http (tls in query)",
preferHttp: true,
config: { url: "http://localhost?tls=1", tls: false },
tls: true,
},
{
name: "http (no tls in query)",
preferHttp: true,
config: { url: "http://localhost?tls=0", tls: true },
tls: false,
},
{
name: "http (no tls in query)",
preferHttp: true,
config: { url: "http://localhost?tls=0", tls: true },
tls: false,
},
];
for (const { name, config, preferHttp, tls } of cases) {
test(name, () => {
expect(expandConfig(config, preferHttp).tls).toEqual(tls);
});
}
});
describe("expandConfig - invalid arguments", () => {
const cases = [
{
name: "in-memory with unsupported query params",
config: { url: "file::memory:?mode=memory" },
error: 'Unsupported URL query parameter "mode"',
},
{
name: "in-memory with tls param",
config: { url: "file::memory:?tls=0" },
error: 'Unsupported URL query parameter "tls"',
},
{
name: "in-memory with authToken param",
config: { url: "file::memory:?authToken=0" },
error: 'Unsupported URL query parameter "authToken"',
},
{
name: "invalid tls param value",
config: { url: "libsql://localhost?tls=2" },
error: 'Unknown value for the "tls" query argument: "2". Supported values are: ["0", "1"]',
},
{
name: "invalid scheme",
config: { url: "ftp://localhost" },
error: /The client supports only.*got "ftp:"/g,
},
{
name: "invalid intMode",
config: { url: "file://localhost", intMode: "decimal" as IntMode },
error: /Invalid value for intMode.*got "decimal"/g,
},
{
name: "fragment in uri",
config: { url: "file://localhost#fragment" },
error: "URL fragments are not supported",
},
{
name: "libsql, no tls, no port",
config: { url: "libsql://localhost?tls=0" },
error: "must specify an explicit port",
},
];
for (const { name, config, error } of cases) {
test(name, () => {
try {
expandConfig(config, false);
throw new Error("expand command must fail");
} catch (e: any) {
expect(e.message).toMatch(error);
}
});
}
});
describe("expandConfig - parsing of valid arguments", () => {
const cases = [
{
name: "in-memory",
config: { url: ":memory:" },
expanded: {
scheme: "file",
tls: false,
intMode: "number",
path: ":memory:",
concurrency: 20,
},
},
{
name: "in-memory with params",
config: { url: "file::memory:?cache=shared" },
expanded: {
scheme: "file",
tls: false,
intMode: "number",
path: ":memory:?cache=shared",
concurrency: 20,
},
},
{
name: "simple local file",
config: { url: "file://local.db" },
expanded: {
scheme: "file",
authority: { host: "local.db" },
tls: true,
intMode: "number",
path: "",
concurrency: 20,
},
},
{
name: "wss with path & port",
config: { url: "wss://localhost:8888/libsql/connect" },
expanded: {
scheme: "wss",
authority: { host: "localhost", port: 8888 },
tls: true,
intMode: "number",
path: "/libsql/connect",
concurrency: 20,
},
},
{
name: "wss with user info",
config: {
url: "wss://user:password@localhost:8888/libsql/connect",
concurrency: 20,
},
expanded: {
scheme: "wss",
authority: {
host: "localhost",
port: 8888,
userinfo: { username: "user", password: "password" },
},
tls: true,
intMode: "number",
path: "/libsql/connect",
concurrency: 20,
},
},
{
name: "override tls=0",
config: { url: "wss://localhost/libsql/connect?tls=0", tls: true },
expanded: {
scheme: "wss",
authority: { host: "localhost" },
tls: false,
intMode: "number",
path: "/libsql/connect",
concurrency: 20,
},
},
{
name: "override tls=1",
config: { url: "wss://localhost/libsql/connect?tls=1", tls: false },
expanded: {
scheme: "wss",
authority: { host: "localhost" },
tls: true,
intMode: "number",
path: "/libsql/connect",
concurrency: 20,
},
},
{
name: "override auth token",
config: {
url: "wss://localhost/libsql/connect?authToken=new",
authToken: "old",
},
expanded: {
authToken: "new",
scheme: "wss",
authority: { host: "localhost" },
tls: true,
intMode: "number",
path: "/libsql/connect",
concurrency: 20,
},
},
];
for (const { name, config, expanded } of cases) {
test(name, () => {
expect(expandConfig(config, false)).toEqual(expanded);
});
}
});
================================================
FILE: packages/libsql-client/src/__tests__/helpers.ts
================================================
import { expect } from "@jest/globals";
import type { MatcherFunction } from "expect";
import { LibsqlError } from "../node.js";
type CodeMatch = {
code: string;
rawCode: number;
};
const toBeLibsqlError: MatcherFunction<
[code?: string | CodeMatch, message?: RegExp]
> = function (actual, code?, messageRe?) {
const pass =
actual instanceof LibsqlError &&
isValidCode(actual, code) &&
(messageRe === undefined || actual.message.match(messageRe) !== null);
const message = (): string => {
const parts = [];
parts.push("expected ");
parts.push(this.utils.printReceived(actual));
parts.push(pass ? " not to be " : " to be ");
parts.push("an instance of LibsqlError");
if (code !== undefined) {
parts.push(" with error code ");
parts.push(this.utils.printExpected(code));
}
if (messageRe !== undefined) {
parts.push(" with error message matching ");
parts.push(this.utils.printExpected(messageRe));
}
return parts.join("");
};
return { pass, message };
};
const isValidCode = (error: LibsqlError, code?: string | CodeMatch) => {
if (code === undefined) {
return true;
}
if (typeof code === "string") {
return error.code === code;
}
return error.code === code.code && error.rawCode === code.rawCode;
};
expect.extend({ toBeLibsqlError });
declare module "expect" {
interface AsymmetricMatchers {
toBeLibsqlError(code?: string | CodeMatch, messageRe?: RegExp): void;
}
interface Matchers<R> {
toBeLibsqlError(code?: string | CodeMatch, messageRe?: RegExp): R;
}
}
================================================
FILE: packages/libsql-client/src/__tests__/mocks/handlers.ts
================================================
import { http, HttpResponse } from "msw";
export const handlers = [
http.get("http://fake-base-url.example.com/v1/jobs", () => {
return HttpResponse.json({
schema_version: 4,
migrations: [
{ job_id: 4, status: "WaitingDryRun" },
{ job_id: 3, status: "RunSuccess" },
{ job_id: 2, status: "RunSuccess" },
{ job_id: 1, status: "RunSuccess" },
],
});
}),
http.get(
"http://fake-base-url.example.com/v1/jobs/:job_id",
({ params }) => {
const { job_id } = params;
return HttpResponse.json({
job_id,
status: "RunSuccess",
progress: [
{
namespace: "b2ab4a64-402c-4bdf-a1e8-27ef33518cbd",
status: "RunSuccess",
error: null,
},
],
});
},
),
];
================================================
FILE: packages/libsql-client/src/__tests__/mocks/node.ts
================================================
import { setupServer } from "msw/node";
import { handlers } from "./handlers";
export const server = setupServer(...handlers);
================================================
FILE: packages/libsql-client/src/__tests__/uri.test.ts
================================================
import { expect } from "@jest/globals";
import "./helpers.js";
import { parseUri, encodeBaseUrl } from "@libsql/core/uri";
describe("parseUri()", () => {
test(":memory: uri", () => {
const cases = [
{ text: "file::memory:", path: ":memory:", query: undefined },
{
text: "file::memory:?cache=shared",
path: ":memory:",
query: { pairs: [{ key: "cache", value: "shared" }] },
},
];
for (const { text, path, query } of cases) {
expect(parseUri(text)).toEqual({ scheme: "file", path, query });
}
});
test("authority and path", () => {
const cases = [
{ text: "file://localhost", path: "" },
{ text: "file://localhost/", path: "/" },
{ text: "file://localhost/absolute/path", path: "/absolute/path" },
{ text: "file://localhost/k%C5%AF%C5%88", path: "/kůň" },
];
for (const { text, path } of cases) {
expect(parseUri(text)).toEqual({
scheme: "file",
authority: { host: "localhost" },
path,
});
}
});
test("empty authority and path", () => {
const cases = [
{ text: "file:///absolute/path", path: "/absolute/path" },
{ text: "file://", path: "" },
{ text: "file:///k%C5%AF%C5%88", path: "/kůň" },
];
for (const { text, path } of cases) {
expect(parseUri(text)).toEqual({
scheme: "file",
authority: { host: "" },
path,
});
}
});
test("no authority and path", () => {
const cases = [
{ text: "file:/absolute/path", path: "/absolute/path" },
{ text: "file:relative/path", path: "relative/path" },
{ text: "file:", path: "" },
{ text: "file:C:/path/to/file", path: "C:/path/to/file" },
{ text: "file:k%C5%AF%C5%88", path: "kůň" },
];
for (const { text, path } of cases) {
expect(parseUri(text)).toEqual({
scheme: "file",
path,
});
}
});
test("authority", () => {
const hosts = [
{ text: "localhost", host: "localhost" },
{ text: "domain.name", host: "domain.name" },
{ text: "some$weird.%20!name", host: "some$weird. !name" },
{ text: "1.20.255.99", host: "1.20.255.99" },
{ text: "[2001:4860:4802:32::a]", host: "2001:4860:4802:32::a" },
{ text: "%61", host: "a" },
{ text: "100%2e100%2e100%2e100", host: "100.100.100.100" },
{ text: "k%C5%AF%C5%88", host: "kůň" },
];
const ports = [
{ text: "", port: undefined },
{ text: ":", port: undefined },
{ text: ":0", port: 0 },
{ text: ":99", port: 99 },
{ text: ":65535", port: 65535 },
];
const userinfos = [
{ text: "", userinfo: undefined },
{ text: "@", userinfo: { username: "" } },
{ text: "alice@", userinfo: { username: "alice" } },
{
text: "alice:secret@",
userinfo: { username: "alice", password: "secret" },
},
{
text: "alice:sec:et@",
userinfo: { username: "alice", password: "sec:et" },
},
{ text: "alice%3Asecret@", userinfo: { username: "alice:secret" } },
{
text: "alice:s%65cret@",
userinfo: { username: "alice", password: "secret" },
},
];
for (const { text: hostText, host } of hosts) {
for (const { text: portText, port } of ports) {
for (const { text: userText, userinfo } of userinfos) {
const text = `http://${userText}${hostText}${portText}`;
expect(parseUri(text)).toEqual({
scheme: "http",
authority: { host, port, userinfo },
path: "",
});
}
}
}
});
test("query", () => {
const cases = [
{ text: "?", pairs: [] },
{ text: "?key=value", pairs: [{ key: "key", value: "value" }] },
{ text: "?&key=value", pairs: [{ key: "key", value: "value" }] },
{ text: "?key=value&&", pairs: [{ key: "key", value: "value" }] },
{ text: "?a", pairs: [{ key: "a", value: "" }] },
{ text: "?a=", pairs: [{ key: "a", value: "" }] },
{ text: "?=a", pairs: [{ key: "", value: "a" }] },
{ text: "?=", pairs: [{ key: "", value: "" }] },
{ text: "?a=b=c", pairs: [{ key: "a", value: "b=c" }] },
{
text: "?a=b&c=d",
pairs: [
{ key: "a", value: "b" },
{ key: "c", value: "d" },
],
},
{ text: "?a+b=c", pairs: [{ key: "a b", value: "c" }] },
{ text: "?a=b+c", pairs: [{ key: "a", value: "b c" }] },
{ text: "?a?b", pairs: [{ key: "a?b", value: "" }] },
{ text: "?%61=%62", pairs: [{ key: "a", value: "b" }] },
{ text: "?a%3db", pairs: [{ key: "a=b", value: "" }] },
{ text: "?a=%2b", pairs: [{ key: "a", value: "+" }] },
{ text: "?%2b=b", pairs: [{ key: "+", value: "b" }] },
{ text: "?a=b%26c", pairs: [{ key: "a", value: "b&c" }] },
{ text: "?a=k%C5%AF%C5%88", pairs: [{ key: "a", value: "kůň" }] },
];
for (const { text: queryText, pairs } of cases) {
const text = `file:${queryText}`;
expect(parseUri(text)).toEqual({
scheme: "file",
path: "",
query: { pairs },
});
}
});
test("fragment", () => {
const cases = [
{ text: "", fragment: undefined },
{ text: "#a", fragment: "a" },
{ text: "#a?b", fragment: "a?b" },
{ text: "#%61", fragment: "a" },
{ text: "#k%C5%AF%C5%88", fragment: "kůň" },
];
for (const { text: fragmentText, fragment } of cases) {
const text = `file:${fragmentText}`;
expect(parseUri(text)).toEqual({
scheme: "file",
path: "",
fragment,
});
}
});
test("parse errors", () => {
const cases = [
{ text: "", message: /format/ },
{ text: "foo", message: /format/ },
{ text: "foo.bar.com", message: /format/ },
{ text: "h$$p://localhost", message: /format/ },
{ text: "h%74%74p://localhost", message: /format/ },
{ text: "http://localhost:%38%38", message: /authority/ },
{ text: "file:k%C5%C5%88", message: /percent encoding/ },
];
for (const { text, message } of cases) {
expect(() => parseUri(text)).toThrow(
expect.toBeLibsqlError("URL_INVALID", message),
);
}
});
});
test("encodeBaseUrl()", () => {
const cases = [
{
scheme: "http",
host: "localhost",
path: "",
url: "http://localhost",
},
{
scheme: "http",
host: "localhost",
path: "/",
url: "http://localhost/",
},
{
scheme: "http",
host: "localhost",
port: 8080,
path: "",
url: "http://localhost:8080",
},
{
scheme: "http",
host: "localhost",
path: "/foo/bar",
url: "http://localhost/foo/bar",
},
{
scheme: "http",
host: "localhost",
path: "foo/bar",
url: "http://localhost/foo/bar",
},
{
scheme: "http",
host: "some.long.domain.name",
path: "",
url: "http://some.long.domain.name",
},
{
scheme: "http",
host: "1.2.3.4",
path: "",
url: "http://1.2.3.4",
},
{
scheme: "http",
host: "2001:4860:4802:32::a",
path: "",
url: "http://[2001:4860:4802:32::a]",
},
{
scheme: "http",
host: "localhost",
userinfo: { username: "alice", password: undefined },
path: "",
url: "http://alice@localhost",
},
{
scheme: "http",
host: "localhost",
userinfo: { username: "alice", password: "secr:t" },
path: "",
url: "http://alice:secr%3At@localhost",
},
{
scheme: "https",
host: "localhost",
userinfo: { username: "alice", password: "secret" },
port: 8080,
path: "/some/path",
url: "https://alice:secret@localhost:8080/some/path",
},
];
for (const { scheme, host, port, userinfo, path, url } of cases) {
expect(
encodeBaseUrl(scheme, { host, port, userinfo }, path),
).toStrictEqual(new URL(url));
}
});
================================================
FILE: packages/libsql-client/src/hrana.ts
================================================
import * as hrana from "@libsql/hrana-client";
import type {
InStatement,
ResultSet,
Transaction,
TransactionMode,
InArgs,
} from "@libsql/core/api";
import { LibsqlError, LibsqlBatchError } from "@libsql/core/api";
import type { SqlCache } from "./sql_cache.js";
import { transactionModeToBegin, ResultSetImpl } from "@libsql/core/util";
export abstract class HranaTransaction implements Transaction {
#mode: TransactionMode;
#version: hrana.ProtocolVersion;
// Promise that is resolved when the BEGIN statement completes, or `undefined` if we haven't executed the
// BEGIN statement yet.
#started: Promise<void> | undefined;
/** @private */
constructor(mode: TransactionMode, version: hrana.ProtocolVersion) {
this.#mode = mode;
this.#version = version;
this.#started = undefined;
}
/** @private */
abstract _getStream(): hrana.Stream;
/** @private */
abstract _getSqlCache(): SqlCache;
abstract close(): void;
abstract get closed(): boolean;
execute(stmt: InStatement): Promise<ResultSet> {
return this.batch([stmt]).then((results) => results[0]);
}
async batch(stmts: Array<InStatement>): Promise<Array<ResultSet>> {
const stream = this._getStream();
if (stream.closed) {
throw new LibsqlError(
"Cannot execute statements because the transaction is closed",
"TRANSACTION_CLOSED",
);
}
try {
const hranaStmts = stmts.map(stmtToHrana);
let rowsPromises: Array<Promise<hrana.RowsResult | undefined>>;
if (this.#started === undefined) {
// The transaction hasn't started yet, so we need to send the BEGIN statement in a batch with
// `hranaStmts`.
this._getSqlCache().apply(hranaStmts);
const batch = stream.batch(this.#version >= 3);
const beginStep = batch.step();
const beginPromise = beginStep.run(
transactionModeToBegin(this.#mode),
);
// Execute the `hranaStmts` only if the BEGIN succeeded, to make sure that we don't execute it
// outside of a transaction.
let lastStep = beginStep;
rowsPromises = hranaStmts.map((hranaStmt) => {
const stmtStep = batch
.step()
.condition(hrana.BatchCond.ok(lastStep));
if (this.#version >= 3) {
// If the Hrana version supports it, make sure that we are still in a transaction
stmtStep.condition(
hrana.BatchCond.not(
hrana.BatchCond.isAutocommit(batch),
),
);
}
const rowsPromise = stmtStep.query(hranaStmt);
rowsPromise.catch(() => undefined); // silence Node warning
lastStep = stmtStep;
return rowsPromise;
});
// `this.#started` is resolved successfully only if the batch and the BEGIN statement inside
// of the batch are both successful.
this.#started = batch
.execute()
.then(() => beginPromise)
.then(() => undefined);
try {
await this.#started;
} catch (e) {
// If the BEGIN failed, the transaction is unusable and we must close it. However, if the
// BEGIN suceeds and `hranaStmts` fail, the transaction is _not_ closed.
this.close();
throw e;
}
} else {
if (this.#version < 3) {
// The transaction has started, so we must wait until the BEGIN statement completed to make
// sure that we don't execute `hranaStmts` outside of a transaction.
await this.#started;
} else {
// The transaction has started, but we will use `hrana.BatchCond.isAutocommit()` to make
// sure that we don't execute `hranaStmts` outside of a transaction, so we don't have to
// wait for `this.#started`
}
this._getSqlCache().apply(hranaStmts);
const batch = stream.batch(this.#version >= 3);
let lastStep: hrana.BatchStep | undefined = undefined;
rowsPromises = hranaStmts.map((hranaStmt) => {
const stmtStep = batch.step();
if (lastStep !== undefined) {
stmtStep.condition(hrana.BatchCond.ok(lastStep));
}
if (this.#version >= 3) {
stmtStep.condition(
hrana.BatchCond.not(
hrana.BatchCond.isAutocommit(batch),
),
);
}
const rowsPromise = stmtStep.query(hranaStmt);
rowsPromise.catch(() => undefined); // silence Node warning
lastStep = stmtStep;
return rowsPromise;
});
await batch.execute();
}
const resultSets = [];
for (let i = 0; i < rowsPromises.length; i++) {
try {
const rows = await rowsPromises[i];
if (rows === undefined) {
throw new LibsqlBatchError(
"Statement in a transaction was not executed, " +
"probably because the transaction has been rolled back",
i,
"TRANSACTION_CLOSED",
);
}
resultSets.push(resultSetFromHrana(rows));
} catch (e) {
if (e instanceof LibsqlBatchError) {
throw e;
}
// Map hrana errors to LibsqlError first, then wrap in LibsqlBatchError
const mappedError = mapHranaError(e);
if (mappedError instanceof LibsqlError) {
throw new LibsqlBatchError(
mappedError.message,
i,
mappedError.code,
mappedError.extendedCode,
mappedError.rawCode,
mappedError.cause instanceof Error
? mappedError.cause
: undefined,
);
}
throw mappedError;
}
}
return resultSets;
} catch (e) {
throw mapHranaError(e);
}
}
async executeMultiple(sql: string): Promise<void> {
const stream = this._getStream();
if (stream.closed) {
throw new LibsqlError(
"Cannot execute statements because the transaction is closed",
"TRANSACTION_CLOSED",
);
}
try {
if (this.#started === undefined) {
// If the transaction hasn't started yet, start it now
this.#started = stream
.run(transactionModeToBegin(this.#mode))
.then(() => undefined);
try {
await this.#started;
} catch (e) {
this.close();
throw e;
}
} else {
// Wait until the transaction has started
await this.#started;
}
await stream.sequence(sql);
} catch (e) {
throw mapHranaError(e);
}
}
async rollback(): Promise<void> {
try {
const stream = this._getStream();
if (stream.closed) {
return;
}
if (this.#started !== undefined) {
// We don't have to wait for the BEGIN statement to complete. If the BEGIN fails, we will
// execute a ROLLBACK outside of an active transaction, which should be harmless.
} else {
// We did nothing in the transaction, so there is nothing to rollback.
return;
}
// Pipeline the ROLLBACK statement and the stream close.
const promise = stream.run("ROLLBACK").catch((e) => {
throw mapHranaError(e);
});
stream.closeGracefully();
await promise;
} catch (e) {
throw mapHranaError(e);
} finally {
// `this.close()` may close the `hrana.Client`, which aborts all pending stream requests, so we
// must call it _after_ we receive the ROLLBACK response.
// Also note that the current stream should already be closed, but we need to call `this.close()`
// anyway, because it may need to do more cleanup.
this.close();
}
}
async commit(): Promise<void> {
// (this method is analogous to `rollback()`)
try {
const stream = this._getStream();
if (stream.closed) {
throw new LibsqlError(
"Cannot commit the transaction because it is already closed",
"TRANSACTION_CLOSED",
);
}
if (this.#started !== undefined) {
// Make sure to execute the COMMIT only if the BEGIN was successful.
await this.#started;
} else {
return;
}
const promise = stream.run("COMMIT").catch((e) => {
throw mapHranaError(e);
});
stream.closeGracefully();
await promise;
} catch (e) {
throw mapHranaError(e);
} finally {
this.close();
}
}
}
export async function executeHranaBatch(
mode: TransactionMode,
version: hrana.ProtocolVersion,
batch: hrana.Batch,
hranaStmts: Array<hrana.Stmt>,
disableForeignKeys: boolean = false,
): Promise<Array<ResultSet>> {
if (disableForeignKeys) {
batch.step().run("PRAGMA foreign_keys=off");
}
const beginStep = batch.step();
const beginPromise = beginStep.run(transactionModeToBegin(mode));
let lastStep = beginStep;
const stmtPromises = hranaStmts.map((hranaStmt) => {
const stmtStep = batch.step().condition(hrana.BatchCond.ok(lastStep));
if (version >= 3) {
stmtStep.condition(
hrana.BatchCond.not(hrana.BatchCond.isAutocommit(batch)),
);
}
const stmtPromise = stmtStep.query(hranaStmt);
lastStep = stmtStep;
return stmtPromise;
});
const commitStep = batch.step().condition(hrana.BatchCond.ok(lastStep));
if (version >= 3) {
commitStep.condition(
hrana.BatchCond.not(hrana.BatchCond.isAutocommit(batch)),
);
}
const commitPromise = commitStep.run("COMMIT");
const rollbackStep = batch
.step()
.condition(hrana.BatchCond.not(hrana.BatchCond.ok(commitStep)));
rollbackStep.run("ROLLBACK").catch((_) => undefined);
if (disableForeignKeys) {
batch.step().run("PRAGMA foreign_keys=on");
}
await batch.execute();
const resultSets = [];
await beginPromise;
for (let i = 0; i < stmtPromises.length; i++) {
try {
const hranaRows = await stmtPromises[i];
if (hranaRows === undefined) {
throw new LibsqlBatchError(
"Statement in a batch was not executed, probably because the transaction has been rolled back",
i,
"TRANSACTION_CLOSED",
);
}
resultSets.push(resultSetFromHrana(hranaRows));
} catch (e) {
if (e instanceof LibsqlBatchError) {
throw e;
}
// Map hrana errors to LibsqlError first, then wrap in LibsqlBatchError
const mappedError = mapHranaError(e);
if (mappedError instanceof LibsqlError) {
throw new LibsqlBatchError(
mappedError.message,
i,
mappedError.code,
mappedError.extendedCode,
mappedError.rawCode,
mappedError.cause instanceof Error
? mappedError.cause
: undefined,
);
}
throw mappedError;
}
}
await commitPromise;
return resultSets;
}
export function stmtToHrana(stmt: InStatement | [string, InArgs?]): hrana.Stmt {
let sql: string;
let args: InArgs | undefined;
if (Array.isArray(stmt)) {
[sql, args] = stmt;
} else if (typeof stmt === "string") {
sql = stmt;
} else {
sql = stmt.sql;
args = stmt.args;
}
const hranaStmt = new hrana.Stmt(sql);
if (args) {
if (Array.isArray(args)) {
hranaStmt.bindIndexes(args);
} else {
for (const [key, value] of Object.entries(args)) {
hranaStmt.bindName(key, value);
}
}
}
return hranaStmt;
}
export function resultSetFromHrana(hranaRows: hrana.RowsResult): ResultSet {
const columns = hranaRows.columnNames.map((c) => c ?? "");
const columnTypes = hranaRows.columnDecltypes.map((c) => c ?? "");
const rows = hranaRows.rows;
const rowsAffected = hranaRows.affectedRowCount;
const lastInsertRowid =
hranaRows.lastInsertRowid !== undefined
? hranaRows.lastInsertRowid
: undefined;
return new ResultSetImpl(
columns,
columnTypes,
rows,
rowsAffected,
lastInsertRowid,
);
}
export function mapHranaError(e: unknown): unknown {
if (e instanceof hrana.ClientError) {
const code = mapHranaErrorCode(e);
// TODO: Parse extendedCode once the SQL over HTTP protocol supports it
return new LibsqlError(e.message, code, undefined, undefined, e);
}
return e;
}
function mapHranaErrorCode(e: hrana.ClientError): string {
if (e instanceof hrana.ResponseError && e.code !== undefined) {
return e.code;
} else if (e instanceof hrana.ProtoError) {
return "HRANA_PROTO_ERROR";
} else if (e instanceof hrana.ClosedError) {
return e.cause instanceof hrana.ClientError
? mapHranaErrorCode(e.cause)
: "HRANA_CLOSED_ERROR";
} else if (e instanceof hrana.WebSocketError) {
return "HRANA_WEBSOCKET_ERROR";
} else if (e instanceof hrana.HttpServerError) {
return "SERVER_ERROR";
} else if (e instanceof hrana.ProtocolVersionError) {
return "PROTOCOL_VERSION_ERROR";
} else if (e instanceof hrana.InternalError) {
return "INTERNAL_ERROR";
} else {
return "UNKNOWN";
}
}
================================================
FILE: packages/libsql-client/src/http.ts
================================================
import * as hrana from "@libsql/hrana-client";
import type { Config, Client } from "@libsql/core/api";
import type {
InStatement,
ResultSet,
Transaction,
IntMode,
InArgs,
Replicated,
} from "@libsql/core/api";
import { TransactionMode, LibsqlError } from "@libsql/core/api";
import type { ExpandedConfig } from "@libsql/core/config";
import { expandConfig } from "@libsql/core/config";
import {
HranaTransaction,
executeHranaBatch,
stmtToHrana,
resultSetFromHrana,
mapHranaError,
} from "./hrana.js";
import { SqlCache } from "./sql_cache.js";
import { encodeBaseUrl } from "@libsql/core/uri";
import { supportedUrlLink } from "@libsql/core/util";
import promiseLimit from "promise-limit";
export * from "@libsql/core/api";
export function createClient(config: Config): Client {
return _createClient(expandConfig(config, true));
}
/** @private */
export function _createClient(config: ExpandedConfig): Client {
if (config.scheme !== "https" && config.scheme !== "http") {
throw new LibsqlError(
'The HTTP client supports only "libsql:", "https:" and "http:" URLs, ' +
`got ${JSON.stringify(config.scheme + ":")}. For more information, please read ${supportedUrlLink}`,
"URL_SCHEME_NOT_SUPPORTED",
);
}
if (config.encryptionKey !== undefined) {
throw new LibsqlError(
"Encryption key is not supported by the remote client.",
"ENCRYPTION_KEY_NOT_SUPPORTED",
);
}
if (config.scheme === "http" && config.tls) {
throw new LibsqlError(
`A "http:" URL cannot opt into TLS by using ?tls=1`,
"URL_INVALID",
);
} else if (config.scheme === "https" && !config.tls) {
throw new LibsqlError(
`A "https:" URL cannot opt out of TLS by using ?tls=0`,
"URL_INVALID",
);
}
const url = encodeBaseUrl(config.scheme, config.authority, config.path);
return new HttpClient(
url,
config.authToken,
config.intMode,
config.fetch,
config.concurrency,
config.remoteEncryptionKey,
);
}
const sqlCacheCapacity = 30;
export class HttpClient implements Client {
#client: hrana.HttpClient;
protocol: "http";
#url: URL;
#intMode: IntMode;
#customFetch: Function | undefined;
#concurrency: number;
#authToken: string | undefined;
#remoteEncryptionKey: string | undefined;
#promiseLimitFunction: ReturnType<typeof promiseLimit<any>>;
/** @private */
constructor(
url: URL,
authToken: string | undefined,
intMode: IntMode,
customFetch: Function | undefined,
concurrency: number,
remoteEncryptionKey: string | undefined,
) {
this.#url = url;
this.#authToken = authToken;
this.#intMode = intMode;
this.#customFetch = customFetch;
this.#concurrency = concurrency;
this.#remoteEncryptionKey = remoteEncryptionKey;
this.#client = hrana.openHttp(
this.#url,
this.#authToken,
this.#customFetch,
remoteEncryptionKey,
);
this.#client.intMode = this.#intMode;
this.protocol = "http";
this.#promiseLimitFunction = promiseLimit<any>(this.#concurrency);
}
private async limit<T>(fn: () => Promise<T>): Promise<T> {
return this.#promiseLimitFunction(fn);
}
async execute(
stmtOrSql: InStatement | string,
args?: InArgs,
): Promise<ResultSet> {
let stmt: InStatement;
if (typeof stmtOrSql === "string") {
stmt = {
sql: stmtOrSql,
args: args || [],
};
} else {
stmt = stmtOrSql;
}
return this.limit<ResultSet>(async () => {
try {
const hranaStmt = stmtToHrana(stmt);
// Pipeline all operations, so `hrana.HttpClient` can open the stream, execute the statement and
// close the stream in a single HTTP request.
let rowsPromise: Promise<hrana.RowsResult>;
const stream = this.#client.openStream();
try {
rowsPromise = stream.query(hranaStmt);
} finally {
stream.closeGracefully();
}
const rowsResult = await rowsPromise;
return resultSetFromHrana(rowsResult);
} catch (e) {
throw mapHranaError(e);
}
});
}
async batch(
stmts: Array<InStatement | [string, InArgs?]>,
mode: TransactionMode = "deferred",
): Promise<Array<ResultSet>> {
return this.limit<Array<ResultSet>>(async () => {
try {
const normalizedStmts = stmts.map((stmt) => {
if (Array.isArray(stmt)) {
return {
sql: stmt[0],
args: stmt[1] || [],
};
}
return stmt;
});
const hranaStmts = normalizedStmts.map(stmtToHrana);
const version = await this.#client.getVersion();
// Pipeline all operations, so `hrana.HttpClient` can open the stream, execute the batch and
// close the stream in a single HTTP request.
let resultsPromise: Promise<Array<ResultSet>>;
const stream = this.#client.openStream();
try {
// It makes sense to use a SQL cache even for a single batch, because it may contain the same
// statement repeated multiple times.
const sqlCache = new SqlCache(stream, sqlCacheCapacity);
sqlCache.apply(hranaStmts);
// TODO: we do not use a cursor here, because it would cause three roundtrips:
// 1. pipeline request to store SQL texts
// 2. cursor request
// 3. pipeline request to close the stream
const batch = stream.batch(false);
resultsPromise = executeHranaBatch(
mode,
version,
batch,
hranaStmts,
);
} finally {
stream.closeGracefully();
}
const results = await resultsPromise;
return results;
} catch (e) {
throw mapHranaError(e);
}
});
}
async migrate(stmts: Array<InStatement>): Promise<Array<ResultSet>> {
return this.limit<Array<ResultSet>>(async () => {
try {
const hranaStmts = stmts.map(stmtToHrana);
const version = await this.#client.getVersion();
// Pipeline all operations, so `hrana.HttpClient` can open the stream, execute the batch and
// close the stream in a single HTTP request.
let resultsPromise: Promise<Array<ResultSet>>;
const stream = this.#client.openStream();
try {
const batch = stream.batch(false);
resultsPromise = executeHranaBatch(
"deferred",
version,
batch,
hranaStmts,
true,
);
} finally {
stream.closeGracefully();
}
const results = await resultsPromise;
return results;
} catch (e) {
throw mapHranaError(e);
}
});
}
async transaction(
mode: TransactionMode = "write",
): Promise<HttpTransaction> {
return this.limit<HttpTransaction>(async () => {
try {
const version = await this.#client.getVersion();
return new HttpTransaction(
this.#client.openStream(),
mode,
version,
);
} catch (e) {
throw mapHranaError(e);
}
});
}
async executeMultiple(sql: string): Promise<void> {
return this.limit<void>(async () => {
try {
// Pipeline all operations, so `hrana.HttpClient` can open the stream, execute the sequence and
// close the stream in a single HTTP request.
let promise: Promise<void>;
const stream = this.#client.openStream();
try {
promise = stream.sequence(sql);
} finally {
stream.closeGracefully();
}
await promise;
} catch (e) {
throw mapHranaError(e);
}
});
}
sync(): Promise<Replicated> {
throw new LibsqlError(
"sync not supported in http mode",
"SYNC_NOT_SUPPORTED",
);
}
close(): void {
this.#client.close();
}
async reconnect(): Promise<void> {
try {
if (!this.closed) {
// Abort in-flight ops and free resources
this.#client.close();
}
} finally {
// Recreate the underlying hrana client
this.#client = hrana.openHttp(
this.#url,
this.#authToken,
this.#customFetch,
this.#remoteEncryptionKey,
);
this.#client.intMode = this.#intMode;
}
}
get closed(): boolean {
return this.#client.closed;
}
}
export class HttpTransaction extends HranaTransaction implements Transaction {
#stream: hrana.HttpStream;
#sqlCache: SqlCache;
/** @private */
constructor(
stream: hrana.HttpStream,
mode: TransactionMode,
version: hrana.ProtocolVersion,
) {
super(mode, version);
this.#stream = stream;
this.#sqlCache = new SqlCache(stream, sqlCacheCapacity);
}
/** @private */
override _getStream(): hrana.Stream {
return this.#stream;
}
/** @private */
override _getSqlCache(): SqlCache {
return this.#sqlCache;
}
override close(): void {
this.#stream.close();
}
override get closed(): boolean {
return this.#stream.closed;
}
}
================================================
FILE: packages/libsql-client/src/node.ts
================================================
import type { Config, Client } from "@libsql/core/api";
import { LibsqlError } from "@libsql/core/api";
import type { ExpandedConfig } from "@libsql/core/config";
import { expandConfig } from "@libsql/core/config";
import { _createClient as _createSqlite3Client } from "./sqlite3.js";
import { _createClient as _createWsClient } from "./ws.js";
import { _createClient as _createHttpClient } from "./http.js";
export * from "@libsql/core/api";
/** Creates a {@link Client} object.
*
* You must pass at least an `url` in the {@link Config} object.
*/
export function createClient(config: Config): Client {
return _createClient(expandConfig(config, true));
}
function _createClient(config: ExpandedConfig) {
if (config.scheme === "wss" || config.scheme === "ws") {
return _createWsClient(config);
} else if (config.scheme === "https" || config.scheme === "http") {
return _createHttpClient(config);
} else {
return _createSqlite3Client(config);
}
}
================================================
FILE: packages/libsql-client/src/sql_cache.ts
================================================
import type * as hrana from "@libsql/hrana-client";
export class SqlCache {
#owner: hrana.SqlOwner;
#sqls: Lru<string, hrana.Sql>;
capacity: number;
constructor(owner: hrana.SqlOwner, capacity: number) {
this.#owner = owner;
this.#sqls = new Lru();
this.capacity = capacity;
}
// Replaces SQL strings with cached `hrana.Sql` objects in the statements in `hranaStmts`. After this
// function returns, we guarantee that all `hranaStmts` refer to valid (not closed) `hrana.Sql` objects,
// but _we may invalidate any other `hrana.Sql` objects_ (by closing them, thus removing them from the
// server).
//
// In practice, this means that after calling this function, you can use the statements only up to the
// first `await`, because concurrent code may also use the cache and invalidate those statements.
apply(hranaStmts: Array<hrana.Stmt>): void {
if (this.capacity <= 0) {
return;
}
const usedSqlObjs: Set<hrana.Sql> = new Set();
for (const hranaStmt of hranaStmts) {
if (typeof hranaStmt.sql !== "string") {
continue;
}
const sqlText = hranaStmt.sql;
// Stored SQL cannot exceed 5kb.
// https://github.com/tursodatabase/libsql/blob/e9d637e051685f92b0da43849507b5ef4232fbeb/libsql-server/src/hrana/http/request.rs#L10
if (sqlText.length >= 5000) {
continue;
}
let sqlObj = this.#sqls.get(sqlText);
if (sqlObj === undefined) {
while (this.#sqls.size + 1 > this.capacity) {
const [evictSqlText, evictSqlObj] = this.#sqls.peekLru()!;
if (usedSqlObjs.has(evictSqlObj)) {
// The SQL object that we are trying to evict is already in use in this batch, so we
// must not evict and close it.
break;
}
evictSqlObj.close();
this.#sqls.delete(evictSqlText);
}
if (this.#sqls.size + 1 <= this.capacity) {
sqlObj = this.#owner.storeSql(sqlText);
this.#sqls.set(sqlText, sqlObj);
}
}
if (sqlObj !== undefined) {
hranaStmt.sql = sqlObj;
usedSqlObjs.add(sqlObj);
}
}
}
}
class Lru<K, V> {
// This maps keys to the cache values. The entries are ordered by their last use (entires that were used
// most recently are at the end).
#cache: Map<K, V>;
constructor() {
this.#cache = new Map();
}
get(key: K): V | undefined {
const value = this.#cache.get(key);
if (value !== undefined) {
// move the entry to the back of the Map
this.#cache.delete(key);
this.#cache.set(key, value);
}
return value;
}
set(key: K, value: V): void {
this.#cache.set(key, value);
}
peekLru(): [K, V] | undefined {
for (const entry of this.#cache.entries()) {
return entry;
}
return undefined;
}
delete(key: K): void {
this.#cache.delete(key);
}
get size(): number {
return this.#cache.size;
}
}
================================================
FILE: packages/libsql-client/src/sqlite3.ts
================================================
import Database from "libsql";
import { Buffer } from "node:buffer";
import type {
Config,
IntMode,
Client,
Transaction,
TransactionMode,
ResultSet,
Row,
Value,
InValue,
InStatement,
InArgs,
Replicated,
} from "@libsql/core/api";
import { LibsqlError, LibsqlBatchError } from "@libsql/core/api";
import type { ExpandedConfig } from "@libsql/core/config";
import { expandConfig, isInMemoryConfig } from "@libsql/core/config";
import {
supportedUrlLink,
transactionModeToBegin,
ResultSetImpl,
} from "@libsql/core/util";
export * from "@libsql/core/api";
export function createClient(config: Config): Client {
return _createClient(expandConfig(config, true));
}
/** @private */
export function _createClient(config: ExpandedConfig): Client {
if (config.scheme !== "file") {
throw new LibsqlError(
`URL scheme ${JSON.stringify(config.scheme + ":")} is not supported by the local sqlite3 client. ` +
`For more information, please read ${supportedUrlLink}`,
"URL_SCHEME_NOT_SUPPORTED",
);
}
const authority = config.authority;
if (authority !== undefined) {
const host = authority.host.toLowerCase();
if (host !== "" && host !== "localhost") {
throw new LibsqlError(
`Invalid host in file URL: ${JSON.stringify(authority.host)}. ` +
'A "file:" URL with an absolute path should start with one slash ("file:/absolute/path.db") ' +
'or with three slashes ("file:///absolute/path.db"). ' +
`For more information, please read ${supportedUrlLink}`,
"URL_INVALID",
);
}
if (authority.port !== undefined) {
throw new LibsqlError("File URL cannot have a port", "URL_INVALID");
}
if (authority.userinfo !== undefined) {
throw new LibsqlError(
"File URL cannot have username and password",
"URL_INVALID",
);
}
}
let isInMemory = isInMemoryConfig(config);
if (isInMemory && config.syncUrl) {
throw new LibsqlError(
`Embedded replica must use file for local db but URI with in-memory mode were provided instead: ${config.path}`,
"URL_INVALID",
);
}
let path = config.path;
if (isInMemory) {
// note: we should prepend file scheme in order for SQLite3 to recognize :memory: connection query parameters
path = `${config.scheme}:${config.path}`;
}
const options = {
authToken: config.authToken,
encryptionKey: config.encryptionKey,
remoteEncryptionKey: config.remoteEncryptionKey,
syncUrl: config.syncUrl,
syncPeriod: config.syncInterval,
readYourWrites: config.readYourWrites,
offline: config.offline,
};
const db = new Database(path, options);
executeStmt(
db,
"SELECT 1 AS checkThatTheDatabaseCanBeOpened",
config.intMode,
);
return new Sqlite3Client(path, options, db, config.intMode);
}
export class Sqlite3Client implements Client {
#path: string;
#options: Database.Options;
#db: Database.Database | null;
#intMode: IntMode;
closed: boolean;
protocol: "file";
/** @private */
constructor(
path: string,
options: Database.Options,
db: Database.Database,
intMode: IntMode,
) {
this.#path = path;
this.#options = options;
this.#db = db;
this.#intMode = intMode;
this.closed = false;
this.protocol = "file";
}
async execute(
stmtOrSql: InStatement | string,
args?: InArgs,
): Promise<ResultSet> {
let stmt: InStatement;
if (typeof stmtOrSql === "string") {
stmt = {
sql: stmtOrSql,
args: args || [],
};
} else {
stmt = stmtOrSql;
}
this.#checkNotClosed();
return executeStmt(this.#getDb(), stmt, this.#intMode);
}
async batch(
stmts: Array<InStatement | [string, InArgs?]>,
mode: TransactionMode = "deferred",
): Promise<Array<ResultSet>> {
this.#checkNotClosed();
const db = this.#getDb();
try {
executeStmt(db, transactionModeToBegin(mode), this.#intMode);
const resultSets = [];
for (let i = 0; i < stmts.length; i++) {
try {
if (!db.inTransaction) {
throw new LibsqlBatchError(
"The transaction has been rolled back",
i,
"TRANSACTION_CLOSED",
);
}
const stmt = stmts[i];
const normalizedStmt: InStatement = Array.isArray(stmt)
? { sql: stmt[0], args: stmt[1] || [] }
: stmt;
resultSets.push(
executeStmt(db, normalizedStmt, this.#intMode),
);
} catch (e) {
if (e instanceof LibsqlBatchError) {
throw e;
}
if (e instanceof LibsqlError) {
throw new LibsqlBatchError(
e.message,
i,
e.code,
e.extendedCode,
e.rawCode,
e.cause instanceof Error ? e.cause : undefined,
);
}
throw e;
}
}
executeStmt(db, "COMMIT", this.#intMode);
return resultSets;
} finally {
if (db.inTransaction) {
executeStmt(db, "ROLLBACK", this.#intMode);
}
}
}
async migrate(stmts: Array<InStatement>): Promise<Array<ResultSet>> {
this.#checkNotClosed();
const db = this.#getDb();
try {
executeStmt(db, "PRAGMA foreign_keys=off", this.#intMode);
executeStmt(db, transactionModeToBegin("deferred"), this.#intMode);
const resultSets = [];
for (let i = 0; i < stmts.length; i++) {
try {
if (!db.inTransaction) {
throw new LibsqlBatchError(
"The transaction has been rolled back",
i,
"TRANSACTION_CLOSED",
);
}
resultSets.push(executeStmt(db, stmts[i], this.#intMode));
} catch (e) {
if (e instanceof LibsqlBatchError) {
throw e;
}
if (e instanceof LibsqlError) {
throw new LibsqlBatchError(
e.message,
i,
e.code,
e.extendedCode,
e.rawCode,
e.cause instanceof Error ? e.cause : undefined,
);
}
throw e;
}
}
executeStmt(db, "COMMIT", this.#intMode);
return resultSets;
} finally {
if (db.inTransaction) {
executeStmt(db, "ROLLBACK", this.#intMode);
}
executeStmt(db, "PRAGMA foreign_keys=on", this.#intMode);
}
}
async transaction(mode: TransactionMode = "write"): Promise<Transaction> {
const db = this.#getDb();
executeStmt(db, transactionModeToBegin(mode), this.#intMode);
this.#db = null; // A new connection will be lazily created on next use
return new Sqlite3Transaction(db, this.#intMode);
}
async executeMultiple(sql: string): Promise<void> {
this.#checkNotClosed();
const db = this.#getDb();
try {
return executeMultiple(db, sql);
} finally {
if (db.inTransaction) {
executeStmt(db, "ROLLBACK", this.#intMode);
}
}
}
async sync(): Promise<Replicated> {
this.#checkNotClosed();
const rep = await this.#getDb().sync();
return {
frames_synced: rep.frames_synced,
frame_no: rep.frame_no,
} as Replicated;
}
async reconnect(): Promise<void> {
try {
if (!this.closed && this.#db !== null) {
this.#db.close();
}
} finally {
this.#db = new Database(this.#path, this.#options);
this.closed = false;
}
}
close(): void {
this.closed = true;
if (this.#db !== null) {
this.#db.close();
this.#db = null;
}
}
#checkNotClosed(): void {
if (this.closed) {
throw new LibsqlError("The client is closed", "CLIENT_CLOSED");
}
}
// Lazily crea
gitextract_x14w5we3/
├── .github/
│ └── workflows/
│ ├── ci.yaml
│ ├── pages.yaml
│ └── publish.yml
├── .gitignore
├── .husky/
│ ├── install.mjs
│ └── pre-commit
├── .lintstagedrc.json
├── .npmrc
├── .prettierignore
├── .prettierrc
├── CHANGELOG.md
├── CONTRIBUTING.md
├── LICENSE
├── examples/
│ ├── batch/
│ │ ├── .gitignore
│ │ ├── README.md
│ │ ├── index.mjs
│ │ └── package.json
│ ├── cloud-encryption/
│ │ ├── .gitignore
│ │ ├── README.md
│ │ ├── package.json
│ │ ├── remote.mjs
│ │ └── sync.mjs
│ ├── encryption/
│ │ ├── .gitignore
│ │ ├── README.md
│ │ ├── index.mjs
│ │ └── package.json
│ ├── local/
│ │ ├── .gitignore
│ │ ├── README.md
│ │ ├── index.mjs
│ │ └── package.json
│ ├── memory/
│ │ ├── .gitignore
│ │ ├── README.md
│ │ ├── index.mjs
│ │ └── package.json
│ ├── ollama/
│ │ ├── .gitignore
│ │ ├── README.md
│ │ ├── index.mjs
│ │ └── package.json
│ ├── read-your-writes/
│ │ ├── package.json
│ │ └── read_your_writes.js
│ ├── remote/
│ │ ├── .gitignore
│ │ ├── README.md
│ │ ├── index.mjs
│ │ └── package.json
│ ├── sync/
│ │ ├── .gitignore
│ │ ├── README.md
│ │ ├── index.mjs
│ │ └── package.json
│ ├── transactions/
│ │ ├── .gitignore
│ │ ├── README.md
│ │ ├── index.mjs
│ │ └── package.json
│ └── vector/
│ ├── .gitignore
│ ├── README.md
│ ├── index.mjs
│ └── package.json
├── package.json
├── packages/
│ ├── libsql-client/
│ │ ├── README.md
│ │ ├── examples/
│ │ │ ├── example.js
│ │ │ ├── package.json
│ │ │ ├── shell.js
│ │ │ ├── sync.js
│ │ │ ├── sync_offline.js
│ │ │ └── sync_vector.js
│ │ ├── jest.config.js
│ │ ├── package-cjs.json
│ │ ├── package.json
│ │ ├── smoke_test/
│ │ │ ├── vercel/
│ │ │ │ ├── .gitignore
│ │ │ │ ├── app/
│ │ │ │ │ ├── .gitignore
│ │ │ │ │ ├── api/
│ │ │ │ │ │ └── function.ts
│ │ │ │ │ └── public/
│ │ │ │ │ └── index.html
│ │ │ │ ├── package.json
│ │ │ │ ├── test.js
│ │ │ │ └── tsconfig.json
│ │ │ └── workers/
│ │ │ ├── .gitignore
│ │ │ ├── package.json
│ │ │ ├── test.js
│ │ │ ├── worker.js
│ │ │ └── wrangler.toml
│ │ ├── src/
│ │ │ ├── __tests__/
│ │ │ │ ├── client.test.ts
│ │ │ │ ├── config.test.ts
│ │ │ │ ├── helpers.ts
│ │ │ │ ├── mocks/
│ │ │ │ │ ├── handlers.ts
│ │ │ │ │ └── node.ts
│ │ │ │ └── uri.test.ts
│ │ │ ├── hrana.ts
│ │ │ ├── http.ts
│ │ │ ├── node.ts
│ │ │ ├── sql_cache.ts
│ │ │ ├── sqlite3.ts
│ │ │ ├── web.ts
│ │ │ └── ws.ts
│ │ ├── tsconfig.base.json
│ │ ├── tsconfig.build-cjs.json
│ │ ├── tsconfig.build-esm.json
│ │ ├── tsconfig.json
│ │ └── typedoc.json
│ ├── libsql-client-wasm/
│ │ ├── LICENSE
│ │ ├── examples/
│ │ │ ├── browser/
│ │ │ │ ├── README.md
│ │ │ │ ├── index.html
│ │ │ │ ├── index.js
│ │ │ │ └── package.json
│ │ │ └── node/
│ │ │ ├── index.js
│ │ │ └── package.json
│ │ ├── jest.config.js
│ │ ├── package-cjs.json
│ │ ├── package.json
│ │ ├── src/
│ │ │ └── wasm.ts
│ │ ├── tsconfig.base.json
│ │ ├── tsconfig.build-esm.json
│ │ ├── tsconfig.json
│ │ └── typedoc.json
│ └── libsql-core/
│ ├── jest.config.js
│ ├── package-cjs.json
│ ├── package.json
│ ├── src/
│ │ ├── api.ts
│ │ ├── config.ts
│ │ ├── uri.ts
│ │ └── util.ts
│ ├── tsconfig.base.json
│ ├── tsconfig.build-cjs.json
│ ├── tsconfig.build-esm.json
│ ├── tsconfig.json
│ └── typedoc.json
└── testing/
├── hrana-test-server/
│ ├── .gitignore
│ ├── LICENSE
│ ├── README.md
│ ├── c3.py
│ ├── from_proto.py
│ ├── gen_sqlite3_error_map.py
│ ├── proto/
│ │ ├── generate.sh
│ │ ├── hrana/
│ │ │ ├── http_pb2.py
│ │ │ └── ws_pb2.py
│ │ ├── hrana.http.proto
│ │ ├── hrana.proto
│ │ ├── hrana.ws.proto
│ │ └── hrana_pb2.py
│ ├── requirements.txt
│ ├── server_v1.py
│ ├── server_v2.py
│ ├── server_v3.py
│ ├── sqlite3_error_map.py
│ └── to_proto.py
└── test.sh
SYMBOL INDEX (353 symbols across 32 files)
FILE: examples/ollama/index.mjs
function getEmbedding (line 16) | async function getEmbedding(prompt) {
function insertMovie (line 25) | async function insertMovie(id, title, description) {
function insertMovieIfNotExists (line 34) | async function insertMovieIfNotExists(id, title, description) {
function findSimilarMovies (line 48) | async function findSimilarMovies(description, limit = 3) {
FILE: packages/libsql-client-wasm/examples/browser/index.js
function main (line 3) | async function main() {
FILE: packages/libsql-client-wasm/examples/node/index.js
function main (line 3) | async function main() {
FILE: packages/libsql-client-wasm/src/wasm.ts
function createClient (line 36) | function createClient(config: Config): Client {
function _createClient (line 41) | function _createClient(config: ExpandedConfig): Client {
function inTransaction (line 98) | function inTransaction(db: Database): boolean {
class Sqlite3Client (line 102) | class Sqlite3Client implements Client {
method constructor (line 111) | constructor(
method execute (line 126) | async execute(
method batch (line 145) | async batch(
method migrate (line 171) | async migrate(stmts: Array<InStatement>): Promise<Array<ResultSet>> {
method transaction (line 196) | async transaction(mode: TransactionMode = "write"): Promise<Transactio...
method executeMultiple (line 203) | async executeMultiple(sql: string): Promise<void> {
method sync (line 215) | async sync(): Promise<Replicated> {
method reconnect (line 222) | async reconnect(): Promise<void> {
method close (line 233) | close(): void {
method #checkNotClosed (line 241) | #checkNotClosed(): void {
method #getDb (line 248) | #getDb(): Database {
class Sqlite3Transaction (line 256) | class Sqlite3Transaction implements Transaction {
method constructor (line 261) | constructor(database: Database, intMode: IntMode) {
method execute (line 266) | async execute(stmt: InStatement): Promise<ResultSet> {
method batch (line 271) | async batch(stmts: Array<InStatement>): Promise<Array<ResultSet>> {
method executeMultiple (line 278) | async executeMultiple(sql: string): Promise<void> {
method rollback (line 283) | async rollback(): Promise<void> {
method commit (line 291) | async commit(): Promise<void> {
method close (line 296) | close(): void {
method closed (line 302) | get closed(): boolean {
method #checkNotClosed (line 306) | #checkNotClosed(): void {
function executeStmt (line 316) | function executeStmt(
function rowFromSql (line 392) | function rowFromSql(
function valueFromSql (line 417) | function valueFromSql(sqlValue: unknown, intMode: IntMode): Value {
function valueToSql (line 440) | function valueToSql(value: InValue, intMode: IntMode): SqlValue {
function executeMultiple (line 478) | function executeMultiple(db: Database, sql: string): void {
function mapSqliteError (line 486) | function mapSqliteError(e: unknown): unknown {
FILE: packages/libsql-client/examples/example.js
function example (line 3) | async function example() {
FILE: packages/libsql-client/examples/shell.js
function main (line 5) | async function main() {
FILE: packages/libsql-client/examples/sync.js
function example (line 4) | async function example() {
FILE: packages/libsql-client/examples/sync_offline.js
function example (line 4) | async function example() {
FILE: packages/libsql-client/examples/sync_vector.js
function example (line 4) | async function example() {
FILE: packages/libsql-client/smoke_test/vercel/app/api/function.ts
function respond (line 8) | function respond(status: number, responseBody: string) {
function assert (line 107) | function assert(value: unknown, message?: string) {
FILE: packages/libsql-client/smoke_test/vercel/test.js
function getEnv (line 7) | function getEnv(name) {
function npm (line 18) | async function npm(
function deployToVercel (line 63) | async function deployToVercel(clientUrlInsideVercel) {
function runTests (line 130) | async function runTests(functionUrl) {
function runTest (line 140) | async function runTest(functionUrl, testCase) {
function main (line 154) | async function main() {
FILE: packages/libsql-client/smoke_test/workers/test.js
function main (line 7) | async function main() {
function runWorker (line 57) | async function runWorker(local, clientUrlInsideWorker) {
function runTest (line 86) | async function runTest(worker, testCase) {
FILE: packages/libsql-client/smoke_test/workers/worker.js
method fetch (line 4) | async fetch(request, env, ctx) {
function assert (line 104) | function assert(value, message) {
FILE: packages/libsql-client/src/__tests__/client.test.ts
function withClient (line 42) | function withClient(
function withInMemoryClient (line 56) | function withInMemoryClient(
function testRoundtrip (line 332) | function testRoundtrip(
function testRoundtripError (line 353) | function testRoundtripError(
function customFetch (line 1583) | function customFetch(request: Request): Promise<Response> {
FILE: packages/libsql-client/src/__tests__/helpers.ts
type CodeMatch (line 6) | type CodeMatch = {
type AsymmetricMatchers (line 50) | interface AsymmetricMatchers {
type Matchers (line 53) | interface Matchers<R> {
FILE: packages/libsql-client/src/hrana.ts
method constructor (line 21) | constructor(mode: TransactionMode, version: hrana.ProtocolVersion) {
method execute (line 35) | execute(stmt: InStatement): Promise<ResultSet> {
method batch (line 39) | async batch(stmts: Array<InStatement>): Promise<Array<ResultSet>> {
method executeMultiple (line 176) | async executeMultiple(sql: string): Promise<void> {
method rollback (line 208) | async rollback(): Promise<void> {
method commit (line 241) | async commit(): Promise<void> {
function executeHranaBatch (line 273) | async function executeHranaBatch(
function stmtToHrana (line 357) | function stmtToHrana(stmt: InStatement | [string, InArgs?]): hrana.Stmt {
function resultSetFromHrana (line 384) | function resultSetFromHrana(hranaRows: hrana.RowsResult): ResultSet {
function mapHranaError (line 402) | function mapHranaError(e: unknown): unknown {
function mapHranaErrorCode (line 411) | function mapHranaErrorCode(e: hrana.ClientError): string {
FILE: packages/libsql-client/src/http.ts
function createClient (line 29) | function createClient(config: Config): Client {
function _createClient (line 34) | function _createClient(config: ExpandedConfig): Client {
class HttpClient (line 75) | class HttpClient implements Client {
method constructor (line 87) | constructor(
method limit (line 113) | private async limit<T>(fn: () => Promise<T>): Promise<T> {
method execute (line 117) | async execute(
method batch (line 155) | async batch(
method migrate (line 208) | async migrate(stmts: Array<InStatement>): Promise<Array<ResultSet>> {
method transaction (line 240) | async transaction(
method executeMultiple (line 257) | async executeMultiple(sql: string): Promise<void> {
method sync (line 277) | sync(): Promise<Replicated> {
method close (line 284) | close(): void {
method reconnect (line 288) | async reconnect(): Promise<void> {
method closed (line 306) | get closed(): boolean {
class HttpTransaction (line 311) | class HttpTransaction extends HranaTransaction implements Transaction {
method constructor (line 316) | constructor(
method _getStream (line 327) | override _getStream(): hrana.Stream {
method _getSqlCache (line 332) | override _getSqlCache(): SqlCache {
method close (line 336) | override close(): void {
method closed (line 340) | override get closed(): boolean {
FILE: packages/libsql-client/src/node.ts
function createClient (line 15) | function createClient(config: Config): Client {
function _createClient (line 19) | function _createClient(config: ExpandedConfig) {
FILE: packages/libsql-client/src/sql_cache.ts
class SqlCache (line 3) | class SqlCache {
method constructor (line 8) | constructor(owner: hrana.SqlOwner, capacity: number) {
method apply (line 21) | apply(hranaStmts: Array<hrana.Stmt>): void {
class Lru (line 67) | class Lru<K, V> {
method constructor (line 72) | constructor() {
method get (line 76) | get(key: K): V | undefined {
method set (line 86) | set(key: K, value: V): void {
method peekLru (line 90) | peekLru(): [K, V] | undefined {
method delete (line 97) | delete(key: K): void {
method size (line 101) | get size(): number {
FILE: packages/libsql-client/src/sqlite3.ts
function createClient (line 29) | function createClient(config: Config): Client {
function _createClient (line 34) | function _createClient(config: ExpandedConfig): Client {
class Sqlite3Client (line 102) | class Sqlite3Client implements Client {
method constructor (line 111) | constructor(
method execute (line 125) | async execute(
method batch (line 144) | async batch(
method migrate (line 195) | async migrate(stmts: Array<InStatement>): Promise<Array<ResultSet>> {
method transaction (line 239) | async transaction(mode: TransactionMode = "write"): Promise<Transactio...
method executeMultiple (line 246) | async executeMultiple(sql: string): Promise<void> {
method sync (line 258) | async sync(): Promise<Replicated> {
method reconnect (line 267) | async reconnect(): Promise<void> {
method close (line 278) | close(): void {
method #checkNotClosed (line 286) | #checkNotClosed(): void {
method #getDb (line 293) | #getDb(): Database.Database {
class Sqlite3Transaction (line 301) | class Sqlite3Transaction implements Transaction {
method constructor (line 306) | constructor(database: Database.Database, intMode: IntMode) {
method execute (line 314) | async execute(
method batch (line 333) | async batch(
method executeMultiple (line 367) | async executeMultiple(sql: string): Promise<void> {
method rollback (line 372) | async rollback(): Promise<void> {
method commit (line 380) | async commit(): Promise<void> {
method close (line 385) | close(): void {
method closed (line 391) | get closed(): boolean {
method #checkNotClosed (line 395) | #checkNotClosed(): void {
function executeStmt (line 405) | function executeStmt(
function rowFromSql (line 474) | function rowFromSql(
function valueFromSql (line 499) | function valueFromSql(sqlValue: unknown, intMode: IntMode): Value {
function valueToSql (line 524) | function valueToSql(value: InValue, intMode: IntMode): unknown {
function executeMultiple (line 564) | function executeMultiple(db: Database.Database, sql: string): void {
function mapSqliteError (line 572) | function mapSqliteError(e: unknown): unknown {
function mapToBaseCode (line 583) | function mapToBaseCode(rawCode: number | undefined): string {
FILE: packages/libsql-client/src/web.ts
function createClient (line 12) | function createClient(config: Config): Client {
function _createClient (line 17) | function _createClient(config: ExpandedConfig): Client {
FILE: packages/libsql-client/src/ws.ts
function createClient (line 30) | function createClient(config: Config): WsClient {
function _createClient (line 35) | function _createClient(config: ExpandedConfig): WsClient {
type ConnState (line 96) | interface ConnState {
type StreamState (line 113) | interface StreamState {
class WsClient (line 121) | class WsClient implements Client {
method constructor (line 136) | constructor(
method limit (line 153) | private async limit<T>(fn: () => Promise<T>): Promise<T> {
method execute (line 157) | async execute(
method batch (line 194) | async batch(
method migrate (line 236) | async migrate(stmts: Array<InStatement>): Promise<Array<ResultSet>> {
method transaction (line 265) | async transaction(mode: TransactionMode = "write"): Promise<WsTransact...
method executeMultiple (line 280) | async executeMultiple(sql: string): Promise<void> {
method sync (line 298) | sync(): Promise<Replicated> {
method #openStream (line 305) | async #openStream(): Promise<StreamState> {
method #openConn (line 391) | #openConn(client?: hrana.WsClient): ConnState {
method reconnect (line 406) | async reconnect(): Promise<void> {
method _closeStream (line 435) | _closeStream(streamState: StreamState): void {
method close (line 450) | close(): void {
class WsTransaction (line 463) | class WsTransaction extends HranaTransaction implements Transaction {
method constructor (line 468) | constructor(
method _getStream (line 480) | override _getStream(): hrana.Stream {
method _getSqlCache (line 485) | override _getSqlCache(): SqlCache {
method close (line 489) | override close(): void {
method closed (line 493) | override get closed(): boolean {
FILE: packages/libsql-core/src/api.ts
type Config (line 2) | interface Config {
type IntMode (line 68) | type IntMode = "number" | "bigint" | "string";
type Client (line 74) | interface Client {
type Transaction (line 298) | interface Transaction {
type TransactionMode (line 392) | type TransactionMode = "write" | "read" | "deferred";
type ResultSet (line 407) | interface ResultSet {
type Row (line 463) | interface Row {
type Replicated (line 477) | type Replicated =
type Value (line 481) | type Value = null | string | number | bigint | ArrayBuffer;
type InValue (line 483) | type InValue = Value | boolean | Uint8Array | Date;
type InStatement (line 485) | type InStatement = { sql: string; args?: InArgs } | string;
type InArgs (line 486) | type InArgs = Array<InValue> | Record<string, InValue>;
class LibsqlError (line 489) | class LibsqlError extends Error {
method constructor (line 497) | constructor(
class LibsqlBatchError (line 516) | class LibsqlBatchError extends LibsqlError {
method constructor (line 520) | constructor(
FILE: packages/libsql-core/src/config.ts
type ExpandedConfig (line 7) | interface ExpandedConfig {
type ExpandedScheme (line 24) | type ExpandedScheme = "wss" | "ws" | "https" | "http" | "file";
type queryParamDef (line 26) | type queryParamDef = {
type queryParamsDef (line 30) | type queryParamsDef = { [key: string]: queryParamDef };
function isInMemoryConfig (line 34) | function isInMemoryConfig(config: ExpandedConfig): boolean {
function expandConfig (line 41) | function expandConfig(
FILE: packages/libsql-core/src/uri.ts
type Uri (line 8) | interface Uri {
type HierPart (line 16) | interface HierPart {
type Authority (line 21) | interface Authority {
type Userinfo (line 27) | interface Userinfo {
type Query (line 32) | interface Query {
type KeyValue (line 36) | interface KeyValue {
function parseUri (line 41) | function parseUri(text: string): Uri {
constant URI_RE (line 66) | const URI_RE = (() => {
function parseAuthority (line 78) | function parseAuthority(text: string): Authority {
constant AUTHORITY_RE (line 103) | const AUTHORITY_RE = (() => {
function parseQuery (line 112) | function parseQuery(text: string): Query {
function percentDecode (line 139) | function percentDecode(text: string): string {
function encodeBaseUrl (line 156) | function encodeBaseUrl(
function encodeHost (line 183) | function encodeHost(host: string): string {
function encodePort (line 187) | function encodePort(port: number | undefined): string {
function encodeUserinfo (line 191) | function encodeUserinfo(userinfo: Userinfo | undefined): string {
FILE: packages/libsql-core/src/util.ts
function transactionModeToBegin (line 14) | function transactionModeToBegin(mode: TransactionMode): string {
class ResultSetImpl (line 28) | class ResultSetImpl implements ResultSet {
method constructor (line 35) | constructor(
method toJSON (line 49) | toJSON(): any {
function rowToJson (line 63) | function rowToJson(row: Row): unknown {
function valueToJson (line 67) | function valueToJson(value: Value): unknown {
FILE: testing/hrana-test-server/c3.py
class Conn (line 126) | class Conn:
method __init__ (line 127) | def __init__(self, db_ptr):
method open (line 131) | def open(cls, filename):
method close (line 139) | def close(self):
method __del__ (line 144) | def __del__(self):
method extended_result_codes (line 147) | def extended_result_codes(self, onoff):
method errmsg (line 151) | def errmsg(self):
method errstr (line 156) | def errstr(cls, code):
method exec (line 159) | def exec(self, sql):
method txn_state (line 167) | def txn_state(self):
method prepare (line 172) | def prepare(self, sql):
method changes (line 186) | def changes(self):
method total_changes (line 190) | def total_changes(self):
method last_insert_rowid (line 194) | def last_insert_rowid(self):
method limit (line 198) | def limit(self, id, new_val):
method busy_timeout (line 202) | def busy_timeout(self, ms):
method get_autocommit (line 206) | def get_autocommit(self):
class Stmt (line 210) | class Stmt:
method __init__ (line 211) | def __init__(self, conn, stmt_ptr):
method close (line 215) | def close(self):
method __del__ (line 220) | def __del__(self):
method param_count (line 223) | def param_count(self):
method param_index (line 227) | def param_index(self, name):
method param_name (line 232) | def param_name(self, param_i):
method bind (line 237) | def bind(self, param_i, value):
method step (line 255) | def step(self):
method column_count (line 264) | def column_count(self):
method column_name (line 268) | def column_name(self, column_i):
method column_decltype (line 272) | def column_decltype(self, column_i):
method column (line 277) | def column(self, column_i):
method readonly (line 302) | def readonly(self):
method isexplain (line 306) | def isexplain(self):
class SqliteError (line 311) | class SqliteError(RuntimeError):
method __init__ (line 312) | def __init__(self, message, error_code=None) -> None:
function _try (line 318) | def _try(error_code, conn=None):
FILE: testing/hrana-test-server/from_proto.py
function ws_client_msg (line 3) | def ws_client_msg(p):
function ws_hello_msg (line 12) | def ws_hello_msg(p):
function ws_request_msg (line 18) | def ws_request_msg(p):
function ws_open_stream_req (line 48) | def ws_open_stream_req(p):
function ws_close_stream_req (line 51) | def ws_close_stream_req(p):
function ws_execute_req (line 54) | def ws_execute_req(p):
function ws_batch_req (line 61) | def ws_batch_req(p):
function ws_open_cursor_req (line 68) | def ws_open_cursor_req(p):
function ws_close_cursor_req (line 76) | def ws_close_cursor_req(p):
function ws_fetch_cursor_req (line 82) | def ws_fetch_cursor_req(p):
function ws_sequence_req (line 89) | def ws_sequence_req(p):
function ws_describe_req (line 97) | def ws_describe_req(p):
function ws_store_sql_req (line 105) | def ws_store_sql_req(p):
function ws_close_sql_req (line 112) | def ws_close_sql_req(p):
function ws_get_autocommit_req (line 118) | def ws_get_autocommit_req(p):
function http_pipeline_req_body (line 126) | def http_pipeline_req_body(p):
function http_stream_request (line 132) | def http_stream_request(p):
function http_execute_stream_req (line 153) | def http_execute_stream_req(p):
function http_batch_stream_req (line 156) | def http_batch_stream_req(p):
function http_sequence_stream_req (line 159) | def http_sequence_stream_req(p):
function http_describe_stream_req (line 166) | def http_describe_stream_req(p):
function http_store_sql_stream_req (line 173) | def http_store_sql_stream_req(p):
function http_close_sql_stream_req (line 176) | def http_close_sql_stream_req(p):
function http_cursor_req_body (line 179) | def http_cursor_req_body(p):
function batch (line 187) | def batch(p):
function batch_step (line 190) | def batch_step(p):
function batch_cond (line 196) | def batch_cond(p):
function stmt (line 213) | def stmt(p):
function named_arg (line 222) | def named_arg(p):
function value (line 228) | def value(p):
FILE: testing/hrana-test-server/server_v1.py
function main (line 16) | async def main(command):
function handle_get_index (line 55) | async def handle_get_index(req):
function handle_websocket (line 67) | async def handle_websocket(app, ws):
function handle_post_execute (line 158) | async def handle_post_execute(req):
function handle_post_batch (line 170) | async def handle_post_batch(req):
function connect (line 182) | def connect(db_file):
class CloseWebSocket (line 187) | class CloseWebSocket(BaseException):
class CloseTcpSocket (line 190) | class CloseTcpSocket(BaseException):
function execute_stmt (line 193) | def execute_stmt(conn, stmt):
function execute_batch (line 248) | def execute_batch(conn, batch):
function eval_cond (line 274) | def eval_cond(step_results, step_errors, cond):
function value_to_sqlite (line 288) | def value_to_sqlite(value):
function value_from_sqlite (line 302) | def value_from_sqlite(value):
class ResponseError (line 316) | class ResponseError(RuntimeError):
function to_thread (line 319) | async def to_thread(func):
FILE: testing/hrana-test-server/server_v2.py
class HttpStream (line 21) | class HttpStream:
function main (line 26) | async def main(command):
function handle_get_index (line 69) | async def handle_get_index(req):
function handle_websocket (line 81) | async def handle_websocket(app, ws):
function handle_post_execute (line 196) | async def handle_post_execute(req):
function handle_post_batch (line 208) | async def handle_post_batch(req):
function handle_post_pipeline (line 220) | async def handle_post_pipeline(req):
function connect (line 295) | def connect(db_file):
function get_sql (line 303) | def get_sql(sqls, obj):
class CloseWebSocket (line 313) | class CloseWebSocket(BaseException):
class CloseTcpSocket (line 316) | class CloseTcpSocket(BaseException):
function execute_stmt (line 319) | def execute_stmt(conn, sqls, stmt):
function describe_stmt (line 410) | def describe_stmt(conn, sql):
function execute_sequence (line 441) | def execute_sequence(conn, sql):
function execute_batch (line 452) | def execute_batch(conn, sqls, batch):
function eval_cond (line 478) | def eval_cond(step_results, step_errors, cond):
function value_to_sqlite (line 492) | def value_to_sqlite(value):
function value_from_sqlite (line 506) | def value_from_sqlite(value):
class ResponseError (line 521) | class ResponseError(RuntimeError):
method __init__ (line 522) | def __init__(self, message, code=None):
method tojson (line 532) | def tojson(self):
function to_thread (line 539) | async def to_thread(func):
FILE: testing/hrana-test-server/server_v3.py
class HttpStream (line 27) | class HttpStream:
function main (line 32) | async def main(command):
function handle_get_index (line 79) | async def handle_get_index(req):
function handle_websocket (line 95) | async def handle_websocket(app, ws):
function handle_post_pipeline (line 245) | async def handle_post_pipeline(req):
function handle_post_cursor (line 327) | async def handle_post_cursor(req):
function handle_baton (line 367) | async def handle_baton(app, baton):
function encode_varint (line 380) | def encode_varint(num):
function connect (line 392) | def connect(db_file):
function get_sql (line 400) | def get_sql(sqls, obj):
class CloseWebSocket (line 410) | class CloseWebSocket(BaseException):
class CloseTcpSocket (line 413) | class CloseTcpSocket(BaseException):
function execute_stmt (line 416) | def execute_stmt(conn, sqls, stmt):
function describe_stmt (line 507) | def describe_stmt(conn, sql):
function execute_sequence (line 538) | def execute_sequence(conn, sql):
function execute_cursor (line 549) | def execute_cursor(conn, sqls, batch):
function execute_batch (line 578) | def execute_batch(conn, sqls, batch):
function eval_cond (line 604) | def eval_cond(conn, step_results, step_errors, cond):
function value_to_sqlite (line 620) | def value_to_sqlite(value):
function value_from_sqlite (line 634) | def value_from_sqlite(value):
class ResponseError (line 649) | class ResponseError(RuntimeError):
method __init__ (line 650) | def __init__(self, message, code=None):
method tojson (line 660) | def tojson(self):
function to_thread (line 667) | async def to_thread(func):
FILE: testing/hrana-test-server/to_proto.py
function ws_server_msg (line 5) | def ws_server_msg(p, m):
function ws_execute_resp (line 40) | def ws_execute_resp(p, m):
function ws_batch_resp (line 43) | def ws_batch_resp(p, m):
function ws_fetch_cursor_resp (line 46) | def ws_fetch_cursor_resp(p, m):
function ws_describe_resp (line 51) | def ws_describe_resp(p, m):
function ws_get_autocommit_resp (line 54) | def ws_get_autocommit_resp(p, m):
function http_pipeline_resp_body (line 59) | def http_pipeline_resp_body(p, m):
function http_stream_result (line 67) | def http_stream_result(p, m):
function http_stream_response (line 73) | def http_stream_response(p, m):
function http_cursor_resp_body (line 91) | def http_cursor_resp_body(p, m):
function error (line 99) | def error(p, m):
function cursor_entry (line 104) | def cursor_entry(p, m):
function stmt_result (line 122) | def stmt_result(p, m):
function col (line 131) | def col(p, m):
function row (line 136) | def row(p, m):
function batch_result (line 140) | def batch_result(p, m):
function describe_result (line 149) | def describe_result(p, m):
function describe_param (line 157) | def describe_param(p, m):
function describe_col (line 161) | def describe_col(p, m):
function value (line 166) | def value(p, m):
Condensed preview — 144 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (429K chars).
[
{
"path": ".github/workflows/ci.yaml",
"chars": 11276,
"preview": "name: \"CI\"\non:\n push:\n branches: [\"main\"]\n pull_request:\n\njobs:\n \"typecheck\":\n name: \"Typecheck\"\n"
},
{
"path": ".github/workflows/pages.yaml",
"chars": 1444,
"preview": "name: \"GitHub Pages\"\non:\n push:\n branches: [\"main\"]\n\njobs:\n \"build\":\n name: \"Build the docs\"\n "
},
{
"path": ".github/workflows/publish.yml",
"chars": 2380,
"preview": "name: publish\n\npermissions:\n contents: read\n id-token: write\n\non:\n push:\n tags:\n - v*\n\njobs:\n"
},
{
"path": ".gitignore",
"chars": 126,
"preview": "node_modules\n/packages/*/lib-esm\n/packages/*/lib-cjs\n/docs\n*.tsbuildinfo\nSession.vim\npackages/libsql-client/hrana-test-s"
},
{
"path": ".husky/install.mjs",
"chars": 282,
"preview": "// See https://typicode.github.io/husky/how-to.html#ci-server-and-docker\n// Skip Husky install in production and CI\nif ("
},
{
"path": ".husky/pre-commit",
"chars": 11,
"preview": "lint-staged"
},
{
"path": ".lintstagedrc.json",
"chars": 53,
"preview": "{ \"*.{js,ts,json,md,yaml,yml}\": \"prettier --write\" }\n"
},
{
"path": ".npmrc",
"chars": 46,
"preview": "//registry.npmjs.org/:_authToken=${NPM_TOKEN}\n"
},
{
"path": ".prettierignore",
"chars": 16,
"preview": "lib-cjs\nlib-esm\n"
},
{
"path": ".prettierrc",
"chars": 22,
"preview": "{\n \"tabWidth\": 4\n}\n"
},
{
"path": "CHANGELOG.md",
"chars": 7050,
"preview": "# 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"
},
{
"path": "CONTRIBUTING.md",
"chars": 561,
"preview": "# Prerequisites\n\n- Have `npm` installed and in PATH\n- Have `git` installed and in PATH\n\n# Setting up the repository "
},
{
"path": "LICENSE",
"chars": 1063,
"preview": "MIT License\n\nCopyright (c) 2023 libSQL\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof "
},
{
"path": "examples/batch/.gitignore",
"chars": 9,
"preview": "local.db\n"
},
{
"path": "examples/batch/README.md",
"chars": 296,
"preview": "# Batch\n\nThis example demonstrates how to use libSQL to execute a batch of SQL statements.\n\n## Install Dependencies\n\n```"
},
{
"path": "examples/batch/index.mjs",
"chars": 647,
"preview": "import { createClient } from \"@libsql/client\";\n\nconst client = createClient({\n url: \"file:local.db\",\n});\n\nawait clien"
},
{
"path": "examples/batch/package.json",
"chars": 176,
"preview": "{\n \"name\": \"batch\",\n \"version\": \"1.0.0\",\n \"main\": \"index.mjs\",\n \"author\": \"Giovanni Benussi\",\n \"license\": \"MIT\",\n "
},
{
"path": "examples/cloud-encryption/.gitignore",
"chars": 9,
"preview": "local.db\n"
},
{
"path": "examples/cloud-encryption/README.md",
"chars": 396,
"preview": "# Cloud Encryption\n\nThese examples demonstrates how to use Turso Cloud encryption.\n\nVisit the documentation here - [Clou"
},
{
"path": "examples/cloud-encryption/package.json",
"chars": 192,
"preview": "{\n \"name\": \"batch\",\n \"version\": \"1.0.0\",\n \"main\": \"remote.mjs\",\n \"author\": \"Turso Authors\",\n \"license\": \""
},
{
"path": "examples/cloud-encryption/remote.mjs",
"chars": 614,
"preview": "import { createClient } from \"@libsql/client\";\n\nconst client = createClient({\n url: process.env.TURSO_DATABASE_URL,\n "
},
{
"path": "examples/cloud-encryption/sync.mjs",
"chars": 644,
"preview": "import { createClient } from \"@libsql/client\";\n\nconst client = createClient({\n url: \"file:local.db\",\n syncUrl: pro"
},
{
"path": "examples/encryption/.gitignore",
"chars": 13,
"preview": "encrypted.db\n"
},
{
"path": "examples/encryption/README.md",
"chars": 320,
"preview": "# Encryption\n\nThis example demonstrates how to create and use an encrypted SQLite database with libSQL.\n\n## Install Depe"
},
{
"path": "examples/encryption/index.mjs",
"chars": 669,
"preview": "import { createClient } from \"@libsql/client\";\n\n// You should set the ENCRYPTION_KEY in a environment variable\n// For de"
},
{
"path": "examples/encryption/package.json",
"chars": 176,
"preview": "{\n \"name\": \"batch\",\n \"version\": \"1.0.0\",\n \"main\": \"index.mjs\",\n \"author\": \"Giovanni Benussi\",\n \"license\": \"MIT\",\n "
},
{
"path": "examples/local/.gitignore",
"chars": 9,
"preview": "local.db\n"
},
{
"path": "examples/local/README.md",
"chars": 273,
"preview": "# Local\n\nThis example demonstrates how to use libSQL with a local SQLite file.\n\n## Install Dependencies\n\n```bash\nnpm i\n`"
},
{
"path": "examples/local/index.mjs",
"chars": 488,
"preview": "import { createClient } from \"@libsql/client\";\n\nconst client = createClient({\n url: \"file:local.db\",\n});\n\nawait clien"
},
{
"path": "examples/local/package.json",
"chars": 176,
"preview": "{\n \"name\": \"batch\",\n \"version\": \"1.0.0\",\n \"main\": \"index.mjs\",\n \"author\": \"Giovanni Benussi\",\n \"license\": \"MIT\",\n "
},
{
"path": "examples/memory/.gitignore",
"chars": 9,
"preview": "local.db\n"
},
{
"path": "examples/memory/README.md",
"chars": 289,
"preview": "# Memory\n\nThis example demonstrates how to use libsql with an in-memory SQLite database.\n\n## Install Dependencies\n\n```ba"
},
{
"path": "examples/memory/index.mjs",
"chars": 469,
"preview": "import { createClient } from \"@libsql/client\";\n\nconst client = createClient({\n url: \":memory:\",\n});\n\nawait client.bat"
},
{
"path": "examples/memory/package.json",
"chars": 176,
"preview": "{\n \"name\": \"batch\",\n \"version\": \"1.0.0\",\n \"main\": \"index.mjs\",\n \"author\": \"Giovanni Benussi\",\n \"license\": \"MIT\",\n "
},
{
"path": "examples/ollama/.gitignore",
"chars": 9,
"preview": "local.db\n"
},
{
"path": "examples/ollama/README.md",
"chars": 587,
"preview": "# Ollama + Vector Search Example\n\nThis example demonstrates how to use libSQL vector search with a local database and Ol"
},
{
"path": "examples/ollama/index.mjs",
"chars": 3316,
"preview": "import { createClient } from \"@libsql/client\";\nimport ollama from \"ollama\";\n\nconst client = createClient({\n url: \"fil"
},
{
"path": "examples/ollama/package.json",
"chars": 165,
"preview": "{\n \"name\": \"ollama\",\n \"private\": true,\n \"main\": \"index.mjs\",\n \"dependencies\": {\n \"@libsql/client\": \"^"
},
{
"path": "examples/read-your-writes/package.json",
"chars": 205,
"preview": "{\n \"name\": \"read-your-writes\",\n \"version\": \"1.0.0\",\n \"main\": \"index.mjs\",\n \"author\": \"Levy Albuquerque\",\n "
},
{
"path": "examples/read-your-writes/read_your_writes.js",
"chars": 936,
"preview": "import { createClient } from \"@libsql/client\";\n\nconst client = createClient({\n url: \"file:local.db\",\n syncUrl: pro"
},
{
"path": "examples/remote/.gitignore",
"chars": 9,
"preview": "local.db\n"
},
{
"path": "examples/remote/README.md",
"chars": 326,
"preview": "# Remote\n\nThis example demonstrates how to use libSQL with a remote database.\n\n## Install Dependencies\n\n```bash\nnpm i\n``"
},
{
"path": "examples/remote/index.mjs",
"chars": 548,
"preview": "import { createClient } from \"@libsql/client\";\n\nconst client = createClient({\n url: process.env.TURSO_DATABASE_URL,\n "
},
{
"path": "examples/remote/package.json",
"chars": 176,
"preview": "{\n \"name\": \"batch\",\n \"version\": \"1.0.0\",\n \"main\": \"index.mjs\",\n \"author\": \"Giovanni Benussi\",\n \"license\": \"MIT\",\n "
},
{
"path": "examples/sync/.gitignore",
"chars": 35,
"preview": "local.db\nlocal.db-client_wal_index\n"
},
{
"path": "examples/sync/README.md",
"chars": 367,
"preview": "# Sync\n\nThis example demonstrates how to use libSQL with a synced database (local file synced with a remote database).\n\n"
},
{
"path": "examples/sync/index.mjs",
"chars": 578,
"preview": "import { createClient } from \"@libsql/client\";\n\nconst client = createClient({\n url: \"file:local.db\",\n syncUrl: pro"
},
{
"path": "examples/sync/package.json",
"chars": 176,
"preview": "{\n \"name\": \"batch\",\n \"version\": \"1.0.0\",\n \"main\": \"index.mjs\",\n \"author\": \"Giovanni Benussi\",\n \"license\": \"MIT\",\n "
},
{
"path": "examples/transactions/.gitignore",
"chars": 9,
"preview": "local.db\n"
},
{
"path": "examples/transactions/README.md",
"chars": 494,
"preview": "# Local\n\nThis example demonstrates how to use transactions with libSQL.\n\n## Install Dependencies\n\n```bash\nnpm i\n```\n\n## "
},
{
"path": "examples/transactions/index.mjs",
"chars": 1191,
"preview": "import { createClient } from \"@libsql/client\";\n\nconst client = createClient({\n url: \"file:local.db\",\n});\n\nawait clien"
},
{
"path": "examples/transactions/package.json",
"chars": 176,
"preview": "{\n \"name\": \"batch\",\n \"version\": \"1.0.0\",\n \"main\": \"index.mjs\",\n \"author\": \"Giovanni Benussi\",\n \"license\": \"MIT\",\n "
},
{
"path": "examples/vector/.gitignore",
"chars": 9,
"preview": "local.db\n"
},
{
"path": "examples/vector/README.md",
"chars": 328,
"preview": "# Local\n\nThis example demonstrates how to use libSQL vector search with a local database.\n\n## Install Dependencies\n\n```b"
},
{
"path": "examples/vector/index.mjs",
"chars": 788,
"preview": "import { createClient } from \"@libsql/client\";\n\nconst client = createClient({\n url: \"file:local.db\",\n});\n\nawait clien"
},
{
"path": "examples/vector/package.json",
"chars": 176,
"preview": "{\n \"name\": \"batch\",\n \"version\": \"1.0.0\",\n \"main\": \"index.mjs\",\n \"author\": \"Giovanni Benussi\",\n \"license\": \"MIT\",\n "
},
{
"path": "package.json",
"chars": 557,
"preview": "{\n \"workspaces\": [\n \"packages/libsql-core\",\n \"packages/libsql-client\",\n \"packages/libsql-client-"
},
{
"path": "packages/libsql-client/README.md",
"chars": 5243,
"preview": "<p align=\"center\">\n <a href=\"https://tur.so/turso-ts\">\n <picture>\n <img src=\"/.github/cover.png\" alt=\"libSQL Ty"
},
{
"path": "packages/libsql-client/examples/example.js",
"chars": 1029,
"preview": "import { createClient } from \"@libsql/client\";\n\nasync function example() {\n const config = {\n url: process.env"
},
{
"path": "packages/libsql-client/examples/package.json",
"chars": 218,
"preview": "{\n \"name\": \"libsql-examples\",\n \"type\": \"module\",\n \"private\": true,\n \"dependencies\": {\n \"@libsql/clien"
},
{
"path": "packages/libsql-client/examples/shell.js",
"chars": 930,
"preview": "import * as readline from \"node:readline/promises\";\nimport { stdin, stdout, argv } from \"node:process\";\nimport * as libs"
},
{
"path": "packages/libsql-client/examples/sync.js",
"chars": 1004,
"preview": "import { createClient } from \"@libsql/client\";\nimport reader from \"readline-sync\";\n\nasync function example() {\n const"
},
{
"path": "packages/libsql-client/examples/sync_offline.js",
"chars": 1022,
"preview": "import { createClient } from \"@libsql/client\";\nimport reader from \"readline-sync\";\n\nasync function example() {\n const"
},
{
"path": "packages/libsql-client/examples/sync_vector.js",
"chars": 1603,
"preview": "import { createClient } from \"@libsql/client\";\nimport reader from \"readline-sync\";\n\nasync function example() {\n const"
},
{
"path": "packages/libsql-client/jest.config.js",
"chars": 178,
"preview": "export default {\n preset: \"ts-jest/presets/default-esm\",\n moduleNameMapper: {\n \"^(\\\\.{1,2}/.*)\\\\.js$\": \"$1\""
},
{
"path": "packages/libsql-client/package-cjs.json",
"chars": 27,
"preview": "{\n \"type\": \"commonjs\"\n}\n"
},
{
"path": "packages/libsql-client/package.json",
"chars": 3612,
"preview": "{\n \"name\": \"@libsql/client\",\n \"version\": \"0.17.2\",\n \"keywords\": [\n \"libsql\",\n \"database\",\n "
},
{
"path": "packages/libsql-client/smoke_test/vercel/.gitignore",
"chars": 41,
"preview": "app/package.json\npackage-lock.json\n*.tgz\n"
},
{
"path": "packages/libsql-client/smoke_test/vercel/app/.gitignore",
"chars": 8,
"preview": ".vercel\n"
},
{
"path": "packages/libsql-client/smoke_test/vercel/app/api/function.ts",
"chars": 3402,
"preview": "import * as libsql from \"@libsql/client\";\n\nexport const config = {\n runtime: \"edge\",\n};\n\nexport default async functio"
},
{
"path": "packages/libsql-client/smoke_test/vercel/app/public/index.html",
"chars": 118,
"preview": "<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",
"chars": 184,
"preview": "{\n \"name\": \"smoke-test\",\n \"dependencies\": {\n \"@types/node\": \"20.4.2\",\n \"localtunnel\": \"^2.0.2\",\n "
},
{
"path": "packages/libsql-client/smoke_test/vercel/test.js",
"chars": 5294,
"preview": "\"use strict\";\nconst { spawn } = require(\"node:child_process\");\nconst fs = require(\"node:fs\");\nconst fetch = require(\"nod"
},
{
"path": "packages/libsql-client/smoke_test/vercel/tsconfig.json",
"chars": 326,
"preview": "{\n \"compilerOptions\": {\n \"target\": \"es5\",\n \"lib\": [\"dom\", \"esnext\"],\n \"module\": \"esnext\",\n "
},
{
"path": "packages/libsql-client/smoke_test/workers/.gitignore",
"chars": 18,
"preview": "package-lock.json\n"
},
{
"path": "packages/libsql-client/smoke_test/workers/package.json",
"chars": 97,
"preview": "{\n \"devDependencies\": {\n \"localtunnel\": \"^2.0.2\",\n \"wrangler\": \"^3.5.1\"\n }\n}\n"
},
{
"path": "packages/libsql-client/smoke_test/workers/test.js",
"chars": 3100,
"preview": "\"use strict\";\nconst localtunnel = require(\"localtunnel\");\nconst wrangler = require(\"wrangler\");\n\nconst testCases = [\"/ex"
},
{
"path": "packages/libsql-client/smoke_test/workers/worker.js",
"chars": 3240,
"preview": "import * as libsql from \"@libsql/client\";\n\nexport default {\n async fetch(request, env, ctx) {\n function respon"
},
{
"path": "packages/libsql-client/smoke_test/workers/wrangler.toml",
"chars": 96,
"preview": "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",
"chars": 55995,
"preview": "import console from \"node:console\";\nimport { expect } from \"@jest/globals\";\nimport type { MatcherFunction } from \"expect"
},
{
"path": "packages/libsql-client/src/__tests__/config.test.ts",
"chars": 6893,
"preview": "import { expect } from \"@jest/globals\";\n\nimport \"./helpers.js\";\n\nimport { expandConfig } from \"@libsql/core/config\";\nimp"
},
{
"path": "packages/libsql-client/src/__tests__/helpers.ts",
"chars": 1713,
"preview": "import { expect } from \"@jest/globals\";\nimport type { MatcherFunction } from \"expect\";\n\nimport { LibsqlError } from \"../"
},
{
"path": "packages/libsql-client/src/__tests__/mocks/handlers.ts",
"chars": 1005,
"preview": "import { http, HttpResponse } from \"msw\";\n\nexport const handlers = [\n http.get(\"http://fake-base-url.example.com/v1/j"
},
{
"path": "packages/libsql-client/src/__tests__/mocks/node.ts",
"chars": 128,
"preview": "import { setupServer } from \"msw/node\";\nimport { handlers } from \"./handlers\";\n\nexport const server = setupServer(...han"
},
{
"path": "packages/libsql-client/src/__tests__/uri.test.ts",
"chars": 9449,
"preview": "import { expect } from \"@jest/globals\";\n\nimport \"./helpers.js\";\n\nimport { parseUri, encodeBaseUrl } from \"@libsql/core/u"
},
{
"path": "packages/libsql-client/src/hrana.ts",
"chars": 15529,
"preview": "import * as hrana from \"@libsql/hrana-client\";\nimport type {\n InStatement,\n ResultSet,\n Transaction,\n Transa"
},
{
"path": "packages/libsql-client/src/http.ts",
"chars": 10697,
"preview": "import * as hrana from \"@libsql/hrana-client\";\n\nimport type { Config, Client } from \"@libsql/core/api\";\nimport type {\n "
},
{
"path": "packages/libsql-client/src/node.ts",
"chars": 996,
"preview": "import type { Config, Client } from \"@libsql/core/api\";\nimport { LibsqlError } from \"@libsql/core/api\";\nimport type { Ex"
},
{
"path": "packages/libsql-client/src/sql_cache.ts",
"chars": 3382,
"preview": "import type * as hrana from \"@libsql/hrana-client\";\n\nexport class SqlCache {\n #owner: hrana.SqlOwner;\n #sqls: Lru<"
},
{
"path": "packages/libsql-client/src/sqlite3.ts",
"chars": 19085,
"preview": "import Database from \"libsql\";\nimport { Buffer } from \"node:buffer\";\n\nimport type {\n Config,\n IntMode,\n Client,"
},
{
"path": "packages/libsql-client/src/web.ts",
"chars": 1180,
"preview": "import type { Config, Client } from \"@libsql/core/api\";\nimport { LibsqlError } from \"@libsql/core/api\";\nimport type { Ex"
},
{
"path": "packages/libsql-client/src/ws.ts",
"chars": 17255,
"preview": "import * as hrana from \"@libsql/hrana-client\";\n\nimport type {\n Config,\n IntMode,\n Client,\n Transaction,\n "
},
{
"path": "packages/libsql-client/tsconfig.base.json",
"chars": 300,
"preview": "{\n \"compilerOptions\": {\n \"moduleResolution\": \"node\",\n \"lib\": [\"esnext\"],\n \"target\": \"esnext\",\n "
},
{
"path": "packages/libsql-client/tsconfig.build-cjs.json",
"chars": 165,
"preview": "{\n \"extends\": \"./tsconfig.base.json\",\n \"compilerOptions\": {\n \"module\": \"commonjs\",\n \"declaration\": f"
},
{
"path": "packages/libsql-client/tsconfig.build-esm.json",
"chars": 162,
"preview": "{\n \"extends\": \"./tsconfig.base.json\",\n \"compilerOptions\": {\n \"module\": \"esnext\",\n \"declaration\": tru"
},
{
"path": "packages/libsql-client/tsconfig.json",
"chars": 126,
"preview": "{\n \"extends\": \"./tsconfig.base.json\",\n \"compilerOptions\": {\n \"noEmit\": true,\n \"incremental\": true\n "
},
{
"path": "packages/libsql-client/typedoc.json",
"chars": 229,
"preview": "{\n \"entryPoints\": [\"src/node.ts\"],\n \"out\": \"docs\",\n \"excludePrivate\": true,\n \"excludeInternal\": true,\n \"v"
},
{
"path": "packages/libsql-client-wasm/LICENSE",
"chars": 1063,
"preview": "MIT License\n\nCopyright (c) 2023 libSQL\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof "
},
{
"path": "packages/libsql-client-wasm/examples/browser/README.md",
"chars": 446,
"preview": "# libSQL Wasm example for browsers\n\n## Building\n\nRun the following in the `packages/libsql-client-wasm` directory:\n\n```\n"
},
{
"path": "packages/libsql-client-wasm/examples/browser/index.html",
"chars": 236,
"preview": "<html>\n <head>\n <meta charset=\"utf-8\" />\n <title>libSQL SDK Wasm Demo</title>\n <script type=\"mod"
},
{
"path": "packages/libsql-client-wasm/examples/browser/index.js",
"chars": 312,
"preview": "import { createClient } from \"@libsql/client-wasm\";\n\nasync function main() {\n const config = {\n url: \"file:loc"
},
{
"path": "packages/libsql-client-wasm/examples/browser/package.json",
"chars": 374,
"preview": "{\n \"name\": \"browser\",\n \"version\": \"1.0.0\",\n \"description\": \"\",\n \"main\": \"index.js\",\n \"type\": \"module\",\n "
},
{
"path": "packages/libsql-client-wasm/examples/node/index.js",
"chars": 457,
"preview": "import { createClient } from \"@libsql/client-wasm\";\n\nasync function main() {\n const config = {\n url: \"file:loc"
},
{
"path": "packages/libsql-client-wasm/examples/node/package.json",
"chars": 313,
"preview": "{\n \"name\": \"nodejs\",\n \"version\": \"1.0.0\",\n \"description\": \"\",\n \"main\": \"index.js\",\n \"type\": \"module\",\n "
},
{
"path": "packages/libsql-client-wasm/jest.config.js",
"chars": 178,
"preview": "export default {\n preset: \"ts-jest/presets/default-esm\",\n moduleNameMapper: {\n \"^(\\\\.{1,2}/.*)\\\\.js$\": \"$1\""
},
{
"path": "packages/libsql-client-wasm/package-cjs.json",
"chars": 27,
"preview": "{\n \"type\": \"commonjs\"\n}\n"
},
{
"path": "packages/libsql-client-wasm/package.json",
"chars": 2082,
"preview": "{\n \"name\": \"@libsql/client-wasm\",\n \"version\": \"0.17.2\",\n \"keywords\": [\n \"libsql\",\n \"database\",\n "
},
{
"path": "packages/libsql-client-wasm/src/wasm.ts",
"chars": 14325,
"preview": "import sqlite3InitModule from \"@libsql/libsql-wasm-experimental\";\n\nimport type {\n Database,\n SqlValue,\n Sqlite3"
},
{
"path": "packages/libsql-client-wasm/tsconfig.base.json",
"chars": 335,
"preview": "{\n \"compilerOptions\": {\n \"module\": \"es2022\",\n \"moduleResolution\": \"node\",\n \"lib\": [\"esnext\", \"do"
},
{
"path": "packages/libsql-client-wasm/tsconfig.build-esm.json",
"chars": 162,
"preview": "{\n \"extends\": \"./tsconfig.base.json\",\n \"compilerOptions\": {\n \"module\": \"esnext\",\n \"declaration\": tru"
},
{
"path": "packages/libsql-client-wasm/tsconfig.json",
"chars": 126,
"preview": "{\n \"extends\": \"./tsconfig.base.json\",\n \"compilerOptions\": {\n \"noEmit\": true,\n \"incremental\": true\n "
},
{
"path": "packages/libsql-client-wasm/typedoc.json",
"chars": 229,
"preview": "{\n \"entryPoints\": [\"src/node.ts\"],\n \"out\": \"docs\",\n \"excludePrivate\": true,\n \"excludeInternal\": true,\n \"v"
},
{
"path": "packages/libsql-core/jest.config.js",
"chars": 178,
"preview": "export default {\n preset: \"ts-jest/presets/default-esm\",\n moduleNameMapper: {\n \"^(\\\\.{1,2}/.*)\\\\.js$\": \"$1\""
},
{
"path": "packages/libsql-core/package-cjs.json",
"chars": 27,
"preview": "{\n \"type\": \"commonjs\"\n}\n"
},
{
"path": "packages/libsql-core/package.json",
"chars": 2536,
"preview": "{\n \"name\": \"@libsql/core\",\n \"version\": \"0.17.2\",\n \"keywords\": [\n \"libsql\",\n \"database\",\n \""
},
{
"path": "packages/libsql-core/src/api.ts",
"chars": 20394,
"preview": "/** Configuration object for {@link createClient}. */\nexport interface Config {\n /** The database URL.\n *\n * "
},
{
"path": "packages/libsql-core/src/config.ts",
"chars": 6588,
"preview": "import type { Config, IntMode } from \"./api.js\";\nimport { LibsqlError } from \"./api.js\";\nimport type { Authority } from "
},
{
"path": "packages/libsql-core/src/uri.ts",
"chars": 5795,
"preview": "// URI parser based on RFC 3986\n// We can't use the standard `URL` object, because we want to support relative `file:` U"
},
{
"path": "packages/libsql-core/src/util.ts",
"chars": 2014,
"preview": "import { Base64 } from \"js-base64\";\nimport {\n ResultSet,\n Row,\n Value,\n TransactionMode,\n InStatement,\n "
},
{
"path": "packages/libsql-core/tsconfig.base.json",
"chars": 300,
"preview": "{\n \"compilerOptions\": {\n \"moduleResolution\": \"node\",\n \"lib\": [\"esnext\"],\n \"target\": \"esnext\",\n "
},
{
"path": "packages/libsql-core/tsconfig.build-cjs.json",
"chars": 165,
"preview": "{\n \"extends\": \"./tsconfig.base.json\",\n \"compilerOptions\": {\n \"module\": \"commonjs\",\n \"declaration\": f"
},
{
"path": "packages/libsql-core/tsconfig.build-esm.json",
"chars": 162,
"preview": "{\n \"extends\": \"./tsconfig.base.json\",\n \"compilerOptions\": {\n \"module\": \"esnext\",\n \"declaration\": tru"
},
{
"path": "packages/libsql-core/tsconfig.json",
"chars": 126,
"preview": "{\n \"extends\": \"./tsconfig.base.json\",\n \"compilerOptions\": {\n \"noEmit\": true,\n \"incremental\": true\n "
},
{
"path": "packages/libsql-core/typedoc.json",
"chars": 229,
"preview": "{\n \"entryPoints\": [\"src/node.ts\"],\n \"out\": \"docs\",\n \"excludePrivate\": true,\n \"excludeInternal\": true,\n \"v"
},
{
"path": "testing/hrana-test-server/.gitignore",
"chars": 18,
"preview": "Session.vim\n*.pyc\n"
},
{
"path": "testing/hrana-test-server/LICENSE",
"chars": 1069,
"preview": "MIT License\n\nCopyright 2023 the sqld authors\n\nPermission is hereby granted, free of charge, to any person obtaining a co"
},
{
"path": "testing/hrana-test-server/README.md",
"chars": 797,
"preview": "# Test servers for Hrana\n\nThis repository contains simple Hrana servers implemented in Python, one for each version of t"
},
{
"path": "testing/hrana-test-server/c3.py",
"chars": 12198,
"preview": "import logging\nimport platform\nfrom ctypes import (\n CDLL, POINTER, CFUNCTYPE,\n pointer, byref, string_at, cast,\n "
},
{
"path": "testing/hrana-test-server/from_proto.py",
"chars": 6780,
"preview": "import base64\n\ndef ws_client_msg(p):\n ty = p.WhichOneof(\"msg\")\n if ty == \"hello\":\n return ws_hello_msg(p.he"
},
{
"path": "testing/hrana-test-server/gen_sqlite3_error_map.py",
"chars": 8448,
"preview": "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."
},
{
"path": "testing/hrana-test-server/proto/generate.sh",
"chars": 172,
"preview": "#!/bin/sh\ncd $(dirname \"$0\")\nprotoc -I. *.proto --python_out=. --experimental_allow_proto3_optional\nsed -i 's/^import hr"
},
{
"path": "testing/hrana-test-server/proto/hrana/http_pb2.py",
"chars": 6963,
"preview": "# -*- coding: utf-8 -*-\n# Generated by the protocol buffer compiler. DO NOT EDIT!\n# source: hrana.http.proto\n\"\"\"Generat"
},
{
"path": "testing/hrana-test-server/proto/hrana/ws_pb2.py",
"chars": 8911,
"preview": "# -*- coding: utf-8 -*-\n# Generated by the protocol buffer compiler. DO NOT EDIT!\n# source: hrana.ws.proto\n\"\"\"Generated"
},
{
"path": "testing/hrana-test-server/proto/hrana.http.proto",
"chars": 2039,
"preview": "syntax = \"proto3\";\npackage hrana.http;\nimport \"hrana.proto\";\n\nmessage PipelineReqBody {\n optional string baton = 1;\n r"
},
{
"path": "testing/hrana-test-server/proto/hrana.proto",
"chars": 1983,
"preview": "syntax = \"proto3\";\npackage hrana;\n\nmessage Error {\n string message = 1;\n optional string code = 2;\n}\n\nmessage Stmt {\n "
},
{
"path": "testing/hrana-test-server/proto/hrana.ws.proto",
"chars": 2782,
"preview": "syntax = \"proto3\";\npackage hrana.ws;\nimport \"hrana.proto\";\n\nmessage ClientMsg {\n oneof msg {\n HelloMsg hello = 1;\n "
},
{
"path": "testing/hrana-test-server/proto/hrana_pb2.py",
"chars": 7235,
"preview": "# -*- coding: utf-8 -*-\n# Generated by the protocol buffer compiler. DO NOT EDIT!\n# source: hrana.proto\n\"\"\"Generated pr"
},
{
"path": "testing/hrana-test-server/requirements.txt",
"chars": 32,
"preview": "aiohttp==3.8.4\nprotobuf==4.24.0\n"
},
{
"path": "testing/hrana-test-server/server_v1.py",
"chars": 10392,
"preview": "import asyncio\nimport base64\nimport collections\nimport json\nimport logging\nimport os\nimport sqlite3\nimport sys\nimport te"
},
{
"path": "testing/hrana-test-server/server_v2.py",
"chars": 18515,
"preview": "import asyncio\nimport base64\nimport collections\nimport dataclasses\nimport json\nimport logging\nimport os\nimport random\nim"
},
{
"path": "testing/hrana-test-server/server_v3.py",
"chars": 23305,
"preview": "import asyncio\nimport base64\nimport collections\nimport dataclasses\nimport json\nimport logging\nimport os\nimport random\nim"
},
{
"path": "testing/hrana-test-server/sqlite3_error_map.py",
"chars": 3497,
"preview": "sqlite_error_code_to_name = {\n 0: \"SQLITE_OK\",\n 1: \"SQLITE_ERROR\",\n 2: \"SQLITE_INTERNAL\",\n 3: \"SQLITE_PERM\","
},
{
"path": "testing/hrana-test-server/to_proto.py",
"chars": 5689,
"preview": "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.Se"
},
{
"path": "testing/test.sh",
"chars": 210,
"preview": "#!/bin/sh\n\npython3 -m venv .venv\nsource .venv/bin/activate\npip3 install aiohttp protobuf\n\nnpm run build && SERVER=test_v"
}
]
About this extraction
This page contains the full source code of the tursodatabase/libsql-client-ts GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 144 files (387.5 KB), approximately 101.2k tokens, and a symbol index with 353 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.