@zenithbuild/runtime 0.7.2 → 0.7.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. package/HYDRATION_CONTRACT.md +1 -1
  2. package/LICENSE +21 -0
  3. package/README.md +16 -18
  4. package/RUNTIME_CONTRACT.md +24 -12
  5. package/dist/cleanup.js +6 -3
  6. package/dist/diagnostics.d.ts +7 -0
  7. package/dist/diagnostics.js +7 -0
  8. package/dist/effect-runtime.d.ts +2 -0
  9. package/dist/effect-runtime.js +139 -0
  10. package/dist/effect-scheduler.d.ts +4 -0
  11. package/dist/effect-scheduler.js +88 -0
  12. package/dist/effect-utils.d.ts +19 -0
  13. package/dist/effect-utils.js +160 -0
  14. package/dist/env.d.ts +12 -0
  15. package/dist/env.js +18 -2
  16. package/dist/events.d.ts +1 -8
  17. package/dist/events.js +108 -46
  18. package/dist/expressions.d.ts +14 -0
  19. package/dist/expressions.js +295 -0
  20. package/dist/fragment-patch.d.ts +12 -0
  21. package/dist/fragment-patch.js +118 -0
  22. package/dist/hydrate.d.ts +3 -117
  23. package/dist/hydrate.js +235 -1817
  24. package/dist/index.d.ts +2 -2
  25. package/dist/index.js +2 -2
  26. package/dist/markup.d.ts +8 -0
  27. package/dist/markup.js +127 -0
  28. package/dist/mount-runtime.d.ts +1 -0
  29. package/dist/mount-runtime.js +39 -0
  30. package/dist/payload.d.ts +21 -0
  31. package/dist/payload.js +386 -0
  32. package/dist/reactivity-core.d.ts +3 -0
  33. package/dist/reactivity-core.js +22 -0
  34. package/dist/render.d.ts +3 -0
  35. package/dist/render.js +340 -0
  36. package/dist/scanner.d.ts +1 -0
  37. package/dist/scanner.js +61 -0
  38. package/dist/side-effect-scope.d.ts +16 -0
  39. package/dist/side-effect-scope.js +99 -0
  40. package/dist/signal.js +1 -1
  41. package/dist/state.js +1 -1
  42. package/dist/template-parser.d.ts +1 -0
  43. package/dist/template-parser.js +268 -0
  44. package/dist/template.js +10 -11
  45. package/dist/zeneffect.d.ts +12 -14
  46. package/dist/zeneffect.js +25 -519
  47. package/package.json +5 -3
