@zhin.js/runtime 1.1.8 → 1.1.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,7 +1,7 @@
1
1
  # @zhin.js/runtime
2
2
 
3
- Zhin.js Plugin 树的 Root 生命周期权威。它从静态 package manifest 构建 Plugin instance
4
- tree,按 owner 组合 schema/config/env,通过 Feature provider 发现 capability,并用
3
+ Zhin.js Plugin 树的 Root 生命周期权威。它从 `configVersion: 2` 应用声明创建 Root 直接实例,
4
+ 从包 manifest 读取 Feature/API 契约,按 owner 组合 schema/config/env,通过 Feature provider 发现 capability,并用
5
5
  immutable snapshot、CAS generation 与 lease 驱动局部 HMR。
6
6
 
7
7
  ```ts
@@ -10,17 +10,23 @@ import { EsmModuleRuntime, RootRuntime } from '@zhin.js/runtime';
10
10
  const runtime = new RootRuntime({
11
11
  projectRoot: process.cwd(),
12
12
  modules: new EsmModuleRuntime(),
13
- environment: { mode: 'development' },
13
+ config: { configVersion: 2, plugins: {} },
14
+ environment: { name: 'development', mode: 'development', platform: 'node' },
14
15
  });
15
16
 
16
17
  await runtime.start();
17
18
  ```
18
19
 
20
+ `plugins` 是实例名到包引用的映射;业务配置放在顶层同名块,停用实例列在 `disable_plugins`。
21
+ Root 自身配置仍放在 `plugin`。缺少版本标记或 Root 包仍含非空 `zhin.plugins` 时会拒绝启动,
22
+ 迁移步骤见 [插件实例声明迁移](../../../docs/migrations/plugin-instance-declarations.md)。
23
+ 自定义 `config` resolver 通过 `application` 选项提供同一 v2 应用声明,不从包清单推断实例。
24
+
19
25
  ## 事务边界
20
26
 
21
27
  - capability 文件变化只 prepare 对应 Slot。
22
28
  - child/schema 变化替换最浅受影响 subtree。
23
- - manifest topology transaction 支持 child/Feature 新增、删除和移动。
29
+ - 应用声明拓扑事务支持 Root 实例新增、删除和重排;Feature 挂载变更仍由包 manifest 驱动。
24
30
  - Root/package ABI、lockfile 和未知 importer 变化升级为 process restart。
25
31
  - 外部 Root 配置按实际投影分类:子 Plugin 配置只替换对应 subtree,Host 配置请求
26
32
  process restart。
@@ -41,3 +47,13 @@ pnpm --filter @zhin.js/runtime build
41
47
  pnpm --filter @zhin.js/runtime test
42
48
  pnpm --filter @zhin.js/runtime check:size
