@deepseek-ai/dsh-client-modules 0.1.7-alpha.1 → 0.1.7-alpha.2
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 +1 -1
- package/README.zh.md +1 -1
- package/lib/client.js +103 -17
- package/lib/types/client/manifest.d.ts +10 -1
- package/lib/types/client/system.d.ts +33 -1
- 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: 3388eb2ae421b303df6709f0bfc656fe67e3a866
|
|
6
|
+
README.zh.md: 3cbc600ee5ed211640070773efb72f58b91d4669
|
package/README.md
CHANGED
|
@@ -35,7 +35,7 @@ A browser plugin package declares `dsh.client` in its `package.json` with `platf
|
|
|
35
35
|
|
|
36
36
|
### What the browser loads
|
|
37
37
|
|
|
38
|
-
The application combo scripts carry only each plugin's `client.js` entry and register those factories once during boot; module bodies remain lazy and run only at first import or materialization. A source `import()` split by tsdown compiles to `require.async("./client.<name>.js")`; its versioned sibling script arrives only when that expression runs. Rows that share a combo URL share one in-flight script task. HMR switches one changed row to its revisioned one-resource combo URL. `<id>/client` and the bare id resolve to the same exports, because a plugin bundle is its package's client half.
|
|
38
|
+
The application combo scripts carry only each plugin's `client.js` entry and register those factories once during boot; module bodies remain lazy and run only at first import or materialization. A source `import()` split by tsdown compiles to `require.async("./client.<name>.js")`; its versioned sibling script arrives only when that expression runs. Rows that share a combo URL share one in-flight script task. A combo whose `<script>` fails to load is requested once more; a combo that loads without registering a row is never re-executed, because the batch script registers its packages in sequence and a replay would stop at the first duplicate registration. In both cases each still-missing row then loads its own one-resource combo URL, so one failed batch costs at most three requests per missing row and leaves the rows it did register untouched. The module system records the last import failure per row (transport, registration, dependency cascade, or factory execution); the Web boot audit reports that text per entry. HMR switches one changed row to its revisioned one-resource combo URL. `<id>/client` and the bare id resolve to the same exports, because a plugin bundle is its package's client half.
|
|
39
39
|
|
|
40
40
|
### Live plugin composition
|
|
41
41
|
|
package/README.zh.md
CHANGED
|
@@ -35,7 +35,7 @@ kind: "package-reference"
|
|
|
35
35
|
|
|
36
36
|
### 浏览器加载什么
|
|
37
37
|
|
|
38
|
-
application combo 脚本只携带每个插件的 `client.js` 入口,并在启动时仅注册一次这些 factory;模块主体仍保持惰性,只在首次 import 或物化时运行。经 tsdown 拆分的源码 `import()` 会编译为 `require.async("./client.<name>.js")`;只有执行该表达式时,对应的带版本同级脚本才会到达。共享 combo URL 的 row
|
|
38
|
+
application combo 脚本只携带每个插件的 `client.js` 入口,并在启动时仅注册一次这些 factory;模块主体仍保持惰性,只在首次 import 或物化时运行。经 tsdown 拆分的源码 `import()` 会编译为 `require.async("./client.<name>.js")`;只有执行该表达式时,对应的带版本同级脚本才会到达。共享 combo URL 的 row 共用一个进行中的脚本任务。`<script>` 加载失败的 combo 会再请求一次;加载成功但没有注册某条 row 的 combo 绝不会重新执行,因为批量脚本按顺序注册各个包,重放会在第一个重复注册处停止。两种情况下,每条仍缺失的 row 随后加载自己的单资源 combo URL,因此一个失败的 batch 对每条缺失 row 最多花费三次请求,且不影响它已经注册的 row。模块系统按 row 记录最后一次 import 失败(传输、注册、依赖级联或 factory 执行);Web 启动审计按条目报告该文本。HMR(热模块替换)会让一条发生变化的 row 改用带 revision 的单资源 combo URL。`<id>/client` 与裸 id 解析到同一组导出,因为插件 bundle 就是其包的客户端半侧。
|
|
39
39
|
|
|
40
40
|
### 插件动态组合
|
|
41
41
|
|
package/lib/client.js
CHANGED
|
@@ -468,6 +468,10 @@ window.__ModuleLoader__.load({
|
|
|
468
468
|
return url.replace(/([?&]rev=)[^&#]*/, `$1${encodeURIComponent(rev)}`);
|
|
469
469
|
}
|
|
470
470
|
const CLIENT_CHUNK = /^client\.[A-Za-z0-9][A-Za-z0-9._-]*\.js$/;
|
|
471
|
+
/** The message of a thrown value: an Error's message, anything else stringified. */
|
|
472
|
+
function describeError(error) {
|
|
473
|
+
return error instanceof Error ? error.message : String(error);
|
|
474
|
+
}
|
|
471
475
|
/** Internal module-table key for one package-local chunk. */
|
|
472
476
|
function chunkId(ownerId, fileName) {
|
|
473
477
|
return `${ownerId}/${fileName}`;
|
|
@@ -517,6 +521,12 @@ window.__ModuleLoader__.load({
|
|
|
517
521
|
materializing = /* @__PURE__ */ new Set();
|
|
518
522
|
graphRows = /* @__PURE__ */ new Map();
|
|
519
523
|
loadBundle;
|
|
524
|
+
/** Last import or prefetch failure per graph row, cleared by a later success or invalidation. */
|
|
525
|
+
importErrors = /* @__PURE__ */ new Map();
|
|
526
|
+
/** Batch URLs whose transport or execution already failed; rows still missing from them go straight to their one-resource URL. */
|
|
527
|
+
failedBundleUrls = /* @__PURE__ */ new Set();
|
|
528
|
+
/** Every URL whose script has executed once; a batch among them is never requested again. */
|
|
529
|
+
executedBundleUrls = /* @__PURE__ */ new Set();
|
|
520
530
|
/**
|
|
521
531
|
* Build the module system over the parsed boot rows.
|
|
522
532
|
* @param options - Parsed graph, platform seed, bootstrap module, registration facade, and transport.
|
|
@@ -569,23 +579,66 @@ window.__ModuleLoader__.load({
|
|
|
569
579
|
rev: this.reloadTargets.get(ownerId)?.rev ?? this.graphRows.get(ownerId)?.rev
|
|
570
580
|
});
|
|
571
581
|
}
|
|
572
|
-
/**
|
|
573
|
-
|
|
574
|
-
const { id } = row;
|
|
575
|
-
if (this.loadCache.has(id) || this.factories.has(id)) return Promise.resolve();
|
|
576
|
-
const reload = this.reloadTargets.get(id);
|
|
577
|
-
const url = reload?.url ?? row.initialUrl;
|
|
582
|
+
/** Run one bundle transport per URL; every row waiting on the same URL shares the in-flight request. */
|
|
583
|
+
loadShared(url) {
|
|
578
584
|
let transport = this.pendingArrival.get(url);
|
|
579
585
|
if (transport === void 0) {
|
|
580
|
-
transport = this.loadBundle(url).
|
|
586
|
+
transport = this.loadBundle(url).then(() => {
|
|
587
|
+
this.executedBundleUrls.add(url);
|
|
588
|
+
}).finally(() => {
|
|
581
589
|
this.pendingArrival.delete(url);
|
|
582
590
|
});
|
|
583
591
|
this.pendingArrival.set(url, transport);
|
|
584
592
|
}
|
|
585
|
-
return transport
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
593
|
+
return transport;
|
|
594
|
+
}
|
|
595
|
+
/**
|
|
596
|
+
* Load one graph row so its factory is registered (idempotent per in-flight
|
|
597
|
+
* arrival). A batch script is one classic script that registers every
|
|
598
|
+
* package in sequence, and {@link register} rejects a second registration,
|
|
599
|
+
* so the two failure kinds differ: a transport failure (`error` event, nothing
|
|
600
|
+
* executed) is retried once on the same URL; a script that loaded without
|
|
601
|
+
* registering this row (a parse error registered nothing, or a runtime throw
|
|
602
|
+
* stopped it after registering others) is never re-executed, because a replay
|
|
603
|
+
* would stop again at the first duplicate registration; that holds even when
|
|
604
|
+
* the row that first imports from the batch is one it did register, because
|
|
605
|
+
* every executed batch URL is remembered. Either way the row then falls back
|
|
606
|
+
* to its own one-resource URL, which the Host serves for every package, so one
|
|
607
|
+
* failed batch costs at most three requests per missing row and never fails
|
|
608
|
+
* the rows that were registered.
|
|
609
|
+
*/
|
|
610
|
+
async arrive(row) {
|
|
611
|
+
const { id } = row;
|
|
612
|
+
if (this.loadCache.has(id) || this.factories.has(id)) return;
|
|
613
|
+
const reload = this.reloadTargets.get(id);
|
|
614
|
+
const preferred = reload?.url ?? row.initialUrl;
|
|
615
|
+
const fallback = reload === void 0 && row.url !== preferred ? row.url : void 0;
|
|
616
|
+
const failures = [];
|
|
617
|
+
const attempt = async (url) => {
|
|
618
|
+
try {
|
|
619
|
+
await this.loadShared(url);
|
|
620
|
+
} catch (error) {
|
|
621
|
+
failures.push(`${url}: ${describeError(error)}`);
|
|
622
|
+
return "transport-failed";
|
|
623
|
+
}
|
|
624
|
+
if (this.factories.has(id)) return "registered";
|
|
625
|
+
failures.push(`${url}: loaded without registering "${id}" via __ModuleLoader__.load`);
|
|
626
|
+
return "not-registered";
|
|
627
|
+
};
|
|
628
|
+
let outcome = "transport-failed";
|
|
629
|
+
if (this.failedBundleUrls.has(preferred)) failures.push(`${preferred}: skipped after an earlier failure of this bundle`);
|
|
630
|
+
else if (fallback !== void 0 && this.executedBundleUrls.has(preferred)) {
|
|
631
|
+
failures.push(`${preferred}: already executed without registering "${id}"`);
|
|
632
|
+
outcome = "not-registered";
|
|
633
|
+
this.failedBundleUrls.add(preferred);
|
|
634
|
+
} else {
|
|
635
|
+
outcome = await attempt(preferred);
|
|
636
|
+
if (outcome === "transport-failed") outcome = await attempt(preferred);
|
|
637
|
+
if (outcome !== "registered" && fallback !== void 0) this.failedBundleUrls.add(preferred);
|
|
638
|
+
}
|
|
639
|
+
if (outcome !== "registered" && fallback !== void 0) outcome = await attempt(fallback);
|
|
640
|
+
if (outcome !== "registered") throw new Error(`client-modules: could not load "${id}": ${failures.join("; ")}`);
|
|
641
|
+
if (reload !== void 0 && this.reloadTargets.get(id) === reload) this.reloadTargets.delete(id);
|
|
589
642
|
}
|
|
590
643
|
/** Register each injected package and unresolved dynamic request before its consumer. */
|
|
591
644
|
async arriveGraphRow(row, open = [], visited = /* @__PURE__ */ new Set()) {
|
|
@@ -598,14 +651,22 @@ window.__ModuleLoader__.load({
|
|
|
598
651
|
const id = stripClientSuffix(request);
|
|
599
652
|
if (this.seed.has(request) || this.loadCache.has(id)) continue;
|
|
600
653
|
const dependency = this.graphRows.get(id);
|
|
601
|
-
if (dependency !== void 0) await this.
|
|
654
|
+
if (dependency !== void 0) await this.arriveDependency(row.id, dependency, next, visited);
|
|
602
655
|
}
|
|
603
656
|
for (const packageName of row.inject) {
|
|
604
657
|
const dependency = this.graphRows.get(packageName);
|
|
605
|
-
if (dependency !== void 0) await this.
|
|
658
|
+
if (dependency !== void 0) await this.arriveDependency(row.id, dependency, [], visited);
|
|
606
659
|
}
|
|
607
660
|
await this.arrive(row);
|
|
608
661
|
}
|
|
662
|
+
/** Arrive one dependency, naming the consumer it failed for so a cascade reads as a chain, not as 44 unrelated failures. */
|
|
663
|
+
async arriveDependency(consumerId, dependency, open, visited) {
|
|
664
|
+
try {
|
|
665
|
+
await this.arriveGraphRow(dependency, open, visited);
|
|
666
|
+
} catch (error) {
|
|
667
|
+
throw new Error(`client-modules: "${consumerId}" not loaded because dependency "${dependency.id}" failed: ${describeError(error)}`, { cause: error });
|
|
668
|
+
}
|
|
669
|
+
}
|
|
609
670
|
/** Materialize a registered factory (synchronous; memoized in loadCache). */
|
|
610
671
|
materialize(id, ownerId = id) {
|
|
611
672
|
const existing = this.loadCache.get(id);
|
|
@@ -685,16 +746,40 @@ window.__ModuleLoader__.load({
|
|
|
685
746
|
const existing = this.loadCache.get(id);
|
|
686
747
|
if (existing !== void 0) return existing.exports;
|
|
687
748
|
const row = this.graphRows.get(id);
|
|
688
|
-
if (row
|
|
689
|
-
|
|
690
|
-
|
|
749
|
+
if (row === void 0) {
|
|
750
|
+
if (this.factories.has(id)) return this.materialize(id).exports;
|
|
751
|
+
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)`);
|
|
752
|
+
}
|
|
753
|
+
return this.recordingImportError(id, async () => {
|
|
754
|
+
await this.arriveGraphRow(row);
|
|
755
|
+
return this.materialize(id).exports;
|
|
756
|
+
});
|
|
691
757
|
}
|
|
692
758
|
async prefetch(id) {
|
|
693
759
|
const normalized = stripClientSuffix(id);
|
|
694
760
|
if (this.loadCache.has(normalized)) return;
|
|
695
761
|
const row = this.graphRows.get(normalized);
|
|
696
762
|
if (row === void 0) throw new Error(`client-modules: prefetch("${id}") — not a graph entry`);
|
|
697
|
-
await this.arriveGraphRow(row);
|
|
763
|
+
await this.recordingImportError(normalized, () => this.arriveGraphRow(row));
|
|
764
|
+
}
|
|
765
|
+
importError(id) {
|
|
766
|
+
return this.importErrors.get(stripClientSuffix(id));
|
|
767
|
+
}
|
|
768
|
+
/**
|
|
769
|
+
* Run one graph-row operation, recording its failure for the boot audit and
|
|
770
|
+
* clearing the record on success. Arrival and materialization both run in
|
|
771
|
+
* here, so a factory that throws is recorded as well as a bundle that never
|
|
772
|
+
* arrived; the Loader only sees a missing fiber either way.
|
|
773
|
+
*/
|
|
774
|
+
async recordingImportError(id, operation) {
|
|
775
|
+
try {
|
|
776
|
+
const result = await operation();
|
|
777
|
+
this.importErrors.delete(id);
|
|
778
|
+
return result;
|
|
779
|
+
} catch (error) {
|
|
780
|
+
this.importErrors.set(id, error instanceof Error ? error : new Error(describeError(error)));
|
|
781
|
+
throw error;
|
|
782
|
+
}
|
|
698
783
|
}
|
|
699
784
|
/** Refresh descriptors and unowned factory revisions before any entry imports its dependencies. */
|
|
700
785
|
updateManifest(manifest, managed) {
|
|
@@ -739,6 +824,7 @@ window.__ModuleLoader__.load({
|
|
|
739
824
|
invalidate(id, rev) {
|
|
740
825
|
const normalized = stripClientSuffix(id);
|
|
741
826
|
if (this.bootstrapIds.has(normalized)) return;
|
|
827
|
+
this.importErrors.delete(normalized);
|
|
742
828
|
this.generations.set(normalized, (this.generations.get(normalized) ?? 0) + 1);
|
|
743
829
|
const row = this.graphRows.get(normalized);
|
|
744
830
|
if (row !== void 0) {
|
|
@@ -92,7 +92,7 @@ export interface WebBootGraph {
|
|
|
92
92
|
export interface BootModuleRow {
|
|
93
93
|
/** Entry name == package name (module-table key). */
|
|
94
94
|
id: string;
|
|
95
|
-
/** Revisioned single-resource combo reference
|
|
95
|
+
/** Revisioned single-resource combo reference: the fallback when the row's batch fails and the reload target after HMR invalidation. */
|
|
96
96
|
url: string;
|
|
97
97
|
/** Content-addressed combo reference used before the first HMR invalidation. */
|
|
98
98
|
initialUrl: string;
|
|
@@ -266,6 +266,15 @@ export interface ClientModuleLoader {
|
|
|
266
266
|
* @param id - graph entry name.
|
|
267
267
|
*/
|
|
268
268
|
prefetch(id: string): Promise<void>;
|
|
269
|
+
/**
|
|
270
|
+
* The last failure of {@link import} or {@link prefetch} for one graph row:
|
|
271
|
+
* transport, registration, dependency cascade, or factory execution. Cleared
|
|
272
|
+
* by a later success and by {@link invalidate}. The boot audit reads it to
|
|
273
|
+
* report why a Loader entry has no fiber.
|
|
274
|
+
* @param id - graph entry name.
|
|
275
|
+
* @returns the recorded failure, or `undefined` when the row never failed or succeeded since.
|
|
276
|
+
*/
|
|
277
|
+
importError(id: string): Error | undefined;
|
|
269
278
|
/**
|
|
270
279
|
* Full reset of one non-bootstrap package: drop its entry and chunk factories
|
|
271
280
|
* and materialized records so the next prefetch/import loads its one-resource
|
|
@@ -25,6 +25,12 @@ export declare class ClientModuleSystem implements ClientModuleLoader {
|
|
|
25
25
|
private readonly materializing;
|
|
26
26
|
private readonly graphRows;
|
|
27
27
|
private readonly loadBundle;
|
|
28
|
+
/** Last import or prefetch failure per graph row, cleared by a later success or invalidation. */
|
|
29
|
+
private readonly importErrors;
|
|
30
|
+
/** Batch URLs whose transport or execution already failed; rows still missing from them go straight to their one-resource URL. */
|
|
31
|
+
private readonly failedBundleUrls;
|
|
32
|
+
/** Every URL whose script has executed once; a batch among them is never requested again. */
|
|
33
|
+
private readonly executedBundleUrls;
|
|
28
34
|
/**
|
|
29
35
|
* Build the module system over the parsed boot rows.
|
|
30
36
|
* @param options - Parsed graph, platform seed, bootstrap module, registration facade, and transport.
|
|
@@ -32,10 +38,28 @@ export declare class ClientModuleSystem implements ClientModuleLoader {
|
|
|
32
38
|
constructor(options: ClientModuleSystemOptions);
|
|
33
39
|
/** Register one bundle factory, rejecting a script that executes twice without invalidation. */
|
|
34
40
|
private register;
|
|
35
|
-
/**
|
|
41
|
+
/** Run one bundle transport per URL; every row waiting on the same URL shares the in-flight request. */
|
|
42
|
+
private loadShared;
|
|
43
|
+
/**
|
|
44
|
+
* Load one graph row so its factory is registered (idempotent per in-flight
|
|
45
|
+
* arrival). A batch script is one classic script that registers every
|
|
46
|
+
* package in sequence, and {@link register} rejects a second registration,
|
|
47
|
+
* so the two failure kinds differ: a transport failure (`error` event, nothing
|
|
48
|
+
* executed) is retried once on the same URL; a script that loaded without
|
|
49
|
+
* registering this row (a parse error registered nothing, or a runtime throw
|
|
50
|
+
* stopped it after registering others) is never re-executed, because a replay
|
|
51
|
+
* would stop again at the first duplicate registration; that holds even when
|
|
52
|
+
* the row that first imports from the batch is one it did register, because
|
|
53
|
+
* every executed batch URL is remembered. Either way the row then falls back
|
|
54
|
+
* to its own one-resource URL, which the Host serves for every package, so one
|
|
55
|
+
* failed batch costs at most three requests per missing row and never fails
|
|
56
|
+
* the rows that were registered.
|
|
57
|
+
*/
|
|
36
58
|
private arrive;
|
|
37
59
|
/** Register each injected package and unresolved dynamic request before its consumer. */
|
|
38
60
|
private arriveGraphRow;
|
|
61
|
+
/** Arrive one dependency, naming the consumer it failed for so a cascade reads as a chain, not as 44 unrelated failures. */
|
|
62
|
+
private arriveDependency;
|
|
39
63
|
/** Materialize a registered factory (synchronous; memoized in loadCache). */
|
|
40
64
|
private materialize;
|
|
41
65
|
/** Build the synchronous module-table require and its asynchronous chunk operation. */
|
|
@@ -44,6 +68,14 @@ export declare class ClientModuleSystem implements ClientModuleLoader {
|
|
|
44
68
|
private importChunk;
|
|
45
69
|
import(specifier: string): Promise<unknown>;
|
|
46
70
|
prefetch(id: string): Promise<void>;
|
|
71
|
+
importError(id: string): Error | undefined;
|
|
72
|
+
/**
|
|
73
|
+
* Run one graph-row operation, recording its failure for the boot audit and
|
|
74
|
+
* clearing the record on success. Arrival and materialization both run in
|
|
75
|
+
* here, so a factory that throws is recorded as well as a bundle that never
|
|
76
|
+
* arrived; the Loader only sees a missing fiber either way.
|
|
77
|
+
*/
|
|
78
|
+
private recordingImportError;
|
|
47
79
|
/** Refresh descriptors and unowned factory revisions before any entry imports its dependencies. */
|
|
48
80
|
private updateManifest;
|
|
49
81
|
/** Retain live Loader modules and their transitive requests before evicting unreferenced graph records. */
|
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.7-alpha.
|
|
4
|
+
"version": "0.1.7-alpha.2",
|
|
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/cordis-plugin-loader": "
|
|
42
|
-
"@deepseek-ai/dsh-host-webserver": "
|
|
43
|
-
"@deepseek-ai/
|
|
44
|
-
"@deepseek-ai/
|
|
45
|
-
"@deepseek-ai/dsh-
|
|
46
|
-
"@deepseek-ai/dsh-
|
|
41
|
+
"@deepseek-ai/cordis-plugin-loader": "~1.0.5",
|
|
42
|
+
"@deepseek-ai/dsh-host-webserver": "0.1.7-alpha.2",
|
|
43
|
+
"@deepseek-ai/cordis": "~4.0.4",
|
|
44
|
+
"@deepseek-ai/dsh-package-manifest": "0.1.7-alpha.2",
|
|
45
|
+
"@deepseek-ai/dsh-client-store": "0.1.7-alpha.2",
|
|
46
|
+
"@deepseek-ai/dsh-invariants": "0.1.7-alpha.2"
|
|
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": "
|
|
55
|
+
"@deepseek-ai/cordis": "~4.0.4"
|
|
56
56
|
},
|
|
57
57
|
"scripts": {
|
|
58
58
|
"bundle": "tsdown",
|