@zenithbuild/cli 0.7.2 → 0.7.4

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 (54) hide show
  1. package/README.md +14 -11
  2. package/dist/adapters/adapter-netlify.js +1 -0
  3. package/dist/adapters/adapter-node.js +8 -0
  4. package/dist/adapters/adapter-vercel.js +1 -0
  5. package/dist/build/compiler-runtime.d.ts +10 -9
  6. package/dist/build/compiler-runtime.js +51 -1
  7. package/dist/build/compiler-signal-expression.d.ts +1 -0
  8. package/dist/build/compiler-signal-expression.js +155 -0
  9. package/dist/build/expression-rewrites.d.ts +1 -6
  10. package/dist/build/expression-rewrites.js +61 -65
  11. package/dist/build/page-component-loop.d.ts +3 -13
  12. package/dist/build/page-component-loop.js +21 -46
  13. package/dist/build/page-ir-normalization.d.ts +0 -8
  14. package/dist/build/page-ir-normalization.js +13 -234
  15. package/dist/build/page-loop-state.d.ts +6 -9
  16. package/dist/build/page-loop-state.js +9 -8
  17. package/dist/build/page-loop.js +27 -22
  18. package/dist/build/scoped-identifier-rewrite.d.ts +37 -44
  19. package/dist/build/scoped-identifier-rewrite.js +28 -128
  20. package/dist/build/server-script.d.ts +2 -1
  21. package/dist/build/server-script.js +29 -3
  22. package/dist/build.js +5 -3
  23. package/dist/component-instance-ir.js +158 -52
  24. package/dist/dev-build-session.js +20 -6
  25. package/dist/dev-server.js +82 -39
  26. package/dist/framework-components/Image.zen +1 -1
  27. package/dist/images/materialization-plan.d.ts +1 -0
  28. package/dist/images/materialization-plan.js +6 -0
  29. package/dist/images/materialize.d.ts +5 -3
  30. package/dist/images/materialize.js +24 -109
  31. package/dist/images/router-manifest.d.ts +1 -0
  32. package/dist/images/router-manifest.js +49 -0
  33. package/dist/index.js +8 -2
  34. package/dist/manifest.js +3 -2
  35. package/dist/preview.d.ts +4 -3
  36. package/dist/preview.js +87 -53
  37. package/dist/request-body.d.ts +2 -0
  38. package/dist/request-body.js +13 -0
  39. package/dist/request-origin.d.ts +2 -0
  40. package/dist/request-origin.js +45 -0
  41. package/dist/route-check-support.d.ts +1 -0
  42. package/dist/route-check-support.js +4 -0
  43. package/dist/server-contract.d.ts +15 -0
  44. package/dist/server-contract.js +102 -32
  45. package/dist/server-error.d.ts +4 -0
  46. package/dist/server-error.js +34 -0
  47. package/dist/server-output.d.ts +2 -0
  48. package/dist/server-output.js +13 -0
  49. package/dist/server-runtime/node-server.js +33 -27
  50. package/dist/server-runtime/route-render.d.ts +3 -3
  51. package/dist/server-runtime/route-render.js +20 -31
  52. package/dist/server-script-composition.d.ts +11 -5
  53. package/dist/server-script-composition.js +25 -10
  54. package/package.json +6 -2
@@ -5,14 +5,14 @@ import { applyOccurrenceRewritePlans } from '../component-instance-ir.js';
5
5
  import { collectExpandedComponentOccurrences } from '../component-occurrences.js';
6
6
  import { expandComponents } from '../resolve-components.js';
7
7
  import { composeServerScriptEnvelope, resolveAdjacentServerModules } from '../server-script-composition.js';
8
- import { createTimedCompilerRunner } from './compiler-runtime.js';
8
+ import { createTimedCompilerRunner, mergePageImageMaterialization } from './compiler-runtime.js';
9
9
  import { buildComponentExpressionRewrite, mergeExpressionRewriteMaps, resolveRewrittenBindingMetadata } from './expression-rewrites.js';
10
10
  import { createPageIrMergeCache } from './merge-component-ir.js';
11
11
  import { buildPageOwnerContext, runPageComponentLoop } from './page-component-loop.js';
12
- import { applyExpressionRewrites, applyScopedIdentifierRewrites, normalizeExpressionBindingDependencies, normalizeExpressionPayload, normalizeHoistedSourcePayload, rewriteLegacyMarkupIdentifiers, rewriteRefBindingIdentifiers, synthesizeSignalBackedCompiledExpressions } from './page-ir-normalization.js';
12
+ import { applyExpressionRewrites, normalizeExpressionPayload, normalizeHoistedSourcePayload, rewriteLegacyMarkupIdentifiers, rewriteRefBindingIdentifiers } from './page-ir-normalization.js';
13
+ import { deferComponentRuntimeBlock } from './hoisted-code-transforms.js';
13
14
  import { addBreakdown, emitPageLoopSummary, recordPageProfile } from './page-loop-metrics.js';
14
15
  import { applyServerEnvelopeToPageIr, buildOccurrenceCountByPath, createPageBuildState, createPageLoopCaches, createPageLoopExecutionState } from './page-loop-state.js';
15
- import { buildScopedIdentifierRewrite } from './scoped-identifier-rewrite.js';
16
16
  import { extractServerScript } from './server-script.js';
