@zhin.js/runtime 1.0.1 → 1.0.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.
@@ -201,14 +201,19 @@ function expandString(input, lookup, onMissing) {
201
201
  if (start < 0)
202
202
  break;
203
203
  let j = start + 2;
204
- if (j >= input.length || !isEnvKeyStart(input[j])) {
204
+ const first = input[j];
205
+ if (first === undefined || !isEnvKeyStart(first)) {
205
206
  out += input.slice(i, start + 1);
206
207
  i = start + 1;
207
208
  continue;
208
209
  }
209
210
  j += 1;
210
- while (j < input.length && isEnvKeyChar(input[j]))
211
+ while (j < input.length) {
212
+ const character = input[j];
213
+ if (character === undefined || !isEnvKeyChar(character))
214
+ break;
211
215
  j += 1;
216
+ }
212
217
  const key = input.slice(start + 2, j);
213
218
  let fallback;
214
219
  if (input[j] === ':' && (input[j + 1] === '-' || input[j + 1] === '=')) {
package/lib/index.d.ts CHANGED
@@ -12,6 +12,7 @@ export * from './module-runtime.js';
12
12
  export * from './native-development-runtime.js';
13
13
  export * from './node-discovery-host.js';
14
14
  export * from './package-resolver.js';
15
+ export * from './primary-config.js';
15
16
  export * from './project-graph.js';
16
17
  export * from './process-restart.js';
17
18
  export * from './restart-boundary.js';
package/lib/index.js CHANGED
@@ -12,6 +12,7 @@ export * from './module-runtime.js';
12
12
  export * from './native-development-runtime.js';
13
13
  export * from './node-discovery-host.js';
14
14
  export * from './package-resolver.js';
15
+ export * from './primary-config.js';
15
16
  export * from './project-graph.js';
16
17
  export * from './process-restart.js';
17
18
  export * from './restart-boundary.js';
@@ -4,11 +4,14 @@ import { type EnvironmentLayers } from './environment-store.js';
4
4
  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
+ import { type PrimaryConfig } from './primary-config.js';
8
+ import type { RuntimeConfigDocument } from './config-composer.js';
7
9
  export type PluginConfigResolver = (node: PluginGraphNode) => unknown;
8
10
  export interface RootResourceContext {
9
11
  readonly resources: Scope;
10
12
  readonly lifecycle: DisposeStack;
11
13
  readonly handoff: GenerationHandoffRegistry;
14
+ readonly config: PrimaryConfig;
12
15
  }
13
16
  export type RootResourceInstaller = (context: RootResourceContext) => void | Promise<void>;
14
17
  export interface PluginAssemblySeed {
@@ -23,13 +26,14 @@ export declare class PluginScopeAssembler {
23
26
  private readonly modules;
24
27
  private readonly configResolver;
25
28
  private readonly environment;
29
+ private readonly primaryConfigDocument;
26
30
  private readonly installResources?;
27
31
  private readonly isolation?;
28
32
  readonly scopes: Map<PluginId, Scope>;
29
33
  readonly tree: Map<PluginId, PluginNodeSnapshot>;
30
34
  readonly config: Map<PluginId, unknown>;
31
35
  readonly resources: Map<PluginId, ReadonlyMap<TokenId, unknown>>;
32
- constructor(modules: ModuleRuntime, configResolver: PluginConfigResolver, environment: RuntimeEnvironment, installResources?: RootResourceInstaller | undefined, environmentLayers?: EnvironmentLayers, seed?: PluginAssemblySeed, isolation?: IsolatedPluginRuntimePort | undefined);
36
+ constructor(modules: ModuleRuntime, configResolver: PluginConfigResolver, environment: RuntimeEnvironment, primaryConfigDocument: RuntimeConfigDocument, installResources?: RootResourceInstaller | undefined, environmentLayers?: EnvironmentLayers, seed?: PluginAssemblySeed, isolation?: IsolatedPluginRuntimePort | undefined);
33
37
  removeSubtrees(roots: readonly PluginId[]): void;
34
38
  setupTree(node: PluginGraphNode): Promise<void>;
35
39
  synchronizeTree(node: PluginGraphNode): void;
@@ -2,11 +2,13 @@ import { resolve } from 'node:path';
2
2
  import { GenerationHandoffStack, Scope, rootPluginId, } from '@zhin.js/plugin-runtime';
3
3
  import { runtimeEnvironmentToken } from './environment.js';
4
4
  import { EnvStoreFactory, envStoreToken, } from './environment-store.js';
5
+ import { createPrimaryConfig, primaryConfigToken, } from './primary-config.js';
5
6
  /** Assembles Plugin setup into mutable shadow maps without publishing them. */
6
7
  export class PluginScopeAssembler {
7
8
  modules;
8
9
  configResolver;
9
10
  environment;
11
+ primaryConfigDocument;
10
12
  installResources;
11
13
  isolation;
12
14
  scopes;
@@ -16,10 +18,11 @@ export class PluginScopeAssembler {
16
18
  #created = [];
17
19
  #handoffs = new GenerationHandoffStack();
18
20
  #envStores;
19
- constructor(modules, configResolver, environment, installResources, environmentLayers = {}, seed, isolation) {
21
+ constructor(modules, configResolver, environment, primaryConfigDocument, installResources, environmentLayers = {}, seed, isolation) {
20
22
  this.modules = modules;
21
23
  this.configResolver = configResolver;
22
24
  this.environment = environment;
25
+ this.primaryConfigDocument = primaryConfigDocument;
23
26
  this.installResources = installResources;
24
27
  this.isolation = isolation;
25
28
  this.#envStores = new EnvStoreFactory(environment, environmentLayers);
@@ -49,13 +52,17 @@ export class PluginScopeAssembler {
49
52
  this.scopes.set(node.id, scope);
50
53
  this.#created.push(node.id);
51
54
  // Every owner shadows the inherited EnvStore with its exact overlay view.
52
- scope.provide(envStoreToken, this.#envStores.create(node.id));
55
+ const environment = this.#envStores.create(node.id);
56
+ scope.provide(envStoreToken, environment);
53
57
  if (!node.parent) {
54
58
  scope.provide(runtimeEnvironmentToken, this.environment);
59
+ const config = createPrimaryConfig(this.primaryConfigDocument, environment);
60
+ scope.provide(primaryConfigToken, config);
55
61
  await this.installResources?.({
56
62
  resources: scope,
57
63
  lifecycle: scope.disposers,
58
64
  handoff: this.#handoffs,
65
+ config,
59
66
  });
60
67
  }
61
68
  const config = Object.freeze(this.configResolver(node) ?? {});
@@ -0,0 +1,16 @@
1
+ import type { EnvStore } from './environment-store.js';
2
+ import type { RuntimeConfigDocument } from './config-composer.js';
3
+ /**
4
+ * Generation-owned, validated Root configuration.
5
+ *
6
+ * `document` deliberately retains environment placeholders for safe display and
7
+ * persistence. Runtime consumers use `expanded`, which is derived exclusively
8
+ * through the Root EnvStore instead of reading process.env themselves.
9
+ */
10
+ export interface PrimaryConfig {
11
+ readonly document: RuntimeConfigDocument;
12
+ readonly expanded: RuntimeConfigDocument;
13
+ get<T = unknown>(key: string): T | undefined;
14
+ }
15
+ export declare const primaryConfigToken: import("@zhin.js/plugin-runtime").Token<PrimaryConfig>;
16
+ export declare function createPrimaryConfig(document: RuntimeConfigDocument, environment: EnvStore): PrimaryConfig;
@@ -0,0 +1,20 @@
1
+ import { createToken } from '@zhin.js/plugin-runtime';
2
+ export const primaryConfigToken = createToken('zhin.primary-config', 'Validated Root configuration for the current generation');
3
+ export function createPrimaryConfig(document, environment) {
4
+ const safeDocument = freezeConfig(structuredClone(document));
5
+ const expanded = freezeConfig(environment.expandMissingAsEmpty(safeDocument));
6
+ return Object.freeze({
7
+ document: safeDocument,
8
+ expanded,
9
+ get(key) {
10
+ return expanded[key];
11
+ },
12
+ });
13
+ }
14
+ function freezeConfig(value) {
15
+ if (!value || typeof value !== 'object' || Object.isFrozen(value))
16
+ return value;
17
+ for (const child of Object.values(value))
18
+ freezeConfig(child);
19
+ return Object.freeze(value);
20
+ }
@@ -118,7 +118,7 @@ export class RootRuntime {
118
118
  restart = new RestartBoundaryPlanner().plan(this.#model.graph, inspected.graph, plan.changed);
119
119
  if (restart)
120
120
  return undefined;
121
- prepared = await new TopologyGenerationPreparer(this.#modules, this.#model, inspected.graph, inspected.configResolver, this.#environment, this.#installResources, this.#environmentLayers, this.#isolation).prepare(current);
121
+ prepared = await new TopologyGenerationPreparer(this.#modules, this.#model, inspected.graph, inspected.configResolver, inspected.primaryConfigDocument, this.#environment, this.#installResources, this.#environmentLayers, this.#isolation).prepare(current);
122
122
  }
123
123
  else if (plan.subtrees.length === 0 && plan.slots.length > 0 && this.#model) {
124
124
  prepared = await new SlotGenerationPreparer(this.#modules, this.#model)
@@ -168,12 +168,19 @@ export class RootRuntime {
168
168
  async #inspectProject() {
169
169
  const resolver = await NodePackageResolver.create(this.#projectRoot);
170
170
  const graph = await new ProjectGraphService(resolver).inspect(this.#projectRoot);
171
- const configResolver = this.#configResolver
172
- ? this.#configResolver
173
- : await new ConfigComposer()
174
- .compose(graph, this.#configDocument)
175
- .then((composed) => this.#configViewResolver(composed.views));
176
- return { graph, configResolver };
171
+ if (this.#configResolver) {
172
+ return {
173
+ graph,
174
+ configResolver: this.#configResolver,
175
+ primaryConfigDocument: Object.freeze({}),
176
+ };
177
+ }
178
+ const composed = await new ConfigComposer().compose(graph, this.#configDocument);
179
+ return {
180
+ graph,
181
+ configResolver: this.#configViewResolver(composed.views),
182
+ primaryConfigDocument: composed.document,
183
+ };
177
184
  }
178
185
  #configViewResolver(views) {
179
186
  const env = createEnvStore(rootPluginId(), this.#environment, this.#environmentLayers);
@@ -217,8 +224,12 @@ export class RootRuntime {
217
224
  const inspected = {
218
225
  graph,
219
226
  configResolver: this.#configViewResolver(planned.views),
227
+ primaryConfigDocument: planned.document,
220
228
  };
221
- if (this.#model && !planned.roots.includes(rootPluginId())) {
229
+ // Root resources may consume any Primary Config section. Reinstall them
230
+ // when present so a committed patch cannot leave Host services on the
231
+ // previous generation's document.
232
+ if (!this.#installResources && this.#model && !planned.roots.includes(rootPluginId())) {
222
233
  prepared = await this.#prepareSubtrees(current, inspected, planned.roots);
223
234
  }
224
235
  else {
@@ -237,14 +248,14 @@ export class RootRuntime {
237
248
  return snapshot;
238
249
  }
239
250
  #prepareInspected(current, inspected) {
240
- const assembler = new GenerationAssembler(inspected.graph, this.#modules, inspected.configResolver, current.generation + 1, this.#environment, this.#installResources, this.#environmentLayers, this.#isolation);
251
+ const assembler = new GenerationAssembler(inspected.graph, this.#modules, inspected.configResolver, inspected.primaryConfigDocument, current.generation + 1, this.#environment, this.#installResources, this.#environmentLayers, this.#isolation);
241
252
  return assembler.prepare();
242
253
  }
243
254
  async #prepareSubtrees(current, inspected, roots) {
244
255
  if (!this.#model)
245
256
  return this.#prepareInspected(current, inspected);
246
257
  try {
247
- return await new SubtreeGenerationPreparer(this.#modules, this.#model, inspected.graph, inspected.configResolver, this.#environment, this.#installResources, this.#environmentLayers, this.#isolation).prepare(current, roots);
258
+ 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);
248
259
  }
249
260
  catch (error) {
250
261
  if (!(error instanceof SubtreeTopologyChangedError))
@@ -292,6 +303,7 @@ class GenerationAssembler {
292
303
  graph;
293
304
  modules;
294
305
  configResolver;
306
+ primaryConfigDocument;
295
307
  generation;
296
308
  environment;
297
309
  installResources;
@@ -304,17 +316,18 @@ class GenerationAssembler {
304
316
  #projectionDisposers = [];
305
317
  #host;
306
318
  #plugins;
307
- constructor(graph, modules, configResolver, generation, environment, installResources, environmentLayers = {}, isolation) {
319
+ constructor(graph, modules, configResolver, primaryConfigDocument, generation, environment, installResources, environmentLayers = {}, isolation) {
308
320
  this.graph = graph;
309
321
  this.modules = modules;
310
322
  this.configResolver = configResolver;
323
+ this.primaryConfigDocument = primaryConfigDocument;
311
324
  this.generation = generation;
312
325
  this.environment = environment;
313
326
  this.installResources = installResources;
314
327
  this.environmentLayers = environmentLayers;
315
328
  this.isolation = isolation;
316
329
  this.#host = new NodeDiscoveryHost(modules);
317
- this.#plugins = new PluginScopeAssembler(modules, configResolver, environment, installResources, environmentLayers, undefined, isolation);
330
+ this.#plugins = new PluginScopeAssembler(modules, configResolver, environment, primaryConfigDocument, installResources, environmentLayers, undefined, isolation);
318
331
  }
319
332
  async prepare() {
320
333
  try {
@@ -1,6 +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 './config-composer.js';
4
5
  import type { IsolatedPluginRuntimePort } from './isolation.js';
5
6
  import type { ModuleRuntime } from './module-runtime.js';
6
7
  import { type PluginConfigResolver, type RootResourceInstaller } from './plugin-scope-assembler.js';
@@ -15,10 +16,11 @@ export declare class SubtreeGenerationPreparer {
15
16
  private readonly model;
16
17
  private readonly graph;
17
18
  private readonly configResolver;
19
+ private readonly primaryConfigDocument;
18
20
  private readonly environment;
19
21
  private readonly installResources?;
20
22
  private readonly environmentLayers;
21
23
  private readonly isolation?;
22
- constructor(modules: ModuleRuntime, model: RuntimeGenerationModel, graph: ProjectGraph, configResolver: PluginConfigResolver, environment: RuntimeEnvironment, installResources?: RootResourceInstaller | undefined, environmentLayers?: EnvironmentLayers, isolation?: IsolatedPluginRuntimePort | undefined);
24
+ constructor(modules: ModuleRuntime, model: RuntimeGenerationModel, graph: ProjectGraph, configResolver: PluginConfigResolver, primaryConfigDocument: RuntimeConfigDocument, environment: RuntimeEnvironment, installResources?: RootResourceInstaller | undefined, environmentLayers?: EnvironmentLayers, isolation?: IsolatedPluginRuntimePort | undefined);
23
25
  prepare(current: RuntimeSnapshot, roots: readonly PluginId[]): Promise<PreparedRuntimeGeneration>;
24
26
  }
@@ -16,15 +16,17 @@ export class SubtreeGenerationPreparer {
16
16
  model;
17
17
  graph;
18
18
  configResolver;
19
+ primaryConfigDocument;
19
20
  environment;
20
21
  installResources;
21
22
  environmentLayers;
22
23
  isolation;
23
- constructor(modules, model, graph, configResolver, environment, installResources, environmentLayers = {}, isolation) {
24
+ constructor(modules, model, graph, configResolver, primaryConfigDocument, environment, installResources, environmentLayers = {}, isolation) {
24
25
  this.modules = modules;
25
26
  this.model = model;
26
27
  this.graph = graph;
27
28
  this.configResolver = configResolver;
29
+ this.primaryConfigDocument = primaryConfigDocument;
28
30
  this.environment = environment;
29
31
  this.installResources = installResources;
30
32
  this.environmentLayers = environmentLayers;
@@ -33,7 +35,7 @@ export class SubtreeGenerationPreparer {
33
35
  async prepare(current, roots) {
34
36
  const nodes = indexGraph(this.graph);
35
37
  assertCompatibleTopology(indexGraph(this.model.graph), nodes, roots);
36
- const plugins = new PluginScopeAssembler(this.modules, this.configResolver, this.environment, this.installResources, this.environmentLayers, {
38
+ const plugins = new PluginScopeAssembler(this.modules, this.configResolver, this.environment, this.primaryConfigDocument, this.installResources, this.environmentLayers, {
37
39
  scopes: this.model.scopes,
38
40
  tree: current.tree,
39
41
  config: current.config,
@@ -1,6 +1,7 @@
1
1
  import { 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 './config-composer.js';
4
5
  import type { IsolatedPluginRuntimePort } from './isolation.js';
5
6
  import type { ModuleRuntime } from './module-runtime.js';
6
7
  import { type PluginConfigResolver, type RootResourceInstaller } from './plugin-scope-assembler.js';
@@ -13,10 +14,11 @@ export declare class TopologyGenerationPreparer {
13
14
  private readonly model;
14
15
  private readonly graph;
15
16
  private readonly configResolver;
17
+ private readonly primaryConfigDocument;
16
18
  private readonly environment;
17
19
  private readonly installResources?;
18
20
  private readonly environmentLayers;
19
21
  private readonly isolation?;
20
- constructor(modules: ModuleRuntime, model: RuntimeGenerationModel, graph: ProjectGraph, configResolver: PluginConfigResolver, environment: RuntimeEnvironment, installResources?: RootResourceInstaller | undefined, environmentLayers?: EnvironmentLayers, isolation?: IsolatedPluginRuntimePort | undefined);
22
+ constructor(modules: ModuleRuntime, model: RuntimeGenerationModel, graph: ProjectGraph, configResolver: PluginConfigResolver, primaryConfigDocument: RuntimeConfigDocument, environment: RuntimeEnvironment, installResources?: RootResourceInstaller | undefined, environmentLayers?: EnvironmentLayers, isolation?: IsolatedPluginRuntimePort | undefined);
21
23
  prepare(current: RuntimeSnapshot): Promise<PreparedRuntimeGeneration | undefined>;
22
24
  }
@@ -13,15 +13,17 @@ export class TopologyGenerationPreparer {
13
13
  model;
14
14
  graph;
15
15
  configResolver;
16
+ primaryConfigDocument;
16
17
  environment;
17
18
  installResources;
18
19
  environmentLayers;
19
20
  isolation;
20
- constructor(modules, model, graph, configResolver, environment, installResources, environmentLayers = {}, isolation) {
21
+ constructor(modules, model, graph, configResolver, primaryConfigDocument, environment, installResources, environmentLayers = {}, isolation) {
21
22
  this.modules = modules;
22
23
  this.model = model;
23
24
  this.graph = graph;
24
25
  this.configResolver = configResolver;
26
+ this.primaryConfigDocument = primaryConfigDocument;
25
27
  this.environment = environment;
26
28
  this.installResources = installResources;
27
29
  this.environmentLayers = environmentLayers;
@@ -39,7 +41,7 @@ export class TopologyGenerationPreparer {
39
41
  if (!plan.changed)
40
42
  return undefined;
41
43
  const featureTopology = await this.#loadFeatureTopology(plan);
42
- const plugins = new PluginScopeAssembler(this.modules, this.configResolver, this.environment, this.installResources, this.environmentLayers, {
44
+ const plugins = new PluginScopeAssembler(this.modules, this.configResolver, this.environment, this.primaryConfigDocument, this.installResources, this.environmentLayers, {
43
45
  scopes: this.model.scopes,
44
46
  tree: current.tree,
45
47
  config: current.config,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zhin.js/runtime",
3
- "version": "1.0.1",
3
+ "version": "1.0.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,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.1",
22
- "@zhin.js/plugin-runtime": "1.0.1"
21
+ "@zhin.js/feature-kit": "1.0.2",
22
+ "@zhin.js/plugin-runtime": "1.1.0"
23
23
  },
24
24
  "devDependencies": {
25
25
  "@types/node": "^26.1.0",
26
26
  "typescript": "^6.0.3",
27
- "@zhin.js/adapter": "1.0.1",
28
- "@zhin.js/agent-feature": "1.0.1",
29
- "@zhin.js/command": "1.0.1",
30
- "@zhin.js/layout": "1.0.1",
31
- "@zhin.js/mcp-feature": "1.0.1",
32
- "@zhin.js/middleware": "1.0.1",
33
- "@zhin.js/component": "1.0.1",
34
- "@zhin.js/page": "1.0.1",
35
- "@zhin.js/skill": "1.0.1",
36
- "@zhin.js/tool": "1.0.1"
27
+ "@zhin.js/adapter": "1.1.0",
28
+ "@zhin.js/command": "1.0.2",
29
+ "@zhin.js/agent-feature": "1.0.2",
30
+ "@zhin.js/component": "1.0.2",
31
+ "@zhin.js/layout": "1.0.2",
32
+ "@zhin.js/mcp-feature": "1.0.2",
33
+ "@zhin.js/middleware": "1.0.2",
34
+ "@zhin.js/tool": "1.0.2",
35
+ "@zhin.js/page": "1.0.2",
36
+ "@zhin.js/skill": "1.0.2"
37
37
  },
38
38
  "repository": {
39
39
  "type": "git",