mirror of
https://github.com/deepseek-ai/deepseek-harness.git
synced 2026-08-29 04:26:38 +00:00
docs(client-modules): align resolver contract
This commit is contained in:
@@ -2,5 +2,5 @@
|
||||
# side as of the last confirmed-consistent state. Both languages carry equal authority;
|
||||
# after editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write docs/subsystems/client-modules.md
|
||||
client-modules.md: 6188880fcd682c7c3c105764f212e92395444b61
|
||||
client-modules.zh.md: a732758492e1dbc9cbaef8d3effbd3310e4f10a1
|
||||
client-modules.md: f58cb6592a009292ffc4f87a207fe4e103fd2365
|
||||
client-modules.zh.md: 18c72dfba6f851776100a9b6200e7222d580db7c
|
||||
|
||||
@@ -74,11 +74,11 @@ Each initial row's `rev` is an opaque process nonce plus sequence, so graph comp
|
||||
|
||||
## The scan
|
||||
|
||||
A package joins the table by declaring `dsh.client` (`platform: 'web'`, optional `inject` edges, optional `immediately`) in its package.json and exporting its built bundle at `exports["./client"]`. Package resolution anchors at the config tree's `ctx.baseUrl` — the cordis.yml directory, whose package declares every composed plugin as a dependency — and construction throws when that anchor is unset.
|
||||
A package joins the table by declaring `dsh.client` (`platform: 'web'`, optional `inject` edges, optional `immediately`) in its package.json and exporting its built bundle at `exports["./client"]`. Each live row resolves from its own Loader specifier and owning-tree `baseUrl`, through the same `loader.internal.resolveSync` implementation that imports its Host face when available. The nearest owning package manifest supplies the browser module id, so relative source and built overlays retain the package identity. Distinct active Loader sources resolving to one package name fail composition; after one source unloads, the surviving source supplies the row without a fiber restart.
|
||||
|
||||
Scanning is incremental per package; there is no full-rescan code path. Every cordis `internal/plugin` emission (fiber construction or disposal) marks the fiber's entry name dirty, and a microtask flush reconciles each dirty name against the live loader entries. The activation pass seeds the same dirty set with all current entries and flushes synchronously, so first scan and steady state share one implementation — with opposite failure postures. At activation, a malformed declaration or missing bundle among the already-loaded entries aggregates into one loud `AggregateError` listing every broken package: the fiber FAILS and the boot's fail-loud sweep reports it. In steady state, a broken package logs a warning and must not poison the others.
|
||||
|
||||
Package metadata — including the negative "not a client package" verdict — is cached per name and never expires: plugin-set changes take effect on restart. A fiber restart reuses its row and rev untouched; bundle content changes reach the graph only through `rebuilt()`.
|
||||
Package metadata — including the negative "not a client package" verdict — is cached per Loader specifier and owning-tree base URL until restart. A fiber restart from the same source reuses its row and rev untouched; bundle content changes reach the graph only through `rebuilt()`.
|
||||
|
||||
## The bundle route and index injection
|
||||
|
||||
|
||||
@@ -74,11 +74,11 @@ interface WebBootGraph {
|
||||
|
||||
## 扫描
|
||||
|
||||
包加入这张表的方式,是在自己的 package.json 中声明 `dsh.client`(`platform: 'web'`、可选的 `inject` 边、可选的 `immediately`),并在 `exports["./client"]` 导出构建好的 bundle。包解析锚定在配置树的 `ctx.baseUrl`——即 cordis.yml 所在目录,该目录的包把每个被组合的插件声明为依赖——这一锚点未设置时,构造即抛错。
|
||||
包加入这张表的方式,是在自己的 package.json 中声明 `dsh.client`(`platform: 'web'`、可选的 `inject` 边、可选的 `immediately`),并在 `exports["./client"]` 导出构建好的 bundle。每个 live row 都从自己的 Loader specifier 与所属 tree `baseUrl` 解析;若 `loader.internal.resolveSync` 可用,则使用 Host face import 所用的同一个实现。最近归属的 package manifest 提供浏览器模块 id,因此相对 source 与 built overlay 仍保留包身份。若不同的 active Loader source 解析到同一包名,组合会失败;一个来源卸载后,仍存活的来源无需重启 fiber 即可提供该 row。
|
||||
|
||||
扫描是单包增量的;不存在全量重扫代码路径。fiber 构造或 dispose(资源释放)时的每次 cordis `internal/plugin` 发射都把该 fiber 的 entry 名标脏,一次微任务 flush 把每个脏名与实时 loader entry 对账。激活趟以全部当前 entry 灌入同一个脏集合并同步 flush,因此初扫与稳态共享一条实现——但失败姿态相反。激活时,已加载 entry 中的畸形声明或缺失 bundle 会聚合为一个大声的 `AggregateError`,列出每个损坏的包:该 fiber 进入 FAILED,由启动的大声失败 sweep 上报。稳态下,损坏的包只记录一条警告,且不得殃及其他包。
|
||||
|
||||
包元数据——包括「非 client 包」这一否定结论——按名缓存且永不过期:插件集合的变更在重启后生效。fiber 重启原样复用其行与 rev;bundle 内容变更只经 `rebuilt()` 到达图。
|
||||
包元数据——包括「非 client 包」这一否定结论——按 Loader specifier 与所属 tree base URL 缓存至重启。同一来源的 fiber 重启会原样复用其 row 与 rev;bundle 内容变更只经 `rebuilt()` 到达图。
|
||||
|
||||
## bundle 路由与 index 注入
|
||||
|
||||
|
||||
@@ -777,7 +777,7 @@ export class ClientModuleRegistry extends Service {
|
||||
* module location is authoritative: the specifier resolves through the same
|
||||
* Loader resolution that imported the row's host half — including any
|
||||
* active ESM hooks — and the nearest ancestor manifest declaring the name
|
||||
* owns the module. Config-anchor `require` resolution remains only for
|
||||
* owns the module. Tree-anchored `require` resolution remains only for
|
||||
* runtimes without Node internals.
|
||||
* @param loaderName - module specifier of the loader row.
|
||||
* @param baseUrl - resolution base of the tree that owns the row.
|
||||
|
||||
@@ -286,6 +286,57 @@ describe('client bundle activation', () => {
|
||||
expect(service.graph().entries.map(entry => entry.id)).toEqual([packageName])
|
||||
})
|
||||
|
||||
it.each(['relative', 'absolute'] as const)(
|
||||
'finds the owning manifest through the %s-path fallback without Node loader internals',
|
||||
(kind) => {
|
||||
const packageName = `@fixture/${kind}-fallback-entry`
|
||||
const clientPath = writePackage(packageName)
|
||||
const packageRoot = dirname(dirname(clientPath))
|
||||
const hostPath = join(packageRoot, 'index.js')
|
||||
mkdirSync(dirname(clientPath), { recursive: true })
|
||||
writeFileSync(hostPath, 'export default {}\n')
|
||||
writeFileSync(clientPath, 'module.exports = {}\n')
|
||||
const loaderName = kind === 'relative' ? './index.js' : hostPath
|
||||
|
||||
const { service } = constructWithRoute([loaderName], {
|
||||
entryBaseUrl: pathToFileURL(packageRoot).href + '/',
|
||||
})
|
||||
|
||||
expect(service.clientPath(packageName)).toBe(clientPath)
|
||||
expect(service.graph().entries.map(entry => entry.id)).toEqual([packageName])
|
||||
},
|
||||
)
|
||||
|
||||
it.each(['v1', 'v2', 'worker'] as const)(
|
||||
'derives a file entry package id through the %s Loader resolver',
|
||||
(version) => {
|
||||
const packageName = `@fixture/file-entry-${version}`
|
||||
const clientPath = writePackage(packageName)
|
||||
const hostPath = join(dirname(clientPath), 'index.js')
|
||||
mkdirSync(dirname(hostPath), { recursive: true })
|
||||
writeFileSync(hostPath, 'export default {}\n')
|
||||
writeFileSync(clientPath, 'module.exports = {}\n')
|
||||
const loaderName = pathToFileURL(hostPath).href
|
||||
const entryBaseUrl = pathToFileURL(join(root!, 'overlay')).href + '/'
|
||||
const calls: unknown[][] = []
|
||||
const resolveSync = (...args: unknown[]) => {
|
||||
calls.push(args)
|
||||
return { format: 'module' as const, url: loaderName }
|
||||
}
|
||||
const internal = { version, resolveSync }
|
||||
|
||||
const { service } = constructWithRoute([loaderName], {
|
||||
entryBaseUrl,
|
||||
internal: internal as NonNullable<Context['loader']['internal']>,
|
||||
})
|
||||
|
||||
expect(calls).toEqual(version === 'v2'
|
||||
? [[entryBaseUrl, { specifier: loaderName, attributes: {} }]]
|
||||
: [[loaderName, entryBaseUrl, {}]])
|
||||
expect(service.graph().entries.map(entry => entry.id)).toEqual([packageName])
|
||||
},
|
||||
)
|
||||
|
||||
it('rejects distinct active Loader sources for one browser package', () => {
|
||||
const packageName = '@fixture/duplicate-source'
|
||||
const clientPath = writePackage(packageName)
|
||||
|
||||
@@ -48,6 +48,7 @@ export type Resolution =
|
||||
/** Node-loader-compatible resolution returned through the Cordis internal seam. */
|
||||
export interface WorkerInternalResolution {
|
||||
readonly format: 'builtin' | 'commonjs' | 'json'
|
||||
/** File URL for VFS modules; the original bare specifier for builtins. */
|
||||
readonly url: string
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user