@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/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 every artifact input served after HMR observes one plugin change. */
191
- function artifactRevision(bundle, sourceMap) {
192
- return framedHash("plugin-artifact", sourceMap === void 0 ? [bundle] : [bundle, sourceMap.body]);
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 comboSource(record) {
222
- let source = record.bundle.toString("utf8");
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/${record.entry.id}/client.js` : /^(?:[A-Za-z][A-Za-z\d+.-]*:|\/)/.test(sourceUrl) ? sourceUrl : `/${sourceUrl}`;
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
- /** Parse an optional source-map artifact; missing maps do not prevent plugin execution. */
237
- function sourceMapSnapshot(clientPath) {
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(record) {
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/${record.entry.id}/client.js.map`, "http://dsh.invalid");
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
- /** Concatenate one or more factory registrations and compose their maps as indexed sections. */
291
- function buildCombo(records, revision) {
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 record of records) {
296
- const prepared = comboSource(record);
297
- const section = record.sourceMap === void 0 ? identitySectionMap(prepared.source, prepared.fallbackSource) : comboSectionMap(record);
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
- const bundle = `${prepared.source};\n`;
306
- source += bundle;
307
- line += newlineCount(bundle);
326
+ line += newlineCount(`${prepared.source};\n`);
308
327
  }
309
- const sourceMap = Buffer.from(`${JSON.stringify({
328
+ return Buffer.from(`${JSON.stringify({
310
329
  version: 3,
311
- file: "client.js",
330
+ file: fileName,
312
331
  sections
313
332
  })}\n`);
314
- const sourceBytes = Buffer.from(source);
315
- const rev = revision ?? framedHash("combo", [sourceBytes, sourceMap]);
316
- const entries = records.map((record) => record.entry.id);
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
- script: comboScript(source, sourceMapUrl),
324
- sourceMap,
325
- sourceMapUrl
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
- * Re-hash one bundle (the HMR watch's registration hook — the only entry
550
- * point through which bundle content changes reach the graph).
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 sourceMap = this.readSourceMapSnapshot(record.meta.clientPath);
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.script,
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.sourceMap,
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.script,
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.sourceMap,
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 and optional source-map snapshots.
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-mapped artifact revision. */
779
- readSourceMapSnapshot(clientPath) {
802
+ /** Treat a missing, torn, or malformed development map as an identity section. */
803
+ readSourceMap = (clientPath) => {
780
804
  try {
781
- return sourceMapSnapshot(clientPath);
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
- bundleResource(method, url) {
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 content-addressed combo script. */
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
- /** Content-addressed combo script endpoint. */
68
+ /** Revisioned combo script endpoint. */
68
69
  url: string;
69
- /** Revision over the combined plugin script bytes and indexed source map. */
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 whole graph (content + bundle hashes). */
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
- /** Content-addressed combo endpoint used before the first HMR invalidation. */
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 synchronous
171
- * require bound to the module table and returns the bundle's exports. Runs
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: (spec: string) => unknown) => Record<string, unknown>;
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
- /** Parsed Host boot graph shared with the web entry after module-system creation. */
239
+ /** Latest parsed Host graph, updated by live entry reconciliation. */
230
240
  manifest: BootManifest;
231
- /** Materialized-module registry: id record. The governance-side read API for entry exports. */
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 module: drop its registered factory and
256
- * materialized record so the next prefetch/import loads its one-resource
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
- /** Parsed boot graph owned by the resulting module system. */
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
- readonly manifest: BootManifest;
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 reloadUrls;
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