├── .github ├── cover.png └── workflows │ ├── ci.yaml │ ├── pages.yaml │ └── publish.yml ├── .gitignore ├── .husky ├── install.mjs └── pre-commit ├── .lintstagedrc.json ├── .npmrc ├── .prettierignore ├── .prettierrc ├── CHANGELOG.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── examples ├── batch │ ├── .gitignore │ ├── README.md │ ├── index.mjs │ ├── package-lock.json │ └── package.json ├── encryption │ ├── .gitignore │ ├── README.md │ ├── index.mjs │ ├── package-lock.json │ └── package.json ├── local │ ├── .gitignore │ ├── README.md │ ├── index.mjs │ ├── package-lock.json │ └── package.json ├── memory │ ├── .gitignore │ ├── README.md │ ├── index.mjs │ ├── package-lock.json │ └── package.json ├── ollama │ ├── .gitignore │ ├── README.md │ ├── index.mjs │ ├── package-lock.json │ └── package.json ├── read-your-writes │ ├── package-lock.json │ ├── package.json │ └── read_your_writes.js ├── remote │ ├── .gitignore │ ├── README.md │ ├── index.mjs │ ├── package-lock.json │ └── package.json ├── sync │ ├── .gitignore │ ├── README.md │ ├── index.mjs │ ├── package-lock.json │ └── package.json ├── transactions │ ├── .gitignore │ ├── README.md │ ├── index.mjs │ ├── package-lock.json │ └── package.json └── vector │ ├── .gitignore │ ├── README.md │ ├── index.mjs │ ├── package-lock.json │ └── package.json ├── package-lock.json ├── package.json └── packages ├── libsql-client-wasm ├── LICENSE ├── examples │ ├── browser │ │ ├── README.md │ │ ├── index.html │ │ ├── index.js │ │ ├── package-lock.json │ │ └── package.json │ └── node │ │ ├── index.js │ │ ├── package-lock.json │ │ └── 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-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-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 /.github/cover.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tursodatabase/libsql-client-ts/5fcb14f72317636b86f81651688c177a28b7d66a/.github/cover.png -------------------------------------------------------------------------------- /.github/workflows/ci.yaml: -------------------------------------------------------------------------------- 1 | name: "CI" 2 | on: 3 | push: 4 | branches: ["main"] 5 | pull_request: 6 | 7 | jobs: 8 | "typecheck": 9 | name: "Typecheck" 10 | runs-on: ubuntu-latest 11 | timeout-minutes: 2 12 | steps: 13 | - name: "Checkout this repo" 14 | uses: actions/checkout@v4 15 | - name: "Setup Node.js" 16 | uses: actions/setup-node@v4 17 | with: 18 | node-version: "18.x" 19 | - name: "Install node dependencies and build" 20 | run: "npm ci && npm run build" 21 | - name: "Typecheck" 22 | run: "npm run typecheck" 23 | 24 | "format-check": 25 | name: "Check formatting" 26 | runs-on: ubuntu-latest 27 | timeout-minutes: 2 28 | steps: 29 | - name: "Checkout this repo" 30 | uses: actions/checkout@v4 31 | - name: "Setup Node.js" 32 | uses: actions/setup-node@v4 33 | with: 34 | node-version: "18.x" 35 | - name: "Install node dependencies" 36 | run: "npm ci" 37 | - name: "Check formatting" 38 | run: "npm run format:check" 39 | 40 | "test-against-sqld": 41 | name: "Tests against sqld" 42 | runs-on: ubuntu-latest 43 | timeout-minutes: 2 44 | defaults: 45 | run: 46 | working-directory: ./packages/libsql-client 47 | env: { "NODE_OPTIONS": "--trace-warnings" } 48 | steps: 49 | - name: "Checkout this repo" 50 | uses: actions/checkout@v4 51 | - name: "Setup Node.js" 52 | uses: actions/setup-node@v4 53 | with: 54 | node-version: "18.x" 55 | - name: "Build core" 56 | run: "npm ci && npm run build" 57 | working-directory: ./packages/libsql-core 58 | - name: "Install npm dependencies" 59 | run: "npm ci" 60 | - name: "Build" 61 | run: "npm run build" 62 | - name: Run Docker container in the background 63 | run: docker run -d -p 8080:8080 -e SQLD_NODE=primary ghcr.io/tursodatabase/libsql-server:latest 64 | - name: Verify container is running 65 | run: docker ps 66 | - name: "Test against sqld" 67 | run: "npm test" 68 | env: { "URL": "http://localhost:8080", "SERVER": "sqld" } 69 | 70 | "wasm-test": 71 | name: "Build and test Wasm on Node.js" 72 | runs-on: ubuntu-latest 73 | timeout-minutes: 2 74 | defaults: 75 | run: 76 | working-directory: ./packages/libsql-client-wasm 77 | env: { "NODE_OPTIONS": "--trace-warnings" } 78 | steps: 79 | - name: "Checkout this repo" 80 | uses: actions/checkout@v4 81 | - name: "Setup Node.js" 82 | uses: actions/setup-node@v4 83 | with: 84 | node-version: "18.x" 85 | cache-dependency-path: "packages/libsql-client-wasm" 86 | - name: "Build core" 87 | run: "npm ci && npm run build" 88 | working-directory: ./packages/libsql-core 89 | - name: "Install npm dependencies" 90 | run: "npm ci" 91 | - name: "Build" 92 | run: "npm run build" 93 | - name: "Test example" 94 | run: "cd examples/node && npm i && node index.js" 95 | env: { "URL": "file:///tmp/example.db" } 96 | 97 | "node-test": 98 | name: "Build and test on Node.js" 99 | runs-on: ubuntu-latest 100 | timeout-minutes: 2 101 | defaults: 102 | run: 103 | working-directory: ./packages/libsql-client 104 | env: { "NODE_OPTIONS": "--trace-warnings" } 105 | steps: 106 | - name: "Checkout this repo" 107 | uses: actions/checkout@v4 108 | - name: "Setup Node.js" 109 | uses: actions/setup-node@v4 110 | with: 111 | node-version: "18.x" 112 | - name: "Build core" 113 | run: "npm ci && npm run build" 114 | working-directory: ./packages/libsql-core 115 | - name: "Install npm dependencies" 116 | run: "npm ci" 117 | - name: "Checkout hrana-test-server" 118 | uses: actions/checkout@v4 119 | with: 120 | repository: "libsql/hrana-test-server" 121 | path: "packages/libsql-client/hrana-test-server" 122 | - name: "Setup Python" 123 | uses: actions/setup-python@v4 124 | with: 125 | python-version: "3.10" 126 | - name: "Install pip dependencies" 127 | run: "pip install -r hrana-test-server/requirements.txt" 128 | 129 | - name: "Build" 130 | run: "npm run build" 131 | 132 | - name: "Test Hrana 1 over WebSocket" 133 | run: "python hrana-test-server/server_v1.py npm test" 134 | env: { "URL": "ws://localhost:8080", "SERVER": "test_v1" } 135 | - name: "Test Hrana 2 over WebSocket" 136 | run: "python hrana-test-server/server_v2.py npm test" 137 | env: { "URL": "ws://localhost:8080", "SERVER": "test_v2" } 138 | - name: "Test Hrana 2 over HTTP" 139 | run: "python hrana-test-server/server_v2.py npm test" 140 | env: { "URL": "http://localhost:8080", "SERVER": "test_v2" } 141 | # - name: "Test Hrana 3 over WebSocket" 142 | # run: "python hrana-test-server/server_v3.py npm test" 143 | # env: {"URL": "ws://localhost:8080", "SERVER": "test_v3"} 144 | # - name: "Test Hrana 3 over HTTP" 145 | # run: "python hrana-test-server/server_v3.py npm test" 146 | # env: {"URL": "http://localhost:8080", "SERVER": "test_v3"} 147 | - name: "Test local file" 148 | run: "npm test" 149 | env: { "URL": "file:///tmp/test.db" } 150 | 151 | - name: "Test example" 152 | run: "cd examples && npm i && node example.js" 153 | env: { "URL": "file:///tmp/example.db" } 154 | 155 | "workers-test": 156 | name: "Build and test with Cloudflare Workers" 157 | if: false 158 | runs-on: ubuntu-latest 159 | timeout-minutes: 2 160 | defaults: 161 | run: 162 | working-directory: ./packages/libsql-client 163 | env: 164 | "CLOUDFLARE_API_TOKEN": "${{ secrets.CLOUDFLARE_API_TOKEN }}" 165 | "CLOUDFLARE_ACCOUNT_ID": "${{ secrets.CLOUDFLARE_ACCOUNT_ID }}" 166 | steps: 167 | - name: "Checkout this repo" 168 | uses: actions/checkout@v4 169 | - name: "Setup Node.js" 170 | uses: actions/setup-node@v4 171 | with: 172 | node-version: "lts/Hydrogen" 173 | - name: "Build core" 174 | run: "npm ci && npm run build" 175 | working-directory: ./packages/libsql-core 176 | - name: "Install npm dependencies" 177 | run: "npm ci" 178 | 179 | - name: "Checkout hrana-test-server" 180 | uses: actions/checkout@v4 181 | with: 182 | repository: "libsql/hrana-test-server" 183 | path: "packages/libsql-client/hrana-test-server" 184 | - name: "Setup Python" 185 | uses: actions/setup-python@v4 186 | with: 187 | python-version: "3.10" 188 | - name: "Install pip dependencies" 189 | run: "pip install -r hrana-test-server/requirements.txt" 190 | 191 | - name: "Build" 192 | run: "npm run build" 193 | - name: "Install npm dependencies of the Workers test" 194 | run: "cd smoke_test/workers && npm link ../.." 195 | 196 | - name: "Local test with Hrana 1 over WebSocket" 197 | run: "cd smoke_test/workers && python ../../hrana-test-server/server_v1.py node --dns-result-order=ipv4first test.js" 198 | env: { "LOCAL": "1", "URL": "ws://localhost:8080" } 199 | - name: "Local test with Hrana 2 over WebSocket" 200 | run: "cd smoke_test/workers && python ../../hrana-test-server/server_v2.py node --dns-result-order=ipv4first test.js" 201 | env: { "LOCAL": "1", "URL": "ws://localhost:8080" } 202 | - name: "Local test with Hrana 2 over HTTP" 203 | run: "cd smoke_test/workers && python ../../hrana-test-server/server_v2.py node --dns-result-order=ipv4first test.js" 204 | env: { "LOCAL": "1", "URL": "http://localhost:8080" } 205 | # - name: "Local test with Hrana 3 over WebSocket" 206 | # run: "cd smoke_test/workers && python ../../hrana-test-server/server_v3.py node --dns-result-order=ipv4first test.js" 207 | # env: {"LOCAL": "1", "URL": "ws://localhost:8080"} 208 | # - name: "Local test with Hrana 3 over HTTP" 209 | # run: "cd smoke_test/workers && python ../../hrana-test-server/server_v3.py node --dns-result-order=ipv4first test.js" 210 | # env: {"LOCAL": "1", "URL": "http://localhost:8080"} 211 | 212 | # - name: "Non-local test with Hrana 1 over WebSocket" 213 | # run: "cd smoke_test/workers && python ../../hrana-test-server/server_v1.py node test.js" 214 | # env: {"LOCAL": "0", "URL": "ws://localhost:8080"} 215 | # - name: "Non-local test with Hrana 2 over WebSocket" 216 | # run: "cd smoke_test/workers && python ../../hrana-test-server/server_v2.py node test.js" 217 | # env: {"LOCAL": "0", "URL": "ws://localhost:8080"} 218 | # - name: "Non-local test with Hrana 2 over HTTP" 219 | # run: "cd smoke_test/workers && python ../../hrana-test-server/server_v2.py node test.js" 220 | # env: {"LOCAL": "0", "URL": "http://localhost:8080"} 221 | # - name: "Non-local test with Hrana 3 over WebSocket" 222 | # run: "cd smoke_test/workers && python ../../hrana-test-server/server_v3.py node test.js" 223 | # env: {"LOCAL": "0", "URL": "ws://localhost:8080"} 224 | # - name: "Non-local test with Hrana 3 over HTTP" 225 | # run: "cd smoke_test/workers && python ../../hrana-test-server/server_v3.py node test.js" 226 | # env: {"LOCAL": "0", "URL": "http://localhost:8080"} 227 | 228 | # "vercel-test": 229 | # name: "Build and test with Vercel Edge Functions" 230 | # runs-on: ubuntu-latest 231 | # env: 232 | # VERCEL_TOKEN: "${{ secrets.VERCEL_TOKEN }}" 233 | # VERCEL_PROJECT_NAME: "smoke-test" 234 | # steps: 235 | # - name: "Checkout this repo" 236 | # uses: actions/checkout@v4 237 | # - name: "Setup Node.js" 238 | # uses: actions/setup-node@v4 239 | # with: 240 | # node-version: "lts/Hydrogen" 241 | # cache: "npm" 242 | # - name: "Install npm dependencies" 243 | # run: "npm ci" 244 | 245 | # - name: "Checkout hrana-test-server" 246 | # uses: actions/checkout@v4 247 | # with: 248 | # repository: "libsql/hrana-test-server" 249 | # path: "hrana-test-server" 250 | # - name: "Setup Python" 251 | # uses: actions/setup-python@v4 252 | # with: 253 | # python-version: "3.10" 254 | # cache: "pip" 255 | # - name: "Install pip dependencies" 256 | # run: "pip install -r hrana-test-server/requirements.txt" 257 | 258 | # - name: "Build" 259 | # run: "npm run build" 260 | # - name: "Install npm dependencies of the Vercel test" 261 | # run: "cd smoke_test/vercel && npm install" 262 | 263 | # - name: "Test with Hrana 1 over WebSocket" 264 | # run: "cd smoke_test/vercel && python ../../hrana-test-server/server_v1.py node test.js" 265 | # env: {"URL": "ws://localhost:8080"} 266 | # - name: "Test with Hrana 2 over WebSocket" 267 | # run: "cd smoke_test/vercel && python ../../hrana-test-server/server_v2.py node test.js" 268 | # env: {"URL": "ws://localhost:8080"} 269 | # - name: "Test with Hrana 2 over HTTP" 270 | # run: "cd smoke_test/vercel && python ../../hrana-test-server/server_v2.py node test.js" 271 | # env: {"URL": "http://localhost:8080"} 272 | # - name: "Test with Hrana 3 over WebSocket" 273 | # run: "cd smoke_test/vercel && python ../../hrana-test-server/server_v3.py node test.js" 274 | # env: {"URL": "ws://localhost:8080"} 275 | # - name: "Test with Hrana 3 over HTTP" 276 | # run: "cd smoke_test/vercel && python ../../hrana-test-server/server_v3.py node test.js" 277 | # env: {"URL": "http://localhost:8080"} 278 | -------------------------------------------------------------------------------- /.github/workflows/pages.yaml: -------------------------------------------------------------------------------- 1 | name: "GitHub Pages" 2 | on: 3 | push: 4 | branches: ["main"] 5 | 6 | jobs: 7 | "build": 8 | name: "Build the docs" 9 | runs-on: ubuntu-latest 10 | defaults: 11 | run: 12 | working-directory: ./packages/libsql-client 13 | steps: 14 | - name: "Checkout this repo" 15 | uses: actions/checkout@v4 16 | - name: "Setup Node.js" 17 | uses: actions/setup-node@v4 18 | with: 19 | node-version: "${{ matrix.node-version }}" 20 | cache: "npm" 21 | - name: "Build core" 22 | run: "npm ci && npm run build" 23 | working-directory: ./packages/libsql-core 24 | - name: "Install npm dependencies" 25 | run: "npm ci" 26 | - name: "Build" 27 | run: "npm run typedoc" 28 | - name: "Upload GitHub Pages artifact" 29 | uses: actions/upload-pages-artifact@v3 30 | id: deployment 31 | with: 32 | path: "./packages/libsql-client/docs" 33 | 34 | "deploy": 35 | name: "Deploy the docs to GitHub Pages" 36 | needs: "build" 37 | permissions: 38 | pages: write 39 | id-token: write 40 | 41 | environment: 42 | name: github-pages 43 | url: ${{ steps.deployment.outputs.page_url }} 44 | 45 | runs-on: ubuntu-latest 46 | steps: 47 | - name: "Deploy to GitHub Pages" 48 | id: deployment 49 | uses: actions/deploy-pages@v4 50 | -------------------------------------------------------------------------------- /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | name: publish 2 | 3 | env: 4 | NPM_REGISTRY: 'https://registry.npmjs.org' 5 | 6 | on: 7 | push: 8 | tags: 9 | - v* 10 | 11 | jobs: 12 | publish-to-npm: 13 | name: "Publish new version to NPM" 14 | runs-on: ubuntu-latest 15 | timeout-minutes: 5 16 | env: 17 | NODE_OPTIONS: "--trace-warnings" 18 | NPM_TOKEN: ${{ secrets.NPM_TOKEN }} 19 | steps: 20 | - name: "Checkout this repo" 21 | uses: actions/checkout@v3 22 | 23 | - name: "Setup Node.js" 24 | uses: actions/setup-node@v3 25 | with: 26 | node-version: "18.x" 27 | 28 | - name: "Build core" 29 | run: "npm ci && npm run build" 30 | working-directory: ./packages/libsql-core 31 | 32 | - name: "Publish core (pre-release)" 33 | if: contains(github.ref, '-pre') 34 | run: npm publish --tag next 35 | working-directory: ./packages/libsql-core 36 | 37 | - name: "Publish core (latest)" 38 | if: "!contains(github.ref, '-pre')" 39 | run: npm publish 40 | working-directory: ./packages/libsql-core 41 | 42 | 43 | - name: "Install npm dependencies (client)" 44 | run: "npm ci" 45 | working-directory: ./packages/libsql-client 46 | 47 | - name: "Publish client (pre-release)" 48 | if: contains(github.ref, '-pre') 49 | run: npm publish --tag next 50 | working-directory: ./packages/libsql-client 51 | 52 | - name: "Publish client (latest)" 53 | if: "!contains(github.ref, '-pre')" 54 | run: npm publish 55 | working-directory: ./packages/libsql-client 56 | 57 | 58 | - name: "Install npm dependencies (client wasm)" 59 | run: "npm ci" 60 | working-directory: ./packages/libsql-client-wasm 61 | 62 | - name: "Publish client-wasm (pre-release)" 63 | if: contains(github.ref, '-pre') 64 | run: npm publish --tag next 65 | working-directory: ./packages/libsql-client-wasm 66 | 67 | - name: "Publish client-wasm (latest)" 68 | if: "!contains(github.ref, '-pre')" 69 | run: npm publish 70 | working-directory: ./packages/libsql-client-wasm 71 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | /packages/*/lib-esm 3 | /packages/*/lib-cjs 4 | /docs 5 | *.tsbuildinfo 6 | Session.vim 7 | packages/libsql-client/hrana-test-server 8 | -------------------------------------------------------------------------------- /.husky/install.mjs: -------------------------------------------------------------------------------- 1 | // See https://typicode.github.io/husky/how-to.html#ci-server-and-docker 2 | // Skip Husky install in production and CI 3 | if (process.env.NODE_ENV === "production" || process.env.CI === "true") { 4 | process.exit(0); 5 | } 6 | const husky = (await import("husky")).default; 7 | console.log(husky()); 8 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | lint-staged -------------------------------------------------------------------------------- /.lintstagedrc.json: -------------------------------------------------------------------------------- 1 | { "*.{js,ts,json,md,yaml,yml}": "prettier --write" } 2 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | //registry.npmjs.org/:_authToken=${NPM_TOKEN} 2 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | lib-cjs 2 | lib-esm 3 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "tabWidth": 4 3 | } 4 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## 0.15.7 -- 2025-05-20 4 | 5 | - Bump to latest `libsql` package. 6 | 7 | ## 0.15.6 -- 2025-05-14 8 | 9 | - Bump to latest `libsql` package. 10 | 11 | ## 0.15.5 -- 2025-05-11 12 | 13 | - Bump to latest `libsql` package. 14 | 15 | ## 0.15.4 -- 2025-04-15 16 | 17 | - Bump to latest `libsql` package. 18 | 19 | ## 0.15.3 -- 2025-04-11 20 | 21 | - Bump to latest `libsql` package. 22 | 23 | ## 0.15.2 -- 2025-04-01 24 | 25 | - Bump to latest `libsql` package. 26 | 27 | ## 0.15.1 -- 2025-03-24 28 | 29 | - Bump to latest `libsql` package. 30 | 31 | ## 0.15.0 -- 2025-03-17 32 | 33 | - Bump to latest `libsql` package. 34 | 35 | ## 0.15.0-pre.3 -- 2025-03-11 36 | 37 | - Fix Bun complaint about duplicate "prepare" key in `package.json` 38 | 39 | ## 0.15.0-pre.2 -- 2024-02-11 40 | 41 | - Bump to latest `libsql` package. 42 | 43 | ## 0.15.0-pre.1 -- 2024-11-15 44 | 45 | - Initial support for offline writes. 46 | 47 | ## 0.12.0 -- 2024-09-16 48 | 49 | - 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) 50 | 51 | ## 0.11.0 -- 2024-09-13 52 | 53 | - 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) 54 | 55 | ## 0.10.0 -- 2024-08-26 56 | 57 | - 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. 58 | 59 | ## 0.8.1 -- 2024-08-03 60 | 61 | - 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)). 62 | 63 | ## 0.8.0 -- 2024-07-30 64 | 65 | - No changes from 0.8.0-pre.1. 66 | 67 | ## 0.8.0-pre.1 -- 2024-07-18 68 | 69 | - Bump hrana client to 0.6.2. 70 | - 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) 71 | - Fix bug in wasm experimental client which appears when transaction are used in local mode (https://github.com/tursodatabase/libsql-client-ts/pull/231) 72 | - Add `execute(sql, args)` overload to make the API similar to other SQLite SDKs 73 | 74 | ## 0.7.0 -- 2024-06-25 75 | 76 | - Add configurable concurrency limit for parallel query execution 77 | (defaults to 20) to address socket hangup errors. 78 | 79 | ## 0.6.2 -- 2024-06-01 80 | 81 | - Fix compatibility issue with libSQL server versions that don't have migrations endpoint. 82 | 83 | ## 0.6.1 -- 2024-05-30 84 | 85 | - Add an option to `batch()` to wait for schema changes to finish when using shared schema. 86 | 87 | ## 0.6.0 -- 2024-04-28 88 | 89 | - Bump hrana client to 0.6.0, which uses native Node fetch(). Note that 90 | `@libsql/client` now requires Node 18 or later. 91 | 92 | ## 0.5.6 -- 2024-03-12 93 | 94 | - Bump `libsql` package dependency to 0.3.10 that adds `wasm32` as 95 | supported CPU, which is needed for StackBlitz compatibility. 96 | 97 | ## 0.5.5 -- 2024-03-11 98 | 99 | - Bump `@libsql/libsql-wasm-experimental"` dependency to 0.0.2, which 100 | fixes a broken sqlite3_get_autocommit() export. 101 | 102 | ## 0.5.4 -- 2024-03-11 103 | 104 | - Bump `libsql` dependency to 0.3.9, which fixes symbol not found errors on Alpine. 105 | 106 | ## 0.5.3 -- 2024-03-06 107 | 108 | - Add `syncInterval` config option to enable periodic sync. 109 | - Bump `libsql` dependency to 0.3.7, which switches default encryption cipher to aes256cbs. 110 | 111 | ## 0.5.2 -- 2024-02-24 112 | 113 | - Disable SQL statemen tracing in Wasm. 114 | 115 | ## 0.5.1 -- 2024-02-19 116 | 117 | - Update `libsql` package to 0.3.2, add `encryptionCipher` option, and switch default cipher to SQLCipher. 118 | 119 | ## 0.5.0 -- 2024-02-15 120 | 121 | - Add a `encryptionKey` config option, which enables encryption at rest for local database files. 122 | 123 | ## 0.4.0 -- 2024-01-26 124 | 125 | - Update hrana-client package to 0.5.6. 126 | - Add a `@libsql/client-wasm` package. 127 | - Fix Bun on Linux/arm64. 128 | 129 | ## 0.3.6 -- 2023-10-20 130 | 131 | - Fix import problems on Cloudflare Workers. 132 | - Add `rawCode` property to errors for local databases. 133 | - Update the `libsql` package to version 0.1.28. 134 | 135 | ## 0.3.5 -- 2023-09-25 136 | 137 | - Performance improvements for local database access by reusing connection in `Client`. 138 | - Embedded replica support. 139 | - Column introspection support via ResultSet.columnTypes property. 140 | 141 | ## 0.3.4 -- 2023-09-11 142 | 143 | - Switch to Hrana 2 by default to let Hrana 3 cook some more. 144 | 145 | ## 0.3.3 -- 2023-09-11 146 | 147 | - Updated `@libsql/hrana-client` to version 0.5.1, which has Bun support. 148 | 149 | - Switched to `libsql` package as a replacement for `better-sqlite3`. 150 | 151 | ## 0.3.2 -- 2023-07-29 152 | 153 | - Updated `@libsql/hrana-client` to version 0.5.0, which implements Hrana 3 154 | - Dropped workarounds for broken WebSocket support in Miniflare 2 155 | - Added a `@libsql/client/node` import for explicit Node.js-specific module 156 | 157 | ## 0.3.1 -- 2023-07-20 158 | 159 | - Added `ResultSet.toJSON()` to provide better JSON serialization. ([#61](https://github.com/libsql/libsql-client-ts/pull/61)) 160 | - 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)) 161 | - Added `Config.fetch` to support overriding the `fetch` implementation from `@libsql/isomorphic-fetch`. ([#66](https://github.com/libsql/libsql-client-ts/pull/66)) 162 | 163 | ## 0.3.0 -- 2023-07-07 164 | 165 | - **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)) 166 | - **Changed the default transaction mode to `"deferred"`**. ([#57](https://github.com/libsql/libsql-client-ts/pull/57)) 167 | - Added `Client.protocol` property to find out which protocol the client uses ([#54](https://github.com/libsql/libsql-client-ts/pull/54)). 168 | 169 | ## 0.2.2 -- 2023-06-22 170 | 171 | - 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)). 172 | 173 | ## 0.2.1 -- 2023-06-13 174 | 175 | - Added `TransactionMode` argument to `batch()` and `transaction()` ([#46](https://github.com/libsql/libsql-client-ts/pull/46)) 176 | - Added `Client.executeMultiple()` and `Transaction.executeMultiple()` ([#49](https://github.com/libsql/libsql-client-ts/pull/49)) 177 | - Added `Transaction.batch()` ([#49](https://github.com/libsql/libsql-client-ts/pull/49)) 178 | - **Changed the default transaction mode** from `BEGIN DEFERRED` to `BEGIN IMMEDIATE` 179 | 180 | ## 0.2.0 -- 2023-06-07 181 | 182 | - **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)) 183 | - Added `?tls=0` query parameter to turn off TLS for `libsql:` URLs 184 | - Changed the `libsql:` URL to use HTTP instead of WebSockets 185 | - Changed the `Value` type to include `bigint` (so that we can add support for reading integers as bigints in the future, without breaking compatibility) 186 | - Removed the `./hrana` import, added `./ws` to import the WebSocket-only client 187 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Prerequisites 2 | 3 | - Have `npm` installed and in PATH 4 | - Have `git` installed and in PATH 5 | 6 | # Setting up the repository for contribution 7 | 8 | - Clone this repository: `git clone https://github.com/tursodatabase/libsql-client-ts` 9 | - Change the current working directory to the cloned repository: `cd libsql-client-ts` 10 | - Install dependencies: `npm i` 11 | - Change the current working directory to `libsql-core`'s workspace: `cd packages/libsql-core` 12 | - Built the core package: `npm run build` 13 | - Go back to the root directory to start making changes: `cd ../..` 14 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 libSQL 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ./packages/libsql-client/README.md -------------------------------------------------------------------------------- /examples/batch/.gitignore: -------------------------------------------------------------------------------- 1 | local.db 2 | -------------------------------------------------------------------------------- /examples/batch/README.md: -------------------------------------------------------------------------------- 1 | # Batch 2 | 3 | This example demonstrates how to use libSQL to execute a batch of SQL statements. 4 | 5 | ## Install Dependencies 6 | 7 | ```bash 8 | npm i 9 | ``` 10 | 11 | ## Running 12 | 13 | Execute the example: 14 | 15 | ```bash 16 | node index.mjs 17 | ``` 18 | 19 | This will setup a SQLite database, execute a batch of SQL statements, and then query the results. 20 | -------------------------------------------------------------------------------- /examples/batch/index.mjs: -------------------------------------------------------------------------------- 1 | import { createClient } from "@libsql/client"; 2 | 3 | const client = createClient({ 4 | url: "file:local.db", 5 | }); 6 | 7 | await client.batch( 8 | [ 9 | "CREATE TABLE IF NOT EXISTS users (email TEXT)", 10 | { 11 | sql: "INSERT INTO users VALUES (?)", 12 | args: ["first@example.com"], 13 | }, 14 | { 15 | sql: "INSERT INTO users VALUES (?)", 16 | args: ["second@example.com"], 17 | }, 18 | { 19 | sql: "INSERT INTO users VALUES (?)", 20 | args: ["third@example.com"], 21 | }, 22 | ], 23 | "write", 24 | ); 25 | 26 | const result = await client.execute("SELECT * FROM users"); 27 | 28 | console.log("Users:", result.rows); 29 | -------------------------------------------------------------------------------- /examples/batch/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "batch", 3 | "version": "1.0.0", 4 | "lockfileVersion": 3, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "batch", 9 | "version": "1.0.0", 10 | "license": "ISC", 11 | "dependencies": { 12 | "@libsql/client": "^0.14.0" 13 | } 14 | }, 15 | "node_modules/@libsql/client": { 16 | "version": "0.14.0", 17 | "resolved": "https://registry.npmjs.org/@libsql/client/-/client-0.14.0.tgz", 18 | "integrity": "sha512-/9HEKfn6fwXB5aTEEoMeFh4CtG0ZzbncBb1e++OCdVpgKZ/xyMsIVYXm0w7Pv4RUel803vE6LwniB3PqD72R0Q==", 19 | "license": "MIT", 20 | "dependencies": { 21 | "@libsql/core": "^0.14.0", 22 | "@libsql/hrana-client": "^0.7.0", 23 | "js-base64": "^3.7.5", 24 | "libsql": "^0.4.4", 25 | "promise-limit": "^2.7.0" 26 | } 27 | }, 28 | "node_modules/@libsql/core": { 29 | "version": "0.14.0", 30 | "resolved": "https://registry.npmjs.org/@libsql/core/-/core-0.14.0.tgz", 31 | "integrity": "sha512-nhbuXf7GP3PSZgdCY2Ecj8vz187ptHlZQ0VRc751oB2C1W8jQUXKKklvt7t1LJiUTQBVJuadF628eUk+3cRi4Q==", 32 | "license": "MIT", 33 | "dependencies": { 34 | "js-base64": "^3.7.5" 35 | } 36 | }, 37 | "node_modules/@libsql/darwin-arm64": { 38 | "version": "0.4.6", 39 | "resolved": "https://registry.npmjs.org/@libsql/darwin-arm64/-/darwin-arm64-0.4.6.tgz", 40 | "integrity": "sha512-45i604CJ2Lubbg7NqtDodjarF6VgST8rS5R8xB++MoRqixtDns9PZ6tocT9pRJDWuTWEiy2sjthPOFWMKwYAsg==", 41 | "cpu": [ 42 | "arm64" 43 | ], 44 | "license": "MIT", 45 | "optional": true, 46 | "os": [ 47 | "darwin" 48 | ] 49 | }, 50 | "node_modules/@libsql/darwin-x64": { 51 | "version": "0.4.6", 52 | "resolved": "https://registry.npmjs.org/@libsql/darwin-x64/-/darwin-x64-0.4.6.tgz", 53 | "integrity": "sha512-dRKliflhfr5zOPSNgNJ6C2nZDd4YA8bTXF3MUNqNkcxQ8BffaH9uUwL9kMq89LkFIZQHcyP75bBgZctxfJ/H5Q==", 54 | "cpu": [ 55 | "x64" 56 | ], 57 | "license": "MIT", 58 | "optional": true, 59 | "os": [ 60 | "darwin" 61 | ] 62 | }, 63 | "node_modules/@libsql/hrana-client": { 64 | "version": "0.7.0", 65 | "resolved": "https://registry.npmjs.org/@libsql/hrana-client/-/hrana-client-0.7.0.tgz", 66 | "integrity": "sha512-OF8fFQSkbL7vJY9rfuegK1R7sPgQ6kFMkDamiEccNUvieQ+3urzfDFI616oPl8V7T9zRmnTkSjMOImYCAVRVuw==", 67 | "license": "MIT", 68 | "dependencies": { 69 | "@libsql/isomorphic-fetch": "^0.3.1", 70 | "@libsql/isomorphic-ws": "^0.1.5", 71 | "js-base64": "^3.7.5", 72 | "node-fetch": "^3.3.2" 73 | } 74 | }, 75 | "node_modules/@libsql/isomorphic-fetch": { 76 | "version": "0.3.1", 77 | "resolved": "https://registry.npmjs.org/@libsql/isomorphic-fetch/-/isomorphic-fetch-0.3.1.tgz", 78 | "integrity": "sha512-6kK3SUK5Uu56zPq/Las620n5aS9xJq+jMBcNSOmjhNf/MUvdyji4vrMTqD7ptY7/4/CAVEAYDeotUz60LNQHtw==", 79 | "license": "MIT", 80 | "engines": { 81 | "node": ">=18.0.0" 82 | } 83 | }, 84 | "node_modules/@libsql/isomorphic-ws": { 85 | "version": "0.1.5", 86 | "resolved": "https://registry.npmjs.org/@libsql/isomorphic-ws/-/isomorphic-ws-0.1.5.tgz", 87 | "integrity": "sha512-DtLWIH29onUYR00i0GlQ3UdcTRC6EP4u9w/h9LxpUZJWRMARk6dQwZ6Jkd+QdwVpuAOrdxt18v0K2uIYR3fwFg==", 88 | "license": "MIT", 89 | "dependencies": { 90 | "@types/ws": "^8.5.4", 91 | "ws": "^8.13.0" 92 | } 93 | }, 94 | "node_modules/@libsql/linux-arm64-gnu": { 95 | "version": "0.4.6", 96 | "resolved": "https://registry.npmjs.org/@libsql/linux-arm64-gnu/-/linux-arm64-gnu-0.4.6.tgz", 97 | "integrity": "sha512-DMPavVyY6vYPAYcQR1iOotHszg+5xSjHSg6F9kNecPX0KKdGq84zuPJmORfKOPtaWvzPewNFdML/e+s1fu09XQ==", 98 | "cpu": [ 99 | "arm64" 100 | ], 101 | "license": "MIT", 102 | "optional": true, 103 | "os": [ 104 | "linux" 105 | ] 106 | }, 107 | "node_modules/@libsql/linux-arm64-musl": { 108 | "version": "0.4.6", 109 | "resolved": "https://registry.npmjs.org/@libsql/linux-arm64-musl/-/linux-arm64-musl-0.4.6.tgz", 110 | "integrity": "sha512-whuHSYAZyclGjM3L0mKGXyWqdAy7qYvPPn+J1ve7FtGkFlM0DiIPjA5K30aWSGJSRh72sD9DBZfnu8CpfSjT6w==", 111 | "cpu": [ 112 | "arm64" 113 | ], 114 | "license": "MIT", 115 | "optional": true, 116 | "os": [ 117 | "linux" 118 | ] 119 | }, 120 | "node_modules/@libsql/linux-x64-gnu": { 121 | "version": "0.4.6", 122 | "resolved": "https://registry.npmjs.org/@libsql/linux-x64-gnu/-/linux-x64-gnu-0.4.6.tgz", 123 | "integrity": "sha512-0ggx+5RwEbYabIlDBBAvavdfIJCZ757u6nDZtBeQIhzW99EKbWG3lvkXHM3qudFb/pDWSUY4RFBm6vVtF1cJGA==", 124 | "cpu": [ 125 | "x64" 126 | ], 127 | "license": "MIT", 128 | "optional": true, 129 | "os": [ 130 | "linux" 131 | ] 132 | }, 133 | "node_modules/@libsql/linux-x64-musl": { 134 | "version": "0.4.6", 135 | "resolved": "https://registry.npmjs.org/@libsql/linux-x64-musl/-/linux-x64-musl-0.4.6.tgz", 136 | "integrity": "sha512-SWNrv7Hz72QWlbM/ZsbL35MPopZavqCUmQz2HNDZ55t0F+kt8pXuP+bbI2KvmaQ7wdsoqAA4qBmjol0+bh4ndw==", 137 | "cpu": [ 138 | "x64" 139 | ], 140 | "license": "MIT", 141 | "optional": true, 142 | "os": [ 143 | "linux" 144 | ] 145 | }, 146 | "node_modules/@libsql/win32-x64-msvc": { 147 | "version": "0.4.6", 148 | "resolved": "https://registry.npmjs.org/@libsql/win32-x64-msvc/-/win32-x64-msvc-0.4.6.tgz", 149 | "integrity": "sha512-Q0axn110zDNELfkEog3Nl8p9BU4eI/UvgaHevGyOiSDN7s0KPfj0j6jwVHk4oz3o/d/Gg3DRIxomZ4ftfTOy/g==", 150 | "cpu": [ 151 | "x64" 152 | ], 153 | "license": "MIT", 154 | "optional": true, 155 | "os": [ 156 | "win32" 157 | ] 158 | }, 159 | "node_modules/@neon-rs/load": { 160 | "version": "0.0.4", 161 | "resolved": "https://registry.npmjs.org/@neon-rs/load/-/load-0.0.4.tgz", 162 | "integrity": "sha512-kTPhdZyTQxB+2wpiRcFWrDcejc4JI6tkPuS7UZCG4l6Zvc5kU/gGQ/ozvHTh1XR5tS+UlfAfGuPajjzQjCiHCw==", 163 | "license": "MIT" 164 | }, 165 | "node_modules/@types/node": { 166 | "version": "22.7.5", 167 | "resolved": "https://registry.npmjs.org/@types/node/-/node-22.7.5.tgz", 168 | "integrity": "sha512-jML7s2NAzMWc//QSJ1a3prpk78cOPchGvXJsC3C6R6PSMoooztvRVQEz89gmBTBY1SPMaqo5teB4uNHPdetShQ==", 169 | "license": "MIT", 170 | "dependencies": { 171 | "undici-types": "~6.19.2" 172 | } 173 | }, 174 | "node_modules/@types/ws": { 175 | "version": "8.5.12", 176 | "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.12.tgz", 177 | "integrity": "sha512-3tPRkv1EtkDpzlgyKyI8pGsGZAGPEaXeu0DOj5DI25Ja91bdAYddYHbADRYVrZMRbfW+1l5YwXVDKohDJNQxkQ==", 178 | "license": "MIT", 179 | "dependencies": { 180 | "@types/node": "*" 181 | } 182 | }, 183 | "node_modules/data-uri-to-buffer": { 184 | "version": "4.0.1", 185 | "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz", 186 | "integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==", 187 | "license": "MIT", 188 | "engines": { 189 | "node": ">= 12" 190 | } 191 | }, 192 | "node_modules/detect-libc": { 193 | "version": "2.0.2", 194 | "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.2.tgz", 195 | "integrity": "sha512-UX6sGumvvqSaXgdKGUsgZWqcUyIXZ/vZTrlRT/iobiKhGL0zL4d3osHj3uqllWJK+i+sixDS/3COVEOFbupFyw==", 196 | "license": "Apache-2.0", 197 | "engines": { 198 | "node": ">=8" 199 | } 200 | }, 201 | "node_modules/fetch-blob": { 202 | "version": "3.2.0", 203 | "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz", 204 | "integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==", 205 | "funding": [ 206 | { 207 | "type": "github", 208 | "url": "https://github.com/sponsors/jimmywarting" 209 | }, 210 | { 211 | "type": "paypal", 212 | "url": "https://paypal.me/jimmywarting" 213 | } 214 | ], 215 | "license": "MIT", 216 | "dependencies": { 217 | "node-domexception": "^1.0.0", 218 | "web-streams-polyfill": "^3.0.3" 219 | }, 220 | "engines": { 221 | "node": "^12.20 || >= 14.13" 222 | } 223 | }, 224 | "node_modules/formdata-polyfill": { 225 | "version": "4.0.10", 226 | "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz", 227 | "integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==", 228 | "license": "MIT", 229 | "dependencies": { 230 | "fetch-blob": "^3.1.2" 231 | }, 232 | "engines": { 233 | "node": ">=12.20.0" 234 | } 235 | }, 236 | "node_modules/js-base64": { 237 | "version": "3.7.7", 238 | "resolved": "https://registry.npmjs.org/js-base64/-/js-base64-3.7.7.tgz", 239 | "integrity": "sha512-7rCnleh0z2CkXhH67J8K1Ytz0b2Y+yxTPL+/KOJoa20hfnVQ/3/T6W/KflYI4bRHRagNeXeU2bkNGI3v1oS/lw==", 240 | "license": "BSD-3-Clause" 241 | }, 242 | "node_modules/libsql": { 243 | "version": "0.4.6", 244 | "resolved": "https://registry.npmjs.org/libsql/-/libsql-0.4.6.tgz", 245 | "integrity": "sha512-F5M+ltteK6dCcpjMahrkgT96uFJvVI8aQ4r9f2AzHQjC7BkAYtvfMSTWGvRBezRgMUIU2h1Sy0pF9nOGOD5iyA==", 246 | "cpu": [ 247 | "x64", 248 | "arm64", 249 | "wasm32" 250 | ], 251 | "license": "MIT", 252 | "os": [ 253 | "darwin", 254 | "linux", 255 | "win32" 256 | ], 257 | "dependencies": { 258 | "@neon-rs/load": "^0.0.4", 259 | "detect-libc": "2.0.2" 260 | }, 261 | "optionalDependencies": { 262 | "@libsql/darwin-arm64": "0.4.6", 263 | "@libsql/darwin-x64": "0.4.6", 264 | "@libsql/linux-arm64-gnu": "0.4.6", 265 | "@libsql/linux-arm64-musl": "0.4.6", 266 | "@libsql/linux-x64-gnu": "0.4.6", 267 | "@libsql/linux-x64-musl": "0.4.6", 268 | "@libsql/win32-x64-msvc": "0.4.6" 269 | } 270 | }, 271 | "node_modules/node-domexception": { 272 | "version": "1.0.0", 273 | "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", 274 | "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==", 275 | "funding": [ 276 | { 277 | "type": "github", 278 | "url": "https://github.com/sponsors/jimmywarting" 279 | }, 280 | { 281 | "type": "github", 282 | "url": "https://paypal.me/jimmywarting" 283 | } 284 | ], 285 | "license": "MIT", 286 | "engines": { 287 | "node": ">=10.5.0" 288 | } 289 | }, 290 | "node_modules/node-fetch": { 291 | "version": "3.3.2", 292 | "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz", 293 | "integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==", 294 | "license": "MIT", 295 | "dependencies": { 296 | "data-uri-to-buffer": "^4.0.0", 297 | "fetch-blob": "^3.1.4", 298 | "formdata-polyfill": "^4.0.10" 299 | }, 300 | "engines": { 301 | "node": "^12.20.0 || ^14.13.1 || >=16.0.0" 302 | }, 303 | "funding": { 304 | "type": "opencollective", 305 | "url": "https://opencollective.com/node-fetch" 306 | } 307 | }, 308 | "node_modules/promise-limit": { 309 | "version": "2.7.0", 310 | "resolved": "https://registry.npmjs.org/promise-limit/-/promise-limit-2.7.0.tgz", 311 | "integrity": "sha512-7nJ6v5lnJsXwGprnGXga4wx6d1POjvi5Qmf1ivTRxTjH4Z/9Czja/UCMLVmB9N93GeWOU93XaFaEt6jbuoagNw==", 312 | "license": "ISC" 313 | }, 314 | "node_modules/undici-types": { 315 | "version": "6.19.8", 316 | "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", 317 | "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==", 318 | "license": "MIT" 319 | }, 320 | "node_modules/web-streams-polyfill": { 321 | "version": "3.3.3", 322 | "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz", 323 | "integrity": "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==", 324 | "license": "MIT", 325 | "engines": { 326 | "node": ">= 8" 327 | } 328 | }, 329 | "node_modules/ws": { 330 | "version": "8.18.0", 331 | "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz", 332 | "integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==", 333 | "license": "MIT", 334 | "engines": { 335 | "node": ">=10.0.0" 336 | }, 337 | "peerDependencies": { 338 | "bufferutil": "^4.0.1", 339 | "utf-8-validate": ">=5.0.2" 340 | }, 341 | "peerDependenciesMeta": { 342 | "bufferutil": { 343 | "optional": true 344 | }, 345 | "utf-8-validate": { 346 | "optional": true 347 | } 348 | } 349 | } 350 | } 351 | } 352 | -------------------------------------------------------------------------------- /examples/batch/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "batch", 3 | "version": "1.0.0", 4 | "main": "index.mjs", 5 | "author": "Giovanni Benussi", 6 | "license": "MIT", 7 | "dependencies": { 8 | "@libsql/client": "^0.14.0" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /examples/encryption/.gitignore: -------------------------------------------------------------------------------- 1 | encrypted.db 2 | -------------------------------------------------------------------------------- /examples/encryption/README.md: -------------------------------------------------------------------------------- 1 | # Encryption 2 | 3 | This example demonstrates how to create and use an encrypted SQLite database with libSQL. 4 | 5 | ## Install Dependencies 6 | 7 | ```bash 8 | npm i 9 | ``` 10 | 11 | ## Running 12 | 13 | Execute the example: 14 | 15 | ```bash 16 | node index.mjs 17 | ``` 18 | 19 | This will setup an encrypted SQLite database, execute a batch of SQL statements, and then query the results. 20 | -------------------------------------------------------------------------------- /examples/encryption/index.mjs: -------------------------------------------------------------------------------- 1 | import { createClient } from "@libsql/client"; 2 | 3 | // You should set the ENCRYPTION_KEY in a environment variable 4 | // For demo purposes, we're using a fixed key 5 | const encryptionKey = "my-safe-encryption-key"; 6 | 7 | const client = createClient({ 8 | url: "file:encrypted.db", 9 | encryptionKey, 10 | }); 11 | 12 | await client.batch( 13 | [ 14 | "CREATE TABLE IF NOT EXISTS users (email TEXT)", 15 | "INSERT INTO users VALUES ('first@example.com')", 16 | "INSERT INTO users VALUES ('second@example.com')", 17 | "INSERT INTO users VALUES ('third@example.com')", 18 | ], 19 | "write", 20 | ); 21 | 22 | const result = await client.execute("SELECT * FROM users"); 23 | 24 | console.log("Users:", result.rows); 25 | -------------------------------------------------------------------------------- /examples/encryption/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "batch", 3 | "version": "1.0.0", 4 | "main": "index.mjs", 5 | "author": "Giovanni Benussi", 6 | "license": "MIT", 7 | "dependencies": { 8 | "@libsql/client": "^0.14.0" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /examples/local/.gitignore: -------------------------------------------------------------------------------- 1 | local.db 2 | -------------------------------------------------------------------------------- /examples/local/README.md: -------------------------------------------------------------------------------- 1 | # Local 2 | 3 | This example demonstrates how to use libSQL with a local SQLite file. 4 | 5 | ## Install Dependencies 6 | 7 | ```bash 8 | npm i 9 | ``` 10 | 11 | ## Running 12 | 13 | Execute the example: 14 | 15 | ```bash 16 | node index.mjs 17 | ``` 18 | 19 | This will setup a local SQLite database, insert some data, and then query the results. 20 | -------------------------------------------------------------------------------- /examples/local/index.mjs: -------------------------------------------------------------------------------- 1 | import { createClient } from "@libsql/client"; 2 | 3 | const client = createClient({ 4 | url: "file:local.db", 5 | }); 6 | 7 | await client.batch( 8 | [ 9 | "CREATE TABLE IF NOT EXISTS users (email TEXT)", 10 | "INSERT INTO users VALUES ('first@example.com')", 11 | "INSERT INTO users VALUES ('second@example.com')", 12 | "INSERT INTO users VALUES ('third@example.com')", 13 | ], 14 | "write", 15 | ); 16 | 17 | const result = await client.execute("SELECT * FROM users"); 18 | 19 | console.log("Users:", result.rows); 20 | -------------------------------------------------------------------------------- /examples/local/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "batch", 3 | "version": "1.0.0", 4 | "lockfileVersion": 3, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "batch", 9 | "version": "1.0.0", 10 | "license": "ISC", 11 | "dependencies": { 12 | "@libsql/client": "^0.14.0" 13 | } 14 | }, 15 | "node_modules/@libsql/client": { 16 | "version": "0.14.0", 17 | "resolved": "https://registry.npmjs.org/@libsql/client/-/client-0.14.0.tgz", 18 | "integrity": "sha512-/9HEKfn6fwXB5aTEEoMeFh4CtG0ZzbncBb1e++OCdVpgKZ/xyMsIVYXm0w7Pv4RUel803vE6LwniB3PqD72R0Q==", 19 | "license": "MIT", 20 | "dependencies": { 21 | "@libsql/core": "^0.14.0", 22 | "@libsql/hrana-client": "^0.7.0", 23 | "js-base64": "^3.7.5", 24 | "libsql": "^0.4.4", 25 | "promise-limit": "^2.7.0" 26 | } 27 | }, 28 | "node_modules/@libsql/core": { 29 | "version": "0.14.0", 30 | "resolved": "https://registry.npmjs.org/@libsql/core/-/core-0.14.0.tgz", 31 | "integrity": "sha512-nhbuXf7GP3PSZgdCY2Ecj8vz187ptHlZQ0VRc751oB2C1W8jQUXKKklvt7t1LJiUTQBVJuadF628eUk+3cRi4Q==", 32 | "license": "MIT", 33 | "dependencies": { 34 | "js-base64": "^3.7.5" 35 | } 36 | }, 37 | "node_modules/@libsql/darwin-arm64": { 38 | "version": "0.4.6", 39 | "resolved": "https://registry.npmjs.org/@libsql/darwin-arm64/-/darwin-arm64-0.4.6.tgz", 40 | "integrity": "sha512-45i604CJ2Lubbg7NqtDodjarF6VgST8rS5R8xB++MoRqixtDns9PZ6tocT9pRJDWuTWEiy2sjthPOFWMKwYAsg==", 41 | "cpu": [ 42 | "arm64" 43 | ], 44 | "license": "MIT", 45 | "optional": true, 46 | "os": [ 47 | "darwin" 48 | ] 49 | }, 50 | "node_modules/@libsql/darwin-x64": { 51 | "version": "0.4.6", 52 | "resolved": "https://registry.npmjs.org/@libsql/darwin-x64/-/darwin-x64-0.4.6.tgz", 53 | "integrity": "sha512-dRKliflhfr5zOPSNgNJ6C2nZDd4YA8bTXF3MUNqNkcxQ8BffaH9uUwL9kMq89LkFIZQHcyP75bBgZctxfJ/H5Q==", 54 | "cpu": [ 55 | "x64" 56 | ], 57 | "license": "MIT", 58 | "optional": true, 59 | "os": [ 60 | "darwin" 61 | ] 62 | }, 63 | "node_modules/@libsql/hrana-client": { 64 | "version": "0.7.0", 65 | "resolved": "https://registry.npmjs.org/@libsql/hrana-client/-/hrana-client-0.7.0.tgz", 66 | "integrity": "sha512-OF8fFQSkbL7vJY9rfuegK1R7sPgQ6kFMkDamiEccNUvieQ+3urzfDFI616oPl8V7T9zRmnTkSjMOImYCAVRVuw==", 67 | "license": "MIT", 68 | "dependencies": { 69 | "@libsql/isomorphic-fetch": "^0.3.1", 70 | "@libsql/isomorphic-ws": "^0.1.5", 71 | "js-base64": "^3.7.5", 72 | "node-fetch": "^3.3.2" 73 | } 74 | }, 75 | "node_modules/@libsql/isomorphic-fetch": { 76 | "version": "0.3.1", 77 | "resolved": "https://registry.npmjs.org/@libsql/isomorphic-fetch/-/isomorphic-fetch-0.3.1.tgz", 78 | "integrity": "sha512-6kK3SUK5Uu56zPq/Las620n5aS9xJq+jMBcNSOmjhNf/MUvdyji4vrMTqD7ptY7/4/CAVEAYDeotUz60LNQHtw==", 79 | "license": "MIT", 80 | "engines": { 81 | "node": ">=18.0.0" 82 | } 83 | }, 84 | "node_modules/@libsql/isomorphic-ws": { 85 | "version": "0.1.5", 86 | "resolved": "https://registry.npmjs.org/@libsql/isomorphic-ws/-/isomorphic-ws-0.1.5.tgz", 87 | "integrity": "sha512-DtLWIH29onUYR00i0GlQ3UdcTRC6EP4u9w/h9LxpUZJWRMARk6dQwZ6Jkd+QdwVpuAOrdxt18v0K2uIYR3fwFg==", 88 | "license": "MIT", 89 | "dependencies": { 90 | "@types/ws": "^8.5.4", 91 | "ws": "^8.13.0" 92 | } 93 | }, 94 | "node_modules/@libsql/linux-arm64-gnu": { 95 | "version": "0.4.6", 96 | "resolved": "https://registry.npmjs.org/@libsql/linux-arm64-gnu/-/linux-arm64-gnu-0.4.6.tgz", 97 | "integrity": "sha512-DMPavVyY6vYPAYcQR1iOotHszg+5xSjHSg6F9kNecPX0KKdGq84zuPJmORfKOPtaWvzPewNFdML/e+s1fu09XQ==", 98 | "cpu": [ 99 | "arm64" 100 | ], 101 | "license": "MIT", 102 | "optional": true, 103 | "os": [ 104 | "linux" 105 | ] 106 | }, 107 | "node_modules/@libsql/linux-arm64-musl": { 108 | "version": "0.4.6", 109 | "resolved": "https://registry.npmjs.org/@libsql/linux-arm64-musl/-/linux-arm64-musl-0.4.6.tgz", 110 | "integrity": "sha512-whuHSYAZyclGjM3L0mKGXyWqdAy7qYvPPn+J1ve7FtGkFlM0DiIPjA5K30aWSGJSRh72sD9DBZfnu8CpfSjT6w==", 111 | "cpu": [ 112 | "arm64" 113 | ], 114 | "license": "MIT", 115 | "optional": true, 116 | "os": [ 117 | "linux" 118 | ] 119 | }, 120 | "node_modules/@libsql/linux-x64-gnu": { 121 | "version": "0.4.6", 122 | "resolved": "https://registry.npmjs.org/@libsql/linux-x64-gnu/-/linux-x64-gnu-0.4.6.tgz", 123 | "integrity": "sha512-0ggx+5RwEbYabIlDBBAvavdfIJCZ757u6nDZtBeQIhzW99EKbWG3lvkXHM3qudFb/pDWSUY4RFBm6vVtF1cJGA==", 124 | "cpu": [ 125 | "x64" 126 | ], 127 | "license": "MIT", 128 | "optional": true, 129 | "os": [ 130 | "linux" 131 | ] 132 | }, 133 | "node_modules/@libsql/linux-x64-musl": { 134 | "version": "0.4.6", 135 | "resolved": "https://registry.npmjs.org/@libsql/linux-x64-musl/-/linux-x64-musl-0.4.6.tgz", 136 | "integrity": "sha512-SWNrv7Hz72QWlbM/ZsbL35MPopZavqCUmQz2HNDZ55t0F+kt8pXuP+bbI2KvmaQ7wdsoqAA4qBmjol0+bh4ndw==", 137 | "cpu": [ 138 | "x64" 139 | ], 140 | "license": "MIT", 141 | "optional": true, 142 | "os": [ 143 | "linux" 144 | ] 145 | }, 146 | "node_modules/@libsql/win32-x64-msvc": { 147 | "version": "0.4.6", 148 | "resolved": "https://registry.npmjs.org/@libsql/win32-x64-msvc/-/win32-x64-msvc-0.4.6.tgz", 149 | "integrity": "sha512-Q0axn110zDNELfkEog3Nl8p9BU4eI/UvgaHevGyOiSDN7s0KPfj0j6jwVHk4oz3o/d/Gg3DRIxomZ4ftfTOy/g==", 150 | "cpu": [ 151 | "x64" 152 | ], 153 | "license": "MIT", 154 | "optional": true, 155 | "os": [ 156 | "win32" 157 | ] 158 | }, 159 | "node_modules/@neon-rs/load": { 160 | "version": "0.0.4", 161 | "resolved": "https://registry.npmjs.org/@neon-rs/load/-/load-0.0.4.tgz", 162 | "integrity": "sha512-kTPhdZyTQxB+2wpiRcFWrDcejc4JI6tkPuS7UZCG4l6Zvc5kU/gGQ/ozvHTh1XR5tS+UlfAfGuPajjzQjCiHCw==", 163 | "license": "MIT" 164 | }, 165 | "node_modules/@types/node": { 166 | "version": "22.7.5", 167 | "resolved": "https://registry.npmjs.org/@types/node/-/node-22.7.5.tgz", 168 | "integrity": "sha512-jML7s2NAzMWc//QSJ1a3prpk78cOPchGvXJsC3C6R6PSMoooztvRVQEz89gmBTBY1SPMaqo5teB4uNHPdetShQ==", 169 | "license": "MIT", 170 | "dependencies": { 171 | "undici-types": "~6.19.2" 172 | } 173 | }, 174 | "node_modules/@types/ws": { 175 | "version": "8.5.12", 176 | "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.12.tgz", 177 | "integrity": "sha512-3tPRkv1EtkDpzlgyKyI8pGsGZAGPEaXeu0DOj5DI25Ja91bdAYddYHbADRYVrZMRbfW+1l5YwXVDKohDJNQxkQ==", 178 | "license": "MIT", 179 | "dependencies": { 180 | "@types/node": "*" 181 | } 182 | }, 183 | "node_modules/data-uri-to-buffer": { 184 | "version": "4.0.1", 185 | "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz", 186 | "integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==", 187 | "license": "MIT", 188 | "engines": { 189 | "node": ">= 12" 190 | } 191 | }, 192 | "node_modules/detect-libc": { 193 | "version": "2.0.2", 194 | "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.2.tgz", 195 | "integrity": "sha512-UX6sGumvvqSaXgdKGUsgZWqcUyIXZ/vZTrlRT/iobiKhGL0zL4d3osHj3uqllWJK+i+sixDS/3COVEOFbupFyw==", 196 | "license": "Apache-2.0", 197 | "engines": { 198 | "node": ">=8" 199 | } 200 | }, 201 | "node_modules/fetch-blob": { 202 | "version": "3.2.0", 203 | "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz", 204 | "integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==", 205 | "funding": [ 206 | { 207 | "type": "github", 208 | "url": "https://github.com/sponsors/jimmywarting" 209 | }, 210 | { 211 | "type": "paypal", 212 | "url": "https://paypal.me/jimmywarting" 213 | } 214 | ], 215 | "license": "MIT", 216 | "dependencies": { 217 | "node-domexception": "^1.0.0", 218 | "web-streams-polyfill": "^3.0.3" 219 | }, 220 | "engines": { 221 | "node": "^12.20 || >= 14.13" 222 | } 223 | }, 224 | "node_modules/formdata-polyfill": { 225 | "version": "4.0.10", 226 | "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz", 227 | "integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==", 228 | "license": "MIT", 229 | "dependencies": { 230 | "fetch-blob": "^3.1.2" 231 | }, 232 | "engines": { 233 | "node": ">=12.20.0" 234 | } 235 | }, 236 | "node_modules/js-base64": { 237 | "version": "3.7.7", 238 | "resolved": "https://registry.npmjs.org/js-base64/-/js-base64-3.7.7.tgz", 239 | "integrity": "sha512-7rCnleh0z2CkXhH67J8K1Ytz0b2Y+yxTPL+/KOJoa20hfnVQ/3/T6W/KflYI4bRHRagNeXeU2bkNGI3v1oS/lw==", 240 | "license": "BSD-3-Clause" 241 | }, 242 | "node_modules/libsql": { 243 | "version": "0.4.6", 244 | "resolved": "https://registry.npmjs.org/libsql/-/libsql-0.4.6.tgz", 245 | "integrity": "sha512-F5M+ltteK6dCcpjMahrkgT96uFJvVI8aQ4r9f2AzHQjC7BkAYtvfMSTWGvRBezRgMUIU2h1Sy0pF9nOGOD5iyA==", 246 | "cpu": [ 247 | "x64", 248 | "arm64", 249 | "wasm32" 250 | ], 251 | "license": "MIT", 252 | "os": [ 253 | "darwin", 254 | "linux", 255 | "win32" 256 | ], 257 | "dependencies": { 258 | "@neon-rs/load": "^0.0.4", 259 | "detect-libc": "2.0.2" 260 | }, 261 | "optionalDependencies": { 262 | "@libsql/darwin-arm64": "0.4.6", 263 | "@libsql/darwin-x64": "0.4.6", 264 | "@libsql/linux-arm64-gnu": "0.4.6", 265 | "@libsql/linux-arm64-musl": "0.4.6", 266 | "@libsql/linux-x64-gnu": "0.4.6", 267 | "@libsql/linux-x64-musl": "0.4.6", 268 | "@libsql/win32-x64-msvc": "0.4.6" 269 | } 270 | }, 271 | "node_modules/node-domexception": { 272 | "version": "1.0.0", 273 | "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", 274 | "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==", 275 | "funding": [ 276 | { 277 | "type": "github", 278 | "url": "https://github.com/sponsors/jimmywarting" 279 | }, 280 | { 281 | "type": "github", 282 | "url": "https://paypal.me/jimmywarting" 283 | } 284 | ], 285 | "license": "MIT", 286 | "engines": { 287 | "node": ">=10.5.0" 288 | } 289 | }, 290 | "node_modules/node-fetch": { 291 | "version": "3.3.2", 292 | "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz", 293 | "integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==", 294 | "license": "MIT", 295 | "dependencies": { 296 | "data-uri-to-buffer": "^4.0.0", 297 | "fetch-blob": "^3.1.4", 298 | "formdata-polyfill": "^4.0.10" 299 | }, 300 | "engines": { 301 | "node": "^12.20.0 || ^14.13.1 || >=16.0.0" 302 | }, 303 | "funding": { 304 | "type": "opencollective", 305 | "url": "https://opencollective.com/node-fetch" 306 | } 307 | }, 308 | "node_modules/promise-limit": { 309 | "version": "2.7.0", 310 | "resolved": "https://registry.npmjs.org/promise-limit/-/promise-limit-2.7.0.tgz", 311 | "integrity": "sha512-7nJ6v5lnJsXwGprnGXga4wx6d1POjvi5Qmf1ivTRxTjH4Z/9Czja/UCMLVmB9N93GeWOU93XaFaEt6jbuoagNw==", 312 | "license": "ISC" 313 | }, 314 | "node_modules/undici-types": { 315 | "version": "6.19.8", 316 | "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", 317 | "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==", 318 | "license": "MIT" 319 | }, 320 | "node_modules/web-streams-polyfill": { 321 | "version": "3.3.3", 322 | "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz", 323 | "integrity": "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==", 324 | "license": "MIT", 325 | "engines": { 326 | "node": ">= 8" 327 | } 328 | }, 329 | "node_modules/ws": { 330 | "version": "8.18.0", 331 | "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz", 332 | "integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==", 333 | "license": "MIT", 334 | "engines": { 335 | "node": ">=10.0.0" 336 | }, 337 | "peerDependencies": { 338 | "bufferutil": "^4.0.1", 339 | "utf-8-validate": ">=5.0.2" 340 | }, 341 | "peerDependenciesMeta": { 342 | "bufferutil": { 343 | "optional": true 344 | }, 345 | "utf-8-validate": { 346 | "optional": true 347 | } 348 | } 349 | } 350 | } 351 | } 352 | -------------------------------------------------------------------------------- /examples/local/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "batch", 3 | "version": "1.0.0", 4 | "main": "index.mjs", 5 | "author": "Giovanni Benussi", 6 | "license": "MIT", 7 | "dependencies": { 8 | "@libsql/client": "^0.14.0" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /examples/memory/.gitignore: -------------------------------------------------------------------------------- 1 | local.db 2 | -------------------------------------------------------------------------------- /examples/memory/README.md: -------------------------------------------------------------------------------- 1 | # Memory 2 | 3 | This example demonstrates how to use libsql with an in-memory SQLite database. 4 | 5 | ## Install Dependencies 6 | 7 | ```bash 8 | npm i 9 | ``` 10 | 11 | ## Running 12 | 13 | Execute the example: 14 | 15 | ```bash 16 | node index.mjs 17 | ``` 18 | 19 | This will create an in-memory SQLite database, insert some data, and then query the results. 20 | -------------------------------------------------------------------------------- /examples/memory/index.mjs: -------------------------------------------------------------------------------- 1 | import { createClient } from "@libsql/client"; 2 | 3 | const client = createClient({ 4 | url: ":memory:", 5 | }); 6 | 7 | await client.batch( 8 | [ 9 | "CREATE TABLE users (email TEXT)", 10 | "INSERT INTO users VALUES ('first@example.com')", 11 | "INSERT INTO users VALUES ('second@example.com')", 12 | "INSERT INTO users VALUES ('third@example.com')", 13 | ], 14 | "write", 15 | ); 16 | 17 | const result = await client.execute("SELECT * FROM users"); 18 | 19 | console.log("Users:", result.rows); 20 | -------------------------------------------------------------------------------- /examples/memory/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "batch", 3 | "version": "1.0.0", 4 | "lockfileVersion": 3, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "batch", 9 | "version": "1.0.0", 10 | "license": "ISC", 11 | "dependencies": { 12 | "@libsql/client": "^0.14.0" 13 | } 14 | }, 15 | "node_modules/@libsql/client": { 16 | "version": "0.14.0", 17 | "resolved": "https://registry.npmjs.org/@libsql/client/-/client-0.14.0.tgz", 18 | "integrity": "sha512-/9HEKfn6fwXB5aTEEoMeFh4CtG0ZzbncBb1e++OCdVpgKZ/xyMsIVYXm0w7Pv4RUel803vE6LwniB3PqD72R0Q==", 19 | "license": "MIT", 20 | "dependencies": { 21 | "@libsql/core": "^0.14.0", 22 | "@libsql/hrana-client": "^0.7.0", 23 | "js-base64": "^3.7.5", 24 | "libsql": "^0.4.4", 25 | "promise-limit": "^2.7.0" 26 | } 27 | }, 28 | "node_modules/@libsql/core": { 29 | "version": "0.14.0", 30 | "resolved": "https://registry.npmjs.org/@libsql/core/-/core-0.14.0.tgz", 31 | "integrity": "sha512-nhbuXf7GP3PSZgdCY2Ecj8vz187ptHlZQ0VRc751oB2C1W8jQUXKKklvt7t1LJiUTQBVJuadF628eUk+3cRi4Q==", 32 | "license": "MIT", 33 | "dependencies": { 34 | "js-base64": "^3.7.5" 35 | } 36 | }, 37 | "node_modules/@libsql/darwin-arm64": { 38 | "version": "0.4.6", 39 | "resolved": "https://registry.npmjs.org/@libsql/darwin-arm64/-/darwin-arm64-0.4.6.tgz", 40 | "integrity": "sha512-45i604CJ2Lubbg7NqtDodjarF6VgST8rS5R8xB++MoRqixtDns9PZ6tocT9pRJDWuTWEiy2sjthPOFWMKwYAsg==", 41 | "cpu": [ 42 | "arm64" 43 | ], 44 | "license": "MIT", 45 | "optional": true, 46 | "os": [ 47 | "darwin" 48 | ] 49 | }, 50 | "node_modules/@libsql/darwin-x64": { 51 | "version": "0.4.6", 52 | "resolved": "https://registry.npmjs.org/@libsql/darwin-x64/-/darwin-x64-0.4.6.tgz", 53 | "integrity": "sha512-dRKliflhfr5zOPSNgNJ6C2nZDd4YA8bTXF3MUNqNkcxQ8BffaH9uUwL9kMq89LkFIZQHcyP75bBgZctxfJ/H5Q==", 54 | "cpu": [ 55 | "x64" 56 | ], 57 | "license": "MIT", 58 | "optional": true, 59 | "os": [ 60 | "darwin" 61 | ] 62 | }, 63 | "node_modules/@libsql/hrana-client": { 64 | "version": "0.7.0", 65 | "resolved": "https://registry.npmjs.org/@libsql/hrana-client/-/hrana-client-0.7.0.tgz", 66 | "integrity": "sha512-OF8fFQSkbL7vJY9rfuegK1R7sPgQ6kFMkDamiEccNUvieQ+3urzfDFI616oPl8V7T9zRmnTkSjMOImYCAVRVuw==", 67 | "license": "MIT", 68 | "dependencies": { 69 | "@libsql/isomorphic-fetch": "^0.3.1", 70 | "@libsql/isomorphic-ws": "^0.1.5", 71 | "js-base64": "^3.7.5", 72 | "node-fetch": "^3.3.2" 73 | } 74 | }, 75 | "node_modules/@libsql/isomorphic-fetch": { 76 | "version": "0.3.1", 77 | "resolved": "https://registry.npmjs.org/@libsql/isomorphic-fetch/-/isomorphic-fetch-0.3.1.tgz", 78 | "integrity": "sha512-6kK3SUK5Uu56zPq/Las620n5aS9xJq+jMBcNSOmjhNf/MUvdyji4vrMTqD7ptY7/4/CAVEAYDeotUz60LNQHtw==", 79 | "license": "MIT", 80 | "engines": { 81 | "node": ">=18.0.0" 82 | } 83 | }, 84 | "node_modules/@libsql/isomorphic-ws": { 85 | "version": "0.1.5", 86 | "resolved": "https://registry.npmjs.org/@libsql/isomorphic-ws/-/isomorphic-ws-0.1.5.tgz", 87 | "integrity": "sha512-DtLWIH29onUYR00i0GlQ3UdcTRC6EP4u9w/h9LxpUZJWRMARk6dQwZ6Jkd+QdwVpuAOrdxt18v0K2uIYR3fwFg==", 88 | "license": "MIT", 89 | "dependencies": { 90 | "@types/ws": "^8.5.4", 91 | "ws": "^8.13.0" 92 | } 93 | }, 94 | "node_modules/@libsql/linux-arm64-gnu": { 95 | "version": "0.4.6", 96 | "resolved": "https://registry.npmjs.org/@libsql/linux-arm64-gnu/-/linux-arm64-gnu-0.4.6.tgz", 97 | "integrity": "sha512-DMPavVyY6vYPAYcQR1iOotHszg+5xSjHSg6F9kNecPX0KKdGq84zuPJmORfKOPtaWvzPewNFdML/e+s1fu09XQ==", 98 | "cpu": [ 99 | "arm64" 100 | ], 101 | "license": "MIT", 102 | "optional": true, 103 | "os": [ 104 | "linux" 105 | ] 106 | }, 107 | "node_modules/@libsql/linux-arm64-musl": { 108 | "version": "0.4.6", 109 | "resolved": "https://registry.npmjs.org/@libsql/linux-arm64-musl/-/linux-arm64-musl-0.4.6.tgz", 110 | "integrity": "sha512-whuHSYAZyclGjM3L0mKGXyWqdAy7qYvPPn+J1ve7FtGkFlM0DiIPjA5K30aWSGJSRh72sD9DBZfnu8CpfSjT6w==", 111 | "cpu": [ 112 | "arm64" 113 | ], 114 | "license": "MIT", 115 | "optional": true, 116 | "os": [ 117 | "linux" 118 | ] 119 | }, 120 | "node_modules/@libsql/linux-x64-gnu": { 121 | "version": "0.4.6", 122 | "resolved": "https://registry.npmjs.org/@libsql/linux-x64-gnu/-/linux-x64-gnu-0.4.6.tgz", 123 | "integrity": "sha512-0ggx+5RwEbYabIlDBBAvavdfIJCZ757u6nDZtBeQIhzW99EKbWG3lvkXHM3qudFb/pDWSUY4RFBm6vVtF1cJGA==", 124 | "cpu": [ 125 | "x64" 126 | ], 127 | "license": "MIT", 128 | "optional": true, 129 | "os": [ 130 | "linux" 131 | ] 132 | }, 133 | "node_modules/@libsql/linux-x64-musl": { 134 | "version": "0.4.6", 135 | "resolved": "https://registry.npmjs.org/@libsql/linux-x64-musl/-/linux-x64-musl-0.4.6.tgz", 136 | "integrity": "sha512-SWNrv7Hz72QWlbM/ZsbL35MPopZavqCUmQz2HNDZ55t0F+kt8pXuP+bbI2KvmaQ7wdsoqAA4qBmjol0+bh4ndw==", 137 | "cpu": [ 138 | "x64" 139 | ], 140 | "license": "MIT", 141 | "optional": true, 142 | "os": [ 143 | "linux" 144 | ] 145 | }, 146 | "node_modules/@libsql/win32-x64-msvc": { 147 | "version": "0.4.6", 148 | "resolved": "https://registry.npmjs.org/@libsql/win32-x64-msvc/-/win32-x64-msvc-0.4.6.tgz", 149 | "integrity": "sha512-Q0axn110zDNELfkEog3Nl8p9BU4eI/UvgaHevGyOiSDN7s0KPfj0j6jwVHk4oz3o/d/Gg3DRIxomZ4ftfTOy/g==", 150 | "cpu": [ 151 | "x64" 152 | ], 153 | "license": "MIT", 154 | "optional": true, 155 | "os": [ 156 | "win32" 157 | ] 158 | }, 159 | "node_modules/@neon-rs/load": { 160 | "version": "0.0.4", 161 | "resolved": "https://registry.npmjs.org/@neon-rs/load/-/load-0.0.4.tgz", 162 | "integrity": "sha512-kTPhdZyTQxB+2wpiRcFWrDcejc4JI6tkPuS7UZCG4l6Zvc5kU/gGQ/ozvHTh1XR5tS+UlfAfGuPajjzQjCiHCw==", 163 | "license": "MIT" 164 | }, 165 | "node_modules/@types/node": { 166 | "version": "22.7.5", 167 | "resolved": "https://registry.npmjs.org/@types/node/-/node-22.7.5.tgz", 168 | "integrity": "sha512-jML7s2NAzMWc//QSJ1a3prpk78cOPchGvXJsC3C6R6PSMoooztvRVQEz89gmBTBY1SPMaqo5teB4uNHPdetShQ==", 169 | "license": "MIT", 170 | "dependencies": { 171 | "undici-types": "~6.19.2" 172 | } 173 | }, 174 | "node_modules/@types/ws": { 175 | "version": "8.5.12", 176 | "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.12.tgz", 177 | "integrity": "sha512-3tPRkv1EtkDpzlgyKyI8pGsGZAGPEaXeu0DOj5DI25Ja91bdAYddYHbADRYVrZMRbfW+1l5YwXVDKohDJNQxkQ==", 178 | "license": "MIT", 179 | "dependencies": { 180 | "@types/node": "*" 181 | } 182 | }, 183 | "node_modules/data-uri-to-buffer": { 184 | "version": "4.0.1", 185 | "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz", 186 | "integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==", 187 | "license": "MIT", 188 | "engines": { 189 | "node": ">= 12" 190 | } 191 | }, 192 | "node_modules/detect-libc": { 193 | "version": "2.0.2", 194 | "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.2.tgz", 195 | "integrity": "sha512-UX6sGumvvqSaXgdKGUsgZWqcUyIXZ/vZTrlRT/iobiKhGL0zL4d3osHj3uqllWJK+i+sixDS/3COVEOFbupFyw==", 196 | "license": "Apache-2.0", 197 | "engines": { 198 | "node": ">=8" 199 | } 200 | }, 201 | "node_modules/fetch-blob": { 202 | "version": "3.2.0", 203 | "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz", 204 | "integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==", 205 | "funding": [ 206 | { 207 | "type": "github", 208 | "url": "https://github.com/sponsors/jimmywarting" 209 | }, 210 | { 211 | "type": "paypal", 212 | "url": "https://paypal.me/jimmywarting" 213 | } 214 | ], 215 | "license": "MIT", 216 | "dependencies": { 217 | "node-domexception": "^1.0.0", 218 | "web-streams-polyfill": "^3.0.3" 219 | }, 220 | "engines": { 221 | "node": "^12.20 || >= 14.13" 222 | } 223 | }, 224 | "node_modules/formdata-polyfill": { 225 | "version": "4.0.10", 226 | "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz", 227 | "integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==", 228 | "license": "MIT", 229 | "dependencies": { 230 | "fetch-blob": "^3.1.2" 231 | }, 232 | "engines": { 233 | "node": ">=12.20.0" 234 | } 235 | }, 236 | "node_modules/js-base64": { 237 | "version": "3.7.7", 238 | "resolved": "https://registry.npmjs.org/js-base64/-/js-base64-3.7.7.tgz", 239 | "integrity": "sha512-7rCnleh0z2CkXhH67J8K1Ytz0b2Y+yxTPL+/KOJoa20hfnVQ/3/T6W/KflYI4bRHRagNeXeU2bkNGI3v1oS/lw==", 240 | "license": "BSD-3-Clause" 241 | }, 242 | "node_modules/libsql": { 243 | "version": "0.4.6", 244 | "resolved": "https://registry.npmjs.org/libsql/-/libsql-0.4.6.tgz", 245 | "integrity": "sha512-F5M+ltteK6dCcpjMahrkgT96uFJvVI8aQ4r9f2AzHQjC7BkAYtvfMSTWGvRBezRgMUIU2h1Sy0pF9nOGOD5iyA==", 246 | "cpu": [ 247 | "x64", 248 | "arm64", 249 | "wasm32" 250 | ], 251 | "license": "MIT", 252 | "os": [ 253 | "darwin", 254 | "linux", 255 | "win32" 256 | ], 257 | "dependencies": { 258 | "@neon-rs/load": "^0.0.4", 259 | "detect-libc": "2.0.2" 260 | }, 261 | "optionalDependencies": { 262 | "@libsql/darwin-arm64": "0.4.6", 263 | "@libsql/darwin-x64": "0.4.6", 264 | "@libsql/linux-arm64-gnu": "0.4.6", 265 | "@libsql/linux-arm64-musl": "0.4.6", 266 | "@libsql/linux-x64-gnu": "0.4.6", 267 | "@libsql/linux-x64-musl": "0.4.6", 268 | "@libsql/win32-x64-msvc": "0.4.6" 269 | } 270 | }, 271 | "node_modules/node-domexception": { 272 | "version": "1.0.0", 273 | "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", 274 | "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==", 275 | "funding": [ 276 | { 277 | "type": "github", 278 | "url": "https://github.com/sponsors/jimmywarting" 279 | }, 280 | { 281 | "type": "github", 282 | "url": "https://paypal.me/jimmywarting" 283 | } 284 | ], 285 | "license": "MIT", 286 | "engines": { 287 | "node": ">=10.5.0" 288 | } 289 | }, 290 | "node_modules/node-fetch": { 291 | "version": "3.3.2", 292 | "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz", 293 | "integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==", 294 | "license": "MIT", 295 | "dependencies": { 296 | "data-uri-to-buffer": "^4.0.0", 297 | "fetch-blob": "^3.1.4", 298 | "formdata-polyfill": "^4.0.10" 299 | }, 300 | "engines": { 301 | "node": "^12.20.0 || ^14.13.1 || >=16.0.0" 302 | }, 303 | "funding": { 304 | "type": "opencollective", 305 | "url": "https://opencollective.com/node-fetch" 306 | } 307 | }, 308 | "node_modules/promise-limit": { 309 | "version": "2.7.0", 310 | "resolved": "https://registry.npmjs.org/promise-limit/-/promise-limit-2.7.0.tgz", 311 | "integrity": "sha512-7nJ6v5lnJsXwGprnGXga4wx6d1POjvi5Qmf1ivTRxTjH4Z/9Czja/UCMLVmB9N93GeWOU93XaFaEt6jbuoagNw==", 312 | "license": "ISC" 313 | }, 314 | "node_modules/undici-types": { 315 | "version": "6.19.8", 316 | "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", 317 | "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==", 318 | "license": "MIT" 319 | }, 320 | "node_modules/web-streams-polyfill": { 321 | "version": "3.3.3", 322 | "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz", 323 | "integrity": "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==", 324 | "license": "MIT", 325 | "engines": { 326 | "node": ">= 8" 327 | } 328 | }, 329 | "node_modules/ws": { 330 | "version": "8.18.0", 331 | "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz", 332 | "integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==", 333 | "license": "MIT", 334 | "engines": { 335 | "node": ">=10.0.0" 336 | }, 337 | "peerDependencies": { 338 | "bufferutil": "^4.0.1", 339 | "utf-8-validate": ">=5.0.2" 340 | }, 341 | "peerDependenciesMeta": { 342 | "bufferutil": { 343 | "optional": true 344 | }, 345 | "utf-8-validate": { 346 | "optional": true 347 | } 348 | } 349 | } 350 | } 351 | } 352 | -------------------------------------------------------------------------------- /examples/memory/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "batch", 3 | "version": "1.0.0", 4 | "main": "index.mjs", 5 | "author": "Giovanni Benussi", 6 | "license": "MIT", 7 | "dependencies": { 8 | "@libsql/client": "^0.14.0" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /examples/ollama/.gitignore: -------------------------------------------------------------------------------- 1 | local.db 2 | -------------------------------------------------------------------------------- /examples/ollama/README.md: -------------------------------------------------------------------------------- 1 | # Ollama + Vector Search Example 2 | 3 | This example demonstrates how to use libSQL vector search with a local database and Ollama. 4 | 5 | ## Install Dependencies 6 | 7 | ```bash 8 | npm i 9 | ``` 10 | 11 | ## Install Ollama 12 | 13 | [Download Ollama](https://ollama.com/download) and install it. 14 | 15 | ## Running 16 | 17 | Make sure Ollama is running with the model `mistral`: 18 | 19 | ```bash 20 | ollama run mistral 21 | ``` 22 | 23 | Execute the example: 24 | 25 | ```bash 26 | node index.mjs 27 | ``` 28 | 29 | 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. 30 | -------------------------------------------------------------------------------- /examples/ollama/index.mjs: -------------------------------------------------------------------------------- 1 | import { createClient } from "@libsql/client"; 2 | import ollama from "ollama"; 3 | 4 | const client = createClient({ 5 | url: "file:local.db", 6 | }); 7 | 8 | await client.batch( 9 | [ 10 | "CREATE TABLE IF NOT EXISTS movies (id INTEGER PRIMARY KEY, title TEXT NOT NULL, description TEXT NOT NULL, embedding F32_BLOB(4096))", 11 | "CREATE INDEX IF NOT EXISTS movies_embedding_idx ON movies(libsql_vector_idx(embedding))", 12 | ], 13 | "write", 14 | ); 15 | 16 | async function getEmbedding(prompt) { 17 | const response = await ollama.embeddings({ 18 | model: "mistral", 19 | prompt, 20 | }); 21 | 22 | return response.embedding; 23 | } 24 | 25 | async function insertMovie(id, title, description) { 26 | const embedding = await getEmbedding(description); 27 | 28 | await client.execute({ 29 | sql: `INSERT OR REPLACE INTO movies (id, title, description, embedding) VALUES (?, ?, ?, vector(?))`, 30 | args: [id, title, description, JSON.stringify(embedding)], 31 | }); 32 | } 33 | 34 | async function insertMovieIfNotExists(id, title, description) { 35 | const existing = await client.execute({ 36 | sql: "SELECT id FROM movies WHERE id = ?", 37 | args: [id], 38 | }); 39 | 40 | if (existing.rows.length === 0) { 41 | await insertMovie(id, title, description); 42 | console.log(`Inserted: ${title} (ID: ${id})`); 43 | } else { 44 | console.log(`Movie already exists: ${title} (ID: ${id})`); 45 | } 46 | } 47 | 48 | async function findSimilarMovies(description, limit = 3) { 49 | const queryEmbedding = await getEmbedding(description); 50 | 51 | const results = await client.execute({ 52 | sql: ` 53 | WITH vector_scores AS ( 54 | SELECT DISTINCT 55 | id, 56 | title, 57 | description, 58 | 1 - vector_distance_cos(embedding, vector32(?)) AS similarity 59 | FROM movies 60 | ORDER BY similarity DESC 61 | LIMIT ? 62 | ) 63 | SELECT id, title, description, similarity FROM vector_scores 64 | `, 65 | args: [JSON.stringify(queryEmbedding), limit], 66 | }); 67 | 68 | return results.rows; 69 | } 70 | 71 | try { 72 | const sampleMovies = [ 73 | { 74 | id: 1, 75 | title: "Inception", 76 | description: 77 | "A thief who enters the dreams of others to steal secrets from their subconscious.", 78 | }, 79 | { 80 | id: 2, 81 | title: "The Matrix", 82 | description: 83 | "A computer programmer discovers that reality as he knows it is a simulation created by machines.", 84 | }, 85 | { 86 | id: 3, 87 | title: "Interstellar", 88 | description: 89 | "Astronauts travel through a wormhole in search of a new habitable planet for humanity.", 90 | }, 91 | ]; 92 | 93 | for (const movie of sampleMovies) { 94 | await insertMovieIfNotExists(movie.id, movie.title, movie.description); 95 | } 96 | 97 | const query = 98 | "A sci-fi movie about virtual reality and artificial intelligence"; 99 | console.log("\nSearching for movies similar to:", query); 100 | 101 | const similarMovies = await findSimilarMovies(query); 102 | console.log("\nSimilar movies found:"); 103 | similarMovies.forEach((movie) => { 104 | console.log(`\nTitle: ${movie.title}`); 105 | console.log(`Description: ${movie.description}`); 106 | console.log(`Similarity: ${movie.similarity.toFixed(4)}`); 107 | }); 108 | } catch (error) { 109 | console.error("Error:", error); 110 | } 111 | -------------------------------------------------------------------------------- /examples/ollama/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ollama", 3 | "private": true, 4 | "main": "index.mjs", 5 | "dependencies": { 6 | "@libsql/client": "^0.14.0", 7 | "ollama": "^0.5.11" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /examples/read-your-writes/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "read-your-writes", 3 | "version": "1.0.0", 4 | "main": "index.mjs", 5 | "author": "Levy Albuquerque", 6 | "license": "MIT", 7 | "dependencies": { 8 | "@libsql/client": "^0.14.0" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /examples/read-your-writes/read_your_writes.js: -------------------------------------------------------------------------------- 1 | import { createClient } from "@libsql/client"; 2 | 3 | const client = createClient({ 4 | url: "file:local.db", 5 | syncUrl: process.env.TURSO_DATABASE_URL, 6 | authToken: process.env.TURSO_AUTH_TOKEN, 7 | readYourWrites: false, 8 | }); 9 | 10 | await client.execute("DROP TABLE users"); 11 | await client.execute("CREATE TABLE IF NOT EXISTS users (email TEXT)"); 12 | await client.sync(); 13 | 14 | await client.execute("INSERT INTO users VALUES ('first@example.com')"); 15 | await client.execute("INSERT INTO users VALUES ('second@example.com')"); 16 | await client.execute("INSERT INTO users VALUES ('third@example.com')"); 17 | 18 | { 19 | // No users, sinc no sync has happend since inserts 20 | const result = await client.execute("SELECT * FROM users"); 21 | 22 | console.log("Users:", result.rows); 23 | } 24 | 25 | { 26 | await client.sync(); 27 | 28 | // No users, sinc no sync has happend since inserts 29 | const result = await client.execute("SELECT * FROM users"); 30 | 31 | console.log("Users:", result.rows); 32 | } 33 | -------------------------------------------------------------------------------- /examples/remote/.gitignore: -------------------------------------------------------------------------------- 1 | local.db 2 | -------------------------------------------------------------------------------- /examples/remote/README.md: -------------------------------------------------------------------------------- 1 | # Remote 2 | 3 | This example demonstrates how to use libSQL with a remote database. 4 | 5 | ## Install Dependencies 6 | 7 | ```bash 8 | npm i 9 | ``` 10 | 11 | ## Running 12 | 13 | Execute the example: 14 | 15 | ```bash 16 | TURSO_DATABASE_URL="..." TURSO_AUTH_TOKEN="..." node index.mjs 17 | ``` 18 | 19 | This will connect to a remote SQLite database, insert some data, and then query the results. 20 | -------------------------------------------------------------------------------- /examples/remote/index.mjs: -------------------------------------------------------------------------------- 1 | import { createClient } from "@libsql/client"; 2 | 3 | const client = createClient({ 4 | url: process.env.TURSO_DATABASE_URL, 5 | authToken: process.env.TURSO_AUTH_TOKEN, 6 | }); 7 | 8 | await client.batch( 9 | [ 10 | "CREATE TABLE IF NOT EXISTS users (email TEXT)", 11 | "INSERT INTO users VALUES ('first@example.com')", 12 | "INSERT INTO users VALUES ('second@example.com')", 13 | "INSERT INTO users VALUES ('third@example.com')", 14 | ], 15 | "write", 16 | ); 17 | 18 | const result = await client.execute("SELECT * FROM users"); 19 | 20 | console.log("Users:", result.rows); 21 | -------------------------------------------------------------------------------- /examples/remote/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "batch", 3 | "version": "1.0.0", 4 | "main": "index.mjs", 5 | "author": "Giovanni Benussi", 6 | "license": "MIT", 7 | "dependencies": { 8 | "@libsql/client": "^0.14.0" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /examples/sync/.gitignore: -------------------------------------------------------------------------------- 1 | local.db 2 | local.db-client_wal_index 3 | -------------------------------------------------------------------------------- /examples/sync/README.md: -------------------------------------------------------------------------------- 1 | # Sync 2 | 3 | This example demonstrates how to use libSQL with a synced database (local file synced with a remote database). 4 | 5 | ## Install Dependencies 6 | 7 | ```bash 8 | npm i 9 | ``` 10 | 11 | ## Running 12 | 13 | Execute the example: 14 | 15 | ```bash 16 | TURSO_DATABASE_URL="..." TURSO_AUTH_TOKEN="..." node index.mjs 17 | ``` 18 | 19 | This will connect to a remote SQLite database, insert some data, and then query the results. 20 | -------------------------------------------------------------------------------- /examples/sync/index.mjs: -------------------------------------------------------------------------------- 1 | import { createClient } from "@libsql/client"; 2 | 3 | const client = createClient({ 4 | url: "file:local.db", 5 | syncUrl: process.env.TURSO_DATABASE_URL, 6 | authToken: process.env.TURSO_AUTH_TOKEN, 7 | }); 8 | 9 | await client.batch( 10 | [ 11 | "CREATE TABLE IF NOT EXISTS users (email TEXT)", 12 | "INSERT INTO users VALUES ('first@example.com')", 13 | "INSERT INTO users VALUES ('second@example.com')", 14 | "INSERT INTO users VALUES ('third@example.com')", 15 | ], 16 | "write", 17 | ); 18 | 19 | const result = await client.execute("SELECT * FROM users"); 20 | 21 | console.log("Users:", result.rows); 22 | -------------------------------------------------------------------------------- /examples/sync/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "batch", 3 | "version": "1.0.0", 4 | "lockfileVersion": 3, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "batch", 9 | "version": "1.0.0", 10 | "license": "ISC", 11 | "dependencies": { 12 | "@libsql/client": "^0.14.0" 13 | } 14 | }, 15 | "node_modules/@libsql/client": { 16 | "version": "0.14.0", 17 | "resolved": "https://registry.npmjs.org/@libsql/client/-/client-0.14.0.tgz", 18 | "integrity": "sha512-/9HEKfn6fwXB5aTEEoMeFh4CtG0ZzbncBb1e++OCdVpgKZ/xyMsIVYXm0w7Pv4RUel803vE6LwniB3PqD72R0Q==", 19 | "license": "MIT", 20 | "dependencies": { 21 | "@libsql/core": "^0.14.0", 22 | "@libsql/hrana-client": "^0.7.0", 23 | "js-base64": "^3.7.5", 24 | "libsql": "^0.4.4", 25 | "promise-limit": "^2.7.0" 26 | } 27 | }, 28 | "node_modules/@libsql/core": { 29 | "version": "0.14.0", 30 | "resolved": "https://registry.npmjs.org/@libsql/core/-/core-0.14.0.tgz", 31 | "integrity": "sha512-nhbuXf7GP3PSZgdCY2Ecj8vz187ptHlZQ0VRc751oB2C1W8jQUXKKklvt7t1LJiUTQBVJuadF628eUk+3cRi4Q==", 32 | "license": "MIT", 33 | "dependencies": { 34 | "js-base64": "^3.7.5" 35 | } 36 | }, 37 | "node_modules/@libsql/darwin-arm64": { 38 | "version": "0.4.6", 39 | "resolved": "https://registry.npmjs.org/@libsql/darwin-arm64/-/darwin-arm64-0.4.6.tgz", 40 | "integrity": "sha512-45i604CJ2Lubbg7NqtDodjarF6VgST8rS5R8xB++MoRqixtDns9PZ6tocT9pRJDWuTWEiy2sjthPOFWMKwYAsg==", 41 | "cpu": [ 42 | "arm64" 43 | ], 44 | "license": "MIT", 45 | "optional": true, 46 | "os": [ 47 | "darwin" 48 | ] 49 | }, 50 | "node_modules/@libsql/darwin-x64": { 51 | "version": "0.4.6", 52 | "resolved": "https://registry.npmjs.org/@libsql/darwin-x64/-/darwin-x64-0.4.6.tgz", 53 | "integrity": "sha512-dRKliflhfr5zOPSNgNJ6C2nZDd4YA8bTXF3MUNqNkcxQ8BffaH9uUwL9kMq89LkFIZQHcyP75bBgZctxfJ/H5Q==", 54 | "cpu": [ 55 | "x64" 56 | ], 57 | "license": "MIT", 58 | "optional": true, 59 | "os": [ 60 | "darwin" 61 | ] 62 | }, 63 | "node_modules/@libsql/hrana-client": { 64 | "version": "0.7.0", 65 | "resolved": "https://registry.npmjs.org/@libsql/hrana-client/-/hrana-client-0.7.0.tgz", 66 | "integrity": "sha512-OF8fFQSkbL7vJY9rfuegK1R7sPgQ6kFMkDamiEccNUvieQ+3urzfDFI616oPl8V7T9zRmnTkSjMOImYCAVRVuw==", 67 | "license": "MIT", 68 | "dependencies": { 69 | "@libsql/isomorphic-fetch": "^0.3.1", 70 | "@libsql/isomorphic-ws": "^0.1.5", 71 | "js-base64": "^3.7.5", 72 | "node-fetch": "^3.3.2" 73 | } 74 | }, 75 | "node_modules/@libsql/isomorphic-fetch": { 76 | "version": "0.3.1", 77 | "resolved": "https://registry.npmjs.org/@libsql/isomorphic-fetch/-/isomorphic-fetch-0.3.1.tgz", 78 | "integrity": "sha512-6kK3SUK5Uu56zPq/Las620n5aS9xJq+jMBcNSOmjhNf/MUvdyji4vrMTqD7ptY7/4/CAVEAYDeotUz60LNQHtw==", 79 | "license": "MIT", 80 | "engines": { 81 | "node": ">=18.0.0" 82 | } 83 | }, 84 | "node_modules/@libsql/isomorphic-ws": { 85 | "version": "0.1.5", 86 | "resolved": "https://registry.npmjs.org/@libsql/isomorphic-ws/-/isomorphic-ws-0.1.5.tgz", 87 | "integrity": "sha512-DtLWIH29onUYR00i0GlQ3UdcTRC6EP4u9w/h9LxpUZJWRMARk6dQwZ6Jkd+QdwVpuAOrdxt18v0K2uIYR3fwFg==", 88 | "license": "MIT", 89 | "dependencies": { 90 | "@types/ws": "^8.5.4", 91 | "ws": "^8.13.0" 92 | } 93 | }, 94 | "node_modules/@libsql/linux-arm64-gnu": { 95 | "version": "0.4.6", 96 | "resolved": "https://registry.npmjs.org/@libsql/linux-arm64-gnu/-/linux-arm64-gnu-0.4.6.tgz", 97 | "integrity": "sha512-DMPavVyY6vYPAYcQR1iOotHszg+5xSjHSg6F9kNecPX0KKdGq84zuPJmORfKOPtaWvzPewNFdML/e+s1fu09XQ==", 98 | "cpu": [ 99 | "arm64" 100 | ], 101 | "license": "MIT", 102 | "optional": true, 103 | "os": [ 104 | "linux" 105 | ] 106 | }, 107 | "node_modules/@libsql/linux-arm64-musl": { 108 | "version": "0.4.6", 109 | "resolved": "https://registry.npmjs.org/@libsql/linux-arm64-musl/-/linux-arm64-musl-0.4.6.tgz", 110 | "integrity": "sha512-whuHSYAZyclGjM3L0mKGXyWqdAy7qYvPPn+J1ve7FtGkFlM0DiIPjA5K30aWSGJSRh72sD9DBZfnu8CpfSjT6w==", 111 | "cpu": [ 112 | "arm64" 113 | ], 114 | "license": "MIT", 115 | "optional": true, 116 | "os": [ 117 | "linux" 118 | ] 119 | }, 120 | "node_modules/@libsql/linux-x64-gnu": { 121 | "version": "0.4.6", 122 | "resolved": "https://registry.npmjs.org/@libsql/linux-x64-gnu/-/linux-x64-gnu-0.4.6.tgz", 123 | "integrity": "sha512-0ggx+5RwEbYabIlDBBAvavdfIJCZ757u6nDZtBeQIhzW99EKbWG3lvkXHM3qudFb/pDWSUY4RFBm6vVtF1cJGA==", 124 | "cpu": [ 125 | "x64" 126 | ], 127 | "license": "MIT", 128 | "optional": true, 129 | "os": [ 130 | "linux" 131 | ] 132 | }, 133 | "node_modules/@libsql/linux-x64-musl": { 134 | "version": "0.4.6", 135 | "resolved": "https://registry.npmjs.org/@libsql/linux-x64-musl/-/linux-x64-musl-0.4.6.tgz", 136 | "integrity": "sha512-SWNrv7Hz72QWlbM/ZsbL35MPopZavqCUmQz2HNDZ55t0F+kt8pXuP+bbI2KvmaQ7wdsoqAA4qBmjol0+bh4ndw==", 137 | "cpu": [ 138 | "x64" 139 | ], 140 | "license": "MIT", 141 | "optional": true, 142 | "os": [ 143 | "linux" 144 | ] 145 | }, 146 | "node_modules/@libsql/win32-x64-msvc": { 147 | "version": "0.4.6", 148 | "resolved": "https://registry.npmjs.org/@libsql/win32-x64-msvc/-/win32-x64-msvc-0.4.6.tgz", 149 | "integrity": "sha512-Q0axn110zDNELfkEog3Nl8p9BU4eI/UvgaHevGyOiSDN7s0KPfj0j6jwVHk4oz3o/d/Gg3DRIxomZ4ftfTOy/g==", 150 | "cpu": [ 151 | "x64" 152 | ], 153 | "license": "MIT", 154 | "optional": true, 155 | "os": [ 156 | "win32" 157 | ] 158 | }, 159 | "node_modules/@neon-rs/load": { 160 | "version": "0.0.4", 161 | "resolved": "https://registry.npmjs.org/@neon-rs/load/-/load-0.0.4.tgz", 162 | "integrity": "sha512-kTPhdZyTQxB+2wpiRcFWrDcejc4JI6tkPuS7UZCG4l6Zvc5kU/gGQ/ozvHTh1XR5tS+UlfAfGuPajjzQjCiHCw==", 163 | "license": "MIT" 164 | }, 165 | "node_modules/@types/node": { 166 | "version": "22.7.5", 167 | "resolved": "https://registry.npmjs.org/@types/node/-/node-22.7.5.tgz", 168 | "integrity": "sha512-jML7s2NAzMWc//QSJ1a3prpk78cOPchGvXJsC3C6R6PSMoooztvRVQEz89gmBTBY1SPMaqo5teB4uNHPdetShQ==", 169 | "license": "MIT", 170 | "dependencies": { 171 | "undici-types": "~6.19.2" 172 | } 173 | }, 174 | "node_modules/@types/ws": { 175 | "version": "8.5.12", 176 | "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.12.tgz", 177 | "integrity": "sha512-3tPRkv1EtkDpzlgyKyI8pGsGZAGPEaXeu0DOj5DI25Ja91bdAYddYHbADRYVrZMRbfW+1l5YwXVDKohDJNQxkQ==", 178 | "license": "MIT", 179 | "dependencies": { 180 | "@types/node": "*" 181 | } 182 | }, 183 | "node_modules/data-uri-to-buffer": { 184 | "version": "4.0.1", 185 | "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz", 186 | "integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==", 187 | "license": "MIT", 188 | "engines": { 189 | "node": ">= 12" 190 | } 191 | }, 192 | "node_modules/detect-libc": { 193 | "version": "2.0.2", 194 | "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.2.tgz", 195 | "integrity": "sha512-UX6sGumvvqSaXgdKGUsgZWqcUyIXZ/vZTrlRT/iobiKhGL0zL4d3osHj3uqllWJK+i+sixDS/3COVEOFbupFyw==", 196 | "license": "Apache-2.0", 197 | "engines": { 198 | "node": ">=8" 199 | } 200 | }, 201 | "node_modules/fetch-blob": { 202 | "version": "3.2.0", 203 | "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz", 204 | "integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==", 205 | "funding": [ 206 | { 207 | "type": "github", 208 | "url": "https://github.com/sponsors/jimmywarting" 209 | }, 210 | { 211 | "type": "paypal", 212 | "url": "https://paypal.me/jimmywarting" 213 | } 214 | ], 215 | "license": "MIT", 216 | "dependencies": { 217 | "node-domexception": "^1.0.0", 218 | "web-streams-polyfill": "^3.0.3" 219 | }, 220 | "engines": { 221 | "node": "^12.20 || >= 14.13" 222 | } 223 | }, 224 | "node_modules/formdata-polyfill": { 225 | "version": "4.0.10", 226 | "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz", 227 | "integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==", 228 | "license": "MIT", 229 | "dependencies": { 230 | "fetch-blob": "^3.1.2" 231 | }, 232 | "engines": { 233 | "node": ">=12.20.0" 234 | } 235 | }, 236 | "node_modules/js-base64": { 237 | "version": "3.7.7", 238 | "resolved": "https://registry.npmjs.org/js-base64/-/js-base64-3.7.7.tgz", 239 | "integrity": "sha512-7rCnleh0z2CkXhH67J8K1Ytz0b2Y+yxTPL+/KOJoa20hfnVQ/3/T6W/KflYI4bRHRagNeXeU2bkNGI3v1oS/lw==", 240 | "license": "BSD-3-Clause" 241 | }, 242 | "node_modules/libsql": { 243 | "version": "0.4.6", 244 | "resolved": "https://registry.npmjs.org/libsql/-/libsql-0.4.6.tgz", 245 | "integrity": "sha512-F5M+ltteK6dCcpjMahrkgT96uFJvVI8aQ4r9f2AzHQjC7BkAYtvfMSTWGvRBezRgMUIU2h1Sy0pF9nOGOD5iyA==", 246 | "cpu": [ 247 | "x64", 248 | "arm64", 249 | "wasm32" 250 | ], 251 | "license": "MIT", 252 | "os": [ 253 | "darwin", 254 | "linux", 255 | "win32" 256 | ], 257 | "dependencies": { 258 | "@neon-rs/load": "^0.0.4", 259 | "detect-libc": "2.0.2" 260 | }, 261 | "optionalDependencies": { 262 | "@libsql/darwin-arm64": "0.4.6", 263 | "@libsql/darwin-x64": "0.4.6", 264 | "@libsql/linux-arm64-gnu": "0.4.6", 265 | "@libsql/linux-arm64-musl": "0.4.6", 266 | "@libsql/linux-x64-gnu": "0.4.6", 267 | "@libsql/linux-x64-musl": "0.4.6", 268 | "@libsql/win32-x64-msvc": "0.4.6" 269 | } 270 | }, 271 | "node_modules/node-domexception": { 272 | "version": "1.0.0", 273 | "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", 274 | "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==", 275 | "funding": [ 276 | { 277 | "type": "github", 278 | "url": "https://github.com/sponsors/jimmywarting" 279 | }, 280 | { 281 | "type": "github", 282 | "url": "https://paypal.me/jimmywarting" 283 | } 284 | ], 285 | "license": "MIT", 286 | "engines": { 287 | "node": ">=10.5.0" 288 | } 289 | }, 290 | "node_modules/node-fetch": { 291 | "version": "3.3.2", 292 | "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz", 293 | "integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==", 294 | "license": "MIT", 295 | "dependencies": { 296 | "data-uri-to-buffer": "^4.0.0", 297 | "fetch-blob": "^3.1.4", 298 | "formdata-polyfill": "^4.0.10" 299 | }, 300 | "engines": { 301 | "node": "^12.20.0 || ^14.13.1 || >=16.0.0" 302 | }, 303 | "funding": { 304 | "type": "opencollective", 305 | "url": "https://opencollective.com/node-fetch" 306 | } 307 | }, 308 | "node_modules/promise-limit": { 309 | "version": "2.7.0", 310 | "resolved": "https://registry.npmjs.org/promise-limit/-/promise-limit-2.7.0.tgz", 311 | "integrity": "sha512-7nJ6v5lnJsXwGprnGXga4wx6d1POjvi5Qmf1ivTRxTjH4Z/9Czja/UCMLVmB9N93GeWOU93XaFaEt6jbuoagNw==", 312 | "license": "ISC" 313 | }, 314 | "node_modules/undici-types": { 315 | "version": "6.19.8", 316 | "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", 317 | "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==", 318 | "license": "MIT" 319 | }, 320 | "node_modules/web-streams-polyfill": { 321 | "version": "3.3.3", 322 | "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz", 323 | "integrity": "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==", 324 | "license": "MIT", 325 | "engines": { 326 | "node": ">= 8" 327 | } 328 | }, 329 | "node_modules/ws": { 330 | "version": "8.18.0", 331 | "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz", 332 | "integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==", 333 | "license": "MIT", 334 | "engines": { 335 | "node": ">=10.0.0" 336 | }, 337 | "peerDependencies": { 338 | "bufferutil": "^4.0.1", 339 | "utf-8-validate": ">=5.0.2" 340 | }, 341 | "peerDependenciesMeta": { 342 | "bufferutil": { 343 | "optional": true 344 | }, 345 | "utf-8-validate": { 346 | "optional": true 347 | } 348 | } 349 | } 350 | } 351 | } 352 | -------------------------------------------------------------------------------- /examples/sync/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "batch", 3 | "version": "1.0.0", 4 | "main": "index.mjs", 5 | "author": "Giovanni Benussi", 6 | "license": "MIT", 7 | "dependencies": { 8 | "@libsql/client": "^0.14.0" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /examples/transactions/.gitignore: -------------------------------------------------------------------------------- 1 | local.db 2 | -------------------------------------------------------------------------------- /examples/transactions/README.md: -------------------------------------------------------------------------------- 1 | # Local 2 | 3 | This example demonstrates how to use transactions with libSQL. 4 | 5 | ## Install Dependencies 6 | 7 | ```bash 8 | npm i 9 | ``` 10 | 11 | ## Running 12 | 13 | Execute the example: 14 | 15 | ```bash 16 | node index.mjs 17 | ``` 18 | 19 | This example will: 20 | 21 | 1. Create a new table called `users`. 22 | 2. Start a transaction. 23 | 3. Insert multiple users within the transaction. 24 | 4. Demonstrate how to rollback a transaction. 25 | 5. Start another transaction. 26 | 6. Insert more users and commit the transaction. 27 | 7. Query and display the final state of the `users` table. 28 | -------------------------------------------------------------------------------- /examples/transactions/index.mjs: -------------------------------------------------------------------------------- 1 | import { createClient } from "@libsql/client"; 2 | 3 | const client = createClient({ 4 | url: "file:local.db", 5 | }); 6 | 7 | await client.batch( 8 | [ 9 | "DROP TABLE IF EXISTS users", 10 | "CREATE TABLE users (id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT)", 11 | "INSERT INTO users (name) VALUES ('Iku Turso')", 12 | ], 13 | "write", 14 | ); 15 | 16 | const names = ["John Doe", "Mary Smith", "Alice Jones", "Mark Taylor"]; 17 | 18 | let transaction, secondTransaction; 19 | try { 20 | transaction = await client.transaction("write"); 21 | 22 | for (const name of names) { 23 | await transaction.execute({ 24 | sql: "INSERT INTO users (name) VALUES (?)", 25 | args: [name], 26 | }); 27 | } 28 | await transaction.rollback(); 29 | 30 | secondTransaction = await client.transaction("write"); 31 | 32 | for (const name of names) { 33 | await secondTransaction.execute({ 34 | sql: "INSERT INTO users (name) VALUES (?)", 35 | args: [name], 36 | }); 37 | } 38 | 39 | await secondTransaction.commit(); 40 | } catch (e) { 41 | console.error(e); 42 | await transaction?.rollback(); 43 | await secondTransaction?.rollback(); 44 | } 45 | 46 | const result = await client.execute("SELECT * FROM users"); 47 | 48 | console.log("Users:", result.rows); 49 | -------------------------------------------------------------------------------- /examples/transactions/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "batch", 3 | "version": "1.0.0", 4 | "main": "index.mjs", 5 | "author": "Giovanni Benussi", 6 | "license": "MIT", 7 | "dependencies": { 8 | "@libsql/client": "^0.14.0" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /examples/vector/.gitignore: -------------------------------------------------------------------------------- 1 | local.db 2 | -------------------------------------------------------------------------------- /examples/vector/README.md: -------------------------------------------------------------------------------- 1 | # Local 2 | 3 | This example demonstrates how to use libSQL vector search with a local database. 4 | 5 | ## Install Dependencies 6 | 7 | ```bash 8 | npm i 9 | ``` 10 | 11 | ## Running 12 | 13 | Execute the example: 14 | 15 | ```bash 16 | node index.mjs 17 | ``` 18 | 19 | This will setup a local SQLite database, insert some data, and then query the results using the vector similarity search function. 20 | -------------------------------------------------------------------------------- /examples/vector/index.mjs: -------------------------------------------------------------------------------- 1 | import { createClient } from "@libsql/client"; 2 | 3 | const client = createClient({ 4 | url: "file:local.db", 5 | }); 6 | 7 | await client.batch( 8 | [ 9 | "DROP TABLE IF EXISTS movies", 10 | "CREATE TABLE IF NOT EXISTS movies (title TEXT, year INT, embedding F32_BLOB(3))", 11 | "CREATE INDEX movies_idx ON movies (libsql_vector_idx(embedding))", 12 | "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]'))", 13 | ], 14 | "write", 15 | ); 16 | 17 | const result = await client.execute( 18 | "SELECT title, year FROM vector_top_k('movies_idx', '[4,5,6]', 3) JOIN movies ON movies.rowid = id", 19 | ); 20 | 21 | console.log("Movies:", result.rows); 22 | -------------------------------------------------------------------------------- /examples/vector/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "batch", 3 | "version": "1.0.0", 4 | "main": "index.mjs", 5 | "author": "Giovanni Benussi", 6 | "license": "MIT", 7 | "dependencies": { 8 | "@libsql/client": "^0.14.0" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "workspaces": [ 3 | "packages/libsql-core", 4 | "packages/libsql-client", 5 | "packages/libsql-client-wasm" 6 | ], 7 | "dependencies": {}, 8 | "scripts": { 9 | "prepare": "node .husky/install.mjs", 10 | "build": "npm run build --workspaces", 11 | "typecheck": "npm run typecheck --workspaces", 12 | "format:check": "npm run format:check --workspaces", 13 | "lint-staged": "lint-staged" 14 | }, 15 | "devDependencies": { 16 | "lint-staged": "^15.2.2", 17 | "husky": "^9.1.5" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /packages/libsql-client-wasm/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 libSQL 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /packages/libsql-client-wasm/examples/browser/README.md: -------------------------------------------------------------------------------- 1 | # libSQL Wasm example for browsers 2 | 3 | ## Building 4 | 5 | Run the following in the `packages/libsql-client-wasm` directory: 6 | 7 | ``` 8 | npm run build 9 | ``` 10 | 11 | Run the following in this directory: 12 | 13 | ``` 14 | npm i 15 | ./node_modules/.bin/esbuild --target=safari16 index.js --bundle --outfile=dist/out.js --format=esm 16 | cp ../../../../node_modules/@libsql/libsql-wasm-experimental/sqlite-wasm/jswasm/sqlite3.wasm dist 17 | ``` 18 | 19 | and open the app in browser: 20 | 21 | ``` 22 | npx http-server -o 23 | ``` 24 | -------------------------------------------------------------------------------- /packages/libsql-client-wasm/examples/browser/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | libSQL SDK Wasm Demo 5 | 6 | 7 | 8 |

