diff --git a/packages/khala/migrations/0001_initial.sql b/packages/khala/migrations/0001_initial.sql new file mode 100644 index 0000000..99b91db --- /dev/null +++ b/packages/khala/migrations/0001_initial.sql @@ -0,0 +1,47 @@ +-- Agent registry +CREATE TABLE agents ( + id TEXT PRIMARY KEY, + token_hash TEXT NOT NULL, + created_at TEXT NOT NULL DEFAULT (datetime('now')) +); + +-- Workflow threads +CREATE TABLE threads ( + id TEXT PRIMARY KEY, + workflow TEXT NOT NULL, + status TEXT NOT NULL DEFAULT 'active', + initiator TEXT NOT NULL, + result TEXT, + created_at TEXT NOT NULL DEFAULT (datetime('now')), + updated_at TEXT NOT NULL DEFAULT (datetime('now')) +); + +-- Thread messages (append-only) +CREATE TABLE messages ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + thread_id TEXT NOT NULL REFERENCES threads (id), + role TEXT NOT NULL, + content TEXT NOT NULL, + meta TEXT, + step INTEGER NOT NULL, + agent_id TEXT, + created_at TEXT NOT NULL DEFAULT (datetime('now')) +); + +CREATE INDEX idx_messages_thread ON messages (thread_id, step); + +-- Task queue +CREATE TABLE tasks ( + id TEXT PRIMARY KEY, + thread_id TEXT NOT NULL REFERENCES threads (id), + role TEXT NOT NULL, + instruction TEXT NOT NULL, + status TEXT NOT NULL DEFAULT 'open', + claim_id TEXT, + claimed_by TEXT, + claimed_at TEXT, + timeout_seconds INTEGER NOT NULL DEFAULT 300, + created_at TEXT NOT NULL DEFAULT (datetime('now')) +); + +CREATE INDEX idx_tasks_status ON tasks (status, created_at); diff --git a/packages/khala/package.json b/packages/khala/package.json new file mode 100644 index 0000000..bf57d90 --- /dev/null +++ b/packages/khala/package.json @@ -0,0 +1,24 @@ +{ + "name": "@uncaged/khala", + "version": "0.0.1", + "private": true, + "type": "module", + "scripts": { + "dev": "wrangler dev", + "deploy": "wrangler deploy", + "test": "vitest run", + "check": "biome check ." + }, + "dependencies": { + "hono": "^4.7.0", + "jsonata": "^2.0.5", + "ulidx": "^2.4.1" + }, + "devDependencies": { + "@cloudflare/vitest-pool-workers": "^0.14.0", + "@cloudflare/workers-types": "^4.20250410.0", + "typescript": "^5.5.0", + "vitest": "^4.1.5", + "wrangler": "^4.14.0" + } +} diff --git a/packages/khala/src/auth.ts b/packages/khala/src/auth.ts new file mode 100644 index 0000000..21e23d3 --- /dev/null +++ b/packages/khala/src/auth.ts @@ -0,0 +1,48 @@ +import { createMiddleware } from "hono/factory"; +import type { KhalaBindings } from "./env.js"; + +const encoder = new TextEncoder(); + +function hexDigest(buf: ArrayBuffer): string { + return [...new Uint8Array(buf)].map((b) => b.toString(16).padStart(2, "0")).join(""); +} + +export async function sha256Hex(input: string): Promise { + const data = encoder.encode(input); + const dig = await crypto.subtle.digest("SHA-256", data); + return hexDigest(dig); +} + +export const agentAuth = createMiddleware<{ + Bindings: KhalaBindings; + Variables: { agentId: string }; +}>(async (c, next) => { + const header = c.req.header("Authorization"); + if (header === undefined || !header.startsWith("Bearer ")) { + return c.json({ error: "missing token" }, 401); + } + const token = header.slice(7); + const hash = await sha256Hex(token); + const agent = await c.env.DB.prepare("SELECT id FROM agents WHERE token_hash = ?") + .bind(hash) + .first<{ id: string }>(); + if (agent === null) { + return c.json({ error: "invalid token" }, 401); + } + c.set("agentId", agent.id); + return next(); +}); + +export function requireAdmin( + c: { req: { header: (k: string) => string | undefined }; env: KhalaBindings }, +): boolean { + const header = c.req.header("Authorization"); + const expected = c.env.ADMIN_SECRET; + if (expected.length === 0) { + return false; + } + if (header === undefined || !header.startsWith("Bearer ")) { + return false; + } + return header.slice(7) === expected; +} diff --git a/packages/khala/src/db.ts b/packages/khala/src/db.ts new file mode 100644 index 0000000..cc084a8 --- /dev/null +++ b/packages/khala/src/db.ts @@ -0,0 +1,389 @@ +import { monotonicFactory, type ULID } from "ulidx"; +import type { Agent, GetThreadMessagesOpts, Message, Task, Thread } from "./types.js"; +import type { Result } from "./result.js"; +import { err, ok } from "./result.js"; + +const generateUlid = monotonicFactory((): number => Date.now()); + +export function newId(): ULID { + return generateUlid(); +} + +export async function createThread( + db: D1Database, + workflow: string, + initiator: string, +): Promise { + const id = newId(); + const now = new Date().toISOString(); + await db + .prepare( + `INSERT INTO threads (id, workflow, status, initiator, result, created_at, updated_at) + VALUES (?, ?, 'active', ?, NULL, datetime('now'), datetime('now'))`, + ) + .bind(id, workflow, initiator) + .run(); + return { + id, + workflow, + status: "active", + initiator, + result: null, + created_at: now, + updated_at: now, + }; +} + +export async function getThread(db: D1Database, id: string): Promise { + const row = await db.prepare("SELECT * FROM threads WHERE id = ?").bind(id).first(); + if (!row) return null; + return rowToThread(row); +} + +type ThreadRow = { + id: string; + workflow: string; + status: string; + initiator: string; + result: string | null; + created_at: string; + updated_at: string; +}; + +function rowToThread(row: ThreadRow): Thread { + const status = row.status === "completed" || row.status === "failed" ? row.status : "active"; + return { + id: row.id, + workflow: row.workflow, + status, + initiator: row.initiator, + result: row.result, + created_at: row.created_at, + updated_at: row.updated_at, + }; +} + +export async function setThreadResult( + db: D1Database, + id: string, + result: string, + status: "completed" | "failed", +): Promise { + await db + .prepare( + `UPDATE threads SET result = ?, status = ?, updated_at = datetime('now') WHERE id = ?`, + ) + .bind(result, status, id) + .run(); +} + +export async function failThread(db: D1Database, id: string, result: string): Promise { + await setThreadResult(db, id, result, "failed"); +} + +export async function appendMessage( + db: D1Database, + threadId: string, + role: string, + content: string, + meta: string | null, + step: number, + agentId: string | null, +): Promise { + await db + .prepare( + `INSERT INTO messages (thread_id, role, content, meta, step, agent_id, created_at) + VALUES (?, ?, ?, ?, ?, ?, datetime('now'))`, + ) + .bind(threadId, role, content, meta, step, agentId) + .run(); + const row = await db + .prepare( + `SELECT * FROM messages WHERE thread_id = ? AND step = ? ORDER BY id DESC LIMIT 1`, + ) + .bind(threadId, step) + .first(); + if (!row) { + throw new Error("appendMessage: row not found after insert"); + } + return rowToMessage(row); +} + +type MessageRow = { + id: number; + thread_id: string; + role: string; + content: string; + meta: string | null; + step: number; + agent_id: string | null; + created_at: string; +}; + +function rowToMessage(row: MessageRow): Message { + return { + id: row.id, + thread_id: row.thread_id, + role: row.role, + content: row.content, + meta: row.meta, + step: row.step, + agent_id: row.agent_id, + created_at: row.created_at, + }; +} + +export async function getThreadMessages( + db: D1Database, + threadId: string, + opts: GetThreadMessagesOpts | null, +): Promise { + const o = opts ?? { role: null, minStep: null, maxStep: null, limit: null }; + const maxRows = o.limit === null ? 10_000 : Math.min(10_000, Math.max(1, o.limit)); + let sql = "SELECT * FROM messages WHERE thread_id = ?"; + const binds: (string | number)[] = [threadId]; + if (o.role !== null) { + sql += " AND role = ?"; + binds.push(o.role); + } + if (o.minStep !== null) { + sql += " AND step >= ?"; + binds.push(o.minStep); + } + if (o.maxStep !== null) { + sql += " AND step <= ?"; + binds.push(o.maxStep); + } + sql += " ORDER BY step ASC, id ASC LIMIT ?"; + binds.push(maxRows); + const st = await db + .prepare(sql) + .bind(...binds) + .all(); + const list = (st.results as MessageRow[] | null) ?? []; + return list.map(rowToMessage); +} + +export async function getMaxMessageStep(db: D1Database, threadId: string): Promise { + const row = await db + .prepare("SELECT MAX(step) as m FROM messages WHERE thread_id = ?") + .bind(threadId) + .first<{ m: number | null }>(); + if (!row || row.m === null) return -1; + return row.m; +} + +export async function createTask( + db: D1Database, + threadId: string, + role: string, + instruction: string, + timeoutSeconds: number, +): Promise { + const id = newId(); + const now = new Date().toISOString(); + const to = Math.max(1, Math.floor(timeoutSeconds)); + await db + .prepare( + `INSERT INTO tasks (id, thread_id, role, instruction, status, claim_id, claimed_by, claimed_at, timeout_seconds, created_at) + VALUES (?, ?, ?, ?, 'open', NULL, NULL, NULL, ?, datetime('now'))`, + ) + .bind(id, threadId, role, instruction, to) + .run(); + return { + id, + thread_id: threadId, + role, + instruction, + status: "open", + claim_id: null, + claimed_by: null, + claimed_at: null, + timeout_seconds: to, + created_at: now, + }; +} + +type TaskRow = { + id: string; + thread_id: string; + role: string; + instruction: string; + status: string; + claim_id: string | null; + claimed_by: string | null; + claimed_at: string | null; + timeout_seconds: number; + created_at: string; +}; + +function rowToTask(row: TaskRow): Task { + const status = + row.status === "open" || row.status === "claimed" || row.status === "completed" || row.status === "expired" + ? row.status + : "open"; + return { + id: row.id, + thread_id: row.thread_id, + role: row.role, + instruction: row.instruction, + status, + claim_id: row.claim_id, + claimed_by: row.claimed_by, + claimed_at: row.claimed_at, + timeout_seconds: row.timeout_seconds, + created_at: row.created_at, + }; +} + +export type ClaimResult = { ok: true; claimId: string } | { ok: false }; + +export async function claimTask( + db: D1Database, + taskId: string, + agentId: string, +): Promise { + const claimId = newId(); + const res = await db + .prepare( + `UPDATE tasks + SET status = 'claimed', claim_id = ?, claimed_by = ?, claimed_at = datetime('now') + WHERE id = ? AND status = 'open'`, + ) + .bind(claimId, agentId, taskId) + .run(); + const changes = res.meta.changes; + if (typeof changes === "number" && changes > 0) { + return { ok: true, claimId }; + } + return { ok: false }; +} + +export async function getTaskById(db: D1Database, id: string): Promise { + const row = await db.prepare("SELECT * FROM tasks WHERE id = ?").bind(id).first(); + if (!row) return null; + return rowToTask(row); +} + +export async function completeTask(db: D1Database, taskId: string, claimId: string): Promise { + const res = await db + .prepare( + `UPDATE tasks SET status = 'completed' WHERE id = ? AND claim_id = ? AND status = 'claimed'`, + ) + .bind(taskId, claimId) + .run(); + const changes = res.meta.changes; + if (typeof changes === "number") return changes > 0; + return res.success === true; +} + +export async function releaseTask(db: D1Database, taskId: string, claimId: string): Promise { + const res = await db + .prepare( + `UPDATE tasks + SET status = 'open', claim_id = NULL, claimed_by = NULL, claimed_at = NULL + WHERE id = ? AND claim_id = ? AND status = 'claimed'`, + ) + .bind(taskId, claimId) + .run(); + const changes = res.meta.changes; + if (typeof changes === "number") return changes > 0; + return res.success === true; +} + +export async function expireTimedOutTasks(db: D1Database): Promise { + const res = await db + .prepare( + `UPDATE tasks + SET status = 'open', claim_id = NULL, claimed_by = NULL, claimed_at = NULL + WHERE status = 'claimed' + AND claimed_at IS NOT NULL + AND (strftime('%s', 'now') - strftime('%s', claimed_at)) > timeout_seconds`, + ) + .run(); + const changes = res.meta.changes; + if (typeof changes === "number") return changes; + return 0; +} + +export async function getOpenTasks( + db: D1Database, + limit: number, + workflow: string | null, +): Promise { + const cap = Math.min(500, Math.max(1, limit)); + if (workflow === null) { + const st = await db + .prepare( + `SELECT t.* FROM tasks t WHERE t.status = 'open' ORDER BY t.created_at ASC LIMIT ?`, + ) + .bind(cap) + .all(); + const results = (st.results as TaskRow[] | null) ?? []; + return results.map(rowToTask); + } + const st = await db + .prepare( + `SELECT t.* FROM tasks t + INNER JOIN threads th ON t.thread_id = th.id + WHERE t.status = 'open' AND th.workflow = ? + ORDER BY t.created_at ASC LIMIT ?`, + ) + .bind(workflow, cap) + .all(); + const results = (st.results as TaskRow[] | null) ?? []; + return results.map(rowToTask); +} + +export async function registerAgent( + db: D1Database, + id: string, + tokenHash: string, +): Promise> { + const existing = await db.prepare("SELECT id FROM agents WHERE id = ?").bind(id).first<{ + id: string; + }>(); + if (existing) { + return err(`agent already exists: ${id}`); + } + await db + .prepare("INSERT INTO agents (id, token_hash, created_at) VALUES (?, ?, datetime('now'))") + .bind(id, tokenHash) + .run(); + const row = await db + .prepare("SELECT * FROM agents WHERE id = ?") + .bind(id) + .first(); + if (!row) return err("registerAgent: insert failed"); + return ok(row); +} + +export async function listAgents( + db: D1Database, +): Promise> { + const st = await db + .prepare("SELECT id, created_at FROM agents ORDER BY id ASC") + .all<{ id: string; created_at: string }>(); + return (st.results as { id: string; created_at: string }[] | null) ?? []; +} + +export async function deleteAgent(db: D1Database, id: string): Promise { + const res = await db.prepare("DELETE FROM agents WHERE id = ?").bind(id).run(); + const changes = res.meta.changes; + if (typeof changes === "number") return changes > 0; + return false; +} + +export async function getTaskForThreadAndClaim( + db: D1Database, + threadId: string, + taskId: string, + claimId: string, +): Promise { + const row = await db + .prepare("SELECT * FROM tasks WHERE id = ? AND thread_id = ? AND claim_id = ?") + .bind(taskId, threadId, claimId) + .first(); + if (!row) return null; + return rowToTask(row); +} diff --git a/packages/khala/src/env.ts b/packages/khala/src/env.ts new file mode 100644 index 0000000..b225323 --- /dev/null +++ b/packages/khala/src/env.ts @@ -0,0 +1,9 @@ +/** + * Cloudflare bindings for the Khala worker. Mirrored in worker-configuration.d.ts (ProvidedEnv). + */ +export type KhalaBindings = { + DB: D1Database; + THREAD: DurableObjectNamespace; + ADMIN_SECRET: string; + TEST_MIGRATIONS: { id: string; name: string; queries: { sql: string }[] }[] | undefined; +}; diff --git a/packages/khala/src/index.ts b/packages/khala/src/index.ts new file mode 100644 index 0000000..6dd081f --- /dev/null +++ b/packages/khala/src/index.ts @@ -0,0 +1,149 @@ +import { Hono } from "hono"; +import type { KhalaBindings } from "./env.js"; +import { admin } from "./routes/admin.js"; +import { agentAuth } from "./auth.js"; +import { ThreadDO } from "./thread-do.js"; +import { + createThread, + getThread, + getThreadMessages, + getOpenTasks, + claimTask, + releaseTask, + getTaskById, + expireTimedOutTasks, +} from "./db.js"; +import { getWorkflow } from "./workflows.js"; + +// Register built-in workflows +import "./workflows/ping-pong.js"; + +type HonoEnv = { Bindings: KhalaBindings; Variables: { agentId: string } }; + +const app = new Hono(); + +// Health +app.get("/health", (c) => c.json({ ok: true })); + +// Admin routes +app.route("/admin", admin); + +// --- Agent-authenticated routes --- + +// Create workflow thread +app.post("/workflows/:name/threads", agentAuth, async (c) => { + const name = c.req.param("name"); + const def = getWorkflow(name); + if (def === null) { + return c.json({ error: `workflow not found: ${name}` }, 404); + } + const thread = await createThread(c.env.DB, name, c.get("agentId")); + const doId = c.env.THREAD.idFromName(thread.id); + const stub = c.env.THREAD.get(doId); + const doReq = new Request("https://do/start", { + method: "POST", + headers: { "X-Thread-Id": thread.id }, + }); + const doRes = await stub.fetch(doReq); + if (!doRes.ok) { + const body = await doRes.text(); + return c.json({ error: `failed to start thread: ${body}` }, 500); + } + return c.json({ threadId: thread.id, workflow: name, status: "active" }, 201); +}); + +// Get thread status +app.get("/threads/:id", agentAuth, async (c) => { + const thread = await getThread(c.env.DB, c.req.param("id")); + if (thread === null) { + return c.json({ error: "not found" }, 404); + } + return c.json(thread); +}); + +// Get thread messages +app.get("/threads/:id/messages", agentAuth, async (c) => { + const doId = c.env.THREAD.idFromName(c.req.param("id")); + const stub = c.env.THREAD.get(doId); + const url = new URL(c.req.url); + const doReq = new Request(`https://do/messages${url.search}`, { + method: "GET", + headers: { "X-Thread-Id": c.req.param("id") }, + }); + return stub.fetch(doReq); +}); + +// Post response to thread +app.post("/threads/:id/response", agentAuth, async (c) => { + const threadId = c.req.param("id"); + const body = await c.req.text(); + const doId = c.env.THREAD.idFromName(threadId); + const stub = c.env.THREAD.get(doId); + const doReq = new Request("https://do/response", { + method: "POST", + headers: { "X-Thread-Id": threadId, "Content-Type": "application/json" }, + body, + }); + return stub.fetch(doReq); +}); + +// --- Task Queue --- + +// List open tasks +app.get("/tasks", agentAuth, async (c) => { + const workflow = c.req.query("workflow") ?? null; + const limit = Number.parseInt(c.req.query("limit") ?? "50", 10); + const tasks = await getOpenTasks(c.env.DB, Number.isFinite(limit) ? limit : 50, workflow); + return c.json({ tasks }); +}); + +// Claim a task +app.post("/tasks/:id/claim", agentAuth, async (c) => { + const taskId = c.req.param("id"); + const agentId = c.get("agentId"); + const result = await claimTask(c.env.DB, taskId, agentId); + if (!result.ok) { + return c.json({ error: "task not available" }, 409); + } + const task = await getTaskById(c.env.DB, taskId); + if (task === null) { + return c.json({ error: "task not found" }, 404); + } + return c.json({ + claimId: result.claimId, + taskId: task.id, + threadId: task.thread_id, + role: task.role, + instruction: task.instruction, + }); +}); + +// Release a task +app.post("/tasks/:id/release", agentAuth, async (c) => { + const taskId = c.req.param("id"); + const raw: unknown = await c.req.json(); + const body = raw as Readonly>; + const claimId = body.claimId; + if (typeof claimId !== "string") { + return c.json({ error: "claimId required" }, 400); + } + const ok = await releaseTask(c.env.DB, taskId, claimId); + if (!ok) { + return c.json({ error: "release failed" }, 409); + } + return c.json({ ok: true }); +}); + +// --- Cron: expire timed-out tasks --- + +export default { + fetch: app.fetch, + async scheduled(_event: ScheduledEvent, env: KhalaBindings, _ctx: ExecutionContext) { + const expired = await expireTimedOutTasks(env.DB); + if (expired > 0) { + console.log(`khala: expired ${expired} timed-out task(s)`); + } + }, +}; + +export { ThreadDO }; diff --git a/packages/khala/src/moderator.ts b/packages/khala/src/moderator.ts new file mode 100644 index 0000000..697956b --- /dev/null +++ b/packages/khala/src/moderator.ts @@ -0,0 +1,37 @@ +import jsonata from "jsonata"; +import type { Result } from "./result.js"; +import { err, ok } from "./result.js"; + +export type ModeratorDecision = { role: string }; + +export async function evaluateModerator( + expression: string, + context: Readonly>, +): Promise> { + let expr: ReturnType; + try { + expr = jsonata(expression); + } catch (e) { + const msg = e instanceof Error ? e.message : String(e); + return err(`jsonata compile: ${msg}`); + } + let out: unknown; + try { + out = await expr.evaluate(context); + } catch (e) { + const msg = e instanceof Error ? e.message : String(e); + return err(`jsonata eval: ${msg}`); + } + if (out === null || out === undefined) { + return err("moderator returned empty"); + } + if (typeof out !== "object" || out === null || Array.isArray(out)) { + return err("moderator must return an object"); + } + const rec = out as Readonly>; + const role = rec.role; + if (typeof role === "string") { + return ok({ role }); + } + return err("moderator result missing string role"); +} diff --git a/packages/khala/src/result.ts b/packages/khala/src/result.ts new file mode 100644 index 0000000..a166c35 --- /dev/null +++ b/packages/khala/src/result.ts @@ -0,0 +1,9 @@ +export type Result = { ok: true; value: T } | { ok: false; error: E }; + +export function ok(value: T): Result { + return { ok: true, value }; +} + +export function err(error: E): Result { + return { ok: false, error }; +} diff --git a/packages/khala/src/routes/admin.ts b/packages/khala/src/routes/admin.ts new file mode 100644 index 0000000..8f0bc57 --- /dev/null +++ b/packages/khala/src/routes/admin.ts @@ -0,0 +1,61 @@ +import { Hono } from "hono"; +import { requireAdmin, sha256Hex } from "../auth.js"; +import { registerAgent, deleteAgent, listAgents } from "../db.js"; +import type { Result } from "../result.js"; +import type { KhalaBindings } from "../env.js"; + +const admin = new Hono<{ Bindings: KhalaBindings }>(); + +function jsonErr(res: Result, status: number) { + if (res.ok) { + return new Response("unexpected", { status: 500 }); + } + return Response.json({ error: String(res.error) }, { status }); +} + +admin.get("/agents", async (c) => { + if (!requireAdmin(c)) { + return c.json({ error: "unauthorized" }, 401); + } + const agents = await listAgents(c.env.DB); + return c.json({ agents: [...agents] }); +}); + +admin.post("/agents", async (c) => { + if (!requireAdmin(c)) { + return c.json({ error: "unauthorized" }, 401); + } + const raw: unknown = await c.req.json(); + if (raw === null || typeof raw !== "object" || Array.isArray(raw)) { + return c.json({ error: "invalid body" }, 400); + } + const body = raw as Readonly>; + const id = body.id; + const token = body.token; + if (typeof id !== "string" || id.length === 0 || typeof token !== "string" || token.length === 0) { + return c.json({ error: "id and token required" }, 400); + } + const hash = await sha256Hex(token); + const res = await registerAgent(c.env.DB, id, hash); + if (!res.ok) { + if (res.error.includes("already exists")) { + return jsonErr(res, 409); + } + return jsonErr(res, 400); + } + return c.json({ id: res.value.id, created_at: res.value.created_at }); +}); + +admin.delete("/agents/:id", async (c) => { + if (!requireAdmin(c)) { + return c.json({ error: "unauthorized" }, 401); + } + const id = c.req.param("id"); + const ok = await deleteAgent(c.env.DB, id); + if (!ok) { + return c.json({ error: "not found" }, 404); + } + return c.json({ ok: true }); +}); + +export { admin }; diff --git a/packages/khala/src/thread-do.ts b/packages/khala/src/thread-do.ts new file mode 100644 index 0000000..3c4b8c4 --- /dev/null +++ b/packages/khala/src/thread-do.ts @@ -0,0 +1,206 @@ +import { + appendMessage, + completeTask, + createTask, + getMaxMessageStep, + getTaskForThreadAndClaim, + getThread, + getThreadMessages, + setThreadResult, +} from "./db.js"; +import type { KhalaBindings } from "./env.js"; +import { evaluateModerator } from "./moderator.js"; +import type { CloudWorkflowDef } from "./workflows.js"; +import { getWorkflow } from "./workflows.js"; + +type StepRow = { role: string; content: string; meta: unknown }; + +function asRecord(value: unknown): Readonly> { + if (value !== null && typeof value === "object" && !Array.isArray(value)) { + return value as Readonly>; + } + return {}; +} + +function normalizeMetaString(value: unknown): string | null { + if (value === null || value === undefined) return null; + if (typeof value === "string") return value; + return JSON.stringify(value); +} + +function buildSteps(msgs: Awaited>): StepRow[] { + return msgs + .filter((m) => m.role !== "__moderator__") + .map((m) => ({ + role: m.role, + content: m.content, + meta: m.meta ? (() => { + try { + return JSON.parse(m.meta) as unknown; + } catch { + return m.meta; + } + })() : null, + })); +} + +/** + * Durable object for one workflow thread. Relies on D1 (messages, tasks, threads) as source of truth. + */ +export class ThreadDO { + private readonly env: KhalaBindings; + + constructor(_state: DurableObjectState, env: KhalaBindings) { + this.env = env; + } + + private async runModeratorAndSchedule( + db: D1Database, + def: CloudWorkflowDef, + threadId: string, + ): Promise<{ ok: true } | { ok: false; error: string }> { + const messages = await getThreadMessages(db, threadId, null); + const steps = buildSteps(messages); + const mod = await evaluateModerator(def.moderator, { steps: [...steps] }); + if (!mod.ok) { + return { ok: false, error: mod.error }; + } + if (mod.value.role === "__end__") { + await setThreadResult( + db, + threadId, + JSON.stringify({ completed: true, stepCount: steps.length }), + "completed", + ); + return { ok: true }; + } + const role = mod.value.role; + const r = def.roles[role] ?? null; + if (r === null) { + await setThreadResult( + db, + threadId, + JSON.stringify({ error: `unknown role: ${role}` }), + "failed", + ); + return { ok: true }; + } + await createTask(db, threadId, role, r.prompt, 300); + return { ok: true }; + } + + private async startThread(threadId: string): Promise { + const db = this.env.DB; + const thread = await getThread(db, threadId); + if (thread === null) { + return Response.json({ error: "thread not found" }, { status: 404 }); + } + if (thread.status !== "active") { + return Response.json({ error: "thread not active" }, { status: 400 }); + } + const def = getWorkflow(thread.workflow); + if (def === null) { + return Response.json({ error: "workflow not registered" }, { status: 500 }); + } + const r = await this.runModeratorAndSchedule(db, def, threadId); + if (!r.ok) { + await setThreadResult(db, threadId, JSON.stringify({ error: r.error }), "failed"); + return Response.json({ error: r.error }, { status: 500 }); + } + return Response.json({ ok: true }); + } + + private async postResponse( + threadId: string, + body: Readonly>, + ): Promise { + const taskId = body.taskId; + const content = body.content; + const claimId = body.claimId; + const agentId = body.agentId; + if (typeof taskId !== "string" || typeof content !== "string" || typeof claimId !== "string" || typeof agentId !== "string") { + return Response.json({ error: "taskId, content, claimId, agentId required" }, { status: 400 }); + } + const db = this.env.DB; + const task = await getTaskForThreadAndClaim(db, threadId, taskId, claimId); + if (task === null) { + return Response.json({ error: "task or claim not found" }, { status: 404 }); + } + if (task.claimed_by !== agentId) { + return Response.json({ error: "agent mismatch" }, { status: 403 }); + } + if (task.status !== "claimed") { + return Response.json({ error: "task not claimable" }, { status: 400 }); + } + const thread = await getThread(db, threadId); + if (thread === null) { + return Response.json({ error: "thread not found" }, { status: 404 }); + } + const def = getWorkflow(thread.workflow); + if (def === null) { + return Response.json({ error: "workflow not registered" }, { status: 500 }); + } + const nextStep = (await getMaxMessageStep(db, threadId)) + 1; + const meta = Object.hasOwn(body, "meta") + ? normalizeMetaString((body as Readonly>).meta) + : null; + await appendMessage(db, threadId, task.role, content, meta, nextStep, agentId); + const done = await completeTask(db, taskId, claimId); + if (!done) { + return Response.json({ error: "could not complete task" }, { status: 409 }); + } + const r = await this.runModeratorAndSchedule(db, def, threadId); + if (!r.ok) { + await setThreadResult(db, threadId, JSON.stringify({ error: r.error }), "failed"); + return Response.json({ error: r.error }, { status: 500 }); + } + return Response.json({ ok: true }); + } + + private async listMessages( + url: URL, + threadId: string, + ): Promise { + const roleP = url.searchParams.get("role"); + const minStepRaw = url.searchParams.get("minStep"); + const maxStepRaw = url.searchParams.get("maxStep"); + const limitRaw = url.searchParams.get("limit"); + const toInt = (s: string | null): number | null => { + if (s === null) return null; + const n = Number.parseInt(s, 10); + return Number.isFinite(n) ? n : null; + }; + const list = await getThreadMessages(this.env.DB, threadId, { + role: roleP, + minStep: toInt(minStepRaw), + maxStep: toInt(maxStepRaw), + limit: toInt(limitRaw), + }); + return Response.json({ messages: list }); + } + + async fetch(request: Request): Promise { + const url = new URL(request.url); + const path = url.pathname; + const threadId = request.headers.get("X-Thread-Id"); + if (threadId === null || threadId.length === 0) { + return Response.json({ error: "X-Thread-Id required" }, { status: 400 }); + } + if (path === "/start" && request.method === "POST") { + return this.startThread(threadId); + } + if (path === "/response" && request.method === "POST") { + const text = await request.text(); + let body: Readonly> = {}; + if (text.length > 0) { + const parsed: unknown = JSON.parse(text) as unknown; + body = asRecord(parsed); + } + return this.postResponse(threadId, body); + } + if (path === "/messages" && request.method === "GET") { + return this.listMessages(url, threadId); + } + return Response.json({ error: "not found" }, { status: 404 }); + } +} diff --git a/packages/khala/src/types.ts b/packages/khala/src/types.ts new file mode 100644 index 0000000..a67fae4 --- /dev/null +++ b/packages/khala/src/types.ts @@ -0,0 +1,46 @@ +export type Agent = { + id: string; + token_hash: string; + created_at: string; +}; + +export type Thread = { + id: string; + workflow: string; + status: "active" | "completed" | "failed"; + initiator: string; + result: string | null; + created_at: string; + updated_at: string; +}; + +export type Message = { + id: number; + thread_id: string; + role: string; + content: string; + meta: string | null; + step: number; + agent_id: string | null; + created_at: string; +}; + +export type Task = { + id: string; + thread_id: string; + role: string; + instruction: string; + status: "open" | "claimed" | "completed" | "expired"; + claim_id: string | null; + claimed_by: string | null; + claimed_at: string | null; + timeout_seconds: number; + created_at: string; +}; + +export type GetThreadMessagesOpts = { + role: string | null; + minStep: number | null; + maxStep: number | null; + limit: number | null; +}; diff --git a/packages/khala/src/workflows.ts b/packages/khala/src/workflows.ts new file mode 100644 index 0000000..c9f1e7b --- /dev/null +++ b/packages/khala/src/workflows.ts @@ -0,0 +1,20 @@ +export type CloudRole = { + prompt: string; +}; + +export type CloudWorkflowDef = { + name: string; + roles: Readonly>; + /** JSONata expression: context includes `steps` (role turn messages). */ + moderator: string; +}; + +const registry = new Map(); + +export function registerWorkflow(def: CloudWorkflowDef): void { + registry.set(def.name, def); +} + +export function getWorkflow(name: string): CloudWorkflowDef | null { + return registry.get(name) ?? null; +} diff --git a/packages/khala/src/workflows/ping-pong.ts b/packages/khala/src/workflows/ping-pong.ts new file mode 100644 index 0000000..11890e8 --- /dev/null +++ b/packages/khala/src/workflows/ping-pong.ts @@ -0,0 +1,14 @@ +import { type CloudWorkflowDef, registerWorkflow } from "../workflows.js"; + +const pingPong: CloudWorkflowDef = { + name: "ping-pong", + roles: { + pinger: { prompt: "Send the literal word ping" }, + ponger: { prompt: "Send the literal word pong" }, + }, + moderator: `$count(steps) >= 6 ? { "role": "__end__" } : $count(steps) % 2 = 0 ? { "role": "pinger" } : { "role": "ponger" }`, +}; + +registerWorkflow(pingPong); + +export { pingPong }; diff --git a/packages/khala/tsconfig.json b/packages/khala/tsconfig.json new file mode 100644 index 0000000..7a2d09a --- /dev/null +++ b/packages/khala/tsconfig.json @@ -0,0 +1,13 @@ +{ + "compilerOptions": { + "target": "ES2022", + "lib": ["ES2022", "WebWorker"], + "module": "ES2022", + "moduleResolution": "bundler", + "strict": true, + "noUncheckedIndexedAccess": true, + "skipLibCheck": true, + "types": ["@cloudflare/workers-types", "./worker-configuration.d.ts"] + }, + "include": ["src", "worker-configuration.d.ts", "vitest.config.ts"] +} diff --git a/packages/khala/worker-configuration.d.ts b/packages/khala/worker-configuration.d.ts new file mode 100644 index 0000000..0c14531 --- /dev/null +++ b/packages/khala/worker-configuration.d.ts @@ -0,0 +1,17 @@ +import type { D1Migration } from "@cloudflare/workers-types"; + +declare module "cloudflare:workers" { + type KhalaEnv = { + DB: D1Database; + THREAD: DurableObjectNamespace; + /** Used by Vitest: migrations to apply in setup (see d1-apply.ts). */ + TEST_MIGRATIONS: D1Migration[] | undefined; + }; + interface Env extends KhalaEnv { + /** Plain-text admin bearer value (set via [vars] or wrangler secret). */ + ADMIN_SECRET: string; + } + interface ProvidedEnv extends Env {} +} + +export {}; diff --git a/packages/khala/wrangler.toml b/packages/khala/wrangler.toml new file mode 100644 index 0000000..b1215da --- /dev/null +++ b/packages/khala/wrangler.toml @@ -0,0 +1,24 @@ +name = "khala" +main = "src/index.ts" +compatibility_date = "2025-04-01" + +[vars] +ADMIN_SECRET = "dev-admin-replace-in-production" + +[[d1_databases]] +binding = "DB" +database_name = "khala" +database_id = "00000000-0000-0000-0000-000000000000" +migrations_dir = "migrations" + +[[migrations]] +tag = "v1" +new_classes = [ "ThreadDO" ] + +[durable_objects] +bindings = [ + { name = "THREAD", class_name = "ThreadDO" } +] + +[triggers] +crons = [ "* * * * *" ] diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b675a63..e1349aa 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -69,7 +69,7 @@ importers: version: link:../store drizzle-orm: specifier: 1.0.0-beta.23-c10d10c - version: 1.0.0-beta.23-c10d10c(@types/better-sqlite3@7.6.13)(@types/mssql@9.1.11(@azure/core-client@1.10.1))(better-sqlite3@11.10.0)(mssql@11.0.1(@azure/core-client@1.10.1))(sql.js@1.14.1)(zod@4.3.6) + version: 1.0.0-beta.23-c10d10c(@cloudflare/workers-types@4.20260425.1)(@types/better-sqlite3@7.6.13)(@types/mssql@9.1.11(@azure/core-client@1.10.1))(better-sqlite3@11.10.0)(mssql@11.0.1(@azure/core-client@1.10.1))(sql.js@1.14.1)(zod@4.3.6) yaml: specifier: ^2.8.3 version: 2.8.3 @@ -84,6 +84,34 @@ importers: specifier: ^4.1.5 version: 4.1.5(@types/node@22.19.17)(vite@8.0.9(@types/node@22.19.17)(esbuild@0.27.7)(yaml@2.8.3)) + packages/khala: + dependencies: + hono: + specifier: ^4.7.0 + version: 4.12.15 + jsonata: + specifier: ^2.0.5 + version: 2.1.0 + ulidx: + specifier: ^2.4.1 + version: 2.4.1 + devDependencies: + '@cloudflare/vitest-pool-workers': + specifier: ^0.14.0 + version: 0.14.9(@cloudflare/workers-types@4.20260425.1)(@vitest/runner@4.1.5)(@vitest/snapshot@4.1.5)(vitest@4.1.5(@types/node@25.6.0)(vite@8.0.9(@types/node@25.6.0)(esbuild@0.27.7)(yaml@2.8.3))) + '@cloudflare/workers-types': + specifier: ^4.20250410.0 + version: 4.20260425.1 + typescript: + specifier: ^5.5.0 + version: 5.9.3 + vitest: + specifier: ^4.1.5 + version: 4.1.5(@types/node@25.6.0)(vite@8.0.9(@types/node@25.6.0)(esbuild@0.27.7)(yaml@2.8.3)) + wrangler: + specifier: ^4.14.0 + version: 4.85.0(@cloudflare/workers-types@4.20260425.1) + packages/store: dependencies: '@uncaged/nerve-core': @@ -311,6 +339,102 @@ packages: cpu: [x64] os: [win32] + '@cloudflare/kv-asset-handler@0.4.2': + resolution: {integrity: sha512-SIOD2DxrRRwQ+jgzlXCqoEFiKOFqaPjhnNTGKXSRLvp1HiOvapLaFG2kEr9dYQTYe8rKrd9uvDUzmAITeNyaHQ==} + engines: {node: '>=18.0.0'} + + '@cloudflare/unenv-preset@2.16.0': + resolution: {integrity: sha512-8ovsRpwzPoEqPUzoErAYVv8l3FMZNeBVQfJTvtzP4AgLSRGZISRfuChFxHWUQd3n6cnrwkuTGxT+2cGo8EsyYg==} + peerDependencies: + unenv: 2.0.0-rc.24 + workerd: 1.20260301.1 || ~1.20260302.1 || ~1.20260303.1 || ~1.20260304.1 || >1.20260305.0 <2.0.0-0 + peerDependenciesMeta: + workerd: + optional: true + + '@cloudflare/unenv-preset@2.16.1': + resolution: {integrity: sha512-ECxObrMfyTl5bhQf/lZCXwo5G6xX9IAUo+nDMKK4SZ8m4Jvvxp52vilxyySSWh2YTZz8+HQ07qGH/2rEom1vDw==} + peerDependencies: + unenv: 2.0.0-rc.24 + workerd: '>1.20260305.0 <2.0.0-0' + peerDependenciesMeta: + workerd: + optional: true + + '@cloudflare/vitest-pool-workers@0.14.9': + resolution: {integrity: sha512-XLJTOd+3A3BB6vPzD7gi1LTVKFSVge9HhGWXnLouPzySphMLbg+6xXELykFxZKyqP1AEjT2tc28AkBjM9GteFQ==} + peerDependencies: + '@vitest/runner': ^4.1.0 + '@vitest/snapshot': ^4.1.0 + vitest: ^4.1.0 + + '@cloudflare/workerd-darwin-64@1.20260421.1': + resolution: {integrity: sha512-DLU5ZTZ1VHeZZnj0PuVJEMHKGisfLe2XShyImP5P/PPj/m/t7CLEJmPiI7FMxvT7ynArkckJl7m+Z5x7u4Kkdw==} + engines: {node: '>=16'} + cpu: [x64] + os: [darwin] + + '@cloudflare/workerd-darwin-64@1.20260424.1': + resolution: {integrity: sha512-yFR1XaJbSDLg/qbwtrYaU2xwFXatIPKR5nrMQCN1q/m6+Qe/j6r+kCnFEvOJjMZOm9iCKsE6Qly5clgl4u32qw==} + engines: {node: '>=16'} + cpu: [x64] + os: [darwin] + + '@cloudflare/workerd-darwin-arm64@1.20260421.1': + resolution: {integrity: sha512-Trotq3xRAkIcpC505WoxM8+kIH4JIvOJCNuRatyHcz9uF5S+ukgiVUFUlM+GIjw1uCM/Bda2St+vSniX1RZdpw==} + engines: {node: '>=16'} + cpu: [arm64] + os: [darwin] + + '@cloudflare/workerd-darwin-arm64@1.20260424.1': + resolution: {integrity: sha512-LqWKcE7x/9KyC2iQvKPeb20hKST3dYXDZlYTvFymgR1DfLS0OFOCzVGTloVNd7WqvK4SkdzBYfxo7QMIAeBK0w==} + engines: {node: '>=16'} + cpu: [arm64] + os: [darwin] + + '@cloudflare/workerd-linux-64@1.20260421.1': + resolution: {integrity: sha512-938QjUv0z+QqK6BAvgwX/lCIZ2b224ZXoXtGTbhyNVMhB+mt4Dj24cj9qca4ekNXjVM7uTKp1yOHZO97fVSacw==} + engines: {node: '>=16'} + cpu: [x64] + os: [linux] + + '@cloudflare/workerd-linux-64@1.20260424.1': + resolution: {integrity: sha512-YlEBFbAYZHe/ylzl8WEYQEU/jr+0XMqXaST2oBk5oVjksdb1NGuJaggluCdZAzuJJ8UqdTmyhY5u/qrasbiFWA==} + engines: {node: '>=16'} + cpu: [x64] + os: [linux] + + '@cloudflare/workerd-linux-arm64@1.20260421.1': + resolution: {integrity: sha512-YI4+mLfwnJcKJ+iPyxzx+tp2Jy4o29BxBPSQGZxl/AZyvZ9eTKsmNZmtjEiT4i3O/M0tdO/B/d9ESDHbRCs2rQ==} + engines: {node: '>=16'} + cpu: [arm64] + os: [linux] + + '@cloudflare/workerd-linux-arm64@1.20260424.1': + resolution: {integrity: sha512-qJ0X0m6cL8fWDUPDg8K4IxYZXNJI6XbeOihqjnqKbAClrjdPDn8VUSd+z2XiCQ5NylMtMrpa/skC9UfaR6mh8g==} + engines: {node: '>=16'} + cpu: [arm64] + os: [linux] + + '@cloudflare/workerd-windows-64@1.20260421.1': + resolution: {integrity: sha512-q1SFgwlNH9lFmw74vh7EJbJtduo92Nx51mNOfd3/u6pux6AldcwRviYzKEEv3FEbtv6OBB7J8D5f8vtZj7Z6Sg==} + engines: {node: '>=16'} + cpu: [x64] + os: [win32] + + '@cloudflare/workerd-windows-64@1.20260424.1': + resolution: {integrity: sha512-tZ7Z9qmYNAP6z1/+8r/zKbk8F8DZmpmwNzMeN+zkde2Wnhfr3FBqOkJXT/5zmli8HPoWrIXxSiyqcNDMy8V2Zg==} + engines: {node: '>=16'} + cpu: [x64] + os: [win32] + + '@cloudflare/workers-types@4.20260425.1': + resolution: {integrity: sha512-f6dlo3SsA+TNqjveavPDN73nxRfCOOd0iMdf8iEosgR/RJtQlrGwfr5L5Vf7x/5cpeeguxScKevuaMmdjpOECw==} + + '@cspotcode/source-map-support@0.8.1': + resolution: {integrity: sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==} + engines: {node: '>=12'} + '@emnapi/core@1.10.0': resolution: {integrity: sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==} @@ -326,165 +450,481 @@ packages: '@emnapi/wasi-threads@1.2.1': resolution: {integrity: sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==} + '@esbuild/aix-ppc64@0.27.3': + resolution: {integrity: sha512-9fJMTNFTWZMh5qwrBItuziu834eOCUcEqymSH7pY+zoMVEZg3gcPuBNxH1EvfVYe9h0x/Ptw8KBzv7qxb7l8dg==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [aix] + '@esbuild/aix-ppc64@0.27.7': resolution: {integrity: sha512-EKX3Qwmhz1eMdEJokhALr0YiD0lhQNwDqkPYyPhiSwKrh7/4KRjQc04sZ8db+5DVVnZ1LmbNDI1uAMPEUBnQPg==} engines: {node: '>=18'} cpu: [ppc64] os: [aix] + '@esbuild/android-arm64@0.27.3': + resolution: {integrity: sha512-YdghPYUmj/FX2SYKJ0OZxf+iaKgMsKHVPF1MAq/P8WirnSpCStzKJFjOjzsW0QQ7oIAiccHdcqjbHmJxRb/dmg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [android] + '@esbuild/android-arm64@0.27.7': resolution: {integrity: sha512-62dPZHpIXzvChfvfLJow3q5dDtiNMkwiRzPylSCfriLvZeq0a1bWChrGx/BbUbPwOrsWKMn8idSllklzBy+dgQ==} engines: {node: '>=18'} cpu: [arm64] os: [android] + '@esbuild/android-arm@0.27.3': + resolution: {integrity: sha512-i5D1hPY7GIQmXlXhs2w8AWHhenb00+GxjxRncS2ZM7YNVGNfaMxgzSGuO8o8SJzRc/oZwU2bcScvVERk03QhzA==} + engines: {node: '>=18'} + cpu: [arm] + os: [android] + '@esbuild/android-arm@0.27.7': resolution: {integrity: sha512-jbPXvB4Yj2yBV7HUfE2KHe4GJX51QplCN1pGbYjvsyCZbQmies29EoJbkEc+vYuU5o45AfQn37vZlyXy4YJ8RQ==} engines: {node: '>=18'} cpu: [arm] os: [android] + '@esbuild/android-x64@0.27.3': + resolution: {integrity: sha512-IN/0BNTkHtk8lkOM8JWAYFg4ORxBkZQf9zXiEOfERX/CzxW3Vg1ewAhU7QSWQpVIzTW+b8Xy+lGzdYXV6UZObQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [android] + '@esbuild/android-x64@0.27.7': resolution: {integrity: sha512-x5VpMODneVDb70PYV2VQOmIUUiBtY3D3mPBG8NxVk5CogneYhkR7MmM3yR/uMdITLrC1ml/NV1rj4bMJuy9MCg==} engines: {node: '>=18'} cpu: [x64] os: [android] + '@esbuild/darwin-arm64@0.27.3': + resolution: {integrity: sha512-Re491k7ByTVRy0t3EKWajdLIr0gz2kKKfzafkth4Q8A5n1xTHrkqZgLLjFEHVD+AXdUGgQMq+Godfq45mGpCKg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [darwin] + '@esbuild/darwin-arm64@0.27.7': resolution: {integrity: sha512-5lckdqeuBPlKUwvoCXIgI2D9/ABmPq3Rdp7IfL70393YgaASt7tbju3Ac+ePVi3KDH6N2RqePfHnXkaDtY9fkw==} engines: {node: '>=18'} cpu: [arm64] os: [darwin] + '@esbuild/darwin-x64@0.27.3': + resolution: {integrity: sha512-vHk/hA7/1AckjGzRqi6wbo+jaShzRowYip6rt6q7VYEDX4LEy1pZfDpdxCBnGtl+A5zq8iXDcyuxwtv3hNtHFg==} + engines: {node: '>=18'} + cpu: [x64] + os: [darwin] + '@esbuild/darwin-x64@0.27.7': resolution: {integrity: sha512-rYnXrKcXuT7Z+WL5K980jVFdvVKhCHhUwid+dDYQpH+qu+TefcomiMAJpIiC2EM3Rjtq0sO3StMV/+3w3MyyqQ==} engines: {node: '>=18'} cpu: [x64] os: [darwin] + '@esbuild/freebsd-arm64@0.27.3': + resolution: {integrity: sha512-ipTYM2fjt3kQAYOvo6vcxJx3nBYAzPjgTCk7QEgZG8AUO3ydUhvelmhrbOheMnGOlaSFUoHXB6un+A7q4ygY9w==} + engines: {node: '>=18'} + cpu: [arm64] + os: [freebsd] + '@esbuild/freebsd-arm64@0.27.7': resolution: {integrity: sha512-B48PqeCsEgOtzME2GbNM2roU29AMTuOIN91dsMO30t+Ydis3z/3Ngoj5hhnsOSSwNzS+6JppqWsuhTp6E82l2w==} engines: {node: '>=18'} cpu: [arm64] os: [freebsd] + '@esbuild/freebsd-x64@0.27.3': + resolution: {integrity: sha512-dDk0X87T7mI6U3K9VjWtHOXqwAMJBNN2r7bejDsc+j03SEjtD9HrOl8gVFByeM0aJksoUuUVU9TBaZa2rgj0oA==} + engines: {node: '>=18'} + cpu: [x64] + os: [freebsd] + '@esbuild/freebsd-x64@0.27.7': resolution: {integrity: sha512-jOBDK5XEjA4m5IJK3bpAQF9/Lelu/Z9ZcdhTRLf4cajlB+8VEhFFRjWgfy3M1O4rO2GQ/b2dLwCUGpiF/eATNQ==} engines: {node: '>=18'} cpu: [x64] os: [freebsd] + '@esbuild/linux-arm64@0.27.3': + resolution: {integrity: sha512-sZOuFz/xWnZ4KH3YfFrKCf1WyPZHakVzTiqji3WDc0BCl2kBwiJLCXpzLzUBLgmp4veFZdvN5ChW4Eq/8Fc2Fg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [linux] + '@esbuild/linux-arm64@0.27.7': resolution: {integrity: sha512-RZPHBoxXuNnPQO9rvjh5jdkRmVizktkT7TCDkDmQ0W2SwHInKCAV95GRuvdSvA7w4VMwfCjUiPwDi0ZO6Nfe9A==} engines: {node: '>=18'} cpu: [arm64] os: [linux] + '@esbuild/linux-arm@0.27.3': + resolution: {integrity: sha512-s6nPv2QkSupJwLYyfS+gwdirm0ukyTFNl3KTgZEAiJDd+iHZcbTPPcWCcRYH+WlNbwChgH2QkE9NSlNrMT8Gfw==} + engines: {node: '>=18'} + cpu: [arm] + os: [linux] + '@esbuild/linux-arm@0.27.7': resolution: {integrity: sha512-RkT/YXYBTSULo3+af8Ib0ykH8u2MBh57o7q/DAs3lTJlyVQkgQvlrPTnjIzzRPQyavxtPtfg0EopvDyIt0j1rA==} engines: {node: '>=18'} cpu: [arm] os: [linux] + '@esbuild/linux-ia32@0.27.3': + resolution: {integrity: sha512-yGlQYjdxtLdh0a3jHjuwOrxQjOZYD/C9PfdbgJJF3TIZWnm/tMd/RcNiLngiu4iwcBAOezdnSLAwQDPqTmtTYg==} + engines: {node: '>=18'} + cpu: [ia32] + os: [linux] + '@esbuild/linux-ia32@0.27.7': resolution: {integrity: sha512-GA48aKNkyQDbd3KtkplYWT102C5sn/EZTY4XROkxONgruHPU72l+gW+FfF8tf2cFjeHaRbWpOYa/uRBz/Xq1Pg==} engines: {node: '>=18'} cpu: [ia32] os: [linux] + '@esbuild/linux-loong64@0.27.3': + resolution: {integrity: sha512-WO60Sn8ly3gtzhyjATDgieJNet/KqsDlX5nRC5Y3oTFcS1l0KWba+SEa9Ja1GfDqSF1z6hif/SkpQJbL63cgOA==} + engines: {node: '>=18'} + cpu: [loong64] + os: [linux] + '@esbuild/linux-loong64@0.27.7': resolution: {integrity: sha512-a4POruNM2oWsD4WKvBSEKGIiWQF8fZOAsycHOt6JBpZ+JN2n2JH9WAv56SOyu9X5IqAjqSIPTaJkqN8F7XOQ5Q==} engines: {node: '>=18'} cpu: [loong64] os: [linux] + '@esbuild/linux-mips64el@0.27.3': + resolution: {integrity: sha512-APsymYA6sGcZ4pD6k+UxbDjOFSvPWyZhjaiPyl/f79xKxwTnrn5QUnXR5prvetuaSMsb4jgeHewIDCIWljrSxw==} + engines: {node: '>=18'} + cpu: [mips64el] + os: [linux] + '@esbuild/linux-mips64el@0.27.7': resolution: {integrity: sha512-KabT5I6StirGfIz0FMgl1I+R1H73Gp0ofL9A3nG3i/cYFJzKHhouBV5VWK1CSgKvVaG4q1RNpCTR2LuTVB3fIw==} engines: {node: '>=18'} cpu: [mips64el] os: [linux] + '@esbuild/linux-ppc64@0.27.3': + resolution: {integrity: sha512-eizBnTeBefojtDb9nSh4vvVQ3V9Qf9Df01PfawPcRzJH4gFSgrObw+LveUyDoKU3kxi5+9RJTCWlj4FjYXVPEA==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [linux] + '@esbuild/linux-ppc64@0.27.7': resolution: {integrity: sha512-gRsL4x6wsGHGRqhtI+ifpN/vpOFTQtnbsupUF5R5YTAg+y/lKelYR1hXbnBdzDjGbMYjVJLJTd2OFmMewAgwlQ==} engines: {node: '>=18'} cpu: [ppc64] os: [linux] + '@esbuild/linux-riscv64@0.27.3': + resolution: {integrity: sha512-3Emwh0r5wmfm3ssTWRQSyVhbOHvqegUDRd0WhmXKX2mkHJe1SFCMJhagUleMq+Uci34wLSipf8Lagt4LlpRFWQ==} + engines: {node: '>=18'} + cpu: [riscv64] + os: [linux] + '@esbuild/linux-riscv64@0.27.7': resolution: {integrity: sha512-hL25LbxO1QOngGzu2U5xeXtxXcW+/GvMN3ejANqXkxZ/opySAZMrc+9LY/WyjAan41unrR3YrmtTsUpwT66InQ==} engines: {node: '>=18'} cpu: [riscv64] os: [linux] + '@esbuild/linux-s390x@0.27.3': + resolution: {integrity: sha512-pBHUx9LzXWBc7MFIEEL0yD/ZVtNgLytvx60gES28GcWMqil8ElCYR4kvbV2BDqsHOvVDRrOxGySBM9Fcv744hw==} + engines: {node: '>=18'} + cpu: [s390x] + os: [linux] + '@esbuild/linux-s390x@0.27.7': resolution: {integrity: sha512-2k8go8Ycu1Kb46vEelhu1vqEP+UeRVj2zY1pSuPdgvbd5ykAw82Lrro28vXUrRmzEsUV0NzCf54yARIK8r0fdw==} engines: {node: '>=18'} cpu: [s390x] os: [linux] + '@esbuild/linux-x64@0.27.3': + resolution: {integrity: sha512-Czi8yzXUWIQYAtL/2y6vogER8pvcsOsk5cpwL4Gk5nJqH5UZiVByIY8Eorm5R13gq+DQKYg0+JyQoytLQas4dA==} + engines: {node: '>=18'} + cpu: [x64] + os: [linux] + '@esbuild/linux-x64@0.27.7': resolution: {integrity: sha512-hzznmADPt+OmsYzw1EE33ccA+HPdIqiCRq7cQeL1Jlq2gb1+OyWBkMCrYGBJ+sxVzve2ZJEVeePbLM2iEIZSxA==} engines: {node: '>=18'} cpu: [x64] os: [linux] + '@esbuild/netbsd-arm64@0.27.3': + resolution: {integrity: sha512-sDpk0RgmTCR/5HguIZa9n9u+HVKf40fbEUt+iTzSnCaGvY9kFP0YKBWZtJaraonFnqef5SlJ8/TiPAxzyS+UoA==} + engines: {node: '>=18'} + cpu: [arm64] + os: [netbsd] + '@esbuild/netbsd-arm64@0.27.7': resolution: {integrity: sha512-b6pqtrQdigZBwZxAn1UpazEisvwaIDvdbMbmrly7cDTMFnw/+3lVxxCTGOrkPVnsYIosJJXAsILG9XcQS+Yu6w==} engines: {node: '>=18'} cpu: [arm64] os: [netbsd] + '@esbuild/netbsd-x64@0.27.3': + resolution: {integrity: sha512-P14lFKJl/DdaE00LItAukUdZO5iqNH7+PjoBm+fLQjtxfcfFE20Xf5CrLsmZdq5LFFZzb5JMZ9grUwvtVYzjiA==} + engines: {node: '>=18'} + cpu: [x64] + os: [netbsd] + '@esbuild/netbsd-x64@0.27.7': resolution: {integrity: sha512-OfatkLojr6U+WN5EDYuoQhtM+1xco+/6FSzJJnuWiUw5eVcicbyK3dq5EeV/QHT1uy6GoDhGbFpprUiHUYggrw==} engines: {node: '>=18'} cpu: [x64] os: [netbsd] + '@esbuild/openbsd-arm64@0.27.3': + resolution: {integrity: sha512-AIcMP77AvirGbRl/UZFTq5hjXK+2wC7qFRGoHSDrZ5v5b8DK/GYpXW3CPRL53NkvDqb9D+alBiC/dV0Fb7eJcw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openbsd] + '@esbuild/openbsd-arm64@0.27.7': resolution: {integrity: sha512-AFuojMQTxAz75Fo8idVcqoQWEHIXFRbOc1TrVcFSgCZtQfSdc1RXgB3tjOn/krRHENUB4j00bfGjyl2mJrU37A==} engines: {node: '>=18'} cpu: [arm64] os: [openbsd] + '@esbuild/openbsd-x64@0.27.3': + resolution: {integrity: sha512-DnW2sRrBzA+YnE70LKqnM3P+z8vehfJWHXECbwBmH/CU51z6FiqTQTHFenPlHmo3a8UgpLyH3PT+87OViOh1AQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [openbsd] + '@esbuild/openbsd-x64@0.27.7': resolution: {integrity: sha512-+A1NJmfM8WNDv5CLVQYJ5PshuRm/4cI6WMZRg1by1GwPIQPCTs1GLEUHwiiQGT5zDdyLiRM/l1G0Pv54gvtKIg==} engines: {node: '>=18'} cpu: [x64] os: [openbsd] + '@esbuild/openharmony-arm64@0.27.3': + resolution: {integrity: sha512-NinAEgr/etERPTsZJ7aEZQvvg/A6IsZG/LgZy+81wON2huV7SrK3e63dU0XhyZP4RKGyTm7aOgmQk0bGp0fy2g==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openharmony] + '@esbuild/openharmony-arm64@0.27.7': resolution: {integrity: sha512-+KrvYb/C8zA9CU/g0sR6w2RBw7IGc5J2BPnc3dYc5VJxHCSF1yNMxTV5LQ7GuKteQXZtspjFbiuW5/dOj7H4Yw==} engines: {node: '>=18'} cpu: [arm64] os: [openharmony] + '@esbuild/sunos-x64@0.27.3': + resolution: {integrity: sha512-PanZ+nEz+eWoBJ8/f8HKxTTD172SKwdXebZ0ndd953gt1HRBbhMsaNqjTyYLGLPdoWHy4zLU7bDVJztF5f3BHA==} + engines: {node: '>=18'} + cpu: [x64] + os: [sunos] + '@esbuild/sunos-x64@0.27.7': resolution: {integrity: sha512-ikktIhFBzQNt/QDyOL580ti9+5mL/YZeUPKU2ivGtGjdTYoqz6jObj6nOMfhASpS4GU4Q/Clh1QtxWAvcYKamA==} engines: {node: '>=18'} cpu: [x64] os: [sunos] + '@esbuild/win32-arm64@0.27.3': + resolution: {integrity: sha512-B2t59lWWYrbRDw/tjiWOuzSsFh1Y/E95ofKz7rIVYSQkUYBjfSgf6oeYPNWHToFRr2zx52JKApIcAS/D5TUBnA==} + engines: {node: '>=18'} + cpu: [arm64] + os: [win32] + '@esbuild/win32-arm64@0.27.7': resolution: {integrity: sha512-7yRhbHvPqSpRUV7Q20VuDwbjW5kIMwTHpptuUzV+AA46kiPze5Z7qgt6CLCK3pWFrHeNfDd1VKgyP4O+ng17CA==} engines: {node: '>=18'} cpu: [arm64] os: [win32] + '@esbuild/win32-ia32@0.27.3': + resolution: {integrity: sha512-QLKSFeXNS8+tHW7tZpMtjlNb7HKau0QDpwm49u0vUp9y1WOF+PEzkU84y9GqYaAVW8aH8f3GcBck26jh54cX4Q==} + engines: {node: '>=18'} + cpu: [ia32] + os: [win32] + '@esbuild/win32-ia32@0.27.7': resolution: {integrity: sha512-SmwKXe6VHIyZYbBLJrhOoCJRB/Z1tckzmgTLfFYOfpMAx63BJEaL9ExI8x7v0oAO3Zh6D/Oi1gVxEYr5oUCFhw==} engines: {node: '>=18'} cpu: [ia32] os: [win32] + '@esbuild/win32-x64@0.27.3': + resolution: {integrity: sha512-4uJGhsxuptu3OcpVAzli+/gWusVGwZZHTlS63hh++ehExkVT8SgiEf7/uC/PclrPPkLhZqGgCTjd0VWLo6xMqA==} + engines: {node: '>=18'} + cpu: [x64] + os: [win32] + '@esbuild/win32-x64@0.27.7': resolution: {integrity: sha512-56hiAJPhwQ1R4i+21FVF7V8kSD5zZTdHcVuRFMW0hn753vVfQN8xlx4uOPT4xoGH0Z/oVATuR82AiqSTDIpaHg==} engines: {node: '>=18'} cpu: [x64] os: [win32] + '@img/colour@1.1.0': + resolution: {integrity: sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ==} + engines: {node: '>=18'} + + '@img/sharp-darwin-arm64@0.34.5': + resolution: {integrity: sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm64] + os: [darwin] + + '@img/sharp-darwin-x64@0.34.5': + resolution: {integrity: sha512-YNEFAF/4KQ/PeW0N+r+aVVsoIY0/qxxikF2SWdp+NRkmMB7y9LBZAVqQ4yhGCm/H3H270OSykqmQMKLBhBJDEw==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [darwin] + + '@img/sharp-libvips-darwin-arm64@1.2.4': + resolution: {integrity: sha512-zqjjo7RatFfFoP0MkQ51jfuFZBnVE2pRiaydKJ1G/rHZvnsrHAOcQALIi9sA5co5xenQdTugCvtb1cuf78Vf4g==} + cpu: [arm64] + os: [darwin] + + '@img/sharp-libvips-darwin-x64@1.2.4': + resolution: {integrity: sha512-1IOd5xfVhlGwX+zXv2N93k0yMONvUlANylbJw1eTah8K/Jtpi15KC+WSiaX/nBmbm2HxRM1gZ0nSdjSsrZbGKg==} + cpu: [x64] + os: [darwin] + + '@img/sharp-libvips-linux-arm64@1.2.4': + resolution: {integrity: sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw==} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@img/sharp-libvips-linux-arm@1.2.4': + resolution: {integrity: sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A==} + cpu: [arm] + os: [linux] + libc: [glibc] + + '@img/sharp-libvips-linux-ppc64@1.2.4': + resolution: {integrity: sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA==} + cpu: [ppc64] + os: [linux] + libc: [glibc] + + '@img/sharp-libvips-linux-riscv64@1.2.4': + resolution: {integrity: sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA==} + cpu: [riscv64] + os: [linux] + libc: [glibc] + + '@img/sharp-libvips-linux-s390x@1.2.4': + resolution: {integrity: sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ==} + cpu: [s390x] + os: [linux] + libc: [glibc] + + '@img/sharp-libvips-linux-x64@1.2.4': + resolution: {integrity: sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw==} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@img/sharp-libvips-linuxmusl-arm64@1.2.4': + resolution: {integrity: sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw==} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@img/sharp-libvips-linuxmusl-x64@1.2.4': + resolution: {integrity: sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg==} + cpu: [x64] + os: [linux] + libc: [musl] + + '@img/sharp-linux-arm64@0.34.5': + resolution: {integrity: sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@img/sharp-linux-arm@0.34.5': + resolution: {integrity: sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm] + os: [linux] + libc: [glibc] + + '@img/sharp-linux-ppc64@0.34.5': + resolution: {integrity: sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [ppc64] + os: [linux] + libc: [glibc] + + '@img/sharp-linux-riscv64@0.34.5': + resolution: {integrity: sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [riscv64] + os: [linux] + libc: [glibc] + + '@img/sharp-linux-s390x@0.34.5': + resolution: {integrity: sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [s390x] + os: [linux] + libc: [glibc] + + '@img/sharp-linux-x64@0.34.5': + resolution: {integrity: sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@img/sharp-linuxmusl-arm64@0.34.5': + resolution: {integrity: sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@img/sharp-linuxmusl-x64@0.34.5': + resolution: {integrity: sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [linux] + libc: [musl] + + '@img/sharp-wasm32@0.34.5': + resolution: {integrity: sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [wasm32] + + '@img/sharp-win32-arm64@0.34.5': + resolution: {integrity: sha512-WQ3AgWCWYSb2yt+IG8mnC6Jdk9Whs7O0gxphblsLvdhSpSTtmu69ZG1Gkb6NuvxsNACwiPV6cNSZNzt0KPsw7g==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm64] + os: [win32] + + '@img/sharp-win32-ia32@0.34.5': + resolution: {integrity: sha512-FV9m/7NmeCmSHDD5j4+4pNI8Cp3aW+JvLoXcTUo0IqyjSfAZJ8dIUmijx1qaJsIiU+Hosw6xM5KijAWRJCSgNg==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [ia32] + os: [win32] + + '@img/sharp-win32-x64@0.34.5': + resolution: {integrity: sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [win32] + + '@jridgewell/resolve-uri@3.1.2': + resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} + engines: {node: '>=6.0.0'} + '@jridgewell/sourcemap-codec@1.5.5': resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==} + '@jridgewell/trace-mapping@0.3.9': + resolution: {integrity: sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==} + '@js-joda/core@5.7.0': resolution: {integrity: sha512-WBu4ULVVxySLLzK1Ppq+OdfP+adRS4ntmDQT915rzDJ++i95gc2jZkM5B6LWEAwN3lGXpfie3yPABozdD3K3Vg==} @@ -497,6 +937,15 @@ packages: '@oxc-project/types@0.126.0': resolution: {integrity: sha512-oGfVtjAgwQVVpfBrbtk4e1XDyWHRFta6BS3GWVzrF8xYBT2VGQAk39yJS/wFSMrZqoiCU4oghT3Ch0HaHGIHcQ==} + '@poppinss/colors@4.1.6': + resolution: {integrity: sha512-H9xkIdFswbS8n1d6vmRd8+c10t2Qe+rZITbbDHHkQixH5+2x1FDGmi/0K+WgWiqQFKPSlIYB7jlH6Kpfn6Fleg==} + + '@poppinss/dumper@0.6.5': + resolution: {integrity: sha512-NBdYIb90J7LfOI32dOewKI1r7wnkiH6m920puQ3qHUeZkxNkQiFnXVWoE6YtFSv6QOiPPf7ys6i+HWWecDz7sw==} + + '@poppinss/exception@1.2.3': + resolution: {integrity: sha512-dCED+QRChTVatE9ibtoaxc+WkdzOSjYTKi/+uacHWIsfodVfpsueo3+DKpgU5Px8qXjgmXkSvhXvSCz3fnP9lw==} + '@rolldown/binding-android-arm64@1.0.0-rc.16': resolution: {integrity: sha512-rhY3k7Bsae9qQfOtph2Pm2jZEA+s8Gmjoz4hhmx70K9iMQ/ddeae+xhRQcM5IuVx5ry1+bGfkvMn7D6MJggVSA==} engines: {node: ^20.19.0 || >=22.12.0} @@ -686,6 +1135,13 @@ packages: '@swc/helpers': optional: true + '@sindresorhus/is@7.2.0': + resolution: {integrity: sha512-P1Cz1dWaFfR4IR+U13mqqiGsLFf1KbayybWwdd2vfctdV6hDpUkgCY0nKOLLTMSoRd/jJNjtbqzf13K8DCCXQw==} + engines: {node: '>=18'} + + '@speed-highlight/core@1.2.15': + resolution: {integrity: sha512-BMq1K3DsElxDWawkX6eLg9+CKJrTVGCBAWVuHXVUV2u0s2711qiChLSId6ikYPfxhdYocLNt3wWwSvDiTvFabw==} + '@standard-schema/spec@1.1.0': resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==} @@ -782,6 +1238,9 @@ packages: bl@6.1.6: resolution: {integrity: sha512-jLsPgN/YSvPUg9UX0Kd73CXpm2Psg9FxMeCSXnk3WBO3CMT10JMwijubhGfHCnFu6TPn1ei3b975dxv7K2pWVg==} + blake3-wasm@2.1.5: + resolution: {integrity: sha512-F1+K8EbfOZE49dtoPtmxUQrpXaBIl3ICvasLh+nJta0xkz+9kF/7uet9fLnwKqhDrmj6g+6K3Tw9yQPUg2ka5g==} + buffer-equal-constant-time@1.0.1: resolution: {integrity: sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==} @@ -805,6 +1264,9 @@ packages: citty@0.1.6: resolution: {integrity: sha512-tskPPKEs8D2KPafUypv2gxwJP8h/OaJmC82QQGGDQcHvXX43xF2VDACcJVmZ0EuSxkpO9Kc4MlrA3q0+FG58AQ==} + cjs-module-lexer@1.4.3: + resolution: {integrity: sha512-9z8TZaGM1pfswYeXrUpzPrkx8UnWYdhJclsiYMm6x/w5+nN+8Tf/LnAgfLGQCm59qAOxU8WwHEq2vNwF6i4j+Q==} + commander@11.1.0: resolution: {integrity: sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==} engines: {node: '>=16'} @@ -816,6 +1278,10 @@ packages: convert-source-map@2.0.0: resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} + cookie@1.1.1: + resolution: {integrity: sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==} + engines: {node: '>=18'} + debug@4.4.3: resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} engines: {node: '>=6.0'} @@ -974,9 +1440,17 @@ packages: end-of-stream@1.4.5: resolution: {integrity: sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==} + error-stack-parser-es@1.0.5: + resolution: {integrity: sha512-5qucVt2XcuGMcEGgWI7i+yZpmpByQ8J1lHhcL7PwqCwu9FPP3VUXzT4ltHe5i2z9dePwEHcDVOAfSnHsOlCXRA==} + es-module-lexer@2.0.0: resolution: {integrity: sha512-5POEcUuZybH7IdmGsD8wlf0AI55wMecM9rVBTI/qEAy2c1kTOm3DjFYjrBdI2K3BaJjJYfYFeRtM0t9ssnRuxw==} + esbuild@0.27.3: + resolution: {integrity: sha512-8VwMnyGCONIs6cWue2IdpHxHnAjzxnw2Zr7MkVxB2vjmQ2ivqGFb4LEG3SMnv0Gb2F/G/2yA8zUaiL1gywDCCg==} + engines: {node: '>=18'} + hasBin: true + esbuild@0.27.7: resolution: {integrity: sha512-IxpibTjyVnmrIQo5aqNpCgoACA/dTKLTlhMHihVHhdkxKyPO1uBBthumT0rdHmcsk9uMonIWS0m4FljWzILh3w==} engines: {node: '>=18'} @@ -1024,6 +1498,10 @@ packages: github-from-package@0.0.0: resolution: {integrity: sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==} + hono@4.12.15: + resolution: {integrity: sha512-qM0jDhFEaCBb4TxoW7f53Qrpv9RBiayUHo0S52JudprkhvpjIrGoU1mnnr29Fvd1U335ZFPZQY1wlkqgfGXyLg==} + engines: {node: '>=16.9.0'} + http-proxy-agent@7.0.2: resolution: {integrity: sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==} engines: {node: '>= 14'} @@ -1071,6 +1549,10 @@ packages: js-md4@0.3.2: resolution: {integrity: sha512-/GDnfQYsltsjRswQhN9fhv3EMw2sCpUdrdxyWDOUK7eyD++r3gRhzgiQgc/x4MAv2i1iuQ4lxO5mvqM3vj4bwA==} + jsonata@2.1.0: + resolution: {integrity: sha512-OCzaRMK8HobtX8fp37uIVmL8CY1IGc/a6gLsDqz3quExFR09/U78HUzWYr7T31UEB6+Eu0/8dkVD5fFDOl9a8w==} + engines: {node: '>= 8'} + jsonwebtoken@9.0.3: resolution: {integrity: sha512-MT/xP0CrubFRNLNKvxJ2BYfy53Zkm++5bX9dtuPbqAeQpTVe0MQTFhao8+Cp//EmJp244xt6Drw/GVEGCUj40g==} engines: {node: '>=12', npm: '>=6'} @@ -1081,6 +1563,13 @@ packages: jws@4.0.1: resolution: {integrity: sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA==} + kleur@4.1.5: + resolution: {integrity: sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==} + engines: {node: '>=6'} + + layerr@3.0.0: + resolution: {integrity: sha512-tv754Ki2dXpPVApOrjTyRo4/QegVb9eVFq4mjqp4+NM5NaX7syQvN5BBNfV/ZpAHCEHV24XdUVrBAoka4jt3pA==} + lightningcss-android-arm64@1.32.0: resolution: {integrity: sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==} engines: {node: '>= 12.0.0'} @@ -1183,6 +1672,16 @@ packages: resolution: {integrity: sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==} engines: {node: '>=10'} + miniflare@4.20260421.0: + resolution: {integrity: sha512-7ZkNQ7brgQ2hh5ha9iQCDUjxBkLvuiG2VdDns9esRL8O8lXg+MoP6E0dO1rtp+ZY2I+vV1tPWr6td5IojkewLw==} + engines: {node: '>=18.0.0'} + hasBin: true + + miniflare@4.20260424.0: + resolution: {integrity: sha512-B6MKBBd5TJ19daUc3Ae9rWctn1nDA/VCXykXfCsp9fTxyfGxnZY27tJs1caxgE9MWEMMKGbGHouqVtgKbKGxmw==} + engines: {node: '>=18.0.0'} + hasBin: true + minimist@1.2.8: resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} @@ -1222,6 +1721,9 @@ packages: resolution: {integrity: sha512-YgBpdJHPyQ2UE5x+hlSXcnejzAvD0b22U2OuAP+8OnlJT+PjWPxtgmGqKKc+RgTM63U9gN0YzrYc71R2WT/hTA==} engines: {node: '>=18'} + path-to-regexp@6.3.0: + resolution: {integrity: sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ==} + pathe@2.0.3: resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} @@ -1300,6 +1802,10 @@ packages: engines: {node: '>=10'} hasBin: true + sharp@0.34.5: + resolution: {integrity: sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + siginfo@2.0.0: resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} @@ -1332,6 +1838,10 @@ packages: resolution: {integrity: sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==} engines: {node: '>=0.10.0'} + supports-color@10.2.2: + resolution: {integrity: sha512-SS+jx45GF1QjgEXQx4NJZV9ImqmO2NPz5FNsIHrsDjh2YsHnawpan7SNQ1o8NuhrbHZy9AZhIoCUiCeaW/C80g==} + engines: {node: '>=18'} + tar-fs@2.1.4: resolution: {integrity: sha512-mDAjwmZdh7LTT6pNleZ05Yt65HC3E+NiQzl672vQG38jIrehtJk/J3mNwIg+vShQPcLF/LV7CMnDW6vjj6sfYQ==} @@ -1377,12 +1887,23 @@ packages: engines: {node: '>=14.17'} hasBin: true + ulidx@2.4.1: + resolution: {integrity: sha512-xY7c8LPyzvhvew0Fn+Ek3wBC9STZAuDI/Y5andCKi9AX6/jvfaX45PhsDX8oxgPL0YFp0Jhr8qWMbS/p9375Xg==} + engines: {node: '>=16'} + undici-types@6.21.0: resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==} undici-types@7.19.2: resolution: {integrity: sha512-qYVnV5OEm2AW8cJMCpdV20CDyaN3g0AjDlOGf1OW4iaDEx8MwdtChUp4zu4H0VP3nDRF/8RKWH+IPp9uW0YGZg==} + undici@7.24.8: + resolution: {integrity: sha512-6KQ/+QxK49Z/p3HO6E5ZCZWNnCasyZLa5ExaVYyvPxUwKtbCPMKELJOqh7EqOle0t9cH/7d2TaaTRRa6Nhs4YQ==} + engines: {node: '>=20.18.1'} + + unenv@2.0.0-rc.24: + resolution: {integrity: sha512-i7qRCmY42zmCwnYlh9H2SvLEypEFGye5iRmEMKjcGi7zk9UquigRjFtTLz0TYqr0ZGLZhaMHl/foy1bZR+Cwlw==} + util-deprecate@1.0.2: resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} @@ -1479,9 +2000,51 @@ packages: engines: {node: '>=8'} hasBin: true + workerd@1.20260421.1: + resolution: {integrity: sha512-zTYD+xFR4d7TUCxsyl7FTPth9a8CDgk8pM7xUWbJxo0SGUx+2e5C7Q5LrramBZwmuAErtzXmOjlQ15PtkPAhZA==} + engines: {node: '>=16'} + hasBin: true + + workerd@1.20260424.1: + resolution: {integrity: sha512-oKsB0Xo/mfkYMdSACoS06XZg09VUK4rXwHfF/1t3P++sMbwzf4UHQvMO57+zxpEB2nVrY/ZkW0bYFGq4GdAFSQ==} + engines: {node: '>=16'} + hasBin: true + + wrangler@4.84.1: + resolution: {integrity: sha512-Xe1S/Bik7pNdtdJ+asHsEZC2dX9k3WxYn2BbxFtOrrLVxN/LKi750zsrjX41jSAk00M/O1l7jzyQV4sQqw8ftg==} + engines: {node: '>=20.3.0'} + hasBin: true + peerDependencies: + '@cloudflare/workers-types': ^4.20260421.1 + peerDependenciesMeta: + '@cloudflare/workers-types': + optional: true + + wrangler@4.85.0: + resolution: {integrity: sha512-93cwt2RPb1qdcmEgPzH7ybiLN4BIKoWpscIX6SywjHrQOeIZrQk2haoc3XMLKtQTmzapxza9OuDD+kMHpsuuhg==} + engines: {node: '>=20.3.0'} + hasBin: true + peerDependencies: + '@cloudflare/workers-types': ^4.20260424.1 + peerDependenciesMeta: + '@cloudflare/workers-types': + optional: true + wrappy@1.0.2: resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} + ws@8.18.0: + resolution: {integrity: sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==} + engines: {node: '>=10.0.0'} + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: '>=5.0.2' + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + wsl-utils@0.1.0: resolution: {integrity: sha512-h3Fbisa2nKGPxCpm89Hk33lBLsnaGBvctQopaBSOW/uIs6FTe1ATyAnKFJrzVs9vpGdsTe73WF3V4lIsk4Gacw==} engines: {node: '>=18'} @@ -1491,6 +2054,15 @@ packages: engines: {node: '>= 14.6'} hasBin: true + youch-core@0.3.3: + resolution: {integrity: sha512-ho7XuGjLaJ2hWHoK8yFnsUGy2Y5uDpqSTq1FkHLK4/oqKtyUU1AFbOOxY4IpC9f0fTLjwYbslUz0Po5BpD1wrA==} + + youch@4.1.0-beta.10: + resolution: {integrity: sha512-rLfVLB4FgQneDr0dv1oddCVZmKjcJ6yX6mS4pU82Mq/Dt9a3cLZQ62pDBL4AUO+uVrCvtWz3ZFUL2HFAFJ/BXQ==} + + zod@3.25.76: + resolution: {integrity: sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==} + zod@4.3.6: resolution: {integrity: sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==} @@ -1731,6 +2303,71 @@ snapshots: '@biomejs/cli-win32-x64@1.9.4': optional: true + '@cloudflare/kv-asset-handler@0.4.2': {} + + '@cloudflare/unenv-preset@2.16.0(unenv@2.0.0-rc.24)(workerd@1.20260421.1)': + dependencies: + unenv: 2.0.0-rc.24 + optionalDependencies: + workerd: 1.20260421.1 + + '@cloudflare/unenv-preset@2.16.1(unenv@2.0.0-rc.24)(workerd@1.20260424.1)': + dependencies: + unenv: 2.0.0-rc.24 + optionalDependencies: + workerd: 1.20260424.1 + + '@cloudflare/vitest-pool-workers@0.14.9(@cloudflare/workers-types@4.20260425.1)(@vitest/runner@4.1.5)(@vitest/snapshot@4.1.5)(vitest@4.1.5(@types/node@25.6.0)(vite@8.0.9(@types/node@25.6.0)(esbuild@0.27.7)(yaml@2.8.3)))': + dependencies: + '@vitest/runner': 4.1.5 + '@vitest/snapshot': 4.1.5 + cjs-module-lexer: 1.4.3 + esbuild: 0.27.3 + miniflare: 4.20260421.0 + vitest: 4.1.5(@types/node@25.6.0)(vite@8.0.9(@types/node@25.6.0)(esbuild@0.27.7)(yaml@2.8.3)) + wrangler: 4.84.1(@cloudflare/workers-types@4.20260425.1) + zod: 3.25.76 + transitivePeerDependencies: + - '@cloudflare/workers-types' + - bufferutil + - utf-8-validate + + '@cloudflare/workerd-darwin-64@1.20260421.1': + optional: true + + '@cloudflare/workerd-darwin-64@1.20260424.1': + optional: true + + '@cloudflare/workerd-darwin-arm64@1.20260421.1': + optional: true + + '@cloudflare/workerd-darwin-arm64@1.20260424.1': + optional: true + + '@cloudflare/workerd-linux-64@1.20260421.1': + optional: true + + '@cloudflare/workerd-linux-64@1.20260424.1': + optional: true + + '@cloudflare/workerd-linux-arm64@1.20260421.1': + optional: true + + '@cloudflare/workerd-linux-arm64@1.20260424.1': + optional: true + + '@cloudflare/workerd-windows-64@1.20260421.1': + optional: true + + '@cloudflare/workerd-windows-64@1.20260424.1': + optional: true + + '@cloudflare/workers-types@4.20260425.1': {} + + '@cspotcode/source-map-support@0.8.1': + dependencies: + '@jridgewell/trace-mapping': 0.3.9 + '@emnapi/core@1.10.0': dependencies: '@emnapi/wasi-threads': 1.2.1 @@ -1758,86 +2395,267 @@ snapshots: tslib: 2.8.1 optional: true + '@esbuild/aix-ppc64@0.27.3': + optional: true + '@esbuild/aix-ppc64@0.27.7': optional: true + '@esbuild/android-arm64@0.27.3': + optional: true + '@esbuild/android-arm64@0.27.7': optional: true + '@esbuild/android-arm@0.27.3': + optional: true + '@esbuild/android-arm@0.27.7': optional: true + '@esbuild/android-x64@0.27.3': + optional: true + '@esbuild/android-x64@0.27.7': optional: true + '@esbuild/darwin-arm64@0.27.3': + optional: true + '@esbuild/darwin-arm64@0.27.7': optional: true + '@esbuild/darwin-x64@0.27.3': + optional: true + '@esbuild/darwin-x64@0.27.7': optional: true + '@esbuild/freebsd-arm64@0.27.3': + optional: true + '@esbuild/freebsd-arm64@0.27.7': optional: true + '@esbuild/freebsd-x64@0.27.3': + optional: true + '@esbuild/freebsd-x64@0.27.7': optional: true + '@esbuild/linux-arm64@0.27.3': + optional: true + '@esbuild/linux-arm64@0.27.7': optional: true + '@esbuild/linux-arm@0.27.3': + optional: true + '@esbuild/linux-arm@0.27.7': optional: true + '@esbuild/linux-ia32@0.27.3': + optional: true + '@esbuild/linux-ia32@0.27.7': optional: true + '@esbuild/linux-loong64@0.27.3': + optional: true + '@esbuild/linux-loong64@0.27.7': optional: true + '@esbuild/linux-mips64el@0.27.3': + optional: true + '@esbuild/linux-mips64el@0.27.7': optional: true + '@esbuild/linux-ppc64@0.27.3': + optional: true + '@esbuild/linux-ppc64@0.27.7': optional: true + '@esbuild/linux-riscv64@0.27.3': + optional: true + '@esbuild/linux-riscv64@0.27.7': optional: true + '@esbuild/linux-s390x@0.27.3': + optional: true + '@esbuild/linux-s390x@0.27.7': optional: true + '@esbuild/linux-x64@0.27.3': + optional: true + '@esbuild/linux-x64@0.27.7': optional: true + '@esbuild/netbsd-arm64@0.27.3': + optional: true + '@esbuild/netbsd-arm64@0.27.7': optional: true + '@esbuild/netbsd-x64@0.27.3': + optional: true + '@esbuild/netbsd-x64@0.27.7': optional: true + '@esbuild/openbsd-arm64@0.27.3': + optional: true + '@esbuild/openbsd-arm64@0.27.7': optional: true + '@esbuild/openbsd-x64@0.27.3': + optional: true + '@esbuild/openbsd-x64@0.27.7': optional: true + '@esbuild/openharmony-arm64@0.27.3': + optional: true + '@esbuild/openharmony-arm64@0.27.7': optional: true + '@esbuild/sunos-x64@0.27.3': + optional: true + '@esbuild/sunos-x64@0.27.7': optional: true + '@esbuild/win32-arm64@0.27.3': + optional: true + '@esbuild/win32-arm64@0.27.7': optional: true + '@esbuild/win32-ia32@0.27.3': + optional: true + '@esbuild/win32-ia32@0.27.7': optional: true + '@esbuild/win32-x64@0.27.3': + optional: true + '@esbuild/win32-x64@0.27.7': optional: true + '@img/colour@1.1.0': {} + + '@img/sharp-darwin-arm64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-darwin-arm64': 1.2.4 + optional: true + + '@img/sharp-darwin-x64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-darwin-x64': 1.2.4 + optional: true + + '@img/sharp-libvips-darwin-arm64@1.2.4': + optional: true + + '@img/sharp-libvips-darwin-x64@1.2.4': + optional: true + + '@img/sharp-libvips-linux-arm64@1.2.4': + optional: true + + '@img/sharp-libvips-linux-arm@1.2.4': + optional: true + + '@img/sharp-libvips-linux-ppc64@1.2.4': + optional: true + + '@img/sharp-libvips-linux-riscv64@1.2.4': + optional: true + + '@img/sharp-libvips-linux-s390x@1.2.4': + optional: true + + '@img/sharp-libvips-linux-x64@1.2.4': + optional: true + + '@img/sharp-libvips-linuxmusl-arm64@1.2.4': + optional: true + + '@img/sharp-libvips-linuxmusl-x64@1.2.4': + optional: true + + '@img/sharp-linux-arm64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linux-arm64': 1.2.4 + optional: true + + '@img/sharp-linux-arm@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linux-arm': 1.2.4 + optional: true + + '@img/sharp-linux-ppc64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linux-ppc64': 1.2.4 + optional: true + + '@img/sharp-linux-riscv64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linux-riscv64': 1.2.4 + optional: true + + '@img/sharp-linux-s390x@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linux-s390x': 1.2.4 + optional: true + + '@img/sharp-linux-x64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linux-x64': 1.2.4 + optional: true + + '@img/sharp-linuxmusl-arm64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linuxmusl-arm64': 1.2.4 + optional: true + + '@img/sharp-linuxmusl-x64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linuxmusl-x64': 1.2.4 + optional: true + + '@img/sharp-wasm32@0.34.5': + dependencies: + '@emnapi/runtime': 1.10.0 + optional: true + + '@img/sharp-win32-arm64@0.34.5': + optional: true + + '@img/sharp-win32-ia32@0.34.5': + optional: true + + '@img/sharp-win32-x64@0.34.5': + optional: true + + '@jridgewell/resolve-uri@3.1.2': {} + '@jridgewell/sourcemap-codec@1.5.5': {} + '@jridgewell/trace-mapping@0.3.9': + dependencies: + '@jridgewell/resolve-uri': 3.1.2 + '@jridgewell/sourcemap-codec': 1.5.5 + '@js-joda/core@5.7.0': optional: true @@ -1857,6 +2675,18 @@ snapshots: '@oxc-project/types@0.126.0': {} + '@poppinss/colors@4.1.6': + dependencies: + kleur: 4.1.5 + + '@poppinss/dumper@0.6.5': + dependencies: + '@poppinss/colors': 4.1.6 + '@sindresorhus/is': 7.2.0 + supports-color: 10.2.2 + + '@poppinss/exception@1.2.3': {} + '@rolldown/binding-android-arm64@1.0.0-rc.16': optional: true @@ -1979,6 +2809,10 @@ snapshots: optionalDependencies: '@swc/helpers': 0.5.21 + '@sindresorhus/is@7.2.0': {} + + '@speed-highlight/core@1.2.15': {} + '@standard-schema/spec@1.1.0': {} '@swc/helpers@0.5.21': @@ -2128,6 +2962,8 @@ snapshots: readable-stream: 4.7.0 optional: true + blake3-wasm@2.1.5: {} + buffer-equal-constant-time@1.0.1: optional: true @@ -2157,6 +2993,8 @@ snapshots: dependencies: consola: 3.4.2 + cjs-module-lexer@1.4.3: {} + commander@11.1.0: optional: true @@ -2164,6 +3002,8 @@ snapshots: convert-source-map@2.0.0: {} + cookie@1.1.1: {} + debug@4.4.3: dependencies: ms: 2.1.3 @@ -2191,8 +3031,9 @@ snapshots: detect-libc@2.1.2: {} - drizzle-orm@1.0.0-beta.23-c10d10c(@types/better-sqlite3@7.6.13)(@types/mssql@9.1.11(@azure/core-client@1.10.1))(better-sqlite3@11.10.0)(mssql@11.0.1(@azure/core-client@1.10.1))(sql.js@1.14.1)(zod@4.3.6): + drizzle-orm@1.0.0-beta.23-c10d10c(@cloudflare/workers-types@4.20260425.1)(@types/better-sqlite3@7.6.13)(@types/mssql@9.1.11(@azure/core-client@1.10.1))(better-sqlite3@11.10.0)(mssql@11.0.1(@azure/core-client@1.10.1))(sql.js@1.14.1)(zod@4.3.6): optionalDependencies: + '@cloudflare/workers-types': 4.20260425.1 '@types/better-sqlite3': 7.6.13 '@types/mssql': 9.1.11(@azure/core-client@1.10.1) better-sqlite3: 11.10.0 @@ -2210,8 +3051,39 @@ snapshots: once: 1.4.0 optional: true + error-stack-parser-es@1.0.5: {} + es-module-lexer@2.0.0: {} + esbuild@0.27.3: + optionalDependencies: + '@esbuild/aix-ppc64': 0.27.3 + '@esbuild/android-arm': 0.27.3 + '@esbuild/android-arm64': 0.27.3 + '@esbuild/android-x64': 0.27.3 + '@esbuild/darwin-arm64': 0.27.3 + '@esbuild/darwin-x64': 0.27.3 + '@esbuild/freebsd-arm64': 0.27.3 + '@esbuild/freebsd-x64': 0.27.3 + '@esbuild/linux-arm': 0.27.3 + '@esbuild/linux-arm64': 0.27.3 + '@esbuild/linux-ia32': 0.27.3 + '@esbuild/linux-loong64': 0.27.3 + '@esbuild/linux-mips64el': 0.27.3 + '@esbuild/linux-ppc64': 0.27.3 + '@esbuild/linux-riscv64': 0.27.3 + '@esbuild/linux-s390x': 0.27.3 + '@esbuild/linux-x64': 0.27.3 + '@esbuild/netbsd-arm64': 0.27.3 + '@esbuild/netbsd-x64': 0.27.3 + '@esbuild/openbsd-arm64': 0.27.3 + '@esbuild/openbsd-x64': 0.27.3 + '@esbuild/openharmony-arm64': 0.27.3 + '@esbuild/sunos-x64': 0.27.3 + '@esbuild/win32-arm64': 0.27.3 + '@esbuild/win32-ia32': 0.27.3 + '@esbuild/win32-x64': 0.27.3 + esbuild@0.27.7: optionalDependencies: '@esbuild/aix-ppc64': 0.27.7 @@ -2273,6 +3145,8 @@ snapshots: github-from-package@0.0.0: optional: true + hono@4.12.15: {} + http-proxy-agent@7.0.2: dependencies: agent-base: 7.1.4 @@ -2326,6 +3200,8 @@ snapshots: js-md4@0.3.2: optional: true + jsonata@2.1.0: {} + jsonwebtoken@9.0.3: dependencies: jws: 4.0.1 @@ -2353,6 +3229,10 @@ snapshots: safe-buffer: 5.2.1 optional: true + kleur@4.1.5: {} + + layerr@3.0.0: {} + lightningcss-android-arm64@1.32.0: optional: true @@ -2430,6 +3310,30 @@ snapshots: mimic-response@3.1.0: optional: true + miniflare@4.20260421.0: + dependencies: + '@cspotcode/source-map-support': 0.8.1 + sharp: 0.34.5 + undici: 7.24.8 + workerd: 1.20260421.1 + ws: 8.18.0 + youch: 4.1.0-beta.10 + transitivePeerDependencies: + - bufferutil + - utf-8-validate + + miniflare@4.20260424.0: + dependencies: + '@cspotcode/source-map-support': 0.8.1 + sharp: 0.34.5 + undici: 7.24.8 + workerd: 1.20260424.1 + ws: 8.18.0 + youch: 4.1.0-beta.10 + transitivePeerDependencies: + - bufferutil + - utf-8-validate + minimist@1.2.8: optional: true @@ -2480,6 +3384,8 @@ snapshots: wsl-utils: 0.1.0 optional: true + path-to-regexp@6.3.0: {} + pathe@2.0.3: {} picocolors@1.1.1: {} @@ -2581,8 +3487,38 @@ snapshots: safer-buffer@2.1.2: optional: true - semver@7.7.4: - optional: true + semver@7.7.4: {} + + sharp@0.34.5: + dependencies: + '@img/colour': 1.1.0 + detect-libc: 2.1.2 + semver: 7.7.4 + optionalDependencies: + '@img/sharp-darwin-arm64': 0.34.5 + '@img/sharp-darwin-x64': 0.34.5 + '@img/sharp-libvips-darwin-arm64': 1.2.4 + '@img/sharp-libvips-darwin-x64': 1.2.4 + '@img/sharp-libvips-linux-arm': 1.2.4 + '@img/sharp-libvips-linux-arm64': 1.2.4 + '@img/sharp-libvips-linux-ppc64': 1.2.4 + '@img/sharp-libvips-linux-riscv64': 1.2.4 + '@img/sharp-libvips-linux-s390x': 1.2.4 + '@img/sharp-libvips-linux-x64': 1.2.4 + '@img/sharp-libvips-linuxmusl-arm64': 1.2.4 + '@img/sharp-libvips-linuxmusl-x64': 1.2.4 + '@img/sharp-linux-arm': 0.34.5 + '@img/sharp-linux-arm64': 0.34.5 + '@img/sharp-linux-ppc64': 0.34.5 + '@img/sharp-linux-riscv64': 0.34.5 + '@img/sharp-linux-s390x': 0.34.5 + '@img/sharp-linux-x64': 0.34.5 + '@img/sharp-linuxmusl-arm64': 0.34.5 + '@img/sharp-linuxmusl-x64': 0.34.5 + '@img/sharp-wasm32': 0.34.5 + '@img/sharp-win32-arm64': 0.34.5 + '@img/sharp-win32-ia32': 0.34.5 + '@img/sharp-win32-x64': 0.34.5 siginfo@2.0.0: {} @@ -2616,6 +3552,8 @@ snapshots: strip-json-comments@2.0.1: optional: true + supports-color@10.2.2: {} + tar-fs@2.1.4: dependencies: chownr: 1.1.4 @@ -2690,11 +3628,21 @@ snapshots: typescript@5.9.3: {} + ulidx@2.4.1: + dependencies: + layerr: 3.0.0 + undici-types@6.21.0: {} undici-types@7.19.2: optional: true + undici@7.24.8: {} + + unenv@2.0.0-rc.24: + dependencies: + pathe: 2.0.3 + util-deprecate@1.0.2: optional: true @@ -2786,9 +3734,61 @@ snapshots: siginfo: 2.0.0 stackback: 0.0.2 + workerd@1.20260421.1: + optionalDependencies: + '@cloudflare/workerd-darwin-64': 1.20260421.1 + '@cloudflare/workerd-darwin-arm64': 1.20260421.1 + '@cloudflare/workerd-linux-64': 1.20260421.1 + '@cloudflare/workerd-linux-arm64': 1.20260421.1 + '@cloudflare/workerd-windows-64': 1.20260421.1 + + workerd@1.20260424.1: + optionalDependencies: + '@cloudflare/workerd-darwin-64': 1.20260424.1 + '@cloudflare/workerd-darwin-arm64': 1.20260424.1 + '@cloudflare/workerd-linux-64': 1.20260424.1 + '@cloudflare/workerd-linux-arm64': 1.20260424.1 + '@cloudflare/workerd-windows-64': 1.20260424.1 + + wrangler@4.84.1(@cloudflare/workers-types@4.20260425.1): + dependencies: + '@cloudflare/kv-asset-handler': 0.4.2 + '@cloudflare/unenv-preset': 2.16.0(unenv@2.0.0-rc.24)(workerd@1.20260421.1) + blake3-wasm: 2.1.5 + esbuild: 0.27.3 + miniflare: 4.20260421.0 + path-to-regexp: 6.3.0 + unenv: 2.0.0-rc.24 + workerd: 1.20260421.1 + optionalDependencies: + '@cloudflare/workers-types': 4.20260425.1 + fsevents: 2.3.3 + transitivePeerDependencies: + - bufferutil + - utf-8-validate + + wrangler@4.85.0(@cloudflare/workers-types@4.20260425.1): + dependencies: + '@cloudflare/kv-asset-handler': 0.4.2 + '@cloudflare/unenv-preset': 2.16.1(unenv@2.0.0-rc.24)(workerd@1.20260424.1) + blake3-wasm: 2.1.5 + esbuild: 0.27.3 + miniflare: 4.20260424.0 + path-to-regexp: 6.3.0 + unenv: 2.0.0-rc.24 + workerd: 1.20260424.1 + optionalDependencies: + '@cloudflare/workers-types': 4.20260425.1 + fsevents: 2.3.3 + transitivePeerDependencies: + - bufferutil + - utf-8-validate + wrappy@1.0.2: optional: true + ws@8.18.0: {} + wsl-utils@0.1.0: dependencies: is-wsl: 3.1.1 @@ -2796,4 +3796,19 @@ snapshots: yaml@2.8.3: {} + youch-core@0.3.3: + dependencies: + '@poppinss/exception': 1.2.3 + error-stack-parser-es: 1.0.5 + + youch@4.1.0-beta.10: + dependencies: + '@poppinss/colors': 4.1.6 + '@poppinss/dumper': 0.6.5 + '@speed-highlight/core': 1.2.15 + cookie: 1.1.1 + youch-core: 0.3.3 + + zod@3.25.76: {} + zod@4.3.6: {}