@zenithbuild/runtime 0.6.5 → 0.6.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Register an effect disposer for later cleanup.
3
+ *
4
+ * @param {function} dispose
5
+ */
6
+ export function _registerDisposer(dispose: Function): void;
7
+ /**
8
+ * Register an event listener for later cleanup.
9
+ *
10
+ * @param {Element} element
11
+ * @param {string} event
12
+ * @param {function} handler
13
+ */
14
+ export function _registerListener(element: Element, event: string, handler: Function): void;
15
+ /**
16
+ * Tear down all active effects and event listeners.
17
+ *
18
+ * - Disposes all effects (clears subscriber sets)
19
+ * - Removes all event listeners
20
+ * - Clears registries
21
+ * - Idempotent: calling twice is a no-op
22
+ */
23
+ export function cleanup(): void;
24
+ /**
25
+ * Get counts for testing/debugging.
26
+ * @returns {{ effects: number, listeners: number }}
27
+ */
28
+ export function _getCounts(): {
29
+ effects: number;
30
+ listeners: number;
31
+ };
package/dist/cleanup.js CHANGED
@@ -10,17 +10,12 @@
10
10
  // cleanup() removes everything.
11
11
  // Calling cleanup() twice is a no-op.
12
12
  // ---------------------------------------------------------------------------
13
-
14
13
  import { resetGlobalSideEffects } from './zeneffect.js';
15
-
16
14
  /** @type {function[]} */
17
15
  const _disposers = [];
18
-
19
16
  /** @type {{ element: Element, event: string, handler: function }[]} */
20
17
  const _listeners = [];
21
-
22
18
  let _cleaned = false;
