@deepseek-ai/dsh-client-modules 0.1.5-rc.1 → 0.1.6-alpha.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.i18n.yaml CHANGED
@@ -2,5 +2,5 @@
2
2
  # side as of the last confirmed-consistent state. Both languages carry equal authority;
3
3
  # after editing either side, bring the other along and re-record with:
4
4
  # pnpm run verify-translation-pairing --write packages/client/modules/README.md
5
- README.md: 246293de32483adba0ee93d2d9bdbe8b8dbcd5fa
6
- README.zh.md: 759eb5b94e63c7108013ed9b223393ba405adc33
5
+ README.md: c83796fc8d6c364b9fad6d44e25316e2c2f7e7af
6
+ README.zh.md: 9ffb68ebc07b5970edc45a54c4cfde7cdf4ef7d8
package/README.md CHANGED
@@ -57,7 +57,7 @@ This section explains how the module system is built; observable behavior is cov
57
57
 
58
58
  ### Design concept
59
59
 
60
- The package is dual-face: the node half is the composition and serving side (`ctx.clientModules`, `ClientModuleRegistry`), the browser half is the loading side (`ctx.modules`, `ClientModuleSystem`). The wire between them is the boot graph — `WebBootEntry` rows injected as `window.__DSH_BOOT__`, with `<` escaped so plugin-controlled strings cannot break out of the script element. The vendored Loader's only consumption point is `EntryTree.import`, so the module system is the single replacement for "how plugin code arrives".
60
+ The package has two sides: the Node half is the composition and serving side (`ctx.clientModules`, `ClientModuleRegistry`), the browser half is the loading side (`ctx.modules`, `ClientModuleSystem`). The wire between them is the boot graph — `WebBootEntry` rows injected as `window.__DSH_BOOT__`, with `<` escaped so plugin-controlled strings cannot break out of the script element. The vendored Loader's only consumption point is `EntryTree.import`, so the module system is the single replacement for "how plugin code arrives".
61
61
 
62
62
  ### Lazy-CJS model
63
63
 
@@ -65,13 +65,15 @@ Executing a plugin bundle only registers its factory; every module-body side eff
65
65
 
66
66
  ### Incremental composition
67
67
 
68
- The node half scans incrementally per package — no full-rescan path. Every `internal/plugin` emission marks the fiber's entry name dirty; a microtask flush reconciles each dirty name against the live loader entries, and the activation pass seeds the same dirty set and flushes synchronously, so first scan and steady state share one implementation. Package metadata is cached per Loader specifier and owning-tree base URL until restart, while the resolved manifest package name identifies the browser module. Distinct active Loader sources resolving to one package name are rejected; removing the conflict promotes the remaining source without requiring its fiber to restart. Bundle content changes reach the graph only through `rebuilt()` (the HMR hook).
68
+ The Node half scans incrementally per package — no full-rescan path. Every `internal/plugin` emission marks the fiber's entry name dirty; a microtask flush reconciles each dirty name against the live loader entries, and the activation pass seeds the same dirty set and flushes synchronously, so first scan and steady state share one implementation. Package metadata is cached per Loader specifier and owning-tree base URL until restart, while the resolved manifest package name identifies the browser module. Distinct active Loader sources resolving to one package name are rejected; removing the conflict promotes the remaining source without requiring its fiber to restart. Bundle content changes reach the graph only through `rebuilt()` (the HMR hook).
69
69
 
70
- The node half snapshots each client bundle and available source map before publication. It groups resources into `/plugins/??...&rev=...` combo URLs, with one bootstrap combo for the modules row and one or more application combos for the other rows; each phase is partitioned before a URL exceeds 3 KiB. Every combo map is Indexed Source Map v3 and uses an authored section when available or an identity section for the packaged bundle. Initial per-plugin revisions use process nonces, so startup does not hash every plugin; HMR hashes only an artifact reported as changed. Advertised responses are immutable, and an unknown combination or revision returns 404.
70
+ The Node half snapshots each client bundle and available source map before publication. It groups resources into `/plugins/??...&rev=...` combo URLs, with one bootstrap combo for the modules row and one or more application combos for the other rows; each phase is partitioned before a URL exceeds 3 KiB. Every combo map is Indexed Source Map v3 and uses an authored section when available or an identity section for the packaged bundle. Initial per-plugin revisions use process nonces, so startup does not hash every plugin; HMR hashes only an artifact reported as changed. Advertised responses are immutable, and an unknown combination or revision returns 404.
71
71
 
