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.
- package/dist/core/scope.d.ts +2 -3
- package/dist/core/scope.js +39 -5
- package/package.json +1 -1
package/dist/core/scope.d.ts
CHANGED
|
@@ -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
|
-
* -
|
|
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
|
*/
|
package/dist/core/scope.js
CHANGED
|
@@ -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
|
-
|
|
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
|
|
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
|
|
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
|
|
144
|
+
registerChildScope(parent, scope);
|
|
111
145
|
}
|
|
112
146
|
for (const listener of scopeCreateListeners) {
|
|
113
147
|
try {
|