Skip to content

Commit 5e24b1a

Browse files
dmealingclaude
andcommitted
fix(runtime-ts): ensure byte-identical /_meta headers across fastify and hono
Extract shared content-type constant and tighten test assertions to verify both frameworks serve identical headers. The Hono test now asserts the content-type header (previously missing), Fastify test uses exact equality instead of substring match, and both mounts import from a single constant to prevent header drift. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015NrWUxA6euwvnFcwKxcK6A
1 parent 2f4b579 commit 5e24b1a

5 files changed

Lines changed: 17 additions & 8 deletions

File tree

‎server/typescript/packages/runtime-ts/src/fastify/index.ts‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -262,7 +262,7 @@ export { parseId } from "../drizzle-fastify/util.js";
262262
// ---------------------------------------------------------------------------
263263

264264
import type { MetaData } from "@metaobjectsdev/metadata";
265-
import { META_ROUTE_PATH, metaJson } from "../meta-endpoint.js";
265+
import { META_ROUTE_PATH, META_CONTENT_TYPE, metaJson } from "../meta-endpoint.js";
266266

267267
export interface MetaRouteOptions {
268268
fastify: FastifyInstance;
@@ -292,7 +292,7 @@ export function mountMetaRoute(opts: MetaRouteOptions): void {
292292
opts.fastify.get(path, async (_req, reply) => {
293293
return reply
294294
.code(200)
295-
.header("content-type", "application/json; charset=utf-8")
295+
.header("content-type", META_CONTENT_TYPE)
296296
.send(metaJson(opts.root));
297297
});
298298
}

‎server/typescript/packages/runtime-ts/src/hono/index.ts‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -326,7 +326,7 @@ export { mountReadOnlyCrudRoutes, type MountReadOnlyOptions } from "./mount-read
326326
// ---------------------------------------------------------------------------
327327

328328
import type { MetaData } from "@metaobjectsdev/metadata";
329-
import { META_ROUTE_PATH, metaJson } from "../meta-endpoint.js";
329+
import { META_ROUTE_PATH, META_CONTENT_TYPE, metaJson } from "../meta-endpoint.js";
330330

331331
export interface HonoMetaRouteOptions {
332332
app: AnyHono;
@@ -344,6 +344,6 @@ export interface HonoMetaRouteOptions {
344344
export function mountMetaRouteHono(opts: HonoMetaRouteOptions): void {
345345
const path = `${opts.prefix ?? ""}${META_ROUTE_PATH}`;
346346
opts.app.get(path, (c) =>
347-
c.body(metaJson(opts.root), 200, { "content-type": "application/json; charset=utf-8" }),
347+
c.body(metaJson(opts.root), 200, { "content-type": META_CONTENT_TYPE }),
348348
);
349349
}

‎server/typescript/packages/runtime-ts/src/index.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,4 +50,4 @@ export {
5050
} from "./constraint-errors.js";
5151

5252
// UI-1 — metadata API contract for the browser read-model.
53-
export { META_ROUTE_PATH, metaJson } from "./meta-endpoint.js";
53+
export { META_ROUTE_PATH, META_CONTENT_TYPE, metaJson } from "./meta-endpoint.js";

‎server/typescript/packages/runtime-ts/src/meta-endpoint.ts‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,14 @@ import { canonicalSerializeEffective, type MetaData } from "@metaobjectsdev/meta
1010
*/
1111
export const META_ROUTE_PATH = "/_meta";
1212

13+
/**
14+
* The `GET {apiPrefix}/_meta` response content-type header.
15+
* A cross-port contract value — one browser read-model has to work against
16+
* every backend, so the header must be byte-identical across all Fastify and
17+
* Hono mounts.
18+
*/
19+
export const META_CONTENT_TYPE = "application/json; charset=utf-8";
20+
1321
/**
1422
* The `GET {apiPrefix}/_meta` response body: the loaded model as EFFECTIVE
1523
* canonical JSON.

‎server/typescript/packages/runtime-ts/test/meta-route.test.ts‎

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { describe, test, expect } from "bun:test";
22
import Fastify from "fastify";
33
import { Hono } from "hono";
4-
import { metaJson } from "../src/meta-endpoint.js";
4+
import { metaJson, META_CONTENT_TYPE } from "../src/meta-endpoint.js";
55
import { mountMetaRoute } from "../src/fastify/index.js";
66
import { mountMetaRouteHono } from "../src/hono/index.js";
77
import { loadTestModel } from "./helpers/load-test-model.js";
@@ -16,7 +16,7 @@ describe("GET /_meta (fastify)", () => {
1616
const res = await app.inject({ method: "GET", url: "/api/_meta" });
1717

1818
expect(res.statusCode).toBe(200);
19-
expect(res.headers["content-type"]).toContain("application/json");
19+
expect(res.headers["content-type"]).toBe(META_CONTENT_TYPE);
2020
expect(res.body).toBe(metaJson(root));
2121
});
2222

@@ -31,14 +31,15 @@ describe("GET /_meta (fastify)", () => {
3131
});
3232

3333
describe("GET /_meta (hono)", () => {
34-
test("serves the same bytes as the fastify mount", async () => {
34+
test("serves the same bytes and headers as the fastify mount", async () => {
3535
const root = await loadTestModel();
3636
const app = new Hono();
3737
mountMetaRouteHono({ app, root, prefix: "/api" });
3838

3939
const res = await app.request("/api/_meta");
4040

4141
expect(res.status).toBe(200);
42+
expect(res.headers.get("content-type")).toBe(META_CONTENT_TYPE);
4243
expect(await res.text()).toBe(metaJson(root));
4344
});
4445
});

0 commit comments

Comments
 (0)