///
import { DurableObject } from "cloudflare:workers";
import { drizzle } from "drizzle-orm/durable-sqlite";
import { migrate } from "drizzle-orm/durable-sqlite/migrator";
import migrations from "../drizzle/durable-object/migrations.js";
import { SQLiteStateStoreOperations } from "../src/state/operations.ts";
import type { StateStoreProxy } from "../src/state/proxy.ts";
import * as schema from "../src/state/schema.ts";
interface Env {
STORE: DurableObjectNamespace;
STATE_TOKEN: string;
}
interface Context {
chain: string[];
}
const validateRequest = async (request: Request, env: Env) => {
const encoder = new TextEncoder();
const token = request.headers.get("Authorization")?.split(" ")[1];
if (!token) {
return false;
}
const expected = await crypto.subtle.digest(
"SHA-256",
encoder.encode(env.STATE_TOKEN),
);
const actual = await crypto.subtle.digest("SHA-256", encoder.encode(token));
// @ts-expect-error - timingSafeEqual is available in workerd
return await crypto.subtle.timingSafeEqual(expected, actual);
};
export default {
fetch: async (request, env) => {
if (!(await validateRequest(request, env))) {
return Response.json(
{
success: false,
status: 401,
error: "Unauthorized",
} as StateStoreProxy.ErrorResponse,
{ status: 401 },
);
}
if (request.method === "HEAD") {
return new Response(null, { status: 200 });
}
if (request.method !== "POST") {
return Response.json(
{
success: false,
status: 405,
error: "Method not allowed",
} as StateStoreProxy.ErrorResponse,
{ status: 405 },
);
}
try {
const store = env.STORE.get(env.STORE.idFromName("default"));
const body = (await request.json()) as StateStoreProxy.Request<
StateStoreProxy.Method,
Context
>;
const result = await store.rpc(body);
return Response.json(
{
success: true,
status: 200,
result,
} as StateStoreProxy.SuccessResponse,
{ status: 200 },
);
} catch (e) {
console.error(e);
return Response.json(
{
success: false,
status: 500,
error: String(e),
} as StateStoreProxy.ErrorResponse,
{ status: 500 },
);
}
},
} satisfies ExportedHandler;
export class Store extends DurableObject {
db = drizzle(this.ctx.storage, { schema });
constructor(ctx: DurableObjectState, env: Env) {
super(ctx, env);
ctx.blockConcurrencyWhile(async () => {
await migrate(this.db, migrations);
});
}
async rpc(request: StateStoreProxy.Request) {
const operations = new SQLiteStateStoreOperations(this.db, {
chain: request.context.chain,
});
return operations.dispatch(request.method, request.params);
}
}