17
17
  /**
18
18
  * @param {{
@@ -33,7 +33,7 @@ export async function buildPageEnvelopes(input) {
33
33
  const { manifest, pagesDir, srcDir, registry, compilerOpts, compilerBin, routerEnabled, startupProfile, compilerTotals, emitCompilerWarning } = input;
34
34
  const cacheState = input.pageLoopCaches || createPageLoopCaches();
35
35
  const executionState = createPageLoopExecutionState();
36
- const { componentIrCache, componentDocumentModeCache, componentExpressionRewriteCache, templateExpressionCache, hoistedCodeTransformCache } = cacheState;
36
+ const { componentIrCache, componentDocumentModeCache, componentExpressionRewriteCache, hoistedCodeTransformCache } = cacheState;
37
37
  const { expressionRewriteMetrics, pagePhaseTotals, occurrenceApplyPhaseTotals, bindingResolutionTotals, scopedRewritePhaseTotals, mergePhaseTotals, componentLoopPhaseTotals, pageProfiles, envelopes } = executionState;
38
38
  async function cooperativeYield() {
39
39
  await new Promise((resolve) => setImmediate(resolve));
@@ -52,7 +52,7 @@ export async function buildPageEnvelopes(input) {
52
52
  const pageOwnerExtractStartedAt = performance.now();
53
53
  const pageOwnerSource = extractServerScript(rawSource, sourceFile, compilerOpts).source;
54
54
  pagePhase.serverExtractMs += startupProfile.roundMs(performance.now() - pageOwnerExtractStartedAt);
55
- const { guardPath: adjacentGuard, loadPath: adjacentLoad } = resolveAdjacentServerModules(sourceFile);
55
+ const { guardPath: adjacentGuard, loadPath: adjacentLoad, actionPath: adjacentAction } = resolveAdjacentServerModules(sourceFile);
56
56
  const expandedStartedAt = performance.now();
57
57
  const { expandedSource } = expandComponents(rawSource, registry, sourceFile);
58
58
  pageExpandMs = startupProfile.roundMs(performance.now() - expandedStartedAt);
@@ -62,21 +62,24 @@ export async function buildPageEnvelopes(input) {
62
62
  const compileSource = extractedServer.source;
63
63
  await cooperativeYield();
64
64
  const pageCompileStartedAt = performance.now();
65
- const pageIr = timedRunCompiler('page', sourceFile, compileSource, compilerOpts, { compilerToolchain: compilerBin, onWarning: emitCompilerWarning });
65
+ let pageIr = timedRunCompiler('page', sourceFile, compileSource, compilerOpts, { compilerToolchain: compilerBin, onWarning: emitCompilerWarning });
66
66
  pageCompileMs = startupProfile.roundMs(performance.now() - pageCompileStartedAt);
67
67
  const composedServer = composeServerScriptEnvelope({
68
68
  sourceFile,
69
69
  inlineServerScript: extractedServer.serverScript,
70
70
  adjacentGuardPath: adjacentGuard,
71
- adjacentLoadPath: adjacentLoad
71
+ adjacentLoadPath: adjacentLoad,
72
+ adjacentActionPath: adjacentAction
72
73
  });
73
74
  const hasGuard = composedServer.serverScript?.has_guard === true;
74
75
  const hasLoad = composedServer.serverScript?.has_load === true;
76
+ const hasAction = composedServer.serverScript?.has_action === true;
75
77
  applyServerEnvelopeToPageIr({
76
78
  pageIr,
77
79
  composedServer,
78
80
  hasGuard,
79
81
  hasLoad,
82
+ hasAction,
80
83
  entry,
81
84
  srcDir,
82
85
  sourceFile
@@ -89,7 +92,8 @@ export async function buildPageEnvelopes(input) {
89
92
  const pageAmbiguousExpressionMap = new Set();
90
93
  const knownRefKeys = new Set();
91
94
  const componentOccurrencePlans = [];
92
- const { pageOwnerCompileMs: resolvedPageOwnerCompileMs, pageOwnerExpressionRewrite, pageOwnerScopeRewrite } = await buildPageOwnerContext({
95
+ const imagePropsLiterals = [];
96
+ const { pageOwnerCompileMs: resolvedPageOwnerCompileMs, pageOwnerExpressionRewrite } = await buildPageOwnerContext({
93
97
  componentOccurrences,
94
98
  sourceFile,
95
99
  pageOwnerSource,
@@ -97,13 +101,12 @@ export async function buildPageEnvelopes(input) {
97
101
  compilerBin,
98
102
  timedRunCompiler,
99
103
  cooperativeYield,
100
- templateExpressionCache,
101
104
  expressionRewriteMetrics,
102
105
  startupProfile
103
106
  });
104
107
  pageOwnerCompileMs = resolvedPageOwnerCompileMs;
105
108
  const pageSelfRewriteStartedAt = performance.now();
106
- const pageSelfExpressionRewrite = buildComponentExpressionRewrite(sourceFile, compileSource, pageIr, compilerOpts, compilerBin, templateExpressionCache, expressionRewriteMetrics);
109
+ const pageSelfExpressionRewrite = buildComponentExpressionRewrite(pageIr, expressionRewriteMetrics);
107
110
  pagePhase.selfRewriteMs = startupProfile.roundMs(performance.now() - pageSelfRewriteStartedAt);
108
111
  mergeExpressionRewriteMaps(pageExpressionRewriteMap, pageExpressionBindingMap, pageAmbiguousExpressionMap, pageSelfExpressionRewrite, pageIrMergeCache, pageBindingResolutionBreakdown);
109
112
  const componentLoopStartedAt = performance.now();
@@ -127,10 +130,8 @@ export async function buildPageEnvelopes(input) {
127
130
  componentIrCache,
128
131
  componentDocumentModeCache,
129
132
  componentExpressionRewriteCache,
130
- templateExpressionCache,
131
133
  expressionRewriteMetrics,
132
134
  pageOwnerExpressionRewrite,
133
- pageOwnerScopeRewrite,
134
135
  pageIr,
135
136
  pageIrMergeCache,
136
137
  seenStaticImports,
@@ -139,6 +140,7 @@ export async function buildPageEnvelopes(input) {
139
140
  pageAmbiguousExpressionMap,
140
141
  knownRefKeys,
141
142
  componentOccurrencePlans,
143
+ imagePropsLiterals,
142
144
  pagePhase,
143
145
  pageBindingResolutionBreakdown,
144
146
  pageMergeBreakdown,
@@ -150,25 +152,25 @@ export async function buildPageEnvelopes(input) {
150
152
  pageComponentCacheHits = pageStats.pageComponentCacheHits;
151
153
  pageComponentCacheMisses = pageStats.pageComponentCacheMisses;
152
154
  pagePhase.componentLoopMs = startupProfile.roundMs(performance.now() - componentLoopStartedAt);
155
+ if (imagePropsLiterals.length > 0) {
156
+ pageIr = mergePageImageMaterialization(pageIr, imagePropsLiterals, {
157
+ compilerToolchain: compilerBin
158
+ });
159
+ }
153
160
  const occurrencePlanApplyStartedAt = performance.now();
154
161
  applyOccurrenceRewritePlans(pageIr, componentOccurrencePlans, (rewrite, binding) => resolveRewrittenBindingMetadata(pageIrMergeCache, rewrite, binding, pageBindingResolutionBreakdown), pageOccurrenceApplyBreakdown);
155
162
  pagePhase.occurrencePlanApplyMs = startupProfile.roundMs(performance.now() - occurrencePlanApplyStartedAt);
156
163
  const expressionApplyStartedAt = performance.now();
157
164
  applyExpressionRewrites(pageIr, pageExpressionRewriteMap, pageExpressionBindingMap, pageAmbiguousExpressionMap);
158
165
  pagePhase.expressionApplyMs = startupProfile.roundMs(performance.now() - expressionApplyStartedAt);
159
- const scopedRewriteStartedAt = performance.now();
160
- const scopedRewritePlanStartedAt = performance.now();
161
- const scopedRewritePlan = buildScopedIdentifierRewrite(pageIr);
162
- pagePhase.scopedRewritePlanMs = startupProfile.roundMs(performance.now() - scopedRewritePlanStartedAt);
163
- const scopedRewriteApplyStartedAt = performance.now();
164
- applyScopedIdentifierRewrites(pageIr, scopedRewritePlan, pageScopedRewriteBreakdown);
165
- pagePhase.scopedRewriteApplyMs = startupProfile.roundMs(performance.now() - scopedRewriteApplyStartedAt);
166
- pagePhase.scopedRewriteMs = startupProfile.roundMs(performance.now() - scopedRewriteStartedAt);
167
166
  const normalizeStartedAt = performance.now();
168
- synthesizeSignalBackedCompiledExpressions(pageIr);
169
167
  normalizeExpressionPayload(pageIr);
170
168
  normalizeHoistedSourcePayload(pageIr);
171
- normalizeExpressionBindingDependencies(pageIr);
169
+ if (Array.isArray(pageIr?.hoisted?.code) && pageIr.hoisted.code.length > 0) {
170
+ pageIr.hoisted.code = pageIr.hoisted.code
171
+ .map((entry) => deferComponentRuntimeBlock(entry, hoistedCodeTransformCache, expressionRewriteMetrics))
172
+ .filter((entry) => typeof entry === 'string' && entry.trim().length > 0);
173
+ }
172
174
  rewriteLegacyMarkupIdentifiers(pageIr);
173
175
  rewriteRefBindingIdentifiers(pageIr, knownRefKeys);
174
176
  pagePhase.normalizeMs = startupProfile.roundMs(performance.now() - normalizeStartedAt);
@@ -182,6 +184,9 @@ export async function buildPageEnvelopes(input) {
182
184
  route: entry.path,
183
185
  file: sourceFile,
184
186
  ir: pageIr,
187
+ image_materialization: Array.isArray(pageIr.image_materialization)
188
+ ? pageIr.image_materialization
189
+ : [],
185
190
  router: routerEnabled
186
191
  });
187
192
  recordPageProfile({
@@ -1,29 +1,3 @@
1
- /**
2
- * @param {object | null | undefined} ir
3
- * @returns {{ map: Map<string, string>, ambiguous: Set<string> }}
4
- */
5
- export function buildScopedIdentifierRewrite(ir: object | null | undefined): {
6
- map: Map<string, string>;
7
- ambiguous: Set<string>;
8
- };
9
- /**
10
- * @param {string} expr
11
- * @param {{
12
- * expressionRewrite?: { map?: Map<string, string>, ambiguous?: Set<string> } | null,
13
- * scopeRewrite?: { map?: Map<string, string>, ambiguous?: Set<string> } | null
14
- * } | null} rewriteContext
15
- * @returns {string}
16
- */
17
- export function rewritePropsExpression(expr: string, rewriteContext?: {
18
- expressionRewrite?: {
19
- map?: Map<string, string>;
20
- ambiguous?: Set<string>;
21
- } | null;
22
- scopeRewrite?: {
23
- map?: Map<string, string>;
24
- ambiguous?: Set<string>;
25
- } | null;
26
- } | null): string;
27
1
  /**
28
2
  * @param {string | null | undefined} compiledExpr
29
3
  * @param {{
@@ -41,6 +15,10 @@ export function resolveCompiledPropsExpression(compiledExpr: string | null | und
41
15
  }>;
42
16
  } | null | undefined): string | null;
43
17
  /**
18
+ * The only allowed downstream props rewrite boundary is compiler-owned exact lookup.
19
+ * If the compiler did not emit a mapping for the attr expression, CLI keeps the
20
+ * original expression text without attempting identifier reinterpretation.
21
+ *
44
22
  * @param {string} expr
45
23
  * @param {{
46
24
  * expressionRewrite?: {
@@ -49,8 +27,7 @@ export function resolveCompiledPropsExpression(compiledExpr: string | null | und
49
27
  * ambiguous?: Set<string>,
50
28
  * signals?: Array<{ state_index?: number }>,
51
29
  * stateBindings?: Array<{ key?: string }>
52
- * } | null,
53
- * scopeRewrite?: { map?: Map<string, string>, ambiguous?: Set<string> } | null
30
+ * } | null
54
31
  * } | null} rewriteContext
55
32
  * @returns {string}
56
33
  */
