summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorAdam Malczewski <[email protected]>2026-06-06 23:30:38 +0900
committerAdam Malczewski <[email protected]>2026-06-06 23:30:38 +0900
commit812621ca2f93d2aaeda98a2d5b5ab6bc8568d596 (patch)
tree99cebe8b2bbb46fb3f9d647ae0f5064589774e39
parent395d7565b46416e9914db099b9a4c226f4c95648 (diff)
downloaddispatch-812621ca2f93d2aaeda98a2d5b5ab6bc8568d596.tar.gz
dispatch-812621ca2f93d2aaeda98a2d5b5ab6bc8568d596.zip
feat(transport-http): wildcard CORS + bump contract pkgs to 0.1.0 (FE Slice 2 handoff)
Unblock the browser frontend (Vite origin :24204 -> HTTP backend :24203): - transport-http: wildcard CORS via hono/cors on all routes (Access-Control-Allow-Origin: *, Allow-Methods GET/POST/OPTIONS, Allow-Headers Content-Type) + OPTIONS preflight (204). Headers present on the streamed POST /chat NDJSON response too. +4 app.fetch tests. - wire / transport-contract / ui-contract: 0.0.0 -> 0.1.0 as the FE-consumable baseline (semver convention ยง2.9: major = cross-repo fan-out signal). Verified live: OPTIONS /chat -> 204 with CORS headers; GET /models -> 200 with Access-Control-Allow-Origin: *. typecheck clean, 502 vitest + 89 bun, biome clean.
-rw-r--r--packages/transport-contract/package.json2
-rw-r--r--packages/transport-http/src/app.test.ts50
-rw-r--r--packages/transport-http/src/app.ts10
-rw-r--r--packages/ui-contract/package.json2
-rw-r--r--packages/wire/package.json2
5 files changed, 63 insertions, 3 deletions
diff --git a/packages/transport-contract/package.json b/packages/transport-contract/package.json
index 03efc2b..0c097db 100644
--- a/packages/transport-contract/package.json
+++ b/packages/transport-contract/package.json
@@ -1,6 +1,6 @@
{
"name": "@dispatch/transport-contract",
- "version": "0.0.0",
+ "version": "0.1.0",
"type": "module",
"private": true,
"main": "dist/index.js",
diff --git a/packages/transport-http/src/app.test.ts b/packages/transport-http/src/app.test.ts
index 2adf4db..aa47dce 100644
--- a/packages/transport-http/src/app.test.ts
+++ b/packages/transport-http/src/app.test.ts
@@ -563,3 +563,53 @@ describe("GET /conversations/:id logging", () => {
expect(infoLogs[0]?.attrs?.count).toBe(2);
});
});
+
+describe("CORS", () => {
+ function createTestApp() {
+ return createApp({
+ conversationStore: createFakeConversationStore(),
+ orchestrator: createFakeOrchestrator([
+ { type: "done", conversationId: "conv1", turnId: "turn1", reason: "stop" },
+ ]),
+ credentialStore: createFakeCredentialStore(["opencode/m1"]),
+ });
+ }
+
+ it("POST /chat response carries Access-Control-Allow-Origin: *", async () => {
+ const app = createTestApp();
+ const res = await app.request("/chat", {
+ method: "POST",
+ headers: { "Content-Type": "application/json" },
+ body: JSON.stringify({ message: "hi", conversationId: "conv1" }),
+ });
+ expect(res.status).toBe(200);
+ expect(res.headers.get("Access-Control-Allow-Origin")).toBe("*");
+ });
+
+ it("GET /models response carries the CORS headers", async () => {
+ const app = createTestApp();
+ const res = await app.request("/models");
+ expect(res.status).toBe(200);
+ expect(res.headers.get("Access-Control-Allow-Origin")).toBe("*");
+ expect(res.headers.get("Access-Control-Expose-Headers")).toBeDefined();
+ });
+
+ it("GET /conversations/:id response carries the CORS headers", async () => {
+ const app = createTestApp();
+ const res = await app.request("/conversations/conv1");
+ expect(res.status).toBe(200);
+ expect(res.headers.get("Access-Control-Allow-Origin")).toBe("*");
+ expect(res.headers.get("Access-Control-Expose-Headers")).toBeDefined();
+ });
+
+ it("OPTIONS preflight for /chat returns 204 with Allow-Methods + Allow-Headers", async () => {
+ const app = createTestApp();
+ const res = await app.request("/chat", { method: "OPTIONS" });
+ expect(res.status).toBe(204);
+ expect(res.headers.get("Access-Control-Allow-Origin")).toBe("*");
+ expect(res.headers.get("Access-Control-Allow-Methods")).toContain("GET");
+ expect(res.headers.get("Access-Control-Allow-Methods")).toContain("POST");
+ expect(res.headers.get("Access-Control-Allow-Methods")).toContain("OPTIONS");
+ expect(res.headers.get("Access-Control-Allow-Headers")).toContain("Content-Type");
+ });
+});
diff --git a/packages/transport-http/src/app.ts b/packages/transport-http/src/app.ts
index bd9db4e..a8edc71 100644
--- a/packages/transport-http/src/app.ts
+++ b/packages/transport-http/src/app.ts
@@ -1,6 +1,7 @@
import type { AgentEvent, Logger } from "@dispatch/kernel";
import type { ConversationHistoryResponse, ModelsResponse } from "@dispatch/transport-contract";
import { Hono } from "hono";
+import { cors } from "hono/cors";
import {
isParseError,
isSinceSeqError,
@@ -45,6 +46,15 @@ export function createApp(opts: CreateServerOptions): Hono {
const log = opts.logger ?? noopLogger;
const generateId = opts.generateId ?? (() => crypto.randomUUID());
+ app.use(
+ "*",
+ cors({
+ origin: "*",
+ allowMethods: ["GET", "POST", "OPTIONS"],
+ allowHeaders: ["Content-Type"],
+ }),
+ );
+
app.get("/health", (c) => c.json({ ok: true }));
app.get("/conversations/:id", async (c) => {
diff --git a/packages/ui-contract/package.json b/packages/ui-contract/package.json
index e1f4c35..e7dd93f 100644
--- a/packages/ui-contract/package.json
+++ b/packages/ui-contract/package.json
@@ -1,6 +1,6 @@
{
"name": "@dispatch/ui-contract",
- "version": "0.0.0",
+ "version": "0.1.0",
"type": "module",
"private": true,
"main": "dist/index.js",
diff --git a/packages/wire/package.json b/packages/wire/package.json
index 2893e79..4d72a81 100644
--- a/packages/wire/package.json
+++ b/packages/wire/package.json
@@ -1,6 +1,6 @@
{
"name": "@dispatch/wire",
- "version": "0.0.0",
+ "version": "0.1.0",
"type": "module",
"private": true,
"main": "dist/index.js",