@zenithbuild/runtime 0.6.13 → 0.6.17
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/diagnostics.js +99 -5
- package/dist/events.js +5 -3
- package/dist/hydrate.d.ts +65 -5
- package/dist/hydrate.js +197 -62
- package/dist/template.js +52 -2
- package/package.json +1 -1
package/dist/diagnostics.js
CHANGED
|
@@ -2,6 +2,9 @@ const OVERLAY_ID = '__zenith_runtime_error_overlay';
|
|
|
2
2
|
const MAX_MESSAGE_LENGTH = 120;
|
|
3
3
|
const MAX_HINT_LENGTH = 140;
|
|
4
4
|
const MAX_PATH_LENGTH = 120;
|
|
5
|
+
const MAX_DOCS_LINK_LENGTH = 180;
|
|
6
|
+
const MAX_SNIPPET_LENGTH = 220;
|
|
7
|
+
const MAX_STACK_LENGTH = 420;
|
|
5
8
|
const VALID_PHASES = new Set(['hydrate', 'bind', 'render', 'event']);
|
|
6
9
|
const VALID_CODES = new Set([
|
|
7
10
|
'UNRESOLVED_EXPRESSION',
|
|
@@ -9,8 +12,20 @@ const VALID_CODES = new Set([
|
|
|
9
12
|
'MARKER_MISSING',
|
|
10
13
|
'FRAGMENT_MOUNT_FAILED',
|
|
11
14
|
'BINDING_APPLY_FAILED',
|
|
12
|
-
'EVENT_HANDLER_FAILED'
|
|
15
|
+
'EVENT_HANDLER_FAILED',
|
|
16
|
+
'COMPONENT_BOOTSTRAP_FAILED',
|
|
17
|
+
'UNSAFE_MEMBER_ACCESS'
|
|
13
18
|
]);
|
|
19
|
+
const DOCS_LINK_BY_CODE = Object.freeze({
|
|
20
|
+
UNRESOLVED_EXPRESSION: '/docs/documentation/reference/reactive-binding-model.md#expression-resolution',
|
|
21
|
+
NON_RENDERABLE_VALUE: '/docs/documentation/reference/reactive-binding-model.md#renderable-values',
|
|
22
|
+
MARKER_MISSING: '/docs/documentation/reference/markers.md',
|
|
23
|
+
FRAGMENT_MOUNT_FAILED: '/docs/documentation/contracts/runtime-contract.md#fragment-contract',
|
|
24
|
+
BINDING_APPLY_FAILED: '/docs/documentation/contracts/runtime-contract.md#binding-application',
|
|
25
|
+
EVENT_HANDLER_FAILED: '/docs/documentation/contracts/runtime-contract.md#event-bindings',
|
|
26
|
+
COMPONENT_BOOTSTRAP_FAILED: '/docs/documentation/contracts/runtime-contract.md#component-bootstrap',
|
|
27
|
+
UNSAFE_MEMBER_ACCESS: '/docs/documentation/reference/reactive-binding-model.md#expression-resolution'
|
|
28
|
+
});
|
|
14
29
|
function _truncate(input, maxLength) {
|
|
15
30
|
const text = String(input ?? '');
|
|
16
31
|
if (text.length <= maxLength)
|
|
@@ -48,6 +63,49 @@ function _sanitizePath(value) {
|
|
|
48
63
|
return undefined;
|
|
49
64
|
return _truncate(compact, MAX_PATH_LENGTH);
|
|
50
65
|
}
|
|
66
|
+
function _sanitizeDocsLink(value) {
|
|
67
|
+
if (value === null || value === undefined || value === false) {
|
|
68
|
+
return undefined;
|
|
69
|
+
}
|
|
70
|
+
const compact = String(value).replace(/\s+/g, ' ').trim();
|
|
71
|
+
if (!compact)
|
|
72
|
+
return undefined;
|
|
73
|
+
return _truncate(compact, MAX_DOCS_LINK_LENGTH);
|
|
74
|
+
}
|
|
75
|
+
function _sanitizeSourceLocation(value) {
|
|
76
|
+
if (!value || typeof value !== 'object')
|
|
77
|
+
return undefined;
|
|
78
|
+
const line = Number(value.line);
|
|
79
|
+
const column = Number(value.column);
|
|
80
|
+
if (!Number.isInteger(line) || !Number.isInteger(column)) {
|
|
81
|
+
return undefined;
|
|
82
|
+
}
|
|
83
|
+
if (line < 1 || column < 1) {
|
|
84
|
+
return undefined;
|
|
85
|
+
}
|
|
86
|
+
return { line, column };
|
|
87
|
+
}
|
|
88
|
+
function _sanitizeSource(value) {
|
|
89
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) {
|
|
90
|
+
return undefined;
|
|
91
|
+
}
|
|
92
|
+
const fileRaw = value.file;
|
|
93
|
+
const file = typeof fileRaw === 'string' ? _truncate(fileRaw.trim(), 240) : '';
|
|
94
|
+
if (!file) {
|
|
95
|
+
return undefined;
|
|
96
|
+
}
|
|
97
|
+
const start = _sanitizeSourceLocation(value.start);
|
|
98
|
+
const end = _sanitizeSourceLocation(value.end);
|
|
99
|
+
const snippet = typeof value.snippet === 'string'
|
|
100
|
+
? _truncate(value.snippet.replace(/\s+/g, ' ').trim(), MAX_SNIPPET_LENGTH)
|
|
101
|
+
: undefined;
|
|
102
|
+
return {
|
|
103
|
+
file,
|
|
104
|
+
...(start ? { start } : null),
|
|
105
|
+
...(end ? { end } : null),
|
|
106
|
+
...(snippet ? { snippet } : null)
|
|
107
|
+
};
|
|
108
|
+
}
|
|
51
109
|
function _normalizeMarker(marker) {
|
|
52
110
|
if (!marker || typeof marker !== 'object')
|
|
53
111
|
return undefined;
|
|
@@ -170,9 +228,25 @@ function _renderOverlay(payload) {
|
|
|
170
228
|
if (payload.path) {
|
|
171
229
|
textLines.push(`path: ${payload.path}`);
|
|
172
230
|
}
|
|
231
|
+
if (payload.source && payload.source.file) {
|
|
232
|
+
const line = payload.source.start?.line;
|
|
233
|
+
const column = payload.source.start?.column;
|
|
234
|
+
if (Number.isInteger(line) && Number.isInteger(column)) {
|
|
235
|
+
textLines.push(`source: ${payload.source.file}:${line}:${column}`);
|
|
236
|
+
}
|
|
237
|
+
else {
|
|
238
|
+
textLines.push(`source: ${payload.source.file}`);
|
|
239
|
+
}
|
|
240
|
+
if (payload.source.snippet) {
|
|
241
|
+
textLines.push(`snippet: ${payload.source.snippet}`);
|
|
242
|
+
}
|
|
243
|
+
}
|
|
173
244
|
if (payload.hint) {
|
|
174
245
|
textLines.push(`hint: ${payload.hint}`);
|
|
175
246
|
}
|
|
247
|
+
if (payload.docsLink) {
|
|
248
|
+
textLines.push(`docs: ${payload.docsLink}`);
|
|
249
|
+
}
|
|
176
250
|
const jsonText = _safeJson(payload);
|
|
177
251
|
const panelText = textLines.join('\n');
|
|
178
252
|
let pre = overlay.querySelector('pre[data-zx-runtime-error]');
|
|
@@ -204,7 +278,9 @@ function _mapLegacyError(error, fallback) {
|
|
|
204
278
|
message: _sanitizeMessage(fallback.message || safeMessage),
|
|
205
279
|
marker: _normalizeMarker(fallback.marker),
|
|
206
280
|
path: _sanitizePath(fallback.path),
|
|
207
|
-
hint: _sanitizeHint(fallback.hint)
|
|
281
|
+
hint: _sanitizeHint(fallback.hint),
|
|
282
|
+
source: _sanitizeSource(fallback.source),
|
|
283
|
+
docsLink: _sanitizeDocsLink(fallback.docsLink)
|
|
208
284
|
};
|
|
209
285
|
if (/failed to resolve expression literal/i.test(rawMessage)) {
|
|
210
286
|
details.phase = 'bind';
|
|
@@ -232,6 +308,9 @@ function _mapLegacyError(error, fallback) {
|
|
|
232
308
|
}
|
|
233
309
|
details.hint = details.hint || 'Confirm SSR markers and client selector tables match.';
|
|
234
310
|
}
|
|
311
|
+
if (!details.docsLink) {
|
|
312
|
+
details.docsLink = DOCS_LINK_BY_CODE[details.code];
|
|
313
|
+
}
|
|
235
314
|
return details;
|
|
236
315
|
}
|
|
237
316
|
export function isZenithRuntimeError(error) {
|
|
@@ -244,11 +323,13 @@ export function createZenithRuntimeError(details, cause) {
|
|
|
244
323
|
const phase = VALID_PHASES.has(details?.phase) ? details.phase : 'hydrate';
|
|
245
324
|
const code = VALID_CODES.has(details?.code) ? details.code : 'BINDING_APPLY_FAILED';
|
|
246
325
|
const message = _sanitizeMessage(details?.message || 'Runtime failure');
|
|
326
|
+
const docsLink = _sanitizeDocsLink(details?.docsLink || DOCS_LINK_BY_CODE[code]);
|
|
247
327
|
const payload = {
|
|
248
328
|
kind: 'ZENITH_RUNTIME_ERROR',
|
|
249
329
|
phase,
|
|
250
330
|
code,
|
|
251
|
-
message
|
|
331
|
+
message,
|
|
332
|
+
...(docsLink ? { docsLink } : null)
|
|
252
333
|
};
|
|
253
334
|
const marker = _normalizeMarker(details?.marker);
|
|
254
335
|
if (marker)
|
|
@@ -259,6 +340,13 @@ export function createZenithRuntimeError(details, cause) {
|
|
|
259
340
|
const hint = _sanitizeHint(details?.hint);
|
|
260
341
|
if (hint)
|
|
261
342
|
payload.hint = hint;
|
|
343
|
+
const source = _sanitizeSource(details?.source);
|
|
344
|
+
if (source)
|
|
345
|
+
payload.source = source;
|
|
346
|
+
const stack = _sanitizeHint(details?.stack);
|
|
347
|
+
if (stack) {
|
|
348
|
+
payload.stack = _truncate(stack, MAX_STACK_LENGTH);
|
|
349
|
+
}
|
|
262
350
|
const error = new Error(`[Zenith Runtime] ${code}: ${message}`);
|
|
263
351
|
error.name = 'ZenithRuntimeError';
|
|
264
352
|
error.zenithRuntimeError = payload;
|
|
@@ -293,12 +381,18 @@ export function rethrowZenithRuntimeError(error, fallback = {}) {
|
|
|
293
381
|
const marker = !payload.marker ? _normalizeMarker(fallback.marker) : payload.marker;
|
|
294
382
|
const path = !payload.path ? _sanitizePath(fallback.path) : payload.path;
|
|
295
383
|
const hint = !payload.hint ? _sanitizeHint(fallback.hint) : payload.hint;
|
|
296
|
-
|
|
384
|
+
const source = !payload.source ? _sanitizeSource(fallback.source) : payload.source;
|
|
385
|
+
const docsLink = !payload.docsLink
|
|
386
|
+
? _sanitizeDocsLink(fallback.docsLink || DOCS_LINK_BY_CODE[payload.code])
|
|
387
|
+
: payload.docsLink;
|
|
388
|
+
if (marker || path || hint || source || docsLink) {
|
|
297
389
|
updatedPayload = {
|
|
298
390
|
...payload,
|
|
299
391
|
...(marker ? { marker } : null),
|
|
300
392
|
...(path ? { path } : null),
|
|
301
|
-
...(hint ? { hint } : null)
|
|
393
|
+
...(hint ? { hint } : null),
|
|
394
|
+
...(source ? { source } : null),
|
|
395
|
+
...(docsLink ? { docsLink } : null)
|
|
302
396
|
};
|
|
303
397
|
error.zenithRuntimeError = updatedPayload;
|
|
304
398
|
error.toJSON = () => updatedPayload;
|
package/dist/events.js
CHANGED
|
@@ -29,10 +29,11 @@ export function bindEvent(element, eventName, exprFn) {
|
|
|
29
29
|
throwZenithRuntimeError({
|
|
30
30
|
phase: 'bind',
|
|
31
31
|
code: 'BINDING_APPLY_FAILED',
|
|
32
|
-
message: `Event binding
|
|
32
|
+
message: `Event binding expected a function reference for "${eventName}"`,
|
|
33
33
|
marker: { type: `data-zx-on-${eventName}`, id: '<unknown>' },
|
|
34
34
|
path: `event:${eventName}`,
|
|
35
|
-
hint: '
|
|
35
|
+
hint: 'Use on:*={handler} and ensure forwarded props are function-valued.',
|
|
36
|
+
docsLink: '/docs/documentation/contracts/runtime-contract.md#event-bindings'
|
|
36
37
|
});
|
|
37
38
|
}
|
|
38
39
|
const wrapped = function zenithBoundEvent(event) {
|
|
@@ -46,7 +47,8 @@ export function bindEvent(element, eventName, exprFn) {
|
|
|
46
47
|
message: `Event handler failed for "${eventName}"`,
|
|
47
48
|
marker: { type: `data-zx-on-${eventName}`, id: '<unknown>' },
|
|
48
49
|
path: `event:${eventName}:${resolved.name || '<anonymous>'}`,
|
|
49
|
-
hint: 'Inspect handler logic and referenced state.'
|
|
50
|
+
hint: 'Inspect handler logic and referenced state.',
|
|
51
|
+
docsLink: '/docs/documentation/contracts/runtime-contract.md#event-bindings'
|
|
50
52
|
});
|
|
51
53
|
}
|
|
52
54
|
};
|
package/dist/hydrate.d.ts
CHANGED
|
@@ -4,14 +4,14 @@
|
|
|
4
4
|
* @param {{
|
|
5
5
|
* ir_version: number,
|
|
6
6
|
* root: Document | Element,
|
|
7
|
-
* expressions: Array<{ marker_index: number, signal_index?: number|null, state_index?: number|null, component_instance?: string|null, component_binding?: string|null, literal?: string|null }>,
|
|
8
|
-
* markers: Array<{ index: number, kind: 'text' | 'attr' | 'event', selector: string, attr?: string }>,
|
|
9
|
-
* events: Array<{ index: number, event: string, selector: string }>,
|
|
10
|
-
* refs?: Array<{ index: number, state_index: number, selector: string }>,
|
|
7
|
+
* expressions: Array<{ marker_index: number, signal_index?: number|null, state_index?: number|null, component_instance?: string|null, component_binding?: string|null, literal?: string|null, source?: { file: string, start?: { line: number, column: number }, end?: { line: number, column: number }, snippet?: string } }>,
|
|
8
|
+
* markers: Array<{ index: number, kind: 'text' | 'attr' | 'event', selector: string, attr?: string, source?: { file: string, start?: { line: number, column: number }, end?: { line: number, column: number }, snippet?: string } }>,
|
|
9
|
+
* events: Array<{ index: number, event: string, selector: string, source?: { file: string, start?: { line: number, column: number }, end?: { line: number, column: number }, snippet?: string } }>,
|
|
10
|
+
* refs?: Array<{ index: number, state_index: number, selector: string, source?: { file: string, start?: { line: number, column: number }, end?: { line: number, column: number }, snippet?: string } }>,
|
|
11
11
|
* state_values: Array<*>,
|
|
12
12
|
* state_keys?: Array<string>,
|
|
13
13
|
* signals: Array<{ id: number, kind: 'signal', state_index: number }>,
|
|
14
|
-
* components?: Array<{ instance: string, selector: string, create: Function }>
|
|
14
|
+
* components?: Array<{ instance: string, selector: string, create: Function, source?: { file: string, start?: { line: number, column: number }, end?: { line: number, column: number }, snippet?: string } }>
|
|
15
15
|
* }} payload
|
|
16
16
|
* @returns {() => void}
|
|
17
17
|
*/
|
|
@@ -25,22 +25,70 @@ export function hydrate(payload: {
|
|
|
25
25
|
component_instance?: string | null;
|
|
26
26
|
component_binding?: string | null;
|
|
27
27
|
literal?: string | null;
|
|
28
|
+
source?: {
|
|
29
|
+
file: string;
|
|
30
|
+
start?: {
|
|
31
|
+
line: number;
|
|
32
|
+
column: number;
|
|
33
|
+
};
|
|
34
|
+
end?: {
|
|
35
|
+
line: number;
|
|
36
|
+
column: number;
|
|
37
|
+
};
|
|
38
|
+
snippet?: string;
|
|
39
|
+
};
|
|
28
40
|
}>;
|
|
29
41
|
markers: Array<{
|
|
30
42
|
index: number;
|
|
31
43
|
kind: "text" | "attr" | "event";
|
|
32
44
|
selector: string;
|
|
33
45
|
attr?: string;
|
|
46
|
+
source?: {
|
|
47
|
+
file: string;
|
|
48
|
+
start?: {
|
|
49
|
+
line: number;
|
|
50
|
+
column: number;
|
|
51
|
+
};
|
|
52
|
+
end?: {
|
|
53
|
+
line: number;
|
|
54
|
+
column: number;
|
|
55
|
+
};
|
|
56
|
+
snippet?: string;
|
|
57
|
+
};
|
|
34
58
|
}>;
|
|
35
59
|
events: Array<{
|
|
36
60
|
index: number;
|
|
37
61
|
event: string;
|
|
38
62
|
selector: string;
|
|
63
|
+
source?: {
|
|
64
|
+
file: string;
|
|
65
|
+
start?: {
|
|
66
|
+
line: number;
|
|
67
|
+
column: number;
|
|
68
|
+
};
|
|
69
|
+
end?: {
|
|
70
|
+
line: number;
|
|
71
|
+
column: number;
|
|
72
|
+
};
|
|
73
|
+
snippet?: string;
|
|
74
|
+
};
|
|
39
75
|
}>;
|
|
40
76
|
refs?: Array<{
|
|
41
77
|
index: number;
|
|
42
78
|
state_index: number;
|
|
43
79
|
selector: string;
|
|
80
|
+
source?: {
|
|
81
|
+
file: string;
|
|
82
|
+
start?: {
|
|
83
|
+
line: number;
|
|
84
|
+
column: number;
|
|
85
|
+
};
|
|
86
|
+
end?: {
|
|
87
|
+
line: number;
|
|
88
|
+
column: number;
|
|
89
|
+
};
|
|
90
|
+
snippet?: string;
|
|
91
|
+
};
|
|
44
92
|
}>;
|
|
45
93
|
state_values: Array<any>;
|
|
46
94
|
state_keys?: Array<string>;
|
|
@@ -53,5 +101,17 @@ export function hydrate(payload: {
|
|
|
53
101
|
instance: string;
|
|
54
102
|
selector: string;
|
|
55
103
|
create: Function;
|
|
104
|
+
source?: {
|
|
105
|
+
file: string;
|
|
106
|
+
start?: {
|
|
107
|
+
line: number;
|
|
108
|
+
column: number;
|
|
109
|
+
};
|
|
110
|
+
end?: {
|
|
111
|
+
line: number;
|
|
112
|
+
column: number;
|
|
113
|
+
};
|
|
114
|
+
snippet?: string;
|
|
115
|
+
};
|
|
56
116
|
}>;
|
|
57
117
|
}): () => void;
|
package/dist/hydrate.js
CHANGED
|
@@ -21,20 +21,27 @@ const BOOLEAN_ATTRIBUTES = new Set([
|
|
|
21
21
|
]);
|
|
22
22
|
const STRICT_MEMBER_CHAIN_LITERAL_RE = /^(?:true|false|null|undefined|[A-Za-z_$][A-Za-z0-9_$]*(\.[A-Za-z_$][A-Za-z0-9_$]*)*)$/;
|
|
23
23
|
const UNSAFE_MEMBER_KEYS = new Set(['__proto__', 'prototype', 'constructor']);
|
|
24
|
+
const DOCS_LINKS = Object.freeze({
|
|
25
|
+
eventBinding: '/docs/documentation/contracts/runtime-contract.md#event-bindings',
|
|
26
|
+
expressionScope: '/docs/documentation/reference/reactive-binding-model.md#expression-resolution',
|
|
27
|
+
markerTable: '/docs/documentation/reference/markers.md',
|
|
28
|
+
componentBootstrap: '/docs/documentation/contracts/runtime-contract.md#component-bootstrap',
|
|
29
|
+
refs: '/docs/documentation/reference/reactive-binding-model.md#refs-and-mount'
|
|
30
|
+
});
|
|
24
31
|
/**
|
|
25
32
|
* Hydrate a pre-rendered DOM tree using explicit payload tables.
|
|
26
33
|
*
|
|
27
34
|
* @param {{
|
|
28
35
|
* ir_version: number,
|
|
29
36
|
* root: Document | Element,
|
|
30
|
-
* expressions: Array<{ marker_index: number, signal_index?: number|null, state_index?: number|null, component_instance?: string|null, component_binding?: string|null, literal?: string|null }>,
|
|
31
|
-
* markers: Array<{ index: number, kind: 'text' | 'attr' | 'event', selector: string, attr?: string }>,
|
|
32
|
-
* events: Array<{ index: number, event: string, selector: string }>,
|
|
33
|
-
* refs?: Array<{ index: number, state_index: number, selector: string }>,
|
|
37
|
+
* expressions: Array<{ marker_index: number, signal_index?: number|null, state_index?: number|null, component_instance?: string|null, component_binding?: string|null, literal?: string|null, source?: { file: string, start?: { line: number, column: number }, end?: { line: number, column: number }, snippet?: string } }>,
|
|
38
|
+
* markers: Array<{ index: number, kind: 'text' | 'attr' | 'event', selector: string, attr?: string, source?: { file: string, start?: { line: number, column: number }, end?: { line: number, column: number }, snippet?: string } }>,
|
|
39
|
+
* events: Array<{ index: number, event: string, selector: string, source?: { file: string, start?: { line: number, column: number }, end?: { line: number, column: number }, snippet?: string } }>,
|
|
40
|
+
* refs?: Array<{ index: number, state_index: number, selector: string, source?: { file: string, start?: { line: number, column: number }, end?: { line: number, column: number }, snippet?: string } }>,
|
|
34
41
|
* state_values: Array<*>,
|
|
35
42
|
* state_keys?: Array<string>,
|
|
36
43
|
* signals: Array<{ id: number, kind: 'signal', state_index: number }>,
|
|
37
|
-
* components?: Array<{ instance: string, selector: string, create: Function }>
|
|
44
|
+
* components?: Array<{ instance: string, selector: string, create: Function, source?: { file: string, start?: { line: number, column: number }, end?: { line: number, column: number }, snippet?: string } }>
|
|
38
45
|
* }} payload
|
|
39
46
|
* @returns {() => void}
|
|
40
47
|
*/
|
|
@@ -61,7 +68,7 @@ export function hydrate(payload) {
|
|
|
61
68
|
for (let i = 0; i < refs.length; i++) {
|
|
62
69
|
const refBinding = refs[i];
|
|
63
70
|
const targetRef = stateValues[refBinding.state_index];
|
|
64
|
-
const nodes = _resolveNodes(root, refBinding.selector, refBinding.index, 'ref');
|
|
71
|
+
const nodes = _resolveNodes(root, refBinding.selector, refBinding.index, 'ref', refBinding.source);
|
|
65
72
|
targetRef.current = nodes[0] || null;
|
|
66
73
|
hydratedRefs.push(targetRef);
|
|
67
74
|
}
|
|
@@ -78,38 +85,56 @@ export function hydrate(payload) {
|
|
|
78
85
|
component: component.instance,
|
|
79
86
|
route
|
|
80
87
|
}));
|
|
81
|
-
const hosts = _resolveNodes(root, component.selector, i, 'component');
|
|
88
|
+
const hosts = _resolveNodes(root, component.selector, i, 'component', component.source);
|
|
82
89
|
for (let j = 0; j < hosts.length; j++) {
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
90
|
+
try {
|
|
91
|
+
const componentScope = createSideEffectScope(`${component.instance}:${j}`);
|
|
92
|
+
const runtimeApi = {
|
|
93
|
+
signal,
|
|
94
|
+
state,
|
|
95
|
+
zeneffect(effect, dependenciesOrOptions) {
|
|
96
|
+
return zeneffect(effect, dependenciesOrOptions, componentScope);
|
|
97
|
+
},
|
|
98
|
+
zenEffect(effect, options) {
|
|
99
|
+
return zenEffect(effect, options, componentScope);
|
|
100
|
+
},
|
|
101
|
+
zenMount(callback) {
|
|
102
|
+
return zenMount(callback, componentScope);
|
|
103
|
+
}
|
|
104
|
+
};
|
|
105
|
+
const instance = component.create(hosts[j], resolvedProps, runtimeApi);
|
|
106
|
+
if (!instance || typeof instance !== 'object') {
|
|
107
|
+
throw new Error(`[Zenith Runtime] component factory for ${component.instance} must return an object`);
|
|
108
|
+
}
|
|
109
|
+
if (typeof instance.mount === 'function') {
|
|
110
|
+
instance.mount();
|
|
111
|
+
}
|
|
112
|
+
activateSideEffectScope(componentScope);
|
|
113
|
+
_registerDisposer(() => {
|
|
114
|
+
disposeSideEffectScope(componentScope);
|
|
115
|
+
if (typeof instance.destroy === 'function') {
|
|
116
|
+
instance.destroy();
|
|
117
|
+
}
|
|
118
|
+
});
|
|
119
|
+
if (instance.bindings && typeof instance.bindings === 'object') {
|
|
120
|
+
componentBindings[component.instance] = instance.bindings;
|
|
95
121
|
}
|
|
96
|
-
};
|
|
97
|
-
const instance = component.create(hosts[j], resolvedProps, runtimeApi);
|
|
98
|
-
if (!instance || typeof instance !== 'object') {
|
|
99
|
-
throw new Error(`[Zenith Runtime] component factory for ${component.instance} must return an object`);
|
|
100
|
-
}
|
|
101
|
-
if (typeof instance.mount === 'function') {
|
|
102
|
-
instance.mount();
|
|
103
122
|
}
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
123
|
+
catch (error) {
|
|
124
|
+
try {
|
|
125
|
+
rethrowZenithRuntimeError(error, {
|
|
126
|
+
phase: 'hydrate',
|
|
127
|
+
code: 'COMPONENT_BOOTSTRAP_FAILED',
|
|
128
|
+
message: `Component bootstrap failed for "${component.instance}"`,
|
|
129
|
+
path: `component[${component.instance}]`,
|
|
130
|
+
hint: 'Fix the failing component and refresh; other components continue mounting.',
|
|
131
|
+
docsLink: DOCS_LINKS.componentBootstrap,
|
|
132
|
+
source: component.source
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
catch {
|
|
136
|
+
// Fault containment: continue mounting remaining components.
|
|
109
137
|
}
|
|
110
|
-
});
|
|
111
|
-
if (instance.bindings && typeof instance.bindings === 'object') {
|
|
112
|
-
componentBindings[component.instance] = instance.bindings;
|
|
113
138
|
}
|
|
114
139
|
}
|
|
115
140
|
}
|
|
@@ -134,9 +159,9 @@ export function hydrate(payload) {
|
|
|
134
159
|
if (marker.kind === 'event') {
|
|
135
160
|
continue;
|
|
136
161
|
}
|
|
137
|
-
const nodes = _resolveNodes(root, marker.selector, marker.index, marker.kind);
|
|
162
|
+
const nodes = _resolveNodes(root, marker.selector, marker.index, marker.kind, marker.source);
|
|
138
163
|
markerNodesByIndex.set(marker.index, nodes);
|
|
139
|
-
const value = _evaluateExpression(expressions[marker.index], stateValues, stateKeys, signalMap, componentBindings, params, ssrData, marker.kind, props, exprFns);
|
|
164
|
+
const value = _evaluateExpression(expressions[marker.index], stateValues, stateKeys, signalMap, componentBindings, params, ssrData, marker.kind, props, exprFns, marker, null);
|
|
140
165
|
_applyMarkerValue(nodes, marker, value);
|
|
141
166
|
}
|
|
142
167
|
for (let i = 0; i < expressions.length; i++) {
|
|
@@ -149,9 +174,9 @@ export function hydrate(payload) {
|
|
|
149
174
|
if (!marker || marker.kind === 'event') {
|
|
150
175
|
return;
|
|
151
176
|
}
|
|
152
|
-
const nodes = markerNodesByIndex.get(index) || _resolveNodes(root, marker.selector, marker.index, marker.kind);
|
|
177
|
+
const nodes = markerNodesByIndex.get(index) || _resolveNodes(root, marker.selector, marker.index, marker.kind, marker.source);
|
|
153
178
|
markerNodesByIndex.set(index, nodes);
|
|
154
|
-
const value = _evaluateExpression(expressions[index], stateValues, stateKeys, signalMap, componentBindings, params, ssrData, marker.kind, props, exprFns);
|
|
179
|
+
const value = _evaluateExpression(expressions[index], stateValues, stateKeys, signalMap, componentBindings, params, ssrData, marker.kind, props, exprFns, marker, null);
|
|
155
180
|
_applyMarkerValue(nodes, marker, value);
|
|
156
181
|
}
|
|
157
182
|
const dependentMarkersBySignal = new Map();
|
|
@@ -225,16 +250,21 @@ export function hydrate(payload) {
|
|
|
225
250
|
throw new Error(`[Zenith Runtime] duplicate event index ${eventBinding.index}`);
|
|
226
251
|
}
|
|
227
252
|
eventIndices.add(eventBinding.index);
|
|
228
|
-
const
|
|
229
|
-
const
|
|
253
|
+
const marker = markerByIndex.get(eventBinding.index) || null;
|
|
254
|
+
const nodes = _resolveNodes(root, eventBinding.selector, eventBinding.index, 'event', eventBinding.source || marker?.source);
|
|
255
|
+
const expressionBinding = expressions[eventBinding.index];
|
|
256
|
+
const handler = _evaluateExpression(expressionBinding, stateValues, stateKeys, signalMap, componentBindings, params, ssrData, 'event', props || {}, exprFns, marker, eventBinding);
|
|
230
257
|
if (typeof handler !== 'function') {
|
|
258
|
+
const passedExpression = _describeBindingExpression(expressionBinding);
|
|
231
259
|
throwZenithRuntimeError({
|
|
232
260
|
phase: 'bind',
|
|
233
261
|
code: 'BINDING_APPLY_FAILED',
|
|
234
|
-
message: `Event binding at index ${eventBinding.index}
|
|
262
|
+
message: `Event binding at index ${eventBinding.index} expected a function reference. You passed: ${passedExpression}`,
|
|
235
263
|
marker: { type: `data-zx-on-${eventBinding.event}`, id: eventBinding.index },
|
|
236
264
|
path: `event[${eventBinding.index}].${eventBinding.event}`,
|
|
237
|
-
hint: '
|
|
265
|
+
hint: 'Use on:*={handler} or ensure the forwarded prop is a function.',
|
|
266
|
+
docsLink: DOCS_LINKS.eventBinding,
|
|
267
|
+
source: _resolveBindingSource(expressionBinding, marker, eventBinding)
|
|
238
268
|
});
|
|
239
269
|
}
|
|
240
270
|
for (let j = 0; j < nodes.length; j++) {
|
|
@@ -250,7 +280,9 @@ export function hydrate(payload) {
|
|
|
250
280
|
message: `Event handler failed for "${eventBinding.event}"`,
|
|
251
281
|
marker: { type: `data-zx-on-${eventBinding.event}`, id: eventBinding.index },
|
|
252
282
|
path: `event[${eventBinding.index}].${eventBinding.event}`,
|
|
253
|
-
hint: 'Inspect the handler body and referenced state.'
|
|
283
|
+
hint: 'Inspect the handler body and referenced state.',
|
|
284
|
+
docsLink: DOCS_LINKS.eventBinding,
|
|
285
|
+
source: _resolveBindingSource(expressionBinding, marker, eventBinding)
|
|
254
286
|
});
|
|
255
287
|
}
|
|
256
288
|
};
|
|
@@ -314,7 +346,8 @@ export function hydrate(payload) {
|
|
|
314
346
|
rethrowZenithRuntimeError(error, {
|
|
315
347
|
phase: 'hydrate',
|
|
316
348
|
code: 'BINDING_APPLY_FAILED',
|
|
317
|
-
hint: 'Inspect marker tables, expression bindings, and the runtime overlay diagnostics.'
|
|
349
|
+
hint: 'Inspect marker tables, expression bindings, and the runtime overlay diagnostics.',
|
|
350
|
+
docsLink: DOCS_LINKS.markerTable
|
|
318
351
|
});
|
|
319
352
|
}
|
|
320
353
|
}
|
|
@@ -390,6 +423,7 @@ function _validatePayload(payload) {
|
|
|
390
423
|
throw new Error(`[Zenith Runtime] expression at position ${i} has invalid fn_index`);
|
|
391
424
|
}
|
|
392
425
|
}
|
|
426
|
+
_assertValidSourceSpan(expression.source, `expression[${i}]`);
|
|
393
427
|
if (expression.signal_indices !== undefined) {
|
|
394
428
|
if (!Array.isArray(expression.signal_indices)) {
|
|
395
429
|
throw new Error(`[Zenith Runtime] expression at position ${i} must provide signal_indices[]`);
|
|
@@ -421,6 +455,7 @@ function _validatePayload(payload) {
|
|
|
421
455
|
if (marker.kind === 'attr' && (typeof marker.attr !== 'string' || marker.attr.length === 0)) {
|
|
422
456
|
throw new Error(`[Zenith Runtime] attr marker at position ${i} requires attr name`);
|
|
423
457
|
}
|
|
458
|
+
_assertValidSourceSpan(marker.source, `marker[${i}]`);
|
|
424
459
|
}
|
|
425
460
|
for (let i = 0; i < events.length; i++) {
|
|
426
461
|
const eventBinding = events[i];
|
|
@@ -436,6 +471,7 @@ function _validatePayload(payload) {
|
|
|
436
471
|
if (typeof eventBinding.selector !== 'string' || eventBinding.selector.length === 0) {
|
|
437
472
|
throw new Error(`[Zenith Runtime] event binding at position ${i} requires selector`);
|
|
438
473
|
}
|
|
474
|
+
_assertValidSourceSpan(eventBinding.source, `event[${i}]`);
|
|
439
475
|
}
|
|
440
476
|
for (let i = 0; i < refs.length; i++) {
|
|
441
477
|
const refBinding = refs[i];
|
|
@@ -453,6 +489,7 @@ function _validatePayload(payload) {
|
|
|
453
489
|
if (typeof refBinding.selector !== 'string' || refBinding.selector.length === 0) {
|
|
454
490
|
throw new Error(`[Zenith Runtime] ref binding at position ${i} requires selector`);
|
|
455
491
|
}
|
|
492
|
+
_assertValidSourceSpan(refBinding.source, `ref[${i}]`);
|
|
456
493
|
const candidate = stateValues[refBinding.state_index];
|
|
457
494
|
if (!candidate || typeof candidate !== 'object' || !Object.prototype.hasOwnProperty.call(candidate, 'current')) {
|
|
458
495
|
throw new Error(`[Zenith Runtime] ref binding at position ${i} must resolve to a ref-like object`);
|
|
@@ -490,6 +527,7 @@ function _validatePayload(payload) {
|
|
|
490
527
|
if (typeof component.create !== 'function') {
|
|
491
528
|
throw new Error(`[Zenith Runtime] component at position ${i} requires create() function`);
|
|
492
529
|
}
|
|
530
|
+
_assertValidSourceSpan(component.source, `component[${i}]`);
|
|
493
531
|
}
|
|
494
532
|
if (payload.params !== undefined) {
|
|
495
533
|
if (!payload.params || typeof payload.params !== 'object' || Array.isArray(payload.params)) {
|
|
@@ -554,6 +592,36 @@ function _validatePayload(payload) {
|
|
|
554
592
|
};
|
|
555
593
|
return Object.freeze(validatedPayload);
|
|
556
594
|
}
|
|
595
|
+
function _assertValidSourceSpan(source, contextLabel) {
|
|
596
|
+
if (source === undefined || source === null) {
|
|
597
|
+
return;
|
|
598
|
+
}
|
|
599
|
+
if (!source || typeof source !== 'object' || Array.isArray(source)) {
|
|
600
|
+
throw new Error(`[Zenith Runtime] ${contextLabel}.source must be an object`);
|
|
601
|
+
}
|
|
602
|
+
if (typeof source.file !== 'string' || source.file.length === 0) {
|
|
603
|
+
throw new Error(`[Zenith Runtime] ${contextLabel}.source.file must be a non-empty string`);
|
|
604
|
+
}
|
|
605
|
+
const points = ['start', 'end'];
|
|
606
|
+
for (let i = 0; i < points.length; i++) {
|
|
607
|
+
const point = source[points[i]];
|
|
608
|
+
if (point === undefined || point === null) {
|
|
609
|
+
continue;
|
|
610
|
+
}
|
|
611
|
+
if (!point || typeof point !== 'object' || Array.isArray(point)) {
|
|
612
|
+
throw new Error(`[Zenith Runtime] ${contextLabel}.source.${points[i]} must be an object`);
|
|
613
|
+
}
|
|
614
|
+
if (!Number.isInteger(point.line) || point.line < 1) {
|
|
615
|
+
throw new Error(`[Zenith Runtime] ${contextLabel}.source.${points[i]}.line must be >= 1`);
|
|
616
|
+
}
|
|
617
|
+
if (!Number.isInteger(point.column) || point.column < 1) {
|
|
618
|
+
throw new Error(`[Zenith Runtime] ${contextLabel}.source.${points[i]}.column must be >= 1`);
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
if (source.snippet !== undefined && source.snippet !== null && typeof source.snippet !== 'string') {
|
|
622
|
+
throw new Error(`[Zenith Runtime] ${contextLabel}.source.snippet must be a string when provided`);
|
|
623
|
+
}
|
|
624
|
+
}
|
|
557
625
|
function _resolveComponentProps(propTable, signalMap, context = {}) {
|
|
558
626
|
if (!Array.isArray(propTable)) {
|
|
559
627
|
throw new Error('[Zenith Runtime] component props must be an array');
|
|
@@ -592,16 +660,21 @@ function _resolveComponentProps(propTable, signalMap, context = {}) {
|
|
|
592
660
|
}
|
|
593
661
|
return resolved;
|
|
594
662
|
}
|
|
595
|
-
function _resolveNodes(root, selector, index, kind) {
|
|
663
|
+
function _resolveNodes(root, selector, index, kind, source = undefined) {
|
|
596
664
|
const nodes = root.querySelectorAll(selector);
|
|
597
665
|
if (!nodes || nodes.length === 0) {
|
|
666
|
+
const isRef = kind === 'ref';
|
|
598
667
|
throwZenithRuntimeError({
|
|
599
668
|
phase: 'bind',
|
|
600
669
|
code: 'MARKER_MISSING',
|
|
601
670
|
message: `Unresolved ${kind} marker index ${index}`,
|
|
602
671
|
marker: { type: kind, id: index },
|
|
603
672
|
path: `selector:${selector}`,
|
|
604
|
-
hint:
|
|
673
|
+
hint: isRef
|
|
674
|
+
? 'Use ref + zenMount and ensure the ref is bound in markup before mount.'
|
|
675
|
+
: 'Confirm SSR marker attributes and runtime selector tables match.',
|
|
676
|
+
docsLink: isRef ? DOCS_LINKS.refs : DOCS_LINKS.markerTable,
|
|
677
|
+
source
|
|
605
678
|
});
|
|
606
679
|
}
|
|
607
680
|
return nodes;
|
|
@@ -618,7 +691,7 @@ function _resolveExpressionSignalIndices(binding) {
|
|
|
618
691
|
}
|
|
619
692
|
return [];
|
|
620
693
|
}
|
|
621
|
-
function _evaluateExpression(binding, stateValues, stateKeys, signalMap, componentBindings, params, ssrData, mode, props, exprFns) {
|
|
694
|
+
function _evaluateExpression(binding, stateValues, stateKeys, signalMap, componentBindings, params, ssrData, mode, props, exprFns, markerBinding = null, eventBinding = null) {
|
|
622
695
|
if (binding.fn_index != null && binding.fn_index !== undefined) {
|
|
623
696
|
const fns = Array.isArray(exprFns) ? exprFns : [];
|
|
624
697
|
const fn = fns[binding.fn_index];
|
|
@@ -678,7 +751,7 @@ function _evaluateExpression(binding, stateValues, stateKeys, signalMap, compone
|
|
|
678
751
|
if (binding.literal !== null && binding.literal !== undefined) {
|
|
679
752
|
if (typeof binding.literal === 'string') {
|
|
680
753
|
const trimmedLiteral = binding.literal.trim();
|
|
681
|
-
const strictMemberValue = _resolveStrictMemberChainLiteral(trimmedLiteral, stateValues, stateKeys, params, ssrData, mode, props, binding.marker_index);
|
|
754
|
+
const strictMemberValue = _resolveStrictMemberChainLiteral(trimmedLiteral, stateValues, stateKeys, params, ssrData, mode, props, binding.marker_index, _resolveBindingSource(binding, markerBinding, eventBinding));
|
|
682
755
|
if (strictMemberValue !== UNRESOLVED_LITERAL) {
|
|
683
756
|
return strictMemberValue;
|
|
684
757
|
}
|
|
@@ -696,16 +769,23 @@ function _evaluateExpression(binding, stateValues, stateKeys, signalMap, compone
|
|
|
696
769
|
return primitiveValue;
|
|
697
770
|
}
|
|
698
771
|
if (_isLikelyExpressionLiteral(trimmedLiteral)) {
|
|
772
|
+
const missingIdentifier = _extractMissingIdentifier(trimmedLiteral);
|
|
699
773
|
throwZenithRuntimeError({
|
|
700
774
|
phase: 'bind',
|
|
701
775
|
code: 'UNRESOLVED_EXPRESSION',
|
|
702
|
-
message:
|
|
776
|
+
message: missingIdentifier
|
|
777
|
+
? `Unresolved expression identifier "${missingIdentifier}" in ${_truncateLiteralForError(trimmedLiteral)}`
|
|
778
|
+
: `Failed to resolve expression literal: ${_truncateLiteralForError(trimmedLiteral)}`,
|
|
703
779
|
marker: {
|
|
704
780
|
type: _markerTypeForError(mode),
|
|
705
781
|
id: binding.marker_index
|
|
706
782
|
},
|
|
707
783
|
path: `expression[${binding.marker_index}]`,
|
|
708
|
-
hint:
|
|
784
|
+
hint: missingIdentifier
|
|
785
|
+
? `Declare "${missingIdentifier}" in scope or pass it via props.`
|
|
786
|
+
: 'Declare the missing identifier in scope or pass it via props.',
|
|
787
|
+
docsLink: DOCS_LINKS.expressionScope,
|
|
788
|
+
source: _resolveBindingSource(binding, markerBinding, eventBinding)
|
|
709
789
|
});
|
|
710
790
|
}
|
|
711
791
|
}
|
|
@@ -713,7 +793,7 @@ function _evaluateExpression(binding, stateValues, stateKeys, signalMap, compone
|
|
|
713
793
|
}
|
|
714
794
|
return '';
|
|
715
795
|
}
|
|
716
|
-
function _throwUnresolvedMemberChainError(literal, markerIndex, mode, pathSuffix, hint) {
|
|
796
|
+
function _throwUnresolvedMemberChainError(literal, markerIndex, mode, pathSuffix, hint, source) {
|
|
717
797
|
throwZenithRuntimeError({
|
|
718
798
|
phase: 'bind',
|
|
719
799
|
code: 'UNRESOLVED_EXPRESSION',
|
|
@@ -723,10 +803,12 @@ function _throwUnresolvedMemberChainError(literal, markerIndex, mode, pathSuffix
|
|
|
723
803
|
id: markerIndex
|
|
724
804
|
},
|
|
725
805
|
path: `marker[${markerIndex}].${pathSuffix}`,
|
|
726
|
-
hint
|
|
806
|
+
hint,
|
|
807
|
+
docsLink: DOCS_LINKS.expressionScope,
|
|
808
|
+
source
|
|
727
809
|
});
|
|
728
810
|
}
|
|
729
|
-
function _resolveStrictMemberChainLiteral(literal, stateValues, stateKeys, params, ssrData, mode, props, markerIndex) {
|
|
811
|
+
function _resolveStrictMemberChainLiteral(literal, stateValues, stateKeys, params, ssrData, mode, props, markerIndex, source) {
|
|
730
812
|
if (typeof literal !== 'string' || !STRICT_MEMBER_CHAIN_LITERAL_RE.test(literal)) {
|
|
731
813
|
return UNRESOLVED_LITERAL;
|
|
732
814
|
}
|
|
@@ -742,7 +824,7 @@ function _resolveStrictMemberChainLiteral(literal, stateValues, stateKeys, param
|
|
|
742
824
|
const baseIdentifier = segments[0];
|
|
743
825
|
const scope = _buildLiteralScope(stateValues, stateKeys, params, ssrData, mode, props);
|
|
744
826
|
if (!Object.prototype.hasOwnProperty.call(scope, baseIdentifier)) {
|
|
745
|
-
_throwUnresolvedMemberChainError(literal, markerIndex, mode, `expression.${baseIdentifier}`, `Base identifier "${baseIdentifier}" is not bound. Check props/data/params and declared state keys
|
|
827
|
+
_throwUnresolvedMemberChainError(literal, markerIndex, mode, `expression.${baseIdentifier}`, `Base identifier "${baseIdentifier}" is not bound. Check props/data/params and declared state keys.`, source);
|
|
746
828
|
}
|
|
747
829
|
let cursor = scope[baseIdentifier];
|
|
748
830
|
let traversedPath = baseIdentifier;
|
|
@@ -754,18 +836,20 @@ function _resolveStrictMemberChainLiteral(literal, stateValues, stateKeys, param
|
|
|
754
836
|
code: 'UNSAFE_MEMBER_ACCESS',
|
|
755
837
|
message: `Blocked unsafe member access: ${segment} in path "${literal}"`,
|
|
756
838
|
path: `marker[${markerIndex}].expression.${literal}`,
|
|
757
|
-
hint: 'Property access to __proto__, prototype, and constructor is forbidden.'
|
|
839
|
+
hint: 'Property access to __proto__, prototype, and constructor is forbidden.',
|
|
840
|
+
docsLink: DOCS_LINKS.expressionScope,
|
|
841
|
+
source
|
|
758
842
|
});
|
|
759
843
|
}
|
|
760
844
|
if (cursor === null || cursor === undefined) {
|
|
761
|
-
_throwUnresolvedMemberChainError(literal, markerIndex, mode, `expression.${traversedPath}.${segment}`, `Cannot read "${segment}" from ${traversedPath} because it is null or undefined
|
|
845
|
+
_throwUnresolvedMemberChainError(literal, markerIndex, mode, `expression.${traversedPath}.${segment}`, `Cannot read "${segment}" from ${traversedPath} because it is null or undefined.`, source);
|
|
762
846
|
}
|
|
763
847
|
const cursorType = typeof cursor;
|
|
764
848
|
if (cursorType !== 'object' && cursorType !== 'function') {
|
|
765
|
-
_throwUnresolvedMemberChainError(literal, markerIndex, mode, `expression.${traversedPath}.${segment}`, `Cannot read "${segment}" from ${traversedPath} because it resolved to a ${cursorType}
|
|
849
|
+
_throwUnresolvedMemberChainError(literal, markerIndex, mode, `expression.${traversedPath}.${segment}`, `Cannot read "${segment}" from ${traversedPath} because it resolved to a ${cursorType}.`, source);
|
|
766
850
|
}
|
|
767
851
|
if (!Object.prototype.hasOwnProperty.call(cursor, segment)) {
|
|
768
|
-
_throwUnresolvedMemberChainError(literal, markerIndex, mode, `expression.${traversedPath}.${segment}`, `Missing member "${segment}" on ${traversedPath}. Check your bindings
|
|
852
|
+
_throwUnresolvedMemberChainError(literal, markerIndex, mode, `expression.${traversedPath}.${segment}`, `Missing member "${segment}" on ${traversedPath}. Check your bindings.`, source);
|
|
769
853
|
}
|
|
770
854
|
cursor = cursor[segment];
|
|
771
855
|
traversedPath = `${traversedPath}.${segment}`;
|
|
@@ -898,6 +982,52 @@ function _isLikelyExpressionLiteral(literal) {
|
|
|
898
982
|
}
|
|
899
983
|
return /=>|[()[\]{}<>=?:.+\-*/%|&!]/.test(trimmed);
|
|
900
984
|
}
|
|
985
|
+
function _extractMissingIdentifier(literal) {
|
|
986
|
+
if (typeof literal !== 'string') {
|
|
987
|
+
return null;
|
|
988
|
+
}
|
|
989
|
+
const match = literal.trim().match(/^([A-Za-z_$][A-Za-z0-9_$]*)/);
|
|
990
|
+
if (!match) {
|
|
991
|
+
return null;
|
|
992
|
+
}
|
|
993
|
+
const candidate = match[1];
|
|
994
|
+
if (candidate === 'true' || candidate === 'false' || candidate === 'null' || candidate === 'undefined') {
|
|
995
|
+
return null;
|
|
996
|
+
}
|
|
997
|
+
return candidate;
|
|
998
|
+
}
|
|
999
|
+
function _resolveBindingSource(binding, markerBinding, eventBinding) {
|
|
1000
|
+
const candidates = [
|
|
1001
|
+
binding?.source,
|
|
1002
|
+
eventBinding?.source,
|
|
1003
|
+
markerBinding?.source
|
|
1004
|
+
];
|
|
1005
|
+
for (let i = 0; i < candidates.length; i++) {
|
|
1006
|
+
const candidate = candidates[i];
|
|
1007
|
+
if (candidate && typeof candidate === 'object' && typeof candidate.file === 'string') {
|
|
1008
|
+
return candidate;
|
|
1009
|
+
}
|
|
1010
|
+
}
|
|
1011
|
+
return undefined;
|
|
1012
|
+
}
|
|
1013
|
+
function _describeBindingExpression(binding) {
|
|
1014
|
+
if (!binding || typeof binding !== 'object') {
|
|
1015
|
+
return '<unknown>';
|
|
1016
|
+
}
|
|
1017
|
+
if (typeof binding.literal === 'string' && binding.literal.trim().length > 0) {
|
|
1018
|
+
return _truncateLiteralForError(binding.literal.trim());
|
|
1019
|
+
}
|
|
1020
|
+
if (Number.isInteger(binding.state_index)) {
|
|
1021
|
+
return `state[${binding.state_index}]`;
|
|
1022
|
+
}
|
|
1023
|
+
if (Number.isInteger(binding.signal_index)) {
|
|
1024
|
+
return `signal[${binding.signal_index}]`;
|
|
1025
|
+
}
|
|
1026
|
+
if (typeof binding.component_instance === 'string' && typeof binding.component_binding === 'string') {
|
|
1027
|
+
return `${binding.component_instance}.${binding.component_binding}`;
|
|
1028
|
+
}
|
|
1029
|
+
return '<unknown expression>';
|
|
1030
|
+
}
|
|
901
1031
|
function _markerTypeForError(kind) {
|
|
902
1032
|
if (kind === 'text')
|
|
903
1033
|
return 'data-zx-e';
|
|
@@ -1337,7 +1467,9 @@ function _applyMarkerValue(nodes, marker, value) {
|
|
|
1337
1467
|
path: marker.kind === 'attr'
|
|
1338
1468
|
? `${markerPath}.attr.${marker.attr}`
|
|
1339
1469
|
: `${markerPath}.${marker.kind}`,
|
|
1340
|
-
hint: 'Check the binding value type and marker mapping.'
|
|
1470
|
+
hint: 'Check the binding value type and marker mapping.',
|
|
1471
|
+
docsLink: DOCS_LINKS.markerTable,
|
|
1472
|
+
source: marker.source
|
|
1341
1473
|
});
|
|
1342
1474
|
}
|
|
1343
1475
|
}
|
|
@@ -1396,7 +1528,8 @@ function _mountStructuralFragment(container, value, rootPath = 'renderable') {
|
|
|
1396
1528
|
code: 'FRAGMENT_MOUNT_FAILED',
|
|
1397
1529
|
message: 'Fragment mount failed',
|
|
1398
1530
|
path: rootPath,
|
|
1399
|
-
hint: 'Verify fragment values and nested renderable arrays.'
|
|
1531
|
+
hint: 'Verify fragment values and nested renderable arrays.',
|
|
1532
|
+
docsLink: DOCS_LINKS.markerTable
|
|
1400
1533
|
});
|
|
1401
1534
|
}
|
|
1402
1535
|
container.__z_unmounts = newUnmounts;
|
|
@@ -1410,7 +1543,8 @@ function _coerceText(value, path = 'renderable') {
|
|
|
1410
1543
|
code: 'NON_RENDERABLE_VALUE',
|
|
1411
1544
|
message: `Zenith Render Error: non-renderable function at ${path}. Use map() to render fields.`,
|
|
1412
1545
|
path,
|
|
1413
|
-
hint: 'Convert functions into explicit event handlers or renderable text.'
|
|
1546
|
+
hint: 'Convert functions into explicit event handlers or renderable text.',
|
|
1547
|
+
docsLink: DOCS_LINKS.expressionScope
|
|
1414
1548
|
});
|
|
1415
1549
|
}
|
|
1416
1550
|
if (value && typeof value === 'object') {
|
|
@@ -1419,7 +1553,8 @@ function _coerceText(value, path = 'renderable') {
|
|
|
1419
1553
|
code: 'NON_RENDERABLE_VALUE',
|
|
1420
1554
|
message: `Zenith Render Error: non-renderable object at ${path}. Use map() to render fields.`,
|
|
1421
1555
|
path,
|
|
1422
|
-
hint: 'Use map() to render object fields into nodes.'
|
|
1556
|
+
hint: 'Use map() to render object fields into nodes.',
|
|
1557
|
+
docsLink: DOCS_LINKS.expressionScope
|
|
1423
1558
|
});
|
|
1424
1559
|
}
|
|
1425
1560
|
return String(value);
|
package/dist/template.js
CHANGED
|
@@ -113,6 +113,8 @@ const RUNTIME_DEV_CLIENT_SOURCE = `(() => {
|
|
|
113
113
|
}
|
|
114
114
|
|
|
115
115
|
let cssSwapEpoch = 0;
|
|
116
|
+
const CSS_UPDATE_DOCS = '/docs/documentation/contracts/hmr-v1-contract.md#css-updates';
|
|
117
|
+
const BUILD_ERROR_DOCS = '/docs/documentation/contracts/runtime-contract.md#diagnostics';
|
|
116
118
|
|
|
117
119
|
function withCacheBuster(nextHref) {
|
|
118
120
|
const separator = nextHref.includes('?') ? '&' : '?';
|
|
@@ -153,7 +155,7 @@ const RUNTIME_DEV_CLIENT_SOURCE = `(() => {
|
|
|
153
155
|
|
|
154
156
|
function scheduleCssRetry(previousHref, attempt) {
|
|
155
157
|
if (attempt >= 3) {
|
|
156
|
-
|
|
158
|
+
reportBuildFailure('CSS update failed (404): server build not ready', CSS_UPDATE_DOCS);
|
|
157
159
|
return;
|
|
158
160
|
}
|
|
159
161
|
const delayMs = (attempt + 1) * 100;
|
|
@@ -220,6 +222,19 @@ const RUNTIME_DEV_CLIENT_SOURCE = `(() => {
|
|
|
220
222
|
shell.style.fontSize = '12px';
|
|
221
223
|
shell.style.pointerEvents = 'none';
|
|
222
224
|
|
|
225
|
+
const buildFailure = document.createElement('div');
|
|
226
|
+
buildFailure.setAttribute('data-zenith-dev-build-error', 'true');
|
|
227
|
+
buildFailure.style.display = 'none';
|
|
228
|
+
buildFailure.style.marginBottom = '8px';
|
|
229
|
+
buildFailure.style.padding = '8px 10px';
|
|
230
|
+
buildFailure.style.maxWidth = '420px';
|
|
231
|
+
buildFailure.style.pointerEvents = 'auto';
|
|
232
|
+
buildFailure.style.border = '1px solid rgba(255,106,106,0.8)';
|
|
233
|
+
buildFailure.style.borderRadius = '8px';
|
|
234
|
+
buildFailure.style.background = 'rgba(90, 16, 16, 0.92)';
|
|
235
|
+
buildFailure.style.color = '#ffe8e8';
|
|
236
|
+
buildFailure.style.whiteSpace = 'pre-wrap';
|
|
237
|
+
|
|
223
238
|
const pill = document.createElement('button');
|
|
224
239
|
pill.type = 'button';
|
|
225
240
|
pill.textContent = 'Zenith Dev';
|
|
@@ -243,6 +258,29 @@ const RUNTIME_DEV_CLIENT_SOURCE = `(() => {
|
|
|
243
258
|
panel.style.borderRadius = '10px';
|
|
244
259
|
panel.style.padding = '10px';
|
|
245
260
|
panel.style.boxShadow = '0 14px 30px rgba(0,0,0,0.35)';
|
|
261
|
+
let lastBuildFailureSignature = '';
|
|
262
|
+
|
|
263
|
+
function reportBuildFailure(message, docsLink) {
|
|
264
|
+
const normalizedMessage = typeof message === 'string' && message.trim().length > 0
|
|
265
|
+
? message.trim()
|
|
266
|
+
: 'Build failed - fix errors to continue.';
|
|
267
|
+
const signature = normalizedMessage + '|' + String(docsLink || '');
|
|
268
|
+
if (signature !== lastBuildFailureSignature) {
|
|
269
|
+
appendLog('[build_failed] ' + normalizedMessage);
|
|
270
|
+
lastBuildFailureSignature = signature;
|
|
271
|
+
}
|
|
272
|
+
const docsText = typeof docsLink === 'string' && docsLink.length > 0
|
|
273
|
+
? '\\nDocs: ' + docsLink
|
|
274
|
+
: '';
|
|
275
|
+
buildFailure.textContent = 'Build failed - fix errors to continue\\n' + normalizedMessage + docsText;
|
|
276
|
+
buildFailure.style.display = 'block';
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
function clearBuildFailure() {
|
|
280
|
+
lastBuildFailureSignature = '';
|
|
281
|
+
buildFailure.style.display = 'none';
|
|
282
|
+
buildFailure.textContent = '';
|
|
283
|
+
}
|
|
246
284
|
|
|
247
285
|
const status = document.createElement('div');
|
|
248
286
|
status.textContent = 'status: connecting';
|
|
@@ -292,7 +330,7 @@ const RUNTIME_DEV_CLIENT_SOURCE = `(() => {
|
|
|
292
330
|
logs.textContent = '[zenith-dev] waiting for server events...';
|
|
293
331
|
|
|
294
332
|
panel.append(status, info, controls, logs);
|
|
295
|
-
shell.append(pill, panel);
|
|
333
|
+
shell.append(buildFailure, pill, panel);
|
|
296
334
|
|
|
297
335
|
function setOpen(open) {
|
|
298
336
|
state.overlay.open = open === true;
|
|
@@ -313,6 +351,9 @@ const RUNTIME_DEV_CLIENT_SOURCE = `(() => {
|
|
|
313
351
|
const serverUrl = typeof payload.serverUrl === 'string' ? payload.serverUrl : window.location.origin;
|
|
314
352
|
const buildId = Number.isInteger(payload.buildId) ? payload.buildId : 'n/a';
|
|
315
353
|
const buildStatus = typeof payload.status === 'string' ? payload.status : 'unknown';
|
|
354
|
+
const errorMessage = payload && payload.error && typeof payload.error.message === 'string'
|
|
355
|
+
? payload.error.message
|
|
356
|
+
: (typeof payload.message === 'string' ? payload.message : '');
|
|
316
357
|
info.textContent =
|
|
317
358
|
'server: ' + serverUrl + '\\n' +
|
|
318
359
|
'route: ' + route + '\\n' +
|
|
@@ -321,6 +362,11 @@ const RUNTIME_DEV_CLIENT_SOURCE = `(() => {
|
|
|
321
362
|
'hash: ' + hash + '\\n' +
|
|
322
363
|
'duration: ' + duration + '\\n' +
|
|
323
364
|
'changed: ' + changed;
|
|
365
|
+
if (buildStatus === 'error') {
|
|
366
|
+
reportBuildFailure(errorMessage || 'Dev build is in an error state.', BUILD_ERROR_DOCS);
|
|
367
|
+
} else if (buildStatus === 'ok' || buildStatus === 'building') {
|
|
368
|
+
clearBuildFailure();
|
|
369
|
+
}
|
|
324
370
|
}
|
|
325
371
|
|
|
326
372
|
function allLogsEnabled() {
|
|
@@ -388,12 +434,14 @@ const RUNTIME_DEV_CLIENT_SOURCE = `(() => {
|
|
|
388
434
|
source.addEventListener('build_start', function (event) {
|
|
389
435
|
const payload = parseEventData(event.data);
|
|
390
436
|
status.textContent = 'status: rebuilding';
|
|
437
|
+
clearBuildFailure();
|
|
391
438
|
appendLog('[build_start] ' + (Array.isArray(payload.changedFiles) ? payload.changedFiles.join(', ') : ''));
|
|
392
439
|
emitDebug('build_start', payload);
|
|
393
440
|
});
|
|
394
441
|
source.addEventListener('build_complete', function (event) {
|
|
395
442
|
const payload = parseEventData(event.data);
|
|
396
443
|
status.textContent = 'status: ready';
|
|
444
|
+
clearBuildFailure();
|
|
397
445
|
updateInfo(payload);
|
|
398
446
|
appendLog('[build_complete] ' + (Number.isFinite(payload.durationMs) ? payload.durationMs + 'ms' : 'done'));
|
|
399
447
|
emitDebug('build_complete', payload);
|
|
@@ -401,6 +449,8 @@ const RUNTIME_DEV_CLIENT_SOURCE = `(() => {
|
|
|
401
449
|
source.addEventListener('build_error', function (event) {
|
|
402
450
|
const payload = parseEventData(event.data);
|
|
403
451
|
status.textContent = 'status: error';
|
|
452
|
+
reportBuildFailure(payload.message || 'Build failed', BUILD_ERROR_DOCS);
|
|
453
|
+
updateInfo(payload);
|
|
404
454
|
appendLog('[build_error] ' + (payload.message || 'Unknown error'));
|
|
405
455
|
emitDebug('build_error', payload);
|
|
406
456
|
});
|