117 lines · 5.0 KB · TypeScript
RawDownload
// ---------------------------------------------------------------------------// Shared FumaDB assembly (pure, driver-agnostic).//// Every host (self-host, local, sdk-test, cloud) historically hand-rolled the// same driver-agnostic FumaDB wiring: build a fumadb factory from the latest// schema, bind it to an already-opened drizzle handle through `drizzleAdapter`,// and expose `{ db: fuma.orm(version), fuma }`. `createExecutorFumaDb` owns ONLY// that assembly — the caller still opens its own driver (libSQL for SQLite,// postgres-js for Postgres), applies its own PRAGMAs, and runs its own schema// bring-up. The factory is dialect-generic via the `provider` param.//// This is a pure helper, not the `DbProvider` Effect seam. The seam// (`DbProvider` / `dbProviderLayer`) is host-composition and lives in the host// layer (`@executor-js/api/server`). This assembly stays in the SDK because the// SDK's own sqlite test backend (`sqlite-test-db.ts`) builds its handle with it;// hosts reach it (and the seam) through `@executor-js/api/server`, which// re-exports `createExecutorFumaDb` from here. It is NOT on the plugin-author// root barrel — host code imports it from `@executor-js/sdk/host-internal`.// --------------------------------------------------------------------------- import { fumadb, type FumaDB } from "@executor-js/fumadb";import { type DrizzleRuntimeProvider } from "@executor-js/fumadb/adapters/drizzle";import { drizzleAdapter } from "@executor-js/fumadb/adapters/drizzle";import { schema as fumaSchema, type RelationsMap } from "@executor-js/fumadb/schema"; import type { BlobStore } from "./blob";import type { FumaDb, FumaTables } from "./fuma-runtime"; // The FumaDB provider both the runtime-schema generator and the drizzle adapter// understand. SQLite (libSQL) and PostgreSQL (postgres-js) are the only// dialects in use today.export type ExecutorDbProvider = DrizzleRuntimeProvider; export type ExecutorFumaSchema<TTables extends FumaTables> = ReturnType<  typeof fumaSchema<string, TTables, RelationsMap<TTables>>>; export interface ExecutorFumaDb<TTables extends FumaTables = FumaTables> {  readonly db: FumaDb<ExecutorFumaSchema<TTables>>;  readonly fuma: FumaDB<ExecutorFumaSchema<TTables>[]>;} export interface CreateExecutorFumaDbOptions<TTables extends FumaTables = FumaTables> {  readonly tables: TTables;  readonly namespace: string;  readonly version: string;  readonly provider: ExecutorDbProvider;  /**   * Whether the engine supports interactive transactions (BEGIN/COMMIT).   * Defaults to `true`. Cloudflare D1 must pass `false` — it rejects   * interactive transactions, so the adapter runs transaction callbacks   * directly (auto-commit per statement). libSQL/Postgres keep real   * transactions.   */  readonly interactiveTransactions?: boolean;  /**   * Maximum bound parameters per query (Cloudflare D1: 100). When set,   * `createMany` batches so `rows * columns` stays within it. Unset for   * libSQL/Postgres (no tight cap).   */  readonly maxBoundParameters?: number;} /** * Driver-agnostic FumaDB assembly. The caller passes an already-opened drizzle * handle (it owns the driver, PRAGMAs, and schema bring-up); this wires the * fumadb client over it and returns the `{ db, fuma }` query surface. * * NOTE: the drizzle `db` must already have its runtime schema attached (via * `createDrizzleRuntimeSchemaFromTables`) for SQLite/Postgres relational * queries to resolve — that schema generation stays caller-side because it is * coupled to the caller's drizzle() construction. */export const createExecutorFumaDb = <const TTables extends FumaTables>(  drizzleDb: unknown,  options: CreateExecutorFumaDbOptions<TTables>,): ExecutorFumaDb<TTables> => {  const latestSchema = fumaSchema({    version: options.version,    tables: options.tables,  });  const factory = fumadb({    namespace: options.namespace,    schemas: [latestSchema],  });  const fuma = factory.client(    drizzleAdapter({      db: drizzleDb,      provider: options.provider,      interactiveTransactions: options.interactiveTransactions,      maxBoundParameters: options.maxBoundParameters,    }),  );   return {    db: fuma.orm(options.version),    fuma,  };}; // The uniform handle each host exposes through the `DbProvider` Layer (defined// in the host layer). The `db`/`fuma` come from `createExecutorFumaDb`; `close`// releases the host's own driver. Hosts that keep extra connection objects (the// raw sqlite handle, the postgres `sql`) layer those into their own concrete// handle type and still satisfy this contract.export interface ExecutorDbHandle<  TTables extends FumaTables = FumaTables,> extends ExecutorFumaDb<TTables> {  readonly close: () => Promise<void>;  /**   * Optional blob backend assembled alongside the driver (an R2 bucket on the   * Cloudflare hosts). `makeScopedExecutor` threads it into   * `createExecutor({ blobs })`; absent, the executor defaults to the FumaDB   * `blob` table over `db`.   */  readonly blobs?: BlobStore;}