├── src ├── main.ts ├── globcon │ ├── utils.js │ └── mod.ts ├── base64 │ ├── utils.js │ └── mod.ts ├── errors.ts ├── commons.ts ├── functions.ts ├── parsers.ts ├── client.ts ├── builders.ts └── official_api_1726336437314.ts ├── _tests ├── one_statement.ts ├── test1.ts ├── test_b64.ts ├── test_special_chars.ts ├── perf.ts ├── test3.ts ├── test2.ts └── test4.ts ├── js_test ├── tsup.config.ts ├── examples └── custom_fetch │ ├── undici.ts │ ├── log_req_body.ts │ ├── generic.ts │ ├── libsql_isomorphic_fetch.ts │ └── axios.ts ├── get_official_api ├── tsconfig.json ├── LICENSE ├── package.json ├── .gitignore └── README.md /src/main.ts: -------------------------------------------------------------------------------- 1 | 2 | export * from './builders.js'; 3 | export * from './parsers.js'; 4 | export * from './functions.js'; 5 | export * from './client.js'; 6 | export * from './errors.js'; 7 | export * from './commons.js'; -------------------------------------------------------------------------------- /src/globcon/utils.js: -------------------------------------------------------------------------------- 1 | export const _hasConsoleError = typeof console.error === 'function'; 2 | export const _hasURL = typeof URL === 'function'; 3 | 4 | export const _useConsoleError = (str) => console.error(str); 5 | export const _newURL = (str) => new URL(str); -------------------------------------------------------------------------------- /_tests/one_statement.ts: -------------------------------------------------------------------------------- 1 | import { libsqlExecute } from "../src/main"; 2 | import { skjdgfksg } from "./conf"; 3 | 4 | (async () => { 5 | const res4 = await libsqlExecute( 6 | skjdgfksg, 7 | "analyze;" 8 | ); 9 | console.log(res4); 10 | })(); -------------------------------------------------------------------------------- /_tests/test1.ts: -------------------------------------------------------------------------------- 1 | import { rawValue } from "../src/commons"; 2 | 3 | (() => { 4 | let a: Record = { 5 | 1: "aa", 6 | null: "bb", 7 | ll: 1 8 | } 9 | let b: Array = ["ll"] 10 | console.log(typeof(a.length)); 11 | console.log(typeof(b.length)); 12 | for (const m in a) { 13 | console.log(m+" "+a[m]); 14 | } 15 | })(); -------------------------------------------------------------------------------- /js_test: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | bun build "$1" --target node > _test_node_run.mjs 4 | bun build "$1" --target browser > _test_web_run.js 5 | bun build "$1" --target bun > _test_bun_run.js 6 | 7 | echo "running node..." 8 | node _test_node_run.mjs 9 | echo "running deno..." 10 | deno run --allow-net _test_web_run.js 11 | echo "running bun..." 12 | bun run _test_bun_run.js 13 | 14 | rm _test_node_run.mjs _test_web_run.js _test_bun_run.js -------------------------------------------------------------------------------- /tsup.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'tsup' 2 | 3 | export default defineConfig({ 4 | entry: ['./src/main.ts'], 5 | splitting: false, 6 | sourcemap: false, 7 | clean: true, 8 | minify: true, 9 | minifyWhitespace: true, 10 | outDir: './dist', 11 | platform: 'neutral', 12 | treeshake: 'safest', 13 | cjsInterop: true, 14 | dts: true, 15 | shims: true, 16 | format: ['cjs', 'esm'], 17 | target: ['deno1', 'node18', 'chrome120', 'edge120', 'firefox120', 'safari16', 'es2020'] 18 | }) -------------------------------------------------------------------------------- /examples/custom_fetch/undici.ts: -------------------------------------------------------------------------------- 1 | //@ts-nocheck 2 | //USING UNDICI 3 | import { fetch as undici_fetch } from 'undici'; 4 | import { createClient } from 'libsql-stateless-easy'; 5 | import { conf } from './conf'; 6 | 7 | (async () => { 8 | const client = createClient({ 9 | url: conf.db_url, 10 | authToken: conf.authToken, 11 | // as easy as that 12 | fetch: undici_fetch 13 | }); 14 | 15 | const res = await client.execute("select * from contacts;"); 16 | console.log(res); 17 | })(); 18 | 19 | -------------------------------------------------------------------------------- /src/base64/utils.js: -------------------------------------------------------------------------------- 1 | export const _hasBuffer = typeof Buffer === 'function'; 2 | export const _hasBtoa = typeof btoa === 'function'; 3 | export const _hasAtob = typeof atob === 'function'; 4 | 5 | export const _useAtob = (str) => atob(str); 6 | export const _useBtoa = (str) => btoa(str); 7 | export const _useBufferU8a = (u8a) => Buffer.from(u8a).toString('base64'); 8 | export const _useBufferBin = (bin) => Buffer.from(bin, 'binary').toString('base64'); 9 | export const _useBufferStr = (a) => Buffer.from(a, 'base64'); 10 | export const _useBufferAsc = (asc) => Buffer.from(asc, 'base64').toString('binary'); -------------------------------------------------------------------------------- /_tests/test_b64.ts: -------------------------------------------------------------------------------- 1 | import { fromUint8Array, toUint8Array } from '../src/base64/mod.js'; 2 | import { Base64 } from 'js-base64'; 3 | 4 | const teststr = "I need to write my resume. \n\nT-T"; 5 | 6 | (() => { 7 | const a = toUint8Array(teststr); 8 | const b = Base64.toUint8Array(teststr); 9 | if (fromUint8Array(a) == Base64.fromUint8Array(b)) console.log("[GOOD] same encode"); 10 | else console.log("[BAD] encode: we fucked up"); 11 | 12 | const ra = fromUint8Array(a); 13 | const rb = Base64.fromUint8Array(b); 14 | if (fromUint8Array(a) == Base64.fromUint8Array(b)) console.log("[GOOD] same decode"); 15 | else console.log("[BAD] decode: we fucked up"); 16 | 17 | })(); -------------------------------------------------------------------------------- /examples/custom_fetch/log_req_body.ts: -------------------------------------------------------------------------------- 1 | //@ts-nocheck 2 | //USING UNDICI 3 | import { createClient, libsqlFetchLike } from 'libsql-stateless-easy'; 4 | import { conf } from './conf'; 5 | 6 | (async () => { 7 | const client = createClient({ 8 | url: conf.db_url, 9 | authToken: conf.authToken, 10 | // as easy as that 11 | fetch: async (...args: Parameters): ReturnType => { 12 | console.log(`[${args[1]?.method} ${args[0]}]: ${args[1]?.body}`); 13 | return await globalThis.fetch(...args); 14 | } 15 | }); 16 | 17 | const res = await client.execute("select * from contacts;"); 18 | console.log(res); 19 | })(); 20 | 21 | -------------------------------------------------------------------------------- /get_official_api: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | UNIXMILI=$(node -e "console.log(Date.now())") 4 | 5 | { 6 | printf "// DO NOT CHANGE THIS FILE. THIS HAS BEEN AUTO GENERATED BY 'get_official_api'.\n"; 7 | printf "// This file has been pulled from the main branch of @libsql/core/api at %s Unix Epoch Miliseconds\n" "$UNIXMILI"; 8 | printf "// to maintain 100%% compatibality with the official SDK.\n"; 9 | printf "\n/* LICENSE for this file\n"; 10 | curl -s "https://raw.githubusercontent.com/tursodatabase/libsql-client-ts/main/LICENSE"; 11 | printf "\n*/\n\n"; 12 | curl -s "https://raw.githubusercontent.com/tursodatabase/libsql-client-ts/main/packages/libsql-core/src/api.ts" 13 | } >> "./src/official_api_${UNIXMILI}.ts" 14 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "strict": true, 4 | "noImplicitAny": true, 5 | "strictNullChecks": true, 6 | "strictFunctionTypes": true, 7 | "strictBindCallApply": true, 8 | "strictPropertyInitialization": true, 9 | "verbatimModuleSyntax": true, 10 | "noImplicitThis": true, 11 | "alwaysStrict": true, 12 | "noUnusedLocals": true, 13 | "noUnusedParameters": true, 14 | "noImplicitReturns": true, 15 | "noFallthroughCasesInSwitch": true, 16 | "isolatedModules": true, 17 | 18 | "allowJs": true, 19 | 20 | "lib": ["ES2023"], 21 | "module": "Node16", 22 | // "target": "ES2019", 23 | "declaration": true, 24 | "outDir": "./dist" 25 | }, 26 | "include": ["./src/*", "./src/*/*"] 27 | } -------------------------------------------------------------------------------- /examples/custom_fetch/generic.ts: -------------------------------------------------------------------------------- 1 | //@ts-nocheck 2 | //A GENERIC BOILERPLATE FOR IMPLEMENTING CUSTOM FETCH 3 | import { createClient, libsqlFetchLike } from 'libsql-stateless-easy'; 4 | import { conf } from './conf'; 5 | 6 | (async () => { 7 | const client = createClient({ 8 | url: conf.db_url, 9 | authToken: conf.authToken, 10 | fetch: async (...args: Parameters): ReturnType => { 11 | //implement your own fetch here (look at libsql_isomorphic_fetch.ts for concrete example) 12 | /** NOTE: 13 | * args[0] is the url string 14 | * args[1] is the request init 15 | * - args[1].body 16 | * - args[1].headers 17 | * - args[1].method 18 | */ 19 | } 20 | }); 21 | 22 | const res = await client.execute("select * from contacts;"); 23 | console.log(res); 24 | })(); -------------------------------------------------------------------------------- /_tests/test_special_chars.ts: -------------------------------------------------------------------------------- 1 | import { createClient } from "../src/main"; 2 | //import { createClient } from '@libsql/client/web'; 3 | import { skjdgfksg } from "./conf"; 4 | 5 | (async () => { 6 | const client = createClient({url: skjdgfksg.db_url, authToken: skjdgfksg.authToken, 7 | //disableCriticalChecks: true 8 | }); 9 | const res2 = await client.execute({ 10 | sql: "insert into contacts (contact_id,first_name,last_name,email,phone) values (?,?,?,?,?);", 11 | args: [6,"glomm\nangk","feru","moca@doro.co","001"] 12 | }); 13 | console.log(res2); 14 | 15 | const res4 = await client.execute({ 16 | sql: "select * from contacts where contact_id = :kkl", 17 | args: {kkl: 6} 18 | }); 19 | console.log("\n===WITH SPECIAL CHARS START===\n"+JSON.stringify(res4.rows[0], null, 4)+"\n===WITH SPECIAL CHARS START===\n"); 20 | 21 | const res3 = await client.execute({ 22 | sql: "delete from contacts where contact_id = :kkl", 23 | args: {kkl: 6} 24 | }); 25 | console.log(res3); 26 | })(); -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Localbox Crox 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 | -------------------------------------------------------------------------------- /_tests/perf.ts: -------------------------------------------------------------------------------- 1 | import { libsqlBatch, libsqlExecute } from "../src/main"; 2 | import { skjdgfksg } from "./conf"; 3 | 4 | (async () => { 5 | const conf = skjdgfksg; 6 | 7 | console.time("libsqlBatch"); 8 | const res = await libsqlBatch(conf, [ 9 | { 10 | sql: "select * from contacts where contact_id = ?;", 11 | args: [3] 12 | }, 13 | { 14 | sql: "select first_name, last_name, email from contacts where contact_id = @koji;", 15 | args: {koji: 2} 16 | }, 17 | { 18 | sql: "insert into contacts (contact_id,first_name,last_name,email,phone) values (?,?,?,?,?);", 19 | args: [6,"glomm","feru","moca@doro.co","001"] 20 | }, 21 | { 22 | sql: "delete from contacts where contact_id = :kkl", 23 | args: {kkl: 6} 24 | } 25 | ]); 26 | console.timeEnd("libsqlBatch"); 27 | 28 | console.log(!!res); 29 | 30 | 31 | 32 | console.time("libsqlExecute"); 33 | const res2 = await libsqlExecute(conf, { 34 | sql: "select first_name, last_name, email, contact_id from contacts where contact_id = ?;", 35 | args: [1] 36 | }); 37 | console.timeEnd("libsqlExecute"); 38 | 39 | console.log(!!res2); 40 | })(); -------------------------------------------------------------------------------- /_tests/test3.ts: -------------------------------------------------------------------------------- 1 | import { libsqlBatch, libsqlExecute, libsqlServerCompatCheck } from "../src/main"; 2 | import { skjdgfksg } from "./conf"; 3 | 4 | (async () => { 5 | const conf = skjdgfksg; 6 | 7 | const res = await libsqlBatch(conf, [ 8 | { 9 | sql: "select * from contacts where contact_id = ?;", 10 | args: [3] 11 | }, 12 | { 13 | sql: "select first_name, last_name, email from contacts where contact_id = @koji;", 14 | args: {koji: 2} 15 | }, 16 | { 17 | sql: "insert into contacts (contact_id,first_name,last_name,email,phone) values (?,?,?,?,?);", 18 | args: [6,"glomm","feru","moca@doro.co","001"] 19 | }, 20 | { 21 | sql: "delete from contacts where contact_id = :kkl", 22 | args: {kkl: 6} 23 | } 24 | ], []); 25 | console.log(res); 26 | 27 | const res2 = await libsqlExecute(conf, { 28 | sql: "select first_name, last_name, email, contact_id from contacts where contact_id = ?;", 29 | args: [1] 30 | }); 31 | console.log(res2); 32 | 33 | const res3 = await libsqlServerCompatCheck(conf); 34 | if (res3) console.log("Server Compat Check OK"); 35 | else console.error("Server Compat Check NOT OK"); 36 | })(); -------------------------------------------------------------------------------- /examples/custom_fetch/libsql_isomorphic_fetch.ts: -------------------------------------------------------------------------------- 1 | //@ts-nocheck 2 | //USEND THE SAME TRANSPORT AS @libsql/client 3 | import { fetch as iso_fetch, Request as iso_Request, Headers as iso_Headers} from '@libsql/isomorphic-fetch'; 4 | import { createClient, libsqlFetchLike } from 'libsql-stateless-easy'; 5 | import { conf } from './conf'; 6 | 7 | (async () => { 8 | const client = createClient({ 9 | url: conf.db_url, 10 | authToken: conf.authToken, 11 | //normally you'd pass the fetch directly but @libsql/isomorphic-fetch is not WEBAPI fetch spec compatible 12 | fetch: async (...args: Parameters): ReturnType => { 13 | return await iso_fetch( 14 | new iso_Request( 15 | args[0], { 16 | body: args[1]?.body, 17 | method: args[1]?.method, 18 | headers: (() => { 19 | const h = new iso_Headers(); 20 | for (const k in args[1]?.headers) h.append(k, args[1]?.headers[k]); 21 | return h; 22 | })() 23 | } 24 | ) 25 | ); 26 | } 27 | }); 28 | 29 | const res = await client.execute("select * from contacts;"); 30 | console.log(res); 31 | })(); 32 | 33 | -------------------------------------------------------------------------------- /examples/custom_fetch/axios.ts: -------------------------------------------------------------------------------- 1 | //@ts-nocheck 2 | //USING AXIOS 3 | import * as axios from 'axios'; 4 | import { createClient, libsqlFetchLike } from 'libsql-stateless-easy'; 5 | import { conf } from './conf'; 6 | 7 | (async () => { 8 | const client = createClient({ 9 | url: conf.db_url, 10 | authToken: conf.authToken, 11 | //creating an usable fetch-like from axios API 12 | fetch: async (...args: Parameters): ReturnType => { 13 | const rawRes = await axios.default({ 14 | url: args[0], 15 | method: args[1]?.method, 16 | headers: args[1]?.headers, 17 | data: args[1]?.body, 18 | responseType: 'text', 19 | responseEncoding: 'utf8', 20 | validateStatus: (s: number) => true 21 | }); 22 | return { 23 | ok: ((rawRes.status > 199)&&(rawRes.status < 300)), 24 | status: rawRes.status, 25 | async text() { 26 | if (typeof(rawRes.data) != 'string') throw new axios.AxiosError("Axios Response not utf8 str"); 27 | return rawRes.data; 28 | }, 29 | async json() { 30 | if (typeof(rawRes.data) != 'string') throw new axios.AxiosError("Axios Response not utf8 str"); 31 | return JSON.parse(rawRes.data); 32 | } 33 | }; 34 | } 35 | }); 36 | 37 | const res = await client.execute("select * from contacts;"); 38 | console.log(res); 39 | })(); -------------------------------------------------------------------------------- /src/errors.ts: -------------------------------------------------------------------------------- 1 | 2 | import { LibsqlError } from "./commons.js"; 3 | export { LibsqlError }; 4 | 5 | /** Error thrown when the server violates the protocol. */ 6 | export class ProtoError extends LibsqlError { 7 | constructor(message: string) { 8 | super(message, "HRANA_PROTO_ERROR"); 9 | this.name = "ProtoError"; 10 | } 11 | } 12 | 13 | /** Error thrown when the server returns an error response. */ 14 | export class ResponseError extends LibsqlError { 15 | constructor(message: string, code?: string|null) { 16 | super(message, code ?? "UNKNOWN"); 17 | this.name = "ResponseError"; 18 | this.stack = undefined; 19 | } 20 | } 21 | 22 | /** Error thrown when the HTTP server returns an error response. */ 23 | export class HttpServerError extends LibsqlError { 24 | http_status_code: number; 25 | constructor(status: number, message?: string|null) { 26 | super(`HTTP code ${status}: ${message ?? "No error message from server."}`, "SERVER_ERROR"); 27 | this.http_status_code = status; 28 | this.name = "HttpServerError"; 29 | } 30 | } 31 | 32 | /** Error thrown when an internal client error happens. */ 33 | // export class InternalError extends LibsqlError { 34 | // constructor(message: string) { 35 | // super(message, "INTERNAL_ERROR", new class extends Error { 36 | // /** @private */ 37 | // constructor() { 38 | // super(message); 39 | // this.name = "InternalError"; 40 | // } 41 | // }()); 42 | // } 43 | // } 44 | 45 | /** Error thrown when the API is misused. */ 46 | export class MisuseError extends LibsqlError { 47 | constructor(message: string) { 48 | super(message, "UNKNOWN"); 49 | this.name = "MisuseError"; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "libsql-stateless-easy", 3 | "version": "1.8.0", 4 | "description": "thin libSQL stateless http driver for TypeScript and JavaScript but easy", 5 | "homepage": "https://github.com/DaBigBlob/libsql-stateless-easy#readme", 6 | "repository": { 7 | "type": "git", 8 | "url": "git+https://github.com/DaBigBlob/libsql-stateless-easy.git" 9 | }, 10 | "bugs": { 11 | "url": "https://github.com/DaBigBlob/libsql-stateless-easy/issues", 12 | "email": "libsqlstateless@hman.io" 13 | }, 14 | "author": { 15 | "name": "LocalBox Crox", 16 | "email": "libsqlstateless@hman.io" 17 | }, 18 | "license": "MIT", 19 | "type": "module", 20 | "main": "./dist/main.js", 21 | "types": "./dist/main.d.ts", 22 | "files": [ 23 | "./dist/*", 24 | "./LICENSE", 25 | "./package.json", 26 | "./README.md" 27 | ], 28 | "exports": { 29 | ".": { 30 | "types": "./dist/main.d.ts", 31 | "import": "./dist/main.js", 32 | "require": "./dist/main.cjs" 33 | } 34 | }, 35 | "devDependencies": { 36 | "js-base64": "^3.7.7", 37 | "tsup": "^8.0.2", 38 | "typescript": "^5.0.0" 39 | }, 40 | "scripts": { 41 | "prepublishOnly": "npm run build", 42 | "prebuild": "rm -rf ./dist", 43 | "build": "tsup && rm ./dist/main.d.cts", 44 | "typecheck": "tsc --noEmit", 45 | "test": "./js_test _tests/test4.ts", 46 | "perf": "bun run _tests/perf.ts", 47 | "clean": "npm run prebuild", 48 | "prod": "npm publish && npm run clean" 49 | }, 50 | "keywords": [ 51 | "libsql", 52 | "database", 53 | "sqlite", 54 | "serverless", 55 | "vercel", 56 | "netlify", 57 | "lambda", 58 | "http", 59 | "https", 60 | "webapi", 61 | "cloudflare-workers", 62 | "cloudflare-pages", 63 | "edge" 64 | ], 65 | "dependencies": { 66 | "libsql-stateless": "2.9.1" 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /_tests/test2.ts: -------------------------------------------------------------------------------- 1 | import { libsqlExecute, libsqlBatch, libsqlServerCompatCheck} from "libsql-stateless"; 2 | import { libsqlStatementBuilder, libsqlBatchStreamResParser, libsqlStatementResParser } from "../src/main"; 3 | import { skjdgfksg } from "./conf"; 4 | 5 | (async () => { 6 | const conf = skjdgfksg; 7 | 8 | const res = await libsqlBatch(conf, [ 9 | { 10 | stmt: libsqlStatementBuilder({ 11 | sql: "select * from contacts where contact_id = ?;", 12 | args: [3] 13 | }), 14 | // condition: BatchReqStepExecCondBuilder({ 15 | // type: "and", 16 | // conds: [ 17 | // BatchReqStepExecCondBuilder({type: "ok", step: 1}), 18 | // BatchReqStepExecCondBuilder({type: "ok", step: 2}) 19 | // ] 20 | // }) 21 | }, 22 | {stmt: libsqlStatementBuilder({ 23 | sql: "select first_name, last_name, email from contacts where contact_id = @koji;", 24 | args: {koji: 2} 25 | })}, 26 | {stmt: libsqlStatementBuilder({ 27 | sql: "insert into contacts (contact_id,first_name,last_name,email,phone) values (?,?,?,?,?);", 28 | args: [6,"glomm","feru","moca@doro.co","001"] 29 | })}, 30 | {stmt: libsqlStatementBuilder({ 31 | sql: "delete from contacts where contact_id = :kkl", 32 | args: {kkl: 6} 33 | })} 34 | ]); 35 | if (res.isOk) console.log(libsqlBatchStreamResParser(res.val)); 36 | else console.error(res.err); 37 | 38 | const res2 = await libsqlExecute(conf, libsqlStatementBuilder({ 39 | sql: "select first_name, last_name, email, contact_id from contacts where contact_id = ?;", 40 | args: [1] 41 | })); 42 | if (res2.isOk) console.log(libsqlStatementResParser(res2.val)); 43 | else console.error(res2.err); 44 | 45 | const res3 = await libsqlServerCompatCheck(conf); 46 | if (res3.isOk) console.log("Server Compat Check OK"); 47 | else console.error("Server Compat Check NOT OK"); 48 | })(); -------------------------------------------------------------------------------- /src/commons.ts: -------------------------------------------------------------------------------- 1 | 2 | import type { libsqlFetchLike } from "libsql-stateless"; 3 | import { 4 | type Client as clientInterface, 5 | type ResultSet as resultSetInterface, 6 | type IntMode as intMode, 7 | type Config as configInterface, 8 | type Row, 9 | type InValue as rawValue, 10 | type TransactionMode, 11 | type InArgs as rawSQLArgs, 12 | LibsqlError 13 | } from './official_api_1726336437314.js'; 14 | 15 | export type rawSQL = string; 16 | export type rawSQLStatement = { 17 | sql: rawSQL, 18 | args: rawSQLArgs, 19 | want_rows?: boolean 20 | } 21 | 22 | export interface ResultSet extends resultSetInterface { 23 | /** Rows read during processing query. 24 | * 25 | * Might not be available on older server versions. 26 | */ 27 | rowsRead: number, 28 | 29 | /** Rows written during processing query. 30 | * 31 | * Might not be available on older server versions. 32 | */ 33 | rowsWritten: number, 34 | 35 | /** Wall time of work done by server. 36 | * 37 | * Might not be available on older server versions. 38 | */ 39 | queryDurationMS: number 40 | } 41 | 42 | export interface libsqlConfig extends configInterface { 43 | /** Disables performing some critical checks to make sure the library works well. 44 | * 45 | * By default, these checks are enabled. Set to true to disable. 46 | * 47 | * These includes: 48 | * - Checking the Database URL is valid (appropriate protocol, etc) 49 | * - Checking if global fetch is available and functioning properly. 50 | * - Checking if the LibSQL server supports this client. 51 | * 52 | * IF YOU ARE SURE ALL OF THESE ARE CORRECT, PLEASE DISABLE THESE CHECKS BY SETTING TO TRUE. 53 | * 54 | */ 55 | disableCriticalChecks?: boolean; 56 | 57 | /** Custom `fetch` function to use for the HTTP client. 58 | * 59 | * By default, the HTTP client uses `globalThis.fetch` but you can pass 60 | * your own function here. Check https://github.com/DaBigBlob/libsql-stateless-easy/#custom-fetch 61 | */ 62 | fetch?: libsqlFetchLike; 63 | } 64 | 65 | export { 66 | type libsqlFetchLike, 67 | type clientInterface, 68 | type intMode, 69 | type Row, 70 | type rawValue, 71 | type TransactionMode, 72 | LibsqlError, 73 | type rawSQLArgs 74 | }; -------------------------------------------------------------------------------- /_tests/test4.ts: -------------------------------------------------------------------------------- 1 | import { createClient, libsqlFetchLike } from "../src/main"; 2 | import { skjdgfksg } from "./conf"; 3 | 4 | (async () => { 5 | let client = createClient({url: skjdgfksg.db_url, authToken: skjdgfksg.authToken}); 6 | 7 | const res = await client.batch([ 8 | { 9 | sql: "select * from contacts where contact_id = ?;", 10 | args: [3] 11 | }, 12 | "select first_name, last_name, email from contacts where contact_id = 2", 13 | { 14 | sql: "insert into contacts (contact_id,first_name,last_name,email,phone) values (?,?,?,?,?);", 15 | args: [6,"glomm","feru","moca@doro.co","001"] 16 | }, 17 | { 18 | sql: "delete from contacts where contact_id = :kkl", 19 | args: {kkl: 6} 20 | } 21 | ]); 22 | console.log(res); 23 | 24 | const res2 = await client.execute({ 25 | sql: "select first_name, last_name, email, contact_id from contacts where contact_id = ?;", 26 | args: [1] 27 | }); 28 | console.log(res2); 29 | 30 | const res3 = await client.execute( 31 | "select first_name, last_name, email, contact_id from contacts where contact_id = ?;", 32 | [1] 33 | ); 34 | console.log(res3); 35 | 36 | client = client.mutateClone({ 37 | fetch: async (...args: Parameters): ReturnType => { 38 | console.log(`[${args[1]?.method} ${args[0]}]: ${args[1]?.body}`); 39 | return await globalThis.fetch(...args); 40 | } 41 | }); 42 | 43 | const res4 = await client.execute( 44 | "select first_name, last_name, email, contact_id from contacts where contact_id = :aae;", 45 | {aae: 1} 46 | ); 47 | console.log(res4); 48 | 49 | const res5 = await client.execute("select first_name, last_name, email, contact_id from contacts where contact_id = 1"); 50 | console.log(res5); 51 | 52 | const ress = await client.executeMultiple( 53 | `insert into contacts (contact_id,first_name,last_name,email,phone) values (6,"glomm","feru","moca@doro.co","001"); delete from contacts where contact_id = 6` 54 | ); 55 | console.log(ress === undefined); 56 | 57 | // const res3 = await client.serverOk(); 58 | // if (res3) console.log("Server Compat Check OK"); 59 | // else console.error("Server Compat Check NOT OK"); 60 | })(); -------------------------------------------------------------------------------- /src/globcon/mod.ts: -------------------------------------------------------------------------------- 1 | import { LibsqlError } from "../errors.js"; 2 | import { libsqlServerCompatCheck } from "../functions.js"; 3 | import type { libsqlConfig } from "../commons.js"; 4 | import { _hasConsoleError, _hasURL, _newURL, _useConsoleError } from "./utils.js"; 5 | 6 | export function ensure_fetch(conf: libsqlConfig) { 7 | try { 8 | const res = libsqlServerCompatCheck(conf); 9 | if (!res) throw new LibsqlError("Server incompatible. Please upgrade your libSQL server.", "OUT_OF_DATE_SERVER"); 10 | } catch { 11 | throw new LibsqlError("The fetch function is non functional.", "FETCH_FUCKED"); 12 | } 13 | } 14 | 15 | export function conserror(str: string) { 16 | if (_hasConsoleError) _useConsoleError(str); 17 | } 18 | 19 | export function checkHttpUrl(url: string) { 20 | const is_bad: boolean = (() => { 21 | if (_hasURL) { 22 | try { 23 | const _url = _newURL(url); 24 | if ( 25 | _url.protocol === 'https:' || 26 | _url.protocol === 'http:' 27 | ) return false; 28 | return true; 29 | } catch (e) { 30 | throw new LibsqlError((e as Error).message, "ERR_INVALID_URL", undefined, (e as Error)); 31 | } 32 | } else if ( 33 | url.startsWith('https://') || 34 | url.startsWith('http://') 35 | ) return false; 36 | return true; 37 | })(); 38 | 39 | if (is_bad) throw new LibsqlError( 40 | 'This is a HTTP client and only supports "https:" and "http:" URLs. For modern libsql DBs simply changing "libsql://" to "https://" should resolve this.', 41 | "URL_SCHEME_NOT_SUPPORTED", 42 | ); 43 | } 44 | 45 | export function checkRedundantConfig(conf: libsqlConfig) { 46 | if (conf.encryptionKey !== undefined) conserror("'encryptionKey' config unsupported."); 47 | if (conf.syncUrl !== undefined) conserror("'syncUrl' config unsupported because 'url' is the remote url. (embedded replicas unsupported)"); 48 | if (conf.syncInterval !== undefined) conserror("'syncInterval' config unsupported because nothing to sync. (embedded replicas unsupported)"); 49 | if (conf.tls !== undefined) conserror("'tls' config unsupported. Change url scheme to 'http' for no tls and 'https' for tls."); 50 | if (conf.concurrency !== undefined) conserror("'concurrency' config unsupported. You may use a custom fetch to specify concurrency."); 51 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Based on https://raw.githubusercontent.com/github/gitignore/main/Node.gitignore 2 | 3 | # Logs 4 | 5 | logs 6 | _.log 7 | npm-debug.log_ 8 | yarn-debug.log* 9 | yarn-error.log* 10 | lerna-debug.log* 11 | .pnpm-debug.log* 12 | 13 | # Diagnostic reports (https://nodejs.org/api/report.html) 14 | 15 | report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json 16 | 17 | # Runtime data 18 | 19 | pids 20 | _.pid 21 | _.seed 22 | \*.pid.lock 23 | 24 | # Directory for instrumented libs generated by jscoverage/JSCover 25 | 26 | lib-cov 27 | 28 | # Coverage directory used by tools like istanbul 29 | 30 | coverage 31 | \*.lcov 32 | 33 | # nyc test coverage 34 | 35 | .nyc_output 36 | 37 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 38 | 39 | .grunt 40 | 41 | # Bower dependency directory (https://bower.io/) 42 | 43 | bower_components 44 | 45 | # node-waf configuration 46 | 47 | .lock-wscript 48 | 49 | # Compiled binary addons (https://nodejs.org/api/addons.html) 50 | 51 | build/Release 52 | 53 | # Dependency directories 54 | 55 | node_modules/ 56 | jspm_packages/ 57 | 58 | # Snowpack dependency directory (https://snowpack.dev/) 59 | 60 | web_modules/ 61 | 62 | # TypeScript cache 63 | 64 | \*.tsbuildinfo 65 | 66 | # Optional npm cache directory 67 | 68 | .npm 69 | 70 | # Optional eslint cache 71 | 72 | .eslintcache 73 | 74 | # Optional stylelint cache 75 | 76 | .stylelintcache 77 | 78 | # Microbundle cache 79 | 80 | .rpt2_cache/ 81 | .rts2_cache_cjs/ 82 | .rts2_cache_es/ 83 | .rts2_cache_umd/ 84 | 85 | # Optional REPL history 86 | 87 | .node_repl_history 88 | 89 | # Output of 'npm pack' 90 | 91 | \*.tgz 92 | 93 | # Yarn Integrity file 94 | 95 | .yarn-integrity 96 | 97 | # dotenv environment variable files 98 | 99 | .env 100 | .env.development.local 101 | .env.test.local 102 | .env.production.local 103 | .env.local 104 | 105 | # parcel-bundler cache (https://parceljs.org/) 106 | 107 | .cache 108 | .parcel-cache 109 | 110 | # Next.js build output 111 | 112 | .next 113 | out 114 | 115 | # Nuxt.js build / generate output 116 | 117 | .nuxt 118 | dist 119 | 120 | # Gatsby files 121 | 122 | .cache/ 123 | 124 | # Comment in the public line in if your project uses Gatsby and not Next.js 125 | 126 | # https://nextjs.org/blog/next-9-1#public-directory-support 127 | 128 | # public 129 | 130 | # vuepress build output 131 | 132 | .vuepress/dist 133 | 134 | # vuepress v2.x temp and cache directory 135 | 136 | .temp 137 | .cache 138 | 139 | # Docusaurus cache and generated files 140 | 141 | .docusaurus 142 | 143 | # Serverless directories 144 | 145 | .serverless/ 146 | 147 | # FuseBox cache 148 | 149 | .fusebox/ 150 | 151 | # DynamoDB Local files 152 | 153 | .dynamodb/ 154 | 155 | # TernJS port file 156 | 157 | .tern-port 158 | 159 | # Stores VSCode versions used for testing VSCode extensions 160 | 161 | .vscode-test 162 | 163 | # yarn v2 164 | 165 | .yarn/cache 166 | .yarn/unplugged 167 | .yarn/build-state.yml 168 | .yarn/install-state.gz 169 | .pnp.\* 170 | 171 | # IntelliJ based IDEs 172 | .idea 173 | 174 | # Finder (MacOS) folder config 175 | .DS_Store 176 | 177 | # Project specific 178 | */dist/ 179 | _tests/conf.ts 180 | *.lockb 181 | 182 | package-lock.json -------------------------------------------------------------------------------- /src/functions.ts: -------------------------------------------------------------------------------- 1 | import { 2 | libsqlExecute as LIBlibsqlExecute, 3 | libsqlBatch as LIBlibsqlBatch, 4 | libsqlServerCompatCheck as LIBlibsqlServerCompatCheck, 5 | type libsqlBatchReqStepExecCond, 6 | type libsqlBatchReqStep, 7 | type libsqlError as LIBlibsqlError 8 | } from "libsql-stateless"; 9 | import type { ResultSet, TransactionMode, rawSQLStatement, libsqlConfig, rawSQLArgs, rawSQL } from "./commons.js"; 10 | import { libsqlBatchReqStepExecCondBuilder, libsqlBatchReqStepsBuilder, libsqlStatementBuilder, libsqlTransactionBatchReqStepsBuilder } from "./builders.js"; 11 | import { libsqlBatchStreamResParser, libsqlStatementResParser, libsqlTransactionBatchStreamResParser } from "./parsers.js"; 12 | import { HttpServerError, ResponseError } from "./errors.js"; 13 | 14 | function errorTranslate(err: LIBlibsqlError) { 15 | if (err.kind==="LIBSQL_SERVER_ERROR") 16 | return new HttpServerError( 17 | err.http_status_code, 18 | err.server_message 19 | ); 20 | else 21 | return new ResponseError( 22 | err.data.message, 23 | err.data.code 24 | ); 25 | } 26 | 27 | export async function libsqlExecute(conf: libsqlConfig, stmt_or_sql: rawSQL|rawSQLStatement, or_args?: rawSQLArgs, or_want_rows?: boolean): Promise { 28 | const res = await LIBlibsqlExecute(conf, libsqlStatementBuilder(stmt_or_sql, or_args, or_want_rows)); 29 | 30 | if (res.isOk) return libsqlStatementResParser(res.val, conf.intMode); 31 | else throw errorTranslate(res.err); 32 | } 33 | 34 | export async function libsqlBatchWithoutTransaction( 35 | conf: libsqlConfig, 36 | steps: Array, 37 | step_conditions: Array 38 | ): Promise> { 39 | const res = await LIBlibsqlBatch(conf, libsqlBatchReqStepsBuilder(steps, step_conditions)); 40 | 41 | if (res.isOk) return libsqlBatchStreamResParser(res.val, conf.intMode); 42 | else throw errorTranslate(res.err); 43 | } 44 | 45 | export async function libsqlServerCompatCheck(conf: libsqlConfig) { 46 | return (await LIBlibsqlServerCompatCheck(conf)).isOk; 47 | } 48 | 49 | export async function libsqlBatch( 50 | conf: libsqlConfig, 51 | steps: Array, 52 | mode: TransactionMode="deferred" 53 | ): Promise> { 54 | const res = await LIBlibsqlBatch(conf, libsqlTransactionBatchReqStepsBuilder(steps, mode)); 55 | 56 | if (res.isOk) return libsqlTransactionBatchStreamResParser(res.val, conf.intMode); 57 | else throw errorTranslate(res.err); 58 | } 59 | 60 | export async function libsqlMigrate( 61 | conf: libsqlConfig, 62 | stmts: Array 63 | ): Promise> { 64 | const res = await LIBlibsqlBatch( 65 | conf, 66 | [{stmt: {sql: "PRAGMA foreign_keys=off"}} as libsqlBatchReqStep] 67 | .concat(libsqlTransactionBatchReqStepsBuilder(stmts, "deferred", 1)) // offset: 1 68 | .concat([{stmt: {sql: "PRAGMA foreign_keys=on"}}]) 69 | ); 70 | 71 | if (res.isOk) return libsqlTransactionBatchStreamResParser(res.val, conf.intMode); 72 | else throw errorTranslate(res.err); 73 | } 74 | 75 | export async function libsqlExecuteMultiple(conf: libsqlConfig, sql: string): Promise { 76 | const sqlArr: Array = sql.split(";").filter(s => s.trim()!=="").map((s, i) => {return { 77 | stmt: {sql: s}, 78 | condition: libsqlBatchReqStepExecCondBuilder.ok(i-1) 79 | }}); 80 | sqlArr[0].condition = undefined; //elm 0's ok index is set to -1; removing that 81 | 82 | const res = await LIBlibsqlBatch(conf, sqlArr); 83 | if (!res.isOk) throw errorTranslate(res.err); 84 | } 85 | -------------------------------------------------------------------------------- /src/parsers.ts: -------------------------------------------------------------------------------- 1 | import type { libsqlBatchStreamResOkData, libsqlSQLValue, libsqlStatementResOkData } from "libsql-stateless"; 2 | import type { ResultSet, Row, rawValue, intMode } from "./commons.js"; 3 | import { toUint8Array } from './base64/mod.js'; 4 | import { MisuseError, ProtoError, ResponseError } from "./errors.js"; 5 | 6 | //======================================================== 7 | // function parseLibsqlInt(number: string, intMode: intMode = "number") { 8 | // switch (intMode) { 9 | // case ("number"): return (+number); 10 | // case ("string"): return number; 11 | // case ("bigint"): return BigInt(number); 12 | // default: throw new MisuseError(`Invalid value for "intMode".`); 13 | // } 14 | // } 15 | 16 | //======================================================== 17 | export function libsqlValueParser(value: libsqlSQLValue, intMode: intMode = "number"): rawValue { 18 | switch (value.type) { 19 | case ("null"): return null; 20 | case ("integer"): { 21 | switch (intMode) { 22 | case ("number"): { 23 | const num = Number(value.value); 24 | if (!Number.isSafeInteger(num)) throw new RangeError("Received integer which is too large to be safely represented as a JavaScript number"); 25 | return num; 26 | } 27 | case ("bigint"): return BigInt(value.value); 28 | case ("string"): return value.value; 29 | default: throw new MisuseError(`Invalid value for "intMode".`); 30 | } 31 | } 32 | case ("float"): return value.value; 33 | case ("text"): return value.value; 34 | case ("blob"): return toUint8Array(value.base64).slice().buffer; 35 | default: throw new ProtoError("Invalid data type from server. Cannot parse."); 36 | } 37 | } 38 | 39 | //======================================================== 40 | //from hrana-client-ts/src/result.ts 41 | // function rowFromRawValue( 42 | // colNames: Array, 43 | // values: Array 44 | // ): Row { 45 | // const row = {}; 46 | // // make sure that the "length" property is not enumerable 47 | // Object.defineProperty(row, "length", { value: values.length }); 48 | // for (let i = 0; i < values.length; ++i) { 49 | // const value = values[i]; 50 | // Object.defineProperty(row, i, { value }); 51 | 52 | // const colName = colNames[i]; 53 | // if (colName !== undefined && !Object.hasOwn(row, colName)) { 54 | // Object.defineProperty(row, colName, { value, enumerable: true }); 55 | // } 56 | // } 57 | // return row as Row; 58 | // } 59 | 60 | //======================================================== 61 | export function libsqlStatementResParser( 62 | res: libsqlStatementResOkData, 63 | intMode?: intMode 64 | ): ResultSet { 65 | let Rows: Array = []; 66 | 67 | for (let i=0;i = { 84 | rows: Rows, 85 | columns: res.cols.map(c => c.name??""), 86 | columnTypes: res.cols.map(c => c.decltype??""), 87 | rowsAffected: res.affected_row_count, 88 | lastInsertRowid: (res.last_insert_rowid) ? BigInt(res.last_insert_rowid) : undefined, 89 | rowsRead: res.rows_read, 90 | rowsWritten: res.rows_written, 91 | queryDurationMS: res.query_duration_ms 92 | } 93 | return { 94 | ...resultset, 95 | toJSON: (): any => {return resultset} 96 | } 97 | } 98 | 99 | //======================================================== 100 | export function libsqlBatchStreamResParser( 101 | res: libsqlBatchStreamResOkData, 102 | intMode?: intMode 103 | ): Array { 104 | return res.step_results.map((r, i) => { 105 | if (r) return libsqlStatementResParser(r, intMode); 106 | else if (res.step_errors[i]) throw new ResponseError(res.step_errors[i]?.message!, res.step_errors[i]?.code); 107 | else return null; 108 | }); 109 | } 110 | 111 | //======================================================== 112 | export function libsqlTransactionBatchStreamResParser( 113 | res: libsqlBatchStreamResOkData, 114 | intMode?: intMode 115 | ): Array { 116 | const resResArr = libsqlBatchStreamResParser(res, intMode); 117 | return resResArr.slice(1, resResArr.length-2).filter(r => r!==null) as Array; 118 | } 119 | -------------------------------------------------------------------------------- /src/base64/mod.ts: -------------------------------------------------------------------------------- 1 | import { LibsqlError } from '../errors.js'; 2 | import { _hasBuffer, _useBufferU8a, _useBufferBin, _useBufferStr, _hasBtoa, _hasAtob, _useAtob, _useBufferAsc, _useBtoa } from './utils.js'; 3 | 4 | const b64ch = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/='; 5 | const b64chs = Array.prototype.slice.call(b64ch); 6 | const b64tab = ((a) => { 7 | let tab: any = {}; 8 | a.forEach((c, i) => tab[c] = i); 9 | return tab; 10 | })(b64chs); 11 | const b64re = /^(?:[A-Za-z\d+\/]{4})*?(?:[A-Za-z\d+\/]{2}(?:==)?|[A-Za-z\d+\/]{3}=?)?$/; 12 | 13 | const btoaPolyfill = (bin: string) => { 14 | let u32, c0, c1, c2, asc = ''; 15 | const pad = bin.length % 3; 16 | for (let i = 0; i < bin.length;) { 17 | if ((c0 = bin.charCodeAt(i++)) > 255 || 18 | (c1 = bin.charCodeAt(i++)) > 255 || 19 | (c2 = bin.charCodeAt(i++)) > 255) 20 | throw new LibsqlError('Invalid character found while polyfilling btoa', "BTOA_POLYFILL_INVALID_CHAR"); 21 | u32 = (c0 << 16) | (c1 << 8) | c2; 22 | asc += b64chs[u32 >> 18 & 63] 23 | + b64chs[u32 >> 12 & 63] 24 | + b64chs[u32 >> 6 & 63] 25 | + b64chs[u32 & 63]; 26 | } 27 | return pad ? asc.slice(0, pad - 3) + "===".substring(pad) : asc; 28 | }; 29 | 30 | const _btoa = _hasBtoa 31 | ? (bin: string) => _useBtoa(bin) 32 | : _hasBuffer 33 | ? (bin: string) => _useBufferBin(bin) 34 | : btoaPolyfill 35 | ; 36 | 37 | const _fromCC = String.fromCharCode.bind(String); 38 | 39 | export const fromUint8Array = _hasBuffer 40 | ? (u8a: Uint8Array) => _useBufferU8a(u8a) as string 41 | : (u8a: Uint8Array) => { 42 | // cf. https://stackoverflow.com/questions/12710001/how-to-convert-uint8-array-to-base64-encoded-string/12713326#12713326 43 | const maxargs = 0x1000; 44 | let strs: string[] = []; 45 | for (let i = 0, l = u8a.length; i < l; i += maxargs) { 46 | let _numarr: number[] = []; 47 | u8a.subarray(i, i + maxargs).forEach(u => _numarr.push(u.valueOf())); 48 | strs.push(_fromCC.apply(null, 49 | _numarr 50 | )); 51 | } 52 | return _btoa(strs.join('')); 53 | } 54 | ; 55 | 56 | // urlsafe is never used in this library 57 | // const _mkUriSafe = (src: string) => src.replace(/=/g, '').replace(/[+\/]/g, (m0) => m0 == '+' ? '-' : '_'); 58 | // export const fromUint8Array = (u8a: Uint8Array, urlsafe: boolean = false): string => urlsafe 59 | // ? _mkUriSafe(_fromUint8Array(u8a)) 60 | // : _fromUint8Array(u8a) 61 | // ; 62 | 63 | const _tidyB64 = (s: string) => s.replace(/[^A-Za-z0-9\+\/]/g, ''); 64 | 65 | const atobPolyfill = (asc: string) => { 66 | asc = asc.replace(/\s+/g, ''); 67 | if (!b64re.test(asc)) throw new LibsqlError("Malformed base64 while polyfilling atob", "MALFORMED_BASE64_FOR_ATOB"); 68 | asc += '=='.slice(2 - (asc.length & 3)); 69 | let u24, bin = '', r1, r2; 70 | for (let i = 0; i < asc.length;) { 71 | u24 = b64tab[asc.charAt(i++)] << 18 72 | | b64tab[asc.charAt(i++)] << 12 73 | | (r1 = b64tab[asc.charAt(i++)]) << 6 74 | | (r2 = b64tab[asc.charAt(i++)]); 75 | bin += r1 === 64 ? _fromCC(u24 >> 16 & 255) 76 | : r2 === 64 ? _fromCC(u24 >> 16 & 255, u24 >> 8 & 255) 77 | : _fromCC(u24 >> 16 & 255, u24 >> 8 & 255, u24 & 255); 78 | } 79 | return bin; 80 | }; 81 | 82 | const _atob = _hasAtob 83 | ? (asc: string) => _useAtob(_tidyB64(asc)) 84 | : _hasBuffer 85 | ? (asc: string) => _useBufferAsc(asc) 86 | : atobPolyfill 87 | ; 88 | 89 | const _U8Afrom = typeof Uint8Array.from === 'function' 90 | ? Uint8Array.from.bind(Uint8Array) 91 | : (it: any) => new Uint8Array(Array.prototype.slice.call(it, 0)); 92 | 93 | const _toUint8Array = _hasBuffer 94 | ? (a: string) => _U8Afrom(_useBufferStr(a)) 95 | : (a: string) => _U8Afrom(_atob(a).split('').map((c: string) => c.charCodeAt(0))) 96 | ; 97 | 98 | const _unURI = (a: string) => _tidyB64(a.replace(/[-_]/g, (m0) => m0 == '-' ? '+' : '/')); 99 | 100 | export const toUint8Array = (a: string): Uint8Array => _toUint8Array(_unURI(a)); 101 | 102 | /** 103 | * To prevent supply chain attack and better control, acquired from https://github.com/dankogai/js-base64 104 | * at 1713503973017 unix epoch miliseconds. 105 | * 106 | * == LICENSE BEGIN == 107 | * Copyright (c) 2014, Dan Kogai 108 | * All rights reserved. 109 | * 110 | * Redistribution and use in source and binary forms, with or without 111 | * modification, are permitted provided that the following conditions are met: 112 | * 113 | * * Redistributions of source code must retain the above copyright notice, this 114 | * list of conditions and the following disclaimer. 115 | * 116 | * * Redistributions in binary form must reproduce the above copyright notice, 117 | * this list of conditions and the following disclaimer in the documentation 118 | * and/or other materials provided with the distribution. 119 | * 120 | * * Neither the name of js-base64 nor the names of its 121 | * contributors may be used to endorse or promote products derived from 122 | * this software without specific prior written permission. 123 | * 124 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 125 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 126 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 127 | * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 128 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 129 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 130 | * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 131 | * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 132 | * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 133 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 134 | * == LICENSE END == 135 | */ -------------------------------------------------------------------------------- /src/client.ts: -------------------------------------------------------------------------------- 1 | import type { TransactionMode, rawSQLStatement, libsqlConfig, rawSQLArgs, rawSQL, ResultSet, clientInterface } from "./commons.js"; 2 | import { libsqlBatchWithoutTransaction, libsqlBatch, libsqlExecute, libsqlExecuteMultiple, libsqlMigrate } from "./functions.js"; 3 | import { MisuseError } from "./errors.js"; 4 | import { checkHttpUrl, checkRedundantConfig, conserror, ensure_fetch } from "./globcon/mod.js"; 5 | import type { libsqlBatchReqStepExecCond } from "libsql-stateless"; 6 | 7 | export function createClient(conf: libsqlConfig) { 8 | return new libsqlClient(conf); 9 | } 10 | 11 | 12 | export class libsqlClient implements clientInterface { 13 | private readonly conf: libsqlConfig; 14 | public closed: boolean; 15 | public protocol: string; 16 | 17 | constructor(conf: libsqlConfig) { 18 | if (!conf.disableCriticalChecks) { 19 | checkHttpUrl(conf.url); 20 | ensure_fetch(conf); 21 | checkRedundantConfig(conf); 22 | } 23 | 24 | this.conf = conf; 25 | this.closed = false; 26 | this.protocol = "http"; 27 | } 28 | 29 | /** Mutate the configuration of the current client and return a new client with that config. 30 | * 31 | * libsql-stateless-easy makes this zero cost because the client is very thin. 32 | */ 33 | public mutateClone(mutConf: Partial = {}): libsqlClient { 34 | let thisConf = this.conf; 35 | for (const key in mutConf) { 36 | // @ts-ignore 37 | thisConf[key] = mutConf[key]; 38 | } 39 | return new libsqlClient(thisConf); 40 | } 41 | 42 | public async execute(stmt: rawSQL, args?: rawSQLArgs, want_rows?: boolean): Promise; 43 | public async execute(stmt: rawSQLStatement): Promise; 44 | public async execute(stmt_or_sql: rawSQL|rawSQLStatement, or_args?: rawSQLArgs, or_want_rows?: boolean) { 45 | return await libsqlExecute(this.conf, stmt_or_sql, or_args, or_want_rows); 46 | } 47 | 48 | /** Execute a batch of SQL statements in a transaction. 49 | * 50 | * NOTE: For batch SQL statement execution without transaction, use {@link batchWithoutTransaction}. 51 | * 52 | * The batch is executed in its own logical database connection and the statements are wrapped in a 53 | * transaction. This ensures that the batch is applied atomically: either all or no changes are applied. 54 | * 55 | * The `mode` parameter selects the transaction mode for the batch; please see {@link TransactionMode} for 56 | * details. The default transaction mode is `"deferred"`. 57 | * 58 | * If any of the statements in the batch fails with an error, the batch is aborted, the transaction is 59 | * rolled back and the returned promise is rejected. 60 | * 61 | * This method provides non-interactive transactions. 62 | * 63 | * ```javascript 64 | * const rss = await client.batch([ 65 | * // batch statement without arguments 66 | * "DELETE FROM books WHERE name LIKE '%Crusoe'", 67 | * 68 | * // batch statement with positional arguments 69 | * { 70 | * sql: "INSERT INTO books (name, author, published_at) VALUES (?, ?, ?)", 71 | * args: ["First Impressions", "Jane Austen", 1813], 72 | * }, 73 | * 74 | * // batch statement with named arguments 75 | * { 76 | * sql: "UPDATE books SET name = $new WHERE name = $old", 77 | * args: {old: "First Impressions", new: "Pride and Prejudice"}, 78 | * }, 79 | * ], "write"); 80 | * ``` 81 | */ 82 | public async batch( 83 | steps: Array, 84 | mode?: TransactionMode 85 | ) { 86 | return await libsqlBatch(this.conf, steps, mode); 87 | } 88 | 89 | /** Execute a batch of SQL statements. 90 | * 91 | * The same as {@link batch} but the SQL statements are not executed in a transaction. 92 | * 93 | * The `step_conditions` is an array of step conditions generated using {@link libsqlBatchReqStepExecCondBuilder}. 94 | */ 95 | public async batchWithoutTransaction( 96 | steps: Array, 97 | step_conditions: Array 98 | ) { 99 | return await libsqlBatchWithoutTransaction(this.conf, steps, step_conditions); 100 | } 101 | 102 | public async migrate(stmts: Array) { 103 | return await libsqlMigrate(this.conf, stmts); 104 | } 105 | 106 | public async transaction(_mode?: TransactionMode): Promise { 107 | throw new MisuseError("'libsql-stateless' is stateless and does not support interactive transactions. Use this.batch() instead."); 108 | } 109 | 110 | /** Execute a sequence of SQL statements separated by semicolons. 111 | * 112 | * NOTE: libsql-stateless-easy implements this function using `batch` under the hood instead of the `serial` endpoint. 113 | * 114 | * The statements are executed sequentially on a new logical database connection. If a statement fails, 115 | * further statements are not executed and this method throws an error. All results from the statements 116 | * are ignored. 117 | * 118 | * We do not wrap the statements in a transaction, but the SQL can contain explicit transaction-control 119 | * statements such as `BEGIN` and `COMMIT`. 120 | * 121 | * This method is intended to be used with existing SQL scripts, such as migrations or small database 122 | * dumps. If you want to execute a sequence of statements programmatically, please use {@link batch} 123 | * instead. 124 | * 125 | * ```javascript 126 | * await client.executeMultiple(` 127 | * CREATE TABLE books (id INTEGER PRIMARY KEY, title TEXT NOT NULL, author_id INTEGER NOT NULL); 128 | * CREATE TABLE authors (id INTEGER PRIMARY KEY, name TEXT NOT NULL); 129 | * `); 130 | * ``` 131 | */ 132 | public async executeMultiple(sql: string) { 133 | return await libsqlExecuteMultiple(this.conf, sql); 134 | } 135 | 136 | public async sync(): Promise { 137 | // throw new LibsqlError("sync not supported in http mode", "SYNC_NOT_SUPPORTED"); 138 | // don't throw error for max compatiblity 139 | conserror("'libsql-stateless' is remote only so nothing to sync."); 140 | } 141 | 142 | public close() { 143 | // throw new InternalError("'libsql-stateless' is stateless therefore no connection to close."); 144 | // don't throw error for max compatiblity 145 | conserror("'libsql-stateless' is stateless therefore no connection to close."); 146 | } 147 | } -------------------------------------------------------------------------------- /src/builders.ts: -------------------------------------------------------------------------------- 1 | import { fromUint8Array } from './base64/mod.js'; 2 | import type { TransactionMode, rawSQL, rawSQLArgs, rawSQLStatement, rawValue } from './commons.js'; 3 | import type { libsqlBatchReqStep, libsqlBatchReqStepExecCond, libsqlSQLStatement, libsqlSQLValue } from 'libsql-stateless'; 4 | import { MisuseError } from './errors.js'; 5 | 6 | //======================================================== 7 | export function libsqlValueBuilder(value: rawValue): libsqlSQLValue { 8 | if (value===null) return {type: "null"}; 9 | if (typeof(value)==="string") return {type: "text", value: value}; 10 | if (typeof(value)==="number") { 11 | if (!Number.isFinite(value)) throw new RangeError("Only finite numbers (not Infinity or NaN) can be passed as arguments"); 12 | return {type: "float", value: value}; 13 | } 14 | if (typeof(value)==="bigint") { 15 | if (value < -9223372036854775808n || value > 9223372036854775807n) 16 | throw new RangeError("This bigint value is too large to be represented as a 64-bit integer and passed as argument"); 17 | return {type: "integer", value: ""+value}; 18 | } 19 | if (typeof value === "boolean") return {type: "integer", value: ""+(value ? 1n : 0n)}; 20 | if (value instanceof ArrayBuffer) return {type: "blob", base64: fromUint8Array(new Uint8Array(value))}; 21 | if (value instanceof Uint8Array) return {type: "blob", base64: fromUint8Array(value)}; 22 | if (value instanceof Date) return {type: "float", value: +value.valueOf()}; 23 | throw new MisuseError("Invalid type of input. Cannot build request to server."); 24 | } 25 | 26 | //======================================================== 27 | export function libsqlArgumentsBuilder(a?: rawSQLArgs): Omit { 28 | if (a === undefined) return {}; 29 | 30 | if (Object.prototype.toString.call(a) === '[object Array]') { 31 | return { 32 | args: (a as Array).map(r => libsqlValueBuilder(r)), 33 | }; 34 | } else { 35 | let p_named_args: Array<{ 36 | name: string, 37 | value: libsqlSQLValue, 38 | }>=[]; 39 | const _args = a as Record; 40 | 41 | for (const key in _args) p_named_args.push({name: key, value: libsqlValueBuilder(_args[key])}); 42 | 43 | return { 44 | named_args: p_named_args, 45 | }; 46 | } 47 | } 48 | 49 | //======================================================== 50 | export function libsqlStatementBuilder(s: rawSQL|rawSQLStatement, or_a?: rawSQLArgs, or_want_rows?: boolean): libsqlSQLStatement { 51 | if (typeof(s)=="string") { // arguments as tuple 52 | const _args = libsqlArgumentsBuilder(or_a); 53 | return { 54 | sql: s, 55 | args: _args.args, 56 | named_args: _args.named_args, 57 | want_rows: or_want_rows 58 | }; 59 | } else { // arguments as object 60 | const s_args = libsqlArgumentsBuilder(s.args); 61 | return { 62 | sql: s.sql, 63 | args: s_args.args, 64 | named_args: s_args.named_args, 65 | want_rows: s.want_rows 66 | } 67 | } 68 | } 69 | 70 | //=========================================================== 71 | export const libsqlBatchReqStepExecCondBuilder = { 72 | ok: (step: number): libsqlBatchReqStepExecCond => { 73 | return { 74 | type: "ok", 75 | step //uint32: 0-based index in the steps array 76 | } 77 | }, 78 | error: (step: number): libsqlBatchReqStepExecCond => { 79 | return { 80 | type: "error", 81 | step //uint32: 0-based index in the steps array 82 | } 83 | }, 84 | not: (cond: libsqlBatchReqStepExecCond): libsqlBatchReqStepExecCond => { 85 | return { 86 | type: "not", 87 | cond, 88 | } 89 | }, 90 | and: (conds: Array): libsqlBatchReqStepExecCond => { 91 | return { 92 | type: "and", 93 | conds 94 | } 95 | }, 96 | or: (conds: Array): libsqlBatchReqStepExecCond => { 97 | return { 98 | type: "or", 99 | conds 100 | } 101 | }, 102 | is_autocommit: (): libsqlBatchReqStepExecCond => { 103 | return { 104 | type: "is_autocommit" 105 | } 106 | } 107 | } 108 | 109 | //=========================================================== 110 | export function libsqlBatchReqStepsBuilder( 111 | batch_queries: Array, 112 | batch_conditions: Array 113 | ): Array { 114 | return batch_queries.map((q, i) => {return { 115 | stmt: libsqlStatementBuilder(q), 116 | condition: batch_conditions[i]??undefined 117 | }}); 118 | } 119 | 120 | //======================================================== 121 | export function libsqlTransactionBeginStatement(mode: TransactionMode): libsqlBatchReqStep { 122 | if (mode === "write") { 123 | return {stmt: {sql: "BEGIN IMMEDIATE"}}; 124 | } else if (mode === "read") { 125 | return {stmt: {sql: "BEGIN TRANSACTION READONLY"}}; 126 | } else if (mode === "deferred") { 127 | return {stmt: {sql: "BEGIN DEFERRED"}}; 128 | } else { 129 | throw RangeError('Unknown transaction mode, supported values are "write", "read" and "deferred"'); 130 | } 131 | } 132 | 133 | //======================================================== 134 | export function libsqlTransactionEndStatements(step_before_this: number): Array { 135 | return [ 136 | {stmt: {sql: "COMMIT"}, condition: {type: "ok", step: step_before_this}}, 137 | {stmt: {sql: "ROLLBACK"}, condition: {type: "not", cond: {type: "ok", step: step_before_this+1}}} 138 | ] 139 | } 140 | 141 | //=========================================================== 142 | export function libsqlTransactionBatchReqStepsBuilder( 143 | queries: Array, 144 | mode: TransactionMode, 145 | offset: number = 0 146 | ): Array { 147 | const main_steps: Array = queries.map((q, i) => {return { 148 | stmt: libsqlStatementBuilder(q), 149 | condition: libsqlBatchReqStepExecCondBuilder.and([ 150 | libsqlBatchReqStepExecCondBuilder.ok(i+offset), // step before this 151 | libsqlBatchReqStepExecCondBuilder.not(libsqlBatchReqStepExecCondBuilder.is_autocommit()) 152 | ]) 153 | }}); 154 | return [libsqlTransactionBeginStatement(mode)] // is idx: 0 + offset 155 | .concat(main_steps) // is idx: (1..=len) + offset 156 | .concat(libsqlTransactionEndStatements(main_steps.length+offset)); // is idx: len+1 + offset 157 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # libsql-stateless-easy 2 | 3 | > Thin libSQL stateless HTTP driver for TypeScript and JavaScript for the edge but easy 🚀 4 | - ✅ **Supported runtime environments:** Web API (browser, serverless), Bun, Node.js (>=18) 5 | - ✅ **Is extremely light:** 8.3kB (unpacked)* / 3.5kB (gzipped) 6 | - ✅ **Is built for:** Quick stateless query execution. (Mainly for serverless and edge functions.) 7 | - ✅ Supports everything in `@libsql/client/web` **except `interactive transactions`. 8 | - ✅ **Is Atomic and stateless**, every function performs complete execution in exactly 1 roundtrip. 9 | - ✅ **`libsql-stateless-easy` is simply a drop-in replacement** and exactly same API as `@libsql/client/web`. 10 | 11 | \* The actual js that is included with your project. (Excluding the type definitions and 2 copies of the main js for esm and cjs. (because you're gonna use one of them)) 12 | \*\*Interactive transactions are not supported because this lib is stateless but [`transactions`](https://github.com/DaBigBlob/libsql-stateless/wiki/transactions) are supported. 13 |
14 | 15 | # Installation 16 | ```sh 17 | $ npm i libsql-stateless-easy #pnpm, yarn, etc. 18 | # or 19 | $ bun add libsql-stateless-easy 20 | ``` 21 | 22 | # Client Usage 23 | `libsql-stateless-easy`'s `client` has the exact same API as [`@libsql/client/web`](https://docs.turso.tech/sdk/ts/reference)'s `client`. 24 | ```ts 25 | import { createClient } from "libsql-stateless-easy"; 26 | //or 27 | const { createClient } = require("libsql-stateless-easy"); 28 | 29 | const client = createClient({ 30 | url: "https://da-fish-mandible.turso.io", 31 | authToken: "fksdgfgksdgfksdg.javsdKDGKSBkgsdfg289374dg" 32 | }); 33 | 34 | const res = await client.batch([ 35 | { 36 | sql: "select * from contacts where contact_id = ?;", 37 | args: [3] 38 | }, 39 | "select first_name, last_name, email from contacts where contact_id = 2", 40 | { 41 | sql: "insert into contacts (contact_id,first_name,last_name,email,phone) values (?,?,?,?,?);", 42 | args: [6,"glomm","feru","moca@doro.co","001"] 43 | }, 44 | { 45 | sql: "delete from contacts where contact_id = :kkl", 46 | args: {kkl: 6} 47 | } 48 | ]); 49 | console.log(res); 50 | 51 | const res2 = await client.execute({ 52 | sql: "select first_name, last_name, email, contact_id from contacts where contact_id = ?;", 53 | args: [1] 54 | }); 55 | console.log(res2); 56 | 57 | const res3 = await client.execute("select first_name, last_name, email, contact_id from contacts where contact_id = 1;"); 58 | console.log(res3); 59 | ``` 60 | 61 | # Drizzle (and other ORMs) 62 | **`libsql-stateless-easy`'s `client` works with drizzle (and other ORMs) out-of-the-box.** 63 | This library implements the same Interface is the official client and therefore works with all the ORMs that the official client works with. 64 | ```ts 65 | import { createClient } from "libsql-stateless-easy"; 66 | import { drizzle } from 'drizzle-orm/libsql'; 67 | 68 | const client = createClient({ 69 | url: "https://da-fish-mandible.turso.io", 70 | authToken: "fksdgfgksdgfksdg.javsdKDGKSBkgsdfg289374dg" 71 | }); 72 | 73 | const db = drizzle(client); 74 | 75 | const result = await db.select().from(table_name).all(); 76 | console.log(result); 77 | ``` 78 | 79 | - **This library has the same `LibsqlError` codes as `@libsql/client`** 80 | 81 | ## Performance 82 | This library checks your configs, environments and server compatibility by default. 83 | However this is _kinda_ resource intensive if you're creating client instances often. 84 | So, **IF YOU ARE SURE YOUR CONFIGURATION, ENVIRONMENT AND SERVER VERSION ARE CORRECT, PLEASE DISABLE THE CHECKS FOR EVEN BETTER PERFORMANCE.** 85 | ```ts 86 | const client = createClient({ 87 | url: "https://da-fish-mandible.turso.io", 88 | authToken: "fksdgfgksdgfksdg.javsdKDGKSBkgsdfg289374dg", 89 | disableCriticalChecks: true 90 | }); 91 | ``` 92 | 93 | ## Custom Fetch 94 | Pass your own implementation of fetch or fetch-like function if you don't want libsql-stateless-easy to use the global fetch or if your global fetch does not exist. 95 | ```ts 96 | import { createClient, libsqlFetchLike } from 'libsql-stateless-easy'; 97 | 98 | const client = createClient({ 99 | url: "https://da-fish-mandible.turso.io", 100 | authToken: "fksdgfgksdgfksdg.javsdKDGKSBkgsdfg289374dg", 101 | fetch: async (...args: Parameters): ReturnType => { 102 | //implement your own fetch here (look at examples/custom_fetch for concrete example) 103 | /** NOTE: 104 | * args[0] is the url string 105 | * args[1] is the request init 106 | */ 107 | } 108 | }); 109 | 110 | const res = await client.execute("select * from contacts;"); 111 | console.log(res); 112 | ``` 113 | 114 | ## Modularity 115 | `libsql-stateless-easy` is extremely modular. `execute`, `batch`, etc. can be used without initiating a `client`. 116 | ```ts 117 | import { libsqlBatchTransaction, libsqlExecute } from "libsql-stateless-easy"; 118 | //or 119 | const { libsqlBatchTransaction, libsqlExecute } = require("libsql-stateless-easy"); 120 | 121 | const config = { 122 | url: "https://da-fish-mandible.turso.io", 123 | authToken: "fksdgfgksdgfksdg.javsdKDGKSBkgsdfg289374dg" 124 | }; 125 | 126 | const res = await libsqlBatchTransaction( 127 | config, 128 | [ 129 | { 130 | sql: "select * from contacts where contact_id = ?;", 131 | args: [3] 132 | }, 133 | "select first_name, last_name, email from contacts where contact_id = 2", 134 | { 135 | sql: "insert into contacts (contact_id,first_name,last_name,email,phone) values (?,?,?,?,?);", 136 | args: [6,"glomm","feru","moca@doro.co","001"] 137 | }, 138 | { 139 | sql: "delete from contacts where contact_id = :kkl", 140 | args: {kkl: 6} 141 | } 142 | ] 143 | ); 144 | console.log(res); 145 | 146 | const res2 = await libsqlExecute( 147 | config, 148 | { 149 | sql: "select first_name, last_name, email, contact_id from contacts where contact_id = ?;", 150 | args: [1] 151 | } 152 | ); 153 | console.log(res2); 154 | 155 | const res3 = await libsqlExecute(config, "select first_name, last_name, email, contact_id from contacts where contact_id = 1;", []); 156 | console.log(res3); 157 | ``` 158 | `libsql-stateless-easy`'s `client` just conveniently packages these individually executable functions to conform to the official sdk interface. 159 | 160 | ### List of other stuff in this library 161 | Feel free to explore them (however you don't need to as they've been neatly packaged into the `client`) 162 | ```ts 163 | import { 164 | libsqlValueBuilder, libsqlArgumentsBuilder, libsqlStatementBuilder, libsqlBatchReqStepsBuilder, libsqlBatchReqStepExecCondBuilder, libsqlTransactionBeginStatement, 165 | libsqlValueParser, libsqlStatementResParser, libsqlBatchStreamResParser, libsqlTransactionBatchStreamResParser, 166 | libsqlExecute, //has easier API than `libsql-stateless`'s function of the same name 167 | libsqlBatch, //has easier API than `libsql-stateless`'s function of the same name 168 | libsqlServerCompatCheck, //has easier API than `libsql-stateless`'s function of the same name, 169 | libsqlBatchTransaction, libsqlExecuteMultiple 170 | createClient //used above 171 | } from "libsql-stateless-easy"; 172 | ``` 173 | 174 | 175 | ## API Level 176 | > NOTE: current API level is that of latest stable [libsql-stateless](https://github.com/DaBigBlob/libsql-stateless). 177 | Read [this section](https://github.com/DaBigBlob/libsql-stateless/?tab=readme-ov-file#api-level). -------------------------------------------------------------------------------- /src/official_api_1726336437314.ts: -------------------------------------------------------------------------------- 1 | // DO NOT CHANGE THIS FILE. THIS HAS BEEN AUTO GENERATED BY 'get_official_api'. 2 | // This file has been pulled from the main branch of @libsql/core/api at 1726336437314 Unix Epoch Miliseconds 3 | // to maintain 100% compatibality with the official SDK. 4 | 5 | /* LICENSE for this file 6 | MIT License 7 | 8 | Copyright (c) 2023 libSQL 9 | 10 | Permission is hereby granted, free of charge, to any person obtaining a copy 11 | of this software and associated documentation files (the "Software"), to deal 12 | in the Software without restriction, including without limitation the rights 13 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 14 | copies of the Software, and to permit persons to whom the Software is 15 | furnished to do so, subject to the following conditions: 16 | 17 | The above copyright notice and this permission notice shall be included in all 18 | copies or substantial portions of the Software. 19 | 20 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 21 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 22 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 23 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 24 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 25 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 26 | SOFTWARE. 27 | 28 | */ 29 | 30 | /** Configuration object for {@link createClient}. */ 31 | export interface Config { 32 | /** The database URL. 33 | * 34 | * The client supports `libsql:`, `http:`/`https:`, `ws:`/`wss:` and `file:` URL. For more infomation, 35 | * please refer to the project README: 36 | * 37 | * https://github.com/libsql/libsql-client-ts#supported-urls 38 | */ 39 | url: string; 40 | 41 | /** Authentication token for the database. */ 42 | authToken?: string; 43 | 44 | /** Encryption key for the database. */ 45 | encryptionKey?: string; 46 | 47 | /** URL of a remote server to synchronize database with. */ 48 | syncUrl?: string; 49 | 50 | /** Sync interval in seconds. */ 51 | syncInterval?: number; 52 | 53 | /** Enables or disables TLS for `libsql:` URLs. 54 | * 55 | * By default, `libsql:` URLs use TLS. You can set this option to `false` to disable TLS. 56 | */ 57 | tls?: boolean; 58 | 59 | /** How to convert SQLite integers to JavaScript values: 60 | * 61 | * - `"number"` (default): returns SQLite integers as JavaScript `number`-s (double precision floats). 62 | * `number` cannot precisely represent integers larger than 2^53-1 in absolute value, so attempting to read 63 | * larger integers will throw a `RangeError`. 64 | * - `"bigint"`: returns SQLite integers as JavaScript `bigint`-s (arbitrary precision integers). Bigints can 65 | * precisely represent all SQLite integers. 66 | * - `"string"`: returns SQLite integers as strings. 67 | */ 68 | intMode?: IntMode; 69 | 70 | /** Custom `fetch` function to use for the HTTP client. 71 | * 72 | * By default, the HTTP client uses `fetch` from the `@libsql/isomorphic-fetch` package, but you can pass 73 | * your own function here. The argument to this function will be `Request` from 74 | * `@libsql/isomorphic-fetch`, and it must return a promise that resolves to an object that is compatible 75 | * with the Web `Response`. 76 | */ 77 | fetch?: Function; 78 | 79 | /** Concurrency limit. 80 | * 81 | * By default, the client performs up to 20 concurrent requests. You can set this option to a higher 82 | * number to increase the concurrency limit or set it to 0 to disable concurrency limits completely. 83 | */ 84 | concurrency?: number | undefined; 85 | } 86 | 87 | /** Representation of integers from database as JavaScript values. See {@link Config.intMode}. */ 88 | export type IntMode = "number" | "bigint" | "string"; 89 | 90 | /** Client object for a remote or local database. 91 | * 92 | * After you are done with the client, you **should** close it by calling {@link close}. 93 | */ 94 | export interface Client { 95 | /** Execute a single SQL statement. 96 | * 97 | * Every statement executed with this method is executed in its own logical database connection. If you 98 | * want to execute a group of statements in a transaction, use the {@link batch} or the {@link 99 | * transaction} methods. 100 | * 101 | * ```javascript 102 | * // execute a statement without arguments 103 | * const rs = await client.execute("SELECT * FROM books"); 104 | * 105 | * // execute a statement with positional arguments 106 | * const rs = await client.execute({ 107 | * sql: "SELECT * FROM books WHERE author = ?", 108 | * args: ["Jane Austen"], 109 | * }); 110 | * 111 | * // execute a statement with named arguments 112 | * const rs = await client.execute({ 113 | * sql: "SELECT * FROM books WHERE published_at > $year", 114 | * args: {year: 1719}, 115 | * }); 116 | * ``` 117 | */ 118 | execute(stmt: InStatement): Promise; 119 | 120 | /** Execute a batch of SQL statements in a transaction. 121 | * 122 | * The batch is executed in its own logical database connection and the statements are wrapped in a 123 | * transaction. This ensures that the batch is applied atomically: either all or no changes are applied. 124 | * 125 | * The `mode` parameter selects the transaction mode for the batch; please see {@link TransactionMode} for 126 | * details. The default transaction mode is `"deferred"`. 127 | * 128 | * If any of the statements in the batch fails with an error, the batch is aborted, the transaction is 129 | * rolled back and the returned promise is rejected. 130 | * 131 | * This method provides non-interactive transactions. If you need interactive transactions, please use the 132 | * {@link transaction} method. 133 | * 134 | * ```javascript 135 | * const rss = await client.batch([ 136 | * // batch statement without arguments 137 | * "DELETE FROM books WHERE name LIKE '%Crusoe'", 138 | * 139 | * // batch statement with positional arguments 140 | * { 141 | * sql: "INSERT INTO books (name, author, published_at) VALUES (?, ?, ?)", 142 | * args: ["First Impressions", "Jane Austen", 1813], 143 | * }, 144 | * 145 | * // batch statement with named arguments 146 | * { 147 | * sql: "UPDATE books SET name = $new WHERE name = $old", 148 | * args: {old: "First Impressions", new: "Pride and Prejudice"}, 149 | * }, 150 | * ], "write"); 151 | * ``` 152 | */ 153 | batch( 154 | stmts: Array, 155 | mode?: TransactionMode, 156 | ): Promise>; 157 | 158 | /** Execute a batch of SQL statements in a transaction with PRAGMA foreign_keys=off; before and PRAGMA foreign_keys=on; after. 159 | * 160 | * The batch is executed in its own logical database connection and the statements are wrapped in a 161 | * transaction. This ensures that the batch is applied atomically: either all or no changes are applied. 162 | * 163 | * The transaction mode is `"deferred"`. 164 | * 165 | * If any of the statements in the batch fails with an error, the batch is aborted, the transaction is 166 | * rolled back and the returned promise is rejected. 167 | * 168 | * ```javascript 169 | * const rss = await client.migrate([ 170 | * // statement without arguments 171 | * "CREATE TABLE test (a INT)", 172 | * 173 | * // statement with positional arguments 174 | * { 175 | * sql: "INSERT INTO books (name, author, published_at) VALUES (?, ?, ?)", 176 | * args: ["First Impressions", "Jane Austen", 1813], 177 | * }, 178 | * 179 | * // statement with named arguments 180 | * { 181 | * sql: "UPDATE books SET name = $new WHERE name = $old", 182 | * args: {old: "First Impressions", new: "Pride and Prejudice"}, 183 | * }, 184 | * ]); 185 | * ``` 186 | */ 187 | migrate( 188 | stmts: Array, 189 | ): Promise>; 190 | 191 | /** Start an interactive transaction. 192 | * 193 | * Interactive transactions allow you to interleave execution of SQL statements with your application 194 | * logic. They can be used if the {@link batch} method is too restrictive, but please note that 195 | * interactive transactions have higher latency. 196 | * 197 | * The `mode` parameter selects the transaction mode for the interactive transaction; please see {@link 198 | * TransactionMode} for details. The default transaction mode is `"deferred"`. 199 | * 200 | * You **must** make sure that the returned {@link Transaction} object is closed, by calling {@link 201 | * Transaction.close}, {@link Transaction.commit} or {@link Transaction.rollback}. The best practice is 202 | * to call {@link Transaction.close} in a `finally` block, as follows: 203 | * 204 | * ```javascript 205 | * const transaction = client.transaction("write"); 206 | * try { 207 | * // do some operations with the transaction here 208 | * await transaction.execute({ 209 | * sql: "INSERT INTO books (name, author) VALUES (?, ?)", 210 | * args: ["First Impressions", "Jane Austen"], 211 | * }); 212 | * await transaction.execute({ 213 | * sql: "UPDATE books SET name = ? WHERE name = ?", 214 | * args: ["Pride and Prejudice", "First Impressions"], 215 | * }); 216 | * 217 | * // if all went well, commit the transaction 218 | * await transaction.commit(); 219 | * } finally { 220 | * // make sure to close the transaction, even if an exception was thrown 221 | * transaction.close(); 222 | * } 223 | * ``` 224 | */ 225 | transaction(mode?: TransactionMode): Promise; 226 | 227 | /** Start an interactive transaction in `"write"` mode. 228 | * 229 | * Please see {@link transaction} for details. 230 | * 231 | * @deprecated Please specify the `mode` explicitly. The default `"write"` will be removed in the next 232 | * major release. 233 | */ 234 | transaction(): Promise; 235 | 236 | /** Execute a sequence of SQL statements separated by semicolons. 237 | * 238 | * The statements are executed sequentially on a new logical database connection. If a statement fails, 239 | * further statements are not executed and this method throws an error. All results from the statements 240 | * are ignored. 241 | * 242 | * We do not wrap the statements in a transaction, but the SQL can contain explicit transaction-control 243 | * statements such as `BEGIN` and `COMMIT`. 244 | * 245 | * This method is intended to be used with existing SQL scripts, such as migrations or small database 246 | * dumps. If you want to execute a sequence of statements programmatically, please use {@link batch} 247 | * instead. 248 | * 249 | * ```javascript 250 | * await client.executeMultiple(` 251 | * CREATE TABLE books (id INTEGER PRIMARY KEY, title TEXT NOT NULL, author_id INTEGER NOT NULL); 252 | * CREATE TABLE authors (id INTEGER PRIMARY KEY, name TEXT NOT NULL); 253 | * `); 254 | * ``` 255 | */ 256 | executeMultiple(sql: string): Promise; 257 | 258 | sync(): Promise; 259 | 260 | /** Close the client and release resources. 261 | * 262 | * This method closes the client (aborting any operations that are currently in progress) and releases any 263 | * resources associated with the client (such as a WebSocket connection). 264 | */ 265 | close(): void; 266 | 267 | /** Is the client closed? 268 | * 269 | * This is set to `true` after a call to {@link close} or if the client encounters an unrecoverable 270 | * error. 271 | */ 272 | closed: boolean; 273 | 274 | /** Which protocol does the client use? 275 | * 276 | * - `"http"` if the client connects over HTTP 277 | * - `"ws"` if the client connects over WebSockets 278 | * - `"file"` if the client works with a local file 279 | */ 280 | protocol: string; 281 | } 282 | 283 | /** Interactive transaction. 284 | * 285 | * A transaction groups multiple SQL statements together, so that they are applied atomically: either all 286 | * changes are applied, or none are. Other SQL statements on the database (including statements executed on 287 | * the same {@link Client} object outside of this transaction) will not see any changes from the transaction 288 | * until the transaction is committed by calling {@link commit}. You can also use {@link rollback} to abort 289 | * the transaction and roll back the changes. 290 | * 291 | * You **must** make sure that the {@link Transaction} object is closed, by calling {@link close}, {@link 292 | * commit} or {@link rollback}. The best practice is to call {@link close} in a `finally` block, as follows: 293 | * 294 | * ```javascript 295 | * const transaction = client.transaction("write"); 296 | * try { 297 | * // do some operations with the transaction here 298 | * await transaction.execute({ 299 | * sql: "INSERT INTO books (name, author) VALUES (?, ?)", 300 | * args: ["First Impressions", "Jane Austen"], 301 | * }); 302 | * await transaction.execute({ 303 | * sql: "UPDATE books SET name = ? WHERE name = ?", 304 | * args: ["Pride and Prejudice", "First Impressions"], 305 | * }); 306 | * 307 | * // if all went well, commit the transaction 308 | * await transaction.commit(); 309 | * } finally { 310 | * // make sure to close the transaction, even if an exception was thrown 311 | * transaction.close(); 312 | * } 313 | * ``` 314 | */ 315 | export interface Transaction { 316 | /** Execute an SQL statement in this transaction. 317 | * 318 | * If the statement makes any changes to the database, these changes won't be visible to statements 319 | * outside of this transaction until you call {@link rollback}. 320 | * 321 | * ```javascript 322 | * await transaction.execute({ 323 | * sql: "INSERT INTO books (name, author) VALUES (?, ?)", 324 | * args: ["First Impressions", "Jane Austen"], 325 | * }); 326 | * ``` 327 | */ 328 | execute(stmt: InStatement): Promise; 329 | 330 | /** Execute a batch of SQL statements in this transaction. 331 | * 332 | * If any of the statements in the batch fails with an error, further statements are not executed and the 333 | * returned promise is rejected with an error, but the transaction is not rolled back. 334 | */ 335 | batch(stmts: Array): Promise>; 336 | 337 | /** Execute a sequence of SQL statements separated by semicolons. 338 | * 339 | * The statements are executed sequentially in the transaction. If a statement fails, further statements 340 | * are not executed and this method throws an error, but the transaction won't be rolled back. All results 341 | * from the statements are ignored. 342 | * 343 | * This method is intended to be used with existing SQL scripts, such as migrations or small database 344 | * dumps. If you want to execute statements programmatically, please use {@link batch} instead. 345 | */ 346 | executeMultiple(sql: string): Promise; 347 | 348 | /** Roll back any changes from this transaction. 349 | * 350 | * This method closes the transaction and undoes any changes done by the previous SQL statements on this 351 | * transaction. You cannot call this method after calling {@link commit}, though. 352 | */ 353 | rollback(): Promise; 354 | 355 | /** Commit changes from this transaction to the database. 356 | * 357 | * This method closes the transaction and applies all changes done by the previous SQL statement on this 358 | * transaction. Once the returned promise is resolved successfully, the database guarantees that the 359 | * changes were applied. 360 | */ 361 | commit(): Promise; 362 | 363 | /** Close the transaction. 364 | * 365 | * This method closes the transaction and releases any resources associated with the transaction. If the 366 | * transaction is already closed (perhaps by a previous call to {@link commit} or {@link rollback}), then 367 | * this method does nothing. 368 | * 369 | * If the transaction wasn't already committed by calling {@link commit}, the transaction is rolled 370 | * back. 371 | */ 372 | close(): void; 373 | 374 | /** Is the transaction closed? 375 | * 376 | * This is set to `true` after a call to {@link close}, {@link commit} or {@link rollback}, or if we 377 | * encounter an unrecoverable error. 378 | */ 379 | closed: boolean; 380 | } 381 | 382 | /** Transaction mode. 383 | * 384 | * The client supports multiple modes for transactions: 385 | * 386 | * - `"write"` is a read-write transaction, started with `BEGIN IMMEDIATE`. This transaction mode supports 387 | * both read statements (`SELECT`) and write statements (`INSERT`, `UPDATE`, `CREATE TABLE`, etc). The libSQL 388 | * server cannot process multiple write transactions concurrently, so if there is another write transaction 389 | * already started, our transaction will wait in a queue before it can begin. 390 | * 391 | * - `"read"` is a read-only transaction, started with `BEGIN TRANSACTION READONLY` (a libSQL extension). This 392 | * transaction mode supports only reads (`SELECT`) and will not accept write statements. The libSQL server can 393 | * handle multiple read transactions at the same time, so we don't need to wait for other transactions to 394 | * complete. A read-only transaction can also be executed on a local replica, so it provides lower latency. 395 | * 396 | * - `"deferred"` is a transaction started with `BEGIN DEFERRED`, which starts as a read transaction, but the 397 | * first write statement will try to upgrade it to a write transaction. However, this upgrade may fail if 398 | * there already is a write transaction executing on the server, so you should be ready to handle these 399 | * failures. 400 | * 401 | * If your transaction includes only read statements, `"read"` is always preferred over `"deferred"` or 402 | * `"write"`, because `"read"` transactions can be executed more efficiently and don't block other 403 | * transactions. 404 | * 405 | * If your transaction includes both read and write statements, you should be using the `"write"` mode most of 406 | * the time. Use the `"deferred"` mode only if you prefer to fail the write transaction instead of waiting for 407 | * the previous write transactions to complete. 408 | */ 409 | export type TransactionMode = "write" | "read" | "deferred"; 410 | 411 | /** Result of executing an SQL statement. 412 | * 413 | * ```javascript 414 | * const rs = await client.execute("SELECT name, title FROM books"); 415 | * console.log(`Found ${rs.rows.length} books`); 416 | * for (const row in rs.rows) { 417 | * console.log(`Book ${row[0]} by ${row[1]}`); 418 | * } 419 | * 420 | * const rs = await client.execute("DELETE FROM books WHERE author = 'Jane Austen'"); 421 | * console.log(`Deleted ${rs.rowsAffected} books`); 422 | * ``` 423 | */ 424 | export interface ResultSet { 425 | /** Names of columns. 426 | * 427 | * Names of columns can be defined using the `AS` keyword in SQL: 428 | * 429 | * ```sql 430 | * SELECT author AS author, COUNT(*) AS count FROM books GROUP BY author 431 | * ``` 432 | */ 433 | columns: Array; 434 | 435 | /** Types of columns. 436 | * 437 | * The types are currently shown for types declared in a SQL table. For 438 | * column types of function calls, for example, an empty string is 439 | * returned. 440 | */ 441 | columnTypes: Array; 442 | 443 | /** Rows produced by the statement. */ 444 | rows: Array; 445 | 446 | /** Number of rows that were affected by an UPDATE, INSERT or DELETE operation. 447 | * 448 | * This value is not specified for other SQL statements. 449 | */ 450 | rowsAffected: number; 451 | 452 | /** ROWID of the last inserted row. 453 | * 454 | * This value is not specified if the SQL statement was not an INSERT or if the table was not a ROWID 455 | * table. 456 | */ 457 | lastInsertRowid: bigint | undefined; 458 | 459 | /** Converts the result set to JSON. 460 | * 461 | * This is used automatically by `JSON.stringify()`, but you can also call it explicitly. 462 | */ 463 | toJSON(): any; 464 | } 465 | 466 | /** Row returned from an SQL statement. 467 | * 468 | * The row object can be used as an `Array` or as an object: 469 | * 470 | * ```javascript 471 | * const rs = await client.execute("SELECT name, title FROM books"); 472 | * for (const row in rs.rows) { 473 | * // Get the value from column `name` 474 | * console.log(row.name); 475 | * // Get the value from second column (`title`) 476 | * console.log(row[1]); 477 | * } 478 | * ``` 479 | */ 480 | export interface Row { 481 | /** Number of columns in this row. 482 | * 483 | * All rows in one {@link ResultSet} have the same number and names of columns. 484 | */ 485 | length: number; 486 | 487 | /** Columns can be accessed like an array by numeric indexes. */ 488 | [index: number]: Value; 489 | 490 | /** Columns can be accessed like an object by column names. */ 491 | [name: string]: Value; 492 | } 493 | 494 | export type Replicated = 495 | | { frame_no: number; frames_synced: number } 496 | | undefined; 497 | 498 | export type Value = null | string | number | bigint | ArrayBuffer; 499 | 500 | export type InValue = Value | boolean | Uint8Array | Date; 501 | 502 | export type InStatement = { sql: string; args: InArgs } | string; 503 | export type InArgs = Array | Record; 504 | 505 | /** Error thrown by the client. */ 506 | export class LibsqlError extends Error { 507 | /** Machine-readable error code. */ 508 | code: string; 509 | /** Raw numeric error code */ 510 | rawCode?: number; 511 | 512 | constructor( 513 | message: string, 514 | code: string, 515 | rawCode?: number, 516 | cause?: Error, 517 | ) { 518 | if (code !== undefined) { 519 | message = `${code}: ${message}`; 520 | } 521 | super(message, { cause }); 522 | this.code = code; 523 | this.rawCode = rawCode; 524 | this.name = "LibsqlError"; 525 | } 526 | } 527 | --------------------------------------------------------------------------------