43
49
  ```
50
+
51
+
52
+ ## 配置解释的证据边界
53
+
54
+ `PrimaryConfig.compositionMode` 由 RootRuntime 实际执行的配置合成分支记录为
55
+ `document` 或 `custom`;没有标记的旧 authority 视为 `unknown`。它随原有
56
+ PrimaryConfig generation 资源提交、保留和回滚,不记录配置值的历史来源。
57
+ `createPrimaryConfig()` 未指定模式时使用 `unknown`,不能根据值相等推断模式。
58
+ 插件的最终 ConfigView 以同一 snapshot 的 `config.get(owner)` 为准;
59
+ PrimaryConfig.expanded 使用 Root EnvStore,不能用其中的子段替代 child owner 的环境覆盖。
@@ -0,0 +1,17 @@
1
+ /** Top-level names which cannot be used for a flat application Plugin instance. */
2
+ export declare const APPLICATION_PLUGIN_RESERVED_KEYS: readonly string[];
3
+ export interface ApplicationPluginInstanceDeclaration {
4
+ readonly instanceKey: string;
5
+ readonly package: string;
6
+ readonly enabled: boolean;
7
+ }
8
+ export type ApplicationPluginDeclaration = Readonly<{
9
+ version: 2;
10
+ instances: readonly ApplicationPluginInstanceDeclaration[];
11
+ disabled: readonly string[];
12
+ }>;
13
+ export declare const PLUGIN_INSTANCE_MIGRATION_GUIDE = "docs/migrations/plugin-instance-declarations.md";
14
+ /** Reads the single supported application protocol; never guesses from business config. */
15
+ export declare function readApplicationPluginDeclaration(document: Readonly<Record<string, unknown>>, source?: string, pluginOrder?: readonly string[]): ApplicationPluginDeclaration;
16
+ /** Structural patch address for one instance's business configuration. */
17
+ export declare function pluginInstanceConfigPath(document: Readonly<Record<string, unknown>>, instanceKey: string): readonly string[];
@@ -0,0 +1,69 @@
1
+ import hostConfigSchema from './host-config-schema.json' with { type: 'json' };
2
+ /** Top-level names which cannot be used for a flat application Plugin instance. */
3
+ export const APPLICATION_PLUGIN_RESERVED_KEYS = Object.freeze([
4
+ ...Object.keys(hostConfigSchema.properties),
5
+ 'configVersion', 'plugins', 'disable_plugins', '__proto__', 'constructor', 'prototype',
6
+ ]);
7
+ const reserved = new Set(APPLICATION_PLUGIN_RESERVED_KEYS);
8
+ export const PLUGIN_INSTANCE_MIGRATION_GUIDE = 'docs/migrations/plugin-instance-declarations.md';
9
+ /** Reads the single supported application protocol; never guesses from business config. */
10
+ export function readApplicationPluginDeclaration(document, source = 'Root config', pluginOrder) {
11
+ if (document.configVersion !== 2) {
12
+ throw new TypeError(`${source}.configVersion must be 2; migrate the application configuration: ${PLUGIN_INSTANCE_MIGRATION_GUIDE}`);
13
+ }
14
+ const plugins = document.plugins === undefined ? {} : document.plugins;
15
+ if (!plugins || typeof plugins !== 'object' || Array.isArray(plugins)) {
16
+ throw new TypeError(`${source}.plugins must map instance aliases to package strings`);
17
+ }
18
+ const entries = plugins;
19
+ const keys = Object.keys(entries);
20
+ let order = keys;
21
+ if (pluginOrder) {
22
+ if (pluginOrder.length !== keys.length || new Set(pluginOrder).size !== keys.length
23
+ || pluginOrder.some(key => !Object.hasOwn(entries, key))) {
24
+ throw new TypeError(`${source} Plugin source order does not match plugins declarations`);
25
+ }
26
+ order = [...pluginOrder];
27
+ }
28
+ const disabled = document.disable_plugins === undefined ? [] : document.disable_plugins;
29
+ if (!Array.isArray(disabled) || disabled.some(key => typeof key !== 'string')) {
30
+ throw new TypeError(`${source}.disable_plugins must be an array of declared instance aliases`);
31
+ }
32
+ if (new Set(disabled).size !== disabled.length)
33
+ throw new TypeError(`${source}.disable_plugins contains duplicate aliases`);
34
+ for (const key of disabled) {
35
+ if (!Object.hasOwn(entries, key))
36
+ throw new TypeError(`${source}.disable_plugins references undeclared instance ${key}`);
37
+ }
38
+ const disabledKeys = new Set(disabled);
39
+ const instances = order.map(instanceKey => {
40
+ if (!/^[a-z0-9][a-z0-9-]*$/.test(instanceKey) || reserved.has(instanceKey)) {
41
+ throw new TypeError(`${source}.plugins has invalid or reserved instance alias ${instanceKey}`);
42
+ }
43
+ const reference = entries[instanceKey];
44
+ if (typeof reference !== 'string' || !isPluginPackageReference(reference)) {
45
+ throw new TypeError(`${source}.plugins.${instanceKey} must be a package name or package-relative ./ path`);
46
+ }
47
+ const config = document[instanceKey];
48
+ if (config !== undefined && (!config || typeof config !== 'object' || Array.isArray(config))) {
49
+ throw new TypeError(`${source}.${instanceKey} must be a Plugin business configuration object`);
50
+ }
51
+ return Object.freeze({ instanceKey, package: reference, enabled: !disabledKeys.has(instanceKey) });
52
+ });
53
+ for (const key of Object.keys(document)) {
54
+ if (!reserved.has(key) && !Object.hasOwn(entries, key))
55
+ throw new TypeError(`${source}.${key} is an undeclared Plugin configuration block`);
56
+ }
57
+ return Object.freeze({ version: 2, instances: Object.freeze(instances), disabled: Object.freeze([...disabled]) });
58
+ }
59
+ /** Structural patch address for one instance's business configuration. */
60
+ export function pluginInstanceConfigPath(document, instanceKey) {
61
+ readApplicationPluginDeclaration(document);
62
+ return Object.freeze([instanceKey]);
63
+ }
64
+ function isPluginPackageReference(value) {
65
+ if (value.startsWith('./')) {
66
+ return value.length > 2 && !value.includes('\\') && !value.split('/').some(part => part === '..' || part === '');
67
+ }
68
+ return /^(?:@[a-z0-9][a-z0-9._-]*\/)?[a-z0-9][a-z0-9._-]*$/.test(value);
69
+ }