import type { ScopedQueryResult, ScopedRunResult, ScopedStatement, ScopedValue } from "./types";
const SCHEMA_KEY = "scoped-schema";
/** A stable fingerprint of the DDL, so a deploy that adds tables re-applies the schema once. */
function fingerprint(schema: readonly string[]): string {
let hash = 0;
for (const statement of schema) {
for (let index = 0; index < statement.length; index++) hash = (hash * 31 + statement.charCodeAt(index)) | 0;
}
return `${schema.length}:${hash}`;
}
/**
* The SQLite inside a scoped Durable Object: applies the scope's schema (every statement is IF NOT EXISTS, so a
* re-run only adds what's new) and runs statements for callers. One object per scope id, so every write here is
* serialized and `transactionSync` makes a batch atomic.
*/
export class ScopedStore {
private ready = false;
constructor(
private readonly storage: DurableObjectStorage,
private readonly schema: readonly string[],
) {}
get sql(): SqlStorage {
return this.storage.sql;
}
/** Must run before any query; cheap after the first call in an isolate. */
async prepare(): Promise<void> {
if (this.ready) return;
const print = fingerprint(this.schema);
if ((await this.storage.get<string>(SCHEMA_KEY)) !== print) {
for (const statement of this.schema) this.sql.exec(statement);
await this.storage.put(SCHEMA_KEY, print);
}
this.ready = true;
}