docs(cordis): document Timer wrapper migration and local docs

This commit is contained in:
Shigma committed 2026-10-15 16:47:43 +08:00
1 parent 7618ccec69
commit f10be1f63a
10 files changed
+81 -7

No files matched your search

@@ -0,0 +1,16 @@
# Bilingual-pair consistency record for guide.md (docs/i18n/README.md): per heading
# section, a hash of its English and Chinese blocks outside code blocks and generated regions.
# After editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write docs/upgrade-guide/v0.2.1-alpha.2/cordis-timer-wrappers/guide.md
/:
en: a1e092dfacd2aed8
zh: 838448e5bf0f9d93
/timer-wrapper-return-types-and-disposal:
en: 8b8de9b0fc2e3fc3
zh: ffa20b7ffa7540ef
/timer-wrapper-return-types-and-disposal/change:
en: 978657a23b852a1e
zh: 1402cc87410cfc7a
/timer-wrapper-return-types-and-disposal/migration:
en: c07c4d66ff9fd1ff
zh: ecebc12b08a9be47
@@ -0,0 +1,21 @@
---
kind: upgrade-guide
description: "Timer wrappers return void in TypeScript and ignore calls after disposal."
---
# Timer wrapper return types and disposal
English | [中文](guide.zh.md)
## Change
The functions returned by `ctx.throttle()` and `ctx.debounce()` now declare a `void` return type. They previously inherited the callback's return type, although they already returned `undefined` at runtime. TypeScript code that uses the wrapper's return value or assigns it to the callback's type may need updating. This applies to Host Timer and browser ClientTimerService.
Calling `.dispose()` or disposing the owning fiber cancels pending work and makes later wrapper calls inert. A disposed throttle wrapper previously could still execute a leading call; debounce wrappers already ignored calls after disposal.
## Migration
1. Let TypeScript infer the wrapper type, or declare its call signature with a `void` return type while retaining `.dispose()` where needed.
2. Consume callback results inside the callback. Awaiting the wrapper does not wait for an asynchronous callback to finish.
3. Move required work before disposal, or create a new wrapper in an active plugin.
4. Run your extension's type check and verify that calls after disposal neither invoke the callback nor schedule work.
@@ -0,0 +1,21 @@
---
kind: upgrade-guide
description: "Timer 包装函数的 TypeScript 返回类型为 void,释放后忽略后续调用。"
---
# Timer 包装函数的返回类型与资源释放
[English](guide.md) | 中文
## 变更
`ctx.throttle()` 和 `ctx.debounce()` 返回的函数现在声明 `void` 返回类型。此前这些函数继承回调的返回类型,但运行时已经返回 `undefined`。使用包装函数返回值或将包装函数赋给回调类型的 TypeScript 代码可能需要更新。此变更适用于 Host Timer 和浏览器 ClientTimerService。
调用 `.dispose()` 或释放所属 fiber 会取消待执行的工作,并使后续包装函数调用不再执行。此前已释放的 throttle 包装函数仍可能执行前沿调用;debounce 包装函数在释放后已经会忽略调用。
## 迁移
1. 让 TypeScript 推断包装函数的类型,或将其调用签名声明为返回 `void`,并按需保留 `.dispose()`。
2. 在回调内部使用回调结果。对包装函数使用 await 不会等待异步回调完成。
3. 将必须执行的工作移到资源释放之前,或在活动插件中创建新的包装函数。
4. 运行扩展的类型检查,并验证释放后的调用既不会执行回调,也不会安排工作。
@@ -33,8 +33,8 @@
en: df635b29eaef982c
zh: ddaa4bd98359bd30
/deepseek-ai-dsh-cordis-client-runner/understand-the-implementation/source-map:
en: 22a43e93fd123200
zh: f8305186a748369b
en: f69f511aa85127f2
zh: 651ea63d81bbd648
/deepseek-ai-dsh-cordis-client-runner/understand-the-implementation/inspecting-slot-inputs:
en: 33f14a7fff7835e6
zh: 53838ac8d4a79600
@@ -64,7 +64,7 @@ The browser half is built on one principle: a dynamic package must ride the same
| [`src/client/guard.ts`](src/client/guard.ts) | The whitelisting `ctx` façade for loaded browser halves |
| [`src/client/inspect-registry.ts`](src/client/inspect-registry.ts) | Client Inspect Providers and the pending-query router |
| [`src/client/providers.ts`](src/client/providers.ts) | First-party client Inspect Providers (slots, theme, events) |
| [`src/client/timer.ts`](src/client/timer.ts) | Client timers with ordered concurrent interval reads and void-returning throttle/debounce wrappers that become inert on disposal |
| [`src/client/timer.ts`](src/client/timer.ts) | Browser timer Service matching the Host Timer API |
### Inspecting slot inputs
@@ -64,7 +64,7 @@ kind: "package-reference"
| [`src/client/guard.ts`](src/client/guard.ts) | 已装载浏览器半收到的白名单 `ctx` façade |
| [`src/client/inspect-registry.ts`](src/client/inspect-registry.ts) | Client Inspect Provider 与待答查询路由器 |
| [`src/client/providers.ts`](src/client/providers.ts) | 第一方 client Inspect Provider(slots、theme、events) |
| [`src/client/timer.ts`](src/client/timer.ts) | client 定时器按顺序完成并发 interval 读取;throttle/debounce 包装函数返回 void,释放后不再执行 |
| [`src/client/timer.ts`](src/client/timer.ts) | 与 Host Timer API 一致的浏览器定时器 Service |
### 查询槽位输入
@@ -50,4 +50,20 @@ describe('client Timer', () => {
expect(work).toHaveBeenCalledExactlyOnceWith('leading')
expect(vi.getTimerCount()).toBe(0)
})
it('retains leading calls and suppresses trailing calls with noTrailing', async () => {
const work = vi.fn()
const throttle = ctx.throttle(work, 20, true)
throttle('first')
throttle('suppressed')
await vi.advanceTimersByTimeAsync(20)
expect(work).toHaveBeenCalledExactlyOnceWith('first')
throttle('next window')
expect(work.mock.calls).toEqual([['first'], ['next window']])
throttle.dispose()
await vi.advanceTimersByTimeAsync(20)
throttle('disposed')
expect(work).toHaveBeenCalledTimes(2)
expect(vi.getTimerCount()).toBe(0)
})
})
+1 -1
View File
@@ -71,7 +71,7 @@ describe('Cordis Timer', () => {
throttled('second')
await vi.advanceTimersByTimeAsync(10)
expect(callback.mock.calls).toEqual(noTrailing ? [['first']] : [['first'], ['second']])
throttled('pending')
throttled('before disposal')
const calls = callback.mock.calls.length
throttled.dispose()
await vi.advanceTimersByTimeAsync(20)
+1 -1
View File
@@ -38,7 +38,7 @@ Keep this log exhaustive — every divergence from upstream must be listed.
4. **Vendored TypeScript source internal specifiers**: changed local relative imports/exports from upstream's specifier shape to explicit `.ts` specifiers so TypeScript rewrites emitted JS to `.js` while declarations keep explicit, NodeNext-safe `.ts` specifiers. This includes `loader/src/config/isolate.ts` using `declare module './entry.ts'`. Type-only dependencies use `import type` or inline `type` modifiers so ESM output does not retain erased interfaces.
5. **`schemastery/tsdown.config.ts` and `logger-console/tsdown.config.ts`**: ours, not upstream files — per-package build-shape overrides (dual ESM+CJS output; separate node/browser entries) for the repo-root tsdown build. They read the JS emitted under `lib/types` and then write the publish runtime entries under `lib/`. Like the regenerated tsconfigs, they are not part of the upstream sync surface.
6. **`cordis/src/fiber.ts` lifecycle hardening**: locally closes three reentrant disposal gaps. An effect's owner-list wrapper is registered before its setup body runs, so an unload begun from inside setup awaits setup and every collected cleanup; synchronous setup failure removes the wrapper and rolls back collected cleanup. Async cleanup stays owner-visible until quiescence, and Cordis's internal effect composition joins an already-running cleanup while repeated public disposer calls retain their upstream single-shot result. Effect creation is rejected while the owner is `UNLOADING` (while `PENDING` and `LOADING` remain legal), preventing cleanup-time registrations from escaping the unload snapshot. Child fibers register and receive their parent-owned disposer before `internal/plugin` publication, resolve dependency declarations added by that notification before activation, drain effects attached while pending, skip plugin execution when reentrant disposal invalidates the load epoch before its first checkpoint, and contain teardown-notification failures per observer so one callback cannot starve peers or interrupt ownership cleanup.
7. **Cordis, Loader and Timer JSDoc enrichment**: added `@param`/`@returns` tags and contract documentation (disposal semantics, waterfall veto, bail conditions, error cases) across the public plugin-author surface — `Context` (class, statics, and the `Context` interface properties incl. `root`), module re-exports, shared utility helpers, `EventsService`, `Fiber`, `RegistryService`, `ReflectService`, `Service`, `LoggerService` and their `declare module './context.ts'` overloads. Loader documentation covers `loader/src/{index,internal,config/entry,config/group,config/isolate,config/tree,config/utils}.ts`, including entry ownership, tree mutation methods, and the `!!js` discriminator. Timer retains its local class and helper summaries. Comment-only; no code changes. Motivation: the website API-reference generator renders these docs and hard-errors on undocumented members. Retire this entry when the enrichment is upstreamed to the fork.
7. **Cordis, Loader and Timer JSDoc enrichment**: added `@param`/`@returns` tags and contract documentation (disposal semantics, waterfall veto, bail conditions, error cases) across the public plugin-author surface — `Context` (class, statics, and the `Context` interface properties incl. `root`), module re-exports, shared utility helpers, `EventsService`, `Fiber`, `RegistryService`, `ReflectService`, `Service`, `LoggerService` and their `declare module './context.ts'` overloads. Loader documentation covers `loader/src/{index,internal,config/entry,config/group,config/isolate,config/tree,config/utils}.ts`, including entry ownership, tree mutation methods, and the `!!js` discriminator. Timer retains its local class and helper summaries; its README is local documentation because the upstream package has none. Comment-only; no code changes. Motivation: the website API-reference generator renders these docs and hard-errors on undocumented members. Retire this entry when the enrichment is upstreamed to the fork.
8. **`include/src/index.ts` resilient file refresh and patch reapplication**: [#514](https://github.com/deepseek-harness/deepseek-harness/pull/514) validates a top-level entry array before caching parsed content, logs refresh failures, reapplies patches after file or Include-config edits, updates the Include config when it vetoes a restart, and uses `initial` only after `ENOENT`. These parse protections retain the running tree after an invalid file; plugin activation failures can leave a partially applied tree. Loader entry/group/tree mutations follow the pinned eager, non-transactional implementation and do not restore previous plugins or options. Covered by `packages/boot/app-boot/tests/{config-reload,user-patches}.spec.ts`.
9. **`hmr/src/index.ts` module-watcher readiness and native paths**: realpath the existing watch base, classify framework modules and attach listeners before reporting readiness, and compare config paths using both canonical and configured spellings. This preserves Node module-cache identity across Windows short-name paths and filesystem aliases. The main watcher uses `ignoreInitial: true` so startup reads do not trigger another refresh; exact profile patch watching is owned by `packages/boot/hmr/src/watch-config.ts`. Covered by `packages/boot/hmr/tests/watch-config.spec.ts`.
10. **Vendored Node-compatible TypeScript**: marked erased imports explicitly across `cordis`, `loader`, `include`, `hmr`, and `schemastery` so Node's native TypeScript transform does not request types as runtime exports. Schemastery's source uses an ESM default export and its package declares `type: module`. Conditional exports select `.mjs` for import and `.cjs` for require; without them, concurrent imports under module-hook hosts reach the CJS entry and can race its require of ESM Cosmokit (`ERR_REQUIRE_ESM_RACE_CONDITION`).
+1 -1
View File
@@ -4,7 +4,7 @@ Status: proposed
## Problem
The [vendor manifest](README.md) records Cordis core 4.0.0-rc.7, Loader 1.0.0-rc.5, Include 1.0.4, HMR 1.0.15 and Timer 1.1.2, with 24 local-modification entries. [Cordis v4.0.0-rc.11](https://github.com/cordiverse/cordis/releases/tag/v4.0.0-rc.11) incorporates some fixes and replaces other mechanisms. Updating every package and retiring every patch in one change would combine configuration, persistence, lifecycle and module-replacement decisions.
The upgrade starts from the [vendored](README.md) baselines Cordis core 4.0.0-rc.7, Loader 1.0.0-rc.5, Include 1.0.4, HMR 1.0.15 and Timer 1.1.2, with 24 local-modification entries. [Cordis v4.0.0-rc.11](https://github.com/cordiverse/cordis/releases/tag/v4.0.0-rc.11) incorporates some fixes and replaces other mechanisms. Updating every package and retiring every patch in one change would combine configuration, persistence, lifecycle and module-replacement decisions.
DSH also has consumers that upstream Cordis does not own. Volatile configuration fields are stable references updated without restarting plugins; the settings UI reads their live values. A profile selects ordered plugin bundles and configuration overrides. [DSH HMR](../packages/boot/hmr/README.md) coordinates module replacement and profile changes through `ctx.hmr`. An entry's module namespace is its raw import result, retained before selecting `default` or `apply` so HMR can identify the module already loaded. These consumers must remain usable at every merge point.