@zenithbuild/runtime 0.7.4 → 0.7.7

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.
@@ -1,6 +1,7 @@
1
1
  import { throwZenithRuntimeError, DOCS_LINKS } from './diagnostics.js';
2
2
  import { _fragment } from './markup.js';
3
3
  export const UNRESOLVED_LITERAL = Symbol('unresolved_literal');
4
+ const OWN = Object.prototype.hasOwnProperty;
4
5
  export const STRICT_MEMBER_CHAIN_LITERAL_RE = /^(?:true|false|null|undefined|[A-Za-z_$][A-Za-z0-9_$]*(\.[A-Za-z_$][A-Za-z0-9_$]*)*)$/;
5
6
  export const CANONICAL_MEMBER_CHAIN_BASES = new Set(['props', 'params', 'data', 'ssr']);
6
7
  export const UNSAFE_MEMBER_KEYS = new Set(['__proto__', 'prototype', 'constructor']);
@@ -17,109 +18,60 @@ export function _resolveExpressionSignalIndices(binding) {
17
18
  return [];
18
19
  }
19
20
  export function _evaluateExpression(binding, stateValues, stateKeys, signalMap, componentBindings, params, ssrData, mode, props, exprFns, markerBinding = null, eventBinding = null) {
20
- if (binding.fn_index != null && binding.fn_index !== undefined) {
21
- const fns = Array.isArray(exprFns) ? exprFns : [];
22
- const fn = fns[binding.fn_index];
21
+ const runtimeProps = props || {};
22
+ if (binding.fn_index != null) {
23
+ const fn = Array.isArray(exprFns) ? exprFns[binding.fn_index] : undefined;
23
24
  if (typeof fn === 'function') {
24
- try {
25
- return fn({
26
- signalMap,
27
- params,
28
- ssrData,
29
- props: props || {},
30
- componentBindings,
31
- fragment: _fragment
32
- });
33
- }
34
- catch (fnErr) {
35
- throw fnErr;
36
- }
25
+ return fn({
26
+ signalMap,
27
+ params,
28
+ ssrData,
29
+ props: runtimeProps,
30
+ componentBindings,
31
+ fragment: _fragment
32
+ });
37
33
  }
38
34
  }
39
- if (binding.signal_index !== null && binding.signal_index !== undefined) {
35
+ if (binding.signal_index != null) {
40
36
  const signalValue = signalMap.get(binding.signal_index);
41
37
  if (!signalValue || typeof signalValue.get !== 'function') {
42
38
  throw new Error('[Zenith Runtime] expression.signal_index did not resolve to a signal');
43
39
  }
44
- return mode === 'event' ? signalValue : signalValue.get();
40
+ return _rvm(signalValue, mode);
45
41
  }
46
- if (binding.state_index !== null && binding.state_index !== undefined) {
47
- const resolved = stateValues[binding.state_index];
48
- if (mode !== 'event' &&
49
- resolved &&
50
- typeof resolved === 'object' &&
51
- typeof resolved.get === 'function') {
52
- return resolved.get();
53
- }
54
- if (mode !== 'event' && typeof resolved === 'function') {
55
- return resolved();
56
- }
57
- return resolved;
42
+ if (binding.state_index != null) {
43
+ return _rvm(stateValues[binding.state_index], mode);
58
44
  }
59
45
  if (typeof binding.component_instance === 'string' && typeof binding.component_binding === 'string') {
60
- const instanceBindings = componentBindings[binding.component_instance];
61
- const resolved = instanceBindings && Object.prototype.hasOwnProperty.call(instanceBindings, binding.component_binding)
62
- ? instanceBindings[binding.component_binding]
63
- : undefined;
64
- if (mode !== 'event' &&
65
- resolved &&
66
- typeof resolved === 'object' &&
67
- typeof resolved.get === 'function') {
68
- return resolved.get();
69
- }
70
- if (mode !== 'event' && typeof resolved === 'function') {
71
- return resolved();
72
- }
73
- return resolved;
46
+ return _rvm(_rcb(binding, componentBindings), mode);
74
47
  }
75
- if (binding.literal !== null && binding.literal !== undefined) {
76
- if (typeof binding.literal === 'string') {
77
- const trimmedLiteral = binding.literal.trim();
78
- // 1. Static primitives (true, false, null, undefined, numbers, quoted strings)
79
- const primitiveValue = _resolvePrimitiveLiteral(trimmedLiteral);
80
- if (primitiveValue !== UNRESOLVED_LITERAL) {
81
- return primitiveValue;
82
- }
83
- // 2. Canonical payload roots
84
- if (trimmedLiteral === 'data' || trimmedLiteral === 'ssr') {
85
- return ssrData;
86
- }
87
- if (trimmedLiteral === 'params') {
88
- return params;
89
- }
90
- if (trimmedLiteral === 'props') {
91
- return props || {};
92
- }
93
- // 3. Bounded canonical member chains (props.*, params.*, data.*, ssr.*, exact stateKeys)
94
- const strictMemberValue = _resolveStrictMemberChainLiteral(trimmedLiteral, stateValues, stateKeys, params, ssrData, mode, props, binding.marker_index, _resolveBindingSource(binding, markerBinding, eventBinding));
95
- if (strictMemberValue !== UNRESOLVED_LITERAL) {
96
- return strictMemberValue;
97
- }
98
- // 4. Anything else is a literal that was not lowered by the compiler.
99
- // No heuristic guessing, no identifier extraction, no alias recovery.
100
- throwZenithRuntimeError({
101
- phase: 'bind',
102
- code: 'EXPRESSION_NOT_LOWERED',
103
- message: `Expression literal was not lowered by the compiler: ${_truncateLiteralForError(trimmedLiteral)}`,
104
- marker: {
105
- type: _markerTypeForError(mode),
106
- id: binding.marker_index
107
- },
108
- path: `expression[${binding.marker_index}]`,
109
- hint: 'This expression must be lowered to fn_index, signal_index, or state_index by the compiler. Literal string interpretation is restricted to static primitives and canonical member chains (props.*, params.*, data.*, ssr.*).',
110
- docsLink: DOCS_LINKS.expressionScope,
111
- source: _resolveBindingSource(binding, markerBinding, eventBinding)
112
- });
113
- }
48
+ if (binding.literal == null) {
49
+ return '';
50
+ }
51
+ if (typeof binding.literal !== 'string') {
114
52
  return binding.literal;
115
53
  }
116
- return '';
54
+ const trimmedLiteral = binding.literal.trim();
55
+ const primitiveValue = _resolvePrimitiveLiteral(trimmedLiteral);
56
+ if (primitiveValue !== UNRESOLVED_LITERAL) {
57
+ return primitiveValue;
58
+ }
59
+ const canonicalRootValue = _rcr(trimmedLiteral, params, ssrData, runtimeProps);
60
+ if (canonicalRootValue !== UNRESOLVED_LITERAL) {
61
+ return canonicalRootValue;
62
+ }
63
+ const source = _resolveBindingSource(binding, markerBinding, eventBinding);
64
+ const strictMemberValue = _resolveStrictMemberChainLiteral(trimmedLiteral, stateValues, stateKeys, params, ssrData, mode, runtimeProps, binding.marker_index, source);
65
+ if (strictMemberValue !== UNRESOLVED_LITERAL) {
66
+ return strictMemberValue;
67
+ }
68
+ _tenl(trimmedLiteral, binding.marker_index, mode, source);
117
69
  }
118
70
  export function _throwUnresolvedMemberChainError(literal, markerIndex, mode, pathSuffix, hint, source) {
119
71
  throwZenithRuntimeError({
120
72
  phase: 'bind',
121
73
  code: 'UNRESOLVED_EXPRESSION',
122
- message: `Failed to resolve expression literal: ${_truncateLiteralForError(literal)}`,
74
+ message: `Failed to resolve literal: ${_truncateLiteralForError(literal)}`,
123
75
  marker: {
124
76
  type: _markerTypeForError(mode),
125
77
  id: markerIndex
@@ -134,53 +86,53 @@ export function _resolveStrictMemberChainLiteral(literal, stateValues, stateKeys
134
86
  if (typeof literal !== 'string' || !STRICT_MEMBER_CHAIN_LITERAL_RE.test(literal)) {
135
87
  return UNRESOLVED_LITERAL;
136
88
  }
137
- // Primitives are handled by _resolvePrimitiveLiteral before this function
138
89
  if (literal === 'true' || literal === 'false' || literal === 'null' || literal === 'undefined') {
139
90
  return UNRESOLVED_LITERAL;
140
91
  }
141
92
  const segments = literal.split('.');
142
93
  const baseIdentifier = segments[0];
143
- // Bounded resolution: only canonical payload prefixes and exact state keys
144
- const isCanonicalBase = CANONICAL_MEMBER_CHAIN_BASES.has(baseIdentifier);
145
- const isExactStateKey = !isCanonicalBase && Array.isArray(stateKeys) && stateKeys.includes(baseIdentifier);
146
- if (!isCanonicalBase && !isExactStateKey) {
147
- // Not a canonical base and not an exact state key — this literal was not lowered
94
+ const baseValue = _resolveStrictBase(baseIdentifier, stateValues, stateKeys, params, ssrData, props);
95
+ if (baseValue === UNRESOLVED_LITERAL) {
148
96
  return UNRESOLVED_LITERAL;
149
97
  }
150
- const scope = _buildLiteralScope(stateValues, stateKeys, params, ssrData, mode, props);
151
- if (!Object.prototype.hasOwnProperty.call(scope, baseIdentifier)) {
152
- _throwUnresolvedMemberChainError(literal, markerIndex, mode, `expression.${baseIdentifier}`, `Base identifier "${baseIdentifier}" is not bound. Check props/data/params and declared state keys.`, source);
153
- }
154
- let cursor = scope[baseIdentifier];
98
+ const failResolve = (suffix, hint) => _throwUnresolvedMemberChainError(literal, markerIndex, mode, `expression.${suffix}`, hint, source);
99
+ let cursor = baseValue;
155
100
  let traversedPath = baseIdentifier;
156
101
  for (let i = 1; i < segments.length; i++) {
157
102
  const segment = segments[i];
158
103
  if (UNSAFE_MEMBER_KEYS.has(segment)) {
159
- throwZenithRuntimeError({
160
- phase: 'bind',
161
- code: 'UNSAFE_MEMBER_ACCESS',
162
- message: `Blocked unsafe member access: ${segment} in path "${literal}"`,
163
- path: `marker[${markerIndex}].expression.${literal}`,
164
- hint: 'Property access to __proto__, prototype, and constructor is forbidden.',
165
- docsLink: DOCS_LINKS.expressionScope,
166
- source
167
- });
104
+ _tuma(literal, markerIndex, segment, source);
168
105
  }
169
106
  if (cursor === null || cursor === undefined) {
170
- _throwUnresolvedMemberChainError(literal, markerIndex, mode, `expression.${traversedPath}.${segment}`, `Cannot read "${segment}" from ${traversedPath} because it is null or undefined.`, source);
107
+ failResolve(`${traversedPath}.${segment}`, `Cannot read "${segment}" from ${traversedPath}; value is null/undefined.`);
171
108
  }
172
109
  const cursorType = typeof cursor;
173
110
  if (cursorType !== 'object' && cursorType !== 'function') {
174
- _throwUnresolvedMemberChainError(literal, markerIndex, mode, `expression.${traversedPath}.${segment}`, `Cannot read "${segment}" from ${traversedPath} because it resolved to a ${cursorType}.`, source);
111
+ failResolve(`${traversedPath}.${segment}`, `Cannot read "${segment}" from ${traversedPath}; value is ${cursorType}.`);
175
112
  }
176
- if (!Object.prototype.hasOwnProperty.call(cursor, segment)) {
177
- _throwUnresolvedMemberChainError(literal, markerIndex, mode, `expression.${traversedPath}.${segment}`, `Missing member "${segment}" on ${traversedPath}. Check your bindings.`, source);
113
+ if (!OWN.call(cursor, segment)) {
114
+ failResolve(`${traversedPath}.${segment}`, `Missing member "${segment}" on ${traversedPath}.`);
178
115
  }
179
116
  cursor = cursor[segment];
180
117
  traversedPath = `${traversedPath}.${segment}`;
181
118
  }
182
119
  return cursor;
183
120
  }
121
+ function _resolveStrictBase(baseIdentifier, stateValues, stateKeys, params, ssrData, props) {
122
+ if (baseIdentifier === '__zenith_fragment')
123
+ return _fragment;
124
+ if (CANONICAL_MEMBER_CHAIN_BASES.has(baseIdentifier)) {
125
+ if (baseIdentifier === 'props')
126
+ return props || {};
127
+ if (baseIdentifier === 'params')
128
+ return params;
129
+ return ssrData;
130
+ }
131
+ if (!Array.isArray(stateKeys))
132
+ return UNRESOLVED_LITERAL;
133
+ const stateIndex = stateKeys.indexOf(baseIdentifier);
134
+ return stateIndex === -1 ? UNRESOLVED_LITERAL : stateValues[stateIndex];
135
+ }
184
136
  export function _resolvePrimitiveLiteral(literal) {
185
137
  if (typeof literal !== 'string') {
186
138
  return UNRESOLVED_LITERAL;
@@ -215,45 +167,21 @@ export function _resolvePrimitiveLiteral(literal) {
215
167
  }
216
168
  return UNRESOLVED_LITERAL;
217
169
  }
218
- export function _buildLiteralScope(stateValues, stateKeys, params, ssrData, mode, props) {
219
- const scope = Object.create(null);
220
- scope.params = params;
221
- scope.data = ssrData;
222
- scope.ssr = ssrData;
223
- scope.props = props || {};
224
- scope.__zenith_fragment = _fragment;
225
- // Exact state keys only — no alias derivation, no mangled-name recovery
226
- if (Array.isArray(stateKeys)) {
227
- for (let i = 0; i < stateKeys.length; i++) {
228
- const key = stateKeys[i];
229
- if (typeof key !== 'string' || key.length === 0) {
230
- continue;
231
- }
232
- if (Object.prototype.hasOwnProperty.call(scope, key)) {
233
- continue;
234
- }
235
- scope[key] = stateValues[i];
236
- }
237
- }
238
- return scope;
239
- }
240
- // _isLikelyExpressionLiteral and _extractMissingIdentifier removed:
241
- // Runtime no longer performs heuristic identifier extraction or expression
242
- // shape guessing. Unresolved literals throw EXPRESSION_NOT_LOWERED directly.
243
170
  export function _resolveBindingSource(binding, markerBinding, eventBinding) {
244
- const candidates = [
245
- binding?.source,
246
- eventBinding?.source,
247
- markerBinding?.source
248
- ];
249
- for (let i = 0; i < candidates.length; i++) {
250
- const candidate = candidates[i];
251
- if (candidate && typeof candidate === 'object' && typeof candidate.file === 'string') {
252
- return candidate;
253
- }
254
- }
171
+ const source = binding?.source;
172
+ if (_isSourceSpan(source))
173
+ return source;
174
+ const eventSource = eventBinding?.source;
175
+ if (_isSourceSpan(eventSource))
176
+ return eventSource;
177
+ const markerSource = markerBinding?.source;
178
+ if (_isSourceSpan(markerSource))
179
+ return markerSource;
255
180
  return undefined;
256
181
  }
182
+ function _isSourceSpan(value) {
183
+ return Boolean(value) && typeof value === 'object' && typeof value.file === 'string';
184
+ }
257
185
  export function _describeBindingExpression(binding) {
258
186
  if (!binding || typeof binding !== 'object') {
259
187
  return '<unknown>';
@@ -286,10 +214,60 @@ export function _truncateLiteralForError(str) {
286
214
  return String(str);
287
215
  const sanitized = str
288
216
  .replace(/[A-Za-z]:\\[^\s"'`]+/g, '<path>')
289
- .replace(/\/Users\/[^\s"'`]+/g, '<path>')
290
- .replace(/\/home\/[^\s"'`]+/g, '<path>')
291
- .replace(/\/private\/[^\s"'`]+/g, '<path>')
292
- .replace(/\/tmp\/[^\s"'`]+/g, '<path>')
293
- .replace(/\/var\/folders\/[^\s"'`]+/g, '<path>');
217
+ .replace(/\/(?:Users|home|private|tmp|var\/folders)\/[^\s"'`]+/g, '<path>');
294
218
  return sanitized.length > 100 ? `${sanitized.substring(0, 97)}...` : sanitized;
295
219
  }
220
+ function _rvm(value, mode) {
221
+ if (mode === 'event') {
222
+ return value;
223
+ }
224
+ if (value && typeof value === 'object' && typeof value.get === 'function') {
225
+ return value.get();
226
+ }
227
+ if (typeof value === 'function') {
228
+ return value();
229
+ }
230
+ return value;
231
+ }
232
+ function _rcb(binding, componentBindings) {
233
+ const instanceBindings = componentBindings[binding.component_instance];
234
+ if (!instanceBindings || !OWN.call(instanceBindings, binding.component_binding)) {
235
+ return undefined;
236
+ }
237
+ return instanceBindings[binding.component_binding];
238
+ }
239
+ function _rcr(literal, params, ssrData, props) {
240
+ if (literal === 'data' || literal === 'ssr')
241
+ return ssrData;
242
+ if (literal === 'params')
243
+ return params;
244
+ if (literal === 'props')
245
+ return props;
246
+ return UNRESOLVED_LITERAL;
247
+ }
248
+ function _tenl(literal, markerIndex, mode, source) {
249
+ throwZenithRuntimeError({
250
+ phase: 'bind',
251
+ code: 'EXPRESSION_NOT_LOWERED',
252
+ message: `Expression literal was not lowered by the compiler: ${_truncateLiteralForError(literal)}`,
253
+ marker: {
254
+ type: _markerTypeForError(mode),
255
+ id: markerIndex
256
+ },
257
+ path: `expression[${markerIndex}]`,
258
+ hint: 'Lower expression to fn_index/signal_index/state_index.',
259
+ docsLink: DOCS_LINKS.expressionScope,
260
+ source
261
+ });
262
+ }
263
+ function _tuma(literal, markerIndex, member, source) {
264
+ throwZenithRuntimeError({
265
+ phase: 'bind',
266
+ code: 'UNSAFE_MEMBER_ACCESS',
267
+ message: `Blocked unsafe member access: ${member} in path "${literal}"`,
268
+ path: `marker[${markerIndex}].expression.${literal}`,
269
+ hint: 'Property access to __proto__/prototype/constructor is forbidden.',
270
+ docsLink: DOCS_LINKS.expressionScope,
271
+ source
272
+ });
273
+ }
package/dist/hydrate.js CHANGED
@@ -10,7 +10,7 @@ import { createNodeResolver } from './scanner.js';
10
10
  import { bindEventMarkers } from './events.js';
11
11
  import { _applyMarkerValue } from './render.js';
12
12
  export { _createContextualFragment, _coerceText } from './render.js';
13
- // Raw HTML boundary enforcement lives in render.js:
13
+ // Raw HTML boundary lock lives in render.js:
14
14
  // attrName.toLowerCase() === 'innerhtml'
15
15
  // innerHTML bindings are forbidden in Zenith
16
16
  // attrName.toLowerCase() === 'unsafehtml'
@@ -19,7 +19,7 @@ export function hydrate(payload) {
19
19
  try {
20
20
  const normalized = _validatePayload(payload);
21
21
  _deepFreezePayload(payload);
22
- const { root, expressions, markers, events, refs, stateValues, stateKeys, signals, components, route, params, ssrData, props, exprFns } = normalized;
22
+ const { root, expressions, markers, events, refs, stateValues, stateKeys, signals, components, params, ssrData, props, exprFns } = normalized;
23
23
  const componentBindings = Object.create(null);
24
24
  const resolveNodes = createNodeResolver(root);
25
25
  const signalMap = _createSignalMap(signals, stateValues);
@@ -28,12 +28,10 @@ export function hydrate(payload) {
28
28
  components,
29
29
  signalMap,
30
30
  componentBindings,
31
- route,
32
31
  resolveNodes
33
32
  });
34
- const markerState = _hydrateMarkers({
33
+ const runtimeContext = {
35
34
  expressions,
36
- markers,
37
35
  stateValues,
38
36
  stateKeys,
39
37
  signalMap,
@@ -43,22 +41,16 @@ export function hydrate(payload) {
43
41
  props,
44
42
  exprFns,
45
43
  resolveNodes
44
+ };
45
+ const markerState = _hydrateMarkers({
46
+ ...runtimeContext,
47
+ markers
46
48
  });
47
- const renderMarkerByIndex = (index) => _renderMarker({
48
- index,
49
- root,
50
- expressions,
51
- stateValues,
52
- stateKeys,
53
- signalMap,
54
- componentBindings,
55
- params,
56
- ssrData,
57
- props,
58
- exprFns,
59
- resolveNodes,
49
+ const renderContext = {
50
+ ...runtimeContext,
60
51
  ...markerState
61
- });
52
+ };
53
+ const renderMarkerByIndex = (index) => _renderMarker(renderContext, index);
62
54
  _bindSignalSubscriptions(expressions, signalMap, stateValues, renderMarkerByIndex);
63
55
  _bindComponentSignalSubscriptions(expressions, componentBindings, renderMarkerByIndex);
64
56
  bindEventMarkers({
@@ -82,7 +74,7 @@ export function hydrate(payload) {
82
74
  rethrowZenithRuntimeError(error, {
83
75
  phase: 'hydrate',
84
76
  code: 'BINDING_APPLY_FAILED',
85
- hint: 'Inspect marker tables, expression bindings, and the runtime overlay diagnostics.',
77
+ hint: 'Inspect marker tables, expression bindings, and runtime diagnostics.',
86
78
  docsLink: DOCS_LINKS.markerTable
87
79
  });
88
80
  }
@@ -121,13 +113,10 @@ function _hydrateRefs(refs, stateValues, resolveNodes) {
121
113
  });
122
114
  }
123
115
  function _mountComponents(context) {
124
- const { components, signalMap, componentBindings, route, resolveNodes } = context;
116
+ const { components, signalMap, componentBindings, resolveNodes } = context;
125
117
  for (let i = 0; i < components.length; i++) {
126
118
  const component = components[i];
127
- const resolvedProps = Object.freeze(_resolveComponentProps(component.props || [], signalMap, {
128
- component: component.instance,
129
- route
130
- }));
119
+ const resolvedProps = Object.freeze(_resolveComponentProps(component.props || [], signalMap));
131
120
  const hosts = resolveNodes(component.selector, i, 'component', component.source);
132
121
  for (let j = 0; j < hosts.length; j++) {
133
122
  try {
@@ -176,7 +165,7 @@ function _mountComponents(context) {
176
165
  code: 'COMPONENT_BOOTSTRAP_FAILED',
177
166
  message: `Component bootstrap failed for "${component.instance}"`,
178
167
  path: `component[${component.instance}]`,
179
- hint: 'Fix the failing component and refresh; other components continue mounting.',
168
+ hint: 'Fix the failing component and refresh.',
180
169
  docsLink: DOCS_LINKS.componentBootstrap,
181
170
  source: component.source
182
171
  });
@@ -188,49 +177,31 @@ function _mountComponents(context) {
188
177
  }
189
178
  }
190
179
  function _hydrateMarkers(context) {
191
- const { expressions, markers, stateValues, stateKeys, signalMap, componentBindings, params, ssrData, props, exprFns, resolveNodes } = context;
180
+ const { expressions, markers, resolveNodes } = context;
192
181
  const markerByIndex = new Map();
193
182
  const markerNodesByIndex = new Map();
194
- const markerIndices = new Set();
195
- const expressionMarkerIndices = new Set();
196
- for (let i = 0; i < expressions.length; i++) {
197
- const expression = expressions[i];
198
- if (expressionMarkerIndices.has(expression.marker_index)) {
199
- throw new Error(`[Zenith Runtime] duplicate expression marker_index ${expression.marker_index}`);
200
- }
201
- expressionMarkerIndices.add(expression.marker_index);
202
- }
203
183
  for (let i = 0; i < markers.length; i++) {
204
184
  const marker = markers[i];
205
- if (markerIndices.has(marker.index)) {
206
- throw new Error(`[Zenith Runtime] duplicate marker index ${marker.index}`);
207
- }
208
- markerIndices.add(marker.index);
209
185
  markerByIndex.set(marker.index, marker);
210
186
  if (marker.kind === 'event') {
211
187
  continue;
212
188
  }
213
189
  const nodes = resolveNodes(marker.selector, marker.index, marker.kind, marker.source);
214
190
  markerNodesByIndex.set(marker.index, nodes);
215
- const value = _evaluateExpression(expressions[marker.index], stateValues, stateKeys, signalMap, componentBindings, params, ssrData, marker.kind, props, exprFns, marker, null);
191
+ const value = _evaluateMarkerBinding(context, expressions[marker.index], marker);
216
192
  _applyMarkerValue(nodes, marker, value);
217
193
  }
218
- for (let i = 0; i < expressions.length; i++) {
219
- if (!markerIndices.has(i)) {
220
- throw new Error(`[Zenith Runtime] missing marker index ${i}`);
221
- }
222
- }
223
194
  return { markerByIndex, markerNodesByIndex };
224
195
  }
225
- function _renderMarker(context) {
226
- const marker = context.markerByIndex.get(context.index);
196
+ function _renderMarker(context, index) {
197
+ const marker = context.markerByIndex.get(index);
227
198
  if (!marker || marker.kind === 'event') {
228
199
  return;
229
200
  }
230
- const nodes = context.markerNodesByIndex.get(context.index)
201
+ const nodes = context.markerNodesByIndex.get(index)
231
202
  || context.resolveNodes(marker.selector, marker.index, marker.kind, marker.source);
232
- context.markerNodesByIndex.set(context.index, nodes);
233
- const value = _evaluateExpression(context.expressions[context.index], context.stateValues, context.stateKeys, context.signalMap, context.componentBindings, context.params, context.ssrData, marker.kind, context.props, context.exprFns, marker, null);
203
+ context.markerNodesByIndex.set(index, nodes);
204
+ const value = _evaluateMarkerBinding(context, context.expressions[index], marker);
234
205
  _applyMarkerValue(nodes, marker, value);
235
206
  }
236
207
  function _isSignalLike(candidate) {
@@ -265,16 +236,7 @@ function _bindSignalSubscriptions(expressions, signalMap, stateValues, renderMar
265
236
  _recordSignalMarkerDependency(dependentMarkersBySignal, stateValues[expression.state_index], expression.marker_index);
266
237
  }
267
238
  }
268
- for (const [targetSignal, markerIndexes] of dependentMarkersBySignal.entries()) {
269
- const unsubscribe = targetSignal.subscribe(() => {
270
- for (let i = 0; i < markerIndexes.length; i++) {
271
- renderMarkerByIndex(markerIndexes[i]);
272
- }
273
- });
274
- if (typeof unsubscribe === 'function') {
275
- _registerDisposer(unsubscribe);
276
- }
277
- }
239
+ _subscribeMarkerDependencies(dependentMarkersBySignal, renderMarkerByIndex);
278
240
  }
279
241
  function _bindComponentSignalSubscriptions(expressions, componentBindings, renderMarkerByIndex) {
280
242
  const dependentMarkersByComponentSignal = new Map();
@@ -285,10 +247,7 @@ function _bindComponentSignalSubscriptions(expressions, componentBindings, rende
285
247
  }
286
248
  const instanceBindings = componentBindings[expression.component_instance];
287
249
  const candidate = instanceBindings?.[expression.component_binding];
288
- if (!candidate || typeof candidate !== 'object') {
289
- continue;
290
- }
291
- if (typeof candidate.get !== 'function' || typeof candidate.subscribe !== 'function') {
250
+ if (!_isSignalLike(candidate)) {
292
251
  continue;
293
252
  }
294
253
  if (!dependentMarkersByComponentSignal.has(candidate)) {
@@ -296,8 +255,14 @@ function _bindComponentSignalSubscriptions(expressions, componentBindings, rende
296
255
  }
297
256
  dependentMarkersByComponentSignal.get(candidate).push(expression.marker_index);
298
257
  }
299
- for (const [componentSignal, markerIndexes] of dependentMarkersByComponentSignal.entries()) {
300
- const unsubscribe = componentSignal.subscribe(() => {
258
+ _subscribeMarkerDependencies(dependentMarkersByComponentSignal, renderMarkerByIndex);
259
+ }
260
+ function _evaluateMarkerBinding(context, expression, marker) {
261
+ return _evaluateExpression(expression, context.stateValues, context.stateKeys, context.signalMap, context.componentBindings, context.params, context.ssrData, marker.kind, context.props, context.exprFns, marker, null);
262
+ }
263
+ function _subscribeMarkerDependencies(dependencies, renderMarkerByIndex) {
264
+ for (const [targetSignal, markerIndexes] of dependencies.entries()) {
265
+ const unsubscribe = targetSignal.subscribe(() => {
301
266
  for (let i = 0; i < markerIndexes.length; i++) {
302
267
  renderMarkerByIndex(markerIndexes[i]);
303
268
  }
package/dist/index.d.ts CHANGED
@@ -2,5 +2,6 @@ export { signal } from "./signal.js";
2
2
  export { state } from "./state.js";
3
3
  export { hydrate } from "./hydrate.js";
4
4
  export { effect, mount, zeneffect, zenEffect, zenMount } from "./zeneffect.js";
5
+ export { zenPresence, presence } from "./presence.js";
5
6
  export { window, document, zenWindow, zenDocument } from "./env.js";
6
7
  export { zenOn, zenResize, collectRefs } from "./platform.js";
package/dist/index.js CHANGED
@@ -1,6 +1,7 @@
1
1
  export { signal } from './signal.js';
2
2
  export { state } from './state.js';
3
3
  export { effect, mount, zeneffect, zenEffect, zenMount } from './zeneffect.js';
4
+ export { zenPresence, presence } from './presence.js';
4
5
  export { hydrate } from './hydrate.js';
5
6
  export { window, document, zenWindow, zenDocument } from './env.js';
6
7
  export { zenOn, zenResize, collectRefs } from './platform.js';
package/dist/markup.js CHANGED
@@ -30,7 +30,7 @@ export function _fragment(strings, ...values) {
30
30
  phase: 'render',
31
31
  code: 'NON_RENDERABLE_VALUE',
32
32
  message: '__zenith_fragment must be called as a tagged template literal',
33
- hint: 'This helper only accepts tagged template syntax.'
33
+ hint: 'This helper only accepts tagged templates.'
34
34
  });
35
35
  }
36
36
  let result = '';
@@ -46,7 +46,7 @@ export function _fragment(strings, ...values) {
46
46
  phase: 'render',
47
47
  code: 'NON_RENDERABLE_VALUE',
48
48
  message: 'Embedded markup expression contains forbidden <script> tag',
49
- hint: 'Script tags are not allowed in embedded markup expressions.'
49
+ hint: 'Script tags are not allowed in embedded markup.'
50
50
  });
51
51
  }
52
52
  if (_FRAGMENT_EVENT_ATTR_RE.test(result)) {
@@ -54,7 +54,7 @@ export function _fragment(strings, ...values) {
54
54
  phase: 'render',
55
55
  code: 'NON_RENDERABLE_VALUE',
56
56
  message: 'Embedded markup expression contains inline event handler (on*=)',
57
- hint: 'Use on:event={handler} bindings instead of inline event attributes.'
57
+ hint: 'Use on:event={handler} instead of inline event attributes.'
58
58
  });
59
59
  }
60
60
  if (_FRAGMENT_JS_URL_RE.test(result)) {
@@ -62,7 +62,7 @@ export function _fragment(strings, ...values) {
62
62
  phase: 'render',
63
63
  code: 'NON_RENDERABLE_VALUE',
64
64
  message: 'Embedded markup expression contains javascript: URL',
65
- hint: 'javascript: URLs are forbidden in embedded markup.'
65
+ hint: 'javascript: URLs are forbidden.'
66
66
  });
67
67
  }
68
68
  result = result.replace(_FRAGMENT_SCRIPT_CLOSE_RE, '<\\/script');
@@ -99,7 +99,7 @@ function _fragmentInterpolate(val, interpolationIndex) {
99
99
  phase: 'render',
100
100
  code: 'NON_RENDERABLE_VALUE',
101
101
  message: `Embedded markup interpolation[${interpolationIndex}] contains forbidden key "${keys[k]}"`,
102
- hint: 'Prototype pollution keys are forbidden in embedded markup expressions.'
102
+ hint: 'Prototype pollution keys are forbidden in embedded markup.'
103
103
  });
104
104
  }
105
105
  }
@@ -107,14 +107,14 @@ function _fragmentInterpolate(val, interpolationIndex) {
107
107
  phase: 'render',
108
108
  code: 'NON_RENDERABLE_VALUE',
109
109
  message: `Embedded markup interpolation[${interpolationIndex}] resolved to a non-renderable object`,
110
- hint: 'Only strings, numbers, booleans, null, undefined, arrays, and compiler-owned fragments are allowed.'
110
+ hint: 'Only primitives, arrays, and compiler-owned fragments are allowed.'
111
111
  });
112
112
  }
113
113
  throwZenithRuntimeError({
114
114
  phase: 'render',
115
115
  code: 'NON_RENDERABLE_VALUE',
116
116
  message: `Embedded markup interpolation[${interpolationIndex}] resolved to type "${typeof val}"`,
117
- hint: 'Only strings, numbers, booleans, null, undefined, arrays, and compiler-owned fragments are allowed.'
117
+ hint: 'Only primitives, arrays, and compiler-owned fragments are allowed.'
118
118
  });
119
119
  }
120
120
  function _escapeFragmentHtml(input) {
package/dist/payload.d.ts CHANGED
@@ -1,20 +1,19 @@
1
- export function _validatePayload(payload: any): Readonly<{
1
+ export function _validatePayload(payload: any): {
2
2
  root: any;
3
- expressions: any[];
4
- markers: any[];
5
- events: any[];
3
+ expressions: any;
4
+ markers: any;
5
+ events: any;
6
6
  refs: any;
7
- stateValues: any[];
8
- stateKeys: any[];
9
- signals: any[];
7
+ stateValues: any;
8
+ stateKeys: any;
9
+ signals: any;
10
10
  components: any;
11
- route: any;
12
11
  params: any;
13
12
  ssrData: any;
14
13
  props: any;
15
14
  exprFns: any;
16
- }>;
17
- export function _resolveComponentProps(propTable: any, signalMap: any, context?: {}): any;
15
+ };
16
+ export function _resolveComponentProps(propTable: any, signalMap: any): any;
18
17
  export function _deepFreezePayload(obj: any): void;
19
18
  export function _isHydrationRefObject(obj: any): boolean;
20
19
  export function _isPlainObject(value: any): boolean;