@blac/core 2.0.0-rc.6 → 2.0.0-rc.8

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.
Files changed (60) hide show
  1. package/dist/adapter/framework-adapter.d.ts +114 -0
  2. package/dist/adapter/framework-adapter.d.ts.map +1 -0
  3. package/dist/adapter/index.d.ts +8 -0
  4. package/dist/adapter/index.d.ts.map +1 -0
  5. package/dist/constants.d.ts +82 -0
  6. package/dist/constants.d.ts.map +1 -0
  7. package/dist/core/Cubit.d.ts +82 -0
  8. package/dist/core/Cubit.d.ts.map +1 -0
  9. package/dist/core/StateContainer.d.ts +200 -0
  10. package/dist/core/StateContainer.d.ts.map +1 -0
  11. package/dist/core/StateContainerRegistry.d.ts +176 -0
  12. package/dist/core/StateContainerRegistry.d.ts.map +1 -0
  13. package/dist/core/Vertex.d.ts +113 -0
  14. package/dist/core/Vertex.d.ts.map +1 -0
  15. package/dist/index.cjs +1158 -326
  16. package/dist/index.cjs.map +1 -1
  17. package/dist/index.d.cts +20 -885
  18. package/dist/index.d.ts +19 -884
  19. package/dist/index.d.ts.map +1 -0
  20. package/dist/index.js +1122 -327
  21. package/dist/index.js.map +1 -1
  22. package/dist/logging/Logger.d.ts +57 -0
  23. package/dist/logging/Logger.d.ts.map +1 -0
  24. package/dist/plugin/BlacPlugin.d.ts +115 -0
  25. package/dist/plugin/BlacPlugin.d.ts.map +1 -0
  26. package/dist/plugin/PluginManager.d.ts +63 -0
  27. package/dist/plugin/PluginManager.d.ts.map +1 -0
  28. package/dist/proxy/ProxyTracker.d.ts +71 -0
  29. package/dist/proxy/ProxyTracker.d.ts.map +1 -0
  30. package/dist/registry/index.d.ts +20 -0
  31. package/dist/registry/index.d.ts.map +1 -0
  32. package/dist/test-utils/index.d.ts +43 -0
  33. package/dist/test-utils/index.d.ts.map +1 -0
  34. package/dist/tracking/dependency-tracker.d.ts +21 -0
  35. package/dist/tracking/dependency-tracker.d.ts.map +1 -0
  36. package/dist/tracking/getter-tracker.d.ts +31 -0
  37. package/dist/tracking/getter-tracker.d.ts.map +1 -0
  38. package/dist/tracking/index.d.ts +38 -0
  39. package/dist/tracking/index.d.ts.map +1 -0
  40. package/dist/tracking/path-utils.d.ts +39 -0
  41. package/dist/tracking/path-utils.d.ts.map +1 -0
  42. package/dist/tracking/proxy-tracker.d.ts +88 -0
  43. package/dist/tracking/proxy-tracker.d.ts.map +1 -0
  44. package/dist/types/branded.d.ts +9 -0
  45. package/dist/types/branded.d.ts.map +1 -0
  46. package/dist/types/events.d.ts +13 -0
  47. package/dist/types/events.d.ts.map +1 -0
  48. package/dist/types/utilities.d.ts +18 -0
  49. package/dist/types/utilities.d.ts.map +1 -0
  50. package/dist/utils/equality.d.ts +9 -0
  51. package/dist/utils/equality.d.ts.map +1 -0
  52. package/dist/utils/idGenerator.d.ts +76 -0
  53. package/dist/utils/idGenerator.d.ts.map +1 -0
  54. package/dist/utils/immutable.d.ts +16 -0
  55. package/dist/utils/immutable.d.ts.map +1 -0
  56. package/dist/utils/stack-trace.d.ts +19 -0
  57. package/dist/utils/stack-trace.d.ts.map +1 -0
  58. package/dist/utils/static-props.d.ts +29 -0
  59. package/dist/utils/static-props.d.ts.map +1 -0
  60. package/package.json +17 -4
package/dist/index.cjs CHANGED
@@ -16,15 +16,15 @@ const globalCounters = /* @__PURE__ */ new Map();
16
16
  * @example
17
17
  * ```ts
18
18
  * const generator = createIdGenerator('sub');
19
- * const id1 = generator.next(); // "sub_1698765432100_1_a3k9d7f2q"
20
- * const id2 = generator.next(); // "sub_1698765432101_2_b4n8e9g3r"
19
+ * const id1 = generator.next(); // "sub:1698765432100_1_a3k9d7f2q"
20
+ * const id2 = generator.next(); // "sub:1698765432101_2_b4n8e9g3r"
21
21
  * ```
22
22
  */
