@zenithbuild/runtime 0.6.6 → 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,80 +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
82
  function _readProcessEnv(name) {
83
83
  const runtime = typeof globalThis !== 'undefined' ? globalThis : {};
84
84
  const runtimeProcess = typeof process !== 'undefined'
85
85
  ? process
86
86
  : runtime.process;
87
-
88
87
  if (!runtimeProcess || typeof runtimeProcess !== 'object' || !runtimeProcess.env) {
89
88
  return undefined;
90
89
  }
91
90
  const value = runtimeProcess.env[name];
92
91
  return typeof value === 'string' ? value : undefined;
93
92
  }
94
-
95
93
  function _shouldLogRuntimeError() {
96
94
  if (_readProcessEnv('ZENITH_LOG_RUNTIME_ERRORS') === '1') {
97
95
  return true;
98
96
  }
99
- const isTestMode =
100
- _readProcessEnv('NODE_ENV') === 'test'
97
+ const isTestMode = _readProcessEnv('NODE_ENV') === 'test'
101
98
  || _readProcessEnv('ZENITH_TEST_MODE') === '1';
102
99
  return !isTestMode;
103
100
  }
104
-
105
101
  function _isDevDiagnosticsMode() {
106
102
  const runtime = typeof globalThis !== 'undefined' ? globalThis : {};
107
103
  if (runtime.__ZENITH_RUNTIME_DEV__ === true || runtime.__ZENITH_DEV__ === true) {
@@ -121,11 +117,11 @@ function _isDevDiagnosticsMode() {
121
117
  }
122
118
  return false;
123
119
  }
124
-
125
120
  function _renderOverlay(payload) {
126
- if (!_isDevDiagnosticsMode()) return;
127
- if (typeof document === 'undefined' || !document.body) return;
128
-
121
+ if (!_isDevDiagnosticsMode())
122
+ return;
123
+ if (typeof document === 'undefined' || !document.body)
124
+ return;
129
125
  let overlay = document.getElementById(OVERLAY_ID);
130
126
  if (!overlay) {
131
127
  overlay = document.createElement('aside');
@@ -148,7 +144,6 @@ function _renderOverlay(payload) {
148
144
  overlay.style.fontSize = '12px';
149
145
  overlay.style.lineHeight = '1.45';
150
146
  overlay.style.boxShadow = '0 12px 40px rgba(0,0,0,0.45)';
151
-
152
147
  const copyButton = document.createElement('button');
153
148
  copyButton.type = 'button';
154
149
  copyButton.setAttribute('data-zx-runtime-copy', 'true');
@@ -161,17 +156,14 @@ function _renderOverlay(payload) {
161
156
  copyButton.style.cursor = 'pointer';
162
157
  copyButton.textContent = 'Copy JSON';
163
158
  overlay.appendChild(copyButton);
164
-
165
159
  document.body.appendChild(overlay);
166
160
  }
167
-
168
161
  const textLines = [
169
162
  'Zenith Runtime Error',
170
163
  `phase: ${payload.phase}`,
171
164
  `code: ${payload.code}`,
172
165
  `message: ${payload.message}`
173
166
  ];
174
-
175
167
  if (payload.marker) {
176
168
  textLines.push(`marker: ${payload.marker.type}#${payload.marker.id}`);
177
169
  }
@@ -181,10 +173,8 @@ function _renderOverlay(payload) {
181
173
  if (payload.hint) {
182
174
  textLines.push(`hint: ${payload.hint}`);
183
175
  }
184
-
185
176
  const jsonText = _safeJson(payload);
186
177
  const panelText = textLines.join('\n');
187
-
188
178
  let pre = overlay.querySelector('pre[data-zx-runtime-error]');
189
179
  if (!pre) {
190
180
  pre = document.createElement('pre');
@@ -195,7 +185,6 @@ function _renderOverlay(payload) {
195
185
  overlay.insertBefore(pre, overlay.firstChild);
196
186
  }
197
187
  pre.textContent = panelText;
198
-
199
188
  const copyButton = overlay.querySelector('button[data-zx-runtime-copy="true"]');
200
189
  if (copyButton) {
201
190
  copyButton.onclick = () => {
@@ -206,11 +195,9 @@ function _renderOverlay(payload) {
206
195
  };
207
196
  }
208
197
  }
209
-
210
198
  function _mapLegacyError(error, fallback) {
211
199
  const rawMessage = _extractErrorMessage(error);
212
200
  const safeMessage = _sanitizeMessage(rawMessage);
213
-
214
201
  const details = {
215
202
  phase: VALID_PHASES.has(fallback.phase) ? fallback.phase : 'hydrate',
216
203
  code: VALID_CODES.has(fallback.code) ? fallback.code : 'BINDING_APPLY_FAILED',
@@ -219,12 +206,12 @@ function _mapLegacyError(error, fallback) {
219
206
  path: _sanitizePath(fallback.path),
220
207
  hint: _sanitizeHint(fallback.hint)
221
208
  };
222
-
223
209
  if (/failed to resolve expression literal/i.test(rawMessage)) {
224
210
  details.phase = 'bind';
225
211
  details.code = 'UNRESOLVED_EXPRESSION';
226
212
  details.hint = details.hint || 'Verify expression scope keys and signal aliases.';
227
- } else if (/non-renderable (object|function)/i.test(rawMessage)) {
213
+ }
214
+ else if (/non-renderable (object|function)/i.test(rawMessage)) {
228
215
  details.phase = 'render';
229
216
  details.code = 'NON_RENDERABLE_VALUE';
230
217
  const match = rawMessage.match(/at\s+([A-Za-z0-9_\[\].-]+)/);
@@ -232,7 +219,8 @@ function _mapLegacyError(error, fallback) {
232
219
  details.path = _sanitizePath(match[1]);
233
220
  }
234
221
  details.hint = details.hint || 'Use map() to render object fields into nodes.';
235
- } else if (/unresolved .* marker index/i.test(rawMessage)) {
222
+ }
223
+ else if (/unresolved .* marker index/i.test(rawMessage)) {
236
224
  details.phase = 'bind';
237
225
  details.code = 'MARKER_MISSING';
238
226
  const markerMatch = rawMessage.match(/unresolved\s+(\w+)\s+marker index\s+(\d+)/i);
@@ -244,40 +232,33 @@ function _mapLegacyError(error, fallback) {
244
232
  }
245
233
  details.hint = details.hint || 'Confirm SSR markers and client selector tables match.';
246
234
  }
247
-
248
235
  return details;
249
236
  }
250
-
251
237
  export function isZenithRuntimeError(error) {
252
- return !!(
253
- error &&
238
+ return !!(error &&
254
239
  typeof error === 'object' &&
255
240
  error.zenithRuntimeError &&
256
- error.zenithRuntimeError.kind === 'ZENITH_RUNTIME_ERROR'
257
- );
241
+ error.zenithRuntimeError.kind === 'ZENITH_RUNTIME_ERROR');
258
242
  }
259
-
260
243
  export function createZenithRuntimeError(details, cause) {
261
244
  const phase = VALID_PHASES.has(details?.phase) ? details.phase : 'hydrate';
262
245
  const code = VALID_CODES.has(details?.code) ? details.code : 'BINDING_APPLY_FAILED';
263
246
  const message = _sanitizeMessage(details?.message || 'Runtime failure');
264
-
265
247
  const payload = {
266
248
  kind: 'ZENITH_RUNTIME_ERROR',
267
249
  phase,
268
250
  code,
269
251
  message
270
252
  };
271
-
272
253
  const marker = _normalizeMarker(details?.marker);
273
- if (marker) payload.marker = marker;
274
-
254
+ if (marker)
255
+ payload.marker = marker;
275
256
  const path = _sanitizePath(details?.path);
276
- if (path) payload.path = path;
277
-
257
+ if (path)
258
+ payload.path = path;
278
259
  const hint = _sanitizeHint(details?.hint);
279
- if (hint) payload.hint = hint;
280
-
260
+ if (hint)
261
+ payload.hint = hint;
281
262
  const error = new Error(`[Zenith Runtime] ${code}: ${message}`);
282
263
  error.name = 'ZenithRuntimeError';
283
264
  error.zenithRuntimeError = payload;
@@ -287,28 +268,24 @@ export function createZenithRuntimeError(details, cause) {
287
268
  error.toJSON = () => payload;
288
269
  return error;
289
270
  }
290
-
291
271
  function _reportRuntimeError(error) {
292
- if (!error || error.__zenithRuntimeErrorReported === true) return;
272
+ if (!error || error.__zenithRuntimeErrorReported === true)
273
+ return;
293
274
  error.__zenithRuntimeErrorReported = true;
294
275
  const payload = error.zenithRuntimeError;
295
- if (
296
- payload
276
+ if (payload
297
277
  && _shouldLogRuntimeError()
298
278
  && typeof console !== 'undefined'
299
- && typeof console.error === 'function'
300
- ) {
279
+ && typeof console.error === 'function') {
301
280
  console.error('[Zenith Runtime]', payload);
302
281
  }
303
282
  _renderOverlay(payload);
304
283
  }
305
-
306
284
  export function throwZenithRuntimeError(details, cause) {
307
285
  const error = createZenithRuntimeError(details, cause);
308
286
  _reportRuntimeError(error);
309
287
  throw error;
310
288
  }
311
-
312
289
  export function rethrowZenithRuntimeError(error, fallback = {}) {
313
290
  if (isZenithRuntimeError(error)) {
314
291
  const payload = error.zenithRuntimeError || {};
@@ -316,7 +293,6 @@ export function rethrowZenithRuntimeError(error, fallback = {}) {
316
293
  const marker = !payload.marker ? _normalizeMarker(fallback.marker) : payload.marker;
317
294
  const path = !payload.path ? _sanitizePath(fallback.path) : payload.path;
318
295
  const hint = !payload.hint ? _sanitizeHint(fallback.hint) : payload.hint;
319
-
320
296
  if (marker || path || hint) {
321
297
  updatedPayload = {
322
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;