23
-
24
19
  /**
25
20
  * Register an effect disposer for later cleanup.
26
21
  *
@@ -30,7 +25,6 @@ export function _registerDisposer(dispose) {
30
25
  _disposers.push(dispose);
31
26
  _cleaned = false;
32
27
  }
33
-
34
28
  /**
35
29
  * Register an event listener for later cleanup.
36
30
  *
@@ -42,7 +36,6 @@ export function _registerListener(element, event, handler) {
42
36
  _listeners.push({ element, event, handler });
43
37
  _cleaned = false;
44
38
  }
45
-
46
39
  /**
47
40
  * Tear down all active effects and event listeners.
48
41
  *
@@ -59,23 +52,19 @@ export function cleanup() {
59
52
  resetGlobalSideEffects();
60
53
  return;
61
54
  }
62
-
63
55
  // 1. Dispose all effects
64
56
  for (let i = 0; i < _disposers.length; i++) {
65
57
  _disposers[i]();
66
58
  }
67
59
  _disposers.length = 0;
68
-
69
60
  // 2. Remove all event listeners
70
61
  for (let i = 0; i < _listeners.length; i++) {
71
62
  const { element, event, handler } = _listeners[i];
72
63
  element.removeEventListener(event, handler);
73
64
  }
74
65
  _listeners.length = 0;
75
-
76
66
  _cleaned = true;
77
67
  }
78
-
79
68
  /**
80
69
  * Get counts for testing/debugging.
81
70
  * @returns {{ effects: number, listeners: number }}
@@ -0,0 +1,4 @@
1
+ export function isZenithRuntimeError(error: any): boolean;
2
+ export function createZenithRuntimeError(details: any, cause: any): Error;
3
+ export function throwZenithRuntimeError(details: any, cause: any): void;
4
+ export function rethrowZenithRuntimeError(error: any, fallback?: {}): void;
@@ -2,7 +2,6 @@ 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
-
6
5
  const VALID_PHASES = new Set(['hydrate', 'bind', 'render', 'event']);
7
6
  const VALID_CODES = new Set([
8
7
  'UNRESOLVED_EXPRESSION',
@@ -12,13 +11,12 @@ const VALID_CODES = new Set([
12
11
  'BINDING_APPLY_FAILED',
13
12
  'EVENT_HANDLER_FAILED'
14
13
  ]);
15
-
16
14
  function _truncate(input, maxLength) {
17
15
  const text = String(input ?? '');
18
- if (text.length <= maxLength) return text;
16
+ if (text.length <= maxLength)
17
+ return text;
19
18
  return `${text.slice(0, maxLength - 3)}...`;
20
19
  }
21
-
22
20
  function _sanitizeAbsolutePaths(value) {
23
21
  return String(value ?? '')
24
22
  .replace(/[A-Za-z]:\\[^\s"'`]+/g, '<path>')
@@ -28,57 +26,78 @@ function _sanitizeAbsolutePaths(value) {
28
26
  .replace(/\/tmp\/[^\s"'`]+/g, '<path>')
29
27
  .replace(/\/var\/folders\/[^\s"'`]+/g, '<path>');
30
28
  }
31
-
32
29
  function _sanitizeMessage(value) {
33
30
  const compact = _sanitizeAbsolutePaths(value).replace(/\s+/g, ' ').trim();
34
31
  return _truncate(compact || 'Runtime failure', MAX_MESSAGE_LENGTH);
35
32
  }
36
-
37
33
  function _sanitizeHint(value) {
38
34
  if (value === null || value === undefined || value === false) {
39
35
  return undefined;
40
36
  }
41
37
  const compact = _sanitizeAbsolutePaths(value).replace(/\s+/g, ' ').trim();
42
- if (!compact) return undefined;
38
+ if (!compact)
39
+ return undefined;
43
40
  return _truncate(compact, MAX_HINT_LENGTH);
44
41
  }
45
-
46
42
  function _sanitizePath(value) {
47
43
  if (value === null || value === undefined || value === false) {
48
44
  return undefined;
49
45
  }
50
46
  const compact = _sanitizeAbsolutePaths(value).replace(/\s+/g, ' ').trim();
51
- if (!compact) return undefined;
47
+ if (!compact)
48
+ return undefined;
52
49
  return _truncate(compact, MAX_PATH_LENGTH);
53
50
  }
54
-
55
51
  function _normalizeMarker(marker) {
56
- if (!marker || typeof marker !== 'object') return undefined;
52
+ if (!marker || typeof marker !== 'object')
53
+ return undefined;
57
54
  const markerType = _truncate(_sanitizeAbsolutePaths(marker.type || 'data-zx'), 48);
58
55
  const markerId = marker.id;
59
- if (markerId === null || markerId === undefined || markerId === '') return undefined;
56
+ if (markerId === null || markerId === undefined || markerId === '')
57
+ return undefined;
60
58
  if (typeof markerId === 'number') {
61
59
  return { type: markerType, id: markerId };
62
60
  }
63
61
  return { type: markerType, id: _truncate(_sanitizeAbsolutePaths(markerId), 48) };
64
62
  }
65
-
66
63
  function _extractErrorMessage(error) {
67
- if (!error) return '';
68
- if (typeof error === 'string') return error;
69
- if (error instanceof Error && typeof error.message === 'string') return error.message;
70
- if (typeof error.message === 'string') return error.message;
64
+ if (!error)
65
+ return '';
66
+ if (typeof error === 'string')
67
+ return error;
68
+ if (error instanceof Error && typeof error.message === 'string')
69
+ return error.message;
70
+ if (typeof error.message === 'string')
71
+ return error.message;
71
72
  return String(error);
72
73
  }
73
-
74
74
  function _safeJson(payload) {
75
75
  try {
76
76
  return JSON.stringify(payload, null, 2);
77
- } catch {
77
+ }
78
+ catch {
78
79
  return '{"kind":"ZENITH_RUNTIME_ERROR","message":"Unable to serialize runtime error payload"}';
79
80
  }
80
81
  }
81
-
82
+ function _readProcessEnv(name) {
83
+ const runtime = typeof globalThis !== 'undefined' ? globalThis : {};
84
+ const runtimeProcess = typeof process !== 'undefined'
85
+ ? process
86
+ : runtime.process;
87
+ if (!runtimeProcess || typeof runtimeProcess !== 'object' || !runtimeProcess.env) {
88
+ return undefined;
89
+ }
90
+ const value = runtimeProcess.env[name];
91
+ return typeof value === 'string' ? value : undefined;
92
+ }
93
+ function _shouldLogRuntimeError() {
94
+ if (_readProcessEnv('ZENITH_LOG_RUNTIME_ERRORS') === '1') {
95
+ return true;
96
+ }
97
+ const isTestMode = _readProcessEnv('NODE_ENV') === 'test'
98
+ || _readProcessEnv('ZENITH_TEST_MODE') === '1';
99
+ return !isTestMode;
100
+ }
82
101
  function _isDevDiagnosticsMode() {
83
102
  const runtime = typeof globalThis !== 'undefined' ? globalThis : {};
84
103
  if (runtime.__ZENITH_RUNTIME_DEV__ === true || runtime.__ZENITH_DEV__ === true) {
@@ -98,11 +117,11 @@ function _isDevDiagnosticsMode() {
98
117
  }
99
118
  return false;
100
119
  }
101
-
102
120
  function _renderOverlay(payload) {
103
- if (!_isDevDiagnosticsMode()) return;
104
- if (typeof document === 'undefined' || !document.body) return;
105
-
121
+ if (!_isDevDiagnosticsMode())
122
+ return;
123
+ if (typeof document === 'undefined' || !document.body)
124
+ return;
106
125
  let overlay = document.getElementById(OVERLAY_ID);
107
126
  if (!overlay) {
108
127
  overlay = document.createElement('aside');
@@ -125,7 +144,6 @@ function _renderOverlay(payload) {
125
144
  overlay.style.fontSize = '12px';
126
145
  overlay.style.lineHeight = '1.45';
127
146
  overlay.style.boxShadow = '0 12px 40px rgba(0,0,0,0.45)';
128
-
129
147
  const copyButton = document.createElement('button');
130
148
  copyButton.type = 'button';
131
149
  copyButton.setAttribute('data-zx-runtime-copy', 'true');
@@ -138,17 +156,14 @@ function _renderOverlay(payload) {
138
156
  copyButton.style.cursor = 'pointer';
139
157
  copyButton.textContent = 'Copy JSON';
140
158
  overlay.appendChild(copyButton);
141
-
142
159
  document.body.appendChild(overlay);
143
160
  }
144
-
145
161
  const textLines = [
146
162
  'Zenith Runtime Error',
147
163
  `phase: ${payload.phase}`,
148
164
  `code: ${payload.code}`,
149
165
  `message: ${payload.message}`
150
166
  ];
151
-
152
167
  if (payload.marker) {
153
168
  textLines.push(`marker: ${payload.marker.type}#${payload.marker.id}`);
154
169
  }
@@ -158,10 +173,8 @@ function _renderOverlay(payload) {
158
173
  if (payload.hint) {
159
174
  textLines.push(`hint: ${payload.hint}`);
160
175
  }
161
-
162
176
  const jsonText = _safeJson(payload);
163
177
  const panelText = textLines.join('\n');
164
-
165
178
  let pre = overlay.querySelector('pre[data-zx-runtime-error]');
166
179
  if (!pre) {
167
180
  pre = document.createElement('pre');
@@ -172,7 +185,6 @@ function _renderOverlay(payload) {
172
185
  overlay.insertBefore(pre, overlay.firstChild);
173
186
  }
174
187
  pre.textContent = panelText;
175
-
176
188
  const copyButton = overlay.querySelector('button[data-zx-runtime-copy="true"]');
177
189
  if (copyButton) {
178
190
  copyButton.onclick = () => {
@@ -183,11 +195,9 @@ function _renderOverlay(payload) {
183
195
  };
184
196
  }
185
197
  }
186
-
187
198
  function _mapLegacyError(error, fallback) {
188
199
  const rawMessage = _extractErrorMessage(error);
189
200
  const safeMessage = _sanitizeMessage(rawMessage);
190
-
191
201
  const details = {
192
202
  phase: VALID_PHASES.has(fallback.phase) ? fallback.phase : 'hydrate',
193
203
  code: VALID_CODES.has(fallback.code) ? fallback.code : 'BINDING_APPLY_FAILED',
@@ -196,12 +206,12 @@ function _mapLegacyError(error, fallback) {
196
206
  path: _sanitizePath(fallback.path),
197
207
  hint: _sanitizeHint(fallback.hint)
198
208
  };
199
-
200
209
  if (/failed to resolve expression literal/i.test(rawMessage)) {
201
210
  details.phase = 'bind';
202
211
  details.code = 'UNRESOLVED_EXPRESSION';
203
212
  details.hint = details.hint || 'Verify expression scope keys and signal aliases.';
204
- } else if (/non-renderable (object|function)/i.test(rawMessage)) {
213
+ }
214
+ else if (/non-renderable (object|function)/i.test(rawMessage)) {
205
215
  details.phase = 'render';
206
216
  details.code = 'NON_RENDERABLE_VALUE';
207
217
  const match = rawMessage.match(/at\s+([A-Za-z0-9_\[\].-]+)/);
@@ -209,7 +219,8 @@ function _mapLegacyError(error, fallback) {
209
219
  details.path = _sanitizePath(match[1]);
210
220
  }
211
221
  details.hint = details.hint || 'Use map() to render object fields into nodes.';
212
- } else if (/unresolved .* marker index/i.test(rawMessage)) {
222
+ }
223
+ else if (/unresolved .* marker index/i.test(rawMessage)) {
213
224
  details.phase = 'bind';
214
225
  details.code = 'MARKER_MISSING';
215
226
  const markerMatch = rawMessage.match(/unresolved\s+(\w+)\s+marker index\s+(\d+)/i);
@@ -221,40 +232,33 @@ function _mapLegacyError(error, fallback) {
221
232
  }
222
233
  details.hint = details.hint || 'Confirm SSR markers and client selector tables match.';
223
234
  }
224
-
225
235
  return details;
226
236
  }
227
-
228
237
  export function isZenithRuntimeError(error) {
229
- return !!(
230
- error &&
238
+ return !!(error &&
231
239
  typeof error === 'object' &&
232
240
  error.zenithRuntimeError &&
233
- error.zenithRuntimeError.kind === 'ZENITH_RUNTIME_ERROR'
234
- );
241
+ error.zenithRuntimeError.kind === 'ZENITH_RUNTIME_ERROR');
235
242
  }
236
-
237
243
  export function createZenithRuntimeError(details, cause) {
238
244
  const phase = VALID_PHASES.has(details?.phase) ? details.phase : 'hydrate';
239
245
  const code = VALID_CODES.has(details?.code) ? details.code : 'BINDING_APPLY_FAILED';
240
246
  const message = _sanitizeMessage(details?.message || 'Runtime failure');
241
-
242
247
  const payload = {
243
248
  kind: 'ZENITH_RUNTIME_ERROR',
244
249
  phase,
245
250
  code,
246
251
  message
247
252
  };
248
-
249
253
  const marker = _normalizeMarker(details?.marker);
250
- if (marker) payload.marker = marker;
251
-
254
+ if (marker)
255
+ payload.marker = marker;
252
256
  const path = _sanitizePath(details?.path);
253
- if (path) payload.path = path;
254
-
257
+ if (path)
258
+ payload.path = path;
255
259
  const hint = _sanitizeHint(details?.hint);
256
- if (hint) payload.hint = hint;
257
-
260
+ if (hint)
261
+ payload.hint = hint;
258
262
  const error = new Error(`[Zenith Runtime] ${code}: ${message}`);
259
263
  error.name = 'ZenithRuntimeError';
260
264
  error.zenithRuntimeError = payload;
@@ -264,23 +268,24 @@ export function createZenithRuntimeError(details, cause) {
264
268
  error.toJSON = () => payload;
265
269
  return error;
266
270
  }
267
-
268
271
  function _reportRuntimeError(error) {
269
- if (!error || error.__zenithRuntimeErrorReported === true) return;
272
+ if (!error || error.__zenithRuntimeErrorReported === true)
273
+ return;
270
274
  error.__zenithRuntimeErrorReported = true;
271
275
  const payload = error.zenithRuntimeError;
272
- if (payload && typeof console !== 'undefined' && typeof console.error === 'function') {
276
+ if (payload
277
+ && _shouldLogRuntimeError()
278
+ && typeof console !== 'undefined'
279
+ && typeof console.error === 'function') {
273
280
  console.error('[Zenith Runtime]', payload);
274
281
  }
275
282
  _renderOverlay(payload);
276
283
  }
277
-
278
284
  export function throwZenithRuntimeError(details, cause) {
279
285
  const error = createZenithRuntimeError(details, cause);
280
286
  _reportRuntimeError(error);
281
287
  throw error;
282
288
  }
283
-
284
289
  export function rethrowZenithRuntimeError(error, fallback = {}) {
285
290
  if (isZenithRuntimeError(error)) {
286
291
  const payload = error.zenithRuntimeError || {};
@@ -288,7 +293,6 @@ export function rethrowZenithRuntimeError(error, fallback = {}) {
288
293
  const marker = !payload.marker ? _normalizeMarker(fallback.marker) : payload.marker;
289
294
  const path = !payload.path ? _sanitizePath(fallback.path) : payload.path;
290
295
  const hint = !payload.hint ? _sanitizeHint(fallback.hint) : payload.hint;
291
-
292
296
  if (marker || path || hint) {
293
297
  updatedPayload = {
294
298
  ...payload,
@@ -0,0 +1 @@
1
+ export { zeneffect as effect } from "./zeneffect.js";
package/dist/env.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ export declare function zenWindow(): Window | null;
2
+ export declare function zenDocument(): Document | null;
package/dist/env.js CHANGED
@@ -1,22 +1,12 @@
1
1
  // ---------------------------------------------------------------------------
2
- // env.js — Zenith Runtime canonical environment accessors
2
+ // env.ts — Zenith Runtime canonical environment accessors
3
3
  // ---------------------------------------------------------------------------
4
4
  // SSR-safe access to window and document. Returns null when not in browser.
5
5
  // Use zenWindow() / zenDocument() instead of direct window/document access.
6
6
  // ---------------------------------------------------------------------------
7
-
8
- /**
9
- * SSR-safe window accessor.
10
- * @returns {Window | null}
11
- */
12
7
  export function zenWindow() {
13
8
  return typeof window === 'undefined' ? null : window;
14
9
  }
