dalila 1.9.20 → 1.9.21

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.
@@ -34,9 +34,8 @@ export declare function isScopeDisposed(scope: Scope): boolean;
34
34
  * Creates a new Scope instance.
35
35
  *
36
36
  * Notes:
37
- * - Cleanups run in FIFO order (registration order).
38
- * - If a cleanup registers another cleanup during disposal, it will NOT run
39
- * in the same dispose pass (because we snapshot via `splice(0)`).
37
+ * - Cleanups registered on the same scope run in FIFO order (registration order).
38
+ * - Child scopes are disposed before parent-local cleanups run (for Dalila-created parents).
40
39
  * - Parent is captured from the current scope context (set by withScope).
41
40
  * - Optional debug names can be passed for DevTools diagnostics.
42
41
  */
@@ -1,6 +1,7 @@
1
1
  import { disposeScope, registerScope } from "./devtools.js";
2
2
  /** Tracks disposed scopes without mutating the public interface. */
3
3
  const disposedScopes = new WeakSet();
4
+ const scopeState = new WeakMap();
4
5
  const scopeCreateListeners = new Set();
5
6
  const scopeDisposeListeners = new Set();
6
7
  /**
@@ -37,6 +38,29 @@ function normalizeScopeName(options) {
37
38
  const trimmed = options.name.trim();
38
39
  return trimmed.length > 0 ? trimmed : undefined;
39
40
  }
41
+ function getInternalScopeState(scope) {
42
+ const state = scopeState.get(scope);
43
+ if (!state) {
44
+ throw new Error('[Dalila] Internal scope state missing.');
45
+ }
46
+ return state;
47
+ }
48
+ function registerChildScope(parent, child) {
49
+ if (isScopeDisposed(parent)) {
50
+ child.dispose();
51
+ return;
52
+ }
53
+ const parentState = scopeState.get(parent);
54
+ if (parentState) {
55
+ parentState.childDisposers.push(() => child.dispose());
56
+ return;
57
+ }
58
+ // External/mock Scope parent: fall back to the public Scope interface.
59
+ // Child-first ordering cannot be guaranteed for interface-only parents.
60
+ // This preserves compatibility (including implementations that run late
61
+ // onCleanup registrations immediately after disposal).
62
+ parent.onCleanup(() => child.dispose());
63
+ }
40
64
  function resolveCreateScopeArgs(parentOrOptions, maybeOptions) {
41
65
  if (parentOrOptions === undefined || parentOrOptions === null || isScopeLike(parentOrOptions)) {
42
66
  return {
@@ -52,7 +76,6 @@ function resolveCreateScopeArgs(parentOrOptions, maybeOptions) {
52
76
  export function createScope(parentOrOptions, maybeOptions) {
53
77
  const { parentOverride, options } = resolveCreateScopeArgs(parentOrOptions, maybeOptions);
54
78
  const name = normalizeScopeName(options);
55
- const cleanups = [];
56
79
  const parentCandidate = parentOverride === undefined ? currentScope : parentOverride === null ? null : parentOverride;
57
80
  // A stale async context can leave `currentScope` pointing to an already
58
81
  // disposed scope; in that case we create a detached scope instead.
@@ -77,15 +100,22 @@ export function createScope(parentOrOptions, maybeOptions) {
77
100
  }
78
101
  return;
79
102
  }
80
- cleanups.push(fn);
103
+ getInternalScopeState(scope).localCleanups.push(fn);
81
104
  },
82
105
  dispose() {
83
106
  if (isScopeDisposed(scope))
84
107
  return;
85
108
  disposedScopes.add(scope);
86
- const snapshot = cleanups.splice(0);
109
+ const state = getInternalScopeState(scope);
110
+ const childSnapshot = state.childDisposers.splice(0);
111
+ const localSnapshot = state.localCleanups.splice(0);
87
112
  const errors = [];
88
- for (const fn of snapshot) {
113
+ for (const fn of childSnapshot) {
114
+ const error = runCleanupSafely(fn);
115
+ if (error)
116
+ errors.push(error);
117
+ }
118
+ for (const fn of localSnapshot) {
89
119
  const error = runCleanupSafely(fn);
90
120
  if (error)
91
121
  errors.push(error);
@@ -105,9 +135,13 @@ export function createScope(parentOrOptions, maybeOptions) {
105
135
  },
106
136
  parent,
107
137
  };
138
+ scopeState.set(scope, {
139
+ localCleanups: [],
140
+ childDisposers: [],
141
+ });
108
142
  registerScope(scope, parent, name);
109
143
  if (parent) {
110
- parent.onCleanup(() => scope.dispose());
144
+ registerChildScope(parent, scope);
111
145
  }
112
146
  for (const listener of scopeCreateListeners) {
113
147
  try {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dalila",
3
- "version": "1.9.20",
3
+ "version": "1.9.21",
4
4
  "description": "DOM-first reactive framework based on signals",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",