diff --git a/Makefile b/Makefile index 8ab6195..e4099f6 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,7 @@ .PHONY: generate generate: examples/plugin.wasm examples/sqlc.dev.yaml - cd examples && sqlc-dev -f sqlc.dev.yaml generate + cd examples && sqlc -f sqlc.dev.yaml generate # https://github.com/bytecodealliance/javy/releases/tag/v1.2.0 examples/plugin.wasm: out.js diff --git a/README.md b/README.md index b913bd4..f8d5323 100644 --- a/README.md +++ b/README.md @@ -29,6 +29,7 @@ sql: - PostgreSQL via [pg](https://www.npmjs.com/package/pg) or [postgres](https://www.npmjs.com/package/postgres). - MySQL via [mysql2](https://www.npmjs.com/package/mysql2). - SQLite via [sqlite3](https://www.npmjs.com/package/better-sqlite3). +- SQLite fork Turso via [@libsql/client](https://www.npmjs.com/package/@libsql/client). ## Getting started @@ -332,4 +333,24 @@ sql: options: runtime: node driver: better-sqlite3 # npm package name + +### Turso SQLite and @libsql/client (Beta) + +```yaml +version: '2' +plugins: +- name: ts + wasm: + url: https://downloads.sqlc.dev/plugin/sqlc-gen-typescript_0.1.4.wasm + sha256: 96fd05db0dc835ffd005b9f571fa5fa6d6cbd5a74e653ca34c2920c1b5d06249 +sql: +- schema: "schema.sql" + queries: "query.sql" + engine: sqlite + codegen: + - out: db + plugin: ts + options: + runtime: node + driver: turso ``` diff --git a/examples/node-turso/package-lock.json b/examples/node-turso/package-lock.json new file mode 100644 index 0000000..0b4cb53 --- /dev/null +++ b/examples/node-turso/package-lock.json @@ -0,0 +1,332 @@ +{ + "name": "turso", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "turso", + "version": "1.0.0", + "license": "ISC", + "dependencies": { + "@libsql/client": "0.6.2" + }, + "devDependencies": { + "typescript": "^5.2.2" + } + }, + "node_modules/@libsql/client": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/@libsql/client/-/client-0.6.2.tgz", + "integrity": "sha512-xRNfRLv/dOCbV4qd+M0baQwGmvuZpMd2wG2UAPs8XmcdaPvu5ErkcaeITkxlm3hDEJVabQM1cFhMBxsugWW9fQ==", + "dependencies": { + "@libsql/core": "^0.6.2", + "@libsql/hrana-client": "^0.6.0", + "js-base64": "^3.7.5", + "libsql": "^0.3.10" + } + }, + "node_modules/@libsql/core": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/@libsql/core/-/core-0.6.2.tgz", + "integrity": "sha512-c2P4M+4u/4b2L02A0KjggO3UW51rGkhxr/7fzJO0fEAqsqrWGxuNj2YtRkina/oxfYvAof6xjp8RucNoIV/Odw==", + "dependencies": { + "js-base64": "^3.7.5" + } + }, + "node_modules/@libsql/darwin-arm64": { + "version": "0.3.18", + "resolved": "https://registry.npmjs.org/@libsql/darwin-arm64/-/darwin-arm64-0.3.18.tgz", + "integrity": "sha512-Zt49dt+cwhPCkuoWgvjbQd4ckNfCJR5xzIAyhgHl3CBZqZaEuaXTOGKLNQT7bnFRPuQcdLt5PBT1cenKu2N6pA==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@libsql/darwin-x64": { + "version": "0.3.18", + "resolved": "https://registry.npmjs.org/@libsql/darwin-x64/-/darwin-x64-0.3.18.tgz", + "integrity": "sha512-faq6HUGDaNaueeqPei5cypHaD/hhazUyfHo094CXiEeRZq6ZKtNl5PHdlr8jE/Uw8USNpVVQaLdnvSgKcpRPHw==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@libsql/hrana-client": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/@libsql/hrana-client/-/hrana-client-0.6.2.tgz", + "integrity": "sha512-MWxgD7mXLNf9FXXiM0bc90wCjZSpErWKr5mGza7ERy2FJNNMXd7JIOv+DepBA1FQTIfI8TFO4/QDYgaQC0goNw==", + "dependencies": { + "@libsql/isomorphic-fetch": "^0.2.1", + "@libsql/isomorphic-ws": "^0.1.5", + "js-base64": "^3.7.5", + "node-fetch": "^3.3.2" + } + }, + "node_modules/@libsql/isomorphic-fetch": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/@libsql/isomorphic-fetch/-/isomorphic-fetch-0.2.1.tgz", + "integrity": "sha512-Sv07QP1Aw8A5OOrmKgRUBKe2fFhF2hpGJhtHe3d1aRnTESZCGkn//0zDycMKTGamVWb3oLYRroOsCV8Ukes9GA==" + }, + "node_modules/@libsql/isomorphic-ws": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/@libsql/isomorphic-ws/-/isomorphic-ws-0.1.5.tgz", + "integrity": "sha512-DtLWIH29onUYR00i0GlQ3UdcTRC6EP4u9w/h9LxpUZJWRMARk6dQwZ6Jkd+QdwVpuAOrdxt18v0K2uIYR3fwFg==", + "dependencies": { + "@types/ws": "^8.5.4", + "ws": "^8.13.0" + } + }, + "node_modules/@libsql/linux-arm64-gnu": { + "version": "0.3.18", + "resolved": "https://registry.npmjs.org/@libsql/linux-arm64-gnu/-/linux-arm64-gnu-0.3.18.tgz", + "integrity": "sha512-5m9xtDAhoyLSV54tho9uQ2ZIDeJWc0vU3Xpe/VK4+6bpURISs23qNhXiCrZnnq3oV0hFlBfcIgQUIATmb6jD2A==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@libsql/linux-arm64-musl": { + "version": "0.3.18", + "resolved": "https://registry.npmjs.org/@libsql/linux-arm64-musl/-/linux-arm64-musl-0.3.18.tgz", + "integrity": "sha512-oYD5+oM2gPEalp+EoR5DVQBRtdGjLsocjsRbQs5O2m4WOBJKER7VUfDYZHsifLGZoBSc11Yo6s9IR9rjGWy20w==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@libsql/linux-x64-gnu": { + "version": "0.3.18", + "resolved": "https://registry.npmjs.org/@libsql/linux-x64-gnu/-/linux-x64-gnu-0.3.18.tgz", + "integrity": "sha512-QDSSP60nS8KIldGE7H3bpEflQHiL1erwED6huoVJdmDFxsyDJX2CYdWUWW8Za0ZUOvUbnEWAOyMhp6j1dBbZqw==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@libsql/linux-x64-musl": { + "version": "0.3.18", + "resolved": "https://registry.npmjs.org/@libsql/linux-x64-musl/-/linux-x64-musl-0.3.18.tgz", + "integrity": "sha512-5SXwTlaLCUPzxYyq+P0c7Ko7tcEjpd1X6RZKe1DuRFmJPg6f7j2+LrPEhMSIbqKcrl5ACUUAyoKmGZqNYwz23w==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@libsql/win32-x64-msvc": { + "version": "0.3.18", + "resolved": "https://registry.npmjs.org/@libsql/win32-x64-msvc/-/win32-x64-msvc-0.3.18.tgz", + "integrity": "sha512-9EEIHz+e8tTbx9TMkb8ByZnzxc0pYFirK1nSbqC6cFEST95fiY0NCfQ/zAzJxe90KckbjifX6BbO69eWIi3TAg==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@neon-rs/load": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/@neon-rs/load/-/load-0.0.4.tgz", + "integrity": "sha512-kTPhdZyTQxB+2wpiRcFWrDcejc4JI6tkPuS7UZCG4l6Zvc5kU/gGQ/ozvHTh1XR5tS+UlfAfGuPajjzQjCiHCw==" + }, + "node_modules/@types/node": { + "version": "20.11.17", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.17.tgz", + "integrity": "sha512-QmgQZGWu1Yw9TDyAP9ZzpFJKynYNeOvwMJmaxABfieQoVoiVOS6MN1WSpqpRcbeA5+RW82kraAVxCCJg+780Qw==", + "dependencies": { + "undici-types": "~5.26.4" + } + }, + "node_modules/@types/ws": { + "version": "8.5.10", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.10.tgz", + "integrity": "sha512-vmQSUcfalpIq0R9q7uTo2lXs6eGIpt9wtnLdMv9LVpIjCA/+ufZRozlVoVelIYixx1ugCBKDhn89vnsEGOCx9A==", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/data-uri-to-buffer": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz", + "integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==", + "engines": { + "node": ">= 12" + } + }, + "node_modules/detect-libc": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.2.tgz", + "integrity": "sha512-UX6sGumvvqSaXgdKGUsgZWqcUyIXZ/vZTrlRT/iobiKhGL0zL4d3osHj3uqllWJK+i+sixDS/3COVEOFbupFyw==", + "engines": { + "node": ">=8" + } + }, + "node_modules/fetch-blob": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz", + "integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "paypal", + "url": "https://paypal.me/jimmywarting" + } + ], + "dependencies": { + "node-domexception": "^1.0.0", + "web-streams-polyfill": "^3.0.3" + }, + "engines": { + "node": "^12.20 || >= 14.13" + } + }, + "node_modules/formdata-polyfill": { + "version": "4.0.10", + "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz", + "integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==", + "dependencies": { + "fetch-blob": "^3.1.2" + }, + "engines": { + "node": ">=12.20.0" + } + }, + "node_modules/js-base64": { + "version": "3.7.7", + "resolved": "https://registry.npmjs.org/js-base64/-/js-base64-3.7.7.tgz", + "integrity": "sha512-7rCnleh0z2CkXhH67J8K1Ytz0b2Y+yxTPL+/KOJoa20hfnVQ/3/T6W/KflYI4bRHRagNeXeU2bkNGI3v1oS/lw==" + }, + "node_modules/libsql": { + "version": "0.3.18", + "resolved": "https://registry.npmjs.org/libsql/-/libsql-0.3.18.tgz", + "integrity": "sha512-lvhKr7WV3NLWRbXkjn/MeKqXOAqWKU0PX9QYrvDh7fneukapj+iUQ4qgJASrQyxcCrEsClXCQiiK5W6OoYPAlA==", + "cpu": [ + "x64", + "arm64", + "wasm32" + ], + "os": [ + "darwin", + "linux", + "win32" + ], + "dependencies": { + "@neon-rs/load": "^0.0.4", + "detect-libc": "2.0.2", + "libsql": "^0.3.15" + }, + "optionalDependencies": { + "@libsql/darwin-arm64": "0.3.18", + "@libsql/darwin-x64": "0.3.18", + "@libsql/linux-arm64-gnu": "0.3.18", + "@libsql/linux-arm64-musl": "0.3.18", + "@libsql/linux-x64-gnu": "0.3.18", + "@libsql/linux-x64-musl": "0.3.18", + "@libsql/win32-x64-msvc": "0.3.18" + } + }, + "node_modules/node-domexception": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", + "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "github", + "url": "https://paypal.me/jimmywarting" + } + ], + "engines": { + "node": ">=10.5.0" + } + }, + "node_modules/node-fetch": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz", + "integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==", + "dependencies": { + "data-uri-to-buffer": "^4.0.0", + "fetch-blob": "^3.1.4", + "formdata-polyfill": "^4.0.10" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/node-fetch" + } + }, + "node_modules/typescript": { + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.3.3.tgz", + "integrity": "sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "5.26.5", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==" + }, + "node_modules/web-streams-polyfill": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz", + "integrity": "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==", + "engines": { + "node": ">= 8" + } + }, + "node_modules/ws": { + "version": "8.17.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.0.tgz", + "integrity": "sha512-uJq6108EgZMAl20KagGkzCKfMEjxmKvZHG7Tlq0Z6nOky7YF7aq4mOx6xK8TJ/i1LeK4Qus7INktacctDgY8Ow==", + "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 + } + } + } + } +} diff --git a/examples/node-turso/package.json b/examples/node-turso/package.json new file mode 100644 index 0000000..6422071 --- /dev/null +++ b/examples/node-turso/package.json @@ -0,0 +1,17 @@ +{ + "name": "turso", + "version": "1.0.0", + "description": "", + "main": "src/main.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "author": "", + "license": "ISC", + "devDependencies": { + "typescript": "^5.2.2" + }, + "dependencies": { + "@libsql/client": "0.6.2" + } +} diff --git a/examples/node-turso/src/db/query_sql.ts b/examples/node-turso/src/db/query_sql.ts new file mode 100644 index 0000000..988f863 --- /dev/null +++ b/examples/node-turso/src/db/query_sql.ts @@ -0,0 +1,81 @@ +// Code generated by sqlc. DO NOT EDIT. + +import { Client } from "@libsql/client"; + +export const getAuthorQuery = `-- name: GetAuthor :one +SELECT id, name, bio FROM authors +WHERE id = ? LIMIT 1`; + +export interface GetAuthorArgs { + id: any; +} + +export interface GetAuthorRow { + id: any; + name: any; + bio: any | null; +} + +export async function getAuthor(client: Client, args: GetAuthorArgs): Promise { + const result = await client.execute({ + sql: getAuthorQuery, + args: [args.id] + }); + if (result == undefined) { + return null; + } + return result.rows.length ? result.rows[0] as unknown as GetAuthorRow : null; +} + +export const listAuthorsQuery = `-- name: ListAuthors :many +SELECT id, name, bio FROM authors +ORDER BY name`; + +export interface ListAuthorsRow { + id: any; + name: any; + bio: any | null; +} + +export async function listAuthors(client: Client): Promise { + const result = await client.execute({ + sql: listAuthorsQuery, + args: [] + }); + return result.rows as ListAuthorsRow[]; +} + +export const createAuthorQuery = `-- name: CreateAuthor :exec +INSERT INTO authors ( + name, bio +) VALUES ( + ?, ? +)`; + +export interface CreateAuthorArgs { + name: any; + bio: any | null; +} + +export async function createAuthor(client: Client, args: CreateAuthorArgs): Promise { + await client.execute({ + sql: createAuthorQuery, + args: [args.name, args.bio] + }); +} + +export const deleteAuthorQuery = `-- name: DeleteAuthor :exec +DELETE FROM authors +WHERE id = ?`; + +export interface DeleteAuthorArgs { + id: any; +} + +export async function deleteAuthor(client: Client, args: DeleteAuthorArgs): Promise { + await client.execute({ + sql: deleteAuthorQuery, + args: [args.id] + }); +} + diff --git a/examples/node-turso/src/main.ts b/examples/node-turso/src/main.ts new file mode 100644 index 0000000..5ee0ee4 --- /dev/null +++ b/examples/node-turso/src/main.ts @@ -0,0 +1,54 @@ +import Database from "better-sqlite3"; +import { readFile } from "node:fs/promises"; +import { join } from "node:path"; + +import { + createAuthor, + deleteAuthor, + getAuthor, + listAuthors, +} from "./db/query_sql"; + +interface Author { + id: string; + name: string; + bio: string | null; +} + +async function main() { + const ddl = await readFile(join(__dirname, "../../authors/sqlite/schema.sql"), { encoding: 'utf-8' }); + const database = new Database(":memory:"); + + // Create tables + database.exec(ddl); + + // Create an author + await createAuthor(database, { + name: "Seal", + bio: "Kissed from a rose", + }); + + // List the authors + const authors = await listAuthors(database); + console.log(authors); + + // Get that author + const seal = await getAuthor(database, { id: authors[0].id }); + if (seal === null) { + throw new Error("seal not found"); + } + console.log(seal); + + // Delete the author + await deleteAuthor(database, { id: seal.id }); +} + +(async () => { + try { + await main(); + process.exit(0); + } catch (e) { + console.error(e); + process.exit(1); + } +})(); diff --git a/examples/node-turso/tsconfig.json b/examples/node-turso/tsconfig.json new file mode 100644 index 0000000..3466896 --- /dev/null +++ b/examples/node-turso/tsconfig.json @@ -0,0 +1,109 @@ +{ + "compilerOptions": { + /* Visit https://aka.ms/tsconfig to read more about this file */ + + /* Projects */ + // "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */ + // "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */ + // "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */ + // "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */ + // "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */ + // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */ + + /* Language and Environment */ + "target": "es2020", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */ + // "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */ + // "jsx": "preserve", /* Specify what JSX code is generated. */ + // "experimentalDecorators": true, /* Enable experimental support for legacy experimental decorators. */ + // "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */ + // "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */ + // "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */ + // "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */ + // "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */ + // "noLib": true, /* Disable including any library files, including the default lib.d.ts. */ + // "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */ + // "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */ + + /* Modules */ + "module": "commonjs", /* Specify what module code is generated. */ + // "rootDir": "./", /* Specify the root folder within your source files. */ + // "moduleResolution": "node10", /* Specify how TypeScript looks up a file from a given module specifier. */ + // "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */ + // "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */ + // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */ + // "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */ + // "types": [], /* Specify type package names to be included without being referenced in a source file. */ + // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ + // "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */ + // "allowImportingTsExtensions": true, /* Allow imports to include TypeScript file extensions. Requires '--moduleResolution bundler' and either '--noEmit' or '--emitDeclarationOnly' to be set. */ + // "resolvePackageJsonExports": true, /* Use the package.json 'exports' field when resolving package imports. */ + // "resolvePackageJsonImports": true, /* Use the package.json 'imports' field when resolving imports. */ + // "customConditions": [], /* Conditions to set in addition to the resolver-specific defaults when resolving imports. */ + // "resolveJsonModule": true, /* Enable importing .json files. */ + // "allowArbitraryExtensions": true, /* Enable importing files with any extension, provided a declaration file is present. */ + // "noResolve": true, /* Disallow 'import's, 'require's or ''s from expanding the number of files TypeScript should add to a project. */ + + /* JavaScript Support */ + // "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */ + // "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */ + // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */ + + /* Emit */ + // "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */ + // "declarationMap": true, /* Create sourcemaps for d.ts files. */ + // "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */ + // "sourceMap": true, /* Create source map files for emitted JavaScript files. */ + // "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */ + // "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */ + // "outDir": "./", /* Specify an output folder for all emitted files. */ + // "removeComments": true, /* Disable emitting comments. */ + // "noEmit": true, /* Disable emitting files from a compilation. */ + // "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */ + // "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types. */ + // "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */ + // "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */ + // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ + // "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */ + // "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */ + // "newLine": "crlf", /* Set the newline character for emitting files. */ + // "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */ + // "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */ + // "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */ + // "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */ + // "declarationDir": "./", /* Specify the output directory for generated declaration files. */ + // "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */ + + /* Interop Constraints */ + // "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */ + // "verbatimModuleSyntax": true, /* Do not transform or elide any imports or exports not marked as type-only, ensuring they are written in the output file's format based on the 'module' setting. */ + // "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */ + "esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */ + // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */ + "forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */ + + /* Type Checking */ + "strict": true, /* Enable all strict type-checking options. */ + // "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */ + // "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */ + // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */ + // "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */ + // "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */ + // "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */ + // "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */ + // "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */ + // "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */ + // "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */ + // "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */ + // "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */ + // "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */ + // "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */ + // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */ + // "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */ + // "allowUnusedLabels": true, /* Disable error reporting for unused labels. */ + // "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */ + + /* Completeness */ + // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */ + "skipLibCheck": true /* Skip type checking all .d.ts files. */ + } +} diff --git a/examples/sqlc.dev.yaml b/examples/sqlc.dev.yaml index f622ec1..5b9eeac 100644 --- a/examples/sqlc.dev.yaml +++ b/examples/sqlc.dev.yaml @@ -70,3 +70,12 @@ sql: options: runtime: node driver: better-sqlite3 +- schema: "authors/sqlite/schema.sql" + queries: "authors/sqlite/query.sql" + engine: "sqlite" + codegen: + - plugin: ts + out: node-turso/src/db + options: + runtime: node + driver: turso diff --git a/package-lock.json b/package-lock.json index e0e0d13..ee5eb41 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,7 +10,8 @@ "license": "ISC", "dependencies": { "@bufbuild/protobuf": "^1.4.2", - "javy": "^0.1.2" + "javy": "^0.1.2", + "pg": "^8.11.3" }, "devDependencies": { "esbuild": "^0.19.5", @@ -416,6 +417,130 @@ "resolved": "https://registry.npmjs.org/javy/-/javy-0.1.2.tgz", "integrity": "sha512-z6Z+CV13SXBGxmY5UseWsYNVVR4SWPfOixKGluWi1Dpn594+M2JaGPBrFIIMnFFfi7kJzHCkcTn7CrhJ7JGKsA==" }, + "node_modules/pg": { + "version": "8.12.0", + "resolved": "https://registry.npmjs.org/pg/-/pg-8.12.0.tgz", + "integrity": "sha512-A+LHUSnwnxrnL/tZ+OLfqR1SxLN3c/pgDztZ47Rpbsd4jUytsTtwQo/TLPRzPJMp/1pbhYVhH9cuSZLAajNfjQ==", + "dependencies": { + "pg-connection-string": "^2.6.4", + "pg-pool": "^3.6.2", + "pg-protocol": "^1.6.1", + "pg-types": "^2.1.0", + "pgpass": "1.x" + }, + "engines": { + "node": ">= 8.0.0" + }, + "optionalDependencies": { + "pg-cloudflare": "^1.1.1" + }, + "peerDependencies": { + "pg-native": ">=3.0.1" + }, + "peerDependenciesMeta": { + "pg-native": { + "optional": true + } + } + }, + "node_modules/pg-cloudflare": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/pg-cloudflare/-/pg-cloudflare-1.1.1.tgz", + "integrity": "sha512-xWPagP/4B6BgFO+EKz3JONXv3YDgvkbVrGw2mTo3D6tVDQRh1e7cqVGvyR3BE+eQgAvx1XhW/iEASj4/jCWl3Q==", + "optional": true + }, + "node_modules/pg-connection-string": { + "version": "2.6.4", + "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.6.4.tgz", + "integrity": "sha512-v+Z7W/0EO707aNMaAEfiGnGL9sxxumwLl2fJvCQtMn9Fxsg+lPpPkdcyBSv/KFgpGdYkMfn+EI1Or2EHjpgLCA==" + }, + "node_modules/pg-int8": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/pg-int8/-/pg-int8-1.0.1.tgz", + "integrity": "sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==", + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/pg-pool": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-3.6.2.tgz", + "integrity": "sha512-Htjbg8BlwXqSBQ9V8Vjtc+vzf/6fVUuak/3/XXKA9oxZprwW3IMDQTGHP+KDmVL7rtd+R1QjbnCFPuTHm3G4hg==", + "peerDependencies": { + "pg": ">=8.0" + } + }, + "node_modules/pg-protocol": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.6.1.tgz", + "integrity": "sha512-jPIlvgoD63hrEuihvIg+tJhoGjUsLPn6poJY9N5CnlPd91c2T18T/9zBtLxZSb1EhYxBRoZJtzScCaWlYLtktg==" + }, + "node_modules/pg-types": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/pg-types/-/pg-types-2.2.0.tgz", + "integrity": "sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==", + "dependencies": { + "pg-int8": "1.0.1", + "postgres-array": "~2.0.0", + "postgres-bytea": "~1.0.0", + "postgres-date": "~1.0.4", + "postgres-interval": "^1.1.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/pgpass": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/pgpass/-/pgpass-1.0.5.tgz", + "integrity": "sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug==", + "dependencies": { + "split2": "^4.1.0" + } + }, + "node_modules/postgres-array": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-2.0.0.tgz", + "integrity": "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==", + "engines": { + "node": ">=4" + } + }, + "node_modules/postgres-bytea": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/postgres-bytea/-/postgres-bytea-1.0.0.tgz", + "integrity": "sha512-xy3pmLuQqRBZBXDULy7KbaitYqLcmxigw14Q5sj8QBVLqEwXfeybIKVWiqAXTlcvdvb0+xkOtDbfQMOf4lST1w==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/postgres-date": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/postgres-date/-/postgres-date-1.0.7.tgz", + "integrity": "sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/postgres-interval": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/postgres-interval/-/postgres-interval-1.2.0.tgz", + "integrity": "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==", + "dependencies": { + "xtend": "^4.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/split2": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz", + "integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==", + "engines": { + "node": ">= 10.x" + } + }, "node_modules/typescript": { "version": "5.2.2", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.2.2.tgz", @@ -428,6 +553,14 @@ "engines": { "node": ">=14.17" } + }, + "node_modules/xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "engines": { + "node": ">=0.4" + } } } } diff --git a/src/app.ts b/src/app.ts index e3a077c..9446ce5 100644 --- a/src/app.ts +++ b/src/app.ts @@ -29,6 +29,7 @@ import { import { argName, colName } from "./drivers/utlis"; import { Driver as Sqlite3Driver } from "./drivers/better-sqlite3"; +import { Driver as TursoDriver } from "./drivers/turso"; import { Driver as PgDriver } from "./drivers/pg"; import { Driver as PostgresDriver } from "./drivers/postgres"; import { Mysql2Options, Driver as MysqlDriver } from "./drivers/mysql2"; @@ -93,6 +94,9 @@ function createNodeGenerator(options: Options): Driver { case "better-sqlite3": { return new Sqlite3Driver(); } + case "turso": { + return new TursoDriver(); + } } throw new Error(`unknown driver: ${options.driver}`); } diff --git a/src/drivers/turso.ts b/src/drivers/turso.ts new file mode 100644 index 0000000..f9226e7 --- /dev/null +++ b/src/drivers/turso.ts @@ -0,0 +1,435 @@ +import { + SyntaxKind, + NodeFlags, + Node, + TypeNode, + factory, + FunctionDeclaration, +} from "typescript"; + +import { Parameter, Column, Query } from "../gen/plugin/codegen_pb"; +import { argName } from "./utlis"; + +function funcParamsDecl(iface: string | undefined, params: Parameter[]) { + let funcParams = [ + factory.createParameterDeclaration( + undefined, + undefined, + factory.createIdentifier("client"), + undefined, + factory.createTypeReferenceNode( + factory.createIdentifier("Client"), + undefined + ), + undefined + ), + ]; + + if (iface && params.length > 0) { + funcParams.push( + factory.createParameterDeclaration( + undefined, + undefined, + factory.createIdentifier("args"), + undefined, + factory.createTypeReferenceNode( + factory.createIdentifier(iface), + undefined + ), + undefined + ) + ); + } + + return funcParams; +} + +export class Driver { + /** + * {@link https://github.com/WiseLibs/better-sqlite3/blob/v9.4.1/docs/api.md#binding-parameters} + * {@link https://github.com/sqlc-dev/sqlc/blob/v1.25.0/internal/codegen/golang/sqlite_type.go} + */ + columnType(column?: Column): TypeNode { + if (column === undefined || column.type === undefined) { + return factory.createKeywordTypeNode(SyntaxKind.AnyKeyword); + } + + let typ: TypeNode = factory.createKeywordTypeNode(SyntaxKind.AnyKeyword); + switch (column.type.name) { + case "int": + case "integer": + case "tinyint": + case "smallint": + case "mediumint": + case "bigint": + case "unsignedbigint": + case "int2": + case "int8": { + // TODO: Improve `BigInt` handling (https://github.com/WiseLibs/better-sqlite3/blob/v9.4.1/docs/integer.md) + typ = factory.createKeywordTypeNode(SyntaxKind.NumberKeyword); + break; + } + case "blob": { + // TODO: Is this correct or node-specific? + typ = factory.createTypeReferenceNode( + factory.createIdentifier("Buffer"), + undefined + ); + break; + } + case "real": + case "double": + case "doubleprecision": + case "float": { + typ = factory.createKeywordTypeNode(SyntaxKind.NumberKeyword); + break; + } + case "boolean": + case "bool": { + typ = factory.createKeywordTypeNode(SyntaxKind.BooleanKeyword); + break; + } + case "date": + case "datetime": + case "timestamp": { + typ = factory.createTypeReferenceNode( + factory.createIdentifier("Date"), + undefined + ); + break; + } + } + + if (column.notNull) { + return typ; + } + + return factory.createUnionTypeNode([ + typ, + factory.createLiteralTypeNode(factory.createNull()), + ]); + } + + preamble(queries: Query[]) { + const imports: Node[] = [ + factory.createImportDeclaration( + undefined, + factory.createImportClause( + false, + undefined, + factory.createNamedImports([ + factory.createImportSpecifier( + false, + undefined, + factory.createIdentifier("Client") + ), + ]) + ), + factory.createStringLiteral("@libsql/client"), + undefined + ), + ]; + + return imports; + } + + execDecl( + funcName: string, + queryName: string, + argIface: string | undefined, + params: Parameter[] + ) { + const funcParams = funcParamsDecl(argIface, params); + + return factory.createFunctionDeclaration( + [ + factory.createToken(SyntaxKind.ExportKeyword), + factory.createToken(SyntaxKind.AsyncKeyword), + ], + undefined, + factory.createIdentifier(funcName), + undefined, + funcParams, + factory.createTypeReferenceNode(factory.createIdentifier("Promise"), [ + factory.createKeywordTypeNode(SyntaxKind.VoidKeyword), + ]), + factory.createBlock( + [ + factory.createExpressionStatement( + factory.createAwaitExpression( + factory.createCallExpression( + factory.createPropertyAccessExpression( + factory.createIdentifier("client"), + factory.createIdentifier("execute") + ), + undefined, + [ + factory.createObjectLiteralExpression( + [ + factory.createPropertyAssignment( + factory.createIdentifier("sql"), + factory.createIdentifier(queryName) + ), + factory.createPropertyAssignment( + factory.createIdentifier("args"), + factory.createArrayLiteralExpression( + params.map((param, i) => + factory.createPropertyAccessExpression( + factory.createIdentifier("args"), + factory.createIdentifier(argName(i, param.column)) + ) + ) + ) + ), + ], + true + ) + ] + ) + ) + ), + ], + true + ) + ); + } + + oneDecl( + funcName: string, + queryName: string, + argIface: string | undefined, + returnIface: string, + params: Parameter[], + columns: Column[] + ) { + const funcParams = funcParamsDecl(argIface, params); + + return factory.createFunctionDeclaration( + [ + factory.createToken(SyntaxKind.ExportKeyword), + factory.createToken(SyntaxKind.AsyncKeyword), + ], + undefined, + factory.createIdentifier(funcName), + undefined, + funcParams, + factory.createTypeReferenceNode(factory.createIdentifier("Promise"), [ + factory.createUnionTypeNode([ + factory.createTypeReferenceNode( + factory.createIdentifier(returnIface), + undefined + ), + factory.createLiteralTypeNode(factory.createNull()), + ]), + ]), + factory.createBlock( + [ + factory.createVariableStatement( + undefined, + factory.createVariableDeclarationList( + [ + factory.createVariableDeclaration( + factory.createIdentifier("result"), + undefined, + undefined, + factory.createAwaitExpression( + factory.createCallExpression( + factory.createPropertyAccessExpression( + factory.createIdentifier("client"), + factory.createIdentifier("execute") + ), + undefined, + [ + factory.createObjectLiteralExpression( + [ + factory.createPropertyAssignment( + factory.createIdentifier("sql"), + factory.createIdentifier(queryName) + ), + factory.createPropertyAssignment( + factory.createIdentifier("args"), + factory.createArrayLiteralExpression( + params.map((param, i) => + factory.createPropertyAccessExpression( + factory.createIdentifier("args"), + factory.createIdentifier(argName(i, param.column)) + ) + ) + ) + ), + ], + true + ) + ] + ) + ) + ), + ], + NodeFlags.Const | + // ts.NodeFlags.Constant | + NodeFlags.AwaitContext | + // ts.NodeFlags.Constant | + NodeFlags.ContextFlags | + NodeFlags.TypeExcludesFlags + ) + ), + factory.createIfStatement( + factory.createBinaryExpression( + factory.createIdentifier("result"), + factory.createToken(SyntaxKind.EqualsEqualsToken), + factory.createIdentifier("undefined") + ), + factory.createBlock( + [factory.createReturnStatement(factory.createNull())], + true + ), + undefined + ), + factory.createReturnStatement( + factory.createConditionalExpression( + factory.createPropertyAccessExpression( + factory.createPropertyAccessExpression( + factory.createIdentifier("result"), + factory.createIdentifier("rows") + ), + factory.createIdentifier("length") + ), + factory.createToken(SyntaxKind.QuestionToken), + factory.createAsExpression( + factory.createAsExpression( + factory.createElementAccessExpression( + factory.createPropertyAccessExpression( + factory.createIdentifier("result"), + factory.createIdentifier("rows") + ), + factory.createNumericLiteral("0") + ), + factory.createTypeReferenceNode( + factory.createIdentifier("unknown"), + undefined + ) + ), + factory.createTypeReferenceNode( + factory.createIdentifier(returnIface), + undefined + ) + ), + factory.createToken(SyntaxKind.ColonToken), + factory.createNull() + ), + ), + ], + true + ) + ); + } + + manyDecl( + funcName: string, + queryName: string, + argIface: string | undefined, + returnIface: string, + params: Parameter[], + columns: Column[] + ) { + const funcParams = funcParamsDecl(argIface, params); + + return factory.createFunctionDeclaration( + [ + factory.createToken(SyntaxKind.ExportKeyword), + factory.createToken(SyntaxKind.AsyncKeyword), + ], + undefined, + factory.createIdentifier(funcName), + undefined, + funcParams, + factory.createTypeReferenceNode(factory.createIdentifier("Promise"), [ + factory.createArrayTypeNode( + factory.createTypeReferenceNode( + factory.createIdentifier(returnIface), + undefined + ) + ), + ]), + factory.createBlock( + [ + factory.createVariableStatement( + undefined, + factory.createVariableDeclarationList( + [ + factory.createVariableDeclaration( + factory.createIdentifier("result"), + undefined, + undefined, + factory.createAwaitExpression( + factory.createCallExpression( + factory.createPropertyAccessExpression( + factory.createIdentifier("client"), + factory.createIdentifier("execute") + ), + undefined, + [ + factory.createObjectLiteralExpression( + [ + factory.createPropertyAssignment( + factory.createIdentifier("sql"), + factory.createIdentifier(queryName) + ), + factory.createPropertyAssignment( + factory.createIdentifier("args"), + factory.createArrayLiteralExpression( + params.map((param, i) => + factory.createPropertyAccessExpression( + factory.createIdentifier("args"), + factory.createIdentifier(argName(i, param.column)) + ) + ) + ) + ), + ], + true + ) + ] + ) + ) + ), + ], + NodeFlags.Const | + // NodeFlags.Constant | + NodeFlags.AwaitContext | + // NodeFlags.Constant | + NodeFlags.ContextFlags | + NodeFlags.TypeExcludesFlags + ) + ), + factory.createReturnStatement( + factory.createAsExpression( + factory.createPropertyAccessExpression( + factory.createIdentifier("result"), + factory.createIdentifier("rows") + ), + factory.createArrayTypeNode( + factory.createTypeReferenceNode( + factory.createIdentifier(returnIface), + undefined + ) + ) + ) + ), + ], + true + ) + ); + } + + execlastidDecl( + funcName: string, + queryName: string, + argIface: string | undefined, + params: Parameter[] + ): FunctionDeclaration { + throw new Error( + "better-sqlite3 driver currently does not support :execlastid" + ); + } +}