@zenithbuild/runtime 0.7.9 → 0.7.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cleanup.js CHANGED
@@ -11,6 +11,7 @@
11
11
  // Calling cleanup() twice is a no-op.
12
12
  // ---------------------------------------------------------------------------
13
13
  import { resetGlobalSideEffects } from './side-effect-scope.js';
14
+ import { runCleanupCallback, throwCleanupErrors } from './effect-utils.js';
14
15
  /** @type {function[]} */
15
16
  const _disposers = [];
16
17
  /** @type {{ element: Element, event: string, handler: function }[]} */
@@ -45,28 +46,31 @@ export function _registerListener(element, event, handler) {
45
46
  * - Idempotent: calling twice is a no-op
46
47
  */
47
48
  export function cleanup() {
49
+ const errors = [];
48
50
  // Global zenMount/zenEffect registrations can be created outside hydrate's
49
51
  // disposer table (e.g. page-level component bootstraps). cleanup() is the
50
52
  // full runtime teardown boundary, so that scope must be reset on every pass,
51
53
  // including the first real cleanup.
52
54
  if (_cleaned) {
53
- resetGlobalSideEffects();
55
+ resetGlobalSideEffects(errors);
56
+ throwCleanupErrors(errors, 'cleanup');
54
57
  return;
55
58
  }
56
59
  // 1. Dispose all effects
57
60
  for (let i = 0; i < _disposers.length; i++) {
58
- _disposers[i]();
61
+ runCleanupCallback(_disposers[i], errors);
59
62
  }
60
63
  _disposers.length = 0;
61
64
  // 2. Remove all event listeners
62
65
  for (let i = 0; i < _listeners.length; i++) {
63
66
  const { element, event, handler } = _listeners[i];
64
- element.removeEventListener(event, handler);
67
+ runCleanupCallback(() => element.removeEventListener(event, handler), errors);
65
68
  }
66
69
  _listeners.length = 0;
67
70
  // 3. Dispose any top-level reactive work registered outside hydrate.
68
- resetGlobalSideEffects();
71
+ resetGlobalSideEffects(errors);
69
72
  _cleaned = true;
73
+ throwCleanupErrors(errors, 'cleanup');
70
74
  }
71
75
  /**
72
76
  * Get counts for testing/debugging.
@@ -1,2 +1,2 @@
1
- export declare function createAutoTrackedEffect(effect: any, options: any, scope: any): () => void;
2
- export declare function createExplicitDependencyEffect(effect: any, dependencies: any, scope: any): () => void;
1
+ export declare function createAutoTrackedEffect(effect: any, options: any, scope: any): (errors?: null) => void;
2
+ export declare function createExplicitDependencyEffect(effect: any, dependencies: any, scope: any): (errors?: null) => void;
@@ -1,11 +1,12 @@
1
1
  // @ts-nocheck
2
2
  import { runWithDependencyCollector } from './reactivity-core.js';
3
3
  import { registerScopeDisposer, queueWhenScopeReady } from './side-effect-scope.js';
4
- import { drainCleanupStack, applyCleanupResult, createEffectContext } from './effect-utils.js';
4
+ import { drainCleanupStack, applyCleanupResult, createEffectContext, runCleanupCallback } from './effect-utils.js';
5
5
  import { createScheduler } from './effect-scheduler.js';
6
6
  let _effectIdCounter = 0;
7
7
  export function createAutoTrackedEffect(effect, options, scope) {
8
8
  let disposed = false;
9
+ let completedSetup = false;
9
10
  const activeSubscriptions = new Map();
10
11
  const runCleanups = [];
11
12
  _effectIdCounter += 1;
@@ -22,47 +23,56 @@ export function createAutoTrackedEffect(effect, options, scope) {
22
23
  }
23
24
  drainCleanupStack(runCleanups);
24
25
  const nextDependenciesById = new Map();
25
- runWithDependencyCollector((source) => {
26
- if (!source || typeof source.subscribe !== 'function') {
27
- return;
28
- }
29
- const reactiveId = Number.isInteger(source.__zenith_id) ? source.__zenith_id : 0;
30
- if (!nextDependenciesById.has(reactiveId)) {
31
- nextDependenciesById.set(reactiveId, source);
32
- }
33
- }, () => {
34
- const result = effect(createEffectContext(registerCleanup));
35
- applyCleanupResult(result, registerCleanup);
36
- });
37
- const nextDependencies = Array.from(nextDependenciesById.values()).sort((left, right) => {
38
- const leftId = Number.isInteger(left.__zenith_id) ? left.__zenith_id : 0;
39
- const rightId = Number.isInteger(right.__zenith_id) ? right.__zenith_id : 0;
40
- return leftId - rightId;
41
- });
42
- const nextSet = new Set(nextDependencies);
43
- for (const [dependency, unsubscribe] of activeSubscriptions.entries()) {
44
- if (nextSet.has(dependency)) {
45
- continue;
26
+ try {
27
+ runWithDependencyCollector((source) => {
28
+ if (!source || typeof source.subscribe !== 'function') {
29
+ return;
30
+ }
31
+ const reactiveId = Number.isInteger(source.__zenith_id) ? source.__zenith_id : 0;
32
+ if (!nextDependenciesById.has(reactiveId)) {
33
+ nextDependenciesById.set(reactiveId, source);
34
+ }
35
+ }, () => {
36
+ const result = effect(createEffectContext(registerCleanup));
37
+ applyCleanupResult(result, registerCleanup);
38
+ });
39
+ const nextDependencies = Array.from(nextDependenciesById.values()).sort((left, right) => {
40
+ const leftId = Number.isInteger(left.__zenith_id) ? left.__zenith_id : 0;
41
+ const rightId = Number.isInteger(right.__zenith_id) ? right.__zenith_id : 0;
42
+ return leftId - rightId;
43
+ });
44
+ const nextSet = new Set(nextDependencies);
45
+ for (const [dependency, unsubscribe] of activeSubscriptions.entries()) {
46
+ if (nextSet.has(dependency)) {
47
+ continue;
48
+ }
49
+ if (typeof unsubscribe === 'function') {
50
+ unsubscribe();
51
+ }
52
+ activeSubscriptions.delete(dependency);
46
53
  }
47
- if (typeof unsubscribe === 'function') {
48
- unsubscribe();
54
+ for (let i = 0; i < nextDependencies.length; i++) {
55
+ const dependency = nextDependencies[i];
56
+ if (activeSubscriptions.has(dependency)) {
57
+ continue;
58
+ }
59
+ const unsubscribe = dependency.subscribe(() => {
60
+ scheduler.schedule();
61
+ });
62
+ activeSubscriptions.set(dependency, typeof unsubscribe === 'function' ? unsubscribe : () => { });
49
63
  }
50
- activeSubscriptions.delete(dependency);
64
+ completedSetup = true;
51
65
  }
52
- for (let i = 0; i < nextDependencies.length; i++) {
53
- const dependency = nextDependencies[i];
54
- if (activeSubscriptions.has(dependency)) {
55
- continue;
66
+ catch (error) {
67
+ if (!completedSetup) {
68
+ disposeEffect();
56
69
  }
57
- const unsubscribe = dependency.subscribe(() => {
58
- scheduler.schedule();
59
- });
60
- activeSubscriptions.set(dependency, typeof unsubscribe === 'function' ? unsubscribe : () => { });
70
+ throw error;
61
71
  }
62
72
  void effectId;
63
73
  }
64
74
  const scheduler = createScheduler(runEffectNow, options);
65
- function disposeEffect() {
75
+ function disposeEffect(errors = null) {
66
76
  if (disposed) {
67
77
  return;
68
78
  }
@@ -70,11 +80,11 @@ export function createAutoTrackedEffect(effect, options, scope) {
70
80
  scheduler.cancel();
71
81
  for (const unsubscribe of activeSubscriptions.values()) {
72
82
  if (typeof unsubscribe === 'function') {
73
- unsubscribe();
83
+ runCleanupCallback(unsubscribe, errors);
74
84
  }
75
85
  }
76
86
  activeSubscriptions.clear();
77
- drainCleanupStack(runCleanups);
87
+ drainCleanupStack(runCleanups, errors);
78
88
  }
79
89
  registerScopeDisposer(scope, disposeEffect);
80
90
  queueWhenScopeReady(scope, () => scheduler.schedule());
@@ -91,7 +101,9 @@ export function createExplicitDependencyEffect(effect, dependencies, scope) {
91
101
  throw new Error('[Zenith Runtime] zeneffect(deps, fn) requires a function');
92
102
  }
93
103
  let disposed = false;
104
+ let completedSetup = false;
94
105
  const runCleanups = [];
106
+ const unsubscribers = [];
95
107
  function registerCleanup(cleanup) {
96
108
  if (typeof cleanup !== 'function') {
97
109
  throw new Error('[Zenith Runtime] cleanup(fn) requires a function');
@@ -103,37 +115,60 @@ export function createExplicitDependencyEffect(effect, dependencies, scope) {
103
115
  return;
104
116
  }
105
117
  drainCleanupStack(runCleanups);
106
- const result = effect(createEffectContext(registerCleanup));
107
- applyCleanupResult(result, registerCleanup);
108
- }
109
- const unsubscribers = dependencies.map((dep, index) => {
110
- if (!dep || typeof dep.subscribe !== 'function') {
111
- throw new Error(`[Zenith Runtime] zeneffect dependency at index ${index} must expose subscribe(fn)`);
112
- }
113
- return dep.subscribe(() => {
114
- if (scope?.mountReady === true) {
115
- runEffectNow();
116
- return;
118
+ try {
119
+ const result = effect(createEffectContext(registerCleanup));
120
+ applyCleanupResult(result, registerCleanup);
121
+ completedSetup = true;
122
+ }
123
+ catch (error) {
124
+ if (!completedSetup) {
125
+ dispose();
117
126
  }
118
- queueWhenScopeReady(scope, runEffectNow);
119
- });
120
- });
121
- if (scope?.mountReady === true) {
122
- runEffectNow();
123
- }
124
- else {
125
- queueWhenScopeReady(scope, runEffectNow);
127
+ throw error;
128
+ }
126
129
  }
127
- const dispose = () => {
130
+ function dispose(errors = null) {
128
131
  if (disposed) {
129
132
  return;
130
133
  }
131
134
  disposed = true;
132
- for (let i = 0; i < unsubscribers.length; i++) {
133
- unsubscribers[i]();
135
+ for (let i = unsubscribers.length - 1; i >= 0; i--) {
136
+ if (typeof unsubscribers[i] === 'function') {
137
+ runCleanupCallback(unsubscribers[i], errors);
138
+ }
134
139
  }
135
- drainCleanupStack(runCleanups);
136
- };
137
- registerScopeDisposer(scope, dispose);
138
- return dispose;
140
+ unsubscribers.length = 0;
141
+ drainCleanupStack(runCleanups, errors);
142
+ }
143
+ try {
144
+ for (let index = 0; index < dependencies.length; index++) {
145
+ const dep = dependencies[index];
146
+ if (!dep || typeof dep.subscribe !== 'function') {
147
+ throw new Error(`[Zenith Runtime] zeneffect dependency at index ${index} must expose subscribe(fn)`);
148
+ }
149
+ }
150
+ for (let index = 0; index < dependencies.length; index++) {
151
+ const dep = dependencies[index];
152
+ const unsubscribe = dep.subscribe(() => {
153
+ if (scope?.mountReady === true) {
154
+ runEffectNow();
155
+ return;
156
+ }
157
+ queueWhenScopeReady(scope, runEffectNow);
158
+ });
159
+ unsubscribers.push(typeof unsubscribe === 'function' ? unsubscribe : () => { });
160
+ }
161
+ if (scope?.mountReady === true) {
162
+ runEffectNow();
163
+ }
164
+ else {
165
+ queueWhenScopeReady(scope, runEffectNow);
166
+ }
167
+ registerScopeDisposer(scope, dispose);
168
+ return dispose;
169
+ }
170
+ catch (error) {
171
+ dispose();
172
+ throw error;
173
+ }
139
174
  }
@@ -5,7 +5,9 @@ export declare function normalizeEffectOptions(options: any): {
5
5
  raf: boolean;
6
6
  flush: string;
7
7
  };
8
- export declare function drainCleanupStack(cleanups: any): void;
8
+ export declare function drainCleanupStack(cleanups: any, errors?: null): void;
9
+ export declare function runCleanupCallback(callback: any, errors?: null): void;
10
+ export declare function throwCleanupErrors(errors: any, label?: string): void;
9
11
  export declare function applyCleanupResult(result: any, registerCleanup: any): void;
10
12
  export declare function createMountContext(registerCleanup: any): {
11
13
  cleanup: any;
@@ -38,7 +38,8 @@ export function normalizeEffectOptions(options) {
38
38
  }
39
39
  return normalized;
40
40
  }
41
- export function drainCleanupStack(cleanups) {
41
+ export function drainCleanupStack(cleanups, errors = null) {
42
+ const errorSink = Array.isArray(errors) ? errors : null;
42
43
  for (let i = cleanups.length - 1; i >= 0; i--) {
43
44
  const cleanup = cleanups[i];
44
45
  if (typeof cleanup !== 'function') {
@@ -47,11 +48,35 @@ export function drainCleanupStack(cleanups) {
47
48
  try {
48
49
  cleanup();
49
50
  }
50
- catch {
51
+ catch (error) {
52
+ if (errorSink) {
53
+ errorSink.push(error);
54
+ }
51
55
  }
52
56
  }
53
57
  cleanups.length = 0;
54
58
  }
59
+ export function runCleanupCallback(callback, errors = null) {
60
+ try {
61
+ callback();
62
+ }
63
+ catch (error) {
64
+ if (Array.isArray(errors)) {
65
+ errors.push(error);
66
+ }
67
+ }
68
+ }
69
+ export function throwCleanupErrors(errors, label = 'cleanup') {
70
+ if (!Array.isArray(errors) || errors.length === 0) {
71
+ return;
72
+ }
73
+ const message = `[Zenith Runtime] ${label} failed with ${errors.length} error(s)`;
74
+ const error = typeof AggregateError === 'function'
75
+ ? new AggregateError(errors, message)
76
+ : new Error(message);
77
+ error.zenithCleanupErrors = errors;
78
+ throw error;
79
+ }
55
80
  export function applyCleanupResult(result, registerCleanup) {
56
81
  if (typeof result === 'function') {
57
82
  registerCleanup(result);
package/dist/markup.js CHANGED
@@ -10,6 +10,19 @@ const _FRAGMENT_EVENT_ATTR_RE = /\bon[a-z]+\s*=/i;
10
10
  const _FRAGMENT_JS_URL_RE = /javascript\s*:/i;
11
11
  const _FRAGMENT_PROTO_KEYS = new Set(['__proto__', 'constructor', 'prototype']);
12
12
  const _FRAGMENT_SCRIPT_CLOSE_RE = /<\/script/gi;
13
+ const _FRAGMENT_URL_ATTRS = new Set(['href', 'src', 'srcset', 'action', 'formaction', 'poster', 'xlink:href']);
14
+ const _FRAGMENT_ALLOWED_PROTOCOLS = new Set(['http:', 'https:', 'mailto:', 'tel:']);
15
+ const _FRAGMENT_URL_ATTR_RE = /\s([:@A-Za-z0-9_.-]+)\s*=\s*(?:"([^"]*)"|'([^']*)'|([^\s"'=<>`]+))/g;
16
+ const _FRAGMENT_NAMED_ENTITIES = {
17
+ amp: '&',
18
+ apos: "'",
19
+ colon: ':',
20
+ gt: '>',
21
+ lt: '<',
22
+ newline: '\n',
23
+ quot: '"',
24
+ tab: '\t'
25
+ };
13
26
  function _createHtmlFragment(html) {
14
27
  return {
15
28
  __zenith_fragment: true,
@@ -65,9 +78,119 @@ export function _fragment(strings, ...values) {
65
78
  hint: 'javascript: URLs are forbidden.'
66
79
  });
67
80
  }
81
+ _validateFragmentUrlAttributes(result);
68
82
  result = result.replace(_FRAGMENT_SCRIPT_CLOSE_RE, '<\\/script');
69
83
  return _createHtmlFragment(result);
70
84
  }
85
+ function _validateFragmentUrlAttributes(html) {
86
+ if (_validateFragmentUrlAttributesWithTemplate(html)) {
87
+ return;
88
+ }
89
+ _FRAGMENT_URL_ATTR_RE.lastIndex = 0;
90
+ for (const match of html.matchAll(_FRAGMENT_URL_ATTR_RE)) {
91
+ const attrName = String(match[1] || '').toLowerCase();
92
+ if (!_FRAGMENT_URL_ATTRS.has(attrName)) {
93
+ continue;
94
+ }
95
+ _validateFragmentUrlAttribute(attrName, match[2] ?? match[3] ?? match[4] ?? '');
96
+ }
97
+ }
98
+ function _validateFragmentUrlAttributesWithTemplate(html) {
99
+ const doc = globalThis.document;
100
+ if (!doc || typeof doc.createElement !== 'function') {
101
+ return false;
102
+ }
103
+ const template = doc.createElement('template');
104
+ if (!template || !('innerHTML' in template)) {
105
+ return false;
106
+ }
107
+ template.innerHTML = html;
108
+ const stack = Array.from(template.content?.childNodes || []);
109
+ while (stack.length > 0) {
110
+ const node = stack.pop();
111
+ if (!node) {
112
+ continue;
113
+ }
114
+ if (node.nodeType === 1 && node.attributes) {
115
+ for (let i = 0; i < node.attributes.length; i++) {
116
+ const attr = node.attributes[i];
117
+ const attrName = String(attr.name || '').toLowerCase();
118
+ if (_FRAGMENT_URL_ATTRS.has(attrName)) {
119
+ _validateFragmentUrlAttribute(attrName, attr.value || '');
120
+ }
121
+ }
122
+ }
123
+ const children = node.childNodes ? Array.from(node.childNodes) : [];
124
+ for (let i = children.length - 1; i >= 0; i--) {
125
+ stack.push(children[i]);
126
+ }
127
+ }
128
+ return true;
129
+ }
130
+ function _validateFragmentUrlAttribute(attrName, value) {
131
+ if (attrName === 'srcset') {
132
+ _validateFragmentSrcsetAttribute(value);
133
+ return;
134
+ }
135
+ _validateFragmentSingleUrlAttribute(attrName, value);
136
+ }
137
+ function _validateFragmentSingleUrlAttribute(attrName, value) {
138
+ const protocol = _normalizedFragmentUrlProtocol(value);
139
+ if (protocol && !_FRAGMENT_ALLOWED_PROTOCOLS.has(protocol)) {
140
+ throwZenithRuntimeError({
141
+ phase: 'render',
142
+ code: 'NON_RENDERABLE_VALUE',
143
+ message: `Embedded markup expression contains unsafe URL protocol in ${attrName}`,
144
+ hint: 'Use a relative URL or an allowed absolute URL.'
145
+ });
146
+ }
147
+ }
148
+ function _validateFragmentSrcsetAttribute(value) {
149
+ const candidates = String(value || '').split(',');
150
+ for (let i = 0; i < candidates.length; i++) {
151
+ const candidate = candidates[i].trim();
152
+ if (!candidate) {
153
+ continue;
154
+ }
155
+ const url = candidate.split(/[\u0000-\u001F\u007F\s]+/, 1)[0] || '';
156
+ _validateFragmentSingleUrlAttribute('srcset', url);
157
+ }
158
+ }
159
+ function _normalizedFragmentUrlProtocol(value) {
160
+ const normalized = _decodeFragmentAttributeValue(value)
161
+ .trim()
162
+ .replace(/[\u0000-\u001F\u007F\s]+/g, '');
163
+ const match = /^([A-Za-z][A-Za-z0-9+.-]*):/.exec(normalized);
164
+ return match ? `${match[1].toLowerCase()}:` : '';
165
+ }
166
+ function _decodeFragmentAttributeValue(value) {
167
+ let decoded = String(value || '');
168
+ for (let pass = 0; pass < 3; pass++) {
169
+ const next = decoded.replace(/&(#x[0-9a-fA-F]+|#[0-9]+|[A-Za-z][A-Za-z0-9]+);?/g, (_, body) => {
170
+ if (body[0] === '#') {
171
+ const radix = body[1] === 'x' || body[1] === 'X' ? 16 : 10;
172
+ const digits = radix === 16 ? body.slice(2) : body.slice(1);
173
+ const codePoint = Number.parseInt(digits, radix);
174
+ if (Number.isFinite(codePoint)) {
175
+ try {
176
+ return String.fromCodePoint(codePoint);
177
+ }
178
+ catch {
179
+ return '';
180
+ }
181
+ }
182
+ return '';
183
+ }
184
+ const named = _FRAGMENT_NAMED_ENTITIES[String(body).toLowerCase()];
185
+ return named === undefined ? `&${body};` : named;
186
+ });
187
+ if (next === decoded) {
188
+ break;
189
+ }
190
+ decoded = next;
191
+ }
192
+ return decoded;
193
+ }
71
194
  function _fragmentInterpolate(val, interpolationIndex) {
72
195
  if (val === null || val === undefined || val === false) {
73
196
  return '';
@@ -23,8 +23,8 @@ export function createMountEffect(callback, scope) {
23
23
  catch (error) {
24
24
  console.error('[Zenith Runtime] Unhandled error during zenMount:', error);
25
25
  }
26
- registeredDisposer = registerScopeDisposer(scope, () => {
27
- drainCleanupStack(cleanups);
26
+ registeredDisposer = registerScopeDisposer(scope, (errors = null) => {
27
+ drainCleanupStack(cleanups, errors);
28
28
  });
29
29
  }
30
30
  queueWhenScopeReady(scope, runMount);
package/dist/render.js CHANGED
@@ -21,13 +21,7 @@ export function _applyMarkerValue(nodes, marker, value) {
21
21
  _mountStructuralFragment(node, value, textPath);
22
22
  continue;
23
23
  }
24
- const html = _renderFragmentValue(value, textPath);
25
- if (html !== null) {
26
- node.innerHTML = html;
27
- }
28
- else {
29
- node.textContent = _coerceText(value, textPath);
30
- }
24
+ _replaceTextMarkerContent(node, value, textPath);
31
25
  continue;
32
26
  }
33
27
  if (marker.kind === 'attr') {
@@ -136,10 +130,22 @@ function _mountStructuralFragment(container, value, rootPath = 'renderable') {
136
130
  if (_tryUpdateFragmentTarget(container, value, container, null, rootPath)) {
137
131
  return;
138
132
  }
139
- _runUnmounts(container);
140
- container.innerHTML = '';
133
+ _clearStructuralTarget(container);
134
+ while (container.firstChild) {
135
+ container.removeChild(container.firstChild);
136
+ }
141
137
  _mountFragmentRegion(container, value, container, null, rootPath);
142
138
  }
139
+ function _replaceTextMarkerContent(node, value, rootPath) {
140
+ _clearStructuralTarget(node);
141
+ const html = _renderFragmentValue(value, rootPath);
142
+ if (html !== null) {
143
+ node.innerHTML = html;
144
+ }
145
+ else {
146
+ node.textContent = _coerceText(value, rootPath);
147
+ }
148
+ }
143
149
  export function _coerceText(value, path = 'renderable') {
144
150
  if (value === null || value === undefined || value === false || value === true) {
145
151
  return '';
@@ -258,6 +264,11 @@ function _runUnmounts(target) {
258
264
  }
259
265
  target.__z_unmounts = [];
260
266
  }
267
+ function _clearStructuralTarget(target) {
268
+ _runUnmounts(target);
269
+ target.__z_fragment_region = null;
270
+ target.__z_fragment_region_active = false;
271
+ }
261
272
  function _updateFragmentRegion(region, value, parent, insertBefore, rootPath) {
262
273
  try {
263
274
  region.update(value, { parent, insertBefore, rootPath });
@@ -1,6 +1,6 @@
1
1
  export declare function isSideEffectScope(value: any): boolean;
2
2
  export declare function resolveSideEffectScope(scopeOverride: any): any;
3
- export declare function resetGlobalSideEffects(): void;
3
+ export declare function resetGlobalSideEffects(errors?: null): void;
4
4
  export declare function createSideEffectScope(label?: string): {
5
5
  __zenith_scope: boolean;
6
6
  id: number;
@@ -12,5 +12,5 @@ export declare function createSideEffectScope(label?: string): {
12
12
  };
13
13
  export declare function activateSideEffectScope(scope: any): void;
14
14
  export declare function registerScopeDisposer(scope: any, disposer: any): () => void;
15
- export declare function disposeSideEffectScope(scope: any): void;
15
+ export declare function disposeSideEffectScope(scope: any, errors?: null): void;
16
16
  export declare function queueWhenScopeReady(scope: any, callback: any): void;
@@ -1,4 +1,5 @@
1
1
  // @ts-nocheck
2
+ import { runCleanupCallback } from './effect-utils.js';
2
3
  let _scopeIdCounter = 0;
3
4
  function createInternalScope(label, mountReady) {
4
5
  _scopeIdCounter += 1;
@@ -22,8 +23,8 @@ export function resolveSideEffectScope(scopeOverride) {
22
23
  }
23
24
  return _globalScope;
24
25
  }
25
- export function resetGlobalSideEffects() {
26
- disposeSideEffectScope(_globalScope);
26
+ export function resetGlobalSideEffects(errors = null) {
27
+ disposeSideEffectScope(_globalScope, errors);
27
28
  _globalScope = createInternalScope('global', true);
28
29
  }
29
30
  export function createSideEffectScope(label = 'anonymous') {
@@ -67,7 +68,7 @@ export function registerScopeDisposer(scope, disposer) {
67
68
  }
68
69
  };
69
70
  }
70
- export function disposeSideEffectScope(scope) {
71
+ export function disposeSideEffectScope(scope, errors = null) {
71
72
  if (!scope || scope.disposed) {
72
73
  return;
73
74
  }
@@ -80,11 +81,7 @@ export function disposeSideEffectScope(scope) {
80
81
  if (typeof disposer !== 'function') {
81
82
  continue;
82
83
  }
83
- try {
84
- disposer();
85
- }
86
- catch {
87
- }
84
+ runCleanupCallback(() => disposer(errors), errors);
88
85
  }
89
86
  }
90
87
  export function queueWhenScopeReady(scope, callback) {
@@ -1,13 +1,13 @@
1
1
  export { _nextReactiveId, _trackDependency } from './reactivity-core.js';
2
2
  export { resetGlobalSideEffects, createSideEffectScope, activateSideEffectScope, disposeSideEffectScope } from './side-effect-scope.js';
3
- export declare function zenEffect(effect: any, options?: null, scopeOverride?: null): () => void;
4
- export declare function zeneffect(effectOrDependencies: any, optionsOrEffect: any, scopeOverride?: null): () => void;
3
+ export declare function zenEffect(effect: any, options?: null, scopeOverride?: null): (errors?: null) => void;
4
+ export declare function zeneffect(effectOrDependencies: any, optionsOrEffect: any, scopeOverride?: null): (errors?: null) => void;
5
5
  export declare function zenMount(callback: any, scopeOverride?: null): () => void;
6
6
  /**
7
7
  * @alias zeneffect
8
8
  * @description Optional secondary alias for the canonical zeneffect primitive.
9
9
  */
10
- export declare function effect(effectOrDependencies: any, optionsOrEffect: any, scopeOverride?: null): () => void;
10
+ export declare function effect(effectOrDependencies: any, optionsOrEffect: any, scopeOverride?: null): (errors?: null) => void;
11
11
  /**
12
12
  * @alias zenMount
13
13
  * @description Optional secondary alias for the canonical zenMount primitive.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zenithbuild/runtime",
3
- "version": "0.7.9",
3
+ "version": "0.7.11",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "exports": {