directory.ts previously exported a plain (ctx: Context) => ... handler.
With that shape ApiInput<> resolved to unknown, so the generated
__wrn_api_check assertion for the demo page passed trivially even with
a field the endpoint does not accept -- the worked example did not
demonstrate the type safety it exists to demonstrate.
Rewrite directory.ts to use defineEndpoint with a schema (matching
typed-user.ts), which gives the generated assertion a real input type
to check against. Confirmed: adding an unaccepted field to the block's
request body now fails typecheck naming
__wrn_api_check_searchDirectory; removing it passes with zero net
diff.
Fix a real bug this surfaced: packages/core/src/endpoint.ts only read
its input from a second 'rawInput' argument, but the actual HTTP
router (packages/dev-server/src/runtime.ts handleApi) invokes route
handlers as handler(ctx) with no second argument. Every
defineEndpoint-based route -- including the pre-existing typed-user.ts
example -- silently received an empty/undefined input through the
real router (confirmed via curl: valid typed-user payloads were
rejected as 'Required'; directory's name filter matched every record
regardless of query). Fixed by having the endpoint wrapper parse the
request itself (query params for GET/HEAD, JSON body otherwise) when
no rawInput is explicitly supplied, while still honoring an explicit
rawInput for direct/unit-test callers.
Also update api-block-demo.wrn's response section: defineEndpoint
wraps handler output as { data: ... }, so the block's raw response
body is now { data: { users: [...] } } -- response reads
data.data.users instead of data.users.
Re-verified in a real browser after the endpoint rewrite and the
router fix: search returns exactly "Ajay, Asha", exactly one
POST /api/directory carrying x-csrf-token, and the error section
still runs cleanly (no exception, empty result) on a missing route.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
39 lines
720 B
Plaintext
39 lines
720 B
Plaintext
page ApiBlockDemo {
|
|
state nameFilter = "a"
|
|
state found = ""
|
|
state failed = ""
|
|
|
|
client {
|
|
api searchDirectory POST /api/directory {
|
|
request {
|
|
body {
|
|
name?: string
|
|
}
|
|
}
|
|
|
|
response {
|
|
return data.data.users
|
|
}
|
|
|
|
error {
|
|
return []
|
|
}
|
|
}
|
|
}
|
|
|
|
functions {
|
|
client async function search(): Promise<void> {
|
|
const users = await api.searchDirectory({ name: nameFilter })
|
|
found = users.map((user) => user.name).join(", ")
|
|
}
|
|
}
|
|
|
|
view {
|
|
<main>
|
|
<button @click="search()">Search</button>
|
|
<p class="found" data-text="found">{found}</p>
|
|
<p class="failed" data-text="failed">{failed}</p>
|
|
</main>
|
|
}
|
|
}
|