@blac/core 2.0.5 → 2.0.6
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/README.md +98 -126
- package/dist/StateContainerRegistry-Brmferpf.js +588 -0
- package/dist/StateContainerRegistry-Brmferpf.js.map +1 -0
- package/dist/StateContainerRegistry-CVPTMaHn.cjs +629 -0
- package/dist/StateContainerRegistry-CVPTMaHn.cjs.map +1 -0
- package/dist/branded-DndTHjv2.cjs +18 -0
- package/dist/branded-DndTHjv2.cjs.map +1 -0
- package/dist/branded-mwGzzPF1.js +13 -0
- package/dist/branded-mwGzzPF1.js.map +1 -0
- package/dist/config-BInS_9WT.cjs +101 -0
- package/dist/config-BInS_9WT.cjs.map +1 -0
- package/dist/config-BmFnPVM0.js +42 -0
- package/dist/config-BmFnPVM0.js.map +1 -0
- package/dist/constants.d.cts +81 -0
- package/dist/core/StateContainer.d.ts +51 -1
- package/dist/debug.cjs +12 -0
- package/dist/debug.d.cts +35 -0
- package/dist/debug.js +3 -0
- package/dist/index.cjs +169 -1515
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1 -2
- package/dist/index.d.ts +1 -2
- package/dist/index.js +136 -1466
- package/dist/index.js.map +1 -1
- package/dist/plugin/BlacPlugin.d.ts +34 -1
- package/dist/plugins.cjs +4 -0
- package/dist/plugins.d.cts +25 -0
- package/dist/plugins.js +2 -0
- package/dist/resolve-dependencies-B22YjFGk.cjs +773 -0
- package/dist/resolve-dependencies-B22YjFGk.cjs.map +1 -0
- package/dist/resolve-dependencies-Cvq0glJF.js +648 -0
- package/dist/resolve-dependencies-Cvq0glJF.js.map +1 -0
- package/dist/tracked-B_MUXIpe.cjs +216 -0
- package/dist/tracked-B_MUXIpe.cjs.map +1 -0
- package/dist/tracked-I_q9buYg.js +181 -0
- package/dist/tracked-I_q9buYg.js.map +1 -0
- package/dist/tracking.cjs +18 -0
- package/dist/tracking.d.cts +9 -0
- package/dist/tracking.js +2 -0
- package/dist/types.cjs +3 -0
- package/dist/types.d.cts +8 -0
- package/dist/types.js +2 -0
- package/dist/watch-entry.cjs +7 -0
- package/dist/watch-entry.d.cts +26 -0
- package/dist/watch-entry.js +2 -0
- package/package.json +57 -3
package/dist/index.js
CHANGED
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
import { a as BLAC_DEFAULTS, i as PluginManager, n as globalRegistry, r as isIsolatedClass, s as BLAC_STATIC_PROPS, t as getPluginManager } from "./StateContainerRegistry-Brmferpf.js";
|
|
2
|
+
import { a as watch, i as instance, n as createTrackedContext, o as ensure, r as tracked, t as TrackedContext } from "./tracked-I_q9buYg.js";
|
|
3
|
+
import { a as clearAll, c as getAll, i as clear, l as getRefCount, n as getStats, o as register, r as setRegistry, s as forEach, t as getRegistry, u as hasInstance } from "./config-BmFnPVM0.js";
|
|
4
|
+
import { t as instanceId } from "./branded-mwGzzPF1.js";
|
|
1
5
|
//#region src/utils/idGenerator.ts
|
|
2
6
|
/**
|
|
3
7
|
* Generate simple ID with timestamp and random (no counter tracking)
|
|
@@ -29,574 +33,6 @@ function generateSimpleId(prefix, affix) {
|
|
|
29
33
|
function generateIsolatedKey() {
|
|
30
34
|
return `isolated-${Math.random().toString(36).slice(2, 11)}`;
|
|
31
35
|
}
|
|
32
|
-
|
|
33
|
-
//#endregion
|
|
34
|
-
//#region src/constants.ts
|
|
35
|
-
/**
|
|
36
|
-
* Default configuration constants for BlaC
|
|
37
|
-
*
|
|
38
|
-
* Centralized location for all magic numbers and default values.
|
|
39
|
-
*/
|
|
40
|
-
/**
|
|
41
|
-
* Default configuration constants for BlaC
|
|
42
|
-
*/
|
|
43
|
-
const BLAC_DEFAULTS = {
|
|
44
|
-
DEFAULT_INSTANCE_KEY: "default",
|
|
45
|
-
MAX_GETTER_DEPTH: 10,
|
|
46
|
-
CLEANUP_INTERVAL_MS: 3e4,
|
|
47
|
-
WEAKREF_CLEANUP_INTERVAL_MS: 1e4,
|
|
48
|
-
MAX_SUBSCRIPTIONS: 1e3,
|
|
49
|
-
MAX_SUBSCRIPTIONS_HIGH_PERF: 1e4,
|
|
50
|
-
PIPELINE_TIMEOUT_MS: 5e3,
|
|
51
|
-
CLEANUP_INTERVAL_HIGH_PERF_MS: 5e3,
|
|
52
|
-
MAX_PIPELINE_STAGES: 30
|
|
53
|
-
};
|
|
54
|
-
/**
|
|
55
|
-
* Static property names for StateContainer classes
|
|
56
|
-
* Used for feature flags and configuration on bloc classes
|
|
57
|
-
*/
|
|
58
|
-
const BLAC_STATIC_PROPS = {
|
|
59
|
-
ISOLATED: "isolated",
|
|
60
|
-
KEEP_ALIVE: "keepAlive",
|
|
61
|
-
EXCLUDE_FROM_DEVTOOLS: "__excludeFromDevTools"
|
|
62
|
-
};
|
|
63
|
-
/**
|
|
64
|
-
* Standard error message prefix
|
|
65
|
-
*/
|
|
66
|
-
const BLAC_ERROR_PREFIX = "[BlaC]";
|
|
67
|
-
|
|
68
|
-
//#endregion
|
|
69
|
-
//#region src/plugin/PluginManager.ts
|
|
70
|
-
/**
|
|
71
|
-
* Manages plugin lifecycle for the BlaC state management system.
|
|
72
|
-
* Plugins receive notifications about state container lifecycle events.
|
|
73
|
-
*
|
|
74
|
-
* @example
|
|
75
|
-
* ```ts
|
|
76
|
-
* const manager = createPluginManager(registry);
|
|
77
|
-
* manager.install(myPlugin, { environment: 'development' });
|
|
78
|
-
* ```
|
|
79
|
-
*/
|
|
80
|
-
var PluginManager = class {
|
|
81
|
-
/**
|
|
82
|
-
* Create a new PluginManager
|
|
83
|
-
* @param registry - The StateContainerRegistry to monitor for lifecycle events
|
|
84
|
-
*/
|
|
85
|
-
constructor(registry) {
|
|
86
|
-
this.plugins = /* @__PURE__ */ new Map();
|
|
87
|
-
this.registry = registry;
|
|
88
|
-
this.setupLifecycleHooks();
|
|
89
|
-
}
|
|
90
|
-
/**
|
|
91
|
-
* Install a plugin with optional configuration
|
|
92
|
-
* @param plugin - The plugin to install
|
|
93
|
-
* @param config - Optional plugin configuration
|
|
94
|
-
* @throws Error if plugin is already installed
|
|
95
|
-
*/
|
|
96
|
-
install(plugin, config = {}) {
|
|
97
|
-
const effectiveConfig = {
|
|
98
|
-
enabled: true,
|
|
99
|
-
environment: "all",
|
|
100
|
-
...config
|
|
101
|
-
};
|
|
102
|
-
if (!this.shouldEnablePlugin(effectiveConfig)) {
|
|
103
|
-
console.log(`[BlaC] Plugin "${plugin.name}" skipped (environment mismatch)`);
|
|
104
|
-
return;
|
|
105
|
-
}
|
|
106
|
-
if (this.plugins.has(plugin.name)) throw new Error(`Plugin "${plugin.name}" is already installed`);
|
|
107
|
-
const context = this.createPluginContext();
|
|
108
|
-
this.plugins.set(plugin.name, {
|
|
109
|
-
plugin,
|
|
110
|
-
config: effectiveConfig,
|
|
111
|
-
context
|
|
112
|
-
});
|
|
113
|
-
if (plugin.onInstall) try {
|
|
114
|
-
plugin.onInstall(context);
|
|
115
|
-
} catch (error) {
|
|
116
|
-
console.error(`[BlaC] Error installing plugin "${plugin.name}":`, error);
|
|
117
|
-
this.plugins.delete(plugin.name);
|
|
118
|
-
throw error;
|
|
119
|
-
}
|
|
120
|
-
console.log(`[BlaC] Plugin "${plugin.name}" v${plugin.version} installed`);
|
|
121
|
-
}
|
|
122
|
-
/**
|
|
123
|
-
* Uninstall a plugin by name
|
|
124
|
-
* @param pluginName - The name of the plugin to uninstall
|
|
125
|
-
* @throws Error if plugin is not installed
|
|
126
|
-
*/
|
|
127
|
-
uninstall(pluginName) {
|
|
128
|
-
const installed = this.plugins.get(pluginName);
|
|
129
|
-
if (!installed) throw new Error(`Plugin "${pluginName}" is not installed`);
|
|
130
|
-
if (installed.plugin.onUninstall) try {
|
|
131
|
-
installed.plugin.onUninstall();
|
|
132
|
-
} catch (error) {
|
|
133
|
-
console.error(`[BlaC] Error uninstalling plugin "${pluginName}":`, error);
|
|
134
|
-
}
|
|
135
|
-
this.plugins.delete(pluginName);
|
|
136
|
-
console.log(`[BlaC] Plugin "${pluginName}" uninstalled`);
|
|
137
|
-
}
|
|
138
|
-
/**
|
|
139
|
-
* Get an installed plugin by name
|
|
140
|
-
* @param pluginName - The name of the plugin to retrieve
|
|
141
|
-
* @returns The plugin instance or undefined if not found
|
|
142
|
-
*/
|
|
143
|
-
getPlugin(pluginName) {
|
|
144
|
-
return this.plugins.get(pluginName)?.plugin;
|
|
145
|
-
}
|
|
146
|
-
/**
|
|
147
|
-
* Get all installed plugins
|
|
148
|
-
* @returns Array of all installed plugins
|
|
149
|
-
*/
|
|
150
|
-
getAllPlugins() {
|
|
151
|
-
return Array.from(this.plugins.values()).map((p) => p.plugin);
|
|
152
|
-
}
|
|
153
|
-
/**
|
|
154
|
-
* Check if a plugin is installed
|
|
155
|
-
* @param pluginName - The name of the plugin to check
|
|
156
|
-
* @returns true if the plugin is installed
|
|
157
|
-
*/
|
|
158
|
-
hasPlugin(pluginName) {
|
|
159
|
-
return this.plugins.has(pluginName);
|
|
160
|
-
}
|
|
161
|
-
/**
|
|
162
|
-
* Uninstall all plugins
|
|
163
|
-
*/
|
|
164
|
-
clear() {
|
|
165
|
-
for (const name of this.plugins.keys()) this.uninstall(name);
|
|
166
|
-
}
|
|
167
|
-
/**
|
|
168
|
-
* Setup lifecycle hooks to notify plugins
|
|
169
|
-
*/
|
|
170
|
-
setupLifecycleHooks() {
|
|
171
|
-
this.registry.on("created", (instance) => {
|
|
172
|
-
this.notifyPlugins("onInstanceCreated", instance);
|
|
173
|
-
});
|
|
174
|
-
this.registry.on("stateChanged", (instance, previousState, currentState, callstack) => {
|
|
175
|
-
this.notifyPlugins("onStateChanged", instance, previousState, currentState, callstack);
|
|
176
|
-
});
|
|
177
|
-
this.registry.on("disposed", (instance) => {
|
|
178
|
-
this.notifyPlugins("onInstanceDisposed", instance);
|
|
179
|
-
});
|
|
180
|
-
}
|
|
181
|
-
/**
|
|
182
|
-
* Notify all plugins of a lifecycle event
|
|
183
|
-
*/
|
|
184
|
-
notifyPlugins(hookName, ...args) {
|
|
185
|
-
for (const { plugin, config, context } of this.plugins.values()) {
|
|
186
|
-
if (!config.enabled) continue;
|
|
187
|
-
const hook = plugin[hookName];
|
|
188
|
-
if (typeof hook === "function") try {
|
|
189
|
-
hook.apply(plugin, [...args, context]);
|
|
190
|
-
} catch (error) {
|
|
191
|
-
console.error(`[BlaC] Error in plugin "${plugin.name}" ${hookName}:`, error);
|
|
192
|
-
}
|
|
193
|
-
}
|
|
194
|
-
}
|
|
195
|
-
/**
|
|
196
|
-
* Create plugin context with safe API access
|
|
197
|
-
*/
|
|
198
|
-
createPluginContext() {
|
|
199
|
-
return {
|
|
200
|
-
getInstanceMetadata: (instance) => {
|
|
201
|
-
return {
|
|
202
|
-
id: instance.instanceId,
|
|
203
|
-
className: instance.constructor.name,
|
|
204
|
-
isDisposed: instance.isDisposed,
|
|
205
|
-
name: instance.name,
|
|
206
|
-
lastStateChangeTimestamp: instance.lastUpdateTimestamp,
|
|
207
|
-
createdAt: instance.createdAt,
|
|
208
|
-
state: instance.state,
|
|
209
|
-
isIsolated: instance.instanceId.startsWith("isolated-")
|
|
210
|
-
};
|
|
211
|
-
},
|
|
212
|
-
getState: (instance) => {
|
|
213
|
-
return instance.state;
|
|
214
|
-
},
|
|
215
|
-
queryInstances: (typeClass) => {
|
|
216
|
-
return this.registry.getAll(typeClass);
|
|
217
|
-
},
|
|
218
|
-
getAllTypes: () => {
|
|
219
|
-
return this.registry.getTypes();
|
|
220
|
-
},
|
|
221
|
-
getStats: () => {
|
|
222
|
-
return this.registry.getStats();
|
|
223
|
-
}
|
|
224
|
-
};
|
|
225
|
-
}
|
|
226
|
-
/**
|
|
227
|
-
* Check if plugin should be enabled based on environment
|
|
228
|
-
*/
|
|
229
|
-
shouldEnablePlugin(config) {
|
|
230
|
-
if (!config.enabled) return false;
|
|
231
|
-
if (config.environment === "all") return true;
|
|
232
|
-
return this.getCurrentEnvironment() === config.environment;
|
|
233
|
-
}
|
|
234
|
-
/**
|
|
235
|
-
* Get current environment
|
|
236
|
-
*/
|
|
237
|
-
getCurrentEnvironment() {
|
|
238
|
-
if (typeof process !== "undefined") {
|
|
239
|
-
if (process.env.NODE_ENV === "test") return "test";
|
|
240
|
-
if (process.env.NODE_ENV === "production") return "production";
|
|
241
|
-
return "development";
|
|
242
|
-
}
|
|
243
|
-
return "development";
|
|
244
|
-
}
|
|
245
|
-
};
|
|
246
|
-
/**
|
|
247
|
-
* Create a plugin manager instance
|
|
248
|
-
* @param registry - The StateContainerRegistry to monitor for lifecycle events
|
|
249
|
-
* @returns A new PluginManager instance
|
|
250
|
-
*/
|
|
251
|
-
function createPluginManager(registry) {
|
|
252
|
-
return new PluginManager(registry);
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
//#endregion
|
|
256
|
-
//#region src/utils/static-props.ts
|
|
257
|
-
/**
|
|
258
|
-
* Utility functions for accessing static properties on StateContainer classes
|
|
259
|
-
*/
|
|
260
|
-
/**
|
|
261
|
-
* Get a static property from a class constructor
|
|
262
|
-
* Type-safe helper that avoids (Type as any) casts
|
|
263
|
-
*
|
|
264
|
-
* @param Type - The class constructor
|
|
265
|
-
* @param propName - The property name to access
|
|
266
|
-
* @param defaultValue - Optional default value if property is undefined
|
|
267
|
-
* @returns The property value or default
|
|
268
|
-
*/
|
|
269
|
-
function getStaticProp(Type, propName, defaultValue) {
|
|
270
|
-
return Type[propName] ?? defaultValue;
|
|
271
|
-
}
|
|
272
|
-
/**
|
|
273
|
-
* Check if a class is marked as isolated.
|
|
274
|
-
* Isolated classes create separate instances per component.
|
|
275
|
-
* @param Type - The class constructor to check
|
|
276
|
-
* @returns true if the class has `static isolated = true`
|
|
277
|
-
*/
|
|
278
|
-
function isIsolatedClass(Type) {
|
|
279
|
-
return getStaticProp(Type, BLAC_STATIC_PROPS.ISOLATED) === true;
|
|
280
|
-
}
|
|
281
|
-
/**
|
|
282
|
-
* Check if a class is marked as keepAlive.
|
|
283
|
-
* KeepAlive classes are never auto-disposed when ref count reaches 0.
|
|
284
|
-
* @param Type - The class constructor to check
|
|
285
|
-
* @returns true if the class has `static keepAlive = true`
|
|
286
|
-
*/
|
|
287
|
-
function isKeepAliveClass(Type) {
|
|
288
|
-
return getStaticProp(Type, BLAC_STATIC_PROPS.KEEP_ALIVE) === true;
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
//#endregion
|
|
292
|
-
//#region src/core/StateContainerRegistry.ts
|
|
293
|
-
/**
|
|
294
|
-
* Central registry for managing StateContainer instances.
|
|
295
|
-
* Handles instance lifecycle, ref counting, and lifecycle event emission.
|
|
296
|
-
*
|
|
297
|
-
* @example
|
|
298
|
-
* ```ts
|
|
299
|
-
* const registry = new StateContainerRegistry();
|
|
300
|
-
* const instance = registry.acquire(MyBloc); // ownership, must release
|
|
301
|
-
* const other = registry.ensure(OtherBloc); // no ownership, bloc-to-bloc
|
|
302
|
-
* registry.on('stateChanged', (container, prev, next) => {
|
|
303
|
-
* console.log('State changed:', prev, '->', next);
|
|
304
|
-
* });
|
|
305
|
-
* ```
|
|
306
|
-
*/
|
|
307
|
-
var StateContainerRegistry = class {
|
|
308
|
-
constructor() {
|
|
309
|
-
this.instancesByConstructor = /* @__PURE__ */ new WeakMap();
|
|
310
|
-
this.types = /* @__PURE__ */ new Set();
|
|
311
|
-
this.typeConfigs = /* @__PURE__ */ new Map();
|
|
312
|
-
this.listeners = /* @__PURE__ */ new Map();
|
|
313
|
-
}
|
|
314
|
-
/**
|
|
315
|
-
* Register a type for lifecycle event tracking
|
|
316
|
-
* @param constructor - The StateContainer class constructor
|
|
317
|
-
*/
|
|
318
|
-
registerType(constructor) {
|
|
319
|
-
this.types.add(constructor);
|
|
320
|
-
}
|
|
321
|
-
/**
|
|
322
|
-
* Register a StateContainer class with configuration
|
|
323
|
-
* @param constructor - The StateContainer class constructor
|
|
324
|
-
* @param isolated - Whether instances should be isolated (component-scoped)
|
|
325
|
-
* @throws Error if type is already registered
|
|
326
|
-
*/
|
|
327
|
-
register(constructor, isolated = false) {
|
|
328
|
-
const className = constructor.name;
|
|
329
|
-
if (!isolated && isIsolatedClass(constructor)) isolated = true;
|
|
330
|
-
if (this.typeConfigs.has(className)) throw new Error(`${BLAC_ERROR_PREFIX} Type "${className}" is already registered`);
|
|
331
|
-
this.typeConfigs.set(className, { isolated });
|
|
332
|
-
this.registerType(constructor);
|
|
333
|
-
}
|
|
334
|
-
ensureInstancesMap(Type) {
|
|
335
|
-
let instances = this.instancesByConstructor.get(Type);
|
|
336
|
-
if (!instances) {
|
|
337
|
-
instances = /* @__PURE__ */ new Map();
|
|
338
|
-
this.instancesByConstructor.set(Type, instances);
|
|
339
|
-
}
|
|
340
|
-
return instances;
|
|
341
|
-
}
|
|
342
|
-
/**
|
|
343
|
-
* Get the instances Map for a specific class (public API for stats/debugging)
|
|
344
|
-
*/
|
|
345
|
-
getInstancesMap(Type) {
|
|
346
|
-
return this.instancesByConstructor.get(Type) || /* @__PURE__ */ new Map();
|
|
347
|
-
}
|
|
348
|
-
/**
|
|
349
|
-
* Acquire an instance with ref counting (ownership semantics).
|
|
350
|
-
* Creates a new instance if one doesn't exist, or returns existing and increments ref count.
|
|
351
|
-
* You must call `release()` when done to decrement the ref count.
|
|
352
|
-
* @param Type - The StateContainer class constructor
|
|
353
|
-
* @param instanceKey - Instance key (defaults to 'default')
|
|
354
|
-
* @param options - Acquisition options
|
|
355
|
-
* @param options.canCreate - Whether to create new instance if not found (default: true)
|
|
356
|
-
* @param options.countRef - Whether to increment ref count (default: true)
|
|
357
|
-
* @returns The state container instance
|
|
358
|
-
*/
|
|
359
|
-
acquire(Type, instanceKey = BLAC_DEFAULTS.DEFAULT_INSTANCE_KEY, options = {}) {
|
|
360
|
-
const { canCreate = true, countRef = true } = options;
|
|
361
|
-
const registryConfig = this.typeConfigs.get(Type.name);
|
|
362
|
-
const isolated = isIsolatedClass(Type) || registryConfig?.isolated === true;
|
|
363
|
-
const config = { instanceId: instanceKey };
|
|
364
|
-
if (isolated && !canCreate) throw new Error(`${BLAC_ERROR_PREFIX} Cannot get isolated instance "${instanceKey}" of ${Type.name} when creation is disabled.`);
|
|
365
|
-
if (isolated) {
|
|
366
|
-
const instance = new Type();
|
|
367
|
-
instance.initConfig(config);
|
|
368
|
-
this.registerType(Type);
|
|
369
|
-
return instance;
|
|
370
|
-
}
|
|
371
|
-
const instances = this.ensureInstancesMap(Type);
|
|
372
|
-
let entry = instances.get(instanceKey);
|
|
373
|
-
if (entry?.instance.isDisposed) {
|
|
374
|
-
instances.delete(instanceKey);
|
|
375
|
-
entry = void 0;
|
|
376
|
-
}
|
|
377
|
-
if (entry && countRef) entry.refCount++;
|
|
378
|
-
if (entry) return entry.instance;
|
|
379
|
-
if (!canCreate) throw new Error(`${BLAC_ERROR_PREFIX} ${Type.name} instance "${instanceKey}" not found and creation is disabled.`);
|
|
380
|
-
const instance = new Type();
|
|
381
|
-
instance.initConfig(config);
|
|
382
|
-
instances.set(instanceKey, {
|
|
383
|
-
instance,
|
|
384
|
-
refCount: 1
|
|
385
|
-
});
|
|
386
|
-
this.registerType(Type);
|
|
387
|
-
return instance;
|
|
388
|
-
}
|
|
389
|
-
/**
|
|
390
|
-
* Borrow an existing instance without incrementing ref count (borrowing semantics).
|
|
391
|
-
* Tracks cross-bloc dependency for reactive updates.
|
|
392
|
-
* @param Type - The StateContainer class constructor
|
|
393
|
-
* @param instanceKey - Instance key (defaults to 'default')
|
|
394
|
-
* @returns The state container instance
|
|
395
|
-
* @throws Error if instance doesn't exist
|
|
396
|
-
*/
|
|
397
|
-
borrow(Type, instanceKey = BLAC_DEFAULTS.DEFAULT_INSTANCE_KEY) {
|
|
398
|
-
return this.acquire(Type, instanceKey, {
|
|
399
|
-
canCreate: false,
|
|
400
|
-
countRef: false
|
|
401
|
-
});
|
|
402
|
-
}
|
|
403
|
-
/**
|
|
404
|
-
* Safely borrow an existing instance (borrowing semantics with error handling).
|
|
405
|
-
* Returns discriminated union for type-safe conditional access.
|
|
406
|
-
* @param Type - The StateContainer class constructor
|
|
407
|
-
* @param instanceKey - Instance key (defaults to 'default')
|
|
408
|
-
* @returns Discriminated union with either the instance or an error
|
|
409
|
-
*/
|
|
410
|
-
borrowSafe(Type, instanceKey = BLAC_DEFAULTS.DEFAULT_INSTANCE_KEY) {
|
|
411
|
-
try {
|
|
412
|
-
return {
|
|
413
|
-
error: null,
|
|
414
|
-
instance: this.borrow(Type, instanceKey)
|
|
415
|
-
};
|
|
416
|
-
} catch (error) {
|
|
417
|
-
return {
|
|
418
|
-
error,
|
|
419
|
-
instance: null
|
|
420
|
-
};
|
|
421
|
-
}
|
|
422
|
-
}
|
|
423
|
-
/**
|
|
424
|
-
* Ensure an instance exists without taking ownership (for bloc-to-bloc communication).
|
|
425
|
-
* Gets existing instance OR creates it if it doesn't exist, without incrementing ref count.
|
|
426
|
-
* Tracks cross-bloc dependency for reactive updates.
|
|
427
|
-
*
|
|
428
|
-
* Use this in bloc-to-bloc communication when you need to ensure an instance exists
|
|
429
|
-
* but don't want to claim ownership (no ref count increment).
|
|
430
|
-
* @param Type - The StateContainer class constructor
|
|
431
|
-
* @param instanceKey - Instance key (defaults to 'default')
|
|
432
|
-
* @returns The state container instance
|
|
433
|
-
*/
|
|
434
|
-
ensure(Type, instanceKey = BLAC_DEFAULTS.DEFAULT_INSTANCE_KEY) {
|
|
435
|
-
return this.acquire(Type, instanceKey, {
|
|
436
|
-
canCreate: true,
|
|
437
|
-
countRef: false
|
|
438
|
-
});
|
|
439
|
-
}
|
|
440
|
-
/**
|
|
441
|
-
* Release a reference to an instance.
|
|
442
|
-
* Decrements ref count and disposes when it reaches 0 (unless keepAlive).
|
|
443
|
-
* @param Type - The StateContainer class constructor
|
|
444
|
-
* @param instanceKey - Instance key (defaults to 'default')
|
|
445
|
-
* @param forceDispose - Force immediate disposal regardless of ref count
|
|
446
|
-
*/
|
|
447
|
-
release(Type, instanceKey = BLAC_DEFAULTS.DEFAULT_INSTANCE_KEY, forceDispose = false) {
|
|
448
|
-
const instances = this.ensureInstancesMap(Type);
|
|
449
|
-
const entry = instances.get(instanceKey);
|
|
450
|
-
if (!entry) return;
|
|
451
|
-
if (forceDispose) {
|
|
452
|
-
if (!entry.instance.isDisposed) entry.instance.dispose();
|
|
453
|
-
instances.delete(instanceKey);
|
|
454
|
-
return;
|
|
455
|
-
}
|
|
456
|
-
entry.refCount--;
|
|
457
|
-
const keepAlive = isKeepAliveClass(Type);
|
|
458
|
-
if (entry.refCount <= 0 && !keepAlive) {
|
|
459
|
-
if (!entry.instance.isDisposed) entry.instance.dispose();
|
|
460
|
-
instances.delete(instanceKey);
|
|
461
|
-
}
|
|
462
|
-
}
|
|
463
|
-
/**
|
|
464
|
-
* Get all instances of a specific type.
|
|
465
|
-
* @param Type - The StateContainer class constructor
|
|
466
|
-
* @returns Array of all instances
|
|
467
|
-
*/
|
|
468
|
-
getAll(Type) {
|
|
469
|
-
const instances = this.ensureInstancesMap(Type);
|
|
470
|
-
const result = [];
|
|
471
|
-
for (const entry of instances.values()) result.push(entry.instance);
|
|
472
|
-
return result;
|
|
473
|
-
}
|
|
474
|
-
/**
|
|
475
|
-
* Safely iterate over all instances of a type.
|
|
476
|
-
* Skips disposed instances and catches callback errors.
|
|
477
|
-
* @param Type - The StateContainer class constructor
|
|
478
|
-
* @param callback - Function to call for each instance
|
|
479
|
-
*/
|
|
480
|
-
forEach(Type, callback) {
|
|
481
|
-
const instances = this.ensureInstancesMap(Type);
|
|
482
|
-
for (const entry of instances.values()) {
|
|
483
|
-
const instance = entry.instance;
|
|
484
|
-
if (!instance.isDisposed) try {
|
|
485
|
-
callback(instance);
|
|
486
|
-
} catch (error) {
|
|
487
|
-
console.error(`${BLAC_ERROR_PREFIX} forEach callback error for ${Type.name}:`, error);
|
|
488
|
-
}
|
|
489
|
-
}
|
|
490
|
-
}
|
|
491
|
-
/**
|
|
492
|
-
* Clear all instances of a specific type (disposes them).
|
|
493
|
-
* @param Type - The StateContainer class constructor
|
|
494
|
-
*/
|
|
495
|
-
clear(Type) {
|
|
496
|
-
const instances = this.ensureInstancesMap(Type);
|
|
497
|
-
for (const entry of instances.values()) if (!entry.instance.isDisposed) entry.instance.dispose();
|
|
498
|
-
instances.clear();
|
|
499
|
-
}
|
|
500
|
-
/**
|
|
501
|
-
* Get reference count for an instance.
|
|
502
|
-
* @param Type - The StateContainer class constructor
|
|
503
|
-
* @param instanceKey - Instance key (defaults to 'default')
|
|
504
|
-
* @returns Current ref count (0 if instance doesn't exist)
|
|
505
|
-
*/
|
|
506
|
-
getRefCount(Type, instanceKey = BLAC_DEFAULTS.DEFAULT_INSTANCE_KEY) {
|
|
507
|
-
return this.ensureInstancesMap(Type).get(instanceKey)?.refCount ?? 0;
|
|
508
|
-
}
|
|
509
|
-
/**
|
|
510
|
-
* Check if an instance exists.
|
|
511
|
-
* @param Type - The StateContainer class constructor
|
|
512
|
-
* @param instanceKey - Instance key (defaults to 'default')
|
|
513
|
-
* @returns true if instance exists
|
|
514
|
-
*/
|
|
515
|
-
hasInstance(Type, instanceKey = BLAC_DEFAULTS.DEFAULT_INSTANCE_KEY) {
|
|
516
|
-
return this.ensureInstancesMap(Type).has(instanceKey);
|
|
517
|
-
}
|
|
518
|
-
/**
|
|
519
|
-
* Clear all instances from all types (for testing)
|
|
520
|
-
*
|
|
521
|
-
* Iterates all registered types and clears their instances.
|
|
522
|
-
* Also clears type tracking to reset the registry state.
|
|
523
|
-
*/
|
|
524
|
-
clearAll() {
|
|
525
|
-
for (const Type of this.types) this.clear(Type);
|
|
526
|
-
this.types.clear();
|
|
527
|
-
this.typeConfigs.clear();
|
|
528
|
-
}
|
|
529
|
-
/**
|
|
530
|
-
* Get registry statistics for debugging.
|
|
531
|
-
* @returns Object with registeredTypes, totalInstances, and typeBreakdown
|
|
532
|
-
*/
|
|
533
|
-
getStats() {
|
|
534
|
-
const typeBreakdown = {};
|
|
535
|
-
let totalInstances = 0;
|
|
536
|
-
for (const Type of this.types) {
|
|
537
|
-
const typeName = Type.name;
|
|
538
|
-
const count = this.getInstancesMap(Type).size;
|
|
539
|
-
typeBreakdown[typeName] = count;
|
|
540
|
-
totalInstances += count;
|
|
541
|
-
}
|
|
542
|
-
return {
|
|
543
|
-
registeredTypes: this.types.size,
|
|
544
|
-
totalInstances,
|
|
545
|
-
typeBreakdown
|
|
546
|
-
};
|
|
547
|
-
}
|
|
548
|
-
/**
|
|
549
|
-
* Get all registered types (for plugin system).
|
|
550
|
-
* @returns Array of all registered StateContainer class constructors
|
|
551
|
-
*/
|
|
552
|
-
getTypes() {
|
|
553
|
-
return Array.from(this.types);
|
|
554
|
-
}
|
|
555
|
-
/**
|
|
556
|
-
* Subscribe to lifecycle events
|
|
557
|
-
* @param event - The lifecycle event to listen for
|
|
558
|
-
* @param listener - The listener function to call when the event occurs
|
|
559
|
-
* @returns Unsubscribe function
|
|
560
|
-
*/
|
|
561
|
-
on(event, listener) {
|
|
562
|
-
if (!this.listeners.has(event)) this.listeners.set(event, /* @__PURE__ */ new Set());
|
|
563
|
-
const instance = this.listeners.get(event);
|
|
564
|
-
if (!instance) throw new Error(`${BLAC_ERROR_PREFIX} Failed to register listener for event '${event}'`);
|
|
565
|
-
instance.add(listener);
|
|
566
|
-
return () => {
|
|
567
|
-
this.listeners.get(event)?.delete(listener);
|
|
568
|
-
};
|
|
569
|
-
}
|
|
570
|
-
/**
|
|
571
|
-
* Emit lifecycle event to all listeners
|
|
572
|
-
* @internal - Called by StateContainer lifecycle methods
|
|
573
|
-
*/
|
|
574
|
-
emit(event, ...args) {
|
|
575
|
-
const listeners = this.listeners.get(event);
|
|
576
|
-
if (!listeners || listeners.size === 0) return;
|
|
577
|
-
for (const listener of listeners) try {
|
|
578
|
-
listener(...args);
|
|
579
|
-
} catch (error) {
|
|
580
|
-
console.error(`${BLAC_ERROR_PREFIX} Listener error for '${event}':`, error);
|
|
581
|
-
}
|
|
582
|
-
}
|
|
583
|
-
};
|
|
584
|
-
/**
|
|
585
|
-
* Global default registry instance
|
|
586
|
-
*/
|
|
587
|
-
const globalRegistry = new StateContainerRegistry();
|
|
588
|
-
/**
|
|
589
|
-
* Global plugin manager (initialized lazily)
|
|
590
|
-
*/
|
|
591
|
-
let _globalPluginManager = null;
|
|
592
|
-
/**
|
|
593
|
-
* Get the global plugin manager
|
|
594
|
-
*/
|
|
595
|
-
function getPluginManager() {
|
|
596
|
-
if (!_globalPluginManager) _globalPluginManager = createPluginManager(globalRegistry);
|
|
597
|
-
return _globalPluginManager;
|
|
598
|
-
}
|
|
599
|
-
|
|
600
36
|
//#endregion
|
|
601
37
|
//#region src/core/StateContainer.ts
|
|
602
38
|
const EMPTY_DEPS = /* @__PURE__ */ new Map();
|
|
@@ -637,6 +73,10 @@ var StateContainer = class StateContainer {
|
|
|
637
73
|
constructor(initialState) {
|
|
638
74
|
this.listeners = /* @__PURE__ */ new Set();
|
|
639
75
|
this._disposed = false;
|
|
76
|
+
this._hydrationStatus = "idle";
|
|
77
|
+
this._changedWhileHydrating = false;
|
|
78
|
+
this.hydrationPromise = null;
|
|
79
|
+
this.hydrationPromiseSettled = false;
|
|
640
80
|
this.config = {};
|
|
641
81
|
this.systemEventHandlers = /* @__PURE__ */ new Map();
|
|
642
82
|
this.name = this.constructor.name;
|
|
@@ -678,6 +118,22 @@ var StateContainer = class StateContainer {
|
|
|
678
118
|
get isDisposed() {
|
|
679
119
|
return this._disposed;
|
|
680
120
|
}
|
|
121
|
+
/** Current hydration status for this instance */
|
|
122
|
+
get hydrationStatus() {
|
|
123
|
+
return this._hydrationStatus;
|
|
124
|
+
}
|
|
125
|
+
/** Error from the latest failed hydration, if any */
|
|
126
|
+
get hydrationError() {
|
|
127
|
+
return this._hydrationError;
|
|
128
|
+
}
|
|
129
|
+
/** Whether this instance has completed hydration successfully */
|
|
130
|
+
get isHydrated() {
|
|
131
|
+
return this._hydrationStatus === "hydrated";
|
|
132
|
+
}
|
|
133
|
+
/** Whether user state changes happened while hydration was pending */
|
|
134
|
+
get changedWhileHydrating() {
|
|
135
|
+
return this._changedWhileHydrating;
|
|
136
|
+
}
|
|
681
137
|
/**
|
|
682
138
|
* Subscribe to state changes
|
|
683
139
|
* @param listener - Function called when state changes
|
|
@@ -696,6 +152,7 @@ var StateContainer = class StateContainer {
|
|
|
696
152
|
if (this._disposed) return;
|
|
697
153
|
if (this.debug) console.log(`[${this.name}] Disposing...`);
|
|
698
154
|
this._disposed = true;
|
|
155
|
+
if (this._hydrationStatus === "hydrating") this.failHydration(/* @__PURE__ */ new Error(`Hydration cancelled because ${this.name} was disposed`));
|
|
699
156
|
this.emitSystemEvent("dispose", void 0);
|
|
700
157
|
this.listeners.clear();
|
|
701
158
|
this.systemEventHandlers.clear();
|
|
@@ -708,22 +165,60 @@ var StateContainer = class StateContainer {
|
|
|
708
165
|
* @protected
|
|
709
166
|
*/
|
|
710
167
|
emit(newState) {
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
});
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
168
|
+
this.applyState(newState, "default");
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* Mark this instance as hydrating. Plugins should call this before starting
|
|
172
|
+
* asynchronous rehydration work.
|
|
173
|
+
*/
|
|
174
|
+
beginHydration() {
|
|
175
|
+
if (this._disposed) throw new Error(`Cannot begin hydration for disposed container ${this.name}`);
|
|
176
|
+
this._changedWhileHydrating = false;
|
|
177
|
+
this._hydrationError = void 0;
|
|
178
|
+
this.hydrationPromise = null;
|
|
179
|
+
this.resolveHydrationPromise = void 0;
|
|
180
|
+
this.rejectHydrationPromise = void 0;
|
|
181
|
+
this.hydrationPromiseSettled = false;
|
|
182
|
+
this.ensureHydrationPromise();
|
|
183
|
+
this.setHydrationStatus("hydrating");
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* Apply hydrated state if no user writes happened since hydration started.
|
|
187
|
+
* Returns false when hydration should be skipped.
|
|
188
|
+
*/
|
|
189
|
+
applyHydratedState(newState) {
|
|
190
|
+
if (this._disposed) return false;
|
|
191
|
+
if (this._hydrationStatus !== "hydrating" || this._changedWhileHydrating) return false;
|
|
192
|
+
this.applyState(newState, "hydration");
|
|
193
|
+
return true;
|
|
194
|
+
}
|
|
195
|
+
/**
|
|
196
|
+
* Mark hydration as completed.
|
|
197
|
+
*/
|
|
198
|
+
finishHydration() {
|
|
199
|
+
if (this._hydrationStatus !== "hydrating") {
|
|
200
|
+
if (this._hydrationStatus === "hydrated") return;
|
|
201
|
+
if (this._hydrationStatus === "error") this.ensureHydrationPromise();
|
|
723
202
|
}
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
203
|
+
this.setHydrationStatus("hydrated");
|
|
204
|
+
this.resolveHydration();
|
|
205
|
+
}
|
|
206
|
+
/**
|
|
207
|
+
* Mark hydration as failed.
|
|
208
|
+
*/
|
|
209
|
+
failHydration(error) {
|
|
210
|
+
const err = error instanceof Error ? error : /* @__PURE__ */ new Error(`Hydration failed: ${error}`);
|
|
211
|
+
this._hydrationError = err;
|
|
212
|
+
this.setHydrationStatus("error", err);
|
|
213
|
+
this.rejectHydration(err);
|
|
214
|
+
}
|
|
215
|
+
/**
|
|
216
|
+
* Wait until the current hydration cycle finishes.
|
|
217
|
+
*/
|
|
218
|
+
waitForHydration() {
|
|
219
|
+
if (this._hydrationStatus === "idle" || this._hydrationStatus === "hydrated") return Promise.resolve();
|
|
220
|
+
if (this._hydrationStatus === "error") return Promise.reject(this._hydrationError ?? /* @__PURE__ */ new Error(`Hydration failed for container ${this.name}`));
|
|
221
|
+
return this.ensureHydrationPromise();
|
|
727
222
|
}
|
|
728
223
|
captureStackTrace() {
|
|
729
224
|
if (!StateContainer.enableStackTrace || typeof process !== "undefined" && process.env?.NODE_ENV === "production") return "";
|
|
@@ -772,6 +267,63 @@ var StateContainer = class StateContainer {
|
|
|
772
267
|
if (this._disposed) throw new Error(`Cannot update state from disposed container ${this.name}`);
|
|
773
268
|
this.emit(updater(this._state));
|
|
774
269
|
}
|
|
270
|
+
applyState(newState, source) {
|
|
271
|
+
if (this._disposed) throw new Error(`Cannot emit state from disposed container ${this.name}`);
|
|
272
|
+
if (this._state === newState) return;
|
|
273
|
+
if (this._hydrationStatus === "hydrating" && source !== "hydration") this._changedWhileHydrating = true;
|
|
274
|
+
const previousState = this._state;
|
|
275
|
+
this._state = newState;
|
|
276
|
+
this.emitSystemEvent("stateChanged", {
|
|
277
|
+
state: newState,
|
|
278
|
+
previousState
|
|
279
|
+
});
|
|
280
|
+
const listeners = Array.from(this.listeners);
|
|
281
|
+
for (const listener of listeners) try {
|
|
282
|
+
listener(newState);
|
|
283
|
+
} catch (error) {
|
|
284
|
+
console.error(`[${this.name}] Error in listener:`, error);
|
|
285
|
+
}
|
|
286
|
+
const stackTrace = this.captureStackTrace();
|
|
287
|
+
globalRegistry.emit("stateChanged", this, previousState, newState, stackTrace);
|
|
288
|
+
this.lastUpdateTimestamp = Date.now();
|
|
289
|
+
}
|
|
290
|
+
setHydrationStatus(status, error) {
|
|
291
|
+
const previousStatus = this._hydrationStatus;
|
|
292
|
+
this._hydrationStatus = status;
|
|
293
|
+
this._hydrationError = error;
|
|
294
|
+
this.emitSystemEvent("hydrationChanged", {
|
|
295
|
+
status,
|
|
296
|
+
previousStatus,
|
|
297
|
+
error,
|
|
298
|
+
changedWhileHydrating: this._changedWhileHydrating
|
|
299
|
+
});
|
|
300
|
+
}
|
|
301
|
+
ensureHydrationPromise() {
|
|
302
|
+
if (!this.hydrationPromise || this.hydrationPromiseSettled) {
|
|
303
|
+
this.hydrationPromiseSettled = false;
|
|
304
|
+
this.hydrationPromise = new Promise((resolve, reject) => {
|
|
305
|
+
this.resolveHydrationPromise = () => {
|
|
306
|
+
if (this.hydrationPromiseSettled) return;
|
|
307
|
+
this.hydrationPromiseSettled = true;
|
|
308
|
+
resolve();
|
|
309
|
+
};
|
|
310
|
+
this.rejectHydrationPromise = (error) => {
|
|
311
|
+
if (this.hydrationPromiseSettled) return;
|
|
312
|
+
this.hydrationPromiseSettled = true;
|
|
313
|
+
reject(error);
|
|
314
|
+
};
|
|
315
|
+
});
|
|
316
|
+
this.hydrationPromise.catch(() => {});
|
|
317
|
+
}
|
|
318
|
+
return this.hydrationPromise;
|
|
319
|
+
}
|
|
320
|
+
resolveHydration() {
|
|
321
|
+
this.resolveHydrationPromise?.();
|
|
322
|
+
}
|
|
323
|
+
rejectHydration(error) {
|
|
324
|
+
this.ensureHydrationPromise();
|
|
325
|
+
this.rejectHydrationPromise?.(error);
|
|
326
|
+
}
|
|
775
327
|
emitSystemEvent(event, payload) {
|
|
776
328
|
const handlers = this.systemEventHandlers.get(event);
|
|
777
329
|
if (!handlers) return;
|
|
@@ -782,7 +334,6 @@ var StateContainer = class StateContainer {
|
|
|
782
334
|
}
|
|
783
335
|
}
|
|
784
336
|
};
|
|
785
|
-
|
|
786
337
|
//#endregion
|
|
787
338
|
//#region src/core/Cubit.ts
|
|
788
339
|
/**
|
|
@@ -823,7 +374,6 @@ var Cubit = class extends StateContainer {
|
|
|
823
374
|
super["update"](updater);
|
|
824
375
|
}
|
|
825
376
|
};
|
|
826
|
-
|
|
827
377
|
//#endregion
|
|
828
378
|
//#region src/registry/acquire.ts
|
|
829
379
|
function acquire(BlocClass, instanceKey) {
|
|
@@ -832,7 +382,6 @@ function acquire(BlocClass, instanceKey) {
|
|
|
832
382
|
countRef: true
|
|
833
383
|
});
|
|
834
384
|
}
|
|
835
|
-
|
|
836
385
|
//#endregion
|
|
837
386
|
//#region src/registry/borrow.ts
|
|
838
387
|
function borrow(BlocClass, instanceKey) {
|
|
@@ -841,60 +390,11 @@ function borrow(BlocClass, instanceKey) {
|
|
|
841
390
|
function borrowSafe(BlocClass, instanceKey) {
|
|
842
391
|
return globalRegistry.borrowSafe(BlocClass, instanceKey);
|
|
843
392
|
}
|
|
844
|
-
|
|
845
|
-
//#endregion
|
|
846
|
-
//#region src/registry/ensure.ts
|
|
847
|
-
function ensure(BlocClass, instanceKey) {
|
|
848
|
-
return globalRegistry.ensure(BlocClass, instanceKey);
|
|
849
|
-
}
|
|
850
|
-
|
|
851
393
|
//#endregion
|
|
852
394
|
//#region src/registry/release.ts
|
|
853
395
|
function release(BlocClass, instanceKey, forceDispose = false) {
|
|
854
396
|
globalRegistry.release(BlocClass, instanceKey, forceDispose);
|
|
855
397
|
}
|
|
856
|
-
|
|
857
|
-
//#endregion
|
|
858
|
-
//#region src/registry/queries.ts
|
|
859
|
-
function hasInstance(BlocClass, instanceKey) {
|
|
860
|
-
return globalRegistry.hasInstance(BlocClass, instanceKey);
|
|
861
|
-
}
|
|
862
|
-
function getRefCount(BlocClass, instanceKey) {
|
|
863
|
-
return globalRegistry.getRefCount(BlocClass, instanceKey);
|
|
864
|
-
}
|
|
865
|
-
function getAll(BlocClass) {
|
|
866
|
-
return globalRegistry.getAll(BlocClass);
|
|
867
|
-
}
|
|
868
|
-
function forEach(BlocClass, callback) {
|
|
869
|
-
globalRegistry.forEach(BlocClass, callback);
|
|
870
|
-
}
|
|
871
|
-
|
|
872
|
-
//#endregion
|
|
873
|
-
//#region src/registry/management.ts
|
|
874
|
-
function clear(BlocClass) {
|
|
875
|
-
globalRegistry.clear(BlocClass);
|
|
876
|
-
}
|
|
877
|
-
function clearAll() {
|
|
878
|
-
globalRegistry.clearAll();
|
|
879
|
-
}
|
|
880
|
-
function register(BlocClass, isolated = false) {
|
|
881
|
-
globalRegistry.register(BlocClass, isolated);
|
|
882
|
-
}
|
|
883
|
-
|
|
884
|
-
//#endregion
|
|
885
|
-
//#region src/registry/config.ts
|
|
886
|
-
let _registry = globalRegistry;
|
|
887
|
-
function getRegistry() {
|
|
888
|
-
return _registry;
|
|
889
|
-
}
|
|
890
|
-
function setRegistry(registry) {
|
|
891
|
-
_registry.clearAll();
|
|
892
|
-
_registry = registry;
|
|
893
|
-
}
|
|
894
|
-
function getStats() {
|
|
895
|
-
return _registry.getStats();
|
|
896
|
-
}
|
|
897
|
-
|
|
898
398
|
//#endregion
|
|
899
399
|
//#region src/decorators/blac.ts
|
|
900
400
|
/**
|
|
@@ -927,837 +427,7 @@ function blac(options) {
|
|
|
927
427
|
return target;
|
|
928
428
|
};
|
|
929
429
|
}
|
|
930
|
-
|
|
931
430
|
//#endregion
|
|
932
|
-
|
|
933
|
-
/**
|
|
934
|
-
* Path utilities for dependency tracking
|
|
935
|
-
*
|
|
936
|
-
* Provides utilities for parsing property paths and extracting values
|
|
937
|
-
* from nested objects using path strings.
|
|
938
|
-
*
|
|
939
|
-
* @internal
|
|
940
|
-
*/
|
|
941
|
-
/**
|
|
942
|
-
* Parse a property path string into an array of segments
|
|
943
|
-
*
|
|
944
|
-
* @internal
|
|
945
|
-
*
|
|
946
|
-
* Handles both dot notation (a.b.c) and bracket notation (a[0].b)
|
|
947
|
-
*
|
|
948
|
-
* @example
|
|
949
|
-
* ```ts
|
|
950
|
-
* parsePath('user.name') // ['user', 'name']
|
|
951
|
-
* parsePath('items[0].name') // ['items', '0', 'name']
|
|
952
|
-
* parsePath('data.users[2].address.city') // ['data', 'users', '2', 'address', 'city']
|
|
953
|
-
* ```
|
|
954
|
-
*/
|
|
955
|
-
function parsePath(path) {
|
|
956
|
-
const segments = [];
|
|
957
|
-
let current = "";
|
|
958
|
-
let i = 0;
|
|
959
|
-
while (i < path.length) {
|
|
960
|
-
const char = path[i];
|
|
961
|
-
if (char === ".") {
|
|
962
|
-
if (current) segments.push(current);
|
|
963
|
-
current = "";
|
|
964
|
-
} else if (char === "[") {
|
|
965
|
-
if (current) segments.push(current);
|
|
966
|
-
current = "";
|
|
967
|
-
i++;
|
|
968
|
-
while (i < path.length && path[i] !== "]") current += path[i++];
|
|
969
|
-
if (current) segments.push(current);
|
|
970
|
-
current = "";
|
|
971
|
-
} else current += char;
|
|
972
|
-
i++;
|
|
973
|
-
}
|
|
974
|
-
if (current) segments.push(current);
|
|
975
|
-
return segments;
|
|
976
|
-
}
|
|
977
|
-
/**
|
|
978
|
-
* Get a value from an object using a path of segments
|
|
979
|
-
*
|
|
980
|
-
* @example
|
|
981
|
-
* ```ts
|
|
982
|
-
* const obj = { user: { name: 'Alice', age: 30 } }
|
|
983
|
-
* getValueAtPath(obj, ['user', 'name']) // 'Alice'
|
|
984
|
-
* getValueAtPath(obj, ['user', 'age']) // 30
|
|
985
|
-
* getValueAtPath(obj, ['user', 'missing']) // undefined
|
|
986
|
-
* ```
|
|
987
|
-
*
|
|
988
|
-
* @internal
|
|
989
|
-
*/
|
|
990
|
-
function getValueAtPath(obj, segments) {
|
|
991
|
-
if (obj == null) return void 0;
|
|
992
|
-
let current = obj;
|
|
993
|
-
for (let i = 0; i < segments.length; i++) {
|
|
994
|
-
current = current[segments[i]];
|
|
995
|
-
if (current == null) return void 0;
|
|
996
|
-
}
|
|
997
|
-
return current;
|
|
998
|
-
}
|
|
999
|
-
/**
|
|
1000
|
-
* Shallow equality comparison for arrays
|
|
1001
|
-
*
|
|
1002
|
-
* Compares two arrays element-by-element using Object.is
|
|
1003
|
-
*
|
|
1004
|
-
* @example
|
|
1005
|
-
* ```ts
|
|
1006
|
-
* shallowEqual([1, 2, 3], [1, 2, 3]) // true
|
|
1007
|
-
* shallowEqual([1, 2, 3], [1, 2, 4]) // false
|
|
1008
|
-
* shallowEqual([1, 2], [1, 2, 3]) // false
|
|
1009
|
-
* ```
|
|
1010
|
-
*
|
|
1011
|
-
* @internal
|
|
1012
|
-
*/
|
|
1013
|
-
function shallowEqual(arr1, arr2) {
|
|
1014
|
-
if (arr1.length !== arr2.length) return false;
|
|
1015
|
-
for (let i = 0; i < arr1.length; i++) if (!Object.is(arr1[i], arr2[i])) return false;
|
|
1016
|
-
return true;
|
|
1017
|
-
}
|
|
431
|
+
export { Cubit, PluginManager, StateContainer, TrackedContext, acquire, blac, borrow, borrowSafe, clear, clearAll, createTrackedContext, ensure, forEach, generateIsolatedKey, getAll, getPluginManager, getRefCount, getRegistry, getStats, globalRegistry, hasInstance, instance, instanceId, isIsolatedClass, register, release, setRegistry, tracked, watch };
|
|
1018
432
|
|
|
1019
|
-
//#endregion
|
|
1020
|
-
//#region src/tracking/tracking-proxy.ts
|
|
1021
|
-
/**
|
|
1022
|
-
* Consolidated Tracking System
|
|
1023
|
-
*
|
|
1024
|
-
* This module provides all dependency and getter tracking functionality:
|
|
1025
|
-
* - Proxy creation for automatic property access tracking
|
|
1026
|
-
* - Dependency path tracking and change detection
|
|
1027
|
-
* - Getter execution tracking for computed properties
|
|
1028
|
-
* - Combined tracking proxy for watch/waitUntil use cases
|
|
1029
|
-
*/
|
|
1030
|
-
/**
|
|
1031
|
-
* Check if a value can be proxied
|
|
1032
|
-
* Returns true for plain objects and arrays only.
|
|
1033
|
-
* @internal
|
|
1034
|
-
*/
|
|
1035
|
-
function isProxyable(value) {
|
|
1036
|
-
if (typeof value !== "object" || value === null) return false;
|
|
1037
|
-
const proto = Object.getPrototypeOf(value);
|
|
1038
|
-
return proto === Object.prototype || proto === Array.prototype;
|
|
1039
|
-
}
|
|
1040
|
-
/**
|
|
1041
|
-
* Create a new proxy tracker state
|
|
1042
|
-
* @internal
|
|
1043
|
-
*/
|
|
1044
|
-
function createProxyState() {
|
|
1045
|
-
return {
|
|
1046
|
-
trackedPaths: /* @__PURE__ */ new Set(),
|
|
1047
|
-
isTracking: false,
|
|
1048
|
-
proxyCache: /* @__PURE__ */ new WeakMap(),
|
|
1049
|
-
boundFunctionsCache: null,
|
|
1050
|
-
lastProxiedState: null,
|
|
1051
|
-
lastProxy: null,
|
|
1052
|
-
maxDepth: 10
|
|
1053
|
-
};
|
|
1054
|
-
}
|
|
1055
|
-
/**
|
|
1056
|
-
* Start tracking property accesses
|
|
1057
|
-
* @internal
|
|
1058
|
-
*/
|
|
1059
|
-
function startProxy(state) {
|
|
1060
|
-
state.isTracking = true;
|
|
1061
|
-
state.trackedPaths.clear();
|
|
1062
|
-
}
|
|
1063
|
-
/**
|
|
1064
|
-
* Stop tracking and return the tracked paths
|
|
1065
|
-
* @internal
|
|
1066
|
-
*/
|
|
1067
|
-
function stopProxy(state) {
|
|
1068
|
-
state.isTracking = false;
|
|
1069
|
-
return new Set(state.trackedPaths);
|
|
1070
|
-
}
|
|
1071
|
-
/**
|
|
1072
|
-
* Create a proxy for an array with property access tracking
|
|
1073
|
-
* @internal
|
|
1074
|
-
*/
|
|
1075
|
-
function createArrayProxy(state, target, path, depth = 0) {
|
|
1076
|
-
const proxy = new Proxy(target, { get: (arr, prop) => {
|
|
1077
|
-
if (typeof prop === "symbol") return Reflect.get(arr, prop);
|
|
1078
|
-
const value = Reflect.get(arr, prop);
|
|
1079
|
-
if (typeof value === "function") {
|
|
1080
|
-
if (!state.boundFunctionsCache) state.boundFunctionsCache = /* @__PURE__ */ new WeakMap();
|
|
1081
|
-
const cached = state.boundFunctionsCache.get(value);
|
|
1082
|
-
if (cached) return cached;
|
|
1083
|
-
const bound = value.bind(arr);
|
|
1084
|
-
state.boundFunctionsCache.set(value, bound);
|
|
1085
|
-
return bound;
|
|
1086
|
-
}
|
|
1087
|
-
if (prop === "length") {
|
|
1088
|
-
if (state.isTracking) {
|
|
1089
|
-
const fullPath = path ? `${path}.length` : "length";
|
|
1090
|
-
state.trackedPaths.add(fullPath);
|
|
1091
|
-
}
|
|
1092
|
-
return value;
|
|
1093
|
-
}
|
|
1094
|
-
let fullPath;
|
|
1095
|
-
if (typeof prop === "string") {
|
|
1096
|
-
const index = Number(prop);
|
|
1097
|
-
if (!isNaN(index) && index >= 0) fullPath = path ? `${path}[${index}]` : `[${index}]`;
|
|
1098
|
-
else fullPath = path ? `${path}.${prop}` : prop;
|
|
1099
|
-
} else return value;
|
|
1100
|
-
if (isProxyable(value)) return createInternal(state, value, fullPath, depth + 1);
|
|
1101
|
-
if (state.isTracking) state.trackedPaths.add(fullPath);
|
|
1102
|
-
return value;
|
|
1103
|
-
} });
|
|
1104
|
-
state.proxyCache.set(target, proxy);
|
|
1105
|
-
return proxy;
|
|
1106
|
-
}
|
|
1107
|
-
/**
|
|
1108
|
-
* Create a proxy for an object with property access tracking
|
|
1109
|
-
* @internal
|
|
1110
|
-
*/
|
|
1111
|
-
function createInternal(state, target, path = "", depth = 0) {
|
|
1112
|
-
if (!state.isTracking || !isProxyable(target)) return target;
|
|
1113
|
-
if (depth >= state.maxDepth) return target;
|
|
1114
|
-
if (state.proxyCache.has(target)) return state.proxyCache.get(target);
|
|
1115
|
-
if (Array.isArray(target)) return createArrayProxy(state, target, path, depth);
|
|
1116
|
-
const proxy = new Proxy(target, {
|
|
1117
|
-
get: (obj, prop) => {
|
|
1118
|
-
if (typeof prop === "symbol") return Reflect.get(obj, prop);
|
|
1119
|
-
const value = Reflect.get(obj, prop);
|
|
1120
|
-
if (typeof value === "function") {
|
|
1121
|
-
if (!state.boundFunctionsCache) state.boundFunctionsCache = /* @__PURE__ */ new WeakMap();
|
|
1122
|
-
const cached = state.boundFunctionsCache.get(value);
|
|
1123
|
-
if (cached) return cached;
|
|
1124
|
-
const bound = value.bind(obj);
|
|
1125
|
-
state.boundFunctionsCache.set(value, bound);
|
|
1126
|
-
return bound;
|
|
1127
|
-
}
|
|
1128
|
-
const fullPath = path ? `${path}.${String(prop)}` : String(prop);
|
|
1129
|
-
if (typeof prop === "string" && state.isTracking) {
|
|
1130
|
-
if (!prop.startsWith("_") && !prop.startsWith("$$")) state.trackedPaths.add(fullPath);
|
|
1131
|
-
}
|
|
1132
|
-
if (isProxyable(value)) return createInternal(state, value, fullPath, depth + 1);
|
|
1133
|
-
return value;
|
|
1134
|
-
},
|
|
1135
|
-
has: (obj, prop) => {
|
|
1136
|
-
if (typeof prop === "string" && state.isTracking) {
|
|
1137
|
-
const fullPath = path ? `${path}.${prop}` : prop;
|
|
1138
|
-
state.trackedPaths.add(fullPath);
|
|
1139
|
-
}
|
|
1140
|
-
return Reflect.has(obj, prop);
|
|
1141
|
-
},
|
|
1142
|
-
ownKeys: (obj) => {
|
|
1143
|
-
if (state.isTracking && path) state.trackedPaths.add(path);
|
|
1144
|
-
return Reflect.ownKeys(obj);
|
|
1145
|
-
}
|
|
1146
|
-
});
|
|
1147
|
-
state.proxyCache.set(target, proxy);
|
|
1148
|
-
return proxy;
|
|
1149
|
-
}
|
|
1150
|
-
/**
|
|
1151
|
-
* Create a proxy for a target with caching
|
|
1152
|
-
* @internal
|
|
1153
|
-
*/
|
|
1154
|
-
function createForTarget(state, target) {
|
|
1155
|
-
if (state.lastProxiedState === target && state.lastProxy) return state.lastProxy;
|
|
1156
|
-
state.proxyCache = /* @__PURE__ */ new WeakMap();
|
|
1157
|
-
state.boundFunctionsCache = null;
|
|
1158
|
-
const proxy = createInternal(state, target, "", 0);
|
|
1159
|
-
state.lastProxiedState = target;
|
|
1160
|
-
state.lastProxy = proxy;
|
|
1161
|
-
return proxy;
|
|
1162
|
-
}
|
|
1163
|
-
function isChildPath(child, parent) {
|
|
1164
|
-
if (child === parent) return false;
|
|
1165
|
-
return child.startsWith(parent + ".") || child.startsWith(parent + "[");
|
|
1166
|
-
}
|
|
1167
|
-
function getArrayParentPath(path) {
|
|
1168
|
-
if (path.endsWith(".length")) return path.slice(0, -7);
|
|
1169
|
-
const arrayIndexMatch = path.match(/^(.+?)\[\d+\]/);
|
|
1170
|
-
if (arrayIndexMatch) return arrayIndexMatch[1];
|
|
1171
|
-
return null;
|
|
1172
|
-
}
|
|
1173
|
-
/**
|
|
1174
|
-
* @internal
|
|
1175
|
-
*/
|
|
1176
|
-
function optimizeTrackedPaths(paths) {
|
|
1177
|
-
if (paths.size === 0) return /* @__PURE__ */ new Set();
|
|
1178
|
-
if (paths.size === 1) return new Set(paths);
|
|
1179
|
-
const sortedPaths = Array.from(paths).sort((a, b) => b.length - a.length);
|
|
1180
|
-
const optimized = /* @__PURE__ */ new Set();
|
|
1181
|
-
for (const path of sortedPaths) {
|
|
1182
|
-
let hasMoreSpecificChild = false;
|
|
1183
|
-
for (const optimizedPath of optimized) if (isChildPath(optimizedPath, path)) {
|
|
1184
|
-
hasMoreSpecificChild = true;
|
|
1185
|
-
break;
|
|
1186
|
-
}
|
|
1187
|
-
if (!hasMoreSpecificChild) optimized.add(path);
|
|
1188
|
-
}
|
|
1189
|
-
const arrayParents = /* @__PURE__ */ new Set();
|
|
1190
|
-
for (const path of optimized) {
|
|
1191
|
-
const arrayParent = getArrayParentPath(path);
|
|
1192
|
-
if (arrayParent) arrayParents.add(arrayParent);
|
|
1193
|
-
}
|
|
1194
|
-
for (const arrayParent of arrayParents) optimized.add(arrayParent);
|
|
1195
|
-
return optimized;
|
|
1196
|
-
}
|
|
1197
|
-
/**
|
|
1198
|
-
* @internal
|
|
1199
|
-
*/
|
|
1200
|
-
function createDependencyState() {
|
|
1201
|
-
return {
|
|
1202
|
-
proxyState: createProxyState(),
|
|
1203
|
-
previousRenderPaths: /* @__PURE__ */ new Set(),
|
|
1204
|
-
currentRenderPaths: /* @__PURE__ */ new Set(),
|
|
1205
|
-
pathCache: /* @__PURE__ */ new Map(),
|
|
1206
|
-
lastCheckedState: null,
|
|
1207
|
-
lastCheckedValues: /* @__PURE__ */ new Map()
|
|
1208
|
-
};
|
|
1209
|
-
}
|
|
1210
|
-
/**
|
|
1211
|
-
* @internal
|
|
1212
|
-
*/
|
|
1213
|
-
function startDependency(tracker) {
|
|
1214
|
-
startProxy(tracker.proxyState);
|
|
1215
|
-
}
|
|
1216
|
-
/**
|
|
1217
|
-
* @internal
|
|
1218
|
-
*/
|
|
1219
|
-
function createDependencyProxy(tracker, state) {
|
|
1220
|
-
return createForTarget(tracker.proxyState, state);
|
|
1221
|
-
}
|
|
1222
|
-
/**
|
|
1223
|
-
* @internal
|
|
1224
|
-
*/
|
|
1225
|
-
function capturePaths(tracker, state) {
|
|
1226
|
-
tracker.previousRenderPaths = tracker.currentRenderPaths;
|
|
1227
|
-
tracker.currentRenderPaths = optimizeTrackedPaths(stopProxy(tracker.proxyState));
|
|
1228
|
-
if (tracker.previousRenderPaths.size === 0 && tracker.currentRenderPaths.size === 0) return;
|
|
1229
|
-
const trackedPathsUnion = new Set(tracker.previousRenderPaths);
|
|
1230
|
-
for (const path of tracker.currentRenderPaths) trackedPathsUnion.add(path);
|
|
1231
|
-
const canReuseCache = tracker.lastCheckedState === state;
|
|
1232
|
-
for (const path of trackedPathsUnion) if (!tracker.pathCache.has(path)) {
|
|
1233
|
-
const segments = parsePath(path);
|
|
1234
|
-
const value = canReuseCache && tracker.lastCheckedValues.has(path) ? tracker.lastCheckedValues.get(path) : getValueAtPath(state, segments);
|
|
1235
|
-
tracker.pathCache.set(path, {
|
|
1236
|
-
segments,
|
|
1237
|
-
value
|
|
1238
|
-
});
|
|
1239
|
-
} else {
|
|
1240
|
-
const info = tracker.pathCache.get(path);
|
|
1241
|
-
info.value = canReuseCache && tracker.lastCheckedValues.has(path) ? tracker.lastCheckedValues.get(path) : getValueAtPath(state, info.segments);
|
|
1242
|
-
}
|
|
1243
|
-
tracker.lastCheckedValues.clear();
|
|
1244
|
-
}
|
|
1245
|
-
/**
|
|
1246
|
-
* @internal
|
|
1247
|
-
*/
|
|
1248
|
-
function hasDependencyChanges(tracker, state) {
|
|
1249
|
-
if (tracker.pathCache.size === 0) return true;
|
|
1250
|
-
tracker.lastCheckedValues.clear();
|
|
1251
|
-
for (const [path, info] of tracker.pathCache.entries()) {
|
|
1252
|
-
const currentValue = getValueAtPath(state, info.segments);
|
|
1253
|
-
tracker.lastCheckedValues.set(path, currentValue);
|
|
1254
|
-
if (!Object.is(currentValue, info.value)) {
|
|
1255
|
-
tracker.lastCheckedState = state;
|
|
1256
|
-
return true;
|
|
1257
|
-
}
|
|
1258
|
-
}
|
|
1259
|
-
tracker.lastCheckedState = state;
|
|
1260
|
-
return false;
|
|
1261
|
-
}
|
|
1262
|
-
/**
|
|
1263
|
-
* @internal
|
|
1264
|
-
*/
|
|
1265
|
-
function hasTrackedData(tracker) {
|
|
1266
|
-
return tracker.proxyState.trackedPaths.size > 0 || tracker.pathCache.size > 0 || tracker.previousRenderPaths.size > 0;
|
|
1267
|
-
}
|
|
1268
|
-
const descriptorCache = /* @__PURE__ */ new WeakMap();
|
|
1269
|
-
const blocProxyCache = /* @__PURE__ */ new WeakMap();
|
|
1270
|
-
const activeTrackerMap = /* @__PURE__ */ new WeakMap();
|
|
1271
|
-
const MAX_GETTER_DEPTH = BLAC_DEFAULTS.MAX_GETTER_DEPTH;
|
|
1272
|
-
/**
|
|
1273
|
-
* @internal
|
|
1274
|
-
*/
|
|
1275
|
-
function getDescriptor(obj, prop) {
|
|
1276
|
-
const constructor = obj.constructor;
|
|
1277
|
-
let constructorCache = descriptorCache.get(constructor);
|
|
1278
|
-
if (constructorCache?.has(prop)) return constructorCache.get(prop);
|
|
1279
|
-
let current = obj;
|
|
1280
|
-
let descriptor;
|
|
1281
|
-
while (current && current !== Object.prototype) {
|
|
1282
|
-
descriptor = Object.getOwnPropertyDescriptor(current, prop);
|
|
1283
|
-
if (descriptor) break;
|
|
1284
|
-
current = Object.getPrototypeOf(current);
|
|
1285
|
-
}
|
|
1286
|
-
if (!constructorCache) {
|
|
1287
|
-
constructorCache = /* @__PURE__ */ new Map();
|
|
1288
|
-
descriptorCache.set(constructor, constructorCache);
|
|
1289
|
-
}
|
|
1290
|
-
constructorCache.set(prop, descriptor);
|
|
1291
|
-
return descriptor;
|
|
1292
|
-
}
|
|
1293
|
-
/**
|
|
1294
|
-
* @internal
|
|
1295
|
-
*/
|
|
1296
|
-
function isGetter(obj, prop) {
|
|
1297
|
-
return getDescriptor(obj, prop)?.get !== void 0;
|
|
1298
|
-
}
|
|
1299
|
-
/**
|
|
1300
|
-
* @internal
|
|
1301
|
-
*/
|
|
1302
|
-
function createGetterState() {
|
|
1303
|
-
return {
|
|
1304
|
-
trackedValues: /* @__PURE__ */ new Map(),
|
|
1305
|
-
currentlyAccessing: /* @__PURE__ */ new Set(),
|
|
1306
|
-
trackedGetters: /* @__PURE__ */ new Set(),
|
|
1307
|
-
isTracking: false,
|
|
1308
|
-
renderCache: /* @__PURE__ */ new Map(),
|
|
1309
|
-
cacheValid: false,
|
|
1310
|
-
depth: 0,
|
|
1311
|
-
visitedBlocs: /* @__PURE__ */ new Set()
|
|
1312
|
-
};
|
|
1313
|
-
}
|
|
1314
|
-
/**
|
|
1315
|
-
* @internal
|
|
1316
|
-
*/
|
|
1317
|
-
function setActiveTracker(bloc, tracker) {
|
|
1318
|
-
activeTrackerMap.set(bloc, tracker);
|
|
1319
|
-
}
|
|
1320
|
-
/**
|
|
1321
|
-
* @internal
|
|
1322
|
-
*/
|
|
1323
|
-
function clearActiveTracker(bloc) {
|
|
1324
|
-
activeTrackerMap.delete(bloc);
|
|
1325
|
-
}
|
|
1326
|
-
/**
|
|
1327
|
-
* @internal
|
|
1328
|
-
*/
|
|
1329
|
-
function commitTrackedGetters(tracker) {
|
|
1330
|
-
if (tracker.currentlyAccessing.size > 0) tracker.trackedGetters = new Set(tracker.currentlyAccessing);
|
|
1331
|
-
tracker.currentlyAccessing.clear();
|
|
1332
|
-
}
|
|
1333
|
-
/**
|
|
1334
|
-
* Execute a tracked getter with depth/circular dependency checks and context management.
|
|
1335
|
-
* @internal
|
|
1336
|
-
*/
|
|
1337
|
-
function executeTrackedGetter(target, prop, tracker) {
|
|
1338
|
-
tracker.currentlyAccessing.add(prop);
|
|
1339
|
-
if (tracker.cacheValid && tracker.renderCache.has(prop)) {
|
|
1340
|
-
const cachedValue = tracker.renderCache.get(prop);
|
|
1341
|
-
tracker.trackedValues.set(prop, cachedValue);
|
|
1342
|
-
return cachedValue;
|
|
1343
|
-
}
|
|
1344
|
-
if (tracker.depth >= MAX_GETTER_DEPTH) {
|
|
1345
|
-
console.warn(`${BLAC_ERROR_PREFIX} Maximum getter depth (${MAX_GETTER_DEPTH}) exceeded. Possible circular dependency in getter "${String(prop)}" on ${target.constructor.name}.`);
|
|
1346
|
-
return;
|
|
1347
|
-
}
|
|
1348
|
-
if (tracker.visitedBlocs.has(target)) {
|
|
1349
|
-
console.warn(`${BLAC_ERROR_PREFIX} Circular dependency detected: getter "${String(prop)}" on ${target.constructor.name}.`);
|
|
1350
|
-
return;
|
|
1351
|
-
}
|
|
1352
|
-
const prevDepth = tracker.depth;
|
|
1353
|
-
const prevVisited = new Set(tracker.visitedBlocs);
|
|
1354
|
-
tracker.depth++;
|
|
1355
|
-
tracker.visitedBlocs.add(target);
|
|
1356
|
-
try {
|
|
1357
|
-
const value = getDescriptor(target, prop).get.call(target);
|
|
1358
|
-
tracker.trackedValues.set(prop, value);
|
|
1359
|
-
return value;
|
|
1360
|
-
} catch (error) {
|
|
1361
|
-
tracker.currentlyAccessing.delete(prop);
|
|
1362
|
-
throw error;
|
|
1363
|
-
} finally {
|
|
1364
|
-
tracker.depth = prevDepth;
|
|
1365
|
-
tracker.visitedBlocs = prevVisited;
|
|
1366
|
-
}
|
|
1367
|
-
}
|
|
1368
|
-
/**
|
|
1369
|
-
* @internal
|
|
1370
|
-
*/
|
|
1371
|
-
function createBlocProxy(bloc) {
|
|
1372
|
-
const cached = blocProxyCache.get(bloc);
|
|
1373
|
-
if (cached) return cached;
|
|
1374
|
-
const proxy = new Proxy(bloc, { get(target, prop, receiver) {
|
|
1375
|
-
const tracker = activeTrackerMap.get(target);
|
|
1376
|
-
if (tracker?.isTracking && isGetter(target, prop)) return executeTrackedGetter(target, prop, tracker);
|
|
1377
|
-
return Reflect.get(target, prop, receiver);
|
|
1378
|
-
} });
|
|
1379
|
-
blocProxyCache.set(bloc, proxy);
|
|
1380
|
-
return proxy;
|
|
1381
|
-
}
|
|
1382
|
-
/**
|
|
1383
|
-
* @internal
|
|
1384
|
-
*/
|
|
1385
|
-
function hasGetterChanges(bloc, tracker) {
|
|
1386
|
-
if (!tracker || tracker.trackedGetters.size === 0) return false;
|
|
1387
|
-
tracker.renderCache.clear();
|
|
1388
|
-
let hasAnyChange = false;
|
|
1389
|
-
for (const prop of tracker.trackedGetters) try {
|
|
1390
|
-
const descriptor = getDescriptor(bloc, prop);
|
|
1391
|
-
if (!descriptor?.get) continue;
|
|
1392
|
-
const newValue = descriptor.get.call(bloc);
|
|
1393
|
-
const oldValue = tracker.trackedValues.get(prop);
|
|
1394
|
-
tracker.renderCache.set(prop, newValue);
|
|
1395
|
-
tracker.trackedValues.set(prop, newValue);
|
|
1396
|
-
if (!Object.is(newValue, oldValue)) hasAnyChange = true;
|
|
1397
|
-
} catch (error) {
|
|
1398
|
-
console.warn(`${BLAC_ERROR_PREFIX} Getter "${String(prop)}" threw error during change detection. Stopping tracking for this getter.`, error);
|
|
1399
|
-
tracker.trackedGetters.delete(prop);
|
|
1400
|
-
tracker.trackedValues.delete(prop);
|
|
1401
|
-
tracker.cacheValid = false;
|
|
1402
|
-
return true;
|
|
1403
|
-
}
|
|
1404
|
-
tracker.cacheValid = true;
|
|
1405
|
-
return hasAnyChange;
|
|
1406
|
-
}
|
|
1407
|
-
/**
|
|
1408
|
-
* @internal
|
|
1409
|
-
*/
|
|
1410
|
-
function invalidateRenderCache(tracker) {
|
|
1411
|
-
tracker.cacheValid = false;
|
|
1412
|
-
}
|
|
1413
|
-
/**
|
|
1414
|
-
* Create a new tracking proxy state.
|
|
1415
|
-
*/
|
|
1416
|
-
function createState() {
|
|
1417
|
-
return {
|
|
1418
|
-
dependencyState: createDependencyState(),
|
|
1419
|
-
getterState: createGetterState(),
|
|
1420
|
-
dependencies: /* @__PURE__ */ new Set(),
|
|
1421
|
-
isTracking: false
|
|
1422
|
-
};
|
|
1423
|
-
}
|
|
1424
|
-
/**
|
|
1425
|
-
* Start tracking on a tracking proxy.
|
|
1426
|
-
*/
|
|
1427
|
-
function startTracking(tracker) {
|
|
1428
|
-
tracker.isTracking = true;
|
|
1429
|
-
tracker.dependencies.clear();
|
|
1430
|
-
tracker.getterState.isTracking = true;
|
|
1431
|
-
startDependency(tracker.dependencyState);
|
|
1432
|
-
}
|
|
1433
|
-
/**
|
|
1434
|
-
* Stop tracking and collect all dependencies.
|
|
1435
|
-
*/
|
|
1436
|
-
function stopTracking(tracker, bloc) {
|
|
1437
|
-
tracker.isTracking = false;
|
|
1438
|
-
tracker.getterState.isTracking = false;
|
|
1439
|
-
capturePaths(tracker.dependencyState, bloc.state);
|
|
1440
|
-
commitTrackedGetters(tracker.getterState);
|
|
1441
|
-
return new Set(tracker.dependencies);
|
|
1442
|
-
}
|
|
1443
|
-
/**
|
|
1444
|
-
* Check if tracked state or getters have changed.
|
|
1445
|
-
*/
|
|
1446
|
-
function hasChanges(tracker, bloc) {
|
|
1447
|
-
invalidateRenderCache(tracker.getterState);
|
|
1448
|
-
const stateChanged = hasDependencyChanges(tracker.dependencyState, bloc.state);
|
|
1449
|
-
const getterChanged = hasGetterChanges(bloc, tracker.getterState);
|
|
1450
|
-
return stateChanged || getterChanged;
|
|
1451
|
-
}
|
|
1452
|
-
/**
|
|
1453
|
-
* Create a tracking proxy for a bloc instance.
|
|
1454
|
-
* Tracks both state property access and getter access.
|
|
1455
|
-
*/
|
|
1456
|
-
function createTrackingProxy(bloc, tracker) {
|
|
1457
|
-
tracker.dependencies.add(bloc);
|
|
1458
|
-
const stateProxyCache = /* @__PURE__ */ new WeakMap();
|
|
1459
|
-
return new Proxy(bloc, { get(target, prop, receiver) {
|
|
1460
|
-
if (prop === "state") {
|
|
1461
|
-
if (!tracker.isTracking) return target.state;
|
|
1462
|
-
const rawState = target.state;
|
|
1463
|
-
if (rawState === null || typeof rawState !== "object") return rawState;
|
|
1464
|
-
if (stateProxyCache.has(rawState)) return stateProxyCache.get(rawState);
|
|
1465
|
-
const stateProxy = createDependencyProxy(tracker.dependencyState, rawState);
|
|
1466
|
-
stateProxyCache.set(rawState, stateProxy);
|
|
1467
|
-
return stateProxy;
|
|
1468
|
-
}
|
|
1469
|
-
if (typeof prop === "symbol") return Reflect.get(target, prop, receiver);
|
|
1470
|
-
const value = Reflect.get(target, prop, receiver);
|
|
1471
|
-
if (typeof value === "function") return value.bind(target);
|
|
1472
|
-
if (tracker.isTracking && isGetter(target, prop)) return executeTrackedGetter(target, prop, tracker.getterState);
|
|
1473
|
-
return value;
|
|
1474
|
-
} });
|
|
1475
|
-
}
|
|
1476
|
-
|
|
1477
|
-
//#endregion
|
|
1478
|
-
//#region src/tracking/dependency-manager.ts
|
|
1479
|
-
/**
|
|
1480
|
-
* Manages subscriptions to state container dependencies.
|
|
1481
|
-
* Provides efficient sync mechanism to add/remove subscriptions
|
|
1482
|
-
* as dependencies change between callback invocations.
|
|
1483
|
-
*/
|
|
1484
|
-
var DependencyManager = class {
|
|
1485
|
-
constructor() {
|
|
1486
|
-
this.subscriptions = /* @__PURE__ */ new Map();
|
|
1487
|
-
this.currentDeps = /* @__PURE__ */ new Set();
|
|
1488
|
-
}
|
|
1489
|
-
/**
|
|
1490
|
-
* Sync subscriptions with a new set of dependencies.
|
|
1491
|
-
* Adds subscriptions for new deps, removes subscriptions for stale deps.
|
|
1492
|
-
*
|
|
1493
|
-
* @param newDeps - The new set of dependencies to subscribe to
|
|
1494
|
-
* @param onChange - Callback to invoke when any dependency changes
|
|
1495
|
-
* @param exclude - Optional instance to exclude from subscriptions (e.g., primary bloc)
|
|
1496
|
-
* @returns true if the dependency set changed, false if unchanged
|
|
1497
|
-
*/
|
|
1498
|
-
sync(newDeps, onChange, exclude) {
|
|
1499
|
-
const filteredNewDeps = /* @__PURE__ */ new Set();
|
|
1500
|
-
for (const dep of newDeps) if (dep !== exclude && !dep.isDisposed) filteredNewDeps.add(dep);
|
|
1501
|
-
if (this.areSetsEqual(this.currentDeps, filteredNewDeps)) return false;
|
|
1502
|
-
for (const dep of this.currentDeps) if (!filteredNewDeps.has(dep)) {
|
|
1503
|
-
const unsub = this.subscriptions.get(dep);
|
|
1504
|
-
if (unsub) {
|
|
1505
|
-
unsub();
|
|
1506
|
-
this.subscriptions.delete(dep);
|
|
1507
|
-
}
|
|
1508
|
-
}
|
|
1509
|
-
for (const dep of filteredNewDeps) if (!this.currentDeps.has(dep) && !this.subscriptions.has(dep)) {
|
|
1510
|
-
const unsub = dep.subscribe(onChange);
|
|
1511
|
-
this.subscriptions.set(dep, unsub);
|
|
1512
|
-
}
|
|
1513
|
-
this.currentDeps = filteredNewDeps;
|
|
1514
|
-
return true;
|
|
1515
|
-
}
|
|
1516
|
-
/**
|
|
1517
|
-
* Add a single dependency subscription.
|
|
1518
|
-
*/
|
|
1519
|
-
add(dep, onChange) {
|
|
1520
|
-
if (this.subscriptions.has(dep) || dep.isDisposed) return;
|
|
1521
|
-
const unsub = dep.subscribe(onChange);
|
|
1522
|
-
this.subscriptions.set(dep, unsub);
|
|
1523
|
-
this.currentDeps.add(dep);
|
|
1524
|
-
}
|
|
1525
|
-
/**
|
|
1526
|
-
* Check if a dependency is currently subscribed.
|
|
1527
|
-
*/
|
|
1528
|
-
has(dep) {
|
|
1529
|
-
return this.currentDeps.has(dep);
|
|
1530
|
-
}
|
|
1531
|
-
/**
|
|
1532
|
-
* Get the current set of dependencies.
|
|
1533
|
-
*/
|
|
1534
|
-
getDependencies() {
|
|
1535
|
-
return new Set(this.currentDeps);
|
|
1536
|
-
}
|
|
1537
|
-
/**
|
|
1538
|
-
* Clean up all active subscriptions.
|
|
1539
|
-
*/
|
|
1540
|
-
cleanup() {
|
|
1541
|
-
for (const unsub of this.subscriptions.values()) unsub();
|
|
1542
|
-
this.subscriptions.clear();
|
|
1543
|
-
this.currentDeps.clear();
|
|
1544
|
-
}
|
|
1545
|
-
areSetsEqual(a, b) {
|
|
1546
|
-
if (a.size !== b.size) return false;
|
|
1547
|
-
for (const item of a) if (!b.has(item)) return false;
|
|
1548
|
-
return true;
|
|
1549
|
-
}
|
|
1550
|
-
};
|
|
1551
|
-
|
|
1552
|
-
//#endregion
|
|
1553
|
-
//#region src/tracking/resolve-dependencies.ts
|
|
1554
|
-
/**
|
|
1555
|
-
* Resolve all transitive dependencies of a bloc via BFS over `dependencies` maps.
|
|
1556
|
-
* Uses cycle detection to avoid infinite loops.
|
|
1557
|
-
* @internal
|
|
1558
|
-
*/
|
|
1559
|
-
function resolveDependencies(bloc) {
|
|
1560
|
-
const result = /* @__PURE__ */ new Set();
|
|
1561
|
-
const visited = /* @__PURE__ */ new Set();
|
|
1562
|
-
const queue = [bloc];
|
|
1563
|
-
while (queue.length > 0) {
|
|
1564
|
-
const current = queue.shift();
|
|
1565
|
-
for (const [Type, key] of current.dependencies) {
|
|
1566
|
-
if (visited.has(Type)) continue;
|
|
1567
|
-
visited.add(Type);
|
|
1568
|
-
const dep = globalRegistry.ensure(Type, key);
|
|
1569
|
-
result.add(dep);
|
|
1570
|
-
if (dep.dependencies.size > 0) queue.push(dep);
|
|
1571
|
-
}
|
|
1572
|
-
}
|
|
1573
|
-
result.delete(bloc);
|
|
1574
|
-
return result;
|
|
1575
|
-
}
|
|
1576
|
-
|
|
1577
|
-
//#endregion
|
|
1578
|
-
//#region src/watch/watch.ts
|
|
1579
|
-
const STOP = Symbol("watch.STOP");
|
|
1580
|
-
const BLOC_REF_MARKER = Symbol("BlocRef");
|
|
1581
|
-
/**
|
|
1582
|
-
* Create a reference to a specific bloc instance.
|
|
1583
|
-
*
|
|
1584
|
-
* @example
|
|
1585
|
-
* ```ts
|
|
1586
|
-
* watch(instance(UserBloc, 'user-123'), (userBloc) => {
|
|
1587
|
-
* console.log(userBloc.state.name);
|
|
1588
|
-
* });
|
|
1589
|
-
* ```
|
|
1590
|
-
*/
|
|
1591
|
-
function instance(BlocClass, instanceId) {
|
|
1592
|
-
return {
|
|
1593
|
-
[BLOC_REF_MARKER]: true,
|
|
1594
|
-
blocClass: BlocClass,
|
|
1595
|
-
instanceId
|
|
1596
|
-
};
|
|
1597
|
-
}
|
|
1598
|
-
function isBlocRef(input) {
|
|
1599
|
-
return typeof input === "object" && input !== null && BLOC_REF_MARKER in input;
|
|
1600
|
-
}
|
|
1601
|
-
function resolveBloc(input) {
|
|
1602
|
-
if (isBlocRef(input)) return ensure(input.blocClass, input.instanceId);
|
|
1603
|
-
return ensure(input, BLAC_DEFAULTS.DEFAULT_INSTANCE_KEY);
|
|
1604
|
-
}
|
|
1605
|
-
function isArray(input) {
|
|
1606
|
-
return Array.isArray(input);
|
|
1607
|
-
}
|
|
1608
|
-
function watchImpl(blocsOrBloc, callback) {
|
|
1609
|
-
const isSingle = !isArray(blocsOrBloc);
|
|
1610
|
-
const instances = (isSingle ? [blocsOrBloc] : blocsOrBloc).map(resolveBloc);
|
|
1611
|
-
const tracker = createState();
|
|
1612
|
-
const proxiedInstances = instances.map((inst) => createTrackingProxy(inst, tracker));
|
|
1613
|
-
const externalDepsManager = new DependencyManager();
|
|
1614
|
-
let disposed = false;
|
|
1615
|
-
const primarySubscriptions = [];
|
|
1616
|
-
const cleanup = () => {
|
|
1617
|
-
if (disposed) return;
|
|
1618
|
-
disposed = true;
|
|
1619
|
-
primarySubscriptions.forEach((unsub) => unsub());
|
|
1620
|
-
externalDepsManager.cleanup();
|
|
1621
|
-
};
|
|
1622
|
-
const runCallback = () => {
|
|
1623
|
-
if (disposed) return;
|
|
1624
|
-
startTracking(tracker);
|
|
1625
|
-
let result;
|
|
1626
|
-
try {
|
|
1627
|
-
result = callback(isSingle ? proxiedInstances[0] : proxiedInstances);
|
|
1628
|
-
} finally {
|
|
1629
|
-
const externalDeps = /* @__PURE__ */ new Set();
|
|
1630
|
-
for (const inst of instances) {
|
|
1631
|
-
const deps = stopTracking(tracker, inst);
|
|
1632
|
-
for (const dep of deps) externalDeps.add(dep);
|
|
1633
|
-
for (const dep of resolveDependencies(inst)) externalDeps.add(dep);
|
|
1634
|
-
}
|
|
1635
|
-
for (const inst of instances) externalDeps.delete(inst);
|
|
1636
|
-
externalDepsManager.sync(externalDeps, runCallback);
|
|
1637
|
-
}
|
|
1638
|
-
if (result === STOP) cleanup();
|
|
1639
|
-
};
|
|
1640
|
-
const onChange = () => {
|
|
1641
|
-
if (disposed) return;
|
|
1642
|
-
runCallback();
|
|
1643
|
-
};
|
|
1644
|
-
for (const inst of instances) primarySubscriptions.push(inst.subscribe(onChange));
|
|
1645
|
-
runCallback();
|
|
1646
|
-
return cleanup;
|
|
1647
|
-
}
|
|
1648
|
-
const watch = Object.assign(watchImpl, { STOP });
|
|
1649
|
-
|
|
1650
|
-
//#endregion
|
|
1651
|
-
//#region src/tracking/tracked.ts
|
|
1652
|
-
/**
|
|
1653
|
-
* Run a callback while tracking all bloc dependencies accessed.
|
|
1654
|
-
* Returns both the result and the set of discovered dependencies.
|
|
1655
|
-
*
|
|
1656
|
-
* @example
|
|
1657
|
-
* ```ts
|
|
1658
|
-
* const { result, dependencies } = tracked(() => {
|
|
1659
|
-
* const user = ensure(UserBloc);
|
|
1660
|
-
* return user.fullName; // getter that may access other blocs
|
|
1661
|
-
* });
|
|
1662
|
-
* // dependencies contains UserBloc + any blocs accessed in fullName getter
|
|
1663
|
-
* ```
|
|
1664
|
-
*/
|
|
1665
|
-
function tracked(callback, options) {
|
|
1666
|
-
const tracker = createState();
|
|
1667
|
-
startTracking(tracker);
|
|
1668
|
-
let result;
|
|
1669
|
-
try {
|
|
1670
|
-
result = callback();
|
|
1671
|
-
} finally {
|
|
1672
|
-
stopTracking(tracker, { state: null });
|
|
1673
|
-
}
|
|
1674
|
-
const dependencies = new Set(tracker.dependencies);
|
|
1675
|
-
if (options?.exclude) dependencies.delete(options.exclude);
|
|
1676
|
-
return {
|
|
1677
|
-
result,
|
|
1678
|
-
dependencies
|
|
1679
|
-
};
|
|
1680
|
-
}
|
|
1681
|
-
/**
|
|
1682
|
-
* Context for running tracked callbacks with bloc proxies.
|
|
1683
|
-
* Provides methods to create proxies and check for changes.
|
|
1684
|
-
*/
|
|
1685
|
-
var TrackedContext = class {
|
|
1686
|
-
constructor() {
|
|
1687
|
-
this.proxiedBlocs = /* @__PURE__ */ new WeakMap();
|
|
1688
|
-
this.primaryBlocs = /* @__PURE__ */ new Set();
|
|
1689
|
-
this.tracker = createState();
|
|
1690
|
-
}
|
|
1691
|
-
/**
|
|
1692
|
-
* Get a tracking proxy for a bloc instance.
|
|
1693
|
-
* The proxy will track state and getter accesses.
|
|
1694
|
-
*/
|
|
1695
|
-
proxy(bloc) {
|
|
1696
|
-
const cached = this.proxiedBlocs.get(bloc);
|
|
1697
|
-
if (cached) return cached;
|
|
1698
|
-
const proxied = createTrackingProxy(bloc, this.tracker);
|
|
1699
|
-
this.proxiedBlocs.set(bloc, proxied);
|
|
1700
|
-
this.primaryBlocs.add(bloc);
|
|
1701
|
-
return proxied;
|
|
1702
|
-
}
|
|
1703
|
-
/**
|
|
1704
|
-
* Start tracking for a new callback execution.
|
|
1705
|
-
*/
|
|
1706
|
-
start() {
|
|
1707
|
-
startTracking(this.tracker);
|
|
1708
|
-
}
|
|
1709
|
-
/**
|
|
1710
|
-
* Stop tracking and get discovered dependencies.
|
|
1711
|
-
* Excludes primary blocs (those explicitly proxied via proxy()).
|
|
1712
|
-
*/
|
|
1713
|
-
stop() {
|
|
1714
|
-
const allDeps = /* @__PURE__ */ new Set();
|
|
1715
|
-
for (const bloc of this.primaryBlocs) {
|
|
1716
|
-
const deps = stopTracking(this.tracker, bloc);
|
|
1717
|
-
for (const dep of deps) if (!this.primaryBlocs.has(dep)) allDeps.add(dep);
|
|
1718
|
-
}
|
|
1719
|
-
return allDeps;
|
|
1720
|
-
}
|
|
1721
|
-
/**
|
|
1722
|
-
* Check if any tracked state or getters have changed.
|
|
1723
|
-
*/
|
|
1724
|
-
changed() {
|
|
1725
|
-
for (const bloc of this.primaryBlocs) if (hasChanges(this.tracker, bloc)) return true;
|
|
1726
|
-
return false;
|
|
1727
|
-
}
|
|
1728
|
-
/**
|
|
1729
|
-
* Get all primary blocs (those explicitly proxied).
|
|
1730
|
-
*/
|
|
1731
|
-
getPrimaryBlocs() {
|
|
1732
|
-
return new Set(this.primaryBlocs);
|
|
1733
|
-
}
|
|
1734
|
-
/**
|
|
1735
|
-
* Reset the context for reuse.
|
|
1736
|
-
*/
|
|
1737
|
-
reset() {
|
|
1738
|
-
this.tracker = createState();
|
|
1739
|
-
this.proxiedBlocs = /* @__PURE__ */ new WeakMap();
|
|
1740
|
-
this.primaryBlocs.clear();
|
|
1741
|
-
}
|
|
1742
|
-
};
|
|
1743
|
-
/**
|
|
1744
|
-
* Create a new tracked context for manual control over tracking.
|
|
1745
|
-
*/
|
|
1746
|
-
function createTrackedContext() {
|
|
1747
|
-
return new TrackedContext();
|
|
1748
|
-
}
|
|
1749
|
-
|
|
1750
|
-
//#endregion
|
|
1751
|
-
//#region src/types/branded.ts
|
|
1752
|
-
/**
|
|
1753
|
-
* Create a branded InstanceId from a string
|
|
1754
|
-
* @param id - The string ID to brand
|
|
1755
|
-
* @returns Branded InstanceId
|
|
1756
|
-
*/
|
|
1757
|
-
function instanceId(id) {
|
|
1758
|
-
return id;
|
|
1759
|
-
}
|
|
1760
|
-
|
|
1761
|
-
//#endregion
|
|
1762
|
-
export { Cubit, DependencyManager, PluginManager, StateContainer, TrackedContext, acquire, blac, borrow, borrowSafe, capturePaths, clear, clearActiveTracker, clearAll, commitTrackedGetters, createBlocProxy, createDependencyProxy, createDependencyState, createGetterState, createTrackedContext, ensure, forEach, generateIsolatedKey, getAll, getPluginManager, getRefCount, getRegistry, getStats, globalRegistry, hasDependencyChanges, hasGetterChanges, hasInstance, hasTrackedData, instance, instanceId, invalidateRenderCache, isIsolatedClass, register, release, resolveDependencies, setActiveTracker, setRegistry, shallowEqual, startDependency, tracked, watch };
|
|
1763
433
|
//# sourceMappingURL=index.js.map
|