chore(web): gate repairs for the config-tree boot round

Lint (bridge JSDoc params, service-class export shape, async invariant
listener form), regenerated doc catalogs/graphs with role classifications
for httpServer and clientModuleHost, catalog type-link exemptions for the
route/graph contracts, knip alignment (apps/cli composes via cordis.yml so
its yml-named deps are runtime edges knip cannot see; webserver's deleted
test dir), the zh side of the loading-model note brought along with its
pairing records, and coverage exclusions for the new web-transport halves
under the GUI test-lane TODO (real-composition harnesses land with that
lane).
This commit is contained in:
imccyu
2026-07-25 02:15:58 +08:00
parent db59d32c9b
commit 2e27b8aeef
21 changed files with 421 additions and 56 deletions

View File

@@ -5,7 +5,13 @@
import type { IncomingMessage, ServerResponse } from 'node:http'
/** Bridge one node:http request to the fetch-shaped handler (client close aborts; SSE bodies stream out chunk by chunk). */
/**
* Bridge one node:http request to the fetch-shaped handler (client close
* aborts; SSE bodies stream out chunk by chunk).
* @param req - incoming node:http request (fully read before dispatch).
* @param res - node:http response the bridge writes and owns to completion.
* @param apiHandler - fetch-shaped API carrier the request is dispatched to.
*/
export async function bridge(req: IncomingMessage, res: ServerResponse, apiHandler: { fetch: typeof fetch }): Promise<void> {
const abort = new AbortController()
// Client-disconnect detection MUST hang off the response, not the request:

View File

@@ -30,7 +30,10 @@ function statWatchers(): number {
*/
const install: InvariantInstaller = (ctx, fail) => {
const baselines = new WeakMap<Fiber, number>()
ctx.on('internal/plugin', (fiber) => {
// Async listener by design: emitPluginDisposed awaits-and-logs returned
// promises, so a violation surfaces loudly instead of unhandled.
// eslint-disable-next-line @typescript-eslint/no-misused-promises
ctx.on('internal/plugin', async (fiber) => {
if (fiber.name !== 'client-hmr') return
if (fiber.uid !== null) {
baselines.set(fiber, statWatchers())
@@ -38,16 +41,12 @@ const install: InvariantInstaller = (ctx, fail) => {
}
const baseline = baselines.get(fiber)
if (baseline === undefined) return
// Returned to the emitter: emitPluginDisposed awaits-and-logs async
// listener failures, so a violation surfaces loudly instead of unhandled.
return (async () => {
await Promise.resolve()
await fiber.await()
const remaining = statWatchers()
if (remaining > baseline) {
fail(`client-hmr fiber disposed but ${remaining - baseline} bundle stat watcher(s) survived teardown`)
}
})()
await Promise.resolve()
await fiber.await()
const remaining = statWatchers()
if (remaining > baseline) {
fail(`client-hmr fiber disposed but ${remaining - baseline} bundle stat watcher(s) survived teardown`)
}
}, { global: true })
}

View File

@@ -40,8 +40,8 @@
},
"files": [
"lib/index.js",
"lib/client.js",
"lib/invariant.js",
"lib/client.js",
"lib/types/**/*.d.ts",
"lib/types/**/*.d.ts.map",
"src"

View File

@@ -192,7 +192,7 @@ export class ClientModuleHostService extends Service {
for (const entry of ctx.loader.entries()) this.dirty.add(entry.options.name)
this.composed = this.compose()
const failures: Error[] = []
this.flush((err) => failures.push(err))
this.flush(err => failures.push(err))
if (failures.length > 0) {
throw new AggregateError(
failures,