Hello libSQL and Wasm!

9 | 10 | 11 | -------------------------------------------------------------------------------- /packages/libsql-client-wasm/examples/browser/index.js: -------------------------------------------------------------------------------- 1 | import { createClient } from "@libsql/client-wasm"; 2 | 3 | async function main() { 4 | const config = { 5 | url: "file:local.db", 6 | }; 7 | const db = await createClient(config); 8 | const rs = await db.execute("SELECT * FROM users"); 9 | console.log(rs); 10 | } 11 | 12 | main().catch((error) => { 13 | console.log(error); 14 | }); 15 | -------------------------------------------------------------------------------- /packages/libsql-client-wasm/examples/browser/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "browser", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "type": "module", 7 | "scripts": { 8 | "test": "echo \"Error: no test specified\" && exit 1" 9 | }, 10 | "author": "", 11 | "license": "MIT", 12 | "dependencies": { 13 | "@libsql/client-wasm": "../.." 14 | }, 15 | "devDependencies": { 16 | "esbuild": "0.19.11" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /packages/libsql-client-wasm/examples/node/index.js: -------------------------------------------------------------------------------- 1 | import { createClient } from "@libsql/client-wasm"; 2 | 3 | async function main() { 4 | const config = { 5 | url: "file:local.db", 6 | }; 7 | const db = await createClient(config); 8 | await db.execute("CREATE TABLE users (id INT PRIMARY KEY, username TEXT)"); 9 | await db.execute("INSERT INTO users VALUES (1, 'penberg')"); 10 | const rs = await db.execute("SELECT * FROM users"); 11 | console.log(rs); 12 | } 13 | 14 | main().catch((error) => { 15 | console.log(error); 16 | }); 17 | -------------------------------------------------------------------------------- /packages/libsql-client-wasm/examples/node/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nodejs", 3 | "version": "1.0.0", 4 | "lockfileVersion": 3, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "nodejs", 9 | "version": "1.0.0", 10 | "license": "MIT", 11 | "dependencies": { 12 | "@libsql/client-wasm": "../../" 13 | } 14 | }, 15 | "../..": { 16 | "name": "@libsql/client-wasm", 17 | "version": "0.4.0-pre.7", 18 | "license": "MIT", 19 | "dependencies": { 20 | "@libsql/core": "0.4.0-pre.7", 21 | "@libsql/libsql-wasm-experimental": "^0.0.1", 22 | "js-base64": "^3.7.5" 23 | }, 24 | "devDependencies": { 25 | "@types/jest": "^29.2.5", 26 | "jest": "^29.3.1", 27 | "ts-jest": "^29.0.5", 28 | "typedoc": "^0.23.28", 29 | "typescript": "^4.9.4" 30 | } 31 | }, 32 | "node_modules/@libsql/client-wasm": { 33 | "resolved": "../..", 34 | "link": true 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /packages/libsql-client-wasm/examples/node/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nodejs", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "type": "module", 7 | "scripts": { 8 | "test": "echo \"Error: no test specified\" && exit 1" 9 | }, 10 | "author": "", 11 | "license": "MIT", 12 | "dependencies": { 13 | "@libsql/client-wasm": "../../" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /packages/libsql-client-wasm/jest.config.js: -------------------------------------------------------------------------------- 1 | export default { 2 | preset: "ts-jest/presets/default-esm", 3 | moduleNameMapper: { 4 | "^(\\.{1,2}/.*)\\.js$": "$1", 5 | }, 6 | testMatch: ["**/__tests__/*.test.[jt]s"], 7 | }; 8 | -------------------------------------------------------------------------------- /packages/libsql-client-wasm/package-cjs.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "commonjs" 3 | } 4 | -------------------------------------------------------------------------------- /packages/libsql-client-wasm/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@libsql/client-wasm", 3 | "version": "0.15.8", 4 | "keywords": [ 5 | "libsql", 6 | "database", 7 | "sqlite", 8 | "serverless", 9 | "vercel", 10 | "netlify", 11 | "lambda" 12 | ], 13 | "description": "libSQL driver for TypeScript and JavaScript", 14 | "repository": { 15 | "type": "git", 16 | "url": "git+https://github.com/libsql/libsql-client-ts", 17 | "directory": "packages/libsql-client-wasm" 18 | }, 19 | "authors": [ 20 | "Jan Špaček ", 21 | "Pekka Enberg ", 22 | "Jan Plhak " 23 | ], 24 | "license": "MIT", 25 | "type": "module", 26 | "bundledDependencies": [ 27 | "@libsql/libsql-wasm-experimental" 28 | ], 29 | "main": "lib-esm/wasm.js", 30 | "types": "lib-esm/wasm.d.ts", 31 | "exports": { 32 | ".": { 33 | "types": "./lib-esm/wasm.d.ts", 34 | "import": { 35 | "default": "./lib-esm/wasm.js" 36 | } 37 | } 38 | }, 39 | "typesVersions": { 40 | "*": { 41 | ".": [ 42 | "./lib-esm/wasm.d.ts" 43 | ] 44 | } 45 | }, 46 | "files": [ 47 | "lib-esm/**" 48 | ], 49 | "scripts": { 50 | "prepublishOnly": "npm run build", 51 | "prebuild": "rm -rf ./lib-esm && npm run bundle", 52 | "build": "npm run build:esm", 53 | "build:esm": "tsc -p tsconfig.build-esm.json", 54 | "bundle": "rm -rf node_modules && mkdir -p node_modules/@libsql/libsql-wasm-experimental && cp -R ../../node_modules/@libsql/libsql-wasm-experimental/* node_modules/@libsql/libsql-wasm-experimental", 55 | "format:check": "prettier --check .", 56 | "test": "jest --runInBand", 57 | "typecheck": "tsc --noEmit", 58 | "typedoc": "rm -rf ./docs && typedoc" 59 | }, 60 | "dependencies": { 61 | "@libsql/core": "^0.15.8", 62 | "@libsql/libsql-wasm-experimental": "^0.0.2", 63 | "js-base64": "^3.7.5" 64 | }, 65 | "devDependencies": { 66 | "@types/jest": "^29.2.5", 67 | "jest": "^29.3.1", 68 | "ts-jest": "^29.0.5", 69 | "typedoc": "^0.23.28", 70 | "typescript": "^4.9.4" 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /packages/libsql-client-wasm/tsconfig.base.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "es2022", 4 | "moduleResolution": "node", 5 | "lib": ["esnext", "dom"], 6 | "target": "es2022", 7 | "esModuleInterop": true, 8 | "isolatedModules": true, 9 | "rootDir": "src/", 10 | "strict": true 11 | }, 12 | "include": ["src/"], 13 | "exclude": ["**/__tests__"] 14 | } 15 | -------------------------------------------------------------------------------- /packages/libsql-client-wasm/tsconfig.build-esm.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.base.json", 3 | "compilerOptions": { 4 | "module": "esnext", 5 | "declaration": true, 6 | "outDir": "./lib-esm/" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /packages/libsql-client-wasm/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.base.json", 3 | "compilerOptions": { 4 | "noEmit": true, 5 | "incremental": true 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /packages/libsql-client-wasm/typedoc.json: -------------------------------------------------------------------------------- 1 | { 2 | "entryPoints": ["src/node.ts"], 3 | "out": "docs", 4 | "excludePrivate": true, 5 | "excludeInternal": true, 6 | "visibilityFilters": { 7 | "inherited": true, 8 | "external": true 9 | }, 10 | "includeVersion": true 11 | } 12 | -------------------------------------------------------------------------------- /packages/libsql-client/README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 | 4 | libSQL TypeScript 5 | 6 | 7 |

libSQL TypeScript

8 |

9 | 10 |

11 | Databases for all TypeScript and JS multi-tenant apps. 12 |

13 | 14 |

15 | Turso · 16 | Docs · 17 | Quickstart · 18 | SDK Reference · 19 | Blog & Tutorials 20 |

21 | 22 |

23 | 24 | 25 | MIT License 26 | 27 | 28 | 29 | 30 | Discord 31 | 32 | 33 | 34 | 35 | Contributors 36 | 37 | 38 | 39 | 40 | Weekly downloads 41 | 42 | 43 | 44 | 45 | Examples 46 | 47 | 48 |

49 | 50 | ## Features 51 | 52 | - 🔌 Works offline with [Embedded Replicas](https://docs.turso.tech/features/embedded-replicas/introduction) 53 | - 🌎 Works with remote Turso databases 54 | - ✨ Works with Turso [AI & Vector Search](https://docs.turso.tech/features/ai-and-embeddings) 55 | - 🔐 Supports [encryption at rest](https://docs.turso.tech/libsql#encryption-at-rest) 56 | 57 | ## Install 58 | 59 | ```bash 60 | npm install @libsql/client 61 | ``` 62 | 63 | ## Quickstart 64 | 65 | The example below uses Embedded Replicas and syncs every minute from Turso. 66 | 67 | ```ts 68 | import { createClient } from "@libsql/client"; 69 | 70 | export const turso = createClient({ 71 | url: "file:local.db", 72 | syncUrl: process.env.TURSO_DATABASE_URL, 73 | authToken: process.env.TURSO_AUTH_TOKEN, 74 | syncInterval: 60000, 75 | }); 76 | 77 | await turso.batch( 78 | [ 79 | "CREATE TABLE IF NOT EXISTS users (id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT)", 80 | { 81 | sql: "INSERT INTO users(name) VALUES (?)", 82 | args: ["Iku"], 83 | }, 84 | ], 85 | "write", 86 | ); 87 | 88 | await turso.execute({ 89 | sql: "SELECT * FROM users WHERE id = ?", 90 | args: [1], 91 | }); 92 | ``` 93 | 94 | ## Examples 95 | 96 | | Example | Description | 97 | | ------------------------------------- | --------------------------------------------------------------------------------------- | 98 | | [local](examples/local) | Uses libsql with a local SQLite file. Creates database, inserts data, and queries. | 99 | | [remote](examples/remote) | Connects to a remote database. Requires environment variables for URL and auth token. | 100 | | [sync](examples/sync) | Demonstrates synchronization between local and remote databases. | 101 | | [batch](examples/batch) | Executes multiple SQL statements in a single batch operation. | 102 | | [transactions](examples/transactions) | Shows transaction usage: starting, performing operations, and committing/rolling back. | 103 | | [memory](examples/memory) | Uses an in-memory SQLite database for temporary storage or fast access. | 104 | | [vector](examples/vector) | Works with vector embeddings, storing and querying for similarity search. | 105 | | [encryption](examples/encryption) | Creates and uses an encrypted SQLite database, demonstrating setup and data operations. | 106 | | [ollama](examples/ollama) | Similarity search with Ollama and Mistral. | 107 | 108 | ## Documentation 109 | 110 | Visit our [official documentation](https://docs.turso.tech/sdk/ts). 111 | 112 | ## Support 113 | 114 | Join us [on Discord](https://tur.so/discord-ts) to get help using this SDK. Report security issues [via email](mailto:security@turso.tech). 115 | 116 | ## Contributors 117 | 118 | See the [contributing guide](CONTRIBUTING.md) to learn how to get involved. 119 | 120 | ![Contributors](https://contrib.nn.ci/api?repo=tursodatabase/libsql-client-ts) 121 | 122 | 123 | 124 | good first issue 125 | 126 | 127 | -------------------------------------------------------------------------------- /packages/libsql-client/examples/example.js: -------------------------------------------------------------------------------- 1 | import { createClient } from "@libsql/client"; 2 | 3 | async function example() { 4 | const config = { 5 | url: process.env.URL ?? "file:local.db", 6 | encryptionKey: process.env.ENCRYPTION_KEY, 7 | }; 8 | const db = createClient(config); 9 | await db.batch( 10 | [ 11 | "CREATE TABLE IF NOT EXISTS users (email TEXT)", 12 | "INSERT INTO users (email) VALUES ('alice@example.com')", 13 | "INSERT INTO users (email) VALUES ('bob@example.com')", 14 | ], 15 | "write", 16 | ); 17 | 18 | await db.batch( 19 | [ 20 | { 21 | sql: "INSERT INTO users (email) VALUES (?)", 22 | args: ["alice@example.com"], 23 | }, 24 | ["INSERT INTO users (email) VALUES (?)", ["bob@example.com"]], 25 | { 26 | sql: "INSERT INTO users (email) VALUES (:email)", 27 | args: { email: "charlie@example.com" }, 28 | }, 29 | ], 30 | "write", 31 | ); 32 | 33 | const rs = await db.execute("SELECT * FROM users"); 34 | console.log(rs); 35 | } 36 | 37 | await example(); 38 | -------------------------------------------------------------------------------- /packages/libsql-client/examples/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "libsql-examples", 3 | "type": "module", 4 | "private": true, 5 | "dependencies": { 6 | "@libsql/client": "..", 7 | "@libsql/core": "../../libsql-core", 8 | "readline-sync": "^1.4.10" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /packages/libsql-client/examples/shell.js: -------------------------------------------------------------------------------- 1 | import * as readline from "node:readline/promises"; 2 | import { stdin, stdout, argv } from "node:process"; 3 | import * as libsql from "@libsql/client"; 4 | 5 | async function main() { 6 | const url = argv[2]; 7 | if (!url) { 8 | console.error("Please specify database URL as command-line argument"); 9 | return; 10 | } 11 | 12 | const client = libsql.createClient({ url }); 13 | const rl = readline.createInterface({ input: stdin, output: stdout }); 14 | 15 | for (;;) { 16 | const sql = await rl.question("> "); 17 | 18 | let rs; 19 | try { 20 | rs = await client.execute(sql); 21 | } catch (e) { 22 | if (e instanceof libsql.LibsqlError) { 23 | console.error(e); 24 | continue; 25 | } 26 | throw e; 27 | } 28 | 29 | console.log(JSON.stringify(rs.columns)); 30 | for (const row of rs.rows) { 31 | console.log(JSON.stringify(Array.from(row))); 32 | } 33 | } 34 | } 35 | 36 | await main(); 37 | -------------------------------------------------------------------------------- /packages/libsql-client/examples/sync.js: -------------------------------------------------------------------------------- 1 | import { createClient } from "@libsql/client"; 2 | import reader from "readline-sync"; 3 | 4 | async function example() { 5 | const config = { 6 | url: process.env.URL ?? "file:local.db", 7 | syncUrl: process.env.SYNC_URL, 8 | authToken: process.env.AUTH_TOKEN, 9 | }; 10 | const db = createClient(config); 11 | await db.sync(); 12 | await db.execute( 13 | "CREATE TABLE IF NOT EXISTS guest_book_entries (comment TEXT)", 14 | ); 15 | const rep = await db.sync(); 16 | 17 | console.log("frames_synced: " + rep.frames_synced); 18 | 19 | const comment = reader.question("Enter your comment: "); 20 | 21 | await db.execute({ 22 | sql: "INSERT INTO guest_book_entries (comment) VALUES (?)", 23 | args: [comment], 24 | }); 25 | 26 | const rep2 = await db.sync(); 27 | 28 | console.log("frames_synced: " + rep2.frames_synced); 29 | 30 | console.log("Guest book entries:"); 31 | const rs = await db.execute("SELECT * FROM guest_book_entries"); 32 | for (const row of rs.rows) { 33 | console.log(" - " + row.comment); 34 | } 35 | } 36 | 37 | example(); 38 | -------------------------------------------------------------------------------- /packages/libsql-client/examples/sync_offline.js: -------------------------------------------------------------------------------- 1 | import { createClient } from "@libsql/client"; 2 | import reader from "readline-sync"; 3 | 4 | async function example() { 5 | const config = { 6 | url: process.env.URL ?? "file:local.db", 7 | syncUrl: process.env.SYNC_URL, 8 | authToken: process.env.AUTH_TOKEN, 9 | offline: true, 10 | }; 11 | 12 | const db = createClient(config); 13 | 14 | console.log("Syncing database ..."); 15 | await db.sync(); 16 | 17 | await db.execute( 18 | "CREATE TABLE IF NOT EXISTS guest_book_entries (comment TEXT)", 19 | ); 20 | 21 | const comment = reader.question("Enter your comment: "); 22 | 23 | await db.execute({ 24 | sql: "INSERT INTO guest_book_entries (comment) VALUES (?)", 25 | args: [comment], 26 | }); 27 | 28 | console.log("Syncing database ..."); 29 | const rep2 = await db.sync(); 30 | 31 | console.log("frames_synced: " + rep2.frames_synced); 32 | 33 | console.log("Guest book entries:"); 34 | const rs = await db.execute("SELECT * FROM guest_book_entries"); 35 | for (const row of rs.rows) { 36 | console.log(" - " + row.comment); 37 | } 38 | } 39 | 40 | example(); 41 | -------------------------------------------------------------------------------- /packages/libsql-client/examples/sync_vector.js: -------------------------------------------------------------------------------- 1 | import { createClient } from "@libsql/client"; 2 | import reader from "readline-sync"; 3 | 4 | async function example() { 5 | const config = { 6 | url: process.env.URL ?? "file:local.db", 7 | syncUrl: process.env.SYNC_URL, 8 | authToken: process.env.AUTH_TOKEN, 9 | }; 10 | const db = createClient(config); 11 | await db.sync(); 12 | await db.execute( 13 | "CREATE TABLE IF NOT EXISTS movies (title TEXT, embedding FLOAT32(4))", 14 | ); 15 | await db.execute( 16 | "CREATE INDEX IF NOT EXISTS movies_idx ON movies (libsql_vector_idx(embedding))", 17 | ); 18 | await db.sync(); 19 | 20 | const title = reader.question("Add movie (title): "); 21 | const embedding = reader.question( 22 | "Add movie (embedding, e.g. [1,2,3,4]): ", 23 | ); 24 | 25 | await db.execute({ 26 | sql: "INSERT INTO movies (title, embedding) VALUES (?, vector32(?))", 27 | args: [title, embedding], 28 | }); 29 | 30 | await db.sync(); 31 | 32 | const all = await db.execute( 33 | "SELECT title, vector_extract(embedding) as embedding FROM movies", 34 | ); 35 | console.info("all movies:"); 36 | for (const row of all.rows) { 37 | console.log(" - " + row.title + ": " + row.embedding); 38 | } 39 | 40 | const query = reader.question("KNN query (e.g. [1,2,3,4]): "); 41 | const nn = await db.execute({ 42 | 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", 43 | args: [query], 44 | }); 45 | console.info("nearest neighbors:"); 46 | for (const row of nn.rows) { 47 | console.log(" - " + row.title + ": " + row.embedding); 48 | } 49 | } 50 | 51 | example(); 52 | -------------------------------------------------------------------------------- /packages/libsql-client/jest.config.js: -------------------------------------------------------------------------------- 1 | export default { 2 | preset: "ts-jest/presets/default-esm", 3 | moduleNameMapper: { 4 | "^(\\.{1,2}/.*)\\.js$": "$1", 5 | }, 6 | testMatch: ["**/__tests__/*.test.[jt]s"], 7 | }; 8 | -------------------------------------------------------------------------------- /packages/libsql-client/package-cjs.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "commonjs" 3 | } 4 | -------------------------------------------------------------------------------- /packages/libsql-client/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@libsql/client", 3 | "version": "0.15.8", 4 | "keywords": [ 5 | "libsql", 6 | "database", 7 | "sqlite", 8 | "serverless", 9 | "vercel", 10 | "netlify", 11 | "lambda" 12 | ], 13 | "description": "libSQL driver for TypeScript and JavaScript", 14 | "repository": { 15 | "type": "git", 16 | "url": "git+https://github.com/libsql/libsql-client-ts", 17 | "directory": "packages/libsql-client" 18 | }, 19 | "authors": [ 20 | "Jan Špaček ", 21 | "Pekka Enberg ", 22 | "Jan Plhak " 23 | ], 24 | "license": "MIT", 25 | "type": "module", 26 | "main": "lib-cjs/node.js", 27 | "types": "lib-esm/node.d.ts", 28 | "exports": { 29 | ".": { 30 | "types": "./lib-esm/node.d.ts", 31 | "import": { 32 | "workerd": "./lib-esm/web.js", 33 | "deno": "./lib-esm/web.js", 34 | "edge-light": "./lib-esm/web.js", 35 | "netlify": "./lib-esm/web.js", 36 | "node": "./lib-esm/node.js", 37 | "browser": "./lib-esm/web.js", 38 | "default": "./lib-esm/node.js" 39 | }, 40 | "require": "./lib-cjs/node.js" 41 | }, 42 | "./node": { 43 | "types": "./lib-esm/node.d.ts", 44 | "import": "./lib-esm/node.js", 45 | "require": "./lib-cjs/node.js" 46 | }, 47 | "./http": { 48 | "types": "./lib-esm/http.d.ts", 49 | "import": "./lib-esm/http.js", 50 | "require": "./lib-cjs/http.js" 51 | }, 52 | "./ws": { 53 | "types": "./lib-esm/ws.d.ts", 54 | "import": "./lib-esm/ws.js", 55 | "require": "./lib-cjs/ws.js" 56 | }, 57 | "./sqlite3": { 58 | "types": "./lib-esm/sqlite3.d.ts", 59 | "import": "./lib-esm/sqlite3.js", 60 | "require": "./lib-cjs/sqlite3.js" 61 | }, 62 | "./web": { 63 | "types": "./lib-esm/web.d.ts", 64 | "import": "./lib-esm/web.js", 65 | "require": "./lib-cjs/web.js" 66 | } 67 | }, 68 | "typesVersions": { 69 | "*": { 70 | ".": [ 71 | "./lib-esm/node.d.ts" 72 | ], 73 | "http": [ 74 | "./lib-esm/http.d.ts" 75 | ], 76 | "hrana": [ 77 | "./lib-esm/hrana.d.ts" 78 | ], 79 | "sqlite3": [ 80 | "./lib-esm/sqlite3.d.ts" 81 | ], 82 | "web": [ 83 | "./lib-esm/web.d.ts" 84 | ] 85 | } 86 | }, 87 | "files": [ 88 | "lib-cjs/**", 89 | "lib-esm/**", 90 | "README.md" 91 | ], 92 | "scripts": { 93 | "prepublishOnly": "npm run build", 94 | "prebuild": "rm -rf ./lib-cjs ./lib-esm", 95 | "build": "npm run build:cjs && npm run build:esm", 96 | "build:cjs": "tsc -p tsconfig.build-cjs.json", 97 | "build:esm": "tsc -p tsconfig.build-esm.json", 98 | "format:check": "prettier --check .", 99 | "postbuild": "cp package-cjs.json ./lib-cjs/package.json", 100 | "test": "jest --runInBand", 101 | "typecheck": "tsc --noEmit", 102 | "typedoc": "rm -rf ./docs && typedoc", 103 | "lint-staged": "lint-staged" 104 | }, 105 | "dependencies": { 106 | "@libsql/core": "^0.15.8", 107 | "@libsql/hrana-client": "^0.7.0", 108 | "js-base64": "^3.7.5", 109 | "libsql": "^0.5.12", 110 | "promise-limit": "^2.7.0" 111 | }, 112 | "devDependencies": { 113 | "@types/jest": "^29.2.5", 114 | "@types/node": "^18.15.5", 115 | "jest": "^29.3.1", 116 | "lint-staged": "^15.2.2", 117 | "msw": "^2.3.0", 118 | "prettier": "3.2.5", 119 | "ts-jest": "^29.0.5", 120 | "typedoc": "^0.23.28", 121 | "typescript": "^4.9.4" 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /packages/libsql-client/smoke_test/vercel/.gitignore: -------------------------------------------------------------------------------- 1 | app/package.json 2 | package-lock.json 3 | *.tgz 4 | -------------------------------------------------------------------------------- /packages/libsql-client/smoke_test/vercel/app/.gitignore: -------------------------------------------------------------------------------- 1 | .vercel 2 | -------------------------------------------------------------------------------- /packages/libsql-client/smoke_test/vercel/app/api/function.ts: -------------------------------------------------------------------------------- 1 | import * as libsql from "@libsql/client"; 2 | 3 | export const config = { 4 | runtime: "edge", 5 | }; 6 | 7 | export default async function (request: Request) { 8 | function respond(status: number, responseBody: string) { 9 | return new Response(responseBody, { 10 | status, 11 | headers: [["content-type", "text/plain"]], 12 | }); 13 | } 14 | 15 | if (request.method !== "GET") { 16 | return respond(405, "Only GET method is supported"); 17 | } 18 | 19 | const url = new URL(request.url); 20 | const testCase = url.searchParams.get("test"); 21 | if (testCase === null) { 22 | return respond( 23 | 400, 24 | "Please specify the test case using the 'test' query parameter", 25 | ); 26 | } 27 | 28 | const testCaseFn = testCases[testCase]; 29 | if (testCaseFn === undefined) { 30 | return respond(404, "Unknown test case"); 31 | } 32 | 33 | let client; 34 | try { 35 | client = libsql.createClient({ url: process.env.CLIENT_URL! }); 36 | await testCaseFn(client); 37 | return respond(200, "Test passed"); 38 | } catch (e) { 39 | return respond(500, `Test failed\n${(e as Error).stack}`); 40 | } finally { 41 | if (client !== undefined) { 42 | client.close(); 43 | } 44 | } 45 | } 46 | 47 | const testCases: Record Promise> = { 48 | execute: async (client: libsql.Client): Promise => { 49 | const rs = await client.execute("SELECT 1+1 AS two"); 50 | assert(rs.columns.length === 1); 51 | assert(rs.columns[0] === "two"); 52 | assert(rs.rows.length === 1); 53 | assert(rs.rows[0].length === 1); 54 | assert(rs.rows[0][0] === 2.0); 55 | }, 56 | 57 | batch: async (client: libsql.Client): Promise => { 58 | const rss = await client.batch([ 59 | "DROP TABLE IF EXISTS t", 60 | "CREATE TABLE t (a, b)", 61 | "INSERT INTO t VALUES (1, 'one'), (2, 'two'), (3, 'three')", 62 | "SELECT * FROM t ORDER BY a", 63 | ]); 64 | 65 | assert(rss[0].columns.length === 0); 66 | assert(rss[0].rows.length === 0); 67 | 68 | assert(rss[1].columns.length === 0); 69 | assert(rss[1].rows.length === 0); 70 | 71 | assert(rss[2].columns.length === 0); 72 | assert(rss[2].rows.length === 0); 73 | 74 | assert(rss[3].columns.length === 2); 75 | assert(rss[3].columns[0] === "a"); 76 | assert(rss[3].columns[1] === "b"); 77 | assert(rss[3].rows.length === 3); 78 | assert(rss[3].rows[0][0] === 1); 79 | assert(rss[3].rows[0][1] === "one"); 80 | assert(rss[3].rows[1][0] === 2); 81 | assert(rss[3].rows[1][1] === "two"); 82 | assert(rss[3].rows[2][0] === 3); 83 | assert(rss[3].rows[2][1] === "three"); 84 | }, 85 | 86 | transaction: async (client: libsql.Client): Promise => { 87 | await client.batch([ 88 | "DROP TABLE IF EXISTS t", 89 | "CREATE TABLE t (a, b)", 90 | "INSERT INTO t VALUES (1, 'one'), (2, 'two'), (3, 'three')", 91 | ]); 92 | 93 | const txn = await client.transaction(); 94 | try { 95 | await txn.execute("INSERT INTO t VALUES (4, 'four')"); 96 | await txn.execute("DELETE FROM t WHERE a <= 2"); 97 | await txn.commit(); 98 | } finally { 99 | txn.close(); 100 | } 101 | 102 | const rs = await client.execute("SELECT COUNT(*) FROM t"); 103 | assert(rs.rows[0][0] === 2); 104 | }, 105 | }; 106 | 107 | function assert(value: unknown, message?: string) { 108 | if (!value) { 109 | throw new Error(message ?? "Assertion failed"); 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /packages/libsql-client/smoke_test/vercel/app/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |

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

4 | 5 | 6 | -------------------------------------------------------------------------------- /packages/libsql-client/smoke_test/vercel/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "smoke-test", 3 | "dependencies": { 4 | "@types/node": "20.4.2", 5 | "localtunnel": "^2.0.2", 6 | "vercel": "^31.0.3", 7 | "typescript": "^4.9.4" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /packages/libsql-client/smoke_test/vercel/test.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | const { spawn } = require("node:child_process"); 3 | const fs = require("node:fs"); 4 | const fetch = require("node-fetch"); 5 | const localtunnel = require("localtunnel"); 6 | 7 | function getEnv(name) { 8 | const value = process.env[name] ?? ""; 9 | if (!value) { 10 | throw new Error(`Please set the env variable ${name}`); 11 | } 12 | return value; 13 | } 14 | 15 | const vercelToken = getEnv("VERCEL_TOKEN"); 16 | const projectName = getEnv("VERCEL_PROJECT_NAME"); 17 | 18 | async function npm( 19 | subcommand, 20 | args, 21 | hiddenArgs = [], 22 | { capture = false } = {}, 23 | ) { 24 | console.info(`$ npm ${subcommand} ${args.join(" ")}`); 25 | 26 | const proc = spawn("npm", [subcommand, ...args, ...hiddenArgs], { 27 | stdio: ["ignore", capture ? "pipe" : "inherit", "inherit"], 28 | }); 29 | 30 | const exitPromise = new Promise((resolve, reject) => { 31 | proc.on("exit", (code, signal) => { 32 | if (signal !== null) { 33 | reject( 34 | new Error( 35 | `vercel command terminated due to signal: ${signal}`, 36 | ), 37 | ); 38 | } else if (code !== 0) { 39 | reject(new Error(`vercel command exited with code: ${code}`)); 40 | } else { 41 | resolve(); 42 | } 43 | }); 44 | }); 45 | 46 | const dataPromise = new Promise((resolve, reject) => { 47 | if (!capture) { 48 | return resolve(); 49 | } 50 | 51 | const stream = proc.stdout; 52 | stream.setEncoding("utf-8"); 53 | 54 | const chunks = []; 55 | stream.on("data", (chunk) => chunks.push(chunk)); 56 | stream.on("end", () => resolve(chunks.join(""))); 57 | stream.on("error", (e) => reject(e)); 58 | }); 59 | 60 | return exitPromise.then(() => dataPromise); 61 | } 62 | 63 | async function deployToVercel(clientUrlInsideVercel) { 64 | console.info("Building and deploying to Vercel..."); 65 | 66 | let tarballName = await npm("pack", ["../.."], [], { capture: true }); 67 | tarballName = tarballName.trim(); 68 | 69 | const appPackageJson = { 70 | dependencies: { 71 | "@libsql/client": `../${tarballName}`, 72 | }, 73 | }; 74 | fs.writeFileSync( 75 | "app/package.json", 76 | JSON.stringify(appPackageJson, null, 4), 77 | ); 78 | 79 | await npm( 80 | "exec", 81 | [ 82 | "--", 83 | "vercel", 84 | "link", 85 | "--yes", 86 | "--project", 87 | projectName, 88 | "--cwd", 89 | "app/", 90 | ], 91 | ["--token", vercelToken], 92 | ); 93 | await npm( 94 | "exec", 95 | [ 96 | "--", 97 | "vercel", 98 | "pull", 99 | "--yes", 100 | "--environment=preview", 101 | "--cwd", 102 | "app/", 103 | ], 104 | ["--token", vercelToken], 105 | ); 106 | await npm("exec", ["--", "vercel", "build", "--cwd", "app/"]); 107 | 108 | const deployUrl = await npm( 109 | "exec", 110 | [ 111 | "--", 112 | "vercel", 113 | "deploy", 114 | "--prebuilt", 115 | "--env", 116 | `CLIENT_URL=${clientUrlInsideVercel}`, 117 | "--cwd", 118 | "app/", 119 | ], 120 | ["--token", vercelToken, "--cwd", "app/"], 121 | { capture: true }, 122 | ); 123 | 124 | console.info(`Deployed Vercel project on ${deployUrl}`); 125 | return deployUrl; 126 | } 127 | 128 | const testCases = ["execute", "batch", "transaction"]; 129 | 130 | async function runTests(functionUrl) { 131 | let ok = true; 132 | for (const testCase of testCases) { 133 | if (!(await runTest(functionUrl, testCase))) { 134 | ok = false; 135 | } 136 | } 137 | return ok; 138 | } 139 | 140 | async function runTest(functionUrl, testCase) { 141 | const resp = await fetch(`${functionUrl}?test=${testCase}`); 142 | const respText = await resp.text(); 143 | const ok = resp.status === 200 && respText === "Test passed"; 144 | if (ok) { 145 | console.info(`TEST ${testCase}: passed`); 146 | } else { 147 | console.warn( 148 | `\nTEST ${testCase}: failed with status ${resp.status}\n${respText}\n`, 149 | ); 150 | } 151 | return ok; 152 | } 153 | 154 | async function main() { 155 | const url = new URL(process.env.URL ?? "ws://localhost:8080"); 156 | 157 | console.info(`Creating a tunnel to ${url}...`); 158 | const tunnel = await localtunnel({ 159 | port: url.port, 160 | // NOTE: if we specify `local_host`, `localtunnel` will try to rewrite the `Host` header in the 161 | // tunnelled HTTP requests. Unfortunately, they do it in a very silly way by converting the 162 | // tunnelled data to a string, thus corrupting the request body. 163 | //local_host: url.hostname, 164 | }); 165 | 166 | let clientUrlInsideVercel = new URL(tunnel.url); 167 | if (url.protocol === "http:") { 168 | clientUrlInsideVercel.protocol = "https:"; 169 | } else if (url.protocol === "ws:") { 170 | clientUrlInsideVercel.protocol = "wss:"; 171 | } else { 172 | clientUrlInsideVercel.protocol = url.protocol; 173 | } 174 | 175 | console.info(`Established a tunnel on ${clientUrlInsideVercel}`); 176 | 177 | let ok = false; 178 | try { 179 | const deployUrl = await deployToVercel(clientUrlInsideVercel); 180 | const functionUrl = new URL("api/function", deployUrl); 181 | ok = await runTests(functionUrl); 182 | if (ok) { 183 | console.log("All tests passed"); 184 | } else { 185 | console.error("Some tests failed"); 186 | } 187 | } finally { 188 | console.info("Closing the tunnel..."); 189 | await tunnel.close(); 190 | } 191 | 192 | process.exit(ok ? 0 : 1); 193 | } 194 | 195 | main(); 196 | -------------------------------------------------------------------------------- /packages/libsql-client/smoke_test/vercel/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": ["dom", "esnext"], 5 | "module": "esnext", 6 | "moduleResolution": "node", 7 | "allowJs": true, 8 | "skipLibCheck": true, 9 | "strict": true, 10 | "noEmit": true, 11 | "esModuleInterop": true, 12 | "isolatedModules": true 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /packages/libsql-client/smoke_test/workers/.gitignore: -------------------------------------------------------------------------------- 1 | package-lock.json 2 | -------------------------------------------------------------------------------- /packages/libsql-client/smoke_test/workers/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "devDependencies": { 3 | "localtunnel": "^2.0.2", 4 | "wrangler": "^3.5.1" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /packages/libsql-client/smoke_test/workers/test.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | const localtunnel = require("localtunnel"); 3 | const wrangler = require("wrangler"); 4 | 5 | const testCases = ["/execute", "/batch", "/transaction"]; 6 | 7 | async function main() { 8 | const local = !!parseInt(process.env.LOCAL ?? "1"); 9 | const url = new URL(process.env.URL ?? "ws://localhost:8080"); 10 | 11 | let clientUrlInsideWorker; 12 | let tunnel = undefined; 13 | if (local) { 14 | clientUrlInsideWorker = url; 15 | } else { 16 | console.info(`Creating an tunnel to ${url}...`); 17 | tunnel = await localtunnel({ 18 | port: url.port, 19 | // NOTE: if we specify `local_host`, `localtunnel` will try to rewrite the `Host` header in the 20 | // tunnelled HTTP requests. Unfortunately, they do it in a very silly way by converting the 21 | // tunnelled data to a string, thus corrupting the request body. 22 | //local_host: url.hostname, 23 | }); 24 | 25 | clientUrlInsideWorker = new URL(tunnel.url); 26 | if (url.protocol === "http:") { 27 | clientUrlInsideWorker.protocol = "https:"; 28 | } else if (url.protocol === "ws:") { 29 | clientUrlInsideWorker.protocol = "wss:"; 30 | } else { 31 | clientUrlInsideWorker.protocol = url.protocol; 32 | } 33 | 34 | console.info(`Established a tunnel on ${clientUrlInsideWorker}`); 35 | } 36 | 37 | let ok = false; 38 | try { 39 | ok = await runWorker(local, clientUrlInsideWorker); 40 | if (ok) { 41 | console.log("All tests passed"); 42 | } else { 43 | console.error("Some tests failed"); 44 | } 45 | } finally { 46 | if (tunnel !== undefined) { 47 | console.info("Closing tunnel..."); 48 | await tunnel.close(); 49 | } 50 | 51 | // TODO: wrangler keeps the program running: 52 | // https://github.com/cloudflare/workers-sdk/issues/2892 53 | setTimeout(() => process.exit(ok ? 0 : 1), 200); 54 | } 55 | } 56 | 57 | async function runWorker(local, clientUrlInsideWorker) { 58 | console.info(`Creating a ${local ? "local" : "nonlocal"} Worker...`); 59 | const worker = await wrangler.unstable_dev("worker.js", { 60 | config: "wrangler.toml", 61 | logLevel: "info", 62 | local, 63 | vars: { 64 | CLIENT_URL: clientUrlInsideWorker.toString(), 65 | }, 66 | experimental: { 67 | disableExperimentalWarning: true, 68 | }, 69 | }); 70 | console.info(`Worker created on ${worker.address}:${worker.port}`); 71 | 72 | try { 73 | let ok = true; 74 | for (const testCase of testCases) { 75 | if (!(await runTest(worker, testCase))) { 76 | ok = false; 77 | } 78 | } 79 | return ok; 80 | } finally { 81 | console.info("Stopping Worker..."); 82 | await worker.stop(); 83 | } 84 | } 85 | 86 | async function runTest(worker, testCase) { 87 | const resp = await worker.fetch(testCase); 88 | const respText = await resp.text(); 89 | const ok = resp.status === 200 && respText === "Test passed"; 90 | if (ok) { 91 | console.info(`TEST ${testCase}: passed`); 92 | } else { 93 | console.warn( 94 | `\nTEST ${testCase}: failed with status ${resp.status}\n${respText}\n`, 95 | ); 96 | } 97 | return ok; 98 | } 99 | 100 | main(); 101 | -------------------------------------------------------------------------------- /packages/libsql-client/smoke_test/workers/worker.js: -------------------------------------------------------------------------------- 1 | import * as libsql from "@libsql/client"; 2 | 3 | export default { 4 | async fetch(request, env, ctx) { 5 | function respond(status, responseBody) { 6 | return new Response(responseBody, { 7 | status, 8 | headers: [["content-type", "text/plain"]], 9 | }); 10 | } 11 | 12 | if (request.method !== "GET") { 13 | return respond(405, "Only GET method is supported"); 14 | } 15 | 16 | const url = new URL(request.url); 17 | if (url.pathname === "/") { 18 | return respond( 19 | 200, 20 | "This is a smoke-test Worker for @libsql/client", 21 | ); 22 | } 23 | 24 | const testCaseFn = testCases[url.pathname]; 25 | if (testCaseFn === undefined) { 26 | return respond(404, "Unknown test case"); 27 | } 28 | 29 | let client; 30 | try { 31 | client = libsql.createClient({ url: env.CLIENT_URL }); 32 | await testCaseFn(client); 33 | return respond(200, "Test passed"); 34 | } catch (e) { 35 | return respond(500, `Test failed\n${e.stack}`); 36 | } finally { 37 | if (client !== undefined) { 38 | client.close(); 39 | } 40 | } 41 | }, 42 | }; 43 | 44 | const testCases = { 45 | "/execute": async (client) => { 46 | const rs = await client.execute("SELECT 1+1 AS two"); 47 | assert(rs.columns.length === 1); 48 | assert(rs.columns[0] === "two"); 49 | assert(rs.rows.length === 1); 50 | assert(rs.rows[0].length === 1); 51 | assert(rs.rows[0][0] === 2.0); 52 | }, 53 | 54 | "/batch": async (client) => { 55 | const rss = await client.batch([ 56 | "DROP TABLE IF EXISTS t", 57 | "CREATE TABLE t (a, b)", 58 | "INSERT INTO t VALUES (1, 'one'), (2, 'two'), (3, 'three')", 59 | "SELECT * FROM t ORDER BY a", 60 | ]); 61 | 62 | assert(rss[0].columns.length === 0); 63 | assert(rss[0].rows.length === 0); 64 | 65 | assert(rss[1].columns.length === 0); 66 | assert(rss[1].rows.length === 0); 67 | 68 | assert(rss[2].columns.length === 0); 69 | assert(rss[2].rows.length === 0); 70 | 71 | assert(rss[3].columns.length === 2); 72 | assert(rss[3].columns[0] === "a"); 73 | assert(rss[3].columns[1] === "b"); 74 | assert(rss[3].rows.length === 3); 75 | assert(rss[3].rows[0][0] === 1); 76 | assert(rss[3].rows[0][1] === "one"); 77 | assert(rss[3].rows[1][0] === 2); 78 | assert(rss[3].rows[1][1] === "two"); 79 | assert(rss[3].rows[2][0] === 3); 80 | assert(rss[3].rows[2][1] === "three"); 81 | }, 82 | 83 | "/transaction": async (client) => { 84 | await client.batch([ 85 | "DROP TABLE IF EXISTS t", 86 | "CREATE TABLE t (a, b)", 87 | "INSERT INTO t VALUES (1, 'one'), (2, 'two'), (3, 'three')", 88 | ]); 89 | 90 | const txn = await client.transaction(); 91 | try { 92 | await txn.execute("INSERT INTO t VALUES (4, 'four')"); 93 | await txn.execute("DELETE FROM t WHERE a <= 2"); 94 | await txn.commit(); 95 | } finally { 96 | txn.close(); 97 | } 98 | 99 | const rs = await client.execute("SELECT COUNT(*) FROM t"); 100 | assert(rs.rows[0][0] === 2); 101 | }, 102 | }; 103 | 104 | function assert(value, message) { 105 | if (!value) { 106 | throw new Error(message ?? "Assertion failed"); 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /packages/libsql-client/smoke_test/workers/wrangler.toml: -------------------------------------------------------------------------------- 1 | main = "worker.js" 2 | compatibility_date = "2023-05-15" 3 | 4 | [vars] 5 | CLIENT_URL = "ws://localhost:8080" 6 | -------------------------------------------------------------------------------- /packages/libsql-client/src/__tests__/config.test.ts: -------------------------------------------------------------------------------- 1 | import { expect } from "@jest/globals"; 2 | 3 | import "./helpers.js"; 4 | 5 | import { expandConfig } from "@libsql/core/config"; 6 | import { IntMode } from "@libsql/hrana-client"; 7 | 8 | describe("expandConfig - default tls values", () => { 9 | const cases = [ 10 | { 11 | name: "file", 12 | preferHttp: true, 13 | config: { url: "file://local.db" }, 14 | tls: true, 15 | }, 16 | { 17 | name: "http", 18 | preferHttp: true, 19 | config: { url: "http://localhost" }, 20 | tls: false, 21 | }, 22 | { 23 | name: "http (tls in config)", 24 | preferHttp: true, 25 | config: { url: "http://localhost", tls: true }, 26 | tls: true, 27 | }, 28 | { 29 | name: "http (tls in query)", 30 | preferHttp: true, 31 | config: { url: "http://localhost?tls=1", tls: false }, 32 | tls: true, 33 | }, 34 | { 35 | name: "http (no tls in query)", 36 | preferHttp: true, 37 | config: { url: "http://localhost?tls=0", tls: true }, 38 | tls: false, 39 | }, 40 | { 41 | name: "http (no tls in query)", 42 | preferHttp: true, 43 | config: { url: "http://localhost?tls=0", tls: true }, 44 | tls: false, 45 | }, 46 | ]; 47 | for (const { name, config, preferHttp, tls } of cases) { 48 | test(name, () => { 49 | expect(expandConfig(config, preferHttp).tls).toEqual(tls); 50 | }); 51 | } 52 | }); 53 | 54 | describe("expandConfig - invalid arguments", () => { 55 | const cases = [ 56 | { 57 | name: "in-memory with unsupported query params", 58 | config: { url: "file::memory:?mode=memory" }, 59 | error: 'Unsupported URL query parameter "mode"', 60 | }, 61 | { 62 | name: "in-memory with tls param", 63 | config: { url: "file::memory:?tls=0" }, 64 | error: 'Unsupported URL query parameter "tls"', 65 | }, 66 | { 67 | name: "in-memory with authToken param", 68 | config: { url: "file::memory:?authToken=0" }, 69 | error: 'Unsupported URL query parameter "authToken"', 70 | }, 71 | { 72 | name: "invalid tls param value", 73 | config: { url: "libsql://localhost?tls=2" }, 74 | error: 'Unknown value for the "tls" query argument: "2". Supported values are: ["0", "1"]', 75 | }, 76 | { 77 | name: "invalid scheme", 78 | config: { url: "ftp://localhost" }, 79 | error: /The client supports only.*got "ftp:"/g, 80 | }, 81 | { 82 | name: "invalid intMode", 83 | config: { url: "file://localhost", intMode: "decimal" as IntMode }, 84 | error: /Invalid value for intMode.*got "decimal"/g, 85 | }, 86 | { 87 | name: "fragment in uri", 88 | config: { url: "file://localhost#fragment" }, 89 | error: "URL fragments are not supported", 90 | }, 91 | { 92 | name: "libsql, no tls, no port", 93 | config: { url: "libsql://localhost?tls=0" }, 94 | error: "must specify an explicit port", 95 | }, 96 | ]; 97 | for (const { name, config, error } of cases) { 98 | test(name, () => { 99 | try { 100 | expandConfig(config, false); 101 | throw new Error("expand command must fail"); 102 | } catch (e: any) { 103 | expect(e.message).toMatch(error); 104 | } 105 | }); 106 | } 107 | }); 108 | 109 | describe("expandConfig - parsing of valid arguments", () => { 110 | const cases = [ 111 | { 112 | name: "in-memory", 113 | config: { url: ":memory:" }, 114 | expanded: { 115 | scheme: "file", 116 | tls: false, 117 | intMode: "number", 118 | path: ":memory:", 119 | concurrency: 20, 120 | }, 121 | }, 122 | { 123 | name: "in-memory with params", 124 | config: { url: "file::memory:?cache=shared" }, 125 | expanded: { 126 | scheme: "file", 127 | tls: false, 128 | intMode: "number", 129 | path: ":memory:?cache=shared", 130 | concurrency: 20, 131 | }, 132 | }, 133 | { 134 | name: "simple local file", 135 | config: { url: "file://local.db" }, 136 | expanded: { 137 | scheme: "file", 138 | authority: { host: "local.db" }, 139 | tls: true, 140 | intMode: "number", 141 | path: "", 142 | concurrency: 20, 143 | }, 144 | }, 145 | { 146 | name: "wss with path & port", 147 | config: { url: "wss://localhost:8888/libsql/connect" }, 148 | expanded: { 149 | scheme: "wss", 150 | authority: { host: "localhost", port: 8888 }, 151 | tls: true, 152 | intMode: "number", 153 | path: "/libsql/connect", 154 | concurrency: 20, 155 | }, 156 | }, 157 | { 158 | name: "wss with user info", 159 | config: { 160 | url: "wss://user:password@localhost:8888/libsql/connect", 161 | concurrency: 20, 162 | }, 163 | expanded: { 164 | scheme: "wss", 165 | authority: { 166 | host: "localhost", 167 | port: 8888, 168 | userinfo: { username: "user", password: "password" }, 169 | }, 170 | tls: true, 171 | intMode: "number", 172 | path: "/libsql/connect", 173 | concurrency: 20, 174 | }, 175 | }, 176 | { 177 | name: "override tls=0", 178 | config: { url: "wss://localhost/libsql/connect?tls=0", tls: true }, 179 | expanded: { 180 | scheme: "wss", 181 | authority: { host: "localhost" }, 182 | tls: false, 183 | intMode: "number", 184 | path: "/libsql/connect", 185 | concurrency: 20, 186 | }, 187 | }, 188 | { 189 | name: "override tls=1", 190 | config: { url: "wss://localhost/libsql/connect?tls=1", tls: false }, 191 | expanded: { 192 | scheme: "wss", 193 | authority: { host: "localhost" }, 194 | tls: true, 195 | intMode: "number", 196 | path: "/libsql/connect", 197 | concurrency: 20, 198 | }, 199 | }, 200 | { 201 | name: "override auth token", 202 | config: { 203 | url: "wss://localhost/libsql/connect?authToken=new", 204 | authToken: "old", 205 | }, 206 | expanded: { 207 | authToken: "new", 208 | scheme: "wss", 209 | authority: { host: "localhost" }, 210 | tls: true, 211 | intMode: "number", 212 | path: "/libsql/connect", 213 | concurrency: 20, 214 | }, 215 | }, 216 | ]; 217 | for (const { name, config, expanded } of cases) { 218 | test(name, () => { 219 | expect(expandConfig(config, false)).toEqual(expanded); 220 | }); 221 | } 222 | }); 223 | -------------------------------------------------------------------------------- /packages/libsql-client/src/__tests__/helpers.ts: -------------------------------------------------------------------------------- 1 | import { expect } from "@jest/globals"; 2 | import type { MatcherFunction } from "expect"; 3 | 4 | import { LibsqlError } from "../node.js"; 5 | 6 | type CodeMatch = { 7 | code: string; 8 | rawCode: number; 9 | }; 10 | 11 | const toBeLibsqlError: MatcherFunction< 12 | [code?: string | CodeMatch, message?: RegExp] 13 | > = function (actual, code?, messageRe?) { 14 | const pass = 15 | actual instanceof LibsqlError && 16 | isValidCode(actual, code) && 17 | (messageRe === undefined || actual.message.match(messageRe) !== null); 18 | 19 | const message = (): string => { 20 | const parts = []; 21 | parts.push("expected "); 22 | parts.push(this.utils.printReceived(actual)); 23 | parts.push(pass ? " not to be " : " to be "); 24 | parts.push("an instance of LibsqlError"); 25 | if (code !== undefined) { 26 | parts.push(" with error code "); 27 | parts.push(this.utils.printExpected(code)); 28 | } 29 | if (messageRe !== undefined) { 30 | parts.push(" with error message matching "); 31 | parts.push(this.utils.printExpected(messageRe)); 32 | } 33 | return parts.join(""); 34 | }; 35 | 36 | return { pass, message }; 37 | }; 38 | 39 | const isValidCode = (error: LibsqlError, code?: string | CodeMatch) => { 40 | if (code === undefined) { 41 | return true; 42 | } 43 | if (typeof code === "string") { 44 | return error.code === code; 45 | } 46 | return error.code === code.code && error.rawCode === code.rawCode; 47 | }; 48 | expect.extend({ toBeLibsqlError }); 49 | declare module "expect" { 50 | interface AsymmetricMatchers { 51 | toBeLibsqlError(code?: string | CodeMatch, messageRe?: RegExp): void; 52 | } 53 | interface Matchers { 54 | toBeLibsqlError(code?: string | CodeMatch, messageRe?: RegExp): R; 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /packages/libsql-client/src/__tests__/mocks/handlers.ts: -------------------------------------------------------------------------------- 1 | import { http, HttpResponse } from "msw"; 2 | 3 | export const handlers = [ 4 | http.get("http://fake-base-url.example.com/v1/jobs", () => { 5 | return HttpResponse.json({ 6 | schema_version: 4, 7 | migrations: [ 8 | { job_id: 4, status: "WaitingDryRun" }, 9 | { job_id: 3, status: "RunSuccess" }, 10 | { job_id: 2, status: "RunSuccess" }, 11 | { job_id: 1, status: "RunSuccess" }, 12 | ], 13 | }); 14 | }), 15 | 16 | http.get( 17 | "http://fake-base-url.example.com/v1/jobs/:job_id", 18 | ({ params }) => { 19 | const { job_id } = params; 20 | 21 | return HttpResponse.json({ 22 | job_id, 23 | status: "RunSuccess", 24 | progress: [ 25 | { 26 | namespace: "b2ab4a64-402c-4bdf-a1e8-27ef33518cbd", 27 | status: "RunSuccess", 28 | error: null, 29 | }, 30 | ], 31 | }); 32 | }, 33 | ), 34 | ]; 35 | -------------------------------------------------------------------------------- /packages/libsql-client/src/__tests__/mocks/node.ts: -------------------------------------------------------------------------------- 1 | import { setupServer } from "msw/node"; 2 | import { handlers } from "./handlers"; 3 | 4 | export const server = setupServer(...handlers); 5 | -------------------------------------------------------------------------------- /packages/libsql-client/src/__tests__/uri.test.ts: -------------------------------------------------------------------------------- 1 | import { expect } from "@jest/globals"; 2 | 3 | import "./helpers.js"; 4 | 5 | import { parseUri, encodeBaseUrl } from "@libsql/core/uri"; 6 | 7 | describe("parseUri()", () => { 8 | test(":memory: uri", () => { 9 | const cases = [ 10 | { text: "file::memory:", path: ":memory:", query: undefined }, 11 | { 12 | text: "file::memory:?cache=shared", 13 | path: ":memory:", 14 | query: { pairs: [{ key: "cache", value: "shared" }] }, 15 | }, 16 | ]; 17 | for (const { text, path, query } of cases) { 18 | expect(parseUri(text)).toEqual({ scheme: "file", path, query }); 19 | } 20 | }); 21 | test("authority and path", () => { 22 | const cases = [ 23 | { text: "file://localhost", path: "" }, 24 | { text: "file://localhost/", path: "/" }, 25 | { text: "file://localhost/absolute/path", path: "/absolute/path" }, 26 | { text: "file://localhost/k%C5%AF%C5%88", path: "/kůň" }, 27 | ]; 28 | for (const { text, path } of cases) { 29 | expect(parseUri(text)).toEqual({ 30 | scheme: "file", 31 | authority: { host: "localhost" }, 32 | path, 33 | }); 34 | } 35 | }); 36 | 37 | test("empty authority and path", () => { 38 | const cases = [ 39 | { text: "file:///absolute/path", path: "/absolute/path" }, 40 | { text: "file://", path: "" }, 41 | { text: "file:///k%C5%AF%C5%88", path: "/kůň" }, 42 | ]; 43 | for (const { text, path } of cases) { 44 | expect(parseUri(text)).toEqual({ 45 | scheme: "file", 46 | authority: { host: "" }, 47 | path, 48 | }); 49 | } 50 | }); 51 | 52 | test("no authority and path", () => { 53 | const cases = [ 54 | { text: "file:/absolute/path", path: "/absolute/path" }, 55 | { text: "file:relative/path", path: "relative/path" }, 56 | { text: "file:", path: "" }, 57 | { text: "file:C:/path/to/file", path: "C:/path/to/file" }, 58 | { text: "file:k%C5%AF%C5%88", path: "kůň" }, 59 | ]; 60 | for (const { text, path } of cases) { 61 | expect(parseUri(text)).toEqual({ 62 | scheme: "file", 63 | path, 64 | }); 65 | } 66 | }); 67 | 68 | test("authority", () => { 69 | const hosts = [ 70 | { text: "localhost", host: "localhost" }, 71 | { text: "domain.name", host: "domain.name" }, 72 | { text: "some$weird.%20!name", host: "some$weird. !name" }, 73 | { text: "1.20.255.99", host: "1.20.255.99" }, 74 | { text: "[2001:4860:4802:32::a]", host: "2001:4860:4802:32::a" }, 75 | { text: "%61", host: "a" }, 76 | { text: "100%2e100%2e100%2e100", host: "100.100.100.100" }, 77 | { text: "k%C5%AF%C5%88", host: "kůň" }, 78 | ]; 79 | const ports = [ 80 | { text: "", port: undefined }, 81 | { text: ":", port: undefined }, 82 | { text: ":0", port: 0 }, 83 | { text: ":99", port: 99 }, 84 | { text: ":65535", port: 65535 }, 85 | ]; 86 | const userinfos = [ 87 | { text: "", userinfo: undefined }, 88 | { text: "@", userinfo: { username: "" } }, 89 | { text: "alice@", userinfo: { username: "alice" } }, 90 | { 91 | text: "alice:secret@", 92 | userinfo: { username: "alice", password: "secret" }, 93 | }, 94 | { 95 | text: "alice:sec:et@", 96 | userinfo: { username: "alice", password: "sec:et" }, 97 | }, 98 | { text: "alice%3Asecret@", userinfo: { username: "alice:secret" } }, 99 | { 100 | text: "alice:s%65cret@", 101 | userinfo: { username: "alice", password: "secret" }, 102 | }, 103 | ]; 104 | 105 | for (const { text: hostText, host } of hosts) { 106 | for (const { text: portText, port } of ports) { 107 | for (const { text: userText, userinfo } of userinfos) { 108 | const text = `http://${userText}${hostText}${portText}`; 109 | expect(parseUri(text)).toEqual({ 110 | scheme: "http", 111 | authority: { host, port, userinfo }, 112 | path: "", 113 | }); 114 | } 115 | } 116 | } 117 | }); 118 | 119 | test("query", () => { 120 | const cases = [ 121 | { text: "?", pairs: [] }, 122 | { text: "?key=value", pairs: [{ key: "key", value: "value" }] }, 123 | { text: "?&key=value", pairs: [{ key: "key", value: "value" }] }, 124 | { text: "?key=value&&", pairs: [{ key: "key", value: "value" }] }, 125 | { text: "?a", pairs: [{ key: "a", value: "" }] }, 126 | { text: "?a=", pairs: [{ key: "a", value: "" }] }, 127 | { text: "?=a", pairs: [{ key: "", value: "a" }] }, 128 | { text: "?=", pairs: [{ key: "", value: "" }] }, 129 | { text: "?a=b=c", pairs: [{ key: "a", value: "b=c" }] }, 130 | { 131 | text: "?a=b&c=d", 132 | pairs: [ 133 | { key: "a", value: "b" }, 134 | { key: "c", value: "d" }, 135 | ], 136 | }, 137 | { text: "?a+b=c", pairs: [{ key: "a b", value: "c" }] }, 138 | { text: "?a=b+c", pairs: [{ key: "a", value: "b c" }] }, 139 | { text: "?a?b", pairs: [{ key: "a?b", value: "" }] }, 140 | { text: "?%61=%62", pairs: [{ key: "a", value: "b" }] }, 141 | { text: "?a%3db", pairs: [{ key: "a=b", value: "" }] }, 142 | { text: "?a=%2b", pairs: [{ key: "a", value: "+" }] }, 143 | { text: "?%2b=b", pairs: [{ key: "+", value: "b" }] }, 144 | { text: "?a=b%26c", pairs: [{ key: "a", value: "b&c" }] }, 145 | { text: "?a=k%C5%AF%C5%88", pairs: [{ key: "a", value: "kůň" }] }, 146 | ]; 147 | for (const { text: queryText, pairs } of cases) { 148 | const text = `file:${queryText}`; 149 | expect(parseUri(text)).toEqual({ 150 | scheme: "file", 151 | path: "", 152 | query: { pairs }, 153 | }); 154 | } 155 | }); 156 | 157 | test("fragment", () => { 158 | const cases = [ 159 | { text: "", fragment: undefined }, 160 | { text: "#a", fragment: "a" }, 161 | { text: "#a?b", fragment: "a?b" }, 162 | { text: "#%61", fragment: "a" }, 163 | { text: "#k%C5%AF%C5%88", fragment: "kůň" }, 164 | ]; 165 | for (const { text: fragmentText, fragment } of cases) { 166 | const text = `file:${fragmentText}`; 167 | expect(parseUri(text)).toEqual({ 168 | scheme: "file", 169 | path: "", 170 | fragment, 171 | }); 172 | } 173 | }); 174 | 175 | test("parse errors", () => { 176 | const cases = [ 177 | { text: "", message: /format/ }, 178 | { text: "foo", message: /format/ }, 179 | { text: "foo.bar.com", message: /format/ }, 180 | { text: "h$$p://localhost", message: /format/ }, 181 | { text: "h%74%74p://localhost", message: /format/ }, 182 | { text: "http://localhost:%38%38", message: /authority/ }, 183 | { text: "file:k%C5%C5%88", message: /percent encoding/ }, 184 | ]; 185 | 186 | for (const { text, message } of cases) { 187 | expect(() => parseUri(text)).toThrow( 188 | expect.toBeLibsqlError("URL_INVALID", message), 189 | ); 190 | } 191 | }); 192 | }); 193 | 194 | test("encodeBaseUrl()", () => { 195 | const cases = [ 196 | { 197 | scheme: "http", 198 | host: "localhost", 199 | path: "", 200 | url: "http://localhost", 201 | }, 202 | { 203 | scheme: "http", 204 | host: "localhost", 205 | path: "/", 206 | url: "http://localhost/", 207 | }, 208 | { 209 | scheme: "http", 210 | host: "localhost", 211 | port: 8080, 212 | path: "", 213 | url: "http://localhost:8080", 214 | }, 215 | { 216 | scheme: "http", 217 | host: "localhost", 218 | path: "/foo/bar", 219 | url: "http://localhost/foo/bar", 220 | }, 221 | { 222 | scheme: "http", 223 | host: "localhost", 224 | path: "foo/bar", 225 | url: "http://localhost/foo/bar", 226 | }, 227 | { 228 | scheme: "http", 229 | host: "some.long.domain.name", 230 | path: "", 231 | url: "http://some.long.domain.name", 232 | }, 233 | { 234 | scheme: "http", 235 | host: "1.2.3.4", 236 | path: "", 237 | url: "http://1.2.3.4", 238 | }, 239 | { 240 | scheme: "http", 241 | host: "2001:4860:4802:32::a", 242 | path: "", 243 | url: "http://[2001:4860:4802:32::a]", 244 | }, 245 | { 246 | scheme: "http", 247 | host: "localhost", 248 | userinfo: { username: "alice", password: undefined }, 249 | path: "", 250 | url: "http://alice@localhost", 251 | }, 252 | { 253 | scheme: "http", 254 | host: "localhost", 255 | userinfo: { username: "alice", password: "secr:t" }, 256 | path: "", 257 | url: "http://alice:secr%3At@localhost", 258 | }, 259 | { 260 | scheme: "https", 261 | host: "localhost", 262 | userinfo: { username: "alice", password: "secret" }, 263 | port: 8080, 264 | path: "/some/path", 265 | url: "https://alice:secret@localhost:8080/some/path", 266 | }, 267 | ]; 268 | 269 | for (const { scheme, host, port, userinfo, path, url } of cases) { 270 | expect( 271 | encodeBaseUrl(scheme, { host, port, userinfo }, path), 272 | ).toStrictEqual(new URL(url)); 273 | } 274 | }); 275 | -------------------------------------------------------------------------------- /packages/libsql-client/src/http.ts: -------------------------------------------------------------------------------- 1 | import * as hrana from "@libsql/hrana-client"; 2 | 3 | import type { Config, Client } from "@libsql/core/api"; 4 | import type { 5 | InStatement, 6 | ResultSet, 7 | Transaction, 8 | IntMode, 9 | InArgs, 10 | Replicated, 11 | } from "@libsql/core/api"; 12 | import { TransactionMode, LibsqlError } from "@libsql/core/api"; 13 | import type { ExpandedConfig } from "@libsql/core/config"; 14 | import { expandConfig } from "@libsql/core/config"; 15 | import { 16 | HranaTransaction, 17 | executeHranaBatch, 18 | stmtToHrana, 19 | resultSetFromHrana, 20 | mapHranaError, 21 | } from "./hrana.js"; 22 | import { SqlCache } from "./sql_cache.js"; 23 | import { encodeBaseUrl } from "@libsql/core/uri"; 24 | import { supportedUrlLink } from "@libsql/core/util"; 25 | import promiseLimit from "promise-limit"; 26 | 27 | export * from "@libsql/core/api"; 28 | 29 | export function createClient(config: Config): Client { 30 | return _createClient(expandConfig(config, true)); 31 | } 32 | 33 | /** @private */ 34 | export function _createClient(config: ExpandedConfig): Client { 35 | if (config.scheme !== "https" && config.scheme !== "http") { 36 | throw new LibsqlError( 37 | 'The HTTP client supports only "libsql:", "https:" and "http:" URLs, ' + 38 | `got ${JSON.stringify(config.scheme + ":")}. For more information, please read ${supportedUrlLink}`, 39 | "URL_SCHEME_NOT_SUPPORTED", 40 | ); 41 | } 42 | 43 | if (config.encryptionKey !== undefined) { 44 | throw new LibsqlError( 45 | "Encryption key is not supported by the remote client.", 46 | "ENCRYPTION_KEY_NOT_SUPPORTED", 47 | ); 48 | } 49 | 50 | if (config.scheme === "http" && config.tls) { 51 | throw new LibsqlError( 52 | `A "http:" URL cannot opt into TLS by using ?tls=1`, 53 | "URL_INVALID", 54 | ); 55 | } else if (config.scheme === "https" && !config.tls) { 56 | throw new LibsqlError( 57 | `A "https:" URL cannot opt out of TLS by using ?tls=0`, 58 | "URL_INVALID", 59 | ); 60 | } 61 | 62 | const url = encodeBaseUrl(config.scheme, config.authority, config.path); 63 | return new HttpClient( 64 | url, 65 | config.authToken, 66 | config.intMode, 67 | config.fetch, 68 | config.concurrency, 69 | ); 70 | } 71 | 72 | const sqlCacheCapacity = 30; 73 | 74 | export class HttpClient implements Client { 75 | #client: hrana.HttpClient; 76 | protocol: "http"; 77 | #authToken: string | undefined; 78 | #promiseLimitFunction: ReturnType>; 79 | 80 | /** @private */ 81 | constructor( 82 | url: URL, 83 | authToken: string | undefined, 84 | intMode: IntMode, 85 | customFetch: Function | undefined, 86 | concurrency: number, 87 | ) { 88 | this.#client = hrana.openHttp(url, authToken, customFetch); 89 | this.#client.intMode = intMode; 90 | this.protocol = "http"; 91 | this.#authToken = authToken; 92 | this.#promiseLimitFunction = promiseLimit(concurrency); 93 | } 94 | 95 | private async limit(fn: () => Promise): Promise { 96 | return this.#promiseLimitFunction(fn); 97 | } 98 | 99 | async execute( 100 | stmtOrSql: InStatement | string, 101 | args?: InArgs, 102 | ): Promise { 103 | let stmt: InStatement; 104 | 105 | if (typeof stmtOrSql === "string") { 106 | stmt = { 107 | sql: stmtOrSql, 108 | args: args || [], 109 | }; 110 | } else { 111 | stmt = stmtOrSql; 112 | } 113 | 114 | return this.limit(async () => { 115 | try { 116 | const hranaStmt = stmtToHrana(stmt); 117 | 118 | // Pipeline all operations, so `hrana.HttpClient` can open the stream, execute the statement and 119 | // close the stream in a single HTTP request. 120 | let rowsPromise: Promise; 121 | const stream = this.#client.openStream(); 122 | try { 123 | rowsPromise = stream.query(hranaStmt); 124 | } finally { 125 | stream.closeGracefully(); 126 | } 127 | 128 | const rowsResult = await rowsPromise; 129 | 130 | return resultSetFromHrana(rowsResult); 131 | } catch (e) { 132 | throw mapHranaError(e); 133 | } 134 | }); 135 | } 136 | 137 | async batch( 138 | stmts: Array, 139 | mode: TransactionMode = "deferred", 140 | ): Promise> { 141 | return this.limit>(async () => { 142 | try { 143 | const normalizedStmts = stmts.map((stmt) => { 144 | if (Array.isArray(stmt)) { 145 | return { 146 | sql: stmt[0], 147 | args: stmt[1] || [], 148 | }; 149 | } 150 | return stmt; 151 | }); 152 | 153 | const hranaStmts = normalizedStmts.map(stmtToHrana); 154 | const version = await this.#client.getVersion(); 155 | 156 | // Pipeline all operations, so `hrana.HttpClient` can open the stream, execute the batch and 157 | // close the stream in a single HTTP request. 158 | let resultsPromise: Promise>; 159 | const stream = this.#client.openStream(); 160 | try { 161 | // It makes sense to use a SQL cache even for a single batch, because it may contain the same 162 | // statement repeated multiple times. 163 | const sqlCache = new SqlCache(stream, sqlCacheCapacity); 164 | sqlCache.apply(hranaStmts); 165 | 166 | // TODO: we do not use a cursor here, because it would cause three roundtrips: 167 | // 1. pipeline request to store SQL texts 168 | // 2. cursor request 169 | // 3. pipeline request to close the stream 170 | const batch = stream.batch(false); 171 | resultsPromise = executeHranaBatch( 172 | mode, 173 | version, 174 | batch, 175 | hranaStmts, 176 | ); 177 | } finally { 178 | stream.closeGracefully(); 179 | } 180 | 181 | const results = await resultsPromise; 182 | 183 | return results; 184 | } catch (e) { 185 | throw mapHranaError(e); 186 | } 187 | }); 188 | } 189 | 190 | async migrate(stmts: Array): Promise> { 191 | return this.limit>(async () => { 192 | try { 193 | const hranaStmts = stmts.map(stmtToHrana); 194 | const version = await this.#client.getVersion(); 195 | 196 | // Pipeline all operations, so `hrana.HttpClient` can open the stream, execute the batch and 197 | // close the stream in a single HTTP request. 198 | let resultsPromise: Promise>; 199 | const stream = this.#client.openStream(); 200 | try { 201 | const batch = stream.batch(false); 202 | resultsPromise = executeHranaBatch( 203 | "deferred", 204 | version, 205 | batch, 206 | hranaStmts, 207 | true, 208 | ); 209 | } finally { 210 | stream.closeGracefully(); 211 | } 212 | 213 | const results = await resultsPromise; 214 | 215 | return results; 216 | } catch (e) { 217 | throw mapHranaError(e); 218 | } 219 | }); 220 | } 221 | 222 | async transaction( 223 | mode: TransactionMode = "write", 224 | ): Promise { 225 | return this.limit(async () => { 226 | try { 227 | const version = await this.#client.getVersion(); 228 | return new HttpTransaction( 229 | this.#client.openStream(), 230 | mode, 231 | version, 232 | ); 233 | } catch (e) { 234 | throw mapHranaError(e); 235 | } 236 | }); 237 | } 238 | 239 | async executeMultiple(sql: string): Promise { 240 | return this.limit(async () => { 241 | try { 242 | // Pipeline all operations, so `hrana.HttpClient` can open the stream, execute the sequence and 243 | // close the stream in a single HTTP request. 244 | let promise: Promise; 245 | const stream = this.#client.openStream(); 246 | try { 247 | promise = stream.sequence(sql); 248 | } finally { 249 | stream.closeGracefully(); 250 | } 251 | 252 | await promise; 253 | } catch (e) { 254 | throw mapHranaError(e); 255 | } 256 | }); 257 | } 258 | 259 | sync(): Promise { 260 | throw new LibsqlError( 261 | "sync not supported in http mode", 262 | "SYNC_NOT_SUPPORTED", 263 | ); 264 | } 265 | 266 | close(): void { 267 | this.#client.close(); 268 | } 269 | 270 | get closed(): boolean { 271 | return this.#client.closed; 272 | } 273 | } 274 | 275 | export class HttpTransaction extends HranaTransaction implements Transaction { 276 | #stream: hrana.HttpStream; 277 | #sqlCache: SqlCache; 278 | 279 | /** @private */ 280 | constructor( 281 | stream: hrana.HttpStream, 282 | mode: TransactionMode, 283 | version: hrana.ProtocolVersion, 284 | ) { 285 | super(mode, version); 286 | this.#stream = stream; 287 | this.#sqlCache = new SqlCache(stream, sqlCacheCapacity); 288 | } 289 | 290 | /** @private */ 291 | override _getStream(): hrana.Stream { 292 | return this.#stream; 293 | } 294 | 295 | /** @private */ 296 | override _getSqlCache(): SqlCache { 297 | return this.#sqlCache; 298 | } 299 | 300 | override close(): void { 301 | this.#stream.close(); 302 | } 303 | 304 | override get closed(): boolean { 305 | return this.#stream.closed; 306 | } 307 | } 308 | -------------------------------------------------------------------------------- /packages/libsql-client/src/node.ts: -------------------------------------------------------------------------------- 1 | import type { Config, Client } from "@libsql/core/api"; 2 | import { LibsqlError } from "@libsql/core/api"; 3 | import type { ExpandedConfig } from "@libsql/core/config"; 4 | import { expandConfig } from "@libsql/core/config"; 5 | import { _createClient as _createSqlite3Client } from "./sqlite3.js"; 6 | import { _createClient as _createWsClient } from "./ws.js"; 7 | import { _createClient as _createHttpClient } from "./http.js"; 8 | 9 | export * from "@libsql/core/api"; 10 | 11 | /** Creates a {@link Client} object. 12 | * 13 | * You must pass at least an `url` in the {@link Config} object. 14 | */ 15 | export function createClient(config: Config): Client { 16 | return _createClient(expandConfig(config, true)); 17 | } 18 | 19 | function _createClient(config: ExpandedConfig) { 20 | if (config.scheme === "wss" || config.scheme === "ws") { 21 | return _createWsClient(config); 22 | } else if (config.scheme === "https" || config.scheme === "http") { 23 | return _createHttpClient(config); 24 | } else { 25 | return _createSqlite3Client(config); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /packages/libsql-client/src/sql_cache.ts: -------------------------------------------------------------------------------- 1 | import type * as hrana from "@libsql/hrana-client"; 2 | 3 | export class SqlCache { 4 | #owner: hrana.SqlOwner; 5 | #sqls: Lru; 6 | capacity: number; 7 | 8 | constructor(owner: hrana.SqlOwner, capacity: number) { 9 | this.#owner = owner; 10 | this.#sqls = new Lru(); 11 | this.capacity = capacity; 12 | } 13 | 14 | // Replaces SQL strings with cached `hrana.Sql` objects in the statements in `hranaStmts`. After this 15 | // function returns, we guarantee that all `hranaStmts` refer to valid (not closed) `hrana.Sql` objects, 16 | // but _we may invalidate any other `hrana.Sql` objects_ (by closing them, thus removing them from the 17 | // server). 18 | // 19 | // In practice, this means that after calling this function, you can use the statements only up to the 20 | // first `await`, because concurrent code may also use the cache and invalidate those statements. 21 | apply(hranaStmts: Array): void { 22 | if (this.capacity <= 0) { 23 | return; 24 | } 25 | 26 | const usedSqlObjs: Set = new Set(); 27 | 28 | for (const hranaStmt of hranaStmts) { 29 | if (typeof hranaStmt.sql !== "string") { 30 | continue; 31 | } 32 | const sqlText = hranaStmt.sql; 33 | 34 | // Stored SQL cannot exceed 5kb. 35 | // https://github.com/tursodatabase/libsql/blob/e9d637e051685f92b0da43849507b5ef4232fbeb/libsql-server/src/hrana/http/request.rs#L10 36 | if (sqlText.length >= 5000) { 37 | continue; 38 | } 39 | 40 | let sqlObj = this.#sqls.get(sqlText); 41 | if (sqlObj === undefined) { 42 | while (this.#sqls.size + 1 > this.capacity) { 43 | const [evictSqlText, evictSqlObj] = this.#sqls.peekLru()!; 44 | if (usedSqlObjs.has(evictSqlObj)) { 45 | // The SQL object that we are trying to evict is already in use in this batch, so we 46 | // must not evict and close it. 47 | break; 48 | } 49 | evictSqlObj.close(); 50 | this.#sqls.delete(evictSqlText); 51 | } 52 | 53 | if (this.#sqls.size + 1 <= this.capacity) { 54 | sqlObj = this.#owner.storeSql(sqlText); 55 | this.#sqls.set(sqlText, sqlObj); 56 | } 57 | } 58 | 59 | if (sqlObj !== undefined) { 60 | hranaStmt.sql = sqlObj; 61 | usedSqlObjs.add(sqlObj); 62 | } 63 | } 64 | } 65 | } 66 | 67 | class Lru { 68 | // This maps keys to the cache values. The entries are ordered by their last use (entires that were used 69 | // most recently are at the end). 70 | #cache: Map; 71 | 72 | constructor() { 73 | this.#cache = new Map(); 74 | } 75 | 76 | get(key: K): V | undefined { 77 | const value = this.#cache.get(key); 78 | if (value !== undefined) { 79 | // move the entry to the back of the Map 80 | this.#cache.delete(key); 81 | this.#cache.set(key, value); 82 | } 83 | return value; 84 | } 85 | 86 | set(key: K, value: V): void { 87 | this.#cache.set(key, value); 88 | } 89 | 90 | peekLru(): [K, V] | undefined { 91 | for (const entry of this.#cache.entries()) { 92 | return entry; 93 | } 94 | return undefined; 95 | } 96 | 97 | delete(key: K): void { 98 | this.#cache.delete(key); 99 | } 100 | 101 | get size(): number { 102 | return this.#cache.size; 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /packages/libsql-client/src/web.ts: -------------------------------------------------------------------------------- 1 | import type { Config, Client } from "@libsql/core/api"; 2 | import { LibsqlError } from "@libsql/core/api"; 3 | import type { ExpandedConfig } from "@libsql/core/config"; 4 | import { expandConfig } from "@libsql/core/config"; 5 | import { supportedUrlLink } from "@libsql/core/util"; 6 | 7 | import { _createClient as _createWsClient } from "./ws.js"; 8 | import { _createClient as _createHttpClient } from "./http.js"; 9 | 10 | export * from "@libsql/core/api"; 11 | 12 | export function createClient(config: Config): Client { 13 | return _createClient(expandConfig(config, true)); 14 | } 15 | 16 | /** @private */ 17 | export function _createClient(config: ExpandedConfig): Client { 18 | if (config.scheme === "ws" || config.scheme === "wss") { 19 | return _createWsClient(config); 20 | } else if (config.scheme === "http" || config.scheme === "https") { 21 | return _createHttpClient(config); 22 | } else { 23 | throw new LibsqlError( 24 | 'The client that uses Web standard APIs supports only "libsql:", "wss:", "ws:", "https:" and "http:" URLs, ' + 25 | `got ${JSON.stringify(config.scheme + ":")}. For more information, please read ${supportedUrlLink}`, 26 | "URL_SCHEME_NOT_SUPPORTED", 27 | ); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /packages/libsql-client/tsconfig.base.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "moduleResolution": "node", 4 | "lib": ["esnext"], 5 | "target": "esnext", 6 | "esModuleInterop": true, 7 | "isolatedModules": true, 8 | "rootDir": "src/", 9 | "strict": true 10 | }, 11 | "include": ["src/"], 12 | "exclude": ["**/__tests__"] 13 | } 14 | -------------------------------------------------------------------------------- /packages/libsql-client/tsconfig.build-cjs.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.base.json", 3 | "compilerOptions": { 4 | "module": "commonjs", 5 | "declaration": false, 6 | "outDir": "./lib-cjs/" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /packages/libsql-client/tsconfig.build-esm.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.base.json", 3 | "compilerOptions": { 4 | "module": "esnext", 5 | "declaration": true, 6 | "outDir": "./lib-esm/" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /packages/libsql-client/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.base.json", 3 | "compilerOptions": { 4 | "noEmit": true, 5 | "incremental": true 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /packages/libsql-client/typedoc.json: -------------------------------------------------------------------------------- 1 | { 2 | "entryPoints": ["src/node.ts"], 3 | "out": "docs", 4 | "excludePrivate": true, 5 | "excludeInternal": true, 6 | "visibilityFilters": { 7 | "inherited": true, 8 | "external": true 9 | }, 10 | "includeVersion": true 11 | } 12 | -------------------------------------------------------------------------------- /packages/libsql-core/jest.config.js: -------------------------------------------------------------------------------- 1 | export default { 2 | preset: "ts-jest/presets/default-esm", 3 | moduleNameMapper: { 4 | "^(\\.{1,2}/.*)\\.js$": "$1", 5 | }, 6 | testMatch: ["**/__tests__/*.test.[jt]s"], 7 | }; 8 | -------------------------------------------------------------------------------- /packages/libsql-core/package-cjs.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "commonjs" 3 | } 4 | -------------------------------------------------------------------------------- /packages/libsql-core/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@libsql/core", 3 | "version": "0.15.8", 4 | "keywords": [ 5 | "libsql", 6 | "database", 7 | "sqlite", 8 | "serverless", 9 | "vercel", 10 | "netlify", 11 | "lambda" 12 | ], 13 | "description": "libSQL driver for TypeScript and JavaScript", 14 | "repository": { 15 | "type": "git", 16 | "url": "git+https://github.com/libsql/libsql-client-ts", 17 | "directory": "packages/libsql-core" 18 | }, 19 | "authors": [ 20 | "Jan Špaček ", 21 | "Pekka Enberg ", 22 | "Jan Plhak " 23 | ], 24 | "license": "MIT", 25 | "type": "module", 26 | "exports": { 27 | "./api": { 28 | "types": "./lib-esm/api.d.ts", 29 | "import": "./lib-esm/api.js", 30 | "require": "./lib-cjs/api.js" 31 | }, 32 | "./config": { 33 | "types": "./lib-esm/config.d.ts", 34 | "import": "./lib-esm/config.js", 35 | "require": "./lib-cjs/config.js" 36 | }, 37 | "./uri": { 38 | "types": "./lib-esm/uri.d.ts", 39 | "import": "./lib-esm/uri.js", 40 | "require": "./lib-cjs/uri.js" 41 | }, 42 | "./util": { 43 | "types": "./lib-esm/util.d.ts", 44 | "import": "./lib-esm/util.js", 45 | "require": "./lib-cjs/util.js" 46 | } 47 | }, 48 | "typesVersions": { 49 | "*": { 50 | "api": [ 51 | "./lib-esm/api.d.ts" 52 | ], 53 | "config": [ 54 | "./lib-esm/config.d.ts" 55 | ], 56 | "uri": [ 57 | "./lib-esm/uri.d.ts" 58 | ], 59 | "util": [ 60 | "./lib-esm/util.d.ts" 61 | ] 62 | } 63 | }, 64 | "files": [ 65 | "lib-cjs/**", 66 | "lib-esm/**" 67 | ], 68 | "scripts": { 69 | "prepublishOnly": "npm run build", 70 | "prebuild": "rm -rf ./lib-cjs ./lib-esm", 71 | "build": "npm run build:cjs && npm run build:esm", 72 | "build:cjs": "tsc -p tsconfig.build-cjs.json", 73 | "build:esm": "tsc -p tsconfig.build-esm.json", 74 | "format:check": "prettier --check .", 75 | "postbuild": "cp package-cjs.json ./lib-cjs/package.json", 76 | "test": "jest --runInBand", 77 | "typecheck": "tsc --noEmit", 78 | "typedoc": "rm -rf ./docs && typedoc" 79 | }, 80 | "dependencies": { 81 | "js-base64": "^3.7.5" 82 | }, 83 | "devDependencies": { 84 | "@types/jest": "^29.2.5", 85 | "@types/node": "^18.15.5", 86 | "jest": "^29.3.1", 87 | "ts-jest": "^29.0.5", 88 | "typedoc": "^0.23.28", 89 | "typescript": "^4.9.4" 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /packages/libsql-core/src/config.ts: -------------------------------------------------------------------------------- 1 | import type { Config, IntMode } from "./api.js"; 2 | import { LibsqlError } from "./api.js"; 3 | import type { Authority } from "./uri.js"; 4 | import { parseUri } from "./uri.js"; 5 | import { supportedUrlLink } from "./util.js"; 6 | 7 | export interface ExpandedConfig { 8 | scheme: ExpandedScheme; 9 | tls: boolean; 10 | authority: Authority | undefined; 11 | path: string; 12 | authToken: string | undefined; 13 | encryptionKey: string | undefined; 14 | syncUrl: string | undefined; 15 | syncInterval: number | undefined; 16 | readYourWrites: boolean | undefined; 17 | offline: boolean | undefined; 18 | intMode: IntMode; 19 | fetch: Function | undefined; 20 | concurrency: number; 21 | } 22 | 23 | export type ExpandedScheme = "wss" | "ws" | "https" | "http" | "file"; 24 | 25 | type queryParamDef = { 26 | values?: string[]; 27 | update?: (key: string, value: string) => void; 28 | }; 29 | type queryParamsDef = { [key: string]: queryParamDef }; 30 | 31 | const inMemoryMode = ":memory:"; 32 | 33 | export function isInMemoryConfig(config: ExpandedConfig): boolean { 34 | return ( 35 | config.scheme === "file" && 36 | (config.path === ":memory:" || config.path.startsWith(":memory:?")) 37 | ); 38 | } 39 | 40 | export function expandConfig( 41 | config: Readonly, 42 | preferHttp: boolean, 43 | ): ExpandedConfig { 44 | if (typeof config !== "object") { 45 | // produce a reasonable error message in the common case where users type 46 | // `createClient("libsql://...")` instead of `createClient({url: "libsql://..."})` 47 | throw new TypeError( 48 | `Expected client configuration as object, got ${typeof config}`, 49 | ); 50 | } 51 | 52 | let { url, authToken, tls, intMode, concurrency } = config; 53 | // fill simple defaults right here 54 | concurrency = Math.max(0, concurrency || 20); 55 | intMode ??= "number"; 56 | 57 | let connectionQueryParams: string[] = []; // recognized query parameters which we sanitize through white list of valid key-value pairs 58 | 59 | // convert plain :memory: url to URI format to make logic more uniform 60 | if (url === inMemoryMode) { 61 | url = "file::memory:"; 62 | } 63 | 64 | // parse url parameters first and override config with update values 65 | const uri = parseUri(url); 66 | const originalUriScheme = uri.scheme.toLowerCase(); 67 | const isInMemoryMode = 68 | originalUriScheme === "file" && 69 | uri.path === inMemoryMode && 70 | uri.authority === undefined; 71 | 72 | let queryParamsDef: queryParamsDef; 73 | if (isInMemoryMode) { 74 | queryParamsDef = { 75 | cache: { 76 | values: ["shared", "private"], 77 | update: (key, value) => 78 | connectionQueryParams.push(`${key}=${value}`), 79 | }, 80 | }; 81 | } else { 82 | queryParamsDef = { 83 | tls: { 84 | values: ["0", "1"], 85 | update: (_, value) => (tls = value === "1"), 86 | }, 87 | authToken: { 88 | update: (_, value) => (authToken = value), 89 | }, 90 | }; 91 | } 92 | 93 | for (const { key, value } of uri.query?.pairs ?? []) { 94 | if (!Object.hasOwn(queryParamsDef, key)) { 95 | throw new LibsqlError( 96 | `Unsupported URL query parameter ${JSON.stringify(key)}`, 97 | "URL_PARAM_NOT_SUPPORTED", 98 | ); 99 | } 100 | const queryParamDef = queryParamsDef[key]; 101 | if ( 102 | queryParamDef.values !== undefined && 103 | !queryParamDef.values.includes(value) 104 | ) { 105 | throw new LibsqlError( 106 | `Unknown value for the "${key}" query argument: ${JSON.stringify(value)}. Supported values are: [${queryParamDef.values.map((x) => '"' + x + '"').join(", ")}]`, 107 | "URL_INVALID", 108 | ); 109 | } 110 | if (queryParamDef.update !== undefined) { 111 | queryParamDef?.update(key, value); 112 | } 113 | } 114 | 115 | // fill complex defaults & validate config 116 | const connectionQueryParamsString = 117 | connectionQueryParams.length === 0 118 | ? "" 119 | : `?${connectionQueryParams.join("&")}`; 120 | const path = uri.path + connectionQueryParamsString; 121 | 122 | let scheme: string; 123 | if (originalUriScheme === "libsql") { 124 | if (tls === false) { 125 | if (uri.authority?.port === undefined) { 126 | throw new LibsqlError( 127 | 'A "libsql:" URL with ?tls=0 must specify an explicit port', 128 | "URL_INVALID", 129 | ); 130 | } 131 | scheme = preferHttp ? "http" : "ws"; 132 | } else { 133 | scheme = preferHttp ? "https" : "wss"; 134 | } 135 | } else { 136 | scheme = originalUriScheme; 137 | } 138 | if (scheme === "http" || scheme === "ws") { 139 | tls ??= false; 140 | } else { 141 | tls ??= true; 142 | } 143 | 144 | if ( 145 | scheme !== "http" && 146 | scheme !== "ws" && 147 | scheme !== "https" && 148 | scheme !== "wss" && 149 | scheme !== "file" 150 | ) { 151 | throw new LibsqlError( 152 | 'The client supports only "libsql:", "wss:", "ws:", "https:", "http:" and "file:" URLs, ' + 153 | `got ${JSON.stringify(uri.scheme + ":")}. ` + 154 | `For more information, please read ${supportedUrlLink}`, 155 | "URL_SCHEME_NOT_SUPPORTED", 156 | ); 157 | } 158 | if (intMode !== "number" && intMode !== "bigint" && intMode !== "string") { 159 | throw new TypeError( 160 | `Invalid value for intMode, expected "number", "bigint" or "string", got ${JSON.stringify(intMode)}`, 161 | ); 162 | } 163 | if (uri.fragment !== undefined) { 164 | throw new LibsqlError( 165 | `URL fragments are not supported: ${JSON.stringify("#" + uri.fragment)}`, 166 | "URL_INVALID", 167 | ); 168 | } 169 | 170 | if (isInMemoryMode) { 171 | return { 172 | scheme: "file", 173 | tls: false, 174 | path, 175 | intMode, 176 | concurrency, 177 | syncUrl: config.syncUrl, 178 | syncInterval: config.syncInterval, 179 | readYourWrites: config.readYourWrites, 180 | offline: config.offline, 181 | fetch: config.fetch, 182 | authToken: undefined, 183 | encryptionKey: undefined, 184 | authority: undefined, 185 | }; 186 | } 187 | 188 | return { 189 | scheme, 190 | tls, 191 | authority: uri.authority, 192 | path, 193 | authToken, 194 | intMode, 195 | concurrency, 196 | encryptionKey: config.encryptionKey, 197 | syncUrl: config.syncUrl, 198 | syncInterval: config.syncInterval, 199 | readYourWrites: config.readYourWrites, 200 | offline: config.offline, 201 | fetch: config.fetch, 202 | }; 203 | } 204 | -------------------------------------------------------------------------------- /packages/libsql-core/src/uri.ts: -------------------------------------------------------------------------------- 1 | // URI parser based on RFC 3986 2 | // We can't use the standard `URL` object, because we want to support relative `file:` URLs like 3 | // `file:relative/path/database.db`, which are not correct according to RFC 8089, which standardizes the 4 | // `file` scheme. 5 | 6 | import { LibsqlError } from "./api.js"; 7 | 8 | export interface Uri { 9 | scheme: string; 10 | authority: Authority | undefined; 11 | path: string; 12 | query: Query | undefined; 13 | fragment: string | undefined; 14 | } 15 | 16 | export interface HierPart { 17 | authority: Authority | undefined; 18 | path: string; 19 | } 20 | 21 | export interface Authority { 22 | host: string; 23 | port: number | undefined; 24 | userinfo: Userinfo | undefined; 25 | } 26 | 27 | export interface Userinfo { 28 | username: string; 29 | password: string | undefined; 30 | } 31 | 32 | export interface Query { 33 | pairs: Array; 34 | } 35 | 36 | export interface KeyValue { 37 | key: string; 38 | value: string; 39 | } 40 | 41 | export function parseUri(text: string): Uri { 42 | const match = URI_RE.exec(text); 43 | if (match === null) { 44 | throw new LibsqlError( 45 | `The URL '${text}' is not in a valid format`, 46 | "URL_INVALID", 47 | ); 48 | } 49 | 50 | const groups = match.groups!; 51 | const scheme = groups["scheme"]!; 52 | const authority = 53 | groups["authority"] !== undefined 54 | ? parseAuthority(groups["authority"]) 55 | : undefined; 56 | const path = percentDecode(groups["path"]!); 57 | const query = 58 | groups["query"] !== undefined ? parseQuery(groups["query"]) : undefined; 59 | const fragment = 60 | groups["fragment"] !== undefined 61 | ? percentDecode(groups["fragment"]) 62 | : undefined; 63 | return { scheme, authority, path, query, fragment }; 64 | } 65 | 66 | const URI_RE = (() => { 67 | const SCHEME = "(?[A-Za-z][A-Za-z.+-]*)"; 68 | const AUTHORITY = "(?[^/?#]*)"; 69 | const PATH = "(?[^?#]*)"; 70 | const QUERY = "(?[^#]*)"; 71 | const FRAGMENT = "(?.*)"; 72 | return new RegExp( 73 | `^${SCHEME}:(//${AUTHORITY})?${PATH}(\\?${QUERY})?(#${FRAGMENT})?$`, 74 | "su", 75 | ); 76 | })(); 77 | 78 | function parseAuthority(text: string): Authority { 79 | const match = AUTHORITY_RE.exec(text); 80 | if (match === null) { 81 | throw new LibsqlError( 82 | "The authority part of the URL is not in a valid format", 83 | "URL_INVALID", 84 | ); 85 | } 86 | 87 | const groups = match.groups!; 88 | const host = percentDecode(groups["host_br"] ?? groups["host"]); 89 | const port = groups["port"] ? parseInt(groups["port"], 10) : undefined; 90 | const userinfo = 91 | groups["username"] !== undefined 92 | ? { 93 | username: percentDecode(groups["username"]), 94 | password: 95 | groups["password"] !== undefined 96 | ? percentDecode(groups["password"]) 97 | : undefined, 98 | } 99 | : undefined; 100 | return { host, port, userinfo }; 101 | } 102 | 103 | const AUTHORITY_RE = (() => { 104 | return new RegExp( 105 | `^((?[^:]*)(:(?.*))?@)?((?[^:\\[\\]]*)|(\\[(?[^\\[\\]]*)\\]))(:(?[0-9]*))?$`, 106 | "su", 107 | ); 108 | })(); 109 | 110 | // Query string is parsed as application/x-www-form-urlencoded according to the Web URL standard: 111 | // https://url.spec.whatwg.org/#urlencoded-parsing 112 | function parseQuery(text: string): Query { 113 | const sequences = text.split("&"); 114 | const pairs = []; 115 | for (const sequence of sequences) { 116 | if (sequence === "") { 117 | continue; 118 | } 119 | 120 | let key: string; 121 | let value: string; 122 | const splitIdx = sequence.indexOf("="); 123 | if (splitIdx < 0) { 124 | key = sequence; 125 | value = ""; 126 | } else { 127 | key = sequence.substring(0, splitIdx); 128 | value = sequence.substring(splitIdx + 1); 129 | } 130 | 131 | pairs.push({ 132 | key: percentDecode(key.replaceAll("+", " ")), 133 | value: percentDecode(value.replaceAll("+", " ")), 134 | }); 135 | } 136 | return { pairs }; 137 | } 138 | 139 | function percentDecode(text: string): string { 140 | try { 141 | return decodeURIComponent(text); 142 | } catch (e) { 143 | if (e instanceof URIError) { 144 | throw new LibsqlError( 145 | `URL component has invalid percent encoding: ${e}`, 146 | "URL_INVALID", 147 | undefined, 148 | e, 149 | ); 150 | } 151 | throw e; 152 | } 153 | } 154 | 155 | export function encodeBaseUrl( 156 | scheme: string, 157 | authority: Authority | undefined, 158 | path: string, 159 | ): URL { 160 | if (authority === undefined) { 161 | throw new LibsqlError( 162 | `URL with scheme ${JSON.stringify(scheme + ":")} requires authority (the "//" part)`, 163 | "URL_INVALID", 164 | ); 165 | } 166 | 167 | const schemeText = `${scheme}:`; 168 | 169 | const hostText = encodeHost(authority.host); 170 | const portText = encodePort(authority.port); 171 | const userinfoText = encodeUserinfo(authority.userinfo); 172 | const authorityText = `//${userinfoText}${hostText}${portText}`; 173 | 174 | let pathText = path.split("/").map(encodeURIComponent).join("/"); 175 | if (pathText !== "" && !pathText.startsWith("/")) { 176 | pathText = "/" + pathText; 177 | } 178 | 179 | return new URL(`${schemeText}${authorityText}${pathText}`); 180 | } 181 | 182 | function encodeHost(host: string): string { 183 | return host.includes(":") ? `[${encodeURI(host)}]` : encodeURI(host); 184 | } 185 | 186 | function encodePort(port: number | undefined): string { 187 | return port !== undefined ? `:${port}` : ""; 188 | } 189 | 190 | function encodeUserinfo(userinfo: Userinfo | undefined): string { 191 | if (userinfo === undefined) { 192 | return ""; 193 | } 194 | 195 | const usernameText = encodeURIComponent(userinfo.username); 196 | const passwordText = 197 | userinfo.password !== undefined 198 | ? `:${encodeURIComponent(userinfo.password)}` 199 | : ""; 200 | return `${usernameText}${passwordText}@`; 201 | } 202 | -------------------------------------------------------------------------------- /packages/libsql-core/src/util.ts: -------------------------------------------------------------------------------- 1 | import { Base64 } from "js-base64"; 2 | import { 3 | ResultSet, 4 | Row, 5 | Value, 6 | TransactionMode, 7 | InStatement, 8 | LibsqlError, 9 | } from "./api"; 10 | 11 | export const supportedUrlLink = 12 | "https://github.com/libsql/libsql-client-ts#supported-urls"; 13 | 14 | export function transactionModeToBegin(mode: TransactionMode): string { 15 | if (mode === "write") { 16 | return "BEGIN IMMEDIATE"; 17 | } else if (mode === "read") { 18 | return "BEGIN TRANSACTION READONLY"; 19 | } else if (mode === "deferred") { 20 | return "BEGIN DEFERRED"; 21 | } else { 22 | throw RangeError( 23 | 'Unknown transaction mode, supported values are "write", "read" and "deferred"', 24 | ); 25 | } 26 | } 27 | 28 | export class ResultSetImpl implements ResultSet { 29 | columns: Array; 30 | columnTypes: Array; 31 | rows: Array; 32 | rowsAffected: number; 33 | lastInsertRowid: bigint | undefined; 34 | 35 | constructor( 36 | columns: Array, 37 | columnTypes: Array, 38 | rows: Array, 39 | rowsAffected: number, 40 | lastInsertRowid: bigint | undefined, 41 | ) { 42 | this.columns = columns; 43 | this.columnTypes = columnTypes; 44 | this.rows = rows; 45 | this.rowsAffected = rowsAffected; 46 | this.lastInsertRowid = lastInsertRowid; 47 | } 48 | 49 | toJSON(): any { 50 | return { 51 | columns: this.columns, 52 | columnTypes: this.columnTypes, 53 | rows: this.rows.map(rowToJson), 54 | rowsAffected: this.rowsAffected, 55 | lastInsertRowid: 56 | this.lastInsertRowid !== undefined 57 | ? "" + this.lastInsertRowid 58 | : null, 59 | }; 60 | } 61 | } 62 | 63 | function rowToJson(row: Row): unknown { 64 | return Array.prototype.map.call(row, valueToJson); 65 | } 66 | 67 | function valueToJson(value: Value): unknown { 68 | if (typeof value === "bigint") { 69 | return "" + value; 70 | } else if (value instanceof ArrayBuffer) { 71 | return Base64.fromUint8Array(new Uint8Array(value)); 72 | } else { 73 | return value; 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /packages/libsql-core/tsconfig.base.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "moduleResolution": "node", 4 | "lib": ["esnext"], 5 | "target": "esnext", 6 | "esModuleInterop": true, 7 | "isolatedModules": true, 8 | "rootDir": "src/", 9 | "strict": true 10 | }, 11 | "include": ["src/"], 12 | "exclude": ["**/__tests__"] 13 | } 14 | -------------------------------------------------------------------------------- /packages/libsql-core/tsconfig.build-cjs.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.base.json", 3 | "compilerOptions": { 4 | "module": "commonjs", 5 | "declaration": false, 6 | "outDir": "./lib-cjs/" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /packages/libsql-core/tsconfig.build-esm.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.base.json", 3 | "compilerOptions": { 4 | "module": "esnext", 5 | "declaration": true, 6 | "outDir": "./lib-esm/" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /packages/libsql-core/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.base.json", 3 | "compilerOptions": { 4 | "noEmit": true, 5 | "incremental": true 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /packages/libsql-core/typedoc.json: -------------------------------------------------------------------------------- 1 | { 2 | "entryPoints": ["src/node.ts"], 3 | "out": "docs", 4 | "excludePrivate": true, 5 | "excludeInternal": true, 6 | "visibilityFilters": { 7 | "inherited": true, 8 | "external": true 9 | }, 10 | "includeVersion": true 11 | } 12 | --------------------------------------------------------------------------------