first commit
This commit is contained in:
125
packages/cloudflare/src/db/do-dialect.ts
Normal file
125
packages/cloudflare/src/db/do-dialect.ts
Normal file
@@ -0,0 +1,125 @@
|
||||
/**
|
||||
* Kysely dialect for Durable Object preview databases
|
||||
*
|
||||
* Proxies all queries to an EmDashPreviewDB DO instance via RPC.
|
||||
* Preview mode is read-only — no transaction support needed.
|
||||
*/
|
||||
|
||||
import type {
|
||||
CompiledQuery,
|
||||
DatabaseConnection,
|
||||
DatabaseIntrospector,
|
||||
Dialect,
|
||||
Driver,
|
||||
Kysely,
|
||||
QueryResult,
|
||||
} from "kysely";
|
||||
import { SqliteAdapter, SqliteQueryCompiler } from "kysely";
|
||||
|
||||
import { D1Introspector } from "./d1-introspector.js";
|
||||
import type { QueryResult as DOQueryResult } from "./do-class.js";
|
||||
|
||||
/**
|
||||
* Minimal interface for the DO stub's RPC methods.
|
||||
*
|
||||
* We define this instead of using DurableObjectStub<EmDashPreviewDB> directly
|
||||
* because Rpc.Result<T> resolves to `never` when the return type contains
|
||||
* `unknown` (Record<string, unknown> in QueryResult.rows). This interface
|
||||
* gives us clean typing without fighting the Rpc type system.
|
||||
*/
|
||||
export interface PreviewDBStub {
|
||||
query(sql: string, params?: unknown[]): Promise<DOQueryResult>;
|
||||
}
|
||||
|
||||
export interface PreviewDODialectConfig {
|
||||
/**
|
||||
* Factory that returns a fresh DO stub on each call.
|
||||
*
|
||||
* DO stubs are bound to the request context that created them.
|
||||
* Since the Kysely instance may be cached across requests, we can't
|
||||
* hold a single stub — each connection must get a fresh one via
|
||||
* namespace.get(id), which is cheap (no RPC, just a local ref).
|
||||
*/
|
||||
getStub: () => PreviewDBStub;
|
||||
}
|
||||
|
||||
export class PreviewDODialect implements Dialect {
|
||||
readonly #config: PreviewDODialectConfig;
|
||||
|
||||
constructor(config: PreviewDODialectConfig) {
|
||||
this.#config = config;
|
||||
}
|
||||
|
||||
createAdapter(): SqliteAdapter {
|
||||
return new SqliteAdapter();
|
||||
}
|
||||
|
||||
createDriver(): Driver {
|
||||
return new PreviewDODriver(this.#config);
|
||||
}
|
||||
|
||||
createQueryCompiler(): SqliteQueryCompiler {
|
||||
return new SqliteQueryCompiler();
|
||||
}
|
||||
|
||||
createIntrospector(db: Kysely<any>): DatabaseIntrospector {
|
||||
return new D1Introspector(db);
|
||||
}
|
||||
}
|
||||
|
||||
class PreviewDODriver implements Driver {
|
||||
readonly #config: PreviewDODialectConfig;
|
||||
|
||||
constructor(config: PreviewDODialectConfig) {
|
||||
this.#config = config;
|
||||
}
|
||||
|
||||
async init(): Promise<void> {}
|
||||
|
||||
async acquireConnection(): Promise<DatabaseConnection> {
|
||||
return new PreviewDOConnection(this.#config.getStub());
|
||||
}
|
||||
|
||||
async beginTransaction(): Promise<void> {
|
||||
// No-op. Preview is read-only.
|
||||
}
|
||||
|
||||
async commitTransaction(): Promise<void> {
|
||||
// No-op.
|
||||
}
|
||||
|
||||
async rollbackTransaction(): Promise<void> {
|
||||
// No-op.
|
||||
}
|
||||
|
||||
async releaseConnection(): Promise<void> {}
|
||||
|
||||
async destroy(): Promise<void> {}
|
||||
}
|
||||
|
||||
class PreviewDOConnection implements DatabaseConnection {
|
||||
readonly #stub: PreviewDBStub;
|
||||
|
||||
constructor(stub: PreviewDBStub) {
|
||||
this.#stub = stub;
|
||||
}
|
||||
|
||||
async executeQuery<O>(compiledQuery: CompiledQuery): Promise<QueryResult<O>> {
|
||||
const sqlText = compiledQuery.sql;
|
||||
// eslint-disable-next-line typescript-eslint(no-unsafe-type-assertion) -- CompiledQuery.parameters is ReadonlyArray<unknown>, stub expects unknown[]
|
||||
const params = compiledQuery.parameters as unknown[];
|
||||
|
||||
const result = await this.#stub.query(sqlText, params);
|
||||
|
||||
return {
|
||||
// eslint-disable-next-line typescript-eslint(no-unsafe-type-assertion) -- Kysely generic O is the caller's row type; we trust the DB returned matching rows
|
||||
rows: result.rows as O[],
|
||||
numAffectedRows: result.changes !== undefined ? BigInt(result.changes) : undefined,
|
||||
};
|
||||
}
|
||||
|
||||
// eslint-disable-next-line require-yield -- interface requires AsyncIterableIterator but DO doesn't support streaming
|
||||
async *streamQuery<O>(): AsyncIterableIterator<QueryResult<O>> {
|
||||
throw new Error("Preview DO dialect does not support streaming");
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user