Service: job event stream, async in-memory backend, preview routes

Ported from PR #15 (packages-side surface only; the fork's dev UI page is
intentionally dropped - the API surface is the product):
- job_events table + repository, worker emits stage transitions, additive
  GET /v1/clones/:id/events polling route. The fork's hand-written
  migration was never registered in Drizzle's journal (it silently never
  applied); regenerated properly via drizzle-kit from schema.ts with the
  (job_id, seq) index declared, and verified against a live Postgres
- In-memory backend moves to the documented enqueue-202 + poll contract
  with single-flight BUSY guard; POST honors backend httpStatus
- app-preview static serving routes; the preview BUILD half is deferred
  until the compiler exports buildApp/DEFAULT_HARNESS_DIR - routes 404
  cleanly until then
- preview option threaded through core types; cache key intentionally
  unchanged while the flag is inert

All workspaces typecheck; api 18/18, core 13/13, worker 1/1 pass;
migration verified applying (table + index + FK) on postgres:16.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Samraaj Bath
2026-07-04 15:00:55 -07:00
co-authored by Claude Fable 5
parent f991f469cf
commit bbe4a5643b
16 changed files with 701 additions and 55 deletions
@@ -0,0 +1,10 @@
CREATE TABLE "job_events" (
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
"job_id" uuid NOT NULL,
"seq" integer NOT NULL,
"payload" jsonb NOT NULL,
"created_at" timestamp with time zone DEFAULT now() NOT NULL
);
--> statement-breakpoint
ALTER TABLE "job_events" ADD CONSTRAINT "job_events_job_id_jobs_id_fk" FOREIGN KEY ("job_id") REFERENCES "public"."jobs"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
CREATE INDEX "job_events_job_id_seq_idx" ON "job_events" USING btree ("job_id","seq");
@@ -0,0 +1,461 @@
{
"id": "de9b94b8-cefb-4d2a-a848-79a552976943",
"prevId": "e076bd1e-a7ce-40ab-abd1-9eb11d00ddb9",
"version": "7",
"dialect": "postgresql",
"tables": {
"public.api_keys": {
"name": "api_keys",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "uuid",
"primaryKey": true,
"notNull": true,
"default": "gen_random_uuid()"
},
"key_hash": {
"name": "key_hash",
"type": "text",
"primaryKey": false,
"notNull": true
},
"label": {
"name": "label",
"type": "text",
"primaryKey": false,
"notNull": false
},
"rate_limit": {
"name": "rate_limit",
"type": "integer",
"primaryKey": false,
"notNull": false
},
"created_at": {
"name": "created_at",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"revoked_at": {
"name": "revoked_at",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": false
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {
"api_keys_key_hash_unique": {
"name": "api_keys_key_hash_unique",
"nullsNotDistinct": false,
"columns": [
"key_hash"
]
}
},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
},
"public.cache": {
"name": "cache",
"schema": "",
"columns": {
"cache_key": {
"name": "cache_key",
"type": "text",
"primaryKey": true,
"notNull": true
},
"job_id": {
"name": "job_id",
"type": "uuid",
"primaryKey": false,
"notNull": false
},
"url": {
"name": "url",
"type": "text",
"primaryKey": false,
"notNull": true
},
"options_hash": {
"name": "options_hash",
"type": "text",
"primaryKey": false,
"notNull": true
},
"compiler_version": {
"name": "compiler_version",
"type": "text",
"primaryKey": false,
"notNull": true
},
"created_at": {
"name": "created_at",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"expires_at": {
"name": "expires_at",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true
}
},
"indexes": {},
"foreignKeys": {
"cache_job_id_jobs_id_fk": {
"name": "cache_job_id_jobs_id_fk",
"tableFrom": "cache",
"tableTo": "jobs",
"columnsFrom": [
"job_id"
],
"columnsTo": [
"id"
],
"onDelete": "cascade",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
},
"public.clones": {
"name": "clones",
"schema": "",
"columns": {
"job_id": {
"name": "job_id",
"type": "uuid",
"primaryKey": true,
"notNull": true
},
"url": {
"name": "url",
"type": "text",
"primaryKey": false,
"notNull": true
},
"route_count": {
"name": "route_count",
"type": "integer",
"primaryKey": false,
"notNull": true,
"default": 1
},
"file_manifest": {
"name": "file_manifest",
"type": "jsonb",
"primaryKey": false,
"notNull": true
},
"bundle_s3_key": {
"name": "bundle_s3_key",
"type": "text",
"primaryKey": false,
"notNull": false
},
"verify": {
"name": "verify",
"type": "jsonb",
"primaryKey": false,
"notNull": false
},
"capture_meta": {
"name": "capture_meta",
"type": "jsonb",
"primaryKey": false,
"notNull": true
},
"created_at": {
"name": "created_at",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
}
},
"indexes": {},
"foreignKeys": {
"clones_job_id_jobs_id_fk": {
"name": "clones_job_id_jobs_id_fk",
"tableFrom": "clones",
"tableTo": "jobs",
"columnsFrom": [
"job_id"
],
"columnsTo": [
"id"
],
"onDelete": "cascade",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
},
"public.job_events": {
"name": "job_events",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "uuid",
"primaryKey": true,
"notNull": true,
"default": "gen_random_uuid()"
},
"job_id": {
"name": "job_id",
"type": "uuid",
"primaryKey": false,
"notNull": true
},
"seq": {
"name": "seq",
"type": "integer",
"primaryKey": false,
"notNull": true
},
"payload": {
"name": "payload",
"type": "jsonb",
"primaryKey": false,
"notNull": true
},
"created_at": {
"name": "created_at",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
}
},
"indexes": {
"job_events_job_id_seq_idx": {
"name": "job_events_job_id_seq_idx",
"columns": [
{
"expression": "job_id",
"isExpression": false,
"asc": true,
"nulls": "last"
},
{
"expression": "seq",
"isExpression": false,
"asc": true,
"nulls": "last"
}
],
"isUnique": false,
"concurrently": false,
"method": "btree",
"with": {}
}
},
"foreignKeys": {
"job_events_job_id_jobs_id_fk": {
"name": "job_events_job_id_jobs_id_fk",
"tableFrom": "job_events",
"tableTo": "jobs",
"columnsFrom": [
"job_id"
],
"columnsTo": [
"id"
],
"onDelete": "cascade",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
},
"public.jobs": {
"name": "jobs",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "uuid",
"primaryKey": true,
"notNull": true,
"default": "gen_random_uuid()"
},
"kind": {
"name": "kind",
"type": "text",
"primaryKey": false,
"notNull": true
},
"url": {
"name": "url",
"type": "text",
"primaryKey": false,
"notNull": true
},
"options": {
"name": "options",
"type": "jsonb",
"primaryKey": false,
"notNull": true,
"default": "'{}'::jsonb"
},
"status": {
"name": "status",
"type": "text",
"primaryKey": false,
"notNull": true,
"default": "'queued'"
},
"cache_key": {
"name": "cache_key",
"type": "text",
"primaryKey": false,
"notNull": true
},
"attempts": {
"name": "attempts",
"type": "integer",
"primaryKey": false,
"notNull": true,
"default": 0
},
"error": {
"name": "error",
"type": "text",
"primaryKey": false,
"notNull": false
},
"compiler_version": {
"name": "compiler_version",
"type": "text",
"primaryKey": false,
"notNull": false
},
"timings": {
"name": "timings",
"type": "jsonb",
"primaryKey": false,
"notNull": false
},
"created_at": {
"name": "created_at",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"started_at": {
"name": "started_at",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": false
},
"finished_at": {
"name": "finished_at",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": false
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
},
"public.signup_tokens": {
"name": "signup_tokens",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "uuid",
"primaryKey": true,
"notNull": true,
"default": "gen_random_uuid()"
},
"email": {
"name": "email",
"type": "text",
"primaryKey": false,
"notNull": true
},
"token_hash": {
"name": "token_hash",
"type": "text",
"primaryKey": false,
"notNull": true
},
"created_at": {
"name": "created_at",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"expires_at": {
"name": "expires_at",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true
},
"consumed_at": {
"name": "consumed_at",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": false
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {
"signup_tokens_token_hash_unique": {
"name": "signup_tokens_token_hash_unique",
"nullsNotDistinct": false,
"columns": [
"token_hash"
]
}
},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
}
},
"enums": {},
"schemas": {},
"sequences": {},
"roles": {},
"policies": {},
"views": {},
"_meta": {
"columns": {},
"schemas": {},
"tables": {}
}
}
+8 -1
View File
@@ -15,6 +15,13 @@
"when": 1782790200000,
"tag": "0001_signup_tokens",
"breakpoints": true
},
{
"idx": 2,
"version": "7",
"when": 1783202414928,
"tag": "0002_productive_wallflower",
"breakpoints": true
}
]
}
}
+22 -1
View File
@@ -1,6 +1,6 @@
import { and, desc, eq, gt, isNull, sql } from "drizzle-orm";
import type { Db } from "./client.js";
import { jobs, clones, cache, apiKeys, signupTokens, type Job, type NewJob, type Clone, type NewClone, type CacheRow, type ApiKey, type SignupToken } from "./schema.js";
import { jobs, clones, cache, apiKeys, signupTokens, jobEvents, type Job, type NewJob, type Clone, type NewClone, type CacheRow, type ApiKey, type SignupToken } from "./schema.js";
// ---- jobs ----
@@ -111,3 +111,24 @@ export async function consumeSignupToken(db: Db, tokenHash: string): Promise<Sig
.returning();
return row;
}
// ---- job events ----
export async function appendJobEvent(db: Db, jobId: string, payload: Record<string, unknown>): Promise<number> {
const [row] = await db
.select({ n: sql<number>`coalesce(max(${jobEvents.seq}), 0)` })
.from(jobEvents)
.where(eq(jobEvents.jobId, jobId));
const seq = (row?.n ?? 0) + 1;
await db.insert(jobEvents).values({ jobId, seq, payload: { t: Date.now(), ...payload } });
return seq;
}
export async function listJobEvents(db: Db, jobId: string, after = 0): Promise<Array<Record<string, unknown>>> {
const rows = await db
.select()
.from(jobEvents)
.where(and(eq(jobEvents.jobId, jobId), sql`${jobEvents.seq} > ${after}`))
.orderBy(jobEvents.seq);
return rows.map((r) => r.payload as Record<string, unknown>);
}
+13 -1
View File
@@ -1,4 +1,4 @@
import { pgTable, text, jsonb, integer, timestamp, uuid } from "drizzle-orm/pg-core";
import { pgTable, text, jsonb, integer, timestamp, uuid, index } from "drizzle-orm/pg-core";
/** Job lifecycle row. Status mirrors the queue; `cacheKey` ties it to the cache row.
* `options`/`timings` are jsonb (the service's typed shapes). */
@@ -66,6 +66,17 @@ export const signupTokens = pgTable("signup_tokens", {
consumedAt: timestamp("consumed_at", { withTimezone: true }),
});
/** Structured clone progress events (mirrors in-memory backend event stream). */
export const jobEvents = pgTable("job_events", {
id: uuid("id").defaultRandom().primaryKey(),
jobId: uuid("job_id")
.notNull()
.references(() => jobs.id, { onDelete: "cascade" }),
seq: integer("seq").notNull(),
payload: jsonb("payload").notNull(),
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
}, (t) => [index("job_events_job_id_seq_idx").on(t.jobId, t.seq)]);
export type Job = typeof jobs.$inferSelect;
export type NewJob = typeof jobs.$inferInsert;
export type Clone = typeof clones.$inferSelect;
@@ -74,3 +85,4 @@ export type CacheRow = typeof cache.$inferSelect;
export type ApiKey = typeof apiKeys.$inferSelect;
export type SignupToken = typeof signupTokens.$inferSelect;
export type NewSignupToken = typeof signupTokens.$inferInsert;
export type JobEvent = typeof jobEvents.$inferSelect;