@@ -68,45 +45,61 @@ export function resolvePropsValueCode(expr: string, rewriteContext?: {
68
45
  key?: string;
69
46
  }>;
70
47
  } | null;
71
- scopeRewrite?: {
72
- map?: Map<string, string>;
73
- ambiguous?: Set<string>;
74
- } | null;
75
48
  } | null): string;
76
49
  /**
77
50
  * @param {string} attrs
78
51
  * @param {{
79
- * expressionRewrite?: { map?: Map<string, string>, ambiguous?: Set<string> } | null,
80
- * scopeRewrite?: { map?: Map<string, string>, ambiguous?: Set<string> } | null
52
+ * expressionRewrite?: {
53
+ * map?: Map<string, string>,
54
+ * bindings?: Map<string, { compiled_expr?: string | null }>,
55
+ * ambiguous?: Set<string>,
56
+ * signals?: Array<{ state_index?: number }>,
57
+ * stateBindings?: Array<{ key?: string }>
58
+ * } | null
81
59
  * } | null} rewriteContext
82
60
  * @returns {string}
83
61
  */
84
62
  export function renderPropsLiteralFromAttrs(attrs: string, rewriteContext?: {
85
63
  expressionRewrite?: {
86
64
  map?: Map<string, string>;
65
+ bindings?: Map<string, {
66
+ compiled_expr?: string | null;
67
+ }>;
87
68
  ambiguous?: Set<string>;
88
- } | null;
89
- scopeRewrite?: {
90
- map?: Map<string, string>;
91
- ambiguous?: Set<string>;
69
+ signals?: Array<{
70
+ state_index?: number;
71
+ }>;
72
+ stateBindings?: Array<{
73
+ key?: string;
74
+ }>;
92
75
  } | null;
93
76
  } | null): string;
