@deepseek-ai/dsh-client-modules 0.1.6-alpha.1 → 0.1.6-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 +16 -5
- package/README.zh.md +16 -5
- package/lib/client.js +407 -26
- package/lib/index.js +139 -72
- package/lib/types/client/entries.d.ts +76 -0
- package/lib/types/client/entry-lifecycle.d.ts +14 -0
- package/lib/types/client/index.d.ts +2 -0
- package/lib/types/client/manifest.d.ts +26 -14
- package/lib/types/client/system.d.ts +13 -8
- package/lib/types/index.d.ts +12 -7
- package/package.json +5 -4
package/lib/index.js
CHANGED
|
@@ -165,6 +165,8 @@ const COMBO_REVISION_PLACEHOLDER = "0".repeat(HASH_REVISION_LENGTH);
|
|
|
165
165
|
const SOURCE_MAP_TRAILER = /(?:\r?\n)?\/\/# sourceMappingURL=[^\r\n]*(?:\r?\n)?$/;
|
|
166
166
|
/** Debugger source name appended to page bundles in the WebWorker image. */
|
|
167
167
|
const SOURCE_URL_TRAILER = /(?:\r?\n)?\/\/# sourceURL=([^\r\n]+)(?:\r?\n)?$/;
|
|
168
|
+
/** Published package-local client chunk names accepted by the on-demand route. */
|
|
169
|
+
const CLIENT_CHUNK = /^client\.[A-Za-z0-9][A-Za-z0-9._-]*\.js$/;
|
|
168
170
|
/** Resolve `exports["./client"]` to a relative path, accepting the string and one-level conditional forms. */
|
|
169
171
|
function clientExportOf(pkgName, exportsField) {
|
|
170
172
|
if (typeof exportsField !== "object" || exportsField === null) return void 0;
|
|
@@ -187,14 +189,18 @@ function framedHash(domain, parts) {
|
|
|
187
189
|
for (const part of parts) hash.update(`${String(part.byteLength)}:`).update(part);
|
|
188
190
|
return hash.digest("hex").slice(0, HASH_REVISION_LENGTH);
|
|
189
191
|
}
|
|
190
|
-
/** Hash
|
|
191
|
-
function artifactRevision(bundle,
|
|
192
|
-
return framedHash("plugin-artifact",
|
|
192
|
+
/** Hash one completed build generation observed through its entry artifact. */
|
|
193
|
+
function artifactRevision(bundle, baseline) {
|
|
194
|
+
return framedHash("plugin-artifact", [bundle, Buffer.from(String(baseline.mtimeMs))]);
|
|
193
195
|
}
|
|
194
196
|
/** Address one ordered plugin-file list through the shared combo route. */
|
|
195
197
|
function comboUrl(ids, rev, sourceMap = false) {
|
|
196
198
|
return `/plugins/??${ids.map((id) => `${id}/client.js${sourceMap ? ".map" : ""}`).join(",")}&rev=${rev}`;
|
|
197
199
|
}
|
|
200
|
+
/** Address one package-local chunk through the same revision as its entry. */
|
|
201
|
+
function chunkUrl(id, fileName, rev, sourceMap = false) {
|
|
202
|
+
return `/plugins/${id}/${fileName}${sourceMap ? ".map" : ""}?rev=${rev}`;
|
|
203
|
+
}
|
|
198
204
|
/** Measure the longer map-form URL used to partition a startup resource list. */
|
|
199
205
|
function projectedComboUrlBytes(records) {
|
|
200
206
|
return Buffer.byteLength(comboUrl(records.map((record) => record.entry.id), COMBO_REVISION_PLACEHOLDER, true));
|
|
@@ -218,12 +224,12 @@ function partitionComboRecords(records) {
|
|
|
218
224
|
return chunks;
|
|
219
225
|
}
|
|
220
226
|
/** Remove bundle-local debug directives and retain their stable generated-file name. */
|
|
221
|
-
function
|
|
222
|
-
let source =
|
|
227
|
+
function prepareSource(resource) {
|
|
228
|
+
let source = resource.bundle.toString("utf8");
|
|
223
229
|
const sourceUrl = SOURCE_URL_TRAILER.exec(source)?.[1];
|
|
224
230
|
source = source.replace(SOURCE_URL_TRAILER, "").replace(SOURCE_MAP_TRAILER, "");
|
|
225
231
|
if (!source.endsWith("\n")) source += "\n";
|
|
226
|
-
const fallbackSource = sourceUrl === void 0 ? `/plugins/${
|
|
232
|
+
const fallbackSource = sourceUrl === void 0 ? `/plugins/${resource.id}/${resource.fileName}` : /^(?:[A-Za-z][A-Za-z\d+.-]*:|\/)/.test(sourceUrl) ? sourceUrl : `/${sourceUrl}`;
|
|
227
233
|
return {
|
|
228
234
|
source,
|
|
229
235
|
fallbackSource
|
|
@@ -233,8 +239,8 @@ function comboSource(record) {
|
|
|
233
239
|
function comboScript(input, sourceMapUrl) {
|
|
234
240
|
return Buffer.from(sourceMapUrl === void 0 ? input : `${input}//# sourceMappingURL=${sourceMapUrl}\n`);
|
|
235
241
|
}
|
|
236
|
-
/**
|
|
237
|
-
function
|
|
242
|
+
/** Read and parse an optional source map when its combo-map endpoint is requested. */
|
|
243
|
+
function readSourceMap(clientPath) {
|
|
238
244
|
let body;
|
|
239
245
|
try {
|
|
240
246
|
body = readFileSync(`${clientPath}.map`);
|
|
@@ -245,10 +251,7 @@ function sourceMapSnapshot(clientPath) {
|
|
|
245
251
|
const value = JSON.parse(body.toString("utf8"));
|
|
246
252
|
const parsed = typeof value === "object" && value !== null ? value : void 0;
|
|
247
253
|
if (parsed === void 0 || parsed.version !== 3 || !Array.isArray(parsed.sources) || parsed.sources.some((source) => typeof source !== "string") || !Array.isArray(parsed.names) || parsed.names.some((name) => typeof name !== "string") || typeof parsed.mappings !== "string") throw new Error(`client-modules: ${clientPath}.map is not a regular Source Map v3 object`);
|
|
248
|
-
return
|
|
249
|
-
body,
|
|
250
|
-
parsed
|
|
251
|
-
};
|
|
254
|
+
return parsed;
|
|
252
255
|
}
|
|
253
256
|
/** Count generated lines while assembling indexed-map section offsets. */
|
|
254
257
|
function newlineCount(value) {
|
|
@@ -257,13 +260,10 @@ function newlineCount(value) {
|
|
|
257
260
|
return count;
|
|
258
261
|
}
|
|
259
262
|
/** Resolve section sources against their original per-plugin map URL before combo relocation. */
|
|
260
|
-
function comboSectionMap(
|
|
261
|
-
const original = record.sourceMap?.parsed;
|
|
262
|
-
/* v8 ignore next -- callers add sections only for records with a source map. */
|
|
263
|
-
if (original === void 0) throw new Error(`client-modules: source map missing for ${record.entry.id}`);
|
|
263
|
+
function comboSectionMap(resource, original) {
|
|
264
264
|
const sourcePaths = original.sources;
|
|
265
265
|
const sourceRoot = typeof original.sourceRoot === "string" ? original.sourceRoot : "";
|
|
266
|
-
const base = new URL(`/plugins/${
|
|
266
|
+
const base = new URL(`/plugins/${resource.id}/client.js.map`, "http://dsh.invalid");
|
|
267
267
|
const relocated = sourcePaths.map((source) => {
|
|
268
268
|
const separator = sourceRoot !== "" && !sourceRoot.endsWith("/") && !source.startsWith("/") ? "/" : "";
|
|
269
269
|
const resolved = new URL(`${sourceRoot}${separator}${source}`, base);
|
|
@@ -287,14 +287,35 @@ function identitySectionMap(source, sourceUrl) {
|
|
|
287
287
|
mappings
|
|
288
288
|
};
|
|
289
289
|
}
|
|
290
|
-
/**
|
|
291
|
-
function
|
|
290
|
+
/** Run one producer in the first requester's microtask, not off-thread, and share its settlement. */
|
|
291
|
+
function lazyBody(produce) {
|
|
292
|
+
let result;
|
|
293
|
+
return () => {
|
|
294
|
+
result ??= Promise.resolve().then(produce);
|
|
295
|
+
return result;
|
|
296
|
+
};
|
|
297
|
+
}
|
|
298
|
+
/** Derive one combo revision from the ordered immutable row revisions. */
|
|
299
|
+
function comboRevision(resources) {
|
|
300
|
+
return framedHash("combo", resources.flatMap((resource) => [Buffer.from(resource.id), Buffer.from(resource.rev)]));
|
|
301
|
+
}
|
|
302
|
+
/** Concatenate one or more factory registrations without reading or composing source maps. */
|
|
303
|
+
function buildComboScript(resources, sourceMapUrl) {
|
|
292
304
|
let source = "";
|
|
305
|
+
for (const resource of resources) source += `${prepareSource(resource).source};\n`;
|
|
306
|
+
return comboScript(source, sourceMapUrl);
|
|
307
|
+
}
|
|
308
|
+
/** Compose one indexed map from source-map files read only for this request. */
|
|
309
|
+
function buildComboSourceMap(resources, sourceMapOf, fileName = "client.js") {
|
|
293
310
|
const sections = [];
|
|
294
311
|
let line = 0;
|
|
295
|
-
for (const
|
|
296
|
-
const prepared =
|
|
297
|
-
const
|
|
312
|
+
for (const resource of resources) {
|
|
313
|
+
const prepared = prepareSource(resource);
|
|
314
|
+
const sourceMap = sourceMapOf(resource.clientPath);
|
|
315
|
+
let section = identitySectionMap(prepared.source, prepared.fallbackSource);
|
|
316
|
+
if (sourceMap !== void 0) try {
|
|
317
|
+
section = comboSectionMap(resource, sourceMap);
|
|
318
|
+
} catch {}
|
|
298
319
|
sections.push({
|
|
299
320
|
offset: {
|
|
300
321
|
line,
|
|
@@ -302,32 +323,39 @@ function buildCombo(records, revision) {
|
|
|
302
323
|
},
|
|
303
324
|
map: section
|
|
304
325
|
});
|
|
305
|
-
|
|
306
|
-
source += bundle;
|
|
307
|
-
line += newlineCount(bundle);
|
|
326
|
+
line += newlineCount(`${prepared.source};\n`);
|
|
308
327
|
}
|
|
309
|
-
|
|
328
|
+
return Buffer.from(`${JSON.stringify({
|
|
310
329
|
version: 3,
|
|
311
|
-
file:
|
|
330
|
+
file: fileName,
|
|
312
331
|
sections
|
|
313
332
|
})}\n`);
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
333
|
+
}
|
|
334
|
+
/** Describe one combo and defer its executable and debug payloads independently. */
|
|
335
|
+
function buildCombo(records, sourceMapOf, revision) {
|
|
336
|
+
const resources = records.map((record) => ({
|
|
337
|
+
id: record.entry.id,
|
|
338
|
+
rev: record.entry.rev,
|
|
339
|
+
clientPath: record.meta.clientPath,
|
|
340
|
+
fileName: "client.js",
|
|
341
|
+
bundle: record.bundle
|
|
342
|
+
}));
|
|
343
|
+
const rev = revision ?? comboRevision(resources);
|
|
344
|
+
const entries = resources.map((resource) => resource.id);
|
|
317
345
|
const url = comboUrl(entries, rev);
|
|
318
346
|
const sourceMapUrl = comboUrl(entries, rev, true);
|
|
319
347
|
return {
|
|
320
348
|
url,
|
|
321
349
|
rev,
|
|
322
350
|
entries,
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
351
|
+
sourceMapUrl,
|
|
352
|
+
scriptBody: lazyBody(() => buildComboScript(resources, sourceMapUrl)),
|
|
353
|
+
sourceMapBody: lazyBody(() => buildComboSourceMap(resources, sourceMapOf))
|
|
326
354
|
};
|
|
327
355
|
}
|
|
328
356
|
/** Add initial-load scheduling metadata to a combo artifact. */
|
|
329
|
-
function buildBatch(phase, records) {
|
|
330
|
-
const artifact = buildCombo(records);
|
|
357
|
+
function buildBatch(phase, records, sourceMapOf) {
|
|
358
|
+
const artifact = buildCombo(records, sourceMapOf);
|
|
331
359
|
return {
|
|
332
360
|
...artifact,
|
|
333
361
|
descriptor: {
|
|
@@ -521,12 +549,13 @@ var ClientModuleRegistry = class extends Service {
|
|
|
521
549
|
/**
|
|
522
550
|
* Serve an advertised revisioned bundle or source map without a Web server.
|
|
523
551
|
* Unknown URLs return 404, unsupported methods return 405, and `HEAD`
|
|
524
|
-
* returns the same immutable headers without a body.
|
|
552
|
+
* returns the same immutable headers without materializing a body. Each body
|
|
553
|
+
* is built once on its first `GET`; script construction never reads maps.
|
|
525
554
|
* @param request - shell-carrier request for a `/plugins` resource.
|
|
526
555
|
* @returns the exact response also exposed by the optional Web route.
|
|
527
556
|
*/
|
|
528
|
-
fetchBundle(request) {
|
|
529
|
-
const resource = this.bundleResource(request.method, request.url);
|
|
557
|
+
async fetchBundle(request) {
|
|
558
|
+
const resource = await this.bundleResource(request.method, request.url);
|
|
530
559
|
const body = resource.body === void 0 ? null : Uint8Array.from(resource.body);
|
|
531
560
|
return new Response(body, {
|
|
532
561
|
status: resource.status,
|
|
@@ -546,8 +575,8 @@ var ClientModuleRegistry = class extends Service {
|
|
|
546
575
|
return baseline === void 0 ? void 0 : { ...baseline };
|
|
547
576
|
}
|
|
548
577
|
/**
|
|
549
|
-
*
|
|
550
|
-
* point through which
|
|
578
|
+
* Publish one completed bundle generation (the HMR watch's registration
|
|
579
|
+
* hook — the only entry point through which build changes reach the graph).
|
|
551
580
|
* @param id - entry id (package name).
|
|
552
581
|
* @returns the new rev, or undefined for an unknown id.
|
|
553
582
|
*/
|
|
@@ -556,14 +585,11 @@ var ClientModuleRegistry = class extends Service {
|
|
|
556
585
|
if (record === void 0) return void 0;
|
|
557
586
|
const baseline = this.captureArtifactBaseline(record.meta.clientPath);
|
|
558
587
|
const bundle = readFileSync(record.meta.clientPath);
|
|
559
|
-
const
|
|
560
|
-
const rev = artifactRevision(bundle, sourceMap);
|
|
588
|
+
const rev = artifactRevision(bundle, baseline);
|
|
561
589
|
record.baseline = baseline;
|
|
562
590
|
if (rev === record.entry.rev) return rev;
|
|
563
591
|
record.entry = graphRow(id, rev, record.meta);
|
|
564
592
|
record.bundle = bundle;
|
|
565
|
-
if (sourceMap === void 0) delete record.sourceMap;
|
|
566
|
-
else record.sourceMap = sourceMap;
|
|
567
593
|
this.composed = this.compose();
|
|
568
594
|
for (const notify of this.rebuildListeners) try {
|
|
569
595
|
notify(id, rev);
|
|
@@ -602,31 +628,32 @@ var ClientModuleRegistry = class extends Service {
|
|
|
602
628
|
const bootstrapIds = new Set(bootstrap.map((record) => record.entry.id));
|
|
603
629
|
const application = entries.filter((entry) => !bootstrapIds.has(entry.id)).map((entry) => this.table.get(entry.id)).filter((record) => record !== void 0);
|
|
604
630
|
const artifacts = [];
|
|
605
|
-
for (const records of partitionComboRecords(bootstrap)) artifacts.push(buildBatch("bootstrap", records));
|
|
606
|
-
for (const records of partitionComboRecords(application)) artifacts.push(buildBatch("application", records));
|
|
631
|
+
for (const records of partitionComboRecords(bootstrap)) artifacts.push(buildBatch("bootstrap", records, this.readSourceMap));
|
|
632
|
+
for (const records of partitionComboRecords(application)) artifacts.push(buildBatch("application", records, this.readSourceMap));
|
|
607
633
|
const batchResponses = /* @__PURE__ */ new Map();
|
|
608
634
|
for (const artifact of artifacts) {
|
|
609
|
-
batchResponses.set(artifact.descriptor.url, {
|
|
610
|
-
body: artifact.
|
|
635
|
+
batchResponses.set(artifact.descriptor.url, this.responses.get(artifact.descriptor.url) ?? {
|
|
636
|
+
body: artifact.scriptBody,
|
|
611
637
|
contentType: "text/javascript; charset=utf-8"
|
|
612
638
|
});
|
|
613
|
-
batchResponses.set(artifact.sourceMapUrl, {
|
|
614
|
-
body: artifact.
|
|
639
|
+
batchResponses.set(artifact.sourceMapUrl, this.responses.get(artifact.sourceMapUrl) ?? {
|
|
640
|
+
body: artifact.sourceMapBody,
|
|
615
641
|
contentType: "application/json; charset=utf-8"
|
|
616
642
|
});
|
|
617
643
|
}
|
|
618
644
|
const responses = new Map(batchResponses);
|
|
619
645
|
for (const record of this.table.values()) {
|
|
620
|
-
const artifact = buildCombo([record], record.entry.rev);
|
|
621
|
-
responses.set(artifact.url, {
|
|
622
|
-
body: artifact.
|
|
646
|
+
const artifact = buildCombo([record], this.readSourceMap, record.entry.rev);
|
|
647
|
+
responses.set(artifact.url, responses.get(artifact.url) ?? this.responses.get(artifact.url) ?? {
|
|
648
|
+
body: artifact.scriptBody,
|
|
623
649
|
contentType: "text/javascript; charset=utf-8"
|
|
624
650
|
});
|
|
625
|
-
responses.set(artifact.sourceMapUrl, {
|
|
626
|
-
body: artifact.
|
|
651
|
+
responses.set(artifact.sourceMapUrl, responses.get(artifact.sourceMapUrl) ?? this.responses.get(artifact.sourceMapUrl) ?? {
|
|
652
|
+
body: artifact.sourceMapBody,
|
|
627
653
|
contentType: "application/json; charset=utf-8"
|
|
628
654
|
});
|
|
629
655
|
}
|
|
656
|
+
for (const [resourceUrl, response] of this.responses) if (this.chunkRequest(new URL(resourceUrl, "http://x")) !== void 0) responses.set(resourceUrl, response);
|
|
630
657
|
this.previousBatchResponses = this.batchResponses;
|
|
631
658
|
this.batchResponses = batchResponses;
|
|
632
659
|
this.responses = responses;
|
|
@@ -754,7 +781,7 @@ var ClientModuleRegistry = class extends Service {
|
|
|
754
781
|
return `${this.initialRevisionNonce}-${String(this.nextInitialRevision++)}`;
|
|
755
782
|
}
|
|
756
783
|
/**
|
|
757
|
-
* Read the activation-time bundle
|
|
784
|
+
* Read the activation-time bundle snapshot.
|
|
758
785
|
* @param pkgName - package that declares the client bundle.
|
|
759
786
|
* @param clientPath - absolute path of the built client artifact.
|
|
760
787
|
* @returns the immutable bytes plus the pre-read filesystem baseline.
|
|
@@ -763,27 +790,24 @@ var ClientModuleRegistry = class extends Service {
|
|
|
763
790
|
initialBundleSnapshot(pkgName, clientPath) {
|
|
764
791
|
try {
|
|
765
792
|
const baseline = this.captureArtifactBaseline(clientPath);
|
|
766
|
-
const bundle = readFileSync(clientPath);
|
|
767
|
-
const sourceMap = this.readSourceMapSnapshot(clientPath);
|
|
768
793
|
return {
|
|
769
|
-
bundle,
|
|
770
|
-
baseline
|
|
771
|
-
...sourceMap === void 0 ? {} : { sourceMap }
|
|
794
|
+
bundle: readFileSync(clientPath),
|
|
795
|
+
baseline
|
|
772
796
|
};
|
|
773
797
|
} catch (error) {
|
|
774
798
|
if (error.code !== "ENOENT") throw error;
|
|
775
799
|
throw new MissingClientBundleError(pkgName, clientPath, error);
|
|
776
800
|
}
|
|
777
801
|
}
|
|
778
|
-
/** Treat a missing, torn, or malformed development map as an identity
|
|
779
|
-
|
|
802
|
+
/** Treat a missing, torn, or malformed development map as an identity section. */
|
|
803
|
+
readSourceMap = (clientPath) => {
|
|
780
804
|
try {
|
|
781
|
-
return
|
|
805
|
+
return readSourceMap(clientPath);
|
|
782
806
|
} catch (error) {
|
|
783
807
|
this.ctx.logger.warn(error);
|
|
784
808
|
return;
|
|
785
809
|
}
|
|
786
|
-
}
|
|
810
|
+
};
|
|
787
811
|
/** Reconcile one entry name against the live Loader sources. @returns whether the table changed. */
|
|
788
812
|
processOne(entryName, onError) {
|
|
789
813
|
const nextSources = /* @__PURE__ */ new Map();
|
|
@@ -841,8 +865,7 @@ var ClientModuleRegistry = class extends Service {
|
|
|
841
865
|
sourceKey: source.sourceKey,
|
|
842
866
|
meta: source.meta,
|
|
843
867
|
bundle: snapshot.bundle,
|
|
844
|
-
baseline: snapshot.baseline
|
|
845
|
-
...snapshot.sourceMap === void 0 ? {} : { sourceMap: snapshot.sourceMap }
|
|
868
|
+
baseline: snapshot.baseline
|
|
846
869
|
});
|
|
847
870
|
return true;
|
|
848
871
|
}
|
|
@@ -867,24 +890,68 @@ var ClientModuleRegistry = class extends Service {
|
|
|
867
890
|
this.composed = composed;
|
|
868
891
|
this.notifyGraphChanged();
|
|
869
892
|
}
|
|
870
|
-
|
|
893
|
+
/** Match an exact current-revision package-local chunk URL without reading its file. */
|
|
894
|
+
chunkRequest(requestUrl) {
|
|
895
|
+
const resourceUrl = `${requestUrl.pathname}${requestUrl.search}`;
|
|
896
|
+
for (const record of this.table.values()) {
|
|
897
|
+
const prefix = `/plugins/${record.entry.id}/`;
|
|
898
|
+
if (!requestUrl.pathname.startsWith(prefix)) continue;
|
|
899
|
+
const requested = requestUrl.pathname.slice(prefix.length);
|
|
900
|
+
const sourceMap = requested.endsWith(".map");
|
|
901
|
+
const fileName = sourceMap ? requested.slice(0, -4) : requested;
|
|
902
|
+
if (!CLIENT_CHUNK.test(fileName)) return void 0;
|
|
903
|
+
if (resourceUrl !== chunkUrl(record.entry.id, fileName, record.entry.rev, sourceMap)) return void 0;
|
|
904
|
+
return {
|
|
905
|
+
record,
|
|
906
|
+
fileName,
|
|
907
|
+
sourceMap,
|
|
908
|
+
resourceUrl
|
|
909
|
+
};
|
|
910
|
+
}
|
|
911
|
+
}
|
|
912
|
+
/** Build a package-local chunk response only when its URL is requested. */
|
|
913
|
+
chunkResponse(requestUrl) {
|
|
914
|
+
const request = this.chunkRequest(requestUrl);
|
|
915
|
+
if (request === void 0) return void 0;
|
|
916
|
+
const { record, fileName, sourceMap, resourceUrl } = request;
|
|
917
|
+
const clientPath = join(dirname(record.meta.clientPath), fileName);
|
|
918
|
+
if (!existsSync(clientPath)) return void 0;
|
|
919
|
+
const sourceMapUrl = chunkUrl(record.entry.id, fileName, record.entry.rev, true);
|
|
920
|
+
const resource = () => ({
|
|
921
|
+
id: record.entry.id,
|
|
922
|
+
rev: record.entry.rev,
|
|
923
|
+
clientPath,
|
|
924
|
+
fileName,
|
|
925
|
+
bundle: readFileSync(clientPath)
|
|
926
|
+
});
|
|
927
|
+
const response = sourceMap ? {
|
|
928
|
+
body: lazyBody(() => buildComboSourceMap([resource()], this.readSourceMap, fileName)),
|
|
929
|
+
contentType: "application/json; charset=utf-8"
|
|
930
|
+
} : {
|
|
931
|
+
body: lazyBody(() => buildComboScript([resource()], sourceMapUrl)),
|
|
932
|
+
contentType: "text/javascript; charset=utf-8"
|
|
933
|
+
};
|
|
934
|
+
this.responses.set(resourceUrl, response);
|
|
935
|
+
return response;
|
|
936
|
+
}
|
|
937
|
+
async bundleResource(method, url) {
|
|
871
938
|
if (method !== "GET" && method !== "HEAD") return { status: 405 };
|
|
872
939
|
const requestUrl = new URL(url, "http://x");
|
|
873
940
|
const resourceUrl = `${requestUrl.pathname}${requestUrl.search}`;
|
|
874
|
-
const response = this.responses.get(resourceUrl) ?? this.previousBatchResponses.get(resourceUrl);
|
|
941
|
+
const response = this.responses.get(resourceUrl) ?? this.previousBatchResponses.get(resourceUrl) ?? this.chunkResponse(requestUrl);
|
|
875
942
|
if (response !== void 0) return {
|
|
876
943
|
status: 200,
|
|
877
944
|
headers: {
|
|
878
945
|
"content-type": response.contentType,
|
|
879
946
|
"cache-control": IMMUTABLE_CACHE
|
|
880
947
|
},
|
|
881
|
-
...method === "HEAD" ? {} : { body: response.body }
|
|
948
|
+
...method === "HEAD" ? {} : { body: await response.body() }
|
|
882
949
|
};
|
|
883
950
|
return { status: 404 };
|
|
884
951
|
}
|
|
885
|
-
serveBundle = (req, res) => {
|
|
952
|
+
serveBundle = async (req, res) => {
|
|
886
953
|
/* v8 ignore next -- `?? '/'` arm: node:http always sets url on server requests. */
|
|
887
|
-
const response = this.bundleResource(req.method, req.url ?? "/");
|
|
954
|
+
const response = await this.bundleResource(req.method, req.url ?? "/");
|
|
888
955
|
res.writeHead(response.status, response.headers);
|
|
889
956
|
res.end(response.body);
|
|
890
957
|
};
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import type { Loader } from '@deepseek-ai/cordis-plugin-loader';
|
|
2
|
+
import type { ObservableSnapshot } from '@deepseek-ai/dsh-client-store';
|
|
3
|
+
import type { BootManifest, ClientModuleLoader } from './manifest.ts';
|
|
4
|
+
/** Page-local failures do not change the Host's bundle enablement. */
|
|
5
|
+
export interface ClientEntryState {
|
|
6
|
+
/** True while a snapshot, retry or code replacement is being applied. */
|
|
7
|
+
readonly syncing: boolean;
|
|
8
|
+
/** Package ids and errors from the latest reconciliation. */
|
|
9
|
+
readonly failures: readonly {
|
|
10
|
+
readonly id: string;
|
|
11
|
+
readonly message: string;
|
|
12
|
+
}[];
|
|
13
|
+
}
|
|
14
|
+
/** Module-table capabilities used within serialized entry operations. */
|
|
15
|
+
interface ModuleIndex {
|
|
16
|
+
update(manifest: BootManifest, managed: Iterable<string>): void;
|
|
17
|
+
invalidateForReplacement(id: string, rev: string): void;
|
|
18
|
+
prune(roots: Iterable<string>): void;
|
|
19
|
+
}
|
|
20
|
+
/** Manages only entries created from the Host manifest; other Loader contributors retain ownership. */
|
|
21
|
+
export declare class ClientEntries {
|
|
22
|
+
private readonly modules;
|
|
23
|
+
private readonly index;
|
|
24
|
+
/** Stable observable consumed by page diagnostics through the renderer's injected hook. */
|
|
25
|
+
readonly state: ObservableSnapshot<ClientEntryState>;
|
|
26
|
+
private snapshot;
|
|
27
|
+
private readonly listeners;
|
|
28
|
+
private readonly managed;
|
|
29
|
+
private readonly revisions;
|
|
30
|
+
private loader;
|
|
31
|
+
private queue;
|
|
32
|
+
private desired;
|
|
33
|
+
private generation;
|
|
34
|
+
private stopped;
|
|
35
|
+
/**
|
|
36
|
+
* Construct the page controller before Cordis boot.
|
|
37
|
+
* @param modules - Module arrival and materialization owner.
|
|
38
|
+
* @param index - Private descriptor replacement and unused-module cleanup.
|
|
39
|
+
*/
|
|
40
|
+
constructor(modules: ClientModuleLoader, index: ModuleIndex);
|
|
41
|
+
/**
|
|
42
|
+
* Create the initial roster and retain its entry identities for subsequent reconciliation.
|
|
43
|
+
* @param loader - Page Loader, already configured with the module system.
|
|
44
|
+
* @param manifest - Initial roster audited by the boot caller.
|
|
45
|
+
* @returns after initial entries and their activation settle; boot owns its activation audit.
|
|
46
|
+
*/
|
|
47
|
+
start(loader: Loader, manifest: BootManifest): Promise<void>;
|
|
48
|
+
/**
|
|
49
|
+
* Validate and apply the latest full Host graph. Changed targets cancel obsolete mounts; identical targets share pending loads.
|
|
50
|
+
* @param graph - JSON-decoded graph received from the Host.
|
|
51
|
+
* @returns after the queued reconciliation; per-package failures remain available in {@link state}.
|
|
52
|
+
*/
|
|
53
|
+
sync(graph: unknown): Promise<void>;
|
|
54
|
+
/**
|
|
55
|
+
* Retry failed entries against the latest graph, including an unchanged revision.
|
|
56
|
+
* @returns after retry settlement, with remaining errors in {@link state}.
|
|
57
|
+
*/
|
|
58
|
+
retry(): Promise<void>;
|
|
59
|
+
/**
|
|
60
|
+
* Replace one entry's code in the same queue as graph updates; duplicate revisions are ignored.
|
|
61
|
+
* Entries missing after a failed import are reconciled; bootstrap replacement fails before teardown.
|
|
62
|
+
* @param id - Package id from a rebuilt frame.
|
|
63
|
+
* @param rev - Opaque revision selecting the rebuilt artifact.
|
|
64
|
+
* @returns after queued work; replacement errors reject, while per-package reconciliation errors remain in {@link state}.
|
|
65
|
+
*/
|
|
66
|
+
reload(id: string, rev: string): Promise<void>;
|
|
67
|
+
private publish;
|
|
68
|
+
private enqueue;
|
|
69
|
+
private current;
|
|
70
|
+
/** Keep ownership even when Loader rejects a module's plugin exports after inserting its entry. */
|
|
71
|
+
private create;
|
|
72
|
+
private replace;
|
|
73
|
+
private reconcile;
|
|
74
|
+
}
|
|
75
|
+
export {};
|
|
76
|
+
//# sourceMappingURL=entries.d.ts.map
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/** Loader lifecycle operations shared by live graph reconciliation and code replacement. */
|
|
2
|
+
import type { Entry } from '@deepseek-ai/cordis-plugin-loader';
|
|
3
|
+
/**
|
|
4
|
+
* Release a runtime before clearing its entry fiber so Loader refresh can import new code.
|
|
5
|
+
* Registry deletion prevents Loader from treating replacement as a user disable.
|
|
6
|
+
* @param entry - Entry retained for code replacement.
|
|
7
|
+
*/
|
|
8
|
+
export declare function tearDownEntryFiber(entry: Entry): Promise<void>;
|
|
9
|
+
/**
|
|
10
|
+
* Remove styles after their plugin's effect cleanup has settled.
|
|
11
|
+
* @param id - Package whose factory owns the style tags.
|
|
12
|
+
*/
|
|
13
|
+
export declare function removeOwnedStyles(id: string): void;
|
|
14
|
+
//# sourceMappingURL=entry-lifecycle.d.ts.map
|
|
@@ -14,6 +14,8 @@ import type { Context } from '@deepseek-ai/cordis';
|
|
|
14
14
|
import { ClientModuleSystem } from './system.ts';
|
|
15
15
|
import type { ClientBootstrapModule, ClientModuleCreateOptions, ClientModuleLoaderTarget } from './manifest.ts';
|
|
16
16
|
export { ClientModuleSystem };
|
|
17
|
+
export { tearDownEntryFiber } from './entry-lifecycle.ts';
|
|
18
|
+
export type { ClientEntries, ClientEntryState } from './entries.ts';
|
|
17
19
|
export { exactPackageSpecifier, parseBootManifest, parseDshClient, stripClientSuffix } from './manifest.ts';
|
|
18
20
|
export type { BootManifest, BootModuleRow, BootPluginRow, ClientBootstrapModule, ClientBundleRegistration, ClientModuleCreateOptions, ClientModuleLoader, ClientModuleLoaderTarget, ClientModuleRecord, ClientModuleSystemOptions, DshWindow, WebBootEntry, WebBootGraph, } from './manifest.ts';
|
|
19
21
|
/**
|
|
@@ -29,6 +29,7 @@
|
|
|
29
29
|
* composes the wire.
|
|
30
30
|
*/
|
|
31
31
|
import type { DshClientManifest } from '@deepseek-ai/dsh-package-manifest';
|
|
32
|
+
import type { ClientEntries } from './entries.ts';
|
|
32
33
|
import type { ClientModuleSystem } from './system.ts';
|
|
33
34
|
declare module '@deepseek-ai/cordis' {
|
|
34
35
|
interface Context {
|
|
@@ -58,22 +59,22 @@ export interface WebBootEntry {
|
|
|
58
59
|
/** Non-baseline module specifiers this row requests; omitted when it requests none. */
|
|
59
60
|
external?: string[];
|
|
60
61
|
}
|
|
61
|
-
/** Initial scheduling phase for one
|
|
62
|
+
/** Initial scheduling phase for one revisioned combo script. */
|
|
62
63
|
export type WebBootBatchPhase = 'bootstrap' | 'application';
|
|
63
64
|
/** One initial combo script; a scheduling phase may span several descriptors. */
|
|
64
65
|
export interface WebBootBatch {
|
|
65
66
|
/** Parser-blocking bootstrap or preloaded application scheduling. */
|
|
66
67
|
phase: WebBootBatchPhase;
|
|
67
|
-
/**
|
|
68
|
+
/** Revisioned combo script endpoint. */
|
|
68
69
|
url: string;
|
|
69
|
-
/** Revision
|
|
70
|
+
/** Revision derived from the ordered entry revisions. */
|
|
70
71
|
rev: string;
|
|
71
72
|
/** Graph entry ids whose factories the script registers, in execution order. */
|
|
72
73
|
entries: string[];
|
|
73
74
|
}
|
|
74
75
|
/** The composed client entry graph the host injects as `window.__DSH_BOOT__`. */
|
|
75
76
|
export interface WebBootGraph {
|
|
76
|
-
/** Consistency anchor over the
|
|
77
|
+
/** Consistency anchor over the current entry and batch descriptors. */
|
|
77
78
|
rev: string;
|
|
78
79
|
/**
|
|
79
80
|
* Composed entries in module-graph order — a dynamic package row precedes
|
|
@@ -90,7 +91,7 @@ export interface BootModuleRow {
|
|
|
90
91
|
id: string;
|
|
91
92
|
/** Revisioned single-resource combo endpoint used after HMR invalidation. */
|
|
92
93
|
url: string;
|
|
93
|
-
/**
|
|
94
|
+
/** Revisioned combo endpoint used before the first HMR invalidation. */
|
|
94
95
|
initialUrl: string;
|
|
95
96
|
/** Opaque plugin-artifact revision used after HMR invalidation. */
|
|
96
97
|
rev: string;
|
|
@@ -162,16 +163,25 @@ export declare function stripClientSuffix(spec: string): string;
|
|
|
162
163
|
* @returns the manifest with optional plugin-view fields normalized.
|
|
163
164
|
*/
|
|
164
165
|
export declare function parseBootManifest(wire: unknown): BootManifest;
|
|
166
|
+
/** Module resolver passed into a registered Client bundle factory. */
|
|
167
|
+
export interface ClientBundleRequire {
|
|
168
|
+
/** Resolve a module-table dependency synchronously. */
|
|
169
|
+
(specifier: string): unknown;
|
|
170
|
+
/** Load and resolve a package-local dynamic chunk asynchronously. */
|
|
171
|
+
async(specifier: string): Promise<unknown>;
|
|
172
|
+
}
|
|
165
173
|
/** One client bundle's factory registration submitted through `window.__ModuleLoader__.load`. */
|
|
166
174
|
export interface ClientBundleRegistration {
|
|
167
175
|
/** Plugin id (package name) — the registration key; must match the graph row being executed. */
|
|
168
176
|
id: string;
|
|
177
|
+
/** Package-local chunk filename; absent for the package's `client.js` entry. */
|
|
178
|
+
chunk?: string;
|
|
169
179
|
/**
|
|
170
|
-
* Closure factory holding the whole bundle body: receives the
|
|
171
|
-
* require
|
|
172
|
-
* once, at materialization.
|
|
180
|
+
* Closure factory holding the whole bundle body: receives the module-table
|
|
181
|
+
* require whose `async` operation loads generated chunks, and returns the
|
|
182
|
+
* bundle's exports. The factory runs once, at materialization.
|
|
173
183
|
*/
|
|
174
|
-
factory: (require:
|
|
184
|
+
factory: (require: ClientBundleRequire) => Record<string, unknown>;
|
|
175
185
|
}
|
|
176
186
|
/** Inputs passed by the web entry when it creates the client module system. */
|
|
177
187
|
export interface ClientModuleCreateOptions {
|
|
@@ -226,9 +236,11 @@ export interface ClientModuleRecord {
|
|
|
226
236
|
export interface ClientModuleLoader {
|
|
227
237
|
/** Discriminant against Node's internal loader shapes ('v1'/'v2'). */
|
|
228
238
|
version: 'client';
|
|
229
|
-
/**
|
|
239
|
+
/** Latest parsed Host graph, updated by live entry reconciliation. */
|
|
230
240
|
manifest: BootManifest;
|
|
231
|
-
/**
|
|
241
|
+
/** Page-owned entry reconciliation, shared by boot, graph updates and HMR. */
|
|
242
|
+
entries: ClientEntries;
|
|
243
|
+
/** Materialized-module registry: entry or package-local chunk id → record. */
|
|
232
244
|
loadCache: Map<string, ClientModuleRecord>;
|
|
233
245
|
/**
|
|
234
246
|
* Internal contract consumed by the vendored Loader's `tree.import`. Resolves
|
|
@@ -252,8 +264,8 @@ export interface ClientModuleLoader {
|
|
|
252
264
|
*/
|
|
253
265
|
prefetch(id: string): Promise<void>;
|
|
254
266
|
/**
|
|
255
|
-
* Full reset of one non-bootstrap
|
|
256
|
-
* materialized
|
|
267
|
+
* Full reset of one non-bootstrap package: drop its entry and chunk factories
|
|
268
|
+
* and materialized records so the next prefetch/import loads its one-resource
|
|
257
269
|
* combo script rather than the initial multi-resource request. The bootstrap
|
|
258
270
|
* module remains materialized.
|
|
259
271
|
* @param id - entry name to invalidate.
|
|
@@ -264,7 +276,7 @@ export interface ClientModuleLoader {
|
|
|
264
276
|
}
|
|
265
277
|
/** Internal construction inputs assembled by the modules bundle's bootstrap export. */
|
|
266
278
|
export interface ClientModuleSystemOptions {
|
|
267
|
-
/**
|
|
279
|
+
/** Boot graph validated by {@link parseBootManifest}, owned by the resulting module system. */
|
|
268
280
|
manifest: BootManifest;
|
|
269
281
|
/** Module-table seed: platform-singleton specifier → shell instance. */
|
|
270
282
|
staticModules: Record<string, unknown>;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { ClientEntries } from './entries.ts';
|
|
1
2
|
import type { BootManifest, ClientModuleLoader, ClientModuleRecord, ClientModuleSystemOptions } from './manifest.ts';
|
|
2
3
|
/**
|
|
3
4
|
* The client module system: state tables plus the arrival/materialization
|
|
@@ -8,15 +9,18 @@ import type { BootManifest, ClientModuleLoader, ClientModuleRecord, ClientModule
|
|
|
8
9
|
*/
|
|
9
10
|
export declare class ClientModuleSystem implements ClientModuleLoader {
|
|
10
11
|
readonly version = "client";
|
|
11
|
-
|
|
12
|
+
manifest: BootManifest;
|
|
13
|
+
readonly entries: ClientEntries;
|
|
12
14
|
readonly loadCache: Map<string, ClientModuleRecord>;
|
|
13
15
|
private readonly seed;
|
|
14
16
|
private readonly factories;
|
|
15
17
|
private readonly bootstrapIds;
|
|
16
18
|
/** In-flight script transport per URL; every row in one batch shares it. */
|
|
17
19
|
private readonly pendingArrival;
|
|
20
|
+
/** Owner generation captured by in-flight chunk requests and advanced on invalidation. */
|
|
21
|
+
private readonly generations;
|
|
18
22
|
/** Single-resource combo URL selected by HMR after invalidating one row. */
|
|
19
|
-
private readonly
|
|
23
|
+
private readonly reloadTargets;
|
|
20
24
|
/** Materialization re-entrancy guard: factory-form CJS cannot deliver partial exports, so a cycle is fatal. */
|
|
21
25
|
private readonly materializing;
|
|
22
26
|
private readonly graphRows;
|
|
@@ -34,15 +38,16 @@ export declare class ClientModuleSystem implements ClientModuleLoader {
|
|
|
34
38
|
private arriveGraphRow;
|
|
35
39
|
/** Materialize a registered factory (synchronous; memoized in loadCache). */
|
|
36
40
|
private materialize;
|
|
37
|
-
/**
|
|
38
|
-
* The synchronous require answered to factories: seed → memoized record →
|
|
39
|
-
* registered factory. Fetching is async and therefore unreachable
|
|
40
|
-
* from here; an external dynamic package must have arrived before its
|
|
41
|
-
* consumer materializes.
|
|
42
|
-
*/
|
|
41
|
+
/** Build the synchronous module-table require and its asynchronous chunk operation. */
|
|
43
42
|
private makeRequire;
|
|
43
|
+
/** Load, register, and materialize one package-local dynamic chunk. */
|
|
44
|
+
private importChunk;
|
|
44
45
|
import(specifier: string): Promise<unknown>;
|
|
45
46
|
prefetch(id: string): Promise<void>;
|
|
47
|
+
/** Refresh descriptors and unowned factory revisions before any entry imports its dependencies. */
|
|
48
|
+
private updateManifest;
|
|
49
|
+
/** Retain live Loader modules and their transitive requests before evicting unreferenced graph records. */
|
|
50
|
+
private prune;
|
|
46
51
|
invalidate(id: string, rev?: string): void;
|
|
47
52
|
}
|
|
48
53
|
//# sourceMappingURL=system.d.ts.map
|