package/dist/render.js ADDED
@@ -0,0 +1,340 @@
1
+ import { rethrowZenithRuntimeError, throwZenithRuntimeError, DOCS_LINKS } from './diagnostics.js';
2
+ import { createFragmentRegion } from './fragment-patch.js';
3
+ import { _isHtmlFragment } from './markup.js';
4
+ const SVG_NAMESPACE = 'http://www.w3.org/2000/svg';
5
+ const BOOLEAN_ATTRIBUTES = new Set([
6
+ 'disabled', 'checked', 'selected', 'readonly', 'multiple',
7
+ 'hidden', 'autofocus', 'required', 'open'
8
+ ]);
9
+ export function _applyMarkerValue(nodes, marker, value) {
10
+ const markerPath = `marker[${marker.index}]`;
11
+ for (let i = 0; i < nodes.length; i++) {
12
+ try {
13
+ const node = nodes[i];
14
+ if (marker.kind === 'text') {
15
+ if (node && node.nodeType === 8) {
16
+ _applyCommentMarkerValue(node, value, `${markerPath}.text`);
17
+ continue;
18
+ }
19
+ if (_isStructuralFragment(value)) {
20
+ _mountStructuralFragment(node, value, `${markerPath}.text`);
21
+ continue;
22
+ }
23
+ const html = _renderFragmentValue(value, `${markerPath}.text`);
24
+ if (html !== null) {
25
+ node.innerHTML = html;
26
+ }
27
+ else {
28
+ node.textContent = _coerceText(value, `${markerPath}.text`);
29
+ }
30
+ continue;
31
+ }
32
+ if (marker.kind === 'attr') {
33
+ _applyAttribute(node, marker.attr, value);
34
+ }
35
+ }
36
+ catch (error) {
37
+ rethrowZenithRuntimeError(error, {
38
+ phase: 'bind',
39
+ code: 'BINDING_APPLY_FAILED',
40
+ message: `Failed to apply ${marker.kind} binding at marker ${marker.index}`,
41
+ marker: {
42
+ type: marker.kind === 'attr' ? `attr:${marker.attr}` : marker.kind,
43
+ id: marker.index
44
+ },
45
+ path: marker.kind === 'attr'
46
+ ? `${markerPath}.attr.${marker.attr}`
47
+ : `${markerPath}.${marker.kind}`,
48
+ hint: 'Check the binding value type and marker mapping.',
49
+ docsLink: DOCS_LINKS.markerTable,
50
+ source: marker.source
51
+ });
52
+ }
53
+ }
54
+ }
55
+ function _applyCommentMarkerValue(anchor, value, rootPath) {
56
+ if (_isStructuralFragment(value)) {
57
+ _mountStructuralFragmentIntoCommentRange(anchor, value, rootPath);
58
+ return;
59
+ }
60
+ const end = _clearCommentPlaceholderContent(anchor);
61
+ const parent = end.parentNode;
62
+ if (!parent) {
63
+ return;
64
+ }
65
+ const html = _renderFragmentValue(value, rootPath);
66
+ if (html !== null) {
67
+ parent.insertBefore(_createContextualFragment(parent, html), end);
68
+ return;
69
+ }
70
+ const textNode = (parent.ownerDocument || document).createTextNode(_coerceText(value, rootPath));
71
+ parent.insertBefore(textNode, end);
72
+ }
73
+ export function _createContextualFragment(parent, html) {
74
+ const doc = parent.ownerDocument || document;
75
+ if (!doc || typeof doc.createRange !== 'function') {
76
+ throw new Error('[Zenith Runtime] comment placeholder HTML rendering requires Range#createContextualFragment');
77
+ }
78
+ const range = doc.createRange();
79
+ range.selectNode(parent);
80
+ return range.createContextualFragment(html);
81
+ }
82
+ function _isStructuralFragment(value) {
83
+ if (Array.isArray(value)) {
84
+ for (let i = 0; i < value.length; i++) {
85
+ if (_isStructuralFragment(value[i])) {
86
+ return true;
87
+ }
88
+ }
89
+ return false;
90
+ }
91
+ return value && typeof value === 'object' && value.__zenith_fragment === true && typeof value.mount === 'function';
92
+ }
93
+ function _ensureCommentPlaceholderEnd(anchor) {
94
+ let end = anchor.__z_range_end || null;
95
+ if (end && end.parentNode === anchor.parentNode) {
96
+ return end;
97
+ }
98
+ const parent = anchor.parentNode;
99
+ if (!parent) {
100
+ return null;
101
+ }
102
+ end = (anchor.ownerDocument || document).createComment(`/ ${anchor.data}`);
103
+ parent.insertBefore(end, anchor.nextSibling);
104
+ anchor.__z_range_end = end;
105
+ return end;
106
+ }
107
+ function _clearCommentPlaceholderContent(anchor) {
108
+ if (anchor.__z_unmounts) {
109
+ for (let i = 0; i < anchor.__z_unmounts.length; i++) {
110
+ try {
111
+ anchor.__z_unmounts[i]();
112
+ }
113
+ catch {
114
+ }
115
+ }
116
+ }
117
+ anchor.__z_unmounts = [];
118
+ const end = _ensureCommentPlaceholderEnd(anchor);
119
+ if (!end) {
120
+ return anchor;
121
+ }
122
+ let current = anchor.nextSibling;
123
+ while (current && current !== end) {
124
+ const next = current.nextSibling;
125
+ if (current.parentNode) {
126
+ current.parentNode.removeChild(current);
127
+ }
128
+ current = next;
129
+ }
130
+ return end;
131
+ }
132
+ function _mountStructuralFragmentIntoCommentRange(anchor, value, rootPath = 'renderable') {
133
+ let region = anchor.__z_fragment_region;
134
+ if (region && anchor.__z_fragment_region_active) {
135
+ try {
136
+ region.update(value, { parent: anchor.parentNode, insertBefore: anchor.__z_range_end, rootPath });
137
+ }
138
+ catch (error) {
139
+ rethrowZenithRuntimeError(error, {
140
+ phase: 'render',
141
+ code: 'FRAGMENT_MOUNT_FAILED',
142
+ message: 'Fragment update failed',
143
+ path: rootPath,
144
+ hint: 'Verify fragment values and nested renderable arrays.',
145
+ docsLink: DOCS_LINKS.markerTable
146
+ });
147
+ }
148
+ return;
149
+ }
150
+ const end = _clearCommentPlaceholderContent(anchor);
151
+ const parent = end.parentNode;
152
+ if (!parent) {
153
+ return;
154
+ }
155
+ region = createFragmentRegion();
156
+ anchor.__z_fragment_region = region;
157
+ anchor.__z_fragment_region_active = true;
158
+ try {
159
+ region.mount(value, { parent, insertBefore: end, rootPath });
160
+ }
161
+ catch (error) {
162
+ rethrowZenithRuntimeError(error, {
163
+ phase: 'render',
164
+ code: 'FRAGMENT_MOUNT_FAILED',
165
+ message: 'Fragment mount failed',
166
+ path: rootPath,
167
+ hint: 'Verify fragment values and nested renderable arrays.',
168
+ docsLink: DOCS_LINKS.markerTable
169
+ });
170
+ }
171
+ anchor.__z_unmounts = [() => {
172
+ anchor.__z_fragment_region_active = false;
173
+ region.destroy();
174
+ }];
175
+ }
176
+ function _mountStructuralFragment(container, value, rootPath = 'renderable') {
177
+ let region = container.__z_fragment_region;
178
+ if (region && container.__z_fragment_region_active) {
179
+ try {
180
+ region.update(value, { parent: container, insertBefore: null, rootPath });
181
+ }
182
+ catch (error) {
183
+ rethrowZenithRuntimeError(error, {
184
+ phase: 'render',
185
+ code: 'FRAGMENT_MOUNT_FAILED',
186
+ message: 'Fragment update failed',
187
+ path: rootPath,
188
+ hint: 'Verify fragment values and nested renderable arrays.',
189
+ docsLink: DOCS_LINKS.markerTable
190
+ });
191
+ }
192
+ return;
193
+ }
194
+ if (container.__z_unmounts) {
195
+ for (let i = 0; i < container.__z_unmounts.length; i++) {
196
+ try {
197
+ container.__z_unmounts[i]();
198
+ }
199
+ catch {
200
+ }
201
+ }
202
+ }
203
+ container.innerHTML = '';
204
+ region = createFragmentRegion();
205
+ container.__z_fragment_region = region;
206
+ container.__z_fragment_region_active = true;
207
+ try {
208
+ region.mount(value, { parent: container, insertBefore: null, rootPath });
209
+ }
210
+ catch (error) {
211
+ rethrowZenithRuntimeError(error, {
212
+ phase: 'render',
213
+ code: 'FRAGMENT_MOUNT_FAILED',
214
+ message: 'Fragment mount failed',
215
+ path: rootPath,
216
+ hint: 'Verify fragment values and nested renderable arrays.',
217
+ docsLink: DOCS_LINKS.markerTable
218
+ });
219
+ }
220
+ container.__z_unmounts = [() => {
221
+ container.__z_fragment_region_active = false;
222
+ region.destroy();
223
+ }];
224
+ }
225
+ export function _coerceText(value, path = 'renderable') {
226
+ if (value === null || value === undefined || value === false || value === true) {
227
+ return '';
228
+ }
229
+ if (typeof value === 'function') {
230
+ throwZenithRuntimeError({
231
+ phase: 'render',
232
+ code: 'NON_RENDERABLE_VALUE',
233
+ message: `Zenith Render Error: non-renderable function at ${path}. Use map() to render fields.`,
234
+ path,
235
+ hint: 'Convert functions into explicit event handlers or renderable text.',
236
+ docsLink: DOCS_LINKS.expressionScope
237
+ });
238
+ }
239
+ if (value && typeof value === 'object') {
240
+ throwZenithRuntimeError({
241
+ phase: 'render',
242
+ code: 'NON_RENDERABLE_VALUE',
243
+ message: `Zenith Render Error: non-renderable object at ${path}. Use map() to render fields.`,
244
+ path,
245
+ hint: 'Use map() to render object fields into nodes.',
246
+ docsLink: DOCS_LINKS.expressionScope
247
+ });
248
+ }
249
+ return String(value);
250
+ }
251
+ function _renderFragmentValue(value, path = 'renderable') {
252
+ if (value === null || value === undefined || value === false || value === true) {
253
+ return '';
254
+ }
255
+ if (Array.isArray(value)) {
256
+ let out = '';
257
+ for (let i = 0; i < value.length; i++) {
258
+ const itemPath = `${path}[${i}]`;
259
+ const piece = _renderFragmentValue(value[i], itemPath);
260
+ if (piece !== null) {
261
+ out += piece;
262
+ continue;
263
+ }
264
+ out += _escapeHtml(_coerceText(value[i], itemPath));
265
+ }
266
+ return out;
267
+ }
268
+ if (_isHtmlFragment(value)) {
269
+ return value.html;
270
+ }
271
+ return null;
272
+ }
273
+ function _escapeHtml(input) {
274
+ return String(input)
275
+ .replace(/&/g, '&amp;')
276
+ .replace(/</g, '&lt;')
277
+ .replace(/>/g, '&gt;')
278
+ .replace(/"/g, '&quot;')
279
+ .replace(/'/g, '&#39;');
280
+ }
281
+ function _applyAttribute(node, attrName, value) {
282
+ if (typeof attrName === 'string' && attrName.toLowerCase() === 'innerhtml') {
283
+ throwZenithRuntimeError({
284
+ phase: 'bind',
285
+ code: 'UNSAFE_HTML_REQUIRES_EXPLICIT_BOUNDARY',
286
+ message: 'innerHTML bindings are forbidden in Zenith',
287
+ path: `attr:${attrName}`,
288
+ hint: 'Use unsafeHTML={value} for explicit raw HTML insertion, or embedded markup expressions for compiler-owned fragments.'
289
+ });
290
+ }
291
+ if (typeof attrName === 'string' && attrName.toLowerCase() === 'unsafehtml') {
292
+ node.innerHTML = value === null || value === undefined || value === false ? '' : String(value);
293
+ return;
294
+ }
295
+ if (attrName === 'class' || attrName === 'className') {
296
+ const classValue = value === null || value === undefined || value === false ? '' : String(value);
297
+ if (node && node.namespaceURI === SVG_NAMESPACE && typeof node.setAttribute === 'function') {
298
+ node.setAttribute('class', classValue);
299
+ return;
300
+ }
301
+ node.className = classValue;
302
+ return;
303
+ }
304
+ if (attrName === 'style') {
305
+ if (value === null || value === undefined || value === false) {
306
+ node.removeAttribute('style');
307
+ return;
308
+ }
309
+ if (typeof value === 'string') {
310
+ node.setAttribute('style', value);
311
+ return;
312
+ }
313
+ if (typeof value === 'object') {
314
+ const entries = Object.entries(value);
315
+ let styleText = '';
316
+ for (let i = 0; i < entries.length; i++) {
317
+ const [key, rawValue] = entries[i];
318
+ styleText += `${key}: ${rawValue};`;
319
+ }
320
+ node.setAttribute('style', styleText);
321
+ return;
322
+ }
323
+ node.setAttribute('style', String(value));
324
+ return;
325
+ }
326
+ if (BOOLEAN_ATTRIBUTES.has(attrName)) {
327
+ if (value) {
328
+ node.setAttribute(attrName, '');
329
+ }
330
+ else {
331
+ node.removeAttribute(attrName);
332
+ }
333
+ return;
334
+ }
335
+ if (value === null || value === undefined || value === false) {
336
+ node.removeAttribute(attrName);
337
+ return;
338
+ }
339
+ node.setAttribute(attrName, String(value));
340
+ }
@@ -0,0 +1 @@
1
+ export function createNodeResolver(root: any): (selector: any, index: any, kind: any, source?: undefined) => any;
@@ -0,0 +1,61 @@
1
+ import { throwZenithRuntimeError, DOCS_LINKS } from './diagnostics.js';
2
+ export function createNodeResolver(root) {
3
+ let commentIndex = null;
4
+ let commentIndexReady = false;
5
+ return function resolveNodes(selector, index, kind, source = undefined) {
6
+ const nodes = selector.startsWith('comment:')
7
+ ? _lookupCommentNodes(selector.slice('comment:'.length), getCommentIndex())
8
+ : root.querySelectorAll(selector);
9
+ if (!nodes || nodes.length === 0) {
10
+ const isRef = kind === 'ref';
11
+ throwZenithRuntimeError({
12
+ phase: 'bind',
13
+ code: 'MARKER_MISSING',
14
+ message: `Unresolved ${kind} marker index ${index}`,
15
+ marker: { type: kind, id: index },
16
+ path: `selector:${selector}`,
17
+ hint: isRef
18
+ ? 'Use ref + zenMount and ensure the ref is bound in markup before mount.'
19
+ : 'Confirm SSR marker attributes and runtime selector tables match.',
20
+ docsLink: isRef ? DOCS_LINKS.refs : DOCS_LINKS.markerTable,
21
+ source
22
+ });
23
+ }
24
+ return nodes;
25
+ };
26
+ function getCommentIndex() {
27
+ if (!commentIndexReady) {
28
+ commentIndex = _buildCommentIndex(root);
29
+ commentIndexReady = true;
30
+ }
31
+ return commentIndex;
32
+ }
33
+ }
34
+ function _buildCommentIndex(root) {
35
+ const walkerRoot = root && root.nodeType === 9 && root.documentElement ? root.documentElement : root;
36
+ const doc = walkerRoot && walkerRoot.ownerDocument ? walkerRoot.ownerDocument : walkerRoot;
37
+ const nodeFilter = doc?.defaultView?.NodeFilter || globalThis.NodeFilter;
38
+ const cache = new Map();
39
+ if (!walkerRoot || !doc || typeof doc.createTreeWalker !== 'function' || !nodeFilter) {
40
+ return cache;
41
+ }
42
+ const walker = doc.createTreeWalker(walkerRoot, nodeFilter.SHOW_COMMENT);
43
+ let current = walker.nextNode();
44
+ while (current) {
45
+ const list = cache.get(current.data);
46
+ if (list) {
47
+ list.push(current);
48
+ }
49
+ else {
50
+ cache.set(current.data, [current]);
51
+ }
52
+ current = walker.nextNode();
53
+ }
54
+ return cache;
55
+ }
56
+ function _lookupCommentNodes(markerText, cache) {
57
+ if (!cache) {
58
+ return [];
59
+ }
60
+ return cache.get(markerText) || [];
61
+ }
@@ -0,0 +1,16 @@
1
+ export declare function isSideEffectScope(value: any): boolean;
2
+ export declare function resolveSideEffectScope(scopeOverride: any): any;
3
+ export declare function resetGlobalSideEffects(): void;
4
+ export declare function createSideEffectScope(label?: string): {
5
+ __zenith_scope: boolean;
6
+ id: number;
7
+ label: any;
8
+ mountReady: boolean;
9
+ disposed: boolean;
10
+ pendingMounts: never[];
11
+ disposers: never[];
12
+ };
13
+ export declare function activateSideEffectScope(scope: any): void;
14
+ export declare function registerScopeDisposer(scope: any, disposer: any): () => void;
15
+ export declare function disposeSideEffectScope(scope: any): void;
16
+ export declare function queueWhenScopeReady(scope: any, callback: any): void;
@@ -0,0 +1,99 @@
1
+ // @ts-nocheck
2
+ let _scopeIdCounter = 0;
3
+ function createInternalScope(label, mountReady) {
4
+ _scopeIdCounter += 1;
5
+ return {
6
+ __zenith_scope: true,
7
+ id: _scopeIdCounter,
8
+ label,
9
+ mountReady: mountReady === true,
10
+ disposed: false,
11
+ pendingMounts: [],
12
+ disposers: []
13
+ };
14
+ }
15
+ let _globalScope = createInternalScope('global', true);
16
+ export function isSideEffectScope(value) {
17
+ return !!value && typeof value === 'object' && value.__zenith_scope === true;
18
+ }
19
+ export function resolveSideEffectScope(scopeOverride) {
20
+ if (isSideEffectScope(scopeOverride)) {
21
+ return scopeOverride;
22
+ }
23
+ return _globalScope;
24
+ }
25
+ export function resetGlobalSideEffects() {
26
+ disposeSideEffectScope(_globalScope);
27
+ _globalScope = createInternalScope('global', true);
28
+ }
29
+ export function createSideEffectScope(label = 'anonymous') {
30
+ return createInternalScope(label, false);
31
+ }
32
+ export function activateSideEffectScope(scope) {
33
+ if (!isSideEffectScope(scope) || scope.disposed || scope.mountReady) {
34
+ return;
35
+ }
36
+ scope.mountReady = true;
37
+ const pending = scope.pendingMounts.slice();
38
+ scope.pendingMounts.length = 0;
39
+ for (let i = 0; i < pending.length; i++) {
40
+ const callback = pending[i];
41
+ if (typeof callback !== 'function') {
42
+ continue;
43
+ }
44
+ try {
45
+ callback();
46
+ }
47
+ catch {
48
+ }
49
+ }
50
+ }
51
+ export function registerScopeDisposer(scope, disposer) {
52
+ if (typeof disposer !== 'function') {
53
+ return () => { };
54
+ }
55
+ if (!scope || scope.disposed) {
56
+ disposer();
57
+ return () => { };
58
+ }
59
+ scope.disposers.push(disposer);
60
+ return function unregisterScopeDisposer() {
61
+ if (!scope || scope.disposed) {
62
+ return;
63
+ }
64
+ const index = scope.disposers.indexOf(disposer);
65
+ if (index >= 0) {
66
+ scope.disposers.splice(index, 1);
67
+ }
68
+ };
69
+ }
70
+ export function disposeSideEffectScope(scope) {
71
+ if (!scope || scope.disposed) {
72
+ return;
73
+ }
74
+ scope.disposed = true;
75
+ const disposers = scope.disposers.slice();
76
+ scope.disposers.length = 0;
77
+ scope.pendingMounts.length = 0;
78
+ for (let i = disposers.length - 1; i >= 0; i--) {
79
+ const disposer = disposers[i];
80
+ if (typeof disposer !== 'function') {
81
+ continue;
82
+ }
83
+ try {
84
+ disposer();
85
+ }
86
+ catch {
87
+ }
88
+ }
89
+ }
90
+ export function queueWhenScopeReady(scope, callback) {
91
+ if (!scope || scope.disposed) {
92
+ return;
93
+ }
94
+ if (scope.mountReady) {
95
+ callback();
96
+ return;
97
+ }
98
+ scope.pendingMounts.push(callback);
99
+ }
package/dist/signal.js CHANGED
@@ -15,7 +15,7 @@
15
15
  // - No scheduler
16
16
  // - No async queue
17
17
  // ---------------------------------------------------------------------------
18
- import { _nextReactiveId, _trackDependency } from './zeneffect.js';
18
+ import { _nextReactiveId, _trackDependency } from './reactivity-core.js';
19
19
  export function signal(initialValue) {
20
20
  let value = initialValue;
21
21
  const subscribers = new Set();
package/dist/state.js CHANGED
@@ -9,7 +9,7 @@
9
9
  // store.set({ count: 1 });
10
10
  // store.set((prev) => ({ ...prev, count: prev.count + 1 }));
11
11
  // ---------------------------------------------------------------------------
12
- import { _nextReactiveId, _trackDependency } from './zeneffect.js';
12
+ import { _nextReactiveId, _trackDependency } from './reactivity-core.js';
13
13
  function isPlainObject(value) {
14
14
  if (!value || typeof value !== 'object' || Array.isArray(value)) {
15
15
  return false;
@@ -0,0 +1 @@
1
+ export function _rewriteMarkupLiterals(expression: any): string;