@blac/core 2.0.0-rc.1 → 2.0.0-rc.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/adapter/framework-adapter.d.ts +33 -0
- package/dist/adapter/index.d.ts +7 -2
- package/dist/constants.d.ts +82 -0
- package/dist/core/Cubit.d.ts +40 -0
- package/dist/core/StateContainer.d.ts +78 -0
- package/dist/core/StateContainerRegistry.d.ts +119 -0
- package/dist/core/Vertex.d.ts +74 -0
- package/dist/decorators/blac.d.ts +31 -0
- package/dist/decorators/index.d.ts +2 -0
- package/dist/index.cjs +1870 -38
- package/dist/index.d.cts +21 -0
- package/dist/index.d.ts +19 -15
- package/dist/index.js +1789 -37
- package/dist/logging/Logger.d.ts +57 -0
- package/dist/plugin/BlacPlugin.d.ts +46 -0
- package/dist/plugin/PluginManager.d.ts +38 -0
- package/dist/registry/index.d.ts +20 -0
- package/dist/test-utils/index.d.ts +43 -0
- package/dist/tracking/dependency-tracker.d.ts +21 -0
- package/dist/tracking/getter-tracker.d.ts +31 -0
- package/dist/tracking/index.d.ts +38 -0
- package/dist/tracking/path-utils.d.ts +39 -0
- package/dist/tracking/proxy-tracker.d.ts +88 -0
- package/dist/types/branded.d.ts +9 -0
- package/dist/types/events.d.ts +13 -0
- package/dist/types/utilities.d.ts +19 -0
- package/dist/utils/equality.d.ts +9 -0
- package/dist/utils/idGenerator.d.ts +76 -0
- package/dist/utils/immutable.d.ts +16 -0
- package/dist/utils/stack-trace.d.ts +19 -0
- package/dist/utils/static-props.d.ts +29 -0
- package/package.json +31 -13
- package/dist/.tsbuildinfo +0 -1
- package/dist/Blac.d.ts +0 -246
- package/dist/Blac.d.ts.map +0 -1
- package/dist/Blac.js +0 -624
- package/dist/Blac.js.map +0 -1
- package/dist/Bloc.d.ts +0 -41
- package/dist/Bloc.d.ts.map +0 -1
- package/dist/Bloc.js +0 -140
- package/dist/Bloc.js.map +0 -1
- package/dist/BlocBase.d.ts +0 -110
- package/dist/BlocBase.d.ts.map +0 -1
- package/dist/BlocBase.js +0 -287
- package/dist/BlocBase.js.map +0 -1
- package/dist/Cubit.d.ts +0 -25
- package/dist/Cubit.d.ts.map +0 -1
- package/dist/Cubit.js +0 -64
- package/dist/Cubit.js.map +0 -1
- package/dist/adapter/BlacAdapter.d.ts +0 -67
- package/dist/adapter/BlacAdapter.d.ts.map +0 -1
- package/dist/adapter/BlacAdapter.js +0 -263
- package/dist/adapter/BlacAdapter.js.map +0 -1
- package/dist/adapter/ProxyFactory.d.ts +0 -50
- package/dist/adapter/ProxyFactory.d.ts.map +0 -1
- package/dist/adapter/ProxyFactory.js +0 -150
- package/dist/adapter/ProxyFactory.js.map +0 -1
- package/dist/adapter/index.d.ts.map +0 -1
- package/dist/adapter/index.js +0 -19
- package/dist/adapter/index.js.map +0 -1
- package/dist/errors/BlacError.d.ts +0 -38
- package/dist/errors/BlacError.d.ts.map +0 -1
- package/dist/errors/BlacError.js +0 -55
- package/dist/errors/BlacError.js.map +0 -1
- package/dist/errors/ErrorManager.d.ts +0 -39
- package/dist/errors/ErrorManager.d.ts.map +0 -1
- package/dist/errors/ErrorManager.js +0 -91
- package/dist/errors/ErrorManager.js.map +0 -1
- package/dist/errors/handleError.d.ts +0 -24
- package/dist/errors/handleError.d.ts.map +0 -1
- package/dist/errors/handleError.js +0 -62
- package/dist/errors/handleError.js.map +0 -1
- package/dist/errors/index.d.ts +0 -5
- package/dist/errors/index.d.ts.map +0 -1
- package/dist/errors/index.js +0 -13
- package/dist/errors/index.js.map +0 -1
- package/dist/events.d.ts +0 -11
- package/dist/events.d.ts.map +0 -1
- package/dist/events.js +0 -17
- package/dist/events.js.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/lifecycle/BlocLifecycle.d.ts +0 -51
- package/dist/lifecycle/BlocLifecycle.d.ts.map +0 -1
- package/dist/lifecycle/BlocLifecycle.js +0 -123
- package/dist/lifecycle/BlocLifecycle.js.map +0 -1
- package/dist/lifecycle/index.d.ts +0 -2
- package/dist/lifecycle/index.d.ts.map +0 -1
- package/dist/lifecycle/index.js +0 -18
- package/dist/lifecycle/index.js.map +0 -1
- package/dist/plugins/BlocPluginRegistry.d.ts +0 -58
- package/dist/plugins/BlocPluginRegistry.d.ts.map +0 -1
- package/dist/plugins/BlocPluginRegistry.js +0 -201
- package/dist/plugins/BlocPluginRegistry.js.map +0 -1
- package/dist/plugins/SystemPluginRegistry.d.ts +0 -79
- package/dist/plugins/SystemPluginRegistry.d.ts.map +0 -1
- package/dist/plugins/SystemPluginRegistry.js +0 -184
- package/dist/plugins/SystemPluginRegistry.js.map +0 -1
- package/dist/plugins/index.d.ts +0 -4
- package/dist/plugins/index.d.ts.map +0 -1
- package/dist/plugins/index.js +0 -26
- package/dist/plugins/index.js.map +0 -1
- package/dist/plugins/types.d.ts +0 -103
- package/dist/plugins/types.d.ts.map +0 -1
- package/dist/plugins/types.js +0 -3
- package/dist/plugins/types.js.map +0 -1
- package/dist/subscription/SubscriptionManager.d.ts +0 -67
- package/dist/subscription/SubscriptionManager.d.ts.map +0 -1
- package/dist/subscription/SubscriptionManager.js +0 -317
- package/dist/subscription/SubscriptionManager.js.map +0 -1
- package/dist/subscription/index.d.ts +0 -3
- package/dist/subscription/index.d.ts.map +0 -1
- package/dist/subscription/index.js +0 -19
- package/dist/subscription/index.js.map +0 -1
- package/dist/subscription/types.d.ts +0 -65
- package/dist/subscription/types.d.ts.map +0 -1
- package/dist/subscription/types.js +0 -3
- package/dist/subscription/types.js.map +0 -1
- package/dist/testing.d.ts +0 -33
- package/dist/testing.d.ts.map +0 -1
- package/dist/testing.js +0 -68
- package/dist/testing.js.map +0 -1
- package/dist/types.d.ts +0 -57
- package/dist/types.d.ts.map +0 -1
- package/dist/types.js +0 -3
- package/dist/types.js.map +0 -1
- package/dist/utils/BatchingManager.d.ts +0 -28
- package/dist/utils/BatchingManager.d.ts.map +0 -1
- package/dist/utils/BatchingManager.js +0 -48
- package/dist/utils/BatchingManager.js.map +0 -1
- package/dist/utils/RerenderLogger.d.ts +0 -60
- package/dist/utils/RerenderLogger.d.ts.map +0 -1
- package/dist/utils/RerenderLogger.js +0 -205
- package/dist/utils/RerenderLogger.js.map +0 -1
- package/dist/utils/generateInstanceId.d.ts +0 -11
- package/dist/utils/generateInstanceId.d.ts.map +0 -1
- package/dist/utils/generateInstanceId.js +0 -50
- package/dist/utils/generateInstanceId.js.map +0 -1
- package/dist/utils/shallowEqual.d.ts +0 -9
- package/dist/utils/shallowEqual.d.ts.map +0 -1
- package/dist/utils/shallowEqual.js +0 -35
- package/dist/utils/shallowEqual.js.map +0 -1
- package/dist/utils/uuid.d.ts +0 -10
- package/dist/utils/uuid.d.ts.map +0 -1
- package/dist/utils/uuid.js +0 -24
- package/dist/utils/uuid.js.map +0 -1
package/dist/index.cjs
CHANGED
|
@@ -1,38 +1,1870 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
1
|
+
|
|
2
|
+
//#region src/utils/idGenerator.ts
|
|
3
|
+
/**
|
|
4
|
+
* Centralized ID Generation
|
|
5
|
+
*
|
|
6
|
+
* Provides consistent, collision-resistant ID generation for all BlaC subsystems.
|
|
7
|
+
* Uses timestamp + counter + random suffix for uniqueness.
|
|
8
|
+
*/
|
|
9
|
+
const globalCounters = /* @__PURE__ */ new Map();
|
|
10
|
+
/**
|
|
11
|
+
* Creates an ID generator with isolated counter state
|
|
12
|
+
*
|
|
13
|
+
* @param prefix - Prefix for generated IDs
|
|
14
|
+
* @returns Object with next(), nextSimple(), and reset() methods
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* ```ts
|
|
18
|
+
* const generator = createIdGenerator('sub');
|
|
19
|
+
* const id1 = generator.next(); // "sub:1698765432100_1_a3k9d7f2q"
|
|
20
|
+
* const id2 = generator.next(); // "sub:1698765432101_2_b4n8e9g3r"
|
|
21
|
+
* ```
|
|
22
|
+
*/
|
|
23
|
+
function createIdGenerator(prefix) {
|
|
24
|
+
let counter = 0;
|
|
25
|
+
return {
|
|
26
|
+
next: () => `${prefix}:${Date.now()}_${++counter}_${Math.random().toString(36).substring(2, 11)}`,
|
|
27
|
+
nextSimple: () => `${prefix}:${++counter}`,
|
|
28
|
+
reset: () => {
|
|
29
|
+
counter = 0;
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Generate ID with timestamp, counter, and random suffix (tree-shakeable)
|
|
35
|
+
*
|
|
36
|
+
* Format: `${prefix}:${timestamp}_${counter}_${random}`
|
|
37
|
+
*
|
|
38
|
+
* @param prefix - Prefix for the ID (e.g., 'sub', 'consumer', 'stage')
|
|
39
|
+
* @returns Branded ID string
|
|
40
|
+
*
|
|
41
|
+
* @example
|
|
42
|
+
* ```ts
|
|
43
|
+
* const id = generateId('sub');
|
|
44
|
+
* // Returns: "sub:1698765432100_1_a3k9d7f2q"
|
|
45
|
+
* ```
|
|
46
|
+
*/
|
|
47
|
+
function generateId(prefix) {
|
|
48
|
+
const counter = (globalCounters.get(prefix) ?? 0) + 1;
|
|
49
|
+
globalCounters.set(prefix, counter);
|
|
50
|
+
return `${prefix}:${Date.now()}_${counter}_${Math.random().toString(36).substring(2, 11)}`;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Generate simple ID with timestamp and random (no counter tracking)
|
|
54
|
+
*
|
|
55
|
+
* Format: `${prefix}:${timestamp}_${random}`
|
|
56
|
+
*
|
|
57
|
+
* @param prefix - Prefix for the ID
|
|
58
|
+
* @returns Branded ID string
|
|
59
|
+
*
|
|
60
|
+
* @example
|
|
61
|
+
* ```ts
|
|
62
|
+
* const id = generateSimpleId('CounterBloc');
|
|
63
|
+
* // Returns: "CounterBloc:1698765432100_a3k9d7f2q"
|
|
64
|
+
* ```
|
|
65
|
+
*/
|
|
66
|
+
function generateSimpleId(prefix, affix) {
|
|
67
|
+
if (affix) return `${prefix}:${affix}`;
|
|
68
|
+
return `${prefix}:${Date.now()}_${Math.random().toString(36).substring(2, 11)}`;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Reset all counters for testing purposes
|
|
72
|
+
* @internal
|
|
73
|
+
*/
|
|
74
|
+
function __resetIdCounters() {
|
|
75
|
+
globalCounters.clear();
|
|
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
|
+
var PluginManager = class {
|
|
101
|
+
constructor(registry) {
|
|
102
|
+
this.plugins = /* @__PURE__ */ new Map();
|
|
103
|
+
this.registry = registry;
|
|
104
|
+
this.setupLifecycleHooks();
|
|
105
|
+
}
|
|
106
|
+
install(plugin, config = {}) {
|
|
107
|
+
const effectiveConfig = {
|
|
108
|
+
enabled: true,
|
|
109
|
+
environment: "all",
|
|
110
|
+
...config
|
|
111
|
+
};
|
|
112
|
+
if (!this.shouldEnablePlugin(effectiveConfig)) {
|
|
113
|
+
console.log(`[BlaC] Plugin "${plugin.name}" skipped (environment mismatch)`);
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
if (this.plugins.has(plugin.name)) throw new Error(`Plugin "${plugin.name}" is already installed`);
|
|
117
|
+
const context = this.createPluginContext();
|
|
118
|
+
this.plugins.set(plugin.name, {
|
|
119
|
+
plugin,
|
|
120
|
+
config: effectiveConfig,
|
|
121
|
+
context
|
|
122
|
+
});
|
|
123
|
+
if (plugin.onInstall) try {
|
|
124
|
+
plugin.onInstall(context);
|
|
125
|
+
} catch (error$1) {
|
|
126
|
+
console.error(`[BlaC] Error installing plugin "${plugin.name}":`, error$1);
|
|
127
|
+
this.plugins.delete(plugin.name);
|
|
128
|
+
throw error$1;
|
|
129
|
+
}
|
|
130
|
+
console.log(`[BlaC] Plugin "${plugin.name}" v${plugin.version} installed`);
|
|
131
|
+
}
|
|
132
|
+
uninstall(pluginName) {
|
|
133
|
+
const installed = this.plugins.get(pluginName);
|
|
134
|
+
if (!installed) throw new Error(`Plugin "${pluginName}" is not installed`);
|
|
135
|
+
if (installed.plugin.onUninstall) try {
|
|
136
|
+
installed.plugin.onUninstall();
|
|
137
|
+
} catch (error$1) {
|
|
138
|
+
console.error(`[BlaC] Error uninstalling plugin "${pluginName}":`, error$1);
|
|
139
|
+
}
|
|
140
|
+
this.plugins.delete(pluginName);
|
|
141
|
+
console.log(`[BlaC] Plugin "${pluginName}" uninstalled`);
|
|
142
|
+
}
|
|
143
|
+
getPlugin(pluginName) {
|
|
144
|
+
return this.plugins.get(pluginName)?.plugin;
|
|
145
|
+
}
|
|
146
|
+
getAllPlugins() {
|
|
147
|
+
return Array.from(this.plugins.values()).map((p) => p.plugin);
|
|
148
|
+
}
|
|
149
|
+
hasPlugin(pluginName) {
|
|
150
|
+
return this.plugins.has(pluginName);
|
|
151
|
+
}
|
|
152
|
+
clear() {
|
|
153
|
+
for (const name of this.plugins.keys()) this.uninstall(name);
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* Setup lifecycle hooks to notify plugins
|
|
157
|
+
*/
|
|
158
|
+
setupLifecycleHooks() {
|
|
159
|
+
this.registry.on("created", (instance) => {
|
|
160
|
+
this.notifyPlugins("onInstanceCreated", instance);
|
|
161
|
+
});
|
|
162
|
+
this.registry.on("stateChanged", (instance, previousState, currentState, callstack) => {
|
|
163
|
+
this.notifyPlugins("onStateChanged", instance, previousState, currentState, callstack);
|
|
164
|
+
});
|
|
165
|
+
this.registry.on("eventAdded", (vertex, event) => {
|
|
166
|
+
this.notifyPlugins("onEventAdded", vertex, event);
|
|
167
|
+
});
|
|
168
|
+
this.registry.on("disposed", (instance) => {
|
|
169
|
+
this.notifyPlugins("onInstanceDisposed", instance);
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* Notify all plugins of a lifecycle event
|
|
174
|
+
*/
|
|
175
|
+
notifyPlugins(hookName, ...args) {
|
|
176
|
+
for (const { plugin, config, context } of this.plugins.values()) {
|
|
177
|
+
if (!config.enabled) continue;
|
|
178
|
+
const hook = plugin[hookName];
|
|
179
|
+
if (typeof hook === "function") try {
|
|
180
|
+
hook.apply(plugin, [...args, context]);
|
|
181
|
+
} catch (error$1) {
|
|
182
|
+
console.error(`[BlaC] Error in plugin "${plugin.name}" ${hookName}:`, error$1);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
/**
|
|
187
|
+
* Create plugin context with safe API access
|
|
188
|
+
*/
|
|
189
|
+
createPluginContext() {
|
|
190
|
+
return {
|
|
191
|
+
getInstanceMetadata: (instance) => {
|
|
192
|
+
return {
|
|
193
|
+
id: instance.instanceId,
|
|
194
|
+
className: instance.constructor.name,
|
|
195
|
+
isDisposed: instance.isDisposed,
|
|
196
|
+
name: instance.name,
|
|
197
|
+
lastStateChangeTimestamp: instance.lastUpdateTimestamp,
|
|
198
|
+
createdAt: instance.createdAt,
|
|
199
|
+
state: instance.state,
|
|
200
|
+
isIsolated: instance.instanceId.startsWith("isolated-")
|
|
201
|
+
};
|
|
202
|
+
},
|
|
203
|
+
getState: (instance) => {
|
|
204
|
+
return instance.state;
|
|
205
|
+
},
|
|
206
|
+
queryInstances: (typeClass) => {
|
|
207
|
+
return typeClass.getAll();
|
|
208
|
+
},
|
|
209
|
+
getAllTypes: () => {
|
|
210
|
+
return this.registry.getTypes();
|
|
211
|
+
},
|
|
212
|
+
getStats: () => {
|
|
213
|
+
return this.registry.getStats();
|
|
214
|
+
}
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
/**
|
|
218
|
+
* Check if plugin should be enabled based on environment
|
|
219
|
+
*/
|
|
220
|
+
shouldEnablePlugin(config) {
|
|
221
|
+
if (!config.enabled) return false;
|
|
222
|
+
if (config.environment === "all") return true;
|
|
223
|
+
return this.getCurrentEnvironment() === config.environment;
|
|
224
|
+
}
|
|
225
|
+
/**
|
|
226
|
+
* Get current environment
|
|
227
|
+
*/
|
|
228
|
+
getCurrentEnvironment() {
|
|
229
|
+
if (typeof process !== "undefined") {
|
|
230
|
+
if (process.env.NODE_ENV === "test") return "test";
|
|
231
|
+
if (process.env.NODE_ENV === "production") return "production";
|
|
232
|
+
return "development";
|
|
233
|
+
}
|
|
234
|
+
return "development";
|
|
235
|
+
}
|
|
236
|
+
};
|
|
237
|
+
/**
|
|
238
|
+
* Create a plugin manager instance
|
|
239
|
+
*/
|
|
240
|
+
function createPluginManager(registry) {
|
|
241
|
+
return new PluginManager(registry);
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
//#endregion
|
|
245
|
+
//#region src/constants.ts
|
|
246
|
+
/**
|
|
247
|
+
* Default configuration constants for BlaC
|
|
248
|
+
*
|
|
249
|
+
* Centralized location for all magic numbers and default values.
|
|
250
|
+
*/
|
|
251
|
+
/**
|
|
252
|
+
* Default configuration constants for BlaC
|
|
253
|
+
*/
|
|
254
|
+
const BLAC_DEFAULTS = {
|
|
255
|
+
DEFAULT_INSTANCE_KEY: "default",
|
|
256
|
+
MAX_GETTER_DEPTH: 10,
|
|
257
|
+
CLEANUP_INTERVAL_MS: 3e4,
|
|
258
|
+
WEAKREF_CLEANUP_INTERVAL_MS: 1e4,
|
|
259
|
+
MAX_SUBSCRIPTIONS: 1e3,
|
|
260
|
+
MAX_SUBSCRIPTIONS_HIGH_PERF: 1e4,
|
|
261
|
+
PIPELINE_TIMEOUT_MS: 5e3,
|
|
262
|
+
CLEANUP_INTERVAL_HIGH_PERF_MS: 5e3,
|
|
263
|
+
MAX_PIPELINE_STAGES: 30
|
|
264
|
+
};
|
|
265
|
+
/**
|
|
266
|
+
* Static property names for StateContainer classes
|
|
267
|
+
* Used for feature flags and configuration on bloc classes
|
|
268
|
+
*/
|
|
269
|
+
const BLAC_STATIC_PROPS = {
|
|
270
|
+
ISOLATED: "isolated",
|
|
271
|
+
KEEP_ALIVE: "keepAlive",
|
|
272
|
+
EXCLUDE_FROM_DEVTOOLS: "__excludeFromDevTools"
|
|
273
|
+
};
|
|
274
|
+
/**
|
|
275
|
+
* ID generation patterns and constants
|
|
276
|
+
*/
|
|
277
|
+
const BLAC_ID_PATTERNS = {
|
|
278
|
+
ISOLATED_PREFIX: "isolated-",
|
|
279
|
+
ID_LENGTH: 9
|
|
280
|
+
};
|
|
281
|
+
/**
|
|
282
|
+
* Standard error message prefix
|
|
283
|
+
*/
|
|
284
|
+
const BLAC_ERROR_PREFIX = "[BlaC]";
|
|
285
|
+
|
|
286
|
+
//#endregion
|
|
287
|
+
//#region src/tracking/getter-tracker.ts
|
|
288
|
+
const descriptorCache = /* @__PURE__ */ new WeakMap();
|
|
289
|
+
const blocProxyCache = /* @__PURE__ */ new WeakMap();
|
|
290
|
+
const activeTrackerMap = /* @__PURE__ */ new WeakMap();
|
|
291
|
+
const getterExecutionContext = {
|
|
292
|
+
tracker: null,
|
|
293
|
+
currentBloc: null,
|
|
294
|
+
depth: 0,
|
|
295
|
+
visitedBlocs: /* @__PURE__ */ new Set()
|
|
296
|
+
};
|
|
297
|
+
const MAX_GETTER_DEPTH = BLAC_DEFAULTS.MAX_GETTER_DEPTH;
|
|
298
|
+
function getGetterExecutionContext() {
|
|
299
|
+
return getterExecutionContext;
|
|
300
|
+
}
|
|
301
|
+
function getDescriptor(obj, prop) {
|
|
302
|
+
const constructor = obj.constructor;
|
|
303
|
+
let constructorCache = descriptorCache.get(constructor);
|
|
304
|
+
if (constructorCache?.has(prop)) return constructorCache.get(prop);
|
|
305
|
+
let current = obj;
|
|
306
|
+
let descriptor;
|
|
307
|
+
while (current && current !== Object.prototype) {
|
|
308
|
+
descriptor = Object.getOwnPropertyDescriptor(current, prop);
|
|
309
|
+
if (descriptor) break;
|
|
310
|
+
current = Object.getPrototypeOf(current);
|
|
311
|
+
}
|
|
312
|
+
if (!constructorCache) {
|
|
313
|
+
constructorCache = /* @__PURE__ */ new Map();
|
|
314
|
+
descriptorCache.set(constructor, constructorCache);
|
|
315
|
+
}
|
|
316
|
+
constructorCache.set(prop, descriptor);
|
|
317
|
+
return descriptor;
|
|
318
|
+
}
|
|
319
|
+
function isGetter(obj, prop) {
|
|
320
|
+
return getDescriptor(obj, prop)?.get !== void 0;
|
|
321
|
+
}
|
|
322
|
+
function createGetterTracker() {
|
|
323
|
+
return {
|
|
324
|
+
trackedValues: /* @__PURE__ */ new Map(),
|
|
325
|
+
currentlyAccessing: /* @__PURE__ */ new Set(),
|
|
326
|
+
trackedGetters: /* @__PURE__ */ new Set(),
|
|
327
|
+
isTracking: false,
|
|
328
|
+
renderCache: /* @__PURE__ */ new Map(),
|
|
329
|
+
cacheValid: false,
|
|
330
|
+
externalDependencies: /* @__PURE__ */ new Set()
|
|
331
|
+
};
|
|
332
|
+
}
|
|
333
|
+
function setActiveTracker(bloc, tracker) {
|
|
334
|
+
activeTrackerMap.set(bloc, tracker);
|
|
335
|
+
}
|
|
336
|
+
function clearActiveTracker(bloc) {
|
|
337
|
+
activeTrackerMap.delete(bloc);
|
|
338
|
+
}
|
|
339
|
+
function getActiveTracker(bloc) {
|
|
340
|
+
return activeTrackerMap.get(bloc);
|
|
341
|
+
}
|
|
342
|
+
function commitTrackedGetters(tracker) {
|
|
343
|
+
if (tracker.currentlyAccessing.size > 0) tracker.trackedGetters = new Set(tracker.currentlyAccessing);
|
|
344
|
+
tracker.currentlyAccessing.clear();
|
|
345
|
+
}
|
|
346
|
+
function createBlocProxy(bloc) {
|
|
347
|
+
const cached = blocProxyCache.get(bloc);
|
|
348
|
+
if (cached) return cached;
|
|
349
|
+
const proxy = new Proxy(bloc, { get(target, prop, receiver) {
|
|
350
|
+
const tracker = activeTrackerMap.get(target);
|
|
351
|
+
if (tracker?.isTracking && isGetter(target, prop)) {
|
|
352
|
+
tracker.currentlyAccessing.add(prop);
|
|
353
|
+
if (tracker.cacheValid && tracker.renderCache.has(prop)) {
|
|
354
|
+
const cachedValue = tracker.renderCache.get(prop);
|
|
355
|
+
tracker.trackedValues.set(prop, cachedValue);
|
|
356
|
+
return cachedValue;
|
|
357
|
+
}
|
|
358
|
+
if (getterExecutionContext.depth >= MAX_GETTER_DEPTH) {
|
|
359
|
+
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.`);
|
|
360
|
+
return;
|
|
361
|
+
}
|
|
362
|
+
if (getterExecutionContext.visitedBlocs.has(target)) {
|
|
363
|
+
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.`);
|
|
364
|
+
return;
|
|
365
|
+
}
|
|
366
|
+
const prevContext = {
|
|
367
|
+
tracker: getterExecutionContext.tracker,
|
|
368
|
+
currentBloc: getterExecutionContext.currentBloc,
|
|
369
|
+
depth: getterExecutionContext.depth,
|
|
370
|
+
visitedBlocs: new Set(getterExecutionContext.visitedBlocs)
|
|
371
|
+
};
|
|
372
|
+
getterExecutionContext.tracker = tracker;
|
|
373
|
+
getterExecutionContext.currentBloc = target;
|
|
374
|
+
getterExecutionContext.depth++;
|
|
375
|
+
getterExecutionContext.visitedBlocs.add(target);
|
|
376
|
+
try {
|
|
377
|
+
const value = getDescriptor(target, prop).get.call(target);
|
|
378
|
+
tracker.trackedValues.set(prop, value);
|
|
379
|
+
return value;
|
|
380
|
+
} catch (error$1) {
|
|
381
|
+
tracker.currentlyAccessing.delete(prop);
|
|
382
|
+
throw error$1;
|
|
383
|
+
} finally {
|
|
384
|
+
getterExecutionContext.tracker = prevContext.tracker;
|
|
385
|
+
getterExecutionContext.currentBloc = prevContext.currentBloc;
|
|
386
|
+
getterExecutionContext.depth = prevContext.depth;
|
|
387
|
+
getterExecutionContext.visitedBlocs = prevContext.visitedBlocs;
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
return Reflect.get(target, prop, receiver);
|
|
391
|
+
} });
|
|
392
|
+
blocProxyCache.set(bloc, proxy);
|
|
393
|
+
return proxy;
|
|
394
|
+
}
|
|
395
|
+
function hasGetterChanges(bloc, tracker) {
|
|
396
|
+
if (!tracker || tracker.trackedGetters.size === 0) return false;
|
|
397
|
+
tracker.renderCache.clear();
|
|
398
|
+
let hasAnyChange = false;
|
|
399
|
+
for (const prop of tracker.trackedGetters) try {
|
|
400
|
+
const descriptor = getDescriptor(bloc, prop);
|
|
401
|
+
if (!descriptor?.get) continue;
|
|
402
|
+
const newValue = descriptor.get.call(bloc);
|
|
403
|
+
const oldValue = tracker.trackedValues.get(prop);
|
|
404
|
+
tracker.renderCache.set(prop, newValue);
|
|
405
|
+
tracker.trackedValues.set(prop, newValue);
|
|
406
|
+
if (!Object.is(newValue, oldValue)) hasAnyChange = true;
|
|
407
|
+
} catch (error$1) {
|
|
408
|
+
console.warn(`${BLAC_ERROR_PREFIX} Getter "${String(prop)}" threw error during change detection. Stopping tracking for this getter.`, error$1);
|
|
409
|
+
tracker.trackedGetters.delete(prop);
|
|
410
|
+
tracker.trackedValues.delete(prop);
|
|
411
|
+
tracker.cacheValid = false;
|
|
412
|
+
return true;
|
|
413
|
+
}
|
|
414
|
+
tracker.cacheValid = true;
|
|
415
|
+
return hasAnyChange;
|
|
416
|
+
}
|
|
417
|
+
function invalidateRenderCache(tracker) {
|
|
418
|
+
tracker.cacheValid = false;
|
|
419
|
+
}
|
|
420
|
+
function clearExternalDependencies(tracker) {
|
|
421
|
+
tracker.externalDependencies.clear();
|
|
422
|
+
}
|
|
423
|
+
function resetGetterTracker(tracker) {
|
|
424
|
+
tracker.trackedValues.clear();
|
|
425
|
+
tracker.currentlyAccessing.clear();
|
|
426
|
+
tracker.trackedGetters.clear();
|
|
427
|
+
tracker.renderCache.clear();
|
|
428
|
+
tracker.cacheValid = false;
|
|
429
|
+
tracker.isTracking = false;
|
|
430
|
+
tracker.externalDependencies.clear();
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
//#endregion
|
|
434
|
+
//#region src/utils/static-props.ts
|
|
435
|
+
/**
|
|
436
|
+
* Utility functions for accessing static properties on StateContainer classes
|
|
437
|
+
*/
|
|
438
|
+
/**
|
|
439
|
+
* Get a static property from a class constructor
|
|
440
|
+
* Type-safe helper that avoids (Type as any) casts
|
|
441
|
+
*
|
|
442
|
+
* @param Type - The class constructor
|
|
443
|
+
* @param propName - The property name to access
|
|
444
|
+
* @param defaultValue - Optional default value if property is undefined
|
|
445
|
+
* @returns The property value or default
|
|
446
|
+
*/
|
|
447
|
+
function getStaticProp(Type, propName, defaultValue) {
|
|
448
|
+
return Type[propName] ?? defaultValue;
|
|
449
|
+
}
|
|
450
|
+
/**
|
|
451
|
+
* Check if a class is marked as isolated
|
|
452
|
+
* Isolated classes create separate instances per component
|
|
453
|
+
*/
|
|
454
|
+
function isIsolatedClass(Type) {
|
|
455
|
+
return getStaticProp(Type, BLAC_STATIC_PROPS.ISOLATED) === true;
|
|
456
|
+
}
|
|
457
|
+
/**
|
|
458
|
+
* Check if a class is marked as keepAlive
|
|
459
|
+
* KeepAlive classes are never auto-disposed when ref count reaches 0
|
|
460
|
+
*/
|
|
461
|
+
function isKeepAliveClass(Type) {
|
|
462
|
+
return getStaticProp(Type, BLAC_STATIC_PROPS.KEEP_ALIVE) === true;
|
|
463
|
+
}
|
|
464
|
+
/**
|
|
465
|
+
* Check if a class should be excluded from DevTools
|
|
466
|
+
* Used to prevent infinite loops when DevTools tracks itself
|
|
467
|
+
*/
|
|
468
|
+
function isExcludedFromDevTools(Type) {
|
|
469
|
+
return getStaticProp(Type, BLAC_STATIC_PROPS.EXCLUDE_FROM_DEVTOOLS) === true;
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
//#endregion
|
|
473
|
+
//#region src/core/StateContainerRegistry.ts
|
|
474
|
+
var StateContainerRegistry = class {
|
|
475
|
+
constructor() {
|
|
476
|
+
this.instancesByConstructor = /* @__PURE__ */ new WeakMap();
|
|
477
|
+
this.types = /* @__PURE__ */ new Set();
|
|
478
|
+
this.typeConfigs = /* @__PURE__ */ new Map();
|
|
479
|
+
this.listeners = /* @__PURE__ */ new Map();
|
|
480
|
+
}
|
|
481
|
+
registerType(constructor) {
|
|
482
|
+
this.types.add(constructor);
|
|
483
|
+
}
|
|
484
|
+
register(constructor, isolated = false) {
|
|
485
|
+
const className = constructor.name;
|
|
486
|
+
if (!isolated && isIsolatedClass(constructor)) isolated = true;
|
|
487
|
+
if (this.typeConfigs.has(className)) throw new Error(`${BLAC_ERROR_PREFIX} Type "${className}" is already registered`);
|
|
488
|
+
this.typeConfigs.set(className, { isolated });
|
|
489
|
+
this.registerType(constructor);
|
|
490
|
+
}
|
|
491
|
+
ensureInstancesMap(Type) {
|
|
492
|
+
let instances = this.instancesByConstructor.get(Type);
|
|
493
|
+
if (!instances) {
|
|
494
|
+
instances = /* @__PURE__ */ new Map();
|
|
495
|
+
this.instancesByConstructor.set(Type, instances);
|
|
496
|
+
}
|
|
497
|
+
return instances;
|
|
498
|
+
}
|
|
499
|
+
/**
|
|
500
|
+
* Get the instances Map for a specific class (public API for stats/debugging)
|
|
501
|
+
*/
|
|
502
|
+
getInstancesMap(Type) {
|
|
503
|
+
return this.instancesByConstructor.get(Type) || /* @__PURE__ */ new Map();
|
|
504
|
+
}
|
|
505
|
+
/**
|
|
506
|
+
* Resolve an instance with ref counting (ownership semantics)
|
|
507
|
+
*/
|
|
508
|
+
resolve(Type, instanceKey = BLAC_DEFAULTS.DEFAULT_INSTANCE_KEY, constructorArgs) {
|
|
509
|
+
const registryConfig = this.typeConfigs.get(Type.name);
|
|
510
|
+
const isolated = isIsolatedClass(Type) || registryConfig?.isolated === true;
|
|
511
|
+
const config = { instanceId: instanceKey };
|
|
512
|
+
if (isolated) {
|
|
513
|
+
const instance$1 = new Type(constructorArgs);
|
|
514
|
+
instance$1.initConfig(config);
|
|
515
|
+
this.registerType(Type);
|
|
516
|
+
return instance$1;
|
|
517
|
+
}
|
|
518
|
+
const instances = this.ensureInstancesMap(Type);
|
|
519
|
+
const entry = instances.get(instanceKey);
|
|
520
|
+
if (entry) {
|
|
521
|
+
entry.refCount++;
|
|
522
|
+
return entry.instance;
|
|
523
|
+
}
|
|
524
|
+
const instance = new Type(constructorArgs);
|
|
525
|
+
instance.initConfig(config);
|
|
526
|
+
instances.set(instanceKey, {
|
|
527
|
+
instance,
|
|
528
|
+
refCount: 1
|
|
529
|
+
});
|
|
530
|
+
this.registerType(Type);
|
|
531
|
+
return instance;
|
|
532
|
+
}
|
|
533
|
+
/**
|
|
534
|
+
* Get an existing instance without ref counting (borrowing semantics)
|
|
535
|
+
* @throws Error if instance doesn't exist
|
|
536
|
+
*/
|
|
537
|
+
get(Type, instanceKey = BLAC_DEFAULTS.DEFAULT_INSTANCE_KEY) {
|
|
538
|
+
const entry = this.ensureInstancesMap(Type).get(instanceKey);
|
|
539
|
+
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.`);
|
|
540
|
+
const context = getGetterExecutionContext();
|
|
541
|
+
if (context.tracker && context.currentBloc && context.currentBloc !== entry.instance) context.tracker.externalDependencies.add(entry.instance);
|
|
542
|
+
return entry.instance;
|
|
543
|
+
}
|
|
544
|
+
/**
|
|
545
|
+
* Safely get an existing instance (borrowing semantics with error handling)
|
|
546
|
+
* Returns discriminated union for type-safe conditional access
|
|
547
|
+
*/
|
|
548
|
+
getSafe(Type, instanceKey = BLAC_DEFAULTS.DEFAULT_INSTANCE_KEY) {
|
|
549
|
+
const entry = this.ensureInstancesMap(Type).get(instanceKey);
|
|
550
|
+
if (!entry) return {
|
|
551
|
+
error: /* @__PURE__ */ new Error(`${BLAC_ERROR_PREFIX} ${Type.name} instance "${instanceKey}" not found`),
|
|
552
|
+
instance: null
|
|
553
|
+
};
|
|
554
|
+
const context = getGetterExecutionContext();
|
|
555
|
+
if (context.tracker && context.currentBloc && context.currentBloc !== entry.instance) context.tracker.externalDependencies.add(entry.instance);
|
|
556
|
+
return {
|
|
557
|
+
error: null,
|
|
558
|
+
instance: entry.instance
|
|
559
|
+
};
|
|
560
|
+
}
|
|
561
|
+
/**
|
|
562
|
+
* Connect to an instance with borrowing semantics (for B2B communication)
|
|
563
|
+
* Gets existing instance OR creates it if it doesn't exist, without incrementing ref count.
|
|
564
|
+
* Tracks cross-bloc dependency for reactive updates.
|
|
565
|
+
*
|
|
566
|
+
* Use this in bloc-to-bloc communication when you need to ensure an instance exists
|
|
567
|
+
* but don't want to claim ownership (no ref count increment).
|
|
568
|
+
*
|
|
569
|
+
* @param Type - The bloc class constructor
|
|
570
|
+
* @param instanceKey - Optional instance key (defaults to 'default')
|
|
571
|
+
* @param constructorArgs - Constructor arguments (only used if creating new instance)
|
|
572
|
+
* @returns The bloc instance
|
|
573
|
+
*/
|
|
574
|
+
connect(Type, instanceKey = BLAC_DEFAULTS.DEFAULT_INSTANCE_KEY, constructorArgs) {
|
|
575
|
+
const registryConfig = this.typeConfigs.get(Type.name);
|
|
576
|
+
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.`);
|
|
577
|
+
const instances = this.ensureInstancesMap(Type);
|
|
578
|
+
let entry = instances.get(instanceKey);
|
|
579
|
+
if (!entry) {
|
|
580
|
+
const instance = new Type(constructorArgs);
|
|
581
|
+
const config = { instanceId: instanceKey };
|
|
582
|
+
instance.initConfig(config);
|
|
583
|
+
entry = {
|
|
584
|
+
instance,
|
|
585
|
+
refCount: 1
|
|
586
|
+
};
|
|
587
|
+
instances.set(instanceKey, entry);
|
|
588
|
+
this.registerType(Type);
|
|
589
|
+
}
|
|
590
|
+
const context = getGetterExecutionContext();
|
|
591
|
+
if (context.tracker && context.currentBloc && context.currentBloc !== entry.instance) context.tracker.externalDependencies.add(entry.instance);
|
|
592
|
+
return entry.instance;
|
|
593
|
+
}
|
|
594
|
+
/**
|
|
595
|
+
* Release a reference to an instance
|
|
596
|
+
*/
|
|
597
|
+
release(Type, instanceKey = BLAC_DEFAULTS.DEFAULT_INSTANCE_KEY, forceDispose = false) {
|
|
598
|
+
const instances = this.ensureInstancesMap(Type);
|
|
599
|
+
const entry = instances.get(instanceKey);
|
|
600
|
+
if (!entry) return;
|
|
601
|
+
if (forceDispose) {
|
|
602
|
+
if (!entry.instance.isDisposed) entry.instance.dispose();
|
|
603
|
+
instances.delete(instanceKey);
|
|
604
|
+
return;
|
|
605
|
+
}
|
|
606
|
+
entry.refCount--;
|
|
607
|
+
const keepAlive = isKeepAliveClass(Type);
|
|
608
|
+
if (entry.refCount <= 0 && !keepAlive) {
|
|
609
|
+
if (!entry.instance.isDisposed) entry.instance.dispose();
|
|
610
|
+
instances.delete(instanceKey);
|
|
611
|
+
}
|
|
612
|
+
}
|
|
613
|
+
/**
|
|
614
|
+
* Get all instances of a specific type
|
|
615
|
+
*/
|
|
616
|
+
getAll(Type) {
|
|
617
|
+
const instances = this.ensureInstancesMap(Type);
|
|
618
|
+
const result = [];
|
|
619
|
+
for (const entry of instances.values()) result.push(entry.instance);
|
|
620
|
+
return result;
|
|
621
|
+
}
|
|
622
|
+
/**
|
|
623
|
+
* Safely iterate over all instances of a type
|
|
624
|
+
*/
|
|
625
|
+
forEach(Type, callback) {
|
|
626
|
+
const instances = this.ensureInstancesMap(Type);
|
|
627
|
+
for (const entry of instances.values()) {
|
|
628
|
+
const instance = entry.instance;
|
|
629
|
+
if (!instance.isDisposed) try {
|
|
630
|
+
callback(instance);
|
|
631
|
+
} catch (error$1) {
|
|
632
|
+
console.error(`${BLAC_ERROR_PREFIX} forEach callback error for ${Type.name}:`, error$1);
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
/**
|
|
637
|
+
* Clear all instances of a specific type
|
|
638
|
+
*/
|
|
639
|
+
clear(Type) {
|
|
640
|
+
const instances = this.ensureInstancesMap(Type);
|
|
641
|
+
for (const entry of instances.values()) if (!entry.instance.isDisposed) entry.instance.dispose();
|
|
642
|
+
instances.clear();
|
|
643
|
+
}
|
|
644
|
+
/**
|
|
645
|
+
* Get reference count for an instance
|
|
646
|
+
*/
|
|
647
|
+
getRefCount(Type, instanceKey = BLAC_DEFAULTS.DEFAULT_INSTANCE_KEY) {
|
|
648
|
+
return this.ensureInstancesMap(Type).get(instanceKey)?.refCount ?? 0;
|
|
649
|
+
}
|
|
650
|
+
/**
|
|
651
|
+
* Check if an instance exists
|
|
652
|
+
*/
|
|
653
|
+
hasInstance(Type, instanceKey = BLAC_DEFAULTS.DEFAULT_INSTANCE_KEY) {
|
|
654
|
+
return this.ensureInstancesMap(Type).has(instanceKey);
|
|
655
|
+
}
|
|
656
|
+
/**
|
|
657
|
+
* Clear all instances from all types (for testing)
|
|
658
|
+
*
|
|
659
|
+
* Iterates all registered types and clears their instances.
|
|
660
|
+
* Also clears type tracking to reset the registry state.
|
|
661
|
+
*/
|
|
662
|
+
clearAll() {
|
|
663
|
+
for (const Type of this.types) this.clear(Type);
|
|
664
|
+
this.types.clear();
|
|
665
|
+
this.typeConfigs.clear();
|
|
666
|
+
}
|
|
667
|
+
/**
|
|
668
|
+
* Get registry statistics (for debugging)
|
|
669
|
+
*/
|
|
670
|
+
getStats() {
|
|
671
|
+
const typeBreakdown = {};
|
|
672
|
+
let totalInstances = 0;
|
|
673
|
+
for (const Type of this.types) {
|
|
674
|
+
const typeName = Type.name;
|
|
675
|
+
const count = this.getInstancesMap(Type).size;
|
|
676
|
+
typeBreakdown[typeName] = count;
|
|
677
|
+
totalInstances += count;
|
|
678
|
+
}
|
|
679
|
+
return {
|
|
680
|
+
registeredTypes: this.types.size,
|
|
681
|
+
totalInstances,
|
|
682
|
+
typeBreakdown
|
|
683
|
+
};
|
|
684
|
+
}
|
|
685
|
+
/**
|
|
686
|
+
* Get all registered types (for plugin system)
|
|
687
|
+
*/
|
|
688
|
+
getTypes() {
|
|
689
|
+
return Array.from(this.types);
|
|
690
|
+
}
|
|
691
|
+
/**
|
|
692
|
+
* Subscribe to lifecycle events
|
|
693
|
+
* @param event - The lifecycle event to listen for
|
|
694
|
+
* @param listener - The listener function to call when the event occurs
|
|
695
|
+
* @returns Unsubscribe function
|
|
696
|
+
*/
|
|
697
|
+
on(event, listener) {
|
|
698
|
+
if (!this.listeners.has(event)) this.listeners.set(event, /* @__PURE__ */ new Set());
|
|
699
|
+
this.listeners.get(event).add(listener);
|
|
700
|
+
return () => {
|
|
701
|
+
this.listeners.get(event)?.delete(listener);
|
|
702
|
+
};
|
|
703
|
+
}
|
|
704
|
+
/**
|
|
705
|
+
* Emit lifecycle event to all listeners
|
|
706
|
+
* @internal - Called by StateContainer/Vertex lifecycle methods
|
|
707
|
+
*/
|
|
708
|
+
emit(event, ...args) {
|
|
709
|
+
const listeners = this.listeners.get(event);
|
|
710
|
+
if (!listeners || listeners.size === 0) return;
|
|
711
|
+
for (const listener of listeners) try {
|
|
712
|
+
listener(...args);
|
|
713
|
+
} catch (error$1) {
|
|
714
|
+
console.error(`${BLAC_ERROR_PREFIX} Listener error for '${event}':`, error$1);
|
|
715
|
+
}
|
|
716
|
+
}
|
|
717
|
+
};
|
|
718
|
+
/**
|
|
719
|
+
* Global default registry instance
|
|
720
|
+
*/
|
|
721
|
+
const globalRegistry = new StateContainerRegistry();
|
|
722
|
+
/**
|
|
723
|
+
* Global plugin manager (initialized lazily)
|
|
724
|
+
*/
|
|
725
|
+
let _globalPluginManager = null;
|
|
726
|
+
/**
|
|
727
|
+
* Get the global plugin manager
|
|
728
|
+
*/
|
|
729
|
+
function getPluginManager() {
|
|
730
|
+
if (!_globalPluginManager) _globalPluginManager = createPluginManager(globalRegistry);
|
|
731
|
+
return _globalPluginManager;
|
|
732
|
+
}
|
|
733
|
+
|
|
734
|
+
//#endregion
|
|
735
|
+
//#region src/core/StateContainer.ts
|
|
736
|
+
var StateContainer = class StateContainer {
|
|
737
|
+
static {
|
|
738
|
+
this.__excludeFromDevTools = false;
|
|
739
|
+
}
|
|
740
|
+
static {
|
|
741
|
+
this._registry = globalRegistry;
|
|
742
|
+
}
|
|
743
|
+
static getRegistry() {
|
|
744
|
+
return StateContainer._registry;
|
|
745
|
+
}
|
|
746
|
+
static setRegistry(registry) {
|
|
747
|
+
StateContainer._registry.clearAll();
|
|
748
|
+
StateContainer._registry = registry;
|
|
749
|
+
}
|
|
750
|
+
static register(isolated = false) {
|
|
751
|
+
StateContainer._registry.register(this, isolated);
|
|
752
|
+
}
|
|
753
|
+
static resolve(instanceKey, constructorArgs) {
|
|
754
|
+
return StateContainer._registry.resolve(this, instanceKey, constructorArgs);
|
|
755
|
+
}
|
|
756
|
+
static get(instanceKey) {
|
|
757
|
+
return StateContainer._registry.get(this, instanceKey);
|
|
758
|
+
}
|
|
759
|
+
static getSafe(instanceKey) {
|
|
760
|
+
return StateContainer._registry.getSafe(this, instanceKey);
|
|
761
|
+
}
|
|
762
|
+
static connect(instanceKey, constructorArgs) {
|
|
763
|
+
return StateContainer._registry.connect(this, instanceKey, constructorArgs);
|
|
764
|
+
}
|
|
765
|
+
static release(instanceKey, forceDispose = false) {
|
|
766
|
+
StateContainer._registry.release(this, instanceKey, forceDispose);
|
|
767
|
+
}
|
|
768
|
+
static getAll() {
|
|
769
|
+
return StateContainer._registry.getAll(this);
|
|
770
|
+
}
|
|
771
|
+
static forEach(callback) {
|
|
772
|
+
StateContainer._registry.forEach(this, callback);
|
|
773
|
+
}
|
|
774
|
+
static clear() {
|
|
775
|
+
StateContainer._registry.clear(this);
|
|
776
|
+
}
|
|
777
|
+
static clearAllInstances() {
|
|
778
|
+
StateContainer._registry.clearAll();
|
|
779
|
+
}
|
|
780
|
+
static getStats() {
|
|
781
|
+
return StateContainer._registry.getStats();
|
|
782
|
+
}
|
|
783
|
+
static getRefCount(instanceKey) {
|
|
784
|
+
return StateContainer._registry.getRefCount(this, instanceKey);
|
|
785
|
+
}
|
|
786
|
+
static hasInstance(instanceKey) {
|
|
787
|
+
return StateContainer._registry.hasInstance(this, instanceKey);
|
|
788
|
+
}
|
|
789
|
+
get props() {
|
|
790
|
+
return this._props;
|
|
791
|
+
}
|
|
792
|
+
constructor(initialState) {
|
|
793
|
+
this.listeners = /* @__PURE__ */ new Set();
|
|
794
|
+
this._disposed = false;
|
|
795
|
+
this.config = {};
|
|
796
|
+
this._props = void 0;
|
|
797
|
+
this.systemEventHandlers = /* @__PURE__ */ new Map();
|
|
798
|
+
this.name = this.constructor.name;
|
|
799
|
+
this.debug = false;
|
|
800
|
+
this.instanceId = generateSimpleId(this.constructor.name, "main");
|
|
801
|
+
this.createdAt = Date.now();
|
|
802
|
+
this.lastUpdateTimestamp = Date.now();
|
|
803
|
+
this.onSystemEvent = (event, handler) => {
|
|
804
|
+
let handlers = this.systemEventHandlers.get(event);
|
|
805
|
+
if (!handlers) {
|
|
806
|
+
handlers = /* @__PURE__ */ new Set();
|
|
807
|
+
this.systemEventHandlers.set(event, handlers);
|
|
808
|
+
}
|
|
809
|
+
handlers.add(handler);
|
|
810
|
+
return () => {
|
|
811
|
+
handlers?.delete(handler);
|
|
812
|
+
};
|
|
813
|
+
};
|
|
814
|
+
this._state = initialState;
|
|
815
|
+
}
|
|
816
|
+
initConfig(config) {
|
|
817
|
+
this.config = { ...config };
|
|
818
|
+
this.name = this.config.name || this.constructor.name;
|
|
819
|
+
this.debug = this.config.debug ?? false;
|
|
820
|
+
this.instanceId = generateSimpleId(this.constructor.name, this.config.instanceId);
|
|
821
|
+
StateContainer._registry.emit("created", this);
|
|
822
|
+
}
|
|
823
|
+
get state() {
|
|
824
|
+
return this._state;
|
|
825
|
+
}
|
|
826
|
+
get isDisposed() {
|
|
827
|
+
return this._disposed;
|
|
828
|
+
}
|
|
829
|
+
subscribe(listener) {
|
|
830
|
+
if (this._disposed) throw new Error(`Cannot subscribe to disposed container ${this.name}`);
|
|
831
|
+
this.listeners.add(listener);
|
|
832
|
+
return () => this.listeners.delete(listener);
|
|
833
|
+
}
|
|
834
|
+
dispose() {
|
|
835
|
+
if (this._disposed) return;
|
|
836
|
+
if (this.debug) console.log(`[${this.name}] Disposing...`);
|
|
837
|
+
this._disposed = true;
|
|
838
|
+
this.emitSystemEvent("dispose", void 0);
|
|
839
|
+
this.listeners.clear();
|
|
840
|
+
this.systemEventHandlers.clear();
|
|
841
|
+
StateContainer._registry.emit("disposed", this);
|
|
842
|
+
if (this.debug) console.log(`[${this.name}] Disposed successfully`);
|
|
843
|
+
}
|
|
844
|
+
emit(newState) {
|
|
845
|
+
if (this._disposed) throw new Error(`Cannot emit state from disposed container ${this.name}`);
|
|
846
|
+
if (this._state === newState) return;
|
|
847
|
+
const previousState = this._state;
|
|
848
|
+
this._state = newState;
|
|
849
|
+
this.emitSystemEvent("stateChanged", {
|
|
850
|
+
state: newState,
|
|
851
|
+
previousState
|
|
852
|
+
});
|
|
853
|
+
for (const listener of this.listeners) try {
|
|
854
|
+
listener(newState);
|
|
855
|
+
} catch (error$1) {
|
|
856
|
+
console.error(`[${this.name}] Error in listener:`, error$1);
|
|
857
|
+
}
|
|
858
|
+
const stackTrace = this.captureStackTrace();
|
|
859
|
+
StateContainer._registry.emit("stateChanged", this, previousState, newState, stackTrace);
|
|
860
|
+
this.lastUpdateTimestamp = Date.now();
|
|
861
|
+
}
|
|
862
|
+
static {
|
|
863
|
+
this.enableStackTrace = true;
|
|
864
|
+
}
|
|
865
|
+
captureStackTrace() {
|
|
866
|
+
if (!StateContainer.enableStackTrace || typeof process !== "undefined" && process.env?.NODE_ENV === "production") return "";
|
|
867
|
+
try {
|
|
868
|
+
const relevantLines = ((/* @__PURE__ */ new Error()).stack || "").split("\n").slice(1);
|
|
869
|
+
const formattedLines = [];
|
|
870
|
+
for (const line of relevantLines) {
|
|
871
|
+
if (!line.trim()) continue;
|
|
872
|
+
if (line.includes("StateContainer.emit") || line.includes("StateContainer.update") || line.includes("StateContainer.captureStackTrace") || line.includes("Cubit.patch") || line.includes("Vertex.")) continue;
|
|
873
|
+
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;
|
|
874
|
+
const formatted = this.formatStackLine(line);
|
|
875
|
+
if (formatted) formattedLines.push(formatted);
|
|
876
|
+
}
|
|
877
|
+
return formattedLines.join("\n");
|
|
878
|
+
} catch {
|
|
879
|
+
return "";
|
|
880
|
+
}
|
|
881
|
+
}
|
|
882
|
+
formatStackLine(line) {
|
|
883
|
+
const match = line.match(/at\s+(.+?)\s+\((.+?):(\d+):(\d+)\)/);
|
|
884
|
+
if (!match) {
|
|
885
|
+
const simpleMatch = line.match(/at\s+(.+?):(\d+):(\d+)/);
|
|
886
|
+
if (simpleMatch) {
|
|
887
|
+
const [, url$1, lineNum$1, col$1] = simpleMatch;
|
|
888
|
+
return ` at ${this.cleanFilePath(url$1)}:${lineNum$1}:${col$1}`;
|
|
889
|
+
}
|
|
890
|
+
return null;
|
|
891
|
+
}
|
|
892
|
+
const [, functionName, url, lineNum, col] = match;
|
|
893
|
+
return ` at ${functionName} (${this.cleanFilePath(url)}:${lineNum}:${col})`;
|
|
894
|
+
}
|
|
895
|
+
cleanFilePath(url) {
|
|
896
|
+
let path = url.replace(/http:\/\/localhost:\d+\/@fs/, "").replace(/http:\/\/localhost:\d+\//, "").replace(/\?t=\d+/, "").replace(/\?v=[a-f0-9]+/, "");
|
|
897
|
+
const projectMatch = path.match(/\/Projects\/blac\/(.+)/);
|
|
898
|
+
if (projectMatch) path = projectMatch[1];
|
|
899
|
+
const segments = path.split("/");
|
|
900
|
+
if (segments.length > 3) path = segments.slice(-3).join("/");
|
|
901
|
+
return path;
|
|
902
|
+
}
|
|
903
|
+
update(updater) {
|
|
904
|
+
if (this._disposed) throw new Error(`Cannot update state from disposed container ${this.name}`);
|
|
905
|
+
this.emit(updater(this._state));
|
|
906
|
+
}
|
|
907
|
+
emitSystemEvent(event, payload) {
|
|
908
|
+
const handlers = this.systemEventHandlers.get(event);
|
|
909
|
+
if (!handlers) return;
|
|
910
|
+
for (const handler of handlers) try {
|
|
911
|
+
handler(payload);
|
|
912
|
+
} catch (error$1) {
|
|
913
|
+
console.error(`[${this.name}] Error in system event handler:`, error$1);
|
|
914
|
+
}
|
|
915
|
+
}
|
|
916
|
+
updateProps(newProps) {
|
|
917
|
+
if (this._disposed) throw new Error(`Cannot update props on disposed container ${this.name}`);
|
|
918
|
+
const previousProps = this._props;
|
|
919
|
+
this._props = newProps;
|
|
920
|
+
this.emitSystemEvent("propsUpdated", {
|
|
921
|
+
props: newProps,
|
|
922
|
+
previousProps
|
|
923
|
+
});
|
|
924
|
+
if (this.debug) console.log(`[${this.name}] Props updated:`, {
|
|
925
|
+
newProps,
|
|
926
|
+
previousProps
|
|
927
|
+
});
|
|
928
|
+
}
|
|
929
|
+
};
|
|
930
|
+
|
|
931
|
+
//#endregion
|
|
932
|
+
//#region src/core/Cubit.ts
|
|
933
|
+
var Cubit = class extends StateContainer {
|
|
934
|
+
constructor(initialState) {
|
|
935
|
+
super(initialState);
|
|
936
|
+
this.patch = ((partial) => {
|
|
937
|
+
if (typeof this.state !== "object" || this.state === null) throw new Error("patch() is only available for object state types");
|
|
938
|
+
this.update((current) => ({
|
|
939
|
+
...current,
|
|
940
|
+
...partial
|
|
941
|
+
}));
|
|
942
|
+
});
|
|
943
|
+
}
|
|
944
|
+
emit(newState) {
|
|
945
|
+
super["emit"](newState);
|
|
946
|
+
}
|
|
947
|
+
update(updater) {
|
|
948
|
+
super["update"](updater);
|
|
949
|
+
}
|
|
950
|
+
};
|
|
951
|
+
var CounterCubit = class extends Cubit {
|
|
952
|
+
constructor() {
|
|
953
|
+
super(0);
|
|
954
|
+
this.increment = () => {
|
|
955
|
+
this.emit(this.state + 1);
|
|
956
|
+
};
|
|
957
|
+
this.decrement = () => {
|
|
958
|
+
this.emit(this.state - 1);
|
|
959
|
+
};
|
|
960
|
+
this.reset = () => {
|
|
961
|
+
this.emit(0);
|
|
962
|
+
};
|
|
963
|
+
this.addAmount = (amount) => {
|
|
964
|
+
this.emit(this.state + amount);
|
|
965
|
+
};
|
|
966
|
+
}
|
|
967
|
+
};
|
|
968
|
+
var TodoCubit = class extends Cubit {
|
|
969
|
+
static {
|
|
970
|
+
this.keepAlive = true;
|
|
971
|
+
}
|
|
972
|
+
constructor() {
|
|
973
|
+
super({
|
|
974
|
+
todos: [],
|
|
975
|
+
filter: "all",
|
|
976
|
+
isLoading: false
|
|
977
|
+
});
|
|
978
|
+
this.nextId = 0;
|
|
979
|
+
this.addTodo = (text) => {
|
|
980
|
+
this.update((state) => ({
|
|
981
|
+
...state,
|
|
982
|
+
todos: [...state.todos, {
|
|
983
|
+
id: `todo-${Date.now()}-${this.nextId++}`,
|
|
984
|
+
text,
|
|
985
|
+
done: false
|
|
986
|
+
}]
|
|
987
|
+
}));
|
|
988
|
+
};
|
|
989
|
+
this.toggleTodo = (id) => {
|
|
990
|
+
this.update((state) => ({
|
|
991
|
+
...state,
|
|
992
|
+
todos: state.todos.map((todo) => todo.id === id ? {
|
|
993
|
+
...todo,
|
|
994
|
+
done: !todo.done
|
|
995
|
+
} : todo)
|
|
996
|
+
}));
|
|
997
|
+
};
|
|
998
|
+
this.removeTodo = (id) => {
|
|
999
|
+
this.update((state) => ({
|
|
1000
|
+
...state,
|
|
1001
|
+
todos: state.todos.filter((todo) => todo.id !== id)
|
|
1002
|
+
}));
|
|
1003
|
+
};
|
|
1004
|
+
this.setFilter = (filter) => {
|
|
1005
|
+
this.patch({ filter });
|
|
1006
|
+
};
|
|
1007
|
+
this.setLoading = (isLoading) => {
|
|
1008
|
+
this.patch({ isLoading });
|
|
1009
|
+
};
|
|
1010
|
+
}
|
|
1011
|
+
get visibleTodos() {
|
|
1012
|
+
const todos = this.state.todos;
|
|
1013
|
+
switch (this.state.filter) {
|
|
1014
|
+
case "active": return todos.filter((t) => !t.done);
|
|
1015
|
+
case "completed": return todos.filter((t) => t.done);
|
|
1016
|
+
case "all":
|
|
1017
|
+
default: return todos;
|
|
1018
|
+
}
|
|
1019
|
+
}
|
|
1020
|
+
get activeTodoCount() {
|
|
1021
|
+
return this.state.todos.filter((t) => !t.done).length;
|
|
1022
|
+
}
|
|
1023
|
+
};
|
|
1024
|
+
|
|
1025
|
+
//#endregion
|
|
1026
|
+
//#region src/core/Vertex.ts
|
|
1027
|
+
var Vertex = class extends StateContainer {
|
|
1028
|
+
constructor(initialState) {
|
|
1029
|
+
super(initialState);
|
|
1030
|
+
this.eventHandlers = /* @__PURE__ */ new Map();
|
|
1031
|
+
this.isProcessing = false;
|
|
1032
|
+
this.eventQueue = [];
|
|
1033
|
+
this.on = (EventClass, handler) => {
|
|
1034
|
+
const className = EventClass.name;
|
|
1035
|
+
const registrations = this.eventHandlers.get(className) || [];
|
|
1036
|
+
registrations.push({
|
|
1037
|
+
eventClass: EventClass,
|
|
1038
|
+
handler
|
|
1039
|
+
});
|
|
1040
|
+
this.eventHandlers.set(className, registrations);
|
|
1041
|
+
};
|
|
1042
|
+
this.add = (event) => {
|
|
1043
|
+
StateContainer._registry.emit("eventAdded", this, event);
|
|
1044
|
+
this.processEvent(event);
|
|
1045
|
+
};
|
|
1046
|
+
}
|
|
1047
|
+
processEvent(event) {
|
|
1048
|
+
if (this.isProcessing) {
|
|
1049
|
+
this.eventQueue.push(event);
|
|
1050
|
+
return;
|
|
1051
|
+
}
|
|
1052
|
+
this.isProcessing = true;
|
|
1053
|
+
try {
|
|
1054
|
+
this.handleEvent(event);
|
|
1055
|
+
while (this.eventQueue.length > 0) {
|
|
1056
|
+
const nextEvent = this.eventQueue.shift();
|
|
1057
|
+
this.handleEvent(nextEvent);
|
|
1058
|
+
}
|
|
1059
|
+
} finally {
|
|
1060
|
+
this.isProcessing = false;
|
|
1061
|
+
}
|
|
1062
|
+
}
|
|
1063
|
+
handleEvent(event) {
|
|
1064
|
+
const className = event.constructor.name;
|
|
1065
|
+
const registrations = this.eventHandlers.get(className);
|
|
1066
|
+
if (!registrations || registrations.length === 0) {
|
|
1067
|
+
if (this.debug) console.warn(`No handler registered for event: ${className}`);
|
|
1068
|
+
return;
|
|
1069
|
+
}
|
|
1070
|
+
const emitFn = (state) => {
|
|
1071
|
+
this.emit(state);
|
|
1072
|
+
};
|
|
1073
|
+
for (const registration of registrations) try {
|
|
1074
|
+
registration.handler(event, emitFn);
|
|
1075
|
+
} catch (error$1) {
|
|
1076
|
+
if (this.debug) console.error(`Error handling event ${className}:`, error$1);
|
|
1077
|
+
this.onEventError(event, error$1);
|
|
1078
|
+
}
|
|
1079
|
+
}
|
|
1080
|
+
onEventError(_event, _error) {}
|
|
1081
|
+
};
|
|
1082
|
+
/**
|
|
1083
|
+
* Example: Counter Events
|
|
1084
|
+
*/
|
|
1085
|
+
var IncrementEvent = class {
|
|
1086
|
+
constructor(amount = 1) {
|
|
1087
|
+
this.amount = amount;
|
|
1088
|
+
this.type = "increment";
|
|
1089
|
+
this.timestamp = Date.now();
|
|
1090
|
+
}
|
|
1091
|
+
};
|
|
1092
|
+
var DecrementEvent = class {
|
|
1093
|
+
constructor(amount = 1) {
|
|
1094
|
+
this.amount = amount;
|
|
1095
|
+
this.type = "decrement";
|
|
1096
|
+
this.timestamp = Date.now();
|
|
1097
|
+
}
|
|
1098
|
+
};
|
|
1099
|
+
var ResetEvent = class {
|
|
1100
|
+
constructor() {
|
|
1101
|
+
this.type = "reset";
|
|
1102
|
+
this.timestamp = Date.now();
|
|
1103
|
+
}
|
|
1104
|
+
};
|
|
1105
|
+
/**
|
|
1106
|
+
* Example: Counter Vertex
|
|
1107
|
+
* Demonstrates proper typing and event handling
|
|
1108
|
+
*/
|
|
1109
|
+
var CounterVertex = class extends Vertex {
|
|
1110
|
+
constructor() {
|
|
1111
|
+
super(0);
|
|
1112
|
+
this.increment = (amount = 1) => {
|
|
1113
|
+
this.add(new IncrementEvent(amount));
|
|
1114
|
+
};
|
|
1115
|
+
this.decrement = (amount = 1) => {
|
|
1116
|
+
this.add(new DecrementEvent(amount));
|
|
1117
|
+
};
|
|
1118
|
+
this.reset = () => {
|
|
1119
|
+
this.add(new ResetEvent());
|
|
1120
|
+
};
|
|
1121
|
+
this.on(IncrementEvent, (event, emit) => {
|
|
1122
|
+
emit(this.state + event.amount);
|
|
1123
|
+
});
|
|
1124
|
+
this.on(DecrementEvent, (event, emit) => {
|
|
1125
|
+
emit(this.state - event.amount);
|
|
1126
|
+
});
|
|
1127
|
+
this.on(ResetEvent, (_, emit) => {
|
|
1128
|
+
emit(0);
|
|
1129
|
+
});
|
|
1130
|
+
}
|
|
1131
|
+
};
|
|
1132
|
+
var LoginEvent = class {
|
|
1133
|
+
constructor(email, password) {
|
|
1134
|
+
this.email = email;
|
|
1135
|
+
this.password = password;
|
|
1136
|
+
this.type = "login";
|
|
1137
|
+
this.timestamp = Date.now();
|
|
1138
|
+
}
|
|
1139
|
+
};
|
|
1140
|
+
var LogoutEvent = class {
|
|
1141
|
+
constructor() {
|
|
1142
|
+
this.type = "logout";
|
|
1143
|
+
this.timestamp = Date.now();
|
|
1144
|
+
}
|
|
1145
|
+
};
|
|
1146
|
+
var AuthVertex = class extends Vertex {
|
|
1147
|
+
constructor() {
|
|
1148
|
+
super({
|
|
1149
|
+
isAuthenticated: false,
|
|
1150
|
+
isLoading: false,
|
|
1151
|
+
user: null,
|
|
1152
|
+
error: null
|
|
1153
|
+
});
|
|
1154
|
+
this.login = (email, password) => {
|
|
1155
|
+
this.add(new LoginEvent(email, password));
|
|
1156
|
+
};
|
|
1157
|
+
this.logout = () => {
|
|
1158
|
+
this.add(new LogoutEvent());
|
|
1159
|
+
};
|
|
1160
|
+
this.on(LoginEvent, (event, emit) => {
|
|
1161
|
+
emit({
|
|
1162
|
+
...this.state,
|
|
1163
|
+
isLoading: true,
|
|
1164
|
+
error: null
|
|
1165
|
+
});
|
|
1166
|
+
if (event.email === "user@example.com" && event.password === "password") emit({
|
|
1167
|
+
isAuthenticated: true,
|
|
1168
|
+
isLoading: false,
|
|
1169
|
+
user: {
|
|
1170
|
+
id: "123",
|
|
1171
|
+
name: "Test User",
|
|
1172
|
+
email: "user@example.com"
|
|
1173
|
+
},
|
|
1174
|
+
error: null
|
|
1175
|
+
});
|
|
1176
|
+
else emit({
|
|
1177
|
+
isAuthenticated: false,
|
|
1178
|
+
isLoading: false,
|
|
1179
|
+
user: null,
|
|
1180
|
+
error: "Invalid credentials"
|
|
1181
|
+
});
|
|
1182
|
+
});
|
|
1183
|
+
this.on(LogoutEvent, (_, emit) => {
|
|
1184
|
+
emit({
|
|
1185
|
+
isAuthenticated: false,
|
|
1186
|
+
isLoading: false,
|
|
1187
|
+
user: null,
|
|
1188
|
+
error: null
|
|
1189
|
+
});
|
|
1190
|
+
});
|
|
1191
|
+
}
|
|
1192
|
+
};
|
|
1193
|
+
|
|
1194
|
+
//#endregion
|
|
1195
|
+
//#region src/plugin/BlacPlugin.ts
|
|
1196
|
+
function hasInitHook(plugin) {
|
|
1197
|
+
return typeof plugin.onInstall === "function";
|
|
1198
|
+
}
|
|
1199
|
+
|
|
1200
|
+
//#endregion
|
|
1201
|
+
//#region src/tracking/proxy-tracker.ts
|
|
1202
|
+
/**
|
|
1203
|
+
* Functional proxy tracker for automatic dependency tracking
|
|
1204
|
+
*
|
|
1205
|
+
* This module provides a functional approach to proxy-based dependency tracking.
|
|
1206
|
+
* Unlike the class-based ProxyTracker, this uses a state object pattern that's
|
|
1207
|
+
* easier to integrate with functional programming patterns and framework hooks.
|
|
1208
|
+
*/
|
|
1209
|
+
/**
|
|
1210
|
+
* Check if a value can be proxied
|
|
1211
|
+
*
|
|
1212
|
+
* Returns true for plain objects and arrays only.
|
|
1213
|
+
* Excludes built-in objects like Date, Map, Set, etc.
|
|
1214
|
+
*/
|
|
1215
|
+
function isProxyable(value) {
|
|
1216
|
+
if (typeof value !== "object" || value === null) return false;
|
|
1217
|
+
const proto = Object.getPrototypeOf(value);
|
|
1218
|
+
return proto === Object.prototype || proto === Array.prototype;
|
|
1219
|
+
}
|
|
1220
|
+
/**
|
|
1221
|
+
* Create a new proxy tracker state
|
|
1222
|
+
*
|
|
1223
|
+
* @example
|
|
1224
|
+
* const state = createProxyTrackerState<MyState>();
|
|
1225
|
+
* startProxyTracking(state);
|
|
1226
|
+
* const proxy = createProxyForTarget(state, myObject);
|
|
1227
|
+
* // ... use proxy ...
|
|
1228
|
+
* const paths = stopProxyTracking(state);
|
|
1229
|
+
*/
|
|
1230
|
+
function createProxyTrackerState() {
|
|
1231
|
+
return {
|
|
1232
|
+
trackedPaths: /* @__PURE__ */ new Set(),
|
|
1233
|
+
isTracking: false,
|
|
1234
|
+
proxyCache: /* @__PURE__ */ new WeakMap(),
|
|
1235
|
+
boundFunctionsCache: null,
|
|
1236
|
+
lastProxiedState: null,
|
|
1237
|
+
lastProxy: null,
|
|
1238
|
+
maxDepth: 10
|
|
1239
|
+
};
|
|
1240
|
+
}
|
|
1241
|
+
/**
|
|
1242
|
+
* Start tracking property accesses
|
|
1243
|
+
*
|
|
1244
|
+
* Clears previous tracked paths and enables tracking mode.
|
|
1245
|
+
*/
|
|
1246
|
+
function startProxyTracking(state) {
|
|
1247
|
+
state.isTracking = true;
|
|
1248
|
+
state.trackedPaths.clear();
|
|
1249
|
+
}
|
|
1250
|
+
/**
|
|
1251
|
+
* Stop tracking and return the tracked paths
|
|
1252
|
+
*
|
|
1253
|
+
* Returns a new Set containing all tracked paths.
|
|
1254
|
+
*/
|
|
1255
|
+
function stopProxyTracking(state) {
|
|
1256
|
+
state.isTracking = false;
|
|
1257
|
+
return new Set(state.trackedPaths);
|
|
1258
|
+
}
|
|
1259
|
+
/**
|
|
1260
|
+
* Create a proxy for an array with property access tracking
|
|
1261
|
+
*
|
|
1262
|
+
* Tracks:
|
|
1263
|
+
* - Array element access (arr[0])
|
|
1264
|
+
* - Length access (arr.length)
|
|
1265
|
+
* - Array method calls (arr.map, arr.filter, etc.)
|
|
1266
|
+
*/
|
|
1267
|
+
function createArrayProxy(state, target, path, depth = 0) {
|
|
1268
|
+
const proxy = new Proxy(target, { get: (arr, prop) => {
|
|
1269
|
+
if (typeof prop === "symbol") return Reflect.get(arr, prop);
|
|
1270
|
+
const value = Reflect.get(arr, prop);
|
|
1271
|
+
if (typeof value === "function") {
|
|
1272
|
+
if (!state.boundFunctionsCache) state.boundFunctionsCache = /* @__PURE__ */ new WeakMap();
|
|
1273
|
+
const cached = state.boundFunctionsCache.get(value);
|
|
1274
|
+
if (cached) return cached;
|
|
1275
|
+
const bound = value.bind(arr);
|
|
1276
|
+
state.boundFunctionsCache.set(value, bound);
|
|
1277
|
+
return bound;
|
|
1278
|
+
}
|
|
1279
|
+
if (prop === "length") {
|
|
1280
|
+
if (state.isTracking) {
|
|
1281
|
+
const fullPath$1 = path ? `${path}.length` : "length";
|
|
1282
|
+
state.trackedPaths.add(fullPath$1);
|
|
1283
|
+
}
|
|
1284
|
+
return value;
|
|
1285
|
+
}
|
|
1286
|
+
let fullPath;
|
|
1287
|
+
if (typeof prop === "string") {
|
|
1288
|
+
const index = Number(prop);
|
|
1289
|
+
if (!isNaN(index) && index >= 0) fullPath = path ? `${path}[${index}]` : `[${index}]`;
|
|
1290
|
+
else fullPath = path ? `${path}.${prop}` : prop;
|
|
1291
|
+
} else return value;
|
|
1292
|
+
if (isProxyable(value)) return createProxyInternal(state, value, fullPath, depth + 1);
|
|
1293
|
+
if (state.isTracking) state.trackedPaths.add(fullPath);
|
|
1294
|
+
return value;
|
|
1295
|
+
} });
|
|
1296
|
+
state.proxyCache.set(target, proxy);
|
|
1297
|
+
return proxy;
|
|
1298
|
+
}
|
|
1299
|
+
/**
|
|
1300
|
+
* Create a proxy for an object with property access tracking
|
|
1301
|
+
*
|
|
1302
|
+
* This is the core proxy creation function that recursively creates proxies
|
|
1303
|
+
* for nested objects and arrays.
|
|
1304
|
+
*
|
|
1305
|
+
* Tracks:
|
|
1306
|
+
* - Property access (obj.prop)
|
|
1307
|
+
* - Nested property access (obj.nested.prop)
|
|
1308
|
+
* - 'in' operator usage ('prop' in obj)
|
|
1309
|
+
* - Object.keys, Object.entries, etc.
|
|
1310
|
+
*/
|
|
1311
|
+
function createProxyInternal(state, target, path = "", depth = 0) {
|
|
1312
|
+
if (!state.isTracking || !isProxyable(target)) return target;
|
|
1313
|
+
if (depth >= state.maxDepth) return target;
|
|
1314
|
+
if (state.proxyCache.has(target)) return state.proxyCache.get(target);
|
|
1315
|
+
if (Array.isArray(target)) return createArrayProxy(state, target, path, depth);
|
|
1316
|
+
const proxy = new Proxy(target, {
|
|
1317
|
+
get: (obj, prop) => {
|
|
1318
|
+
if (typeof prop === "symbol") return Reflect.get(obj, prop);
|
|
1319
|
+
const value = Reflect.get(obj, prop);
|
|
1320
|
+
if (typeof value === "function") {
|
|
1321
|
+
if (!state.boundFunctionsCache) state.boundFunctionsCache = /* @__PURE__ */ new WeakMap();
|
|
1322
|
+
const cached = state.boundFunctionsCache.get(value);
|
|
1323
|
+
if (cached) return cached;
|
|
1324
|
+
const bound = value.bind(obj);
|
|
1325
|
+
state.boundFunctionsCache.set(value, bound);
|
|
1326
|
+
return bound;
|
|
1327
|
+
}
|
|
1328
|
+
const fullPath = path ? `${path}.${String(prop)}` : String(prop);
|
|
1329
|
+
if (typeof prop === "string" && state.isTracking) {
|
|
1330
|
+
if (!prop.startsWith("_") && !prop.startsWith("$$")) state.trackedPaths.add(fullPath);
|
|
1331
|
+
}
|
|
1332
|
+
if (isProxyable(value)) return createProxyInternal(state, value, fullPath, depth + 1);
|
|
1333
|
+
return value;
|
|
1334
|
+
},
|
|
1335
|
+
has: (obj, prop) => {
|
|
1336
|
+
if (typeof prop === "string" && state.isTracking) {
|
|
1337
|
+
const fullPath = path ? `${path}.${prop}` : prop;
|
|
1338
|
+
state.trackedPaths.add(fullPath);
|
|
1339
|
+
}
|
|
1340
|
+
return Reflect.has(obj, prop);
|
|
1341
|
+
},
|
|
1342
|
+
ownKeys: (obj) => {
|
|
1343
|
+
if (state.isTracking && path) state.trackedPaths.add(path);
|
|
1344
|
+
return Reflect.ownKeys(obj);
|
|
1345
|
+
}
|
|
1346
|
+
});
|
|
1347
|
+
state.proxyCache.set(target, proxy);
|
|
1348
|
+
return proxy;
|
|
1349
|
+
}
|
|
1350
|
+
/**
|
|
1351
|
+
* Create a proxy for a target with caching
|
|
1352
|
+
*
|
|
1353
|
+
* If the target hasn't changed since the last call, returns the cached proxy.
|
|
1354
|
+
* Otherwise, creates a new proxy and caches it.
|
|
1355
|
+
*
|
|
1356
|
+
* This is the main entry point for creating tracked proxies.
|
|
1357
|
+
*/
|
|
1358
|
+
function createProxyForTarget(state, target) {
|
|
1359
|
+
if (state.lastProxiedState === target && state.lastProxy) return state.lastProxy;
|
|
1360
|
+
state.proxyCache = /* @__PURE__ */ new WeakMap();
|
|
1361
|
+
state.boundFunctionsCache = null;
|
|
1362
|
+
const proxy = createProxyInternal(state, target, "", 0);
|
|
1363
|
+
state.lastProxiedState = target;
|
|
1364
|
+
state.lastProxy = proxy;
|
|
1365
|
+
return proxy;
|
|
1366
|
+
}
|
|
1367
|
+
|
|
1368
|
+
//#endregion
|
|
1369
|
+
//#region src/tracking/path-utils.ts
|
|
1370
|
+
/**
|
|
1371
|
+
* Path utilities for dependency tracking
|
|
1372
|
+
*
|
|
1373
|
+
* Provides utilities for parsing property paths and extracting values
|
|
1374
|
+
* from nested objects using path strings.
|
|
1375
|
+
*/
|
|
1376
|
+
/**
|
|
1377
|
+
* Parse a property path string into an array of segments
|
|
1378
|
+
*
|
|
1379
|
+
* Handles both dot notation (a.b.c) and bracket notation (a[0].b)
|
|
1380
|
+
*
|
|
1381
|
+
* @example
|
|
1382
|
+
* parsePath('user.name') // ['user', 'name']
|
|
1383
|
+
* parsePath('items[0].name') // ['items', '0', 'name']
|
|
1384
|
+
* parsePath('data.users[2].address.city') // ['data', 'users', '2', 'address', 'city']
|
|
1385
|
+
*/
|
|
1386
|
+
function parsePath(path) {
|
|
1387
|
+
const segments = [];
|
|
1388
|
+
let current = "";
|
|
1389
|
+
let i = 0;
|
|
1390
|
+
while (i < path.length) {
|
|
1391
|
+
const char = path[i];
|
|
1392
|
+
if (char === ".") {
|
|
1393
|
+
if (current) segments.push(current);
|
|
1394
|
+
current = "";
|
|
1395
|
+
} else if (char === "[") {
|
|
1396
|
+
if (current) segments.push(current);
|
|
1397
|
+
current = "";
|
|
1398
|
+
i++;
|
|
1399
|
+
while (i < path.length && path[i] !== "]") current += path[i++];
|
|
1400
|
+
if (current) segments.push(current);
|
|
1401
|
+
current = "";
|
|
1402
|
+
} else current += char;
|
|
1403
|
+
i++;
|
|
1404
|
+
}
|
|
1405
|
+
if (current) segments.push(current);
|
|
1406
|
+
return segments;
|
|
1407
|
+
}
|
|
1408
|
+
/**
|
|
1409
|
+
* Get a value from an object using a path of segments
|
|
1410
|
+
*
|
|
1411
|
+
* @example
|
|
1412
|
+
* const obj = { user: { name: 'Alice', age: 30 } }
|
|
1413
|
+
* getValueAtPath(obj, ['user', 'name']) // 'Alice'
|
|
1414
|
+
* getValueAtPath(obj, ['user', 'age']) // 30
|
|
1415
|
+
* getValueAtPath(obj, ['user', 'missing']) // undefined
|
|
1416
|
+
*/
|
|
1417
|
+
function getValueAtPath(obj, segments) {
|
|
1418
|
+
if (obj == null) return void 0;
|
|
1419
|
+
let current = obj;
|
|
1420
|
+
for (let i = 0; i < segments.length; i++) {
|
|
1421
|
+
current = current[segments[i]];
|
|
1422
|
+
if (current == null) return void 0;
|
|
1423
|
+
}
|
|
1424
|
+
return current;
|
|
1425
|
+
}
|
|
1426
|
+
/**
|
|
1427
|
+
* Shallow equality comparison for arrays
|
|
1428
|
+
*
|
|
1429
|
+
* Compares two arrays element-by-element using Object.is
|
|
1430
|
+
*
|
|
1431
|
+
* @example
|
|
1432
|
+
* shallowEqual([1, 2, 3], [1, 2, 3]) // true
|
|
1433
|
+
* shallowEqual([1, 2, 3], [1, 2, 4]) // false
|
|
1434
|
+
* shallowEqual([1, 2], [1, 2, 3]) // false
|
|
1435
|
+
*/
|
|
1436
|
+
function shallowEqual(arr1, arr2) {
|
|
1437
|
+
if (arr1.length !== arr2.length) return false;
|
|
1438
|
+
for (let i = 0; i < arr1.length; i++) if (!Object.is(arr1[i], arr2[i])) return false;
|
|
1439
|
+
return true;
|
|
1440
|
+
}
|
|
1441
|
+
|
|
1442
|
+
//#endregion
|
|
1443
|
+
//#region src/tracking/dependency-tracker.ts
|
|
1444
|
+
function isChildPath(child, parent) {
|
|
1445
|
+
if (child === parent) return false;
|
|
1446
|
+
return child.startsWith(parent + ".") || child.startsWith(parent + "[");
|
|
1447
|
+
}
|
|
1448
|
+
/**
|
|
1449
|
+
* Extract the parent array path from an array child path
|
|
1450
|
+
* Examples:
|
|
1451
|
+
* 'messages.length' -> 'messages'
|
|
1452
|
+
* 'messages[0]' -> 'messages'
|
|
1453
|
+
* 'messages[0].text' -> 'messages'
|
|
1454
|
+
* 'data.items[0]' -> 'data.items'
|
|
1455
|
+
*/
|
|
1456
|
+
function getArrayParentPath(path) {
|
|
1457
|
+
if (path.endsWith(".length")) return path.slice(0, -7);
|
|
1458
|
+
const arrayIndexMatch = path.match(/^(.+?)\[\d+\]/);
|
|
1459
|
+
if (arrayIndexMatch) return arrayIndexMatch[1];
|
|
1460
|
+
return null;
|
|
1461
|
+
}
|
|
1462
|
+
function optimizeTrackedPaths(paths) {
|
|
1463
|
+
if (paths.size === 0) return /* @__PURE__ */ new Set();
|
|
1464
|
+
if (paths.size === 1) return new Set(paths);
|
|
1465
|
+
const sortedPaths = Array.from(paths).sort((a, b) => b.length - a.length);
|
|
1466
|
+
const optimized = /* @__PURE__ */ new Set();
|
|
1467
|
+
for (const path of sortedPaths) {
|
|
1468
|
+
let hasMoreSpecificChild = false;
|
|
1469
|
+
for (const optimizedPath of optimized) if (isChildPath(optimizedPath, path)) {
|
|
1470
|
+
hasMoreSpecificChild = true;
|
|
1471
|
+
break;
|
|
1472
|
+
}
|
|
1473
|
+
if (!hasMoreSpecificChild) optimized.add(path);
|
|
1474
|
+
}
|
|
1475
|
+
const arrayParents = /* @__PURE__ */ new Set();
|
|
1476
|
+
for (const path of optimized) {
|
|
1477
|
+
const arrayParent = getArrayParentPath(path);
|
|
1478
|
+
if (arrayParent) arrayParents.add(arrayParent);
|
|
1479
|
+
}
|
|
1480
|
+
for (const arrayParent of arrayParents) optimized.add(arrayParent);
|
|
1481
|
+
return optimized;
|
|
1482
|
+
}
|
|
1483
|
+
function createTrackerState() {
|
|
1484
|
+
return {
|
|
1485
|
+
proxyTrackerState: createProxyTrackerState(),
|
|
1486
|
+
previousRenderPaths: /* @__PURE__ */ new Set(),
|
|
1487
|
+
currentRenderPaths: /* @__PURE__ */ new Set(),
|
|
1488
|
+
pathCache: /* @__PURE__ */ new Map(),
|
|
1489
|
+
lastCheckedState: null,
|
|
1490
|
+
lastCheckedValues: /* @__PURE__ */ new Map()
|
|
1491
|
+
};
|
|
1492
|
+
}
|
|
1493
|
+
function startTracking(tracker) {
|
|
1494
|
+
startProxyTracking(tracker.proxyTrackerState);
|
|
1495
|
+
}
|
|
1496
|
+
function createProxy(tracker, state) {
|
|
1497
|
+
return createProxyForTarget(tracker.proxyTrackerState, state);
|
|
1498
|
+
}
|
|
1499
|
+
function captureTrackedPaths(tracker, state) {
|
|
1500
|
+
tracker.previousRenderPaths = tracker.currentRenderPaths;
|
|
1501
|
+
tracker.currentRenderPaths = optimizeTrackedPaths(stopProxyTracking(tracker.proxyTrackerState));
|
|
1502
|
+
if (tracker.previousRenderPaths.size === 0 && tracker.currentRenderPaths.size === 0) return;
|
|
1503
|
+
const trackedPathsUnion = new Set(tracker.previousRenderPaths);
|
|
1504
|
+
for (const path of tracker.currentRenderPaths) trackedPathsUnion.add(path);
|
|
1505
|
+
const canReuseCache = tracker.lastCheckedState === state;
|
|
1506
|
+
for (const path of trackedPathsUnion) if (!tracker.pathCache.has(path)) {
|
|
1507
|
+
const segments = parsePath(path);
|
|
1508
|
+
const value = canReuseCache && tracker.lastCheckedValues.has(path) ? tracker.lastCheckedValues.get(path) : getValueAtPath(state, segments);
|
|
1509
|
+
tracker.pathCache.set(path, {
|
|
1510
|
+
segments,
|
|
1511
|
+
value
|
|
1512
|
+
});
|
|
1513
|
+
} else {
|
|
1514
|
+
const info$1 = tracker.pathCache.get(path);
|
|
1515
|
+
info$1.value = canReuseCache && tracker.lastCheckedValues.has(path) ? tracker.lastCheckedValues.get(path) : getValueAtPath(state, info$1.segments);
|
|
1516
|
+
}
|
|
1517
|
+
tracker.lastCheckedValues.clear();
|
|
1518
|
+
}
|
|
1519
|
+
function hasChanges(tracker, state) {
|
|
1520
|
+
if (tracker.pathCache.size === 0) return true;
|
|
1521
|
+
tracker.lastCheckedValues.clear();
|
|
1522
|
+
for (const [path, info$1] of tracker.pathCache.entries()) {
|
|
1523
|
+
const currentValue = getValueAtPath(state, info$1.segments);
|
|
1524
|
+
tracker.lastCheckedValues.set(path, currentValue);
|
|
1525
|
+
if (!Object.is(currentValue, info$1.value)) {
|
|
1526
|
+
tracker.lastCheckedState = state;
|
|
1527
|
+
return true;
|
|
1528
|
+
}
|
|
1529
|
+
}
|
|
1530
|
+
tracker.lastCheckedState = state;
|
|
1531
|
+
return false;
|
|
1532
|
+
}
|
|
1533
|
+
function hasTrackedData(tracker) {
|
|
1534
|
+
return tracker.proxyTrackerState.trackedPaths.size > 0 || tracker.pathCache.size > 0 || tracker.previousRenderPaths.size > 0;
|
|
1535
|
+
}
|
|
1536
|
+
|
|
1537
|
+
//#endregion
|
|
1538
|
+
//#region src/adapter/framework-adapter.ts
|
|
1539
|
+
var ExternalDependencyManager = class {
|
|
1540
|
+
constructor() {
|
|
1541
|
+
this.subscriptions = [];
|
|
1542
|
+
this.previousDeps = /* @__PURE__ */ new Set();
|
|
1543
|
+
}
|
|
1544
|
+
areDependenciesEqual(oldDeps, newDeps) {
|
|
1545
|
+
if (oldDeps.size !== newDeps.size) return false;
|
|
1546
|
+
for (const dep of newDeps) if (!oldDeps.has(dep)) return false;
|
|
1547
|
+
return true;
|
|
1548
|
+
}
|
|
1549
|
+
updateSubscriptions(getterTracker, rawInstance, onGetterChange) {
|
|
1550
|
+
if (!getterTracker?.externalDependencies) return false;
|
|
1551
|
+
const currentDeps = getterTracker.externalDependencies;
|
|
1552
|
+
if (this.areDependenciesEqual(this.previousDeps, currentDeps)) {
|
|
1553
|
+
clearExternalDependencies(getterTracker);
|
|
1554
|
+
return false;
|
|
1555
|
+
}
|
|
1556
|
+
this.cleanup();
|
|
1557
|
+
for (const externalBloc of currentDeps) {
|
|
1558
|
+
if (externalBloc === rawInstance) continue;
|
|
1559
|
+
const unsub = externalBloc.subscribe(() => {
|
|
1560
|
+
invalidateRenderCache(getterTracker);
|
|
1561
|
+
if (hasGetterChanges(rawInstance, getterTracker)) onGetterChange();
|
|
1562
|
+
});
|
|
1563
|
+
this.subscriptions.push(unsub);
|
|
1564
|
+
}
|
|
1565
|
+
this.previousDeps = new Set(currentDeps);
|
|
1566
|
+
clearExternalDependencies(getterTracker);
|
|
1567
|
+
return true;
|
|
1568
|
+
}
|
|
1569
|
+
cleanup() {
|
|
1570
|
+
this.subscriptions.forEach((unsub) => unsub());
|
|
1571
|
+
this.subscriptions = [];
|
|
1572
|
+
}
|
|
1573
|
+
};
|
|
1574
|
+
function createAutoTrackSubscribe(instance, adapterState) {
|
|
1575
|
+
return (callback) => {
|
|
1576
|
+
return instance.subscribe(() => {
|
|
1577
|
+
const tracker = adapterState.tracker || (adapterState.tracker = createTrackerState());
|
|
1578
|
+
const hasStateDeps = tracker.pathCache && tracker.pathCache.size > 0;
|
|
1579
|
+
const hasGetterDeps = adapterState.getterTracker && adapterState.getterTracker.trackedGetters.size > 0;
|
|
1580
|
+
const isPrimitiveState = instance.state !== null && typeof instance.state !== "object" && typeof instance.state !== "function";
|
|
1581
|
+
if (!hasStateDeps && !hasGetterDeps && !isPrimitiveState) return;
|
|
1582
|
+
let stateChanged = hasChanges(tracker, instance.state);
|
|
1583
|
+
if (!hasStateDeps && hasGetterDeps) stateChanged = false;
|
|
1584
|
+
if (stateChanged) {
|
|
1585
|
+
callback();
|
|
1586
|
+
return;
|
|
1587
|
+
}
|
|
1588
|
+
if (hasGetterChanges(instance, adapterState.getterTracker)) callback();
|
|
1589
|
+
});
|
|
1590
|
+
};
|
|
1591
|
+
}
|
|
1592
|
+
function createManualDepsSubscribe(instance, adapterState, config) {
|
|
1593
|
+
return (callback) => {
|
|
1594
|
+
return instance.subscribe(() => {
|
|
1595
|
+
const newDeps = config.dependencies(instance.state, instance);
|
|
1596
|
+
if (!adapterState.manualDepsCache || !shallowEqual(adapterState.manualDepsCache, newDeps)) {
|
|
1597
|
+
adapterState.manualDepsCache = newDeps;
|
|
1598
|
+
callback();
|
|
1599
|
+
}
|
|
1600
|
+
});
|
|
1601
|
+
};
|
|
1602
|
+
}
|
|
1603
|
+
function createNoTrackSubscribe(instance) {
|
|
1604
|
+
return (callback) => instance.subscribe(callback);
|
|
1605
|
+
}
|
|
1606
|
+
function createAutoTrackSnapshot(instance, adapterState) {
|
|
1607
|
+
return () => {
|
|
1608
|
+
const tracker = adapterState.tracker || (adapterState.tracker = createTrackerState());
|
|
1609
|
+
if (hasTrackedData(tracker)) captureTrackedPaths(tracker, instance.state);
|
|
1610
|
+
if (adapterState.getterTracker) {
|
|
1611
|
+
invalidateRenderCache(adapterState.getterTracker);
|
|
1612
|
+
commitTrackedGetters(adapterState.getterTracker);
|
|
1613
|
+
adapterState.getterTracker.isTracking = true;
|
|
1614
|
+
setActiveTracker(instance, adapterState.getterTracker);
|
|
1615
|
+
}
|
|
1616
|
+
startTracking(tracker);
|
|
1617
|
+
return createProxy(tracker, instance.state);
|
|
1618
|
+
};
|
|
1619
|
+
}
|
|
1620
|
+
function createManualDepsSnapshot(instance, adapterState, config) {
|
|
1621
|
+
return () => {
|
|
1622
|
+
adapterState.manualDepsCache = config.dependencies(instance.state, instance);
|
|
1623
|
+
return instance.state;
|
|
1624
|
+
};
|
|
1625
|
+
}
|
|
1626
|
+
function createNoTrackSnapshot(instance) {
|
|
1627
|
+
return () => instance.state;
|
|
1628
|
+
}
|
|
1629
|
+
function initAutoTrackState(instance) {
|
|
1630
|
+
return {
|
|
1631
|
+
tracker: null,
|
|
1632
|
+
manualDepsCache: null,
|
|
1633
|
+
getterTracker: createGetterTracker(),
|
|
1634
|
+
proxiedBloc: createBlocProxy(instance)
|
|
1635
|
+
};
|
|
1636
|
+
}
|
|
1637
|
+
function initManualDepsState(instance) {
|
|
1638
|
+
return {
|
|
1639
|
+
tracker: null,
|
|
1640
|
+
manualDepsCache: null,
|
|
1641
|
+
getterTracker: null,
|
|
1642
|
+
proxiedBloc: instance
|
|
1643
|
+
};
|
|
1644
|
+
}
|
|
1645
|
+
function initNoTrackState(instance) {
|
|
1646
|
+
return {
|
|
1647
|
+
tracker: null,
|
|
1648
|
+
manualDepsCache: null,
|
|
1649
|
+
getterTracker: null,
|
|
1650
|
+
proxiedBloc: instance
|
|
1651
|
+
};
|
|
1652
|
+
}
|
|
1653
|
+
function disableGetterTracking(adapterState, rawInstance) {
|
|
1654
|
+
if (adapterState.getterTracker) {
|
|
1655
|
+
adapterState.getterTracker.isTracking = false;
|
|
1656
|
+
clearActiveTracker(rawInstance);
|
|
1657
|
+
}
|
|
1658
|
+
}
|
|
1659
|
+
|
|
1660
|
+
//#endregion
|
|
1661
|
+
//#region src/logging/Logger.ts
|
|
1662
|
+
let LogLevel = /* @__PURE__ */ function(LogLevel$1) {
|
|
1663
|
+
LogLevel$1[LogLevel$1["ERROR"] = 0] = "ERROR";
|
|
1664
|
+
LogLevel$1[LogLevel$1["WARN"] = 1] = "WARN";
|
|
1665
|
+
LogLevel$1[LogLevel$1["INFO"] = 2] = "INFO";
|
|
1666
|
+
LogLevel$1[LogLevel$1["DEBUG"] = 3] = "DEBUG";
|
|
1667
|
+
return LogLevel$1;
|
|
1668
|
+
}({});
|
|
1669
|
+
/**
|
|
1670
|
+
* Creates a logger instance with given configuration
|
|
1671
|
+
*
|
|
1672
|
+
* @param config - Logger configuration
|
|
1673
|
+
* @returns Logger instance with debug, info, warn, error, and configure methods
|
|
1674
|
+
*
|
|
1675
|
+
* @example
|
|
1676
|
+
* ```ts
|
|
1677
|
+
* const logger = createLogger({
|
|
1678
|
+
* enabled: true,
|
|
1679
|
+
* level: LogLevel.DEBUG,
|
|
1680
|
+
* output: (entry) => console.log(JSON.stringify(entry))
|
|
1681
|
+
* });
|
|
1682
|
+
* logger.debug('MyComponent', 'Rendering', { props: { foo: 'bar' } });
|
|
1683
|
+
* ```
|
|
1684
|
+
*/
|
|
1685
|
+
function createLogger(config) {
|
|
1686
|
+
const shouldLog = (level) => {
|
|
1687
|
+
return config.enabled && config.level >= level;
|
|
1688
|
+
};
|
|
1689
|
+
const log = (level, context, message, data) => {
|
|
1690
|
+
try {
|
|
1691
|
+
const entry = {
|
|
1692
|
+
level,
|
|
1693
|
+
context,
|
|
1694
|
+
message,
|
|
1695
|
+
timestamp: Date.now(),
|
|
1696
|
+
...data !== void 0 && { data: serialize(data) }
|
|
1697
|
+
};
|
|
1698
|
+
config.output(entry);
|
|
1699
|
+
} catch (e) {
|
|
1700
|
+
console.error("[BlacLogger] Error logging:", e);
|
|
1701
|
+
}
|
|
1702
|
+
};
|
|
1703
|
+
const serialize = (data) => {
|
|
1704
|
+
try {
|
|
1705
|
+
return JSON.parse(JSON.stringify(data));
|
|
1706
|
+
} catch {
|
|
1707
|
+
return String(data);
|
|
1708
|
+
}
|
|
1709
|
+
};
|
|
1710
|
+
return {
|
|
1711
|
+
debug: (context, message, data) => {
|
|
1712
|
+
if (shouldLog(LogLevel.DEBUG)) log("DEBUG", context, message, data);
|
|
1713
|
+
},
|
|
1714
|
+
info: (context, message, data) => {
|
|
1715
|
+
if (shouldLog(LogLevel.INFO)) log("INFO", context, message, data);
|
|
1716
|
+
},
|
|
1717
|
+
warn: (context, message, data) => {
|
|
1718
|
+
if (shouldLog(LogLevel.WARN)) log("WARN", context, message, data);
|
|
1719
|
+
},
|
|
1720
|
+
error: (context, message, data) => {
|
|
1721
|
+
if (shouldLog(LogLevel.ERROR)) log("ERROR", context, message, data);
|
|
1722
|
+
},
|
|
1723
|
+
configure: (opts) => {
|
|
1724
|
+
Object.assign(config, opts);
|
|
1725
|
+
}
|
|
1726
|
+
};
|
|
1727
|
+
}
|
|
1728
|
+
let currentConfig = {
|
|
1729
|
+
enabled: false,
|
|
1730
|
+
level: LogLevel.INFO,
|
|
1731
|
+
output: (entry) => console.log(JSON.stringify(entry))
|
|
1732
|
+
};
|
|
1733
|
+
let defaultLogger = createLogger(currentConfig);
|
|
1734
|
+
const debug = (context, message, data) => defaultLogger.debug(context, message, data);
|
|
1735
|
+
const info = (context, message, data) => defaultLogger.info(context, message, data);
|
|
1736
|
+
const warn = (context, message, data) => defaultLogger.warn(context, message, data);
|
|
1737
|
+
const error = (context, message, data) => defaultLogger.error(context, message, data);
|
|
1738
|
+
/**
|
|
1739
|
+
* Configuration function that recreates the default logger
|
|
1740
|
+
*
|
|
1741
|
+
* @param opts - Partial logger configuration
|
|
1742
|
+
*
|
|
1743
|
+
* @example
|
|
1744
|
+
* ```ts
|
|
1745
|
+
* configureLogger({ enabled: true, level: LogLevel.DEBUG });
|
|
1746
|
+
* ```
|
|
1747
|
+
*/
|
|
1748
|
+
function configureLogger(opts) {
|
|
1749
|
+
currentConfig = {
|
|
1750
|
+
...currentConfig,
|
|
1751
|
+
...opts
|
|
1752
|
+
};
|
|
1753
|
+
defaultLogger = createLogger(currentConfig);
|
|
1754
|
+
}
|
|
1755
|
+
|
|
1756
|
+
//#endregion
|
|
1757
|
+
//#region src/decorators/blac.ts
|
|
1758
|
+
/**
|
|
1759
|
+
* Decorator to configure StateContainer classes.
|
|
1760
|
+
*
|
|
1761
|
+
* @example Decorator syntax (requires experimentalDecorators or TC39 decorators)
|
|
1762
|
+
* ```typescript
|
|
1763
|
+
* @blac({ isolated: true })
|
|
1764
|
+
* class FormBloc extends Cubit<FormState> {}
|
|
1765
|
+
*
|
|
1766
|
+
* @blac({ keepAlive: true })
|
|
1767
|
+
* class AuthBloc extends Cubit<AuthState> {}
|
|
1768
|
+
*
|
|
1769
|
+
* @blac({ excludeFromDevTools: true })
|
|
1770
|
+
* class InternalBloc extends Cubit<InternalState> {}
|
|
1771
|
+
* ```
|
|
1772
|
+
*
|
|
1773
|
+
* @example Function syntax (no decorator support needed)
|
|
1774
|
+
* ```typescript
|
|
1775
|
+
* const FormBloc = blac({ isolated: true })(
|
|
1776
|
+
* class extends Cubit<FormState> {}
|
|
1777
|
+
* );
|
|
1778
|
+
* ```
|
|
1779
|
+
*/
|
|
1780
|
+
function blac(options) {
|
|
1781
|
+
return function(target, _context) {
|
|
1782
|
+
if ("isolated" in options && options.isolated) target[BLAC_STATIC_PROPS.ISOLATED] = true;
|
|
1783
|
+
if ("keepAlive" in options && options.keepAlive) target[BLAC_STATIC_PROPS.KEEP_ALIVE] = true;
|
|
1784
|
+
if ("excludeFromDevTools" in options && options.excludeFromDevTools) target[BLAC_STATIC_PROPS.EXCLUDE_FROM_DEVTOOLS] = true;
|
|
1785
|
+
return target;
|
|
1786
|
+
};
|
|
1787
|
+
}
|
|
1788
|
+
|
|
1789
|
+
//#endregion
|
|
1790
|
+
exports.AuthVertex = AuthVertex;
|
|
1791
|
+
exports.BLAC_DEFAULTS = BLAC_DEFAULTS;
|
|
1792
|
+
exports.BLAC_ERROR_PREFIX = BLAC_ERROR_PREFIX;
|
|
1793
|
+
exports.BLAC_ID_PATTERNS = BLAC_ID_PATTERNS;
|
|
1794
|
+
exports.BLAC_STATIC_PROPS = BLAC_STATIC_PROPS;
|
|
1795
|
+
exports.CounterCubit = CounterCubit;
|
|
1796
|
+
exports.CounterVertex = CounterVertex;
|
|
1797
|
+
exports.Cubit = Cubit;
|
|
1798
|
+
exports.DecrementEvent = DecrementEvent;
|
|
1799
|
+
exports.ExternalDependencyManager = ExternalDependencyManager;
|
|
1800
|
+
exports.IncrementEvent = IncrementEvent;
|
|
1801
|
+
exports.LogLevel = LogLevel;
|
|
1802
|
+
exports.LoginEvent = LoginEvent;
|
|
1803
|
+
exports.LogoutEvent = LogoutEvent;
|
|
1804
|
+
exports.PluginManager = PluginManager;
|
|
1805
|
+
exports.ResetEvent = ResetEvent;
|
|
1806
|
+
exports.StateContainer = StateContainer;
|
|
1807
|
+
exports.StateContainerRegistry = StateContainerRegistry;
|
|
1808
|
+
exports.TodoCubit = TodoCubit;
|
|
1809
|
+
exports.Vertex = Vertex;
|
|
1810
|
+
exports.__resetIdCounters = __resetIdCounters;
|
|
1811
|
+
exports.blac = blac;
|
|
1812
|
+
exports.captureTrackedPaths = captureTrackedPaths;
|
|
1813
|
+
exports.clearActiveTracker = clearActiveTracker;
|
|
1814
|
+
exports.clearExternalDependencies = clearExternalDependencies;
|
|
1815
|
+
exports.commitTrackedGetters = commitTrackedGetters;
|
|
1816
|
+
exports.configureLogger = configureLogger;
|
|
1817
|
+
exports.createArrayProxy = createArrayProxy;
|
|
1818
|
+
exports.createAutoTrackSnapshot = createAutoTrackSnapshot;
|
|
1819
|
+
exports.createAutoTrackSubscribe = createAutoTrackSubscribe;
|
|
1820
|
+
exports.createBlocProxy = createBlocProxy;
|
|
1821
|
+
exports.createGetterTracker = createGetterTracker;
|
|
1822
|
+
exports.createIdGenerator = createIdGenerator;
|
|
1823
|
+
exports.createLogger = createLogger;
|
|
1824
|
+
exports.createManualDepsSnapshot = createManualDepsSnapshot;
|
|
1825
|
+
exports.createManualDepsSubscribe = createManualDepsSubscribe;
|
|
1826
|
+
exports.createNoTrackSnapshot = createNoTrackSnapshot;
|
|
1827
|
+
exports.createNoTrackSubscribe = createNoTrackSubscribe;
|
|
1828
|
+
exports.createPluginManager = createPluginManager;
|
|
1829
|
+
exports.createProxy = createProxy;
|
|
1830
|
+
exports.createProxyForTarget = createProxyForTarget;
|
|
1831
|
+
exports.createProxyInternal = createProxyInternal;
|
|
1832
|
+
exports.createProxyTrackerState = createProxyTrackerState;
|
|
1833
|
+
exports.createTrackerState = createTrackerState;
|
|
1834
|
+
exports.debug = debug;
|
|
1835
|
+
exports.disableGetterTracking = disableGetterTracking;
|
|
1836
|
+
exports.error = error;
|
|
1837
|
+
exports.generateId = generateId;
|
|
1838
|
+
exports.generateIsolatedKey = generateIsolatedKey;
|
|
1839
|
+
exports.generateSimpleId = generateSimpleId;
|
|
1840
|
+
exports.getActiveTracker = getActiveTracker;
|
|
1841
|
+
exports.getDescriptor = getDescriptor;
|
|
1842
|
+
exports.getGetterExecutionContext = getGetterExecutionContext;
|
|
1843
|
+
exports.getPluginManager = getPluginManager;
|
|
1844
|
+
exports.getStaticProp = getStaticProp;
|
|
1845
|
+
exports.getValueAtPath = getValueAtPath;
|
|
1846
|
+
exports.globalRegistry = globalRegistry;
|
|
1847
|
+
exports.hasChanges = hasChanges;
|
|
1848
|
+
exports.hasGetterChanges = hasGetterChanges;
|
|
1849
|
+
exports.hasInitHook = hasInitHook;
|
|
1850
|
+
exports.hasTrackedData = hasTrackedData;
|
|
1851
|
+
exports.info = info;
|
|
1852
|
+
exports.initAutoTrackState = initAutoTrackState;
|
|
1853
|
+
exports.initManualDepsState = initManualDepsState;
|
|
1854
|
+
exports.initNoTrackState = initNoTrackState;
|
|
1855
|
+
exports.invalidateRenderCache = invalidateRenderCache;
|
|
1856
|
+
exports.isExcludedFromDevTools = isExcludedFromDevTools;
|
|
1857
|
+
exports.isGetter = isGetter;
|
|
1858
|
+
exports.isIsolatedClass = isIsolatedClass;
|
|
1859
|
+
exports.isIsolatedKey = isIsolatedKey;
|
|
1860
|
+
exports.isKeepAliveClass = isKeepAliveClass;
|
|
1861
|
+
exports.isProxyable = isProxyable;
|
|
1862
|
+
exports.parsePath = parsePath;
|
|
1863
|
+
exports.resetGetterTracker = resetGetterTracker;
|
|
1864
|
+
exports.setActiveTracker = setActiveTracker;
|
|
1865
|
+
exports.shallowEqual = shallowEqual;
|
|
1866
|
+
exports.startProxyTracking = startProxyTracking;
|
|
1867
|
+
exports.startTracking = startTracking;
|
|
1868
|
+
exports.stopProxyTracking = stopProxyTracking;
|
|
1869
|
+
exports.warn = warn;
|
|
1870
|
+
//# sourceMappingURL=index.cjs.map
|