94
77
  /**
95
78
  * @param {string} source
96
79
  * @param {string} attrs
97
80
  * @param {{
98
- * expressionRewrite?: { map?: Map<string, string>, ambiguous?: Set<string> } | null,
99
- * scopeRewrite?: { map?: Map<string, string>, ambiguous?: Set<string> } | null
81
+ * expressionRewrite?: {
82
+ * map?: Map<string, string>,
83
+ * bindings?: Map<string, { compiled_expr?: string | null }>,
84
+ * ambiguous?: Set<string>,
85
+ * signals?: Array<{ state_index?: number }>,
86
+ * stateBindings?: Array<{ key?: string }>
87
+ * } | null
100
88
  * } | null} rewriteContext
101
89
  * @returns {string}
102
90
  */
103
91
  export function injectPropsPrelude(source: string, attrs: string, rewriteContext?: {
104
92
  expressionRewrite?: {
105
93
  map?: Map<string, string>;
94
+ bindings?: Map<string, {
95
+ compiled_expr?: string | null;
96
+ }>;
106
97
  ambiguous?: Set<string>;
107
- } | null;
108
- scopeRewrite?: {
109
- map?: Map<string, string>;
110
- ambiguous?: Set<string>;
98
+ signals?: Array<{
99
+ state_index?: number;
100
+ }>;
101
+ stateBindings?: Array<{
102
+ key?: string;
103
+ }>;
111
104
  } | null;
112
105
  } | null): string;
