@deepseek-ai/dsh-client-modules 0.1.0-rc.7 → 0.1.1-rc.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 +2 -2
- package/README.md +7 -3
- package/README.zh.md +7 -3
- package/lib/client.js +188 -123
- package/lib/index.js +177 -31
- package/lib/types/client/index.d.ts +19 -8
- package/lib/types/client/manifest.d.ts +90 -41
- package/lib/types/client/system.d.ts +15 -17
- package/lib/types/index.d.ts +28 -12
- package/package.json +7 -5
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:
|
|
6
|
-
README.zh.md:
|
|
5
|
+
README.md: eaf64599bc5fc5d9663d00c3b341764a4ccd38c8
|
|
6
|
+
README.zh.md: ea051338bb7837cb49f7a5ecd4cdad5b6b3ad71d
|
package/README.md
CHANGED
|
@@ -4,11 +4,15 @@ English | [中文](README.zh.md)
|
|
|
4
4
|
|
|
5
5
|
Client module system: the browser peer of Node's internal ESM loader, built as a lazy CJS table. The web shell mounts the vendored cordis Loader for entry governance (fiber lifecycle, inject waiting, update/refresh) and injects this package's `ClientModuleLoader` through its `internal` contract — the vendored side's only consumption point is `EntryTree.import`, so replacing `internal` replaces exactly "how plugin code arrives" and nothing else.
|
|
6
6
|
|
|
7
|
-
Lazy CJS model (web2): executing a plugin bundle only REGISTERS its factory (`window.__ModuleLoader__.load({id, factory})`); every module body side effect — CSS injection included — lives in the factory closure and runs at materialization (`factory(require)` → exports, memoized in `loadCache`), not at script execution. A factory that requires another registered-but-unmaterialized module materializes it recursively
|
|
7
|
+
Lazy CJS model (web2): executing a plugin bundle only REGISTERS its factory (`window.__ModuleLoader__.load({id, factory})`); every module body side effect — CSS injection included — lives in the factory closure and runs at materialization (`factory(require)` → exports, memoized in `loadCache`), not at script execution. A factory that requires another registered-but-unmaterialized module materializes it recursively; graph composition places declared dynamic requests before their consumers, and require cycles throw because factory-form CJS cannot deliver partial exports. `<id>/client` and the bare id resolve to the same exports (a plugin bundle IS its package's client half).
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
The Host installs `window.__ModuleLoader__` before parser preloads run. Its queue-mode `load()` retains early registrations; `create()` materializes this package's factory with an external-rejecting bootstrap require and calls its `createClientModuleSystem` export. Construction caches those same exports as the modules row, switches the same facade to live registration, and drains the remaining queue. The bundle retains the resulting system in a module closure, so its later Cordis `apply()` provides the identical instance as `ctx.modules` without another page global.
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
Resolution branch order (`import(specifier)`): platform seed word → shell instance; memoized record → exports; graph row (`window.__DSH_BOOT__`) → register its classic-script factory; registered factory → materialize; anything else throws — the runtime mirror of the build-time bundle purity gate. The synchronous `require` handed to factories walks the same order minus the asynchronous graph-row load and records observed edges into the module record. `prefetch` is the stage-one arrival hook (script load and factory registration only; concurrent calls share one in-flight task); `invalidate` drops a non-bootstrap factory and materialized record so the next prefetch/import reloads the script (the HMR hook).
|
|
12
|
+
|
|
13
|
+
The Node half scans enabled Loader entries for web `dsh.client` packages, resolves each `exports["./client"]`, hashes the built bundle into the boot graph, carries package-specific `dsh.client.external` requests, orders dynamic providers before consumers, and serves each bundle with its source map under `/plugins`. Source launch maps host imports to TypeScript source but still consumes this built client export; missing files share one build instruction followed by a package/path list, while unrelated filesystem errors remain separate failures.
|
|
14
|
+
|
|
15
|
+
`dsh.client.external` is an optional exact-specifier request list beyond the implicit baseline: shell-seeded React, Cordis, and static UI libraries plus parser-preloaded runtime. A request is answered by the dynamic package row it names or an exact static-table key; only a trailing `/client` aliases a package row, and there is no provider-alias declaration. Type-only imports are erased and create no request. Composition rejects malformed requests, missing suppliers, self-requests, and synchronous request cycles; import and prefetch recursively register dynamic suppliers before their consumers materialize. See [shared modules and the module graph](../AGENTS.md#shared-modules-and-the-module-graph).
|
|
12
16
|
|
|
13
17
|
## Model Experience
|
|
14
18
|
|
package/README.zh.md
CHANGED
|
@@ -4,11 +4,15 @@
|
|
|
4
4
|
|
|
5
5
|
客户端模块系统:Node 内部 ESM loader 的浏览器端对等实现,以惰性 CJS 表实现。web 外壳挂载 vendored cordis Loader 来治理配置项(fiber 生命周期、inject 等待、update/refresh),并通过其 `internal` 约定注入该包的 `ClientModuleLoader`;vendored 一侧唯一的消费点是 `EntryTree.import`,因此替换 `internal` 恰好只会替换「插件代码如何到达」,不会改变其他内容。
|
|
6
6
|
|
|
7
|
-
惰性 CJS 模型(web2):执行插件 bundle 只会注册其 factory(`window.__ModuleLoader__.load({id, factory})`);每个模块主体的副作用(包括 CSS 注入)都位于 factory 闭包中,在物化时运行(`factory(require)` → 导出表层,并在 `loadCache` 中记忆化),不会在脚本执行时运行。如果 factory
|
|
7
|
+
惰性 CJS 模型(web2):执行插件 bundle 只会注册其 factory(`window.__ModuleLoader__.load({id, factory})`);每个模块主体的副作用(包括 CSS 注入)都位于 factory 闭包中,在物化时运行(`factory(require)` → 导出表层,并在 `loadCache` 中记忆化),不会在脚本执行时运行。如果 factory 依赖另一个已注册但尚未物化的模块,系统会递归物化它;图组合会把声明的动态请求提供方放在消费者之前,而 require 循环会抛出异常,因为 factory 形式的 CJS 无法提供部分导出。`<id>/client` 与裸 id 指向同一表层(一个插件 bundle 就是其包的客户端侧)。
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
Host 会在 parser preload 运行前安装 `window.__ModuleLoader__`。其 queue 模式的 `load()` 保存提前到达的 registration;`create()` 使用拒绝 external 的 bootstrap require 物化本包 factory,并调用其 `createClientModuleSystem` 导出。构造过程把同一组导出缓存为 modules row,把同一个 facade 切换到 live registration,再排空余下 queue。Bundle 通过模块闭包保留生成的系统,因此随后 Cordis `apply()` 能把同一实例提供为 `ctx.modules`,无需另一个页面全局变量。
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
解析分支顺序(`import(specifier)`):平台种子词 → 外壳实例;记忆化记录 → 导出;模块图记录(`window.__DSH_BOOT__`)→ 登记其 classic-script factory;已登记 factory → 物化;其他情况一律抛出异常。这是构建时 bundle 纯度门禁的运行时镜像。交给 factory 的同步 `require` 采用相同顺序,但不含异步 graph-row 加载分支,并把观察到的边记录到模块记录中。`prefetch` 是第一阶段到达钩子(只加载脚本并登记 factory;并发调用共享一个进行中的任务);`invalidate` 会丢弃非 bootstrap factory 与物化记录,使下一次 prefetch/import 重新加载脚本;它是 HMR(热模块替换)钩子。
|
|
12
|
+
|
|
13
|
+
Node 侧会扫描已启用的 Loader 配置项以发现 web `dsh.client` 包,解析每个 `exports["./client"]`,把构建后的 bundle 哈希和包专属 `dsh.client.external` 请求写入启动图,把动态提供方排在消费者之前,并通过 `/plugins` 提供该文件及其 sourcemap。源码启动会把宿主侧导入映射到 TypeScript 源码,但仍消费这一构建后的客户端导出;缺失文件共享一条构建说明,随后以包/路径列表列出各项,而无关的文件系统错误仍是独立故障。
|
|
14
|
+
|
|
15
|
+
`dsh.client.external` 是统一基座之外的可选精确 specifier 请求列表。统一基座包括外壳播种的 React、Cordis 和静态 UI 库,以及由 HTML parser 预载的 runtime。请求由其命名的动态 package row 或精确静态表键回答;只有末尾 `/client` 会别名到 package row,并且不存在 provider 别名声明。纯类型 import 会被擦除,不产生请求。组合阶段会拒绝畸形请求、缺失提供方、自请求和同步请求环;import 与 prefetch 会在消费者物化前递归登记动态提供方。参见[共享模块与模块图](../AGENTS.md#shared-modules-and-the-module-graph)。
|
|
12
16
|
|
|
13
17
|
## 模型体验
|
|
14
18
|
|
package/lib/client.js
CHANGED
|
@@ -4,7 +4,112 @@ window.__ModuleLoader__.load({
|
|
|
4
4
|
var module = { exports: {} };
|
|
5
5
|
var exports = module.exports;
|
|
6
6
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
7
|
+
//#region lib/types/client/manifest.js
|
|
8
|
+
/**
|
|
9
|
+
* Client module system: the browser peer of Node's internal ESM loader, built
|
|
10
|
+
* as a lazy CJS table. The vendored cordis Loader consumes this object
|
|
11
|
+
* through its `internal` contract (the only call site is `EntryTree.import` →
|
|
12
|
+
* `internal.import`), which keeps entry governance (fiber lifecycle, inject
|
|
13
|
+
* waiting, update/refresh) entirely on the vendored side while this package
|
|
14
|
+
* owns code arrival.
|
|
15
|
+
*
|
|
16
|
+
* Lazy CJS model: executing a plugin bundle only REGISTERS its
|
|
17
|
+
* factory (`window.__ModuleLoader__.load({id, factory})`); every module body
|
|
18
|
+
* side effect — including CSS injection — lives inside the factory closure
|
|
19
|
+
* and runs at materialization, not at script execution. Materialization
|
|
20
|
+
* (factory(require) → exports) happens on first import/require and is
|
|
21
|
+
* memoized in {@link ClientModuleLoader.loadCache}; a factory that requires
|
|
22
|
+
* another registered-but-unmaterialized module materializes it recursively,
|
|
23
|
+
* so load order needs no external sequencing.
|
|
24
|
+
*
|
|
25
|
+
* Resolution branch order (import): seed word → shell instance; memoized
|
|
26
|
+
* record → exports; graph row → register its dependency factories and own
|
|
27
|
+
* factory; registered factory → materialize; anything else → throw (loud —
|
|
28
|
+
* the runtime mirror of the build-time bundle purity gate).
|
|
29
|
+
* The synchronous `require` handed to factories walks the same order minus
|
|
30
|
+
* the load branch. Loading is async, so a requested dynamic package must have
|
|
31
|
+
* registered its factory before a consumer materializes.
|
|
32
|
+
*
|
|
33
|
+
* This file is the browser-safe contract face (zero node imports): the
|
|
34
|
+
* `__DSH_BOOT__` wire types, the boot-manifest parser, and the boundaries around
|
|
35
|
+
* {@link ClientModuleSystem}. The package root is the host-side service that
|
|
36
|
+
* composes the wire.
|
|
37
|
+
*/
|
|
38
|
+
/**
|
|
39
|
+
* Validate an optional string-array field read from a `dsh.client` declaration
|
|
40
|
+
* or from the boot wire.
|
|
41
|
+
* @param subject - diagnostic prefix naming the package or the wire row.
|
|
42
|
+
* @param field - field name as it appears in the diagnostic.
|
|
43
|
+
* @param value - the raw field value.
|
|
44
|
+
* @returns the validated array, or undefined when the field is absent.
|
|
45
|
+
* @throws {Error} when the value is present but is not an array of strings.
|
|
46
|
+
*/
|
|
47
|
+
function optionalStringArray(subject, field, value) {
|
|
48
|
+
if (value === void 0) return void 0;
|
|
49
|
+
if (!Array.isArray(value) || value.some((item) => typeof item !== "string")) throw new Error(`client-modules: ${subject} ${field} must be a string array`);
|
|
50
|
+
return value;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Normalize a module specifier onto the graph row that owns it: a plugin bundle
|
|
54
|
+
* IS its package's client half, so `<id>/client` (the exports subpath external
|
|
55
|
+
* bundles emit) and the bare package name resolve to the same exports. Both the
|
|
56
|
+
* require path and graph composition normalize here, which is what lets each
|
|
57
|
+
* importing package request the subpath its own code imports.
|
|
58
|
+
* @param spec - module specifier as a bundle requires it or a declaration spells it.
|
|
59
|
+
* @returns the specifier with a trailing `/client` removed.
|
|
60
|
+
*/
|
|
61
|
+
function stripClientSuffix(spec) {
|
|
62
|
+
return spec.endsWith("/client") ? spec.slice(0, -7) : spec;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Parse `window.__DSH_BOOT__` into the two consumer views. Wire boundary:
|
|
66
|
+
* a missing or malformed graph throws (the shell shows the loud failure —
|
|
67
|
+
* a page without a valid manifest cannot boot anything).
|
|
68
|
+
* @param wire - the raw `window.__DSH_BOOT__` value.
|
|
69
|
+
* @returns the manifest with optional plugin-view fields normalized.
|
|
70
|
+
*/
|
|
71
|
+
function parseBootManifest(wire) {
|
|
72
|
+
if (typeof wire !== "object" || wire === null) throw new Error("client-modules: window.__DSH_BOOT__ is missing or not an object");
|
|
73
|
+
const graph = wire;
|
|
74
|
+
if (typeof graph.rev !== "string") throw new Error("client-modules: boot manifest rev must be a string");
|
|
75
|
+
if (!Array.isArray(graph.entries)) throw new Error("client-modules: boot manifest entries must be an array");
|
|
76
|
+
const modules = [];
|
|
77
|
+
const plugins = [];
|
|
78
|
+
for (const value of graph.entries) {
|
|
79
|
+
if (typeof value !== "object" || value === null) throw new Error("client-modules: boot manifest entry is not an object");
|
|
80
|
+
const row = value;
|
|
81
|
+
const where = typeof row.id === "string" ? `"${row.id}"` : JSON.stringify(row);
|
|
82
|
+
if (typeof row.id !== "string" || typeof row.url !== "string" || typeof row.rev !== "string") throw new Error(`client-modules: boot manifest entry ${where} must carry string id/url/rev`);
|
|
83
|
+
const subject = `boot manifest entry ${where}`;
|
|
84
|
+
const inject = optionalStringArray(subject, "inject", row.inject);
|
|
85
|
+
const external = optionalStringArray(subject, "external", row.external);
|
|
86
|
+
if (row.immediately !== void 0 && typeof row.immediately !== "boolean") throw new Error(`client-modules: boot manifest entry ${where} immediately must be a boolean`);
|
|
87
|
+
modules.push({
|
|
88
|
+
id: row.id,
|
|
89
|
+
url: row.url,
|
|
90
|
+
rev: row.rev,
|
|
91
|
+
external: external === void 0 ? [] : [...external]
|
|
92
|
+
});
|
|
93
|
+
plugins.push({
|
|
94
|
+
id: row.id,
|
|
95
|
+
inject: inject === void 0 ? [] : [...inject],
|
|
96
|
+
immediately: row.immediately === true
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
return {
|
|
100
|
+
rev: graph.rev,
|
|
101
|
+
modules,
|
|
102
|
+
plugins
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
//#endregion
|
|
7
106
|
//#region lib/types/client/system.js
|
|
107
|
+
/**
|
|
108
|
+
* ClientModuleSystem — the implementation behind the {@link ClientModuleLoader}
|
|
109
|
+
* contract. The conceptual contract (lazy CJS model, resolution branch order) is
|
|
110
|
+
* documented on the public interfaces in `./manifest.ts`; this file owns the
|
|
111
|
+
* state tables and the load/materialize machinery.
|
|
112
|
+
*/
|
|
8
113
|
/** Default bundle-load hook: same-origin external classic script. */
|
|
9
114
|
const defaultLoadBundle = (url) => new Promise((resolve, reject) => {
|
|
10
115
|
const el = document.createElement("script");
|
|
@@ -21,12 +126,6 @@ window.__ModuleLoader__.load({
|
|
|
21
126
|
document.head.append(el);
|
|
22
127
|
});
|
|
23
128
|
/**
|
|
24
|
-
* A plugin bundle IS its package's client half: `<id>/client` (the exports
|
|
25
|
-
* subpath external bundles emit) and the bare graph id name the same
|
|
26
|
-
* exports, so table lookups normalize the suffix away.
|
|
27
|
-
*/
|
|
28
|
-
const stripClientSuffix = (spec) => spec.endsWith("/client") ? spec.slice(0, -7) : spec;
|
|
29
|
-
/**
|
|
30
129
|
* Claim and inventory the <style> tags a factory injected during
|
|
31
130
|
* materialization: preset-emitted tags arrive pre-tagged with data-plugin;
|
|
32
131
|
* any untagged tag is claimed for the materializing plugin (HMR bookkeeping).
|
|
@@ -41,15 +140,17 @@ window.__ModuleLoader__.load({
|
|
|
41
140
|
/**
|
|
42
141
|
* The client module system: state tables plus the arrival/materialization
|
|
43
142
|
* machinery implementing {@link ClientModuleLoader} (whose members carry the
|
|
44
|
-
* contract documentation). Construction indexes the boot rows
|
|
45
|
-
*
|
|
143
|
+
* contract documentation). Construction indexes the boot rows, retains the
|
|
144
|
+
* already-materialized bootstrap module, and switches the HTML-installed
|
|
145
|
+
* loader facade from its pending queue to live registration.
|
|
46
146
|
*/
|
|
47
147
|
var ClientModuleSystem = class {
|
|
48
148
|
version = "client";
|
|
149
|
+
manifest;
|
|
49
150
|
loadCache = /* @__PURE__ */ new Map();
|
|
50
151
|
seed;
|
|
51
|
-
statics = /* @__PURE__ */ new Map();
|
|
52
152
|
factories = /* @__PURE__ */ new Map();
|
|
153
|
+
bootstrapIds = /* @__PURE__ */ new Set();
|
|
53
154
|
/** In-flight prefetch (script load) per id; concurrent callers share it. */
|
|
54
155
|
pendingArrival = /* @__PURE__ */ new Map();
|
|
55
156
|
/** Materialization re-entrancy guard: factory-form CJS cannot deliver partial exports, so a cycle is fatal. */
|
|
@@ -58,28 +159,45 @@ window.__ModuleLoader__.load({
|
|
|
58
159
|
loadBundle;
|
|
59
160
|
/**
|
|
60
161
|
* Build the module system over the parsed boot rows.
|
|
61
|
-
* @param options -
|
|
162
|
+
* @param options - Parsed graph, platform seed, bootstrap module, registration facade, and transport.
|
|
62
163
|
*/
|
|
63
164
|
constructor(options) {
|
|
165
|
+
this.manifest = options.manifest;
|
|
64
166
|
this.seed = new Map(Object.entries(options.staticModules));
|
|
65
167
|
this.loadBundle = options.loadBundle ?? defaultLoadBundle;
|
|
66
|
-
for (const row of options.modules) {
|
|
168
|
+
for (const row of options.manifest.modules) {
|
|
67
169
|
if (this.graphRows.has(row.id)) throw new Error(`client-modules: duplicate graph entry "${row.id}"`);
|
|
68
170
|
this.graphRows.set(row.id, row);
|
|
69
171
|
}
|
|
70
|
-
const
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
172
|
+
const bootstrapId = stripClientSuffix(options.bootstrapModule.id);
|
|
173
|
+
this.bootstrapIds.add(bootstrapId);
|
|
174
|
+
this.loadCache.set(bootstrapId, {
|
|
175
|
+
id: bootstrapId,
|
|
176
|
+
exports: options.bootstrapModule.exports,
|
|
177
|
+
styles: [],
|
|
178
|
+
edges: /* @__PURE__ */ new Set()
|
|
179
|
+
});
|
|
180
|
+
const target = options.registrationTarget;
|
|
181
|
+
if (target.mode !== "queue") throw new Error("client-modules: window.__ModuleLoader__.create called after module-system boot");
|
|
182
|
+
const pending = target.pendingQueue.splice(0);
|
|
183
|
+
target.mode = "live";
|
|
184
|
+
target.load = (registration) => {
|
|
185
|
+
this.register(registration);
|
|
186
|
+
};
|
|
187
|
+
for (const registration of pending) target.load(registration);
|
|
188
|
+
}
|
|
189
|
+
/** Register one bundle factory, rejecting a script that executes twice without invalidation. */
|
|
190
|
+
register(registration) {
|
|
191
|
+
const id = stripClientSuffix(registration.id);
|
|
192
|
+
if (this.bootstrapIds.has(id) || this.factories.has(id)) throw new Error(`client-modules: duplicate factory registration for "${registration.id}" (bundle executed twice without invalidate?)`);
|
|
193
|
+
this.factories.set(id, registration.factory);
|
|
76
194
|
}
|
|
77
195
|
/** Load one graph row so its factory is registered (idempotent per in-flight arrival). */
|
|
78
196
|
arrive(row) {
|
|
79
197
|
const { id, url } = row;
|
|
80
198
|
const pending = this.pendingArrival.get(id);
|
|
81
199
|
if (pending !== void 0) return pending;
|
|
82
|
-
if (this.factories.has(id)) return Promise.resolve();
|
|
200
|
+
if (this.loadCache.has(id) || this.factories.has(id)) return Promise.resolve();
|
|
83
201
|
const task = this.loadBundle(url).then(() => {
|
|
84
202
|
if (!this.factories.has(id)) throw new Error(`client-modules: bundle ${url} loaded without registering "${id}" via __ModuleLoader__.load`);
|
|
85
203
|
}).finally(() => {
|
|
@@ -88,6 +206,19 @@ window.__ModuleLoader__.load({
|
|
|
88
206
|
this.pendingArrival.set(id, task);
|
|
89
207
|
return task;
|
|
90
208
|
}
|
|
209
|
+
/** Register each unresolved dynamic request before registering its consumer. */
|
|
210
|
+
async arriveGraphRow(row, open = []) {
|
|
211
|
+
const cycleStart = open.indexOf(row.id);
|
|
212
|
+
if (cycleStart !== -1) throw new Error(`client-modules: module arrival cycle ${[...open.slice(cycleStart), row.id].join(" -> ")} (the host must reject this graph before serving it)`);
|
|
213
|
+
const next = [...open, row.id];
|
|
214
|
+
for (const request of row.external) {
|
|
215
|
+
const id = stripClientSuffix(request);
|
|
216
|
+
if (this.seed.has(request) || this.loadCache.has(id)) continue;
|
|
217
|
+
const dependency = this.graphRows.get(id);
|
|
218
|
+
if (dependency !== void 0) await this.arriveGraphRow(dependency, next);
|
|
219
|
+
}
|
|
220
|
+
await this.arrive(row);
|
|
221
|
+
}
|
|
91
222
|
/** Materialize a registered factory (synchronous; memoized in loadCache). */
|
|
92
223
|
materialize(id) {
|
|
93
224
|
const existing = this.loadCache.get(id);
|
|
@@ -112,146 +243,80 @@ window.__ModuleLoader__.load({
|
|
|
112
243
|
}
|
|
113
244
|
}
|
|
114
245
|
/**
|
|
115
|
-
* The synchronous require answered to factories: seed →
|
|
116
|
-
*
|
|
117
|
-
*
|
|
118
|
-
*
|
|
119
|
-
* cross-plugin value import is already a build error upstream).
|
|
246
|
+
* The synchronous require answered to factories: seed → memoized record →
|
|
247
|
+
* registered factory. Fetching is async and therefore unreachable
|
|
248
|
+
* from here; an external dynamic package must have arrived before its
|
|
249
|
+
* consumer materializes.
|
|
120
250
|
*/
|
|
121
251
|
makeRequire(edges) {
|
|
122
252
|
return (spec) => {
|
|
123
253
|
edges.add(spec);
|
|
124
254
|
if (this.seed.has(spec)) return this.seed.get(spec);
|
|
125
|
-
if (this.statics.has(spec)) return this.statics.get(spec);
|
|
126
255
|
const id = stripClientSuffix(spec);
|
|
127
256
|
const record = this.loadCache.get(id);
|
|
128
257
|
if (record !== void 0) return record.exports;
|
|
129
258
|
if (this.factories.has(id)) return this.materialize(id).exports;
|
|
130
|
-
throw new Error(`client-modules: require("${spec}") missed the module table — not a platform seed word, not a
|
|
259
|
+
throw new Error(`client-modules: require("${spec}") missed the module table — not a platform seed word, not a materialized module, and no registered package factory (a build-time externals drift, or a dynamic dependency that did not arrive)`);
|
|
131
260
|
};
|
|
132
261
|
}
|
|
133
262
|
async import(specifier) {
|
|
134
263
|
if (this.seed.has(specifier)) return this.seed.get(specifier);
|
|
135
|
-
const
|
|
264
|
+
const id = stripClientSuffix(specifier);
|
|
265
|
+
const existing = this.loadCache.get(id);
|
|
136
266
|
if (existing !== void 0) return existing.exports;
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
exports,
|
|
142
|
-
styles: [],
|
|
143
|
-
edges: /* @__PURE__ */ new Set()
|
|
144
|
-
});
|
|
145
|
-
return exports;
|
|
146
|
-
}
|
|
147
|
-
if (!this.factories.has(specifier)) {
|
|
148
|
-
const row = this.graphRows.get(specifier);
|
|
149
|
-
if (row === void 0) throw new Error(`client-modules: cannot resolve "${specifier}" — not a seed word, not a shell-own module, and not a row in the boot graph (the runtime mirror of the bundle purity gate)`);
|
|
150
|
-
await this.arrive(row);
|
|
151
|
-
}
|
|
152
|
-
return this.materialize(specifier).exports;
|
|
153
|
-
}
|
|
154
|
-
registerStatic(id, module) {
|
|
155
|
-
if (this.statics.has(id)) throw new Error(`client-modules: shell-own module "${id}" registered twice`);
|
|
156
|
-
this.statics.set(id, module);
|
|
267
|
+
const row = this.graphRows.get(id);
|
|
268
|
+
if (row !== void 0) await this.arriveGraphRow(row);
|
|
269
|
+
else if (!this.factories.has(id)) throw new Error(`client-modules: cannot resolve "${specifier}" — not a seed word, not a materialized module, and not a row in the boot graph (the runtime mirror of the bundle purity gate)`);
|
|
270
|
+
return this.materialize(id).exports;
|
|
157
271
|
}
|
|
158
272
|
async prefetch(id) {
|
|
159
|
-
|
|
160
|
-
|
|
273
|
+
const normalized = stripClientSuffix(id);
|
|
274
|
+
if (this.loadCache.has(normalized)) return;
|
|
275
|
+
const row = this.graphRows.get(normalized);
|
|
161
276
|
if (row === void 0) throw new Error(`client-modules: prefetch("${id}") — not a graph entry`);
|
|
162
|
-
await this.
|
|
277
|
+
await this.arriveGraphRow(row);
|
|
163
278
|
}
|
|
164
279
|
invalidate(id) {
|
|
165
|
-
|
|
166
|
-
this.
|
|
280
|
+
const normalized = stripClientSuffix(id);
|
|
281
|
+
if (this.bootstrapIds.has(normalized)) return;
|
|
282
|
+
this.factories.delete(normalized);
|
|
283
|
+
this.loadCache.delete(normalized);
|
|
167
284
|
}
|
|
168
285
|
};
|
|
169
286
|
//#endregion
|
|
170
|
-
//#region lib/types/client/
|
|
171
|
-
|
|
172
|
-
* Client module system: the browser peer of Node's internal ESM loader, built
|
|
173
|
-
* as a lazy CJS table. The vendored cordis Loader consumes this object
|
|
174
|
-
* through its `internal` contract (the only call site is `EntryTree.import` →
|
|
175
|
-
* `internal.import`), which keeps entry governance (fiber lifecycle, inject
|
|
176
|
-
* waiting, update/refresh) entirely on the vendored side while this package
|
|
177
|
-
* owns code arrival.
|
|
178
|
-
*
|
|
179
|
-
* Lazy CJS model: executing a plugin bundle only REGISTERS its
|
|
180
|
-
* factory (`window.__ModuleLoader__.load({id, factory})`); every module body
|
|
181
|
-
* side effect — including CSS injection — lives inside the factory closure
|
|
182
|
-
* and runs at materialization, not at script execution. Materialization
|
|
183
|
-
* (factory(require) → exports) happens on first import/require and is
|
|
184
|
-
* memoized in {@link ClientModuleLoader.loadCache}; a factory that requires
|
|
185
|
-
* another registered-but-unmaterialized module materializes it recursively,
|
|
186
|
-
* so load order needs no external sequencing.
|
|
187
|
-
*
|
|
188
|
-
* Resolution branch order (import): seed word → shell instance; memoized
|
|
189
|
-
* record → exports; static registry (shell-own modules, e.g. app-shell) →
|
|
190
|
-
* module; registered factory → materialize; graph row → load + materialize;
|
|
191
|
-
* anything else → throw (loud — the runtime mirror of the
|
|
192
|
-
* build-time bundle purity gate). The synchronous `require` handed to
|
|
193
|
-
* factories walks the same order minus the load branch: loading is async,
|
|
194
|
-
* so only already-registered bundles can be required — and cross-plugin value
|
|
195
|
-
* imports are a build error anyway.
|
|
196
|
-
*
|
|
197
|
-
* This file is the browser-safe contract face (zero node imports): the
|
|
198
|
-
* `__DSH_BOOT__` wire types, the boot-manifest parser, and the boundaries around
|
|
199
|
-
* {@link ClientModuleSystem}. The package root is the host-side service that
|
|
200
|
-
* composes the wire.
|
|
201
|
-
*/
|
|
287
|
+
//#region lib/types/client/index.js
|
|
288
|
+
let moduleSystem;
|
|
202
289
|
/**
|
|
203
|
-
*
|
|
204
|
-
*
|
|
205
|
-
*
|
|
206
|
-
* @param
|
|
207
|
-
* @returns
|
|
290
|
+
* Build the live module system from the HTML facade's materialized modules bundle.
|
|
291
|
+
* @param target - Stable registration facade whose pending queue becomes the live sink.
|
|
292
|
+
* @param bootstrapModule - This bundle's id and already-materialized exports.
|
|
293
|
+
* @param options - Raw boot graph, platform seed, and optional bundle transport.
|
|
294
|
+
* @returns The created module system, also published for this package's Cordis plugin face.
|
|
208
295
|
*/
|
|
209
|
-
function
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
const row = value;
|
|
219
|
-
const where = typeof row.id === "string" ? `"${row.id}"` : JSON.stringify(row);
|
|
220
|
-
if (typeof row.id !== "string" || typeof row.url !== "string" || typeof row.rev !== "string") throw new Error(`client-modules: boot manifest entry ${where} must carry string id/url/rev`);
|
|
221
|
-
if (row.inject !== void 0 && (!Array.isArray(row.inject) || row.inject.some((i) => typeof i !== "string"))) throw new Error(`client-modules: boot manifest entry ${where} inject must be a string array`);
|
|
222
|
-
if (row.immediately !== void 0 && typeof row.immediately !== "boolean") throw new Error(`client-modules: boot manifest entry ${where} immediately must be a boolean`);
|
|
223
|
-
modules.push({
|
|
224
|
-
id: row.id,
|
|
225
|
-
url: row.url,
|
|
226
|
-
rev: row.rev
|
|
227
|
-
});
|
|
228
|
-
plugins.push({
|
|
229
|
-
id: row.id,
|
|
230
|
-
inject: row.inject === void 0 ? [] : [...row.inject],
|
|
231
|
-
immediately: row.immediately === true
|
|
232
|
-
});
|
|
233
|
-
}
|
|
234
|
-
return {
|
|
235
|
-
rev: graph.rev,
|
|
236
|
-
modules,
|
|
237
|
-
plugins
|
|
238
|
-
};
|
|
296
|
+
function createClientModuleSystem(target, bootstrapModule, options) {
|
|
297
|
+
moduleSystem = new ClientModuleSystem({
|
|
298
|
+
manifest: parseBootManifest(options.boot),
|
|
299
|
+
staticModules: options.staticModules,
|
|
300
|
+
registrationTarget: target,
|
|
301
|
+
bootstrapModule,
|
|
302
|
+
...options.loadBundle === void 0 ? {} : { loadBundle: options.loadBundle }
|
|
303
|
+
});
|
|
304
|
+
return moduleSystem;
|
|
239
305
|
}
|
|
240
|
-
//#endregion
|
|
241
|
-
//#region lib/types/client/index.js
|
|
242
306
|
/**
|
|
243
307
|
* Enroll the kernel-built module system as `ctx.modules`.
|
|
244
308
|
* @param ctx - client root context.
|
|
245
309
|
*/
|
|
246
310
|
function apply(ctx) {
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
ctx.reflect.provide("modules", modules);
|
|
311
|
+
if (moduleSystem === void 0) throw new Error("client-modules: createClientModuleSystem must run before plugin boot");
|
|
312
|
+
ctx.reflect.provide("modules", moduleSystem);
|
|
250
313
|
}
|
|
251
314
|
//#endregion
|
|
252
315
|
exports.ClientModuleSystem = ClientModuleSystem;
|
|
253
316
|
exports.apply = apply;
|
|
317
|
+
exports.createClientModuleSystem = createClientModuleSystem;
|
|
254
318
|
exports.parseBootManifest = parseBootManifest;
|
|
319
|
+
exports.stripClientSuffix = stripClientSuffix;
|
|
255
320
|
return module.exports;
|
|
256
321
|
}
|
|
257
322
|
});
|
package/lib/index.js
CHANGED
|
@@ -4,15 +4,74 @@ import { readFileSync } from "node:fs";
|
|
|
4
4
|
import { readFile } from "node:fs/promises";
|
|
5
5
|
import { dirname, join } from "node:path";
|
|
6
6
|
import { Service } from "@deepseek-ai/cordis";
|
|
7
|
+
//#region lib/types/client/manifest.js
|
|
8
|
+
/**
|
|
9
|
+
* Client module system: the browser peer of Node's internal ESM loader, built
|
|
10
|
+
* as a lazy CJS table. The vendored cordis Loader consumes this object
|
|
11
|
+
* through its `internal` contract (the only call site is `EntryTree.import` →
|
|
12
|
+
* `internal.import`), which keeps entry governance (fiber lifecycle, inject
|
|
13
|
+
* waiting, update/refresh) entirely on the vendored side while this package
|
|
14
|
+
* owns code arrival.
|
|
15
|
+
*
|
|
16
|
+
* Lazy CJS model: executing a plugin bundle only REGISTERS its
|
|
17
|
+
* factory (`window.__ModuleLoader__.load({id, factory})`); every module body
|
|
18
|
+
* side effect — including CSS injection — lives inside the factory closure
|
|
19
|
+
* and runs at materialization, not at script execution. Materialization
|
|
20
|
+
* (factory(require) → exports) happens on first import/require and is
|
|
21
|
+
* memoized in {@link ClientModuleLoader.loadCache}; a factory that requires
|
|
22
|
+
* another registered-but-unmaterialized module materializes it recursively,
|
|
23
|
+
* so load order needs no external sequencing.
|
|
24
|
+
*
|
|
25
|
+
* Resolution branch order (import): seed word → shell instance; memoized
|
|
26
|
+
* record → exports; graph row → register its dependency factories and own
|
|
27
|
+
* factory; registered factory → materialize; anything else → throw (loud —
|
|
28
|
+
* the runtime mirror of the build-time bundle purity gate).
|
|
29
|
+
* The synchronous `require` handed to factories walks the same order minus
|
|
30
|
+
* the load branch. Loading is async, so a requested dynamic package must have
|
|
31
|
+
* registered its factory before a consumer materializes.
|
|
32
|
+
*
|
|
33
|
+
* This file is the browser-safe contract face (zero node imports): the
|
|
34
|
+
* `__DSH_BOOT__` wire types, the boot-manifest parser, and the boundaries around
|
|
35
|
+
* {@link ClientModuleSystem}. The package root is the host-side service that
|
|
36
|
+
* composes the wire.
|
|
37
|
+
*/
|
|
38
|
+
/**
|
|
39
|
+
* Validate an optional string-array field read from a `dsh.client` declaration
|
|
40
|
+
* or from the boot wire.
|
|
41
|
+
* @param subject - diagnostic prefix naming the package or the wire row.
|
|
42
|
+
* @param field - field name as it appears in the diagnostic.
|
|
43
|
+
* @param value - the raw field value.
|
|
44
|
+
* @returns the validated array, or undefined when the field is absent.
|
|
45
|
+
* @throws {Error} when the value is present but is not an array of strings.
|
|
46
|
+
*/
|
|
47
|
+
function optionalStringArray(subject, field, value) {
|
|
48
|
+
if (value === void 0) return void 0;
|
|
49
|
+
if (!Array.isArray(value) || value.some((item) => typeof item !== "string")) throw new Error(`client-modules: ${subject} ${field} must be a string array`);
|
|
50
|
+
return value;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Normalize a module specifier onto the graph row that owns it: a plugin bundle
|
|
54
|
+
* IS its package's client half, so `<id>/client` (the exports subpath external
|
|
55
|
+
* bundles emit) and the bare package name resolve to the same exports. Both the
|
|
56
|
+
* require path and graph composition normalize here, which is what lets each
|
|
57
|
+
* importing package request the subpath its own code imports.
|
|
58
|
+
* @param spec - module specifier as a bundle requires it or a declaration spells it.
|
|
59
|
+
* @returns the specifier with a trailing `/client` removed.
|
|
60
|
+
*/
|
|
61
|
+
function stripClientSuffix(spec) {
|
|
62
|
+
return spec.endsWith("/client") ? spec.slice(0, -7) : spec;
|
|
63
|
+
}
|
|
64
|
+
//#endregion
|
|
7
65
|
//#region lib/types/index.js
|
|
8
66
|
/**
|
|
9
67
|
* Node half of the client module system (`dsh.client` dual-face package): scans
|
|
10
68
|
* the host Loader's entries for packages declaring `dsh.client`, composes the
|
|
11
69
|
* `window.__DSH_BOOT__` entry graph (wire single source: {@link WebBootEntry}
|
|
12
|
-
* in `./client/manifest.ts`)
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
70
|
+
* in `./client/manifest.ts`) in module-graph order, serves
|
|
71
|
+
* `/plugins/<id>/client.js` and its source map, contributes the boot manifest
|
|
72
|
+
* plus the parser-blocking bootstrap preloads to the webserver's index
|
|
73
|
+
* injection table, and provides the `clientModuleHost` service (the HMR node
|
|
74
|
+
* half's registration/notification face).
|
|
16
75
|
*
|
|
17
76
|
* Scanning is incremental per package — there is no full-rescan code path.
|
|
18
77
|
* Every cordis `internal/plugin` emission (fiber construction/disposal) marks
|
|
@@ -63,11 +122,13 @@ function parseDshClient(pkgName, value) {
|
|
|
63
122
|
if (typeof value !== "object" || value === null) throw new Error(`client-modules: ${pkgName} has a non-object dsh.client declaration`);
|
|
64
123
|
const decl = value;
|
|
65
124
|
if (typeof decl.platform !== "string") throw new Error(`client-modules: ${pkgName} dsh.client.platform must be a string`);
|
|
66
|
-
|
|
125
|
+
const inject = optionalStringArray(pkgName, "dsh.client.inject", decl.inject);
|
|
126
|
+
const external = optionalStringArray(pkgName, "dsh.client.external", decl.external);
|
|
67
127
|
if (decl.immediately !== void 0 && typeof decl.immediately !== "boolean") throw new Error(`client-modules: ${pkgName} dsh.client.immediately must be a boolean`);
|
|
68
128
|
return {
|
|
69
129
|
platform: decl.platform,
|
|
70
|
-
...
|
|
130
|
+
...inject !== void 0 ? { inject } : {},
|
|
131
|
+
...external !== void 0 ? { external } : {},
|
|
71
132
|
...decl.immediately !== void 0 ? { immediately: decl.immediately } : {}
|
|
72
133
|
};
|
|
73
134
|
}
|
|
@@ -88,32 +149,108 @@ function shortHash(input) {
|
|
|
88
149
|
return createHash("sha1").update(input).digest("hex").slice(0, 12);
|
|
89
150
|
}
|
|
90
151
|
/** Graph row for one bundle rev (url carries the rev as its cache-busting query). */
|
|
91
|
-
function graphRow(id, rev,
|
|
152
|
+
function graphRow(id, rev, fields) {
|
|
92
153
|
return {
|
|
93
154
|
id,
|
|
94
155
|
url: `/plugins/${id}/client.js?rev=${rev}`,
|
|
95
156
|
rev,
|
|
96
|
-
...
|
|
97
|
-
...immediately ? { immediately: true } : {}
|
|
157
|
+
...fields.inject !== void 0 ? { inject: fields.inject } : {},
|
|
158
|
+
...fields.immediately ? { immediately: true } : {},
|
|
159
|
+
...fields.external.length > 0 ? { external: fields.external } : {}
|
|
98
160
|
};
|
|
99
161
|
}
|
|
100
162
|
/**
|
|
101
|
-
*
|
|
102
|
-
*
|
|
103
|
-
*
|
|
104
|
-
*
|
|
163
|
+
* Order composed rows so every requested dynamic package precedes its
|
|
164
|
+
* consumers. An `external` specifier is either the package row it names
|
|
165
|
+
* (`<pkg>/client` aliases the bare package) or a static-table name that adds no
|
|
166
|
+
* graph edge.
|
|
167
|
+
* @param entries - composed rows in scan order.
|
|
168
|
+
* @returns the same rows reordered; scan order breaks every tie.
|
|
169
|
+
* @throws {Error} when a row requests itself or when the module graph has a
|
|
170
|
+
* cycle; the message lists the packages on it.
|
|
171
|
+
*/
|
|
172
|
+
function orderByModuleGraph(entries) {
|
|
173
|
+
const rowsById = /* @__PURE__ */ new Map();
|
|
174
|
+
for (const entry of entries) rowsById.set(entry.id, entry);
|
|
175
|
+
const ordered = [];
|
|
176
|
+
const placed = /* @__PURE__ */ new Set();
|
|
177
|
+
const open = [];
|
|
178
|
+
const visit = (entry) => {
|
|
179
|
+
if (placed.has(entry.id)) return;
|
|
180
|
+
const cycleStart = open.indexOf(entry.id);
|
|
181
|
+
if (cycleStart !== -1) throw new Error(`client-modules: module graph cycle ${[...open.slice(cycleStart), entry.id].join(" -> ")} — a requested package row must precede its consumers, and factory-form CJS cannot deliver partial exports`);
|
|
182
|
+
open.push(entry.id);
|
|
183
|
+
for (const name of entry.external ?? []) {
|
|
184
|
+
const dependency = rowsById.get(name) ?? rowsById.get(stripClientSuffix(name));
|
|
185
|
+
if (dependency === entry) throw new Error(`client-modules: "${entry.id}" requests module "${name}" that it answers itself — a row must not declare its own package in dsh.client.external`);
|
|
186
|
+
if (dependency !== void 0) visit(dependency);
|
|
187
|
+
}
|
|
188
|
+
open.pop();
|
|
189
|
+
placed.add(entry.id);
|
|
190
|
+
ordered.push(entry);
|
|
191
|
+
};
|
|
192
|
+
for (const entry of entries) visit(entry);
|
|
193
|
+
return ordered;
|
|
194
|
+
}
|
|
195
|
+
/** Bootstrap package whose ordinary client bundle supplies the module-system implementation. */
|
|
196
|
+
const CLIENT_MODULES_ID = "@deepseek-ai/dsh-client-modules";
|
|
197
|
+
/** Ordinary dynamic bundles the HTML parser executes before the Vite shell. */
|
|
198
|
+
const PARSER_PRELOAD_IDS = [CLIENT_MODULES_ID, "@deepseek-ai/dsh-client-runtime"];
|
|
199
|
+
/**
|
|
200
|
+
* The boot protocol as index injection rows. The inline registration queue
|
|
201
|
+
* precedes blocking classic scripts for modules' and runtime's ordinary
|
|
202
|
+
* `lib/client.js` artifacts. Its `create()` method materializes the modules
|
|
203
|
+
* bundle, delegates construction to that bundle, and leaves the same facade
|
|
204
|
+
* in live-registration mode. The graph global follows before the shell reads
|
|
205
|
+
* it.
|
|
105
206
|
* @param graph - the composed entry graph.
|
|
106
|
-
* @returns
|
|
207
|
+
* @returns head rows in execution order: queue script, preload scripts, graph global.
|
|
107
208
|
*/
|
|
108
|
-
function
|
|
109
|
-
const
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
209
|
+
function bootInjections(graph) {
|
|
210
|
+
const queue = `(()=>{
|
|
211
|
+
const pendingQueue=[]
|
|
212
|
+
window.__ModuleLoader__={
|
|
213
|
+
mode:"queue",
|
|
214
|
+
pendingQueue,
|
|
215
|
+
load(registration){pendingQueue.push(registration)},
|
|
216
|
+
create(options){
|
|
217
|
+
if(this.mode!=="queue")throw new Error("client-modules: window.__ModuleLoader__.create called after module-system boot")
|
|
218
|
+
const index=pendingQueue.findIndex(registration=>registration.id===${JSON.stringify(CLIENT_MODULES_ID)})
|
|
219
|
+
const registration=pendingQueue[index]
|
|
220
|
+
if(registration===undefined)throw new Error("client-modules: HTML did not preload ${CLIENT_MODULES_ID}/client.js")
|
|
221
|
+
pendingQueue.splice(index,1)
|
|
222
|
+
const exports=registration.factory(specifier=>{
|
|
223
|
+
throw new Error('client-modules: ${CLIENT_MODULES_ID}/client.js requested external "'+specifier+'" before the module system existed')
|
|
224
|
+
})
|
|
225
|
+
if(typeof exports!=="object"||exports===null||typeof exports.createClientModuleSystem!=="function"||typeof exports.apply!=="function"){
|
|
226
|
+
throw new Error("client-modules: ${CLIENT_MODULES_ID}/client.js did not export the bootstrap module face")
|
|
227
|
+
}
|
|
228
|
+
return exports.createClientModuleSystem(this,{id:registration.id,exports},options)
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
})()`;
|
|
232
|
+
const preload = PARSER_PRELOAD_IDS.map((id) => graph.entries.find((entry) => entry.id === id)).filter((entry) => entry !== void 0).map((entry) => ({
|
|
233
|
+
kind: "script-src",
|
|
234
|
+
placement: "head",
|
|
235
|
+
src: entry.url
|
|
236
|
+
}));
|
|
237
|
+
return [
|
|
238
|
+
{
|
|
239
|
+
kind: "script",
|
|
240
|
+
placement: "head",
|
|
241
|
+
text: queue
|
|
242
|
+
},
|
|
243
|
+
...preload,
|
|
244
|
+
{
|
|
245
|
+
kind: "global",
|
|
246
|
+
name: "__DSH_BOOT__",
|
|
247
|
+
value: graph
|
|
248
|
+
}
|
|
249
|
+
];
|
|
113
250
|
}
|
|
114
251
|
/**
|
|
115
252
|
* The web plugin table service: incremental `dsh.client` scan + wire composition
|
|
116
|
-
* + bundle route + index
|
|
253
|
+
* + bundle route + index injection rows. Construction runs the activation scan
|
|
117
254
|
* synchronously — a malformed declaration or missing bundle among the
|
|
118
255
|
* already-loaded entries aggregates into one loud throw (FAILED fiber; the
|
|
119
256
|
* boot activation audit reports it).
|
|
@@ -160,7 +297,9 @@ var ClientModuleRegistry = class extends Service {
|
|
|
160
297
|
path: "/plugins",
|
|
161
298
|
handler: this.serveBundle
|
|
162
299
|
}), "client-modules: bundle route");
|
|
163
|
-
ctx.
|
|
300
|
+
ctx.on("webserver/index-inject", (table) => {
|
|
301
|
+
table.push(...bootInjections(this.composed));
|
|
302
|
+
});
|
|
164
303
|
}
|
|
165
304
|
/**
|
|
166
305
|
* Current composed entry graph (stable object between changes).
|
|
@@ -175,7 +314,7 @@ var ClientModuleRegistry = class extends Service {
|
|
|
175
314
|
* @returns the path, or undefined for an unknown id.
|
|
176
315
|
*/
|
|
177
316
|
clientPath(id) {
|
|
178
|
-
return this.table.get(id)?.clientPath;
|
|
317
|
+
return this.table.get(id)?.meta.clientPath;
|
|
179
318
|
}
|
|
180
319
|
/**
|
|
181
320
|
* Re-hash one bundle (the HMR watch's registration hook — the only entry
|
|
@@ -186,9 +325,9 @@ var ClientModuleRegistry = class extends Service {
|
|
|
186
325
|
rebuilt(id) {
|
|
187
326
|
const record = this.table.get(id);
|
|
188
327
|
if (record === void 0) return void 0;
|
|
189
|
-
const rev = shortHash(readFileSync(record.clientPath));
|
|
328
|
+
const rev = shortHash(readFileSync(record.meta.clientPath));
|
|
190
329
|
if (rev === record.entry.rev) return rev;
|
|
191
|
-
record.entry = graphRow(id, rev, record.
|
|
330
|
+
record.entry = graphRow(id, rev, record.meta);
|
|
192
331
|
this.composed = this.compose();
|
|
193
332
|
for (const notify of this.rebuildListeners) try {
|
|
194
333
|
notify(id, rev);
|
|
@@ -222,7 +361,7 @@ var ClientModuleRegistry = class extends Service {
|
|
|
222
361
|
};
|
|
223
362
|
}
|
|
224
363
|
compose() {
|
|
225
|
-
const entries = [...this.table.values()].map((record) => record.entry);
|
|
364
|
+
const entries = orderByModuleGraph([...this.table.values()].map((record) => record.entry));
|
|
226
365
|
return {
|
|
227
366
|
rev: shortHash(JSON.stringify(entries)),
|
|
228
367
|
entries
|
|
@@ -257,6 +396,7 @@ var ClientModuleRegistry = class extends Service {
|
|
|
257
396
|
const meta = {
|
|
258
397
|
clientPath: join(dirname(pkgPath), clientRel),
|
|
259
398
|
...decl.inject !== void 0 ? { inject: decl.inject } : {},
|
|
399
|
+
external: decl.external ?? [],
|
|
260
400
|
immediately: decl.immediately === true
|
|
261
401
|
};
|
|
262
402
|
this.pkgMeta.set(pkgName, meta);
|
|
@@ -290,8 +430,8 @@ var ClientModuleRegistry = class extends Service {
|
|
|
290
430
|
if (meta === null) return false;
|
|
291
431
|
const rev = this.initialBundleRevision(entryName, meta.clientPath);
|
|
292
432
|
this.table.set(entryName, {
|
|
293
|
-
entry: graphRow(entryName, rev, meta
|
|
294
|
-
|
|
433
|
+
entry: graphRow(entryName, rev, meta),
|
|
434
|
+
meta
|
|
295
435
|
});
|
|
296
436
|
return true;
|
|
297
437
|
}
|
|
@@ -305,10 +445,16 @@ var ClientModuleRegistry = class extends Service {
|
|
|
305
445
|
onError(error instanceof Error ? error : new Error(String(error)));
|
|
306
446
|
}
|
|
307
447
|
}
|
|
308
|
-
if (changed)
|
|
309
|
-
|
|
310
|
-
|
|
448
|
+
if (!changed) return;
|
|
449
|
+
let composed;
|
|
450
|
+
try {
|
|
451
|
+
composed = this.compose();
|
|
452
|
+
} catch (error) {
|
|
453
|
+
onError(error);
|
|
454
|
+
return;
|
|
311
455
|
}
|
|
456
|
+
this.composed = composed;
|
|
457
|
+
this.notifyGraphChanged();
|
|
312
458
|
}
|
|
313
459
|
serveBundle = async (req, res) => {
|
|
314
460
|
if (req.method !== "GET" && req.method !== "HEAD") {
|
|
@@ -344,4 +490,4 @@ var ClientModuleRegistry = class extends Service {
|
|
|
344
490
|
};
|
|
345
491
|
};
|
|
346
492
|
//#endregion
|
|
347
|
-
export { ClientModuleRegistry, ClientModuleRegistry as default,
|
|
493
|
+
export { ClientModuleRegistry, ClientModuleRegistry as default, bootInjections, orderByModuleGraph, stripClientSuffix };
|
|
@@ -2,17 +2,28 @@
|
|
|
2
2
|
* Browser half (the standard `./client` export): the module-system class and
|
|
3
3
|
* wire contract, plus the enrollment plugin face. The module system itself is
|
|
4
4
|
* built by the shell kernel BEFORE cordis exists (the bootstrap exception —
|
|
5
|
-
* the mechanism that loads plugins cannot arrive through
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
5
|
+
* the mechanism that loads plugins cannot arrive through itself). The host
|
|
6
|
+
* parser-preloads this ordinary client bundle into the pending registration
|
|
7
|
+
* queue. The HTML-installed loader facade materializes this bundle and calls
|
|
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`.
|
|
10
11
|
* @module @deepseek-ai/dsh-client-modules/client
|
|
11
12
|
*/
|
|
12
13
|
import type { Context } from '@deepseek-ai/cordis';
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
export
|
|
14
|
+
import { ClientModuleSystem } from './system.ts';
|
|
15
|
+
import type { ClientBootstrapModule, ClientModuleCreateOptions, ClientModuleLoaderTarget } from './manifest.ts';
|
|
16
|
+
export { ClientModuleSystem };
|
|
17
|
+
export { parseBootManifest, stripClientSuffix } from './manifest.ts';
|
|
18
|
+
export type { BootManifest, BootModuleRow, BootPluginRow, ClientBootstrapModule, ClientBundleRegistration, ClientModuleCreateOptions, ClientModuleLoader, ClientModuleLoaderTarget, ClientModuleRecord, ClientModuleSystemOptions, DshWindow, WebBootEntry, WebBootGraph, } from './manifest.ts';
|
|
19
|
+
/**
|
|
20
|
+
* Build the live module system from the HTML facade's materialized modules bundle.
|
|
21
|
+
* @param target - Stable registration facade whose pending queue becomes the live sink.
|
|
22
|
+
* @param bootstrapModule - This bundle's id and already-materialized exports.
|
|
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.
|
|
25
|
+
*/
|
|
26
|
+
export declare function createClientModuleSystem(target: ClientModuleLoaderTarget, bootstrapModule: ClientBootstrapModule, options: ClientModuleCreateOptions): ClientModuleSystem;
|
|
16
27
|
/**
|
|
17
28
|
* Enroll the kernel-built module system as `ctx.modules`.
|
|
18
29
|
* @param ctx - client root context.
|
|
@@ -16,13 +16,12 @@
|
|
|
16
16
|
* so load order needs no external sequencing.
|
|
17
17
|
*
|
|
18
18
|
* Resolution branch order (import): seed word → shell instance; memoized
|
|
19
|
-
* record → exports;
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
* imports are a build error anyway.
|
|
19
|
+
* record → exports; graph row → register its dependency factories and own
|
|
20
|
+
* factory; registered factory → materialize; anything else → throw (loud —
|
|
21
|
+
* the runtime mirror of the build-time bundle purity gate).
|
|
22
|
+
* The synchronous `require` handed to factories walks the same order minus
|
|
23
|
+
* the load branch. Loading is async, so a requested dynamic package must have
|
|
24
|
+
* registered its factory before a consumer materializes.
|
|
26
25
|
*
|
|
27
26
|
* This file is the browser-safe contract face (zero node imports): the
|
|
28
27
|
* `__DSH_BOOT__` wire types, the boot-manifest parser, and the boundaries around
|
|
@@ -41,7 +40,9 @@ declare module '@deepseek-ai/cordis' {
|
|
|
41
40
|
* single source: the host node half (package root) produces this same shape.
|
|
42
41
|
* `immediately` marks stage-one prefetch; `inject` is informational graph
|
|
43
42
|
* metadata (the authoritative edges live in each package's `dsh.client`
|
|
44
|
-
* declaration and reach fibers through entry creation).
|
|
43
|
+
* declaration and reach fibers through entry creation). `external` carries
|
|
44
|
+
* module-graph edges: unlike `inject`, they constrain code arrival because
|
|
45
|
+
* `require` is synchronous (see {@link WebBootGraph.entries}).
|
|
45
46
|
*/
|
|
46
47
|
export interface WebBootEntry {
|
|
47
48
|
/** Entry name == package name. */
|
|
@@ -54,12 +55,18 @@ export interface WebBootEntry {
|
|
|
54
55
|
inject?: string[];
|
|
55
56
|
/** Stage-one prefetch mark: load the script for factory registration during module-face boot. */
|
|
56
57
|
immediately?: boolean;
|
|
58
|
+
/** Non-baseline module specifiers this row requests; omitted when it requests none. */
|
|
59
|
+
external?: string[];
|
|
57
60
|
}
|
|
58
61
|
/** The composed client entry graph the host injects as `window.__DSH_BOOT__`. */
|
|
59
62
|
export interface WebBootGraph {
|
|
60
63
|
/** Consistency anchor over the whole graph (content + bundle hashes). */
|
|
61
64
|
rev: string;
|
|
62
|
-
/**
|
|
65
|
+
/**
|
|
66
|
+
* Composed entries in module-graph order — a dynamic package row precedes
|
|
67
|
+
* rows whose `external` requests that package. Cordis activation order is
|
|
68
|
+
* unrelated and remains owned by fiber service waiting.
|
|
69
|
+
*/
|
|
63
70
|
entries: WebBootEntry[];
|
|
64
71
|
}
|
|
65
72
|
/** The npm-package view of one boot row: what the module table needs to fetch the bundle. */
|
|
@@ -70,6 +77,8 @@ export interface BootModuleRow {
|
|
|
70
77
|
url: string;
|
|
71
78
|
/** Bundle content hash. */
|
|
72
79
|
rev: string;
|
|
80
|
+
/** Module specifiers this row requests from the module table ([] when the wire omits them). */
|
|
81
|
+
external: string[];
|
|
73
82
|
}
|
|
74
83
|
/** The cordis-plugin view of one boot row: what entry composition needs (optional wire fields normalized). */
|
|
75
84
|
export interface BootPluginRow {
|
|
@@ -89,6 +98,26 @@ export interface BootManifest {
|
|
|
89
98
|
/** Rows as entry composition consumes them. */
|
|
90
99
|
plugins: BootPluginRow[];
|
|
91
100
|
}
|
|
101
|
+
/**
|
|
102
|
+
* Validate an optional string-array field read from a `dsh.client` declaration
|
|
103
|
+
* or from the boot wire.
|
|
104
|
+
* @param subject - diagnostic prefix naming the package or the wire row.
|
|
105
|
+
* @param field - field name as it appears in the diagnostic.
|
|
106
|
+
* @param value - the raw field value.
|
|
107
|
+
* @returns the validated array, or undefined when the field is absent.
|
|
108
|
+
* @throws {Error} when the value is present but is not an array of strings.
|
|
109
|
+
*/
|
|
110
|
+
export declare function optionalStringArray(subject: string, field: string, value: unknown): string[] | undefined;
|
|
111
|
+
/**
|
|
112
|
+
* Normalize a module specifier onto the graph row that owns it: a plugin bundle
|
|
113
|
+
* IS its package's client half, so `<id>/client` (the exports subpath external
|
|
114
|
+
* bundles emit) and the bare package name resolve to the same exports. Both the
|
|
115
|
+
* require path and graph composition normalize here, which is what lets each
|
|
116
|
+
* importing package request the subpath its own code imports.
|
|
117
|
+
* @param spec - module specifier as a bundle requires it or a declaration spells it.
|
|
118
|
+
* @returns the specifier with a trailing `/client` removed.
|
|
119
|
+
*/
|
|
120
|
+
export declare function stripClientSuffix(spec: string): string;
|
|
92
121
|
/**
|
|
93
122
|
* Parse `window.__DSH_BOOT__` into the two consumer views. Wire boundary:
|
|
94
123
|
* a missing or malformed graph throws (the shell shows the loud failure —
|
|
@@ -97,8 +126,8 @@ export interface BootManifest {
|
|
|
97
126
|
* @returns the manifest with optional plugin-view fields normalized.
|
|
98
127
|
*/
|
|
99
128
|
export declare function parseBootManifest(wire: unknown): BootManifest;
|
|
100
|
-
/**
|
|
101
|
-
export interface
|
|
129
|
+
/** One client bundle's factory registration submitted through `window.__ModuleLoader__.load`. */
|
|
130
|
+
export interface ClientBundleRegistration {
|
|
102
131
|
/** Plugin id (package name) — the registration key; must match the graph row being executed. */
|
|
103
132
|
id: string;
|
|
104
133
|
/**
|
|
@@ -108,27 +137,45 @@ export interface ClientPluginHandoff {
|
|
|
108
137
|
*/
|
|
109
138
|
factory: (require: (spec: string) => unknown) => Record<string, unknown>;
|
|
110
139
|
}
|
|
111
|
-
/**
|
|
140
|
+
/** Inputs passed by the web entry when it creates the client module system. */
|
|
141
|
+
export interface ClientModuleCreateOptions {
|
|
142
|
+
/** Raw Host-injected boot graph; the modules bundle owns validation and projection. */
|
|
143
|
+
boot: unknown;
|
|
144
|
+
/** Module-table seed: platform-singleton specifier → shell instance. */
|
|
145
|
+
staticModules: Record<string, unknown>;
|
|
146
|
+
/** Bundle-load hook. Defaults to a same-origin classic `<script src>` element. */
|
|
147
|
+
loadBundle?: (url: string) => Promise<void>;
|
|
148
|
+
}
|
|
149
|
+
/** The modules bundle after its factory has been materialized by the HTML bootstrap facade. */
|
|
150
|
+
export interface ClientBootstrapModule {
|
|
151
|
+
/** Graph/module id carried by the modules bundle registration. */
|
|
152
|
+
id: string;
|
|
153
|
+
/** Materialized exports reused when Cordis later activates the modules entry. */
|
|
154
|
+
exports: Record<string, unknown>;
|
|
155
|
+
}
|
|
156
|
+
/** Stable page-global facade: queues early bundle registrations, then registers them live. */
|
|
157
|
+
export interface ClientModuleLoaderTarget {
|
|
158
|
+
/** Queue before {@link create}; live registration after it returns. */
|
|
159
|
+
mode: 'queue' | 'live';
|
|
160
|
+
/** Registrations submitted by parser-preloaded scripts before the module system exists. */
|
|
161
|
+
pendingQueue: ClientBundleRegistration[];
|
|
162
|
+
/** Queue or immediately register one bundle factory according to {@link mode}. */
|
|
163
|
+
load(registration: ClientBundleRegistration): void;
|
|
164
|
+
/** Create the module system exactly once from the parser-preloaded modules bundle. */
|
|
165
|
+
create(options: ClientModuleCreateOptions): ClientModuleSystem;
|
|
166
|
+
}
|
|
167
|
+
/** Window API of the web boot protocol: the host-injected graph and registration facade. */
|
|
112
168
|
export interface DshWindow {
|
|
113
169
|
/** Host-composed entry graph, injected before the shell bundle runs; wire-boundary raw until {@link parseBootManifest}. */
|
|
114
170
|
__DSH_BOOT__?: unknown;
|
|
115
|
-
/**
|
|
116
|
-
__ModuleLoader__?:
|
|
117
|
-
load(handoff: ClientPluginHandoff): void;
|
|
118
|
-
};
|
|
119
|
-
/**
|
|
120
|
-
* Kernel handoff slot: the shell kernel stores the instance here right
|
|
121
|
-
* after construction (before cordis exists) so the `./client` wrapper
|
|
122
|
-
* plugin can provide it as `ctx.modules`. Missing slot at wrapper apply
|
|
123
|
-
* time = kernel sequencing bug, thrown loud.
|
|
124
|
-
*/
|
|
125
|
-
__DSH_MODULES__?: ClientModuleSystem;
|
|
171
|
+
/** HTML-installed facade: a pending registration queue, then the live module-system target. */
|
|
172
|
+
__ModuleLoader__?: ClientModuleLoaderTarget;
|
|
126
173
|
}
|
|
127
174
|
/** Per-module bookkeeping in {@link ClientModuleLoader.loadCache} (module-graph boundary, flat today). */
|
|
128
175
|
export interface ClientModuleRecord {
|
|
129
176
|
/** Module id (entry name / package name). */
|
|
130
177
|
id: string;
|
|
131
|
-
/** Materialized exports (`module.exports` from a factory
|
|
178
|
+
/** Materialized exports (`module.exports` from a factory or bootstrap registration). */
|
|
132
179
|
exports: unknown;
|
|
133
180
|
/** Owned `<style data-plugin>` tag ids (`data-plugin-css` values) injected during materialization. */
|
|
134
181
|
styles: string[];
|
|
@@ -143,6 +190,8 @@ export interface ClientModuleRecord {
|
|
|
143
190
|
export interface ClientModuleLoader {
|
|
144
191
|
/** Discriminant against Node's internal loader shapes ('v1'/'v2'). */
|
|
145
192
|
version: 'client';
|
|
193
|
+
/** Parsed Host boot graph shared with the web entry after module-system creation. */
|
|
194
|
+
manifest: BootManifest;
|
|
146
195
|
/** Materialized-module registry: id → record. The governance-side read API for entry exports. */
|
|
147
196
|
loadCache: Map<string, ClientModuleRecord>;
|
|
148
197
|
/**
|
|
@@ -156,34 +205,34 @@ export interface ClientModuleLoader {
|
|
|
156
205
|
*/
|
|
157
206
|
import(specifier: string, parentURL: string, attrs: Record<string, unknown>): Promise<unknown>;
|
|
158
207
|
/**
|
|
159
|
-
*
|
|
160
|
-
*
|
|
161
|
-
*
|
|
162
|
-
*
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
* Stage-one arrival: load the entry's script to register its factory (no
|
|
167
|
-
* materialization — module side effects wait for import).
|
|
168
|
-
* No-op for static-registered ids and ids whose factory is already
|
|
169
|
-
* registered; concurrent calls share one in-flight task. To force a fresh
|
|
170
|
-
* load (HMR), {@link invalidate} first.
|
|
208
|
+
* Stage-one arrival: load the entry's declared dynamic requests, then its
|
|
209
|
+
* own script, to register their factories (no materialization — module side
|
|
210
|
+
* effects wait for import).
|
|
211
|
+
* No-op for materialized bootstrap ids. A registered graph row still
|
|
212
|
+
* registers any unresolved declared requests before skipping its own script;
|
|
213
|
+
* concurrent arrivals share one in-flight task. To force a fresh load (HMR),
|
|
214
|
+
* {@link invalidate} first.
|
|
171
215
|
* @param id - graph entry name.
|
|
172
216
|
*/
|
|
173
217
|
prefetch(id: string): Promise<void>;
|
|
174
218
|
/**
|
|
175
|
-
* Full reset of one module: drop its registered factory and
|
|
176
|
-
* record so the next prefetch/import reloads it (the HMR
|
|
219
|
+
* Full reset of one non-bootstrap module: drop its registered factory and
|
|
220
|
+
* materialized record so the next prefetch/import reloads it (the HMR
|
|
221
|
+
* invalidation hook). The bootstrap module remains materialized.
|
|
177
222
|
* @param id - entry name to invalidate.
|
|
178
223
|
*/
|
|
179
224
|
invalidate(id: string): void;
|
|
180
225
|
}
|
|
181
|
-
/**
|
|
226
|
+
/** Internal construction inputs assembled by the modules bundle's bootstrap export. */
|
|
182
227
|
export interface ClientModuleSystemOptions {
|
|
183
|
-
/**
|
|
184
|
-
|
|
228
|
+
/** Parsed boot graph owned by the resulting module system. */
|
|
229
|
+
manifest: BootManifest;
|
|
185
230
|
/** Module-table seed: platform-singleton specifier → shell instance. */
|
|
186
231
|
staticModules: Record<string, unknown>;
|
|
232
|
+
/** Stable HTML-installed registration facade to switch from queue to live mode. */
|
|
233
|
+
registrationTarget: ClientModuleLoaderTarget;
|
|
234
|
+
/** Already-materialized modules bundle consumed while creating the system. */
|
|
235
|
+
bootstrapModule: ClientBootstrapModule;
|
|
187
236
|
/** Bundle-load hook. Defaults to a same-origin classic `<script src>` element. */
|
|
188
237
|
loadBundle?: (url: string) => Promise<void>;
|
|
189
238
|
}
|
|
@@ -1,22 +1,18 @@
|
|
|
1
|
-
|
|
2
|
-
* ClientModuleSystem — the implementation behind the {@link ClientModuleLoader}
|
|
3
|
-
* contract. The conceptual contract (lazy CJS model, resolution branch order) is
|
|
4
|
-
* documented on the public interfaces in `./manifest.ts`; this file owns the
|
|
5
|
-
* state tables and the load/materialize machinery.
|
|
6
|
-
*/
|
|
7
|
-
import type { ClientModuleLoader, ClientModuleRecord, ClientModuleSystemOptions } from './manifest.ts';
|
|
1
|
+
import type { BootManifest, ClientModuleLoader, ClientModuleRecord, ClientModuleSystemOptions } from './manifest.ts';
|
|
8
2
|
/**
|
|
9
3
|
* The client module system: state tables plus the arrival/materialization
|
|
10
4
|
* machinery implementing {@link ClientModuleLoader} (whose members carry the
|
|
11
|
-
* contract documentation). Construction indexes the boot rows
|
|
12
|
-
*
|
|
5
|
+
* contract documentation). Construction indexes the boot rows, retains the
|
|
6
|
+
* already-materialized bootstrap module, and switches the HTML-installed
|
|
7
|
+
* loader facade from its pending queue to live registration.
|
|
13
8
|
*/
|
|
14
9
|
export declare class ClientModuleSystem implements ClientModuleLoader {
|
|
15
10
|
readonly version = "client";
|
|
11
|
+
readonly manifest: BootManifest;
|
|
16
12
|
readonly loadCache: Map<string, ClientModuleRecord>;
|
|
17
13
|
private readonly seed;
|
|
18
|
-
private readonly statics;
|
|
19
14
|
private readonly factories;
|
|
15
|
+
private readonly bootstrapIds;
|
|
20
16
|
/** In-flight prefetch (script load) per id; concurrent callers share it. */
|
|
21
17
|
private readonly pendingArrival;
|
|
22
18
|
/** Materialization re-entrancy guard: factory-form CJS cannot deliver partial exports, so a cycle is fatal. */
|
|
@@ -25,23 +21,25 @@ export declare class ClientModuleSystem implements ClientModuleLoader {
|
|
|
25
21
|
private readonly loadBundle;
|
|
26
22
|
/**
|
|
27
23
|
* Build the module system over the parsed boot rows.
|
|
28
|
-
* @param options -
|
|
24
|
+
* @param options - Parsed graph, platform seed, bootstrap module, registration facade, and transport.
|
|
29
25
|
*/
|
|
30
26
|
constructor(options: ClientModuleSystemOptions);
|
|
27
|
+
/** Register one bundle factory, rejecting a script that executes twice without invalidation. */
|
|
28
|
+
private register;
|
|
31
29
|
/** Load one graph row so its factory is registered (idempotent per in-flight arrival). */
|
|
32
30
|
private arrive;
|
|
31
|
+
/** Register each unresolved dynamic request before registering its consumer. */
|
|
32
|
+
private arriveGraphRow;
|
|
33
33
|
/** Materialize a registered factory (synchronous; memoized in loadCache). */
|
|
34
34
|
private materialize;
|
|
35
35
|
/**
|
|
36
|
-
* The synchronous require answered to factories: seed →
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
-
* cross-plugin value import is already a build error upstream).
|
|
36
|
+
* The synchronous require answered to factories: seed → memoized record →
|
|
37
|
+
* registered factory. Fetching is async and therefore unreachable
|
|
38
|
+
* from here; an external dynamic package must have arrived before its
|
|
39
|
+
* consumer materializes.
|
|
41
40
|
*/
|
|
42
41
|
private makeRequire;
|
|
43
42
|
import(specifier: string): Promise<unknown>;
|
|
44
|
-
registerStatic(id: string, module: unknown): void;
|
|
45
43
|
prefetch(id: string): Promise<void>;
|
|
46
44
|
invalidate(id: string): void;
|
|
47
45
|
}
|
package/lib/types/index.d.ts
CHANGED
|
@@ -2,10 +2,11 @@
|
|
|
2
2
|
* Node half of the client module system (`dsh.client` dual-face package): scans
|
|
3
3
|
* the host Loader's entries for packages declaring `dsh.client`, composes the
|
|
4
4
|
* `window.__DSH_BOOT__` entry graph (wire single source: {@link WebBootEntry}
|
|
5
|
-
* in `./client/manifest.ts`)
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
5
|
+
* in `./client/manifest.ts`) in module-graph order, serves
|
|
6
|
+
* `/plugins/<id>/client.js` and its source map, contributes the boot manifest
|
|
7
|
+
* plus the parser-blocking bootstrap preloads to the webserver's index
|
|
8
|
+
* injection table, and provides the `clientModuleHost` service (the HMR node
|
|
9
|
+
* half's registration/notification face).
|
|
9
10
|
*
|
|
10
11
|
* Scanning is incremental per package — there is no full-rescan code path.
|
|
11
12
|
* Every cordis `internal/plugin` emission (fiber construction/disposal) marks
|
|
@@ -21,7 +22,9 @@
|
|
|
21
22
|
*/
|
|
22
23
|
import { Service } from '@deepseek-ai/cordis';
|
|
23
24
|
import type { Context } from '@deepseek-ai/cordis';
|
|
24
|
-
import type {
|
|
25
|
+
import type { IndexInjection } from '@deepseek-ai/dsh-host-webserver';
|
|
26
|
+
import type { WebBootEntry, WebBootGraph } from './client/manifest.ts';
|
|
27
|
+
export { stripClientSuffix } from './client/manifest.ts';
|
|
25
28
|
export type { BootManifest, BootModuleRow, BootPluginRow, WebBootEntry, WebBootGraph, } from './client/manifest.ts';
|
|
26
29
|
declare module '@deepseek-ai/cordis' {
|
|
27
30
|
interface Context {
|
|
@@ -30,17 +33,30 @@ declare module '@deepseek-ai/cordis' {
|
|
|
30
33
|
}
|
|
31
34
|
}
|
|
32
35
|
/**
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
36
|
+
* Order composed rows so every requested dynamic package precedes its
|
|
37
|
+
* consumers. An `external` specifier is either the package row it names
|
|
38
|
+
* (`<pkg>/client` aliases the bare package) or a static-table name that adds no
|
|
39
|
+
* graph edge.
|
|
40
|
+
* @param entries - composed rows in scan order.
|
|
41
|
+
* @returns the same rows reordered; scan order breaks every tie.
|
|
42
|
+
* @throws {Error} when a row requests itself or when the module graph has a
|
|
43
|
+
* cycle; the message lists the packages on it.
|
|
44
|
+
*/
|
|
45
|
+
export declare function orderByModuleGraph(entries: readonly WebBootEntry[]): WebBootEntry[];
|
|
46
|
+
/**
|
|
47
|
+
* The boot protocol as index injection rows. The inline registration queue
|
|
48
|
+
* precedes blocking classic scripts for modules' and runtime's ordinary
|
|
49
|
+
* `lib/client.js` artifacts. Its `create()` method materializes the modules
|
|
50
|
+
* bundle, delegates construction to that bundle, and leaves the same facade
|
|
51
|
+
* in live-registration mode. The graph global follows before the shell reads
|
|
52
|
+
* it.
|
|
37
53
|
* @param graph - the composed entry graph.
|
|
38
|
-
* @returns
|
|
54
|
+
* @returns head rows in execution order: queue script, preload scripts, graph global.
|
|
39
55
|
*/
|
|
40
|
-
export declare function
|
|
56
|
+
export declare function bootInjections(graph: WebBootGraph): IndexInjection[];
|
|
41
57
|
/**
|
|
42
58
|
* The web plugin table service: incremental `dsh.client` scan + wire composition
|
|
43
|
-
* + bundle route + index
|
|
59
|
+
* + bundle route + index injection rows. Construction runs the activation scan
|
|
44
60
|
* synchronously — a malformed declaration or missing bundle among the
|
|
45
61
|
* already-loaded entries aggregates into one loud throw (FAILED fiber; the
|
|
46
62
|
* boot activation audit reports it).
|
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.
|
|
4
|
+
"version": "0.1.1-rc.1",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
7
7
|
},
|
|
@@ -39,8 +39,8 @@
|
|
|
39
39
|
"license": "MIT",
|
|
40
40
|
"devDependencies": {
|
|
41
41
|
"@deepseek-ai/cordis-plugin-loader": "^1.0.2",
|
|
42
|
-
"@deepseek-ai/dsh-host-webserver": "^0.1.
|
|
43
|
-
"@deepseek-ai/dsh-invariants": "^0.1.
|
|
42
|
+
"@deepseek-ai/dsh-host-webserver": "^0.1.1-rc.1",
|
|
43
|
+
"@deepseek-ai/dsh-invariants": "^0.1.1-rc.1",
|
|
44
44
|
"@deepseek-ai/cordis": "^4.0.1"
|
|
45
45
|
},
|
|
46
46
|
"files": [
|
|
@@ -50,8 +50,10 @@
|
|
|
50
50
|
"lib/types/**/*.d.ts"
|
|
51
51
|
],
|
|
52
52
|
"peerDependencies": {
|
|
53
|
-
"@deepseek-ai/dsh-invariants": "^0.1.
|
|
54
|
-
"@deepseek-ai/cordis": "^4.0.1"
|
|
53
|
+
"@deepseek-ai/dsh-invariants": "^0.1.1-rc.1",
|
|
54
|
+
"@deepseek-ai/cordis": "^4.0.1",
|
|
55
|
+
"@deepseek-ai/cordis-plugin-loader": "^1.0.2",
|
|
56
|
+
"@deepseek-ai/dsh-host-webserver": "^0.1.1-rc.1"
|
|
55
57
|
},
|
|
56
58
|
"scripts": {
|
|
57
59
|
"bundle": "tsdown",
|