@zenithbuild/cli 0.7.11 → 0.7.12

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.
Files changed (87) hide show
  1. package/README.md +10 -1
  2. package/dist/adapters/adapter-netlify-static.d.ts +2 -5
  3. package/dist/adapters/adapter-netlify.d.ts +2 -5
  4. package/dist/adapters/adapter-netlify.js +22 -5
  5. package/dist/adapters/adapter-types.d.ts +32 -13
  6. package/dist/adapters/adapter-types.js +0 -59
  7. package/dist/adapters/adapter-vercel-static.d.ts +2 -5
  8. package/dist/adapters/adapter-vercel.d.ts +2 -5
  9. package/dist/adapters/adapter-vercel.js +21 -6
  10. package/dist/adapters/copy-hosted-page-runtime.d.ts +2 -1
  11. package/dist/adapters/copy-hosted-page-runtime.js +68 -3
  12. package/dist/adapters/resolve-adapter.d.ts +6 -4
  13. package/dist/build/expression-rewrites.d.ts +3 -1
  14. package/dist/build/expression-rewrites.js +14 -2
  15. package/dist/build/page-component-loop.d.ts +1 -0
  16. package/dist/build/page-component-loop.js +66 -6
  17. package/dist/build/page-ir-normalization.js +7 -0
  18. package/dist/build/page-loop-state.d.ts +2 -1
  19. package/dist/build/page-loop-state.js +9 -2
  20. package/dist/build/page-loop.js +10 -1
  21. package/dist/build/scoped-expression-context.d.ts +5 -0
  22. package/dist/build/scoped-expression-context.js +133 -0
  23. package/dist/build/type-declarations.d.ts +2 -1
  24. package/dist/build/type-declarations.js +31 -1
  25. package/dist/build-output-manifest.d.ts +10 -6
  26. package/dist/build-output-manifest.js +4 -1
  27. package/dist/build.js +11 -2
  28. package/dist/component-instance-ir.js +1 -0
  29. package/dist/component-occurrences.d.ts +9 -0
  30. package/dist/component-occurrences.js +18 -0
  31. package/dist/config-plugins.d.ts +12 -0
  32. package/dist/config-plugins.js +100 -0
  33. package/dist/config.d.ts +1 -0
  34. package/dist/config.js +56 -5
  35. package/dist/dev-server/request-handler.js +46 -4
  36. package/dist/dev-server.js +92 -4
  37. package/dist/global-middleware-runtime-source.d.ts +15 -0
  38. package/dist/global-middleware-runtime-source.js +62 -0
  39. package/dist/global-middleware.d.ts +13 -0
  40. package/dist/global-middleware.js +252 -0
  41. package/dist/manifest.d.ts +9 -1
  42. package/dist/manifest.js +66 -26
  43. package/dist/preview/request-handler.js +78 -5
  44. package/dist/preview/server-runner.d.ts +7 -2
  45. package/dist/preview/server-runner.js +19 -6
  46. package/dist/preview/server-script-runner-template.js +97 -29
  47. package/dist/route-classification.d.ts +2 -1
  48. package/dist/route-classification.js +6 -2
  49. package/dist/scoped-server-data/analyze-owner-file.d.ts +3 -0
  50. package/dist/scoped-server-data/analyze-owner-file.js +149 -0
  51. package/dist/scoped-server-data/diagnostics.d.ts +18 -0
  52. package/dist/scoped-server-data/diagnostics.js +32 -0
  53. package/dist/scoped-server-data/lowering.d.ts +27 -0
  54. package/dist/scoped-server-data/lowering.js +242 -0
  55. package/dist/scoped-server-data/manifest-integration.d.ts +4 -0
  56. package/dist/scoped-server-data/manifest-integration.js +125 -0
  57. package/dist/scoped-server-data/owner-scanner.d.ts +6 -0
  58. package/dist/scoped-server-data/owner-scanner.js +55 -0
  59. package/dist/scoped-server-data/parse-owner-server-block.d.ts +12 -0
  60. package/dist/scoped-server-data/parse-owner-server-block.js +35 -0
  61. package/dist/scoped-server-data/runtime.d.ts +24 -0
  62. package/dist/scoped-server-data/runtime.js +121 -0
  63. package/dist/scoped-server-data/serialization-set.d.ts +2 -0
  64. package/dist/scoped-server-data/serialization-set.js +52 -0
  65. package/dist/scoped-server-data/static-props.d.ts +12 -0
  66. package/dist/scoped-server-data/static-props.js +307 -0
  67. package/dist/scoped-server-data/type-declarations.d.ts +10 -0
  68. package/dist/scoped-server-data/type-declarations.js +368 -0
  69. package/dist/scoped-server-data/types.d.ts +74 -0
  70. package/dist/scoped-server-data/types.js +1 -0
  71. package/dist/server-contract/auth-control-flow.d.ts +1 -0
  72. package/dist/server-contract/auth-control-flow.js +10 -0
  73. package/dist/server-contract/resolve.d.ts +19 -0
  74. package/dist/server-contract/resolve.js +85 -13
  75. package/dist/server-contract/resolved-envelope.d.ts +9 -0
  76. package/dist/server-contract/resolved-envelope.js +14 -0
  77. package/dist/server-contract/stage.js +1 -10
  78. package/dist/server-module-output.d.ts +9 -0
  79. package/dist/server-module-output.js +250 -0
  80. package/dist/server-output.d.ts +7 -1
  81. package/dist/server-output.js +138 -179
  82. package/dist/server-runtime/matched-route-pipeline.d.ts +1 -0
  83. package/dist/server-runtime/matched-route-pipeline.js +1 -0
  84. package/dist/server-runtime/node-server.js +21 -1
  85. package/dist/server-runtime/route-render.d.ts +12 -3
  86. package/dist/server-runtime/route-render.js +67 -13
  87. package/package.json +3 -3
