@zhin.js/runtime 1.1.0 → 1.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -0
- package/lib/config-composer.d.ts +1 -1
- package/lib/config-composer.js +14 -1
- package/lib/config-patch-planner.d.ts +2 -13
- package/lib/config-patch-planner.js +4 -114
- package/lib/convention-capability-delta.js +3 -1
- package/lib/environment-store.d.ts +5 -0
- package/lib/hmr-coordinator.d.ts +1 -0
- package/lib/hmr-coordinator.js +2 -1
- package/lib/host-config-schema.json +4 -4
- package/lib/index.d.ts +1 -2
- package/lib/index.js +1 -2
- package/lib/native-development-runtime.d.ts +6 -0
- package/lib/native-development-runtime.js +65 -29
- package/lib/{compatibility.d.ts → package-contract.d.ts} +1 -1
- package/lib/{compatibility.js → package-contract.js} +8 -8
- package/lib/plugin-scope-assembler.d.ts +2 -6
- package/lib/plugin-scope-assembler.js +6 -14
- package/lib/primary-config.d.ts +1 -1
- package/lib/project-graph.js +1 -1
- package/lib/root-runtime.d.ts +3 -5
- package/lib/root-runtime.js +122 -22
- package/lib/runtime-input-planner.d.ts +24 -0
- package/lib/runtime-input-planner.js +65 -0
- package/lib/subtree-generation-preparer.d.ts +1 -1
- package/lib/topology-generation-preparer.d.ts +1 -1
- package/package.json +15 -14
- package/lib/config-document.d.ts +0 -19
- package/lib/config-document.js +0 -6
package/README.md
CHANGED
|
@@ -22,6 +22,10 @@ await runtime.start();
|
|
|
22
22
|
- child/schema 变化替换最浅受影响 subtree。
|
|
23
23
|
- manifest topology transaction 支持 child/Feature 新增、删除和移动。
|
|
24
24
|
- Root/package ABI、lockfile 和未知 importer 变化升级为 process restart。
|
|
25
|
+
- 外部 Root 配置按实际投影分类:子 Plugin 配置只替换对应 subtree,Host 配置请求
|
|
26
|
+
process restart。
|
|
27
|
+
- `.env` / `.env.<environment>` 由 `EnvironmentLayersPort` 重读;Runtime 重新展开配置引用,
|
|
28
|
+
Host 投影变化时重启进程,否则提交新的 Plugin generation。
|
|
25
29
|
- 候选校验或 handoff 失败不提交 generation;旧 snapshot 保持可用。
|
|
26
30
|
- 最后一个旧 lease 释放后,旧资源才按 children-first 顺序回收。
|
|
27
31
|
|
package/lib/config-composer.d.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
+
import type { RuntimeConfigDocument } from '@zhin.js/plugin-runtime';
|
|
1
2
|
import type { PluginId } from '@zhin.js/plugin-runtime';
|
|
2
3
|
import type { ProjectGraph } from './project-graph.js';
|
|
3
4
|
/** Host configuration keys consumed by Runtime composition and Host installers. */
|
|
4
5
|
export declare const HOST_CONFIG_KEYS: readonly string[];
|
|
5
6
|
export type JsonSchema = Readonly<Record<string, unknown>>;
|
|
6
|
-
export type RuntimeConfigDocument = Readonly<Record<string, unknown>>;
|
|
7
7
|
export interface ComposedConfig {
|
|
8
8
|
readonly effectiveSchema: JsonSchema;
|
|
9
9
|
readonly document: RuntimeConfigDocument;
|
package/lib/config-composer.js
CHANGED
|
@@ -199,12 +199,25 @@ async function readOwnSchema(node) {
|
|
|
199
199
|
|| schema.allOf !== undefined || schema.$ref !== undefined)) {
|
|
200
200
|
throw new TypeError(`${file} root schema must declare properties; composition keywords (anyOf/oneOf/allOf/$ref) are not supported`);
|
|
201
201
|
}
|
|
202
|
+
const properties = schemaProperties(schema);
|
|
203
|
+
if (Object.hasOwn(properties, 'commandNamespace')) {
|
|
204
|
+
throw new ConfigSchemaCollisionError(node.id, 'commandNamespace');
|
|
205
|
+
}
|
|
202
206
|
return Object.freeze({
|
|
203
207
|
$id: `urn:zhin:plugin-config:${encodeURIComponent(String(node.id))}`,
|
|
204
208
|
type: 'object',
|
|
205
209
|
additionalProperties: false,
|
|
206
210
|
...schema,
|
|
207
|
-
properties:
|
|
211
|
+
properties: {
|
|
212
|
+
commandNamespace: {
|
|
213
|
+
type: 'string',
|
|
214
|
+
minLength: 1,
|
|
215
|
+
pattern: '^\\S+(?: \\S+)*$',
|
|
216
|
+
description: 'Explicit command namespace prepended after the Endpoint commandPrefix.',
|
|
217
|
+
'x-descriptionZh': '显式指令命名空间;在 Endpoint commandPrefix 之后参与路由。',
|
|
218
|
+
},
|
|
219
|
+
...properties,
|
|
220
|
+
},
|
|
208
221
|
});
|
|
209
222
|
}
|
|
210
223
|
function schemaProperties(schema) {
|
|
@@ -1,22 +1,11 @@
|
|
|
1
|
-
import { type PluginId } from '@zhin.js/plugin-runtime';
|
|
2
|
-
import { ConfigComposer, type ComposedConfig
|
|
1
|
+
import { type ConfigPatch, type PluginId, type RuntimeConfigDocument } from '@zhin.js/plugin-runtime';
|
|
2
|
+
import { ConfigComposer, type ComposedConfig } from './config-composer.js';
|
|
3
3
|
import type { ProjectGraph } from './project-graph.js';
|
|
4
|
-
export type ConfigPatch = {
|
|
5
|
-
readonly op: 'set';
|
|
6
|
-
readonly path: readonly string[];
|
|
7
|
-
readonly value: unknown;
|
|
8
|
-
} | {
|
|
9
|
-
readonly op: 'remove';
|
|
10
|
-
readonly path: readonly string[];
|
|
11
|
-
};
|
|
12
4
|
export interface ConfigPatchPlan extends ComposedConfig {
|
|
13
5
|
readonly candidate: RuntimeConfigDocument;
|
|
14
6
|
readonly documentChanged: boolean;
|
|
15
7
|
readonly roots: readonly PluginId[];
|
|
16
8
|
}
|
|
17
|
-
export declare class ConfigPatchPathError extends Error {
|
|
18
|
-
constructor(message: string);
|
|
19
|
-
}
|
|
20
9
|
/** Validates a candidate document before deriving its minimal replacement forest. */
|
|
21
10
|
export declare class ConfigPatchPlanner {
|
|
22
11
|
private readonly composer;
|
|
@@ -1,12 +1,6 @@
|
|
|
1
1
|
import { isDeepStrictEqual } from 'node:util';
|
|
2
|
-
import { rootPluginId } from '@zhin.js/plugin-runtime';
|
|
3
|
-
import { ConfigComposer
|
|
4
|
-
export class ConfigPatchPathError extends Error {
|
|
5
|
-
constructor(message) {
|
|
6
|
-
super(message);
|
|
7
|
-
this.name = 'ConfigPatchPathError';
|
|
8
|
-
}
|
|
9
|
-
}
|
|
2
|
+
import { applyConfigPatches, rootPluginId, } from '@zhin.js/plugin-runtime';
|
|
3
|
+
import { ConfigComposer } from './config-composer.js';
|
|
10
4
|
/** Validates a candidate document before deriving its minimal replacement forest. */
|
|
11
5
|
export class ConfigPatchPlanner {
|
|
12
6
|
composer;
|
|
@@ -15,123 +9,19 @@ export class ConfigPatchPlanner {
|
|
|
15
9
|
}
|
|
16
10
|
async plan(graph, current, patches) {
|
|
17
11
|
const previous = await this.composer.compose(graph, current);
|
|
18
|
-
|
|
19
|
-
for (const patch of patches)
|
|
20
|
-
candidate = applyPatch(candidate, patch);
|
|
12
|
+
const candidate = applyConfigPatches(current, patches);
|
|
21
13
|
const next = await this.composer.compose(graph, candidate);
|
|
22
14
|
const changed = indexGraph(graph)
|
|
23
15
|
.filter((node) => !isDeepStrictEqual(previous.views.get(node.id), next.views.get(node.id)))
|
|
24
16
|
.map((node) => node.id);
|
|
25
17
|
return Object.freeze({
|
|
26
18
|
...next,
|
|
27
|
-
candidate
|
|
19
|
+
candidate,
|
|
28
20
|
documentChanged: !isDeepStrictEqual(current, candidate),
|
|
29
21
|
roots: Object.freeze(collapseRoots(changed)),
|
|
30
22
|
});
|
|
31
23
|
}
|
|
32
24
|
}
|
|
33
|
-
function applyPatch(document, patch) {
|
|
34
|
-
assertPath(patch.path);
|
|
35
|
-
if (patch.path.length === 0) {
|
|
36
|
-
if (patch.op === 'remove') {
|
|
37
|
-
throw new ConfigPatchPathError('The config document root cannot be removed');
|
|
38
|
-
}
|
|
39
|
-
return cloneDocument(patch.value);
|
|
40
|
-
}
|
|
41
|
-
if (patch.op === 'set')
|
|
42
|
-
setValue(document, patch.path, structuredClone(patch.value));
|
|
43
|
-
else
|
|
44
|
-
removeValue(document, patch.path);
|
|
45
|
-
return document;
|
|
46
|
-
}
|
|
47
|
-
function setValue(document, path, value) {
|
|
48
|
-
let target = document;
|
|
49
|
-
for (const [index, segment] of path.slice(0, -1).entries()) {
|
|
50
|
-
const existing = readChild(target, segment, path.slice(0, index + 1));
|
|
51
|
-
if (existing === undefined) {
|
|
52
|
-
// Missing intermediates are always created as records; arrays only ever
|
|
53
|
-
// come from the existing document (numeric segments index into them).
|
|
54
|
-
const created = {};
|
|
55
|
-
target[segment] = created;
|
|
56
|
-
target = created;
|
|
57
|
-
}
|
|
58
|
-
else {
|
|
59
|
-
target = requireContainer(existing, path.slice(0, index + 1));
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
writeChild(target, lastSegment(path), value, path);
|
|
63
|
-
}
|
|
64
|
-
function removeValue(document, path) {
|
|
65
|
-
let target = document;
|
|
66
|
-
for (const [index, segment] of path.slice(0, -1).entries()) {
|
|
67
|
-
const existing = readChild(target, segment, path.slice(0, index + 1));
|
|
68
|
-
if (existing === undefined)
|
|
69
|
-
return;
|
|
70
|
-
target = requireContainer(existing, path.slice(0, index + 1));
|
|
71
|
-
}
|
|
72
|
-
const segment = lastSegment(path);
|
|
73
|
-
if (Array.isArray(target)) {
|
|
74
|
-
target.splice(arrayIndex(segment, target, path), 1);
|
|
75
|
-
return;
|
|
76
|
-
}
|
|
77
|
-
delete target[segment];
|
|
78
|
-
}
|
|
79
|
-
function cloneDocument(value) {
|
|
80
|
-
return requireRecord(structuredClone(value), []);
|
|
81
|
-
}
|
|
82
|
-
function readChild(container, segment, path) {
|
|
83
|
-
if (Array.isArray(container))
|
|
84
|
-
return container[arrayIndex(segment, container, path)];
|
|
85
|
-
return container[segment];
|
|
86
|
-
}
|
|
87
|
-
function writeChild(container, segment, value, path) {
|
|
88
|
-
if (Array.isArray(container)) {
|
|
89
|
-
container[arrayIndex(segment, container, path)] = value;
|
|
90
|
-
return;
|
|
91
|
-
}
|
|
92
|
-
container[segment] = value;
|
|
93
|
-
}
|
|
94
|
-
/** Resolves a numeric path segment against an array, rejecting non-numeric segments and out-of-bounds indexes. */
|
|
95
|
-
function arrayIndex(segment, container, path) {
|
|
96
|
-
if (!/^(0|[1-9]\d*)$/.test(segment)) {
|
|
97
|
-
throw new ConfigPatchPathError(`Config path ${pointer(path)} requires an array index, got "${segment}"`);
|
|
98
|
-
}
|
|
99
|
-
const index = Number(segment);
|
|
100
|
-
if (index >= container.length) {
|
|
101
|
-
throw new ConfigPatchPathError(`Config path ${pointer(path)} is out of bounds (array length ${container.length})`);
|
|
102
|
-
}
|
|
103
|
-
return index;
|
|
104
|
-
}
|
|
105
|
-
function requireContainer(value, path) {
|
|
106
|
-
if (!value || typeof value !== 'object') {
|
|
107
|
-
throw new ConfigPatchPathError(`Config path ${pointer(path)} is not an object`);
|
|
108
|
-
}
|
|
109
|
-
return value;
|
|
110
|
-
}
|
|
111
|
-
function requireRecord(value, path) {
|
|
112
|
-
if (!value || typeof value !== 'object' || Array.isArray(value)) {
|
|
113
|
-
throw new ConfigPatchPathError(`Config path ${pointer(path)} is not an object`);
|
|
114
|
-
}
|
|
115
|
-
return value;
|
|
116
|
-
}
|
|
117
|
-
function assertPath(path) {
|
|
118
|
-
for (const segment of path) {
|
|
119
|
-
if (!segment || segment === '__proto__' || segment === 'prototype' || segment === 'constructor') {
|
|
120
|
-
throw new ConfigPatchPathError(`Unsafe config path segment: ${segment || '<empty>'}`);
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
function lastSegment(path) {
|
|
125
|
-
const segment = path[path.length - 1];
|
|
126
|
-
if (!segment)
|
|
127
|
-
throw new ConfigPatchPathError('Config patch path is empty');
|
|
128
|
-
return segment;
|
|
129
|
-
}
|
|
130
|
-
function pointer(path) {
|
|
131
|
-
if (path.length === 0)
|
|
132
|
-
return '/';
|
|
133
|
-
return `/${path.map((segment) => segment.replaceAll('~', '~0').replaceAll('/', '~1')).join('/')}`;
|
|
134
|
-
}
|
|
135
25
|
function indexGraph(graph) {
|
|
136
26
|
const result = [];
|
|
137
27
|
const visit = (node) => {
|
|
@@ -28,7 +28,8 @@ export class ConventionCapabilityDeltaResolver {
|
|
|
28
28
|
for (const root of roots) {
|
|
29
29
|
const discovered = await this.#discover(cache, provider, root);
|
|
30
30
|
for (const candidate of discovered) {
|
|
31
|
-
if (candidate.source !== fallback.source
|
|
31
|
+
if (candidate.source !== fallback.source
|
|
32
|
+
&& !candidate.relatedSources.includes(fallback.source))
|
|
32
33
|
continue;
|
|
33
34
|
const ids = capabilities.get(feature) ?? new Set();
|
|
34
35
|
ids.add(candidate.id);
|
|
@@ -82,6 +83,7 @@ export class ConventionCapabilityDeltaResolver {
|
|
|
82
83
|
for await (const source of convention.discover(context)) {
|
|
83
84
|
discovered.push(Object.freeze({
|
|
84
85
|
source: resolve(source.source),
|
|
86
|
+
relatedSources: Object.freeze((source.relatedSources ?? []).map((item) => resolve(item))),
|
|
85
87
|
id: capabilityId(root.owner, provider.id, source.localName),
|
|
86
88
|
}));
|
|
87
89
|
}
|
|
@@ -6,6 +6,11 @@ export interface EnvironmentLayers {
|
|
|
6
6
|
readonly environments?: Readonly<Record<string, EnvironmentSource>>;
|
|
7
7
|
readonly plugins?: Readonly<Record<string, EnvironmentSource>>;
|
|
8
8
|
}
|
|
9
|
+
/** Reloadable authority for environment layers and their watched files. */
|
|
10
|
+
export interface EnvironmentLayersPort {
|
|
11
|
+
readonly sources: readonly string[];
|
|
12
|
+
read(): Promise<EnvironmentLayers>;
|
|
13
|
+
}
|
|
9
14
|
export interface EnvSchema<T> {
|
|
10
15
|
readonly secretKeys?: readonly string[];
|
|
11
16
|
parse(source: EnvironmentSource): T;
|
package/lib/hmr-coordinator.d.ts
CHANGED
|
@@ -3,6 +3,7 @@ import { type GenerationInvalidationPlan, type InvalidationPlan, type ProcessInv
|
|
|
3
3
|
import type { ModuleRuntime } from './module-runtime.js';
|
|
4
4
|
import type { SourceOwnershipIndex } from './source-ownership.js';
|
|
5
5
|
export interface HmrReloadPort {
|
|
6
|
+
handlesSource?(source: string): boolean;
|
|
6
7
|
reload(plan: GenerationInvalidationPlan): Promise<ProcessInvalidationPlan | void>;
|
|
7
8
|
}
|
|
8
9
|
export interface HmrCoordinatorOptions {
|
package/lib/hmr-coordinator.js
CHANGED
|
@@ -68,7 +68,8 @@ export class HmrCoordinator {
|
|
|
68
68
|
while (this.#pending.size > 0) {
|
|
69
69
|
const changed = [...this.#pending];
|
|
70
70
|
this.#pending.clear();
|
|
71
|
-
const forcedRestart = changed.filter((source) => this.options.
|
|
71
|
+
const forcedRestart = changed.filter((source) => (!this.options.runtime.handlesSource?.(source)
|
|
72
|
+
&& this.options.modules.requiresProcessRestart?.(source)));
|
|
72
73
|
if (forcedRestart.length > 0) {
|
|
73
74
|
this.#restartRequired = true;
|
|
74
75
|
this.#pending.clear();
|
|
@@ -123,10 +123,10 @@
|
|
|
123
123
|
"description": "Command allowlist preset used outside full mode.",
|
|
124
124
|
"x-descriptionZh": "非 full 模式使用的命令白名单预设。"
|
|
125
125
|
},
|
|
126
|
-
"execApprovalMode": { "type": "string", "enum": ["ask", "
|
|
127
|
-
"subagentExecApprovalMode": { "type": "string", "enum": ["ask", "
|
|
128
|
-
"workerExecApprovalMode": { "type": "string", "enum": ["ask", "
|
|
129
|
-
"taskExecApprovalMode": { "type": "string", "enum": ["ask", "
|
|
126
|
+
"execApprovalMode": { "type": "string", "enum": ["ask", "bypass", "auto"], "description": "Approval policy for main Agent commands.", "x-descriptionZh": "主 Agent 命令的审批策略。" },
|
|
127
|
+
"subagentExecApprovalMode": { "type": "string", "enum": ["ask", "bypass", "auto"], "description": "Approval policy for sub-Agent commands.", "x-descriptionZh": "子 Agent 命令的审批策略。" },
|
|
128
|
+
"workerExecApprovalMode": { "type": "string", "enum": ["ask", "bypass", "auto"], "description": "Approval policy for worker commands.", "x-descriptionZh": "Worker 命令的审批策略。" },
|
|
129
|
+
"taskExecApprovalMode": { "type": "string", "enum": ["ask", "bypass", "auto"], "description": "Approval policy for task commands.", "x-descriptionZh": "Task 命令的审批策略。" },
|
|
130
130
|
"toolExecution": {
|
|
131
131
|
"type": "string",
|
|
132
132
|
"enum": ["parallel", "sequential", "tiered"],
|
package/lib/index.d.ts
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
export * from './config-composer.js';
|
|
2
|
-
export * from './config-document.js';
|
|
3
2
|
export * from './config-patch-planner.js';
|
|
4
|
-
export * from './
|
|
3
|
+
export * from './package-contract.js';
|
|
5
4
|
export * from './environment.js';
|
|
6
5
|
export * from './environment-store.js';
|
|
7
6
|
export * from './hmr-coordinator.js';
|
package/lib/index.js
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
export * from './config-composer.js';
|
|
2
|
-
export * from './config-document.js';
|
|
3
2
|
export * from './config-patch-planner.js';
|
|
4
|
-
export * from './
|
|
3
|
+
export * from './package-contract.js';
|
|
5
4
|
export * from './environment.js';
|
|
6
5
|
export * from './environment-store.js';
|
|
7
6
|
export * from './hmr-coordinator.js';
|
|
@@ -3,6 +3,12 @@ import type { ModuleRuntime, ModuleWatchRoot } from './module-runtime.js';
|
|
|
3
3
|
export interface NativeDevelopmentModuleRuntimeOptions {
|
|
4
4
|
readonly projectRoot: string;
|
|
5
5
|
readonly watch?: boolean;
|
|
6
|
+
/** Optional scoped loader used only when the active Node loader rejects TSX. */
|
|
7
|
+
readonly tsxLoader?: TsxModuleLoader;
|
|
8
|
+
}
|
|
9
|
+
export interface TsxModuleLoader {
|
|
10
|
+
load<T = unknown>(source: string, parentURL: string): Promise<T>;
|
|
11
|
+
close?(): Promise<void>;
|
|
6
12
|
}
|
|
7
13
|
/**
|
|
8
14
|
* Uses Node's native ESM/TypeScript loader and adds only cache busting and watch.
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { readdirSync, statSync, watch as watchDirectory, } from 'node:fs';
|
|
2
|
-
import { extname, isAbsolute, relative, resolve, sep } from 'node:path';
|
|
2
|
+
import { basename, extname, isAbsolute, relative, resolve, sep } from 'node:path';
|
|
3
3
|
import { pathToFileURL } from 'node:url';
|
|
4
4
|
const ignoredDirectories = new Set([
|
|
5
5
|
'.git', '.zhin', 'coverage', 'data', 'dist', 'lib', 'node_modules',
|
|
@@ -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', 'handlers', '
|
|
11
|
+
'adapters', 'agents', 'commands', 'components', 'handlers', 'hooks', 'mcps', 'middlewares', 'pages', 'prompt-sections', 'schedules', 'skills', 'tools',
|
|
12
12
|
]);
|
|
13
13
|
/**
|
|
14
14
|
* Uses Node's native ESM/TypeScript loader and adds only cache busting and watch.
|
|
@@ -18,6 +18,7 @@ const capabilityRoots = new Set([
|
|
|
18
18
|
export class NativeDevelopmentModuleRuntime {
|
|
19
19
|
#projectRoot;
|
|
20
20
|
#watchEnabled;
|
|
21
|
+
#tsxLoader;
|
|
21
22
|
#revisions = new Map();
|
|
22
23
|
#watchers = new Set();
|
|
23
24
|
#watchRoots;
|
|
@@ -25,19 +26,25 @@ export class NativeDevelopmentModuleRuntime {
|
|
|
25
26
|
constructor(options) {
|
|
26
27
|
this.#projectRoot = resolve(options.projectRoot);
|
|
27
28
|
this.#watchEnabled = options.watch ?? true;
|
|
29
|
+
this.#tsxLoader = options.tsxLoader;
|
|
28
30
|
this.#watchRoots = normalizeWatchRoots([this.#projectRoot]);
|
|
29
31
|
}
|
|
30
32
|
async load(source) {
|
|
31
33
|
this.#assertOpen();
|
|
32
34
|
const normalized = resolve(source);
|
|
33
|
-
if (normalized.endsWith('.tsx')) {
|
|
34
|
-
throw new Error(`Node native TypeScript does not support TSX: ${normalized}`);
|
|
35
|
-
}
|
|
36
35
|
if (normalized.endsWith('.ts'))
|
|
37
36
|
assertNativeTypeScriptSupport();
|
|
38
37
|
const url = pathToFileURL(normalized);
|
|
39
38
|
url.searchParams.set('zhin-generation', String(this.#revisions.get(normalized) ?? 0));
|
|
40
|
-
|
|
39
|
+
try {
|
|
40
|
+
return await import(url.href);
|
|
41
|
+
}
|
|
42
|
+
catch (error) {
|
|
43
|
+
if (!normalized.endsWith('.tsx') || !isUnsupportedTsxError(error) || !this.#tsxLoader) {
|
|
44
|
+
throw error;
|
|
45
|
+
}
|
|
46
|
+
return this.#tsxLoader.load(url.href, pathToFileURL(`${this.#projectRoot}${sep}`).href);
|
|
47
|
+
}
|
|
41
48
|
}
|
|
42
49
|
invalidate(source) {
|
|
43
50
|
const normalized = resolve(source);
|
|
@@ -50,23 +57,40 @@ export class NativeDevelopmentModuleRuntime {
|
|
|
50
57
|
// The HMR coordinator turns this into a visible process restart reason.
|
|
51
58
|
if (!packageRoot || isNodeModulesSource(packageRoot, normalized))
|
|
52
59
|
return true;
|
|
60
|
+
if (packageRoot === this.#projectRoot && basename(normalized).startsWith('.env'))
|
|
61
|
+
return true;
|
|
53
62
|
const parts = relative(packageRoot, normalized).split(sep);
|
|
54
63
|
const capability = parts.findIndex((part) => capabilityRoots.has(part));
|
|
55
64
|
if (capability < 0)
|
|
56
65
|
return isExecutableSource(normalized);
|
|
57
66
|
const root = parts[capability];
|
|
58
|
-
if (root === '
|
|
59
|
-
|
|
60
|
-
|
|
67
|
+
if (root === 'agents' || root === 'skills') {
|
|
68
|
+
const local = parts.slice(capability + 1);
|
|
69
|
+
if (isNestedDirectoryEntry(local))
|
|
70
|
+
return false;
|
|
61
71
|
return extname(normalized) !== '.md';
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
72
|
+
}
|
|
73
|
+
if (root === 'commands') {
|
|
74
|
+
const local = parts.slice(capability + 1);
|
|
75
|
+
if (!isExecutableSource(normalized))
|
|
76
|
+
return extname(normalized) === '.json';
|
|
77
|
+
return local.length < 2
|
|
78
|
+
|| !local.slice(0, -1).every(isCommandDirectorySegment);
|
|
79
|
+
}
|
|
80
|
+
if (root === 'adapters' || root === 'components' || root === 'handlers'
|
|
81
|
+
|| root === 'hooks' || root === 'mcps' || root === 'middlewares'
|
|
82
|
+
|| root === 'pages' || root === 'prompt-sections' || root === 'schedules'
|
|
83
|
+
|| root === 'tools') {
|
|
84
|
+
const local = parts.slice(capability + 1);
|
|
85
|
+
if (!isExecutableSource(normalized))
|
|
86
|
+
return extname(normalized) === '.json';
|
|
87
|
+
return local.length < 2
|
|
88
|
+
|| !isNamedCapabilityDirectory(local[0] ?? '', root === 'tools');
|
|
89
|
+
}
|
|
66
90
|
// Support files inside capability directories (e.g. commands/_utils.ts)
|
|
67
91
|
// are not discovery entries: reloading the entry URL only bumps that
|
|
68
92
|
// entry's zhin-generation, so the importer closure keeps the old code.
|
|
69
|
-
return ['.js', '.json', '.ts'].includes(extname(normalized));
|
|
93
|
+
return ['.js', '.json', '.ts', '.tsx'].includes(extname(normalized));
|
|
70
94
|
}
|
|
71
95
|
updateWatchRoots(roots) {
|
|
72
96
|
this.#assertOpen();
|
|
@@ -98,12 +122,34 @@ export class NativeDevelopmentModuleRuntime {
|
|
|
98
122
|
for (const watcher of this.#watchers)
|
|
99
123
|
watcher.close();
|
|
100
124
|
this.#watchers.clear();
|
|
125
|
+
await this.#tsxLoader?.close?.();
|
|
101
126
|
}
|
|
102
127
|
#assertOpen() {
|
|
103
128
|
if (this.#closed)
|
|
104
129
|
throw new Error('NativeDevelopmentModuleRuntime is closed');
|
|
105
130
|
}
|
|
106
131
|
}
|
|
132
|
+
function isCommandDirectorySegment(value) {
|
|
133
|
+
return /^[a-z0-9][a-z0-9-]*$/u.test(value)
|
|
134
|
+
|| (!/[A-Z]/u.test(value) && /[^\x00-\x7F]/u.test(value) && !/[\s/\\]/u.test(value))
|
|
135
|
+
|| /^\[(?:\[)?(?:\.\.\.)?[a-zA-Z][a-zA-Z0-9]*\](?:\])?$/u.test(value);
|
|
136
|
+
}
|
|
137
|
+
function isNamedCapabilityDirectory(value, allowSnake) {
|
|
138
|
+
return allowSnake
|
|
139
|
+
? /^[a-z0-9][a-z0-9_-]*$/u.test(value)
|
|
140
|
+
: /^[a-z0-9][a-z0-9-]*$/u.test(value);
|
|
141
|
+
}
|
|
142
|
+
function isDirectoryCapabilityEntry(parts) {
|
|
143
|
+
return parts.length === 2
|
|
144
|
+
&& parts[1] === `index${extname(parts[1] ?? '')}`
|
|
145
|
+
&& ['.cjs', '.js', '.mjs', '.ts', '.tsx'].includes(extname(parts[1] ?? ''));
|
|
146
|
+
}
|
|
147
|
+
function isNestedDirectoryEntry(parts) {
|
|
148
|
+
const index = parts.indexOf('tools');
|
|
149
|
+
const hookIndex = parts.indexOf('hooks');
|
|
150
|
+
const capability = index >= 0 ? index : hookIndex;
|
|
151
|
+
return capability >= 1 && isDirectoryCapabilityEntry(parts.slice(capability + 1));
|
|
152
|
+
}
|
|
107
153
|
export function supportsNativeTypeScript(version = process.versions.node, execArguments = process.execArgv, nodeOptions = process.env.NODE_OPTIONS ?? '') {
|
|
108
154
|
if (execArguments.includes('--experimental-strip-types')
|
|
109
155
|
|| /(?:^|\s)--experimental-strip-types(?:\s|$)/u.test(nodeOptions))
|
|
@@ -119,6 +165,11 @@ export function assertNativeTypeScriptSupport() {
|
|
|
119
165
|
'Use Node >=22.18.0 or start Node with --experimental-strip-types.',
|
|
120
166
|
].join(' '));
|
|
121
167
|
}
|
|
168
|
+
function isUnsupportedTsxError(error) {
|
|
169
|
+
return error instanceof Error
|
|
170
|
+
&& 'code' in error
|
|
171
|
+
&& error.code === 'ERR_UNKNOWN_FILE_EXTENSION';
|
|
172
|
+
}
|
|
122
173
|
class PortableSourceWatcher {
|
|
123
174
|
listener;
|
|
124
175
|
#watchers = new Set();
|
|
@@ -238,21 +289,6 @@ function isWatchedSource(source) {
|
|
|
238
289
|
function isIgnoredSource(root, source) {
|
|
239
290
|
return relative(root, source).split(sep).some((segment) => ignoredDirectories.has(segment));
|
|
240
291
|
}
|
|
241
|
-
/**
|
|
242
|
-
* Discovery entries follow the typeScriptModules convention
|
|
243
|
-
* (feature-kit typescript-convention.ts): lowercase segment directories and
|
|
244
|
-
* lowercase .ts/.tsx module names only.
|
|
245
|
-
*/
|
|
246
|
-
function isCapabilityEntry(segments) {
|
|
247
|
-
const file = segments[segments.length - 1] ?? '';
|
|
248
|
-
return segments.slice(0, -1).every(isCapabilitySegment) && isCapabilityModule(file);
|
|
249
|
-
}
|
|
250
|
-
function isCapabilitySegment(value) {
|
|
251
|
-
return /^[a-z0-9][a-z0-9-]*$/u.test(value);
|
|
252
|
-
}
|
|
253
|
-
function isCapabilityModule(value) {
|
|
254
|
-
return /^[a-z0-9][a-z0-9-]*\.tsx?$/u.test(value);
|
|
255
|
-
}
|
|
256
292
|
function isExecutableSource(source) {
|
|
257
293
|
return ['.cjs', '.js', '.mjs', '.ts', '.tsx'].includes(extname(source));
|
|
258
294
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { PackageReference } from './manifest.js';
|
|
2
2
|
import type { ResolvedPackage } from './package-resolver.js';
|
|
3
3
|
export declare const runtimeEngineVersion = "1.0.0";
|
|
4
|
-
export declare class
|
|
4
|
+
export declare class PackageContractError extends Error {
|
|
5
5
|
readonly packageName: string;
|
|
6
6
|
readonly contract: 'engine' | 'feature-api';
|
|
7
7
|
constructor(packageName: string, contract: 'engine' | 'feature-api', message: string);
|
|
@@ -2,14 +2,14 @@ import { createRequire } from 'node:module';
|
|
|
2
2
|
const require = createRequire(import.meta.url);
|
|
3
3
|
const semver = require('semver');
|
|
4
4
|
export const runtimeEngineVersion = '1.0.0';
|
|
5
|
-
export class
|
|
5
|
+
export class PackageContractError extends Error {
|
|
6
6
|
packageName;
|
|
7
7
|
contract;
|
|
8
8
|
constructor(packageName, contract, message) {
|
|
9
|
-
super(`
|
|
9
|
+
super(`Invalid ${contract} contract for ${packageName}: ${message}`);
|
|
10
10
|
this.packageName = packageName;
|
|
11
11
|
this.contract = contract;
|
|
12
|
-
this.name = '
|
|
12
|
+
this.name = 'PackageContractError';
|
|
13
13
|
}
|
|
14
14
|
}
|
|
15
15
|
export function assertPackageEngine(pkg, engineVersion = runtimeEngineVersion) {
|
|
@@ -18,7 +18,7 @@ export function assertPackageEngine(pkg, engineVersion = runtimeEngineVersion) {
|
|
|
18
18
|
return;
|
|
19
19
|
assertRange(pkg.name, 'engine', range);
|
|
20
20
|
if (!semver.satisfies(engineVersion, range, { includePrerelease: true })) {
|
|
21
|
-
throw new
|
|
21
|
+
throw new PackageContractError(pkg.name, 'engine', `requires ${range}, Runtime provides ${engineVersion}`);
|
|
22
22
|
}
|
|
23
23
|
}
|
|
24
24
|
export function assertFeatureApi(owner, reference, feature) {
|
|
@@ -26,20 +26,20 @@ export function assertFeatureApi(owner, reference, feature) {
|
|
|
26
26
|
? feature.packageJson.zhin.featureApi
|
|
27
27
|
: undefined;
|
|
28
28
|
if (actual && !semver.valid(actual)) {
|
|
29
|
-
throw new
|
|
29
|
+
throw new PackageContractError(feature.name, 'feature-api', `declares invalid featureApi version ${actual}`);
|
|
30
30
|
}
|
|
31
31
|
if (!reference.api)
|
|
32
32
|
return;
|
|
33
33
|
assertRange(owner.name, 'feature-api', reference.api);
|
|
34
34
|
if (!actual) {
|
|
35
|
-
throw new
|
|
35
|
+
throw new PackageContractError(feature.name, 'feature-api', `must declare a valid featureApi version for ${owner.name}'s ${reference.api} requirement`);
|
|
36
36
|
}
|
|
37
37
|
if (!semver.satisfies(actual, reference.api, { includePrerelease: true })) {
|
|
38
|
-
throw new
|
|
38
|
+
throw new PackageContractError(feature.name, 'feature-api', `${owner.name} requires ${reference.api}, provider declares ${actual}`);
|
|
39
39
|
}
|
|
40
40
|
}
|
|
41
41
|
function assertRange(packageName, contract, range) {
|
|
42
42
|
if (!semver.validRange(range)) {
|
|
43
|
-
throw new
|
|
43
|
+
throw new PackageContractError(packageName, contract, `invalid semver range ${range}`);
|
|
44
44
|
}
|
|
45
45
|
}
|
|
@@ -5,7 +5,7 @@ import type { ModuleRuntime } from './module-runtime.js';
|
|
|
5
5
|
import type { PluginGraphNode } from './project-graph.js';
|
|
6
6
|
import type { IsolatedPluginRuntimePort } from './isolation.js';
|
|
7
7
|
import { type PrimaryConfig } from './primary-config.js';
|
|
8
|
-
import type { RuntimeConfigDocument } from '
|
|
8
|
+
import type { RuntimeConfigDocument } from '@zhin.js/plugin-runtime';
|
|
9
9
|
export type PluginConfigResolver = (node: PluginGraphNode) => unknown;
|
|
10
10
|
export interface RootResourceContext {
|
|
11
11
|
/** Exact shadow generation being assembled; never inferred from a latest snapshot. */
|
|
@@ -53,10 +53,6 @@ export declare class PluginScopeAssembler {
|
|
|
53
53
|
createdScopeDisposers(): readonly (readonly [PluginId, Dispose])[];
|
|
54
54
|
generationHandoff(): GenerationHandoff | undefined;
|
|
55
55
|
setupCapabilities(): readonly Readonly<SetupCapabilityRegistration>[];
|
|
56
|
-
/**
|
|
57
|
-
* Root resources are process-owned. A Plugin Scope receives only a facade
|
|
58
|
-
* that translates logical table/job names to its owner namespace. The root
|
|
59
|
-
* owner deliberately keeps bare names for backward-compatible projects.
|
|
60
|
-
*/
|
|
56
|
+
/** Root resources are process-owned; every Plugin Scope receives an owner-bound facade. */
|
|
61
57
|
private installOwnerScopedHosts;
|
|
62
58
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { resolve } from 'node:path';
|
|
2
|
-
import { GenerationHandoffStack, Scope, capabilityId, createGenerationAdmissionGate, createToken,
|
|
2
|
+
import { GenerationHandoffStack, Scope, capabilityId, createGenerationAdmissionGate, createToken, PluginDatabaseHost, PluginScheduleHost, databaseHostToken, databaseRootHostToken, featureId, rootPluginId, scheduleHostToken, scheduleRootHostToken, } 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';
|
|
@@ -212,27 +212,19 @@ export class PluginScopeAssembler {
|
|
|
212
212
|
setupCapabilities() {
|
|
213
213
|
return Object.freeze([...this.#setupCapabilities.values()]);
|
|
214
214
|
}
|
|
215
|
-
/**
|
|
216
|
-
* Root resources are process-owned. A Plugin Scope receives only a facade
|
|
217
|
-
* that translates logical table/job names to its owner namespace. The root
|
|
218
|
-
* owner deliberately keeps bare names for backward-compatible projects.
|
|
219
|
-
*/
|
|
215
|
+
/** Root resources are process-owned; every Plugin Scope receives an owner-bound facade. */
|
|
220
216
|
installOwnerScopedHosts(scope, owner) {
|
|
221
217
|
const database = scope.has(databaseRootHostToken)
|
|
222
218
|
? scope.use(databaseRootHostToken)
|
|
223
|
-
:
|
|
224
|
-
? unwrapPluginDatabaseHost(scope.use(databaseHostToken))
|
|
225
|
-
: undefined;
|
|
219
|
+
: undefined;
|
|
226
220
|
if (database) {
|
|
227
|
-
scope.provide(databaseHostToken,
|
|
221
|
+
scope.provide(databaseHostToken, new PluginDatabaseHost(owner, database));
|
|
228
222
|
}
|
|
229
223
|
const schedule = scope.has(scheduleRootHostToken)
|
|
230
224
|
? scope.use(scheduleRootHostToken)
|
|
231
|
-
:
|
|
232
|
-
? unwrapPluginScheduleHost(scope.use(scheduleHostToken))
|
|
233
|
-
: undefined;
|
|
225
|
+
: undefined;
|
|
234
226
|
if (schedule) {
|
|
235
|
-
scope.provide(scheduleHostToken,
|
|
227
|
+
scope.provide(scheduleHostToken, new PluginScheduleHost(owner, schedule));
|
|
236
228
|
}
|
|
237
229
|
}
|
|
238
230
|
}
|
package/lib/primary-config.d.ts
CHANGED
package/lib/project-graph.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { childPluginId, rootPluginId } from '@zhin.js/plugin-runtime';
|
|
2
|
-
import { assertFeatureApi, assertPackageEngine, runtimeEngineVersion, } from './
|
|
2
|
+
import { assertFeatureApi, assertPackageEngine, runtimeEngineVersion, } from './package-contract.js';
|
|
3
3
|
import { PackageResolutionError, } from './package-resolver.js';
|
|
4
4
|
import { PLATFORM_FEATURE_CARRIER, PLATFORM_FEATURE_FACADE, declaredPackageDependency, mergeChildPluginReferences, mergeFeatureReferences, } from './platform-features.js';
|
|
5
5
|
export class ProjectGraphError extends Error {
|
package/lib/root-runtime.d.ts
CHANGED
|
@@ -1,9 +1,6 @@
|
|
|
1
|
-
import { type ControlErrorHandler, type GenerationCommitListener, type PluginId, type RuntimeSnapshot, type SnapshotReader } from '@zhin.js/plugin-runtime';
|
|
2
|
-
import { type RuntimeConfigDocument } from './config-composer.js';
|
|
3
|
-
import { type ConfigDocumentPort } from './config-document.js';
|
|
4
|
-
import { type ConfigPatch } from './config-patch-planner.js';
|
|
1
|
+
import { type ControlErrorHandler, type ConfigDocumentPort, type ConfigPatch, type GenerationCommitListener, type PluginId, type RuntimeConfigDocument, type RuntimeSnapshot, type SnapshotReader } from '@zhin.js/plugin-runtime';
|
|
5
2
|
import { type RuntimeEnvironment } from './environment.js';
|
|
6
|
-
import { type EnvironmentLayers } from './environment-store.js';
|
|
3
|
+
import { type EnvironmentLayers, type EnvironmentLayersPort } from './environment-store.js';
|
|
7
4
|
import type { IsolatedPluginRuntimePort } from './isolation.js';
|
|
8
5
|
import type { ModuleRuntime } from './module-runtime.js';
|
|
9
6
|
import { type PluginConfigResolver, type RootResourceInstaller } from './plugin-scope-assembler.js';
|
|
@@ -17,6 +14,7 @@ export interface RootRuntimeOptions {
|
|
|
17
14
|
readonly modules: ModuleRuntime;
|
|
18
15
|
readonly environment: RuntimeEnvironment;
|
|
19
16
|
readonly environmentVariables?: EnvironmentLayers;
|
|
17
|
+
readonly environmentSource?: EnvironmentLayersPort;
|
|
20
18
|
readonly config?: PluginConfigResolver | RuntimeConfigDocument | ConfigDocumentPort;
|
|
21
19
|
readonly installResources?: RootResourceInstaller;
|
|
22
20
|
readonly isolation?: IsolatedPluginRuntimePort;
|
package/lib/root-runtime.js
CHANGED
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
import { resolve } from 'node:path';
|
|
2
2
|
import { isDeepStrictEqual } from 'node:util';
|
|
3
|
-
import { DisposeStack, GenerationHandoffStack, RootController, createSnapshotView, rootPluginId, } from '@zhin.js/plugin-runtime';
|
|
3
|
+
import { DisposeStack, GenerationHandoffStack, RootController, ConfigDocumentDivergenceError, createSnapshotView, rootPluginId, } from '@zhin.js/plugin-runtime';
|
|
4
4
|
import { FeatureCatalog, FeatureDiscovery, } from '@zhin.js/feature-kit';
|
|
5
5
|
import { ConfigComposer } from './config-composer.js';
|
|
6
|
-
import {
|
|
7
|
-
import { ConfigPatchPlanner, } from './config-patch-planner.js';
|
|
6
|
+
import { ConfigPatchPlanner } from './config-patch-planner.js';
|
|
8
7
|
import { defineRuntimeEnvironment } from './environment.js';
|
|
9
8
|
import { createEnvStore, defineEnvironmentLayers, } from './environment-store.js';
|
|
10
9
|
import { FeatureProjector, composeGenerationHandoffs, } from './feature-projector.js';
|
|
@@ -23,11 +22,14 @@ import { addCapabilitySlot, featureSetupAliases, mergeSetupCapabilities, } from
|
|
|
23
22
|
import { SubtreeGenerationPreparer, SubtreeTopologyChangedError, } from './subtree-generation-preparer.js';
|
|
24
23
|
import { TopologyGenerationPreparer } from './topology-generation-preparer.js';
|
|
25
24
|
import { RestartBoundaryPlanner } from './restart-boundary.js';
|
|
25
|
+
import { RuntimeInputPlanner } from './runtime-input-planner.js';
|
|
26
26
|
export class RootRuntime {
|
|
27
27
|
#projectRoot;
|
|
28
28
|
#modules;
|
|
29
29
|
#environment;
|
|
30
30
|
#environmentLayers;
|
|
31
|
+
#environmentSource;
|
|
32
|
+
#runtimeInputSources;
|
|
31
33
|
#configResolver;
|
|
32
34
|
#configPort;
|
|
33
35
|
#configSnapshot;
|
|
@@ -43,12 +45,17 @@ export class RootRuntime {
|
|
|
43
45
|
this.#modules = options.modules;
|
|
44
46
|
this.#environment = defineRuntimeEnvironment(options.environment);
|
|
45
47
|
this.#environmentLayers = defineEnvironmentLayers(options.environmentVariables);
|
|
48
|
+
this.#environmentSource = options.environmentSource;
|
|
46
49
|
if (typeof options.config === 'function')
|
|
47
50
|
this.#configResolver = options.config;
|
|
48
51
|
else if (isConfigDocumentPort(options.config))
|
|
49
52
|
this.#configPort = options.config;
|
|
50
53
|
else
|
|
51
54
|
this.#configDocument = structuredClone(options.config ?? {});
|
|
55
|
+
this.#runtimeInputSources = new Set([
|
|
56
|
+
...(this.#configPort?.sources ?? []),
|
|
57
|
+
...(this.#environmentSource?.sources ?? []),
|
|
58
|
+
].map((source) => resolve(source)));
|
|
52
59
|
this.#installResources = options.installResources;
|
|
53
60
|
this.#isolation = options.isolation;
|
|
54
61
|
this.#disabledPluginInstanceKeys = Object.freeze([...(options.disabledPluginInstanceKeys ?? [])]);
|
|
@@ -67,6 +74,9 @@ export class RootRuntime {
|
|
|
67
74
|
return this.#controller.committed.state.ownership;
|
|
68
75
|
}
|
|
69
76
|
async start() {
|
|
77
|
+
if (this.#environmentSource) {
|
|
78
|
+
this.#environmentLayers = defineEnvironmentLayers(await this.#environmentSource.read());
|
|
79
|
+
}
|
|
70
80
|
if (this.#configPort) {
|
|
71
81
|
const snapshot = await this.#configPort.read();
|
|
72
82
|
this.#configSnapshot = snapshot;
|
|
@@ -95,6 +105,7 @@ export class RootRuntime {
|
|
|
95
105
|
modules: this.#modules,
|
|
96
106
|
ownership: () => this.sourceOwnership,
|
|
97
107
|
runtime: {
|
|
108
|
+
handlesSource: (source) => this.#runtimeInputSources.has(resolve(source)),
|
|
98
109
|
reload: async (plan) => {
|
|
99
110
|
const result = await this.#reloadPlan(plan);
|
|
100
111
|
return isProcessPlan(result) ? result : undefined;
|
|
@@ -120,40 +131,115 @@ export class RootRuntime {
|
|
|
120
131
|
return result;
|
|
121
132
|
}
|
|
122
133
|
async #reloadPlan(plan) {
|
|
134
|
+
const inputs = await this.#planRuntimeInputs(plan.changed);
|
|
135
|
+
if (inputs && 'kind' in inputs)
|
|
136
|
+
return inputs;
|
|
137
|
+
const previousInputs = inputs ? this.#runtimeInputState() : undefined;
|
|
138
|
+
const effectiveInputPlan = inputs
|
|
139
|
+
? Object.freeze({
|
|
140
|
+
...plan,
|
|
141
|
+
subtrees: collapseInvalidationSubtrees([...plan.subtrees, ...inputs.roots]),
|
|
142
|
+
fallbacks: Object.freeze(plan.fallbacks.filter((fallback) => !inputs.inputSources.has(resolve(fallback.source)))),
|
|
143
|
+
})
|
|
144
|
+
: plan;
|
|
123
145
|
let restart;
|
|
124
|
-
const snapshot = await this.#controller.reload(
|
|
146
|
+
const snapshot = await this.#controller.reload(effectiveInputPlan.subtrees[0] ?? effectiveInputPlan.slots[0] ?? rootPluginId(), async (current, signal) => {
|
|
125
147
|
let prepared;
|
|
126
|
-
const resolved = await this.#resolveCapabilityDelta(current,
|
|
148
|
+
const resolved = await this.#resolveCapabilityDelta(current, effectiveInputPlan);
|
|
127
149
|
const effective = resolved.plan;
|
|
150
|
+
if (inputs
|
|
151
|
+
&& effective.manifestSources.length === 0
|
|
152
|
+
&& effective.subtrees.length === 0
|
|
153
|
+
&& effective.slots.length === 0
|
|
154
|
+
&& resolved.capabilities.size === 0)
|
|
155
|
+
return undefined;
|
|
128
156
|
if (this.#model && effective.manifestSources.length > 0) {
|
|
129
|
-
const inspected = await this.#inspectProject();
|
|
157
|
+
const inspected = await this.#inspectProject(inputs?.document, inputs?.environmentLayers);
|
|
130
158
|
restart = new RestartBoundaryPlanner().plan(this.#model.graph, inspected.graph, effective.changed);
|
|
131
159
|
if (restart)
|
|
132
160
|
return undefined;
|
|
133
161
|
prepared = effective.subtrees.includes(rootPluginId())
|
|
134
162
|
? await this.#prepareInspected(current, inspected, signal)
|
|
135
|
-
: await new TopologyGenerationPreparer(this.#modules, this.#model, inspected.graph, inspected.configResolver, inspected.primaryConfigDocument, this.#environment, this.#installResources,
|
|
163
|
+
: await new TopologyGenerationPreparer(this.#modules, this.#model, inspected.graph, inspected.configResolver, inspected.primaryConfigDocument, this.#environment, this.#installResources, inspected.environmentLayers, this.#isolation).prepare(current, signal, { ...effective, capabilities: resolved.capabilities });
|
|
136
164
|
}
|
|
137
165
|
else if (effective.subtrees.length === 0 && resolved.capabilities.size > 0 && this.#model) {
|
|
138
166
|
prepared = await new SlotGenerationPreparer(this.#modules, this.#model)
|
|
139
167
|
.prepare(current, resolved.capabilities, signal);
|
|
140
168
|
}
|
|
141
169
|
else if (this.#model && this.#canPrepareSubtrees(effective)) {
|
|
142
|
-
const inspected = await this.#inspectProject();
|
|
170
|
+
const inspected = await this.#inspectProject(inputs?.document, inputs?.environmentLayers);
|
|
143
171
|
prepared = await this.#prepareSubtrees(current, inspected, effective.subtrees, signal);
|
|
144
172
|
}
|
|
145
173
|
else {
|
|
146
|
-
prepared = await this.#prepare(current, signal);
|
|
174
|
+
prepared = await this.#prepare(current, signal, inputs?.document, inputs?.environmentLayers);
|
|
147
175
|
}
|
|
148
|
-
|
|
176
|
+
if (!prepared)
|
|
177
|
+
return undefined;
|
|
178
|
+
return inputs && previousInputs
|
|
179
|
+
? withRuntimeInputHandoff(prepared.generation, () => this.#adoptRuntimeInputs(inputs), () => this.#restoreRuntimeInputs(previousInputs))
|
|
180
|
+
: prepared.generation;
|
|
149
181
|
});
|
|
150
182
|
if (restart)
|
|
151
183
|
return restart;
|
|
184
|
+
if (inputs)
|
|
185
|
+
this.#adoptRuntimeInputs(inputs);
|
|
152
186
|
return snapshot;
|
|
153
187
|
}
|
|
154
188
|
get #model() {
|
|
155
189
|
return this.#controller.committed.state.model;
|
|
156
190
|
}
|
|
191
|
+
async #planRuntimeInputs(changed) {
|
|
192
|
+
const inputSources = new Set(changed.map((source) => resolve(source)).filter((source) => this.#runtimeInputSources.has(source)));
|
|
193
|
+
if (inputSources.size === 0 || !this.#model)
|
|
194
|
+
return undefined;
|
|
195
|
+
const configChanged = this.#configPort?.sources?.some((source) => inputSources.has(resolve(source))) ?? false;
|
|
196
|
+
const environmentChanged = this.#environmentSource?.sources.some((source) => inputSources.has(resolve(source))) ?? false;
|
|
197
|
+
const configSnapshot = configChanged ? await this.#configPort?.read() : undefined;
|
|
198
|
+
const document = configSnapshot?.document ?? requireConfigDocument(this.#configDocument);
|
|
199
|
+
const environmentLayers = environmentChanged && this.#environmentSource
|
|
200
|
+
? defineEnvironmentLayers(await this.#environmentSource.read())
|
|
201
|
+
: this.#environmentLayers;
|
|
202
|
+
const inputPlan = await new RuntimeInputPlanner(new ConfigComposer(this.#disabledPluginInstanceKeys)).plan({
|
|
203
|
+
graph: this.#model.graph,
|
|
204
|
+
environment: this.#environment,
|
|
205
|
+
previousDocument: requireConfigDocument(this.#configDocument),
|
|
206
|
+
nextDocument: document,
|
|
207
|
+
previousEnvironment: this.#environmentLayers,
|
|
208
|
+
nextEnvironment: environmentLayers,
|
|
209
|
+
});
|
|
210
|
+
if (inputPlan.hostKeys.length > 0) {
|
|
211
|
+
return Object.freeze({
|
|
212
|
+
kind: 'process',
|
|
213
|
+
changed: Object.freeze([...changed].map((source) => resolve(source))),
|
|
214
|
+
reasons: Object.freeze(inputPlan.hostKeys.map((key) => `Host configuration changed: ${key}`)),
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
return Object.freeze({
|
|
218
|
+
document,
|
|
219
|
+
configSnapshot,
|
|
220
|
+
environmentLayers,
|
|
221
|
+
inputSources,
|
|
222
|
+
roots: inputPlan.roots,
|
|
223
|
+
});
|
|
224
|
+
}
|
|
225
|
+
#adoptRuntimeInputs(candidate) {
|
|
226
|
+
this.#configDocument = structuredClone(candidate.document);
|
|
227
|
+
if (candidate.configSnapshot)
|
|
228
|
+
this.#configSnapshot = candidate.configSnapshot;
|
|
229
|
+
this.#environmentLayers = candidate.environmentLayers;
|
|
230
|
+
}
|
|
231
|
+
#runtimeInputState() {
|
|
232
|
+
return Object.freeze({
|
|
233
|
+
document: this.#configDocument,
|
|
234
|
+
configSnapshot: this.#configSnapshot,
|
|
235
|
+
environmentLayers: this.#environmentLayers,
|
|
236
|
+
});
|
|
237
|
+
}
|
|
238
|
+
#restoreRuntimeInputs(state) {
|
|
239
|
+
this.#configDocument = state.document;
|
|
240
|
+
this.#configSnapshot = state.configSnapshot;
|
|
241
|
+
this.#environmentLayers = state.environmentLayers;
|
|
242
|
+
}
|
|
157
243
|
async #resolveCapabilityDelta(current, plan) {
|
|
158
244
|
const known = capabilityDeltaFromSlots(current, plan.slots);
|
|
159
245
|
if (!this.#model || (plan.fallbacks.length === 0 && plan.slots.length === 0)) {
|
|
@@ -177,35 +263,41 @@ export class RootRuntime {
|
|
|
177
263
|
if (plan.subtrees.length === 0 || plan.subtrees.includes(rootPluginId()))
|
|
178
264
|
return false;
|
|
179
265
|
return plan.changed.every((source) => {
|
|
266
|
+
if (this.#runtimeInputSources.has(resolve(source)))
|
|
267
|
+
return true;
|
|
180
268
|
const records = this.sourceOwnership.recordsFor(source);
|
|
181
269
|
return records.length > 0 && records.every((record) => record.role === 'plugin' || record.role === 'schema');
|
|
182
270
|
});
|
|
183
271
|
}
|
|
184
|
-
async #prepare(current, signal) {
|
|
272
|
+
async #prepare(current, signal, document, environmentLayers) {
|
|
185
273
|
signal.throwIfAborted();
|
|
186
|
-
const inspected = await this.#inspectProject();
|
|
274
|
+
const inspected = await this.#inspectProject(document, environmentLayers);
|
|
187
275
|
signal.throwIfAborted();
|
|
188
276
|
return this.#prepareInspected(current, inspected, signal);
|
|
189
277
|
}
|
|
190
|
-
async #inspectProject() {
|
|
278
|
+
async #inspectProject(document, environmentLayers = this.#environmentLayers) {
|
|
191
279
|
const resolver = await NodePackageResolver.create(this.#projectRoot);
|
|
192
280
|
const graph = await new ProjectGraphService(resolver, {
|
|
193
281
|
disabledPluginInstanceKeys: this.#disabledPluginInstanceKeys,
|
|
194
282
|
}).inspect(this.#projectRoot);
|
|
195
|
-
|
|
283
|
+
if (document === undefined)
|
|
284
|
+
await this.#refreshConfigDocument();
|
|
285
|
+
const activeDocument = document ?? this.#configDocument;
|
|
196
286
|
if (this.#configResolver) {
|
|
197
287
|
return {
|
|
198
288
|
graph,
|
|
199
289
|
configResolver: this.#configResolver,
|
|
200
290
|
primaryConfigDocument: Object.freeze({}),
|
|
291
|
+
environmentLayers,
|
|
201
292
|
};
|
|
202
293
|
}
|
|
203
294
|
const composed = await new ConfigComposer(this.#disabledPluginInstanceKeys)
|
|
204
|
-
.compose(graph,
|
|
295
|
+
.compose(graph, activeDocument);
|
|
205
296
|
return {
|
|
206
297
|
graph,
|
|
207
|
-
configResolver: this.#configViewResolver(composed.views),
|
|
298
|
+
configResolver: this.#configViewResolver(composed.views, environmentLayers),
|
|
208
299
|
primaryConfigDocument: composed.document,
|
|
300
|
+
environmentLayers,
|
|
209
301
|
};
|
|
210
302
|
}
|
|
211
303
|
/**
|
|
@@ -224,13 +316,13 @@ export class RootRuntime {
|
|
|
224
316
|
this.#configSnapshot = snapshot;
|
|
225
317
|
this.#configDocument = structuredClone(snapshot.document);
|
|
226
318
|
}
|
|
227
|
-
#configViewResolver(views) {
|
|
228
|
-
const env = createEnvStore(rootPluginId(), this.#environment, this.#environmentLayers);
|
|
319
|
+
#configViewResolver(views, environmentLayers = this.#environmentLayers) {
|
|
229
320
|
return (node) => {
|
|
230
321
|
const view = views.get(node.id);
|
|
231
322
|
if (view === undefined)
|
|
232
323
|
return undefined;
|
|
233
|
-
return
|
|
324
|
+
return createEnvStore(node.id, this.#environment, environmentLayers)
|
|
325
|
+
.expandMissingAsEmpty(view);
|
|
234
326
|
};
|
|
235
327
|
}
|
|
236
328
|
async #applyConfigPatches(patches) {
|
|
@@ -278,11 +370,12 @@ export class RootRuntime {
|
|
|
278
370
|
graph,
|
|
279
371
|
configResolver: this.#configViewResolver(planned.views),
|
|
280
372
|
primaryConfigDocument: planned.document,
|
|
373
|
+
environmentLayers: this.#environmentLayers,
|
|
281
374
|
};
|
|
282
375
|
// Root resources may consume any Primary Config section. Reinstall them
|
|
283
376
|
// when present so a committed patch cannot leave Host services on the
|
|
284
377
|
// previous generation's document.
|
|
285
|
-
if (
|
|
378
|
+
if (this.#model && !planned.roots.includes(rootPluginId()) && planned.roots.length > 0) {
|
|
286
379
|
prepared = await this.#prepareSubtrees(current, inspected, planned.roots, signal);
|
|
287
380
|
}
|
|
288
381
|
else {
|
|
@@ -308,14 +401,14 @@ export class RootRuntime {
|
|
|
308
401
|
return snapshot;
|
|
309
402
|
}
|
|
310
403
|
#prepareInspected(current, inspected, signal) {
|
|
311
|
-
const assembler = new GenerationAssembler(inspected.graph, this.#modules, inspected.configResolver, inspected.primaryConfigDocument, current.generation + 1, this.#environment, this.#installResources,
|
|
404
|
+
const assembler = new GenerationAssembler(inspected.graph, this.#modules, inspected.configResolver, inspected.primaryConfigDocument, current.generation + 1, this.#environment, this.#installResources, inspected.environmentLayers, this.#isolation);
|
|
312
405
|
return assembler.prepare(signal);
|
|
313
406
|
}
|
|
314
407
|
async #prepareSubtrees(current, inspected, roots, signal) {
|
|
315
408
|
if (!this.#model)
|
|
316
409
|
return this.#prepareInspected(current, inspected, signal);
|
|
317
410
|
try {
|
|
318
|
-
return await new SubtreeGenerationPreparer(this.#modules, this.#model, inspected.graph, inspected.configResolver, inspected.primaryConfigDocument, this.#environment, this.#installResources,
|
|
411
|
+
return await new SubtreeGenerationPreparer(this.#modules, this.#model, inspected.graph, inspected.configResolver, inspected.primaryConfigDocument, this.#environment, this.#installResources, inspected.environmentLayers, this.#isolation).prepare(current, roots, signal);
|
|
319
412
|
}
|
|
320
413
|
catch (error) {
|
|
321
414
|
if (!(error instanceof SubtreeTopologyChangedError))
|
|
@@ -347,6 +440,13 @@ function withConfigDocumentHandoff(generation, document, committed) {
|
|
|
347
440
|
});
|
|
348
441
|
return { ...generation, handoff: handoffs.seal() };
|
|
349
442
|
}
|
|
443
|
+
function withRuntimeInputHandoff(generation, activate, deactivate) {
|
|
444
|
+
const handoffs = new GenerationHandoffStack();
|
|
445
|
+
if (generation.handoff)
|
|
446
|
+
handoffs.add(generation.handoff);
|
|
447
|
+
handoffs.add({ activateNext: activate, deactivateNext: deactivate });
|
|
448
|
+
return { ...generation, handoff: handoffs.seal() };
|
|
449
|
+
}
|
|
350
450
|
function isConfigDocumentPort(value) {
|
|
351
451
|
if (!value || typeof value !== 'object')
|
|
352
452
|
return false;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { type PluginId, type RuntimeConfigDocument } from '@zhin.js/plugin-runtime';
|
|
2
|
+
import { ConfigComposer, type ComposedConfig } from './config-composer.js';
|
|
3
|
+
import { type EnvironmentLayers } from './environment-store.js';
|
|
4
|
+
import type { RuntimeEnvironment } from './environment.js';
|
|
5
|
+
import type { ProjectGraph } from './project-graph.js';
|
|
6
|
+
export interface RuntimeInputPlan {
|
|
7
|
+
readonly composed: ComposedConfig;
|
|
8
|
+
readonly roots: readonly PluginId[];
|
|
9
|
+
readonly hostKeys: readonly string[];
|
|
10
|
+
readonly environmentChanged: boolean;
|
|
11
|
+
}
|
|
12
|
+
/** Classifies config/environment changes by their actual expanded runtime projection. */
|
|
13
|
+
export declare class RuntimeInputPlanner {
|
|
14
|
+
private readonly composer;
|
|
15
|
+
constructor(composer?: ConfigComposer);
|
|
16
|
+
plan(options: {
|
|
17
|
+
readonly graph: ProjectGraph;
|
|
18
|
+
readonly environment: RuntimeEnvironment;
|
|
19
|
+
readonly previousDocument: RuntimeConfigDocument;
|
|
20
|
+
readonly nextDocument: RuntimeConfigDocument;
|
|
21
|
+
readonly previousEnvironment: EnvironmentLayers;
|
|
22
|
+
readonly nextEnvironment: EnvironmentLayers;
|
|
23
|
+
}): Promise<RuntimeInputPlan>;
|
|
24
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { isDeepStrictEqual } from 'node:util';
|
|
2
|
+
import { rootPluginId, } from '@zhin.js/plugin-runtime';
|
|
3
|
+
import { ConfigComposer, HOST_CONFIG_KEYS } from './config-composer.js';
|
|
4
|
+
import { createEnvStore, } from './environment-store.js';
|
|
5
|
+
/** Classifies config/environment changes by their actual expanded runtime projection. */
|
|
6
|
+
export class RuntimeInputPlanner {
|
|
7
|
+
composer;
|
|
8
|
+
constructor(composer = new ConfigComposer()) {
|
|
9
|
+
this.composer = composer;
|
|
10
|
+
}
|
|
11
|
+
async plan(options) {
|
|
12
|
+
const previous = await this.composer.compose(options.graph, options.previousDocument);
|
|
13
|
+
const next = await this.composer.compose(options.graph, options.nextDocument);
|
|
14
|
+
const environmentChanged = !isDeepStrictEqual(options.previousEnvironment, options.nextEnvironment);
|
|
15
|
+
const hostKeys = HOST_CONFIG_KEYS.filter((key) => !isDeepStrictEqual(expandHostValue(previous.document[key], options.environment, options.previousEnvironment), expandHostValue(next.document[key], options.environment, options.nextEnvironment)));
|
|
16
|
+
const changed = indexGraph(options.graph)
|
|
17
|
+
.filter((node) => !isDeepStrictEqual(expandPluginView(node.id, previous.views.get(node.id), options.environment, options.previousEnvironment), expandPluginView(node.id, next.views.get(node.id), options.environment, options.nextEnvironment)))
|
|
18
|
+
.map((node) => node.id);
|
|
19
|
+
// EnvStore is itself a public Plugin resource. A changed global layer can
|
|
20
|
+
// affect direct env.get()/parse() consumers even when no ${VAR} occurs in
|
|
21
|
+
// config, so conservatively rebuild the Root generation when no narrower
|
|
22
|
+
// config-derived owner proves the impact.
|
|
23
|
+
const roots = environmentChanged ? [rootPluginId()] : collapseRoots(changed);
|
|
24
|
+
return Object.freeze({
|
|
25
|
+
composed: next,
|
|
26
|
+
roots: Object.freeze(roots),
|
|
27
|
+
hostKeys: Object.freeze(hostKeys),
|
|
28
|
+
environmentChanged,
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
function expandHostValue(value, environment, layers) {
|
|
33
|
+
return createEnvStore(rootPluginId(), environment, layers).expandMissingAsEmpty(value);
|
|
34
|
+
}
|
|
35
|
+
function expandPluginView(owner, value, environment, layers) {
|
|
36
|
+
return createEnvStore(owner, environment, layers).expandMissingAsEmpty(value);
|
|
37
|
+
}
|
|
38
|
+
function indexGraph(graph) {
|
|
39
|
+
const result = [];
|
|
40
|
+
const visit = (node) => {
|
|
41
|
+
result.push(node);
|
|
42
|
+
for (const child of node.children)
|
|
43
|
+
visit(child);
|
|
44
|
+
};
|
|
45
|
+
visit(graph.root);
|
|
46
|
+
return result;
|
|
47
|
+
}
|
|
48
|
+
function collapseRoots(plugins) {
|
|
49
|
+
if (plugins.includes(rootPluginId()))
|
|
50
|
+
return [rootPluginId()];
|
|
51
|
+
const selected = new Set(plugins);
|
|
52
|
+
return plugins.filter((plugin) => {
|
|
53
|
+
let parent = parentPlugin(plugin);
|
|
54
|
+
while (parent) {
|
|
55
|
+
if (selected.has(parent))
|
|
56
|
+
return false;
|
|
57
|
+
parent = parentPlugin(parent);
|
|
58
|
+
}
|
|
59
|
+
return true;
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
function parentPlugin(plugin) {
|
|
63
|
+
const separator = plugin.lastIndexOf('/');
|
|
64
|
+
return separator < 0 ? undefined : plugin.slice(0, separator);
|
|
65
|
+
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { type PluginId, type RuntimeSnapshot } from '@zhin.js/plugin-runtime';
|
|
2
2
|
import type { RuntimeEnvironment } from './environment.js';
|
|
3
3
|
import type { EnvironmentLayers } from './environment-store.js';
|
|
4
|
-
import type { RuntimeConfigDocument } from '
|
|
4
|
+
import type { RuntimeConfigDocument } from '@zhin.js/plugin-runtime';
|
|
5
5
|
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';
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { type CapabilityId, type PluginId, type RuntimeSnapshot } from '@zhin.js/plugin-runtime';
|
|
2
2
|
import type { RuntimeEnvironment } from './environment.js';
|
|
3
3
|
import type { EnvironmentLayers } from './environment-store.js';
|
|
4
|
-
import type { RuntimeConfigDocument } from '
|
|
4
|
+
import type { RuntimeConfigDocument } from '@zhin.js/plugin-runtime';
|
|
5
5
|
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';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zhin.js/runtime",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.2",
|
|
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,23 +18,24 @@
|
|
|
18
18
|
"dependencies": {
|
|
19
19
|
"ajv": "8.18.0",
|
|
20
20
|
"semver": "7.8.5",
|
|
21
|
-
"@zhin.js/
|
|
22
|
-
"@zhin.js/
|
|
21
|
+
"@zhin.js/feature-kit": "1.1.2",
|
|
22
|
+
"@zhin.js/plugin-runtime": "1.1.11"
|
|
23
23
|
},
|
|
24
24
|
"devDependencies": {
|
|
25
25
|
"@types/node": "^26.1.2",
|
|
26
|
+
"tsx": "^4.23.1",
|
|
26
27
|
"typescript": "^6.0.3",
|
|
27
|
-
"@zhin.js/adapter": "1.1.
|
|
28
|
-
"@zhin.js/agent-feature": "1.1.
|
|
29
|
-
"@zhin.js/command": "1.1.
|
|
30
|
-
"@zhin.js/component": "1.1.
|
|
31
|
-
"@zhin.js/layout": "1.1.
|
|
32
|
-
"@zhin.js/mcp-feature": "1.1.
|
|
33
|
-
"@zhin.js/middleware": "1.1.
|
|
34
|
-
"@zhin.js/
|
|
35
|
-
"@zhin.js/prompt-section": "1.1.
|
|
36
|
-
"@zhin.js/
|
|
37
|
-
"@zhin.js/
|
|
28
|
+
"@zhin.js/adapter": "1.1.15",
|
|
29
|
+
"@zhin.js/agent-feature": "1.1.2",
|
|
30
|
+
"@zhin.js/command": "1.1.2",
|
|
31
|
+
"@zhin.js/component": "1.1.2",
|
|
32
|
+
"@zhin.js/layout": "1.1.2",
|
|
33
|
+
"@zhin.js/mcp-feature": "1.1.2",
|
|
34
|
+
"@zhin.js/middleware": "1.1.2",
|
|
35
|
+
"@zhin.js/page": "1.1.2",
|
|
36
|
+
"@zhin.js/prompt-section": "1.1.2",
|
|
37
|
+
"@zhin.js/skill": "1.1.2",
|
|
38
|
+
"@zhin.js/tool": "1.1.2"
|
|
38
39
|
},
|
|
39
40
|
"repository": {
|
|
40
41
|
"type": "git",
|
package/lib/config-document.d.ts
DELETED
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
import type { RuntimeConfigDocument } from './config-composer.js';
|
|
2
|
-
import type { ConfigPatch } from './config-patch-planner.js';
|
|
3
|
-
export interface ConfigDocumentSnapshot {
|
|
4
|
-
readonly document: RuntimeConfigDocument;
|
|
5
|
-
readonly revision: string;
|
|
6
|
-
}
|
|
7
|
-
/** A prepared write is inert until the generation handoff activates it. */
|
|
8
|
-
export interface PreparedConfigDocument {
|
|
9
|
-
readonly document: RuntimeConfigDocument;
|
|
10
|
-
commit(): Promise<ConfigDocumentSnapshot>;
|
|
11
|
-
rollback(): Promise<void>;
|
|
12
|
-
}
|
|
13
|
-
export interface ConfigDocumentPort {
|
|
14
|
-
read(): Promise<ConfigDocumentSnapshot>;
|
|
15
|
-
prepare(current: ConfigDocumentSnapshot, patches: readonly ConfigPatch[]): Promise<PreparedConfigDocument>;
|
|
16
|
-
}
|
|
17
|
-
export declare class ConfigDocumentDivergenceError extends Error {
|
|
18
|
-
constructor();
|
|
19
|
-
}
|