/** * @cxai/job app * * Hono app with job control routes. * Uses module-level Y.Doc — access via `app.doc` to connect provider. * * Usage: * import app from '@cxai/job/app' * // connect provider: new WebsocketProvider(..., app.doc) * mainApp.route('/jobs', app) * * Endpoints: * POST /:name/start — trigger (start) a job * POST /:name/stop — stop a running job * POST /:name/push — push an item into the job's Y.Map * POST /:name/clear — clear all items and reset * GET /:name/status — get job status * GET /:name/items — get all items * GET /status — all jobs status */ import { Hono } from "hono"; import * as Y from "yjs"; import { Job, createJob, getJob } from "./job.js"; import type { JobResult } from "./job.js"; const doc = new Y.Doc(); const app = new Hono(); // ─── Routes ────────────────────────────────────────────────────────── app.post("/:name/start", (c) => { const name = c.req.param("name"); const job = getJob(doc, name); if (!job) return c.json({ error: `job ${name} not found` }, 404); job.trigger(); return c.json({ status: "started", job: name }); }); app.post("/:name/stop", (c) => { const name = c.req.param("name"); const job = getJob(doc, name); if (!job) return c.json({ error: `job ${name} not found` }, 404); job.stop(); return c.json({ status: "stopped", job: name }); }); app.post("/:name/push", async (c) => { const name = c.req.param("name"); const job = getJob(doc, name); if (!job) return c.json({ error: `job ${name} not found` }, 404); const body = await c.req.json(); const key = body.id ?? `item-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`; doc.transact(() => { job.items.set(String(key), body); }); return c.json({ ok: true, job: name, key: String(key) }, 201); }); app.post("/:name/clear", (c) => { const name = c.req.param("name"); const job = getJob(doc, name); if (!job) return c.json({ error: `job ${name} not found` }, 404); job.clear(); return c.json({ status: "cleared", job: name }); }); app.get("/:name/status", (c) => { const name = c.req.param("name"); const job = getJob(doc, name); if (!job) return c.json({ error: `job ${name} not found` }, 404); return c.json({ job: name, path: job.path, running: job.isRunning(), items: job.items.size, ...job.getStatus(), }); }); app.get("/:name/items", (c) => { const name = c.req.param("name"); const job = getJob(doc, name); if (!job) return c.json({ error: `job ${name} not found` }, 404); return c.json({ job: name, count: job.items.size, items: job.getItems() }); }); app.get("/status", (c) => { // List all jobs from the doc const result: Record = {}; // Jobs register themselves at @job.{path}.status doc.getMap("@jobs").forEach((_, name: string) => { const job = getJob(doc, name); if (job) { result[name] = { path: job.path, running: job.isRunning(), items: job.items.size, ...job.getStatus(), }; } }); return c.json(result); }); // ─── Export app with doc attached ──────────────────────────────────── export default Object.assign(app, { doc });