@crazx/dsh-client-modules 0.1.6-alpha.1.zw.3 → 0.1.6-alpha.2.zw.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
@@ -181,6 +181,8 @@ const COMBO_REVISION_PLACEHOLDER = "0".repeat(HASH_REVISION_LENGTH);
181
181
  const SOURCE_MAP_TRAILER = /(?:\r?\n)?\/\/# sourceMappingURL=[^\r\n]*(?:\r?\n)?$/;
182
182
  /** Debugger source name appended to page bundles in the WebWorker image. */
183
183
  const SOURCE_URL_TRAILER = /(?:\r?\n)?\/\/# sourceURL=([^\r\n]+)(?:\r?\n)?$/;
184
+ /** Published package-local client chunk names accepted by the on-demand route. */
185
+ const CLIENT_CHUNK = /^client\.[A-Za-z0-9][A-Za-z0-9._-]*\.js$/;
184
186
  /** Resolve `exports["./client"]` to a relative path, accepting the string and one-level conditional forms. */
185
187
  function clientExportOf(pkgName, exportsField) {
186
188
  if (typeof exportsField !== "object" || exportsField === null) return void 0;
@@ -203,14 +205,18 @@ function framedHash(domain, parts) {
203
205
  for (const part of parts) hash.update(`${String(part.byteLength)}:`).update(part);
204
206
  return hash.digest("hex").slice(0, HASH_REVISION_LENGTH);
205
207
  }
206
- /** Hash every artifact input served after HMR observes one plugin change. */
207
- function artifactRevision(bundle, sourceMap) {
208
- return framedHash("plugin-artifact", sourceMap === void 0 ? [bundle] : [bundle, sourceMap.body]);
208
+ /** Hash one completed build generation observed through its entry artifact. */
209
+ function artifactRevision(bundle, baseline) {
210
+ return framedHash("plugin-artifact", [bundle, Buffer.from(String(baseline.mtimeMs))]);
209
211
  }
210
212
  /** Address one ordered plugin-file list through the shared combo route. */
211
213
  function comboUrl(ids, rev, sourceMap = false) {
212
214
  return `/plugins/??${ids.map((id) => `${id}/client.js${sourceMap ? ".map" : ""}`).join(",")}&rev=${rev}`;
213
215
  }
216
+ /** Address one package-local chunk through the same revision as its entry. */
217
+ function chunkUrl(id, fileName, rev, sourceMap = false) {
218
+ return `/plugins/${id}/${fileName}${sourceMap ? ".map" : ""}?rev=${rev}`;
219
+ }
214
220
  /** Measure the longer map-form URL used to partition a startup resource list. */
215
221
  function projectedComboUrlBytes(records) {
216
222
  return Buffer.byteLength(comboUrl(records.map((record) => record.entry.id), COMBO_REVISION_PLACEHOLDER, true));
@@ -234,12 +240,12 @@ function partitionComboRecords(records) {
234
240
  return chunks;
235
241
  }
236
242
  /** Remove bundle-local debug directives and retain their stable generated-file name. */
237
- function comboSource(record) {
238
- let source = record.bundle.toString("utf8");
243
+ function prepareSource(resource) {
244
+ let source = resource.bundle.toString("utf8");
239
245
  const sourceUrl = SOURCE_URL_TRAILER.exec(source)?.[1];
240
246
  source = source.replace(SOURCE_URL_TRAILER, "").replace(SOURCE_MAP_TRAILER, "");
241
247
  if (!source.endsWith("\n")) source += "\n";
242
- const fallbackSource = sourceUrl === void 0 ? `/plugins/${record.entry.id}/client.js` : /^(?:[A-Za-z][A-Za-z\d+.-]*:|\/)/.test(sourceUrl) ? sourceUrl : `/${sourceUrl}`;
248
+ const fallbackSource = sourceUrl === void 0 ? `/plugins/${resource.id}/${resource.fileName}` : /^(?:[A-Za-z][A-Za-z\d+.-]*:|\/)/.test(sourceUrl) ? sourceUrl : `/${sourceUrl}`;
243
249
  return {
244
250
  source,
245
251
  fallbackSource
@@ -249,8 +255,8 @@ function comboSource(record) {
249
255
  function comboScript(input, sourceMapUrl) {
250
256
  return Buffer.from(sourceMapUrl === void 0 ? input : `${input}//# sourceMappingURL=${sourceMapUrl}\n`);
251
257
  }
252
- /** Parse an optional source-map artifact; missing maps do not prevent plugin execution. */
253
- function sourceMapSnapshot(clientPath) {
258
+ /** Read and parse an optional source map when its combo-map endpoint is requested. */
259
+ function readSourceMap(clientPath) {
254
260
  let body;
255
261
  try {
256
262
  body = readFileSync(`${clientPath}.map`);
@@ -261,10 +267,7 @@ function sourceMapSnapshot(clientPath) {
261
267
  const value = JSON.parse(body.toString("utf8"));
262
268
  const parsed = typeof value === "object" && value !== null ? value : void 0;
263
269
  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`);
264
- return {
265
- body,
266
- parsed
267
- };
270
+ return parsed;
268
271
  }
269
272
  /** Count generated lines while assembling indexed-map section offsets. */
270
273
  function newlineCount(value) {
@@ -273,13 +276,10 @@ function newlineCount(value) {
273
276
  return count;
274
277
  }
275
278
  /** Resolve section sources against their original per-plugin map URL before combo relocation. */
276
- function comboSectionMap(record) {
277
- const original = record.sourceMap?.parsed;
278
- /* v8 ignore next -- callers add sections only for records with a source map. */
279
- if (original === void 0) throw new Error(`client-modules: source map missing for ${record.entry.id}`);
279
+ function comboSectionMap(resource, original) {
280
280
  const sourcePaths = original.sources;
281
281
  const sourceRoot = typeof original.sourceRoot === "string" ? original.sourceRoot : "";
282
- const base = new URL(`/plugins/${record.entry.id}/client.js.map`, "http://dsh.invalid");
282
+ const base = new URL(`/plugins/${resource.id}/client.js.map`, "http://dsh.invalid");
283
283
  const relocated = sourcePaths.map((source) => {
284
284
  const separator = sourceRoot !== "" && !sourceRoot.endsWith("/") && !source.startsWith("/") ? "/" : "";
285
285
  const resolved = new URL(`${sourceRoot}${separator}${source}`, base);
@@ -303,14 +303,35 @@ function identitySectionMap(source, sourceUrl) {
303
303
  mappings
304
304
  };
305
305
  }
306
- /** Concatenate one or more factory registrations and compose their maps as indexed sections. */
307
- function buildCombo(records, revision) {
306
+ /** Run one producer in the first requester's microtask, not off-thread, and share its settlement. */
307
+ function lazyBody(produce) {
308
+ let result;
309
+ return () => {
310
+ result ??= Promise.resolve().then(produce);
311
+ return result;
312
+ };
313
+ }
314
+ /** Derive one combo revision from the ordered immutable row revisions. */
315
+ function comboRevision(resources) {
316
+ return framedHash("combo", resources.flatMap((resource) => [Buffer.from(resource.id), Buffer.from(resource.rev)]));
317
+ }
318
+ /** Concatenate one or more factory registrations without reading or composing source maps. */
319
+ function buildComboScript(resources, sourceMapUrl) {
308
320
  let source = "";
321
+ for (const resource of resources) source += `${prepareSource(resource).source};\n`;
322
+ return comboScript(source, sourceMapUrl);
323
+ }
324
+ /** Compose one indexed map from source-map files read only for this request. */
325
+ function buildComboSourceMap(resources, sourceMapOf, fileName = "client.js") {
309
326
  const sections = [];
310
327
  let line = 0;
311
- for (const record of records) {
312
- const prepared = comboSource(record);
313
- const section = record.sourceMap === void 0 ? identitySectionMap(prepared.source, prepared.fallbackSource) : comboSectionMap(record);
328
+ for (const resource of resources) {
329
+ const prepared = prepareSource(resource);
330
+ const sourceMap = sourceMapOf(resource.clientPath);
331
+ let section = identitySectionMap(prepared.source, prepared.fallbackSource);
332
+ if (sourceMap !== void 0) try {
333
+ section = comboSectionMap(resource, sourceMap);
334
+ } catch {}
314
335
  sections.push({
315
336
  offset: {
316
337
  line,
@@ -318,32 +339,39 @@ function buildCombo(records, revision) {
318
339
  },
319
340
  map: section
320
341
  });
321
- const bundle = `${prepared.source};\n`;
322
- source += bundle;
323
- line += newlineCount(bundle);
342
+ line += newlineCount(`${prepared.source};\n`);
324
343
  }
325
- const sourceMap = Buffer.from(`${JSON.stringify({
344
+ return Buffer.from(`${JSON.stringify({
326
345
  version: 3,
327
- file: "client.js",
346
+ file: fileName,
328
347
  sections
329
348
  })}\n`);
330
- const sourceBytes = Buffer.from(source);
331
- const rev = revision ?? framedHash("combo", [sourceBytes, sourceMap]);
332
- const entries = records.map((record) => record.entry.id);
349
+ }
350
+ /** Describe one combo and defer its executable and debug payloads independently. */
351
+ function buildCombo(records, sourceMapOf, revision) {
352
+ const resources = records.map((record) => ({
353
+ id: record.entry.id,
354
+ rev: record.entry.rev,
355
+ clientPath: record.meta.clientPath,
356
+ fileName: "client.js",
357
+ bundle: record.bundle
358
+ }));
359
+ const rev = revision ?? comboRevision(resources);
360
+ const entries = resources.map((resource) => resource.id);
333
361
  const url = comboUrl(entries, rev);
334
362
  const sourceMapUrl = comboUrl(entries, rev, true);
335
363
  return {
336
364
  url,
337
365
  rev,
338
366
  entries,
339
- script: comboScript(source, sourceMapUrl),
340
- sourceMap,
341
- sourceMapUrl
367
+ sourceMapUrl,
368
+ scriptBody: lazyBody(() => buildComboScript(resources, sourceMapUrl)),
369
+ sourceMapBody: lazyBody(() => buildComboSourceMap(resources, sourceMapOf))
342
370
  };
343
371
  }
344
372
  /** Add initial-load scheduling metadata to a combo artifact. */
345
- function buildBatch(phase, records) {
346
- const artifact = buildCombo(records);
373
+ function buildBatch(phase, records, sourceMapOf) {
374
+ const artifact = buildCombo(records, sourceMapOf);
347
375
  return {
348
376
  ...artifact,
349
377
  descriptor: {
@@ -537,12 +565,13 @@ var ClientModuleRegistry = class extends Service {
537
565
  /**
538
566
  * Serve an advertised revisioned bundle or source map without a Web server.
539
567
  * Unknown URLs return 404, unsupported methods return 405, and `HEAD`
540
- * returns the same immutable headers without a body.
568
+ * returns the same immutable headers without materializing a body. Each body
569
+ * is built once on its first `GET`; script construction never reads maps.
541
570
  * @param request - shell-carrier request for a `/plugins` resource.
542
571
  * @returns the exact response also exposed by the optional Web route.
543
572
  */
544
- fetchBundle(request) {
545
- const resource = this.bundleResource(request.method, request.url);
573
+ async fetchBundle(request) {
574
+ const resource = await this.bundleResource(request.method, request.url);
546
575
  const body = resource.body === void 0 ? null : Uint8Array.from(resource.body);
547
576
  return new Response(body, {
548
577
  status: resource.status,
@@ -562,8 +591,8 @@ var ClientModuleRegistry = class extends Service {
562
591
  return baseline === void 0 ? void 0 : { ...baseline };
563
592
  }
564
593
  /**
565
- * Re-hash one bundle (the HMR watch's registration hook — the only entry
566
- * point through which bundle content changes reach the graph).
594
+ * Publish one completed bundle generation (the HMR watch's registration
595
+ * hook — the only entry point through which build changes reach the graph).
567
596
  * @param id - entry id (package name).
568
597
  * @returns the new rev, or undefined for an unknown id.
569
598
  */
@@ -572,14 +601,11 @@ var ClientModuleRegistry = class extends Service {
572
601
  if (record === void 0) return void 0;
573
602
  const baseline = this.captureArtifactBaseline(record.meta.clientPath);
574
603
  const bundle = readFileSync(record.meta.clientPath);
575
- const sourceMap = this.readSourceMapSnapshot(record.meta.clientPath);
576
- const rev = artifactRevision(bundle, sourceMap);
604
+ const rev = artifactRevision(bundle, baseline);
577
605
  record.baseline = baseline;
578
606
  if (rev === record.entry.rev) return rev;
579
607
  record.entry = graphRow(id, rev, record.meta);
580
608
  record.bundle = bundle;
581
- if (sourceMap === void 0) delete record.sourceMap;
582
- else record.sourceMap = sourceMap;
583
609
  this.composed = this.compose();
584
610
  for (const notify of this.rebuildListeners) try {
585
611
  notify(id, rev);
@@ -618,31 +644,32 @@ var ClientModuleRegistry = class extends Service {
618
644
  const bootstrapIds = new Set(bootstrap.map((record) => record.entry.id));
619
645
  const application = entries.filter((entry) => !bootstrapIds.has(entry.id)).map((entry) => this.table.get(entry.id)).filter((record) => record !== void 0);
620
646
  const artifacts = [];
621
- for (const records of partitionComboRecords(bootstrap)) artifacts.push(buildBatch("bootstrap", records));
622
- for (const records of partitionComboRecords(application)) artifacts.push(buildBatch("application", records));
647
+ for (const records of partitionComboRecords(bootstrap)) artifacts.push(buildBatch("bootstrap", records, this.readSourceMap));
648
+ for (const records of partitionComboRecords(application)) artifacts.push(buildBatch("application", records, this.readSourceMap));
623
649
  const batchResponses = /* @__PURE__ */ new Map();
624
650
  for (const artifact of artifacts) {
625
- batchResponses.set(artifact.descriptor.url, {
626
- body: artifact.script,
651
+ batchResponses.set(artifact.descriptor.url, this.responses.get(artifact.descriptor.url) ?? {
652
+ body: artifact.scriptBody,
627
653
  contentType: "text/javascript; charset=utf-8"
628
654
  });
629
- batchResponses.set(artifact.sourceMapUrl, {
630
- body: artifact.sourceMap,
655
+ batchResponses.set(artifact.sourceMapUrl, this.responses.get(artifact.sourceMapUrl) ?? {
656
+ body: artifact.sourceMapBody,
631
657
  contentType: "application/json; charset=utf-8"
632
658
  });
633
659
  }
634
660
  const responses = new Map(batchResponses);
635
661
  for (const record of this.table.values()) {
636
- const artifact = buildCombo([record], record.entry.rev);
637
- responses.set(artifact.url, {
638
- body: artifact.script,
662
+ const artifact = buildCombo([record], this.readSourceMap, record.entry.rev);
663
+ responses.set(artifact.url, responses.get(artifact.url) ?? this.responses.get(artifact.url) ?? {
664
+ body: artifact.scriptBody,
639
665
  contentType: "text/javascript; charset=utf-8"
640
666
  });
641
- responses.set(artifact.sourceMapUrl, {
642
- body: artifact.sourceMap,
667
+ responses.set(artifact.sourceMapUrl, responses.get(artifact.sourceMapUrl) ?? this.responses.get(artifact.sourceMapUrl) ?? {
668
+ body: artifact.sourceMapBody,
643
669
  contentType: "application/json; charset=utf-8"
644
670
  });
645
671
  }
672
+ for (const [resourceUrl, response] of this.responses) if (this.chunkRequest(new URL(resourceUrl, "http://x")) !== void 0) responses.set(resourceUrl, response);
646
673
  this.previousBatchResponses = this.batchResponses;
647
674
  this.batchResponses = batchResponses;
648
675
  this.responses = responses;
@@ -772,7 +799,7 @@ var ClientModuleRegistry = class extends Service {
772
799
  return `${this.initialRevisionNonce}-${String(this.nextInitialRevision++)}`;
773
800
  }
774
801
  /**
775
- * Read the activation-time bundle and optional source-map snapshots.
802
+ * Read the activation-time bundle snapshot.
776
803
  * @param pkgName - package that declares the client bundle.
777
804
  * @param clientPath - absolute path of the built client artifact.
778
805
  * @returns the immutable bytes plus the pre-read filesystem baseline.
@@ -781,27 +808,24 @@ var ClientModuleRegistry = class extends Service {
781
808
  initialBundleSnapshot(pkgName, clientPath) {
782
809
  try {
783
810
  const baseline = this.captureArtifactBaseline(clientPath);
784
- const bundle = readFileSync(clientPath);
785
- const sourceMap = this.readSourceMapSnapshot(clientPath);
786
811
  return {
787
- bundle,
788
- baseline,
789
- ...sourceMap === void 0 ? {} : { sourceMap }
812
+ bundle: readFileSync(clientPath),
813
+ baseline
790
814
  };
791
815
  } catch (error) {
792
816
  if (error.code !== "ENOENT") throw error;
793
817
  throw new MissingClientBundleError(pkgName, clientPath, error);
794
818
  }
795
819
  }
796
- /** Treat a missing, torn, or malformed development map as an identity-mapped artifact revision. */
797
- readSourceMapSnapshot(clientPath) {
820
+ /** Treat a missing, torn, or malformed development map as an identity section. */
821
+ readSourceMap = (clientPath) => {
798
822
  try {
799
- return sourceMapSnapshot(clientPath);
823
+ return readSourceMap(clientPath);
800
824
  } catch (error) {
801
825
  this.ctx.logger.warn(error);
802
826
  return;
803
827
  }
804
- }
828
+ };
805
829
  /** Reconcile one entry name against the live Loader sources. @returns whether the table changed. */
806
830
  processOne(entryName, onError) {
807
831
  const nextSources = /* @__PURE__ */ new Map();
@@ -859,8 +883,7 @@ var ClientModuleRegistry = class extends Service {
859
883
  sourceKey: source.sourceKey,
860
884
  meta: source.meta,
861
885
  bundle: snapshot.bundle,
862
- baseline: snapshot.baseline,
863
- ...snapshot.sourceMap === void 0 ? {} : { sourceMap: snapshot.sourceMap }
886
+ baseline: snapshot.baseline
864
887
  });
865
888
  return true;
866
889
  }
@@ -885,25 +908,72 @@ var ClientModuleRegistry = class extends Service {
885
908
  this.composed = composed;
886
909
  this.notifyGraphChanged();
887
910
  }
888
- bundleResource(method, url) {
911
+ /** Match an exact current-revision package-local chunk URL without reading its file. */
912
+ chunkRequest(requestUrl) {
913
+ const resourceUrl = `${requestUrl.pathname}${requestUrl.search}`;
914
+ for (const record of this.table.values()) {
915
+ const prefix = `/plugins/${record.entry.id}/`;
916
+ if (!requestUrl.pathname.startsWith(prefix)) continue;
917
+ const requested = requestUrl.pathname.slice(prefix.length);
918
+ const sourceMap = requested.endsWith(".map");
919
+ const fileName = sourceMap ? requested.slice(0, -4) : requested;
920
+ if (!CLIENT_CHUNK.test(fileName)) return void 0;
921
+ if (resourceUrl !== chunkUrl(record.entry.id, fileName, record.entry.rev, sourceMap)) return void 0;
922
+ return {
923
+ record,
924
+ fileName,
925
+ sourceMap,
926
+ resourceUrl
927
+ };
928
+ }
929
+ }
930
+ /** Build a package-local chunk response only when its URL is requested. */
931
+ chunkResponse(requestUrl) {
932
+ const request = this.chunkRequest(requestUrl);
933
+ if (request === void 0) return void 0;
934
+ const { record, fileName, sourceMap, resourceUrl } = request;
935
+ const clientPath = join(dirname(record.meta.clientPath), fileName);
936
+ if (!existsSync(clientPath)) return void 0;
937
+ const sourceMapUrl = chunkUrl(record.entry.id, fileName, record.entry.rev, true);
938
+ const resource = () => ({
939
+ id: record.entry.id,
940
+ rev: record.entry.rev,
941
+ clientPath,
942
+ fileName,
943
+ bundle: readFileSync(clientPath)
944
+ });
945
+ const response = sourceMap ? {
946
+ body: lazyBody(() => buildComboSourceMap([resource()], this.readSourceMap, fileName)),
947
+ contentType: "application/json; charset=utf-8"
948
+ } : {
949
+ body: lazyBody(() => buildComboScript([resource()], sourceMapUrl)),
950
+ contentType: "text/javascript; charset=utf-8"
951
+ };
952
+ this.responses.set(resourceUrl, response);
953
+ return response;
954
+ }
955
+ async bundleResource(method, url) {
889
956
  if (method !== "GET" && method !== "HEAD") return { status: 405 };
890
957
  const requestUrl = new URL(url, "http://x");
891
958
  const resourceUrl = `${requestUrl.pathname}${requestUrl.search}`;
892
- const response = this.responses.get(resourceUrl) ?? this.previousBatchResponses.get(resourceUrl);
893
- if (response !== void 0) return {
894
- status: 200,
895
- headers: {
896
- "content-type": response.contentType,
897
- "cache-control": IMMUTABLE_CACHE,
898
- "content-length": String(response.body.length)
899
- },
900
- ...method === "HEAD" ? {} : { body: response.body }
901
- };
959
+ const response = this.responses.get(resourceUrl) ?? this.previousBatchResponses.get(resourceUrl) ?? this.chunkResponse(requestUrl);
960
+ if (response !== void 0) {
961
+ const body = await response.body();
962
+ return {
963
+ status: 200,
964
+ headers: {
965
+ "content-type": response.contentType,
966
+ "cache-control": IMMUTABLE_CACHE,
967
+ "content-length": String(body.length)
968
+ },
969
+ ...method === "HEAD" ? {} : { body }
970
+ };
971
+ }
902
972
  return { status: 404 };
903
973
  }
904
- serveBundle = (req, res) => {
974
+ serveBundle = async (req, res) => {
905
975
  /* v8 ignore next -- `?? '/'` arm: node:http always sets url on server requests. */
906
- const response = this.bundleResource(req.method, req.url ?? "/");
976
+ const response = await this.bundleResource(req.method, req.url ?? "/");
907
977
  res.writeHead(response.status, response.headers);
908
978
  res.end(response.body);
909
979
  };
@@ -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