@zenithbuild/runtime 0.7.3 → 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.
- package/HYDRATION_CONTRACT.md +1 -1
- package/LICENSE +21 -0
- package/README.md +16 -18
- package/RUNTIME_CONTRACT.md +24 -12
- package/dist/cleanup.js +6 -3
- package/dist/diagnostics.d.ts +7 -0
- package/dist/diagnostics.js +7 -0
- package/dist/effect-runtime.d.ts +2 -0
- package/dist/effect-runtime.js +139 -0
- package/dist/effect-scheduler.d.ts +4 -0
- package/dist/effect-scheduler.js +88 -0
- package/dist/effect-utils.d.ts +19 -0
- package/dist/effect-utils.js +160 -0
- package/dist/env.d.ts +12 -0
- package/dist/env.js +18 -2
- package/dist/events.d.ts +1 -8
- package/dist/events.js +108 -46
- package/dist/expressions.d.ts +14 -0
- package/dist/expressions.js +295 -0
- package/dist/fragment-patch.d.ts +12 -0
- package/dist/fragment-patch.js +118 -0
- package/dist/hydrate.d.ts +3 -117
- package/dist/hydrate.js +235 -1817
- package/dist/index.d.ts +2 -2
- package/dist/index.js +2 -2
- package/dist/markup.d.ts +8 -0
- package/dist/markup.js +127 -0
- package/dist/mount-runtime.d.ts +1 -0
- package/dist/mount-runtime.js +39 -0
- package/dist/payload.d.ts +21 -0
- package/dist/payload.js +386 -0
- package/dist/reactivity-core.d.ts +3 -0
- package/dist/reactivity-core.js +22 -0
- package/dist/render.d.ts +3 -0
- package/dist/render.js +340 -0
- package/dist/scanner.d.ts +1 -0
- package/dist/scanner.js +61 -0
- package/dist/side-effect-scope.d.ts +16 -0
- package/dist/side-effect-scope.js +99 -0
- package/dist/signal.js +1 -1
- package/dist/state.js +1 -1
- package/dist/template-parser.d.ts +1 -0
- package/dist/template-parser.js +268 -0
- package/dist/template.js +10 -11
- package/dist/zeneffect.d.ts +12 -14
- package/dist/zeneffect.js +25 -519
- package/package.json +5 -3
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export { signal } from "./signal.js";
|
|
2
2
|
export { state } from "./state.js";
|
|
3
|
-
export { zeneffect } from "./zeneffect.js";
|
|
4
3
|
export { hydrate } from "./hydrate.js";
|
|
5
|
-
export {
|
|
4
|
+
export { effect, mount, zeneffect, zenEffect, zenMount } from "./zeneffect.js";
|
|
5
|
+
export { window, document, zenWindow, zenDocument } from "./env.js";
|
|
6
6
|
export { zenOn, zenResize, collectRefs } from "./platform.js";
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export { signal } from './signal.js';
|
|
2
2
|
export { state } from './state.js';
|
|
3
|
-
export { zeneffect } from './zeneffect.js';
|
|
3
|
+
export { effect, mount, zeneffect, zenEffect, zenMount } from './zeneffect.js';
|
|
4
4
|
export { hydrate } from './hydrate.js';
|
|
5
|
-
export { zenWindow, zenDocument } from './env.js';
|
|
5
|
+
export { window, document, zenWindow, zenDocument } from './env.js';
|
|
6
6
|
export { zenOn, zenResize, collectRefs } from './platform.js';
|
package/dist/markup.d.ts
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export function _isHtmlFragment(value: any): boolean;
|
|
2
|
+
export function _fragment(strings: any, ...values: any[]): {
|
|
3
|
+
__zenith_fragment: boolean;
|
|
4
|
+
html: any;
|
|
5
|
+
[HTML_FRAGMENT_BRAND]: boolean;
|
|
6
|
+
};
|
|
7
|
+
export { _rewriteMarkupLiterals } from "./template-parser.js";
|
|
8
|
+
declare const HTML_FRAGMENT_BRAND: unique symbol;
|
package/dist/markup.js
ADDED
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import { throwZenithRuntimeError } from './diagnostics.js';
|
|
2
|
+
export { _rewriteMarkupLiterals } from './template-parser.js';
|
|
3
|
+
const HTML_FRAGMENT_BRAND = Symbol.for('zenith.html_fragment');
|
|
4
|
+
// This function is intentionally NOT exported. Embedded markup expressions
|
|
5
|
+
// lower to this helper, which escapes interpolated strings by default and
|
|
6
|
+
// returns compiler-owned fragment objects.
|
|
7
|
+
// ──────────────────────────────────────────────────────────────────────────────
|
|
8
|
+
const _FRAGMENT_UNSAFE_TAG_RE = /<script[\s>]/i;
|
|
9
|
+
const _FRAGMENT_EVENT_ATTR_RE = /\bon[a-z]+\s*=/i;
|
|
10
|
+
const _FRAGMENT_JS_URL_RE = /javascript\s*:/i;
|
|
11
|
+
const _FRAGMENT_PROTO_KEYS = new Set(['__proto__', 'constructor', 'prototype']);
|
|
12
|
+
const _FRAGMENT_SCRIPT_CLOSE_RE = /<\/script/gi;
|
|
13
|
+
function _createHtmlFragment(html) {
|
|
14
|
+
return {
|
|
15
|
+
__zenith_fragment: true,
|
|
16
|
+
html,
|
|
17
|
+
[HTML_FRAGMENT_BRAND]: true
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
export function _isHtmlFragment(value) {
|
|
21
|
+
return !!(value &&
|
|
22
|
+
typeof value === 'object' &&
|
|
23
|
+
value.__zenith_fragment === true &&
|
|
24
|
+
value[HTML_FRAGMENT_BRAND] === true &&
|
|
25
|
+
typeof value.html === 'string');
|
|
26
|
+
}
|
|
27
|
+
export function _fragment(strings, ...values) {
|
|
28
|
+
if (!Array.isArray(strings)) {
|
|
29
|
+
throwZenithRuntimeError({
|
|
30
|
+
phase: 'render',
|
|
31
|
+
code: 'NON_RENDERABLE_VALUE',
|
|
32
|
+
message: '__zenith_fragment must be called as a tagged template literal',
|
|
33
|
+
hint: 'This helper only accepts tagged template syntax.'
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
let result = '';
|
|
37
|
+
for (let i = 0; i < strings.length; i++) {
|
|
38
|
+
result += strings[i];
|
|
39
|
+
if (i < values.length) {
|
|
40
|
+
const val = values[i];
|
|
41
|
+
result += _fragmentInterpolate(val, i);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
if (_FRAGMENT_UNSAFE_TAG_RE.test(result)) {
|
|
45
|
+
throwZenithRuntimeError({
|
|
46
|
+
phase: 'render',
|
|
47
|
+
code: 'NON_RENDERABLE_VALUE',
|
|
48
|
+
message: 'Embedded markup expression contains forbidden <script> tag',
|
|
49
|
+
hint: 'Script tags are not allowed in embedded markup expressions.'
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
if (_FRAGMENT_EVENT_ATTR_RE.test(result)) {
|
|
53
|
+
throwZenithRuntimeError({
|
|
54
|
+
phase: 'render',
|
|
55
|
+
code: 'NON_RENDERABLE_VALUE',
|
|
56
|
+
message: 'Embedded markup expression contains inline event handler (on*=)',
|
|
57
|
+
hint: 'Use on:event={handler} bindings instead of inline event attributes.'
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
if (_FRAGMENT_JS_URL_RE.test(result)) {
|
|
61
|
+
throwZenithRuntimeError({
|
|
62
|
+
phase: 'render',
|
|
63
|
+
code: 'NON_RENDERABLE_VALUE',
|
|
64
|
+
message: 'Embedded markup expression contains javascript: URL',
|
|
65
|
+
hint: 'javascript: URLs are forbidden in embedded markup.'
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
result = result.replace(_FRAGMENT_SCRIPT_CLOSE_RE, '<\\/script');
|
|
69
|
+
return _createHtmlFragment(result);
|
|
70
|
+
}
|
|
71
|
+
function _fragmentInterpolate(val, interpolationIndex) {
|
|
72
|
+
if (val === null || val === undefined || val === false) {
|
|
73
|
+
return '';
|
|
74
|
+
}
|
|
75
|
+
if (val === true) {
|
|
76
|
+
return '';
|
|
77
|
+
}
|
|
78
|
+
if (typeof val === 'string') {
|
|
79
|
+
return _escapeFragmentHtml(val);
|
|
80
|
+
}
|
|
81
|
+
if (typeof val === 'number') {
|
|
82
|
+
return _escapeFragmentHtml(String(val));
|
|
83
|
+
}
|
|
84
|
+
if (_isHtmlFragment(val)) {
|
|
85
|
+
return val.html;
|
|
86
|
+
}
|
|
87
|
+
if (Array.isArray(val)) {
|
|
88
|
+
let out = '';
|
|
89
|
+
for (let j = 0; j < val.length; j++) {
|
|
90
|
+
out += _fragmentInterpolate(val[j], interpolationIndex);
|
|
91
|
+
}
|
|
92
|
+
return out;
|
|
93
|
+
}
|
|
94
|
+
if (typeof val === 'object') {
|
|
95
|
+
const keys = Object.keys(val);
|
|
96
|
+
for (let k = 0; k < keys.length; k++) {
|
|
97
|
+
if (_FRAGMENT_PROTO_KEYS.has(keys[k])) {
|
|
98
|
+
throwZenithRuntimeError({
|
|
99
|
+
phase: 'render',
|
|
100
|
+
code: 'NON_RENDERABLE_VALUE',
|
|
101
|
+
message: `Embedded markup interpolation[${interpolationIndex}] contains forbidden key "${keys[k]}"`,
|
|
102
|
+
hint: 'Prototype pollution keys are forbidden in embedded markup expressions.'
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
throwZenithRuntimeError({
|
|
107
|
+
phase: 'render',
|
|
108
|
+
code: 'NON_RENDERABLE_VALUE',
|
|
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.'
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
throwZenithRuntimeError({
|
|
114
|
+
phase: 'render',
|
|
115
|
+
code: 'NON_RENDERABLE_VALUE',
|
|
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.'
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
function _escapeFragmentHtml(input) {
|
|
121
|
+
return String(input)
|
|
122
|
+
.replace(/&/g, '&')
|
|
123
|
+
.replace(/</g, '<')
|
|
124
|
+
.replace(/>/g, '>')
|
|
125
|
+
.replace(/"/g, '"')
|
|
126
|
+
.replace(/'/g, ''');
|
|
127
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function createMountEffect(callback: any, scope: any): () => void;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
// @ts-nocheck
|
|
2
|
+
import { registerScopeDisposer, queueWhenScopeReady } from './side-effect-scope.js';
|
|
3
|
+
import { drainCleanupStack, applyCleanupResult, createMountContext } from './effect-utils.js';
|
|
4
|
+
export function createMountEffect(callback, scope) {
|
|
5
|
+
const cleanups = [];
|
|
6
|
+
let executed = false;
|
|
7
|
+
let registeredDisposer = null;
|
|
8
|
+
function registerCleanup(fn) {
|
|
9
|
+
if (typeof fn !== 'function') {
|
|
10
|
+
throw new Error('[Zenith Runtime] cleanup(fn) requires a function');
|
|
11
|
+
}
|
|
12
|
+
cleanups.push(fn);
|
|
13
|
+
}
|
|
14
|
+
function runMount() {
|
|
15
|
+
if (scope.disposed || executed) {
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
executed = true;
|
|
19
|
+
try {
|
|
20
|
+
const result = callback(createMountContext(registerCleanup));
|
|
21
|
+
applyCleanupResult(result, registerCleanup);
|
|
22
|
+
}
|
|
23
|
+
catch (error) {
|
|
24
|
+
console.error('[Zenith Runtime] Unhandled error during zenMount:', error);
|
|
25
|
+
}
|
|
26
|
+
registeredDisposer = registerScopeDisposer(scope, () => {
|
|
27
|
+
drainCleanupStack(cleanups);
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
queueWhenScopeReady(scope, runMount);
|
|
31
|
+
return function dispose() {
|
|
32
|
+
if (registeredDisposer) {
|
|
33
|
+
registeredDisposer();
|
|
34
|
+
}
|
|
35
|
+
else {
|
|
36
|
+
drainCleanupStack(cleanups);
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export function _validatePayload(payload: any): Readonly<{
|
|
2
|
+
root: any;
|
|
3
|
+
expressions: any[];
|
|
4
|
+
markers: any[];
|
|
5
|
+
events: any[];
|
|
6
|
+
refs: any;
|
|
7
|
+
stateValues: any[];
|
|
8
|
+
stateKeys: any[];
|
|
9
|
+
signals: any[];
|
|
10
|
+
components: any;
|
|
11
|
+
route: any;
|
|
12
|
+
params: any;
|
|
13
|
+
ssrData: any;
|
|
14
|
+
props: any;
|
|
15
|
+
exprFns: any;
|
|
16
|
+
}>;
|
|
17
|
+
export function _resolveComponentProps(propTable: any, signalMap: any, context?: {}): any;
|
|
18
|
+
export function _deepFreezePayload(obj: any): void;
|
|
19
|
+
export function _isHydrationRefObject(obj: any): boolean;
|
|
20
|
+
export function _isPlainObject(value: any): boolean;
|
|
21
|
+
export function _isHydrationFreezableContainer(value: any): boolean;
|
package/dist/payload.js
ADDED
|
@@ -0,0 +1,386 @@
|
|
|
1
|
+
export function _validatePayload(payload) {
|
|
2
|
+
if (!payload || typeof payload !== 'object') {
|
|
3
|
+
throw new Error('[Zenith Runtime] hydrate(payload) requires an object payload');
|
|
4
|
+
}
|
|
5
|
+
if (payload.ir_version !== 1) {
|
|
6
|
+
throw new Error('[Zenith Runtime] unsupported ir_version (expected 1)');
|
|
7
|
+
}
|
|
8
|
+
const root = payload.root;
|
|
9
|
+
const hasQuery = !!root && typeof root.querySelectorAll === 'function';
|
|
10
|
+
if (!hasQuery) {
|
|
11
|
+
throw new Error('[Zenith Runtime] hydrate(payload) requires payload.root with querySelectorAll');
|
|
12
|
+
}
|
|
13
|
+
const expressions = payload.expressions;
|
|
14
|
+
if (!Array.isArray(expressions)) {
|
|
15
|
+
throw new Error('[Zenith Runtime] hydrate(payload) requires expressions[]');
|
|
16
|
+
}
|
|
17
|
+
const markers = payload.markers;
|
|
18
|
+
if (!Array.isArray(markers)) {
|
|
19
|
+
throw new Error('[Zenith Runtime] hydrate(payload) requires markers[]');
|
|
20
|
+
}
|
|
21
|
+
const events = payload.events;
|
|
22
|
+
if (!Array.isArray(events)) {
|
|
23
|
+
throw new Error('[Zenith Runtime] hydrate(payload) requires events[]');
|
|
24
|
+
}
|
|
25
|
+
const refs = Array.isArray(payload.refs) ? payload.refs : [];
|
|
26
|
+
const stateValues = payload.state_values;
|
|
27
|
+
if (!Array.isArray(stateValues)) {
|
|
28
|
+
throw new Error('[Zenith Runtime] hydrate(payload) requires state_values[]');
|
|
29
|
+
}
|
|
30
|
+
const stateKeys = Array.isArray(payload.state_keys) ? payload.state_keys : [];
|
|
31
|
+
if (!Array.isArray(stateKeys)) {
|
|
32
|
+
throw new Error('[Zenith Runtime] hydrate(payload) requires state_keys[] when provided');
|
|
33
|
+
}
|
|
34
|
+
for (let i = 0; i < stateKeys.length; i++) {
|
|
35
|
+
if (typeof stateKeys[i] !== 'string') {
|
|
36
|
+
throw new Error(`[Zenith Runtime] state_keys[${i}] must be a string`);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
const signals = payload.signals;
|
|
40
|
+
if (!Array.isArray(signals)) {
|
|
41
|
+
throw new Error('[Zenith Runtime] hydrate(payload) requires signals[]');
|
|
42
|
+
}
|
|
43
|
+
const components = Array.isArray(payload.components) ? payload.components : [];
|
|
44
|
+
const route = typeof payload.route === 'string' && payload.route.length > 0
|
|
45
|
+
? payload.route
|
|
46
|
+
: '<unknown>';
|
|
47
|
+
const params = payload.params && typeof payload.params === 'object'
|
|
48
|
+
? payload.params
|
|
49
|
+
: {};
|
|
50
|
+
const ssrData = payload.ssr_data && typeof payload.ssr_data === 'object'
|
|
51
|
+
? payload.ssr_data
|
|
52
|
+
: {};
|
|
53
|
+
const exprFns = Array.isArray(payload.expr_fns) ? payload.expr_fns : [];
|
|
54
|
+
if (markers.length !== expressions.length) {
|
|
55
|
+
throw new Error(`[Zenith Runtime] marker/expression mismatch: markers=${markers.length}, expressions=${expressions.length}`);
|
|
56
|
+
}
|
|
57
|
+
for (let i = 0; i < expressions.length; i++) {
|
|
58
|
+
const expression = expressions[i];
|
|
59
|
+
if (!expression || typeof expression !== 'object' || Array.isArray(expression)) {
|
|
60
|
+
throw new Error(`[Zenith Runtime] expression at position ${i} must be an object`);
|
|
61
|
+
}
|
|
62
|
+
if (!Number.isInteger(expression.marker_index) || expression.marker_index < 0 || expression.marker_index >= expressions.length) {
|
|
63
|
+
throw new Error(`[Zenith Runtime] expression at position ${i} has invalid marker_index`);
|
|
64
|
+
}
|
|
65
|
+
if (expression.marker_index !== i) {
|
|
66
|
+
throw new Error(`[Zenith Runtime] expression table out of order at position ${i}: marker_index=${expression.marker_index}`);
|
|
67
|
+
}
|
|
68
|
+
if (expression.fn_index !== undefined && expression.fn_index !== null) {
|
|
69
|
+
if (!Number.isInteger(expression.fn_index) || expression.fn_index < 0) {
|
|
70
|
+
throw new Error(`[Zenith Runtime] expression at position ${i} has invalid fn_index`);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
_assertValidSourceSpan(expression.source, `expression[${i}]`);
|
|
74
|
+
if (expression.signal_indices !== undefined) {
|
|
75
|
+
if (!Array.isArray(expression.signal_indices)) {
|
|
76
|
+
throw new Error(`[Zenith Runtime] expression at position ${i} must provide signal_indices[]`);
|
|
77
|
+
}
|
|
78
|
+
for (let j = 0; j < expression.signal_indices.length; j++) {
|
|
79
|
+
if (!Number.isInteger(expression.signal_indices[j]) || expression.signal_indices[j] < 0) {
|
|
80
|
+
throw new Error(`[Zenith Runtime] expression at position ${i} has invalid signal_indices[${j}]`);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
for (let i = 0; i < markers.length; i++) {
|
|
86
|
+
const marker = markers[i];
|
|
87
|
+
if (!marker || typeof marker !== 'object' || Array.isArray(marker)) {
|
|
88
|
+
throw new Error(`[Zenith Runtime] marker at position ${i} must be an object`);
|
|
89
|
+
}
|
|
90
|
+
if (!Number.isInteger(marker.index) || marker.index < 0 || marker.index >= expressions.length) {
|
|
91
|
+
throw new Error(`[Zenith Runtime] marker at position ${i} has out-of-bounds index`);
|
|
92
|
+
}
|
|
93
|
+
if (marker.index !== i) {
|
|
94
|
+
throw new Error(`[Zenith Runtime] marker table out of order at position ${i}: index=${marker.index}`);
|
|
95
|
+
}
|
|
96
|
+
if (marker.kind !== 'text' && marker.kind !== 'attr' && marker.kind !== 'event') {
|
|
97
|
+
throw new Error(`[Zenith Runtime] marker at position ${i} has invalid kind`);
|
|
98
|
+
}
|
|
99
|
+
if (typeof marker.selector !== 'string' || marker.selector.length === 0) {
|
|
100
|
+
throw new Error(`[Zenith Runtime] marker at position ${i} requires selector`);
|
|
101
|
+
}
|
|
102
|
+
if (marker.kind === 'attr' && (typeof marker.attr !== 'string' || marker.attr.length === 0)) {
|
|
103
|
+
throw new Error(`[Zenith Runtime] attr marker at position ${i} requires attr name`);
|
|
104
|
+
}
|
|
105
|
+
_assertValidSourceSpan(marker.source, `marker[${i}]`);
|
|
106
|
+
}
|
|
107
|
+
for (let i = 0; i < events.length; i++) {
|
|
108
|
+
const eventBinding = events[i];
|
|
109
|
+
if (!eventBinding || typeof eventBinding !== 'object' || Array.isArray(eventBinding)) {
|
|
110
|
+
throw new Error(`[Zenith Runtime] event binding at position ${i} must be an object`);
|
|
111
|
+
}
|
|
112
|
+
if (!Number.isInteger(eventBinding.index) || eventBinding.index < 0 || eventBinding.index >= expressions.length) {
|
|
113
|
+
throw new Error(`[Zenith Runtime] event binding at position ${i} has out-of-bounds index`);
|
|
114
|
+
}
|
|
115
|
+
if (typeof eventBinding.event !== 'string' || eventBinding.event.length === 0) {
|
|
116
|
+
throw new Error(`[Zenith Runtime] event binding at position ${i} requires event name`);
|
|
117
|
+
}
|
|
118
|
+
if (typeof eventBinding.selector !== 'string' || eventBinding.selector.length === 0) {
|
|
119
|
+
throw new Error(`[Zenith Runtime] event binding at position ${i} requires selector`);
|
|
120
|
+
}
|
|
121
|
+
_assertValidSourceSpan(eventBinding.source, `event[${i}]`);
|
|
122
|
+
}
|
|
123
|
+
for (let i = 0; i < refs.length; i++) {
|
|
124
|
+
const refBinding = refs[i];
|
|
125
|
+
if (!refBinding || typeof refBinding !== 'object' || Array.isArray(refBinding)) {
|
|
126
|
+
throw new Error(`[Zenith Runtime] ref binding at position ${i} must be an object`);
|
|
127
|
+
}
|
|
128
|
+
if (!Number.isInteger(refBinding.index) || refBinding.index < 0) {
|
|
129
|
+
throw new Error(`[Zenith Runtime] ref binding at position ${i} requires non-negative index`);
|
|
130
|
+
}
|
|
131
|
+
if (!Number.isInteger(refBinding.state_index) ||
|
|
132
|
+
refBinding.state_index < 0 ||
|
|
133
|
+
refBinding.state_index >= stateValues.length) {
|
|
134
|
+
throw new Error(`[Zenith Runtime] ref binding at position ${i} has out-of-bounds state_index`);
|
|
135
|
+
}
|
|
136
|
+
if (typeof refBinding.selector !== 'string' || refBinding.selector.length === 0) {
|
|
137
|
+
throw new Error(`[Zenith Runtime] ref binding at position ${i} requires selector`);
|
|
138
|
+
}
|
|
139
|
+
_assertValidSourceSpan(refBinding.source, `ref[${i}]`);
|
|
140
|
+
const candidate = stateValues[refBinding.state_index];
|
|
141
|
+
if (!candidate || typeof candidate !== 'object' || !Object.prototype.hasOwnProperty.call(candidate, 'current')) {
|
|
142
|
+
throw new Error(`[Zenith Runtime] ref binding at position ${i} must resolve to a ref-like object`);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
for (let i = 0; i < signals.length; i++) {
|
|
146
|
+
const signalDescriptor = signals[i];
|
|
147
|
+
if (!signalDescriptor || typeof signalDescriptor !== 'object' || Array.isArray(signalDescriptor)) {
|
|
148
|
+
throw new Error(`[Zenith Runtime] signal descriptor at position ${i} must be an object`);
|
|
149
|
+
}
|
|
150
|
+
if (signalDescriptor.kind !== 'signal') {
|
|
151
|
+
throw new Error(`[Zenith Runtime] signal descriptor at position ${i} requires kind="signal"`);
|
|
152
|
+
}
|
|
153
|
+
if (!Number.isInteger(signalDescriptor.id) || signalDescriptor.id < 0) {
|
|
154
|
+
throw new Error(`[Zenith Runtime] signal descriptor at position ${i} requires non-negative id`);
|
|
155
|
+
}
|
|
156
|
+
if (signalDescriptor.id !== i) {
|
|
157
|
+
throw new Error(`[Zenith Runtime] signal table out of order at position ${i}: id=${signalDescriptor.id}`);
|
|
158
|
+
}
|
|
159
|
+
if (!Number.isInteger(signalDescriptor.state_index) || signalDescriptor.state_index < 0 || signalDescriptor.state_index >= stateValues.length) {
|
|
160
|
+
throw new Error(`[Zenith Runtime] signal descriptor at position ${i} has out-of-bounds state_index`);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
for (let i = 0; i < components.length; i++) {
|
|
164
|
+
const component = components[i];
|
|
165
|
+
if (!component || typeof component !== 'object' || Array.isArray(component)) {
|
|
166
|
+
throw new Error(`[Zenith Runtime] component at position ${i} must be an object`);
|
|
167
|
+
}
|
|
168
|
+
if (typeof component.instance !== 'string' || component.instance.length === 0) {
|
|
169
|
+
throw new Error(`[Zenith Runtime] component at position ${i} requires instance`);
|
|
170
|
+
}
|
|
171
|
+
if (typeof component.selector !== 'string' || component.selector.length === 0) {
|
|
172
|
+
throw new Error(`[Zenith Runtime] component at position ${i} requires selector`);
|
|
173
|
+
}
|
|
174
|
+
if (typeof component.create !== 'function') {
|
|
175
|
+
throw new Error(`[Zenith Runtime] component at position ${i} requires create() function`);
|
|
176
|
+
}
|
|
177
|
+
if (component.props !== undefined) {
|
|
178
|
+
if (!Array.isArray(component.props)) {
|
|
179
|
+
throw new Error(`[Zenith Runtime] component at position ${i} requires props to be an array`);
|
|
180
|
+
}
|
|
181
|
+
for (let j = 0; j < component.props.length; j++) {
|
|
182
|
+
const prop = component.props[j];
|
|
183
|
+
if (!prop || typeof prop !== 'object' || Array.isArray(prop)) {
|
|
184
|
+
throw new Error(`[Zenith Runtime] component prop at position ${j} for component ${i} must be an object`);
|
|
185
|
+
}
|
|
186
|
+
if (typeof prop.name !== 'string' || prop.name.length === 0) {
|
|
187
|
+
throw new Error(`[Zenith Runtime] component prop at position ${j} for component ${i} requires a non-empty name`);
|
|
188
|
+
}
|
|
189
|
+
if (prop.type !== 'static' && prop.type !== 'signal') {
|
|
190
|
+
throw new Error(`[Zenith Runtime] component prop "${prop.name}" for component ${i} has unsupported type "${prop.type}"`);
|
|
191
|
+
}
|
|
192
|
+
if (prop.type === 'static' && !Object.prototype.hasOwnProperty.call(prop, 'value')) {
|
|
193
|
+
throw new Error(`[Zenith Runtime] component prop "${prop.name}" for component ${i} requires a value`);
|
|
194
|
+
}
|
|
195
|
+
if (prop.type === 'signal' && (!Number.isInteger(prop.index) || prop.index < 0)) {
|
|
196
|
+
throw new Error(`[Zenith Runtime] component prop "${prop.name}" for component ${i} requires a valid signal index`);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
_assertValidSourceSpan(component.source, `component[${i}]`);
|
|
201
|
+
}
|
|
202
|
+
if (payload.params !== undefined) {
|
|
203
|
+
if (!payload.params || typeof payload.params !== 'object' || Array.isArray(payload.params)) {
|
|
204
|
+
throw new Error('[Zenith Runtime] hydrate() requires params object');
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
if (payload.ssr_data !== undefined) {
|
|
208
|
+
if (!payload.ssr_data || typeof payload.ssr_data !== 'object' || Array.isArray(payload.ssr_data)) {
|
|
209
|
+
throw new Error('[Zenith Runtime] hydrate() requires ssr_data object');
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
const props = payload.props && typeof payload.props === 'object' && !Array.isArray(payload.props)
|
|
213
|
+
? payload.props
|
|
214
|
+
: {};
|
|
215
|
+
for (let i = 0; i < expressions.length; i++)
|
|
216
|
+
Object.freeze(expressions[i]);
|
|
217
|
+
for (let i = 0; i < markers.length; i++)
|
|
218
|
+
Object.freeze(markers[i]);
|
|
219
|
+
for (let i = 0; i < events.length; i++)
|
|
220
|
+
Object.freeze(events[i]);
|
|
221
|
+
for (let i = 0; i < refs.length; i++)
|
|
222
|
+
Object.freeze(refs[i]);
|
|
223
|
+
for (let i = 0; i < signals.length; i++)
|
|
224
|
+
Object.freeze(signals[i]);
|
|
225
|
+
for (let i = 0; i < components.length; i++) {
|
|
226
|
+
const c = components[i];
|
|
227
|
+
if (Array.isArray(c.props)) {
|
|
228
|
+
for (let j = 0; j < c.props.length; j++) {
|
|
229
|
+
const propDesc = c.props[j];
|
|
230
|
+
if (propDesc &&
|
|
231
|
+
typeof propDesc === 'object' &&
|
|
232
|
+
_isHydrationFreezableContainer(propDesc.value)) {
|
|
233
|
+
Object.freeze(propDesc.value);
|
|
234
|
+
}
|
|
235
|
+
Object.freeze(propDesc);
|
|
236
|
+
}
|
|
237
|
+
Object.freeze(c.props);
|
|
238
|
+
}
|
|
239
|
+
Object.freeze(c);
|
|
240
|
+
}
|
|
241
|
+
Object.freeze(expressions);
|
|
242
|
+
Object.freeze(markers);
|
|
243
|
+
Object.freeze(events);
|
|
244
|
+
Object.freeze(refs);
|
|
245
|
+
Object.freeze(signals);
|
|
246
|
+
Object.freeze(components);
|
|
247
|
+
const validatedPayload = {
|
|
248
|
+
root,
|
|
249
|
+
expressions,
|
|
250
|
+
markers,
|
|
251
|
+
events,
|
|
252
|
+
refs,
|
|
253
|
+
stateValues,
|
|
254
|
+
stateKeys,
|
|
255
|
+
signals,
|
|
256
|
+
components,
|
|
257
|
+
route,
|
|
258
|
+
params: Object.freeze(params),
|
|
259
|
+
ssrData: Object.freeze(ssrData),
|
|
260
|
+
props: Object.freeze(props),
|
|
261
|
+
exprFns: Object.freeze(exprFns)
|
|
262
|
+
};
|
|
263
|
+
return Object.freeze(validatedPayload);
|
|
264
|
+
}
|
|
265
|
+
function _assertValidSourceSpan(source, contextLabel) {
|
|
266
|
+
if (source === undefined || source === null) {
|
|
267
|
+
return;
|
|
268
|
+
}
|
|
269
|
+
if (!source || typeof source !== 'object' || Array.isArray(source)) {
|
|
270
|
+
throw new Error(`[Zenith Runtime] ${contextLabel}.source must be an object`);
|
|
271
|
+
}
|
|
272
|
+
if (typeof source.file !== 'string' || source.file.length === 0) {
|
|
273
|
+
throw new Error(`[Zenith Runtime] ${contextLabel}.source.file must be a non-empty string`);
|
|
274
|
+
}
|
|
275
|
+
const points = ['start', 'end'];
|
|
276
|
+
for (let i = 0; i < points.length; i++) {
|
|
277
|
+
const point = source[points[i]];
|
|
278
|
+
if (point === undefined || point === null) {
|
|
279
|
+
continue;
|
|
280
|
+
}
|
|
281
|
+
if (!point || typeof point !== 'object' || Array.isArray(point)) {
|
|
282
|
+
throw new Error(`[Zenith Runtime] ${contextLabel}.source.${points[i]} must be an object`);
|
|
283
|
+
}
|
|
284
|
+
if (!Number.isInteger(point.line) || point.line < 1) {
|
|
285
|
+
throw new Error(`[Zenith Runtime] ${contextLabel}.source.${points[i]}.line must be >= 1`);
|
|
286
|
+
}
|
|
287
|
+
if (!Number.isInteger(point.column) || point.column < 1) {
|
|
288
|
+
throw new Error(`[Zenith Runtime] ${contextLabel}.source.${points[i]}.column must be >= 1`);
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
if (source.snippet !== undefined && source.snippet !== null && typeof source.snippet !== 'string') {
|
|
292
|
+
throw new Error(`[Zenith Runtime] ${contextLabel}.source.snippet must be a string when provided`);
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
export function _resolveComponentProps(propTable, signalMap, context = {}) {
|
|
296
|
+
if (!Array.isArray(propTable)) {
|
|
297
|
+
throw new Error('[Zenith Runtime] component props must be an array');
|
|
298
|
+
}
|
|
299
|
+
const resolved = Object.create(null);
|
|
300
|
+
for (let i = 0; i < propTable.length; i++) {
|
|
301
|
+
const descriptor = propTable[i];
|
|
302
|
+
if (!descriptor || typeof descriptor !== 'object' || Array.isArray(descriptor)) {
|
|
303
|
+
throw new Error(`[Zenith Runtime] component prop descriptor at index ${i} must be an object`);
|
|
304
|
+
}
|
|
305
|
+
if (typeof descriptor.name !== 'string' || descriptor.name.length === 0) {
|
|
306
|
+
throw new Error(`[Zenith Runtime] component prop descriptor at index ${i} requires non-empty name`);
|
|
307
|
+
}
|
|
308
|
+
if (Object.prototype.hasOwnProperty.call(resolved, descriptor.name)) {
|
|
309
|
+
throw new Error(`[Zenith Runtime] duplicate component prop "${descriptor.name}"`);
|
|
310
|
+
}
|
|
311
|
+
if (descriptor.type === 'static') {
|
|
312
|
+
if (!Object.prototype.hasOwnProperty.call(descriptor, 'value')) {
|
|
313
|
+
throw new Error(`[Zenith Runtime] component prop "${descriptor.name}" static value is missing`);
|
|
314
|
+
}
|
|
315
|
+
resolved[descriptor.name] = descriptor.value;
|
|
316
|
+
continue;
|
|
317
|
+
}
|
|
318
|
+
if (descriptor.type === 'signal') {
|
|
319
|
+
if (!Number.isInteger(descriptor.index)) {
|
|
320
|
+
throw new Error(`[Zenith Runtime] component prop "${descriptor.name}" signal index must be an integer`);
|
|
321
|
+
}
|
|
322
|
+
const signalValue = signalMap.get(descriptor.index);
|
|
323
|
+
if (!signalValue || typeof signalValue.get !== 'function') {
|
|
324
|
+
throw new Error(`[Zenith Runtime]\nComponent: ${context.component || '<unknown>'}\nRoute: ${context.route || '<unknown>'}\nProp: ${descriptor.name}\nReason: signal index ${descriptor.index} did not resolve`);
|
|
325
|
+
}
|
|
326
|
+
resolved[descriptor.name] = signalValue;
|
|
327
|
+
continue;
|
|
328
|
+
}
|
|
329
|
+
throw new Error(`[Zenith Runtime] unsupported component prop type "${descriptor.type}" for "${descriptor.name}"`);
|
|
330
|
+
}
|
|
331
|
+
return resolved;
|
|
332
|
+
}
|
|
333
|
+
export function _deepFreezePayload(obj) {
|
|
334
|
+
if (!_isHydrationFreezableContainer(obj) || Object.isFrozen(obj))
|
|
335
|
+
return;
|
|
336
|
+
Object.freeze(obj);
|
|
337
|
+
const keys = Object.keys(obj);
|
|
338
|
+
for (let i = 0; i < keys.length; i++) {
|
|
339
|
+
const val = obj[keys[i]];
|
|
340
|
+
if (val && typeof val === 'object' && typeof val !== 'function') {
|
|
341
|
+
_deepFreezePayload(val);
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
export function _isHydrationRefObject(obj) {
|
|
346
|
+
if (!obj || typeof obj !== 'object') {
|
|
347
|
+
return false;
|
|
348
|
+
}
|
|
349
|
+
if (obj.__zenith_ref === true) {
|
|
350
|
+
return true;
|
|
351
|
+
}
|
|
352
|
+
if (!Object.prototype.hasOwnProperty.call(obj, 'current')) {
|
|
353
|
+
return false;
|
|
354
|
+
}
|
|
355
|
+
if (typeof obj.get === 'function' && typeof obj.subscribe === 'function') {
|
|
356
|
+
return false;
|
|
357
|
+
}
|
|
358
|
+
const keys = Object.keys(obj);
|
|
359
|
+
if (keys.length === 1 && keys[0] === 'current') {
|
|
360
|
+
return true;
|
|
361
|
+
}
|
|
362
|
+
if (keys.length === 2 && keys.includes('current') && keys.includes('__zenith_ref')) {
|
|
363
|
+
return true;
|
|
364
|
+
}
|
|
365
|
+
return false;
|
|
366
|
+
}
|
|
367
|
+
export function _isPlainObject(value) {
|
|
368
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) {
|
|
369
|
+
return false;
|
|
370
|
+
}
|
|
371
|
+
const proto = Object.getPrototypeOf(value);
|
|
372
|
+
return proto === Object.prototype || proto === null;
|
|
373
|
+
}
|
|
374
|
+
export function _isHydrationFreezableContainer(value) {
|
|
375
|
+
if (Array.isArray(value))
|
|
376
|
+
return true;
|
|
377
|
+
if (!_isPlainObject(value))
|
|
378
|
+
return false;
|
|
379
|
+
if (_isHydrationRefObject(value)) {
|
|
380
|
+
return false;
|
|
381
|
+
}
|
|
382
|
+
if (typeof value.get === 'function' && typeof value.subscribe === 'function') {
|
|
383
|
+
return false;
|
|
384
|
+
}
|
|
385
|
+
return true;
|
|
386
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
// @ts-nocheck
|
|
2
|
+
let _activeDependencyCollector = null;
|
|
3
|
+
let _reactiveIdCounter = 0;
|
|
4
|
+
export function _nextReactiveId() {
|
|
5
|
+
_reactiveIdCounter += 1;
|
|
6
|
+
return _reactiveIdCounter;
|
|
7
|
+
}
|
|
8
|
+
export function _trackDependency(source) {
|
|
9
|
+
if (typeof _activeDependencyCollector === 'function') {
|
|
10
|
+
_activeDependencyCollector(source);
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
export function runWithDependencyCollector(collector, callback) {
|
|
14
|
+
const previousCollector = _activeDependencyCollector;
|
|
15
|
+
_activeDependencyCollector = typeof collector === 'function' ? collector : null;
|
|
16
|
+
try {
|
|
17
|
+
return callback();
|
|
18
|
+
}
|
|
19
|
+
finally {
|
|
20
|
+
_activeDependencyCollector = previousCollector;
|
|
21
|
+
}
|
|
22
|
+
}
|
package/dist/render.d.ts
ADDED