15
-
16
- /**
17
- * SSR-safe document accessor.
18
- * @returns {Document | null}
19
- */
20
10
  export function zenDocument() {
21
11
  return typeof document === 'undefined' ? null : document;
22
12
  }
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Bind an event listener to a DOM element.
3
+ *
4
+ * @param {Element} element - The DOM element
5
+ * @param {string} eventName - The event name (e.g. "click")
6
+ * @param {() => any} exprFn - Pre-bound expression function that must resolve to a handler
7
+ */
8
+ export function bindEvent(element: Element, eventName: string, exprFn: () => any): void;
package/dist/events.js CHANGED
@@ -14,10 +14,8 @@
14
14
  // No delegation.
15
15
  // No wrapping.
16
16
  // ---------------------------------------------------------------------------
17
-
18
17
  import { _registerListener } from './cleanup.js';
19
18
  import { rethrowZenithRuntimeError, throwZenithRuntimeError } from './diagnostics.js';
20
-
21
19
  /**
22
20
  * Bind an event listener to a DOM element.
23
21
  *
@@ -27,7 +25,6 @@ import { rethrowZenithRuntimeError, throwZenithRuntimeError } from './diagnostic
27
25
  */
28
26
  export function bindEvent(element, eventName, exprFn) {
29
27
  const resolved = exprFn();
30
-
31
28
  if (typeof resolved !== 'function') {
32
29
  throwZenithRuntimeError({
33
30
  phase: 'bind',
@@ -38,11 +35,11 @@ export function bindEvent(element, eventName, exprFn) {
38
35
  hint: 'Bind events to function references.'
39
36
  });
40
37
  }
41
-
42
38
  const wrapped = function zenithBoundEvent(event) {
43
39
  try {
44
40
  return resolved.call(this, event);
45
- } catch (error) {
41
+ }
42
+ catch (error) {
46
43
  rethrowZenithRuntimeError(error, {
47
44
  phase: 'event',
48
45
  code: 'EVENT_HANDLER_FAILED',
@@ -53,7 +50,6 @@ export function bindEvent(element, eventName, exprFn) {
53
50
  });
54
51
  }
55
52
  };
56
-
57
53
  element.addEventListener(eventName, wrapped);
58
54
  _registerListener(element, eventName, wrapped);
59
55
  }
@@ -0,0 +1,57 @@
1
+ /**
2
+ * Hydrate a pre-rendered DOM tree using explicit payload tables.
3
+ *
4
+ * @param {{
5
+ * ir_version: number,
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 }>,
11
+ * state_values: Array<*>,
12
+ * state_keys?: Array<string>,
13
+ * signals: Array<{ id: number, kind: 'signal', state_index: number }>,
14
+ * components?: Array<{ instance: string, selector: string, create: Function }>
15
+ * }} payload
16
+ * @returns {() => void}
17
+ */
18
+ export function hydrate(payload: {
19
+ ir_version: number;
20
+ root: Document | Element;
21
+ expressions: Array<{
22
+ marker_index: number;
23
+ signal_index?: number | null;
24
+ state_index?: number | null;
25
+ component_instance?: string | null;
26
+ component_binding?: string | null;
27
+ literal?: string | null;
28
+ }>;
29
+ markers: Array<{
30
+ index: number;
31
+ kind: "text" | "attr" | "event";
32
+ selector: string;
33
+ attr?: string;
34
+ }>;
35
+ events: Array<{
36
+ index: number;
37
+ event: string;
38
+ selector: string;
39
+ }>;
40
+ refs?: Array<{
41
+ index: number;
42
+ state_index: number;
43
+ selector: string;
44
+ }>;
45
+ state_values: Array<any>;
46
+ state_keys?: Array<string>;
47
+ signals: Array<{
48
+ id: number;
49
+ kind: "signal";
50
+ state_index: number;
51
+ }>;
52
+ components?: Array<{
53
+ instance: string;
54
+ selector: string;
55
+ create: Function;
56
+ }>;
57
+ }): () => void;