From 77b6093f04c7533f339bd07119e342bb4f7a1db3 Mon Sep 17 00:00:00 2001 From: Heb Date: Fri, 21 Jul 2023 20:43:48 +0800 Subject: [PATCH] feat: add libsql support (#25) --- docs/content/100.connectors/libsql.md | 55 ++++++++-- package.json | 5 + pnpm-lock.yaml | 150 +++++++++++++++++++++++++- src/connectors/libsql.ts | 61 +++++++++++ src/index.ts | 1 + test/connectors/libsql.test.ts | 22 ++++ 6 files changed, 282 insertions(+), 12 deletions(-) create mode 100644 src/connectors/libsql.ts create mode 100644 test/connectors/libsql.test.ts diff --git a/docs/content/100.connectors/libsql.md b/docs/content/100.connectors/libsql.md index ab1e5f3..e74e7d6 100644 --- a/docs/content/100.connectors/libsql.md +++ b/docs/content/100.connectors/libsql.md @@ -4,19 +4,58 @@ navigation.title: LibSQL # LibSQL Connector -Connect to [LibSQL](https://libsql.org/) database. - -::alert{type="primary"} -🚀 This connector will be comming soon! Follow up via [unjs/db0#14](https://github.com/unjs/db0/issues/14). -:: +Connect to a [LibSQL](https://libsql.org/) database. ```js import { createDB, sql } from "db0"; -import vercelPostgres from "db0/connectors/libsql"; +import libSql from "db0/connectors/libsql"; const db = createDB( - libsql({ - /* options */ + libSql({ + url: `file:local.db`, }) ); ``` + +## Options + +### `url` + +Type: `string` + +The database URL. The client supports `libsql:`, `http:`/`https:`, `ws:`/`wss:` and `file:` URL. For more information, please refer to the project README: [link](https://github.com/libsql/libsql-client-ts#supported-urls) + +--- + +### `authToken` + +Type: `string` (optional) + +Authentication token for the database. + +--- + +### `tls` + +Type: `boolean` (optional) + +Enables or disables TLS for `libsql:` URLs. By default, `libsql:` URLs use TLS. You can set this option to `false` to disable TLS. + +--- + +### `intMode` + +Type: `IntMode` (optional) + +How to convert SQLite integers to JavaScript values: + +- `"number"` (default): returns SQLite integers as JavaScript `number`-s (double precision floats). `number` cannot precisely represent integers larger than 2^53-1 in absolute value, so attempting to read larger integers will throw a `RangeError`. +- `"bigint"`: returns SQLite integers as JavaScript `bigint`-s (arbitrary precision integers). Bigints can precisely represent all SQLite integers. +- `"string"`: returns SQLite integers as strings. + +## References + +- [LibSQL Website](https://libsql.org/) +- [LibSQL GitHub Repository](https://github.com/libsql/libsql) +- [LibSQL Client API Reference](https://libsql.org/libsql-client-ts/index.html) +- [LibSQL Client GitHub Repository](https://github.com/libsql/libsql-client-ts) diff --git a/package.json b/package.json index eadb9fe..2566679 100644 --- a/package.json +++ b/package.json @@ -40,6 +40,7 @@ "db0": "pnpm jiti src/cli" }, "devDependencies": { + "@libsql/client": "^0.2.2", "@types/better-sqlite3": "^7.6.4", "@types/pg": "^8.10.2", "@vitest/coverage-v8": "^0.33.0", @@ -57,10 +58,14 @@ "vitest": "^0.33.0" }, "peerDependencies": { + "@libsql/client": "^0.2.2", "better-sqlite3": "^8.4.0", "drizzle-orm": "^0.27.2" }, "peerDependenciesMeta": { + "@libsql/client": { + "optional": true + }, "better-sqlite3": { "optional": true }, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index aa1cae6..8bea039 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -8,6 +8,9 @@ importers: .: devDependencies: + '@libsql/client': + specifier: ^0.2.2 + version: 0.2.2 '@types/better-sqlite3': specifier: ^7.6.4 version: 7.6.4 @@ -28,7 +31,7 @@ importers: version: 16.3.1 drizzle-orm: specifier: ^0.27.2 - version: 0.27.2(@types/better-sqlite3@7.6.4)(@types/pg@8.10.2)(better-sqlite3@8.5.0)(pg@8.11.1) + version: 0.27.2(@libsql/client@0.2.2)(@types/better-sqlite3@7.6.4)(@types/pg@8.10.2)(better-sqlite3@8.5.0)(pg@8.11.1) eslint: specifier: ^8.45.0 version: 8.45.0 @@ -61,7 +64,7 @@ importers: version: 0.19.2 drizzle-orm: specifier: ^0.27.0 - version: 0.27.0(@types/better-sqlite3@7.6.4)(@types/pg@8.10.2)(better-sqlite3@8.5.0)(pg@8.11.1) + version: 0.27.0(@libsql/client@0.2.2)(@types/better-sqlite3@7.6.4)(@types/pg@8.10.2)(better-sqlite3@8.5.0)(pg@8.11.1) jiti: specifier: ^1.18.2 version: 1.18.2 @@ -994,6 +997,49 @@ packages: '@jridgewell/sourcemap-codec': 1.4.14 dev: true + /@libsql/client@0.2.2: + resolution: {integrity: sha512-HDMbnQq9Ekqb4DEOqLmn2nedeFNMEYSzGhZEGJC0ILFhbUnPVFsEQ/dtUKwE1wgOr8koB/D7qmL3wpujhxvHTA==} + dependencies: + '@libsql/hrana-client': 0.4.3 + better-sqlite3: 8.5.0 + js-base64: 3.7.5 + transitivePeerDependencies: + - bufferutil + - encoding + - utf-8-validate + dev: true + + /@libsql/hrana-client@0.4.3: + resolution: {integrity: sha512-ZlAXy3PpB8B93iu8MyDyMpzbu1b4G4oa5KOjbABHe7iBybx7yC+t3U/sK7c37QsmpRZQXi0S+vNPpGTlje3bCA==} + dependencies: + '@libsql/isomorphic-fetch': 0.1.4 + '@libsql/isomorphic-ws': 0.1.3 + js-base64: 3.7.5 + transitivePeerDependencies: + - bufferutil + - encoding + - utf-8-validate + dev: true + + /@libsql/isomorphic-fetch@0.1.4: + resolution: {integrity: sha512-eRgV4b1d6RVLciafGEKZghDOvrQ7fDuPgfGzelsfPz1HxFERaTQuOwL5FthZcKBtHI6Wqu5zmhUu5bREZr1mpA==} + dependencies: + '@types/node-fetch': 2.6.4 + node-fetch: 2.6.12 + transitivePeerDependencies: + - encoding + dev: true + + /@libsql/isomorphic-ws@0.1.3: + resolution: {integrity: sha512-54dZXgYwWDKsnfWv8GCVYvhn6RDlqFDGAc8EQMd941yvGMsGzo06Gn6Iyjw//nJ1iJO97FbXgoQ1apikoFD/WA==} + dependencies: + '@types/ws': 8.5.5 + ws: 8.13.0 + transitivePeerDependencies: + - bufferutil + - utf-8-validate + dev: true + /@nodelib/fs.scandir@2.1.5: resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} engines: {node: '>= 8'} @@ -1154,6 +1200,13 @@ packages: resolution: {integrity: sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==} dev: true + /@types/node-fetch@2.6.4: + resolution: {integrity: sha512-1ZX9fcN4Rvkvgv4E6PAY5WXUFWFcRWxZa3EW83UjycOB9ljJCedb2CupIP4RZMEwF/M3eTcCihbBRgwtGbg5Rg==} + dependencies: + '@types/node': 20.4.2 + form-data: 3.0.1 + dev: true + /@types/node@20.4.2: resolution: {integrity: sha512-Dd0BYtWgnWJKwO1jkmTrzofjK2QXXcai0dmtzvIBhcA+RsG5h8R3xlyta0kGOZRNfL9GuRtb1knmPEhQrePCEw==} dev: true @@ -1178,6 +1231,12 @@ packages: resolution: {integrity: sha512-G8hZ6XJiHnuhQKR7ZmysCeJWE08o8T0AXtk5darsCaTVsYZhhgUrq53jizaR2FvsoeCwJhlmwTjkXBY5Pn/ZHw==} dev: true + /@types/ws@8.5.5: + resolution: {integrity: sha512-lwhs8hktwxSjf9UaZ9tG5M03PGogvFaH8gUgLNbN9HKIg0dvv6q+gkSuJ8HN4/VbyxkuLzCjlN7GquQ0gUJfIg==} + dependencies: + '@types/node': 20.4.2 + dev: true + /@typescript-eslint/eslint-plugin@5.62.0(@typescript-eslint/parser@5.62.0)(eslint@8.45.0)(typescript@5.1.6): resolution: {integrity: sha512-TiZzBSJja/LbhNPvk6yc0JrX9XqhQ0hdh6M2svYfsHGejaKFIAGd9MQ+ERIMzLGlN/kZoYIgdxFV0PuljTKXag==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -1499,6 +1558,10 @@ packages: resolution: {integrity: sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==} dev: true + /asynckit@0.4.0: + resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} + dev: true + /available-typed-arrays@1.0.5: resolution: {integrity: sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==} engines: {node: '>= 0.4'} @@ -1800,6 +1863,13 @@ packages: resolution: {integrity: sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==} dev: true + /combined-stream@1.0.8: + resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} + engines: {node: '>= 0.8'} + dependencies: + delayed-stream: 1.0.0 + dev: true + /commander@9.5.0: resolution: {integrity: sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==} engines: {node: ^12.20.0 || >=14} @@ -1928,6 +1998,11 @@ packages: resolution: {integrity: sha512-+uO4+qr7msjNNWKYPHqN/3+Dx3NFkmIzayk2L1MyZQlvgZb/J1A0fo410dpKrN2SnqFjt8n4JL8fDJE0wIgjFQ==} dev: true + /delayed-stream@1.0.0: + resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} + engines: {node: '>=0.4.0'} + dev: true + /destr@2.0.0: resolution: {integrity: sha512-FJ9RDpf3GicEBvzI3jxc2XhHzbqD8p4ANw/1kPsFBfTvP1b7Gn/Lg1vO7R9J4IVgoMbyUmFrFGZafJ1hPZpvlg==} dev: true @@ -2000,7 +2075,7 @@ packages: - supports-color dev: true - /drizzle-orm@0.27.0(@types/better-sqlite3@7.6.4)(@types/pg@8.10.2)(better-sqlite3@8.5.0)(pg@8.11.1): + /drizzle-orm@0.27.0(@libsql/client@0.2.2)(@types/better-sqlite3@7.6.4)(@types/pg@8.10.2)(better-sqlite3@8.5.0)(pg@8.11.1): resolution: {integrity: sha512-LGiJ0icB+wQwgbSCOvAjONY8Ec6G/EDzQQP5PmUaQYeI9OqgpVKHC2T1fFIbvk5dabWsbokJ5NOciVAxriStig==} peerDependencies: '@aws-sdk/client-rds-data': '>=3' @@ -2062,13 +2137,14 @@ packages: sqlite3: optional: true dependencies: + '@libsql/client': 0.2.2 '@types/better-sqlite3': 7.6.4 '@types/pg': 8.10.2 better-sqlite3: 8.5.0 pg: 8.11.1 dev: true - /drizzle-orm@0.27.2(@types/better-sqlite3@7.6.4)(@types/pg@8.10.2)(better-sqlite3@8.5.0)(pg@8.11.1): + /drizzle-orm@0.27.2(@libsql/client@0.2.2)(@types/better-sqlite3@7.6.4)(@types/pg@8.10.2)(better-sqlite3@8.5.0)(pg@8.11.1): resolution: {integrity: sha512-ZvBvceff+JlgP7FxHKe0zOU9CkZ4RcOtibumIrqfYzDGuOeF0YUY0F9iMqYpRM7pxnLRfC+oO7rWOUH3T5oFQA==} peerDependencies: '@aws-sdk/client-rds-data': '>=3' @@ -2130,6 +2206,7 @@ packages: sqlite3: optional: true dependencies: + '@libsql/client': 0.2.2 '@types/better-sqlite3': 7.6.4 '@types/pg': 8.10.2 better-sqlite3: 8.5.0 @@ -2867,6 +2944,15 @@ packages: is-callable: 1.2.7 dev: true + /form-data@3.0.1: + resolution: {integrity: sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg==} + engines: {node: '>= 6'} + dependencies: + asynckit: 0.4.0 + combined-stream: 1.0.8 + mime-types: 2.1.35 + dev: true + /fs-constants@1.0.0: resolution: {integrity: sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==} dev: true @@ -3438,6 +3524,10 @@ packages: hasBin: true dev: true + /js-base64@3.7.5: + resolution: {integrity: sha512-3MEt5DTINKqfScXKfJFrRbxkrnk2AxPWGBL/ycjz4dK8iqiSJ06UxD8jh8xuh6p10TX4t2+7FsBYVxxQbMg+qA==} + dev: true + /js-tokens@4.0.0: resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} dev: true @@ -3631,6 +3721,18 @@ packages: picomatch: 2.3.1 dev: true + /mime-db@1.52.0: + resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} + engines: {node: '>= 0.6'} + dev: true + + /mime-types@2.1.35: + resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} + engines: {node: '>= 0.6'} + dependencies: + mime-db: 1.52.0 + dev: true + /mimic-fn@2.1.0: resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==} engines: {node: '>=6'} @@ -3784,6 +3886,18 @@ packages: resolution: {integrity: sha512-5IAMBTl9p6PaAjYCnMv5FmqIF6GcZnawAVnzaCG0rX2aYZJ4CxEkZNtVPuTRug7fL7wyM5BQYTlAzcyMPi6oTQ==} dev: true + /node-fetch@2.6.12: + resolution: {integrity: sha512-C/fGU2E8ToujUivIO0H+tpQ6HWo4eEmchoPIoXtxCrVghxdKq+QOHqEZW7tuP3KlV3bC8FRMO5nMCC7Zm1VP6g==} + engines: {node: 4.x || >=6.0.0} + peerDependencies: + encoding: ^0.1.0 + peerDependenciesMeta: + encoding: + optional: true + dependencies: + whatwg-url: 5.0.0 + dev: true + /node-releases@2.0.13: resolution: {integrity: sha512-uYr7J37ae/ORWdZeQ1xxMJe3NtdmqMC/JZK+geofDrkLUApKRHPd18/TxtBOJ4A0/+uUIliorNrfYV6s1b02eQ==} dev: true @@ -4720,6 +4834,10 @@ packages: is-number: 7.0.0 dev: true + /tr46@0.0.3: + resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==} + dev: true + /tsconfig-paths@3.14.2: resolution: {integrity: sha512-o/9iXgCYc5L/JxCHPe3Hvh8Q/2xm5Z+p18PESBU6Ff33695QnCHBEjcytY2q19ua7Mbl/DavtBOLq+oG0RCL+g==} dependencies: @@ -5064,6 +5182,17 @@ packages: - terser dev: true + /webidl-conversions@3.0.1: + resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==} + dev: true + + /whatwg-url@5.0.0: + resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==} + dependencies: + tr46: 0.0.3 + webidl-conversions: 3.0.1 + dev: true + /which-boxed-primitive@1.0.2: resolution: {integrity: sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==} dependencies: @@ -5110,6 +5239,19 @@ packages: resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} dev: true + /ws@8.13.0: + resolution: {integrity: sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA==} + engines: {node: '>=10.0.0'} + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: '>=5.0.2' + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + dev: true + /xtend@4.0.2: resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==} engines: {node: '>=0.4'} diff --git a/src/connectors/libsql.ts b/src/connectors/libsql.ts new file mode 100644 index 0000000..c315c47 --- /dev/null +++ b/src/connectors/libsql.ts @@ -0,0 +1,61 @@ +import { createClient } from "@libsql/client"; +import type { Client, InStatement, Config } from "@libsql/client"; +import type { Connector, Statement } from "../types"; + +export type ConnectorOptions = Config; + +export default function libSqlConnector(opts: ConnectorOptions) { + let _client: undefined | Client; + function getClient() { + if (_client) { + return _client; + } + // TODO: Normalize options for file: protocol to be relative to project .data + _client = createClient(opts); + return _client; + } + + function query(sql: InStatement) { + const client = getClient(); + return client.execute(sql); + } + + return { + name: "libsql", + exec(sql: string) { + return query(sql); + }, + prepare(sql: string) { + const stmt = { + _sql: sql, + _params: [], + bind(...params) { + if (params.length > 0) { + this._params = params; + } + return stmt; + }, + all(...params) { + return query({ sql: this._sql, args: params || this._params }).then( + (r) => r.rows, + ); + }, + run(...params) { + return query({ sql: this._sql, args: params || this._params }).then( + (r) => ({ + result: r, + rows: r.rows, + }), + ); + }, + get(...params) { + // TODO: Append limit? + return query({ sql: this._sql, args: params || this._params }).then( + (r) => r.rows[0], + ); + }, + }; + return stmt; + }, + }; +} diff --git a/src/index.ts b/src/index.ts index 62b5d96..b3ae4c2 100644 --- a/src/index.ts +++ b/src/index.ts @@ -3,6 +3,7 @@ export * from "./types"; export const connectors = { sqlite: "db0/connectors/better-sqlite3", + libsql: "db0/connectors/libsql", postgresql: "db0/connectors/postgresql", "cloudflare-d1": "db0/connectors/cloudflare-d1", } as const; diff --git a/test/connectors/libsql.test.ts b/test/connectors/libsql.test.ts new file mode 100644 index 0000000..5e1a1ae --- /dev/null +++ b/test/connectors/libsql.test.ts @@ -0,0 +1,22 @@ +import { fileURLToPath } from "node:url"; +import { existsSync, unlinkSync, mkdirSync } from "node:fs"; +import { dirname, resolve } from "node:path"; +import { describe } from "vitest"; +import libSql from "../../src/connectors/libsql"; +import { testConnector } from "./_tests"; + +describe("connectors: libsql", () => { + const dbPath = resolve( + dirname(fileURLToPath(import.meta.url)), + ".tmp/libsql/.data/local.db", + ); + if (existsSync(dbPath)) { + unlinkSync(dbPath); + } + mkdirSync(dirname(dbPath), { recursive: true }); + testConnector({ + connector: libSql({ + url: `file:${dbPath}`, + }), + }); +});