@@ -1,116 +1,5 @@
1
- import { extractDeclaredIdentifiers, normalizeTypeScriptExpression, renderObjectKey, rewriteIdentifiersWithinExpression } from './typescript-expression-utils.js';
2
- /**
3
- * @param {string} value
4
- * @returns {string | null}
5
- */
6
- function deriveScopedIdentifierAlias(value) {
7
- const ident = String(value || '').trim();
8
- if (!/^[A-Za-z_$][A-Za-z0-9_$]*$/.test(ident)) {
9
- return null;
10
- }
11
- const parts = ident.split('_').filter(Boolean);
12
- const candidate = parts.length > 1 ? parts[parts.length - 1] : ident;
13
- return /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(candidate) ? candidate : ident;
14
- }
15
- /**
16
- * @param {Map<string, string>} map
17
- * @param {Set<string>} ambiguous
18
- * @param {string | null} raw
19
- * @param {string | null} rewritten
20
- */
21
- function recordScopedIdentifierRewrite(map, ambiguous, raw, rewritten) {
22
- if (typeof raw !== 'string' || raw.length === 0 || typeof rewritten !== 'string' || rewritten.length === 0) {
23
- return;
24
- }
25
- const existing = map.get(raw);
26
- if (existing && existing !== rewritten) {
27
- map.delete(raw);
28
- ambiguous.add(raw);
29
- return;
30
- }
31
- if (!ambiguous.has(raw)) {
32
- map.set(raw, rewritten);
33
- }
34
- }
35
- /**
36
- * @param {object | null | undefined} ir
37
- * @returns {{ map: Map<string, string>, ambiguous: Set<string> }}
38
- */
39
- export function buildScopedIdentifierRewrite(ir) {
40
- const out = { map: new Map(), ambiguous: new Set() };
41
- if (!ir || typeof ir !== 'object') {
42
- return out;
43
- }
44
- const stateBindings = Array.isArray(ir?.hoisted?.state) ? ir.hoisted.state : [];
45
- for (const stateEntry of stateBindings) {
46
- const key = typeof stateEntry?.key === 'string' ? stateEntry.key : null;
47
- recordScopedIdentifierRewrite(out.map, out.ambiguous, deriveScopedIdentifierAlias(key), key);
48
- }
49
- const functionBindings = Array.isArray(ir?.hoisted?.functions) ? ir.hoisted.functions : [];
50
- for (const fnName of functionBindings) {
51
- if (typeof fnName !== 'string') {
52
- continue;
53
- }
54
- recordScopedIdentifierRewrite(out.map, out.ambiguous, deriveScopedIdentifierAlias(fnName), fnName);
55
- }
56
- const declarations = Array.isArray(ir?.hoisted?.declarations) ? ir.hoisted.declarations : [];
57
- for (const declaration of declarations) {
58
- if (typeof declaration !== 'string') {
59
- continue;
60
- }
61
- for (const identifier of extractDeclaredIdentifiers(declaration)) {
62
- recordScopedIdentifierRewrite(out.map, out.ambiguous, deriveScopedIdentifierAlias(identifier), identifier);
63
- }
64
- }
65
- return out;
66
- }
67
- /**
68
- * @param {string} expr
69
- * @param {{
70
- * expressionRewrite?: { map?: Map<string, string>, ambiguous?: Set<string> } | null,
71
- * scopeRewrite?: { map?: Map<string, string>, ambiguous?: Set<string> } | null
72
- * } | null} rewriteContext
73
- * @returns {string}
74
- */
75
- export function rewritePropsExpression(expr, rewriteContext = null) {
76
- const trimmed = String(expr || '').trim();
77
- if (!trimmed) {
78
- return trimmed;
79
- }
80
- const expressionMap = rewriteContext?.expressionRewrite?.map;
81
- const expressionAmbiguous = rewriteContext?.expressionRewrite?.ambiguous;
82
- if (expressionMap instanceof Map &&
83
- !(expressionAmbiguous instanceof Set && expressionAmbiguous.has(trimmed))) {
84
- const exact = expressionMap.get(trimmed);
85
- if (typeof exact === 'string' && exact.length > 0) {
86
- return normalizeTypeScriptExpression(exact);
87
- }
88
- }
89
- const scopeMap = rewriteContext?.scopeRewrite?.map;
90
- const scopeAmbiguous = rewriteContext?.scopeRewrite?.ambiguous;
91
- const rootMatch = trimmed.match(/^([A-Za-z_$][A-Za-z0-9_$]*)([\s\S]*)$/);
92
- if (!(scopeMap instanceof Map)) {
93
- return normalizeTypeScriptExpression(trimmed);
94
- }
95
- if (!rootMatch) {
96
- return rewriteIdentifiersWithinExpression(trimmed, scopeMap, scopeAmbiguous);
97
- }
98
- const root = rootMatch[1];
99
- if (scopeAmbiguous instanceof Set && scopeAmbiguous.has(root)) {
100
- return rewriteIdentifiersWithinExpression(trimmed, scopeMap, scopeAmbiguous);
101
- }
102
- const rewrittenRoot = scopeMap.get(root);
103
- if (typeof rewrittenRoot !== 'string' || rewrittenRoot.length === 0 || rewrittenRoot === root) {
104
- return rewriteIdentifiersWithinExpression(trimmed, scopeMap, scopeAmbiguous);
105
- }
106
- if (rootMatch[2].trim().length === 0) {
107
- return normalizeTypeScriptExpression(rewrittenRoot);
108
- }
109
- const rewrittenExpr = rewriteIdentifiersWithinExpression(trimmed, scopeMap, scopeAmbiguous);
110
- return typeof rewrittenExpr === 'string' && rewrittenExpr.length > 0
111
- ? rewrittenExpr
112
- : normalizeTypeScriptExpression(`${rewrittenRoot}${rootMatch[2]}`);
113
- }
1
+ import { normalizeTypeScriptExpression, renderObjectKey } from './typescript-expression-utils.js';
2
+ import { rewriteCompilerSignalMapReferences } from './compiler-signal-expression.js';
114
3
  /**
115
4
  * @param {string | null | undefined} compiledExpr
116
5
  * @param {{
@@ -126,22 +15,24 @@ export function resolveCompiledPropsExpression(compiledExpr, expressionRewrite =
126
15
  }
127
16
  const signals = Array.isArray(expressionRewrite?.signals) ? expressionRewrite.signals : [];
128
17
  const stateBindings = Array.isArray(expressionRewrite?.stateBindings) ? expressionRewrite.stateBindings : [];
129
- const resolved = source.replace(/signalMap\.get\((\d+)\)(?:\.get\(\))?/g, (full, rawIndex) => {
130
- const signalIndex = Number.parseInt(rawIndex, 10);
131
- if (!Number.isInteger(signalIndex)) {
132
- return full;
133
- }
18
+ return rewriteCompilerSignalMapReferences(source, ({ ts, signalIndex, valueRead }) => {
134
19
  const signal = signals[signalIndex];
135
20
  const stateIndex = signal?.state_index;
136
21
  const stateKey = Number.isInteger(stateIndex) ? stateBindings[stateIndex]?.key : null;
137
22
  if (typeof stateKey !== 'string' || stateKey.length === 0) {
138
- return full;
23
+ return null;
24
+ }
25
+ if (valueRead) {
26
+ return ts.factory.createCallExpression(ts.factory.createPropertyAccessExpression(ts.factory.createIdentifier(stateKey), 'get'), undefined, []);
139
27
  }
140
- return full.endsWith('.get()') ? `${stateKey}.get()` : stateKey;
28
+ return ts.factory.createIdentifier(stateKey);
141
29
  });
142
- return normalizeTypeScriptExpression(resolved);
143
30
  }
144
31
  /**
32
+ * The only allowed downstream props rewrite boundary is compiler-owned exact lookup.
33
+ * If the compiler did not emit a mapping for the attr expression, CLI keeps the
34
+ * original expression text without attempting identifier reinterpretation.
35
+ *
145
36
  * @param {string} expr
146
37
  * @param {{
147
38
  * expressionRewrite?: {
@@ -150,8 +41,7 @@ export function resolveCompiledPropsExpression(compiledExpr, expressionRewrite =
150
41
  * ambiguous?: Set<string>,
151
42
  * signals?: Array<{ state_index?: number }>,
152
43
  * stateBindings?: Array<{ key?: string }>
153
- * } | null,
154
- * scopeRewrite?: { map?: Map<string, string>, ambiguous?: Set<string> } | null
44
+ * } | null
155
45
  * } | null} rewriteContext
156
46
  * @returns {string}
157
47
  */
