feat(cli): generate type assertions for api blocks
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,73 @@
|
||||
import { afterEach, expect, test } from "bun:test";
|
||||
import { mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
||||
import { tmpdir } from "node:os";
|
||||
import { join } from "node:path";
|
||||
import { generateApplicationTypes } from "../src/types.ts";
|
||||
|
||||
const roots: string[] = [];
|
||||
afterEach(() => {
|
||||
for (const root of roots.splice(0)) rmSync(root, { recursive: true, force: true });
|
||||
});
|
||||
|
||||
/** Minimal app with one typed endpoint and one page that calls it. */
|
||||
function fixture(block: string): string {
|
||||
const root = mkdtempSync(join(tmpdir(), "wrnexus-api-types-"));
|
||||
roots.push(root);
|
||||
mkdirSync(join(root, "app/pages"), { recursive: true });
|
||||
mkdirSync(join(root, "app/api"), { recursive: true });
|
||||
writeFileSync(
|
||||
join(root, "app/api/users.ts"),
|
||||
`export const POST = async () => Response.json({ users: [] });\n`,
|
||||
);
|
||||
writeFileSync(
|
||||
join(root, "app/pages/search.wrn"),
|
||||
`page Search {\n client {\n${block}\n }\n\n view { <main>x</main> }\n}\n`,
|
||||
);
|
||||
return root;
|
||||
}
|
||||
|
||||
const BLOCK = ` api searchUsers POST /api/users {
|
||||
request {
|
||||
body {
|
||||
name?: string
|
||||
age?: number
|
||||
}
|
||||
}
|
||||
|
||||
response {
|
||||
return data.users
|
||||
}
|
||||
}`;
|
||||
|
||||
test("emits the ApiInput, ApiOutput and AssertAssignable helpers", () => {
|
||||
const root = fixture(BLOCK);
|
||||
generateApplicationTypes(root);
|
||||
const generated = readFileSync(join(root, "app/types/wrnexus.generated.d.ts"), "utf8");
|
||||
|
||||
expect(generated).toContain("type AssertAssignable<");
|
||||
expect(generated).toContain('type ApiInput<P extends ApiRoute, M> = ApiContracts[P][M]["input"]');
|
||||
expect(generated).toContain(
|
||||
'type ApiOutput<P extends ApiRoute, M> = ApiContracts[P][M]["output"]',
|
||||
);
|
||||
});
|
||||
|
||||
test("emits one assertion per sectioned block, naming its route and method", () => {
|
||||
const root = fixture(BLOCK);
|
||||
generateApplicationTypes(root);
|
||||
const generated = readFileSync(join(root, "app/types/wrnexus.generated.d.ts"), "utf8");
|
||||
|
||||
expect(generated).toContain("__wrn_api_check_searchUsers");
|
||||
expect(generated).toContain('ApiInput<"/api/users", "POST">');
|
||||
expect(generated).toContain("name?: string");
|
||||
expect(generated).toContain("age?: number");
|
||||
});
|
||||
|
||||
test("a legacy bare-body block produces no assertion", () => {
|
||||
const root = fixture(` api legacyUsers GET /api/users {
|
||||
return users.length
|
||||
}`);
|
||||
generateApplicationTypes(root);
|
||||
const generated = readFileSync(join(root, "app/types/wrnexus.generated.d.ts"), "utf8");
|
||||
|
||||
expect(generated).not.toContain("__wrn_api_check_legacyUsers");
|
||||
});
|
||||
Reference in New Issue
Block a user