feat(examples): worked example for typed api blocks

- Add examples/basic-app/app/api/directory.ts and app/pages/api-block-demo.wrn
  as the end-to-end worked example for typed api blocks (Task 6).
- Add **/*.generated.api-checks.ts to .prettierignore: this generated file
  must match the CLI's raw output byte-for-byte for check:generated-types,
  and prettier was reformatting it.
- Fix packages/csr/test/api-call.test.ts: no-unsafe-function-type lint error
  from the raw Function type, uncovered while running the full gate.
- Rebuild editors/vscode bundles (packages/compiler and packages/syntax
  changed in Tasks 1-5).
- Regenerate docs/public-api-0.8.json (additive: isIdentPart, isIdentStart,
  skipLiteralOrComment newly exported from packages/syntax).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-19 17:08:49 +05:30
co-authored by Claude Opus 5
parent 3252b1b20e
commit e5de7b54a5
11 changed files with 527 additions and 76 deletions
+159 -30
View File
@@ -1,5 +1,5 @@
#!/usr/bin/env node
// WRN editor language server source hash: afce4195f664e656dc8f38283bda8118637d977cd2eee8ba08c80906681510cd
// WRN editor language server source hash: cf98947fd66392200bdfb18524a13e8bbb77d4920ba65a4d724e1124152571a3
// WRN editor language server generator hash: f593a44aaf05495b789ce7a3086bee1eebb951b884d41c0e017bbcfe5f547e72
// @bun @bun-cjs
(function(exports, require, module, __filename, __dirname) {var __create = Object.create;
@@ -169631,6 +169631,32 @@ var isWs = (c) => c === " " || c === "\t" || c === `
` || c === "\r";
var isIdentStart = (c) => /[A-Za-z_]/.test(c);
var isIdentPart = (c) => /[A-Za-z0-9_]/.test(c);
function skipLiteralOrComment(src, i, atLineStart) {
const c = src[i];
if (c === "/" && src[i + 1] === "*") {
const close = src.indexOf("*/", i + 2);
return close === -1 ? src.length : close + 2;
}
if (atLineStart && c === "/" && src[i + 1] === "/") {
const newline = src.indexOf(`
`, i + 2);
return newline === -1 ? src.length : newline;
}
if (c === '"' || c === "'" || c === "`") {
let j = i + 1;
while (j < src.length) {
if (src[j] === "\\") {
j += 2;
continue;
}
if (src[j] === c)
return j + 1;
j++;
}
return src.length;
}
return null;
}
class Lexer {
src;
@@ -169892,46 +169918,23 @@ class Lexer {
const start = this.pos + 1;
let depth = 0;
let i = this.pos;
let str = null;
let atLineStart = false;
for (;i < src.length; i++) {
while (i < src.length) {
const c = src[i];
if (str) {
if (c === "\\") {
i++;
continue;
}
if (c === str)
str = null;
continue;
}
if (c === `
`) {
atLineStart = true;
i++;
continue;
}
if (c === "/" && src[i + 1] === "*") {
const close = src.indexOf("*/", i + 2);
if (close === -1)
break;
i = close + 1;
const skipped = skipLiteralOrComment(src, i, atLineStart);
if (skipped !== null) {
i = skipped;
atLineStart = false;
continue;
}
if (atLineStart && c === "/" && src[i + 1] === "/") {
const newline = src.indexOf(`
`, i + 2);
if (newline === -1)
break;
i = newline - 1;
continue;
}
if (c !== " " && c !== "\t" && c !== "\r")
atLineStart = false;
if (c === '"' || c === "'" || c === "`") {
str = c;
continue;
}
if (c === "{")
depth++;
else if (c === "}") {
@@ -169941,6 +169944,7 @@ class Lexer {
return src.slice(start, i);
}
}
i++;
}
throw new LexError(`Unbalanced braces starting at offset ${this.pos}`);
}
@@ -170752,6 +170756,120 @@ var WRN_DIAGNOSTIC_CODES = {
migration: "WRN-MIGRATION-001"
};
// packages/syntax/src/api-sections.ts
var SECTION_NAMES = ["request", "response", "error"];
var REQUEST_SUBSECTION_NAMES = ["parameters", "body"];
function scanTopLevelBlocks(source, names) {
const found = new Map;
const lx = new Lexer(source);
let depth = 0;
let i = 0;
let atLineStart = true;
while (i < source.length) {
const c = source[i];
if (c === `
`) {
atLineStart = true;
i++;
continue;
}
const skipped = skipLiteralOrComment(source, i, atLineStart);
if (skipped !== null) {
i = skipped;
atLineStart = false;
continue;
}
if (c !== " " && c !== "\t" && c !== "\r")
atLineStart = false;
if (depth === 0 && isIdentStart(c)) {
let j = i + 1;
while (j < source.length && isIdentPart(source[j]))
j++;
const word = source.slice(i, j);
let k = j;
let lineStartAtK = false;
while (k < source.length) {
const kc = source[k];
if (kc === " " || kc === "\t" || kc === "\r") {
k++;
continue;
}
if (kc === `
`) {
lineStartAtK = true;
k++;
continue;
}
const kSkipped = skipLiteralOrComment(source, k, lineStartAtK);
if (kSkipped !== null) {
k = kSkipped;
lineStartAtK = false;
continue;
}
break;
}
if (names.includes(word) && source[k] === "{") {
lx.pos = k;
const start = k + 1;
const text = lx.readBalancedBraces();
if (!found.has(word))
found.set(word, { text, start });
i = lx.pos;
continue;
}
i = j;
continue;
}
if (c === "{")
depth++;
else if (c === "}")
depth = Math.max(0, depth - 1);
i++;
}
return found;
}
function absolutize(span, outer) {
if (!span)
return;
return outer ? { text: span.text, start: outer.start + span.start } : span;
}
function parseFields(span) {
if (!span)
return [];
const fields = [];
let cursor = 0;
for (const rawLine of span.text.split(`
`)) {
const lineOffset = span.start + cursor;
cursor += rawLine.length + 1;
const line = rawLine.trim().replace(/,$/, "");
if (!line || line.startsWith("//"))
continue;
const match = /^([A-Za-z_$][A-Za-z0-9_$]*)(\?)?\s*:\s*(.+)$/.exec(line);
if (!match) {
throw new LexError(`Expected "name: type" in an api request section, got "${line}" at offset ${lineOffset}`);
}
fields.push({ name: match[1], optional: match[2] === "?", type: match[3].trim() });
}
return fields;
}
function parseApiSections(source) {
const top = scanTopLevelBlocks(source, SECTION_NAMES);
if (top.size === 0)
return null;
const request = top.get("request");
const sub = request ? scanTopLevelBlocks(request.text, REQUEST_SUBSECTION_NAMES) : new Map;
return {
parameters: parseFields(absolutize(sub.get("parameters"), request)),
body: parseFields(absolutize(sub.get("body"), request)),
response: top.get("response")?.text ?? "",
error: top.get("error")?.text ?? ""
};
}
function hasRequestSection(source) {
return scanTopLevelBlocks(source, SECTION_NAMES).has("request");
}
// packages/syntax/src/types.ts
function runtimeTypeOf(annotation) {
if (!annotation)
@@ -171632,7 +171750,18 @@ function parse(source) {
const method = expect("ident").value.toUpperCase();
const path = lx.readPath();
const body = lx.readBalancedBraces();
dataApis.push({ mode, name: name2, method, path, body });
const sections = parseApiSections(body);
if (sections && mode !== "client" && hasRequestSection(body)) {
throw new ParseError(`An ssr api block cannot declare "request": there is no caller at render time to supply it. Use a client block, or a server function.`);
}
dataApis.push({
mode,
name: name2,
method,
path,
body: sections ? "" : body,
...sections ? { sections } : {}
});
break;
}
case "functions": {