@@ -177,13 +67,18 @@ export function resolvePropsValueCode(expr, rewriteContext = null) {
177
67
  return normalizeTypeScriptExpression(exact);
178
68
  }
179
69
  }
180
- return rewritePropsExpression(trimmed, rewriteContext);
70
+ return normalizeTypeScriptExpression(trimmed);
181
71
  }
182
72
  /**
183
73
  * @param {string} attrs
184
74
  * @param {{
185
- * expressionRewrite?: { map?: Map<string, string>, ambiguous?: Set<string> } | null,
186
- * scopeRewrite?: { map?: Map<string, string>, ambiguous?: Set<string> } | null
75
+ * expressionRewrite?: {
76
+ * map?: Map<string, string>,
77
+ * bindings?: Map<string, { compiled_expr?: string | null }>,
78
+ * ambiguous?: Set<string>,
79
+ * signals?: Array<{ state_index?: number }>,
80
+ * stateBindings?: Array<{ key?: string }>
81
+ * } | null
187
82
  * } | null} rewriteContext
188
83
  * @returns {string}
189
84
  */
@@ -225,8 +120,13 @@ export function renderPropsLiteralFromAttrs(attrs, rewriteContext = null) {
225
120
  * @param {string} source
226
121
  * @param {string} attrs
227
122
  * @param {{
228
- * expressionRewrite?: { map?: Map<string, string>, ambiguous?: Set<string> } | null,
229
- * scopeRewrite?: { map?: Map<string, string>, ambiguous?: Set<string> } | null
123
+ * expressionRewrite?: {
124
+ * map?: Map<string, string>,
125
+ * bindings?: Map<string, { compiled_expr?: string | null }>,
126
+ * ambiguous?: Set<string>,
127
+ * signals?: Array<{ state_index?: number }>,
128
+ * stateBindings?: Array<{ key?: string }>
129
+ * } | null
230
130
  * } | null} rewriteContext
231
131
  * @returns {string}
232
132
  */
@@ -2,7 +2,7 @@
2
2
  * @param {string} source
3
3
  * @param {string} sourceFile
4
4
  * @param {object} [compilerOpts]
5
- * @returns {{ source: string, serverScript: { source: string, prerender: boolean, has_guard: boolean, has_load: boolean, source_path: string } | null }}
5
+ * @returns {{ source: string, serverScript: { source: string, prerender: boolean, has_guard: boolean, has_load: boolean, has_action: boolean, source_path: string } | null }}
6
6
  */
7
7
  export function extractServerScript(source: string, sourceFile: string, compilerOpts?: object): {
8
8
  source: string;
@@ -11,6 +11,7 @@ export function extractServerScript(source: string, sourceFile: string, compiler
11
11
  prerender: boolean;
12
12
  has_guard: boolean;
13
13
  has_load: boolean;
14
+ has_action: boolean;
14
15
  source_path: string;
15
16
  } | null;
16
17
  };
@@ -4,12 +4,12 @@ import { findNextKnownComponentTag } from '../component-tag-parser.js';
4
4
  * @param {string} source
5
5
  * @param {string} sourceFile
6
6
  * @param {object} [compilerOpts]
7
- * @returns {{ source: string, serverScript: { source: string, prerender: boolean, has_guard: boolean, has_load: boolean, source_path: string } | null }}
7
+ * @returns {{ source: string, serverScript: { source: string, prerender: boolean, has_guard: boolean, has_load: boolean, has_action: boolean, source_path: string } | null }}
8
8
  */
9
9
  export function extractServerScript(source, sourceFile, compilerOpts = {}) {
10
10
  const scriptRe = /<script\b([^>]*)>([\s\S]*?)<\/script>/gi;
11
11
  const serverMatches = [];
12
- const reservedServerExportRe = /\bexport\s+const\s+(?:data|prerender|guard|load|ssr_data|props|ssr)\b|\bexport\s+(?:async\s+)?function\s+(?:load|guard)\s*\(|\bexport\s+const\s+(?:load|guard)\s*=/;
12
+ const reservedServerExportRe = /\bexport\s+const\s+(?:data|prerender|guard|load|action|ssr_data|props|ssr)\b|\bexport\s+(?:async\s+)?function\s+(?:load|guard|action)\s*\(|\bexport\s+const\s+(?:load|guard|action)\s*=/;
13
13
  for (const match of source.matchAll(scriptRe)) {
14
14
  const attrs = String(match[1] || '');
15
15
  const body = String(match[2] || '');
@@ -17,7 +17,7 @@ export function extractServerScript(source, sourceFile, compilerOpts = {}) {
17
17
  if (!isServer && reservedServerExportRe.test(body)) {
18
18
  throw new Error(`Zenith server script contract violation:\n` +
19
19
  ` File: ${sourceFile}\n` +
20
- ` Reason: guard/load/data exports are only allowed in <script server lang="ts"> or adjacent .guard.ts / .load.ts files\n` +
20
+ ` Reason: guard/load/action/data exports are only allowed in <script server lang="ts"> or adjacent .guard.ts / .load.ts / .action.ts files\n` +
21
21
  ` Example: move the export into <script server lang="ts">`);
22
22
  }
23
23
  if (isServer) {
@@ -81,6 +81,19 @@ export function extractServerScript(source, sourceFile, compilerOpts = {}) {
81
81
  ` Reason: multiple guard exports detected\n` +
82
82
  ` Example: keep exactly one export const guard = async (ctx) => ({ ... })`);
83
83
  }
84
+ const actionFnMatch = serverSource.match(/\bexport\s+(?:async\s+)?function\s+action\s*\(([^)]*)\)/);
85
+ const actionConstParenMatch = serverSource.match(/\bexport\s+const\s+action\s*=\s*(?:async\s*)?\(([^)]*)\)\s*=>/);
86
+ const actionConstSingleArgMatch = serverSource.match(/\bexport\s+const\s+action\s*=\s*(?:async\s*)?([a-zA-Z_$][a-zA-Z0-9_$]*)\s*=>/);
87
+ const hasAction = Boolean(actionFnMatch || actionConstParenMatch || actionConstSingleArgMatch);
88
+ const actionMatchCount = Number(Boolean(actionFnMatch)) +
89
+ Number(Boolean(actionConstParenMatch)) +
90
+ Number(Boolean(actionConstSingleArgMatch));
91
+ if (actionMatchCount > 1) {
92
+ throw new Error(`Zenith server script contract violation:\n` +
93
+ ` File: ${sourceFile}\n` +
94
+ ` Reason: multiple action exports detected\n` +
95
+ ` Example: keep exactly one export const action = async (ctx) => ({ ... })`);
96
+ }
84
97
  const hasData = /\bexport\s+const\s+data\b/.test(serverSource);
85
98
  const hasSsrData = /\bexport\s+const\s+ssr_data\b/.test(serverSource);
86
99
  const hasSsr = /\bexport\s+const\s+ssr\b/.test(serverSource);
@@ -119,6 +132,17 @@ export function extractServerScript(source, sourceFile, compilerOpts = {}) {
119
132
  ` Example: export const guard = async (ctx) => ({ ... })`);
120
133
  }
121
134
  }
135
+ if (hasAction) {
136
+ const singleArg = String(actionConstSingleArgMatch?.[1] || '').trim();
137
+ const paramsText = String((actionFnMatch || actionConstParenMatch)?.[1] || '').trim();
138
+ const arity = singleArg ? 1 : paramsText.length === 0 ? 0 : paramsText.split(',').length;
139
+ if (arity !== 1) {
140
+ throw new Error(`Zenith server script contract violation:\n` +
141
+ ` File: ${sourceFile}\n` +
142
+ ` Reason: action(ctx) must accept exactly one argument\n` +
143
+ ` Example: export const action = async (ctx) => ({ ... })`);
144
+ }
145
+ }
122
146
  const prerenderMatch = serverSource.match(/\bexport\s+const\s+prerender\s*=\s*([^\n;]+)/);
123
147
  let prerender = false;
124
148
  if (prerenderMatch) {
@@ -140,6 +164,7 @@ export function extractServerScript(source, sourceFile, compilerOpts = {}) {
140
164
  prerender,
141
165
  has_guard: hasGuard,
142
166
  has_load: hasLoad,
167
+ has_action: hasAction,
143
168
  source_path: sourceFile
144
169
  }
145
170
  };
@@ -153,6 +178,7 @@ export function extractServerScript(source, sourceFile, compilerOpts = {}) {
153
178
  prerender,
154
179
  has_guard: hasGuard,
155
180
  has_load: hasLoad,
181
+ has_action: hasAction,
156
182
  source_path: sourceFile
157
183
  }
158
184
  };
package/dist/build.js CHANGED
@@ -11,14 +11,15 @@ import { buildPageEnvelopes } from './build/page-loop.js';
11
11
  import { deriveProjectRootFromPagesDir, ensureZenithTypeDeclarations } from './build/type-declarations.js';
12
12
  import { materializeImageMarkupInHtmlFiles } from './images/materialize.js';
13
13
  import { buildImageArtifacts } from './images/service.js';
14
+ import { injectImageMaterializationIntoRouterManifest } from './images/router-manifest.js';
14
15
  import { createImageRuntimePayload, injectImageRuntimePayloadIntoHtmlFiles } from './images/payload.js';
16
+ import { supportsTargetRouteCheck } from './route-check-support.js';
15
17
  import { createStartupProfiler } from './startup-profile.js';
16
18
  import { writeServerOutput } from './server-output.js';
17
19
  import { resolveBundlerBin } from './toolchain-paths.js';
18
20
  import { createBundlerToolchain, createCompilerToolchain, ensureToolchainCompatibility, getActiveToolchainCandidate } from './toolchain-runner.js';
19
21
  import { maybeWarnAboutZenithVersionMismatch } from './version-check.js';
20
22
  export { createCompilerWarningEmitter };
21
- const RUNTIME_MARKUP_BINDING = '__ZENITH_INTERNAL_ZENHTML';
22
23
  function createCompilerTotals() {
23
24
  return {
24
25
  pageMs: 0,
@@ -55,6 +56,7 @@ export async function build(options) {
55
56
  const compilerTotals = createCompilerTotals();
56
57
  const { target, adapter, mode } = resolveBuildAdapter(config);
57
58
  const basePath = normalizeBasePath(config.basePath || '/');
59
+ const routeCheckEnabled = supportsTargetRouteCheck(target);
58
60
  const routerEnabled = config.router === true;
59
61
  const compilerOpts = {
60
62
  typescriptDefault: config.typescriptDefault === true,
@@ -72,7 +74,6 @@ export async function build(options) {
72
74
  }));
73
75
  }
74
76
  const registry = startupProfile.measureSync('build_component_registry', () => buildComponentRegistry(srcDir));
75
- void RUNTIME_MARKUP_BINDING;
76
77
  const manifest = await startupProfile.measureAsync('generate_manifest', () => generateManifest(pagesDir, '.zen', { compilerOpts }));
77
78
  if (mode !== 'legacy') {
78
79
  adapter.validateRoutes(manifest);
@@ -103,7 +104,8 @@ export async function build(options) {
103
104
  emitCompilerWarning
104
105
  });
105
106
  if (envelopes.length > 0) {
106
- await startupProfile.measureAsync('run_bundler', () => runBundler(envelopes, staticOutputDir, projectRoot, logger, showBundlerInfo, bundlerBin, { basePath }), { envelopes: envelopes.length });
107
+ await startupProfile.measureAsync('run_bundler', () => runBundler(envelopes, staticOutputDir, projectRoot, logger, showBundlerInfo, bundlerBin, { basePath, routeCheck: routeCheckEnabled }), { envelopes: envelopes.length });
108
+ await startupProfile.measureAsync('inject_image_materialization_manifest', () => injectImageMaterializationIntoRouterManifest(staticOutputDir, envelopes), { envelopes: envelopes.length });
107
109
  }
108
110
  await startupProfile.measureAsync('rewrite_soft_navigation_base_path', () => rewriteSoftNavigationHrefBasePathInHtmlFiles(staticOutputDir, basePath));
109
111
  const { manifest: imageManifest } = await startupProfile.measureAsync('build_image_artifacts', () => buildImageArtifacts({