@zhin.js/runtime 1.0.11 → 1.0.13
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/hmr-coordinator.d.ts +1 -1
- package/lib/hmr-coordinator.js +82 -9
- package/lib/plugin-scope-assembler.d.ts +4 -1
- package/lib/plugin-scope-assembler.js +4 -1
- package/lib/root-runtime.d.ts +2 -1
- package/lib/root-runtime.js +27 -48
- package/lib/runtime-generation.d.ts +7 -3
- package/lib/runtime-generation.js +8 -1
- package/lib/slot-generation-preparer.d.ts +1 -1
- package/lib/slot-generation-preparer.js +6 -9
- package/lib/subtree-generation-preparer.d.ts +1 -1
- package/lib/subtree-generation-preparer.js +12 -15
- package/lib/topology-generation-preparer.d.ts +1 -1
- package/lib/topology-generation-preparer.js +14 -17
- package/package.json +13 -13
package/lib/hmr-coordinator.d.ts
CHANGED
package/lib/hmr-coordinator.js
CHANGED
|
@@ -5,12 +5,19 @@ export class HmrCoordinator {
|
|
|
5
5
|
#waiters = [];
|
|
6
6
|
#draining;
|
|
7
7
|
#unwatch;
|
|
8
|
+
#closing = false;
|
|
9
|
+
#restartRequired = false;
|
|
10
|
+
#stopResult;
|
|
8
11
|
constructor(options) {
|
|
9
12
|
this.options = options;
|
|
10
13
|
}
|
|
11
14
|
start() {
|
|
12
15
|
if (this.#unwatch)
|
|
13
16
|
throw new Error('HmrCoordinator is already started');
|
|
17
|
+
if (this.#closing)
|
|
18
|
+
throw new Error('HmrCoordinator has been stopped');
|
|
19
|
+
if (this.#restartRequired)
|
|
20
|
+
throw new Error('HmrCoordinator requires a process restart');
|
|
14
21
|
if (!this.options.modules.watch) {
|
|
15
22
|
throw new Error('ModuleRuntime does not provide a file watcher');
|
|
16
23
|
}
|
|
@@ -21,10 +28,23 @@ export class HmrCoordinator {
|
|
|
21
28
|
return () => this.stop();
|
|
22
29
|
}
|
|
23
30
|
stop() {
|
|
31
|
+
if (this.#stopResult)
|
|
32
|
+
return this.#stopResult;
|
|
33
|
+
this.#closing = true;
|
|
24
34
|
this.#unwatch?.();
|
|
25
35
|
this.#unwatch = undefined;
|
|
36
|
+
this.#stopResult = (async () => {
|
|
37
|
+
await this.#draining;
|
|
38
|
+
})();
|
|
39
|
+
return this.#stopResult;
|
|
26
40
|
}
|
|
27
41
|
enqueue(source) {
|
|
42
|
+
if (this.#closing) {
|
|
43
|
+
return Promise.reject(new Error('HMR coordinator is stopping'));
|
|
44
|
+
}
|
|
45
|
+
if (this.#restartRequired) {
|
|
46
|
+
return Promise.reject(new Error('HMR coordinator requires a process restart'));
|
|
47
|
+
}
|
|
28
48
|
this.#pending.add(source);
|
|
29
49
|
const completed = new Promise((resolve, reject) => {
|
|
30
50
|
this.#waiters.push({ resolve, reject });
|
|
@@ -50,14 +70,16 @@ export class HmrCoordinator {
|
|
|
50
70
|
this.#pending.clear();
|
|
51
71
|
const forcedRestart = changed.filter((source) => this.options.modules.requiresProcessRestart?.(source));
|
|
52
72
|
if (forcedRestart.length > 0) {
|
|
53
|
-
|
|
73
|
+
this.#restartRequired = true;
|
|
74
|
+
this.#pending.clear();
|
|
75
|
+
this.#notifyRestart(Object.freeze({
|
|
54
76
|
kind: 'process',
|
|
55
77
|
changed: Object.freeze(changed),
|
|
56
78
|
reasons: Object.freeze([
|
|
57
79
|
`Module loader cannot safely invalidate: ${forcedRestart.join(', ')}`,
|
|
58
80
|
]),
|
|
59
81
|
}));
|
|
60
|
-
|
|
82
|
+
break;
|
|
61
83
|
}
|
|
62
84
|
const dependencyPort = this.options.modules.affectedSources
|
|
63
85
|
? {
|
|
@@ -65,11 +87,14 @@ export class HmrCoordinator {
|
|
|
65
87
|
}
|
|
66
88
|
: undefined;
|
|
67
89
|
const plan = new InvalidationPlanner(this.options.ownership(), dependencyPort).plan(changed);
|
|
68
|
-
await this.options.onPlan?.(plan);
|
|
69
90
|
if (plan.kind === 'process') {
|
|
70
|
-
|
|
71
|
-
|
|
91
|
+
this.#restartRequired = true;
|
|
92
|
+
this.#pending.clear();
|
|
93
|
+
this.#notifyPlan(plan);
|
|
94
|
+
this.#notifyRestart(plan);
|
|
95
|
+
break;
|
|
72
96
|
}
|
|
97
|
+
this.#notifyPlan(plan);
|
|
73
98
|
if (plan.kind === 'none')
|
|
74
99
|
continue;
|
|
75
100
|
const startedAt = performance.now();
|
|
@@ -77,15 +102,30 @@ export class HmrCoordinator {
|
|
|
77
102
|
await this.options.modules.invalidate?.(source);
|
|
78
103
|
}
|
|
79
104
|
const restart = await this.options.runtime.reload(plan);
|
|
80
|
-
if (restart)
|
|
81
|
-
|
|
105
|
+
if (restart) {
|
|
106
|
+
this.#restartRequired = true;
|
|
107
|
+
this.#pending.clear();
|
|
108
|
+
this.#notifyRestart(restart);
|
|
109
|
+
break;
|
|
110
|
+
}
|
|
82
111
|
else {
|
|
83
112
|
// reload resolves only after RootController has committed the new
|
|
84
113
|
// generation. Read ownership now so failed transactions never make
|
|
85
114
|
// newly discovered workspace packages observable to the watcher.
|
|
86
115
|
this.#syncWatchRoots();
|
|
87
116
|
const durationMs = Number((performance.now() - startedAt).toFixed(1));
|
|
88
|
-
|
|
117
|
+
try {
|
|
118
|
+
await this.options.onReload?.(plan, durationMs);
|
|
119
|
+
}
|
|
120
|
+
catch (error) {
|
|
121
|
+
// A projection/observer cannot change an already committed reload.
|
|
122
|
+
try {
|
|
123
|
+
await this.options.onError(error);
|
|
124
|
+
}
|
|
125
|
+
catch {
|
|
126
|
+
// Diagnostic reporting is deliberately outside the outcome.
|
|
127
|
+
}
|
|
128
|
+
}
|
|
89
129
|
}
|
|
90
130
|
}
|
|
91
131
|
this.#resolveWaiters();
|
|
@@ -105,7 +145,7 @@ export class HmrCoordinator {
|
|
|
105
145
|
this.#draining = undefined;
|
|
106
146
|
// A source may arrive after the loop observed an empty queue but before
|
|
107
147
|
// this promise settled. Keep its waiter attached to a fresh transaction.
|
|
108
|
-
if (this.#pending.size > 0)
|
|
148
|
+
if (!this.#closing && !this.#restartRequired && this.#pending.size > 0)
|
|
109
149
|
this.#ensureDrain();
|
|
110
150
|
}
|
|
111
151
|
}
|
|
@@ -120,4 +160,37 @@ export class HmrCoordinator {
|
|
|
120
160
|
#syncWatchRoots() {
|
|
121
161
|
this.options.modules.updateWatchRoots?.(this.options.ownership().watchRoots());
|
|
122
162
|
}
|
|
163
|
+
#notifyRestart(plan) {
|
|
164
|
+
// Restart is a committed control outcome. Invoke the observer without
|
|
165
|
+
// awaiting it so a Process Host may await RootHost.stop() without waiting
|
|
166
|
+
// on the HMR drain that is currently delivering this notification.
|
|
167
|
+
try {
|
|
168
|
+
void Promise.resolve(this.options.onRestartRequired(plan)).catch((error) => {
|
|
169
|
+
this.#reportDiagnostic(error);
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
catch (error) {
|
|
173
|
+
this.#reportDiagnostic(error);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
#notifyPlan(plan) {
|
|
177
|
+
if (!this.options.onPlan)
|
|
178
|
+
return;
|
|
179
|
+
try {
|
|
180
|
+
void Promise.resolve(this.options.onPlan(plan)).catch((error) => {
|
|
181
|
+
this.#reportDiagnostic(error);
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
catch (error) {
|
|
185
|
+
this.#reportDiagnostic(error);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
#reportDiagnostic(error) {
|
|
189
|
+
try {
|
|
190
|
+
void Promise.resolve(this.options.onError(error)).catch(() => undefined);
|
|
191
|
+
}
|
|
192
|
+
catch {
|
|
193
|
+
// Diagnostic reporting cannot change an invalidation outcome.
|
|
194
|
+
}
|
|
195
|
+
}
|
|
123
196
|
}
|
|
@@ -8,6 +8,8 @@ import { type PrimaryConfig } from './primary-config.js';
|
|
|
8
8
|
import type { RuntimeConfigDocument } from './config-composer.js';
|
|
9
9
|
export type PluginConfigResolver = (node: PluginGraphNode) => unknown;
|
|
10
10
|
export interface RootResourceContext {
|
|
11
|
+
/** Exact shadow generation being assembled; never inferred from a latest snapshot. */
|
|
12
|
+
readonly generation: number;
|
|
11
13
|
readonly signal: AbortSignal;
|
|
12
14
|
readonly resources: Scope;
|
|
13
15
|
readonly lifecycle: DisposeStack;
|
|
@@ -36,13 +38,14 @@ export declare class PluginScopeAssembler {
|
|
|
36
38
|
private readonly configResolver;
|
|
37
39
|
private readonly environment;
|
|
38
40
|
private readonly primaryConfigDocument;
|
|
41
|
+
private readonly generation;
|
|
39
42
|
private readonly installResources?;
|
|
40
43
|
private readonly isolation?;
|
|
41
44
|
readonly scopes: Map<PluginId, Scope>;
|
|
42
45
|
readonly tree: Map<PluginId, PluginNodeSnapshot>;
|
|
43
46
|
readonly config: Map<PluginId, unknown>;
|
|
44
47
|
readonly resources: Map<PluginId, ReadonlyMap<TokenId, unknown>>;
|
|
45
|
-
constructor(modules: ModuleRuntime, configResolver: PluginConfigResolver, environment: RuntimeEnvironment, primaryConfigDocument: RuntimeConfigDocument, installResources?: RootResourceInstaller | undefined, environmentLayers?: EnvironmentLayers, seed?: PluginAssemblySeed, isolation?: IsolatedPluginRuntimePort | undefined);
|
|
48
|
+
constructor(modules: ModuleRuntime, configResolver: PluginConfigResolver, environment: RuntimeEnvironment, primaryConfigDocument: RuntimeConfigDocument, generation: number, installResources?: RootResourceInstaller | undefined, environmentLayers?: EnvironmentLayers, seed?: PluginAssemblySeed, isolation?: IsolatedPluginRuntimePort | undefined);
|
|
46
49
|
removeSubtrees(roots: readonly PluginId[]): void;
|
|
47
50
|
installSetupFeatureAliases(aliases: ReadonlyMap<string, FeatureId>): void;
|
|
48
51
|
setupTree(node: PluginGraphNode, signal: AbortSignal): Promise<void>;
|
|
@@ -9,6 +9,7 @@ export class PluginScopeAssembler {
|
|
|
9
9
|
configResolver;
|
|
10
10
|
environment;
|
|
11
11
|
primaryConfigDocument;
|
|
12
|
+
generation;
|
|
12
13
|
installResources;
|
|
13
14
|
isolation;
|
|
14
15
|
scopes;
|
|
@@ -21,11 +22,12 @@ export class PluginScopeAssembler {
|
|
|
21
22
|
#admission = createGenerationAdmissionGate();
|
|
22
23
|
#envStores;
|
|
23
24
|
#setupFeatureAliases = new Map();
|
|
24
|
-
constructor(modules, configResolver, environment, primaryConfigDocument, installResources, environmentLayers = {}, seed, isolation) {
|
|
25
|
+
constructor(modules, configResolver, environment, primaryConfigDocument, generation, installResources, environmentLayers = {}, seed, isolation) {
|
|
25
26
|
this.modules = modules;
|
|
26
27
|
this.configResolver = configResolver;
|
|
27
28
|
this.environment = environment;
|
|
28
29
|
this.primaryConfigDocument = primaryConfigDocument;
|
|
30
|
+
this.generation = generation;
|
|
29
31
|
this.installResources = installResources;
|
|
30
32
|
this.isolation = isolation;
|
|
31
33
|
this.#envStores = new EnvStoreFactory(environment, environmentLayers);
|
|
@@ -68,6 +70,7 @@ export class PluginScopeAssembler {
|
|
|
68
70
|
const config = createPrimaryConfig(this.primaryConfigDocument, environment);
|
|
69
71
|
scope.provide(primaryConfigToken, config);
|
|
70
72
|
await this.installResources?.({
|
|
73
|
+
generation: this.generation,
|
|
71
74
|
signal,
|
|
72
75
|
resources: scope,
|
|
73
76
|
lifecycle: scope.disposers,
|
package/lib/root-runtime.d.ts
CHANGED
|
@@ -8,6 +8,7 @@ import type { IsolatedPluginRuntimePort } from './isolation.js';
|
|
|
8
8
|
import type { ModuleRuntime } from './module-runtime.js';
|
|
9
9
|
import { type PluginConfigResolver, type RootResourceInstaller } from './plugin-scope-assembler.js';
|
|
10
10
|
import { HmrCoordinator, type HmrCoordinatorOptions } from './hmr-coordinator.js';
|
|
11
|
+
import { type RuntimeGenerationState } from './runtime-generation.js';
|
|
11
12
|
import { RootProcessRestartExecutor, type ProcessRestartAdapter } from './process-restart.js';
|
|
12
13
|
import { SourceOwnershipIndex } from './source-ownership.js';
|
|
13
14
|
export type { PluginConfigResolver, RootResourceContext, RootResourceInstaller, } from './plugin-scope-assembler.js';
|
|
@@ -27,7 +28,7 @@ export declare class RootRuntime {
|
|
|
27
28
|
constructor(options: RootRuntimeOptions);
|
|
28
29
|
get snapshot(): RuntimeSnapshot;
|
|
29
30
|
get snapshots(): SnapshotReader;
|
|
30
|
-
onGenerationCommit(listener: GenerationCommitListener): () => void;
|
|
31
|
+
onGenerationCommit(listener: GenerationCommitListener<RuntimeGenerationState>): () => void;
|
|
31
32
|
get sourceOwnership(): SourceOwnershipIndex;
|
|
32
33
|
start(): Promise<RuntimeSnapshot>;
|
|
33
34
|
reload(target?: PluginId | string): Promise<RuntimeSnapshot>;
|
package/lib/root-runtime.js
CHANGED
|
@@ -14,6 +14,7 @@ import { NodePackageResolver } from './package-resolver.js';
|
|
|
14
14
|
import { PluginScopeAssembler, } from './plugin-scope-assembler.js';
|
|
15
15
|
import { ProjectGraphService, } from './project-graph.js';
|
|
16
16
|
import { HmrCoordinator } from './hmr-coordinator.js';
|
|
17
|
+
import { prepareRuntimeGeneration, } from './runtime-generation.js';
|
|
17
18
|
import { RootProcessRestartExecutor, } from './process-restart.js';
|
|
18
19
|
import { SlotGenerationPreparer } from './slot-generation-preparer.js';
|
|
19
20
|
import { capabilityDeltaFromSlots, capabilityDeltaIds, ConventionCapabilityDeltaResolver, filterCapabilityDelta, mergeCapabilityDeltas, } from './convention-capability-delta.js';
|
|
@@ -33,8 +34,6 @@ export class RootRuntime {
|
|
|
33
34
|
#configDocument;
|
|
34
35
|
#installResources;
|
|
35
36
|
#isolation;
|
|
36
|
-
#ownership = SourceOwnershipIndex.empty();
|
|
37
|
-
#model;
|
|
38
37
|
#configPatchTail = Promise.resolve();
|
|
39
38
|
#stopResult;
|
|
40
39
|
#controller;
|
|
@@ -51,7 +50,7 @@ export class RootRuntime {
|
|
|
51
50
|
this.#configDocument = structuredClone(options.config ?? {});
|
|
52
51
|
this.#installResources = options.installResources;
|
|
53
52
|
this.#isolation = options.isolation;
|
|
54
|
-
this.#controller = new RootController(emptyState(), options.onControlError);
|
|
53
|
+
this.#controller = new RootController(emptyState(), options.onControlError, Object.freeze({ ownership: SourceOwnershipIndex.empty() }));
|
|
55
54
|
}
|
|
56
55
|
get snapshot() {
|
|
57
56
|
return this.#controller.snapshot;
|
|
@@ -63,7 +62,7 @@ export class RootRuntime {
|
|
|
63
62
|
return this.#controller.onGenerationCommit(listener);
|
|
64
63
|
}
|
|
65
64
|
get sourceOwnership() {
|
|
66
|
-
return this.#ownership;
|
|
65
|
+
return this.#controller.committed.state.ownership;
|
|
67
66
|
}
|
|
68
67
|
async start() {
|
|
69
68
|
if (this.#configPort) {
|
|
@@ -71,21 +70,15 @@ export class RootRuntime {
|
|
|
71
70
|
this.#configSnapshot = snapshot;
|
|
72
71
|
this.#configDocument = structuredClone(snapshot.document);
|
|
73
72
|
}
|
|
74
|
-
let prepared;
|
|
75
73
|
const snapshot = await this.#controller.start(async (current, signal) => {
|
|
76
|
-
|
|
77
|
-
return prepared.generation;
|
|
74
|
+
return (await this.#prepare(current, signal)).generation;
|
|
78
75
|
});
|
|
79
|
-
this.#accept(requirePrepared(prepared));
|
|
80
76
|
return snapshot;
|
|
81
77
|
}
|
|
82
78
|
async reload(target = rootPluginId()) {
|
|
83
|
-
let prepared;
|
|
84
79
|
const snapshot = await this.#controller.reload(target, async (current, signal) => {
|
|
85
|
-
|
|
86
|
-
return prepared.generation;
|
|
80
|
+
return (await this.#prepare(current, signal)).generation;
|
|
87
81
|
});
|
|
88
|
-
this.#accept(requirePrepared(prepared));
|
|
89
82
|
return snapshot;
|
|
90
83
|
}
|
|
91
84
|
patchConfig(patches) {
|
|
@@ -98,7 +91,7 @@ export class RootRuntime {
|
|
|
98
91
|
return new HmrCoordinator({
|
|
99
92
|
...options,
|
|
100
93
|
modules: this.#modules,
|
|
101
|
-
ownership: () => this
|
|
94
|
+
ownership: () => this.sourceOwnership,
|
|
102
95
|
runtime: {
|
|
103
96
|
reload: async (plan) => {
|
|
104
97
|
const result = await this.#reloadPlan(plan);
|
|
@@ -125,9 +118,9 @@ export class RootRuntime {
|
|
|
125
118
|
return result;
|
|
126
119
|
}
|
|
127
120
|
async #reloadPlan(plan) {
|
|
128
|
-
let prepared;
|
|
129
121
|
let restart;
|
|
130
122
|
const snapshot = await this.#controller.reload(plan.subtrees[0] ?? plan.slots[0] ?? rootPluginId(), async (current, signal) => {
|
|
123
|
+
let prepared;
|
|
131
124
|
const resolved = await this.#resolveCapabilityDelta(current, plan);
|
|
132
125
|
const effective = resolved.plan;
|
|
133
126
|
if (this.#model && effective.manifestSources.length > 0) {
|
|
@@ -154,13 +147,10 @@ export class RootRuntime {
|
|
|
154
147
|
});
|
|
155
148
|
if (restart)
|
|
156
149
|
return restart;
|
|
157
|
-
if (prepared)
|
|
158
|
-
this.#accept(prepared);
|
|
159
150
|
return snapshot;
|
|
160
151
|
}
|
|
161
|
-
#
|
|
162
|
-
this.#
|
|
163
|
-
this.#model = prepared.model;
|
|
152
|
+
get #model() {
|
|
153
|
+
return this.#controller.committed.state.model;
|
|
164
154
|
}
|
|
165
155
|
async #resolveCapabilityDelta(current, plan) {
|
|
166
156
|
const known = capabilityDeltaFromSlots(current, plan.slots);
|
|
@@ -185,7 +175,7 @@ export class RootRuntime {
|
|
|
185
175
|
if (plan.subtrees.length === 0 || plan.subtrees.includes(rootPluginId()))
|
|
186
176
|
return false;
|
|
187
177
|
return plan.changed.every((source) => {
|
|
188
|
-
const records = this
|
|
178
|
+
const records = this.sourceOwnership.recordsFor(source);
|
|
189
179
|
return records.length > 0 && records.every((record) => record.role === 'plugin' || record.role === 'schema');
|
|
190
180
|
});
|
|
191
181
|
}
|
|
@@ -247,10 +237,10 @@ export class RootRuntime {
|
|
|
247
237
|
await this.#refreshConfigDocument();
|
|
248
238
|
const currentDocument = requireConfigDocument(this.#configDocument);
|
|
249
239
|
let plan;
|
|
250
|
-
let prepared;
|
|
251
240
|
let documentTransaction;
|
|
252
241
|
let committedDocument;
|
|
253
242
|
const snapshot = await this.#controller.reload(rootPluginId(), async (current, signal) => {
|
|
243
|
+
let prepared;
|
|
254
244
|
signal.throwIfAborted();
|
|
255
245
|
const resolver = await NodePackageResolver.create(this.#projectRoot);
|
|
256
246
|
const graph = await new ProjectGraphService(resolver).inspect(this.#projectRoot);
|
|
@@ -305,8 +295,6 @@ export class RootRuntime {
|
|
|
305
295
|
}
|
|
306
296
|
});
|
|
307
297
|
const completed = requireConfigPatchPlan(plan);
|
|
308
|
-
if (prepared)
|
|
309
|
-
this.#accept(prepared);
|
|
310
298
|
this.#configDocument = completed.candidate;
|
|
311
299
|
if (committedDocument)
|
|
312
300
|
this.#configSnapshot = committedDocument;
|
|
@@ -406,7 +394,7 @@ class GenerationAssembler {
|
|
|
406
394
|
this.environmentLayers = environmentLayers;
|
|
407
395
|
this.isolation = isolation;
|
|
408
396
|
this.#host = new NodeDiscoveryHost(modules);
|
|
409
|
-
this.#plugins = new PluginScopeAssembler(modules, configResolver, environment, primaryConfigDocument, installResources, environmentLayers, undefined, isolation);
|
|
397
|
+
this.#plugins = new PluginScopeAssembler(modules, configResolver, environment, primaryConfigDocument, generation, installResources, environmentLayers, undefined, isolation);
|
|
410
398
|
}
|
|
411
399
|
async prepare(signal) {
|
|
412
400
|
signal.throwIfAborted();
|
|
@@ -426,25 +414,21 @@ class GenerationAssembler {
|
|
|
426
414
|
const snapshot = createSnapshotView(this.generation, state);
|
|
427
415
|
const ownership = SourceOwnershipIndex.fromGeneration(this.graph, snapshot, this.#featureIdsByPackageRoot);
|
|
428
416
|
const assets = GenerationAssets.create(this.#plugins.createdScopeDisposers(), this.#projectionDisposers);
|
|
429
|
-
return {
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
scopes: new Map(this.#plugins.scopes),
|
|
445
|
-
assets,
|
|
446
|
-
},
|
|
447
|
-
};
|
|
417
|
+
return prepareRuntimeGeneration({
|
|
418
|
+
snapshot: state,
|
|
419
|
+
dispose: () => assets.dispose(),
|
|
420
|
+
handoff: composeGenerationHandoffs(this.#plugins.generationHandoff(), projected.handoff),
|
|
421
|
+
}, ownership, {
|
|
422
|
+
graph: this.graph,
|
|
423
|
+
providers: new Map(this.#catalog.values().map((provider) => [provider.id, provider])),
|
|
424
|
+
rootsByFeature: new Map([...this.#rootsByFeature].map(([feature, roots]) => [
|
|
425
|
+
feature,
|
|
426
|
+
Object.freeze([...roots]),
|
|
427
|
+
])),
|
|
428
|
+
featureIdsByPackageRoot: new Map(this.#featureIdsByPackageRoot),
|
|
429
|
+
scopes: new Map(this.#plugins.scopes),
|
|
430
|
+
assets,
|
|
431
|
+
});
|
|
448
432
|
}
|
|
449
433
|
catch (error) {
|
|
450
434
|
await disposePreparedParts(this.#plugins.createdScopeDisposers().map(([, dispose]) => dispose), [...this.#projectionDisposers.values()], error);
|
|
@@ -507,11 +491,6 @@ function emptyState() {
|
|
|
507
491
|
projections: new Map(),
|
|
508
492
|
};
|
|
509
493
|
}
|
|
510
|
-
function requirePrepared(prepared) {
|
|
511
|
-
if (!prepared)
|
|
512
|
-
throw new Error('RootController committed without a prepared generation');
|
|
513
|
-
return prepared;
|
|
514
|
-
}
|
|
515
494
|
function isProcessPlan(value) {
|
|
516
495
|
return 'kind' in value && value.kind === 'process';
|
|
517
496
|
}
|
|
@@ -11,8 +11,12 @@ export interface RuntimeGenerationModel {
|
|
|
11
11
|
readonly scopes: ReadonlyMap<PluginId, Scope>;
|
|
12
12
|
readonly assets: GenerationAssets;
|
|
13
13
|
}
|
|
14
|
-
|
|
15
|
-
|
|
14
|
+
/** Sidecar state that must never be observed from a different generation. */
|
|
15
|
+
export interface RuntimeGenerationState {
|
|
16
16
|
readonly ownership: SourceOwnershipIndex;
|
|
17
|
-
readonly model
|
|
17
|
+
readonly model?: RuntimeGenerationModel;
|
|
18
|
+
}
|
|
19
|
+
export interface PreparedRuntimeGeneration {
|
|
20
|
+
readonly generation: PreparedGeneration<RuntimeGenerationState>;
|
|
18
21
|
}
|
|
22
|
+
export declare function prepareRuntimeGeneration(generation: PreparedGeneration, ownership: SourceOwnershipIndex, model: RuntimeGenerationModel): PreparedRuntimeGeneration;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { type CapabilityId, type RuntimeSnapshot } from '@zhin.js/plugin-runtime';
|
|
2
2
|
import type { ModuleRuntime } from './module-runtime.js';
|
|
3
|
-
import type
|
|
3
|
+
import { type PreparedRuntimeGeneration, type RuntimeGenerationModel } from './runtime-generation.js';
|
|
4
4
|
import { type CapabilityDelta } from './convention-capability-delta.js';
|
|
5
5
|
export declare class SlotGenerationPreparer {
|
|
6
6
|
private readonly modules;
|
|
@@ -2,6 +2,7 @@ import { DisposeStack, createSnapshotView, } from '@zhin.js/plugin-runtime';
|
|
|
2
2
|
import { FeatureDiscovery } from '@zhin.js/feature-kit';
|
|
3
3
|
import { FeatureProjector, composeGenerationHandoffs } from './feature-projector.js';
|
|
4
4
|
import { NodeDiscoveryHost } from './node-discovery-host.js';
|
|
5
|
+
import { prepareRuntimeGeneration, } from './runtime-generation.js';
|
|
5
6
|
import { SourceOwnershipIndex } from './source-ownership.js';
|
|
6
7
|
import { capabilityDeltaFromSlots, } from './convention-capability-delta.js';
|
|
7
8
|
export class SlotGenerationPreparer {
|
|
@@ -48,15 +49,11 @@ export class SlotGenerationPreparer {
|
|
|
48
49
|
const snapshot = createSnapshotView(current.generation + 1, projected.state);
|
|
49
50
|
const ownership = SourceOwnershipIndex.fromGeneration(this.model.graph, snapshot, this.model.featureIdsByPackageRoot);
|
|
50
51
|
const assets = this.model.assets.replaceProjections(selectedByFeature.keys(), projected.disposers);
|
|
51
|
-
return {
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
},
|
|
57
|
-
ownership,
|
|
58
|
-
model: { ...this.model, assets },
|
|
59
|
-
};
|
|
52
|
+
return prepareRuntimeGeneration({
|
|
53
|
+
snapshot: projected.state,
|
|
54
|
+
dispose: () => assets.dispose(),
|
|
55
|
+
handoff: composeGenerationHandoffs(projected.handoff),
|
|
56
|
+
}, ownership, { ...this.model, assets });
|
|
60
57
|
}
|
|
61
58
|
catch (error) {
|
|
62
59
|
await disposeProjections(projected.disposers.values(), error);
|
|
@@ -6,7 +6,7 @@ import type { IsolatedPluginRuntimePort } from './isolation.js';
|
|
|
6
6
|
import type { ModuleRuntime } from './module-runtime.js';
|
|
7
7
|
import { type PluginConfigResolver, type RootResourceInstaller } from './plugin-scope-assembler.js';
|
|
8
8
|
import type { ProjectGraph } from './project-graph.js';
|
|
9
|
-
import type
|
|
9
|
+
import { type PreparedRuntimeGeneration, type RuntimeGenerationModel } from './runtime-generation.js';
|
|
10
10
|
export declare class SubtreeTopologyChangedError extends Error {
|
|
11
11
|
constructor(message: string);
|
|
12
12
|
}
|
|
@@ -3,6 +3,7 @@ import { FeatureDiscovery } from '@zhin.js/feature-kit';
|
|
|
3
3
|
import { FeatureProjector, composeGenerationHandoffs } from './feature-projector.js';
|
|
4
4
|
import { NodeDiscoveryHost } from './node-discovery-host.js';
|
|
5
5
|
import { PluginScopeAssembler, } from './plugin-scope-assembler.js';
|
|
6
|
+
import { prepareRuntimeGeneration, } from './runtime-generation.js';
|
|
6
7
|
import { SourceOwnershipIndex } from './source-ownership.js';
|
|
7
8
|
import { addCapabilitySlot, featureSetupAliases, mergeSetupCapabilities, } from './setup-capabilities.js';
|
|
8
9
|
export class SubtreeTopologyChangedError extends Error {
|
|
@@ -37,7 +38,7 @@ export class SubtreeGenerationPreparer {
|
|
|
37
38
|
signal.throwIfAborted();
|
|
38
39
|
const nodes = indexGraph(this.graph);
|
|
39
40
|
assertCompatibleTopology(indexGraph(this.model.graph), nodes, roots);
|
|
40
|
-
const plugins = new PluginScopeAssembler(this.modules, this.configResolver, this.environment, this.primaryConfigDocument, this.installResources, this.environmentLayers, {
|
|
41
|
+
const plugins = new PluginScopeAssembler(this.modules, this.configResolver, this.environment, this.primaryConfigDocument, current.generation + 1, this.installResources, this.environmentLayers, {
|
|
41
42
|
scopes: this.model.scopes,
|
|
42
43
|
tree: current.tree,
|
|
43
44
|
config: current.config,
|
|
@@ -86,20 +87,16 @@ export class SubtreeGenerationPreparer {
|
|
|
86
87
|
// Local prepare still commits a complete immutable generation. The
|
|
87
88
|
// replacement map controls lifetime ownership, not snapshot granularity.
|
|
88
89
|
const assets = this.model.assets.replaceScopes([...nodes.keys()], replacements, projectionDisposers);
|
|
89
|
-
return {
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
scopes: new Map(plugins.scopes),
|
|
100
|
-
assets,
|
|
101
|
-
},
|
|
102
|
-
};
|
|
90
|
+
return prepareRuntimeGeneration({
|
|
91
|
+
snapshot: projected.state,
|
|
92
|
+
dispose: () => assets.dispose(),
|
|
93
|
+
handoff: composeGenerationHandoffs(plugins.generationHandoff(), projected.handoff),
|
|
94
|
+
}, ownership, {
|
|
95
|
+
...this.model,
|
|
96
|
+
graph: this.graph,
|
|
97
|
+
scopes: new Map(plugins.scopes),
|
|
98
|
+
assets,
|
|
99
|
+
});
|
|
103
100
|
}
|
|
104
101
|
catch (error) {
|
|
105
102
|
await rollback(plugins.createdScopeDisposers().map(([, dispose]) => dispose), [...projectionDisposers.values()], error);
|
|
@@ -6,7 +6,7 @@ import type { IsolatedPluginRuntimePort } from './isolation.js';
|
|
|
6
6
|
import type { ModuleRuntime } from './module-runtime.js';
|
|
7
7
|
import { type PluginConfigResolver, type RootResourceInstaller } from './plugin-scope-assembler.js';
|
|
8
8
|
import type { ProjectGraph } from './project-graph.js';
|
|
9
|
-
import type
|
|
9
|
+
import { type PreparedRuntimeGeneration, type RuntimeGenerationModel } from './runtime-generation.js';
|
|
10
10
|
import { type CapabilityDelta } from './convention-capability-delta.js';
|
|
11
11
|
/** Runtime-local invalidation to commit alongside an ABI-safe manifest change. */
|
|
12
12
|
export interface TopologyRuntimeDelta {
|
|
@@ -5,6 +5,7 @@ import { FeatureCatalog, FeatureDiscovery, } from '@zhin.js/feature-kit';
|
|
|
5
5
|
import { FeatureProjector, composeGenerationHandoffs } from './feature-projector.js';
|
|
6
6
|
import { NodeDiscoveryHost } from './node-discovery-host.js';
|
|
7
7
|
import { PluginScopeAssembler, } from './plugin-scope-assembler.js';
|
|
8
|
+
import { prepareRuntimeGeneration, } from './runtime-generation.js';
|
|
8
9
|
import { SourceOwnershipIndex } from './source-ownership.js';
|
|
9
10
|
import { capabilityDeltaFromSlots, capabilityDeltaIds, } from './convention-capability-delta.js';
|
|
10
11
|
import { addCapabilitySlot, featureSetupAliases, mergeSetupCapabilities, } from './setup-capabilities.js';
|
|
@@ -46,7 +47,7 @@ export class TopologyGenerationPreparer {
|
|
|
46
47
|
if (!plan.changed && subtreeRoots.length === 0 && selected.size === 0)
|
|
47
48
|
return undefined;
|
|
48
49
|
const featureTopology = await this.#loadFeatureTopology(plan);
|
|
49
|
-
const plugins = new PluginScopeAssembler(this.modules, this.configResolver, this.environment, this.primaryConfigDocument, this.installResources, this.environmentLayers, {
|
|
50
|
+
const plugins = new PluginScopeAssembler(this.modules, this.configResolver, this.environment, this.primaryConfigDocument, current.generation + 1, this.installResources, this.environmentLayers, {
|
|
50
51
|
scopes: this.model.scopes,
|
|
51
52
|
tree: current.tree,
|
|
52
53
|
config: current.config,
|
|
@@ -91,22 +92,18 @@ export class TopologyGenerationPreparer {
|
|
|
91
92
|
const ownership = SourceOwnershipIndex.fromGeneration(this.graph, snapshot, featureTopology.featureIdsByPackageRoot);
|
|
92
93
|
const replacements = new Map(plugins.createdScopeDisposers());
|
|
93
94
|
const assets = this.model.assets.replaceScopes(graphOrder(this.graph), replacements, projectionDisposers);
|
|
94
|
-
return {
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
scopes: new Map(plugins.scopes),
|
|
107
|
-
assets,
|
|
108
|
-
},
|
|
109
|
-
};
|
|
95
|
+
return prepareRuntimeGeneration({
|
|
96
|
+
snapshot: projected.state,
|
|
97
|
+
dispose: () => assets.dispose(),
|
|
98
|
+
handoff: composeGenerationHandoffs(plugins.generationHandoff(), projected.handoff),
|
|
99
|
+
}, ownership, {
|
|
100
|
+
graph: this.graph,
|
|
101
|
+
providers: featureTopology.providers,
|
|
102
|
+
rootsByFeature: featureTopology.rootsByFeature,
|
|
103
|
+
featureIdsByPackageRoot: featureTopology.featureIdsByPackageRoot,
|
|
104
|
+
scopes: new Map(plugins.scopes),
|
|
105
|
+
assets,
|
|
106
|
+
});
|
|
110
107
|
}
|
|
111
108
|
catch (error) {
|
|
112
109
|
await rollback(plugins.createdScopeDisposers().map(([, dispose]) => dispose), [...projectionDisposers.values()], error);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zhin.js/runtime",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.13",
|
|
4
4
|
"description": "Static Plugin graph, generation transaction and HMR Root runtime for Zhin.js",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./lib/index.js",
|
|
@@ -18,22 +18,22 @@
|
|
|
18
18
|
"dependencies": {
|
|
19
19
|
"ajv": "8.18.0",
|
|
20
20
|
"semver": "7.8.5",
|
|
21
|
-
"@zhin.js/feature-kit": "1.0.
|
|
22
|
-
"@zhin.js/plugin-runtime": "1.1.
|
|
21
|
+
"@zhin.js/feature-kit": "1.0.12",
|
|
22
|
+
"@zhin.js/plugin-runtime": "1.1.7"
|
|
23
23
|
},
|
|
24
24
|
"devDependencies": {
|
|
25
25
|
"@types/node": "^26.1.2",
|
|
26
26
|
"typescript": "^6.0.3",
|
|
27
|
-
"@zhin.js/adapter": "1.1.
|
|
28
|
-
"@zhin.js/agent-feature": "1.0.
|
|
29
|
-
"@zhin.js/command": "1.0.
|
|
30
|
-
"@zhin.js/component": "1.0.
|
|
31
|
-
"@zhin.js/
|
|
32
|
-
"@zhin.js/
|
|
33
|
-
"@zhin.js/
|
|
34
|
-
"@zhin.js/
|
|
35
|
-
"@zhin.js/
|
|
36
|
-
"@zhin.js/tool": "1.0.
|
|
27
|
+
"@zhin.js/adapter": "1.1.11",
|
|
28
|
+
"@zhin.js/agent-feature": "1.0.12",
|
|
29
|
+
"@zhin.js/command": "1.0.15",
|
|
30
|
+
"@zhin.js/component": "1.0.12",
|
|
31
|
+
"@zhin.js/mcp-feature": "1.0.12",
|
|
32
|
+
"@zhin.js/page": "1.0.12",
|
|
33
|
+
"@zhin.js/skill": "1.0.12",
|
|
34
|
+
"@zhin.js/layout": "1.0.12",
|
|
35
|
+
"@zhin.js/middleware": "1.0.12",
|
|
36
|
+
"@zhin.js/tool": "1.0.12"
|
|
37
37
|
},
|
|
38
38
|
"repository": {
|
|
39
39
|
"type": "git",
|