From 11d90b3960bd84c17d65f4224dd0b8aacbe399d1 Mon Sep 17 00:00:00 2001 From: earthlingdavey <15802017+earthlingdavey@users.noreply.github.com> Date: Tue, 3 Mar 2026 18:39:40 +0000 Subject: [PATCH 1/6] Att CI --- .github/workflows/example-publish.yml | 48 ++++++ .github/workflows/example-test-patch.yml | 187 ++++++++++++++++++++++ .github/workflows/publish.yml | 48 ++++++ .github/workflows/test.yml | 45 ++++++ Makefile | 4 +- README.md | 2 +- REVIEW.md | 189 ----------------------- deno.jsonc | 3 +- 8 files changed, 332 insertions(+), 194 deletions(-) create mode 100644 .github/workflows/example-publish.yml create mode 100644 .github/workflows/example-test-patch.yml create mode 100644 .github/workflows/publish.yml create mode 100644 .github/workflows/test.yml delete mode 100644 REVIEW.md diff --git a/.github/workflows/example-publish.yml b/.github/workflows/example-publish.yml new file mode 100644 index 0000000..716ae5f --- /dev/null +++ b/.github/workflows/example-publish.yml @@ -0,0 +1,48 @@ +name: Publish to JSR + +on: + push: + tags: + - "v*" + +jobs: + publish: + runs-on: ubuntu-latest + + permissions: + contents: read + id-token: write # Required for JSR provenance + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup Deno + uses: denoland/setup-deno@v2 + with: + deno-version: v2.x + + - name: Verify version matches tag + run: | + TAG_VERSION="${GITHUB_REF#refs/tags/v}" + PACKAGE_VERSION=$(deno eval "const c = JSON.parse(await Deno.readTextFile('deno.jsonc').then(t => t.replace(/\/\/.*/g, ''))); console.log(c.version)") + if [ "$TAG_VERSION" != "$PACKAGE_VERSION" ]; then + echo "❌ Tag version ($TAG_VERSION) does not match package version ($PACKAGE_VERSION)" + exit 1 + fi + echo "✅ Version $TAG_VERSION matches" + + - name: Clone Drizzle shared test suite + run: make clone-repo + + - name: Run tests + run: deno task test + + - name: Show lockfile changes (if any) + run: git diff deno.lock || true + + - name: Restore lockfile (tests may modify it due to Deno version differences) + run: git checkout deno.lock + + - name: Publish to JSR + run: deno publish diff --git a/.github/workflows/example-test-patch.yml b/.github/workflows/example-test-patch.yml new file mode 100644 index 0000000..322f50e --- /dev/null +++ b/.github/workflows/example-test-patch.yml @@ -0,0 +1,187 @@ +name: Test Patch Compatibility + +on: + push: + branches: [main] + paths: + - "scripts/patch-drizzle-kit.ts" + - "scripts/pgsql/**" + - "scripts/libsql/**" + - ".github/workflows/test-patch.yml" + pull_request: + branches: [main] + paths: + - "scripts/patch-drizzle-kit.ts" + - "scripts/pgsql/**" + - "scripts/libsql/**" + - ".github/workflows/test-patch.yml" + workflow_dispatch: + inputs: + versions: + description: 'Versions to test (comma-separated, or "all" for all supported)' + required: false + default: "all" + quick: + description: "Quick mode (patch only, skip runtime tests)" + required: false + type: boolean + default: false + +jobs: + test-pglite: + name: Test PGlite - drizzle-kit@${{ matrix.version }} + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + version: ["0.30.6", "0.31.8", "0.31.9"] + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup Deno + uses: denoland/setup-deno@v2 + with: + deno-version: v2.x + + - name: Ensure latest Deno + run: | + INSTALLED=$(deno --version | head -1 | cut -d' ' -f2) + LATEST=$(curl -s https://dl.deno.land/release-latest.txt | tr -d 'v') + echo "Installed: $INSTALLED" + echo "Latest: $LATEST" + if [ "$INSTALLED" != "$LATEST" ]; then + echo "Upgrading Deno from $INSTALLED to $LATEST..." + deno upgrade + fi + + - name: Cache Deno dependencies + uses: actions/cache@v4 + with: + path: | + ~/.cache/deno + ~/.deno + # Include patch script in cache key so cache busts when patch version changes + # No restore-keys fallback - stale caches have patched npm packages that can't be re-patched + key: ${{ runner.os }}-deno-${{ hashFiles('deno.jsonc', 'deno.lock', 'scripts/patch-drizzle-kit.ts') }} + + - name: Install dependencies + run: deno install + + - name: Cache test script dependencies + run: deno cache --config deno.jsonc scripts/pgsql/test-patch.ts + + - name: Clean test directory + run: rm -rf .test-patch + + - name: Clear drizzle-kit from npm cache + # Ensures we get fresh unpatched drizzle-kit for each test run + run: rm -rf ~/.cache/deno/npm/registry.npmjs.org/drizzle-kit + + - name: Run PGlite test for drizzle-kit@${{ matrix.version }} + run: deno run --allow-read=scripts,deno.jsonc,.test-patch --allow-write=.test-patch --allow-run=deno --allow-net scripts/pgsql/test-patch.ts ${{ matrix.version }} + + test-libsql: + name: Test LibSQL - drizzle-kit@${{ matrix.version }} + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + version: ["0.30.6", "0.31.8", "0.31.9"] + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup Deno + uses: denoland/setup-deno@v2 + with: + deno-version: v2.x + + - name: Ensure latest Deno + run: | + INSTALLED=$(deno --version | head -1 | cut -d' ' -f2) + LATEST=$(curl -s https://dl.deno.land/release-latest.txt | tr -d 'v') + echo "Installed: $INSTALLED" + echo "Latest: $LATEST" + if [ "$INSTALLED" != "$LATEST" ]; then + echo "Upgrading Deno from $INSTALLED to $LATEST..." + deno upgrade + fi + + - name: Cache Deno dependencies + uses: actions/cache@v4 + with: + path: | + ~/.cache/deno + ~/.deno + key: ${{ runner.os }}-deno-libsql-${{ hashFiles('deno.jsonc', 'deno.lock', 'scripts/patch-drizzle-kit.ts') }} + + - name: Install dependencies + run: deno install + + - name: Cache test script dependencies + run: deno cache --config deno.jsonc scripts/libsql/test-patch.ts + + - name: Clean test directory + run: rm -rf .test-patch-sqlite + + - name: Clear drizzle-kit from npm cache + run: rm -rf ~/.cache/deno/npm/registry.npmjs.org/drizzle-kit + + - name: Run LibSQL test for drizzle-kit@${{ matrix.version }} + run: deno run --allow-read=scripts,deno.jsonc,.test-patch-sqlite --allow-write=.test-patch-sqlite --allow-run=deno --allow-net --allow-env scripts/libsql/test-patch.ts ${{ matrix.version }} + + # Optional: Run quick tests for all versions in a single job + test-patch-quick: + name: Quick Test - All Versions + runs-on: ubuntu-latest + if: github.event_name == 'pull_request' + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup Deno + uses: denoland/setup-deno@v2 + with: + deno-version: v2.x + + - name: Install dependencies + run: deno install + + - name: Cache test script dependencies + run: deno cache --config deno.jsonc scripts/pgsql/test-patch.ts + + - name: Clean test directory + run: rm -rf .test-patch + + - name: Clear drizzle-kit from npm cache + # Ensures we get fresh unpatched drizzle-kit for each test run + run: rm -rf ~/.cache/deno/npm/registry.npmjs.org/drizzle-kit + + - name: Run quick patch tests + run: deno run --allow-read=scripts,deno.jsonc,.test-patch --allow-write=.test-patch --allow-run=deno --allow-net scripts/pgsql/test-patch.ts --quick + + # Summary job that depends on all matrix jobs + test-summary: + name: Test Summary + runs-on: ubuntu-latest + needs: [test-pglite, test-libsql] + if: always() + + steps: + - name: Check test results + run: | + PGLITE="${{ needs.test-pglite.result }}" + LIBSQL="${{ needs.test-libsql.result }}" + echo "PGlite tests: $PGLITE" + echo "LibSQL tests: $LIBSQL" + if [[ "$PGLITE" == "success" && "$LIBSQL" == "success" ]]; then + echo "✅ All patch compatibility tests passed!" + exit 0 + else + echo "❌ Some patch compatibility tests failed" + exit 1 + fi diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml new file mode 100644 index 0000000..716ae5f --- /dev/null +++ b/.github/workflows/publish.yml @@ -0,0 +1,48 @@ +name: Publish to JSR + +on: + push: + tags: + - "v*" + +jobs: + publish: + runs-on: ubuntu-latest + + permissions: + contents: read + id-token: write # Required for JSR provenance + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup Deno + uses: denoland/setup-deno@v2 + with: + deno-version: v2.x + + - name: Verify version matches tag + run: | + TAG_VERSION="${GITHUB_REF#refs/tags/v}" + PACKAGE_VERSION=$(deno eval "const c = JSON.parse(await Deno.readTextFile('deno.jsonc').then(t => t.replace(/\/\/.*/g, ''))); console.log(c.version)") + if [ "$TAG_VERSION" != "$PACKAGE_VERSION" ]; then + echo "❌ Tag version ($TAG_VERSION) does not match package version ($PACKAGE_VERSION)" + exit 1 + fi + echo "✅ Version $TAG_VERSION matches" + + - name: Clone Drizzle shared test suite + run: make clone-repo + + - name: Run tests + run: deno task test + + - name: Show lockfile changes (if any) + run: git diff deno.lock || true + + - name: Restore lockfile (tests may modify it due to Deno version differences) + run: git checkout deno.lock + + - name: Publish to JSR + run: deno publish diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..edc2b87 --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,45 @@ +name: Test + +on: + push: + branches: [main] + pull_request: + branches: [main] + +jobs: + test: + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup Deno + uses: denoland/setup-deno@v2 + with: + deno-version: v2.x + + - name: Cache Deno dependencies + uses: actions/cache@v4 + with: + path: | + ~/.cache/deno + ~/.deno + key: ${{ runner.os }}-deno-${{ hashFiles('deno.jsonc', 'deno.lock') }} + restore-keys: | + ${{ runner.os }}-deno- + + - name: Clone Drizzle shared test suite + run: make clone-repo + + - name: Install dependencies + run: deno install + + - name: Check formatting + run: deno fmt --check + + - name: Lint + run: deno lint + + - name: Run tests + run: deno task test diff --git a/Makefile b/Makefile index 9b478d8..57b5949 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,7 @@ -.PHONY: clone-repos clean-repos test test-node22 test-node24 test-deno build lint fmt check +.PHONY: clone-repo clean-repos test test-node22 test-node24 test-deno build lint fmt check # Clone Drizzle ORM repo for shared test suite -clone-repos: +clone-repo: @mkdir -p repos @if [ ! -d repos/drizzle-orm ]; then \ git clone --depth 1 https://github.com/drizzle-team/drizzle-orm.git repos/drizzle-orm; \ diff --git a/README.md b/README.md index 97f0d8d..cde6fba 100644 --- a/README.md +++ b/README.md @@ -144,7 +144,7 @@ Tests run in Docker containers to ensure consistent environments. ```bash # Clone Drizzle's shared test suite (required) -make clone-repos +make clone-repo # Run Deno 2.7.2 tests (default) make test-deno-2.7 diff --git a/REVIEW.md b/REVIEW.md deleted file mode 100644 index 5c08f34..0000000 --- a/REVIEW.md +++ /dev/null @@ -1,189 +0,0 @@ -# Code Review: drizzle-runtime-sqlite - -**Reviewer:** GitHub Copilot -**Date:** 2026-03-03 -**Files Reviewed:** `src/callback.ts`, `src/types.ts`, `mod.ts` - ---- - -## Summary - -This is a well-structured Drizzle ORM adapter bridging Deno's `node:sqlite` with the sqlite-proxy interface. The code is clean, documented, and handles version-specific Node.js APIs gracefully. There are several areas for improvement around type safety, error handling, and code organization. - -**Overall Assessment:** Good quality with minor issues - ---- - -## Strengths - -### 1. Clean Documentation -- Comprehensive JSDoc comments with examples -- Clear explanation of version-specific behavior (Node 22.16+/24+) -- Documented limitations (e.g., join column name collision) - -### 2. Good API Design -- Flexible `drizzle()` overloads supporting multiple call patterns -- Clean `createClient()` helper for direct access scenarios -- Proper re-exports for consumer convenience - -### 3. Feature Detection -- Graceful handling of `setReturnArrays` availability -- Fallback to object-to-array conversion for older Node versions - ---- - -## Issues - -### High Priority - -#### 1. Missing Error Handling -**Location:** [callback.ts](src/callback.ts#L46-L81) - -Database operations can throw errors (constraint violations, invalid SQL, etc.). The callbacks should wrap operations in try/catch and handle errors appropriately. - -```typescript -// Current: No error handling -return executeStatement(stmt, params, method); - -// Suggested: -try { - return executeStatement(stmt, params, method); -} catch (error) { - // Consider wrapping in a custom error type or re-throwing with context - throw error; -} -``` - -#### 2. Unnecessary Async/Await -**Location:** [callback.ts](src/callback.ts#L96-L116), [callback.ts](src/callback.ts#L133-L156) - -The callbacks are marked `async` but perform no asynchronous operations—`node:sqlite` is synchronous. This adds unnecessary Promise overhead. - -```typescript -// Current: -return async (sql, params, method): Promise => { - const stmt = db.prepare(sql); - // ...synchronous code... -}; - -// Note: May be intentional to match Drizzle's AsyncRemoteCallback signature -// Consider documenting that this is sync-under-async for compatibility -``` - -### Medium Priority - -#### 3. Code Duplication in Statement Options -**Location:** [callback.ts](src/callback.ts#L99-L110) and [callback.ts](src/callback.ts#L140-L151) - -Statement option application is duplicated between `createCallback` and `createBatchCallback`. - -```typescript -// Consider extracting: -function applyStatementOptions( - stmt: StatementSync, - options: SqliteOptions -): void { - if (options.readBigInts) { - stmt.setReadBigInts(true); - } - // ...etc -} -``` - -#### 4. Loose Typing in ProxyResult -**Location:** [types.ts](src/types.ts#L11-L13) - -```typescript -// Current: -export interface ProxyResult { - rows: any[] | undefined; -} - -// Suggested: -export interface ProxyResult { - rows: unknown[] | unknown[][] | undefined; -} -``` - -#### 5. Unused Type Definitions -**Location:** [types.ts](src/types.ts#L53-L68) - -`ColumnInfo`, `SQLInputValue`, and `SQLOutputValue` are defined but never imported or used. Either remove them or utilize them for better type safety in the callback implementation. - -### Low Priority - -#### 6. Type Assertion Chain -**Location:** [callback.ts](src/callback.ts#L33) - -```typescript -const extStmt = stmt as unknown as ExtendedStatementMethods; -``` - -This pattern appears multiple times. Consider a helper function: - -```typescript -function asExtended(stmt: StatementSync): ExtendedStatementMethods { - return stmt as unknown as ExtendedStatementMethods; -} -``` - -#### 7. Known Limitation with Join Column Names -**Location:** [callback.ts](src/callback.ts#L21-L23) - -The comment correctly documents this limitation: -```typescript -// Note: This doesn't work correctly for joins with duplicate column names. -``` - -This is acceptable for the fallback path, but consider: -- Adding a runtime warning when this scenario is detected -- Documenting this limitation in README.md - -#### 8. BatchItem Interface Location -**Location:** [callback.ts](src/callback.ts#L119-L123) - -`BatchItem` is defined in callback.ts but could live in types.ts with other interfaces for consistency. - ---- - -## Test Coverage Observations - -From [sqlite.test.ts](tests/node/sqlite.test.ts): - -- Good use of Drizzle's official shared test suite -- Proper feature detection for skippping tests -- Several tests skipped due to driver-specific behavior—consider documenting why in the skip list comments - -Tests skipped without array mode: -- `partial join with alias` -- `full join with alias` -- `select from alias` -- `join view as subquery` -- `cross join` - -These should be re-enabled automatically when running on Node 24+. - ---- - -## Security Considerations - -- No SQL injection risks—using parameterized statements correctly -- No credential handling -- File path handling relies on Node.js/Deno sandboxing - ---- - -## Recommendations - -1. **Add error handling** to callbacks with meaningful error messages -2. **Extract statement options application** to a shared helper function -3. **Document the sync-as-async pattern** if intentional for Drizzle compatibility -4. **Clean up unused types** or use them to strengthen type safety -5. **Move `BatchItem`** to types.ts for consistency -6. **Add README documentation** for known limitations with join columns - ---- - -## Conclusion - -The codebase is well-organized and follows good practices for a database adapter library. The main areas for improvement are error handling robustness and minor code organization. The version-detection strategy for Node.js API differences is particularly well done. diff --git a/deno.jsonc b/deno.jsonc index 5e99ad6..4b5cc59 100644 --- a/deno.jsonc +++ b/deno.jsonc @@ -1,6 +1,5 @@ { - "$schema": "https://jsr.io/schema/config-file.v1.json", - "name": "@hotsauce/drizzle-deno-sqlite", + "name": "@hotsauce/drizzle-runtime-sqlite", "version": "0.1.0", "exports": "./mod.ts", "imports": { From 08432c9be9908223290d658d9a356e988068ff42 Mon Sep 17 00:00:00 2001 From: earthlingdavey <15802017+earthlingdavey@users.noreply.github.com> Date: Tue, 3 Mar 2026 18:40:37 +0000 Subject: [PATCH 2/6] deno fmt --- mod.ts | 19 +++++++++++++------ src/callback.ts | 36 +++++++++++++++++++++++++++++------- src/types.ts | 2 -- tests/deno/common.ts | 12 ++++++------ tests/deno/sqlite.test.ts | 33 ++++++++++++++++++++++++++------- tests/deno/utils.ts | 16 ++++++++++------ tests/node/common.ts | 12 ++++++------ tests/node/sqlite.test.ts | 31 +++++++++++++++++++++++++------ tests/node/utils.ts | 16 ++++++++++------ 9 files changed, 125 insertions(+), 52 deletions(-) diff --git a/mod.ts b/mod.ts index decaeee..8a1f73a 100644 --- a/mod.ts +++ b/mod.ts @@ -22,15 +22,19 @@ import { DatabaseSync } from "node:sqlite"; import { drizzle as proxyDrizzle } from "drizzle-orm/sqlite-proxy"; import type { - SqliteRemoteDatabase, - AsyncRemoteCallback, AsyncBatchRemoteCallback, + AsyncRemoteCallback, + SqliteRemoteDatabase, } from "drizzle-orm/sqlite-proxy"; -import { createCallback, createBatchCallback } from "./src/callback.ts"; +import { createBatchCallback, createCallback } from "./src/callback.ts"; import type { DrizzleDenoSqliteConfig } from "./src/types.ts"; -export type { BatchItem, DrizzleDenoSqliteConfig, SqliteOptions } from "./src/types.ts"; -export { createCallback, createBatchCallback } from "./src/callback.ts"; +export type { + BatchItem, + DrizzleDenoSqliteConfig, + SqliteOptions, +} from "./src/types.ts"; +export { createBatchCallback, createCallback } from "./src/callback.ts"; /** * Create a Drizzle ORM instance backed by node:sqlite. @@ -71,7 +75,10 @@ export function drizzle< export function drizzle< TSchema extends Record = Record, >( - clientOrPathOrConfig?: DatabaseSync | string | DrizzleDenoSqliteConfig, + clientOrPathOrConfig?: + | DatabaseSync + | string + | DrizzleDenoSqliteConfig, maybeConfig?: DrizzleDenoSqliteConfig, ): SqliteRemoteDatabase { let client: DatabaseSync; diff --git a/src/callback.ts b/src/callback.ts index d358525..0f2bf57 100644 --- a/src/callback.ts +++ b/src/callback.ts @@ -4,8 +4,17 @@ * Clean room implementation based on Drizzle's sqlite-proxy contract. */ -import type { DatabaseSync, StatementSync, SupportedValueType } from "node:sqlite"; -import type { BatchItem, ProxyMethod, ProxyResult, SqliteOptions } from "./types.ts"; +import type { + DatabaseSync, + StatementSync, + SupportedValueType, +} from "node:sqlite"; +import type { + BatchItem, + ProxyMethod, + ProxyResult, + SqliteOptions, +} from "./types.ts"; /** * Extended StatementSync interface for newer Node.js APIs not yet in Deno's types. @@ -38,7 +47,9 @@ function applyStatementOptions( stmt.setAllowBareNamedParameters(options.allowBareNamedParameters); } if (options.allowUnknownNamedParameters !== undefined) { - asExtended(stmt).setAllowUnknownNamedParameters(options.allowUnknownNamedParameters); + asExtended(stmt).setAllowUnknownNamedParameters( + options.allowUnknownNamedParameters, + ); } } @@ -57,7 +68,8 @@ function rowToArray( * Check if the statement supports setReturnArrays (Node 22.16+/24+) */ function hasArrayMode(stmt: StatementSync): boolean { - return typeof (stmt as unknown as ExtendedStatementMethods).setReturnArrays === "function"; + return typeof (stmt as unknown as ExtendedStatementMethods) + .setReturnArrays === "function"; } /** @@ -88,7 +100,10 @@ function executeStatement( return { rows: [] }; } case "get": { - const row = stmt.get(...typedParams) as unknown[] | Record | undefined; + const row = stmt.get(...typedParams) as + | unknown[] + | Record + | undefined; if (row === undefined) { return { rows: undefined }; } @@ -102,7 +117,10 @@ function executeStatement( } case "all": case "values": { - const rows = stmt.all(...typedParams) as unknown[][] | Record[]; + const rows = stmt.all(...typedParams) as unknown[][] | Record< + string, + unknown + >[]; if (rows.length === 0) { return { rows: [] }; } @@ -112,7 +130,11 @@ function executeStatement( } // Convert objects to arrays using column metadata const columns = extStmt.columns(); - return { rows: (rows as Record[]).map((row) => rowToArray(row, columns)) }; + return { + rows: (rows as Record[]).map((row) => + rowToArray(row, columns) + ), + }; } default: { throw new Error(`Unknown method: ${method}`); diff --git a/src/types.ts b/src/types.ts index 3753572..fe84d63 100644 --- a/src/types.ts +++ b/src/types.ts @@ -63,5 +63,3 @@ export interface DrizzleDenoSqliteConfig< */ logger?: boolean; } - - diff --git a/tests/deno/common.ts b/tests/deno/common.ts index a9bd0bb..31286ed 100644 --- a/tests/deno/common.ts +++ b/tests/deno/common.ts @@ -2,12 +2,12 @@ * Common test utilities (from Drizzle's integration-tests/tests/common.ts) */ -import { beforeEach } from 'vitest'; +import { beforeEach } from "vitest"; export function skipTests(names: string[]) { - beforeEach((ctx) => { - if (ctx.task.suite?.name === 'common' && names.includes(ctx.task.name)) { - ctx.skip(); - } - }); + beforeEach((ctx) => { + if (ctx.task.suite?.name === "common" && names.includes(ctx.task.name)) { + ctx.skip(); + } + }); } diff --git a/tests/deno/sqlite.test.ts b/tests/deno/sqlite.test.ts index 8115f62..504a64f 100644 --- a/tests/deno/sqlite.test.ts +++ b/tests/deno/sqlite.test.ts @@ -1,6 +1,6 @@ /** * Integration tests using Drizzle's official shared test suite. - * + * * Runs the same tests as Node but using Deno's built-in node:sqlite. */ @@ -9,7 +9,11 @@ import type { SqliteRemoteDatabase } from "drizzle-orm/sqlite-proxy"; import { drizzle as proxyDrizzle } from "drizzle-orm/sqlite-proxy"; import { afterAll, beforeAll, beforeEach, expect, test } from "vitest"; import { skipTests } from "./common.ts"; -import { createCallback, createBatchCallback, createClient } from "../../mod.ts"; +import { + createBatchCallback, + createCallback, + createClient, +} from "../../mod.ts"; import { tests, usersTable, @@ -70,7 +74,9 @@ const requiresArrayMode = [ "cross join", ]; -skipTests(supportsArrayMode ? alwaysSkip : [...alwaysSkip, ...requiresArrayMode]); +skipTests( + supportsArrayMode ? alwaysSkip : [...alwaysSkip, ...requiresArrayMode], +); tests(); @@ -89,14 +95,23 @@ beforeEach(async () => { }); test("insert via db.get w/ query builder", async () => { - const inserted = await db.get>( - db.insert(usersTable).values({ name: "John" }).returning({ id: usersTable.id, name: usersTable.name }), + const inserted = await db.get< + Pick + >( + db.insert(usersTable).values({ name: "John" }).returning({ + id: usersTable.id, + name: usersTable.name, + }), ); expect(inserted).toEqual([1, "John"]); }); test("insert via db.run + select via db.get", async () => { - await db.run(sql`insert into ${usersTable} (${new Name(usersTable.name.name)}) values (${"John"})`); + await db.run( + sql`insert into ${usersTable} (${new Name( + usersTable.name.name, + )}) values (${"John"})`, + ); const result = await db.get<{ id: number; name: string }>( sql`select ${usersTable.id}, ${usersTable.name} from ${usersTable}`, @@ -114,7 +129,11 @@ test("insert via db.get", async () => { }); test("insert via db.run + select via db.all", async () => { - await db.run(sql`insert into ${usersTable} (${new Name(usersTable.name.name)}) values (${"John"})`); + await db.run( + sql`insert into ${usersTable} (${new Name( + usersTable.name.name, + )}) values (${"John"})`, + ); const result = await db.all<{ id: number; name: string }>( sql`select ${usersTable.id}, ${usersTable.name} from ${usersTable}`, diff --git a/tests/deno/utils.ts b/tests/deno/utils.ts index 18db1b6..ba462e6 100644 --- a/tests/deno/utils.ts +++ b/tests/deno/utils.ts @@ -5,14 +5,18 @@ // eslint-disable-next-line @typescript-eslint/no-empty-function, @typescript-eslint/no-unused-vars export function Expect() {} -export type Equal = (() => T extends X ? 1 : 2) extends (() => T extends Y ? 1 : 2) ? true - : false; +export type Equal = (() => T extends X ? 1 : 2) extends + (() => T extends Y ? 1 : 2) ? true + : false; export function toLocalDate(date: Date) { - const localTime = new Date(date.getTime() - date.getTimezoneOffset() * 60000); - localTime.setUTCHours(0); - return localTime; + const localTime = new Date(date.getTime() - date.getTimezoneOffset() * 60000); + localTime.setUTCHours(0); + return localTime; } export const randomString = () => - Array.from({ length: 10 }, () => 'abcdefghijklmnopqrstuvwxyz'[Math.floor(Math.random() * 26)]).join(''); + Array.from( + { length: 10 }, + () => "abcdefghijklmnopqrstuvwxyz"[Math.floor(Math.random() * 26)], + ).join(""); diff --git a/tests/node/common.ts b/tests/node/common.ts index a9bd0bb..31286ed 100644 --- a/tests/node/common.ts +++ b/tests/node/common.ts @@ -2,12 +2,12 @@ * Common test utilities (from Drizzle's integration-tests/tests/common.ts) */ -import { beforeEach } from 'vitest'; +import { beforeEach } from "vitest"; export function skipTests(names: string[]) { - beforeEach((ctx) => { - if (ctx.task.suite?.name === 'common' && names.includes(ctx.task.name)) { - ctx.skip(); - } - }); + beforeEach((ctx) => { + if (ctx.task.suite?.name === "common" && names.includes(ctx.task.name)) { + ctx.skip(); + } + }); } diff --git a/tests/node/sqlite.test.ts b/tests/node/sqlite.test.ts index 6236eec..a583546 100644 --- a/tests/node/sqlite.test.ts +++ b/tests/node/sqlite.test.ts @@ -7,7 +7,11 @@ import type { SqliteRemoteDatabase } from "drizzle-orm/sqlite-proxy"; import { drizzle as proxyDrizzle } from "drizzle-orm/sqlite-proxy"; import { afterAll, beforeAll, beforeEach, expect, test } from "vitest"; import { skipTests } from "./common"; -import { createCallback, createBatchCallback, createClient } from "../../mod.ts"; +import { + createBatchCallback, + createCallback, + createClient, +} from "../../mod.ts"; import { tests, usersTable, @@ -68,7 +72,9 @@ const requiresArrayMode = [ "cross join", ]; -skipTests(supportsArrayMode ? alwaysSkip : [...alwaysSkip, ...requiresArrayMode]); +skipTests( + supportsArrayMode ? alwaysSkip : [...alwaysSkip, ...requiresArrayMode], +); tests(); @@ -87,14 +93,23 @@ beforeEach(async () => { }); test("insert via db.get w/ query builder", async () => { - const inserted = await db.get>( - db.insert(usersTable).values({ name: "John" }).returning({ id: usersTable.id, name: usersTable.name }), + const inserted = await db.get< + Pick + >( + db.insert(usersTable).values({ name: "John" }).returning({ + id: usersTable.id, + name: usersTable.name, + }), ); expect(inserted).toEqual([1, "John"]); }); test("insert via db.run + select via db.get", async () => { - await db.run(sql`insert into ${usersTable} (${new Name(usersTable.name.name)}) values (${"John"})`); + await db.run( + sql`insert into ${usersTable} (${new Name( + usersTable.name.name, + )}) values (${"John"})`, + ); const result = await db.get<{ id: number; name: string }>( sql`select ${usersTable.id}, ${usersTable.name} from ${usersTable}`, @@ -112,7 +127,11 @@ test("insert via db.get", async () => { }); test("insert via db.run + select via db.all", async () => { - await db.run(sql`insert into ${usersTable} (${new Name(usersTable.name.name)}) values (${"John"})`); + await db.run( + sql`insert into ${usersTable} (${new Name( + usersTable.name.name, + )}) values (${"John"})`, + ); const result = await db.all<{ id: number; name: string }>( sql`select ${usersTable.id}, ${usersTable.name} from ${usersTable}`, diff --git a/tests/node/utils.ts b/tests/node/utils.ts index 18db1b6..ba462e6 100644 --- a/tests/node/utils.ts +++ b/tests/node/utils.ts @@ -5,14 +5,18 @@ // eslint-disable-next-line @typescript-eslint/no-empty-function, @typescript-eslint/no-unused-vars export function Expect() {} -export type Equal = (() => T extends X ? 1 : 2) extends (() => T extends Y ? 1 : 2) ? true - : false; +export type Equal = (() => T extends X ? 1 : 2) extends + (() => T extends Y ? 1 : 2) ? true + : false; export function toLocalDate(date: Date) { - const localTime = new Date(date.getTime() - date.getTimezoneOffset() * 60000); - localTime.setUTCHours(0); - return localTime; + const localTime = new Date(date.getTime() - date.getTimezoneOffset() * 60000); + localTime.setUTCHours(0); + return localTime; } export const randomString = () => - Array.from({ length: 10 }, () => 'abcdefghijklmnopqrstuvwxyz'[Math.floor(Math.random() * 26)]).join(''); + Array.from( + { length: 10 }, + () => "abcdefghijklmnopqrstuvwxyz"[Math.floor(Math.random() * 26)], + ).join(""); From 6a20120bcde74e93d75a78f5cbf758aed8613686 Mon Sep 17 00:00:00 2001 From: earthlingdavey <15802017+earthlingdavey@users.noreply.github.com> Date: Tue, 3 Mar 2026 18:42:14 +0000 Subject: [PATCH 3/6] Add publish and test workflows --- .github/workflows/example-publish.yml | 48 ------ .github/workflows/example-test-patch.yml | 187 ----------------------- .github/workflows/test.yml | 60 +++++++- 3 files changed, 52 insertions(+), 243 deletions(-) delete mode 100644 .github/workflows/example-publish.yml delete mode 100644 .github/workflows/example-test-patch.yml diff --git a/.github/workflows/example-publish.yml b/.github/workflows/example-publish.yml deleted file mode 100644 index 716ae5f..0000000 --- a/.github/workflows/example-publish.yml +++ /dev/null @@ -1,48 +0,0 @@ -name: Publish to JSR - -on: - push: - tags: - - "v*" - -jobs: - publish: - runs-on: ubuntu-latest - - permissions: - contents: read - id-token: write # Required for JSR provenance - - steps: - - name: Checkout - uses: actions/checkout@v4 - - - name: Setup Deno - uses: denoland/setup-deno@v2 - with: - deno-version: v2.x - - - name: Verify version matches tag - run: | - TAG_VERSION="${GITHUB_REF#refs/tags/v}" - PACKAGE_VERSION=$(deno eval "const c = JSON.parse(await Deno.readTextFile('deno.jsonc').then(t => t.replace(/\/\/.*/g, ''))); console.log(c.version)") - if [ "$TAG_VERSION" != "$PACKAGE_VERSION" ]; then - echo "❌ Tag version ($TAG_VERSION) does not match package version ($PACKAGE_VERSION)" - exit 1 - fi - echo "✅ Version $TAG_VERSION matches" - - - name: Clone Drizzle shared test suite - run: make clone-repo - - - name: Run tests - run: deno task test - - - name: Show lockfile changes (if any) - run: git diff deno.lock || true - - - name: Restore lockfile (tests may modify it due to Deno version differences) - run: git checkout deno.lock - - - name: Publish to JSR - run: deno publish diff --git a/.github/workflows/example-test-patch.yml b/.github/workflows/example-test-patch.yml deleted file mode 100644 index 322f50e..0000000 --- a/.github/workflows/example-test-patch.yml +++ /dev/null @@ -1,187 +0,0 @@ -name: Test Patch Compatibility - -on: - push: - branches: [main] - paths: - - "scripts/patch-drizzle-kit.ts" - - "scripts/pgsql/**" - - "scripts/libsql/**" - - ".github/workflows/test-patch.yml" - pull_request: - branches: [main] - paths: - - "scripts/patch-drizzle-kit.ts" - - "scripts/pgsql/**" - - "scripts/libsql/**" - - ".github/workflows/test-patch.yml" - workflow_dispatch: - inputs: - versions: - description: 'Versions to test (comma-separated, or "all" for all supported)' - required: false - default: "all" - quick: - description: "Quick mode (patch only, skip runtime tests)" - required: false - type: boolean - default: false - -jobs: - test-pglite: - name: Test PGlite - drizzle-kit@${{ matrix.version }} - runs-on: ubuntu-latest - strategy: - fail-fast: false - matrix: - version: ["0.30.6", "0.31.8", "0.31.9"] - - steps: - - name: Checkout - uses: actions/checkout@v4 - - - name: Setup Deno - uses: denoland/setup-deno@v2 - with: - deno-version: v2.x - - - name: Ensure latest Deno - run: | - INSTALLED=$(deno --version | head -1 | cut -d' ' -f2) - LATEST=$(curl -s https://dl.deno.land/release-latest.txt | tr -d 'v') - echo "Installed: $INSTALLED" - echo "Latest: $LATEST" - if [ "$INSTALLED" != "$LATEST" ]; then - echo "Upgrading Deno from $INSTALLED to $LATEST..." - deno upgrade - fi - - - name: Cache Deno dependencies - uses: actions/cache@v4 - with: - path: | - ~/.cache/deno - ~/.deno - # Include patch script in cache key so cache busts when patch version changes - # No restore-keys fallback - stale caches have patched npm packages that can't be re-patched - key: ${{ runner.os }}-deno-${{ hashFiles('deno.jsonc', 'deno.lock', 'scripts/patch-drizzle-kit.ts') }} - - - name: Install dependencies - run: deno install - - - name: Cache test script dependencies - run: deno cache --config deno.jsonc scripts/pgsql/test-patch.ts - - - name: Clean test directory - run: rm -rf .test-patch - - - name: Clear drizzle-kit from npm cache - # Ensures we get fresh unpatched drizzle-kit for each test run - run: rm -rf ~/.cache/deno/npm/registry.npmjs.org/drizzle-kit - - - name: Run PGlite test for drizzle-kit@${{ matrix.version }} - run: deno run --allow-read=scripts,deno.jsonc,.test-patch --allow-write=.test-patch --allow-run=deno --allow-net scripts/pgsql/test-patch.ts ${{ matrix.version }} - - test-libsql: - name: Test LibSQL - drizzle-kit@${{ matrix.version }} - runs-on: ubuntu-latest - strategy: - fail-fast: false - matrix: - version: ["0.30.6", "0.31.8", "0.31.9"] - - steps: - - name: Checkout - uses: actions/checkout@v4 - - - name: Setup Deno - uses: denoland/setup-deno@v2 - with: - deno-version: v2.x - - - name: Ensure latest Deno - run: | - INSTALLED=$(deno --version | head -1 | cut -d' ' -f2) - LATEST=$(curl -s https://dl.deno.land/release-latest.txt | tr -d 'v') - echo "Installed: $INSTALLED" - echo "Latest: $LATEST" - if [ "$INSTALLED" != "$LATEST" ]; then - echo "Upgrading Deno from $INSTALLED to $LATEST..." - deno upgrade - fi - - - name: Cache Deno dependencies - uses: actions/cache@v4 - with: - path: | - ~/.cache/deno - ~/.deno - key: ${{ runner.os }}-deno-libsql-${{ hashFiles('deno.jsonc', 'deno.lock', 'scripts/patch-drizzle-kit.ts') }} - - - name: Install dependencies - run: deno install - - - name: Cache test script dependencies - run: deno cache --config deno.jsonc scripts/libsql/test-patch.ts - - - name: Clean test directory - run: rm -rf .test-patch-sqlite - - - name: Clear drizzle-kit from npm cache - run: rm -rf ~/.cache/deno/npm/registry.npmjs.org/drizzle-kit - - - name: Run LibSQL test for drizzle-kit@${{ matrix.version }} - run: deno run --allow-read=scripts,deno.jsonc,.test-patch-sqlite --allow-write=.test-patch-sqlite --allow-run=deno --allow-net --allow-env scripts/libsql/test-patch.ts ${{ matrix.version }} - - # Optional: Run quick tests for all versions in a single job - test-patch-quick: - name: Quick Test - All Versions - runs-on: ubuntu-latest - if: github.event_name == 'pull_request' - - steps: - - name: Checkout - uses: actions/checkout@v4 - - - name: Setup Deno - uses: denoland/setup-deno@v2 - with: - deno-version: v2.x - - - name: Install dependencies - run: deno install - - - name: Cache test script dependencies - run: deno cache --config deno.jsonc scripts/pgsql/test-patch.ts - - - name: Clean test directory - run: rm -rf .test-patch - - - name: Clear drizzle-kit from npm cache - # Ensures we get fresh unpatched drizzle-kit for each test run - run: rm -rf ~/.cache/deno/npm/registry.npmjs.org/drizzle-kit - - - name: Run quick patch tests - run: deno run --allow-read=scripts,deno.jsonc,.test-patch --allow-write=.test-patch --allow-run=deno --allow-net scripts/pgsql/test-patch.ts --quick - - # Summary job that depends on all matrix jobs - test-summary: - name: Test Summary - runs-on: ubuntu-latest - needs: [test-pglite, test-libsql] - if: always() - - steps: - - name: Check test results - run: | - PGLITE="${{ needs.test-pglite.result }}" - LIBSQL="${{ needs.test-libsql.result }}" - echo "PGlite tests: $PGLITE" - echo "LibSQL tests: $LIBSQL" - if [[ "$PGLITE" == "success" && "$LIBSQL" == "success" ]]; then - echo "✅ All patch compatibility tests passed!" - exit 0 - else - echo "❌ Some patch compatibility tests failed" - exit 1 - fi diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index edc2b87..065288f 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -7,7 +7,7 @@ on: branches: [main] jobs: - test: + lint: runs-on: ubuntu-latest steps: @@ -19,15 +19,38 @@ jobs: with: deno-version: v2.x + - name: Check formatting + run: deno fmt --check + + - name: Lint + run: deno lint + + test-deno: + name: Test Deno ${{ matrix.deno-version }} + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + deno-version: ["2.6", "2.7.2"] + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup Deno + uses: denoland/setup-deno@v2 + with: + deno-version: ${{ matrix.deno-version }} + - name: Cache Deno dependencies uses: actions/cache@v4 with: path: | ~/.cache/deno ~/.deno - key: ${{ runner.os }}-deno-${{ hashFiles('deno.jsonc', 'deno.lock') }} + key: ${{ runner.os }}-deno-${{ matrix.deno-version }}-${{ hashFiles('deno.jsonc', 'deno.lock') }} restore-keys: | - ${{ runner.os }}-deno- + ${{ runner.os }}-deno-${{ matrix.deno-version }}- - name: Clone Drizzle shared test suite run: make clone-repo @@ -35,11 +58,32 @@ jobs: - name: Install dependencies run: deno install - - name: Check formatting - run: deno fmt --check + - name: Run tests + run: deno task test - - name: Lint - run: deno lint + test-node: + name: Test Node ${{ matrix.node-version }} + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + node-version: ["22", "24"] + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup Node + uses: actions/setup-node@v4 + with: + node-version: ${{ matrix.node-version }} + cache: npm + + - name: Clone Drizzle shared test suite + run: make clone-repo + + - name: Install dependencies + run: npm install - name: Run tests - run: deno task test + run: npm test From 0f9b0c0ccc4e51153d5b174c49dbe56419fd921b Mon Sep 17 00:00:00 2001 From: earthlingdavey <15802017+earthlingdavey@users.noreply.github.com> Date: Tue, 3 Mar 2026 18:43:39 +0000 Subject: [PATCH 4/6] Remove npm cache (no lock file) --- .github/workflows/test.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 065288f..2fb9b8a 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -77,7 +77,6 @@ jobs: uses: actions/setup-node@v4 with: node-version: ${{ matrix.node-version }} - cache: npm - name: Clone Drizzle shared test suite run: make clone-repo From c1c7433024af71262f836e02368698c239e6e2c4 Mon Sep 17 00:00:00 2001 From: earthlingdavey <15802017+earthlingdavey@users.noreply.github.com> Date: Tue, 3 Mar 2026 18:45:47 +0000 Subject: [PATCH 5/6] Add local setup instructions for pre-commit hooks --- README.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/README.md b/README.md index cde6fba..444efff 100644 --- a/README.md +++ b/README.md @@ -138,6 +138,14 @@ This matches the sqlite-proxy specification. For most use cases, prefer the quer - Docker and Docker Compose - Make +### Local Setup + +```bash +# Enable pre-commit hooks (runs fmt/lint/check) +git config core.hooksPath .githooks && \ +chmod +x .githooks/pre-commit +``` + ### Running Tests Tests run in Docker containers to ensure consistent environments. From 0d1b6df825474125e6bb4745c0918efa88bea67a Mon Sep 17 00:00:00 2001 From: earthlingdavey <15802017+earthlingdavey@users.noreply.github.com> Date: Tue, 3 Mar 2026 18:47:32 +0000 Subject: [PATCH 6/6] Fix lint errors in test files --- tests/deno/sqlite.test.ts | 5 +++-- tests/node/sqlite.test.ts | 6 ++++-- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/tests/deno/sqlite.test.ts b/tests/deno/sqlite.test.ts index 504a64f..e38aea1 100644 --- a/tests/deno/sqlite.test.ts +++ b/tests/deno/sqlite.test.ts @@ -26,6 +26,7 @@ let client: ReturnType; function hasArrayMode(): boolean { const testClient = createClient(":memory:"); const stmt = testClient.db.prepare("SELECT 1"); + // deno-lint-ignore no-explicit-any const has = typeof (stmt as any).setReturnArrays === "function"; testClient.db.close(); return has; @@ -33,7 +34,7 @@ function hasArrayMode(): boolean { const supportsArrayMode = hasArrayMode(); -beforeAll(async () => { +beforeAll(() => { const dbPath = Deno.env.get("SQLITE_DB_PATH") ?? ":memory:"; client = createClient(dbPath); @@ -49,7 +50,7 @@ beforeEach((ctx) => { }; }); -afterAll(async () => { +afterAll(() => { client?.db.close(); }); diff --git a/tests/node/sqlite.test.ts b/tests/node/sqlite.test.ts index a583546..65c8d83 100644 --- a/tests/node/sqlite.test.ts +++ b/tests/node/sqlite.test.ts @@ -2,6 +2,7 @@ * Integration tests using Drizzle's official shared test suite. */ +import process from "node:process"; import { Name, sql } from "drizzle-orm"; import type { SqliteRemoteDatabase } from "drizzle-orm/sqlite-proxy"; import { drizzle as proxyDrizzle } from "drizzle-orm/sqlite-proxy"; @@ -24,6 +25,7 @@ let client: ReturnType; function hasArrayMode(): boolean { const testClient = createClient(":memory:"); const stmt = testClient.db.prepare("SELECT 1"); + // deno-lint-ignore no-explicit-any const has = typeof (stmt as any).setReturnArrays === "function"; testClient.db.close(); return has; @@ -31,7 +33,7 @@ function hasArrayMode(): boolean { const supportsArrayMode = hasArrayMode(); -beforeAll(async () => { +beforeAll(() => { const dbPath = process.env["SQLITE_DB_PATH"] ?? ":memory:"; client = createClient(dbPath); @@ -47,7 +49,7 @@ beforeEach((ctx) => { }; }); -afterAll(async () => { +afterAll(() => { client?.db.close(); });