@validation-engineering/cordis-loader 0.1.0 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -60,96 +60,18 @@ The Loader imports modules and validates candidate JSON inputs before disturbing
60
60
 
61
61
  `loader.reload()` re-reads its last file and included JSON files, then reconciles the changed subtrees. With unchanged configuration and factory identities, it does not remount the graph. After a programmatic `update()` the source is an in-memory recipe; subsequent `reload()` uses that recipe and asks the ModuleHost to prepare its factories again. There is no implicit write-back to the original configuration files and no background file watcher.
62
62
 
63
- ## Explicit transactions for the official Harness Loader
64
-
65
- The separate `@validation-engineering/cordis-loader/harness` entry adapts the **installed,
66
- unchanged** official Harness `Loader` and `Include` classes. It does not replace
67
- this package's JSON API. `LoaderTransactions` offers explicit host operations;
68
- `installOfficialTransactions` additionally connects the official configuration UI
69
- and refresh/HMR queues when installed by the application before boot.
70
- The tested contract is Harness Cordis 4.0.4, Loader 1.0.5 and Include 1.0.9.
71
- Use the native Harness profile and a live, fully initialized tree:
72
-
73
- ```js
74
- import { LoaderTransactions } from '@validation-engineering/cordis-loader/harness';
75
-
76
- // ctx and ctx.loader already belong to the booted official application.
77
- // Select an Include file owned by this host, not an unrelated profile overlay.
78
- const edits = new LoaderTransactions(ctx, ctx.loader.resolve('custom').subtree);
79
- await edits.update('greeting', { config: { prefix: 'Welcome' } });
80
- const id = await edits.create({ name: './another-plugin.mjs', config: {} });
81
- // create preserves the official fully qualified id; resolve it from the root.
82
- const rootEdits = new LoaderTransactions(ctx, ctx.loader);
83
- await rootEdits.remove(id);
84
- await edits.close(); // closes this adapter's admission; does not dispose the tree
85
- ```
86
-
87
- `create`, `update` and `remove` enter the same domain FIFO as Fiber revisions,
88
- JSON Loader updates and Context shutdown **before** changing options or files.
89
- Cloneable options are snapshotted at admission, including `undefined` deletion
90
- values; functions and other non-cloneable values reject before admission.
91
- The adapter retains old and new Fibers, waits for official Loader and native
92
- lifecycle work, and observes the actual Include write queues, including writes
93
- scheduled by timers or teardown. This is conservatively a **whole Loader graph
94
- barrier**: sibling Includes and existing work may delay it or report failure.
95
- Use it from an external host operation, outside plugin setup/cleanup actions.
96
-
97
- Success means that the observed lifecycle and persistence work completed. It is
98
- not an atomic rollback mechanism or a guarantee of physical-disk durability after
99
- power loss. An `OFFICIAL_PERSISTENCE_FAILED` error names affected files and retains
100
- causes; running plugin changes may already have happened. A subsequent explicit
101
- revision can retry the official write queue. Cleanup failures still block new
102
- revisions until actual cleanup recovery succeeds. Pending dependencies retain
103
- upstream Pending semantics; completion does not certify every plugin Active.
104
- `revision` counts successful operations, `requestedRevision` counts admitted
105
- requests, and `lastFailure` retains the last failed request's operation and error.
106
-
107
- A cached adapter belongs to its tree owner's current episode. If that Include
108
- is removed or restarted, `STALE_LOADER` rejects old requests before mutation;
109
- obtain a new adapter for the new tree. Foreign domains, incompatible class shapes
110
- and methods replaced after construction are rejected. Nested removal selects the
111
- entry's owning tree and local id before invoking the unchanged official method.
112
-
113
- The bridge uses `MutationSteps.capture(callback)`, whose authority lasts only
114
- through the callback's synchronous call stack in that domain. A returned Promise
115
- does not extend that authority; plugin lifecycle callbacks and async update
116
- continuations cannot borrow it. Ignored lifecycle Promises are still joined.
117
-
118
- The explicit adapter does not intercept arbitrary direct object or file mutations.
119
- Applications can additionally call `installOfficialTransactions` with the pinned
120
- `Entry`, `EntryGroup`, `EntryTree`, `Hmr` and `ConfigEditor` classes **before boot**.
121
- The companion Harness application already installs it in its default preload.
122
- That host bridge admits ConfigEditor edits before acquiring the profile file lock,
123
- then keeps write/reconcile/rollback and Include refresh in the same domain slot.
124
- It also orders `hmr.runExclusive` with configuration and shutdown work. The original
125
- methods still implement the configuration semantics; no upstream source files
126
- are rewritten. See [official configuration transactions](../../docs/official-config-transactions.md)
127
- for setup, failure and callback contracts.
128
-
129
- A failed inverse is not automatically retried by rollback. Readiness and restart
130
- report the retained native barrier even when a transient JavaScript error has
131
- already been observed; explicit cleanup recovery is required. Event and edit
132
- callbacks cannot borrow transaction steps. `steps.observe(fiber, callback)` retains
133
- the callback's episode identity, and `steps.isCurrent()` lets a trusted bridge
134
- check its exact coordinator origin before capturing a synchronous continuation.
135
- These are host APIs, not a sandbox for arbitrary application code.
136
-
137
- Official main-thread `partialReload()` now performs dependency-closed ESM/CJS
138
- replacement in the existing Context. It waits for old cleanup before changing
139
- caches, preserves unrelated identities and retains a recovery journal when
140
- candidate cleanup or old restoration fails. The official watcher includes pure
141
- CJS leaves. See [in-place HMR](../../docs/official-in-place-hmr.md) for observed
142
- graph coverage, concurrency, failure and native-addon boundaries.
143
- [Worker module graphs](../../docs/module-graph-reloads.md) replace a complete
144
- captured environment; application native addons use `ProcessDomain` below.
145
- Runtime shape checks do not certify a different upstream version or arbitrary
146
- EntryTree subclass.
147
-
148
- The core gate checks admission contracts, callback isolation and type declarations;
149
- the companion `cordis-harness` gate executes installed official classes, default
150
- boot, real JSON/YAML edits, write/rollback failures, cleanup barriers and stale
151
- owner rejection. These are behavioral checks, not proofs of filesystem durability
152
- or arbitrary callback effects.
63
+ ## DeepSeek Harness application integration
64
+
65
+ The pinned official Loader/Include, ConfigEditor and in-process HMR bridges are
66
+ maintained and tested by the companion Harness application. Starting with Loader
67
+ 0.2.0, import those APIs from `@validation-engineering/harness-verus/official-loader-api`.
68
+ The former `@validation-engineering/cordis-loader/harness` entry belongs to the
69
+ published 0.1.0 release and is no longer exported by this package.
70
+
71
+ The runtime still provides the shared domain transaction, callback isolation,
72
+ cleanup and JSON Loader primitives. Application-specific method interception,
73
+ module-cache recovery and official version checks live with the application.
74
+ See [package boundaries](../../docs/package-boundaries.md) for the migration.
153
75
 
