diff --git a/packages/pulse/src/database.ts b/packages/pulse/src/database.ts new file mode 100644 index 0000000..f4f2c38 --- /dev/null +++ b/packages/pulse/src/database.ts @@ -0,0 +1,21 @@ +/** + * Abstract database interface for Pulse. + * + * Decouples core logic from bun:sqlite so alternative backends + * (e.g. Cloudflare D1) can provide their own implementation. + */ + +export interface PulseStatement { + get(...params: any[]): any; + all(...params: any[]): any[]; + run(...params: any[]): { changes: number; lastInsertRowid: number | bigint }; +} + +export interface PulseDatabase { + exec(sql: string): void; + prepare(sql: string): PulseStatement; + run(sql: string, ...params: any[]): void; + transaction(fn: () => T): (...args: any[]) => T; + close(): void; + readonly inTransaction: boolean; +} diff --git a/packages/pulse/src/defs.d.ts b/packages/pulse/src/defs.d.ts index 694095c..a8c8752 100644 --- a/packages/pulse/src/defs.d.ts +++ b/packages/pulse/src/defs.d.ts @@ -4,7 +4,7 @@ * Append-only definition tables for objects, events, and projections. * Content-addressed versioning with code_rev binding. */ -import type { Database } from 'bun:sqlite'; +import type { PulseDatabase as Database } from './database.js'; export interface ObjectDef { name: string; codeRev: string; diff --git a/packages/pulse/src/defs.ts b/packages/pulse/src/defs.ts index 86eeff9..c2e41f2 100644 --- a/packages/pulse/src/defs.ts +++ b/packages/pulse/src/defs.ts @@ -5,9 +5,9 @@ * Content-addressed versioning with code_rev binding. */ -import type { Database } from 'bun:sqlite'; import { createHash } from 'node:crypto'; import jsonata from 'jsonata'; +import type { PulseDatabase as Database } from './database.js'; // ── Types ────────────────────────────────────────────────────── diff --git a/packages/pulse/src/guard-projection.ts b/packages/pulse/src/guard-projection.ts index 78d8cb0..5b44585 100644 --- a/packages/pulse/src/guard-projection.ts +++ b/packages/pulse/src/guard-projection.ts @@ -3,7 +3,7 @@ * Core logic delegated to guard-core.ts (pure, portable). */ -import type { Database } from 'bun:sqlite'; +import type { PulseDatabase as Database } from './database.js'; import { checkGuardsCore, clearGuardExpressionCache as clearCoreCache, diff --git a/packages/pulse/src/index.ts b/packages/pulse/src/index.ts index b0a9581..bc7cdca 100644 --- a/packages/pulse/src/index.ts +++ b/packages/pulse/src/index.ts @@ -899,6 +899,10 @@ export function createRule( return (prev, curr, inner) => logic(accessor(prev), accessor(curr), inner); } +// ── Database Abstraction ──────────────────────────────────────── + +export type { PulseDatabase, PulseStatement } from './database.js'; + // ── Storage ──────────────────────────────────────────────────── export { diff --git a/packages/pulse/src/projection-engine.d.ts b/packages/pulse/src/projection-engine.d.ts index 101b6b2..4e0b6fd 100644 --- a/packages/pulse/src/projection-engine.d.ts +++ b/packages/pulse/src/projection-engine.d.ts @@ -4,7 +4,7 @@ * Incremental fold engine for projections with JSONata expressions. * Projections are first-class citizens with their own state table. */ -import type { Database } from 'bun:sqlite'; +import type { PulseDatabase as Database } from './database.js'; /** Clear the compiled expression cache (useful for testing). */ export declare function clearExpressionCache(): void; export interface ProjectionState { diff --git a/packages/pulse/src/projection-engine.ts b/packages/pulse/src/projection-engine.ts index 9017846..2a144b8 100644 --- a/packages/pulse/src/projection-engine.ts +++ b/packages/pulse/src/projection-engine.ts @@ -5,7 +5,7 @@ * Projections are first-class citizens with their own state table. */ -import type { Database } from 'bun:sqlite'; +import type { PulseDatabase as Database } from './database.js'; import jsonata, { type Expression } from 'jsonata'; import { getProjectionDef } from './defs.js'; diff --git a/packages/pulse/src/store.d.ts b/packages/pulse/src/store.d.ts index d71509d..4d4341b 100644 --- a/packages/pulse/src/store.d.ts +++ b/packages/pulse/src/store.d.ts @@ -5,6 +5,7 @@ * content-addressed object store (CAS) on disk via SHA-256 hashes. */ import { Database } from 'bun:sqlite'; +import type { PulseDatabase } from './database.js'; export interface EventRecord { id: number; occurredAt: number; @@ -99,8 +100,8 @@ export interface CreateScopedStoreOptions { export interface ScopedStore { scope(name: string): PulseStore; listScopes(): string[]; - /** Get underlying Database for scope (used by projection engine) */ - scopeDatabase(name: string): Database; + /** Get underlying database for scope (used by projection engine) */ + scopeDatabase(name: string): PulseDatabase; putObject(data: unknown): Promise; getObject(hash: string): Promise; close(): Promise; diff --git a/packages/pulse/src/store.ts b/packages/pulse/src/store.ts index 558a725..73e45da 100644 --- a/packages/pulse/src/store.ts +++ b/packages/pulse/src/store.ts @@ -6,6 +6,7 @@ */ import { Database } from 'bun:sqlite'; +import type { PulseDatabase } from './database.js'; import { createHash } from 'node:crypto'; import { existsSync, @@ -108,8 +109,8 @@ export interface PulseStore { /** Read data from CAS store by hash. Returns null if not found. */ getObject(hash: string): Promise; - /** Get the underlying bun:sqlite Database handle (for guard projections etc.) */ - getDatabase(): Database; + /** Get the underlying database handle (for guard projections etc.) */ + getDatabase(): PulseDatabase; /** Close the database */ close(): Promise; @@ -544,8 +545,8 @@ export interface ScopedStore { scope(name: string): PulseStore; listScopes(): string[]; - /** Get underlying Database for scope (used by projection engine) */ - scopeDatabase(name: string): Database; + /** Get underlying database for scope (used by projection engine) */ + scopeDatabase(name: string): PulseDatabase; putObject(data: unknown): Promise; getObject(hash: string): Promise;