@@ -4,6 +4,7 @@ import { cloneComponentIrForInstance } from '../component-instance-ir.js';
4
4
  import { renderPropsLiteralFromAttrs } from './scoped-identifier-rewrite.js';
5
5
  import { extractTemplate, isDocumentMode } from '../resolve-components.js';
6
6
  import { buildComponentExpressionRewrite, mergeExpressionRewriteMaps, resolveStateKeyFromBindings } from './expression-rewrites.js';
7
+ import { applyScopedDataContextToExpressionRewrite, resolveScopedExpressionContext } from './scoped-expression-context.js';
7
8
  import { mergeComponentIr } from './merge-component-ir.js';
8
9
  import { stripStyleBlocks } from './compiler-runtime.js';
9
10
  import { extractDeclaredIdentifiers } from './typescript-expression-utils.js';
@@ -17,6 +18,55 @@ function createEmptyExpressionRewrite() {
17
18
  sequence: []
18
19
  };
19
20
  }
21
+ const SERVER_CONST_RE = /(?:^|\n)\s*const\s+([A-Za-z_$][\w$]*)\s*=/g;
22
+ function stripOwnerServerBlocks(source) {
23
+ return String(source || '').replace(/<script\b([^>]*)>([\s\S]*?)<\/script>/gi, (full, attrs) => {
24
+ if (/\bserver\b/i.test(String(attrs || ''))) {
25
+ return '';
26
+ }
27
+ return full;
28
+ });
29
+ }
30
+ function collectServerConstNames(source) {
31
+ const names = [];
32
+ for (const match of String(source || '').matchAll(SERVER_CONST_RE)) {
33
+ const name = String(match[1] || '');
34
+ if (name && !names.includes(name)) {
35
+ names.push(name);
36
+ }
37
+ }
38
+ return names;
39
+ }
40
+ function createClientPlaceholderPrelude(source, strippedSource) {
41
+ const names = [];
42
+ String(source || '').replace(/<script\b([^>]*)>([\s\S]*?)<\/script>/gi, (full, attrs, body) => {
43
+ if (/\bserver\b/i.test(String(attrs || ''))) {
44
+ for (const name of collectServerConstNames(body)) {
45
+ const refRe = new RegExp(`\\b${escapeRegExp(name)}\\b`);
46
+ if (refRe.test(strippedSource) && !names.includes(name)) {
47
+ names.push(name);
48
+ }
49
+ }
50
+ }
51
+ return full;
52
+ });
53
+ if (names.length === 0) {
54
+ return '';
55
+ }
56
+ return [
57
+ '<script lang="ts">',
58
+ ...names.map((name) => `const ${name} = undefined;`),
59
+ '</script>',
60
+ ''
61
+ ].join('\n');
62
+ }
63
+ function prepareOwnerClientCompileSource(source) {
64
+ const strippedSource = stripOwnerServerBlocks(source);
65
+ return stripStyleBlocks(`${createClientPlaceholderPrelude(source, strippedSource)}${strippedSource}`);
66
+ }
67
+ function escapeRegExp(value) {
68
+ return String(value).replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
69
+ }
20
70
  async function resolveComponentIr({ compPath, componentSource, compilerOpts, compilerBin, timedRunCompiler, cooperativeYield, componentIrCache, compilerTotals, pageStats, startupProfile, emitCompilerWarning }) {
21
71
  let compIr;
22
72
  if (componentIrCache.has(compPath)) {
@@ -27,7 +77,7 @@ async function resolveComponentIr({ compPath, componentSource, compilerOpts, com
27
77
  else {
28
78
  await cooperativeYield();
29
79
  const componentCompileStartedAt = performance.now();
30
- compIr = timedRunCompiler('component', compPath, stripStyleBlocks(componentSource), compilerOpts, { compilerToolchain: compilerBin, onWarning: emitCompilerWarning });
80
+ compIr = timedRunCompiler('component', compPath, prepareOwnerClientCompileSource(componentSource), compilerOpts, { compilerToolchain: compilerBin, onWarning: emitCompilerWarning });
31
81
  pageStats.pageComponentCompileMs += startupProfile.roundMs(performance.now() - componentCompileStartedAt);
32
82
  compilerTotals.componentCacheMisses += 1;
33
83
  pageStats.pageComponentCacheMisses += 1;
@@ -72,7 +122,7 @@ async function resolveOwnerRewriteContext({ occurrence, sourceFile, compilerOpts
72
122
  pageComponentLoopBreakdown.ownerSourceReadMs += startupProfile.roundMs(performance.now() - ownerSourceReadStartedAt);
73
123
  await cooperativeYield();
74
124
  const ownerCompileStartedAt = performance.now();
75
- ownerIr = timedRunCompiler('component', ownerPath, stripStyleBlocks(ownerSource), compilerOpts, { compilerToolchain: compilerBin, onWarning: emitCompilerWarning });
125
+ ownerIr = timedRunCompiler('component', ownerPath, prepareOwnerClientCompileSource(ownerSource), compilerOpts, { compilerToolchain: compilerBin, onWarning: emitCompilerWarning });
76
126
  pageStats.pageComponentCompileMs += startupProfile.roundMs(performance.now() - ownerCompileStartedAt);
77
127
  compilerTotals.componentCacheMisses += 1;
78
128
  pageStats.pageComponentCacheMisses += 1;
@@ -136,6 +186,7 @@ export async function buildPageOwnerContext({ componentOccurrences, sourceFile,
136
186
  }
137
187
  export async function runPageComponentLoop({ componentOccurrences, occurrenceCountByPath, sourceFile, registry, compilerOpts, compilerBin, timedRunCompiler, cooperativeYield, startupProfile, compilerTotals, emitCompilerWarning, componentIrCache, componentDocumentModeCache, componentExpressionRewriteCache, expressionRewriteMetrics, pageOwnerExpressionRewrite, pageIr, pageIrMergeCache, seenStaticImports, pageExpressionRewriteMap, pageExpressionBindingMap, pageAmbiguousExpressionMap, knownRefKeys, componentOccurrencePlans, imagePropsLiterals, pagePhase, pageBindingResolutionBreakdown, pageMergeBreakdown, pageComponentLoopBreakdown, hoistedCodeTransformCache, pageStats }) {
138
188
  let componentInstanceCounter = 0;
189
+ const scopedOccurrenceIndexByOwnerKey = new Map();
139
190
  for (const occurrence of componentOccurrences) {
140
191
  await cooperativeYield();
141
192
  const compName = occurrence.name;
@@ -146,7 +197,9 @@ export async function runPageComponentLoop({ componentOccurrences, occurrenceCou
146
197
  const componentSourceReadStartedAt = performance.now();
147
198
  const componentSource = readFileSync(compPath, 'utf8');
148
199
  pageComponentLoopBreakdown.componentSourceReadMs += startupProfile.roundMs(performance.now() - componentSourceReadStartedAt);
149
- const occurrenceCount = occurrenceCountByPath.get(compPath) || 0;
200
+ const occurrenceCount = occurrenceCountByPath.get(compPath)
201
+ || occurrenceCountByPath.get(compName)
202
+ || 0;
150
203
  const compIr = await resolveComponentIr({
151
204
  compPath,
152
205
  componentSource,
@@ -175,6 +228,10 @@ export async function runPageComponentLoop({ componentOccurrences, occurrenceCou
175
228
  pageComponentLoopBreakdown,
176
229
  startupProfile
177
230
  });
231
+ const scopedContext = resolveScopedExpressionContext(pageIr, compPath, scopedOccurrenceIndexByOwnerKey);
232
+ const scopedExpressionRewrite = scopedContext
233
+ ? applyScopedDataContextToExpressionRewrite(expressionRewrite, scopedContext)
234
+ : expressionRewrite;
178
235
  const { attrExpressionRewrite } = await resolveOwnerRewriteContext({
179
236
  occurrence,
180
237
  sourceFile,
@@ -220,13 +277,16 @@ export async function runPageComponentLoop({ componentOccurrences, occurrenceCou
220
277
  }, seenStaticImports, knownRefKeys, pageIrMergeCache, pageMergeBreakdown, hoistedCodeTransformCache);
221
278
  pagePhase.mergeMs += startupProfile.roundMs(performance.now() - mergeStartedAt);
222
279
  if (useIsolatedInstance) {
280
+ const scopedInstanceRewrite = scopedContext
281
+ ? applyScopedDataContextToExpressionRewrite(instanceState.instanceRewrite, scopedContext)
282
+ : instanceState.instanceRewrite;
223
283
  componentOccurrencePlans.push({
224
- rewrite: instanceState.instanceRewrite,
225
- expressionSequence: instanceState.instanceRewrite.sequence,
284
+ rewrite: scopedInstanceRewrite,
285
+ expressionSequence: scopedInstanceRewrite.sequence,
226
286
  refSequence: instanceState.refIdentifierPairs
227
287
  });
228
288
  continue;
229
289
  }
230
- mergeExpressionRewriteMaps(pageExpressionRewriteMap, pageExpressionBindingMap, pageAmbiguousExpressionMap, expressionRewrite, pageIrMergeCache, pageBindingResolutionBreakdown);
290
+ mergeExpressionRewriteMaps(pageExpressionRewriteMap, pageExpressionBindingMap, pageAmbiguousExpressionMap, scopedExpressionRewrite, pageIrMergeCache, pageBindingResolutionBreakdown);
231
291
  }
232
292
  }
@@ -50,6 +50,12 @@ export function applyExpressionRewrites(pageIr, expressionMap, bindingMap, ambig
50
50
  if (rewritten && rewritten !== current && bindings[index].literal === current) {
51
51
  bindings[index].literal = rewritten;
52
52
  }
53
+ if (rewrittenBinding &&
54
+ typeof bindings[index].scoped_data_key === 'string' &&
55
+ bindings[index].scoped_data_key.length > 0 &&
56
+ !(typeof rewrittenBinding.scoped_data_key === 'string' && rewrittenBinding.scoped_data_key.length > 0)) {
57
+ continue;
58
+ }
53
59
  if (rewrittenBinding) {
54
60
  bindings[index].compiled_expr = rewrittenBinding.compiled_expr;
55
61
  bindings[index].signal_index = rewrittenBinding.signal_index;
@@ -57,6 +63,7 @@ export function applyExpressionRewrites(pageIr, expressionMap, bindingMap, ambig
57
63
  bindings[index].state_index = rewrittenBinding.state_index;
58
64
  bindings[index].component_instance = rewrittenBinding.component_instance;
59
65
  bindings[index].component_binding = rewrittenBinding.component_binding;
66
+ bindings[index].scoped_data_key = rewrittenBinding.scoped_data_key;
60
67
  }
61
68
  else if (rewritten && rewritten !== current && bindings[index].compiled_expr === current) {
62
69
  bindings[index].compiled_expr = rewritten;
@@ -169,12 +169,13 @@ export function createPageLoopExecutionState(): {
169
169
  envelopes: never[];
170
170
  };
171
171
  export function preparePageIrForMerge(pageIr: any): void;
172
- export function applyServerEnvelopeToPageIr({ pageIr, composedServer, entry, srcDir, sourceFile }: {
172
+ export function applyServerEnvelopeToPageIr({ pageIr, composedServer, entry, srcDir, sourceFile, scopedMetadata }: {
173
173
  pageIr: any;
174
174
  composedServer: any;
175
175
  entry: any;
176
176
  srcDir: any;
177
177
  sourceFile: any;
178
+ scopedMetadata: any;
178
179
  }): void;
179
180
  export function buildOccurrenceCountByPath(componentOccurrences: any): Map<any, any>;
180
181
  export function createPageBuildState(): {
@@ -1,5 +1,6 @@
1
1
  import { relative } from 'node:path';
2
2
  import { classifyPageRoute } from '../route-classification.js';
3
+ import { assertNoScopedServerBuildErrors } from '../manifest.js';
3
4
  import { createBindingResolutionTotals, createComponentLoopPhaseTotals, createMergePhaseTotals, createOccurrenceApplyPhaseTotals, createPagePhaseTotals, createScopedRewritePhaseTotals } from './page-loop-metrics.js';
4
5
  export function createPageLoopState() {
5
6
  return {
@@ -47,10 +48,12 @@ export function preparePageIrForMerge(pageIr) {
47
48
  pageIr.hoisted.state = pageIr.hoisted.state || [];
48
49
  pageIr.hoisted.code = pageIr.hoisted.code || [];
49
50
  }
50
- export function applyServerEnvelopeToPageIr({ pageIr, composedServer, entry, srcDir, sourceFile }) {
51
+ export function applyServerEnvelopeToPageIr({ pageIr, composedServer, entry, srcDir, sourceFile, scopedMetadata }) {
52
+ assertNoScopedServerBuildErrors(scopedMetadata.diagnostics, entry.file);
51
53
  const classification = classifyPageRoute({
52
54
  file: entry.file,
53
- serverScript: composedServer.serverScript
55
+ serverScript: composedServer.serverScript,
56
+ hasScopedServerData: scopedMetadata.hasScopedServerData
54
57
  });
55
58
  if (composedServer.serverScript) {
56
59
  const { has_action: _unusedHasAction, export_paths: _unusedExportPaths, ...serverScript } = composedServer.serverScript;
@@ -63,6 +66,10 @@ export function applyServerEnvelopeToPageIr({ pageIr, composedServer, entry, src
63
66
  pageIr.has_guard = classification.hasGuard;
64
67
  pageIr.has_load = classification.hasLoad;
65
68
  pageIr.has_action = classification.hasAction;
69
+ pageIr.has_scoped_server_data = scopedMetadata.hasScopedServerData;
70
+ pageIr.scoped_server_data = scopedMetadata.hasScopedServerData
71
+ ? scopedMetadata.scopedServerData
72
+ : [];
66
73
  pageIr.guard_module_ref = composedServer.guardPath ? relative(srcDir, composedServer.guardPath).replaceAll('\\', '/') : null;
67
74
  pageIr.load_module_ref = composedServer.loadPath ? relative(srcDir, composedServer.loadPath).replaceAll('\\', '/') : null;
68
75
  pageIr.action_module_ref = composedServer.actionPath ? relative(srcDir, composedServer.actionPath).replaceAll('\\', '/') : null;
@@ -14,6 +14,7 @@ import { deferComponentRuntimeBlock } from './hoisted-code-transforms.js';
14
14
  import { addBreakdown, emitPageLoopSummary, recordPageProfile } from './page-loop-metrics.js';
15
15
  import { applyServerEnvelopeToPageIr, buildOccurrenceCountByPath, createPageBuildState, createPageLoopCaches, createPageLoopExecutionState } from './page-loop-state.js';
16
16
  import { extractServerScript } from './server-script.js';
17
+ import { analyzeRouteScopedServerMetadata } from '../manifest.js';
17
18
  /**
18
19
  * @param {{
19
20
  * manifest: Array<{ path: string, file: string }>
@@ -78,12 +79,20 @@ export async function buildPageEnvelopes(input) {
78
79
  adjacentLoadPath: adjacentLoad,
79
80
  adjacentActionPath: adjacentAction
80
81
  });
82
+ const scopedMetadata = analyzeRouteScopedServerMetadata({
83
+ pageSource: rawSource,
84
+ pageFile: sourceFile,
85
+ registry,
86
+ srcDir,
87
+ compilerOpts
88
+ });
81
89
  applyServerEnvelopeToPageIr({
82
90
  pageIr,
83
91
  composedServer,
84
92
  entry,
85
93
  srcDir,
86
- sourceFile
94
+ sourceFile,
95
+ scopedMetadata
87
96
  });
88
97
  const pageIrMergeCache = createPageIrMergeCache(pageIr);
89
98
  const seenStaticImports = new Set();
@@ -0,0 +1,5 @@
1
+ export function applyScopedDataContextToExpressionRewrite(componentRewrite: any, scopedContext: any): any;
2
+ export function resolveScopedExpressionContext(pageIr: any, compPath: any, occurrenceIndexByOwnerKey: any): {
3
+ scopedDataKey: any;
4
+ serializedVariableNames: any;
5
+ } | null;
@@ -0,0 +1,133 @@
1
+ function escapeRegExp(value) {
2
+ return String(value).replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
3
+ }
4
+ function scopedDataKey(scopedContext) {
5
+ return typeof scopedContext?.scopedDataKey === 'string' && scopedContext.scopedDataKey.length > 0
6
+ ? scopedContext.scopedDataKey
7
+ : null;
8
+ }
9
+ function scopedDataExpressionForSource(expression, scopedContext) {
10
+ const match = String(expression || '').trim().match(/^([A-Za-z_$][\w$]*)([\s\S]*)$/);
11
+ if (!match)
12
+ return null;
13
+ const root = match[1];
14
+ const rest = match[2] || '';
15
+ if (root === 'data' || root === 'ssr')
16
+ return `${root}${rest}`;
17
+ for (const name of Array.isArray(scopedContext?.serializedVariableNames) ? scopedContext.serializedVariableNames : []) {
18
+ if (typeof name === 'string' &&
19
+ name.length > 0 &&
20
+ (root === name || new RegExp(`(?:^|_)${escapeRegExp(name)}(?:__inst\\d+)?$`).test(root))) {
21
+ return `data.${name}${rest}`;
22
+ }
23
+ }
24
+ return null;
25
+ }
26
+ function scopedExpressionCandidate(raw, rewritten, binding, scopedContext) {
27
+ if (!scopedDataKey(scopedContext))
28
+ return null;
29
+ for (const candidate of [rewritten, binding?.compiled_expr, binding?.literal, raw]) {
30
+ const scopedExpression = scopedDataExpressionForSource(candidate, scopedContext);
31
+ if (scopedExpression)
32
+ return scopedExpression;
33
+ }
34
+ return null;
35
+ }
36
+ function createScopedBinding(scopedExpression, scopedContext, baseBinding = null) {
37
+ return scopedExpression
38
+ ? {
39
+ ...(baseBinding && typeof baseBinding === 'object' ? baseBinding : {}),
40
+ compiled_expr: scopedExpression,
41
+ literal: scopedExpression,
42
+ signal_index: null,
43
+ signal_indices: [],
44
+ state_index: null,
45
+ component_instance: null,
46
+ component_binding: null,
47
+ scoped_data_key: scopedDataKey(scopedContext)
48
+ }
49
+ : null;
50
+ }
51
+ function applyScopedContextToBinding(binding, scopedContext, fallbackExpression = null) {
52
+ if (!binding || typeof binding !== 'object' || !scopedDataKey(scopedContext))
53
+ return binding;
54
+ const scopedExpression = scopedDataExpressionForSource(binding.compiled_expr, scopedContext)
55
+ || scopedDataExpressionForSource(binding.literal, scopedContext)
56
+ || scopedDataExpressionForSource(fallbackExpression, scopedContext);
57
+ return scopedExpression ? createScopedBinding(scopedExpression, scopedContext, binding) : binding;
58
+ }
59
+ function hasScopedBinding(binding) {
60
+ return typeof binding?.scoped_data_key === 'string' && binding.scoped_data_key.length > 0;
61
+ }
62
+ export function applyScopedDataContextToExpressionRewrite(componentRewrite, scopedContext) {
63
+ if (!componentRewrite || !scopedDataKey(scopedContext))
64
+ return componentRewrite;
65
+ const next = {
66
+ map: new Map(),
67
+ bindings: new Map(),
68
+ signals: componentRewrite.signals,
69
+ stateBindings: componentRewrite.stateBindings,
70
+ ambiguous: new Set(componentRewrite.ambiguous),
71
+ sequence: []
72
+ };
73
+ for (const item of Array.isArray(componentRewrite.sequence) ? componentRewrite.sequence : []) {
74
+ const scopedBinding = applyScopedContextToBinding(item.binding, scopedContext, item.rewritten || item.raw);
75
+ const scopedExpression = scopedExpressionCandidate(item.raw, item.rewritten, scopedBinding, scopedContext);
76
+ next.sequence.push({
77
+ raw: item.raw,
78
+ rewritten: scopedExpression || item.rewritten,
79
+ binding: hasScopedBinding(scopedBinding)
80
+ ? scopedBinding
81
+ : createScopedBinding(scopedExpression, scopedContext)
82
+ });
83
+ }
84
+ for (const [raw, binding] of componentRewrite.bindings.entries()) {
85
+ next.bindings.set(raw, applyScopedContextToBinding(binding, scopedContext, raw));
86
+ }
87
+ for (const [raw, rewritten] of componentRewrite.map.entries()) {
88
+ const binding = next.bindings.get(raw);
89
+ next.map.set(raw, scopedExpressionCandidate(raw, rewritten, binding, scopedContext) || rewritten);
90
+ }
91
+ return next;
92
+ }
93
+ function normalizeSourcePath(value) {
94
+ return String(value || '').replaceAll('\\', '/');
95
+ }
96
+ function ownerKeyMatchesPath(ownerKey, filePath) {
97
+ const key = normalizeSourcePath(ownerKey);
98
+ const file = normalizeSourcePath(filePath);
99
+ return key.length > 0 && (file === key || file.endsWith(`/${key}`));
100
+ }
101
+ export function resolveScopedExpressionContext(pageIr, compPath, occurrenceIndexByOwnerKey) {
102
+ const entries = Array.isArray(pageIr?.scoped_server_data) ? pageIr.scoped_server_data : [];
103
+ const matches = entries.filter((entry) => entry &&
104
+ typeof entry === 'object' &&
105
+ (entry.ownerKind === 'layout' || entry.ownerKind === 'component') &&
106
+ ownerKeyMatchesPath(entry.ownerKey, compPath));
107
+ const entry = matches.length === 1 ? matches[0] : null;
108
+ if (!entry || typeof entry.ownerKey !== 'string')
109
+ return null;
110
+ let scopedDataKey = null;
111
+ if (entry.ownerKind === 'layout') {
112
+ scopedDataKey = `layout:${entry.ownerKey}`;
113
+ }
114
+ else if (entry.ownerKind === 'component' && entry.instanceStrategy === 'per-instance') {
115
+ const index = occurrenceIndexByOwnerKey.get(entry.ownerKey) || 0;
116
+ occurrenceIndexByOwnerKey.set(entry.ownerKey, index + 1);
117
+ const instance = Array.isArray(entry.instances) ? entry.instances[index] : null;
118
+ scopedDataKey = typeof instance?.key === 'string' && instance.key.length > 0
119
+ ? instance.key
120
+ : `component:${entry.ownerKey}:o${index}`;
121
+ }
122
+ else if (entry.ownerKind === 'component') {
123
+ scopedDataKey = `component:${entry.ownerKey}`;
124
+ }
125
+ return scopedDataKey
126
+ ? {
127
+ scopedDataKey,
128
+ serializedVariableNames: Array.isArray(entry.serializedVariableNames)
129
+ ? entry.serializedVariableNames
130
+ : []
131
+ }
132
+ : null;
133
+ }
@@ -4,13 +4,14 @@
4
4
  */
5
5
  export function deriveProjectRootFromPagesDir(pagesDir: string): string;
6
6
  /**
7
- * @param {{ manifest: Array<{ path: string, file: string }>, pagesDir: string }} input
7
+ * @param {{ manifest: Array<{ path: string, file: string, scoped_server_data?: import('../scoped-server-data/types.js').ManifestScopedServerDataEntry[] }>, pagesDir: string }} input
8
8
  * @returns {Promise<void>}
9
9
  */
10
10
  export function ensureZenithTypeDeclarations(input: {
11
11
  manifest: Array<{
12
12
  path: string;
13
13
  file: string;
14
+ scoped_server_data?: import("../scoped-server-data/types.js").ManifestScopedServerDataEntry[];
14
15
  }>;
15
16
  pagesDir: string;
16
17
  }): Promise<void>;
@@ -2,6 +2,8 @@ import { existsSync, readFileSync, writeFileSync } from 'node:fs';
2
2
  import { mkdir } from 'node:fs/promises';
3
3
  import { basename, dirname, join, resolve } from 'node:path';
4
4
  import { renderZenithEnvDts } from '../types/zenith-env-dts.js';
5
+ const SCOPED_SERVER_TYPE_DECLARATIONS_HELPER_UNAVAILABLE = '[Zenith:ScopedServerData] Type declaration helper is unavailable. Run the CLI build step before generating scoped server data type declarations.';
6
+ let scopedTypeDeclarationRendererPromise = null;
5
7
  /**
6
8
  * @param {string} targetPath
7
9
  * @param {string} next
@@ -61,6 +63,27 @@ function renderZenithRouteDts(manifest) {
61
63
  lines.push('');
62
64
  return `${lines.join('\n')}\n`;
63
65
  }
66
+ /**
67
+ * @returns {Promise<(input: { manifest: Array<{ scoped_server_data?: import('../scoped-server-data/types.js').ManifestScopedServerDataEntry[] }>, srcDir: string }) => string>}
68
+ */
69
+ async function getScopedServerDataTypeRenderer() {
70
+ if (!scopedTypeDeclarationRendererPromise) {
71
+ scopedTypeDeclarationRendererPromise = import('../scoped-server-data/type-declarations.js')
72
+ .then((helper) => {
73
+ if (typeof helper.renderScopedServerDataDts !== 'function') {
74
+ throw new Error(SCOPED_SERVER_TYPE_DECLARATIONS_HELPER_UNAVAILABLE);
75
+ }
76
+ return helper.renderScopedServerDataDts;
77
+ })
78
+ .catch((error) => {
79
+ if (error && error.message === SCOPED_SERVER_TYPE_DECLARATIONS_HELPER_UNAVAILABLE) {
80
+ throw error;
81
+ }
82
+ throw new Error(SCOPED_SERVER_TYPE_DECLARATIONS_HELPER_UNAVAILABLE);
83
+ });
84
+ }
85
+ return scopedTypeDeclarationRendererPromise;
86
+ }
64
87
  /**
65
88
  * @param {string} pagesDir
66
89
  * @returns {string}
@@ -74,17 +97,24 @@ export function deriveProjectRootFromPagesDir(pagesDir) {
74
97
  return parent;
75
98
  }
76
99
  /**
77
- * @param {{ manifest: Array<{ path: string, file: string }>, pagesDir: string }} input
100
+ * @param {{ manifest: Array<{ path: string, file: string, scoped_server_data?: import('../scoped-server-data/types.js').ManifestScopedServerDataEntry[] }>, pagesDir: string }} input
78
101
  * @returns {Promise<void>}
79
102
  */
80
103
  export async function ensureZenithTypeDeclarations(input) {
81
104
  const projectRoot = deriveProjectRootFromPagesDir(input.pagesDir);
105
+ const srcDir = dirname(resolve(input.pagesDir));
82
106
  const zenithDir = resolve(projectRoot, '.zenith');
83
107
  await mkdir(zenithDir, { recursive: true });
84
108
  const envPath = join(zenithDir, 'zenith-env.d.ts');
85
109
  const routesPath = join(zenithDir, 'zenith-routes.d.ts');
110
+ const scopedPath = join(zenithDir, 'zenith-scoped-server-data.d.ts');
111
+ const renderScopedServerDataDts = await getScopedServerDataTypeRenderer();
86
112
  writeIfChanged(envPath, renderZenithEnvDts());
87
113
  writeIfChanged(routesPath, renderZenithRouteDts(input.manifest));
114
+ writeIfChanged(scopedPath, renderScopedServerDataDts({
115
+ manifest: input.manifest,
116
+ srcDir
117
+ }));
88
118
  const tsconfigPath = resolve(projectRoot, 'tsconfig.json');
89
119
  if (!existsSync(tsconfigPath)) {
90
120
  return;
@@ -1,15 +1,11 @@
1
- export function writeBuildOutputManifest({ coreOutputDir, staticDir, target, routeManifest, basePath }: {
1
+ export function writeBuildOutputManifest({ coreOutputDir, staticDir, target, routeManifest, basePath, globalMiddleware }: {
2
2
  coreOutputDir: any;
3
3
  staticDir: any;
4
4
  target: any;
5
5
  routeManifest: any;
6
6
  basePath?: string | undefined;
7
+ globalMiddleware?: null | undefined;
7
8
  }): Promise<{
8
- schema_version: number;
9
- zenith_version: any;
10
- target: any;
11
- base_path: string;
12
- content_hash: any;
13
9
  routes: {
14
10
  html: any;
15
11
  assets: any[];
@@ -26,4 +22,12 @@ export function writeBuildOutputManifest({ coreOutputDir, staticDir, target, rou
26
22
  css: any[];
27
23
  vendor: any;
28
24
  };
25
+ global_middleware?: {
26
+ source_file: any;
27
+ } | undefined;
28
+ schema_version: number;
29
+ zenith_version: any;
30
+ target: any;
31
+ base_path: string;
32
+ content_hash: any;
29
33
  }>;
@@ -1,6 +1,7 @@
1
1
  import { readFileSync } from 'node:fs';
2
2
  import { readFile, writeFile } from 'node:fs/promises';
3
3
  import { join } from 'node:path';
4
+ import { normalizeGlobalMiddlewareMetadata } from './global-middleware.js';
4
5
  const CLI_VERSION = (() => {
5
6
  try {
6
7
  const pkg = JSON.parse(readFileSync(new URL('../package.json', import.meta.url), 'utf8'));
@@ -39,7 +40,7 @@ async function readRouteHtml(staticDir, htmlPath) {
39
40
  return '';
40
41
  }
41
42
  }
42
- export async function writeBuildOutputManifest({ coreOutputDir, staticDir, target, routeManifest, basePath = '/' }) {
43
+ export async function writeBuildOutputManifest({ coreOutputDir, staticDir, target, routeManifest, basePath = '/', globalMiddleware = null }) {
43
44
  const bundlerManifest = await readJson(join(staticDir, 'manifest.json'), {});
44
45
  const routerManifest = await readJson(join(staticDir, 'assets', 'router-manifest.json'), { routes: [] });
45
46
  const routeByPath = new Map((Array.isArray(routerManifest.routes) ? routerManifest.routes : []).map((entry) => [entry.path, entry]));
@@ -85,12 +86,14 @@ export async function writeBuildOutputManifest({ coreOutputDir, staticDir, targe
85
86
  bundlerManifest.css,
86
87
  ...routeAssetCss
87
88
  ].filter((value) => typeof value === 'string' && value.endsWith('.css')));
89
+ const globalMiddlewareMetadata = normalizeGlobalMiddlewareMetadata(globalMiddleware);
88
90
  const buildManifest = {
89
91
  schema_version: 1,
90
92
  zenith_version: CLI_VERSION,
91
93
  target,
92
94
  base_path: basePath,
93
95
  content_hash: typeof bundlerManifest.hash === 'string' ? bundlerManifest.hash : '',
96
+ ...(globalMiddlewareMetadata ? { global_middleware: globalMiddlewareMetadata } : {}),
94
97
  routes,
95
98
  assets: {
96
99
  js: [...jsAssets].sort(),
package/dist/build.js CHANGED
@@ -16,6 +16,7 @@ import { createImageRuntimePayload, injectImageRuntimePayloadIntoHtmlFiles } fro
16
16
  import { supportsTargetRouteCheck } from './route-check-support.js';
17
17
  import { createStartupProfiler } from './startup-profile.js';
18
18
  import { writeServerOutput } from './server-output.js';
19
+ import { resolveGlobalMiddleware } from './global-middleware.js';
19
20
  import { resolveBundlerBin } from './toolchain-paths.js';
20
21
  import { createBundlerToolchain, createCompilerToolchain, ensureToolchainCompatibility, getActiveToolchainCandidate } from './toolchain-runner.js';
21
22
  import { maybeWarnAboutZenithVersionMismatch } from './version-check.js';
@@ -75,6 +76,7 @@ export async function build(options) {
75
76
  }));
76
77
  }
77
78
  const registry = startupProfile.measureSync('build_component_registry', () => buildComponentRegistry(srcDir));
79
+ const globalMiddleware = await startupProfile.measureAsync('resolve_global_middleware', () => resolveGlobalMiddleware({ projectRoot, pagesDir, target }));
78
80
  const manifest = await startupProfile.measureAsync('generate_manifest', () => generateManifest(pagesDir, '.zen', { compilerOpts }));
79
81
  const pageManifest = manifest.filter((entry) => entry?.route_kind !== 'resource');
80
82
  if (mode !== 'legacy') {
@@ -136,14 +138,21 @@ export async function build(options) {
136
138
  staticDir: staticOutputDir,
137
139
  target,
138
140
  routeManifest: pageManifest,
139
- basePath
141
+ basePath,
142
+ globalMiddleware: globalMiddleware?.metadata || null
140
143
  }));
141
144
  await startupProfile.measureAsync('write_server_output', () => writeServerOutput({
142
145
  coreOutputDir,
143
146
  staticDir: staticOutputDir,
144
147
  projectRoot,
145
148
  config,
146
- basePath
149
+ basePath,
150
+ globalMiddleware: globalMiddleware?.metadata || null,
151
+ pageManifest,
152
+ pagesDir,
153
+ srcDir,
154
+ registry,
155
+ compilerOpts
147
156
  }));
148
157
  await startupProfile.measureAsync('adapt_output', () => adapter.adapt({ coreOutput: coreOutputDir, outDir, manifest: buildManifest, config }));
149
158
  const assets = await startupProfile.measureAsync('collect_assets', () => collectAssets(outDir));
@@ -388,6 +388,7 @@ export function applyOccurrenceRewritePlans(pageIr, occurrencePlans, resolveBind
388
388
  binding.state_index = resolved.state_index;
389
389
  binding.component_instance = resolved.component_instance;
390
390
  binding.component_binding = resolved.component_binding;
391
+ binding.scoped_data_key = resolved.scoped_data_key;
391
392
  }
392
393
  }
393
394
  exprCursor = found + 1;
@@ -4,3 +4,12 @@ export function collectExpandedComponentOccurrences(source: any, registry: any,
4
4
  ownerPath: string;
5
5
  componentPath: string;
6
6
  }[];
7
+ /**
8
+ * Unique layout/component `.zen` paths reachable from a page dependency graph.
9
+ *
10
+ * @param {string} source
11
+ * @param {Map<string, string>} registry
12
+ * @param {string} sourceFile
13
+ * @returns {string[]}
14
+ */
15
+ export function collectReachableOwnerPaths(source: string, registry: Map<string, string>, sourceFile: string): string[];
@@ -10,6 +10,24 @@ export function collectExpandedComponentOccurrences(source, registry, sourceFile
10
10
  }, [], occurrences);
11
11
  return occurrences;
12
12
  }
13
+ /**
14
+ * Unique layout/component `.zen` paths reachable from a page dependency graph.
15
+ *
16
+ * @param {string} source
17
+ * @param {Map<string, string>} registry
18
+ * @param {string} sourceFile
19
+ * @returns {string[]}
20
+ */
21
+ export function collectReachableOwnerPaths(source, registry, sourceFile) {
22
+ const occurrences = collectExpandedComponentOccurrences(source, registry, sourceFile);
23
+ const paths = new Set();
24
+ for (const occurrence of occurrences) {
25
+ if (occurrence.componentPath) {
26
+ paths.add(occurrence.componentPath);
27
+ }
28
+ }
29
+ return [...paths];
30
+ }
13
31
  function walkSource(source, registry, context, chain, occurrences) {
14
32
  let cursor = 0;
15
33
  while (cursor < source.length) {
@@ -0,0 +1,12 @@
1
+ export function normalizePlugins(value: any): ({
2
+ name: any;
3
+ config: any;
4
+ } | {
5
+ name: any;
6
+ config?: undefined;
7
+ })[];
8
+ export function assertPluginConfigPatch(value: any): void;
9
+ export function cloneConfigValue(value: any, seen?: Map<any, any>): any;
10
+ export function deepFreeze(value: any, seen?: Set<any>): any;
11
+ export function pluginHookError(pluginName: any, hookName: any, error: any): Error;
12
+ export const PLUGIN_CONFIG_PATCH_KEYS: Set<string>;