72
72
  ### Boot manifest injection
73
73
 
74
- The host contributes structured index rows that inject, into `<head>`: the `window.__ModuleLoader__` queue facade, advisory preloads for every application combo, the parser-blocking bootstrap combo scripts, then the boot graph before the shell reads it. A Web carrier renders those rows into its index response; a shell-owned carrier can render the same rows without a Web server. The facade's `create()` materializes the modules bundle, delegates construction to its `createClientModuleSystem` export, and leaves the same facade in live-registration mode.
74
+ The bundle route follows the injected `webServer` lifetime: it registers when the service is ready and is removed and re-registered when that service is replaced. Module composition and `fetchBundle()` remain available without a Web server.
75
+
76
+ The host contributes structured index rows that inject, into `<head>`: the `window.__ModuleLoader__` queue facade, advisory preloads for every application combo, the parser-blocking bootstrap combo scripts, then the boot graph before the shell reads it. A Web carrier renders those rows into its index response; a shell-owned carrier can render the same rows without a Web server. The facade's `create()` materializes the modules bundle, delegates construction to its `createClientModuleSystem` export, and leaves the same facade in live-registration mode. The shell installs that returned system as its Loader's `internal`; the modules plugin publishes that instance as `ctx.modules`, so separate Cordis trees never select an instance through module-global state.
75
77
 
76
78
  ### Source map
77
79
 
