@deepseek-ai/dsh-client-modules 0.1.6-alpha.2 → 0.1.7-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 +2 -2
- package/README.md +2 -1
- package/README.zh.md +2 -1
- package/lib/index.js +57 -36
- package/lib/invariant.js +2 -2
- package/lib/types/client/manifest.d.ts +7 -4
- package/lib/types/index.d.ts +5 -6
- package/package.json +8 -8
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: c99293b1f7a26a86279c4d71d9d6f41c6452b13e
|
|
6
|
+
README.zh.md: f6057204e1a039cecb824a3c355f4d7b906abac9
|
package/README.md
CHANGED
|
@@ -71,7 +71,7 @@ Executing a plugin bundle only registers its factory; every module-body side eff
|
|
|
71
71
|
|
|
72
72
|
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).
|
|
73
73
|
|
|
74
|
-
The Node half snapshots each `client.js` entry before publication and creates combo descriptors without building response bodies. It groups resources into `/plugins/??...&rev=...`
|
|
74
|
+
The Node half snapshots each `client.js` entry before publication and creates combo descriptors without building response bodies. It groups resources into combo route keys (`/plugins/??...&rev=...`), 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. The graph and batch descriptors carry the app-directory-relative form of the same key (`plugins/??...&rev=...`), which a browser document resolves under its own mount, while the response table stays keyed by the absolute route; the source-map trailer is resolved against the combo script's own directory. A script body is combined once on its first `GET` and ends with its map reference. The corresponding map files are read, validated, and combined separately on the first map `GET`; `HEAD` materializes neither body. The Host does not scan or preload sibling chunks: an exact `/plugins/<package>/client.<name>.js?rev=<rev>` request reads and caches that script, and its map remains uncomputed until the map URL is requested. The chunk reference the browser derives from its owner row is document-relative as well (`plugins/<package>/client.<name>.js?rev=<rev>`), and a chunk script's map trailer is the bare `client.<name>.js.map?rev=<rev>` name, which resolves against the script's own directory. Every combo or chunk map is Indexed Source Map v3 and uses an authored section when available or an identity section for the packaged bundle. Initial publication and HMR derive per-plugin revisions from the entry's `mtimeMs`, `ctimeMs`, and size, without hashing artifact contents. Status-change time distinguishes rewrites that preserve mtime and size. Unchanged artifacts therefore retain their revisions across Host restarts, so SSE reconnection does not replace their browser plugins. The shared preset stamps `client.js` after every package output is written, so a chunk-only rebuild changes the owner revision without a Host-side chunk scan. Combo revisions derive from the ordered row revisions. Advertised combo responses and requested chunk responses remain immutable across unrelated graph recomposition, and an unknown resource or revision returns 404.
|
|
75
75
|
|
|
76
76
|
### Boot manifest injection
|
|
77
77
|
|
|
@@ -127,6 +127,7 @@ None; this package neither assembles nor sends a provider request.
|
|
|
127
127
|
|
|
128
128
|
These limits define what the module system does not do. They are current package constraints, not a task backlog.
|
|
129
129
|
|
|
130
|
+
- **Metadata-based revisions** — revisions identify filesystem generations, not content equality. Metadata-only changes can reload a plugin; changes invisible in mtime, ctime, and size cannot be distinguished.
|
|
130
131
|
- **Flat module graph by design** — every bundle is one module node whose edges point only at table leaves; the interface (`loadCache`/`edges`/`invalidate`) already supports a general module graph, so the externalization granularity can change without an interface change.
|
|
131
132
|
- **Bootstrap and code replacement limits** — the page retains its modules bootstrap and static platform identities. Removing or replacing the bootstrap requires a page reload; live replacement requests report a page-local error while retaining its fiber and exports; replacing package code and all existing consumers is outside ordinary enable/disable synchronization.
|
|
132
133
|
- **Lazy delivery retains requested bodies** — the Host holds each bundle and lazy response plan; a script or map body remains cached after its first `GET`, and HMR additionally retains one prior startup generation. Memory grows only for response bodies that clients request while preserving one-generation race tolerance.
|
package/README.zh.md
CHANGED
|
@@ -71,7 +71,7 @@ application combo 脚本只携带每个插件的 `client.js` 入口,并在启
|
|
|
71
71
|
|
|
72
72
|
Node 半侧逐包增量扫描——没有全量重扫路径。每次发出 `internal/plugin` 事件时,系统都会把该 fiber 的 entry 名标脏;微任务 flush 会把每个脏名与当前 loader 条目对账,激活 pass 会初始化同一个脏集合并同步 flush,因此首次扫描与稳态共用同一实现。包元数据按 Loader specifier 与所属 tree base URL 缓存至重启,解析出的 manifest(元数据清单)包名作为浏览器模块身份。若不同的 active Loader source 解析到同一包名,组合会失败;移除冲突来源后,剩余来源无需重启 fiber 即可接替。bundle 内容变更只能通过 `rebuilt()`(HMR 钩子)进入图。
|
|
73
73
|
|
|
74
|
-
Node 半侧会在发布前快照每个 `client.js` 入口,并在不构建响应 body 的情况下创建 combo descriptor
|
|
74
|
+
Node 半侧会在发布前快照每个 `client.js` 入口,并在不构建响应 body 的情况下创建 combo descriptor。它把资源分组为 combo 路由键(`/plugins/??...&rev=...`):modules row 使用一个 bootstrap combo,其余 row 使用一个或多个 application combo;每个阶段都会在 URL 超过 3 KiB 之前分区。启动图与批次描述符携带同一路由键的应用目录相对形式(`plugins/??...&rev=...`),浏览器文档会按其自身挂载解析它,而响应表仍以绝对路由为键;source-map trailer 则相对该 combo 脚本自身目录解析。脚本 body 在首次 `GET` 时只组合一次,并以对应的 map 引用结尾;map 文件则在首次 map `GET` 时单独读取、校验并组合,`HEAD` 不会物化任一 body。Host 不扫描也不预加载同级 chunk:精确的 `/plugins/<package>/client.<name>.js?rev=<rev>` 请求会读取并缓存该脚本,其 map 仍会等到 map URL 被请求后才计算。浏览器从所属 row 派生出该 chunk 引用,因此它同样是文档相对形式(`plugins/<package>/client.<name>.js?rev=<rev>`);chunk 脚本携带的 map trailer 则是裸文件名 `client.<name>.js.map?rev=<rev>`,由该脚本自身目录解析。每个 combo 或 chunk map 都是 Indexed Source Map v3,并在可用时使用作者提供的 section,否则为已打包 bundle 生成 identity section。首次发布与 HMR 都从入口的 `mtimeMs`、`ctimeMs` 和大小派生逐插件 revision,不对产物内容求哈希。状态变更时间用于区分保留 mtime 和大小的重写。未变化的产物因此会跨 Host 重启保持 revision,SSE 重连不会替换其浏览器插件。共享预设会在该包所有输出写完后标记 `client.js`,因此仅 chunk 发生重建也会更换 owner revision,无需 Host 扫描 chunk。combo revision 从有序 row revision 派生。已公告的 combo 响应与已请求的 chunk 响应会跨无关图重组保持不可变;未知资源或 revision 返回 404。
|
|
75
75
|
|
|
76
76
|
### 启动 manifest 注入
|
|
77
77
|
|
|
@@ -127,6 +127,7 @@ bundle 路由随注入的 `webServer` 生命周期注册:服务就绪时注册
|
|
|
127
127
|
|
|
128
128
|
这些限制说明模块系统不做什么。它们是当前包约束,不是任务积压。
|
|
129
129
|
|
|
130
|
+
- **基于元数据的 revision**——revision 标识文件系统代际,而非内容相等性。仅元数据变化也可能重载插件;mtime、ctime 和大小都无法反映的变化无法区分。
|
|
130
131
|
- **有意采用扁平模块图**——每个 bundle 是一个模块节点,其边只指向表中的叶节点;接口(`loadCache`/`edges`/`invalidate`)已经支持通用模块图,因此可以改变 externalization 粒度而不更改接口。
|
|
131
132
|
- **Bootstrap 与代码替换限制**——页面保留 modules bootstrap 和静态平台模块的身份。移除或替换 bootstrap 需要刷新页面;动态替换请求会报告页面本地错误,并保留其 fiber 与导出;替换包代码及其所有现有消费者不属于普通启停同步。
|
|
132
133
|
- **惰性提供会保留已请求的 body**——Host 在内存中保留每个 bundle 与惰性响应计划;脚本或 map body 在首次 `GET` 后保留缓存,HMR 还会保留上一代启动响应。内存仅随客户端实际请求的响应 body 增长,同时保留一代竞态容忍。
|
package/lib/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { createRequire } from "node:module";
|
|
2
|
-
import { createHash
|
|
2
|
+
import { createHash } from "node:crypto";
|
|
3
3
|
import { existsSync, readFileSync, statSync } from "node:fs";
|
|
4
4
|
import { dirname, isAbsolute, join } from "node:path";
|
|
5
5
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
@@ -179,31 +179,58 @@ function clientExportOf(pkgName, exportsField) {
|
|
|
179
179
|
}
|
|
180
180
|
throw new Error(`client-modules: ${pkgName} exports["./client"] must be a string or an object with a string default`);
|
|
181
181
|
}
|
|
182
|
-
/** sha1
|
|
182
|
+
/** sha1 metadata hash shortened to 12 hex chars. */
|
|
183
183
|
function shortHash(input) {
|
|
184
184
|
return createHash("sha1").update(input).digest("hex").slice(0, HASH_REVISION_LENGTH);
|
|
185
185
|
}
|
|
186
186
|
/** Hash several response fields without allowing bytes to move across field boundaries. */
|
|
187
187
|
function framedHash(domain, parts) {
|
|
188
188
|
const hash = createHash("sha1").update(domain).update("\0");
|
|
189
|
-
for (const part of parts) hash.update(`${String(
|
|
189
|
+
for (const part of parts) hash.update(`${String(Buffer.byteLength(part))}:`).update(part);
|
|
190
190
|
return hash.digest("hex").slice(0, HASH_REVISION_LENGTH);
|
|
191
191
|
}
|
|
192
|
-
/**
|
|
193
|
-
function artifactRevision(
|
|
194
|
-
return framedHash("plugin-artifact", [
|
|
192
|
+
/** Identify an entry's build from filesystem metadata without hashing its contents. */
|
|
193
|
+
function artifactRevision(baseline) {
|
|
194
|
+
return framedHash("plugin-artifact", [
|
|
195
|
+
String(baseline.mtimeMs),
|
|
196
|
+
String(baseline.ctimeMs),
|
|
197
|
+
String(baseline.size)
|
|
198
|
+
]);
|
|
195
199
|
}
|
|
196
|
-
/**
|
|
200
|
+
/** Absolute route prefix serving every plugin resource. */
|
|
201
|
+
const PLUGIN_ROUTE = "/plugins";
|
|
202
|
+
/** Combo query addressing one ordered plugin-file list. */
|
|
203
|
+
function comboSearch(ids, rev, sourceMap = false) {
|
|
204
|
+
return `??${ids.map((id) => `${id}/client.js${sourceMap ? ".map" : ""}`).join(",")}&rev=${rev}`;
|
|
205
|
+
}
|
|
206
|
+
/** Absolute route URL for one combo resource. */
|
|
197
207
|
function comboUrl(ids, rev, sourceMap = false) {
|
|
198
|
-
return
|
|
208
|
+
return `${PLUGIN_ROUTE}/${comboSearch(ids, rev, sourceMap)}`;
|
|
199
209
|
}
|
|
200
|
-
/**
|
|
210
|
+
/**
|
|
211
|
+
* Browser reference to one combo resource: app-owned browser routes are
|
|
212
|
+
* document-relative, so the route key's leading slash is stripped here, at the
|
|
213
|
+
* boundary between the two halves. The rule and its reasons are owned by
|
|
214
|
+
* .agents/notes/implemented/architecture/2026-09-14-web-document-relative-app-routes.md.
|
|
215
|
+
*/
|
|
216
|
+
function comboReference(ids, rev, sourceMap = false) {
|
|
217
|
+
return comboUrl(ids, rev, sourceMap).slice(1);
|
|
218
|
+
}
|
|
219
|
+
/** Absolute route URL for one package-local chunk. */
|
|
201
220
|
function chunkUrl(id, fileName, rev, sourceMap = false) {
|
|
202
|
-
return
|
|
221
|
+
return `${PLUGIN_ROUTE}/${id}/${fileName}${sourceMap ? ".map" : ""}?rev=${rev}`;
|
|
222
|
+
}
|
|
223
|
+
/**
|
|
224
|
+
* Source-map reference stamped into one chunk script. A script's map reference
|
|
225
|
+
* resolves against that script's own directory rather than the document, so
|
|
226
|
+
* this is the bare map file name, not the document-relative route.
|
|
227
|
+
*/
|
|
228
|
+
function chunkMapReference(fileName, rev) {
|
|
229
|
+
return `${fileName}.map?rev=${rev}`;
|
|
203
230
|
}
|
|
204
|
-
/** Measure the
|
|
231
|
+
/** Measure the longest browser-facing combo URL used to partition a startup resource list. */
|
|
205
232
|
function projectedComboUrlBytes(records) {
|
|
206
|
-
return Buffer.byteLength(
|
|
233
|
+
return Buffer.byteLength(comboReference(records.map((record) => record.entry.id), COMBO_REVISION_PLACEHOLDER, true));
|
|
207
234
|
}
|
|
208
235
|
/** Partition one phase in graph order without allowing a generated URL above the protocol limit. */
|
|
209
236
|
function partitionComboRecords(records) {
|
|
@@ -235,7 +262,7 @@ function prepareSource(resource) {
|
|
|
235
262
|
fallbackSource
|
|
236
263
|
};
|
|
237
264
|
}
|
|
238
|
-
/** Stamp a combo script's
|
|
265
|
+
/** Stamp a combo script's source-map reference onto its executable bytes. */
|
|
239
266
|
function comboScript(input, sourceMapUrl) {
|
|
240
267
|
return Buffer.from(sourceMapUrl === void 0 ? input : `${input}//# sourceMappingURL=${sourceMapUrl}\n`);
|
|
241
268
|
}
|
|
@@ -297,7 +324,7 @@ function lazyBody(produce) {
|
|
|
297
324
|
}
|
|
298
325
|
/** Derive one combo revision from the ordered immutable row revisions. */
|
|
299
326
|
function comboRevision(resources) {
|
|
300
|
-
return framedHash("combo", resources.flatMap((resource) => [
|
|
327
|
+
return framedHash("combo", resources.flatMap((resource) => [resource.id, resource.rev]));
|
|
301
328
|
}
|
|
302
329
|
/** Concatenate one or more factory registrations without reading or composing source maps. */
|
|
303
330
|
function buildComboScript(resources, sourceMapUrl) {
|
|
@@ -342,14 +369,12 @@ function buildCombo(records, sourceMapOf, revision) {
|
|
|
342
369
|
}));
|
|
343
370
|
const rev = revision ?? comboRevision(resources);
|
|
344
371
|
const entries = resources.map((resource) => resource.id);
|
|
345
|
-
const url = comboUrl(entries, rev);
|
|
346
|
-
const sourceMapUrl = comboUrl(entries, rev, true);
|
|
347
372
|
return {
|
|
348
|
-
url,
|
|
373
|
+
url: comboUrl(entries, rev),
|
|
349
374
|
rev,
|
|
350
375
|
entries,
|
|
351
|
-
sourceMapUrl,
|
|
352
|
-
scriptBody: lazyBody(() => buildComboScript(resources,
|
|
376
|
+
sourceMapUrl: comboUrl(entries, rev, true),
|
|
377
|
+
scriptBody: lazyBody(() => buildComboScript(resources, comboSearch(entries, rev, true))),
|
|
353
378
|
sourceMapBody: lazyBody(() => buildComboSourceMap(resources, sourceMapOf))
|
|
354
379
|
};
|
|
355
380
|
}
|
|
@@ -360,17 +385,17 @@ function buildBatch(phase, records, sourceMapOf) {
|
|
|
360
385
|
...artifact,
|
|
361
386
|
descriptor: {
|
|
362
387
|
phase,
|
|
363
|
-
url: artifact.url,
|
|
388
|
+
url: artifact.url.slice(1),
|
|
364
389
|
rev: artifact.rev,
|
|
365
390
|
entries: artifact.entries
|
|
366
391
|
}
|
|
367
392
|
};
|
|
368
393
|
}
|
|
369
|
-
/** Graph row for one bundle rev (
|
|
394
|
+
/** Graph row for one bundle rev (the reference carries the rev as its cache-busting query). */
|
|
370
395
|
function graphRow(id, rev, fields) {
|
|
371
396
|
return {
|
|
372
397
|
id,
|
|
373
|
-
url:
|
|
398
|
+
url: comboReference([id], rev),
|
|
374
399
|
rev,
|
|
375
400
|
...fields.inject !== void 0 ? { inject: fields.inject } : {},
|
|
376
401
|
...fields.immediately ? { immediately: true } : {},
|
|
@@ -486,8 +511,6 @@ var ClientModuleRegistry = class extends Service {
|
|
|
486
511
|
rebuildListeners = /* @__PURE__ */ new Set();
|
|
487
512
|
graphListeners = /* @__PURE__ */ new Set();
|
|
488
513
|
dirty = /* @__PURE__ */ new Set();
|
|
489
|
-
initialRevisionNonce = randomBytes(8).toString("hex");
|
|
490
|
-
nextInitialRevision = 0;
|
|
491
514
|
responses = /* @__PURE__ */ new Map();
|
|
492
515
|
batchResponses = /* @__PURE__ */ new Map();
|
|
493
516
|
/** One prior graph generation covers a request racing the HMR recomposition that replaced its URL. */
|
|
@@ -522,7 +545,7 @@ var ClientModuleRegistry = class extends Service {
|
|
|
522
545
|
const registerWebCarrier = (webCtx) => {
|
|
523
546
|
webCtx.effect(() => webCtx.webServer.register({
|
|
524
547
|
kind: "prefix",
|
|
525
|
-
path:
|
|
548
|
+
path: PLUGIN_ROUTE,
|
|
526
549
|
handler: this.serveBundle
|
|
527
550
|
}), "client-modules: bundle route");
|
|
528
551
|
};
|
|
@@ -577,17 +600,18 @@ var ClientModuleRegistry = class extends Service {
|
|
|
577
600
|
/**
|
|
578
601
|
* Publish one completed bundle generation (the HMR watch's registration
|
|
579
602
|
* hook — the only entry point through which build changes reach the graph).
|
|
603
|
+
* Unchanged mtime, ctime and size preserve the graph without reading the bundle.
|
|
580
604
|
* @param id - entry id (package name).
|
|
581
|
-
* @returns the
|
|
605
|
+
* @returns the current artifact rev, or undefined for an unknown id.
|
|
582
606
|
*/
|
|
583
607
|
rebuilt(id) {
|
|
584
608
|
const record = this.table.get(id);
|
|
585
609
|
if (record === void 0) return void 0;
|
|
586
610
|
const baseline = this.captureArtifactBaseline(record.meta.clientPath);
|
|
611
|
+
const rev = artifactRevision(baseline);
|
|
612
|
+
if (rev === record.entry.rev) return rev;
|
|
587
613
|
const bundle = readFileSync(record.meta.clientPath);
|
|
588
|
-
const rev = artifactRevision(bundle, baseline);
|
|
589
614
|
record.baseline = baseline;
|
|
590
|
-
if (rev === record.entry.rev) return rev;
|
|
591
615
|
record.entry = graphRow(id, rev, record.meta);
|
|
592
616
|
record.bundle = bundle;
|
|
593
617
|
this.composed = this.compose();
|
|
@@ -600,7 +624,7 @@ var ClientModuleRegistry = class extends Service {
|
|
|
600
624
|
return rev;
|
|
601
625
|
}
|
|
602
626
|
/**
|
|
603
|
-
* Subscribe to bundle rebuilds; fires only when
|
|
627
|
+
* Subscribe to bundle rebuilds; fires only when artifact metadata changes the rev.
|
|
604
628
|
* @param listener - receives the entry id and its new bundle rev.
|
|
605
629
|
* @returns the unsubscriber.
|
|
606
630
|
*/
|
|
@@ -632,7 +656,7 @@ var ClientModuleRegistry = class extends Service {
|
|
|
632
656
|
for (const records of partitionComboRecords(application)) artifacts.push(buildBatch("application", records, this.readSourceMap));
|
|
633
657
|
const batchResponses = /* @__PURE__ */ new Map();
|
|
634
658
|
for (const artifact of artifacts) {
|
|
635
|
-
batchResponses.set(artifact.
|
|
659
|
+
batchResponses.set(artifact.url, this.responses.get(artifact.url) ?? {
|
|
636
660
|
body: artifact.scriptBody,
|
|
637
661
|
contentType: "text/javascript; charset=utf-8"
|
|
638
662
|
});
|
|
@@ -773,13 +797,10 @@ var ClientModuleRegistry = class extends Service {
|
|
|
773
797
|
return {
|
|
774
798
|
path: clientPath,
|
|
775
799
|
mtimeMs: bundle.mtimeMs,
|
|
800
|
+
ctimeMs: bundle.ctimeMs,
|
|
776
801
|
size: bundle.size
|
|
777
802
|
};
|
|
778
803
|
}
|
|
779
|
-
/** Allocate an opaque initial row revision without inspecting artifact bytes. */
|
|
780
|
-
allocateInitialRevision() {
|
|
781
|
-
return `${this.initialRevisionNonce}-${String(this.nextInitialRevision++)}`;
|
|
782
|
-
}
|
|
783
804
|
/**
|
|
784
805
|
* Read the activation-time bundle snapshot.
|
|
785
806
|
* @param pkgName - package that declares the client bundle.
|
|
@@ -858,7 +879,7 @@ var ClientModuleRegistry = class extends Service {
|
|
|
858
879
|
if (source === void 0) return this.table.delete(packageName);
|
|
859
880
|
if (this.table.get(packageName)?.sourceKey === source.sourceKey) return false;
|
|
860
881
|
const snapshot = this.initialBundleSnapshot(packageName, source.meta.clientPath);
|
|
861
|
-
const rev =
|
|
882
|
+
const rev = artifactRevision(snapshot.baseline);
|
|
862
883
|
this.table.set(packageName, {
|
|
863
884
|
entry: graphRow(packageName, rev, source.meta),
|
|
864
885
|
loaderName: source.loaderName,
|
|
@@ -916,7 +937,7 @@ var ClientModuleRegistry = class extends Service {
|
|
|
916
937
|
const { record, fileName, sourceMap, resourceUrl } = request;
|
|
917
938
|
const clientPath = join(dirname(record.meta.clientPath), fileName);
|
|
918
939
|
if (!existsSync(clientPath)) return void 0;
|
|
919
|
-
const sourceMapUrl =
|
|
940
|
+
const sourceMapUrl = chunkMapReference(fileName, record.entry.rev);
|
|
920
941
|
const resource = () => ({
|
|
921
942
|
id: record.entry.id,
|
|
922
943
|
rev: record.entry.rev,
|
package/lib/invariant.js
CHANGED
|
@@ -11,8 +11,8 @@ const inject = ["invariants"];
|
|
|
11
11
|
/**
|
|
12
12
|
* Owned relation: the node half's boot entry graph must stay self-consistent
|
|
13
13
|
* — every row must resolve a clientPath under the same id (the
|
|
14
|
-
*
|
|
15
|
-
* that just received the graph). Checked on every scan trigger (cordis
|
|
14
|
+
* `plugins/<id>/client.js` reference it advertises would otherwise 404 in the
|
|
15
|
+
* browser that just received the graph). Checked on every scan trigger (cordis
|
|
16
16
|
* 'internal/plugin'): graph() and clientPath() read the same table object,
|
|
17
17
|
* so the relation holds at any instant — no need to wait out the node half's
|
|
18
18
|
* own microtask-debounced flush.
|
|
@@ -48,7 +48,10 @@ declare module '@deepseek-ai/cordis' {
|
|
|
48
48
|
export interface WebBootEntry {
|
|
49
49
|
/** Entry name == package name. */
|
|
50
50
|
id: string;
|
|
51
|
-
/**
|
|
51
|
+
/**
|
|
52
|
+
* Revisioned single-resource combo reference used by HMR. It is relative to
|
|
53
|
+
* the document, so the browser resolves it under whatever mount served the page.
|
|
54
|
+
*/
|
|
52
55
|
url: string;
|
|
53
56
|
/** Opaque plugin-artifact revision used for HMR cache busting. */
|
|
54
57
|
rev: string;
|
|
@@ -65,7 +68,7 @@ export type WebBootBatchPhase = 'bootstrap' | 'application';
|
|
|
65
68
|
export interface WebBootBatch {
|
|
66
69
|
/** Parser-blocking bootstrap or preloaded application scheduling. */
|
|
67
70
|
phase: WebBootBatchPhase;
|
|
68
|
-
/**
|
|
71
|
+
/** Content-addressed combo script reference, document-relative like {@link WebBootEntry.url}. */
|
|
69
72
|
url: string;
|
|
70
73
|
/** Revision derived from the ordered entry revisions. */
|
|
71
74
|
rev: string;
|
|
@@ -89,9 +92,9 @@ export interface WebBootGraph {
|
|
|
89
92
|
export interface BootModuleRow {
|
|
90
93
|
/** Entry name == package name (module-table key). */
|
|
91
94
|
id: string;
|
|
92
|
-
/** Revisioned single-resource combo
|
|
95
|
+
/** Revisioned single-resource combo reference used after HMR invalidation. */
|
|
93
96
|
url: string;
|
|
94
|
-
/**
|
|
97
|
+
/** Content-addressed combo reference used before the first HMR invalidation. */
|
|
95
98
|
initialUrl: string;
|
|
96
99
|
/** Opaque plugin-artifact revision used after HMR invalidation. */
|
|
97
100
|
rev: string;
|
package/lib/types/index.d.ts
CHANGED
|
@@ -40,6 +40,8 @@ export interface ClientArtifactBaseline {
|
|
|
40
40
|
readonly path: string;
|
|
41
41
|
/** Bundle modification time in milliseconds. */
|
|
42
42
|
readonly mtimeMs: number;
|
|
43
|
+
/** Bundle status-change time in milliseconds, including writes that preserve mtime. */
|
|
44
|
+
readonly ctimeMs: number;
|
|
43
45
|
/** Bundle size in bytes. */
|
|
44
46
|
readonly size: number;
|
|
45
47
|
}
|
|
@@ -81,8 +83,6 @@ export declare class ClientModuleRegistry extends Service {
|
|
|
81
83
|
private readonly rebuildListeners;
|
|
82
84
|
private readonly graphListeners;
|
|
83
85
|
private readonly dirty;
|
|
84
|
-
private readonly initialRevisionNonce;
|
|
85
|
-
private nextInitialRevision;
|
|
86
86
|
private responses;
|
|
87
87
|
private batchResponses;
|
|
88
88
|
/** One prior graph generation covers a request racing the HMR recomposition that replaced its URL. */
|
|
@@ -127,12 +127,13 @@ export declare class ClientModuleRegistry extends Service {
|
|
|
127
127
|
/**
|
|
128
128
|
* Publish one completed bundle generation (the HMR watch's registration
|
|
129
129
|
* hook — the only entry point through which build changes reach the graph).
|
|
130
|
+
* Unchanged mtime, ctime and size preserve the graph without reading the bundle.
|
|
130
131
|
* @param id - entry id (package name).
|
|
131
|
-
* @returns the
|
|
132
|
+
* @returns the current artifact rev, or undefined for an unknown id.
|
|
132
133
|
*/
|
|
133
134
|
rebuilt(id: string): string | undefined;
|
|
134
135
|
/**
|
|
135
|
-
* Subscribe to bundle rebuilds; fires only when
|
|
136
|
+
* Subscribe to bundle rebuilds; fires only when artifact metadata changes the rev.
|
|
136
137
|
* @param listener - receives the entry id and its new bundle rev.
|
|
137
138
|
* @returns the unsubscriber.
|
|
138
139
|
*/
|
|
@@ -163,8 +164,6 @@ export declare class ClientModuleRegistry extends Service {
|
|
|
163
164
|
private sourceKey;
|
|
164
165
|
/** Capture the bundle stats before reading its bytes. */
|
|
165
166
|
private captureArtifactBaseline;
|
|
166
|
-
/** Allocate an opaque initial row revision without inspecting artifact bytes. */
|
|
167
|
-
private allocateInitialRevision;
|
|
168
167
|
/**
|
|
169
168
|
* Read the activation-time bundle snapshot.
|
|
170
169
|
* @param pkgName - package that declares the client bundle.
|
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.7-alpha.1",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
7
7
|
},
|
|
@@ -38,12 +38,12 @@
|
|
|
38
38
|
},
|
|
39
39
|
"license": "MIT",
|
|
40
40
|
"devDependencies": {
|
|
41
|
-
"@deepseek-ai/
|
|
42
|
-
"@deepseek-ai/dsh-
|
|
43
|
-
"@deepseek-ai/
|
|
44
|
-
"@deepseek-ai/cordis": "^4.0.
|
|
45
|
-
"@deepseek-ai/dsh-package-manifest": "^0.1.
|
|
46
|
-
"@deepseek-ai/dsh-client-store": "^0.1.
|
|
41
|
+
"@deepseek-ai/cordis-plugin-loader": "^1.0.4",
|
|
42
|
+
"@deepseek-ai/dsh-host-webserver": "^0.1.7-alpha.1",
|
|
43
|
+
"@deepseek-ai/dsh-invariants": "^0.1.7-alpha.1",
|
|
44
|
+
"@deepseek-ai/cordis": "^4.0.3",
|
|
45
|
+
"@deepseek-ai/dsh-package-manifest": "^0.1.7-alpha.1",
|
|
46
|
+
"@deepseek-ai/dsh-client-store": "^0.1.7-alpha.1"
|
|
47
47
|
},
|
|
48
48
|
"files": [
|
|
49
49
|
"lib/index.js",
|
|
@@ -52,7 +52,7 @@
|
|
|
52
52
|
"lib/types/**/*.d.ts"
|
|
53
53
|
],
|
|
54
54
|
"peerDependencies": {
|
|
55
|
-
"@deepseek-ai/cordis": "^4.0.
|
|
55
|
+
"@deepseek-ai/cordis": "^4.0.3"
|
|
56
56
|
},
|
|
57
57
|
"scripts": {
|
|
58
58
|
"bundle": "tsdown",
|