@zhin.js/runtime 1.0.9 → 1.0.11
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/config-composer.js +1 -5
- package/lib/feature-projector.d.ts +1 -1
- package/lib/feature-projector.js +4 -1
- package/lib/isolation.d.ts +1 -1
- package/lib/native-development-runtime.js +1 -1
- package/lib/platform-features.d.ts +1 -1
- package/lib/platform-features.js +1 -1
- package/lib/plugin-scope-assembler.d.ts +5 -2
- package/lib/plugin-scope-assembler.js +14 -4
- package/lib/root-runtime.d.ts +3 -2
- package/lib/root-runtime.js +64 -40
- package/lib/slot-generation-preparer.d.ts +1 -1
- package/lib/slot-generation-preparer.js +4 -2
- package/lib/subtree-generation-preparer.d.ts +1 -1
- package/lib/subtree-generation-preparer.js +5 -3
- package/lib/topology-generation-preparer.d.ts +1 -1
- package/lib/topology-generation-preparer.js +4 -3
- package/package.json +13 -13
package/lib/config-composer.js
CHANGED
|
@@ -37,7 +37,7 @@ export class ConfigComposer {
|
|
|
37
37
|
await composeNode(child, ownSchemas),
|
|
38
38
|
]));
|
|
39
39
|
// Host-level keys (`http`, `database`, `ai`, `mcp`, `a2a`, `speech`,
|
|
40
|
-
// `htmlRenderer`, `assistant`, `
|
|
40
|
+
// `htmlRenderer`, `assistant`, `log_level`) are consumed by CLI Root installers /
|
|
41
41
|
// start-command, not Plugin ConfigViews.
|
|
42
42
|
const effectiveSchema = Object.freeze({
|
|
43
43
|
type: 'object',
|
|
@@ -82,10 +82,6 @@ export class ConfigComposer {
|
|
|
82
82
|
type: 'object',
|
|
83
83
|
additionalProperties: true,
|
|
84
84
|
}),
|
|
85
|
-
collaboration: Object.freeze({
|
|
86
|
-
type: 'object',
|
|
87
|
-
additionalProperties: true,
|
|
88
|
-
}),
|
|
89
85
|
log_level: Object.freeze({
|
|
90
86
|
type: ['string', 'number'],
|
|
91
87
|
}),
|
|
@@ -10,6 +10,6 @@ export interface ProjectedFeatures {
|
|
|
10
10
|
export declare class FeatureProjector {
|
|
11
11
|
private readonly providers;
|
|
12
12
|
constructor(providers: Iterable<FeatureProvider>);
|
|
13
|
-
project(generation: number, base: ProjectionState, retained?: ReadonlyMap<FeatureId, unknown>): Promise<ProjectedFeatures>;
|
|
13
|
+
project(generation: number, base: ProjectionState, signal: AbortSignal, retained?: ReadonlyMap<FeatureId, unknown>): Promise<ProjectedFeatures>;
|
|
14
14
|
}
|
|
15
15
|
export declare function composeGenerationHandoffs(...handoffs: readonly (GenerationHandoff | undefined)[]): GenerationHandoff | undefined;
|
package/lib/feature-projector.js
CHANGED
|
@@ -5,7 +5,7 @@ export class FeatureProjector {
|
|
|
5
5
|
constructor(providers) {
|
|
6
6
|
this.providers = providers;
|
|
7
7
|
}
|
|
8
|
-
async project(generation, base, retained = new Map()) {
|
|
8
|
+
async project(generation, base, signal, retained = new Map()) {
|
|
9
9
|
// Slot HMR seeds this map with the committed projections. Only providers
|
|
10
10
|
// passed to this projector replace their entry; every other Feature keeps
|
|
11
11
|
// its live instance and therefore keeps its external resources running.
|
|
@@ -15,10 +15,13 @@ export class FeatureProjector {
|
|
|
15
15
|
const state = { ...base, projections };
|
|
16
16
|
try {
|
|
17
17
|
for (const provider of this.providers) {
|
|
18
|
+
signal.throwIfAborted();
|
|
18
19
|
const slots = [...base.capabilities.values()].filter((slot) => slot.feature === provider.id);
|
|
19
20
|
const projection = await provider.runtime.project(slots, {
|
|
20
21
|
snapshot: createSnapshotView(generation, state),
|
|
22
|
+
signal,
|
|
21
23
|
});
|
|
24
|
+
signal.throwIfAborted();
|
|
22
25
|
projections.set(provider.id, projection.value);
|
|
23
26
|
if (projection.dispose)
|
|
24
27
|
disposers.set(provider.id, projection.dispose);
|
package/lib/isolation.d.ts
CHANGED
|
@@ -24,5 +24,5 @@ export interface PreparedIsolatedPlugin {
|
|
|
24
24
|
}
|
|
25
25
|
/** Adapter seam for child Plugin lifecycle that must not execute in the Host realm. */
|
|
26
26
|
export interface IsolatedPluginRuntimePort {
|
|
27
|
-
prepare(request: IsolatedPluginPrepareRequest): Promise<PreparedIsolatedPlugin>;
|
|
27
|
+
prepare(request: IsolatedPluginPrepareRequest, signal: AbortSignal): Promise<PreparedIsolatedPlugin>;
|
|
28
28
|
}
|
|
@@ -8,7 +8,7 @@ const watchedExtensions = new Set([
|
|
|
8
8
|
'.cjs', '.js', '.json', '.md', '.mjs', '.ts', '.tsx', '.yaml', '.yml',
|
|
9
9
|
]);
|
|
10
10
|
const capabilityRoots = new Set([
|
|
11
|
-
'adapters', 'agents', 'commands', 'components', 'mcp', 'middlewares', 'pages', 'skills', 'tools',
|
|
11
|
+
'adapters', 'agents', 'commands', 'components', 'handlers', 'mcp', 'middlewares', 'pages', 'skills', 'tools',
|
|
12
12
|
]);
|
|
13
13
|
/**
|
|
14
14
|
* Uses Node's native ESM/TypeScript loader and adds only cache busting and watch.
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { ChildPluginReference, PackageReference } from './manifest.js';
|
|
2
2
|
/**
|
|
3
3
|
* Package whose `zhin.features` define the Stable Feature composition
|
|
4
|
-
* (adapter / command / component / middleware).
|
|
4
|
+
* (adapter / command / component / middleware / handler).
|
|
5
5
|
*
|
|
6
6
|
* Root inherits these when it depends on `@zhin.js/core` directly, or on the
|
|
7
7
|
* `zhin.js` facade (which depends on `@zhin.js/core`).
|
package/lib/platform-features.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Package whose `zhin.features` define the Stable Feature composition
|
|
3
|
-
* (adapter / command / component / middleware).
|
|
3
|
+
* (adapter / command / component / middleware / handler).
|
|
4
4
|
*
|
|
5
5
|
* Root inherits these when it depends on `@zhin.js/core` directly, or on the
|
|
6
6
|
* `zhin.js` facade (which depends on `@zhin.js/core`).
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { DisposeStack, Scope, type Dispose, type FeatureId, type GenerationHandoff, type GenerationHandoffRegistry, type PluginId, type PluginNodeSnapshot, type SetupCapabilityRegistration, type TokenId } from '@zhin.js/plugin-runtime';
|
|
1
|
+
import { DisposeStack, Scope, type Dispose, type FeatureId, type GenerationHandoff, type GenerationAdmissionGate, type GenerationHandoffRegistry, type PluginId, type PluginNodeSnapshot, type SetupCapabilityRegistration, type TokenId } from '@zhin.js/plugin-runtime';
|
|
2
2
|
import { type RuntimeEnvironment } from './environment.js';
|
|
3
3
|
import { type EnvironmentLayers } from './environment-store.js';
|
|
4
4
|
import type { ModuleRuntime } from './module-runtime.js';
|
|
@@ -8,9 +8,12 @@ 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
|
+
readonly signal: AbortSignal;
|
|
11
12
|
readonly resources: Scope;
|
|
12
13
|
readonly lifecycle: DisposeStack;
|
|
13
14
|
readonly handoff: GenerationHandoffRegistry;
|
|
15
|
+
/** Candidate-wide gate atomically switched by SnapshotStore at commit. */
|
|
16
|
+
readonly admission: GenerationAdmissionGate;
|
|
14
17
|
readonly config: PrimaryConfig;
|
|
15
18
|
/**
|
|
16
19
|
* Adds a root-owned capability to this shadow generation. The definition is
|
|
@@ -42,7 +45,7 @@ export declare class PluginScopeAssembler {
|
|
|
42
45
|
constructor(modules: ModuleRuntime, configResolver: PluginConfigResolver, environment: RuntimeEnvironment, primaryConfigDocument: RuntimeConfigDocument, installResources?: RootResourceInstaller | undefined, environmentLayers?: EnvironmentLayers, seed?: PluginAssemblySeed, isolation?: IsolatedPluginRuntimePort | undefined);
|
|
43
46
|
removeSubtrees(roots: readonly PluginId[]): void;
|
|
44
47
|
installSetupFeatureAliases(aliases: ReadonlyMap<string, FeatureId>): void;
|
|
45
|
-
setupTree(node: PluginGraphNode): Promise<void>;
|
|
48
|
+
setupTree(node: PluginGraphNode, signal: AbortSignal): Promise<void>;
|
|
46
49
|
synchronizeTree(node: PluginGraphNode): void;
|
|
47
50
|
createdScopeDisposers(): readonly (readonly [PluginId, Dispose])[];
|
|
48
51
|
generationHandoff(): GenerationHandoff | undefined;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { resolve } from 'node:path';
|
|
2
|
-
import { GenerationHandoffStack, Scope, capabilityId, createPluginDatabaseHost, createPluginScheduleHost, databaseHostToken, databaseRootHostToken, featureId, rootPluginId, scheduleHostToken, scheduleRootHostToken, unwrapPluginDatabaseHost, unwrapPluginScheduleHost, } from '@zhin.js/plugin-runtime';
|
|
2
|
+
import { GenerationHandoffStack, Scope, capabilityId, createGenerationAdmissionGate, createToken, createPluginDatabaseHost, createPluginScheduleHost, databaseHostToken, databaseRootHostToken, featureId, rootPluginId, scheduleHostToken, scheduleRootHostToken, unwrapPluginDatabaseHost, unwrapPluginScheduleHost, } from '@zhin.js/plugin-runtime';
|
|
3
3
|
import { runtimeEnvironmentToken } from './environment.js';
|
|
4
4
|
import { EnvStoreFactory, envStoreToken, } from './environment-store.js';
|
|
5
5
|
import { createPrimaryConfig, primaryConfigToken, } from './primary-config.js';
|
|
@@ -18,6 +18,7 @@ export class PluginScopeAssembler {
|
|
|
18
18
|
#setupCapabilities = new Map();
|
|
19
19
|
#created = [];
|
|
20
20
|
#handoffs = new GenerationHandoffStack();
|
|
21
|
+
#admission = createGenerationAdmissionGate();
|
|
21
22
|
#envStores;
|
|
22
23
|
#setupFeatureAliases = new Map();
|
|
23
24
|
constructor(modules, configResolver, environment, primaryConfigDocument, installResources, environmentLayers = {}, seed, isolation) {
|
|
@@ -48,7 +49,8 @@ export class PluginScopeAssembler {
|
|
|
48
49
|
installSetupFeatureAliases(aliases) {
|
|
49
50
|
this.#setupFeatureAliases = new Map(aliases);
|
|
50
51
|
}
|
|
51
|
-
async setupTree(node) {
|
|
52
|
+
async setupTree(node, signal) {
|
|
53
|
+
signal.throwIfAborted();
|
|
52
54
|
const manifest = node.package.packageJson.zhin;
|
|
53
55
|
const parentScope = node.parent ? this.scopes.get(node.parent) : undefined;
|
|
54
56
|
if (node.parent && !parentScope)
|
|
@@ -62,12 +64,15 @@ export class PluginScopeAssembler {
|
|
|
62
64
|
const register = (feature, localName, capabilityDefinition) => this.#registerSetupCapability(node, manifest, feature, localName, capabilityDefinition);
|
|
63
65
|
if (!node.parent) {
|
|
64
66
|
scope.provide(runtimeEnvironmentToken, this.environment);
|
|
67
|
+
scope.provide(rootGenerationAdmissionToken, this.#admission);
|
|
65
68
|
const config = createPrimaryConfig(this.primaryConfigDocument, environment);
|
|
66
69
|
scope.provide(primaryConfigToken, config);
|
|
67
70
|
await this.installResources?.({
|
|
71
|
+
signal,
|
|
68
72
|
resources: scope,
|
|
69
73
|
lifecycle: scope.disposers,
|
|
70
74
|
handoff: this.#handoffs,
|
|
75
|
+
admission: this.#admission,
|
|
71
76
|
config,
|
|
72
77
|
addFeature: register,
|
|
73
78
|
});
|
|
@@ -100,7 +105,8 @@ export class PluginScopeAssembler {
|
|
|
100
105
|
entry: resolve(node.package.root, manifest.entry),
|
|
101
106
|
config,
|
|
102
107
|
environment: this.environment,
|
|
103
|
-
});
|
|
108
|
+
}, signal);
|
|
109
|
+
signal.throwIfAborted();
|
|
104
110
|
// Ownership transfers to the shadow Scope immediately. Every later
|
|
105
111
|
// validation or binding failure is then covered by normal rollback.
|
|
106
112
|
scope.disposers.add(prepared.dispose);
|
|
@@ -116,6 +122,7 @@ export class PluginScopeAssembler {
|
|
|
116
122
|
}
|
|
117
123
|
else {
|
|
118
124
|
const module = await this.modules.load(resolve(node.package.root, manifest.entry));
|
|
125
|
+
signal.throwIfAborted();
|
|
119
126
|
const definition = module.default;
|
|
120
127
|
if (!definition || typeof definition.name !== 'string') {
|
|
121
128
|
throw new TypeError(`${node.package.name} does not default-export a Plugin definition`);
|
|
@@ -126,6 +133,7 @@ export class PluginScopeAssembler {
|
|
|
126
133
|
}
|
|
127
134
|
}
|
|
128
135
|
const setupContext = {
|
|
136
|
+
signal,
|
|
129
137
|
plugin,
|
|
130
138
|
config: view,
|
|
131
139
|
resources: scope,
|
|
@@ -137,6 +145,7 @@ export class PluginScopeAssembler {
|
|
|
137
145
|
setupContext[method] = (name, value) => register(feature, name, value);
|
|
138
146
|
}
|
|
139
147
|
const returned = await definition.setup?.(Object.freeze(setupContext));
|
|
148
|
+
signal.throwIfAborted();
|
|
140
149
|
if (returned)
|
|
141
150
|
scope.disposers.add(returned);
|
|
142
151
|
metadata = definition.metadata;
|
|
@@ -154,7 +163,7 @@ export class PluginScopeAssembler {
|
|
|
154
163
|
this.config.set(node.id, config);
|
|
155
164
|
this.resources.set(node.id, scope.snapshot());
|
|
156
165
|
for (const child of node.children)
|
|
157
|
-
await this.setupTree(child);
|
|
166
|
+
await this.setupTree(child, signal);
|
|
158
167
|
}
|
|
159
168
|
#registerSetupCapability(node, manifest, feature, localName, definition) {
|
|
160
169
|
const featureName = featureId(String(feature));
|
|
@@ -224,6 +233,7 @@ export class PluginScopeAssembler {
|
|
|
224
233
|
}
|
|
225
234
|
}
|
|
226
235
|
}
|
|
236
|
+
const rootGenerationAdmissionToken = createToken('zhin.runtime.root-generation-admission');
|
|
227
237
|
function isWithin(plugin, root) {
|
|
228
238
|
return plugin === root || plugin.startsWith(`${root}/`);
|
|
229
239
|
}
|
package/lib/root-runtime.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { type ControlErrorHandler, type GenerationCommitListener, type PluginId, type RuntimeSnapshot, type SnapshotReader } from '@zhin.js/plugin-runtime';
|
|
2
2
|
import { type RuntimeConfigDocument } from './config-composer.js';
|
|
3
3
|
import { type ConfigDocumentPort } from './config-document.js';
|
|
4
4
|
import { type ConfigPatch } from './config-patch-planner.js';
|
|
@@ -24,9 +24,10 @@ export interface RootRuntimeOptions {
|
|
|
24
24
|
export type RootHmrOptions = Omit<HmrCoordinatorOptions, 'modules' | 'ownership' | 'runtime'>;
|
|
25
25
|
export declare class RootRuntime {
|
|
26
26
|
#private;
|
|
27
|
-
readonly controller: RootController;
|
|
28
27
|
constructor(options: RootRuntimeOptions);
|
|
29
28
|
get snapshot(): RuntimeSnapshot;
|
|
29
|
+
get snapshots(): SnapshotReader;
|
|
30
|
+
onGenerationCommit(listener: GenerationCommitListener): () => void;
|
|
30
31
|
get sourceOwnership(): SourceOwnershipIndex;
|
|
31
32
|
start(): Promise<RuntimeSnapshot>;
|
|
32
33
|
reload(target?: PluginId | string): Promise<RuntimeSnapshot>;
|
package/lib/root-runtime.js
CHANGED
|
@@ -23,7 +23,6 @@ import { SubtreeGenerationPreparer, SubtreeTopologyChangedError, } from './subtr
|
|
|
23
23
|
import { TopologyGenerationPreparer } from './topology-generation-preparer.js';
|
|
24
24
|
import { RestartBoundaryPlanner } from './restart-boundary.js';
|
|
25
25
|
export class RootRuntime {
|
|
26
|
-
controller;
|
|
27
26
|
#projectRoot;
|
|
28
27
|
#modules;
|
|
29
28
|
#environment;
|
|
@@ -37,6 +36,8 @@ export class RootRuntime {
|
|
|
37
36
|
#ownership = SourceOwnershipIndex.empty();
|
|
38
37
|
#model;
|
|
39
38
|
#configPatchTail = Promise.resolve();
|
|
39
|
+
#stopResult;
|
|
40
|
+
#controller;
|
|
40
41
|
constructor(options) {
|
|
41
42
|
this.#projectRoot = resolve(options.projectRoot);
|
|
42
43
|
this.#modules = options.modules;
|
|
@@ -50,10 +51,16 @@ export class RootRuntime {
|
|
|
50
51
|
this.#configDocument = structuredClone(options.config ?? {});
|
|
51
52
|
this.#installResources = options.installResources;
|
|
52
53
|
this.#isolation = options.isolation;
|
|
53
|
-
this
|
|
54
|
+
this.#controller = new RootController(emptyState(), options.onControlError);
|
|
54
55
|
}
|
|
55
56
|
get snapshot() {
|
|
56
|
-
return this
|
|
57
|
+
return this.#controller.snapshot;
|
|
58
|
+
}
|
|
59
|
+
get snapshots() {
|
|
60
|
+
return this.#controller.snapshots;
|
|
61
|
+
}
|
|
62
|
+
onGenerationCommit(listener) {
|
|
63
|
+
return this.#controller.onGenerationCommit(listener);
|
|
57
64
|
}
|
|
58
65
|
get sourceOwnership() {
|
|
59
66
|
return this.#ownership;
|
|
@@ -65,8 +72,8 @@ export class RootRuntime {
|
|
|
65
72
|
this.#configDocument = structuredClone(snapshot.document);
|
|
66
73
|
}
|
|
67
74
|
let prepared;
|
|
68
|
-
const snapshot = await this
|
|
69
|
-
prepared = await this.#prepare(current);
|
|
75
|
+
const snapshot = await this.#controller.start(async (current, signal) => {
|
|
76
|
+
prepared = await this.#prepare(current, signal);
|
|
70
77
|
return prepared.generation;
|
|
71
78
|
});
|
|
72
79
|
this.#accept(requirePrepared(prepared));
|
|
@@ -74,8 +81,8 @@ export class RootRuntime {
|
|
|
74
81
|
}
|
|
75
82
|
async reload(target = rootPluginId()) {
|
|
76
83
|
let prepared;
|
|
77
|
-
const snapshot = await this
|
|
78
|
-
prepared = await this.#prepare(current);
|
|
84
|
+
const snapshot = await this.#controller.reload(target, async (current, signal) => {
|
|
85
|
+
prepared = await this.#prepare(current, signal);
|
|
79
86
|
return prepared.generation;
|
|
80
87
|
});
|
|
81
88
|
this.#accept(requirePrepared(prepared));
|
|
@@ -103,18 +110,24 @@ export class RootRuntime {
|
|
|
103
110
|
createProcessRestartExecutor(adapter) {
|
|
104
111
|
return new RootProcessRestartExecutor(this, adapter);
|
|
105
112
|
}
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
+
stop() {
|
|
114
|
+
if (this.#stopResult)
|
|
115
|
+
return this.#stopResult;
|
|
116
|
+
const result = (async () => {
|
|
117
|
+
try {
|
|
118
|
+
await this.#controller.stop();
|
|
119
|
+
}
|
|
120
|
+
finally {
|
|
121
|
+
await this.#modules.close();
|
|
122
|
+
}
|
|
123
|
+
})();
|
|
124
|
+
this.#stopResult = result;
|
|
125
|
+
return result;
|
|
113
126
|
}
|
|
114
127
|
async #reloadPlan(plan) {
|
|
115
128
|
let prepared;
|
|
116
129
|
let restart;
|
|
117
|
-
const snapshot = await this
|
|
130
|
+
const snapshot = await this.#controller.reload(plan.subtrees[0] ?? plan.slots[0] ?? rootPluginId(), async (current, signal) => {
|
|
118
131
|
const resolved = await this.#resolveCapabilityDelta(current, plan);
|
|
119
132
|
const effective = resolved.plan;
|
|
120
133
|
if (this.#model && effective.manifestSources.length > 0) {
|
|
@@ -123,19 +136,19 @@ export class RootRuntime {
|
|
|
123
136
|
if (restart)
|
|
124
137
|
return undefined;
|
|
125
138
|
prepared = effective.subtrees.includes(rootPluginId())
|
|
126
|
-
? await this.#prepareInspected(current, inspected)
|
|
127
|
-
: await new TopologyGenerationPreparer(this.#modules, this.#model, inspected.graph, inspected.configResolver, inspected.primaryConfigDocument, this.#environment, this.#installResources, this.#environmentLayers, this.#isolation).prepare(current, { ...effective, capabilities: resolved.capabilities });
|
|
139
|
+
? await this.#prepareInspected(current, inspected, signal)
|
|
140
|
+
: await new TopologyGenerationPreparer(this.#modules, this.#model, inspected.graph, inspected.configResolver, inspected.primaryConfigDocument, this.#environment, this.#installResources, this.#environmentLayers, this.#isolation).prepare(current, signal, { ...effective, capabilities: resolved.capabilities });
|
|
128
141
|
}
|
|
129
142
|
else if (effective.subtrees.length === 0 && resolved.capabilities.size > 0 && this.#model) {
|
|
130
143
|
prepared = await new SlotGenerationPreparer(this.#modules, this.#model)
|
|
131
|
-
.prepare(current, resolved.capabilities);
|
|
144
|
+
.prepare(current, resolved.capabilities, signal);
|
|
132
145
|
}
|
|
133
146
|
else if (this.#model && this.#canPrepareSubtrees(effective)) {
|
|
134
147
|
const inspected = await this.#inspectProject();
|
|
135
|
-
prepared = await this.#prepareSubtrees(current, inspected, effective.subtrees);
|
|
148
|
+
prepared = await this.#prepareSubtrees(current, inspected, effective.subtrees, signal);
|
|
136
149
|
}
|
|
137
150
|
else {
|
|
138
|
-
prepared = await this.#prepare(current);
|
|
151
|
+
prepared = await this.#prepare(current, signal);
|
|
139
152
|
}
|
|
140
153
|
return prepared?.generation;
|
|
141
154
|
});
|
|
@@ -176,8 +189,11 @@ export class RootRuntime {
|
|
|
176
189
|
return records.length > 0 && records.every((record) => record.role === 'plugin' || record.role === 'schema');
|
|
177
190
|
});
|
|
178
191
|
}
|
|
179
|
-
async #prepare(current) {
|
|
180
|
-
|
|
192
|
+
async #prepare(current, signal) {
|
|
193
|
+
signal.throwIfAborted();
|
|
194
|
+
const inspected = await this.#inspectProject();
|
|
195
|
+
signal.throwIfAborted();
|
|
196
|
+
return this.#prepareInspected(current, inspected, signal);
|
|
181
197
|
}
|
|
182
198
|
async #inspectProject() {
|
|
183
199
|
const resolver = await NodePackageResolver.create(this.#projectRoot);
|
|
@@ -234,7 +250,8 @@ export class RootRuntime {
|
|
|
234
250
|
let prepared;
|
|
235
251
|
let documentTransaction;
|
|
236
252
|
let committedDocument;
|
|
237
|
-
const snapshot = await this
|
|
253
|
+
const snapshot = await this.#controller.reload(rootPluginId(), async (current, signal) => {
|
|
254
|
+
signal.throwIfAborted();
|
|
238
255
|
const resolver = await NodePackageResolver.create(this.#projectRoot);
|
|
239
256
|
const graph = await new ProjectGraphService(resolver).inspect(this.#projectRoot);
|
|
240
257
|
const planned = await new ConfigPatchPlanner().plan(graph, currentDocument, patches);
|
|
@@ -269,10 +286,10 @@ export class RootRuntime {
|
|
|
269
286
|
// when present so a committed patch cannot leave Host services on the
|
|
270
287
|
// previous generation's document.
|
|
271
288
|
if (!this.#installResources && this.#model && !planned.roots.includes(rootPluginId())) {
|
|
272
|
-
prepared = await this.#prepareSubtrees(current, inspected, planned.roots);
|
|
289
|
+
prepared = await this.#prepareSubtrees(current, inspected, planned.roots, signal);
|
|
273
290
|
}
|
|
274
291
|
else {
|
|
275
|
-
prepared = await this.#prepareInspected(current, inspected);
|
|
292
|
+
prepared = await this.#prepareInspected(current, inspected, signal);
|
|
276
293
|
}
|
|
277
294
|
return documentTransaction
|
|
278
295
|
? withConfigDocumentHandoff(prepared.generation, documentTransaction, (committed) => { committedDocument = committed; })
|
|
@@ -295,20 +312,20 @@ export class RootRuntime {
|
|
|
295
312
|
this.#configSnapshot = committedDocument;
|
|
296
313
|
return snapshot;
|
|
297
314
|
}
|
|
298
|
-
#prepareInspected(current, inspected) {
|
|
315
|
+
#prepareInspected(current, inspected, signal) {
|
|
299
316
|
const assembler = new GenerationAssembler(inspected.graph, this.#modules, inspected.configResolver, inspected.primaryConfigDocument, current.generation + 1, this.#environment, this.#installResources, this.#environmentLayers, this.#isolation);
|
|
300
|
-
return assembler.prepare();
|
|
317
|
+
return assembler.prepare(signal);
|
|
301
318
|
}
|
|
302
|
-
async #prepareSubtrees(current, inspected, roots) {
|
|
319
|
+
async #prepareSubtrees(current, inspected, roots, signal) {
|
|
303
320
|
if (!this.#model)
|
|
304
|
-
return this.#prepareInspected(current, inspected);
|
|
321
|
+
return this.#prepareInspected(current, inspected, signal);
|
|
305
322
|
try {
|
|
306
|
-
return await new SubtreeGenerationPreparer(this.#modules, this.#model, inspected.graph, inspected.configResolver, inspected.primaryConfigDocument, this.#environment, this.#installResources, this.#environmentLayers, this.#isolation).prepare(current, roots);
|
|
323
|
+
return await new SubtreeGenerationPreparer(this.#modules, this.#model, inspected.graph, inspected.configResolver, inspected.primaryConfigDocument, this.#environment, this.#installResources, this.#environmentLayers, this.#isolation).prepare(current, roots, signal);
|
|
307
324
|
}
|
|
308
325
|
catch (error) {
|
|
309
326
|
if (!(error instanceof SubtreeTopologyChangedError))
|
|
310
327
|
throw error;
|
|
311
|
-
return this.#prepareInspected(current, inspected);
|
|
328
|
+
return this.#prepareInspected(current, inspected, signal);
|
|
312
329
|
}
|
|
313
330
|
}
|
|
314
331
|
}
|
|
@@ -326,8 +343,10 @@ function withConfigDocumentHandoff(generation, document, committed) {
|
|
|
326
343
|
// File commit follows Resource activation, so reverse compensation restores
|
|
327
344
|
// the document before it deactivates the shadow generation.
|
|
328
345
|
handoffs.add({
|
|
329
|
-
async activateNext() {
|
|
346
|
+
async activateNext(signal) {
|
|
347
|
+
signal.throwIfAborted();
|
|
330
348
|
committed(await document.commit());
|
|
349
|
+
signal.throwIfAborted();
|
|
331
350
|
},
|
|
332
351
|
deactivateNext: () => document.rollback(),
|
|
333
352
|
});
|
|
@@ -389,16 +408,17 @@ class GenerationAssembler {
|
|
|
389
408
|
this.#host = new NodeDiscoveryHost(modules);
|
|
390
409
|
this.#plugins = new PluginScopeAssembler(modules, configResolver, environment, primaryConfigDocument, installResources, environmentLayers, undefined, isolation);
|
|
391
410
|
}
|
|
392
|
-
async prepare() {
|
|
411
|
+
async prepare(signal) {
|
|
412
|
+
signal.throwIfAborted();
|
|
393
413
|
try {
|
|
394
414
|
// Prepare is deliberately ordered: providers define discovery, setup
|
|
395
415
|
// creates owner scopes, then definitions can be projected against both.
|
|
396
|
-
await this.#loadProviders(this.graph.root);
|
|
416
|
+
await this.#loadProviders(this.graph.root, signal);
|
|
397
417
|
this.#plugins.installSetupFeatureAliases(featureSetupAliases(this.#catalog.values()));
|
|
398
|
-
await this.#plugins.setupTree(this.graph.root);
|
|
399
|
-
await this.#discover();
|
|
418
|
+
await this.#plugins.setupTree(this.graph.root, signal);
|
|
419
|
+
await this.#discover(signal);
|
|
400
420
|
const projected = await new FeatureProjector(this.#catalog.values())
|
|
401
|
-
.project(this.generation, this.#projectionState());
|
|
421
|
+
.project(this.generation, this.#projectionState(), signal);
|
|
402
422
|
for (const [feature, dispose] of projected.disposers) {
|
|
403
423
|
this.#projectionDisposers.set(feature, dispose);
|
|
404
424
|
}
|
|
@@ -431,10 +451,12 @@ class GenerationAssembler {
|
|
|
431
451
|
throw error;
|
|
432
452
|
}
|
|
433
453
|
}
|
|
434
|
-
async #loadProviders(node) {
|
|
454
|
+
async #loadProviders(node, signal) {
|
|
455
|
+
signal.throwIfAborted();
|
|
435
456
|
for (const requirement of node.features) {
|
|
436
457
|
const manifest = requirement.package.packageJson.zhin;
|
|
437
458
|
const module = await this.modules.load(resolve(requirement.package.root, manifest.entry));
|
|
459
|
+
signal.throwIfAborted();
|
|
438
460
|
const provider = module.default;
|
|
439
461
|
if (!provider || provider.protocol !== 1) {
|
|
440
462
|
throw new TypeError(`${requirement.package.name} does not default-export a Feature provider`);
|
|
@@ -451,14 +473,16 @@ class GenerationAssembler {
|
|
|
451
473
|
this.#rootsByFeature.set(provider.id, roots);
|
|
452
474
|
}
|
|
453
475
|
for (const child of node.children)
|
|
454
|
-
await this.#loadProviders(child);
|
|
476
|
+
await this.#loadProviders(child, signal);
|
|
455
477
|
}
|
|
456
|
-
async #discover() {
|
|
478
|
+
async #discover(signal) {
|
|
457
479
|
mergeSetupCapabilities(this.#capabilities, this.#plugins.setupCapabilities(), new Map(this.#catalog.values().map((provider) => [provider.id, provider])), this.#rootsByFeature);
|
|
458
480
|
const discovery = new FeatureDiscovery(this.#host);
|
|
459
481
|
for (const provider of this.#catalog.values()) {
|
|
482
|
+
signal.throwIfAborted();
|
|
460
483
|
const roots = this.#rootsByFeature.get(provider.id) ?? [];
|
|
461
484
|
const slots = await discovery.discover(provider, roots);
|
|
485
|
+
signal.throwIfAborted();
|
|
462
486
|
for (const slot of slots)
|
|
463
487
|
addCapabilitySlot(this.#capabilities, slot);
|
|
464
488
|
}
|
|
@@ -6,5 +6,5 @@ export declare class SlotGenerationPreparer {
|
|
|
6
6
|
private readonly modules;
|
|
7
7
|
private readonly model;
|
|
8
8
|
constructor(modules: ModuleRuntime, model: RuntimeGenerationModel);
|
|
9
|
-
prepare(current: RuntimeSnapshot, selected: readonly CapabilityId[] | CapabilityDelta): Promise<PreparedRuntimeGeneration>;
|
|
9
|
+
prepare(current: RuntimeSnapshot, selected: readonly CapabilityId[] | CapabilityDelta, signal: AbortSignal): Promise<PreparedRuntimeGeneration>;
|
|
10
10
|
}
|
|
@@ -11,7 +11,8 @@ export class SlotGenerationPreparer {
|
|
|
11
11
|
this.modules = modules;
|
|
12
12
|
this.model = model;
|
|
13
13
|
}
|
|
14
|
-
async prepare(current, selected) {
|
|
14
|
+
async prepare(current, selected, signal) {
|
|
15
|
+
signal.throwIfAborted();
|
|
15
16
|
const selectedByFeature = Array.isArray(selected)
|
|
16
17
|
? capabilityDeltaFromSlots(current, selected)
|
|
17
18
|
: selected;
|
|
@@ -24,6 +25,7 @@ export class SlotGenerationPreparer {
|
|
|
24
25
|
for (const id of ids)
|
|
25
26
|
capabilities.delete(id);
|
|
26
27
|
const replacements = await discovery.discover(provider, this.model.rootsByFeature.get(feature) ?? [], { capabilities: ids });
|
|
28
|
+
signal.throwIfAborted();
|
|
27
29
|
for (const slot of replacements)
|
|
28
30
|
capabilities.set(slot.id, slot);
|
|
29
31
|
}
|
|
@@ -41,7 +43,7 @@ export class SlotGenerationPreparer {
|
|
|
41
43
|
config: current.config,
|
|
42
44
|
resources: current.resources,
|
|
43
45
|
capabilities,
|
|
44
|
-
}, current.projections);
|
|
46
|
+
}, signal, current.projections);
|
|
45
47
|
try {
|
|
46
48
|
const snapshot = createSnapshotView(current.generation + 1, projected.state);
|
|
47
49
|
const ownership = SourceOwnershipIndex.fromGeneration(this.model.graph, snapshot, this.model.featureIdsByPackageRoot);
|
|
@@ -22,5 +22,5 @@ export declare class SubtreeGenerationPreparer {
|
|
|
22
22
|
private readonly environmentLayers;
|
|
23
23
|
private readonly isolation?;
|
|
24
24
|
constructor(modules: ModuleRuntime, model: RuntimeGenerationModel, graph: ProjectGraph, configResolver: PluginConfigResolver, primaryConfigDocument: RuntimeConfigDocument, environment: RuntimeEnvironment, installResources?: RootResourceInstaller | undefined, environmentLayers?: EnvironmentLayers, isolation?: IsolatedPluginRuntimePort | undefined);
|
|
25
|
-
prepare(current: RuntimeSnapshot, roots: readonly PluginId[]): Promise<PreparedRuntimeGeneration>;
|
|
25
|
+
prepare(current: RuntimeSnapshot, roots: readonly PluginId[], signal: AbortSignal): Promise<PreparedRuntimeGeneration>;
|
|
26
26
|
}
|
|
@@ -33,7 +33,8 @@ export class SubtreeGenerationPreparer {
|
|
|
33
33
|
this.environmentLayers = environmentLayers;
|
|
34
34
|
this.isolation = isolation;
|
|
35
35
|
}
|
|
36
|
-
async prepare(current, roots) {
|
|
36
|
+
async prepare(current, roots, signal) {
|
|
37
|
+
signal.throwIfAborted();
|
|
37
38
|
const nodes = indexGraph(this.graph);
|
|
38
39
|
assertCompatibleTopology(indexGraph(this.model.graph), nodes, roots);
|
|
39
40
|
const plugins = new PluginScopeAssembler(this.modules, this.configResolver, this.environment, this.primaryConfigDocument, this.installResources, this.environmentLayers, {
|
|
@@ -50,7 +51,7 @@ export class SubtreeGenerationPreparer {
|
|
|
50
51
|
const node = nodes.get(root);
|
|
51
52
|
if (!node)
|
|
52
53
|
throw new SubtreeTopologyChangedError(`Missing subtree root: ${root}`);
|
|
53
|
-
await plugins.setupTree(node);
|
|
54
|
+
await plugins.setupTree(node, signal);
|
|
54
55
|
}
|
|
55
56
|
const capabilities = new Map(current.capabilities);
|
|
56
57
|
for (const [id, slot] of capabilities) {
|
|
@@ -65,6 +66,7 @@ export class SubtreeGenerationPreparer {
|
|
|
65
66
|
if (affectedRoots.length === 0)
|
|
66
67
|
continue;
|
|
67
68
|
const slots = await discovery.discover(provider, affectedRoots);
|
|
69
|
+
signal.throwIfAborted();
|
|
68
70
|
for (const slot of slots)
|
|
69
71
|
addCapabilitySlot(capabilities, slot);
|
|
70
72
|
}
|
|
@@ -74,7 +76,7 @@ export class SubtreeGenerationPreparer {
|
|
|
74
76
|
config: plugins.config,
|
|
75
77
|
resources: plugins.resources,
|
|
76
78
|
capabilities,
|
|
77
|
-
});
|
|
79
|
+
}, signal);
|
|
78
80
|
for (const [feature, dispose] of projected.disposers) {
|
|
79
81
|
projectionDisposers.set(feature, dispose);
|
|
80
82
|
}
|
|
@@ -27,5 +27,5 @@ export declare class TopologyGenerationPreparer {
|
|
|
27
27
|
private readonly environmentLayers;
|
|
28
28
|
private readonly isolation?;
|
|
29
29
|
constructor(modules: ModuleRuntime, model: RuntimeGenerationModel, graph: ProjectGraph, configResolver: PluginConfigResolver, primaryConfigDocument: RuntimeConfigDocument, environment: RuntimeEnvironment, installResources?: RootResourceInstaller | undefined, environmentLayers?: EnvironmentLayers, isolation?: IsolatedPluginRuntimePort | undefined);
|
|
30
|
-
prepare(current: RuntimeSnapshot, delta?: TopologyRuntimeDelta): Promise<PreparedRuntimeGeneration | undefined>;
|
|
30
|
+
prepare(current: RuntimeSnapshot, signal: AbortSignal, delta?: TopologyRuntimeDelta): Promise<PreparedRuntimeGeneration | undefined>;
|
|
31
31
|
}
|
|
@@ -31,7 +31,8 @@ export class TopologyGenerationPreparer {
|
|
|
31
31
|
this.environmentLayers = environmentLayers;
|
|
32
32
|
this.isolation = isolation;
|
|
33
33
|
}
|
|
34
|
-
async prepare(current, delta = { slots: [], subtrees: [] }) {
|
|
34
|
+
async prepare(current, signal, delta = { slots: [], subtrees: [] }) {
|
|
35
|
+
signal.throwIfAborted();
|
|
35
36
|
const planned = new TopologyTransactionPlanner().plan(this.model.graph, this.graph);
|
|
36
37
|
const nextNodes = graphNodes(this.graph);
|
|
37
38
|
const configReplacements = changedConfigRoots(current, nextNodes, this.configResolver);
|
|
@@ -69,7 +70,7 @@ export class TopologyGenerationPreparer {
|
|
|
69
70
|
const node = nextNodes.get(root);
|
|
70
71
|
if (!node)
|
|
71
72
|
throw new Error(`Missing topology setup root: ${root}`);
|
|
72
|
-
await plugins.setupTree(node);
|
|
73
|
+
await plugins.setupTree(node, signal);
|
|
73
74
|
}
|
|
74
75
|
// Retained parents still need a new immutable children view after add,
|
|
75
76
|
// remove, move, or reorder operations.
|
|
@@ -82,7 +83,7 @@ export class TopologyGenerationPreparer {
|
|
|
82
83
|
config: plugins.config,
|
|
83
84
|
resources: plugins.resources,
|
|
84
85
|
capabilities,
|
|
85
|
-
});
|
|
86
|
+
}, signal);
|
|
86
87
|
for (const [feature, dispose] of projected.disposers) {
|
|
87
88
|
projectionDisposers.set(feature, dispose);
|
|
88
89
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zhin.js/runtime",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.11",
|
|
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.10",
|
|
22
|
+
"@zhin.js/plugin-runtime": "1.1.6"
|
|
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/layout": "1.0.
|
|
32
|
-
"@zhin.js/mcp-feature": "1.0.
|
|
33
|
-
"@zhin.js/middleware": "1.0.
|
|
34
|
-
"@zhin.js/page": "1.0.
|
|
35
|
-
"@zhin.js/skill": "1.0.
|
|
36
|
-
"@zhin.js/tool": "1.0.
|
|
27
|
+
"@zhin.js/adapter": "1.1.9",
|
|
28
|
+
"@zhin.js/agent-feature": "1.0.10",
|
|
29
|
+
"@zhin.js/command": "1.0.13",
|
|
30
|
+
"@zhin.js/component": "1.0.10",
|
|
31
|
+
"@zhin.js/layout": "1.0.10",
|
|
32
|
+
"@zhin.js/mcp-feature": "1.0.10",
|
|
33
|
+
"@zhin.js/middleware": "1.0.10",
|
|
34
|
+
"@zhin.js/page": "1.0.10",
|
|
35
|
+
"@zhin.js/skill": "1.0.10",
|
|
36
|
+
"@zhin.js/tool": "1.0.10"
|
|
37
37
|
},
|
|
38
38
|
"repository": {
|
|
39
39
|
"type": "git",
|