23
23
  function createIdGenerator(prefix) {
24
24
  let counter = 0;
25
25
  return {
26
- next: () => `${prefix}_${Date.now()}_${++counter}_${Math.random().toString(36).substring(2, 11)}`,
27
- nextSimple: () => `${prefix}_${++counter}`,
26
+ next: () => `${prefix}:${Date.now()}_${++counter}_${Math.random().toString(36).substring(2, 11)}`,
27
+ nextSimple: () => `${prefix}:${++counter}`,
28
28
  reset: () => {
29
29
  counter = 0;
30
30
  }
@@ -33,7 +33,7 @@ function createIdGenerator(prefix) {
33
33
  /**
34
34
  * Generate ID with timestamp, counter, and random suffix (tree-shakeable)
35
35
  *
36
- * Format: `${prefix}_${timestamp}_${counter}_${random}`
36
+ * Format: `${prefix}:${timestamp}_${counter}_${random}`
37
37
  *
38
38
  * @param prefix - Prefix for the ID (e.g., 'sub', 'consumer', 'stage')
39
39
  * @returns Branded ID string
@@ -41,18 +41,18 @@ function createIdGenerator(prefix) {
41
41
  * @example
42
42
  * ```ts
43
43
  * const id = generateId('sub');
44
- * // Returns: "sub_1698765432100_1_a3k9d7f2q"
44
+ * // Returns: "sub:1698765432100_1_a3k9d7f2q"
45
45
  * ```
46
46
  */
47
47
  function generateId(prefix) {
48
48
  const counter = (globalCounters.get(prefix) ?? 0) + 1;
49
49
  globalCounters.set(prefix, counter);
50
- return `${prefix}_${Date.now()}_${counter}_${Math.random().toString(36).substring(2, 11)}`;
50
+ return `${prefix}:${Date.now()}_${counter}_${Math.random().toString(36).substring(2, 11)}`;
51
51
  }
52
52
  /**
53
53
  * Generate simple ID with timestamp and random (no counter tracking)
54
54
  *
55
- * Format: `${prefix}_${timestamp}_${random}`
55
+ * Format: `${prefix}:${timestamp}_${random}`
56
56
  *
57
57
  * @param prefix - Prefix for the ID
58
58
  * @returns Branded ID string
@@ -60,11 +60,12 @@ function generateId(prefix) {
60
60
  * @example
61
61
  * ```ts
62
62
  * const id = generateSimpleId('CounterBloc');
63
- * // Returns: "CounterBloc_1698765432100_a3k9d7f2q"
63
+ * // Returns: "CounterBloc:1698765432100_a3k9d7f2q"
64
64
  * ```
65
65
  */
66
- function generateSimpleId(prefix) {
67
- return `${prefix}_${Date.now()}_${Math.random().toString(36).substring(2, 11)}`;
66
+ function generateSimpleId(prefix, affix) {
67
+ if (affix) return `${prefix}:${affix}`;
68
+ return `${prefix}:${Date.now()}_${Math.random().toString(36).substring(2, 11)}`;
68
69
  }
69
70
  /**
70
71
  * Reset all counters for testing purposes
@@ -73,129 +74,653 @@ function generateSimpleId(prefix) {
73
74
  function __resetIdCounters() {
74
75
  globalCounters.clear();
75
76
  }
77
+ /**
78
+ * Generate a unique isolated instance key
79
+ * Uses base36 encoding for compact, URL-safe identifiers
80
+ *
81
+ * Format: "isolated-{9-char-random-string}"
82
+ * Example: "isolated-k7x2m9p4q"
83
+ *
84
+ * @returns A unique isolated instance key
85
+ */
86
+ function generateIsolatedKey() {
87
+ return `isolated-${Math.random().toString(36).slice(2, 11)}`;
88
+ }
89
+ /**
90
+ * Check if a key is an isolated instance key
91
+ * @param key - The instance key to check
92
+ * @returns true if the key is an isolated instance key
93
+ */
94
+ function isIsolatedKey(key) {
95
+ return key.startsWith("isolated-");
96
+ }
97
+
98
+ //#endregion
99
+ //#region src/plugin/PluginManager.ts
100
+ /**
101
+ * Plugin manager for registering and coordinating plugins
102
+ */
103
+ var PluginManager = class {
104
+ constructor(registry) {
105
+ this.plugins = /* @__PURE__ */ new Map();
106
+ this.registry = registry;
107
+ this.setupLifecycleHooks();
108
+ }
109
+ /**
110
+ * Install a plugin
111
+ */
112
+ install(plugin, config = {}) {
113
+ const effectiveConfig = {
114
+ enabled: true,
115
+ environment: "all",
116
+ ...config
117
+ };
118
+ if (!this.shouldEnablePlugin(effectiveConfig)) {
119
+ console.log(`[BlaC] Plugin "${plugin.name}" skipped (environment mismatch)`);
120
+ return;
121
+ }
122
+ if (this.plugins.has(plugin.name)) throw new Error(`Plugin "${plugin.name}" is already installed`);
123
+ const context = this.createPluginContext();
124
+ this.plugins.set(plugin.name, {
125
+ plugin,
126
+ config: effectiveConfig,
127
+ context
128
+ });
129
+ if (plugin.onInstall) try {
130
+ plugin.onInstall(context);
131
+ } catch (error$1) {
132
+ console.error(`[BlaC] Error installing plugin "${plugin.name}":`, error$1);
133
+ this.plugins.delete(plugin.name);
134
+ throw error$1;
135
+ }
136
+ console.log(`[BlaC] Plugin "${plugin.name}" v${plugin.version} installed`);
137
+ }
138
+ /**
139
+ * Uninstall a plugin
140
+ */
141
+ uninstall(pluginName) {
142
+ const installed = this.plugins.get(pluginName);
143
+ if (!installed) throw new Error(`Plugin "${pluginName}" is not installed`);
144
+ if (installed.plugin.onUninstall) try {
145
+ installed.plugin.onUninstall();
146
+ } catch (error$1) {
147
+ console.error(`[BlaC] Error uninstalling plugin "${pluginName}":`, error$1);
148
+ }
149
+ this.plugins.delete(pluginName);
150
+ console.log(`[BlaC] Plugin "${pluginName}" uninstalled`);
151
+ }
152
+ /**
153
+ * Get installed plugin
154
+ */
155
+ getPlugin(pluginName) {
156
+ return this.plugins.get(pluginName)?.plugin;
157
+ }
158
+ /**
159
+ * Get all installed plugins
160
+ */
161
+ getAllPlugins() {
162
+ return Array.from(this.plugins.values()).map((p) => p.plugin);
163
+ }
164
+ /**
165
+ * Check if plugin is installed
166
+ */
167
+ hasPlugin(pluginName) {
168
+ return this.plugins.has(pluginName);
169
+ }
170
+ /**
171
+ * Clear all plugins (for testing)
172
+ */
173
+ clear() {
174
+ for (const name of this.plugins.keys()) this.uninstall(name);
175
+ }
176
+ /**
177
+ * Setup lifecycle hooks to notify plugins
178
+ */
179
+ setupLifecycleHooks() {
180
+ this.registry.on("created", (instance) => {
181
+ this.notifyPlugins("onInstanceCreated", instance);
182
+ });
183
+ this.registry.on("stateChanged", (instance, previousState, currentState, callstack) => {
184
+ this.notifyPlugins("onStateChanged", instance, previousState, currentState, callstack);
185
+ });
186
+ this.registry.on("eventAdded", (vertex, event) => {
187
+ this.notifyPlugins("onEventAdded", vertex, event);
188
+ });
189
+ this.registry.on("disposed", (instance) => {
190
+ this.notifyPlugins("onInstanceDisposed", instance);
191
+ });
192
+ }
193
+ /**
194
+ * Notify all plugins of a lifecycle event
195
+ */
196
+ notifyPlugins(hookName, ...args) {
197
+ for (const { plugin, config, context } of this.plugins.values()) {
198
+ if (!config.enabled) continue;
199
+ const hook = plugin[hookName];
200
+ if (typeof hook === "function") try {
201
+ hook.apply(plugin, [...args, context]);
202
+ } catch (error$1) {
203
+ console.error(`[BlaC] Error in plugin "${plugin.name}" ${hookName}:`, error$1);
204
+ }
205
+ }
206
+ }
207
+ /**
208
+ * Create plugin context with safe API access
209
+ */
210
+ createPluginContext() {
211
+ return {
212
+ getInstanceMetadata: (instance) => {
213
+ return {
214
+ id: instance.instanceId,
215
+ className: instance.constructor.name,
216
+ isDisposed: instance.isDisposed,
217
+ name: instance.name,
218
+ lastStateChangeTimestamp: instance.lastUpdateTimestamp,
219
+ createdAt: instance.createdAt,
220
+ state: instance.state,
221
+ isIsolated: instance.instanceId.startsWith("isolated-")
222
+ };
223
+ },
224
+ getState: (instance) => {
225
+ return instance.state;
226
+ },
227
+ queryInstances: (typeClass) => {
228
+ return typeClass.getAll();
229
+ },
230
+ getAllTypes: () => {
231
+ return this.registry.getTypes();
232
+ },
233
+ getStats: () => {
234
+ return this.registry.getStats();
235
+ }
236
+ };
237
+ }
238
+ /**
239
+ * Check if plugin should be enabled based on environment
240
+ */
241
+ shouldEnablePlugin(config) {
242
+ if (!config.enabled) return false;
243
+ if (config.environment === "all") return true;
244
+ return this.getCurrentEnvironment() === config.environment;
245
+ }
246
+ /**
247
+ * Get current environment
248
+ */
249
+ getCurrentEnvironment() {
250
+ if (typeof process !== "undefined") {
251
+ if (process.env.NODE_ENV === "test") return "test";
252
+ if (process.env.NODE_ENV === "production") return "production";
253
+ return "development";
254
+ }
255
+ return "development";
256
+ }
257
+ };
258
+ /**
259
+ * Create a plugin manager instance
260
+ */
261
+ function createPluginManager(registry) {
262
+ return new PluginManager(registry);
263
+ }
264
+
265
+ //#endregion
266
+ //#region src/constants.ts
267
+ /**
268
+ * Default configuration constants for BlaC
269
+ *
270
+ * Centralized location for all magic numbers and default values.
271
+ */
272
+ /**
273
+ * Default configuration constants for BlaC
274
+ */
275
+ const BLAC_DEFAULTS = {
276
+ DEFAULT_INSTANCE_KEY: "default",
277
+ MAX_GETTER_DEPTH: 10,
278
+ CLEANUP_INTERVAL_MS: 3e4,
279
+ WEAKREF_CLEANUP_INTERVAL_MS: 1e4,
280
+ MAX_SUBSCRIPTIONS: 1e3,
281
+ MAX_SUBSCRIPTIONS_HIGH_PERF: 1e4,
282
+ PIPELINE_TIMEOUT_MS: 5e3,
283
+ CLEANUP_INTERVAL_HIGH_PERF_MS: 5e3,
284
+ MAX_PIPELINE_STAGES: 30
285
+ };
286
+ /**
287
+ * Static property names for StateContainer classes
288
+ * Used for feature flags and configuration on bloc classes
289
+ */
290
+ const BLAC_STATIC_PROPS = {
291
+ ISOLATED: "isolated",
292
+ KEEP_ALIVE: "keepAlive",
293
+ EXCLUDE_FROM_DEVTOOLS: "__excludeFromDevTools"
294
+ };
295
+ /**
296
+ * ID generation patterns and constants
297
+ */
298
+ const BLAC_ID_PATTERNS = {
299
+ ISOLATED_PREFIX: "isolated-",
300
+ ID_LENGTH: 9
301
+ };
302
+ /**
303
+ * Standard error message prefix
304
+ */
305
+ const BLAC_ERROR_PREFIX = "[BlaC]";
306
+
307
+ //#endregion
308
+ //#region src/tracking/getter-tracker.ts
309
+ const descriptorCache = /* @__PURE__ */ new WeakMap();
310
+ const blocProxyCache = /* @__PURE__ */ new WeakMap();
311
+ const activeTrackerMap = /* @__PURE__ */ new WeakMap();
312
+ const getterExecutionContext = {
313
+ tracker: null,
314
+ currentBloc: null,
315
+ depth: 0,
316
+ visitedBlocs: /* @__PURE__ */ new Set()
317
+ };
318
+ const MAX_GETTER_DEPTH = BLAC_DEFAULTS.MAX_GETTER_DEPTH;
319
+ function getGetterExecutionContext() {
320
+ return getterExecutionContext;
321
+ }
322
+ function getDescriptor(obj, prop) {
323
+ const constructor = obj.constructor;
324
+ let constructorCache = descriptorCache.get(constructor);
325
+ if (constructorCache?.has(prop)) return constructorCache.get(prop);
326
+ let current = obj;
327
+ let descriptor;
328
+ while (current && current !== Object.prototype) {
329
+ descriptor = Object.getOwnPropertyDescriptor(current, prop);
330
+ if (descriptor) break;
331
+ current = Object.getPrototypeOf(current);
332
+ }
333
+ if (!constructorCache) {
334
+ constructorCache = /* @__PURE__ */ new Map();
335
+ descriptorCache.set(constructor, constructorCache);
336
+ }
337
+ constructorCache.set(prop, descriptor);
338
+ return descriptor;
339
+ }
340
+ function isGetter(obj, prop) {
341
+ return getDescriptor(obj, prop)?.get !== void 0;
342
+ }
343
+ function createGetterTracker() {
344
+ return {
345
+ trackedValues: /* @__PURE__ */ new Map(),
346
+ currentlyAccessing: /* @__PURE__ */ new Set(),
347
+ trackedGetters: /* @__PURE__ */ new Set(),
348
+ isTracking: false,
349
+ renderCache: /* @__PURE__ */ new Map(),
350
+ cacheValid: false,
351
+ externalDependencies: /* @__PURE__ */ new Set()
352
+ };
353
+ }
354
+ function setActiveTracker(bloc, tracker) {
355
+ activeTrackerMap.set(bloc, tracker);
356
+ }
357
+ function clearActiveTracker(bloc) {
358
+ activeTrackerMap.delete(bloc);
359
+ }
360
+ function getActiveTracker(bloc) {
361
+ return activeTrackerMap.get(bloc);
362
+ }
363
+ function commitTrackedGetters(tracker) {
364
+ if (tracker.currentlyAccessing.size > 0) tracker.trackedGetters = new Set(tracker.currentlyAccessing);
365
+ tracker.currentlyAccessing.clear();
366
+ }
367
+ function createBlocProxy(bloc) {
368
+ const cached = blocProxyCache.get(bloc);
369
+ if (cached) return cached;
370
+ const proxy = new Proxy(bloc, { get(target, prop, receiver) {
371
+ const tracker = activeTrackerMap.get(target);
372
+ if (tracker?.isTracking && isGetter(target, prop)) {
373
+ tracker.currentlyAccessing.add(prop);
374
+ if (tracker.cacheValid && tracker.renderCache.has(prop)) {
375
+ const cachedValue = tracker.renderCache.get(prop);
376
+ tracker.trackedValues.set(prop, cachedValue);
377
+ return cachedValue;
378
+ }
379
+ if (getterExecutionContext.depth >= MAX_GETTER_DEPTH) {
380
+ console.warn(`${BLAC_ERROR_PREFIX} Maximum getter depth (${MAX_GETTER_DEPTH}) exceeded. Possible circular dependency in getter "${String(prop)}" on ${target.constructor.name}. Returning undefined to prevent stack overflow.`);
381
+ return;
382
+ }
383
+ if (getterExecutionContext.visitedBlocs.has(target)) {
384
+ console.warn(`${BLAC_ERROR_PREFIX} Circular dependency detected: getter "${String(prop)}" on ${target.constructor.name} is calling back into itself. Returning undefined to prevent infinite recursion.`);
385
+ return;
386
+ }
387
+ const prevContext = {
388
+ tracker: getterExecutionContext.tracker,
389
+ currentBloc: getterExecutionContext.currentBloc,
390
+ depth: getterExecutionContext.depth,
391
+ visitedBlocs: new Set(getterExecutionContext.visitedBlocs)
392
+ };
393
+ getterExecutionContext.tracker = tracker;
394
+ getterExecutionContext.currentBloc = target;
395
+ getterExecutionContext.depth++;
396
+ getterExecutionContext.visitedBlocs.add(target);
397
+ try {
398
+ const value = getDescriptor(target, prop).get.call(target);
399
+ tracker.trackedValues.set(prop, value);
400
+ return value;
401
+ } finally {
402
+ getterExecutionContext.tracker = prevContext.tracker;
403
+ getterExecutionContext.currentBloc = prevContext.currentBloc;
404
+ getterExecutionContext.depth = prevContext.depth;
405
+ getterExecutionContext.visitedBlocs = prevContext.visitedBlocs;
406
+ }
407
+ }
408
+ return Reflect.get(target, prop, receiver);
409
+ } });
410
+ blocProxyCache.set(bloc, proxy);
411
+ return proxy;
412
+ }
413
+ function hasGetterChanges(bloc, tracker) {
414
+ if (!tracker || tracker.trackedGetters.size === 0) return false;
415
+ tracker.renderCache.clear();
416
+ let hasAnyChange = false;
417
+ for (const prop of tracker.trackedGetters) try {
418
+ const descriptor = getDescriptor(bloc, prop);
419
+ if (!descriptor?.get) continue;
420
+ const newValue = descriptor.get.call(bloc);
421
+ const oldValue = tracker.trackedValues.get(prop);
422
+ tracker.renderCache.set(prop, newValue);
423
+ tracker.trackedValues.set(prop, newValue);
424
+ if (!Object.is(newValue, oldValue)) hasAnyChange = true;
425
+ } catch (error$1) {
426
+ console.warn(`${BLAC_ERROR_PREFIX} Getter "${String(prop)}" threw error during change detection. Stopping tracking for this getter.`, error$1);
427
+ tracker.trackedGetters.delete(prop);
428
+ tracker.trackedValues.delete(prop);
429
+ tracker.cacheValid = false;
430
+ return true;
431
+ }
432
+ tracker.cacheValid = true;
433
+ return hasAnyChange;
434
+ }
435
+ function invalidateRenderCache(tracker) {
436
+ tracker.cacheValid = false;
437
+ }
438
+ function clearExternalDependencies(tracker) {
439
+ tracker.externalDependencies.clear();
440
+ }
441
+ function resetGetterTracker(tracker) {
442
+ tracker.trackedValues.clear();
443
+ tracker.currentlyAccessing.clear();
444
+ tracker.trackedGetters.clear();
445
+ tracker.renderCache.clear();
446
+ tracker.cacheValid = false;
447
+ tracker.isTracking = false;
448
+ tracker.externalDependencies.clear();
449
+ }
450
+
451
+ //#endregion
452
+ //#region src/utils/static-props.ts
453
+ /**
454
+ * Utility functions for accessing static properties on StateContainer classes
455
+ */
456
+ /**
457
+ * Get a static property from a class constructor
458
+ * Type-safe helper that avoids (Type as any) casts
459
+ *
460
+ * @param Type - The class constructor
461
+ * @param propName - The property name to access
462
+ * @param defaultValue - Optional default value if property is undefined
463
+ * @returns The property value or default
464
+ */
465
+ function getStaticProp(Type, propName, defaultValue) {
466
+ return Type[propName] ?? defaultValue;
467
+ }
468
+ /**
469
+ * Check if a class is marked as isolated
470
+ * Isolated classes create separate instances per component
471
+ */
472
+ function isIsolatedClass(Type) {
473
+ return getStaticProp(Type, BLAC_STATIC_PROPS.ISOLATED) === true;
474
+ }
475
+ /**
476
+ * Check if a class is marked as keepAlive
477
+ * KeepAlive classes are never auto-disposed when ref count reaches 0
478
+ */
479
+ function isKeepAliveClass(Type) {
480
+ return getStaticProp(Type, BLAC_STATIC_PROPS.KEEP_ALIVE) === true;
481
+ }
482
+ /**
483
+ * Check if a class should be excluded from DevTools
484
+ * Used to prevent infinite loops when DevTools tracks itself
485
+ */
486
+ function isExcludedFromDevTools(Type) {
487
+ return getStaticProp(Type, BLAC_STATIC_PROPS.EXCLUDE_FROM_DEVTOOLS) === true;
488
+ }
76
489
 
77
490
  //#endregion
78
491
  //#region src/core/StateContainerRegistry.ts
79
492
  /**
80
- * Registry for managing StateContainer instances
493
+ * Registry for coordinating StateContainer lifecycle and managing instances
494
+ *
495
+ * Centralizes all instance management using WeakMap to ensure proper isolation.
81
496
  */
82
497
  var StateContainerRegistry = class {
83
- instances = /* @__PURE__ */ new Map();
84
- types = /* @__PURE__ */ new Map();
85
- listeners = /* @__PURE__ */ new Map();
498
+ constructor() {
499
+ this.instancesByConstructor = /* @__PURE__ */ new WeakMap();
500
+ this.types = /* @__PURE__ */ new Set();
501
+ this.typeConfigs = /* @__PURE__ */ new Map();
502
+ this.listeners = /* @__PURE__ */ new Map();
503
+ }
86
504
  /**
87
- * Register a type with isolation mode
505
+ * Register a type for tracking
506
+ * Called automatically on first instance creation
507
+ */
508
+ registerType(constructor) {
509
+ this.types.add(constructor);
510
+ }
511
+ /**
512
+ * Register a type with isolation mode (explicit registration)
88
513
  */
89
514
  register(constructor, isolated = false) {
90
515
  const className = constructor.name;
91
- if (!isolated && constructor.isolated === true) isolated = true;
92
- if (this.types.has(className)) throw new Error(`Type "${className}" is already registered`);
93
- this.types.set(className, { isolated });
516
+ if (!isolated && isIsolatedClass(constructor)) isolated = true;
517
+ if (this.typeConfigs.has(className)) throw new Error(`${BLAC_ERROR_PREFIX} Type "${className}" is already registered`);
518
+ this.typeConfigs.set(className, { isolated });
519
+ this.registerType(constructor);
94
520
  }
95
521
  /**
96
- * Get or create a StateContainer instance
97
- * For isolated: always creates new
98
- * For shared: singleton per key
522
+ * Get the instances Map for a specific class
523
+ * Creates a new Map on first access.
99
524
  */
100
- getOrCreate(constructor, key, ...args) {
101
- const className = constructor.name;
102
- let config = this.types.get(className);
103
- if (!config) {
104
- const isolated = constructor.isolated === true;
105
- this.types.set(className, { isolated });
106
- config = this.types.get(className);
525
+ ensureInstancesMap(Type) {
526
+ let instances = this.instancesByConstructor.get(Type);
527
+ if (!instances) {
528
+ instances = /* @__PURE__ */ new Map();
529
+ this.instancesByConstructor.set(Type, instances);
530
+ }
531
+ return instances;
532
+ }
533
+ /**
534
+ * Get the instances Map for a specific class (public API for stats/debugging)
535
+ */
536
+ getInstancesMap(Type) {
537
+ return this.instancesByConstructor.get(Type) || /* @__PURE__ */ new Map();
538
+ }
539
+ /**
540
+ * Resolve an instance with ref counting (ownership semantics)
541
+ */
542
+ resolve(Type, instanceKey = BLAC_DEFAULTS.DEFAULT_INSTANCE_KEY, constructorArgs) {
543
+ const registryConfig = this.typeConfigs.get(Type.name);
544
+ const isolated = isIsolatedClass(Type) || registryConfig?.isolated === true;
545
+ const config = { instanceId: instanceKey };
546
+ if (isolated) {
547
+ const instance$1 = new Type(constructorArgs);
548
+ instance$1.initiConfig(config);
549
+ this.registerType(Type);
550
+ return instance$1;
107
551
  }
108
- if (config.isolated) return new constructor(...args);
109
- const instanceKey = `${className}:${key || "default"}`;
110
- const existing = this.instances.get(instanceKey);
111
- if (existing) {
112
- existing.refCount++;
113
- return existing.instance;
552
+ const instances = this.ensureInstancesMap(Type);
553
+ const entry = instances.get(instanceKey);
554
+ if (entry) {
555
+ entry.refCount++;
556
+ return entry.instance;
114
557
  }
115
- const instance = new constructor(...args);
116
- this.instances.set(instanceKey, {
558
+ const instance = new Type(constructorArgs);
559
+ instance.initiConfig(config);
560
+ instances.set(instanceKey, {
117
561
  instance,
118
562
  refCount: 1
119
563
  });
564
+ this.registerType(Type);
120
565
  return instance;
121
566
  }
122
567
  /**
568
+ * Get an existing instance without ref counting (borrowing semantics)
569
+ * @throws Error if instance doesn't exist
570
+ */
571
+ get(Type, instanceKey = BLAC_DEFAULTS.DEFAULT_INSTANCE_KEY) {
572
+ const entry = this.ensureInstancesMap(Type).get(instanceKey);
573
+ if (!entry) throw new Error(`${BLAC_ERROR_PREFIX} ${Type.name} instance "${instanceKey}" not found.\nUse .resolve() to create and claim ownership, or .getSafe() for conditional access.`);
574
+ const context = getGetterExecutionContext();
575
+ if (context.tracker && context.currentBloc && context.currentBloc !== entry.instance) context.tracker.externalDependencies.add(entry.instance);
576
+ return entry.instance;
577
+ }
578
+ /**
579
+ * Safely get an existing instance (borrowing semantics with error handling)
580
+ * Returns discriminated union for type-safe conditional access
581
+ */
582
+ getSafe(Type, instanceKey = BLAC_DEFAULTS.DEFAULT_INSTANCE_KEY) {
583
+ const entry = this.ensureInstancesMap(Type).get(instanceKey);
584
+ if (!entry) return {
585
+ error: /* @__PURE__ */ new Error(`${BLAC_ERROR_PREFIX} ${Type.name} instance "${instanceKey}" not found`),
586
+ instance: null
587
+ };
588
+ const context = getGetterExecutionContext();
589
+ if (context.tracker && context.currentBloc && context.currentBloc !== entry.instance) context.tracker.externalDependencies.add(entry.instance);
590
+ return {
591
+ error: null,
592
+ instance: entry.instance
593
+ };
594
+ }
595
+ /**
596
+ * Connect to an instance with borrowing semantics (for B2B communication)
597
+ * Gets existing instance OR creates it if it doesn't exist, without incrementing ref count.
598
+ * Tracks cross-bloc dependency for reactive updates.
599
+ *
600
+ * Use this in bloc-to-bloc communication when you need to ensure an instance exists
601
+ * but don't want to claim ownership (no ref count increment).
602
+ *
603
+ * @param Type - The bloc class constructor
604
+ * @param instanceKey - Optional instance key (defaults to 'default')
605
+ * @param constructorArgs - Constructor arguments (only used if creating new instance)
606
+ * @returns The bloc instance
607
+ */
608
+ connect(Type, instanceKey = BLAC_DEFAULTS.DEFAULT_INSTANCE_KEY, constructorArgs) {
609
+ const registryConfig = this.typeConfigs.get(Type.name);
610
+ if (isIsolatedClass(Type) || registryConfig?.isolated === true) throw new Error(`${BLAC_ERROR_PREFIX} Cannot use .connect() with isolated bloc "${Type.name}".\nIsolated blocs are component-scoped. Use .resolve() in components instead.`);
611
+ const instances = this.ensureInstancesMap(Type);
612
+ let entry = instances.get(instanceKey);
613
+ if (!entry) {
614
+ const instance = new Type(constructorArgs);
615
+ const config = { instanceId: instanceKey };
616
+ instance.initiConfig(config);
617
+ entry = {
618
+ instance,
619
+ refCount: 1
620
+ };
621
+ instances.set(instanceKey, entry);
622
+ this.registerType(Type);
623
+ }
624
+ const context = getGetterExecutionContext();
625
+ if (context.tracker && context.currentBloc && context.currentBloc !== entry.instance) context.tracker.externalDependencies.add(entry.instance);
626
+ return entry.instance;
627
+ }
628
+ /**
123
629
  * Release a reference to an instance
124
- * Disposes when ref count reaches zero (unless static keepAlive is true)
125
630
  */
126
- release(constructor, key, forceDispose = false) {
127
- const instanceKey = `${constructor.name}:${key || "default"}`;
128
- const entry = this.instances.get(instanceKey);
631
+ release(Type, instanceKey = BLAC_DEFAULTS.DEFAULT_INSTANCE_KEY, forceDispose = false) {
632
+ const instances = this.ensureInstancesMap(Type);
633
+ const entry = instances.get(instanceKey);
129
634
  if (!entry) return;
130
635
  if (forceDispose) {
131
- entry.instance.dispose();
132
- this.instances.delete(instanceKey);
636
+ if (!entry.instance.isDisposed) entry.instance.dispose();
637
+ instances.delete(instanceKey);
133
638
  return;
134
639
  }
135
640
  entry.refCount--;
136
- const keepAlive = constructor.keepAlive === true;
641
+ const keepAlive = isKeepAliveClass(Type);
137
642
  if (entry.refCount <= 0 && !keepAlive) {
138
- entry.instance.dispose();
139
- this.instances.delete(instanceKey);
643
+ if (!entry.instance.isDisposed) entry.instance.dispose();
644
+ instances.delete(instanceKey);
140
645
  }
141
646
  }
142
647
  /**
143
- * Get all instances of a type (shared only, isolated aren't tracked)
648
+ * Get all instances of a specific type
144
649
  */
145
- getAll(constructor) {
146
- const prefix = `${constructor.name}:`;
147
- const results = [];
148
- for (const [key, entry] of this.instances) if (key.startsWith(prefix)) results.push(entry.instance);
149
- return results;
650
+ getAll(Type) {
651
+ const instances = this.ensureInstancesMap(Type);
652
+ const result = [];
653
+ for (const entry of instances.values()) result.push(entry.instance);
654
+ return result;
150
655
  }
151
656
  /**
152
- * Clear all instances of a type
657
+ * Safely iterate over all instances of a type
153
658
  */
154
- clear(constructor) {
155
- const prefix = `${constructor.name}:`;
156
- const toDelete = [];
157
- for (const [key, entry] of this.instances) if (key.startsWith(prefix)) {
158
- if (!entry.instance.isDisposed) entry.instance.dispose();
159
- toDelete.push(key);
659
+ forEach(Type, callback) {
660
+ const instances = this.ensureInstancesMap(Type);
661
+ for (const entry of instances.values()) {
662
+ const instance = entry.instance;
663
+ if (!instance.isDisposed) try {
664
+ callback(instance);
665
+ } catch (error$1) {
666
+ console.error(`${BLAC_ERROR_PREFIX} forEach callback error for ${Type.name}:`, error$1);
667
+ }
160
668
  }
161
- for (const key of toDelete) this.instances.delete(key);
669
+ }
670
+ /**
671
+ * Clear all instances of a specific type
672
+ */
673
+ clear(Type) {
674
+ const instances = this.ensureInstancesMap(Type);
675
+ for (const entry of instances.values()) if (!entry.instance.isDisposed) entry.instance.dispose();
676
+ instances.clear();
677
+ }
678
+ /**
679
+ * Get reference count for an instance
680
+ */
681
+ getRefCount(Type, instanceKey = BLAC_DEFAULTS.DEFAULT_INSTANCE_KEY) {
682
+ return this.ensureInstancesMap(Type).get(instanceKey)?.refCount ?? 0;
683
+ }
684
+ /**
685
+ * Check if an instance exists
686
+ */
687
+ hasInstance(Type, instanceKey = BLAC_DEFAULTS.DEFAULT_INSTANCE_KEY) {
688
+ return this.ensureInstancesMap(Type).has(instanceKey);
162
689
  }
163
690
  /**
164
691
  * Clear all instances from all types (for testing)
692
+ *
693
+ * Iterates all registered types and clears their instances.
694
+ * Also clears type tracking to reset the registry state.
165
695
  */
166
696
  clearAll() {
167
- for (const entry of this.instances.values()) if (!entry.instance.isDisposed) entry.instance.dispose();
168
- this.instances.clear();
697
+ for (const Type of this.types) this.clear(Type);
169
698
  this.types.clear();
699
+ this.typeConfigs.clear();
170
700
  }
171
701
  /**
172
702
  * Get registry statistics (for debugging)
173
703
  */
174
704
  getStats() {
175
705
  const typeBreakdown = {};
176
- for (const key of this.instances.keys()) {
177
- const typeName = key.split(":")[0];
178
- typeBreakdown[typeName] = (typeBreakdown[typeName] || 0) + 1;
706
+ let totalInstances = 0;
707
+ for (const Type of this.types) {
708
+ const typeName = Type.name;
709
+ const count = this.getInstancesMap(Type).size;
710
+ typeBreakdown[typeName] = count;
711
+ totalInstances += count;
179
712
  }
180
713
  return {
181
714
  registeredTypes: this.types.size,
182
- totalInstances: this.instances.size,
715
+ totalInstances,
183
716
  typeBreakdown
184
717
  };
185
718
  }
186
719
  /**
187
- * Get reference count for an instance
188
- */
189
- getRefCount(constructor, key) {
190
- const instanceKey = `${constructor.name}:${key || "default"}`;
191
- return this.instances.get(instanceKey)?.refCount ?? 0;
192
- }
193
- /**
194
- * Check if an instance exists
720
+ * Get all registered types (for plugin system)
195
721
  */
196
- hasInstance(constructor, key) {
197
- const instanceKey = `${constructor.name}:${key || "default"}`;
198
- return this.instances.has(instanceKey);
722
+ getTypes() {
723
+ return Array.from(this.types);
199
724
  }
200
725
  /**
201
726
  * Subscribe to lifecycle events
@@ -220,7 +745,7 @@ var StateContainerRegistry = class {
220
745
  for (const listener of listeners) try {
221
746
  listener(...args);
222
747
  } catch (error$1) {
223
- console.error(`[BlaC] Listener error for '${event}':`, error$1);
748
+ console.error(`${BLAC_ERROR_PREFIX} Listener error for '${event}':`, error$1);
224
749
  }
225
750
  }
226
751
  };
@@ -228,18 +753,51 @@ var StateContainerRegistry = class {
228
753
  * Global default registry instance
229
754
  */
230
755
  const globalRegistry = new StateContainerRegistry();
756
+ /**
757
+ * Global plugin manager (initialized lazily)
758
+ */
759
+ let _globalPluginManager = null;
760
+ /**
761
+ * Get the global plugin manager
762
+ */
763
+ function getPluginManager() {
764
+ if (!_globalPluginManager) _globalPluginManager = createPluginManager(globalRegistry);
765
+ return _globalPluginManager;
766
+ }
231
767
 
232
768
  //#endregion
233
769
  //#region src/core/StateContainer.ts
234
770
  /**
771
+ * StateContainer - Clean, minimal state management container
772
+ *
773
+ * Responsibilities:
774
+ * - State storage and updates
775
+ * - Change notifications to subscribers
776
+ * - Lifecycle management
777
+ */
778
+ /**
235
779
  * Base abstract class for all state containers
236
780
  */
237
781
  var StateContainer = class StateContainer {
238
- static _registry = globalRegistry;
782
+ static {
783
+ this.__excludeFromDevTools = false;
784
+ }
785
+ static {
786
+ this._registry = globalRegistry;
787
+ }
788
+ /**
789
+ * Get the global registry (mainly for testing)
790
+ */
791
+ static getRegistry() {
792
+ return StateContainer._registry;
793
+ }
239
794
  /**
240
795
  * Set a custom registry (mainly for testing)
796
+ *
797
+ * Clears all instances before switching to ensure clean test isolation.
241
798
  */
242
799
  static setRegistry(registry) {
800
+ StateContainer._registry.clearAll();
243
801
  StateContainer._registry = registry;
244
802
  }
245
803
  /**
@@ -249,75 +807,123 @@ var StateContainer = class StateContainer {
249
807
  StateContainer._registry.register(this, isolated);
250
808
  }
251
809
  /**
252
- * Get or create an instance
253
- * @param key - Optional instance key for shared instances
254
- * @param args - Constructor arguments
810
+ * Resolve an instance with ref counting (ownership semantics)
811
+ *
812
+ * Delegates to the global registry for instance management.
813
+ *
814
+ * @param instanceKey - Optional instance key for shared instances
815
+ * @param constructorArgs - Constructor arguments
816
+ * @returns Instance with incremented ref count
817
+ */
818
+ static resolve(instanceKey, constructorArgs) {
819
+ return StateContainer._registry.resolve(this, instanceKey, constructorArgs);
820
+ }
821
+ /**
822
+ * Get an existing instance without ref counting (borrowing semantics)
823
+ * Delegates to the global registry.
255
824
  */
256
- static getOrCreate(key, ...args) {
257
- return StateContainer._registry.getOrCreate(this, key, ...args);
825
+ static get(instanceKey) {
826
+ return StateContainer._registry.get(this, instanceKey);
827
+ }
828
+ /**
829
+ * Safely get an existing instance (borrowing semantics with error handling)
830
+ * Delegates to the global registry.
831
+ */
832
+ static getSafe(instanceKey) {
833
+ return StateContainer._registry.getSafe(this, instanceKey);
834
+ }
835
+ /**
836
+ * Connect to an instance with borrowing semantics (for B2B communication)
837
+ * Gets existing instance OR creates it if it doesn't exist, without incrementing ref count.
838
+ * Tracks cross-bloc dependency for reactive updates.
839
+ *
840
+ * Use this in bloc-to-bloc communication when you need to ensure an instance exists
841
+ * but don't want to claim ownership (no ref count increment).
842
+ *
843
+ * Delegates to the global registry.
844
+ *
845
+ * @param instanceKey - Optional instance key (defaults to 'default')
846
+ * @param constructorArgs - Constructor arguments (only used if creating new instance)
847
+ * @returns The bloc instance
848
+ */
849
+ static connect(instanceKey, constructorArgs) {
850
+ return StateContainer._registry.connect(this, instanceKey, constructorArgs);
258
851
  }
259
852
  /**
260
853
  * Release a reference to an instance
261
- * @param key - Optional instance key
262
- * @param forceDispose - Force dispose regardless of ref count or keepAlive
854
+ * Delegates to the global registry.
263
855
  */
264
- static release(key, forceDispose = false) {
265
- StateContainer._registry.release(this, key, forceDispose);
856
+ static release(instanceKey, forceDispose = false) {
857
+ StateContainer._registry.release(this, instanceKey, forceDispose);
266
858
  }
267
859
  /**
268
860
  * Get all instances of this type
861
+ * Delegates to the global registry.
269
862
  */
270
863
  static getAll() {
271
864
  return StateContainer._registry.getAll(this);
272
865
  }
273
866
  /**
867
+ * Safely iterate over all instances of this type
868
+ * Delegates to the global registry.
869
+ */
870
+ static forEach(callback) {
871
+ StateContainer._registry.forEach(this, callback);
872
+ }
873
+ /**
274
874
  * Clear all instances of this type
875
+ * Delegates to the global registry.
275
876
  */
276
877
  static clear() {
277
878
  StateContainer._registry.clear(this);
278
879
  }
279
880
  /**
280
881
  * Clear all instances from all types (for testing)
882
+ * Delegates to the global registry.
281
883
  */
282
884
  static clearAllInstances() {
283
885
  StateContainer._registry.clearAll();
284
886
  }
285
887
  /**
286
888
  * Get registry statistics (for debugging)
889
+ * Delegates to the global registry.
287
890
  */
288
891
  static getStats() {
289
892
  return StateContainer._registry.getStats();
290
893
  }
291
894
  /**
292
895
  * Get reference count for an instance
293
- * @param key - Optional instance key
896
+ * Delegates to the global registry.
294
897
  */
295
- static getRefCount(key) {
296
- return StateContainer._registry.getRefCount(this, key);
898
+ static getRefCount(instanceKey) {
899
+ return StateContainer._registry.getRefCount(this, instanceKey);
297
900
  }
298
901
  /**
299
902
  * Check if an instance exists
300
- * @param key - Optional instance key
903
+ * Delegates to the global registry.
301
904
  */
302
- static hasInstance(key) {
303
- return StateContainer._registry.hasInstance(this, key);
905
+ static hasInstance(instanceKey) {
906
+ return StateContainer._registry.hasInstance(this, instanceKey);
304
907
  }
305
- _state;
306
- listeners = /* @__PURE__ */ new Set();
307
- _disposed = false;
308
- config;
309
- name;
310
- debug;
311
- instanceId;
312
908
  /**
313
909
  * Create a new StateContainer
314
910
  */
315
- constructor(initialState, config = {}) {
911
+ constructor(initialState) {
912
+ this.listeners = /* @__PURE__ */ new Set();
913
+ this._disposed = false;
914
+ this.config = {};
915
+ this.name = this.constructor.name;
916
+ this.debug = false;
917
+ this.instanceId = generateSimpleId(this.constructor.name, "main");
918
+ this.createdAt = Date.now();
919
+ this.lastUpdateTimestamp = Date.now();
316
920
  this._state = initialState;
317
- this.config = config;
318
- this.name = config.name || this.constructor.name;
319
- this.debug = config.debug ?? false;
320
- this.instanceId = config.instanceId || generateSimpleId(this.constructor.name);
921
+ }
922
+ initiConfig(config) {
923
+ this.config = { ...config };
924
+ this.name = this.config.name || this.constructor.name;
925
+ this.debug = this.config.debug ?? false;
926
+ this.instanceId = generateSimpleId(this.constructor.name, this.config.instanceId);
321
927
  StateContainer._registry.emit("created", this);
322
928
  }
323
929
  /**
@@ -367,7 +973,58 @@ var StateContainer = class StateContainer {
367
973
  } catch (error$1) {
368
974
  console.error(`[${this.name}] Error in listener:`, error$1);
369
975
  }
370
- StateContainer._registry.emit("stateChanged", this, previousState, newState);
976
+ const stackTrace = this.captureStackTrace();
977
+ StateContainer._registry.emit("stateChanged", this, previousState, newState, stackTrace);
978
+ this.lastUpdateTimestamp = Date.now();
979
+ }
980
+ /**
981
+ * Capture stack trace for debugging
982
+ * Can be overridden to disable in production
983
+ */
984
+ captureStackTrace() {
985
+ if (typeof process !== "undefined" && process.env?.NODE_ENV === "production") return "";
986
+ try {
987
+ const relevantLines = ((/* @__PURE__ */ new Error()).stack || "").split("\n").slice(1);
988
+ const formattedLines = [];
989
+ for (const line of relevantLines) {
990
+ if (!line.trim()) continue;
991
+ if (line.includes("StateContainer.emit") || line.includes("StateContainer.update") || line.includes("StateContainer.captureStackTrace") || line.includes("Cubit.patch") || line.includes("Vertex.")) continue;
992
+ if (line.includes("node_modules") || line.includes("react-dom") || line.includes("react_jsx") || line.includes(".vite/deps") || line.includes("executeDispatch") || line.includes("runWithFiber") || line.includes("invokeGuarded") || line.includes("callCallback") || line.includes("processDispatchQueue") || line.includes("dispatchEvent") || line.includes("batchedUpdates")) continue;
993
+ const formatted = this.formatStackLine(line);
994
+ if (formatted) formattedLines.push(formatted);
995
+ }
996
+ return formattedLines.join("\n");
997
+ } catch {
998
+ return "";
999
+ }
1000
+ }
1001
+ /**
1002
+ * Format a single stack trace line for better readability
1003
+ * Removes URL prefixes, query params, and cleans up the output
1004
+ */
1005
+ formatStackLine(line) {
1006
+ const match = line.match(/at\s+(.+?)\s+\((.+?):(\d+):(\d+)\)/);
1007
+ if (!match) {
1008
+ const simpleMatch = line.match(/at\s+(.+?):(\d+):(\d+)/);
1009
+ if (simpleMatch) {
1010
+ const [, url$1, lineNum$1, col$1] = simpleMatch;
1011
+ return ` at ${this.cleanFilePath(url$1)}:${lineNum$1}:${col$1}`;
1012
+ }
1013
+ return null;
1014
+ }
1015
+ const [, functionName, url, lineNum, col] = match;
1016
+ return ` at ${functionName} (${this.cleanFilePath(url)}:${lineNum}:${col})`;
1017
+ }
1018
+ /**
1019
+ * Clean up file path by removing URL prefixes and query params
1020
+ */
1021
+ cleanFilePath(url) {
1022
+ let path = url.replace(/http:\/\/localhost:\d+\/@fs/, "").replace(/http:\/\/localhost:\d+\//, "").replace(/\?t=\d+/, "").replace(/\?v=[a-f0-9]+/, "");
1023
+ const projectMatch = path.match(/\/Projects\/blac\/(.+)/);
1024
+ if (projectMatch) path = projectMatch[1];
1025
+ const segments = path.split("/");
1026
+ if (segments.length > 3) path = segments.slice(-3).join("/");
1027
+ return path;
371
1028
  }
372
1029
  /**
373
1030
  * Update state using a function
@@ -381,6 +1038,12 @@ var StateContainer = class StateContainer {
381
1038
  //#endregion
382
1039
  //#region src/core/Cubit.ts
383
1040
  /**
1041
+ * Cubit - Simple state container with direct state emission
1042
+ *
1043
+ * This validates that our StateContainer design can support
1044
+ * the simple Cubit pattern from the original BlaC.
1045
+ */
1046
+ /**
384
1047
  * Cubit is a simple state container that allows direct state emission
385
1048
  *
386
1049
  * Unlike StateContainer (where emit is protected), Cubit exposes emit and update
@@ -390,10 +1053,16 @@ var Cubit = class extends StateContainer {
390
1053
  /**
391
1054
  * Create a new Cubit
392
1055
  * @param initialState Initial state value
393
- * @param config Configuration options
394
1056
  */
395
- constructor(initialState, config) {
396
- super(initialState, config);
1057
+ constructor(initialState) {
1058
+ super(initialState);
1059
+ this.patch = ((partial) => {
1060
+ if (typeof this.state !== "object" || this.state === null) throw new Error("patch() is only available for object state types");
1061
+ this.update((current) => ({
1062
+ ...current,
1063
+ ...partial
1064
+ }));
1065
+ });
397
1066
  }
398
1067
  /**
399
1068
  * Emit a new state (public override of protected parent method)
@@ -407,89 +1076,70 @@ var Cubit = class extends StateContainer {
407
1076
  update(updater) {
408
1077
  super["update"](updater);
409
1078
  }
410
- /**
411
- * Patch state with partial updates (shallow merge)
412
- * Only available when state is an object type
413
- * Arrow function to maintain correct 'this' binding in React
414
- *
415
- * @example
416
- * ```typescript
417
- * // Update single field
418
- * this.patch({ count: 5 });
419
- *
420
- * // Update multiple fields
421
- * this.patch({ count: 5, name: 'Updated' });
422
- * ```
423
- */
424
- patch = ((partial) => {
425
- if (typeof this.state !== "object" || this.state === null) throw new Error("patch() is only available for object state types");
426
- this.update((current) => ({
427
- ...current,
428
- ...partial
429
- }));
430
- });
431
1079
  };
432
1080
  /**
433
1081
  * Example: Counter Cubit
434
1082
  */
435
1083
  var CounterCubit = class extends Cubit {
436
1084
  constructor() {
437
- super(0, { name: "CounterCubit" });
1085
+ super(0);
1086
+ this.increment = () => {
1087
+ this.emit(this.state + 1);
1088
+ };
1089
+ this.decrement = () => {
1090
+ this.emit(this.state - 1);
1091
+ };
1092
+ this.reset = () => {
1093
+ this.emit(0);
1094
+ };
1095
+ this.addAmount = (amount) => {
1096
+ this.emit(this.state + amount);
1097
+ };
438
1098
  }
439
- increment = () => {
440
- this.emit(this.state + 1);
441
- };
442
- decrement = () => {
443
- this.emit(this.state - 1);
444
- };
445
- reset = () => {
446
- this.emit(0);
447
- };
448
- addAmount = (amount) => {
449
- this.emit(this.state + amount);
450
- };
451
1099
  };
452
1100
  var TodoCubit = class extends Cubit {
453
- static keepAlive = true;
454
- nextId = 0;
1101
+ static {
1102
+ this.keepAlive = true;
1103
+ }
455
1104
  constructor() {
456
1105
  super({
457
1106
  todos: [],
458
1107
  filter: "all",
459
1108
  isLoading: false
460
- }, { name: "TodoCubit" });
461
- }
462
- addTodo = (text) => {
463
- this.update((state) => ({
464
- ...state,
465
- todos: [...state.todos, {
466
- id: `todo-${Date.now()}-${this.nextId++}`,
467
- text,
468
- done: false
469
- }]
470
- }));
471
- };
472
- toggleTodo = (id) => {
473
- this.update((state) => ({
474
- ...state,
475
- todos: state.todos.map((todo) => todo.id === id ? {
476
- ...todo,
477
- done: !todo.done
478
- } : todo)
479
- }));
480
- };
481
- removeTodo = (id) => {
482
- this.update((state) => ({
483
- ...state,
484
- todos: state.todos.filter((todo) => todo.id !== id)
485
- }));
486
- };
487
- setFilter = (filter) => {
488
- this.patch({ filter });
489
- };
490
- setLoading = (isLoading) => {
491
- this.patch({ isLoading });
492
- };
1109
+ });
1110
+ this.nextId = 0;
1111
+ this.addTodo = (text) => {
1112
+ this.update((state) => ({
1113
+ ...state,
1114
+ todos: [...state.todos, {
1115
+ id: `todo-${Date.now()}-${this.nextId++}`,
1116
+ text,
1117
+ done: false
1118
+ }]
1119
+ }));
1120
+ };
1121
+ this.toggleTodo = (id) => {
1122
+ this.update((state) => ({
1123
+ ...state,
1124
+ todos: state.todos.map((todo) => todo.id === id ? {
1125
+ ...todo,
1126
+ done: !todo.done
1127
+ } : todo)
1128
+ }));
1129
+ };
1130
+ this.removeTodo = (id) => {
1131
+ this.update((state) => ({
1132
+ ...state,
1133
+ todos: state.todos.filter((todo) => todo.id !== id)
1134
+ }));
1135
+ };
1136
+ this.setFilter = (filter) => {
1137
+ this.patch({ filter });
1138
+ };
1139
+ this.setLoading = (isLoading) => {
1140
+ this.patch({ isLoading });
1141
+ };
1142
+ }
493
1143
  get visibleTodos() {
494
1144
  const todos = this.state.todos;
495
1145
  switch (this.state.filter) {
@@ -507,45 +1157,42 @@ var TodoCubit = class extends Cubit {
507
1157
  //#endregion
508
1158
  //#region src/core/Vertex.ts
509
1159
  /**
1160
+ * Vertex - Event-driven state container (Bloc pattern)
1161
+ *
1162
+ * Simplified version that includes its own event handling,
1163
+ * since events are only needed for the Vertex pattern.
1164
+ */
1165
+ /**
510
1166
  * Vertex is an event-driven state container (Bloc pattern)
511
1167
  *
512
1168
  * Includes its own simple event system since events are only needed
513
1169
  * for the Vertex pattern, not for the base StateContainer.
514
1170
  */
515
1171
  var Vertex = class extends StateContainer {
516
- eventHandlers = /* @__PURE__ */ new Map();
517
- isProcessing = false;
518
- eventQueue = [];
519
1172
  /**
520
1173
  * Create a new Vertex
521
1174
  * @param initialState Initial state value
522
- * @param config Configuration options
523
1175
  */
524
- constructor(initialState, config) {
525
- super(initialState, config);
1176
+ constructor(initialState) {
1177
+ super(initialState);
1178
+ this.eventHandlers = /* @__PURE__ */ new Map();
1179
+ this.isProcessing = false;
1180
+ this.eventQueue = [];
1181
+ this.on = (EventClass, handler) => {
1182
+ const className = EventClass.name;
1183
+ const registrations = this.eventHandlers.get(className) || [];
1184
+ registrations.push({
1185
+ eventClass: EventClass,
1186
+ handler
1187
+ });
1188
+ this.eventHandlers.set(className, registrations);
1189
+ };
1190
+ this.add = (event) => {
1191
+ StateContainer._registry.emit("eventAdded", this, event);
1192
+ this.processEvent(event);
1193
+ };
526
1194
  }
527
1195
  /**
528
- * Register an event handler
529
- * Arrow function to maintain correct 'this' binding
530
- */
531
- on = (EventClass, handler) => {
532
- const className = EventClass.name;
533
- const registrations = this.eventHandlers.get(className) || [];
534
- registrations.push({
535
- eventClass: EventClass,
536
- handler
537
- });
538
- this.eventHandlers.set(className, registrations);
539
- };
540
- /**
541
- * Add an event to be processed
542
- * Arrow function to maintain correct 'this' binding in React
543
- */
544
- add = (event) => {
545
- StateContainer._registry.emit("eventAdded", this, event);
546
- this.processEvent(event);
547
- };
548
- /**
549
1196
  * Process an event (synchronously)
550
1197
  */
551
1198
  processEvent(event) {
@@ -587,28 +1234,30 @@ var Vertex = class extends StateContainer {
587
1234
  /**
588
1235
  * Hook for handling event processing errors
589
1236
  */
590
- onEventError(event, error$1) {}
1237
+ onEventError(_event, _error) {}
591
1238
  };
592
1239
  /**
593
1240
  * Example: Counter Events
594
1241
  */
595
1242
  var IncrementEvent = class {
596
- type = "increment";
597
- timestamp = Date.now();
598
1243
  constructor(amount = 1) {
599
1244
  this.amount = amount;
1245
+ this.type = "increment";
1246
+ this.timestamp = Date.now();
600
1247
  }
601
1248
  };
602
1249
  var DecrementEvent = class {
603
- type = "decrement";
604
- timestamp = Date.now();
605
1250
  constructor(amount = 1) {
606
1251
  this.amount = amount;
1252
+ this.type = "decrement";
1253
+ this.timestamp = Date.now();
607
1254
  }
608
1255
  };
609
1256
  var ResetEvent = class {
610
- type = "reset";
611
- timestamp = Date.now();
1257
+ constructor() {
1258
+ this.type = "reset";
1259
+ this.timestamp = Date.now();
1260
+ }
612
1261
  };
613
1262
  /**
614
1263
  * Example: Counter Vertex
@@ -617,6 +1266,15 @@ var ResetEvent = class {
617
1266
  var CounterVertex = class extends Vertex {
618
1267
  constructor() {
619
1268
  super(0);
1269
+ this.increment = (amount = 1) => {
1270
+ this.add(new IncrementEvent(amount));
1271
+ };
1272
+ this.decrement = (amount = 1) => {
1273
+ this.add(new DecrementEvent(amount));
1274
+ };
1275
+ this.reset = () => {
1276
+ this.add(new ResetEvent());
1277
+ };
620
1278
  this.on(IncrementEvent, (event, emit) => {
621
1279
  emit(this.state + event.amount);
622
1280
  });
@@ -627,27 +1285,20 @@ var CounterVertex = class extends Vertex {
627
1285
  emit(0);
628
1286
  });
629
1287
  }
630
- increment = (amount = 1) => {
631
- this.add(new IncrementEvent(amount));
632
- };
633
- decrement = (amount = 1) => {
634
- this.add(new DecrementEvent(amount));
635
- };
636
- reset = () => {
637
- this.add(new ResetEvent());
638
- };
639
1288
  };
640
1289
  var LoginEvent = class {
641
- type = "login";
642
- timestamp = Date.now();
643
1290
  constructor(email, password) {
644
1291
  this.email = email;
645
1292
  this.password = password;
1293
+ this.type = "login";
1294
+ this.timestamp = Date.now();
646
1295
  }
647
1296
  };
648
1297
  var LogoutEvent = class {
649
- type = "logout";
650
- timestamp = Date.now();
1298
+ constructor() {
1299
+ this.type = "logout";
1300
+ this.timestamp = Date.now();
1301
+ }
651
1302
  };
652
1303
  var AuthVertex = class extends Vertex {
653
1304
  constructor() {
@@ -657,6 +1308,12 @@ var AuthVertex = class extends Vertex {
657
1308
  user: null,
658
1309
  error: null
659
1310
  });
1311
+ this.login = (email, password) => {
1312
+ this.add(new LoginEvent(email, password));
1313
+ };
1314
+ this.logout = () => {
1315
+ this.add(new LogoutEvent());
1316
+ };
660
1317
  this.on(LoginEvent, (event, emit) => {
661
1318
  emit({
662
1319
  ...this.state,
@@ -689,35 +1346,19 @@ var AuthVertex = class extends Vertex {
689
1346
  });
690
1347
  });
691
1348
  }
692
- login = (email, password) => {
693
- this.add(new LoginEvent(email, password));
694
- };
695
- logout = () => {
696
- this.add(new LogoutEvent());
697
- };
698
1349
  };
699
1350
 
700
1351
  //#endregion
701
- //#region src/tracking/proxy-tracker.ts
1352
+ //#region src/plugin/BlacPlugin.ts
702
1353
  /**
703
- * Functional proxy tracker for automatic dependency tracking
704
- *
705
- * This module provides a functional approach to proxy-based dependency tracking.
706
- * Unlike the class-based ProxyTracker, this uses a state object pattern that's
707
- * easier to integrate with functional programming patterns and framework hooks.
708
- */
709
- /**
710
- * Array methods that should trigger tracking of the entire array
1354
+ * Type guard for plugins with init
711
1355
  */
712
- const ARRAY_METHODS = new Set([
713
- "map",
714
- "filter",
715
- "forEach",
716
- "find",
717
- "some",
718
- "every",
719
- "reduce"
720
- ]);
1356
+ function hasInitHook(plugin) {
1357
+ return typeof plugin.onInstall === "function";
1358
+ }
1359
+
1360
+ //#endregion
1361
+ //#region src/tracking/proxy-tracker.ts
721
1362
  /**
722
1363
  * Check if a value can be proxied
723
1364
  *
@@ -781,9 +1422,6 @@ function createArrayProxy(state, target, path, depth = 0) {
781
1422
  if (typeof prop === "symbol") return Reflect.get(arr, prop);
782
1423
  const value = Reflect.get(arr, prop);
783
1424
  if (typeof value === "function") {
784
- if (ARRAY_METHODS.has(prop)) {
785
- if (state.isTracking && path) state.trackedPaths.add(path);
786
- }
787
1425
  if (!state.boundFunctionsCache) state.boundFunctionsCache = /* @__PURE__ */ new WeakMap();
788
1426
  const cached = state.boundFunctionsCache.get(value);
789
1427
  if (cached) return cached;
@@ -791,9 +1429,15 @@ function createArrayProxy(state, target, path, depth = 0) {
791
1429
  state.boundFunctionsCache.set(value, bound);
792
1430
  return bound;
793
1431
  }
1432
+ if (prop === "length") {
1433
+ if (state.isTracking) {
1434
+ const fullPath$1 = path ? `${path}.length` : "length";
1435
+ state.trackedPaths.add(fullPath$1);
1436
+ }
1437
+ return value;
1438
+ }
794
1439
  let fullPath;
795
- if (prop === "length") fullPath = path ? `${path}.length` : "length";
796
- else if (typeof prop === "string") {
1440
+ if (typeof prop === "string") {
797
1441
  const index = Number(prop);
798
1442
  if (!isNaN(index) && index >= 0) fullPath = path ? `${path}[${index}]` : `[${index}]`;
799
1443
  else fullPath = path ? `${path}.${prop}` : prop;
@@ -835,10 +1479,10 @@ function createProxyInternal(state, target, path = "", depth = 0) {
835
1479
  return bound;
836
1480
  }
837
1481
  const fullPath = path ? `${path}.${String(prop)}` : String(prop);
838
- if (isProxyable(value)) return createProxyInternal(state, value, fullPath, depth + 1);
839
1482
  if (typeof prop === "string" && state.isTracking) {
840
1483
  if (!prop.startsWith("_") && !prop.startsWith("$$")) state.trackedPaths.add(fullPath);
841
1484
  }
1485
+ if (isProxyable(value)) return createProxyInternal(state, value, fullPath, depth + 1);
842
1486
  return value;
843
1487
  },
844
1488
  has: (obj, prop) => {
@@ -950,13 +1594,45 @@ function shallowEqual(arr1, arr2) {
950
1594
 
951
1595
  //#endregion
952
1596
  //#region src/tracking/dependency-tracker.ts
1597
+ function isChildPath(child, parent) {
1598
+ if (child === parent) return false;
1599
+ return child.startsWith(parent + ".") || child.startsWith(parent + "[");
1600
+ }
953
1601
  /**
954
- * How often to clean up unused paths from the cache
955
- */
956
- const CLEANUP_INTERVAL = 10;
957
- /**
958
- * Create a new tracker state
1602
+ * Extract the parent array path from an array child path
1603
+ * Examples:
1604
+ * 'messages.length' -> 'messages'
1605
+ * 'messages[0]' -> 'messages'
1606
+ * 'messages[0].text' -> 'messages'
1607
+ * 'data.items[0]' -> 'data.items'
959
1608
  */
1609
+ function getArrayParentPath(path) {
1610
+ if (path.endsWith(".length")) return path.slice(0, -7);
1611
+ const arrayIndexMatch = path.match(/^(.+?)\[\d+\]/);
1612
+ if (arrayIndexMatch) return arrayIndexMatch[1];
1613
+ return null;
1614
+ }
1615
+ function optimizeTrackedPaths(paths) {
1616
+ if (paths.size === 0) return /* @__PURE__ */ new Set();
1617
+ if (paths.size === 1) return new Set(paths);
1618
+ const sortedPaths = Array.from(paths).sort((a, b) => b.length - a.length);
1619
+ const optimized = /* @__PURE__ */ new Set();
1620
+ for (const path of sortedPaths) {
1621
+ let hasMoreSpecificChild = false;
1622
+ for (const optimizedPath of optimized) if (isChildPath(optimizedPath, path)) {
1623
+ hasMoreSpecificChild = true;
1624
+ break;
1625
+ }
1626
+ if (!hasMoreSpecificChild) optimized.add(path);
1627
+ }
1628
+ const arrayParents = /* @__PURE__ */ new Set();
1629
+ for (const path of optimized) {
1630
+ const arrayParent = getArrayParentPath(path);
1631
+ if (arrayParent) arrayParents.add(arrayParent);
1632
+ }
1633
+ for (const arrayParent of arrayParents) optimized.add(arrayParent);
1634
+ return optimized;
1635
+ }
960
1636
  function createTrackerState() {
961
1637
  return {
962
1638
  proxyTrackerState: createProxyTrackerState(),
@@ -964,31 +1640,18 @@ function createTrackerState() {
964
1640
  currentRenderPaths: /* @__PURE__ */ new Set(),
965
1641
  pathCache: /* @__PURE__ */ new Map(),
966
1642
  lastCheckedState: null,
967
- lastCheckedValues: /* @__PURE__ */ new Map(),
968
- cleanupCounter: 0
1643
+ lastCheckedValues: /* @__PURE__ */ new Map()
969
1644
  };
970
1645
  }
971
- /**
972
- * Start tracking property accesses
973
- */
974
1646
  function startTracking(tracker) {
975
1647
  startProxyTracking(tracker.proxyTrackerState);
976
1648
  }
977
- /**
978
- * Create a proxy for the given state
979
- */
980
1649
  function createProxy(tracker, state) {
981
1650
  return createProxyForTarget(tracker.proxyTrackerState, state);
982
1651
  }
983
- /**
984
- * Capture the paths that were tracked during a render
985
- *
986
- * This should be called after each render to record which paths were accessed.
987
- * It also performs periodic cleanup of unused paths to prevent memory leaks.
988
- */
989
1652
  function captureTrackedPaths(tracker, state) {
990
1653
  tracker.previousRenderPaths = tracker.currentRenderPaths;
991
- tracker.currentRenderPaths = stopProxyTracking(tracker.proxyTrackerState);
1654
+ tracker.currentRenderPaths = optimizeTrackedPaths(stopProxyTracking(tracker.proxyTrackerState));
992
1655
  if (tracker.previousRenderPaths.size === 0 && tracker.currentRenderPaths.size === 0) return;
993
1656
  const trackedPathsUnion = new Set(tracker.previousRenderPaths);
994
1657
  for (const path of tracker.currentRenderPaths) trackedPathsUnion.add(path);
@@ -1004,19 +1667,8 @@ function captureTrackedPaths(tracker, state) {
1004
1667
  const info$1 = tracker.pathCache.get(path);
1005
1668
  info$1.value = canReuseCache && tracker.lastCheckedValues.has(path) ? tracker.lastCheckedValues.get(path) : getValueAtPath(state, info$1.segments);
1006
1669
  }
1007
- tracker.cleanupCounter++;
1008
- if (tracker.cleanupCounter >= CLEANUP_INTERVAL) {
1009
- tracker.cleanupCounter = 0;
1010
- for (const path of tracker.pathCache.keys()) if (!trackedPathsUnion.has(path)) tracker.pathCache.delete(path);
1011
- }
1012
1670
  tracker.lastCheckedValues.clear();
1013
1671
  }
1014
- /**
1015
- * Check if any tracked paths have changed
1016
- *
1017
- * This is called when state changes to determine if a re-render is needed.
1018
- * Returns true if any tracked path has a different value than before.
1019
- */
1020
1672
  function hasChanges(tracker, state) {
1021
1673
  if (tracker.pathCache.size === 0) return true;
1022
1674
  tracker.lastCheckedValues.clear();
@@ -1031,11 +1683,6 @@ function hasChanges(tracker, state) {
1031
1683
  tracker.lastCheckedState = state;
1032
1684
  return false;
1033
1685
  }
1034
- /**
1035
- * Check if tracker has any tracked data
1036
- *
1037
- * Returns true if there are any tracked paths or cached paths.
1038
- */
1039
1686
  function hasTrackedData(tracker) {
1040
1687
  return tracker.proxyTrackerState.trackedPaths.size > 0 || tracker.pathCache.size > 0 || tracker.previousRenderPaths.size > 0;
1041
1688
  }
@@ -1139,6 +1786,12 @@ function configureLogger(opts) {
1139
1786
  //#endregion
1140
1787
  //#region src/proxy/ProxyTracker.ts
1141
1788
  /**
1789
+ * ProxyTracker - Automatic dependency tracking through Proxies
1790
+ *
1791
+ * Tracks property access on state objects to automatically determine
1792
+ * which parts of the state a component depends on.
1793
+ */
1794
+ /**
1142
1795
  * Check if a value is an object that can be proxied
1143
1796
  */
1144
1797
  function isProxyable$1(value) {
@@ -1148,10 +1801,12 @@ function isProxyable$1(value) {
1148
1801
  * Tracks property access on state objects
1149
1802
  */
1150
1803
  var ProxyTracker = class {
1151
- trackedPaths = /* @__PURE__ */ new Set();
1152
- isTracking = false;
1153
- proxyCache = /* @__PURE__ */ new WeakMap();
1154
- maxDepth = 10;
1804
+ constructor() {
1805
+ this.trackedPaths = /* @__PURE__ */ new Set();
1806
+ this.isTracking = false;
1807
+ this.proxyCache = /* @__PURE__ */ new WeakMap();
1808
+ this.maxDepth = 10;
1809
+ }
1155
1810
  /**
1156
1811
  * Create a proxy that tracks property access
1157
1812
  */
@@ -1203,22 +1858,12 @@ var ProxyTracker = class {
1203
1858
  const proxy = new Proxy(target, { get: (arr, prop) => {
1204
1859
  const value = Reflect.get(arr, prop);
1205
1860
  if (typeof value === "function") {
1206
- if ([
1207
- "map",
1208
- "filter",
1209
- "forEach",
1210
- "find",
1211
- "some",
1212
- "every",
1213
- "reduce"
1214
- ].includes(String(prop))) {
1215
- if (this.isTracking && path) {
1216
- this.trackedPaths.add(path);
1217
- debug("ProxyTracker", "Tracked array method access", {
1218
- path,
1219
- method: String(prop)
1220
- });
1221
- }
1861
+ if (this.isTracking && path && typeof prop === "string") {
1862
+ this.trackedPaths.add(path);
1863
+ debug("ProxyTracker", "Tracked array method access", {
1864
+ path,
1865
+ method: String(prop)
1866
+ });
1222
1867
  }
1223
1868
  return value.bind(arr);
1224
1869
  }
@@ -1321,36 +1966,191 @@ function trackAccess(state, callback) {
1321
1966
  }
1322
1967
 
1323
1968
  //#endregion
1324
- //#region src/constants.ts
1969
+ //#region src/adapter/framework-adapter.ts
1325
1970
  /**
1326
- * Default configuration constants for BlaC
1327
- *
1328
- * Centralized location for all magic numbers and default values.
1971
+ * External dependency manager
1972
+ * Handles subscriptions to external blocs accessed in getters
1329
1973
  */
1974
+ var ExternalDependencyManager = class {
1975
+ constructor() {
1976
+ this.subscriptions = [];
1977
+ this.previousDeps = /* @__PURE__ */ new Set();
1978
+ }
1979
+ /**
1980
+ * Check if dependencies have changed
1981
+ */
1982
+ areDependenciesEqual(oldDeps, newDeps) {
1983
+ if (oldDeps.size !== newDeps.size) return false;
1984
+ for (const dep of newDeps) if (!oldDeps.has(dep)) return false;
1985
+ return true;
1986
+ }
1987
+ /**
1988
+ * Update external subscriptions
1989
+ * @param getterTracker - The getter tracker state
1990
+ * @param rawInstance - The raw bloc instance (exclude from subscriptions)
1991
+ * @param onGetterChange - Callback when external getter changes
1992
+ * @returns Whether subscriptions were updated
1993
+ */
1994
+ updateSubscriptions(getterTracker, rawInstance, onGetterChange) {
1995
+ if (!getterTracker?.externalDependencies) return false;
1996
+ const currentDeps = getterTracker.externalDependencies;
1997
+ if (this.areDependenciesEqual(this.previousDeps, currentDeps)) {
1998
+ clearExternalDependencies(getterTracker);
1999
+ return false;
2000
+ }
2001
+ this.cleanup();
2002
+ for (const externalBloc of currentDeps) {
2003
+ if (externalBloc === rawInstance) continue;
2004
+ const unsub = externalBloc.subscribe(() => {
2005
+ invalidateRenderCache(getterTracker);
2006
+ if (hasGetterChanges(rawInstance, getterTracker)) onGetterChange();
2007
+ });
2008
+ this.subscriptions.push(unsub);
2009
+ }
2010
+ this.previousDeps = new Set(currentDeps);
2011
+ clearExternalDependencies(getterTracker);
2012
+ return true;
2013
+ }
2014
+ /**
2015
+ * Cleanup all subscriptions
2016
+ */
2017
+ cleanup() {
2018
+ this.subscriptions.forEach((unsub) => unsub());
2019
+ this.subscriptions = [];
2020
+ }
2021
+ };
1330
2022
  /**
1331
- * Default configuration constants for BlaC
2023
+ * Create a subscription function for auto-tracking mode
1332
2024
  */
1333
- const BLAC_DEFAULTS = {
1334
- CLEANUP_INTERVAL_MS: 3e4,
1335
- WEAKREF_CLEANUP_INTERVAL_MS: 1e4,
1336
- MAX_SUBSCRIPTIONS: 1e3,
1337
- MAX_SUBSCRIPTIONS_HIGH_PERF: 1e4,
1338
- PIPELINE_TIMEOUT_MS: 5e3,
1339
- CLEANUP_INTERVAL_HIGH_PERF_MS: 5e3,
1340
- MAX_PIPELINE_STAGES: 30
1341
- };
2025
+ function createAutoTrackSubscribe(instance, adapterState) {
2026
+ return (callback) => {
2027
+ return instance.subscribe(() => {
2028
+ const tracker = adapterState.tracker || (adapterState.tracker = createTrackerState());
2029
+ const hasStateDeps = tracker.pathCache && tracker.pathCache.size > 0;
2030
+ const hasGetterDeps = adapterState.getterTracker && adapterState.getterTracker.trackedGetters.size > 0;
2031
+ const isPrimitiveState = instance.state !== null && typeof instance.state !== "object" && typeof instance.state !== "function";
2032
+ if (!hasStateDeps && !hasGetterDeps && !isPrimitiveState) return;
2033
+ let stateChanged = hasChanges(tracker, instance.state);
2034
+ if (!hasStateDeps && hasGetterDeps) stateChanged = false;
2035
+ if (stateChanged) {
2036
+ callback();
2037
+ return;
2038
+ }
2039
+ if (hasGetterChanges(instance, adapterState.getterTracker)) callback();
2040
+ });
2041
+ };
2042
+ }
2043
+ /**
2044
+ * Create a subscription function for manual dependencies mode
2045
+ */
2046
+ function createManualDepsSubscribe(instance, adapterState, config) {
2047
+ return (callback) => {
2048
+ return instance.subscribe(() => {
2049
+ const newDeps = config.dependencies(instance.state, instance);
2050
+ if (!adapterState.manualDepsCache || !shallowEqual(adapterState.manualDepsCache, newDeps)) {
2051
+ adapterState.manualDepsCache = newDeps;
2052
+ callback();
2053
+ }
2054
+ });
2055
+ };
2056
+ }
2057
+ /**
2058
+ * Create a subscription function for no-tracking mode
2059
+ */
2060
+ function createNoTrackSubscribe(instance) {
2061
+ return (callback) => instance.subscribe(callback);
2062
+ }
2063
+ /**
2064
+ * Create a snapshot function for auto-tracking mode
2065
+ */
2066
+ function createAutoTrackSnapshot(instance, adapterState) {
2067
+ return () => {
2068
+ const tracker = adapterState.tracker || (adapterState.tracker = createTrackerState());
2069
+ if (hasTrackedData(tracker)) captureTrackedPaths(tracker, instance.state);
2070
+ if (adapterState.getterTracker) {
2071
+ invalidateRenderCache(adapterState.getterTracker);
2072
+ commitTrackedGetters(adapterState.getterTracker);
2073
+ adapterState.getterTracker.isTracking = true;
2074
+ setActiveTracker(instance, adapterState.getterTracker);
2075
+ }
2076
+ startTracking(tracker);
2077
+ return createProxy(tracker, instance.state);
2078
+ };
2079
+ }
2080
+ /**
2081
+ * Create a snapshot function for manual dependencies mode
2082
+ */
2083
+ function createManualDepsSnapshot(instance, adapterState, config) {
2084
+ return () => {
2085
+ adapterState.manualDepsCache = config.dependencies(instance.state, instance);
2086
+ return instance.state;
2087
+ };
2088
+ }
2089
+ /**
2090
+ * Create a snapshot function for no-tracking mode
2091
+ */
2092
+ function createNoTrackSnapshot(instance) {
2093
+ return () => instance.state;
2094
+ }
2095
+ /**
2096
+ * Initialize adapter state for auto-tracking mode
2097
+ */
2098
+ function initAutoTrackState(instance) {
2099
+ return {
2100
+ tracker: null,
2101
+ manualDepsCache: null,
2102
+ getterTracker: createGetterTracker(),
2103
+ proxiedBloc: createBlocProxy(instance)
2104
+ };
2105
+ }
2106
+ /**
2107
+ * Initialize adapter state for manual dependencies mode
2108
+ */
2109
+ function initManualDepsState(instance) {
2110
+ return {
2111
+ tracker: null,
2112
+ manualDepsCache: null,
2113
+ getterTracker: null,
2114
+ proxiedBloc: instance
2115
+ };
2116
+ }
2117
+ /**
2118
+ * Initialize adapter state for no-tracking mode
2119
+ */
2120
+ function initNoTrackState(instance) {
2121
+ return {
2122
+ tracker: null,
2123
+ manualDepsCache: null,
2124
+ getterTracker: null,
2125
+ proxiedBloc: instance
2126
+ };
2127
+ }
2128
+ /**
2129
+ * Disable getter tracking (call after render completes)
2130
+ */
2131
+ function disableGetterTracking(adapterState, rawInstance) {
2132
+ if (adapterState.getterTracker) {
2133
+ adapterState.getterTracker.isTracking = false;
2134
+ clearActiveTracker(rawInstance);
2135
+ }
2136
+ }
1342
2137
 
1343
2138
  //#endregion
1344
2139
  exports.AuthVertex = AuthVertex;
1345
2140
  exports.BLAC_DEFAULTS = BLAC_DEFAULTS;
2141
+ exports.BLAC_ERROR_PREFIX = BLAC_ERROR_PREFIX;
2142
+ exports.BLAC_ID_PATTERNS = BLAC_ID_PATTERNS;
2143
+ exports.BLAC_STATIC_PROPS = BLAC_STATIC_PROPS;
1346
2144
  exports.CounterCubit = CounterCubit;
1347
2145
  exports.CounterVertex = CounterVertex;
1348
2146
  exports.Cubit = Cubit;
1349
2147
  exports.DecrementEvent = DecrementEvent;
2148
+ exports.ExternalDependencyManager = ExternalDependencyManager;
1350
2149
  exports.IncrementEvent = IncrementEvent;
1351
2150
  exports.LogLevel = LogLevel;
1352
2151
  exports.LoginEvent = LoginEvent;
1353
2152
  exports.LogoutEvent = LogoutEvent;
2153
+ exports.PluginManager = PluginManager;
1354
2154
  exports.ProxyTracker = ProxyTracker;
1355
2155
  exports.ResetEvent = ResetEvent;
1356
2156
  exports.StateContainer = StateContainer;
@@ -1359,27 +2159,59 @@ exports.TodoCubit = TodoCubit;
1359
2159
  exports.Vertex = Vertex;
1360
2160
  exports.__resetIdCounters = __resetIdCounters;
1361
2161
  exports.captureTrackedPaths = captureTrackedPaths;
2162
+ exports.clearActiveTracker = clearActiveTracker;
2163
+ exports.clearExternalDependencies = clearExternalDependencies;
2164
+ exports.commitTrackedGetters = commitTrackedGetters;
1362
2165
  exports.configureLogger = configureLogger;
1363
2166
  exports.createArrayProxy = createArrayProxy;
2167
+ exports.createAutoTrackSnapshot = createAutoTrackSnapshot;
2168
+ exports.createAutoTrackSubscribe = createAutoTrackSubscribe;
2169
+ exports.createBlocProxy = createBlocProxy;
2170
+ exports.createGetterTracker = createGetterTracker;
1364
2171
  exports.createIdGenerator = createIdGenerator;
1365
2172
  exports.createLogger = createLogger;
2173
+ exports.createManualDepsSnapshot = createManualDepsSnapshot;
2174
+ exports.createManualDepsSubscribe = createManualDepsSubscribe;
2175
+ exports.createNoTrackSnapshot = createNoTrackSnapshot;
2176
+ exports.createNoTrackSubscribe = createNoTrackSubscribe;
2177
+ exports.createPluginManager = createPluginManager;
1366
2178
  exports.createProxy = createProxy;
1367
2179
  exports.createProxyForTarget = createProxyForTarget;
1368
2180
  exports.createProxyInternal = createProxyInternal;
1369
2181
  exports.createProxyTrackerState = createProxyTrackerState;
1370
2182
  exports.createTrackerState = createTrackerState;
1371
2183
  exports.debug = debug;
2184
+ exports.disableGetterTracking = disableGetterTracking;
1372
2185
  exports.error = error;
1373
2186
  exports.generateId = generateId;
2187
+ exports.generateIsolatedKey = generateIsolatedKey;
1374
2188
  exports.generateSimpleId = generateSimpleId;
2189
+ exports.getActiveTracker = getActiveTracker;
2190
+ exports.getDescriptor = getDescriptor;
2191
+ exports.getGetterExecutionContext = getGetterExecutionContext;
2192
+ exports.getPluginManager = getPluginManager;
2193
+ exports.getStaticProp = getStaticProp;
1375
2194
  exports.getValueAtPath = getValueAtPath;
1376
2195
  exports.globalProxyTracker = globalProxyTracker;
1377
2196
  exports.globalRegistry = globalRegistry;
1378
2197
  exports.hasChanges = hasChanges;
2198
+ exports.hasGetterChanges = hasGetterChanges;
2199
+ exports.hasInitHook = hasInitHook;
1379
2200
  exports.hasTrackedData = hasTrackedData;
1380
2201
  exports.info = info;
2202
+ exports.initAutoTrackState = initAutoTrackState;
2203
+ exports.initManualDepsState = initManualDepsState;
2204
+ exports.initNoTrackState = initNoTrackState;
2205
+ exports.invalidateRenderCache = invalidateRenderCache;
2206
+ exports.isExcludedFromDevTools = isExcludedFromDevTools;
2207
+ exports.isGetter = isGetter;
2208
+ exports.isIsolatedClass = isIsolatedClass;
2209
+ exports.isIsolatedKey = isIsolatedKey;
2210
+ exports.isKeepAliveClass = isKeepAliveClass;
1381
2211
  exports.isProxyable = isProxyable;
1382
2212
  exports.parsePath = parsePath;
2213
+ exports.resetGetterTracker = resetGetterTracker;
2214
+ exports.setActiveTracker = setActiveTracker;
1383
2215
  exports.shallowEqual = shallowEqual;
1384
2216
  exports.startProxyTracking = startProxyTracking;
1385
2217
  exports.startTracking = startTracking;