@@ -80,7 +82,7 @@ The host contributes structured index rows that inject, into `<head>`: the `wind
80
82
  | [`src/index.ts`](src/index.ts) | Node half: `ClientModuleRegistry`, scan, artifact snapshots, optional combo route, structured index rows |
81
83
  | [`src/client/index.ts`](src/client/index.ts) | Browser half: bootstrap export, `ctx.modules` enrollment |
82
84
  | [`src/client/system.ts`](src/client/system.ts) | `ClientModuleSystem`: load/materialize/invalidate machinery |
83
- | [`src/client/manifest.ts`](src/client/manifest.ts) | Wire types and boot-manifest parsing |
85
+ | [`src/client/manifest.ts`](src/client/manifest.ts) | Wire types, boot-manifest parsing, and the `dsh.client` declaration parser |
84
86
 
85
87
  </details>
86
88
 
package/README.zh.md CHANGED
@@ -25,7 +25,7 @@ kind: "package-reference"
25
25
  <a id="use-this-package"></a>
26
26
  ## 使用本包
27
27
 
28
- 声明类型使用 [`DshClientManifest`](../../util/package-manifest/README.zh.md)。Client-modules 负责 JSON 校验和归一化的启动图。
28
+ 声明类型使用 [`DshClientManifest`](../../util/package-manifest/README.zh.md)。Client-modules 校验 JSON,并持有归一化后的启动图。
29
29
 
30
30
  组合或构建浏览器客户端插件时使用它:本包把包的 `dsh.client` 声明变成可加载的浏览器 bundle,无需任何逐插件接线。它随 web 组合激活;外壳在任何插件运行前启动它。
31
31
 
@@ -35,15 +35,15 @@ kind: "package-reference"
35
35
 
36
36
  ### 浏览器加载什么
37
37
 
38
- application combo 脚本在启动时注册插件 factory;模块主体仍保持惰性,只在首次 import 或物化时运行。共享 combo URL 的 row 共用一个进行中的脚本任务。HMR 会让一条发生变化的 row 改用带 revision 的单资源 combo URL。`<id>/client` 与裸 id 解析到同一组导出,因为插件 bundle 就是其包的客户端半侧。
38
+ application combo 脚本在启动时仅注册一次插件 factory;模块主体仍保持惰性,只在首次 import 或物化时运行。共享 combo URL 的 row 共用一个进行中的脚本任务。HMR(热模块替换)会让一条发生变化的 row 改用带 revision 的单资源 combo URL。`<id>/client` 与裸 id 解析到同一组导出,因为插件 bundle 就是其包的客户端半侧。
39
39
 
40
40
  ### 共享模块
41
41
 
42
- 外壳播种一张冻结模块表(`PLATFORM_MODULES`:React、Cordis 与静态 UI 库);每个动态 bundle 都精确针对该基座解析其 external。`dsh.client.external` 只添加基座之外的精确请求,每个请求由其命名的动态包 row 或精确静态表键回答。纯类型 import 会被擦除,不产生请求。组合阶段会拒绝畸形请求、缺失提供方、自请求与同步请求环。
42
+ 外壳初始化一张冻结的模块表(`PLATFORM_MODULES`:React、Cordis 与静态 UI 库);每个动态 bundle 都精确针对该基座解析其 external。`dsh.client.external` 只添加基座之外的精确请求;系统会将每个请求解析到其指定的动态包 row 或完全匹配的静态表键。纯类型 import 会被擦除,不产生请求。组合阶段会拒绝畸形请求、缺失提供方、自请求与同步请求环。
43
43
 
44
44
  ### 构建要求
45
45
 
46
- 宿主提供的是已构建的客户端 bundle,因此启动前 `pnpm run build` 必须已产出每个 `lib/client.js`;缺失 bundle 会以一条构建说明加包/路径列表的方式让激活大声失败。源码启动会把宿主侧导入映射到 TypeScript 源码,但仍消费这一构建后的客户端导出。本包自身不接受任何插件配置。
46
+ 宿主提供的是已构建的客户端 bundle,因此启动前 `pnpm run build` 必须已产出每个 `lib/client.js`;缺失 bundle 会明确导致激活失败,并给出一条构建说明及包/路径列表。源码启动会把宿主侧导入映射到 TypeScript 源码,但仍消费这一构建后的客户端导出。本包自身不接受任何插件配置。
47
47
 
48
48
  -----
49
49
 
@@ -57,7 +57,7 @@ application combo 脚本在启动时注册插件 factory;模块主体仍保持
57
57
 
58
58
  ### 设计理念
59
59
 
60
- 本包是双面孔:node 半侧是组合与服务端(`ctx.clientModules`,`ClientModuleRegistry`),浏览器半侧是加载端(`ctx.modules`,`ClientModuleSystem`)。两者之间的协议是启动图——以 `window.__DSH_BOOT__` 注入的 `WebBootEntry` 行,`<` 已转义,插件控制的字符串无法逃出 script 元素。vendored Loader 唯一的消费点是 `EntryTree.import`,因此模块系统就是「插件代码如何到达」的唯一可替换实现。
60
+ 本包分为两侧:Node 半侧负责组合与提供(`ctx.clientModules`,`ClientModuleRegistry`),浏览器半侧负责加载(`ctx.modules`,`ClientModuleSystem`)。两者之间的协议是启动图——以 `window.__DSH_BOOT__` 注入的 `WebBootEntry` 行,`<` 已转义,插件控制的字符串无法逃出 script 元素。vendored Loader 唯一的消费点是 `EntryTree.import`,因此模块系统就是「插件代码如何到达」的唯一可替换实现。
61
61
 
62
62
  ### 惰性 CJS 模型
63
63
 
@@ -65,22 +65,24 @@ application combo 脚本在启动时注册插件 factory;模块主体仍保持
65
65
 
66
66
  ### 增量组合
67
67
 
68
- node 半侧逐包增量扫描——没有全量重扫路径。每次 `internal/plugin` 发出都会把该 fiber 的 entry 名标脏;一个微任务 flush 会把每个脏名与当前 loader 条目对账,激活 pass 播种同一脏集合并同步 flush,因此首次扫描与稳态共用同一实现。包元数据按 Loader specifier 与所属 tree base URL 缓存至重启,解析出的 manifest 包名作为浏览器模块身份。若不同的 active Loader source 解析到同一包名,组合会失败;移除冲突来源后,剩余来源无需重启 fiber 即可接替。bundle 内容变更只能通过 `rebuilt()`(HMR 钩子)进入图。
68
+ Node 半侧逐包增量扫描——没有全量重扫路径。每次发出 `internal/plugin` 事件时,系统都会把该 fiber 的 entry 名标脏;微任务 flush 会把每个脏名与当前 loader 条目对账,激活 pass 会初始化同一个脏集合并同步 flush,因此首次扫描与稳态共用同一实现。包元数据按 Loader specifier 与所属 tree base URL 缓存至重启,解析出的 manifest(元数据清单)包名作为浏览器模块身份。若不同的 active Loader source 解析到同一包名,组合会失败;移除冲突来源后,剩余来源无需重启 fiber 即可接替。bundle 内容变更只能通过 `rebuilt()`(HMR 钩子)进入图。
69
69
 
70
- node 半侧会在发布前快照每个客户端 bundle 及其现有 source map。它把资源分组到 `/plugins/??...&rev=...` combo URL:modules row 使用一个 bootstrap combo,其余 row 使用一个或多个 application combo;每个阶段都会在 URL 超过 3 KiB 之前分区。每个 combo map 都是 Indexed Source Map v3,并在可用时使用作者提供的 section,否则为已打包 bundle 生成 identity section。初始逐插件 revision 使用进程 nonce,所以启动时不哈希每个插件;HMR 只哈希被报告为已变化的产物。已公告响应不可变;未知组合或 revision 返回 404。
70
+ Node 半侧会在发布前快照每个客户端 bundle 及其现有 source map。它把资源分组到 `/plugins/??...&rev=...` combo URL:modules row 使用一个 bootstrap combo,其余 row 使用一个或多个 application combo;每个阶段都会在 URL 超过 3 KiB 之前分区。每个 combo map 都是 Indexed Source Map v3,并在可用时使用作者提供的 section,否则为已打包 bundle 生成 identity section。初始逐插件 revision 使用进程 nonce,所以启动时不哈希每个插件;HMR 只哈希被报告为已变化的产物。已公告响应不可变;未知组合或 revision 返回 404。
71
71
 
72
- ### 启动清单注入
72
+ ### 启动 manifest 注入
73
73
 
74
- 宿主贡献结构化 index 行,并向 `<head>` 注入:`window.__ModuleLoader__` queue facade、每个 application combo 的提示性 preload、阻塞 parser 的 bootstrap combo 脚本,然后才是外壳读取前的启动图。Web 载体把这些行渲染进 index 响应;由 shell 持有的载体则可以在没有 Web server 时渲染同一批行。facade 的 `create()` 物化 modules bundle、把构造委托给其 `createClientModuleSystem` 导出,并让同一 facade 进入 live registration 模式。
74
+ bundle 路由随注入的 `webServer` 生命周期注册:服务就绪时注册,服务被替换时移除并重新注册。模块组合与 `fetchBundle()` 在没有 Web server 时仍可用。
75
75
 
76
- ### 源码地图
76
+ 宿主贡献结构化 index 行,并向 `<head>` 注入:`window.__ModuleLoader__` queue facade、每个 application combo 的提示性 preload、阻塞 parser 的 bootstrap combo 脚本,然后才是外壳读取前的启动图。Web 载体把这些行渲染进 index 响应;由 shell 持有的载体则可以在没有 Web server 时渲染同一批行。facade 的 `create()` 物化 modules bundle、把构造委托给其 `createClientModuleSystem` 导出,并让同一 facade 进入 live registration 模式。外壳把返回的系统装成自身 Loader 的 `internal`;modules 插件将该实例发布为 `ctx.modules`,因此不同 Cordis 树不会通过模块级全局状态选择实例。
77
+
78
+ ### 源码索引
77
79
 
78
80
  | 文件 | 职责 |
79
81
  |---|---|
80
- | [`src/index.ts`](src/index.ts) | node 半侧:`ClientModuleRegistry`、扫描、产物快照、可选 combo 路由、结构化 index 行 |
82
+ | [`src/index.ts`](src/index.ts) | Node 半侧:`ClientModuleRegistry`、扫描、产物快照、可选 combo 路由、结构化 index 行 |
81
83
  | [`src/client/index.ts`](src/client/index.ts) | 浏览器半侧:bootstrap 导出、`ctx.modules` 登记 |
82
84
  | [`src/client/system.ts`](src/client/system.ts) | `ClientModuleSystem`:加载/物化/失效机制 |
83
- | [`src/client/manifest.ts`](src/client/manifest.ts) | 协议类型与启动清单解析 |
85
+ | [`src/client/manifest.ts`](src/client/manifest.ts) | 协议类型、启动清单解析与 `dsh.client` 声明解析器 |
84
86
 
85
87
  </details>
86
88
 
package/lib/client.js CHANGED
@@ -50,6 +50,43 @@ window.__ModuleLoader__.load({
50
50
  return value;
51
51
  }
52
52
  /**
53
+ * Narrow an unknown parsed JSON value to the `dsh.client` declaration. Shared
54
+ * by the node half's Loader scan and the roster generator, so both read a
55
+ * package's browser declaration through one validator.
56
+ * @param pkgName - package name used as the diagnostic prefix.
57
+ * @param value - the raw `dsh.client` field of the package manifest.
58
+ * @returns the validated declaration, or undefined when the field is absent.
59
+ * @throws {Error} when the field is present but any member is malformed.
60
+ */
61
+ function parseDshClient(pkgName, value) {
62
+ if (value === void 0) return void 0;
63
+ if (typeof value !== "object" || value === null) throw new Error(`client-modules: ${pkgName} has a non-object dsh.client declaration`);
64
+ const decl = value;
65
+ if (typeof decl.platform !== "string") throw new Error(`client-modules: ${pkgName} dsh.client.platform must be a string`);
66
+ const inject = optionalStringArray(pkgName, "dsh.client.inject", decl.inject);
67
+ const external = optionalStringArray(pkgName, "dsh.client.external", decl.external);
68
+ if (decl.immediately !== void 0 && typeof decl.immediately !== "boolean") throw new Error(`client-modules: ${pkgName} dsh.client.immediately must be a boolean`);
69
+ return {
70
+ platform: decl.platform,
71
+ ...inject !== void 0 ? { inject } : {},
72
+ ...external !== void 0 ? { external } : {},
73
+ ...decl.immediately !== void 0 ? { immediately: decl.immediately } : {}
74
+ };
75
+ }
76
+ /**
77
+ * The bare package-root specifier `specifier` names, or undefined for a subpath, a path, or any scheme-qualified
78
+ * specifier (`cordis:` builtins, `node:` modules, URLs).
79
+ * @param specifier - Loader row name.
80
+ * @returns the package name, or undefined.
81
+ */
82
+ function exactPackageSpecifier(specifier) {
83
+ if (specifier.startsWith("@")) {
84
+ const parts = specifier.split("/");
85
+ return parts.length === 2 && parts.every(Boolean) ? specifier : void 0;
86
+ }
87
+ return specifier.length > 0 && !specifier.includes("/") && !specifier.includes(":") ? specifier : void 0;
88
+ }
89
+ /**
53
90
  * Normalize a module specifier onto the graph row that owns it: a plugin bundle
54
91
  * IS its package's client half, so `<id>/client` (the exports subpath external
55
92
  * bundles emit) and the bare package name resolve to the same exports. Both the
@@ -337,37 +374,41 @@ window.__ModuleLoader__.load({
337
374
  };
338
375
  //#endregion
339
376
  //#region lib/types/client/index.js
340
- let moduleSystem;
341
377
  /**
342
378
  * Build the live module system from the HTML facade's materialized modules bundle.
343
379
  * @param target - Stable registration facade whose pending queue becomes the live sink.
344
380
  * @param bootstrapModule - This bundle's id and already-materialized exports.
345
381
  * @param options - Raw boot graph, platform seed, and optional bundle transport.
346
- * @returns The created module system, also published for this package's Cordis plugin face.
382
+ * @returns The created module system.
347
383
  */
348
384
  function createClientModuleSystem(target, bootstrapModule, options) {
349
- moduleSystem = new ClientModuleSystem({
385
+ return new ClientModuleSystem({
350
386
  manifest: parseBootManifest(options.boot),
351
387
  staticModules: options.staticModules,
352
388
  registrationTarget: target,
353
389
  bootstrapModule,
354
390
  ...options.loadBundle === void 0 ? {} : { loadBundle: options.loadBundle }
355
391
  });
356
- return moduleSystem;
357
392
  }
393
+ /** Required service: the Loader whose internal module system this plugin publishes. */
394
+ const inject = ["loader"];
358
395
  /**
359
396
  * Enroll the kernel-built module system as `ctx.modules`.
360
397
  * @param ctx - client root context.
361
398
  */
362
399
  function apply(ctx) {
363
- if (moduleSystem === void 0) throw new Error("client-modules: createClientModuleSystem must run before plugin boot");
364
- ctx.reflect.provide("modules", moduleSystem);
400
+ const modules = ctx.loader.internal;
401
+ if (modules?.version !== "client") throw new Error("client-modules: the Loader has no client module system");
402
+ ctx.reflect.provide("modules", modules);
365
403
  }
366
404
  //#endregion
367
405
  exports.ClientModuleSystem = ClientModuleSystem;
368
406
  exports.apply = apply;
369
407
  exports.createClientModuleSystem = createClientModuleSystem;
408
+ exports.exactPackageSpecifier = exactPackageSpecifier;
409
+ exports.inject = inject;
370
410
  exports.parseBootManifest = parseBootManifest;
411
+ exports.parseDshClient = parseDshClient;
371
412
  exports.stripClientSuffix = stripClientSuffix;
372
413
  return module.exports;
373
414
  }
package/lib/index.js CHANGED
@@ -50,6 +50,43 @@ function optionalStringArray(subject, field, value) {
50
50
  return value;
51
51
  }
52
52
  /**
53
+ * Narrow an unknown parsed JSON value to the `dsh.client` declaration. Shared
54
+ * by the node half's Loader scan and the roster generator, so both read a
55
+ * package's browser declaration through one validator.
56
+ * @param pkgName - package name used as the diagnostic prefix.
57
+ * @param value - the raw `dsh.client` field of the package manifest.
58
+ * @returns the validated declaration, or undefined when the field is absent.
59
+ * @throws {Error} when the field is present but any member is malformed.
60
+ */
61
+ function parseDshClient(pkgName, value) {
62
+ if (value === void 0) return void 0;
63
+ if (typeof value !== "object" || value === null) throw new Error(`client-modules: ${pkgName} has a non-object dsh.client declaration`);
64
+ const decl = value;
65
+ if (typeof decl.platform !== "string") throw new Error(`client-modules: ${pkgName} dsh.client.platform must be a string`);
66
+ const inject = optionalStringArray(pkgName, "dsh.client.inject", decl.inject);
67
+ const external = optionalStringArray(pkgName, "dsh.client.external", decl.external);
68
+ if (decl.immediately !== void 0 && typeof decl.immediately !== "boolean") throw new Error(`client-modules: ${pkgName} dsh.client.immediately must be a boolean`);
69
+ return {
70
+ platform: decl.platform,
71
+ ...inject !== void 0 ? { inject } : {},
72
+ ...external !== void 0 ? { external } : {},
73
+ ...decl.immediately !== void 0 ? { immediately: decl.immediately } : {}
74
+ };
75
+ }
76
+ /**
77
+ * The bare package-root specifier `specifier` names, or undefined for a subpath, a path, or any scheme-qualified
78
+ * specifier (`cordis:` builtins, `node:` modules, URLs).
79
+ * @param specifier - Loader row name.
80
+ * @returns the package name, or undefined.
81
+ */
82
+ function exactPackageSpecifier(specifier) {
83
+ if (specifier.startsWith("@")) {
84
+ const parts = specifier.split("/");
85
+ return parts.length === 2 && parts.every(Boolean) ? specifier : void 0;
86
+ }
87
+ return specifier.length > 0 && !specifier.includes("/") && !specifier.includes(":") ? specifier : void 0;
88
+ }
89
+ /**
53
90
  * Normalize a module specifier onto the graph row that owns it: a plugin bundle
54
91
  * IS its package's client half, so `<id>/client` (the exports subpath external
55
92
  * bundles emit) and the bare package name resolve to the same exports. Both the
@@ -71,7 +108,7 @@ function stripClientSuffix(spec) {
71
108
  * combo scripts plus their source maps,
72
109
  * contributes the registration facade, application preloads, bootstrap scripts,
73
110
  * and graph to the webserver's index injection table, and provides the
74
- * `clientModuleHost` service (the HMR node half's registration/notification
111
+ * `clientModules` service (the HMR node half's registration/notification
75
112
  * face).
76
113
  *
77
114
  * Scanning is incremental per package — there is no full-rescan code path.
@@ -128,30 +165,6 @@ const COMBO_REVISION_PLACEHOLDER = "0".repeat(HASH_REVISION_LENGTH);
128
165
  const SOURCE_MAP_TRAILER = /(?:\r?\n)?\/\/# sourceMappingURL=[^\r\n]*(?:\r?\n)?$/;
129
166
  /** Debugger source name appended to page bundles in the WebWorker image. */
130
167
  const SOURCE_URL_TRAILER = /(?:\r?\n)?\/\/# sourceURL=([^\r\n]+)(?:\r?\n)?$/;
131
- /** Return a bare package-root specifier, excluding package subpaths and path-like entries. */
132
- function exactPackageSpecifier(specifier) {
133
- if (specifier.startsWith("@")) {
134
- const parts = specifier.split("/");
135
- return parts.length === 2 && parts.every(Boolean) ? specifier : void 0;
136
- }
137
- return specifier.length > 0 && !specifier.includes("/") ? specifier : void 0;
138
- }
139
- /** Narrow an unknown parsed JSON value to the `dsh.client` declaration, throwing on malformed fields. */
140
- function parseDshClient(pkgName, value) {
141
- if (value === void 0) return void 0;
142
- if (typeof value !== "object" || value === null) throw new Error(`client-modules: ${pkgName} has a non-object dsh.client declaration`);
143
- const decl = value;
144
- if (typeof decl.platform !== "string") throw new Error(`client-modules: ${pkgName} dsh.client.platform must be a string`);
145
- const inject = optionalStringArray(pkgName, "dsh.client.inject", decl.inject);
146
- const external = optionalStringArray(pkgName, "dsh.client.external", decl.external);
147
- if (decl.immediately !== void 0 && typeof decl.immediately !== "boolean") throw new Error(`client-modules: ${pkgName} dsh.client.immediately must be a boolean`);
148
- return {
149
- platform: decl.platform,
150
- ...inject !== void 0 ? { inject } : {},
151
- ...external !== void 0 ? { external } : {},
152
- ...decl.immediately !== void 0 ? { immediately: decl.immediately } : {}
153
- };
154
- }
155
168
  /** Resolve `exports["./client"]` to a relative path, accepting the string and one-level conditional forms. */
156
169
  function clientExportOf(pkgName, exportsField) {
157
170
  if (typeof exportsField !== "object" || exportsField === null) return void 0;
@@ -455,6 +468,7 @@ var ClientModuleRegistry = class extends Service {
455
468
  composed;
456
469
  /**
457
470
  * Build the service: subscribe, seed, and run the activation flush.
471
+ * Bundle routes follow the optional Web carrier's injected lifecycle.
458
472
  * @param ctx - plugin context carrying Loader and an optional Web carrier.
459
473
  */
460
474
  constructor(ctx) {
@@ -484,8 +498,7 @@ var ClientModuleRegistry = class extends Service {
484
498
  handler: this.serveBundle
485
499
  }), "client-modules: bundle route");
486
500
  };
487
- if (ctx.get("webServer") === void 0) ctx.inject(["webServer"], registerWebCarrier);
488
- else registerWebCarrier(ctx);
501
+ ctx.inject(["webServer"], registerWebCarrier);
489
502
  ctx.on("webserver/index-inject", (table) => {
490
503
  table.push(...bootInjections(this.composed));
491
504
  });
@@ -6,24 +6,26 @@
6
6
  * parser-preloads this ordinary client bundle into the pending registration
7
7
  * queue. The HTML-installed loader facade materializes this bundle and calls
8
8
  * its bootstrap export, which constructs the system and retains the same
9
- * exports for this package's graph row. The plugin face only enrolls that
10
- * pre-existing instance by providing it as `ctx.modules`.
9
+ * exports for this package's graph row. The plugin face enrolls the module
10
+ * system attached to its own Loader as `ctx.modules`.
11
11
  * @module @deepseek-ai/dsh-client-modules/client
12
12
  */
13
13
  import type { Context } from '@deepseek-ai/cordis';
14
14
  import { ClientModuleSystem } from './system.ts';
15
15
  import type { ClientBootstrapModule, ClientModuleCreateOptions, ClientModuleLoaderTarget } from './manifest.ts';
16
16
  export { ClientModuleSystem };
17
- export { parseBootManifest, stripClientSuffix } from './manifest.ts';
17
+ export { exactPackageSpecifier, parseBootManifest, parseDshClient, stripClientSuffix } from './manifest.ts';
18
18
  export type { BootManifest, BootModuleRow, BootPluginRow, ClientBootstrapModule, ClientBundleRegistration, ClientModuleCreateOptions, ClientModuleLoader, ClientModuleLoaderTarget, ClientModuleRecord, ClientModuleSystemOptions, DshWindow, WebBootEntry, WebBootGraph, } from './manifest.ts';
19
19
  /**
20
20
  * Build the live module system from the HTML facade's materialized modules bundle.
21
21
  * @param target - Stable registration facade whose pending queue becomes the live sink.
22
22
  * @param bootstrapModule - This bundle's id and already-materialized exports.
23
23
  * @param options - Raw boot graph, platform seed, and optional bundle transport.
24
- * @returns The created module system, also published for this package's Cordis plugin face.
24
+ * @returns The created module system.
25
25
  */
26
26
  export declare function createClientModuleSystem(target: ClientModuleLoaderTarget, bootstrapModule: ClientBootstrapModule, options: ClientModuleCreateOptions): ClientModuleSystem;
27
+ /** Required service: the Loader whose internal module system this plugin publishes. */
28
+ export declare const inject: string[];
27
29
  /**
28
30
  * Enroll the kernel-built module system as `ctx.modules`.
29
31
  * @param ctx - client root context.
@@ -28,6 +28,7 @@
28
28
  * {@link ClientModuleSystem}. The package root is the host-side service that
29
29
  * composes the wire.
30
30
  */
31
+ import type { DshClientManifest } from '@deepseek-ai/dsh-package-manifest';
31
32
  import type { ClientModuleSystem } from './system.ts';
32
33
  declare module '@deepseek-ai/cordis' {
33
34
  interface Context {
@@ -126,6 +127,23 @@ export interface BootManifest {
126
127
  * @throws {Error} when the value is present but is not an array of strings.
127
128
  */
128
129
  export declare function optionalStringArray(subject: string, field: string, value: unknown): string[] | undefined;
130
+ /**
131
+ * Narrow an unknown parsed JSON value to the `dsh.client` declaration. Shared
132
+ * by the node half's Loader scan and the roster generator, so both read a
133
+ * package's browser declaration through one validator.
134
+ * @param pkgName - package name used as the diagnostic prefix.
135
+ * @param value - the raw `dsh.client` field of the package manifest.
136
+ * @returns the validated declaration, or undefined when the field is absent.
137
+ * @throws {Error} when the field is present but any member is malformed.
138
+ */
139
+ export declare function parseDshClient(pkgName: string, value: unknown): DshClientManifest | undefined;
140
+ /**
141
+ * The bare package-root specifier `specifier` names, or undefined for a subpath, a path, or any scheme-qualified
142
+ * specifier (`cordis:` builtins, `node:` modules, URLs).
143
+ * @param specifier - Loader row name.
144
+ * @returns the package name, or undefined.
145
+ */
146
+ export declare function exactPackageSpecifier(specifier: string): string | undefined;
129
147
  /**
130
148
  * Normalize a module specifier onto the graph row that owns it: a plugin bundle
131
149
  * IS its package's client half, so `<id>/client` (the exports subpath external
@@ -6,7 +6,7 @@
6
6
  * combo scripts plus their source maps,
7
7
  * contributes the registration facade, application preloads, bootstrap scripts,
8
8
  * and graph to the webserver's index injection table, and provides the
9
- * `clientModuleHost` service (the HMR node half's registration/notification
9
+ * `clientModules` service (the HMR node half's registration/notification
10
10
  * face).
11
11
  *
12
12
  * Scanning is incremental per package — there is no full-rescan code path.
@@ -91,6 +91,7 @@ export declare class ClientModuleRegistry extends Service {
91
91
  private composed;
92
92
  /**
93
93
  * Build the service: subscribe, seed, and run the activation flush.
94
+ * Bundle routes follow the optional Web carrier's injected lifecycle.
94
95
  * @param ctx - plugin context carrying Loader and an optional Web carrier.
95
96
  */
96
97
  constructor(ctx: Context);
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@deepseek-ai/dsh-client-modules",
3
3
  "description": "Client module system, dual-face: node half composes the __DSH_BOOT__ entry graph (incremental dsh.client scan, bundle route, index tap, webPlugins service); browser half is the lazy-CJS module table the vendored cordis Loader consumes as its internal seam",
4
- "version": "0.1.5-rc.1",
4
+ "version": "0.1.6-alpha.1",
5
5
  "publishConfig": {
6
6
  "access": "public"
7
7
  },
@@ -39,10 +39,10 @@
39
39
  "license": "MIT",
40
40
  "devDependencies": {
41
41
  "@deepseek-ai/cordis-plugin-loader": "^1.0.3",
42
- "@deepseek-ai/dsh-invariants": "^0.1.5-rc.1",
42
+ "@deepseek-ai/dsh-host-webserver": "^0.1.6-alpha.1",
43
+ "@deepseek-ai/dsh-invariants": "^0.1.6-alpha.1",
43
44
  "@deepseek-ai/cordis": "^4.0.2",
44
- "@deepseek-ai/dsh-package-manifest": "^0.1.5-rc.1",
45
- "@deepseek-ai/dsh-host-webserver": "^0.1.5-rc.1"
45
+ "@deepseek-ai/dsh-package-manifest": "^0.1.6-alpha.1"
46
46
  },
47
47
  "files": [
48
48
  "lib/index.js",