From 9f0727b6fc91096da14d1598cc3c888638f2c5a3 Mon Sep 17 00:00:00 2001 From: Joseph Mearman Date: Sat, 10 Oct 2026 17:25:24 +0100 Subject: [PATCH 1/8] feat: add sqlite-wasm package The SQLite graph store in the browser: @sqlite.org/sqlite-wasm in a dedicated worker on the OPFS opfs-sahpool VFS, reached by Drizzle through drizzle-orm/sqlite-proxy, served to the page over oRPC's message-port transport (serveGraphStoreInWorker and connectGraphStore). Each store operation holds an exclusive Web Lock named after the database while it unpauses the pool, opens the file, runs and pauses it again, so tabs take turns on one database with no cross-origin isolation headers. Committed changes reach every tab's subscribers over a BroadcastChannel. The conformance kit runs in Chromium through Vitest's browser mode, and further browser tests run two workers on one database and reopen a database after its writer has gone. The worker module's dependencies are scanned up front so Vite never reloads the page under running tests. Refs #22 --- README.md | 1 + packages/adapters/graph/sqlite-wasm/AGENTS.md | 1 + packages/adapters/graph/sqlite-wasm/CLAUDE.md | 1 + packages/adapters/graph/sqlite-wasm/README.md | 43 +++ .../graph/sqlite-wasm/eslint.config.ts | 16 ++ .../adapters/graph/sqlite-wasm/package.json | 87 ++++++ .../adapters/graph/sqlite-wasm/src/client.ts | 11 + .../graph/sqlite-wasm/src/database.ts | 82 ++++++ .../adapters/graph/sqlite-wasm/src/feed.ts | 40 +++ .../graph/sqlite-wasm/src/feed.unit.test.ts | 35 +++ .../adapters/graph/sqlite-wasm/src/index.ts | 4 + .../adapters/graph/sqlite-wasm/src/worker.ts | 38 +++ .../graph/sqlite-wasm/stryker.config.ts | 3 + .../test/browser/store.integration.test.ts | 100 +++++++ .../sqlite-wasm/test/browser/store.worker.ts | 4 + .../sqlite-wasm/test/package.smoke.test.ts | 7 + .../graph/sqlite-wasm/tsconfig.browser.json | 8 + .../graph/sqlite-wasm/tsconfig.build.json | 20 ++ .../adapters/graph/sqlite-wasm/tsconfig.json | 9 + .../graph/sqlite-wasm/tsconfig.node.json | 8 + .../graph/sqlite-wasm/vitest.config.ts | 37 +++ pnpm-lock.yaml | 262 ++++++++++++++++-- tsconfig.build.json | 3 + 23 files changed, 796 insertions(+), 24 deletions(-) create mode 120000 packages/adapters/graph/sqlite-wasm/AGENTS.md create mode 120000 packages/adapters/graph/sqlite-wasm/CLAUDE.md create mode 100644 packages/adapters/graph/sqlite-wasm/README.md create mode 100644 packages/adapters/graph/sqlite-wasm/eslint.config.ts create mode 100644 packages/adapters/graph/sqlite-wasm/package.json create mode 100644 packages/adapters/graph/sqlite-wasm/src/client.ts create mode 100644 packages/adapters/graph/sqlite-wasm/src/database.ts create mode 100644 packages/adapters/graph/sqlite-wasm/src/feed.ts create mode 100644 packages/adapters/graph/sqlite-wasm/src/feed.unit.test.ts create mode 100644 packages/adapters/graph/sqlite-wasm/src/index.ts create mode 100644 packages/adapters/graph/sqlite-wasm/src/worker.ts create mode 100644 packages/adapters/graph/sqlite-wasm/stryker.config.ts create mode 100644 packages/adapters/graph/sqlite-wasm/test/browser/store.integration.test.ts create mode 100644 packages/adapters/graph/sqlite-wasm/test/browser/store.worker.ts create mode 100644 packages/adapters/graph/sqlite-wasm/test/package.smoke.test.ts create mode 100644 packages/adapters/graph/sqlite-wasm/tsconfig.browser.json create mode 100644 packages/adapters/graph/sqlite-wasm/tsconfig.build.json create mode 100644 packages/adapters/graph/sqlite-wasm/tsconfig.json create mode 100644 packages/adapters/graph/sqlite-wasm/tsconfig.node.json create mode 100644 packages/adapters/graph/sqlite-wasm/vitest.config.ts diff --git a/README.md b/README.md index 2e6f9cc..1243a47 100644 --- a/README.md +++ b/README.md @@ -57,6 +57,7 @@ Configuration lives at exactly one of two levels: in the package (`eslint.config | [`graph-store-contract`](packages/core/graph/store-contract/README.md) | The persistence port as an oRPC contract: save, load and delete models, set run state, upsert items from a source, structural ready, blocked-chain and impact queries, and a change feed; with an in-memory fake and a conformance kit. | | [`graph-store-sqlite`](packages/adapters/graph/store-sqlite/README.md) | The store port on SQLite through Drizzle: one table definition, committed drizzle-kit migrations applied the same way in every runtime, recursive SQL for the structural queries, against Drizzle's async SQLite database so any driver plugs in. | | [`graph-sqlite-node`](packages/adapters/graph/sqlite-node/README.md) | The SQLite store on Node's built-in `node:sqlite` through `drizzle-orm/node-sqlite`, for the command line and servers. | +| [`graph-sqlite-wasm`](packages/adapters/graph/sqlite-wasm/README.md) | The SQLite store in the browser: SQLite wasm in a dedicated worker on OPFS `opfs-sahpool`, shared between tabs under a Web Lock with no special headers, served to the page over oRPC. | | [`process-graph`](packages/targets/process-graph/README.md) | The primary entry point: re-exports the schema, projection and domain APIs flat and the DAG API as the `dag` namespace. | ## Conventions diff --git a/packages/adapters/graph/sqlite-wasm/AGENTS.md b/packages/adapters/graph/sqlite-wasm/AGENTS.md new file mode 120000 index 0000000..42061c0 --- /dev/null +++ b/packages/adapters/graph/sqlite-wasm/AGENTS.md @@ -0,0 +1 @@ +README.md \ No newline at end of file diff --git a/packages/adapters/graph/sqlite-wasm/CLAUDE.md b/packages/adapters/graph/sqlite-wasm/CLAUDE.md new file mode 120000 index 0000000..42061c0 --- /dev/null +++ b/packages/adapters/graph/sqlite-wasm/CLAUDE.md @@ -0,0 +1 @@ +README.md \ No newline at end of file diff --git a/packages/adapters/graph/sqlite-wasm/README.md b/packages/adapters/graph/sqlite-wasm/README.md new file mode 100644 index 0000000..c7f6089 --- /dev/null +++ b/packages/adapters/graph/sqlite-wasm/README.md @@ -0,0 +1,43 @@ +# @exadev/process-graph-sqlite-wasm + +[![GitHub](https://img.shields.io/badge/GitHub-181717?logo=github&logoColor=white)](https://github.com/ExaDev/process-graph) [![npm](https://img.shields.io/badge/npm-CB3837?logo=npm&logoColor=white)](https://www.npmjs.com/package/@exadev/process-graph-sqlite-wasm) [![Release](https://img.shields.io/github/v/release/ExaDev/process-graph)](https://github.com/ExaDev/process-graph/releases/latest) [![CI](https://img.shields.io/github/actions/workflow/status/ExaDev/process-graph/ci.yml?branch=main)](https://github.com/ExaDev/process-graph/actions) + +The SQLite graph store from `@exadev/process-graph-store-sqlite` in the browser. SQLite compiled to WebAssembly (`@sqlite.org/sqlite-wasm`) runs in a dedicated worker on the origin private file system, Drizzle reaches it through `drizzle-orm/sqlite-proxy`, and the page talks to the store over oRPC's message-port transport. Any number of tabs can use one database, and the page needs no `Cross-Origin-Opener-Policy` or `Cross-Origin-Embedder-Policy` headers. + +```ts +// store.worker.ts +import { serveGraphStoreInWorker } from "@exadev/process-graph-sqlite-wasm/worker"; + +serveGraphStoreInWorker({ name: "graph" }); +``` + +```ts +// in the page +import { connectGraphStore } from "@exadev/process-graph-sqlite-wasm"; + +const store = connectGraphStore( + new Worker(new URL("./store.worker.ts", import.meta.url), { type: "module" }), +); +await store.v1.query.ready({ modelId: "release" }); +``` + +With Vite, exclude `@sqlite.org/sqlite-wasm` from dependency pre-bundling (`optimizeDeps.exclude`), since it loads its `.wasm` file relative to itself. + +## How tabs share one database + +The database lives on SQLite's `opfs-sahpool` VFS. A pool holds its OPFS sync access handles exclusively, so only one worker in the origin can have it open at a time. Every store operation therefore takes an exclusive Web Lock named after the database, unpauses the pool, opens the file, runs, then closes the file and pauses the pool before releasing the lock: tabs take turns rather than failing. The lock covers the whole store operation, not each SQL statement, because a Drizzle transaction is several statements through the proxy. Changes committed in one tab reach every tab's `changes` subscribers over a `BroadcastChannel` named after the database. The schema is migrated on the first operation with the migrations embedded in `graph-store-sqlite`, the same statements the Node driver applies. + +## Why this combination + +The choice came from a spike recorded in [#22](https://github.com/ExaDev/process-graph/issues/22): two tabs on one database under Playwright, against a Vite dev server with no cross-origin isolation headers, and again with them as a control. + +- SQLite's `opfs` and `opfs-wl` VFSes need `SharedArrayBuffer`, which a page has only when cross-origin isolated. Without the headers, `opfs-wl` never installs. +- SQLocal only opens the `opfs` VFS. Without the headers it falls back to an in-memory database with a console warning, so each tab silently writes to its own database and loses it on reload. +- `opfs-sahpool` needs neither, but a second tab cannot open a pool another tab holds (`NoModificationAllowedError`). Pausing the pool between operations under a Web Lock removes that, and is the only combination tried that kept two tabs reading and writing one database without the headers. +- Drizzle's `sqlite-proxy` migrator cannot run in a browser, because `drizzle-orm/migrator` imports `node:fs`; the store applies its migrations itself through the proxy. + +Reacquiring the pool's handles on every operation is the cost of this choice. Electing one long-lived owner tab is the alternative if that cost ever matters, and it changes nothing above the worker. + +## Tests + +The contract's conformance kit runs in Chromium through Vitest's browser mode (`pnpm test:integration`), with a fresh database per test and no cross-origin isolation. Further browser tests run two workers on one database, as two tabs would, and reopen a database after its writer has gone. CI installs Chromium for them with Playwright. diff --git a/packages/adapters/graph/sqlite-wasm/eslint.config.ts b/packages/adapters/graph/sqlite-wasm/eslint.config.ts new file mode 100644 index 0000000..be66b32 --- /dev/null +++ b/packages/adapters/graph/sqlite-wasm/eslint.config.ts @@ -0,0 +1,16 @@ +import { defineConfig } from "eslint/config"; +import { packageLintConfig } from "../../../../eslint.shared.ts"; + +export default defineConfig( + packageLintConfig({ + tsconfigRootDir: import.meta.dirname, + projects: [ + "./tsconfig.json", + "./tsconfig.node.json", + "./tsconfig.browser.json", + ], + isomorphic: true, + // It starts and talks to workers, BroadcastChannels and Web Locks. + browserGlobals: true, + }), +); diff --git a/packages/adapters/graph/sqlite-wasm/package.json b/packages/adapters/graph/sqlite-wasm/package.json new file mode 100644 index 0000000..84c86ec --- /dev/null +++ b/packages/adapters/graph/sqlite-wasm/package.json @@ -0,0 +1,87 @@ +{ + "name": "@exadev/process-graph-sqlite-wasm", + "version": "0.0.0", + "description": "The SQLite graph store in the browser: SQLite compiled to WebAssembly in a dedicated worker on the OPFS sahpool VFS, shared safely between tabs with no special headers, served to the page over oRPC.", + "type": "module", + "repository": { + "type": "git", + "url": "git+https://github.com/ExaDev/process-graph.git", + "directory": "packages/adapters/graph/sqlite-wasm" + }, + "homepage": "https://github.com/ExaDev/process-graph/tree/main/packages/adapters/graph/sqlite-wasm#readme", + "bugs": { + "url": "https://github.com/ExaDev/process-graph/issues" + }, + "exports": { + ".": { + "types": "./dist/index.d.ts", + "import": "./dist/index.js" + }, + "./worker": { + "types": "./dist/worker.d.ts", + "import": "./dist/worker.js" + } + }, + "types": "./dist/index.d.ts", + "files": [ + "dist" + ], + "publishConfig": { + "access": "public", + "provenance": true, + "registry": "https://registry.npmjs.org/" + }, + "sideEffects": false, + "license": "MIT", + "scripts": { + "build": "turbo run _build", + "_build": "tsc -b tsconfig.build.json", + "lint": "turbo run _lint", + "_lint": "eslint . --cache --max-warnings 0", + "_lint:fix": "eslint . --fix --cache --max-warnings 0", + "typecheck": "turbo run _typecheck _typecheck:attw", + "_typecheck": "tsc -p tsconfig.json --noEmit && tsc -p tsconfig.node.json --noEmit && tsc -p tsconfig.browser.json --noEmit", + "_typecheck:attw": "attw --pack --profile esm-only", + "test": "turbo run _test", + "_test": "vitest run --project unit", + "test:coverage": "turbo run _test:coverage", + "_test:coverage": "vitest run --project unit --coverage", + "test:smoke": "turbo run _test:smoke", + "_test:smoke": "vitest run --project smoke", + "test:mutation": "turbo run _test:mutation", + "_test:mutation": "stryker run stryker.config.ts", + "prepublishOnly": "pnpm run lint && pnpm run typecheck && pnpm run test && pnpm run build && publint && attw --pack --profile esm-only", + "test:integration": "turbo run _test:integration", + "_test:integration": "vitest run --project browser" + }, + "keywords": [ + "process-model", + "store", + "sqlite", + "wasm", + "opfs", + "worker" + ], + "dependencies": { + "@exadev/process-graph-store-contract": "0.0.0", + "@exadev/process-graph-store-sqlite": "0.0.0", + "@orpc/client": "2.0.0-beta.41", + "@orpc/server": "2.0.0-beta.41", + "@sqlite.org/sqlite-wasm": "3.53.4-build2", + "drizzle-orm": "1.0.0-rc.4" + }, + "devDependencies": { + "@arethetypeswrong/cli": "0.18.5", + "@stryker-mutator/core": "10.0.0", + "@stryker-mutator/typescript-checker": "10.0.0", + "@stryker-mutator/vitest-runner": "10.0.0", + "@types/node": "26.6.4", + "@vitest/browser-playwright": "5.0.3", + "@vitest/coverage-v8": "5.0.3", + "eslint": "10.12.0", + "playwright": "1.63.0", + "publint": "0.3.25", + "typescript": "6.0.3", + "vitest": "5.0.3" + } +} diff --git a/packages/adapters/graph/sqlite-wasm/src/client.ts b/packages/adapters/graph/sqlite-wasm/src/client.ts new file mode 100644 index 0000000..d0830d4 --- /dev/null +++ b/packages/adapters/graph/sqlite-wasm/src/client.ts @@ -0,0 +1,11 @@ +import type { GraphStoreClient } from "@exadev/process-graph-store-contract"; +import { createORPCClient } from "@orpc/client"; +import { RPCLink } from "@orpc/client/message-port"; + +/** + * A client for the graph store a worker serves with `serveGraphStoreInWorker`. Every procedure of the contract is available, with its abort signal and typed errors, as if the store were in the page. + * @param worker - The dedicated worker running the store. + * @returns The store's oRPC client. + */ +export const connectGraphStore = (worker: Readonly): GraphStoreClient => + createORPCClient(new RPCLink({ port: worker })); diff --git a/packages/adapters/graph/sqlite-wasm/src/database.ts b/packages/adapters/graph/sqlite-wasm/src/database.ts new file mode 100644 index 0000000..2ed8abf --- /dev/null +++ b/packages/adapters/graph/sqlite-wasm/src/database.ts @@ -0,0 +1,82 @@ +import type { + Database, + SAHPoolUtil, + Sqlite3Static, +} from "@sqlite.org/sqlite-wasm"; +import type { Exclusive } from "@exadev/process-graph-store-sqlite"; +import { drizzle, type SqliteRemoteDatabase } from "drizzle-orm/sqlite-proxy"; + +/** Where the database lives: an OPFS `opfs-sahpool` pool and a file in it. */ +export interface DatabaseLocation { + /** The pool's name, which is also its OPFS directory. */ + readonly pool: string; + /** The database file's path within the pool. */ + readonly file: string; +} + +/** The database a worker serves, and the exclusivity that makes it safe to share. */ +export interface WorkerDatabase { + /** A Drizzle database whose statements run on the file this worker currently holds open. */ + readonly db: SqliteRemoteDatabase; + /** Holds the database across every tab for one store operation. */ + readonly exclusive: Exclusive; +} + +/** + * Opens a database on the `opfs-sahpool` VFS so that any number of tabs, each with its own worker, can use it with no cross-origin isolation headers. + * + * A pool holds its OPFS sync access handles exclusively, so only one worker in the origin can have it open at a time. Each store operation therefore takes an exclusive Web Lock named after the pool, unpauses the pool (reacquiring its handles), opens the file, runs, then closes the file and pauses the pool (releasing them) before the lock is released. A Drizzle transaction is several statements, so the lock covers the whole store operation rather than each statement. The choice and the evidence for it are recorded in https://github.com/ExaDev/process-graph/issues/22. + * @param sqlite3 - The SQLite module, initialising; the first operation waits for it. + * @param location - The pool and file. + * @returns The database and its exclusivity. + */ +export const openWorkerDatabase = ( + sqlite3: Readonly>, + location: DatabaseLocation, +): WorkerDatabase => { + // The pool, once its installation has started. It is installed inside the first operation, under the lock, because installing takes the handles another tab may hold. + let pool: Promise | undefined; + let open: Database | undefined; + + // The store reads through `values`, `all` and query builders, never `.get()`, so `get` is refused rather than answered: the proxy's declared result type has no way to say "no row", which Drizzle's `get` signals with a missing `rows`. + const db = drizzle(async (statement, params, method) => { + if (method === "get") { + throw new Error("The graph store's worker database does not serve get"); + } + if (open === undefined) { + throw new Error( + "The database was used outside a store operation; only the store's exclusive section may run statements", + ); + } + const rows = open.exec(statement, { + bind: params, + rowMode: "array", + returnValue: "resultRows", + }); + + return Promise.resolve({ rows }); + }); + + const exclusive: Exclusive = async (operation) => + navigator.locks.request(location.pool, { mode: "exclusive" }, async () => { + // Installing the pool takes its handles, so only an already installed pool needs unpausing. + const installing = pool === undefined; + pool ??= sqlite3.then(async (module) => + module.installOpfsSAHPoolVfs({ name: location.pool }), + ); + const held = await pool; + if (!installing) { + await held.unpauseVfs(); + } + open = new held.OpfsSAHPoolDb(location.file); + try { + return await operation(); + } finally { + open.close(); + open = undefined; + held.pauseVfs(); + } + }); + + return { db, exclusive }; +}; diff --git a/packages/adapters/graph/sqlite-wasm/src/feed.ts b/packages/adapters/graph/sqlite-wasm/src/feed.ts new file mode 100644 index 0000000..ac7e29a --- /dev/null +++ b/packages/adapters/graph/sqlite-wasm/src/feed.ts @@ -0,0 +1,40 @@ +import { + ChangeFeed, + storeChangeSchema, + type StoreChange, +} from "@exadev/process-graph-store-contract"; + +/** + * A change feed shared by every worker serving the same database in this origin: each change committed here is published locally and posted on a `BroadcastChannel`, and each change another worker posts there is published locally. Every tab's subscribers therefore hear every tab's writes. + */ +export class BroadcastChangeFeed extends ChangeFeed { + readonly #channel: BroadcastChannel; + + /** + * @param name - The channel name, one per database, so two databases in one origin do not hear each other. + */ + constructor(name: string) { + super(); + this.#channel = new BroadcastChannel(name); + this.#channel.addEventListener("message", (event: MessageEvent) => { + const parsed = storeChangeSchema.safeParse(event.data); + if (parsed.success) { + super.publish(parsed.data); + } + }); + } + + /** + * Publishes a change committed in this worker, here and to every other worker on the channel. + * @param change - The committed change. + */ + override publish(change: StoreChange): void { + super.publish(change); + this.#channel.postMessage(change); + } + + /** Stops hearing and posting changes. */ + close(): void { + this.#channel.close(); + } +} diff --git a/packages/adapters/graph/sqlite-wasm/src/feed.unit.test.ts b/packages/adapters/graph/sqlite-wasm/src/feed.unit.test.ts new file mode 100644 index 0000000..21fc66b --- /dev/null +++ b/packages/adapters/graph/sqlite-wasm/src/feed.unit.test.ts @@ -0,0 +1,35 @@ +import { describe, expect, test } from "vitest"; +import { BroadcastChangeFeed } from "./feed.ts"; + +const change = { modelId: "m", kind: "saved", revision: 1 } as const; + +/** Reads the next change a subscription delivers. */ +const next = async (feed: BroadcastChangeFeed) => + feed.subscribe("m")[Symbol.asyncIterator]().next(); + +describe("BroadcastChangeFeed", () => { + test("delivers a change published in one feed to the subscribers of another on the same channel", async () => { + const name = `feed-${crypto.randomUUID()}`; + const here = new BroadcastChangeFeed(name); + const there = new BroadcastChangeFeed(name); + const heard = next(there); + here.publish(change); + + await expect(heard).resolves.toEqual({ done: false, value: change }); + here.close(); + there.close(); + }); + + test("ignores a message on the channel that is not a change", async () => { + const name = `feed-${crypto.randomUUID()}`; + const feed = new BroadcastChangeFeed(name); + const heard = next(feed); + const stranger = new BroadcastChannel(name); + stranger.postMessage({ modelId: "m", kind: "renamed" }); + new BroadcastChangeFeed(name).publish(change); + + await expect(heard).resolves.toEqual({ done: false, value: change }); + stranger.close(); + feed.close(); + }); +}); diff --git a/packages/adapters/graph/sqlite-wasm/src/index.ts b/packages/adapters/graph/sqlite-wasm/src/index.ts new file mode 100644 index 0000000..b0099de --- /dev/null +++ b/packages/adapters/graph/sqlite-wasm/src/index.ts @@ -0,0 +1,4 @@ +/** + * The SQLite graph store in the browser. The worker module (`./worker`) serves the store from a dedicated worker over SQLite compiled to WebAssembly on the OPFS `opfs-sahpool` VFS; this entry connects a page to it. + */ +export { connectGraphStore } from "./client.ts"; diff --git a/packages/adapters/graph/sqlite-wasm/src/worker.ts b/packages/adapters/graph/sqlite-wasm/src/worker.ts new file mode 100644 index 0000000..ccd50c1 --- /dev/null +++ b/packages/adapters/graph/sqlite-wasm/src/worker.ts @@ -0,0 +1,38 @@ +import sqlite3InitModule from "@sqlite.org/sqlite-wasm"; +import { createGraphStoreRouter } from "@exadev/process-graph-store-contract"; +import { createSqliteGraphStore } from "@exadev/process-graph-store-sqlite"; +import { RPCHandler } from "@orpc/server/message-port"; +import { openWorkerDatabase } from "./database.ts"; +import { BroadcastChangeFeed } from "./feed.ts"; + +/** Which database a worker serves. */ +export interface GraphStoreWorkerOptions { + /** + * The database's name. It names the OPFS `opfs-sahpool` pool (and so its directory), the Web Lock that serialises tabs, and the channel changes are shared on, so every worker in the origin given the same name serves the same database. + */ + readonly name: string; +} + +/** + * Serves the graph store from inside a dedicated worker, to the page that started it, over oRPC's message-port transport. Call it once at the top level of the worker module; connect from the page with `connectGraphStore`. + * + * The SQLite module starts initialising at once and the first store operation waits for it. The database is opened only inside each store operation, under a Web Lock shared by every tab, so tabs take turns rather than failing; see `openWorkerDatabase`. Changes committed in any tab reach every tab's `changes` subscribers through a `BroadcastChannel`. + * @param options - Which database to serve. + */ +export const serveGraphStoreInWorker = ( + options: GraphStoreWorkerOptions, +): void => { + const { db, exclusive } = openWorkerDatabase(sqlite3InitModule(), { + pool: options.name, + file: "/graph.sqlite3", + }); + new RPCHandler( + createGraphStoreRouter( + createSqliteGraphStore({ + db, + exclusive, + feed: new BroadcastChangeFeed(options.name), + }), + ), + ).upgrade(self); +}; diff --git a/packages/adapters/graph/sqlite-wasm/stryker.config.ts b/packages/adapters/graph/sqlite-wasm/stryker.config.ts new file mode 100644 index 0000000..6429b27 --- /dev/null +++ b/packages/adapters/graph/sqlite-wasm/stryker.config.ts @@ -0,0 +1,3 @@ +import { packageStrykerConfig } from "../../../../stryker.shared.ts"; + +export default packageStrykerConfig(); diff --git a/packages/adapters/graph/sqlite-wasm/test/browser/store.integration.test.ts b/packages/adapters/graph/sqlite-wasm/test/browser/store.integration.test.ts new file mode 100644 index 0000000..52bd0b8 --- /dev/null +++ b/packages/adapters/graph/sqlite-wasm/test/browser/store.integration.test.ts @@ -0,0 +1,100 @@ +import type { StoreChange } from "@exadev/process-graph-store-contract"; +import { + everyFieldModel, + graphStoreConformance, +} from "@exadev/process-graph-store-contract/conformance"; +import { describe, expect, test } from "vitest"; +import { connectGraphStore } from "../../src/client.ts"; + +/** Starts a worker serving the named database, as one tab of the app would. */ +const startWorker = (name: string): Worker => + new Worker(new URL("store.worker.ts", import.meta.url), { + type: "module", + name, + }); + +graphStoreConformance({ + name: "SQLite wasm in a worker on opfs-sahpool, no cross-origin isolation", + createClient: async () => { + const worker = startWorker(`conformance-${crypto.randomUUID()}`); + + return Promise.resolve({ + client: connectGraphStore(worker), + close: async () => { + worker.terminate(); + + return Promise.resolve(); + }, + }); + }, +}); + +describe("two workers on one database, as two tabs", () => { + test("runs without cross-origin isolation", () => { + expect(crossOriginIsolated).toBe(false); + }); + + test("both read and write it, each sees the other's writes, and a subscriber in one hears the other", async () => { + const name = `tabs-${crypto.randomUUID()}`; + const first = startWorker(name); + const second = startWorker(name); + const one = connectGraphStore(first); + const two = connectGraphStore(second); + try { + const saved = await one.v1.models.save({ + modelId: "orders", + model: everyFieldModel, + }); + const events = await one.v1.changes({ modelId: "orders" }); + const heard = (async (): Promise => { + for await (const change of events) { + return change; + } + + return undefined; + })(); + + await expect( + two.v1.models.load({ modelId: "orders" }), + ).resolves.toMatchObject({ + revision: saved.revision, + model: everyFieldModel, + }); + const set = await two.v1.overlay.set({ + modelId: "orders", + overlay: { ship: "done" }, + }); + + await expect(heard).resolves.toEqual({ + modelId: "orders", + kind: "overlay", + revision: set.revision, + }); + await expect( + one.v1.models.load({ modelId: "orders" }), + ).resolves.toMatchObject({ overlay: { ship: "done" } }); + } finally { + first.terminate(); + second.terminate(); + } + }); + + test("a database outlives the workers that wrote it", async () => { + const name = `reopen-${crypto.randomUUID()}`; + const writer = startWorker(name); + await connectGraphStore(writer).v1.models.save({ + modelId: "orders", + model: everyFieldModel, + }); + writer.terminate(); + + const reader = startWorker(name); + try { + await expect( + connectGraphStore(reader).v1.models.load({ modelId: "orders" }), + ).resolves.toMatchObject({ model: everyFieldModel }); + } finally { + reader.terminate(); + } + }); +}); diff --git a/packages/adapters/graph/sqlite-wasm/test/browser/store.worker.ts b/packages/adapters/graph/sqlite-wasm/test/browser/store.worker.ts new file mode 100644 index 0000000..23b613a --- /dev/null +++ b/packages/adapters/graph/sqlite-wasm/test/browser/store.worker.ts @@ -0,0 +1,4 @@ +import { serveGraphStoreInWorker } from "../../src/worker.ts"; + +// The test names each worker after the database it should serve. +serveGraphStoreInWorker({ name: self.name }); diff --git a/packages/adapters/graph/sqlite-wasm/test/package.smoke.test.ts b/packages/adapters/graph/sqlite-wasm/test/package.smoke.test.ts new file mode 100644 index 0000000..dfa3348 --- /dev/null +++ b/packages/adapters/graph/sqlite-wasm/test/package.smoke.test.ts @@ -0,0 +1,7 @@ +import { expect, test } from "vitest"; +// Deliberately imports the built dist/, not src/: this is the check that catches a wrong exports map or a build misconfiguration that a unit test importing straight from src/ never touches. +import { connectGraphStore } from "../dist/index.js"; + +test("the built page entry exports the client factory", () => { + expect(typeof connectGraphStore).toBe("function"); +}); diff --git a/packages/adapters/graph/sqlite-wasm/tsconfig.browser.json b/packages/adapters/graph/sqlite-wasm/tsconfig.browser.json new file mode 100644 index 0000000..1bf5be5 --- /dev/null +++ b/packages/adapters/graph/sqlite-wasm/tsconfig.browser.json @@ -0,0 +1,8 @@ +{ + "extends": "../../../../tsconfig.base.json", + "compilerOptions": { + "lib": ["ES2024", "WebWorker"], + "types": [] + }, + "include": ["test/browser/**/*.ts"] +} diff --git a/packages/adapters/graph/sqlite-wasm/tsconfig.build.json b/packages/adapters/graph/sqlite-wasm/tsconfig.build.json new file mode 100644 index 0000000..01f7db7 --- /dev/null +++ b/packages/adapters/graph/sqlite-wasm/tsconfig.build.json @@ -0,0 +1,20 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "composite": true, + "noEmit": false, + "declaration": true, + "declarationMap": false, + "rootDir": "src", + "outDir": "dist", + "rewriteRelativeImportExtensions": true + }, + "references": [ + { + "path": "../../../core/graph/store-contract/tsconfig.build.json" + }, + { + "path": "../store-sqlite/tsconfig.build.json" + } + ] +} diff --git a/packages/adapters/graph/sqlite-wasm/tsconfig.json b/packages/adapters/graph/sqlite-wasm/tsconfig.json new file mode 100644 index 0000000..8895dd1 --- /dev/null +++ b/packages/adapters/graph/sqlite-wasm/tsconfig.json @@ -0,0 +1,9 @@ +{ + "extends": "../../../../tsconfig.base.json", + "compilerOptions": { + "lib": ["ES2024", "WebWorker"], + "types": [] + }, + "include": ["src/**/*.ts"], + "exclude": ["src/**/*.test.ts", "src/test-support/**"] +} diff --git a/packages/adapters/graph/sqlite-wasm/tsconfig.node.json b/packages/adapters/graph/sqlite-wasm/tsconfig.node.json new file mode 100644 index 0000000..71f245c --- /dev/null +++ b/packages/adapters/graph/sqlite-wasm/tsconfig.node.json @@ -0,0 +1,8 @@ +{ + "extends": "../../../../tsconfig.base.json", + "compilerOptions": { + "lib": ["ES2024"], + "types": ["node"] + }, + "include": ["src/**/*.test.ts", "test/*.ts", "*.config.ts"] +} diff --git a/packages/adapters/graph/sqlite-wasm/vitest.config.ts b/packages/adapters/graph/sqlite-wasm/vitest.config.ts new file mode 100644 index 0000000..11360e0 --- /dev/null +++ b/packages/adapters/graph/sqlite-wasm/vitest.config.ts @@ -0,0 +1,37 @@ +import { playwright } from "@vitest/browser-playwright"; +import { defineConfig } from "vitest/config"; + +export default defineConfig({ + test: { + projects: [ + { + extends: true, + test: { name: "unit", include: ["src/**/*.test.ts"] }, + }, + { + extends: true, + test: { name: "smoke", include: ["test/package.smoke.test.ts"] }, + }, + { + extends: true, + optimizeDeps: { + // The worker module is only found when a test starts a worker, so Vite would discover its dependencies mid-run and reload the page under the running tests. Scanning every browser test file, the worker included, up front optimises them before the first test. + entries: ["test/browser/**/*.ts"], + // SQLite's wasm module loads its own .wasm file relative to itself, which pre-bundling would break. + exclude: ["@sqlite.org/sqlite-wasm"], + }, + test: { + name: "browser", + include: ["test/browser/**/*.test.ts"], + // Served by Vite as configured here, with no Cross-Origin-Opener-Policy or Cross-Origin-Embedder-Policy headers: the store must work without cross-origin isolation. + browser: { + enabled: true, + headless: true, + provider: playwright(), + instances: [{ browser: "chromium" }], + }, + }, + }, + ], + }, +}); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 69e49c2..9c5534c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -264,7 +264,7 @@ importers: version: 8.71.0(eslint@10.12.0(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) vitest: specifier: 5.0.3 - version: 5.0.3(@types/node@26.6.4)(@vitest/coverage-v8@5.0.3)(vite@8.3.2(@types/node@26.6.4)(jiti@2.7.0)(yaml@2.9.1)) + version: 5.0.3(@types/node@26.6.4)(@vitest/browser-playwright@5.0.3)(@vitest/coverage-v8@5.0.3)(@vitest/ui@5.0.3)(vite@8.3.2(@types/node@26.6.4)(jiti@2.7.0)(yaml@2.9.1)) packages/adapters/graph/source-github-issues: dependencies: @@ -341,7 +341,7 @@ importers: version: 26.6.4 '@vitest/coverage-v8': specifier: 5.0.3 - version: 5.0.3(vitest@5.0.3) + version: 5.0.3(@vitest/browser@5.0.3)(vitest@5.0.3) eslint: specifier: 10.12.0 version: 10.12.0(jiti@2.7.0)(supports-color@7.2.0) @@ -353,7 +353,65 @@ importers: version: 6.0.3 vitest: specifier: 5.0.3 - version: 5.0.3(@types/node@26.6.4)(@vitest/coverage-v8@5.0.3)(vite@8.3.2(@types/node@26.6.4)(jiti@2.7.0)(yaml@2.9.1)) + version: 5.0.3(@types/node@26.6.4)(@vitest/browser-playwright@5.0.3)(@vitest/coverage-v8@5.0.3)(@vitest/ui@5.0.3)(vite@8.3.2(@types/node@26.6.4)(jiti@2.7.0)(yaml@2.9.1)) + + packages/adapters/graph/sqlite-wasm: + dependencies: + '@exadev/process-graph-store-contract': + specifier: 0.0.0 + version: link:../../../core/graph/store-contract + '@exadev/process-graph-store-sqlite': + specifier: 0.0.0 + version: link:../store-sqlite + '@orpc/client': + specifier: 2.0.0-beta.41 + version: 2.0.0-beta.41 + '@orpc/server': + specifier: 2.0.0-beta.41 + version: 2.0.0-beta.41 + '@sqlite.org/sqlite-wasm': + specifier: 3.53.4-build2 + version: 3.53.4-build2 + drizzle-orm: + specifier: 1.0.0-rc.4 + version: 1.0.0-rc.4(zod@4.6.5) + devDependencies: + '@arethetypeswrong/cli': + specifier: 0.18.5 + version: 0.18.5 + '@stryker-mutator/core': + specifier: 10.0.0 + version: 10.0.0(@types/node@26.6.4) + '@stryker-mutator/typescript-checker': + specifier: 10.0.0 + version: 10.0.0(@stryker-mutator/core@10.0.0(@types/node@26.6.4))(typescript@6.0.3) + '@stryker-mutator/vitest-runner': + specifier: 10.0.0 + version: 10.0.0(@stryker-mutator/core@10.0.0(@types/node@26.6.4))(vitest@5.0.3) + '@types/node': + specifier: 26.6.4 + version: 26.6.4 + '@vitest/browser-playwright': + specifier: 5.0.3 + version: 5.0.3(playwright@1.63.0)(vite@8.3.2(@types/node@26.6.4)(jiti@2.7.0)(yaml@2.9.1))(vitest@5.0.3) + '@vitest/coverage-v8': + specifier: 5.0.3 + version: 5.0.3(@vitest/browser@5.0.3)(vitest@5.0.3) + eslint: + specifier: 10.12.0 + version: 10.12.0(jiti@2.7.0)(supports-color@7.2.0) + playwright: + specifier: 1.63.0 + version: 1.63.0 + publint: + specifier: 0.3.25 + version: 0.3.25 + typescript: + specifier: 6.0.3 + version: 6.0.3 + vitest: + specifier: 5.0.3 + version: 5.0.3(@types/node@26.6.4)(@vitest/browser-playwright@5.0.3)(@vitest/coverage-v8@5.0.3)(@vitest/ui@5.0.3)(vite@8.3.2(@types/node@26.6.4)(jiti@2.7.0)(yaml@2.9.1)) packages/adapters/graph/store-sqlite: dependencies: @@ -393,7 +451,7 @@ importers: version: 26.6.4 '@vitest/coverage-v8': specifier: 5.0.3 - version: 5.0.3(vitest@5.0.3) + version: 5.0.3(@vitest/browser@5.0.3)(vitest@5.0.3) drizzle-kit: specifier: 1.0.0-rc.4 version: 1.0.0-rc.4 @@ -408,7 +466,7 @@ importers: version: 6.0.3 vitest: specifier: 5.0.3 - version: 5.0.3(@types/node@26.6.4)(@vitest/coverage-v8@5.0.3)(vite@8.3.2(@types/node@26.6.4)(jiti@2.7.0)(yaml@2.9.1)) + version: 5.0.3(@types/node@26.6.4)(@vitest/browser-playwright@5.0.3)(@vitest/coverage-v8@5.0.3)(@vitest/ui@5.0.3)(vite@8.3.2(@types/node@26.6.4)(jiti@2.7.0)(yaml@2.9.1)) packages/core/graph/dag: devDependencies: @@ -429,7 +487,7 @@ importers: version: 26.6.4 '@vitest/coverage-v8': specifier: 5.0.3 - version: 5.0.3(vitest@5.0.3) + version: 5.0.3(@vitest/browser@5.0.3)(vitest@5.0.3) eslint: specifier: 10.12.0 version: 10.12.0(jiti@2.7.0)(supports-color@7.2.0) @@ -441,7 +499,7 @@ importers: version: 6.0.3 vitest: specifier: 5.0.3 - version: 5.0.3(@types/node@26.6.4)(@vitest/coverage-v8@5.0.3)(vite@8.3.2(@types/node@26.6.4)(jiti@2.7.0)(yaml@2.9.1)) + version: 5.0.3(@types/node@26.6.4)(@vitest/browser-playwright@5.0.3)(@vitest/coverage-v8@5.0.3)(@vitest/ui@5.0.3)(vite@8.3.2(@types/node@26.6.4)(jiti@2.7.0)(yaml@2.9.1)) packages/core/graph/domain: dependencies: @@ -472,7 +530,7 @@ importers: version: 26.6.4 '@vitest/coverage-v8': specifier: 5.0.3 - version: 5.0.3(vitest@5.0.3) + version: 5.0.3(@vitest/browser@5.0.3)(vitest@5.0.3) eslint: specifier: 10.12.0 version: 10.12.0(jiti@2.7.0)(supports-color@7.2.0) @@ -484,7 +542,7 @@ importers: version: 6.0.3 vitest: specifier: 5.0.3 - version: 5.0.3(@types/node@26.6.4)(@vitest/coverage-v8@5.0.3)(vite@8.3.2(@types/node@26.6.4)(jiti@2.7.0)(yaml@2.9.1)) + version: 5.0.3(@types/node@26.6.4)(@vitest/browser-playwright@5.0.3)(@vitest/coverage-v8@5.0.3)(@vitest/ui@5.0.3)(vite@8.3.2(@types/node@26.6.4)(jiti@2.7.0)(yaml@2.9.1)) packages/core/graph/layout: dependencies: @@ -515,7 +573,7 @@ importers: version: 26.6.4 '@vitest/coverage-v8': specifier: 5.0.3 - version: 5.0.3(vitest@5.0.3) + version: 5.0.3(@vitest/browser@5.0.3)(vitest@5.0.3) eslint: specifier: 10.12.0 version: 10.12.0(jiti@2.7.0)(supports-color@7.2.0) @@ -527,7 +585,7 @@ importers: version: 6.0.3 vitest: specifier: 5.0.3 - version: 5.0.3(@types/node@26.6.4)(@vitest/coverage-v8@5.0.3)(vite@8.3.2(@types/node@26.6.4)(jiti@2.7.0)(yaml@2.9.1)) + version: 5.0.3(@types/node@26.6.4)(@vitest/browser-playwright@5.0.3)(@vitest/coverage-v8@5.0.3)(@vitest/ui@5.0.3)(vite@8.3.2(@types/node@26.6.4)(jiti@2.7.0)(yaml@2.9.1)) packages/core/graph/projection: dependencies: @@ -552,7 +610,7 @@ importers: version: 26.6.4 '@vitest/coverage-v8': specifier: 5.0.3 - version: 5.0.3(vitest@5.0.3) + version: 5.0.3(@vitest/browser@5.0.3)(vitest@5.0.3) eslint: specifier: 10.12.0 version: 10.12.0(jiti@2.7.0)(supports-color@7.2.0) @@ -564,7 +622,7 @@ importers: version: 6.0.3 vitest: specifier: 5.0.3 - version: 5.0.3(@types/node@26.6.4)(@vitest/coverage-v8@5.0.3)(vite@8.3.2(@types/node@26.6.4)(jiti@2.7.0)(yaml@2.9.1)) + version: 5.0.3(@types/node@26.6.4)(@vitest/browser-playwright@5.0.3)(@vitest/coverage-v8@5.0.3)(@vitest/ui@5.0.3)(vite@8.3.2(@types/node@26.6.4)(jiti@2.7.0)(yaml@2.9.1)) packages/core/graph/schema: dependencies: @@ -592,7 +650,7 @@ importers: version: 26.6.4 '@vitest/coverage-v8': specifier: 5.0.3 - version: 5.0.3(vitest@5.0.3) + version: 5.0.3(@vitest/browser@5.0.3)(vitest@5.0.3) eslint: specifier: 10.12.0 version: 10.12.0(jiti@2.7.0)(supports-color@7.2.0) @@ -604,7 +662,7 @@ importers: version: 6.0.3 vitest: specifier: 5.0.3 - version: 5.0.3(@types/node@26.6.4)(@vitest/coverage-v8@5.0.3)(vite@8.3.2(@types/node@26.6.4)(jiti@2.7.0)(yaml@2.9.1)) + version: 5.0.3(@types/node@26.6.4)(@vitest/browser-playwright@5.0.3)(@vitest/coverage-v8@5.0.3)(@vitest/ui@5.0.3)(vite@8.3.2(@types/node@26.6.4)(jiti@2.7.0)(yaml@2.9.1)) packages/core/graph/source-contract: dependencies: @@ -699,7 +757,7 @@ importers: version: 26.6.4 '@vitest/coverage-v8': specifier: 5.0.3 - version: 5.0.3(vitest@5.0.3) + version: 5.0.3(@vitest/browser@5.0.3)(vitest@5.0.3) eslint: specifier: 10.12.0 version: 10.12.0(jiti@2.7.0)(supports-color@7.2.0) @@ -711,7 +769,7 @@ importers: version: 6.0.3 vitest: specifier: 5.0.3 - version: 5.0.3(@types/node@26.6.4)(@vitest/coverage-v8@5.0.3)(vite@8.3.2(@types/node@26.6.4)(jiti@2.7.0)(yaml@2.9.1)) + version: 5.0.3(@types/node@26.6.4)(@vitest/browser-playwright@5.0.3)(@vitest/coverage-v8@5.0.3)(@vitest/ui@5.0.3)(vite@8.3.2(@types/node@26.6.4)(jiti@2.7.0)(yaml@2.9.1)) packages/targets/process-graph: dependencies: @@ -745,7 +803,7 @@ importers: version: 26.6.4 '@vitest/coverage-v8': specifier: 5.0.3 - version: 5.0.3(vitest@5.0.3) + version: 5.0.3(@vitest/browser@5.0.3)(vitest@5.0.3) eslint: specifier: 10.12.0 version: 10.12.0(jiti@2.7.0)(supports-color@7.2.0) @@ -757,7 +815,7 @@ importers: version: 6.0.3 vitest: specifier: 5.0.3 - version: 5.0.3(@types/node@26.6.4)(@vitest/coverage-v8@5.0.3)(vite@8.3.2(@types/node@26.6.4)(jiti@2.7.0)(yaml@2.9.1)) + version: 5.0.3(@types/node@26.6.4)(@vitest/browser-playwright@5.0.3)(@vitest/coverage-v8@5.0.3)(@vitest/ui@5.0.3)(vite@8.3.2(@types/node@26.6.4)(jiti@2.7.0)(yaml@2.9.1)) packages: @@ -995,6 +1053,9 @@ packages: resolution: {integrity: sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA==} engines: {node: '>=18'} + '@blazediff/core@1.10.0': + resolution: {integrity: sha512-AOQff0zgR7cGsZL+4E7hVkmujoPUpm0J9xzWGWZj5wCjd3gmxESXAPfKyuzs93VdpQNFhHlBhfOjrcZ+XTERtQ==} + '@braidai/lang@1.1.2': resolution: {integrity: sha512-qBcknbBufNHlui137Hft8xauQMTZDKdophmLFv05r2eNmdIv/MlPuP4TdUknHG68UdWLgVZwgxVe735HzJNIwA==} @@ -1894,6 +1955,9 @@ packages: resolution: {integrity: sha512-//0sR/cow/s4ICQaYoAobOl4aU8cjU6x/V24V7XkKotb9+O+3zySIYp146vpaobYHnxa4pZX8NkV54Z5AwbDKA==} engines: {node: '>=12'} + '@polka/url@1.0.0-next.29': + resolution: {integrity: sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww==} + '@publint/pack@0.1.7': resolution: {integrity: sha512-4EDEmvxWtgsCnnVeBvtFIFZtUhPPt1+bA9JrSwU4Sa//6oKtzCSlGGXYJr44OD9aGISymbieJ4mCKHUygUDU+g==} engines: {node: '>=18'} @@ -2064,6 +2128,10 @@ packages: resolution: {integrity: sha512-tlqY9xq5ukxTUZBmoOp+m61cqwQD5pHJtFY3Mn8CA8ps6yghLH/Hw8UPdqg4OLmFW3IFlcXnQNmo/dh8HzXYIQ==} engines: {node: '>=18'} + '@sqlite.org/sqlite-wasm@3.53.4-build2': + resolution: {integrity: sha512-kQVArGJA4z7s56Dm8t+eTBXad4LiohuVj9pfgDNOcxa+Ic5xMrWlLPTq6cT0hw5S4MIvtpsraW+B0wD+IPvC7A==} + engines: {node: '>=22'} + '@standard-schema/spec@1.1.0': resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==} @@ -2346,6 +2414,17 @@ packages: resolution: {integrity: sha512-8eQ9R218XORK+KLosnf4bu/QsUXvUyVwTbArg7/0NMB1Pu87OJKvj4nhFblkYE8gQV73mW1dx1ptlPCkwRGa7A==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@vitest/browser-playwright@5.0.3': + resolution: {integrity: sha512-rYBHwIFL/ZtI6dl0rbrAatScXCrvX+TL2BX2AGOigkC1Y6u3rIWw8KMQJJMS4drSa1ksMCmZU2wpgwdE7kqu3g==} + peerDependencies: + playwright: '*' + vitest: 5.0.3 + + '@vitest/browser@5.0.3': + resolution: {integrity: sha512-7KHIkVA/gTRxQV9qPa9qLK+g5INZG6iRrlwXSFG5EKsOomW3UzzW7wsdt1Yp5PufzQ43lniuONlb4lUVCC2qSg==} + peerDependencies: + vitest: 5.0.3 + '@vitest/coverage-v8@5.0.3': resolution: {integrity: sha512-+klsyz7BvT1vCU28Zkfzms1Ia78XD0V41U3FUtRaa3S+vOr/EXvK1O6BvVD7l1RzEjm6SONR2aybOvDDf9u0mQ==} peerDependencies: @@ -2374,9 +2453,20 @@ packages: vite: optional: true + '@vitest/pretty-format@5.0.3': + resolution: {integrity: sha512-3hr5eV5z2ff0l2Q1vaKQbB4j3M3tElw8stDokEOTqXJB3Es5VrT8gBRIMY59gGuFp8Y61sWlWFK1aNUH5dHDYw==} + '@vitest/spy@5.0.3': resolution: {integrity: sha512-XhFysQTB8AZ+P4gMi+Lpo99vg2AZi0qKpaB9yXQl37+CaMEAPO3iH/wGVnSyL5MPERiLezpqTVtrR6UZH5GCXg==} + '@vitest/ui@5.0.3': + resolution: {integrity: sha512-9dfvqDBvSbHM6y9YVXxAypkl2z7mrWozK/JFo237qI0cj/3M/kzDKiVZqzi3TE54Ler7Jditud7OMBZCyUvRHA==} + peerDependencies: + vitest: 5.0.3 + + '@vitest/utils@5.0.3': + resolution: {integrity: sha512-77fDTZv8eu1CzlIfgPU2yrl5MXSUaUAOvgdZEeVm3X3WIxUubIX2SforqS6176lT32HCF6uBVMYVqfHxw7o6eg==} + acorn-jsx@5.3.2: resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} peerDependencies: @@ -3828,6 +3918,10 @@ packages: resolution: {integrity: sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==} engines: {node: '>=4'} + mrmime@2.0.1: + resolution: {integrity: sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==} + engines: {node: '>=10'} + ms@2.1.3: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} @@ -4099,6 +4193,9 @@ packages: resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} engines: {node: '>=8'} + pathe@2.0.3: + resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} + picocolors@1.1.1: resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} @@ -4118,6 +4215,20 @@ packages: resolution: {integrity: sha512-C+VUP+8jis7EsQZIhDYmS5qlNtjv2yP4SNtjXK9AP1ZcTRlnSfuumaTnRfYZnYgUUYVIKqL0fRvmUGDV2fmp6g==} engines: {node: '>=4'} + playwright-core@1.63.0: + resolution: {integrity: sha512-rYCsBF/M5HjUch52bbtVONEFjv6Xu8sm8h72dNlR5bzIE1fvC/bxgspzkjSfU+MweEMmPM8KJebG6nnyxo5mCg==} + engines: {node: '>=20'} + hasBin: true + + playwright@1.63.0: + resolution: {integrity: sha512-+7ziBLidS4NaNCdt57SUDT+wYmmd5fmiQejUic/kb+YsYSCPyOOE9sebzMjNmQrsnNpDJqd4WHvV/8lfKfUDUg==} + engines: {node: '>=20'} + hasBin: true + + pngjs@7.0.0: + resolution: {integrity: sha512-LKWqWJRhstyYo9pGvgor/ivk2w94eSjE3RGVuzLGlr3NmD8bf7RcYGze1mNdEHRP6TRP6rMuDHk5t44hnTRyow==} + engines: {node: '>=14.19.0'} + postcss@8.5.28: resolution: {integrity: sha512-RRuzqDtt5Y9h3quz5hWhK+TPnsmVs6WwSU6LkJMeY4HstUEDuYTG8UJSdawMRzmzAtV+KEoG8N3Qg2qLy5vM/A==} engines: {node: ^10 || ^12 || >=14} @@ -4298,6 +4409,10 @@ packages: resolution: {integrity: sha512-iuh+gPf28RkltuJC7W5MRi6XAjTDCAPC/prJUpQoG4vIP3MJZ+GTydVnodXA7pwvTKb2cA0m9OFZW/cdWy/I/w==} engines: {node: '>=6'} + sirv@3.0.2: + resolution: {integrity: sha512-2wcC/oGxHis/BoHkkPwldgiPSYcpZK3JU28WoMVv55yHJgcZ8rlXvuG9iZggz+sU1d4bRgIGASwyWqjxu3FM0g==} + engines: {node: '>=18'} + skin-tone@2.0.0: resolution: {integrity: sha512-kUMbT1oBJCpgrnKoSr0o6wPtvRWT9W9UKvGLwfJYO2WuahZRHOpEyL1ckyMGgMWh0UdpmaoFqKKD29WTomNEGA==} engines: {node: '>=8'} @@ -4511,6 +4626,10 @@ packages: resolution: {integrity: sha512-41wJyvKep3yT2tyPqX/4blcfybknGB4D+oETKLs7Q76UiPqRpUJK3hr1nxelyYO0PHKVzJwlu0aCeEAsGI6rpw==} engines: {node: '>=20'} + totalist@3.0.1: + resolution: {integrity: sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==} + engines: {node: '>=6'} + traverse@0.6.8: resolution: {integrity: sha512-aXJDbk6SnumuaZSANd21XAo15ucCDE38H4fkqiGsc3MhCK+wOlZvLP9cB/TvpHT0mOyWgC4Z8EwRlzqYSUzdsA==} engines: {node: '>= 0.4'} @@ -4809,6 +4928,18 @@ packages: resolution: {integrity: sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==} engines: {node: '>=18'} + ws@8.22.0: + resolution: {integrity: sha512-Ydggc987+RO0AnWtZ/7Wq9FtNvcrL1b/RO0ud9mWjUPgDrsAAwQSF51sm2hm1XofbU/4jkpGEsLFsZZxU+1DOg==} + 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 + xtend@4.0.2: resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==} engines: {node: '>=0.4'} @@ -5142,6 +5273,8 @@ snapshots: '@bcoe/v8-coverage@1.0.2': {} + '@blazediff/core@1.10.0': {} + '@braidai/lang@1.1.2': {} '@cacheable/memory@2.2.0': @@ -5915,6 +6048,8 @@ snapshots: '@pnpm/network.ca-file': 1.0.2 config-chain: 1.1.13 + '@polka/url@1.0.0-next.29': {} + '@publint/pack@0.1.7': dependencies: tinyexec: 1.3.1 @@ -6076,6 +6211,8 @@ snapshots: '@sindresorhus/merge-streams@4.0.0': {} + '@sqlite.org/sqlite-wasm@3.53.4-build2': {} + '@standard-schema/spec@1.1.0': {} '@standard-server/aws-lambda@0.10.0': @@ -6184,7 +6321,7 @@ snapshots: '@stryker-mutator/util': 10.0.0 semver: 7.8.5 tslib: 2.8.1 - vitest: 5.0.3(@types/node@26.6.4)(@vitest/coverage-v8@5.0.3)(vite@8.3.2(@types/node@26.6.4)(jiti@2.7.0)(yaml@2.9.1)) + vitest: 5.0.3(@types/node@26.6.4)(@vitest/browser-playwright@5.0.3)(@vitest/coverage-v8@5.0.3)(@vitest/ui@5.0.3)(vite@8.3.2(@types/node@26.6.4)(jiti@2.7.0)(yaml@2.9.1)) '@stylistic/eslint-plugin@6.0.0-beta.6(eslint@10.12.0(jiti@2.7.0)(supports-color@7.2.0))': dependencies: @@ -6453,7 +6590,38 @@ snapshots: '@typescript-eslint/types': 8.71.0 eslint-visitor-keys: 5.0.1 - '@vitest/coverage-v8@5.0.3(vitest@5.0.3)': + '@vitest/browser-playwright@5.0.3(playwright@1.63.0)(vite@8.3.2(@types/node@26.6.4)(jiti@2.7.0)(yaml@2.9.1))(vitest@5.0.3)': + dependencies: + '@vitest/browser': 5.0.3(vite@8.3.2(@types/node@26.6.4)(jiti@2.7.0)(yaml@2.9.1))(vitest@5.0.3) + '@vitest/mocker': 5.0.3(vite@8.3.2(@types/node@26.6.4)(jiti@2.7.0)(yaml@2.9.1)) + playwright: 1.63.0 + tinyrainbow: 3.2.0 + vitest: 5.0.3(@types/node@26.6.4)(@vitest/browser-playwright@5.0.3)(@vitest/coverage-v8@5.0.3)(@vitest/ui@5.0.3)(vite@8.3.2(@types/node@26.6.4)(jiti@2.7.0)(yaml@2.9.1)) + transitivePeerDependencies: + - bufferutil + - msw + - utf-8-validate + - vite + + '@vitest/browser@5.0.3(vite@8.3.2(@types/node@26.6.4)(jiti@2.7.0)(yaml@2.9.1))(vitest@5.0.3)': + dependencies: + '@blazediff/core': 1.10.0 + '@vitest/mocker': 5.0.3(vite@8.3.2(@types/node@26.6.4)(jiti@2.7.0)(yaml@2.9.1)) + '@vitest/ui': 5.0.3(vitest@5.0.3) + '@vitest/utils': 5.0.3 + magic-string: 1.4.2 + pngjs: 7.0.0 + sirv: 3.0.2 + tinyrainbow: 3.2.0 + vitest: 5.0.3(@types/node@26.6.4)(@vitest/browser-playwright@5.0.3)(@vitest/coverage-v8@5.0.3)(@vitest/ui@5.0.3)(vite@8.3.2(@types/node@26.6.4)(jiti@2.7.0)(yaml@2.9.1)) + ws: 8.22.0 + transitivePeerDependencies: + - bufferutil + - msw + - utf-8-validate + - vite + + '@vitest/coverage-v8@5.0.3(@vitest/browser@5.0.3)(vitest@5.0.3)': dependencies: '@bcoe/v8-coverage': 1.0.2 '@vitest/istanbul-lib-coverage': 1.0.2 @@ -6463,7 +6631,9 @@ snapshots: obug: 2.2.1 std-env: 4.3.0 tinyrainbow: 3.2.0 - vitest: 5.0.3(@types/node@26.6.4)(@vitest/coverage-v8@5.0.3)(vite@8.3.2(@types/node@26.6.4)(jiti@2.7.0)(yaml@2.9.1)) + vitest: 5.0.3(@types/node@26.6.4)(@vitest/browser-playwright@5.0.3)(@vitest/coverage-v8@5.0.3)(@vitest/ui@5.0.3)(vite@8.3.2(@types/node@26.6.4)(jiti@2.7.0)(yaml@2.9.1)) + optionalDependencies: + '@vitest/browser': 5.0.3(vite@8.3.2(@types/node@26.6.4)(jiti@2.7.0)(yaml@2.9.1))(vitest@5.0.3) '@vitest/istanbul-lib-coverage@1.0.2': {} @@ -6480,8 +6650,28 @@ snapshots: optionalDependencies: vite: 8.3.2(@types/node@26.6.4)(jiti@2.7.0)(yaml@2.9.1) + '@vitest/pretty-format@5.0.3': + dependencies: + tinyrainbow: 3.2.0 + '@vitest/spy@5.0.3': {} + '@vitest/ui@5.0.3(vitest@5.0.3)': + dependencies: + '@vitest/utils': 5.0.3 + fflate: 0.8.3 + flatted: 3.4.4 + pathe: 2.0.3 + sirv: 3.0.2 + tinyrainbow: 3.2.0 + vitest: 5.0.3(@types/node@26.6.4)(@vitest/browser-playwright@5.0.3)(@vitest/coverage-v8@5.0.3)(@vitest/ui@5.0.3)(vite@8.3.2(@types/node@26.6.4)(jiti@2.7.0)(yaml@2.9.1)) + + '@vitest/utils@5.0.3': + dependencies: + '@vitest/pretty-format': 5.0.3 + convert-source-map: 2.0.0 + tinyrainbow: 3.2.0 + acorn-jsx@5.3.2(acorn@8.18.0): dependencies: acorn: 8.18.0 @@ -7973,6 +8163,8 @@ snapshots: mri@1.2.0: {} + mrmime@2.0.1: {} + ms@2.1.3: {} mutation-server-protocol@0.4.1: @@ -8190,6 +8382,8 @@ snapshots: path-type@4.0.0: {} + pathe@2.0.3: {} + picocolors@1.1.1: {} picomatch@2.3.2: {} @@ -8203,6 +8397,14 @@ snapshots: find-up: 2.1.0 load-json-file: 4.0.0 + playwright-core@1.63.0: {} + + playwright@1.63.0: + dependencies: + playwright-core: 1.63.0 + + pngjs@7.0.0: {} + postcss@8.5.28: dependencies: nanoid: 3.3.19 @@ -8430,6 +8632,12 @@ snapshots: figures: 2.0.0 pkg-conf: 2.1.0 + sirv@3.0.2: + dependencies: + '@polka/url': 1.0.0-next.29 + mrmime: 2.0.1 + totalist: 3.0.1 + skin-tone@2.0.0: dependencies: unicode-emoji-modifier-base: 1.0.0 @@ -8623,6 +8831,8 @@ snapshots: '@sindresorhus/base62': 1.0.0 reserved-identifiers: 1.2.0 + totalist@3.0.1: {} + traverse@0.6.8: {} tree-kill@1.2.2: {} @@ -8781,7 +8991,7 @@ snapshots: jiti: 2.7.0 yaml: 2.9.1 - vitest@5.0.3(@types/node@26.6.4)(@vitest/coverage-v8@5.0.3)(vite@8.3.2(@types/node@26.6.4)(jiti@2.7.0)(yaml@2.9.1)): + vitest@5.0.3(@types/node@26.6.4)(@vitest/browser-playwright@5.0.3)(@vitest/coverage-v8@5.0.3)(@vitest/ui@5.0.3)(vite@8.3.2(@types/node@26.6.4)(jiti@2.7.0)(yaml@2.9.1)): dependencies: '@types/chai': 5.2.3 '@vitest/mocker': 5.0.3(vite@8.3.2(@types/node@26.6.4)(jiti@2.7.0)(yaml@2.9.1)) @@ -8799,7 +9009,9 @@ snapshots: why-is-node-running: 3.2.1 optionalDependencies: '@types/node': 26.6.4 - '@vitest/coverage-v8': 5.0.3(vitest@5.0.3) + '@vitest/browser-playwright': 5.0.3(playwright@1.63.0)(vite@8.3.2(@types/node@26.6.4)(jiti@2.7.0)(yaml@2.9.1))(vitest@5.0.3) + '@vitest/coverage-v8': 5.0.3(@vitest/browser@5.0.3)(vitest@5.0.3) + '@vitest/ui': 5.0.3(vitest@5.0.3) transitivePeerDependencies: - msw @@ -8833,6 +9045,8 @@ snapshots: string-width: 7.2.0 strip-ansi: 7.2.0 + ws@8.22.0: {} + xtend@4.0.2: {} y18n@5.0.8: {} diff --git a/tsconfig.build.json b/tsconfig.build.json index 9c492a3..19cf48c 100644 --- a/tsconfig.build.json +++ b/tsconfig.build.json @@ -31,6 +31,9 @@ { "path": "packages/adapters/graph/sqlite-node/tsconfig.build.json" }, + { + "path": "packages/adapters/graph/sqlite-wasm/tsconfig.build.json" + }, { "path": "packages/targets/process-graph/tsconfig.build.json" } From f099fb8117f3b6584a9aa05201c17fbe3436eb90 Mon Sep 17 00:00:00 2001 From: Joseph Mearman Date: Sat, 10 Oct 2026 17:25:35 +0100 Subject: [PATCH 2/8] ci: install Chromium for the browser integration tests The SQLite wasm store's integration tests run in Chromium through Vitest's browser mode, so the test job installs the browser build the package's pinned Playwright expects before running them. --- .github/workflows/ci.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 68dcc49..2871c92 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -78,7 +78,8 @@ jobs: - uses: ./.github/actions/setup-workspace with: task: test - command: pnpm test:coverage && pnpm test:integration && pnpm test:smoke + # The SQLite wasm store's integration tests run in Chromium through Vitest's browser mode; Playwright installs the browser build its pinned version expects. + command: pnpm --filter @exadev/process-graph-sqlite-wasm exec playwright install --with-deps chromium && pnpm test:coverage && pnpm test:integration && pnpm test:smoke knip: name: Knip From 62e168e2d3941c4e460010f5b17205d831d1ac08 Mon Sep 17 00:00:00 2001 From: Joseph Mearman Date: Sat, 10 Oct 2026 20:56:46 +0100 Subject: [PATCH 3/8] build(sqlite-wasm): depend on the published store releases --- packages/adapters/graph/sqlite-wasm/package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/adapters/graph/sqlite-wasm/package.json b/packages/adapters/graph/sqlite-wasm/package.json index 84c86ec..aaea788 100644 --- a/packages/adapters/graph/sqlite-wasm/package.json +++ b/packages/adapters/graph/sqlite-wasm/package.json @@ -63,8 +63,8 @@ "worker" ], "dependencies": { - "@exadev/process-graph-store-contract": "0.0.0", - "@exadev/process-graph-store-sqlite": "0.0.0", + "@exadev/process-graph-store-contract": "1.0.1", + "@exadev/process-graph-store-sqlite": "1.0.0", "@orpc/client": "2.0.0-beta.41", "@orpc/server": "2.0.0-beta.41", "@sqlite.org/sqlite-wasm": "3.53.4-build2", From 3ee1fd9ca11959a74a1be2ca483abc7286da0bed Mon Sep 17 00:00:00 2001 From: Joseph Mearman Date: Sat, 10 Oct 2026 21:08:05 +0100 Subject: [PATCH 4/8] build(sqlite-wasm): depend on the current store contract release --- pnpm-lock.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 9c5534c..c6c7294 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -358,10 +358,10 @@ importers: packages/adapters/graph/sqlite-wasm: dependencies: '@exadev/process-graph-store-contract': - specifier: 0.0.0 + specifier: 1.0.1 version: link:../../../core/graph/store-contract '@exadev/process-graph-store-sqlite': - specifier: 0.0.0 + specifier: 1.0.0 version: link:../store-sqlite '@orpc/client': specifier: 2.0.0-beta.41 From 1810ddc73ab47d3411c4fab1deee6978376d9958 Mon Sep 17 00:00:00 2001 From: Joseph Mearman Date: Sat, 10 Oct 2026 22:01:58 +0100 Subject: [PATCH 5/8] build: ignore vitest browser-mode attachments --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 0f900e3..1595179 100644 --- a/.gitignore +++ b/.gitignore @@ -13,3 +13,4 @@ reports !CLAUDE.md !**/AGENTS.md !**/CLAUDE.md +.vitest From b88c4bba105e82c92b82fac6eecda675964f21cd Mon Sep 17 00:00:00 2001 From: Joseph Mearman Date: Sat, 10 Oct 2026 23:08:49 +0100 Subject: [PATCH 6/8] build(sqlite-wasm): keep the package internal to the workspace --- packages/adapters/graph/sqlite-wasm/package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/adapters/graph/sqlite-wasm/package.json b/packages/adapters/graph/sqlite-wasm/package.json index aaea788..e601459 100644 --- a/packages/adapters/graph/sqlite-wasm/package.json +++ b/packages/adapters/graph/sqlite-wasm/package.json @@ -2,6 +2,7 @@ "name": "@exadev/process-graph-sqlite-wasm", "version": "0.0.0", "description": "The SQLite graph store in the browser: SQLite compiled to WebAssembly in a dedicated worker on the OPFS sahpool VFS, shared safely between tabs with no special headers, served to the page over oRPC.", + "private": true, "type": "module", "repository": { "type": "git", From 02f13cb3b703c24f2b4f80495a43845517c412cc Mon Sep 17 00:00:00 2001 From: Joseph Mearman Date: Sun, 11 Oct 2026 00:10:29 +0100 Subject: [PATCH 7/8] build(sqlite-wasm): depend on the current store-sqlite release --- packages/adapters/graph/sqlite-wasm/package.json | 2 +- pnpm-lock.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/adapters/graph/sqlite-wasm/package.json b/packages/adapters/graph/sqlite-wasm/package.json index e601459..aa0293d 100644 --- a/packages/adapters/graph/sqlite-wasm/package.json +++ b/packages/adapters/graph/sqlite-wasm/package.json @@ -65,7 +65,7 @@ ], "dependencies": { "@exadev/process-graph-store-contract": "1.0.1", - "@exadev/process-graph-store-sqlite": "1.0.0", + "@exadev/process-graph-store-sqlite": "1.0.1", "@orpc/client": "2.0.0-beta.41", "@orpc/server": "2.0.0-beta.41", "@sqlite.org/sqlite-wasm": "3.53.4-build2", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c6c7294..b2aed6b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -361,7 +361,7 @@ importers: specifier: 1.0.1 version: link:../../../core/graph/store-contract '@exadev/process-graph-store-sqlite': - specifier: 1.0.0 + specifier: 1.0.1 version: link:../store-sqlite '@orpc/client': specifier: 2.0.0-beta.41 From 9d385cea69bf251a53078d700b2e00fb7db2bcf2 Mon Sep 17 00:00:00 2001 From: Joseph Mearman Date: Sun, 11 Oct 2026 00:30:19 +0100 Subject: [PATCH 8/8] build(sqlite-wasm): leave the package out of mutation testing Its tests drive a real browser over OPFS, which Stryker's sandboxed copies cannot start, so a run could only score the few in-process unit tests as if they were the package's coverage. --- packages/adapters/graph/sqlite-wasm/package.json | 5 ----- packages/adapters/graph/sqlite-wasm/stryker.config.ts | 3 --- pnpm-lock.yaml | 9 --------- 3 files changed, 17 deletions(-) delete mode 100644 packages/adapters/graph/sqlite-wasm/stryker.config.ts diff --git a/packages/adapters/graph/sqlite-wasm/package.json b/packages/adapters/graph/sqlite-wasm/package.json index aa0293d..c023edb 100644 --- a/packages/adapters/graph/sqlite-wasm/package.json +++ b/packages/adapters/graph/sqlite-wasm/package.json @@ -49,8 +49,6 @@ "_test:coverage": "vitest run --project unit --coverage", "test:smoke": "turbo run _test:smoke", "_test:smoke": "vitest run --project smoke", - "test:mutation": "turbo run _test:mutation", - "_test:mutation": "stryker run stryker.config.ts", "prepublishOnly": "pnpm run lint && pnpm run typecheck && pnpm run test && pnpm run build && publint && attw --pack --profile esm-only", "test:integration": "turbo run _test:integration", "_test:integration": "vitest run --project browser" @@ -73,9 +71,6 @@ }, "devDependencies": { "@arethetypeswrong/cli": "0.18.5", - "@stryker-mutator/core": "10.0.0", - "@stryker-mutator/typescript-checker": "10.0.0", - "@stryker-mutator/vitest-runner": "10.0.0", "@types/node": "26.6.4", "@vitest/browser-playwright": "5.0.3", "@vitest/coverage-v8": "5.0.3", diff --git a/packages/adapters/graph/sqlite-wasm/stryker.config.ts b/packages/adapters/graph/sqlite-wasm/stryker.config.ts deleted file mode 100644 index 6429b27..0000000 --- a/packages/adapters/graph/sqlite-wasm/stryker.config.ts +++ /dev/null @@ -1,3 +0,0 @@ -import { packageStrykerConfig } from "../../../../stryker.shared.ts"; - -export default packageStrykerConfig(); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b2aed6b..67c6aba 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -379,15 +379,6 @@ importers: '@arethetypeswrong/cli': specifier: 0.18.5 version: 0.18.5 - '@stryker-mutator/core': - specifier: 10.0.0 - version: 10.0.0(@types/node@26.6.4) - '@stryker-mutator/typescript-checker': - specifier: 10.0.0 - version: 10.0.0(@stryker-mutator/core@10.0.0(@types/node@26.6.4))(typescript@6.0.3) - '@stryker-mutator/vitest-runner': - specifier: 10.0.0 - version: 10.0.0(@stryker-mutator/core@10.0.0(@types/node@26.6.4))(vitest@5.0.3) '@types/node': specifier: 26.6.4 version: 26.6.4