154
76
  ## Replace code in a fresh Worker
155
77
 
package/module-graph.js CHANGED
@@ -12,8 +12,8 @@ const facadeURLs = new Set([
12
12
  ]);
13
13
  const harnessURLs = new Set();
14
14
  try {
15
- harnessURLs.add(import.meta.resolve('@validation-engineering/cordis-harness'));
16
- harnessURLs.add(pathToFileURL(require.resolve('@validation-engineering/cordis-harness')).href);
15
+ harnessURLs.add(import.meta.resolve('@validation-engineering/cordis-compat-deepseek'));
16
+ harnessURLs.add(pathToFileURL(require.resolve('@validation-engineering/cordis-compat-deepseek')).href);
17
17
  } catch { /* Harness is optional for the default Cordis host. */ }
18
18
 
19
19
  /** Observed Node module edges in one Worker, not a static parser or cache reset. */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@validation-engineering/cordis-loader",
3
- "version": "0.1.0",
3
+ "version": "0.2.1",
4
4
  "type": "module",
5
5
  "description": "Experimental JSON module loader over the native Cordis facade",
6
6
  "engines": {
@@ -15,10 +15,6 @@
15
15
  "types": "./worker.d.ts",
16
16
  "import": "./worker.js"
17
17
  },
18
- "./harness": {
19
- "types": "./harness.d.ts",
20
- "import": "./harness.js"
21
- },
22
18
  "./process": {
23
19
  "types": "./process.d.ts",
24
20
  "import": "./process.js"
@@ -33,7 +29,7 @@
33
29
  }
34
30
  },
35
31
  "peerDependencies": {
36
- "@validation-engineering/cordis": "0.1.0"
32
+ "@validation-engineering/cordis": "0.2.0"
37
33
  },
38
34
  "license": "MIT",
