@blac/core 2.1.1 → 2.1.2
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/StateContainer-CwByTd5L.js +656 -0
- package/dist/StateContainer-CwByTd5L.js.map +1 -0
- package/dist/StateContainer-D2b10eR4.cjs +691 -0
- package/dist/StateContainer-D2b10eR4.cjs.map +1 -0
- package/dist/StateContainerRegistry-B6O6cfJd.cjs +903 -0
- package/dist/StateContainerRegistry-B6O6cfJd.cjs.map +1 -0
- package/dist/StateContainerRegistry-CrTtRX5V.js +772 -0
- package/dist/StateContainerRegistry-CrTtRX5V.js.map +1 -0
- package/dist/constants.d.cts +2 -0
- package/dist/constants.d.ts +2 -0
- package/dist/core/StateContainer.d.ts +7 -1
- package/dist/core/StateContainerRegistry.d.ts +2 -0
- package/dist/core/meta.d.ts +3 -0
- package/dist/core/onActivated.d.ts +10 -0
- package/dist/core/symbols.d.ts +5 -0
- package/dist/decorators/blac.d.ts +7 -0
- package/dist/ensure-CwtQVTam.js +46 -0
- package/dist/ensure-CwtQVTam.js.map +1 -0
- package/dist/ensure-DShrbIYY.cjs +63 -0
- package/dist/ensure-DShrbIYY.cjs.map +1 -0
- package/dist/index.cjs +27 -3
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +3 -2
- package/dist/index.d.ts +3 -2
- package/dist/index.js +25 -4
- package/dist/index.js.map +1 -1
- package/dist/internal.cjs +2 -2
- package/dist/internal.js +2 -2
- package/dist/plugin/BlacPlugin.d.ts +3 -1
- package/dist/plugin/PluginManager.d.ts +4 -4
- package/dist/plugins.cjs +5 -5
- package/dist/plugins.cjs.map +1 -1
- package/dist/plugins.js +5 -5
- package/dist/plugins.js.map +1 -1
- package/dist/registry/index.d.ts +1 -1
- package/dist/registry/management.d.ts +2 -0
- package/dist/testing.cjs +28 -3
- package/dist/testing.cjs.map +1 -1
- package/dist/testing.d.cts +9 -1
- package/dist/testing.d.ts +9 -1
- package/dist/testing.js +28 -4
- package/dist/testing.js.map +1 -1
- package/dist/utils/static-props.d.ts +6 -0
- package/dist/watch/watch.d.ts +6 -0
- package/package.json +2 -2
|
@@ -0,0 +1,772 @@
|
|
|
1
|
+
//#region src/config.ts
|
|
2
|
+
/**
|
|
3
|
+
* Default equality: shallow per-key `Object.is` comparison.
|
|
4
|
+
* Falls through to `false` for primitives/null so reference-only state still
|
|
5
|
+
* behaves correctly.
|
|
6
|
+
*/
|
|
7
|
+
const shallowEqualState = (prev, next) => {
|
|
8
|
+
if (Object.is(prev, next)) return true;
|
|
9
|
+
if (typeof prev !== "object" || prev === null) return false;
|
|
10
|
+
if (typeof next !== "object" || next === null) return false;
|
|
11
|
+
const p = prev;
|
|
12
|
+
const n = next;
|
|
13
|
+
let prevCount = 0;
|
|
14
|
+
for (const key in p) {
|
|
15
|
+
prevCount++;
|
|
16
|
+
if (!Object.is(p[key], n[key])) return false;
|
|
17
|
+
}
|
|
18
|
+
let nextCount = 0;
|
|
19
|
+
for (const _ in n) nextCount++;
|
|
20
|
+
return prevCount === nextCount;
|
|
21
|
+
};
|
|
22
|
+
const defaultConfig = {
|
|
23
|
+
equality: shallowEqualState,
|
|
24
|
+
maxInstancesPerType: 1e5,
|
|
25
|
+
maxRefsPerInstance: 1e5,
|
|
26
|
+
maxEmitsPerSecond: 1e3,
|
|
27
|
+
unownedSweepDelayMs: 5e3
|
|
28
|
+
};
|
|
29
|
+
let globalConfig = { ...defaultConfig };
|
|
30
|
+
/**
|
|
31
|
+
* Configure global defaults for `@blac/core`.
|
|
32
|
+
*
|
|
33
|
+
* @example
|
|
34
|
+
* ```ts
|
|
35
|
+
* import { configureBlac, shallowEqualState } from '@blac/core';
|
|
36
|
+
*
|
|
37
|
+
* // Swap in a deep-equality check for all blocs
|
|
38
|
+
* configureBlac({ equality: myDeepEqual });
|
|
39
|
+
* ```
|
|
40
|
+
*/
|
|
41
|
+
function configureBlac(config) {
|
|
42
|
+
globalConfig = {
|
|
43
|
+
...globalConfig,
|
|
44
|
+
...config
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Get the current global configuration.
|
|
49
|
+
* @internal
|
|
50
|
+
*/
|
|
51
|
+
function getBlacConfig() {
|
|
52
|
+
return globalConfig;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Reset configuration to defaults (useful for testing).
|
|
56
|
+
* @internal
|
|
57
|
+
*/
|
|
58
|
+
function resetBlacConfig() {
|
|
59
|
+
globalConfig = { ...defaultConfig };
|
|
60
|
+
}
|
|
61
|
+
//#endregion
|
|
62
|
+
//#region src/constants.ts
|
|
63
|
+
/** Static property names used for feature flags and configuration on bloc classes. */
|
|
64
|
+
const BLAC_STATIC_PROPS = {
|
|
65
|
+
/** Mark a bloc to never be auto-disposed (kept alive permanently). */
|
|
66
|
+
KEEP_ALIVE: "keepAlive",
|
|
67
|
+
/** Give each React `useBloc` mount its own private instance. */
|
|
68
|
+
ISOLATED: "isolated",
|
|
69
|
+
/** Exclude a bloc from DevTools reporting (prevents infinite loops). */
|
|
70
|
+
EXCLUDE_FROM_DEVTOOLS: "__excludeFromDevTools",
|
|
71
|
+
/**
|
|
72
|
+
* Per-class equality function used to short-circuit `emit`/`patch` when the
|
|
73
|
+
* new state is structurally equal to the previous state. Falls back to the
|
|
74
|
+
* global `configureBlac({ equality })` value when unset.
|
|
75
|
+
*/
|
|
76
|
+
EQUALITY: "__equality",
|
|
77
|
+
/**
|
|
78
|
+
* Per-class key function: `static key = (args) => string`.
|
|
79
|
+
* When set, the registry calls `Type.key(args)` to derive the instance key
|
|
80
|
+
* instead of using a structural hash of `args`. Allows blocs to ignore
|
|
81
|
+
* non-identity fields (e.g., `readonly` flags) while still keying by id.
|
|
82
|
+
*/
|
|
83
|
+
KEY: "key",
|
|
84
|
+
/**
|
|
85
|
+
* Explicit, minification-safe identity for a bloc class: `static blacName = '...'`.
|
|
86
|
+
* When unset, identity falls back to `constructor.name`.
|
|
87
|
+
*/
|
|
88
|
+
BLAC_NAME: "blacName"
|
|
89
|
+
};
|
|
90
|
+
const BLAC_ERROR_PREFIX = "[BlaC]";
|
|
91
|
+
function readNodeEnv() {
|
|
92
|
+
try {
|
|
93
|
+
return process.env.NODE_ENV;
|
|
94
|
+
} catch {
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
const nodeEnv = readNodeEnv();
|
|
99
|
+
/**
|
|
100
|
+
* True only when `process.env.NODE_ENV` is defined and not `'production'`.
|
|
101
|
+
*
|
|
102
|
+
* Dev-only checks (emit-rate breaker, deps collision scan, args-mismatch key)
|
|
103
|
+
* cost real work per emit/acquire, so an environment that says nothing about
|
|
104
|
+
* its mode — plain ESM in the browser, Deno, a bundle without a `process`
|
|
105
|
+
* shim — is treated as production. Bundlers that define `process.env.NODE_ENV`
|
|
106
|
+
* still constant-fold the read.
|
|
107
|
+
*/
|
|
108
|
+
const IS_DEV = nodeEnv !== void 0 && nodeEnv !== "production";
|
|
109
|
+
//#endregion
|
|
110
|
+
//#region src/utils/static-props.ts
|
|
111
|
+
/**
|
|
112
|
+
* Get a static property from a class constructor, walking the prototype
|
|
113
|
+
* chain so subclasses inherit the value unless they define their own.
|
|
114
|
+
*/
|
|
115
|
+
function getStaticProp(Type, propName) {
|
|
116
|
+
return Type[propName];
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Get a static property declared directly on `Type`, ignoring the prototype
|
|
120
|
+
* chain, so subclasses never inherit a base class's value.
|
|
121
|
+
*/
|
|
122
|
+
function getOwnStaticProp(Type, propName) {
|
|
123
|
+
return Object.hasOwn(Type, propName) ? Type[propName] : void 0;
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Whether a class is marked `keepAlive` (never auto-disposed at ref count 0).
|
|
127
|
+
* Inherited: a subclass keeps a base class's `keepAlive` unless overridden.
|
|
128
|
+
*/
|
|
129
|
+
function isKeepAliveClass(Type) {
|
|
130
|
+
return getStaticProp(Type, BLAC_STATIC_PROPS.KEEP_ALIVE) === true;
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Whether a class is marked `isolated` (each `useBloc` mount gets its own
|
|
134
|
+
* instance). Inherited: a subclass keeps a base class's `isolated` unless
|
|
135
|
+
* overridden.
|
|
136
|
+
*/
|
|
137
|
+
function isIsolatedClass(Type) {
|
|
138
|
+
return getStaticProp(Type, BLAC_STATIC_PROPS.ISOLATED) === true;
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Whether a class is excluded from DevTools (avoids DevTools tracking itself
|
|
142
|
+
* into an infinite loop). Inherited unless overridden by a subclass.
|
|
143
|
+
*/
|
|
144
|
+
function isExcludedFromDevTools(Type) {
|
|
145
|
+
return getStaticProp(Type, BLAC_STATIC_PROPS.EXCLUDE_FROM_DEVTOOLS) === true;
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* Get the per-class equality function set via `@blac({ equality })`, if any.
|
|
149
|
+
* Inherited: a subclass keeps a base class's equality fn unless overridden.
|
|
150
|
+
*/
|
|
151
|
+
function getClassEquality(Type) {
|
|
152
|
+
const fn = getStaticProp(Type, BLAC_STATIC_PROPS.EQUALITY);
|
|
153
|
+
return typeof fn === "function" ? fn : void 0;
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* Get a class's minification-safe identity name (`static blacName`), falling
|
|
157
|
+
* back to `Type.name`. Own only: a subclass never inherits a base class's
|
|
158
|
+
* explicit `blacName`, since that would collapse two distinct blocs onto one
|
|
159
|
+
* identity.
|
|
160
|
+
*/
|
|
161
|
+
function getBlacName(Type) {
|
|
162
|
+
return getOwnStaticProp(Type, BLAC_STATIC_PROPS.BLAC_NAME) ?? Type.name;
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* Get the per-class key function set via `@blac({ key })` or `static key = …`.
|
|
166
|
+
* Own only: a subclass never inherits a base class's `key`, since a
|
|
167
|
+
* different `Args` shape would derive the wrong instance key.
|
|
168
|
+
*/
|
|
169
|
+
function getClassKey(Type) {
|
|
170
|
+
const fn = getOwnStaticProp(Type, BLAC_STATIC_PROPS.KEY);
|
|
171
|
+
return typeof fn === "function" ? fn : void 0;
|
|
172
|
+
}
|
|
173
|
+
//#endregion
|
|
174
|
+
//#region src/utils/structural-key.ts
|
|
175
|
+
const DEFAULT_STRUCTURAL_KEY = "default";
|
|
176
|
+
/**
|
|
177
|
+
* Produce a stable string key for serializable args.
|
|
178
|
+
* - `undefined` / `void` args → `'default'` sentinel (no-args blocs stay on the default key).
|
|
179
|
+
* - Object keys are sorted for order-independence.
|
|
180
|
+
* - Arrays keep their insertion order.
|
|
181
|
+
* - Functions throw — they must be passed via `deps`, not `args`.
|
|
182
|
+
* - JSON semantics otherwise: `{ a: undefined }` equals `{}`, and `NaN` and
|
|
183
|
+
* `Infinity` serialize as `null`.
|
|
184
|
+
* - Object args are memoized by identity; callers on hot paths (`resolveKey`,
|
|
185
|
+
* `depend`) re-resolve the same args object on every call. Mutating an args
|
|
186
|
+
* object after first use therefore keeps its old key.
|
|
187
|
+
*/
|
|
188
|
+
const keyCache = /* @__PURE__ */ new WeakMap();
|
|
189
|
+
function structuralKey(args) {
|
|
190
|
+
if (args === void 0 || args === null) return DEFAULT_STRUCTURAL_KEY;
|
|
191
|
+
if (typeof args === "object") {
|
|
192
|
+
const cached = keyCache.get(args);
|
|
193
|
+
if (cached !== void 0) return cached;
|
|
194
|
+
const key = serialize(args);
|
|
195
|
+
keyCache.set(args, key);
|
|
196
|
+
return key;
|
|
197
|
+
}
|
|
198
|
+
return serialize(args);
|
|
199
|
+
}
|
|
200
|
+
function serialize(args) {
|
|
201
|
+
return JSON.stringify(args, function(_k, v) {
|
|
202
|
+
if (typeof v === "function") throw new Error(`[blac] args must be serializable; put refs/callbacks in \`deps\` (found a function at key "${_k || "(root)"}")`);
|
|
203
|
+
if (v && typeof v === "object" && !Array.isArray(v)) {
|
|
204
|
+
if (IS_DEV && !isPlainObject(v)) throw new Error(`[blac] args must be plain data; put refs/instances in \`deps\` (found a ${v.constructor?.name ?? "non-plain object"} at key "${_k || "(root)"}")`);
|
|
205
|
+
return Object.keys(v).sort().reduce((o, k) => {
|
|
206
|
+
o[k] = v[k];
|
|
207
|
+
return o;
|
|
208
|
+
}, {});
|
|
209
|
+
}
|
|
210
|
+
return v;
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
function isPlainObject(value) {
|
|
214
|
+
const proto = Object.getPrototypeOf(value);
|
|
215
|
+
return proto === Object.prototype || proto === null;
|
|
216
|
+
}
|
|
217
|
+
//#endregion
|
|
218
|
+
//#region src/core/symbols.ts
|
|
219
|
+
/**
|
|
220
|
+
* @internal Apply one owner's (consumer's) deps slice to an instance.
|
|
221
|
+
* Called by framework adapters (e.g. `@blac/react` useBloc) — not public API.
|
|
222
|
+
*/
|
|
223
|
+
const APPLY_DEPS = Symbol("blac.applyDeps");
|
|
224
|
+
/**
|
|
225
|
+
* @internal Withdraw one owner's entire deps slice (consumer unmounted).
|
|
226
|
+
* Called by framework adapters — not public API.
|
|
227
|
+
*/
|
|
228
|
+
const REMOVE_DEPS_OWNER = Symbol("blac.removeDepsOwner");
|
|
229
|
+
/**
|
|
230
|
+
* @internal Subscribe to this specific instance's own disposal, bypassing
|
|
231
|
+
* the global registry `'disposed'` event. Called by `watch()` — not public
|
|
232
|
+
* API.
|
|
233
|
+
*/
|
|
234
|
+
const ON_DISPOSE = Symbol("blac.onDispose");
|
|
235
|
+
/**
|
|
236
|
+
* @internal Register a callback that runs on each activation with that
|
|
237
|
+
* activation's signal. Called by `onActivated()` — not public API.
|
|
238
|
+
*/
|
|
239
|
+
const ON_ACTIVATED = Symbol("blac.onActivated");
|
|
240
|
+
/**
|
|
241
|
+
* @internal Symbol-keyed configuration entry point on `StateContainer`.
|
|
242
|
+
* Writes the instance's identity fields (`_name`/`_debug`/`_instanceId`),
|
|
243
|
+
* resolves per-class equality, emits the registry `created` event, and runs
|
|
244
|
+
* `init()` once.
|
|
245
|
+
*
|
|
246
|
+
* Framework-only — the registry (`StateContainerRegistry.ensure`) and testing
|
|
247
|
+
* helpers are the sole callers, mirroring the `APPLY_DEPS` precedent.
|
|
248
|
+
*/
|
|
249
|
+
const INIT_CONFIG = Symbol("blac.initConfig");
|
|
250
|
+
/**
|
|
251
|
+
* @internal Run `fn` with `state` temporarily reporting a render's tracking
|
|
252
|
+
* proxy, so getters record the paths they read. Called by `@blac/react`'s
|
|
253
|
+
* `buildTrackedProxy` — not public API. Getters run with `this` bound to the
|
|
254
|
+
* real instance, so ES `#private` works in user blocs.
|
|
255
|
+
*/
|
|
256
|
+
const WITH_TRACKED_STATE = Symbol("blac.withTrackedState");
|
|
257
|
+
/**
|
|
258
|
+
* @internal Drive the activation lifecycle from the registry's 0↔1 ownership
|
|
259
|
+
* transition. Owns the per-instance AbortController so `onActivate`'s signal
|
|
260
|
+
* aborts on the matching deactivation and on dispose.
|
|
261
|
+
*
|
|
262
|
+
* Framework-only — `StateContainerRegistry` is the sole caller.
|
|
263
|
+
*/
|
|
264
|
+
const SET_ACTIVE = Symbol("blac.setActive");
|
|
265
|
+
/**
|
|
266
|
+
* @internal Insert an instance entry directly, bypassing acquire/release.
|
|
267
|
+
* Called by the testing helpers — not public API.
|
|
268
|
+
*/
|
|
269
|
+
const INSERT_INSTANCE = Symbol("blac.insertInstance");
|
|
270
|
+
//#endregion
|
|
271
|
+
//#region src/core/StateContainerRegistry.ts
|
|
272
|
+
const EMPTY_INSTANCES_MAP = /* @__PURE__ */ new Map();
|
|
273
|
+
/**
|
|
274
|
+
* Owns StateContainer instances: creation, ref tracking, disposal and
|
|
275
|
+
* lifecycle events.
|
|
276
|
+
*
|
|
277
|
+
* @example
|
|
278
|
+
* ```ts
|
|
279
|
+
* const registry = new StateContainerRegistry();
|
|
280
|
+
* const instance = registry.acquire(MyBloc); // ownership, must release
|
|
281
|
+
* const other = registry.ensure(OtherBloc); // no ownership, bloc-to-bloc
|
|
282
|
+
* registry.on('stateChanged', (container, prev, next) => {
|
|
283
|
+
* console.log('State changed:', prev, '->', next);
|
|
284
|
+
* });
|
|
285
|
+
* ```
|
|
286
|
+
*
|
|
287
|
+
* `stateChanged` fires once per emit (microtask-deferred, not coalesced).
|
|
288
|
+
* Plugins' `onStateChange` fires once per channel flush with coalesced
|
|
289
|
+
* `prev`/`next` and a `PathSet`.
|
|
290
|
+
*/
|
|
291
|
+
var StateContainerRegistry = class {
|
|
292
|
+
get hasStateChangedListeners() {
|
|
293
|
+
return (this.listeners.get("stateChanged")?.size ?? 0) > 0;
|
|
294
|
+
}
|
|
295
|
+
constructor() {
|
|
296
|
+
this.instancesByConstructor = /* @__PURE__ */ new WeakMap();
|
|
297
|
+
this.types = /* @__PURE__ */ new Set();
|
|
298
|
+
this._dependentEdges = /* @__PURE__ */ new WeakMap();
|
|
299
|
+
this._entryByInstance = /* @__PURE__ */ new WeakMap();
|
|
300
|
+
this._entryById = /* @__PURE__ */ new Map();
|
|
301
|
+
this.listeners = /* @__PURE__ */ new Map();
|
|
302
|
+
this._pendingStateChanges = null;
|
|
303
|
+
this._autoRefIdCounter = 0;
|
|
304
|
+
this.on("disposed", (container) => this._handleDisposed(container));
|
|
305
|
+
}
|
|
306
|
+
/** Prune a tracked instance's entry and release its `depend()` edges. */
|
|
307
|
+
_handleDisposed(container) {
|
|
308
|
+
const Type = container.constructor;
|
|
309
|
+
if (!this._pruneEntry(Type, container)) return;
|
|
310
|
+
const edges = this._dependentEdges.get(container);
|
|
311
|
+
if (!edges) return;
|
|
312
|
+
this._dependentEdges.delete(container);
|
|
313
|
+
for (const [DepType, keys] of edges) for (const depKey of keys) this._releaseDependent(DepType, depKey, container);
|
|
314
|
+
}
|
|
315
|
+
/** The single dispose decision: no refs, no dependents, not keepAlive. */
|
|
316
|
+
_isUnowned(Type, entry) {
|
|
317
|
+
return this._hasNoOwners(entry) && !isKeepAliveClass(Type);
|
|
318
|
+
}
|
|
319
|
+
/** Ignores keepAlive: a keepAlive instance still deactivates. */
|
|
320
|
+
_hasNoOwners(entry) {
|
|
321
|
+
return entry.refs.size === 0 && (entry.dependents === void 0 || entry.dependents.size === 0);
|
|
322
|
+
}
|
|
323
|
+
/**
|
|
324
|
+
* Idempotent, so every acquire/release path calls it. The events fire after
|
|
325
|
+
* the instance's own `onActivate`/`onDeactivate`.
|
|
326
|
+
*/
|
|
327
|
+
_syncActivation(entry) {
|
|
328
|
+
const transition = entry.instance[SET_ACTIVE](!this._hasNoOwners(entry));
|
|
329
|
+
if (transition === "none") return;
|
|
330
|
+
if (transition === "deactivated") this.emit("deactivated", entry.instance);
|
|
331
|
+
else this.emit("activated", entry.instance, transition.signal);
|
|
332
|
+
}
|
|
333
|
+
_recordDependentEdge(dependent, Type, key) {
|
|
334
|
+
let byType = this._dependentEdges.get(dependent);
|
|
335
|
+
if (!byType) this._dependentEdges.set(dependent, byType = /* @__PURE__ */ new Map());
|
|
336
|
+
let keys = byType.get(Type);
|
|
337
|
+
if (!keys) byType.set(Type, keys = /* @__PURE__ */ new Set());
|
|
338
|
+
keys.add(key);
|
|
339
|
+
}
|
|
340
|
+
/** @returns true if this registry tracked `container`. */
|
|
341
|
+
_pruneEntry(Type, container) {
|
|
342
|
+
const entry = this._entryByInstance.get(container);
|
|
343
|
+
if (entry === void 0) return false;
|
|
344
|
+
this._entryByInstance.delete(container);
|
|
345
|
+
const instances = this.instancesByConstructor.get(Type);
|
|
346
|
+
if (instances?.get(entry.key) === entry) instances.delete(entry.key);
|
|
347
|
+
const id = container.$blac.id;
|
|
348
|
+
if (this._entryById.get(id) === entry) this._entryById.delete(id);
|
|
349
|
+
return true;
|
|
350
|
+
}
|
|
351
|
+
_indexEntry(entry) {
|
|
352
|
+
this._entryByInstance.set(entry.instance, entry);
|
|
353
|
+
this._entryById.set(entry.instance.$blac.id, entry);
|
|
354
|
+
}
|
|
355
|
+
/** Disposing here recurses through `disposed`, unwinding dep chains. */
|
|
356
|
+
_releaseDependent(Type, key, dependent) {
|
|
357
|
+
const entry = this.instancesByConstructor.get(Type)?.get(key);
|
|
358
|
+
if (!entry) return;
|
|
359
|
+
entry.dependents?.delete(dependent);
|
|
360
|
+
if (this._isUnowned(Type, entry)) {
|
|
361
|
+
if (!entry.instance.$blac.disposed) entry.instance.dispose();
|
|
362
|
+
return;
|
|
363
|
+
}
|
|
364
|
+
this._syncActivation(entry);
|
|
365
|
+
}
|
|
366
|
+
/**
|
|
367
|
+
* Register a StateContainer class with configuration
|
|
368
|
+
* @param constructor - The StateContainer class constructor
|
|
369
|
+
* @throws Error if type is already registered
|
|
370
|
+
*/
|
|
371
|
+
register(constructor) {
|
|
372
|
+
if (this.types.has(constructor)) throw new Error(`${BLAC_ERROR_PREFIX} Type "${getBlacName(constructor)}" is already registered`);
|
|
373
|
+
this.types.add(constructor);
|
|
374
|
+
}
|
|
375
|
+
ensureInstancesMap(Type) {
|
|
376
|
+
let instances = this.instancesByConstructor.get(Type);
|
|
377
|
+
if (!instances) {
|
|
378
|
+
instances = /* @__PURE__ */ new Map();
|
|
379
|
+
this.instancesByConstructor.set(Type, instances);
|
|
380
|
+
}
|
|
381
|
+
return instances;
|
|
382
|
+
}
|
|
383
|
+
/**
|
|
384
|
+
* Get the instances Map for a specific class (public API for stats/debugging)
|
|
385
|
+
*/
|
|
386
|
+
getInstancesMap(Type) {
|
|
387
|
+
return this.instancesByConstructor.get(Type) || EMPTY_INSTANCES_MAP;
|
|
388
|
+
}
|
|
389
|
+
/**
|
|
390
|
+
* Insert an entry, replacing the one at `instanceKey`. The instance is not
|
|
391
|
+
* configured here.
|
|
392
|
+
*
|
|
393
|
+
* @internal Used by testing helpers only.
|
|
394
|
+
*/
|
|
395
|
+
[INSERT_INSTANCE](Type, instanceKey, instance, refs = /* @__PURE__ */ new Map()) {
|
|
396
|
+
const instances = this.ensureInstancesMap(Type);
|
|
397
|
+
const existingEntry = instances.get(instanceKey);
|
|
398
|
+
if (existingEntry && existingEntry.instance !== instance && !existingEntry.instance.$blac.disposed) existingEntry.instance.dispose();
|
|
399
|
+
const entry = {
|
|
400
|
+
instance,
|
|
401
|
+
key: instanceKey,
|
|
402
|
+
refs
|
|
403
|
+
};
|
|
404
|
+
instances.set(instanceKey, entry);
|
|
405
|
+
this._indexEntry(entry);
|
|
406
|
+
this.types.add(Type);
|
|
407
|
+
}
|
|
408
|
+
/**
|
|
409
|
+
* The single source of keying, so `acquire` and `release` agree: explicit
|
|
410
|
+
* key \> `static key(args)` \> structural hash of args \> default key.
|
|
411
|
+
*
|
|
412
|
+
* @param Type - The StateContainer class constructor
|
|
413
|
+
* @param instanceKey - Explicit key, or undefined to derive from args
|
|
414
|
+
* @param args - Construction args used for structural/`static key` derivation
|
|
415
|
+
*/
|
|
416
|
+
resolveKey(Type, instanceKey, args) {
|
|
417
|
+
if (instanceKey !== void 0) return instanceKey;
|
|
418
|
+
const keyFn = getClassKey(Type);
|
|
419
|
+
if (keyFn) return keyFn(args);
|
|
420
|
+
if (args !== void 0) return structuralKey(args);
|
|
421
|
+
return DEFAULT_STRUCTURAL_KEY;
|
|
422
|
+
}
|
|
423
|
+
assertInstanceLimit(Type, currentCount) {
|
|
424
|
+
const limit = getBlacConfig().maxInstancesPerType;
|
|
425
|
+
if (limit > 0 && currentCount >= limit) throw new Error(`${BLAC_ERROR_PREFIX} ${getBlacName(Type)} exceeded the maximum of ${limit} live instances. This usually means the instance key is unstable — e.g. \`args\` that change identity every render, or a missing \`static key\` — so a new instance is created and never disposed (memory leak). Stabilize the key, add a \`static key(args)\`, or raise \`configureBlac({ maxInstancesPerType })\`.`);
|
|
426
|
+
}
|
|
427
|
+
assertRefLimit(Type, resolvedKey, refCount) {
|
|
428
|
+
const limit = getBlacConfig().maxRefsPerInstance;
|
|
429
|
+
if (limit > 0 && refCount > limit) throw new Error(`${BLAC_ERROR_PREFIX} ${getBlacName(Type)} instance "${resolvedKey}" exceeded the maximum of ${limit} live references. This usually means references are acquired without a matching release (e.g. a consumer that never unmounts/cleans up), leaking refs that keep the instance alive forever. Ensure every acquire is paired with a release, or raise \`configureBlac({ maxRefsPerInstance })\`.`);
|
|
430
|
+
}
|
|
431
|
+
/**
|
|
432
|
+
* Get or create an instance and add a ref; `release()` it with the same
|
|
433
|
+
* refId.
|
|
434
|
+
*
|
|
435
|
+
* @internal Internal key tier. Public callers use the args-based `acquire`
|
|
436
|
+
* wrapper; `useBloc` and `watch` address a pre-resolved key here.
|
|
437
|
+
* @param Type - The StateContainer class constructor
|
|
438
|
+
* @param instanceKey - Pre-resolved instance key (defaults to 'default')
|
|
439
|
+
* @param options - `canCreate` and `countRef` default to true; `refId` is
|
|
440
|
+
* auto-generated when omitted; `dependent` is the `depend()`-owner, whose
|
|
441
|
+
* edge is released on its disposal; `sweepIfUnowned` is for speculative
|
|
442
|
+
* creates (see `_scheduleSweep`).
|
|
443
|
+
*/
|
|
444
|
+
acquire(Type, instanceKey, options = {}) {
|
|
445
|
+
const { canCreate = true, countRef = true } = options;
|
|
446
|
+
const args = options.args;
|
|
447
|
+
const resolvedKey = this.resolveKey(Type, instanceKey, args);
|
|
448
|
+
const instances = this.ensureInstancesMap(Type);
|
|
449
|
+
let entry = instances.get(resolvedKey);
|
|
450
|
+
if (entry?.instance.$blac.disposed) {
|
|
451
|
+
instances.delete(resolvedKey);
|
|
452
|
+
entry = void 0;
|
|
453
|
+
}
|
|
454
|
+
if (entry) {
|
|
455
|
+
if (IS_DEV && args !== void 0 && entry.args !== void 0) {
|
|
456
|
+
const incomingKey = structuralKey(args);
|
|
457
|
+
const storedKey = entry.argsKey ??= structuralKey(entry.args);
|
|
458
|
+
if (incomingKey !== storedKey) console.warn(`${BLAC_ERROR_PREFIX} ${getBlacName(Type)} instance key "${resolvedKey}" was acquired with different args. Existing args: ${storedKey}, new args: ${incomingKey}. The existing instance will be reused. If distinct args should produce distinct instances, either remove the explicit instanceKey or provide a \`static key\` function that reflects the difference.`);
|
|
459
|
+
}
|
|
460
|
+
if (countRef) {
|
|
461
|
+
const refId = options.refId ?? `_auto_${this._autoRefIdCounter++}`;
|
|
462
|
+
const count = entry.refs.get(refId) ?? 0;
|
|
463
|
+
if (count === 0) this.assertRefLimit(Type, resolvedKey, entry.refs.size + 1);
|
|
464
|
+
entry.refs.set(refId, count + 1);
|
|
465
|
+
this.emit("refAcquired", entry.instance, refId);
|
|
466
|
+
}
|
|
467
|
+
if (options.dependent) {
|
|
468
|
+
(entry.dependents ??= /* @__PURE__ */ new Set()).add(options.dependent);
|
|
469
|
+
this._recordDependentEdge(options.dependent, Type, resolvedKey);
|
|
470
|
+
}
|
|
471
|
+
this._syncActivation(entry);
|
|
472
|
+
if (!options.sweepIfUnowned) {
|
|
473
|
+
clearTimeout(entry.sweepTimer);
|
|
474
|
+
entry.sweepTimer = void 0;
|
|
475
|
+
} else if (entry.sweepTimer !== void 0) this._scheduleSweep(Type, entry);
|
|
476
|
+
return entry.instance;
|
|
477
|
+
}
|
|
478
|
+
if (!canCreate) throw new Error(`${BLAC_ERROR_PREFIX} ${getBlacName(Type)} instance "${resolvedKey}" not found and creation is disabled.`);
|
|
479
|
+
this.assertInstanceLimit(Type, instances.size);
|
|
480
|
+
const config = {
|
|
481
|
+
instanceId: resolvedKey,
|
|
482
|
+
args,
|
|
483
|
+
registry: this
|
|
484
|
+
};
|
|
485
|
+
const instance = new Type();
|
|
486
|
+
instance[INIT_CONFIG](config);
|
|
487
|
+
const initialRefs = /* @__PURE__ */ new Map();
|
|
488
|
+
let initialRefId;
|
|
489
|
+
if (countRef) {
|
|
490
|
+
initialRefId = options.refId ?? `_auto_${this._autoRefIdCounter++}`;
|
|
491
|
+
initialRefs.set(initialRefId, 1);
|
|
492
|
+
}
|
|
493
|
+
const newEntry = {
|
|
494
|
+
instance,
|
|
495
|
+
key: resolvedKey,
|
|
496
|
+
refs: initialRefs,
|
|
497
|
+
args
|
|
498
|
+
};
|
|
499
|
+
if (options.dependent) {
|
|
500
|
+
(newEntry.dependents ??= /* @__PURE__ */ new Set()).add(options.dependent);
|
|
501
|
+
this._recordDependentEdge(options.dependent, Type, resolvedKey);
|
|
502
|
+
}
|
|
503
|
+
instances.set(resolvedKey, newEntry);
|
|
504
|
+
this._indexEntry(newEntry);
|
|
505
|
+
this.types.add(Type);
|
|
506
|
+
if (initialRefId) this.emit("refAcquired", instance, initialRefId);
|
|
507
|
+
this._syncActivation(newEntry);
|
|
508
|
+
if (options.sweepIfUnowned && this._hasNoOwners(newEntry)) this._scheduleSweep(Type, newEntry);
|
|
509
|
+
return instance;
|
|
510
|
+
}
|
|
511
|
+
/**
|
|
512
|
+
* Dispose a speculative create (a render that never commits, SSR) that is
|
|
513
|
+
* still unowned after `unownedSweepDelayMs`. The delay must outlast a
|
|
514
|
+
* time-sliced render; a repeat speculative acquire restarts it. Opt-in, so a
|
|
515
|
+
* bare `ensure()` stays usable across an `await`.
|
|
516
|
+
*/
|
|
517
|
+
_scheduleSweep(Type, entry) {
|
|
518
|
+
clearTimeout(entry.sweepTimer);
|
|
519
|
+
entry.sweepTimer = setTimeout(() => {
|
|
520
|
+
entry.sweepTimer = void 0;
|
|
521
|
+
const instances = this.instancesByConstructor.get(Type);
|
|
522
|
+
if (instances?.get(entry.key) !== entry) return;
|
|
523
|
+
if (entry.instance.$blac.disposed) return;
|
|
524
|
+
if (!this._isUnowned(Type, entry)) return;
|
|
525
|
+
entry.instance.dispose();
|
|
526
|
+
instances.delete(entry.key);
|
|
527
|
+
}, getBlacConfig().unownedSweepDelayMs);
|
|
528
|
+
entry.sweepTimer.unref?.();
|
|
529
|
+
}
|
|
530
|
+
/**
|
|
531
|
+
* Get an existing instance without adding a ref.
|
|
532
|
+
*
|
|
533
|
+
* @internal Internal key tier; public callers use the args-based `borrow`.
|
|
534
|
+
* @param Type - The StateContainer class constructor
|
|
535
|
+
* @param instanceKey - Pre-resolved instance key (defaults to 'default')
|
|
536
|
+
* @returns The state container instance
|
|
537
|
+
* @throws Error if instance doesn't exist
|
|
538
|
+
*/
|
|
539
|
+
borrow(Type, instanceKey = DEFAULT_STRUCTURAL_KEY) {
|
|
540
|
+
return this.acquire(Type, instanceKey, {
|
|
541
|
+
canCreate: false,
|
|
542
|
+
countRef: false
|
|
543
|
+
});
|
|
544
|
+
}
|
|
545
|
+
/**
|
|
546
|
+
* Like `borrow`, but returns the error instead of throwing.
|
|
547
|
+
*
|
|
548
|
+
* @internal Internal key tier; public callers use the args-based `borrowSafe`.
|
|
549
|
+
* @param Type - The StateContainer class constructor
|
|
550
|
+
* @param instanceKey - Pre-resolved instance key (defaults to 'default')
|
|
551
|
+
* @returns Discriminated union with either the instance or an error
|
|
552
|
+
*/
|
|
553
|
+
borrowSafe(Type, instanceKey = DEFAULT_STRUCTURAL_KEY) {
|
|
554
|
+
try {
|
|
555
|
+
return {
|
|
556
|
+
error: null,
|
|
557
|
+
instance: this.borrow(Type, instanceKey)
|
|
558
|
+
};
|
|
559
|
+
} catch (error) {
|
|
560
|
+
return {
|
|
561
|
+
error,
|
|
562
|
+
instance: null
|
|
563
|
+
};
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
/**
|
|
567
|
+
* Get or create an instance without adding a ref.
|
|
568
|
+
*
|
|
569
|
+
* @internal Internal key tier; public callers use the args-based `ensure`.
|
|
570
|
+
* @param Type - The StateContainer class constructor
|
|
571
|
+
* @param instanceKey - Pre-resolved instance key, or undefined to derive from `args`
|
|
572
|
+
* (`static key(args)` / structural hash), matching `acquire`.
|
|
573
|
+
* @param args - Construction args; used for keying when no explicit key is given
|
|
574
|
+
* @returns The state container instance
|
|
575
|
+
*/
|
|
576
|
+
ensure(Type, instanceKey, args) {
|
|
577
|
+
return this.acquire(Type, instanceKey, {
|
|
578
|
+
canCreate: true,
|
|
579
|
+
countRef: false,
|
|
580
|
+
args
|
|
581
|
+
});
|
|
582
|
+
}
|
|
583
|
+
/**
|
|
584
|
+
* Remove a ref and dispose the instance once nothing owns it. Releasing a
|
|
585
|
+
* ref that isn't held is a no-op.
|
|
586
|
+
*
|
|
587
|
+
* @internal Internal key tier; public callers use the args-based `release`.
|
|
588
|
+
* @param Type - The StateContainer class constructor
|
|
589
|
+
* @param instanceKey - Pre-resolved instance key (defaults to 'default')
|
|
590
|
+
* @param forceDispose - Force immediate disposal regardless of refs
|
|
591
|
+
* @param refId - The specific ref to remove; removes one arbitrary ref if omitted
|
|
592
|
+
*/
|
|
593
|
+
release(Type, instanceKey = DEFAULT_STRUCTURAL_KEY, forceDispose = false, refId) {
|
|
594
|
+
const instances = this.instancesByConstructor.get(Type);
|
|
595
|
+
const entry = instances?.get(instanceKey);
|
|
596
|
+
if (!instances || !entry) return;
|
|
597
|
+
if (forceDispose) {
|
|
598
|
+
if (!entry.instance.$blac.disposed) entry.instance.dispose();
|
|
599
|
+
instances.delete(instanceKey);
|
|
600
|
+
return;
|
|
601
|
+
}
|
|
602
|
+
const releasedRefId = refId ?? entry.refs.keys().next().value;
|
|
603
|
+
if (releasedRefId === void 0) return;
|
|
604
|
+
const count = entry.refs.get(releasedRefId) ?? 0;
|
|
605
|
+
if (count === 0) return;
|
|
606
|
+
if (count === 1) entry.refs.delete(releasedRefId);
|
|
607
|
+
else entry.refs.set(releasedRefId, count - 1);
|
|
608
|
+
this.emit("refReleased", entry.instance, releasedRefId);
|
|
609
|
+
if (this._isUnowned(Type, entry)) {
|
|
610
|
+
if (!entry.instance.$blac.disposed) entry.instance.dispose();
|
|
611
|
+
instances.delete(instanceKey);
|
|
612
|
+
return;
|
|
613
|
+
}
|
|
614
|
+
this._syncActivation(entry);
|
|
615
|
+
}
|
|
616
|
+
/** Live instances of `Type`. */
|
|
617
|
+
getAll(Type) {
|
|
618
|
+
const instances = this.getInstancesMap(Type);
|
|
619
|
+
const result = [];
|
|
620
|
+
for (const entry of instances.values()) if (!entry.instance.$blac.disposed) result.push(entry.instance);
|
|
621
|
+
return result;
|
|
622
|
+
}
|
|
623
|
+
/** Iterate live instances of `Type`; callback errors are logged. */
|
|
624
|
+
forEach(Type, callback) {
|
|
625
|
+
const instances = this.getInstancesMap(Type);
|
|
626
|
+
for (const entry of instances.values()) {
|
|
627
|
+
const instance = entry.instance;
|
|
628
|
+
if (!instance.$blac.disposed) try {
|
|
629
|
+
callback(instance);
|
|
630
|
+
} catch (error) {
|
|
631
|
+
console.error(`${BLAC_ERROR_PREFIX} forEach callback error for ${getBlacName(Type)}:`, error);
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
/** Dispose all instances of `Type`. */
|
|
636
|
+
clear(Type) {
|
|
637
|
+
const instances = this.instancesByConstructor.get(Type);
|
|
638
|
+
if (!instances) return;
|
|
639
|
+
for (const entry of instances.values()) if (!entry.instance.$blac.disposed) entry.instance.dispose();
|
|
640
|
+
instances.clear();
|
|
641
|
+
}
|
|
642
|
+
/** Dispose every live instance, keepAlive included, for which `predicate` is true. */
|
|
643
|
+
clearWhere(predicate) {
|
|
644
|
+
const matches = [];
|
|
645
|
+
for (const Type of this.types) for (const entry of this.getInstancesMap(Type).values()) if (!entry.instance.$blac.disposed && predicate(entry.instance)) matches.push(entry.instance);
|
|
646
|
+
for (const instance of matches) if (!instance.$blac.disposed) instance.dispose();
|
|
647
|
+
}
|
|
648
|
+
/**
|
|
649
|
+
* Get reference count for an instance (number of active refs).
|
|
650
|
+
*
|
|
651
|
+
* @internal Internal key tier; public callers use the args-based `getRefCount`.
|
|
652
|
+
* @param Type - The StateContainer class constructor
|
|
653
|
+
* @param instanceKey - Pre-resolved instance key (defaults to 'default')
|
|
654
|
+
* @returns Current ref count (0 if instance doesn't exist)
|
|
655
|
+
*/
|
|
656
|
+
getRefCount(Type, instanceKey = DEFAULT_STRUCTURAL_KEY) {
|
|
657
|
+
return this.getInstancesMap(Type).get(instanceKey)?.refs.size ?? 0;
|
|
658
|
+
}
|
|
659
|
+
/**
|
|
660
|
+
* Get all active reference IDs for an instance.
|
|
661
|
+
*
|
|
662
|
+
* @internal Internal key tier; public callers use the args-based `getRefIds`.
|
|
663
|
+
* @param Type - The StateContainer class constructor
|
|
664
|
+
* @param instanceKey - Pre-resolved instance key (defaults to 'default')
|
|
665
|
+
* @returns Array of ref ID strings (empty if instance doesn't exist)
|
|
666
|
+
*/
|
|
667
|
+
getRefIds(Type, instanceKey = DEFAULT_STRUCTURAL_KEY) {
|
|
668
|
+
const entry = this.getInstancesMap(Type).get(instanceKey);
|
|
669
|
+
return entry ? Array.from(entry.refs.keys()) : [];
|
|
670
|
+
}
|
|
671
|
+
/** Ref ids by `$blac.id`, for `PluginContext.getRefIds`. */
|
|
672
|
+
getRefIdsById(instanceId) {
|
|
673
|
+
const entry = this._entryById.get(instanceId);
|
|
674
|
+
return entry ? Array.from(entry.refs.keys()) : [];
|
|
675
|
+
}
|
|
676
|
+
/**
|
|
677
|
+
* Check if an instance exists.
|
|
678
|
+
*
|
|
679
|
+
* @internal Internal key tier; public callers use the args-based `hasInstance`.
|
|
680
|
+
* @param Type - The StateContainer class constructor
|
|
681
|
+
* @param instanceKey - Pre-resolved instance key (defaults to 'default')
|
|
682
|
+
* @returns true if a live (not disposed) instance exists
|
|
683
|
+
*/
|
|
684
|
+
hasInstance(Type, instanceKey = DEFAULT_STRUCTURAL_KEY) {
|
|
685
|
+
const entry = this.getInstancesMap(Type).get(instanceKey);
|
|
686
|
+
return entry !== void 0 && !entry.instance.$blac.disposed;
|
|
687
|
+
}
|
|
688
|
+
/** Dispose every instance and forget all types (for testing). */
|
|
689
|
+
clearAll() {
|
|
690
|
+
for (const Type of this.types) this.clear(Type);
|
|
691
|
+
this.types.clear();
|
|
692
|
+
}
|
|
693
|
+
/** Instance counts, for debugging. */
|
|
694
|
+
getStats() {
|
|
695
|
+
const typeBreakdown = {};
|
|
696
|
+
let totalInstances = 0;
|
|
697
|
+
for (const Type of this.types) {
|
|
698
|
+
const typeName = Type.name;
|
|
699
|
+
const count = this.getInstancesMap(Type).size;
|
|
700
|
+
typeBreakdown[typeName] = count;
|
|
701
|
+
totalInstances += count;
|
|
702
|
+
}
|
|
703
|
+
return {
|
|
704
|
+
registeredTypes: this.types.size,
|
|
705
|
+
totalInstances,
|
|
706
|
+
typeBreakdown
|
|
707
|
+
};
|
|
708
|
+
}
|
|
709
|
+
/** All registered types (for plugins). */
|
|
710
|
+
getTypes() {
|
|
711
|
+
return Array.from(this.types);
|
|
712
|
+
}
|
|
713
|
+
/**
|
|
714
|
+
* Subscribe to a lifecycle event.
|
|
715
|
+
* @returns Unsubscribe function
|
|
716
|
+
*/
|
|
717
|
+
on(event, listener) {
|
|
718
|
+
let instance = this.listeners.get(event);
|
|
719
|
+
if (!instance) {
|
|
720
|
+
instance = /* @__PURE__ */ new Set();
|
|
721
|
+
this.listeners.set(event, instance);
|
|
722
|
+
}
|
|
723
|
+
instance.add(listener);
|
|
724
|
+
return () => {
|
|
725
|
+
this.listeners.get(event)?.delete(listener);
|
|
726
|
+
};
|
|
727
|
+
}
|
|
728
|
+
emit(event, ...args) {
|
|
729
|
+
const listeners = this.listeners.get(event);
|
|
730
|
+
if (!listeners || listeners.size === 0) return;
|
|
731
|
+
for (const listener of listeners) try {
|
|
732
|
+
listener(...args);
|
|
733
|
+
} catch (error) {
|
|
734
|
+
console.error(`${BLAC_ERROR_PREFIX} Listener error for '${event}':`, error);
|
|
735
|
+
}
|
|
736
|
+
}
|
|
737
|
+
/**
|
|
738
|
+
* Queue a microtask-deferred `stateChanged`.
|
|
739
|
+
* @internal - Called by StateContainer.applyState
|
|
740
|
+
*/
|
|
741
|
+
notifyStateChanged(container, previousState, newState) {
|
|
742
|
+
if (!this.hasStateChangedListeners) return;
|
|
743
|
+
if (!this._pendingStateChanges) {
|
|
744
|
+
this._pendingStateChanges = [];
|
|
745
|
+
queueMicrotask(() => this.flushStateChanged());
|
|
746
|
+
}
|
|
747
|
+
this._pendingStateChanges.push([
|
|
748
|
+
container,
|
|
749
|
+
previousState,
|
|
750
|
+
newState
|
|
751
|
+
]);
|
|
752
|
+
}
|
|
753
|
+
/**
|
|
754
|
+
* One event per emit, deliberately not coalesced: devtools and time-travel
|
|
755
|
+
* need every intermediate transition. Plugins get the coalesced lane.
|
|
756
|
+
*/
|
|
757
|
+
flushStateChanged() {
|
|
758
|
+
const pending = this._pendingStateChanges;
|
|
759
|
+
this._pendingStateChanges = null;
|
|
760
|
+
if (!pending) return;
|
|
761
|
+
for (const [container, prev, next] of pending) this.emit("stateChanged", container, prev, next);
|
|
762
|
+
}
|
|
763
|
+
};
|
|
764
|
+
/**
|
|
765
|
+
* The global default registry.
|
|
766
|
+
* @public
|
|
767
|
+
*/
|
|
768
|
+
const globalRegistry = new StateContainerRegistry();
|
|
769
|
+
//#endregion
|
|
770
|
+
export { shallowEqualState as S, IS_DEV as _, INSERT_INSTANCE as a, getBlacConfig as b, REMOVE_DEPS_OWNER as c, getBlacName as d, getClassEquality as f, BLAC_STATIC_PROPS as g, isKeepAliveClass as h, INIT_CONFIG as i, SET_ACTIVE as l, isIsolatedClass as m, globalRegistry as n, ON_ACTIVATED as o, isExcludedFromDevTools as p, APPLY_DEPS as r, ON_DISPOSE as s, StateContainerRegistry as t, WITH_TRACKED_STATE as u, readNodeEnv as v, resetBlacConfig as x, configureBlac as y };
|
|
771
|
+
|
|
772
|
+
//# sourceMappingURL=StateContainerRegistry-CrTtRX5V.js.map
|