mirror of
https://github.com/deepseek-ai/deepseek-harness
synced 2026-08-15 21:04:50 +00:00
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).
60 lines
2.4 KiB
TypeScript
60 lines
2.4 KiB
TypeScript
/**
|
|
* Package-owned invariant companion for `@deepseek-ai/dsh-client-hmr`.
|
|
* @module @deepseek-ai/dsh-client-hmr/invariant
|
|
*/
|
|
|
|
import type { Context, Fiber } from 'cordis'
|
|
import type { InvariantInstaller } from '@deepseek-ai/dsh-invariants'
|
|
|
|
const PACKAGE_NAME = '@deepseek-ai/dsh-client-hmr'
|
|
|
|
/** Cordis companion plugin name. */
|
|
export const name = 'client-hmr-invariant'
|
|
/** Service required before the companion can reserve package ownership. */
|
|
export const inject = ['invariants']
|
|
|
|
/** Live fs.watchFile pollers (this package is the composition's only stat-poll user). */
|
|
function statWatchers(): number {
|
|
return process.getActiveResourcesInfo().filter(kind => kind === 'StatWatcher').length
|
|
}
|
|
|
|
/**
|
|
* Owned relation: every bundle stat watcher the node half starts must die
|
|
* with its fiber — a surviving poller would keep re-hashing bundles for a
|
|
* torn-down dev chain forever. Checked as a baseline delta: the StatWatcher
|
|
* count observed at fiber creation must be restored once disposal has drained
|
|
* the fiber's effects (`internal/plugin` fires at dispose start; the microtask
|
|
* hop lets the disposer queue its unload before `fiber.await()` joins it).
|
|
* SSE-connection and listener teardown live inside the same ctx.effect
|
|
* disposers, so the watcher count is the relation's observable proxy.
|
|
*/
|
|
const install: InvariantInstaller = (ctx, fail) => {
|
|
const baselines = new WeakMap<Fiber, number>()
|
|
// 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())
|
|
return
|
|
}
|
|
const baseline = baselines.get(fiber)
|
|
if (baseline === undefined) return
|
|
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 })
|
|
}
|
|
|
|
/**
|
|
* Register this package's invariant companion.
|
|
* @param ctx - Cordis context carrying the invariant service.
|
|
* @returns the installed registration's disposer after setup succeeds.
|
|
*/
|
|
export const apply = (ctx: Context): Promise<() => void> =>
|
|
Promise.resolve(ctx.invariants.register(PACKAGE_NAME, install))
|