39
35
  "publishConfig": {
package/harness.d.ts DELETED
@@ -1,57 +0,0 @@
1
- import type { Context } from '@validation-engineering/cordis';
2
-
3
- /** Structural surface of the pinned official Harness Loader/Include classes. */
4
- export interface OfficialEntryTree {
5
- ctx: object;
6
- root: { tree: OfficialEntryTree; data: unknown[] };
7
- store: Record<string, { fiber?: object; subtree?: OfficialEntryTree }>;
8
- entries(): Iterable<unknown>;
9
- getTasks(): Promise<unknown>[];
10
- await(): Promise<void>;
11
- resolve(id: string): unknown;
12
- resolveGroup(id: string | null): unknown;
13
- create(options: { name: string; [key: string]: any }, parent?: string | null, position?: number): Promise<string>;
14
- update(id: string, options: Record<string, any>, parent?: string | null, position?: number): Promise<void>;
15
- remove(id: string): void;
16
- write(): void;
17
- }
18
- export class LoaderTransactions {
19
- constructor(ctx: Context, tree: OfficialEntryTree);
20
- readonly ctx: Context;
21
- readonly tree: OfficialEntryTree;
22
- /** Number of successful create/update/remove operations. */
23
- readonly revision: number;
24
- /** Number of admitted operations, including queued or failed operations. */
25
- readonly requestedRevision: number;
26
- readonly lastFailure: { revision: number; operation: 'create' | 'update' | 'remove'; error: unknown } | undefined;
27
- readonly closed: boolean;
28
- /** Options are structured-cloned at admission. */
29
- create(options: { name: string; [key: string]: any }, parent?: string | null, position?: number): Promise<string>;
30
- update(id: string, options: Record<string, any>, parent?: string | null, position?: number): Promise<void>;
31
- remove(id: string): Promise<void>;
32
- /** Closes this adapter's admission and drains earlier work; does not dispose the tree. */
33
- close(): Promise<void>;
34
- }
35
-
36
- /** Installs transparent host-only domain coordination before mounting pinned official classes. */
37
- export function installOfficialTransactions(classes: {
38
- Entry: Function; EntryGroup: Function; EntryTree: Function; Hmr: Function; ConfigEditor: Function;
39
- }): void;
40
-
41
- /** Additional structural HMR surface installed on the pinned official service. */
42
- export interface OfficialHmrTransactions {
43
- /** Current retained cleanup/restoration failure; cleared when its journal recovers. */
44
- readonly lastReloadFailure: (Error & { code: string; details: { restored?: boolean; cacheChanged?: boolean } }) | undefined;
45
- /** Canonical file URLs changed since the last admitted batch. */
46
- readonly stashed: Set<string>;
47
- /** Joins an existing official transaction or admits through the domain FIFO. */
48
- partialReload(): Promise<void>;
49
- runExclusive<T>(operation: () => T | Promise<T>): Promise<T>;
50
- }
51
-
52
- /** Admit one host revision around official ConfigEditor/HMR calls and readiness
53
- * checks. Official methods join this exact coordinator after awaits; lifecycle
54
- * and config-change callbacks remain observers and cannot borrow its authority.
55
- * The callback must perform its own application-level restoration on failure.
56
- */
57
- export function officialTransaction<T>(context: Context, execute: () => T | PromiseLike<T>): Promise<Awaited<T>>;
package/harness.js DELETED
@@ -1,394 +0,0 @@
1
- import { AsyncLocalStorage } from 'node:async_hooks';
2
- import { FiberState, assertDomainMutation, domainMutation } from '@validation-engineering/cordis';
3
- import { LoaderError } from './config.js';
4
- import { installModuleObservation, reloadModules } from './official-hmr.js';
5
-
6
- const methods = ['entries', 'getTasks', 'await', 'resolve', 'resolveGroup', 'create', 'update', 'remove', 'write'];
7
- const incompatible = message => new LoaderError('INCOMPATIBLE_OFFICIAL_LOADER', message);
8
- const thenable = value => value && typeof value.then === 'function';
9
-
10
- // This deliberately recognizes the pinned Harness Loader/Include contract. It
11
- // does not turn an arbitrary EntryTree subclass into a durable configuration API.
12
- function canonicalTree(tree) {
13
- // Cordis exposes services through context-bound tracing proxies. EntryGroup
14
- // retains the actual tree, so use that identity without changing its methods.
15
- const original = tree?.root?.tree;
16
- if (original && original !== tree && original.store === tree.store) return original;
17
- return tree;
18
- }
19
-
20
- function isInclude(tree) {
21
- return tree?.constructor?.name === 'Include' || tree?.constructor?.name === 'HostResolvedRootInclude'
22
- && Object.getPrototypeOf(tree.constructor.prototype)?.constructor?.name === 'Include';
23
- }
24
-
25
- function inspectTree(tree, domain, allowRetired = false) {
26
- if (!tree || typeof tree !== 'object' || !tree.ctx?.fiber?._domain) throw incompatible('Expected an official Harness Loader or Include tree');
27
- if (tree.ctx.fiber._domain !== domain) throw new LoaderError('FOREIGN_DOMAIN', 'Loader tree belongs to another execution domain');
28
- if (!(tree.constructor?.name === 'Loader' || isInclude(tree))
29
- || methods.some(name => typeof tree[name] !== 'function')
30
- || tree.root?.tree !== tree || !Array.isArray(tree.root.data) || !tree.store || typeof tree.store !== 'object') {
31
- throw incompatible('Unsupported official Loader tree contract');
32
- }
33
- if (!allowRetired && (tree.ctx.fiber.uid === null || tree.ctx.fiber._removedFlag
34
- || [FiberState.DISPOSED, FiberState.UNLOADING].includes(tree.ctx.fiber.state))) {
35
- throw new LoaderError('STALE_LOADER', 'The official tree owner has been disposed or is unloading');
36
- }
37
- const include = isInclude(tree);
38
- if (include && (typeof tree.filename !== 'string' || typeof tree.flushWrite !== 'function'
39
- || typeof tree._writeFile !== 'function' || typeof tree.readonly !== 'boolean' || !thenable(tree.writeQueue)
40
- || !Object.hasOwn(tree, 'pendingWrite') || !Object.hasOwn(tree, 'writeTask'))) {
41
- throw incompatible('Include requires the pinned pendingWrite/flushWrite/writeQueue persistence contract');
42
- }
43
- return include;
44
- }
45
-
46
- function optionsCopy(options) {
47
- if (!options || typeof options !== 'object' || Array.isArray(options)) throw new TypeError('Loader options must be an object');
48
- // The official methods mutate their options. Give each queued revision its own
49
- // copy, including undefined values used by Entry.update to delete fields.
50
- return structuredClone(options);
51
- }
52
-
53
- /** Explicit transactions around the unchanged, pinned Harness EntryTree API. */
54
- export class LoaderTransactions {
55
- constructor(ctx, tree) {
56
- const domain = ctx?.fiber?._domain;
57
- if (!domain || domain.profile !== 'harness') throw incompatible('LoaderTransactions requires a native Harness-profile Context');
58
- tree = canonicalTree(tree);
59
- inspectTree(tree, domain);
60
- this.ctx = ctx;
61
- this.tree = tree;
62
- this.revision = 0;
63
- this.requestedRevision = 0;
64
- this.lastFailure = undefined;
65
- this.closed = false;
66
- this._accepting = true;
67
- this._domain = domain;
68
- this._ownerGeneration = tree.ctx.fiber._generation;
69
- this._contract = new Map(methods.map(name => [name, tree[name]]));
70
- }
71
-
72
- create(options, parent = null, position = Infinity) {
73
- return this._submit('create', () => [optionsCopy(options), parent, position]);
74
- }
75
- update(id, options, parent, position) {
76
- return this._submit('update', () => [id, optionsCopy(options), parent, position]);
77
- }
78
- remove(id) { return this._submit('remove', () => [id]); }
79
-
80
- _check() {
81
- inspectTree(this.tree, this._domain);
82
- if (this.tree.ctx.fiber._generation !== this._ownerGeneration) throw new LoaderError('STALE_LOADER', 'The official tree belongs to a previous owner activation');
83
- for (const [name, original] of this._contract) {
84
- if (this.tree[name] !== original) throw incompatible(`Official tree method ${name} changed after adapter creation`);
85
- }
86
- }
87
-
88
- _invoke(operation, args) {
89
- if (operation !== 'remove') return Reflect.apply(this._contract.get(operation), this.tree, args);
90
- const entry = this.tree.resolve(args[0]);
91
- const owner = canonicalTree(entry?.parent?.tree);
92
- inspectTree(owner, this._domain);
93
- const id = entry.options?.id;
94
- if (typeof id !== 'string' || owner.store[id] !== entry) throw incompatible('Entry removal requires its original containing tree and local id');
95
- // The pinned root remove() forwards a qualified id into a local store. Use
96
- // the same official method on the owning tree with its actual local id.
97
- return owner.remove(id);
98
- }
99
-
100
- _submit(operation, prepare) {
101
- let args, request;
102
- try {
103
- assertDomainMutation(this.ctx);
104
- if (!this._accepting) throw new LoaderError('LOADER_CLOSED', 'Loader transaction adapter is closing or closed');
105
- this._check();
106
- args = prepare();
107
- request = ++this.requestedRevision;
108
- } catch (error) { return Promise.reject(error); }
109
- const task = domainMutation(this.ctx, async steps => {
110
- this._check();
111
- return this._run(steps, () => this._invoke(operation, args));
112
- });
113
- return task.then(value => {
114
- this.revision++;
115
- this.lastFailure = undefined;
116
- return value;
117
- }, error => {
118
- this.lastFailure = { revision: request, operation, error };
119
- throw error;
120
- });
121
- }
122
-
123
- async _run(steps, invoke, closing = false) {
124
- const trees = new Set(), fibers = new Set(), hooks = new Map();
125
- const writes = new Map(), failures = [], writeFailures = [];
126
- const remember = error => { if (!failures.includes(error)) failures.push(error); };
127
- const observe = (tree, promise, prior = false) => {
128
- if (!thenable(promise)) throw incompatible('Include.flushWrite must return its persistence promise');
129
- if (!writes.has(promise)) writes.set(promise, Promise.resolve(promise).catch(error => { writeFailures.push({ tree, error, promise, prior }); }));
130
- return promise;
131
- };
132
- const hook = tree => {
133
- if (hooks.has(tree)) return;
134
- const descriptor = Object.getOwnPropertyDescriptor(tree, 'flushWrite');
135
- if (descriptor && (!('value' in descriptor) || !descriptor.configurable)) throw incompatible('Include.flushWrite cannot be observed safely');
136
- const original = tree.flushWrite;
137
- const wrapped = function (...args) {
138
- try { return observe(tree, Reflect.apply(original, this, args)); }
139
- catch (error) { writeFailures.push({ tree, error }); throw error; }
140
- };
141
- Object.defineProperty(tree, 'flushWrite', { value: wrapped, configurable: true, writable: true });
142
- hooks.set(tree, { descriptor, wrapped });
143
- observe(tree, tree.writeQueue, true);
144
- };
145
- const visit = source => {
146
- const tree = canonicalTree(source);
147
- const include = inspectTree(tree, this._domain, trees.has(tree));
148
- trees.add(tree);
149
- if (include) hook(tree);
150
- for (const entry of Object.values(tree.store)) {
151
- if (entry.fiber) {
152
- if (entry.fiber._domain !== this._domain) throw new LoaderError('FOREIGN_DOMAIN', 'Entry fiber belongs to another execution domain');
153
- fibers.add(entry.fiber);
154
- }
155
- if (entry.subtree && !scanned.has(entry.subtree)) { scanned.add(entry.subtree); visit(entry.subtree); }
156
- }
157
- };
158
- let scanned;
159
- const discover = () => {
160
- scanned = new Set();
161
- for (const tree of [this.tree, canonicalTree(this.tree.ctx.loader)]) {
162
- if (tree && !scanned.has(tree)) { scanned.add(tree); visit(tree); }
163
- }
164
- };
165
- const flush = () => {
166
- for (const tree of hooks.keys()) {
167
- try { observe(tree, tree.flushWrite()); } catch (error) { remember(error); }
168
- }
169
- };
170
- const drainWrites = async () => {
171
- // Timers and stop() may have added another queued write while an earlier
172
- // filesystem operation was pending. Keep the slot until all observed runs
173
- // have landed, including failures followed by a successful retry.
174
- while (true) {
175
- flush();
176
- const count = writes.size;
177
- await Promise.all(writes.values());
178
- if (writes.size === count && [...hooks.keys()].every(tree => tree.pendingWrite === undefined && tree.writeTask === undefined)) break;
179
- }
180
- };
181
- let listener, value;
182
- try {
183
- discover();
184
- // Include emits this before scheduling its timer. Discover newly created
185
- // Include subtrees in time to observe their first ordinary write as well.
186
- if (!closing) listener = this.ctx.on('loader/config-update', discover, { global: true });
187
- let result;
188
- try { result = steps.capture(invoke); } catch (error) { remember(error); }
189
- // Flush the synchronous revision before yielding: a following revision
190
- // cannot overtake this write even if plugin cleanup is asynchronous.
191
- try { discover(); } catch (error) { remember(error); }
192
- flush();
193
- try { value = await result; } catch (error) { remember(error); }
194
- try { discover(); } catch (error) { remember(error); }
195
- for (const tree of trees) {
196
- try { await tree.await(); } catch (error) { remember(error); }
197
- }
198
- try { await this.ctx.settle(); } catch (error) { remember(error); }
199
- try { discover(); } catch (error) { remember(error); }
200
- for (const fiber of fibers) {
201
- // HMR explicitly joined this disposal; a removed prior generation's
202
- // historical setup error must not poison a successful recovery.
203
- if (fiber._removedFlag && scoped(this.ctx)?.retired.has(fiber)) continue;
204
- try { await fiber.await(); } catch (error) { remember(error); }
205
- }
206
- } catch (error) { remember(error); }
207
- finally {
208
- try { await drainWrites(); } catch (error) { remember(error); }
209
- try { listener?.(); } catch (error) { remember(error); }
210
- for (const [tree, { descriptor, wrapped }] of hooks) {
211
- try {
212
- if (tree.flushWrite !== wrapped) throw incompatible('Include.flushWrite changed during a transaction');
213
- if (descriptor) Object.defineProperty(tree, 'flushWrite', descriptor);
214
- else delete tree.flushWrite;
215
- } catch (error) { remember(error); }
216
- }
217
- }
218
- // The previous revision may have left a rejected queue. Include explicitly
219
- // retries through that rejection when a new write is scheduled. A successful
220
- // new write recovers that historical failure, but never hides a failure of
221
- // any write started during this revision.
222
- const persistenceFailures = writeFailures.filter(({ tree, promise, prior }) => !prior || tree.writeQueue === promise);
223
- if (persistenceFailures.length) {
224
- const errors = [...new Set([...failures, ...persistenceFailures.map(({ error }) => error)])];
225
- throw new LoaderError('OFFICIAL_PERSISTENCE_FAILED', 'Official Include persistence failed; in-memory changes have not been rolled back',
226
- { files: [...new Set(persistenceFailures.map(({ tree }) => tree.filename))] }, new AggregateError(errors, 'Loader revision and persistence errors'));
227
- }
228
- if (failures.length > 1 && failures[0] instanceof LoaderError && failures[0].code.startsWith('OFFICIAL_HMR_')) {
229
- const first = failures[0];
230
- throw new LoaderError(first.code, first.message, first.details, new AggregateError(failures, 'HMR revision and lifecycle errors'));
231
- }
232
- if (failures.length === 1) throw failures[0];
233
- if (failures.length) throw new AggregateError(failures, 'Official Loader revision failed');
234
- return value;
235
- }
236
-
237
- /** Stop admission and wait for earlier revisions; does not dispose the tree. */
238
- close() {
239
- try { assertDomainMutation(this.ctx, { recovery: true }); }
240
- catch (error) { return Promise.reject(error); }
241
- if (this._closeTask) return this._closeTask;
242
- this._accepting = false;
243
- const task = domainMutation(this.ctx, async steps => {
244
- this._check();
245
- await this._run(steps, () => undefined, true);
246
- this.closed = true;
247
- }, { recovery: true }).catch(error => { this._closeTask = undefined; throw error; });
248
- this._closeTask = task;
249
- return task;
250
- }
251
- }
252
-
253
-
254
- // The scope carries no authority by itself. Every bridge checks the runtime's
255
- // exact coordinator origin again, including after official asynchronous I/O.
256
- const officialScope = new AsyncLocalStorage();
257
- const installedContracts = new WeakMap();
258
- const refreshedTrees = new WeakMap();
259
-
260
- function scoped(ctx) {
261
- const scope = officialScope.getStore();
262
- if (!scope?.steps.isCurrent()) return;
263
- if (ctx?.fiber?._domain !== scope.domain) throw new LoaderError('FOREIGN_DOMAIN', 'Official operation crossed execution domains');
264
- return scope;
265
- }
266
-
267
- function serviceCheck(service, name) {
268
- const ctx = service.ownerContext;
269
- const fiber = ctx?.fiber;
270
- const original = value => value?.[Symbol.for('cordis.original')] ?? value;
271
- if (!fiber || fiber.uid === null || fiber._removedFlag || fiber.state !== FiberState.ACTIVE
272
- || original(ctx.get(name)) !== original(service)) {
273
- throw new LoaderError('STALE_OFFICIAL_SERVICE', `Official ${name} belongs to an inactive or replaced service`);
274
- }
275
- return ctx;
276
- }
277
-
278
- function coordinate(ctx, execute) {
279
- try { assertDomainMutation(ctx); }
280
- catch (error) { return Promise.reject(error); }
281
- return domainMutation(ctx, steps => {
282
- const adapter = new LoaderTransactions(ctx.root, canonicalTree(ctx.loader));
283
- return officialScope.run({ domain: ctx.fiber._domain, steps, retired: new Set() }, () => adapter._run(steps, execute));
284
- });
285
- }
286
-
287
- /** Compose official host operations and application readiness checks in one
288
- * domain revision. Managed callbacks cannot borrow this transaction's authority.
289
- */
290
- export function officialTransaction(ctx, execute) {
291
- if (typeof execute !== 'function') return Promise.reject(new TypeError('officialTransaction requires a callback'));
292
- return coordinate(ctx, execute);
293
- }
294
-
295
- function installRefresh(tree) {
296
- if (!isInclude(tree) || refreshedTrees.has(tree)) return;
297
- const original = tree.refresh;
298
- if (typeof original !== 'function') throw incompatible('Official Include.refresh is unavailable');
299
- const owner = tree.ctx.fiber, generation = owner._generation;
300
- const check = () => {
301
- if (owner.uid === null || owner._removedFlag || owner._generation !== generation
302
- || [FiberState.DISPOSED, FiberState.UNLOADING].includes(owner.state)) {
303
- throw new LoaderError('STALE_LOADER', 'Cannot refresh an Include from a previous owner activation');
304
- }
305
- };
306
- const wrapped = function (...args) {
307
- const invoke = () => { check(); return Reflect.apply(original, this, args); };
308
- try {
309
- check();
310
- return scoped(tree.ctx) ? invoke() : coordinate(tree.ctx.root, invoke);
311
- } catch (error) { return Promise.reject(error); }
312
- };
313
- Object.defineProperty(tree, 'refresh', { value: wrapped, configurable: true, writable: true });
314
- refreshedTrees.set(tree, { original, wrapped });
315
- }
316
-
317
- /**
318
- * Install host-only bridges on the pinned official classes before mounting the
319
- * application. Official methods, filesystem locks, rollback, watcher dispatch
320
- * and config semantics still execute unchanged. The domain queue is outermost.
321
- */
322
- export function installOfficialTransactions({ Entry, EntryGroup, EntryTree, Hmr, ConfigEditor }) {
323
- const classes = [Entry, EntryGroup, EntryTree, Hmr, ConfigEditor];
324
- if (classes.some(value => typeof value !== 'function' || !value.prototype)) throw incompatible('All five pinned official classes are required');
325
- const previous = classes.map(value => installedContracts.get(value)).find(Boolean);
326
- if (previous) {
327
- if (classes.some((value, index) => value !== previous[index])) throw incompatible('Official transaction installation changed class identities');
328
- return;
329
- }
330
- const contracts = [
331
- [Entry.prototype, ['update'], entry => entry.ctx],
332
- [EntryGroup.prototype, ['update', 'create', 'remove', 'stop'], group => group.ctx],
333
- [EntryTree.prototype, ['create', 'update', 'remove'], tree => tree.ctx],
334
- ];
335
- for (const [prototype, names] of contracts) for (const name of names) {
336
- if (typeof prototype[name] !== 'function') throw incompatible(`Official Loader contract lacks ${name}`);
337
- }
338
- if (typeof Hmr.prototype.runExclusive !== 'function' || typeof ConfigEditor.prototype.edit !== 'function') {
339
- throw incompatible('Official HMR/ConfigEditor transaction entrypoints are unavailable');
340
- }
341
- for (const [prototype, names, context] of contracts) for (const name of names) {
342
- const original = prototype[name];
343
- prototype[name] = function (...args) {
344
- installRefresh(this.tree ?? this.parent?.tree ?? this);
345
- const scope = scoped(context(this));
346
- return scope ? scope.steps.capture(() => Reflect.apply(original, this, args)) : Reflect.apply(original, this, args);
347
- };
348
- }
349
- const exclusive = Hmr.prototype.runExclusive;
350
- Hmr.prototype.runExclusive = function (operation) {
351
- try {
352
- const ctx = serviceCheck(this, 'hmr');
353
- const generation = ctx.fiber._generation;
354
- const invoke = () => {
355
- serviceCheck(this, 'hmr');
356
- if (ctx.fiber._generation !== generation) throw new LoaderError('STALE_OFFICIAL_SERVICE', 'HMR changed while its operation was queued');
357
- return Reflect.apply(exclusive, this, [operation]);
358
- };
359
- return scoped(ctx) ? invoke() : coordinate(ctx.root, invoke);
360
- } catch (error) { return Promise.reject(error); }
361
- };
362
- installModuleObservation(Hmr);
363
- Hmr.prototype.partialReload = function () {
364
- try {
365
- const ctx = serviceCheck(this, 'hmr');
366
- const changed = [...this.stashed];
367
- const execute = () => {
368
- const scope = scoped(ctx);
369
- if (!scope) throw incompatible('HMR escaped its admitted transaction');
370
- return reloadModules(this, scope.steps, changed, scope.retired);
371
- };
372
- return scoped(ctx) ? execute() : this.runExclusive(execute);
373
- } catch (error) { return Promise.reject(error); }
374
- };
375
- const edit = ConfigEditor.prototype.edit;
376
- ConfigEditor.prototype.edit = function (entry, change) {
377
- try {
378
- const ctx = serviceCheck(this, 'configEditor');
379
- const generation = ctx.fiber._generation;
380
- if (typeof change !== 'function') throw new TypeError('ConfigEditor.edit requires a change callback');
381
- const invoke = () => {
382
- serviceCheck(this, 'configEditor');
383
- if (ctx.fiber._generation !== generation) throw new LoaderError('STALE_OFFICIAL_SERVICE', 'ConfigEditor changed while its edit was queued');
384
- return Reflect.apply(edit, this, [entry, (...args) => {
385
- const scope = scoped(ctx);
386
- if (!scope) throw incompatible('ConfigEditor callback escaped its admitted transaction');
387
- return scope.steps.observe(entry.fiber, () => change(...args));
388
- }]);
389
- };
390
- return scoped(ctx) ? invoke() : coordinate(ctx.root, invoke);
391
- } catch (error) { return Promise.reject(error); }
392
- };
393
- for (const value of classes) installedContracts.set(value, classes);
394
- }
package/official-hmr.js DELETED
@@ -1,304 +0,0 @@
1
- import { AsyncLocalStorage } from 'node:async_hooks';
2
- import { createRequire, registerHooks } from 'node:module';
3
- import { realpathSync } from 'node:fs';
4
- import { resolve } from 'node:path';
5
- import { fileURLToPath, pathToFileURL } from 'node:url';
6
- import { FiberState, Service } from '@validation-engineering/cordis';
7
- import { LoaderError } from './config.js';
8
-
9
- const require = createRequire(import.meta.url);
10
- const observed = new Map(), hosts = new Set(), pending = new WeakMap();
11
- const candidateScope = new AsyncLocalStorage();
12
- const original = value => value?.[Symbol.for('cordis.original')] ?? value;
13
- let installed = false, active, tail = Promise.resolve();
14
- const file = url => typeof url === 'string' && url.startsWith('file:');
15
- const native = url => file(url) && fileURLToPath(url).endsWith('.node');
16
- const fail = (code, message, details, cause) => new LoaderError(code, message, details, cause);
17
- const add = (graph, parent, child) => {
18
- if (!file(parent) || !file(child)) return;
19
- if (!graph.has(parent)) graph.set(parent, new Set());
20
- graph.get(parent).add(child);
21
- };
22
- const clone = graph => new Map([...graph].map(([key, values]) => [key, new Set(values)]));
23
- function boundary(url) {
24
- const bootstrap = globalThis[Symbol.for('cordis-verus.compat-cordis.bootstrap.v1')];
25
- return url === bootstrap?.facade || url === bootstrap?.facade?.replace(/index\.js$/, 'index.cjs');
26
- }
27
-
28
- /** Install once before application imports, recording dynamic import/createRequire edges too. */
29
- export function installModuleObservation(Hmr) {
30
- if (!installed) {
31
- installed = true;
32
- registerHooks({ resolve(specifier, context, next) {
33
- const result = next(specifier, context);
34
- const candidate = candidateScope.getStore();
35
- const ours = candidate && candidate === active && candidate.active;
36
- if (active && !ours && file(result.url)) {
37
- if (active.affected.has(result.url)) throw fail('OFFICIAL_HMR_MODULE_BUSY', 'An affected module is being replaced', { url: result.url });
38
- active.foreignURLs.add(result.url);
39
- active.foreignEdges.add(JSON.stringify([context.parentURL, result.url]));
40
- }
41
- if (ours && candidate.urls.has(context.parentURL) && file(result.url) && !boundary(result.url)) {
42
- if (native(result.url)) throw fail('OFFICIAL_HMR_NATIVE_ADDON', 'Native addons require a process restart', { url: result.url });
43
- candidate.urls.add(result.url);
44
- if (!observed.get(context.parentURL)?.has(result.url)) candidate.addedEdges.push([context.parentURL, result.url]);
45
- }
46
- add(observed, context.parentURL, result.url);
47
- return result;
48
- } });
49
- }
50
- Object.defineProperty(Hmr.prototype, 'lastReloadFailure', { configurable: true, get() { return pending.get(original(this))?.error; } });
51
- const init = Hmr.prototype[Service.init];
52
- if (typeof init !== 'function') return; // Contract-only fixtures have no runtime.
53
- Hmr.prototype[Service.init] = async function* () {
54
- hosts.add(this);
55
- yield () => { hosts.delete(this); };
56
- yield* Reflect.apply(init, this, []);
57
- // The official watcher only tests ESM loadCache.has(). Pure require() leaves
58
- // must also enter its existing stashed batch, before the debounce fires.
59
- const changed = path => {
60
- let filename = resolve(this.baseDir, path);
61
- try { filename = realpathSync(filename); } catch { /* deleted file */ }
62
- if (require.cache[filename]) this.stashed.add(pathToFileURL(filename).href);
63
- };
64
- this.watcher.on('change', changed);
65
- yield () => { this.watcher.off('change', changed); };
66
- };
67
- }
68
-
69
- async function graphOf(cache) {
70
- const graph = clone(observed);
71
- // Cache values differ across supported Node internals; use the official get
72
- // API for the job and retain raw Map values only for exact restoration.
73
- for (const url of cache.keys()) {
74
- const job = cache.get(url);
75
- if (!job?.module || !file(url)) continue;
76
- const children = await job.linked;
77
- for (const child of Array.from(children ?? [])) add(graph, url, child.url);
78
- }
79
- for (const module of Object.values(require.cache)) {
80
- if (!module?.filename) continue;
81
- for (const child of module.children ?? []) if (child.filename) add(graph,
82
- pathToFileURL(module.filename).href, pathToFileURL(child.filename).href);
83
- }
84
- return graph;
85
- }
86
- function closure(roots, graph) {
87
- const result = new Set(), queue = [...roots];
88
- for (const url of queue) {
89
- if (result.has(url) || boundary(url)) continue;
90
- result.add(url);
91
- queue.push(...graph.get(url) ?? []);
92
- }
93
- return result;
94
- }
95
- async function entriesOf(hmr) {
96
- const result = new Map();
97
- for (const entry of hmr.ownerContext.loader.entries()) {
98
- if (!entry.fiber || entry.fiber.uid === null || entry.options.name.startsWith('cordis:')) continue;
99
- const resolved = await hmr._resolve(entry.options.name, entry.parent.tree.ctx.baseUrl, {});
100
- if (!file(resolved?.url)) continue;
101
- const job = hmr.internal.loadCache.get(resolved.url);
102
- const plugin = hmr.ownerContext.loader.unwrapExports(job?.module?.getNamespace());
103
- if (!plugin) throw fail('OFFICIAL_HMR_GRAPH_UNAVAILABLE', 'A live entry has no evaluated module job', { url: resolved.url });
104
- result.set(resolved.url, { url: resolved.url, plugin, runtime: hmr.ownerContext.registry.get(plugin) });
105
- }
106
- return result;
107
- }
108
- async function plan(hmr, changed) {
109
- const cache = hmr.internal?.loadCache;
110
- try { Map.prototype.has.call(cache, ''); }
111
- catch { throw fail('OFFICIAL_HMR_GRAPH_UNAVAILABLE', 'Supported Node module internals are required'); }
112
- if (changed.some(url => boundary(url) || native(url))) throw fail('OFFICIAL_HMR_HOST_BOUNDARY', 'Native runtime and facade changes require a process restart');
113
- const graph = await graphOf(cache), entries = await entriesOf(hmr);
114
- const owned = closure(entries.keys(), graph), reverse = new Map();
115
- for (const [parent, children] of graph) for (const child of children) add(reverse, child, parent);
116
- const affected = closure(changed.filter(url => cache.has(url) || file(url) && require.cache[fileURLToPath(url)]), reverse);
117
- if (!affected.size) return { cache, graph, affected, generations: [] };
118
- const loaded = url => cache.has(url) || file(url) && !!require.cache[fileURLToPath(url)];
119
- for (const url of affected) {
120
- if (boundary(url) || hmr.externals?.has(url) || loaded(url) && !owned.has(url)) {
121
- throw fail('OFFICIAL_HMR_HOST_BOUNDARY', `A changed module is retained outside the configured plugin graph: ${url}`, { url });
122
- }
123
- }
124
- // Node caches are process-global. Another native domain retaining a factory
125
- // cannot silently inherit this domain's replacement.
126
- for (const host of hosts) if (host.ownerContext.fiber !== hmr.ownerContext.fiber && host.ownerContext.fiber.uid !== null) {
127
- const other = closure((await entriesOf(host)).keys(), graph);
128
- if ([...affected].some(url => other.has(url))) throw fail('OFFICIAL_HMR_HOST_BOUNDARY', 'Another application domain retains the changed module');
129
- }
130
- const generations = [...entries.values()].filter(entry => affected.has(entry.url));
131
- if (!generations.length) throw fail('OFFICIAL_HMR_HOST_BOUNDARY', 'Changed modules have no configured live plugin owner');
132
- const dependencies = closure(generations.map(entry => entry.url), graph);
133
- const addon = [...dependencies].find(native);
134
- if (addon) throw fail('OFFICIAL_HMR_NATIVE_ADDON', 'A plugin with native addon dependencies requires a process restart', { url: addon });
135
- const represented = new Set(generations.map(entry => entry.runtime?.callback));
136
- const all = new Set(generations.flatMap(entry => [...entry.runtime?.fibers ?? []]));
137
- const ownedByReplacement = fiber => {
138
- for (let parent = fiber.parent.fiber; parent && parent !== parent.parent.fiber; parent = parent.parent.fiber) if (all.has(parent)) return true;
139
- return false;
140
- };
141
- for (const url of affected) {
142
- const job = cache.get(url);
143
- let namespace;
144
- try { namespace = job?.module?.getNamespace() ?? (file(url) && require.cache[fileURLToPath(url)] ? { default: require.cache[fileURLToPath(url)].exports } : undefined); } catch { continue; }
145
- if (!namespace) continue;
146
- for (const value of Object.values(namespace)) {
147
- const runtime = hmr.ownerContext.registry.get(value);
148
- if (runtime?.fibers.length && !represented.has(runtime.callback) && ![...runtime.fibers].every(ownedByReplacement)) throw fail('OFFICIAL_HMR_HOST_BOUNDARY',
149
- 'A loaded factory retained outside named Loader entries depends on the changed module', { url });
150
- }
151
- }
152
- for (let owner = hmr.ownerContext.fiber; owner; owner = owner === owner.parent.fiber ? undefined : owner.parent.fiber) {
153
- if (all.has(owner)) throw fail('OFFICIAL_HMR_HOST_BOUNDARY', 'HMR cannot replace its own lifecycle coordinator');
154
- }
155
- for (const generation of generations) generation.fibers = [...generation.runtime?.fibers ?? []].filter(fiber => {
156
- return !ownedByReplacement(fiber);
157
- }).map(fiber => ({ fiber, parent: fiber.parent, entry: fiber.entry?.fiber === fiber ? fiber.entry : undefined,
158
- parentGeneration: fiber.parent.fiber._generation, config: fiber.entry?.fiber === fiber ? fiber.entry.options.config : fiber._config }));
159
- return { cache, graph, affected: new Set([...affected].filter(loaded)), generations };
160
- }
161
-
162
- async function landAll(tasks, message) {
163
- const result = await Promise.allSettled(tasks), errors = result.filter(item => item.status === 'rejected').map(item => item.reason);
164
- if (errors.length) throw new AggregateError(errors, message);
165
- }
166
- function cacheJournal(cache, affected) {
167
- const esm = new Map(cache), cjs = new Map(Object.entries(require.cache));
168
- const edges = clone(observed);
169
- const children = new Map([...cjs.values()].map(module => [module, [...module.children ?? []]]));
170
- const urls = new Set(affected);
171
- const addedEdges = [], foreignURLs = new Set(), foreignEdges = new Set();
172
- return { urls, affected, addedEdges, foreignURLs, foreignEdges, active: true,
173
- release() {
174
- // Live timers/promises inherit candidateScope. Do not retain complete old
175
- // module caches through the successful generation's asynchronous work.
176
- this.active = false;
177
- esm.clear(); cjs.clear(); edges.clear(); children.clear();
178
- urls.clear(); addedEdges.length = 0; foreignURLs.clear(); foreignEdges.clear(); affected.clear();
179
- },
180
- invalidate() {
181
- for (const url of affected) {
182
- Map.prototype.delete.call(cache, url); observed.delete(url);
183
- if (file(url)) delete require.cache[fileURLToPath(url)];
184
- }
185
- },
186
- restore() {
187
- for (const url of urls) {
188
- if (affected.has(url) || !esm.has(url) && !foreignURLs.has(url)) {
189
- if (esm.has(url)) Map.prototype.set.call(cache, url, esm.get(url));
190
- else Map.prototype.delete.call(cache, url);
191
- }
192
- if (file(url)) {
193
- const path = fileURLToPath(url);
194
- if (affected.has(url) || !cjs.has(path) && !foreignURLs.has(url)) {
195
- if (cjs.has(path)) require.cache[path] = cjs.get(path);
196
- else delete require.cache[path];
197
- }
198
- }
199
- if (affected.has(url)) {
200
- if (edges.has(url)) observed.set(url, new Set(edges.get(url))); else observed.delete(url);
201
- }
202
- }
203
- for (const [parent, child] of addedEdges) {
204
- if (!edges.get(parent)?.has(child) && !foreignEdges.has(JSON.stringify([parent, child]))) observed.get(parent)?.delete(child);
205
- }
206
- for (const [module, prior] of children) {
207
- // Remove only this candidate's new child links; concurrent unrelated
208
- // imports and their cache identities remain untouched.
209
- module.children = [...prior, ...(module.children ?? []).filter(child => !prior.includes(child)
210
- && (!child.filename || !urls.has(pathToFileURL(child.filename).href) || foreignURLs.has(pathToFileURL(child.filename).href)))];
211
- }
212
- },
213
- };
214
- }
215
- async function activate(hmr, generations, restored, created) {
216
- for (const generation of generations) {
217
- const plugin = restored ? generation.plugin : generation.replacement;
218
- for (const record of generation.fibers) {
219
- if (record.parent.fiber.uid === null || record.parent.fiber._generation !== record.parentGeneration
220
- || record.entry && (record.entry.parent.tree.store[record.entry.options.id] !== record.entry || record.entry.options.config !== record.config)) throw fail('OFFICIAL_HMR_OWNER_REMOVED', 'Plugin owner disappeared during replacement');
221
- const fiber = record.parent.registry.plugin(plugin, record.config, hmr.getOuterStack).ctx.fiber;
222
- created.add(fiber);
223
- if (record.entry) { fiber.entry = record.entry; record.entry.fiber = fiber; }
224
- }
225
- }
226
- await landAll([...created].map(async fiber => {
227
- await fiber.await();
228
- if (fiber.state !== FiberState.ACTIVE) throw fail('OFFICIAL_HMR_NOT_ACTIVE', 'Replacement did not reach Active', { fiber: fiber.id, state: fiber.state });
229
- }), 'Plugin activation failed');
230
- }
231
-
232
- /** Caller already owns both the native domain coordinator and official HMR queue. */
233
- export function reloadModules(hmr, steps, changed, retired) {
234
- const task = tail.then(() => performReload(hmr, steps, changed, retired));
235
- tail = task.catch(() => {});
236
- return task;
237
- }
238
- async function recover(hmr, steps, state, retired) {
239
- try {
240
- await landAll([...state.created].map(fiber => steps.dispose(fiber).then(() => { retired.add(fiber); })), 'Retained generation cleanup failed');
241
- } catch (cause) {
242
- state.error = fail('OFFICIAL_HMR_RECOVERY_BLOCKED', 'Retained cleanup remains unconfirmed; explicitly retry failed inverses first',
243
- { restored: false, cacheChanged: state.invalidated }, new AggregateError([state.cause, cause]));
244
- throw state.error;
245
- }
246
- if (state.invalidated) state.journal.restore();
247
- state.invalidated = false;
248
- // A later old-activation failure retains only factories/ownership recipes:
249
- // the cache itself has already been restored and no longer needs a backup.
250
- state.journal?.release();
251
- state.created = new Set();
252
- try { await activate(hmr, state.generations, true, state.created); }
253
- catch (cause) {
254
- state.error = fail('OFFICIAL_HMR_RESTORE_FAILED', 'Old cache restored but old activation failed',
255
- { restored: false, cacheChanged: false }, new AggregateError([state.cause, cause], 'Reload and restoration failed'));
256
- throw state.error;
257
- }
258
- pending.delete(original(hmr));
259
- }
260
- async function performReload(hmr, steps, changed, retired) {
261
- if (!steps.isCurrent()) throw fail('REENTRANT_MUTATION', 'Module replacement lost its coordinator');
262
- const retained = pending.get(original(hmr));
263
- if (retained) await recover(hmr, steps, retained, retired);
264
- const { cache, affected, generations } = await plan(hmr, changed);
265
- if (!generations.length) { for (const url of changed) hmr.stashed.delete(url); return; }
266
- const old = generations.flatMap(generation => generation.fibers.map(record => record.fiber));
267
- const created = new Set();
268
- try { await landAll(old.map(fiber => steps.dispose(fiber).then(() => { retired.add(fiber); })), 'Old plugin cleanup failed'); }
269
- catch (cause) {
270
- const error = fail('OFFICIAL_HMR_CLEANUP_FAILED', 'Old cleanup is unconfirmed; module caches were not changed', { cacheChanged: false, restored: false }, cause);
271
- pending.set(original(hmr), { generations, created: new Set(old), invalidated: false, cause, error });
272
- throw error;
273
- }
274
- // Old inverses may import modules. Their completed imports are part of the
275
- // pre-candidate baseline and must survive a later candidate failure.
276
- const journal = cacheJournal(cache, affected);
277
- journal.invalidate(); active = journal;
278
- return candidateScope.run(journal, async () => {
279
- try {
280
- for (const generation of generations) generation.replacement = hmr.ownerContext.loader.unwrapExports(
281
- await hmr.ownerContext.loader.import(generation.url, hmr.getOuterStack));
282
- const candidateGraph = await graphOf(cache);
283
- const addon = [...closure(generations.map(generation => generation.url), candidateGraph)].find(native);
284
- if (addon) throw fail('OFFICIAL_HMR_NATIVE_ADDON', 'Candidate contains a native addon dependency', { url: addon });
285
- await activate(hmr, generations, false, created);
286
- } catch (cause) {
287
- // Include roots created by a synchronous plugin() exception, which may not
288
- // have returned a handle. Their retained runtime remains observable.
289
- for (const generation of generations) if (generation.replacement) {
290
- for (const fiber of hmr.ownerContext.registry.get(generation.replacement)?.fibers ?? []) created.add(fiber);
291
- }
292
- const state = { generations, journal, created, invalidated: true, cause };
293
- pending.set(original(hmr), state);
294
- await recover(hmr, steps, state, retired);
295
- active = undefined;
296
- throw fail('OFFICIAL_HMR_RELOAD_FAILED', 'Candidate failed; old module identities and plugins were restored',
297
- { restored: true, cacheChanged: false }, cause);
298
- } finally { journal.active = false; active = undefined; }
299
- for (const url of changed) hmr.stashed.delete(url);
300
- hmr.accepted = new Set(affected);
301
- journal.release();
302
- hmr.ownerContext.emit('hmr/reload', new Map(generations.map(generation => [generation.plugin, { filename: generation.url, runtime: generation.runtime }])));
303
- });
304
- }