@blac/core 2.0.1 → 2.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -55
- package/dist/StateContainerRegistry.cjs.map +1 -0
- package/dist/StateContainerRegistry.js.map +1 -0
- package/dist/debug.cjs +27 -0
- package/dist/debug.cjs.map +1 -0
- package/dist/debug.js +19 -0
- package/dist/debug.js.map +1 -0
- package/dist/dependency-manager.cjs.map +1 -0
- package/dist/dependency-manager.js.map +1 -0
- package/dist/index.cjs +227 -2874
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +14 -1115
- package/dist/index.d.ts +14 -1115
- package/dist/index.js +197 -2771
- package/dist/index.js.map +1 -1
- package/dist/management.cjs.map +1 -0
- package/dist/management.js.map +1 -0
- package/dist/plugins.cjs +4 -0
- package/dist/plugins.js +3 -0
- package/dist/tracking/dependency-manager.d.ts +37 -0
- package/dist/tracking/index.d.ts +12 -0
- package/dist/tracking/path-utils.d.ts +52 -0
- package/dist/tracking/tracked.d.ts +70 -0
- package/dist/tracking/tracking-proxy.d.ts +197 -0
- package/dist/tracking-proxy.cjs.map +1 -0
- package/dist/tracking-proxy.js.map +1 -0
- package/dist/tracking.cjs +19 -0
- package/dist/tracking.d.cts +9 -0
- package/dist/tracking.d.ts +9 -0
- package/dist/tracking.js +4 -0
- package/dist/types.cjs +14 -0
- package/dist/types.cjs.map +1 -0
- package/dist/types.d.cts +8 -0
- package/dist/types.d.ts +8 -0
- package/dist/types.js +13 -0
- package/dist/types.js.map +1 -0
- package/dist/watch-entry.cjs +111 -0
- package/dist/watch-entry.cjs.map +1 -0
- package/dist/watch-entry.js +107 -0
- package/dist/watch-entry.js.map +1 -0
- package/dist/watch.cjs.map +1 -0
- package/dist/watch.js.map +1 -0
- package/package.json +73 -2
package/dist/index.js
CHANGED
|
@@ -1,52 +1,12 @@
|
|
|
1
|
+
import { i as PluginManager, n as globalRegistry, r as isIsolatedClass, t as getPluginManager } from "./StateContainerRegistry.js";
|
|
2
|
+
import { _ as setActiveTracker, a as createBlocProxy, c as createGetterState, g as invalidateRenderCache, h as hasTrackedData, i as commitTrackedGetters, m as hasGetterChanges, n as clearActiveTracker, o as createDependencyProxy, p as hasDependencyChanges, r as clearExternalDependencies, s as createDependencyState, t as capturePaths, v as startDependency, w as BLAC_STATIC_PROPS, x as shallowEqual } from "./tracking-proxy.js";
|
|
3
|
+
import { a as getAll, i as forEach, n as clearAll, o as getRefCount, s as hasInstance, t as clear } from "./management.js";
|
|
4
|
+
import { n as watch, r as ensure, t as instance } from "./watch.js";
|
|
5
|
+
import { t as DependencyManager } from "./dependency-manager.js";
|
|
6
|
+
import "./tracking.js";
|
|
7
|
+
|
|
1
8
|
//#region src/utils/idGenerator.ts
|
|
2
9
|
/**
|
|
3
|
-
* Centralized ID Generation
|
|
4
|
-
*
|
|
5
|
-
* Provides consistent, collision-resistant ID generation for all BlaC subsystems.
|
|
6
|
-
* Uses timestamp + counter + random suffix for uniqueness.
|
|
7
|
-
*/
|
|
8
|
-
const globalCounters = /* @__PURE__ */ new Map();
|
|
9
|
-
/**
|
|
10
|
-
* Creates an ID generator with isolated counter state
|
|
11
|
-
*
|
|
12
|
-
* @param prefix - Prefix for generated IDs
|
|
13
|
-
* @returns Object with next(), nextSimple(), and reset() methods
|
|
14
|
-
*
|
|
15
|
-
* @example
|
|
16
|
-
* ```ts
|
|
17
|
-
* const generator = createIdGenerator('sub');
|
|
18
|
-
* const id1 = generator.next(); // "sub:1698765432100_1_a3k9d7f2q"
|
|
19
|
-
* const id2 = generator.next(); // "sub:1698765432101_2_b4n8e9g3r"
|
|
20
|
-
* ```
|
|
21
|
-
*/
|
|
22
|
-
function createIdGenerator(prefix) {
|
|
23
|
-
let counter = 0;
|
|
24
|
-
return {
|
|
25
|
-
next: () => `${prefix}:${Date.now()}_${++counter}_${Math.random().toString(36).substring(2, 11)}`,
|
|
26
|
-
nextSimple: () => `${prefix}:${++counter}`,
|
|
27
|
-
reset: () => {
|
|
28
|
-
counter = 0;
|
|
29
|
-
}
|
|
30
|
-
};
|
|
31
|
-
}
|
|
32
|
-
/**
|
|
33
|
-
* Generate ID with timestamp, counter, and random suffix (tree-shakeable)
|
|
34
|
-
*
|
|
35
|
-
* Format: `${prefix}:${timestamp}_${counter}_${random}`
|
|
36
|
-
*
|
|
37
|
-
* @param prefix - Prefix for the ID (e.g., 'sub', 'consumer', 'stage')
|
|
38
|
-
* @returns Branded ID string
|
|
39
|
-
*
|
|
40
|
-
* @example
|
|
41
|
-
* const id = generateId('sub');
|
|
42
|
-
* // Returns: "sub:1698765432100_1_a3k9d7f2q"
|
|
43
|
-
*/
|
|
44
|
-
function generateId(prefix) {
|
|
45
|
-
const counter = (globalCounters.get(prefix) ?? 0) + 1;
|
|
46
|
-
globalCounters.set(prefix, counter);
|
|
47
|
-
return `${prefix}:${Date.now()}_${counter}_${Math.random().toString(36).substring(2, 11)}`;
|
|
48
|
-
}
|
|
49
|
-
/**
|
|
50
10
|
* Generate simple ID with timestamp and random (no counter tracking)
|
|
51
11
|
*
|
|
52
12
|
* Format: `${prefix}:${timestamp}_${random}`
|
|
@@ -65,13 +25,6 @@ function generateSimpleId(prefix, affix) {
|
|
|
65
25
|
return `${prefix}:${Date.now()}_${Math.random().toString(36).substring(2, 11)}`;
|
|
66
26
|
}
|
|
67
27
|
/**
|
|
68
|
-
* Reset all counters for testing purposes
|
|
69
|
-
* @internal
|
|
70
|
-
*/
|
|
71
|
-
function __resetIdCounters() {
|
|
72
|
-
globalCounters.clear();
|
|
73
|
-
}
|
|
74
|
-
/**
|
|
75
28
|
* Generate a unique isolated instance key
|
|
76
29
|
* Uses base36 encoding for compact, URL-safe identifiers
|
|
77
30
|
*
|
|
@@ -83,2448 +36,244 @@ function __resetIdCounters() {
|
|
|
83
36
|
function generateIsolatedKey() {
|
|
84
37
|
return `isolated-${Math.random().toString(36).slice(2, 11)}`;
|
|
85
38
|
}
|
|
86
|
-
/**
|
|
87
|
-
* Check if a key is an isolated instance key
|
|
88
|
-
* @param key - The instance key to check
|
|
89
|
-
* @returns true if the key is an isolated instance key
|
|
90
|
-
*/
|
|
91
|
-
function isIsolatedKey(key) {
|
|
92
|
-
return key.startsWith("isolated-");
|
|
93
|
-
}
|
|
94
39
|
|
|
95
40
|
//#endregion
|
|
96
|
-
//#region src/
|
|
41
|
+
//#region src/core/StateContainer.ts
|
|
97
42
|
/**
|
|
98
|
-
*
|
|
99
|
-
*
|
|
43
|
+
* Abstract base class for all state containers in BlaC.
|
|
44
|
+
* Provides lifecycle management, subscription handling, ref counting,
|
|
45
|
+
* and integration with the global registry.
|
|
46
|
+
*
|
|
47
|
+
* @template S - State type managed by this container
|
|
100
48
|
*
|
|
101
49
|
* @example
|
|
102
50
|
* ```ts
|
|
103
|
-
*
|
|
104
|
-
*
|
|
51
|
+
* class CounterBloc extends StateContainer<{ count: number }> {
|
|
52
|
+
* constructor() {
|
|
53
|
+
* super({ count: 0 });
|
|
54
|
+
* }
|
|
55
|
+
* increment() {
|
|
56
|
+
* this.emit({ count: this.state.count + 1 });
|
|
57
|
+
* }
|
|
58
|
+
* }
|
|
105
59
|
* ```
|
|
106
60
|
*/
|
|
107
|
-
var
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
* @param registry - The StateContainerRegistry to monitor for lifecycle events
|
|
111
|
-
*/
|
|
112
|
-
constructor(registry) {
|
|
113
|
-
this.plugins = /* @__PURE__ */ new Map();
|
|
114
|
-
this.registry = registry;
|
|
115
|
-
this.setupLifecycleHooks();
|
|
61
|
+
var StateContainer = class StateContainer {
|
|
62
|
+
static {
|
|
63
|
+
this.__excludeFromDevTools = false;
|
|
116
64
|
}
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
65
|
+
static {
|
|
66
|
+
this.enableStackTrace = true;
|
|
67
|
+
}
|
|
68
|
+
constructor(initialState) {
|
|
69
|
+
this.listeners = /* @__PURE__ */ new Set();
|
|
70
|
+
this._disposed = false;
|
|
71
|
+
this.config = {};
|
|
72
|
+
this.systemEventHandlers = /* @__PURE__ */ new Map();
|
|
73
|
+
this.name = this.constructor.name;
|
|
74
|
+
this.debug = false;
|
|
75
|
+
this.instanceId = generateSimpleId(this.constructor.name, "main");
|
|
76
|
+
this.createdAt = Date.now();
|
|
77
|
+
this.lastUpdateTimestamp = Date.now();
|
|
78
|
+
this.onSystemEvent = (event, handler) => {
|
|
79
|
+
let handlers = this.systemEventHandlers.get(event);
|
|
80
|
+
if (!handlers) {
|
|
81
|
+
handlers = /* @__PURE__ */ new Set();
|
|
82
|
+
this.systemEventHandlers.set(event, handlers);
|
|
83
|
+
}
|
|
84
|
+
handlers.add(handler);
|
|
85
|
+
return () => {
|
|
86
|
+
handlers?.delete(handler);
|
|
87
|
+
};
|
|
128
88
|
};
|
|
129
|
-
|
|
130
|
-
console.log(`[BlaC] Plugin "${plugin.name}" skipped (environment mismatch)`);
|
|
131
|
-
return;
|
|
132
|
-
}
|
|
133
|
-
if (this.plugins.has(plugin.name)) throw new Error(`Plugin "${plugin.name}" is already installed`);
|
|
134
|
-
const context = this.createPluginContext();
|
|
135
|
-
this.plugins.set(plugin.name, {
|
|
136
|
-
plugin,
|
|
137
|
-
config: effectiveConfig,
|
|
138
|
-
context
|
|
139
|
-
});
|
|
140
|
-
if (plugin.onInstall) try {
|
|
141
|
-
plugin.onInstall(context);
|
|
142
|
-
} catch (error$1) {
|
|
143
|
-
console.error(`[BlaC] Error installing plugin "${plugin.name}":`, error$1);
|
|
144
|
-
this.plugins.delete(plugin.name);
|
|
145
|
-
throw error$1;
|
|
146
|
-
}
|
|
147
|
-
console.log(`[BlaC] Plugin "${plugin.name}" v${plugin.version} installed`);
|
|
89
|
+
this._state = initialState;
|
|
148
90
|
}
|
|
149
91
|
/**
|
|
150
|
-
*
|
|
151
|
-
*
|
|
152
|
-
* @
|
|
92
|
+
* Initialize configuration for this instance.
|
|
93
|
+
* Called by the registry after construction.
|
|
94
|
+
* @param config - Configuration options
|
|
153
95
|
*/
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
console.error(`[BlaC] Error uninstalling plugin "${pluginName}":`, error$1);
|
|
161
|
-
}
|
|
162
|
-
this.plugins.delete(pluginName);
|
|
163
|
-
console.log(`[BlaC] Plugin "${pluginName}" uninstalled`);
|
|
96
|
+
initConfig(config) {
|
|
97
|
+
this.config = { ...config };
|
|
98
|
+
this.name = this.config.name || this.constructor.name;
|
|
99
|
+
this.debug = this.config.debug ?? false;
|
|
100
|
+
this.instanceId = generateSimpleId(this.constructor.name, this.config.instanceId);
|
|
101
|
+
globalRegistry.emit("created", this);
|
|
164
102
|
}
|
|
165
|
-
/**
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
* @returns The plugin instance or undefined if not found
|
|
169
|
-
*/
|
|
170
|
-
getPlugin(pluginName) {
|
|
171
|
-
return this.plugins.get(pluginName)?.plugin;
|
|
103
|
+
/** Current state value */
|
|
104
|
+
get state() {
|
|
105
|
+
return this._state;
|
|
172
106
|
}
|
|
173
|
-
/**
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
*/
|
|
177
|
-
getAllPlugins() {
|
|
178
|
-
return Array.from(this.plugins.values()).map((p) => p.plugin);
|
|
107
|
+
/** Whether this instance has been disposed */
|
|
108
|
+
get isDisposed() {
|
|
109
|
+
return this._disposed;
|
|
179
110
|
}
|
|
180
111
|
/**
|
|
181
|
-
*
|
|
182
|
-
* @param
|
|
183
|
-
* @returns
|
|
112
|
+
* Subscribe to state changes
|
|
113
|
+
* @param listener - Function called when state changes
|
|
114
|
+
* @returns Unsubscribe function
|
|
184
115
|
*/
|
|
185
|
-
|
|
186
|
-
|
|
116
|
+
subscribe(listener) {
|
|
117
|
+
if (this._disposed) throw new Error(`Cannot subscribe to disposed container ${this.name}`);
|
|
118
|
+
this.listeners.add(listener);
|
|
119
|
+
return () => this.listeners.delete(listener);
|
|
187
120
|
}
|
|
188
121
|
/**
|
|
189
|
-
*
|
|
122
|
+
* Dispose this instance and clean up resources.
|
|
123
|
+
* Clears all listeners and emits the 'dispose' system event.
|
|
190
124
|
*/
|
|
191
|
-
|
|
192
|
-
|
|
125
|
+
dispose() {
|
|
126
|
+
if (this._disposed) return;
|
|
127
|
+
if (this.debug) console.log(`[${this.name}] Disposing...`);
|
|
128
|
+
this._disposed = true;
|
|
129
|
+
this.emitSystemEvent("dispose", void 0);
|
|
130
|
+
this.listeners.clear();
|
|
131
|
+
this.systemEventHandlers.clear();
|
|
132
|
+
globalRegistry.emit("disposed", this);
|
|
133
|
+
if (this.debug) console.log(`[${this.name}] Disposed successfully`);
|
|
193
134
|
}
|
|
194
135
|
/**
|
|
195
|
-
*
|
|
136
|
+
* Emit a new state value and notify all listeners.
|
|
137
|
+
* @param newState - The new state value
|
|
138
|
+
* @protected
|
|
196
139
|
*/
|
|
197
|
-
|
|
198
|
-
this.
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
this.
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
this.notifyPlugins("onEventAdded", vertex, event);
|
|
206
|
-
});
|
|
207
|
-
this.registry.on("disposed", (instance$1) => {
|
|
208
|
-
this.notifyPlugins("onInstanceDisposed", instance$1);
|
|
140
|
+
emit(newState) {
|
|
141
|
+
if (this._disposed) throw new Error(`Cannot emit state from disposed container ${this.name}`);
|
|
142
|
+
if (this._state === newState) return;
|
|
143
|
+
const previousState = this._state;
|
|
144
|
+
this._state = newState;
|
|
145
|
+
this.emitSystemEvent("stateChanged", {
|
|
146
|
+
state: newState,
|
|
147
|
+
previousState
|
|
209
148
|
});
|
|
149
|
+
for (const listener of this.listeners) try {
|
|
150
|
+
listener(newState);
|
|
151
|
+
} catch (error) {
|
|
152
|
+
console.error(`[${this.name}] Error in listener:`, error);
|
|
153
|
+
}
|
|
154
|
+
const stackTrace = this.captureStackTrace();
|
|
155
|
+
globalRegistry.emit("stateChanged", this, previousState, newState, stackTrace);
|
|
156
|
+
this.lastUpdateTimestamp = Date.now();
|
|
210
157
|
}
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
158
|
+
captureStackTrace() {
|
|
159
|
+
if (!StateContainer.enableStackTrace || typeof process !== "undefined" && process.env?.NODE_ENV === "production") return "";
|
|
160
|
+
try {
|
|
161
|
+
const relevantLines = ((/* @__PURE__ */ new Error()).stack || "").split("\n").slice(1);
|
|
162
|
+
const formattedLines = [];
|
|
163
|
+
for (const line of relevantLines) {
|
|
164
|
+
if (!line.trim()) continue;
|
|
165
|
+
if (line.includes("StateContainer.emit") || line.includes("StateContainer.update") || line.includes("StateContainer.captureStackTrace") || line.includes("Cubit.patch")) continue;
|
|
166
|
+
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;
|
|
167
|
+
const formatted = this.formatStackLine(line);
|
|
168
|
+
if (formatted) formattedLines.push(formatted);
|
|
222
169
|
}
|
|
170
|
+
return formattedLines.join("\n");
|
|
171
|
+
} catch {
|
|
172
|
+
return "";
|
|
223
173
|
}
|
|
224
174
|
}
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
return {
|
|
232
|
-
id: instance$1.instanceId,
|
|
233
|
-
className: instance$1.constructor.name,
|
|
234
|
-
isDisposed: instance$1.isDisposed,
|
|
235
|
-
name: instance$1.name,
|
|
236
|
-
lastStateChangeTimestamp: instance$1.lastUpdateTimestamp,
|
|
237
|
-
createdAt: instance$1.createdAt,
|
|
238
|
-
state: instance$1.state,
|
|
239
|
-
isIsolated: instance$1.instanceId.startsWith("isolated-")
|
|
240
|
-
};
|
|
241
|
-
},
|
|
242
|
-
getState: (instance$1) => {
|
|
243
|
-
return instance$1.state;
|
|
244
|
-
},
|
|
245
|
-
queryInstances: (typeClass) => {
|
|
246
|
-
return this.registry.getAll(typeClass);
|
|
247
|
-
},
|
|
248
|
-
getAllTypes: () => {
|
|
249
|
-
return this.registry.getTypes();
|
|
250
|
-
},
|
|
251
|
-
getStats: () => {
|
|
252
|
-
return this.registry.getStats();
|
|
175
|
+
formatStackLine(line) {
|
|
176
|
+
const match = line.match(/at\s+(.+?)\s+\((.+?):(\d+):(\d+)\)/);
|
|
177
|
+
if (!match) {
|
|
178
|
+
const simpleMatch = line.match(/at\s+(.+?):(\d+):(\d+)/);
|
|
179
|
+
if (simpleMatch) {
|
|
180
|
+
const [, url, lineNum, col] = simpleMatch;
|
|
181
|
+
return ` at ${this.cleanFilePath(url)}:${lineNum}:${col}`;
|
|
253
182
|
}
|
|
254
|
-
|
|
183
|
+
return null;
|
|
184
|
+
}
|
|
185
|
+
const [, functionName, url, lineNum, col] = match;
|
|
186
|
+
return ` at ${functionName} (${this.cleanFilePath(url)}:${lineNum}:${col})`;
|
|
255
187
|
}
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
if (
|
|
262
|
-
return
|
|
188
|
+
cleanFilePath(url) {
|
|
189
|
+
let path = url.replace(/http:\/\/localhost:\d+\/@fs/, "").replace(/http:\/\/localhost:\d+\//, "").replace(/\?t=\d+/, "").replace(/\?v=[a-f0-9]+/, "");
|
|
190
|
+
const projectMatch = path.match(/\/Projects\/blac\/(.+)/);
|
|
191
|
+
if (projectMatch) path = projectMatch[1];
|
|
192
|
+
const segments = path.split("/");
|
|
193
|
+
if (segments.length > 3) path = segments.slice(-3).join("/");
|
|
194
|
+
return path;
|
|
263
195
|
}
|
|
264
196
|
/**
|
|
265
|
-
*
|
|
197
|
+
* Update state using a transform function.
|
|
198
|
+
* @param updater - Function that receives current state and returns new state
|
|
199
|
+
* @protected
|
|
266
200
|
*/
|
|
267
|
-
|
|
268
|
-
if (
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
201
|
+
update(updater) {
|
|
202
|
+
if (this._disposed) throw new Error(`Cannot update state from disposed container ${this.name}`);
|
|
203
|
+
this.emit(updater(this._state));
|
|
204
|
+
}
|
|
205
|
+
emitSystemEvent(event, payload) {
|
|
206
|
+
const handlers = this.systemEventHandlers.get(event);
|
|
207
|
+
if (!handlers) return;
|
|
208
|
+
for (const handler of handlers) try {
|
|
209
|
+
handler(payload);
|
|
210
|
+
} catch (error) {
|
|
211
|
+
console.error(`[${this.name}] Error in system event handler:`, error);
|
|
272
212
|
}
|
|
273
|
-
return "development";
|
|
274
213
|
}
|
|
275
214
|
};
|
|
276
|
-
/**
|
|
277
|
-
* Create a plugin manager instance
|
|
278
|
-
* @param registry - The StateContainerRegistry to monitor for lifecycle events
|
|
279
|
-
* @returns A new PluginManager instance
|
|
280
|
-
*/
|
|
281
|
-
function createPluginManager(registry) {
|
|
282
|
-
return new PluginManager(registry);
|
|
283
|
-
}
|
|
284
215
|
|
|
285
216
|
//#endregion
|
|
286
|
-
//#region src/
|
|
217
|
+
//#region src/core/Cubit.ts
|
|
287
218
|
/**
|
|
288
|
-
*
|
|
219
|
+
* Simple state container with direct state emission.
|
|
220
|
+
* Extends StateContainer with public methods for emitting and updating state.
|
|
289
221
|
*
|
|
290
|
-
*
|
|
291
|
-
*/
|
|
292
|
-
/**
|
|
293
|
-
* Default configuration constants for BlaC
|
|
294
|
-
*/
|
|
295
|
-
const BLAC_DEFAULTS = {
|
|
296
|
-
DEFAULT_INSTANCE_KEY: "default",
|
|
297
|
-
MAX_GETTER_DEPTH: 10,
|
|
298
|
-
CLEANUP_INTERVAL_MS: 3e4,
|
|
299
|
-
WEAKREF_CLEANUP_INTERVAL_MS: 1e4,
|
|
300
|
-
MAX_SUBSCRIPTIONS: 1e3,
|
|
301
|
-
MAX_SUBSCRIPTIONS_HIGH_PERF: 1e4,
|
|
302
|
-
PIPELINE_TIMEOUT_MS: 5e3,
|
|
303
|
-
CLEANUP_INTERVAL_HIGH_PERF_MS: 5e3,
|
|
304
|
-
MAX_PIPELINE_STAGES: 30
|
|
305
|
-
};
|
|
306
|
-
/**
|
|
307
|
-
* Static property names for StateContainer classes
|
|
308
|
-
* Used for feature flags and configuration on bloc classes
|
|
309
|
-
*/
|
|
310
|
-
const BLAC_STATIC_PROPS = {
|
|
311
|
-
ISOLATED: "isolated",
|
|
312
|
-
KEEP_ALIVE: "keepAlive",
|
|
313
|
-
EXCLUDE_FROM_DEVTOOLS: "__excludeFromDevTools"
|
|
314
|
-
};
|
|
315
|
-
/**
|
|
316
|
-
* ID generation patterns and constants
|
|
222
|
+
* @template S - State type
|
|
317
223
|
*/
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
224
|
+
var Cubit = class extends StateContainer {
|
|
225
|
+
constructor(initialState) {
|
|
226
|
+
super(initialState);
|
|
227
|
+
this.patch = ((partial) => {
|
|
228
|
+
if (typeof this.state !== "object" || this.state === null) throw new Error("patch() is only available for object state types");
|
|
229
|
+
this.update((current) => ({
|
|
230
|
+
...current,
|
|
231
|
+
...partial
|
|
232
|
+
}));
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
/**
|
|
236
|
+
* Replace state with a new value and notify all listeners
|
|
237
|
+
* @param newState - The new state value
|
|
238
|
+
*/
|
|
239
|
+
emit(newState) {
|
|
240
|
+
super["emit"](newState);
|
|
241
|
+
}
|
|
242
|
+
/**
|
|
243
|
+
* Transform current state using an updater function and emit the new state
|
|
244
|
+
* @param updater - Function that receives current state and returns new state
|
|
245
|
+
*/
|
|
246
|
+
update(updater) {
|
|
247
|
+
super["update"](updater);
|
|
248
|
+
}
|
|
321
249
|
};
|
|
322
|
-
/**
|
|
323
|
-
* Standard error message prefix
|
|
324
|
-
*/
|
|
325
|
-
const BLAC_ERROR_PREFIX = "[BlaC]";
|
|
326
250
|
|
|
327
251
|
//#endregion
|
|
328
|
-
//#region src/
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
currentBloc: null,
|
|
335
|
-
depth: 0,
|
|
336
|
-
visitedBlocs: /* @__PURE__ */ new Set()
|
|
337
|
-
};
|
|
338
|
-
const MAX_GETTER_DEPTH$1 = BLAC_DEFAULTS.MAX_GETTER_DEPTH;
|
|
339
|
-
/**
|
|
340
|
-
* @internal
|
|
341
|
-
*/
|
|
342
|
-
function getGetterExecutionContext() {
|
|
343
|
-
return getterExecutionContext;
|
|
252
|
+
//#region src/registry/acquire.ts
|
|
253
|
+
function acquire(BlocClass, instanceKey) {
|
|
254
|
+
return globalRegistry.acquire(BlocClass, instanceKey, {
|
|
255
|
+
canCreate: true,
|
|
256
|
+
countRef: true
|
|
257
|
+
});
|
|
344
258
|
}
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
function
|
|
349
|
-
|
|
350
|
-
let constructorCache = descriptorCache.get(constructor);
|
|
351
|
-
if (constructorCache?.has(prop)) return constructorCache.get(prop);
|
|
352
|
-
let current = obj;
|
|
353
|
-
let descriptor;
|
|
354
|
-
while (current && current !== Object.prototype) {
|
|
355
|
-
descriptor = Object.getOwnPropertyDescriptor(current, prop);
|
|
356
|
-
if (descriptor) break;
|
|
357
|
-
current = Object.getPrototypeOf(current);
|
|
358
|
-
}
|
|
359
|
-
if (!constructorCache) {
|
|
360
|
-
constructorCache = /* @__PURE__ */ new Map();
|
|
361
|
-
descriptorCache.set(constructor, constructorCache);
|
|
362
|
-
}
|
|
363
|
-
constructorCache.set(prop, descriptor);
|
|
364
|
-
return descriptor;
|
|
259
|
+
|
|
260
|
+
//#endregion
|
|
261
|
+
//#region src/registry/borrow.ts
|
|
262
|
+
function borrow(BlocClass, instanceKey) {
|
|
263
|
+
return globalRegistry.borrow(BlocClass, instanceKey);
|
|
365
264
|
}
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
*/
|
|
369
|
-
function isGetter(obj, prop) {
|
|
370
|
-
return getDescriptor(obj, prop)?.get !== void 0;
|
|
265
|
+
function borrowSafe(BlocClass, instanceKey) {
|
|
266
|
+
return globalRegistry.borrowSafe(BlocClass, instanceKey);
|
|
371
267
|
}
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
function
|
|
376
|
-
|
|
377
|
-
trackedValues: /* @__PURE__ */ new Map(),
|
|
378
|
-
currentlyAccessing: /* @__PURE__ */ new Set(),
|
|
379
|
-
trackedGetters: /* @__PURE__ */ new Set(),
|
|
380
|
-
isTracking: false,
|
|
381
|
-
renderCache: /* @__PURE__ */ new Map(),
|
|
382
|
-
cacheValid: false,
|
|
383
|
-
externalDependencies: /* @__PURE__ */ new Set()
|
|
384
|
-
};
|
|
268
|
+
|
|
269
|
+
//#endregion
|
|
270
|
+
//#region src/registry/release.ts
|
|
271
|
+
function release(BlocClass, instanceKey, forceDispose = false) {
|
|
272
|
+
globalRegistry.release(BlocClass, instanceKey, forceDispose);
|
|
385
273
|
}
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
function setActiveTracker(bloc, tracker) {
|
|
390
|
-
activeTrackerMap.set(bloc, tracker);
|
|
391
|
-
}
|
|
392
|
-
/**
|
|
393
|
-
* @internal
|
|
394
|
-
*/
|
|
395
|
-
function clearActiveTracker(bloc) {
|
|
396
|
-
activeTrackerMap.delete(bloc);
|
|
397
|
-
}
|
|
398
|
-
/**
|
|
399
|
-
* @internal
|
|
400
|
-
*/
|
|
401
|
-
function getActiveTracker(bloc) {
|
|
402
|
-
return activeTrackerMap.get(bloc);
|
|
403
|
-
}
|
|
404
|
-
/**
|
|
405
|
-
* @internal
|
|
406
|
-
*/
|
|
407
|
-
function commitTrackedGetters(tracker) {
|
|
408
|
-
if (tracker.currentlyAccessing.size > 0) tracker.trackedGetters = new Set(tracker.currentlyAccessing);
|
|
409
|
-
tracker.currentlyAccessing.clear();
|
|
410
|
-
}
|
|
411
|
-
/**
|
|
412
|
-
* @internal
|
|
413
|
-
*/
|
|
414
|
-
function createBlocProxy(bloc) {
|
|
415
|
-
const cached = blocProxyCache.get(bloc);
|
|
416
|
-
if (cached) return cached;
|
|
417
|
-
const proxy = new Proxy(bloc, { get(target, prop, receiver) {
|
|
418
|
-
const tracker = activeTrackerMap.get(target);
|
|
419
|
-
if (tracker?.isTracking && isGetter(target, prop)) {
|
|
420
|
-
tracker.currentlyAccessing.add(prop);
|
|
421
|
-
if (tracker.cacheValid && tracker.renderCache.has(prop)) {
|
|
422
|
-
const cachedValue = tracker.renderCache.get(prop);
|
|
423
|
-
tracker.trackedValues.set(prop, cachedValue);
|
|
424
|
-
return cachedValue;
|
|
425
|
-
}
|
|
426
|
-
if (getterExecutionContext.depth >= MAX_GETTER_DEPTH$1) {
|
|
427
|
-
console.warn(`${BLAC_ERROR_PREFIX} Maximum getter depth (${MAX_GETTER_DEPTH$1}) exceeded. Possible circular dependency in getter "${String(prop)}" on ${target.constructor.name}. Returning undefined to prevent stack overflow.`);
|
|
428
|
-
return;
|
|
429
|
-
}
|
|
430
|
-
if (getterExecutionContext.visitedBlocs.has(target)) {
|
|
431
|
-
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.`);
|
|
432
|
-
return;
|
|
433
|
-
}
|
|
434
|
-
const prevContext = {
|
|
435
|
-
tracker: getterExecutionContext.tracker,
|
|
436
|
-
currentBloc: getterExecutionContext.currentBloc,
|
|
437
|
-
depth: getterExecutionContext.depth,
|
|
438
|
-
visitedBlocs: new Set(getterExecutionContext.visitedBlocs)
|
|
439
|
-
};
|
|
440
|
-
getterExecutionContext.tracker = tracker;
|
|
441
|
-
getterExecutionContext.currentBloc = target;
|
|
442
|
-
getterExecutionContext.depth++;
|
|
443
|
-
getterExecutionContext.visitedBlocs.add(target);
|
|
444
|
-
try {
|
|
445
|
-
const value = getDescriptor(target, prop).get.call(target);
|
|
446
|
-
tracker.trackedValues.set(prop, value);
|
|
447
|
-
return value;
|
|
448
|
-
} catch (error$1) {
|
|
449
|
-
tracker.currentlyAccessing.delete(prop);
|
|
450
|
-
throw error$1;
|
|
451
|
-
} finally {
|
|
452
|
-
getterExecutionContext.tracker = prevContext.tracker;
|
|
453
|
-
getterExecutionContext.currentBloc = prevContext.currentBloc;
|
|
454
|
-
getterExecutionContext.depth = prevContext.depth;
|
|
455
|
-
getterExecutionContext.visitedBlocs = prevContext.visitedBlocs;
|
|
456
|
-
}
|
|
457
|
-
}
|
|
458
|
-
return Reflect.get(target, prop, receiver);
|
|
459
|
-
} });
|
|
460
|
-
blocProxyCache.set(bloc, proxy);
|
|
461
|
-
return proxy;
|
|
462
|
-
}
|
|
463
|
-
/**
|
|
464
|
-
* @internal
|
|
465
|
-
*/
|
|
466
|
-
function hasGetterChanges(bloc, tracker) {
|
|
467
|
-
if (!tracker || tracker.trackedGetters.size === 0) return false;
|
|
468
|
-
tracker.renderCache.clear();
|
|
469
|
-
let hasAnyChange = false;
|
|
470
|
-
for (const prop of tracker.trackedGetters) try {
|
|
471
|
-
const descriptor = getDescriptor(bloc, prop);
|
|
472
|
-
if (!descriptor?.get) continue;
|
|
473
|
-
const newValue = descriptor.get.call(bloc);
|
|
474
|
-
const oldValue = tracker.trackedValues.get(prop);
|
|
475
|
-
tracker.renderCache.set(prop, newValue);
|
|
476
|
-
tracker.trackedValues.set(prop, newValue);
|
|
477
|
-
if (!Object.is(newValue, oldValue)) hasAnyChange = true;
|
|
478
|
-
} catch (error$1) {
|
|
479
|
-
console.warn(`${BLAC_ERROR_PREFIX} Getter "${String(prop)}" threw error during change detection. Stopping tracking for this getter.`, error$1);
|
|
480
|
-
tracker.trackedGetters.delete(prop);
|
|
481
|
-
tracker.trackedValues.delete(prop);
|
|
482
|
-
tracker.cacheValid = false;
|
|
483
|
-
return true;
|
|
484
|
-
}
|
|
485
|
-
tracker.cacheValid = true;
|
|
486
|
-
return hasAnyChange;
|
|
487
|
-
}
|
|
488
|
-
/**
|
|
489
|
-
* @internal
|
|
490
|
-
*/
|
|
491
|
-
function invalidateRenderCache(tracker) {
|
|
492
|
-
tracker.cacheValid = false;
|
|
493
|
-
}
|
|
494
|
-
/**
|
|
495
|
-
* @internal
|
|
496
|
-
*/
|
|
497
|
-
function clearExternalDependencies(tracker) {
|
|
498
|
-
tracker.externalDependencies.clear();
|
|
499
|
-
}
|
|
500
|
-
/**
|
|
501
|
-
* @internal
|
|
502
|
-
*/
|
|
503
|
-
function resetGetterState(tracker) {
|
|
504
|
-
tracker.trackedValues.clear();
|
|
505
|
-
tracker.currentlyAccessing.clear();
|
|
506
|
-
tracker.trackedGetters.clear();
|
|
507
|
-
tracker.renderCache.clear();
|
|
508
|
-
tracker.cacheValid = false;
|
|
509
|
-
tracker.isTracking = false;
|
|
510
|
-
tracker.externalDependencies.clear();
|
|
511
|
-
}
|
|
512
|
-
|
|
513
|
-
//#endregion
|
|
514
|
-
//#region src/core/markers.ts
|
|
515
|
-
/**
|
|
516
|
-
* Marker symbols for identifying container types.
|
|
517
|
-
* These are in a separate file to avoid circular dependencies.
|
|
518
|
-
* @internal
|
|
519
|
-
*/
|
|
520
|
-
/**
|
|
521
|
-
* Marker symbol to identify stateless containers at the type level.
|
|
522
|
-
* Used by React hooks to prevent `useBloc` from accepting stateless containers.
|
|
523
|
-
*/
|
|
524
|
-
const STATELESS_MARKER = Symbol("STATELESS_CONTAINER");
|
|
525
|
-
|
|
526
|
-
//#endregion
|
|
527
|
-
//#region src/utils/static-props.ts
|
|
528
|
-
/**
|
|
529
|
-
* Utility functions for accessing static properties on StateContainer classes
|
|
530
|
-
*/
|
|
531
|
-
/**
|
|
532
|
-
* Get a static property from a class constructor
|
|
533
|
-
* Type-safe helper that avoids (Type as any) casts
|
|
534
|
-
*
|
|
535
|
-
* @param Type - The class constructor
|
|
536
|
-
* @param propName - The property name to access
|
|
537
|
-
* @param defaultValue - Optional default value if property is undefined
|
|
538
|
-
* @returns The property value or default
|
|
539
|
-
*/
|
|
540
|
-
function getStaticProp(Type, propName, defaultValue) {
|
|
541
|
-
return Type[propName] ?? defaultValue;
|
|
542
|
-
}
|
|
543
|
-
/**
|
|
544
|
-
* Check if a class is marked as isolated.
|
|
545
|
-
* Isolated classes create separate instances per component.
|
|
546
|
-
* @param Type - The class constructor to check
|
|
547
|
-
* @returns true if the class has `static isolated = true`
|
|
548
|
-
*/
|
|
549
|
-
function isIsolatedClass(Type) {
|
|
550
|
-
return getStaticProp(Type, BLAC_STATIC_PROPS.ISOLATED) === true;
|
|
551
|
-
}
|
|
552
|
-
/**
|
|
553
|
-
* Check if a class is marked as keepAlive.
|
|
554
|
-
* KeepAlive classes are never auto-disposed when ref count reaches 0.
|
|
555
|
-
* @param Type - The class constructor to check
|
|
556
|
-
* @returns true if the class has `static keepAlive = true`
|
|
557
|
-
*/
|
|
558
|
-
function isKeepAliveClass(Type) {
|
|
559
|
-
return getStaticProp(Type, BLAC_STATIC_PROPS.KEEP_ALIVE) === true;
|
|
560
|
-
}
|
|
561
|
-
/**
|
|
562
|
-
* Check if a class should be excluded from DevTools.
|
|
563
|
-
* Used to prevent infinite loops when DevTools tracks itself.
|
|
564
|
-
* @param Type - The class constructor to check
|
|
565
|
-
* @returns true if the class has `static __excludeFromDevTools = true`
|
|
566
|
-
*/
|
|
567
|
-
function isExcludedFromDevTools(Type) {
|
|
568
|
-
return getStaticProp(Type, BLAC_STATIC_PROPS.EXCLUDE_FROM_DEVTOOLS) === true;
|
|
569
|
-
}
|
|
570
|
-
/**
|
|
571
|
-
* Check if a class is a stateless container (StatelessCubit or StatelessVertex).
|
|
572
|
-
* Stateless containers don't have state, emit, update, or subscribe methods.
|
|
573
|
-
* @param Type - The class constructor to check
|
|
574
|
-
* @returns true if the class is a stateless container
|
|
575
|
-
*/
|
|
576
|
-
function isStatelessClass(Type) {
|
|
577
|
-
return Type[STATELESS_MARKER] === true;
|
|
578
|
-
}
|
|
579
|
-
|
|
580
|
-
//#endregion
|
|
581
|
-
//#region src/core/StateContainerRegistry.ts
|
|
582
|
-
/**
|
|
583
|
-
* Central registry for managing StateContainer instances.
|
|
584
|
-
* Handles instance lifecycle, ref counting, and lifecycle event emission.
|
|
585
|
-
*
|
|
586
|
-
* @example
|
|
587
|
-
* ```ts
|
|
588
|
-
* const registry = new StateContainerRegistry();
|
|
589
|
-
* const instance = registry.acquire(MyBloc); // ownership, must release
|
|
590
|
-
* const other = registry.ensure(OtherBloc); // no ownership, bloc-to-bloc
|
|
591
|
-
* registry.on('stateChanged', (container, prev, next) => {
|
|
592
|
-
* console.log('State changed:', prev, '->', next);
|
|
593
|
-
* });
|
|
594
|
-
* ```
|
|
595
|
-
*/
|
|
596
|
-
var StateContainerRegistry = class {
|
|
597
|
-
constructor() {
|
|
598
|
-
this.instancesByConstructor = /* @__PURE__ */ new WeakMap();
|
|
599
|
-
this.types = /* @__PURE__ */ new Set();
|
|
600
|
-
this.typeConfigs = /* @__PURE__ */ new Map();
|
|
601
|
-
this.listeners = /* @__PURE__ */ new Map();
|
|
602
|
-
}
|
|
603
|
-
/**
|
|
604
|
-
* Register a type for lifecycle event tracking
|
|
605
|
-
* @param constructor - The StateContainer class constructor
|
|
606
|
-
*/
|
|
607
|
-
registerType(constructor) {
|
|
608
|
-
this.types.add(constructor);
|
|
609
|
-
}
|
|
610
|
-
/**
|
|
611
|
-
* Register a StateContainer class with configuration
|
|
612
|
-
* @param constructor - The StateContainer class constructor
|
|
613
|
-
* @param isolated - Whether instances should be isolated (component-scoped)
|
|
614
|
-
* @throws Error if type is already registered
|
|
615
|
-
*/
|
|
616
|
-
register(constructor, isolated = false) {
|
|
617
|
-
const className = constructor.name;
|
|
618
|
-
if (!isolated && isIsolatedClass(constructor)) isolated = true;
|
|
619
|
-
if (this.typeConfigs.has(className)) throw new Error(`${BLAC_ERROR_PREFIX} Type "${className}" is already registered`);
|
|
620
|
-
this.typeConfigs.set(className, { isolated });
|
|
621
|
-
this.registerType(constructor);
|
|
622
|
-
}
|
|
623
|
-
ensureInstancesMap(Type) {
|
|
624
|
-
let instances = this.instancesByConstructor.get(Type);
|
|
625
|
-
if (!instances) {
|
|
626
|
-
instances = /* @__PURE__ */ new Map();
|
|
627
|
-
this.instancesByConstructor.set(Type, instances);
|
|
628
|
-
}
|
|
629
|
-
return instances;
|
|
630
|
-
}
|
|
631
|
-
/**
|
|
632
|
-
* Get the instances Map for a specific class (public API for stats/debugging)
|
|
633
|
-
*/
|
|
634
|
-
getInstancesMap(Type) {
|
|
635
|
-
return this.instancesByConstructor.get(Type) || /* @__PURE__ */ new Map();
|
|
636
|
-
}
|
|
637
|
-
trackExecutionContext(instance$1) {
|
|
638
|
-
const context = getGetterExecutionContext();
|
|
639
|
-
if (context.tracker && context.currentBloc && context.currentBloc !== instance$1) context.tracker.externalDependencies.add(instance$1);
|
|
640
|
-
}
|
|
641
|
-
/**
|
|
642
|
-
* Acquire an instance with ref counting (ownership semantics).
|
|
643
|
-
* Creates a new instance if one doesn't exist, or returns existing and increments ref count.
|
|
644
|
-
* You must call `release()` when done to decrement the ref count.
|
|
645
|
-
* @param Type - The StateContainer class constructor
|
|
646
|
-
* @param instanceKey - Instance key (defaults to 'default')
|
|
647
|
-
* @param options - Acquisition options
|
|
648
|
-
* @param options.canCreate - Whether to create new instance if not found (default: true)
|
|
649
|
-
* @param options.countRef - Whether to increment ref count (default: true)
|
|
650
|
-
* @param options.props - Props to pass to constructor if creating new instance
|
|
651
|
-
* @param options.trackExecutionContext - Whether to track cross-bloc dependency (default: false)
|
|
652
|
-
* @returns The state container instance
|
|
653
|
-
*/
|
|
654
|
-
acquire(Type, instanceKey = BLAC_DEFAULTS.DEFAULT_INSTANCE_KEY, options = {}) {
|
|
655
|
-
const { canCreate = true, countRef = true, props, trackExecutionContext = false } = options;
|
|
656
|
-
const registryConfig = this.typeConfigs.get(Type.name);
|
|
657
|
-
const isolated = isIsolatedClass(Type) || registryConfig?.isolated === true;
|
|
658
|
-
const config = { instanceId: instanceKey };
|
|
659
|
-
if (isolated && !canCreate) throw new Error(`${BLAC_ERROR_PREFIX} Cannot get isolated instance "${instanceKey}" of ${Type.name} when creation is disabled.`);
|
|
660
|
-
if (isolated) {
|
|
661
|
-
const instance$2 = new Type(props);
|
|
662
|
-
instance$2.initConfig(config);
|
|
663
|
-
this.registerType(Type);
|
|
664
|
-
if (trackExecutionContext) this.trackExecutionContext(instance$2);
|
|
665
|
-
return instance$2;
|
|
666
|
-
}
|
|
667
|
-
const instances = this.ensureInstancesMap(Type);
|
|
668
|
-
const entry = instances.get(instanceKey);
|
|
669
|
-
if (entry && (countRef || entry.refCount === 0)) entry.refCount++;
|
|
670
|
-
if (entry) {
|
|
671
|
-
if (trackExecutionContext) this.trackExecutionContext(entry.instance);
|
|
672
|
-
return entry.instance;
|
|
673
|
-
}
|
|
674
|
-
if (!canCreate) throw new Error(`${BLAC_ERROR_PREFIX} ${Type.name} instance "${instanceKey}" not found and creation is disabled.`);
|
|
675
|
-
const instance$1 = new Type(props);
|
|
676
|
-
instance$1.initConfig(config);
|
|
677
|
-
instances.set(instanceKey, {
|
|
678
|
-
instance: instance$1,
|
|
679
|
-
refCount: 1
|
|
680
|
-
});
|
|
681
|
-
this.registerType(Type);
|
|
682
|
-
if (trackExecutionContext) this.trackExecutionContext(instance$1);
|
|
683
|
-
return instance$1;
|
|
684
|
-
}
|
|
685
|
-
/**
|
|
686
|
-
* Borrow an existing instance without incrementing ref count (borrowing semantics).
|
|
687
|
-
* Tracks cross-bloc dependency for reactive updates.
|
|
688
|
-
* @param Type - The StateContainer class constructor
|
|
689
|
-
* @param instanceKey - Instance key (defaults to 'default')
|
|
690
|
-
* @returns The state container instance
|
|
691
|
-
* @throws Error if instance doesn't exist
|
|
692
|
-
*/
|
|
693
|
-
borrow(Type, instanceKey = BLAC_DEFAULTS.DEFAULT_INSTANCE_KEY) {
|
|
694
|
-
return this.acquire(Type, instanceKey, {
|
|
695
|
-
canCreate: false,
|
|
696
|
-
trackExecutionContext: true
|
|
697
|
-
});
|
|
698
|
-
}
|
|
699
|
-
/**
|
|
700
|
-
* Safely borrow an existing instance (borrowing semantics with error handling).
|
|
701
|
-
* Returns discriminated union for type-safe conditional access.
|
|
702
|
-
* @param Type - The StateContainer class constructor
|
|
703
|
-
* @param instanceKey - Instance key (defaults to 'default')
|
|
704
|
-
* @returns Discriminated union with either the instance or an error
|
|
705
|
-
*/
|
|
706
|
-
borrowSafe(Type, instanceKey = BLAC_DEFAULTS.DEFAULT_INSTANCE_KEY) {
|
|
707
|
-
try {
|
|
708
|
-
return {
|
|
709
|
-
error: null,
|
|
710
|
-
instance: this.borrow(Type, instanceKey)
|
|
711
|
-
};
|
|
712
|
-
} catch (error$1) {
|
|
713
|
-
return {
|
|
714
|
-
error: error$1,
|
|
715
|
-
instance: null
|
|
716
|
-
};
|
|
717
|
-
}
|
|
718
|
-
}
|
|
719
|
-
/**
|
|
720
|
-
* Ensure an instance exists without taking ownership (for bloc-to-bloc communication).
|
|
721
|
-
* Gets existing instance OR creates it if it doesn't exist, without incrementing ref count.
|
|
722
|
-
* Tracks cross-bloc dependency for reactive updates.
|
|
723
|
-
*
|
|
724
|
-
* Use this in bloc-to-bloc communication when you need to ensure an instance exists
|
|
725
|
-
* but don't want to claim ownership (no ref count increment).
|
|
726
|
-
* @param Type - The StateContainer class constructor
|
|
727
|
-
* @param instanceKey - Instance key (defaults to 'default')
|
|
728
|
-
* @returns The state container instance
|
|
729
|
-
*/
|
|
730
|
-
ensure(Type, instanceKey = BLAC_DEFAULTS.DEFAULT_INSTANCE_KEY) {
|
|
731
|
-
return this.acquire(Type, instanceKey, {
|
|
732
|
-
canCreate: true,
|
|
733
|
-
countRef: false,
|
|
734
|
-
trackExecutionContext: true
|
|
735
|
-
});
|
|
736
|
-
}
|
|
737
|
-
/**
|
|
738
|
-
* Release a reference to an instance.
|
|
739
|
-
* Decrements ref count and disposes when it reaches 0 (unless keepAlive).
|
|
740
|
-
* @param Type - The StateContainer class constructor
|
|
741
|
-
* @param instanceKey - Instance key (defaults to 'default')
|
|
742
|
-
* @param forceDispose - Force immediate disposal regardless of ref count
|
|
743
|
-
*/
|
|
744
|
-
release(Type, instanceKey = BLAC_DEFAULTS.DEFAULT_INSTANCE_KEY, forceDispose = false) {
|
|
745
|
-
const instances = this.ensureInstancesMap(Type);
|
|
746
|
-
const entry = instances.get(instanceKey);
|
|
747
|
-
if (!entry) return;
|
|
748
|
-
if (forceDispose) {
|
|
749
|
-
if (!entry.instance.isDisposed) entry.instance.dispose();
|
|
750
|
-
instances.delete(instanceKey);
|
|
751
|
-
return;
|
|
752
|
-
}
|
|
753
|
-
entry.refCount--;
|
|
754
|
-
const keepAlive = isKeepAliveClass(Type);
|
|
755
|
-
if (entry.refCount <= 0 && !keepAlive) {
|
|
756
|
-
if (!entry.instance.isDisposed) entry.instance.dispose();
|
|
757
|
-
instances.delete(instanceKey);
|
|
758
|
-
}
|
|
759
|
-
}
|
|
760
|
-
/**
|
|
761
|
-
* Get all instances of a specific type.
|
|
762
|
-
* @param Type - The StateContainer class constructor
|
|
763
|
-
* @returns Array of all instances
|
|
764
|
-
*/
|
|
765
|
-
getAll(Type) {
|
|
766
|
-
const instances = this.ensureInstancesMap(Type);
|
|
767
|
-
const result = [];
|
|
768
|
-
for (const entry of instances.values()) result.push(entry.instance);
|
|
769
|
-
return result;
|
|
770
|
-
}
|
|
771
|
-
/**
|
|
772
|
-
* Safely iterate over all instances of a type.
|
|
773
|
-
* Skips disposed instances and catches callback errors.
|
|
774
|
-
* @param Type - The StateContainer class constructor
|
|
775
|
-
* @param callback - Function to call for each instance
|
|
776
|
-
*/
|
|
777
|
-
forEach(Type, callback) {
|
|
778
|
-
const instances = this.ensureInstancesMap(Type);
|
|
779
|
-
for (const entry of instances.values()) {
|
|
780
|
-
const instance$1 = entry.instance;
|
|
781
|
-
if (!instance$1.isDisposed) try {
|
|
782
|
-
callback(instance$1);
|
|
783
|
-
} catch (error$1) {
|
|
784
|
-
console.error(`${BLAC_ERROR_PREFIX} forEach callback error for ${Type.name}:`, error$1);
|
|
785
|
-
}
|
|
786
|
-
}
|
|
787
|
-
}
|
|
788
|
-
/**
|
|
789
|
-
* Clear all instances of a specific type (disposes them).
|
|
790
|
-
* @param Type - The StateContainer class constructor
|
|
791
|
-
*/
|
|
792
|
-
clear(Type) {
|
|
793
|
-
const instances = this.ensureInstancesMap(Type);
|
|
794
|
-
for (const entry of instances.values()) if (!entry.instance.isDisposed) entry.instance.dispose();
|
|
795
|
-
instances.clear();
|
|
796
|
-
}
|
|
797
|
-
/**
|
|
798
|
-
* Get reference count for an instance.
|
|
799
|
-
* @param Type - The StateContainer class constructor
|
|
800
|
-
* @param instanceKey - Instance key (defaults to 'default')
|
|
801
|
-
* @returns Current ref count (0 if instance doesn't exist)
|
|
802
|
-
*/
|
|
803
|
-
getRefCount(Type, instanceKey = BLAC_DEFAULTS.DEFAULT_INSTANCE_KEY) {
|
|
804
|
-
return this.ensureInstancesMap(Type).get(instanceKey)?.refCount ?? 0;
|
|
805
|
-
}
|
|
806
|
-
/**
|
|
807
|
-
* Check if an instance exists.
|
|
808
|
-
* @param Type - The StateContainer class constructor
|
|
809
|
-
* @param instanceKey - Instance key (defaults to 'default')
|
|
810
|
-
* @returns true if instance exists
|
|
811
|
-
*/
|
|
812
|
-
hasInstance(Type, instanceKey = BLAC_DEFAULTS.DEFAULT_INSTANCE_KEY) {
|
|
813
|
-
return this.ensureInstancesMap(Type).has(instanceKey);
|
|
814
|
-
}
|
|
815
|
-
/**
|
|
816
|
-
* Clear all instances from all types (for testing)
|
|
817
|
-
*
|
|
818
|
-
* Iterates all registered types and clears their instances.
|
|
819
|
-
* Also clears type tracking to reset the registry state.
|
|
820
|
-
*/
|
|
821
|
-
clearAll() {
|
|
822
|
-
for (const Type of this.types) this.clear(Type);
|
|
823
|
-
this.types.clear();
|
|
824
|
-
this.typeConfigs.clear();
|
|
825
|
-
}
|
|
826
|
-
/**
|
|
827
|
-
* Get registry statistics for debugging.
|
|
828
|
-
* @returns Object with registeredTypes, totalInstances, and typeBreakdown
|
|
829
|
-
*/
|
|
830
|
-
getStats() {
|
|
831
|
-
const typeBreakdown = {};
|
|
832
|
-
let totalInstances = 0;
|
|
833
|
-
for (const Type of this.types) {
|
|
834
|
-
const typeName = Type.name;
|
|
835
|
-
const count = this.getInstancesMap(Type).size;
|
|
836
|
-
typeBreakdown[typeName] = count;
|
|
837
|
-
totalInstances += count;
|
|
838
|
-
}
|
|
839
|
-
return {
|
|
840
|
-
registeredTypes: this.types.size,
|
|
841
|
-
totalInstances,
|
|
842
|
-
typeBreakdown
|
|
843
|
-
};
|
|
844
|
-
}
|
|
845
|
-
/**
|
|
846
|
-
* Get all registered types (for plugin system).
|
|
847
|
-
* @returns Array of all registered StateContainer class constructors
|
|
848
|
-
*/
|
|
849
|
-
getTypes() {
|
|
850
|
-
return Array.from(this.types);
|
|
851
|
-
}
|
|
852
|
-
/**
|
|
853
|
-
* Subscribe to lifecycle events
|
|
854
|
-
* @param event - The lifecycle event to listen for
|
|
855
|
-
* @param listener - The listener function to call when the event occurs
|
|
856
|
-
* @returns Unsubscribe function
|
|
857
|
-
*/
|
|
858
|
-
on(event, listener) {
|
|
859
|
-
if (!this.listeners.has(event)) this.listeners.set(event, /* @__PURE__ */ new Set());
|
|
860
|
-
const instance$1 = this.listeners.get(event);
|
|
861
|
-
if (!instance$1) throw new Error(`${BLAC_ERROR_PREFIX} Failed to register listener for event '${event}'`);
|
|
862
|
-
instance$1.add(listener);
|
|
863
|
-
return () => {
|
|
864
|
-
this.listeners.get(event)?.delete(listener);
|
|
865
|
-
};
|
|
866
|
-
}
|
|
867
|
-
/**
|
|
868
|
-
* Emit lifecycle event to all listeners
|
|
869
|
-
* @internal - Called by StateContainer/Vertex lifecycle methods
|
|
870
|
-
*/
|
|
871
|
-
emit(event, ...args) {
|
|
872
|
-
const listeners = this.listeners.get(event);
|
|
873
|
-
if (!listeners || listeners.size === 0) return;
|
|
874
|
-
for (const listener of listeners) try {
|
|
875
|
-
listener(...args);
|
|
876
|
-
} catch (error$1) {
|
|
877
|
-
console.error(`${BLAC_ERROR_PREFIX} Listener error for '${event}':`, error$1);
|
|
878
|
-
}
|
|
879
|
-
}
|
|
880
|
-
};
|
|
881
|
-
/**
|
|
882
|
-
* Global default registry instance
|
|
883
|
-
*/
|
|
884
|
-
const globalRegistry = new StateContainerRegistry();
|
|
885
|
-
/**
|
|
886
|
-
* Global plugin manager (initialized lazily)
|
|
887
|
-
*/
|
|
888
|
-
let _globalPluginManager = null;
|
|
889
|
-
/**
|
|
890
|
-
* Get the global plugin manager
|
|
891
|
-
*/
|
|
892
|
-
function getPluginManager() {
|
|
893
|
-
if (!_globalPluginManager) _globalPluginManager = createPluginManager(globalRegistry);
|
|
894
|
-
return _globalPluginManager;
|
|
895
|
-
}
|
|
896
|
-
|
|
897
|
-
//#endregion
|
|
898
|
-
//#region src/core/StateContainer.ts
|
|
899
|
-
/**
|
|
900
|
-
* Abstract base class for all state containers in BlaC.
|
|
901
|
-
* Provides lifecycle management, subscription handling, ref counting,
|
|
902
|
-
* and integration with the global registry.
|
|
903
|
-
*
|
|
904
|
-
* @template S - State type managed by this container
|
|
905
|
-
* @template P - Props type passed to the container (optional)
|
|
906
|
-
*
|
|
907
|
-
* @example
|
|
908
|
-
* ```ts
|
|
909
|
-
* class CounterBloc extends StateContainer<{ count: number }> {
|
|
910
|
-
* constructor() {
|
|
911
|
-
* super({ count: 0 });
|
|
912
|
-
* }
|
|
913
|
-
* increment() {
|
|
914
|
-
* this.emit({ count: this.state.count + 1 });
|
|
915
|
-
* }
|
|
916
|
-
* }
|
|
917
|
-
* ```
|
|
918
|
-
*/
|
|
919
|
-
var StateContainer = class StateContainer {
|
|
920
|
-
static {
|
|
921
|
-
this.__excludeFromDevTools = false;
|
|
922
|
-
}
|
|
923
|
-
static {
|
|
924
|
-
this.enableStackTrace = true;
|
|
925
|
-
}
|
|
926
|
-
/** Current props value passed to this container */
|
|
927
|
-
get props() {
|
|
928
|
-
return this._props;
|
|
929
|
-
}
|
|
930
|
-
constructor(initialState) {
|
|
931
|
-
this.listeners = /* @__PURE__ */ new Set();
|
|
932
|
-
this._disposed = false;
|
|
933
|
-
this.config = {};
|
|
934
|
-
this._props = void 0;
|
|
935
|
-
this.systemEventHandlers = /* @__PURE__ */ new Map();
|
|
936
|
-
this.name = this.constructor.name;
|
|
937
|
-
this.debug = false;
|
|
938
|
-
this.instanceId = generateSimpleId(this.constructor.name, "main");
|
|
939
|
-
this.createdAt = Date.now();
|
|
940
|
-
this.lastUpdateTimestamp = Date.now();
|
|
941
|
-
this.onSystemEvent = (event, handler) => {
|
|
942
|
-
let handlers = this.systemEventHandlers.get(event);
|
|
943
|
-
if (!handlers) {
|
|
944
|
-
handlers = /* @__PURE__ */ new Set();
|
|
945
|
-
this.systemEventHandlers.set(event, handlers);
|
|
946
|
-
}
|
|
947
|
-
handlers.add(handler);
|
|
948
|
-
return () => {
|
|
949
|
-
handlers?.delete(handler);
|
|
950
|
-
};
|
|
951
|
-
};
|
|
952
|
-
this._state = initialState;
|
|
953
|
-
}
|
|
954
|
-
/**
|
|
955
|
-
* Initialize configuration for this instance.
|
|
956
|
-
* Called by the registry after construction.
|
|
957
|
-
* @param config - Configuration options
|
|
958
|
-
*/
|
|
959
|
-
initConfig(config) {
|
|
960
|
-
this.config = { ...config };
|
|
961
|
-
this.name = this.config.name || this.constructor.name;
|
|
962
|
-
this.debug = this.config.debug ?? false;
|
|
963
|
-
this.instanceId = generateSimpleId(this.constructor.name, this.config.instanceId);
|
|
964
|
-
globalRegistry.emit("created", this);
|
|
965
|
-
}
|
|
966
|
-
/** Current state value */
|
|
967
|
-
get state() {
|
|
968
|
-
return this._state;
|
|
969
|
-
}
|
|
970
|
-
/** Whether this instance has been disposed */
|
|
971
|
-
get isDisposed() {
|
|
972
|
-
return this._disposed;
|
|
973
|
-
}
|
|
974
|
-
/**
|
|
975
|
-
* Subscribe to state changes
|
|
976
|
-
* @param listener - Function called when state changes
|
|
977
|
-
* @returns Unsubscribe function
|
|
978
|
-
*/
|
|
979
|
-
subscribe(listener) {
|
|
980
|
-
if (this._disposed) throw new Error(`Cannot subscribe to disposed container ${this.name}`);
|
|
981
|
-
this.listeners.add(listener);
|
|
982
|
-
return () => this.listeners.delete(listener);
|
|
983
|
-
}
|
|
984
|
-
/**
|
|
985
|
-
* Dispose this instance and clean up resources.
|
|
986
|
-
* Clears all listeners and emits the 'dispose' system event.
|
|
987
|
-
*/
|
|
988
|
-
dispose() {
|
|
989
|
-
if (this._disposed) return;
|
|
990
|
-
if (this.debug) console.log(`[${this.name}] Disposing...`);
|
|
991
|
-
this._disposed = true;
|
|
992
|
-
this.emitSystemEvent("dispose", void 0);
|
|
993
|
-
this.listeners.clear();
|
|
994
|
-
this.systemEventHandlers.clear();
|
|
995
|
-
globalRegistry.emit("disposed", this);
|
|
996
|
-
if (this.debug) console.log(`[${this.name}] Disposed successfully`);
|
|
997
|
-
}
|
|
998
|
-
/**
|
|
999
|
-
* Emit a new state value and notify all listeners.
|
|
1000
|
-
* @param newState - The new state value
|
|
1001
|
-
* @protected
|
|
1002
|
-
*/
|
|
1003
|
-
emit(newState) {
|
|
1004
|
-
if (this._disposed) throw new Error(`Cannot emit state from disposed container ${this.name}`);
|
|
1005
|
-
if (this._state === newState) return;
|
|
1006
|
-
const previousState = this._state;
|
|
1007
|
-
this._state = newState;
|
|
1008
|
-
this.emitSystemEvent("stateChanged", {
|
|
1009
|
-
state: newState,
|
|
1010
|
-
previousState
|
|
1011
|
-
});
|
|
1012
|
-
for (const listener of this.listeners) try {
|
|
1013
|
-
listener(newState);
|
|
1014
|
-
} catch (error$1) {
|
|
1015
|
-
console.error(`[${this.name}] Error in listener:`, error$1);
|
|
1016
|
-
}
|
|
1017
|
-
const stackTrace = this.captureStackTrace();
|
|
1018
|
-
globalRegistry.emit("stateChanged", this, previousState, newState, stackTrace);
|
|
1019
|
-
this.lastUpdateTimestamp = Date.now();
|
|
1020
|
-
}
|
|
1021
|
-
captureStackTrace() {
|
|
1022
|
-
if (!StateContainer.enableStackTrace || typeof process !== "undefined" && process.env?.NODE_ENV === "production") return "";
|
|
1023
|
-
try {
|
|
1024
|
-
const relevantLines = ((/* @__PURE__ */ new Error()).stack || "").split("\n").slice(1);
|
|
1025
|
-
const formattedLines = [];
|
|
1026
|
-
for (const line of relevantLines) {
|
|
1027
|
-
if (!line.trim()) continue;
|
|
1028
|
-
if (line.includes("StateContainer.emit") || line.includes("StateContainer.update") || line.includes("StateContainer.captureStackTrace") || line.includes("Cubit.patch") || line.includes("Vertex.")) continue;
|
|
1029
|
-
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;
|
|
1030
|
-
const formatted = this.formatStackLine(line);
|
|
1031
|
-
if (formatted) formattedLines.push(formatted);
|
|
1032
|
-
}
|
|
1033
|
-
return formattedLines.join("\n");
|
|
1034
|
-
} catch {
|
|
1035
|
-
return "";
|
|
1036
|
-
}
|
|
1037
|
-
}
|
|
1038
|
-
formatStackLine(line) {
|
|
1039
|
-
const match = line.match(/at\s+(.+?)\s+\((.+?):(\d+):(\d+)\)/);
|
|
1040
|
-
if (!match) {
|
|
1041
|
-
const simpleMatch = line.match(/at\s+(.+?):(\d+):(\d+)/);
|
|
1042
|
-
if (simpleMatch) {
|
|
1043
|
-
const [, url$1, lineNum$1, col$1] = simpleMatch;
|
|
1044
|
-
return ` at ${this.cleanFilePath(url$1)}:${lineNum$1}:${col$1}`;
|
|
1045
|
-
}
|
|
1046
|
-
return null;
|
|
1047
|
-
}
|
|
1048
|
-
const [, functionName, url, lineNum, col] = match;
|
|
1049
|
-
return ` at ${functionName} (${this.cleanFilePath(url)}:${lineNum}:${col})`;
|
|
1050
|
-
}
|
|
1051
|
-
cleanFilePath(url) {
|
|
1052
|
-
let path = url.replace(/http:\/\/localhost:\d+\/@fs/, "").replace(/http:\/\/localhost:\d+\//, "").replace(/\?t=\d+/, "").replace(/\?v=[a-f0-9]+/, "");
|
|
1053
|
-
const projectMatch = path.match(/\/Projects\/blac\/(.+)/);
|
|
1054
|
-
if (projectMatch) path = projectMatch[1];
|
|
1055
|
-
const segments = path.split("/");
|
|
1056
|
-
if (segments.length > 3) path = segments.slice(-3).join("/");
|
|
1057
|
-
return path;
|
|
1058
|
-
}
|
|
1059
|
-
/**
|
|
1060
|
-
* Update state using a transform function.
|
|
1061
|
-
* @param updater - Function that receives current state and returns new state
|
|
1062
|
-
* @protected
|
|
1063
|
-
*/
|
|
1064
|
-
update(updater) {
|
|
1065
|
-
if (this._disposed) throw new Error(`Cannot update state from disposed container ${this.name}`);
|
|
1066
|
-
this.emit(updater(this._state));
|
|
1067
|
-
}
|
|
1068
|
-
emitSystemEvent(event, payload) {
|
|
1069
|
-
const handlers = this.systemEventHandlers.get(event);
|
|
1070
|
-
if (!handlers) return;
|
|
1071
|
-
for (const handler of handlers) try {
|
|
1072
|
-
handler(payload);
|
|
1073
|
-
} catch (error$1) {
|
|
1074
|
-
console.error(`[${this.name}] Error in system event handler:`, error$1);
|
|
1075
|
-
}
|
|
1076
|
-
}
|
|
1077
|
-
/**
|
|
1078
|
-
* Update the props for this container.
|
|
1079
|
-
* Emits the 'propsUpdated' system event.
|
|
1080
|
-
* @param newProps - The new props value
|
|
1081
|
-
*/
|
|
1082
|
-
updateProps(newProps) {
|
|
1083
|
-
if (this._disposed) throw new Error(`Cannot update props on disposed container ${this.name}`);
|
|
1084
|
-
const previousProps = this._props;
|
|
1085
|
-
this._props = newProps;
|
|
1086
|
-
this.emitSystemEvent("propsUpdated", {
|
|
1087
|
-
props: newProps,
|
|
1088
|
-
previousProps
|
|
1089
|
-
});
|
|
1090
|
-
if (this.debug) console.log(`[${this.name}] Props updated:`, {
|
|
1091
|
-
newProps,
|
|
1092
|
-
previousProps
|
|
1093
|
-
});
|
|
1094
|
-
}
|
|
1095
|
-
};
|
|
1096
|
-
|
|
1097
|
-
//#endregion
|
|
1098
|
-
//#region src/core/Cubit.ts
|
|
1099
|
-
/**
|
|
1100
|
-
* Simple state container with direct state emission.
|
|
1101
|
-
* Extends StateContainer with public methods for emitting and updating state.
|
|
1102
|
-
*
|
|
1103
|
-
* @template S - State type
|
|
1104
|
-
* @template P - Props type (optional)
|
|
1105
|
-
*/
|
|
1106
|
-
var Cubit = class extends StateContainer {
|
|
1107
|
-
constructor(initialState) {
|
|
1108
|
-
super(initialState);
|
|
1109
|
-
this.patch = ((partial) => {
|
|
1110
|
-
if (typeof this.state !== "object" || this.state === null) throw new Error("patch() is only available for object state types");
|
|
1111
|
-
this.update((current) => ({
|
|
1112
|
-
...current,
|
|
1113
|
-
...partial
|
|
1114
|
-
}));
|
|
1115
|
-
});
|
|
1116
|
-
}
|
|
1117
|
-
/**
|
|
1118
|
-
* Replace state with a new value and notify all listeners
|
|
1119
|
-
* @param newState - The new state value
|
|
1120
|
-
*/
|
|
1121
|
-
emit(newState) {
|
|
1122
|
-
super["emit"](newState);
|
|
1123
|
-
}
|
|
1124
|
-
/**
|
|
1125
|
-
* Transform current state using an updater function and emit the new state
|
|
1126
|
-
* @param updater - Function that receives current state and returns new state
|
|
1127
|
-
*/
|
|
1128
|
-
update(updater) {
|
|
1129
|
-
super["update"](updater);
|
|
1130
|
-
}
|
|
1131
|
-
};
|
|
1132
|
-
|
|
1133
|
-
//#endregion
|
|
1134
|
-
//#region src/core/Vertex.ts
|
|
1135
|
-
/**
|
|
1136
|
-
* Event-driven state container using discriminated union events.
|
|
1137
|
-
* Extends StateContainer with event-based state transitions and
|
|
1138
|
-
* compile-time exhaustive checking for event handlers.
|
|
1139
|
-
*
|
|
1140
|
-
* @template S - State type (must be an object)
|
|
1141
|
-
* @template E - Event union type (discriminated by 'type' property)
|
|
1142
|
-
* @template P - Props type (optional)
|
|
1143
|
-
*
|
|
1144
|
-
* @example
|
|
1145
|
-
* ```typescript
|
|
1146
|
-
* type CounterEvent =
|
|
1147
|
-
* | { type: 'increment'; amount: number }
|
|
1148
|
-
* | { type: 'decrement' }
|
|
1149
|
-
* | { type: 'reset' };
|
|
1150
|
-
*
|
|
1151
|
-
* class CounterVertex extends Vertex<{ count: number }, CounterEvent> {
|
|
1152
|
-
* constructor() {
|
|
1153
|
-
* super({ count: 0 });
|
|
1154
|
-
* this.createHandlers({
|
|
1155
|
-
* increment: (event, emit) => {
|
|
1156
|
-
* emit({ count: this.state.count + event.amount });
|
|
1157
|
-
* },
|
|
1158
|
-
* decrement: (_, emit) => {
|
|
1159
|
-
* emit({ count: this.state.count - 1 });
|
|
1160
|
-
* },
|
|
1161
|
-
* reset: (_, emit) => {
|
|
1162
|
-
* emit({ count: 0 });
|
|
1163
|
-
* },
|
|
1164
|
-
* });
|
|
1165
|
-
* }
|
|
1166
|
-
*
|
|
1167
|
-
* increment = (amount = 1) => this.add({ type: 'increment', amount });
|
|
1168
|
-
* }
|
|
1169
|
-
* ```
|
|
1170
|
-
*/
|
|
1171
|
-
var Vertex = class extends StateContainer {
|
|
1172
|
-
constructor(initialState) {
|
|
1173
|
-
super(initialState);
|
|
1174
|
-
this.handlers = /* @__PURE__ */ new Map();
|
|
1175
|
-
this.isProcessing = false;
|
|
1176
|
-
this.eventQueue = [];
|
|
1177
|
-
this.add = (event) => {
|
|
1178
|
-
const enrichedEvent = this.enrichEvent(event);
|
|
1179
|
-
globalRegistry.emit("eventAdded", this, enrichedEvent);
|
|
1180
|
-
this.processEvent(enrichedEvent);
|
|
1181
|
-
};
|
|
1182
|
-
}
|
|
1183
|
-
/**
|
|
1184
|
-
* Register all event handlers with exhaustive type checking.
|
|
1185
|
-
* TypeScript will error if any event type from the union is missing.
|
|
1186
|
-
*
|
|
1187
|
-
* @param handlers - Map of event type to handler function
|
|
1188
|
-
*/
|
|
1189
|
-
createHandlers(handlers) {
|
|
1190
|
-
for (const [eventType, handler] of Object.entries(handlers)) this.handlers.set(eventType, handler);
|
|
1191
|
-
}
|
|
1192
|
-
/** @internal */
|
|
1193
|
-
enrichEvent(event) {
|
|
1194
|
-
return {
|
|
1195
|
-
...event,
|
|
1196
|
-
timestamp: Date.now()
|
|
1197
|
-
};
|
|
1198
|
-
}
|
|
1199
|
-
/** @internal */
|
|
1200
|
-
processEvent(event) {
|
|
1201
|
-
if (this.isProcessing) {
|
|
1202
|
-
this.eventQueue.push(event);
|
|
1203
|
-
return;
|
|
1204
|
-
}
|
|
1205
|
-
this.isProcessing = true;
|
|
1206
|
-
try {
|
|
1207
|
-
this.handleEvent(event);
|
|
1208
|
-
while (this.eventQueue.length > 0) {
|
|
1209
|
-
const nextEvent = this.eventQueue.shift();
|
|
1210
|
-
this.handleEvent(nextEvent);
|
|
1211
|
-
}
|
|
1212
|
-
} finally {
|
|
1213
|
-
this.isProcessing = false;
|
|
1214
|
-
}
|
|
1215
|
-
}
|
|
1216
|
-
/** @internal */
|
|
1217
|
-
handleEvent(event) {
|
|
1218
|
-
const handler = this.handlers.get(event.type);
|
|
1219
|
-
if (!handler) {
|
|
1220
|
-
if (this.debug) console.warn(`No handler registered for event type: ${event.type}`);
|
|
1221
|
-
return;
|
|
1222
|
-
}
|
|
1223
|
-
const emitFn = (state) => {
|
|
1224
|
-
this.emit(state);
|
|
1225
|
-
};
|
|
1226
|
-
try {
|
|
1227
|
-
handler(event, emitFn);
|
|
1228
|
-
} catch (error$1) {
|
|
1229
|
-
if (this.debug) console.error(`Error handling event ${event.type}:`, error$1);
|
|
1230
|
-
this.onEventError(event, error$1);
|
|
1231
|
-
}
|
|
1232
|
-
}
|
|
1233
|
-
/**
|
|
1234
|
-
* Called when an error occurs during event processing.
|
|
1235
|
-
* Override to implement custom error handling.
|
|
1236
|
-
*
|
|
1237
|
-
* @param _event - The event that caused the error
|
|
1238
|
-
* @param _error - The error that occurred
|
|
1239
|
-
*/
|
|
1240
|
-
onEventError(_event, _error) {}
|
|
1241
|
-
};
|
|
1242
|
-
|
|
1243
|
-
//#endregion
|
|
1244
|
-
//#region src/core/StatelessCubit.ts
|
|
1245
|
-
/**
|
|
1246
|
-
* A Cubit without state management capabilities.
|
|
1247
|
-
* Use this for action-only containers that don't need to manage or emit state.
|
|
1248
|
-
*
|
|
1249
|
-
* StatelessCubit:
|
|
1250
|
-
* - Has no `emit()`, `update()`, or `patch()` methods
|
|
1251
|
-
* - Has no accessible `state` property
|
|
1252
|
-
* - Cannot be subscribed to (throws error)
|
|
1253
|
-
* - Is rejected by `useBloc` hook (use `useBlocActions` instead)
|
|
1254
|
-
*
|
|
1255
|
-
* @template P - Props type (optional)
|
|
1256
|
-
*
|
|
1257
|
-
* @example
|
|
1258
|
-
* ```typescript
|
|
1259
|
-
* class AnalyticsService extends StatelessCubit {
|
|
1260
|
-
* trackPageView(page: string) {
|
|
1261
|
-
* analytics.track('page_view', { page });
|
|
1262
|
-
* }
|
|
1263
|
-
*
|
|
1264
|
-
* trackClick(element: string) {
|
|
1265
|
-
* analytics.track('click', { element });
|
|
1266
|
-
* }
|
|
1267
|
-
* }
|
|
1268
|
-
*
|
|
1269
|
-
* // In React component:
|
|
1270
|
-
* const analytics = useBlocActions(AnalyticsService);
|
|
1271
|
-
* analytics.trackPageView('/home');
|
|
1272
|
-
* ```
|
|
1273
|
-
*/
|
|
1274
|
-
var StatelessCubit = class extends Cubit {
|
|
1275
|
-
static {
|
|
1276
|
-
this[STATELESS_MARKER] = true;
|
|
1277
|
-
}
|
|
1278
|
-
constructor() {
|
|
1279
|
-
super({});
|
|
1280
|
-
this.patch = (_partial) => {
|
|
1281
|
-
throw new Error(`${this.name} is a StatelessCubit and does not support patch(). Use a regular Cubit if you need state management.`);
|
|
1282
|
-
};
|
|
1283
|
-
}
|
|
1284
|
-
/**
|
|
1285
|
-
* State is not available on StatelessCubit.
|
|
1286
|
-
* @throws Always throws an error when accessed.
|
|
1287
|
-
*/
|
|
1288
|
-
get state() {
|
|
1289
|
-
throw new Error(`${this.name} is a StatelessCubit and does not have state. Use a regular Cubit if you need state management.`);
|
|
1290
|
-
}
|
|
1291
|
-
/**
|
|
1292
|
-
* Subscriptions are not available on StatelessCubit.
|
|
1293
|
-
* @throws Always throws an error when called.
|
|
1294
|
-
*/
|
|
1295
|
-
subscribe(_listener) {
|
|
1296
|
-
throw new Error(`${this.name} is a StatelessCubit and does not support subscriptions. Use useBlocActions() instead of useBloc() in React.`);
|
|
1297
|
-
}
|
|
1298
|
-
/**
|
|
1299
|
-
* emit() is not available on StatelessCubit.
|
|
1300
|
-
* TypeScript will show an error if you try to call this method.
|
|
1301
|
-
*/
|
|
1302
|
-
emit(_newState) {
|
|
1303
|
-
throw new Error(`${this.name} is a StatelessCubit and does not support emit(). Use a regular Cubit if you need state management.`);
|
|
1304
|
-
}
|
|
1305
|
-
/**
|
|
1306
|
-
* update() is not available on StatelessCubit.
|
|
1307
|
-
* TypeScript will show an error if you try to call this method.
|
|
1308
|
-
*/
|
|
1309
|
-
update(_updater) {
|
|
1310
|
-
throw new Error(`${this.name} is a StatelessCubit and does not support update(). Use a regular Cubit if you need state management.`);
|
|
1311
|
-
}
|
|
1312
|
-
};
|
|
1313
|
-
|
|
1314
|
-
//#endregion
|
|
1315
|
-
//#region src/core/StatelessVertex.ts
|
|
1316
|
-
/**
|
|
1317
|
-
* An event-driven container without state management capabilities.
|
|
1318
|
-
* Use this for event processing that only produces side effects (logging, analytics, etc).
|
|
1319
|
-
*
|
|
1320
|
-
* StatelessVertex:
|
|
1321
|
-
* - Processes events via `add()` method
|
|
1322
|
-
* - Handlers receive only the event (no `emit` function)
|
|
1323
|
-
* - Has no accessible `state` property
|
|
1324
|
-
* - Cannot be subscribed to (throws error)
|
|
1325
|
-
* - Is rejected by `useBloc` hook (use `useBlocActions` instead)
|
|
1326
|
-
*
|
|
1327
|
-
* @template E - Event union type (discriminated by 'type' property)
|
|
1328
|
-
* @template P - Props type (optional)
|
|
1329
|
-
*
|
|
1330
|
-
* @example
|
|
1331
|
-
* ```typescript
|
|
1332
|
-
* type LogEvent =
|
|
1333
|
-
* | { type: 'info'; message: string }
|
|
1334
|
-
* | { type: 'error'; message: string; error: Error }
|
|
1335
|
-
* | { type: 'warn'; message: string };
|
|
1336
|
-
*
|
|
1337
|
-
* class LoggingVertex extends StatelessVertex<LogEvent> {
|
|
1338
|
-
* constructor() {
|
|
1339
|
-
* super();
|
|
1340
|
-
* this.createHandlers({
|
|
1341
|
-
* info: (event) => console.log('[INFO]', event.message),
|
|
1342
|
-
* error: (event) => console.error('[ERROR]', event.message, event.error),
|
|
1343
|
-
* warn: (event) => console.warn('[WARN]', event.message),
|
|
1344
|
-
* });
|
|
1345
|
-
* }
|
|
1346
|
-
*
|
|
1347
|
-
* info = (message: string) => this.add({ type: 'info', message });
|
|
1348
|
-
* error = (message: string, error: Error) => this.add({ type: 'error', message, error });
|
|
1349
|
-
* warn = (message: string) => this.add({ type: 'warn', message });
|
|
1350
|
-
* }
|
|
1351
|
-
*
|
|
1352
|
-
* // In React component:
|
|
1353
|
-
* const logger = useBlocActions(LoggingVertex);
|
|
1354
|
-
* logger.info('Page loaded');
|
|
1355
|
-
* ```
|
|
1356
|
-
*/
|
|
1357
|
-
var StatelessVertex = class extends StateContainer {
|
|
1358
|
-
static {
|
|
1359
|
-
this[STATELESS_MARKER] = true;
|
|
1360
|
-
}
|
|
1361
|
-
constructor() {
|
|
1362
|
-
super({});
|
|
1363
|
-
this.handlers = /* @__PURE__ */ new Map();
|
|
1364
|
-
this.isProcessing = false;
|
|
1365
|
-
this.eventQueue = [];
|
|
1366
|
-
this.add = (event) => {
|
|
1367
|
-
const enrichedEvent = this.enrichEvent(event);
|
|
1368
|
-
globalRegistry.emit("eventAdded", this, enrichedEvent);
|
|
1369
|
-
this.processEvent(enrichedEvent);
|
|
1370
|
-
};
|
|
1371
|
-
}
|
|
1372
|
-
/**
|
|
1373
|
-
* State is not available on StatelessVertex.
|
|
1374
|
-
* @throws Always throws an error when accessed.
|
|
1375
|
-
*/
|
|
1376
|
-
get state() {
|
|
1377
|
-
throw new Error(`${this.name} is a StatelessVertex and does not have state. Use a regular Vertex if you need state management.`);
|
|
1378
|
-
}
|
|
1379
|
-
/**
|
|
1380
|
-
* Subscriptions are not available on StatelessVertex.
|
|
1381
|
-
* @throws Always throws an error when called.
|
|
1382
|
-
*/
|
|
1383
|
-
subscribe(_listener) {
|
|
1384
|
-
throw new Error(`${this.name} is a StatelessVertex and does not support subscriptions. Use useBlocActions() instead of useBloc() in React.`);
|
|
1385
|
-
}
|
|
1386
|
-
/**
|
|
1387
|
-
* Register all event handlers with exhaustive type checking.
|
|
1388
|
-
* TypeScript will error if any event type from the union is missing.
|
|
1389
|
-
*
|
|
1390
|
-
* Unlike regular Vertex, handlers do NOT receive an emit function
|
|
1391
|
-
* since StatelessVertex cannot emit state.
|
|
1392
|
-
*
|
|
1393
|
-
* @param handlers - Map of event type to handler function
|
|
1394
|
-
*/
|
|
1395
|
-
createHandlers(handlers) {
|
|
1396
|
-
for (const [eventType, handler] of Object.entries(handlers)) this.handlers.set(eventType, handler);
|
|
1397
|
-
}
|
|
1398
|
-
/** @internal */
|
|
1399
|
-
enrichEvent(event) {
|
|
1400
|
-
return {
|
|
1401
|
-
...event,
|
|
1402
|
-
timestamp: Date.now()
|
|
1403
|
-
};
|
|
1404
|
-
}
|
|
1405
|
-
/** @internal */
|
|
1406
|
-
processEvent(event) {
|
|
1407
|
-
if (this.isProcessing) {
|
|
1408
|
-
this.eventQueue.push(event);
|
|
1409
|
-
return;
|
|
1410
|
-
}
|
|
1411
|
-
this.isProcessing = true;
|
|
1412
|
-
try {
|
|
1413
|
-
this.handleEvent(event);
|
|
1414
|
-
while (this.eventQueue.length > 0) {
|
|
1415
|
-
const nextEvent = this.eventQueue.shift();
|
|
1416
|
-
this.handleEvent(nextEvent);
|
|
1417
|
-
}
|
|
1418
|
-
} finally {
|
|
1419
|
-
this.isProcessing = false;
|
|
1420
|
-
}
|
|
1421
|
-
}
|
|
1422
|
-
/** @internal */
|
|
1423
|
-
handleEvent(event) {
|
|
1424
|
-
const handler = this.handlers.get(event.type);
|
|
1425
|
-
if (!handler) {
|
|
1426
|
-
if (this.debug) console.warn(`No handler registered for event type: ${event.type}`);
|
|
1427
|
-
return;
|
|
1428
|
-
}
|
|
1429
|
-
try {
|
|
1430
|
-
handler(event);
|
|
1431
|
-
} catch (error$1) {
|
|
1432
|
-
if (this.debug) console.error(`Error handling event ${event.type}:`, error$1);
|
|
1433
|
-
this.onEventError(event, error$1);
|
|
1434
|
-
}
|
|
1435
|
-
}
|
|
1436
|
-
/**
|
|
1437
|
-
* Called when an error occurs during event processing.
|
|
1438
|
-
* Override to implement custom error handling.
|
|
1439
|
-
*
|
|
1440
|
-
* @param _event - The event that caused the error
|
|
1441
|
-
* @param _error - The error that occurred
|
|
1442
|
-
*/
|
|
1443
|
-
onEventError(_event, _error) {}
|
|
1444
|
-
/**
|
|
1445
|
-
* emit() is not available on StatelessVertex.
|
|
1446
|
-
* @internal
|
|
1447
|
-
*/
|
|
1448
|
-
emit(_newState) {
|
|
1449
|
-
throw new Error(`${this.name} is a StatelessVertex and does not support emit(). Use a regular Vertex if you need state management.`);
|
|
1450
|
-
}
|
|
1451
|
-
/**
|
|
1452
|
-
* update() is not available on StatelessVertex.
|
|
1453
|
-
* @internal
|
|
1454
|
-
*/
|
|
1455
|
-
update(_updater) {
|
|
1456
|
-
throw new Error(`${this.name} is a StatelessVertex and does not support update(). Use a regular Vertex if you need state management.`);
|
|
1457
|
-
}
|
|
1458
|
-
};
|
|
1459
|
-
|
|
1460
|
-
//#endregion
|
|
1461
|
-
//#region src/plugin/BlacPlugin.ts
|
|
1462
|
-
/**
|
|
1463
|
-
* Type guard to check if a plugin has a required onInstall hook
|
|
1464
|
-
* @param plugin - The plugin to check
|
|
1465
|
-
* @returns true if the plugin implements BlacPluginWithInit
|
|
1466
|
-
*/
|
|
1467
|
-
function hasInitHook(plugin) {
|
|
1468
|
-
return typeof plugin.onInstall === "function";
|
|
1469
|
-
}
|
|
1470
|
-
|
|
1471
|
-
//#endregion
|
|
1472
|
-
//#region src/tracking/proxy-tracker.ts
|
|
1473
|
-
/**
|
|
1474
|
-
* Functional proxy tracker for automatic dependency tracking
|
|
1475
|
-
*
|
|
1476
|
-
* This module provides a functional approach to proxy-based dependency tracking.
|
|
1477
|
-
* Unlike the class-based ProxyTracker, this uses a state object pattern that's
|
|
1478
|
-
* easier to integrate with functional programming patterns and framework hooks.
|
|
1479
|
-
*
|
|
1480
|
-
* @internal
|
|
1481
|
-
*/
|
|
1482
|
-
/**
|
|
1483
|
-
* Check if a value can be proxied
|
|
1484
|
-
*
|
|
1485
|
-
* Returns true for plain objects and arrays only.
|
|
1486
|
-
* Excludes built-in objects like Date, Map, Set, etc.
|
|
1487
|
-
*
|
|
1488
|
-
* @internal
|
|
1489
|
-
*/
|
|
1490
|
-
function isProxyable(value) {
|
|
1491
|
-
if (typeof value !== "object" || value === null) return false;
|
|
1492
|
-
const proto = Object.getPrototypeOf(value);
|
|
1493
|
-
return proto === Object.prototype || proto === Array.prototype;
|
|
1494
|
-
}
|
|
1495
|
-
/**
|
|
1496
|
-
* Create a new proxy tracker state
|
|
1497
|
-
*
|
|
1498
|
-
* @example
|
|
1499
|
-
* const state = createProxyTrackerState<MyState>();
|
|
1500
|
-
* startProxyTracking(state);
|
|
1501
|
-
* const proxy = createProxyForTarget(state, myObject);
|
|
1502
|
-
* // ... use proxy ...
|
|
1503
|
-
* const paths = stopProxyTracking(state);
|
|
1504
|
-
*
|
|
1505
|
-
* @internal
|
|
1506
|
-
*/
|
|
1507
|
-
function createProxyState() {
|
|
1508
|
-
return {
|
|
1509
|
-
trackedPaths: /* @__PURE__ */ new Set(),
|
|
1510
|
-
isTracking: false,
|
|
1511
|
-
proxyCache: /* @__PURE__ */ new WeakMap(),
|
|
1512
|
-
boundFunctionsCache: null,
|
|
1513
|
-
lastProxiedState: null,
|
|
1514
|
-
lastProxy: null,
|
|
1515
|
-
maxDepth: 10
|
|
1516
|
-
};
|
|
1517
|
-
}
|
|
1518
|
-
/**
|
|
1519
|
-
* Start tracking property accesses
|
|
1520
|
-
*
|
|
1521
|
-
* Clears previous tracked paths and enables tracking mode.
|
|
1522
|
-
*
|
|
1523
|
-
* @internal
|
|
1524
|
-
*/
|
|
1525
|
-
function startProxy(state) {
|
|
1526
|
-
state.isTracking = true;
|
|
1527
|
-
state.trackedPaths.clear();
|
|
1528
|
-
}
|
|
1529
|
-
/**
|
|
1530
|
-
* Stop tracking and return the tracked paths
|
|
1531
|
-
*
|
|
1532
|
-
* Returns a new Set containing all tracked paths.
|
|
1533
|
-
*
|
|
1534
|
-
* @internal
|
|
1535
|
-
*/
|
|
1536
|
-
function stopProxy(state) {
|
|
1537
|
-
state.isTracking = false;
|
|
1538
|
-
return new Set(state.trackedPaths);
|
|
1539
|
-
}
|
|
1540
|
-
/**
|
|
1541
|
-
* Create a proxy for an array with property access tracking
|
|
1542
|
-
*
|
|
1543
|
-
* Tracks:
|
|
1544
|
-
* - Array element access (arr[0])
|
|
1545
|
-
* - Length access (arr.length)
|
|
1546
|
-
* - Array method calls (arr.map, arr.filter, etc.)
|
|
1547
|
-
*
|
|
1548
|
-
* @internal
|
|
1549
|
-
*/
|
|
1550
|
-
function createArrayProxy(state, target, path, depth = 0) {
|
|
1551
|
-
const proxy = new Proxy(target, { get: (arr, prop) => {
|
|
1552
|
-
if (typeof prop === "symbol") return Reflect.get(arr, prop);
|
|
1553
|
-
const value = Reflect.get(arr, prop);
|
|
1554
|
-
if (typeof value === "function") {
|
|
1555
|
-
if (!state.boundFunctionsCache) state.boundFunctionsCache = /* @__PURE__ */ new WeakMap();
|
|
1556
|
-
const cached = state.boundFunctionsCache.get(value);
|
|
1557
|
-
if (cached) return cached;
|
|
1558
|
-
const bound = value.bind(arr);
|
|
1559
|
-
state.boundFunctionsCache.set(value, bound);
|
|
1560
|
-
return bound;
|
|
1561
|
-
}
|
|
1562
|
-
if (prop === "length") {
|
|
1563
|
-
if (state.isTracking) {
|
|
1564
|
-
const fullPath$1 = path ? `${path}.length` : "length";
|
|
1565
|
-
state.trackedPaths.add(fullPath$1);
|
|
1566
|
-
}
|
|
1567
|
-
return value;
|
|
1568
|
-
}
|
|
1569
|
-
let fullPath;
|
|
1570
|
-
if (typeof prop === "string") {
|
|
1571
|
-
const index = Number(prop);
|
|
1572
|
-
if (!isNaN(index) && index >= 0) fullPath = path ? `${path}[${index}]` : `[${index}]`;
|
|
1573
|
-
else fullPath = path ? `${path}.${prop}` : prop;
|
|
1574
|
-
} else return value;
|
|
1575
|
-
if (isProxyable(value)) return createInternal(state, value, fullPath, depth + 1);
|
|
1576
|
-
if (state.isTracking) state.trackedPaths.add(fullPath);
|
|
1577
|
-
return value;
|
|
1578
|
-
} });
|
|
1579
|
-
state.proxyCache.set(target, proxy);
|
|
1580
|
-
return proxy;
|
|
1581
|
-
}
|
|
1582
|
-
/**
|
|
1583
|
-
* Create a proxy for an object with property access tracking
|
|
1584
|
-
*
|
|
1585
|
-
* This is the core proxy creation function that recursively creates proxies
|
|
1586
|
-
* for nested objects and arrays.
|
|
1587
|
-
*
|
|
1588
|
-
* Tracks:
|
|
1589
|
-
* - Property access (obj.prop)
|
|
1590
|
-
* - Nested property access (obj.nested.prop)
|
|
1591
|
-
* - 'in' operator usage ('prop' in obj)
|
|
1592
|
-
* - Object.keys, Object.entries, etc.
|
|
1593
|
-
*
|
|
1594
|
-
* @internal
|
|
1595
|
-
*/
|
|
1596
|
-
function createInternal(state, target, path = "", depth = 0) {
|
|
1597
|
-
if (!state.isTracking || !isProxyable(target)) return target;
|
|
1598
|
-
if (depth >= state.maxDepth) return target;
|
|
1599
|
-
if (state.proxyCache.has(target)) return state.proxyCache.get(target);
|
|
1600
|
-
if (Array.isArray(target)) return createArrayProxy(state, target, path, depth);
|
|
1601
|
-
const proxy = new Proxy(target, {
|
|
1602
|
-
get: (obj, prop) => {
|
|
1603
|
-
if (typeof prop === "symbol") return Reflect.get(obj, prop);
|
|
1604
|
-
const value = Reflect.get(obj, prop);
|
|
1605
|
-
if (typeof value === "function") {
|
|
1606
|
-
if (!state.boundFunctionsCache) state.boundFunctionsCache = /* @__PURE__ */ new WeakMap();
|
|
1607
|
-
const cached = state.boundFunctionsCache.get(value);
|
|
1608
|
-
if (cached) return cached;
|
|
1609
|
-
const bound = value.bind(obj);
|
|
1610
|
-
state.boundFunctionsCache.set(value, bound);
|
|
1611
|
-
return bound;
|
|
1612
|
-
}
|
|
1613
|
-
const fullPath = path ? `${path}.${String(prop)}` : String(prop);
|
|
1614
|
-
if (typeof prop === "string" && state.isTracking) {
|
|
1615
|
-
if (!prop.startsWith("_") && !prop.startsWith("$$")) state.trackedPaths.add(fullPath);
|
|
1616
|
-
}
|
|
1617
|
-
if (isProxyable(value)) return createInternal(state, value, fullPath, depth + 1);
|
|
1618
|
-
return value;
|
|
1619
|
-
},
|
|
1620
|
-
has: (obj, prop) => {
|
|
1621
|
-
if (typeof prop === "string" && state.isTracking) {
|
|
1622
|
-
const fullPath = path ? `${path}.${prop}` : prop;
|
|
1623
|
-
state.trackedPaths.add(fullPath);
|
|
1624
|
-
}
|
|
1625
|
-
return Reflect.has(obj, prop);
|
|
1626
|
-
},
|
|
1627
|
-
ownKeys: (obj) => {
|
|
1628
|
-
if (state.isTracking && path) state.trackedPaths.add(path);
|
|
1629
|
-
return Reflect.ownKeys(obj);
|
|
1630
|
-
}
|
|
1631
|
-
});
|
|
1632
|
-
state.proxyCache.set(target, proxy);
|
|
1633
|
-
return proxy;
|
|
1634
|
-
}
|
|
1635
|
-
/**
|
|
1636
|
-
* Create a proxy for a target with caching
|
|
1637
|
-
*
|
|
1638
|
-
* If the target hasn't changed since the last call, returns the cached proxy.
|
|
1639
|
-
* Otherwise, creates a new proxy and caches it.
|
|
1640
|
-
*
|
|
1641
|
-
* This is the main entry point for creating tracked proxies.
|
|
1642
|
-
*
|
|
1643
|
-
* @internal
|
|
1644
|
-
*/
|
|
1645
|
-
function createForTarget(state, target) {
|
|
1646
|
-
if (state.lastProxiedState === target && state.lastProxy) return state.lastProxy;
|
|
1647
|
-
state.proxyCache = /* @__PURE__ */ new WeakMap();
|
|
1648
|
-
state.boundFunctionsCache = null;
|
|
1649
|
-
const proxy = createInternal(state, target, "", 0);
|
|
1650
|
-
state.lastProxiedState = target;
|
|
1651
|
-
state.lastProxy = proxy;
|
|
1652
|
-
return proxy;
|
|
1653
|
-
}
|
|
1654
|
-
|
|
1655
|
-
//#endregion
|
|
1656
|
-
//#region src/tracking/path-utils.ts
|
|
1657
|
-
/**
|
|
1658
|
-
* Path utilities for dependency tracking
|
|
1659
|
-
*
|
|
1660
|
-
* Provides utilities for parsing property paths and extracting values
|
|
1661
|
-
* from nested objects using path strings.
|
|
1662
|
-
*
|
|
1663
|
-
* @internal
|
|
1664
|
-
*/
|
|
1665
|
-
/**
|
|
1666
|
-
* Parse a property path string into an array of segments
|
|
1667
|
-
*
|
|
1668
|
-
* @internal
|
|
1669
|
-
*
|
|
1670
|
-
* Handles both dot notation (a.b.c) and bracket notation (a[0].b)
|
|
1671
|
-
*
|
|
1672
|
-
* @example
|
|
1673
|
-
* ```ts
|
|
1674
|
-
* parsePath('user.name') // ['user', 'name']
|
|
1675
|
-
* parsePath('items[0].name') // ['items', '0', 'name']
|
|
1676
|
-
* parsePath('data.users[2].address.city') // ['data', 'users', '2', 'address', 'city']
|
|
1677
|
-
* ```
|
|
1678
|
-
*/
|
|
1679
|
-
function parsePath(path) {
|
|
1680
|
-
const segments = [];
|
|
1681
|
-
let current = "";
|
|
1682
|
-
let i = 0;
|
|
1683
|
-
while (i < path.length) {
|
|
1684
|
-
const char = path[i];
|
|
1685
|
-
if (char === ".") {
|
|
1686
|
-
if (current) segments.push(current);
|
|
1687
|
-
current = "";
|
|
1688
|
-
} else if (char === "[") {
|
|
1689
|
-
if (current) segments.push(current);
|
|
1690
|
-
current = "";
|
|
1691
|
-
i++;
|
|
1692
|
-
while (i < path.length && path[i] !== "]") current += path[i++];
|
|
1693
|
-
if (current) segments.push(current);
|
|
1694
|
-
current = "";
|
|
1695
|
-
} else current += char;
|
|
1696
|
-
i++;
|
|
1697
|
-
}
|
|
1698
|
-
if (current) segments.push(current);
|
|
1699
|
-
return segments;
|
|
1700
|
-
}
|
|
1701
|
-
/**
|
|
1702
|
-
* Get a value from an object using a path of segments
|
|
1703
|
-
*
|
|
1704
|
-
* @example
|
|
1705
|
-
* ```ts
|
|
1706
|
-
* const obj = { user: { name: 'Alice', age: 30 } }
|
|
1707
|
-
* getValueAtPath(obj, ['user', 'name']) // 'Alice'
|
|
1708
|
-
* getValueAtPath(obj, ['user', 'age']) // 30
|
|
1709
|
-
* getValueAtPath(obj, ['user', 'missing']) // undefined
|
|
1710
|
-
* ```
|
|
1711
|
-
*
|
|
1712
|
-
* @internal
|
|
1713
|
-
*/
|
|
1714
|
-
function getValueAtPath(obj, segments) {
|
|
1715
|
-
if (obj == null) return void 0;
|
|
1716
|
-
let current = obj;
|
|
1717
|
-
for (let i = 0; i < segments.length; i++) {
|
|
1718
|
-
current = current[segments[i]];
|
|
1719
|
-
if (current == null) return void 0;
|
|
1720
|
-
}
|
|
1721
|
-
return current;
|
|
1722
|
-
}
|
|
1723
|
-
/**
|
|
1724
|
-
* Shallow equality comparison for arrays
|
|
1725
|
-
*
|
|
1726
|
-
* Compares two arrays element-by-element using Object.is
|
|
1727
|
-
*
|
|
1728
|
-
* @example
|
|
1729
|
-
* ```ts
|
|
1730
|
-
* shallowEqual([1, 2, 3], [1, 2, 3]) // true
|
|
1731
|
-
* shallowEqual([1, 2, 3], [1, 2, 4]) // false
|
|
1732
|
-
* shallowEqual([1, 2], [1, 2, 3]) // false
|
|
1733
|
-
* ```
|
|
1734
|
-
*
|
|
1735
|
-
* @internal
|
|
1736
|
-
*/
|
|
1737
|
-
function shallowEqual(arr1, arr2) {
|
|
1738
|
-
if (arr1.length !== arr2.length) return false;
|
|
1739
|
-
for (let i = 0; i < arr1.length; i++) if (!Object.is(arr1[i], arr2[i])) return false;
|
|
1740
|
-
return true;
|
|
1741
|
-
}
|
|
1742
|
-
|
|
1743
|
-
//#endregion
|
|
1744
|
-
//#region src/tracking/dependency-tracker.ts
|
|
1745
|
-
function isChildPath(child, parent) {
|
|
1746
|
-
if (child === parent) return false;
|
|
1747
|
-
return child.startsWith(parent + ".") || child.startsWith(parent + "[");
|
|
1748
|
-
}
|
|
1749
|
-
/**
|
|
1750
|
-
* Extract the parent array path from an array child path
|
|
1751
|
-
* Examples:
|
|
1752
|
-
* 'messages.length' -> 'messages'
|
|
1753
|
-
* 'messages[0]' -> 'messages'
|
|
1754
|
-
* 'messages[0].text' -> 'messages'
|
|
1755
|
-
* 'data.items[0]' -> 'data.items'
|
|
1756
|
-
*/
|
|
1757
|
-
function getArrayParentPath(path) {
|
|
1758
|
-
if (path.endsWith(".length")) return path.slice(0, -7);
|
|
1759
|
-
const arrayIndexMatch = path.match(/^(.+?)\[\d+\]/);
|
|
1760
|
-
if (arrayIndexMatch) return arrayIndexMatch[1];
|
|
1761
|
-
return null;
|
|
1762
|
-
}
|
|
1763
|
-
/**
|
|
1764
|
-
* @internal
|
|
1765
|
-
*/
|
|
1766
|
-
function optimizeTrackedPaths(paths) {
|
|
1767
|
-
if (paths.size === 0) return /* @__PURE__ */ new Set();
|
|
1768
|
-
if (paths.size === 1) return new Set(paths);
|
|
1769
|
-
const sortedPaths = Array.from(paths).sort((a, b) => b.length - a.length);
|
|
1770
|
-
const optimized = /* @__PURE__ */ new Set();
|
|
1771
|
-
for (const path of sortedPaths) {
|
|
1772
|
-
let hasMoreSpecificChild = false;
|
|
1773
|
-
for (const optimizedPath of optimized) if (isChildPath(optimizedPath, path)) {
|
|
1774
|
-
hasMoreSpecificChild = true;
|
|
1775
|
-
break;
|
|
1776
|
-
}
|
|
1777
|
-
if (!hasMoreSpecificChild) optimized.add(path);
|
|
1778
|
-
}
|
|
1779
|
-
const arrayParents = /* @__PURE__ */ new Set();
|
|
1780
|
-
for (const path of optimized) {
|
|
1781
|
-
const arrayParent = getArrayParentPath(path);
|
|
1782
|
-
if (arrayParent) arrayParents.add(arrayParent);
|
|
1783
|
-
}
|
|
1784
|
-
for (const arrayParent of arrayParents) optimized.add(arrayParent);
|
|
1785
|
-
return optimized;
|
|
1786
|
-
}
|
|
1787
|
-
/**
|
|
1788
|
-
* @internal
|
|
1789
|
-
*/
|
|
1790
|
-
function createDependencyState() {
|
|
1791
|
-
return {
|
|
1792
|
-
proxyState: createProxyState(),
|
|
1793
|
-
previousRenderPaths: /* @__PURE__ */ new Set(),
|
|
1794
|
-
currentRenderPaths: /* @__PURE__ */ new Set(),
|
|
1795
|
-
pathCache: /* @__PURE__ */ new Map(),
|
|
1796
|
-
lastCheckedState: null,
|
|
1797
|
-
lastCheckedValues: /* @__PURE__ */ new Map()
|
|
1798
|
-
};
|
|
1799
|
-
}
|
|
1800
|
-
/**
|
|
1801
|
-
* @internal
|
|
1802
|
-
*/
|
|
1803
|
-
function startDependency(tracker) {
|
|
1804
|
-
startProxy(tracker.proxyState);
|
|
1805
|
-
}
|
|
1806
|
-
/**
|
|
1807
|
-
* @internal
|
|
1808
|
-
*/
|
|
1809
|
-
function createDependencyProxy(tracker, state) {
|
|
1810
|
-
return createForTarget(tracker.proxyState, state);
|
|
1811
|
-
}
|
|
1812
|
-
/**
|
|
1813
|
-
* @internal
|
|
1814
|
-
*/
|
|
1815
|
-
function capturePaths(tracker, state) {
|
|
1816
|
-
tracker.previousRenderPaths = tracker.currentRenderPaths;
|
|
1817
|
-
tracker.currentRenderPaths = optimizeTrackedPaths(stopProxy(tracker.proxyState));
|
|
1818
|
-
if (tracker.previousRenderPaths.size === 0 && tracker.currentRenderPaths.size === 0) return;
|
|
1819
|
-
const trackedPathsUnion = new Set(tracker.previousRenderPaths);
|
|
1820
|
-
for (const path of tracker.currentRenderPaths) trackedPathsUnion.add(path);
|
|
1821
|
-
const canReuseCache = tracker.lastCheckedState === state;
|
|
1822
|
-
for (const path of trackedPathsUnion) if (!tracker.pathCache.has(path)) {
|
|
1823
|
-
const segments = parsePath(path);
|
|
1824
|
-
const value = canReuseCache && tracker.lastCheckedValues.has(path) ? tracker.lastCheckedValues.get(path) : getValueAtPath(state, segments);
|
|
1825
|
-
tracker.pathCache.set(path, {
|
|
1826
|
-
segments,
|
|
1827
|
-
value
|
|
1828
|
-
});
|
|
1829
|
-
} else {
|
|
1830
|
-
const info$1 = tracker.pathCache.get(path);
|
|
1831
|
-
info$1.value = canReuseCache && tracker.lastCheckedValues.has(path) ? tracker.lastCheckedValues.get(path) : getValueAtPath(state, info$1.segments);
|
|
1832
|
-
}
|
|
1833
|
-
tracker.lastCheckedValues.clear();
|
|
1834
|
-
}
|
|
1835
|
-
/**
|
|
1836
|
-
* @internal
|
|
1837
|
-
*/
|
|
1838
|
-
function hasDependencyChanges(tracker, state) {
|
|
1839
|
-
if (tracker.pathCache.size === 0) return true;
|
|
1840
|
-
tracker.lastCheckedValues.clear();
|
|
1841
|
-
for (const [path, info$1] of tracker.pathCache.entries()) {
|
|
1842
|
-
const currentValue = getValueAtPath(state, info$1.segments);
|
|
1843
|
-
tracker.lastCheckedValues.set(path, currentValue);
|
|
1844
|
-
if (!Object.is(currentValue, info$1.value)) {
|
|
1845
|
-
tracker.lastCheckedState = state;
|
|
1846
|
-
return true;
|
|
1847
|
-
}
|
|
1848
|
-
}
|
|
1849
|
-
tracker.lastCheckedState = state;
|
|
1850
|
-
return false;
|
|
1851
|
-
}
|
|
1852
|
-
/**
|
|
1853
|
-
* @internal
|
|
1854
|
-
*/
|
|
1855
|
-
function hasTrackedData(tracker) {
|
|
1856
|
-
return tracker.proxyState.trackedPaths.size > 0 || tracker.pathCache.size > 0 || tracker.previousRenderPaths.size > 0;
|
|
1857
|
-
}
|
|
1858
|
-
|
|
1859
|
-
//#endregion
|
|
1860
|
-
//#region src/tracking/tracking-proxy.ts
|
|
1861
|
-
/**
|
|
1862
|
-
* Create a new tracking proxy state.
|
|
1863
|
-
*/
|
|
1864
|
-
function createState() {
|
|
1865
|
-
return {
|
|
1866
|
-
dependencyState: createDependencyState(),
|
|
1867
|
-
getterState: createGetterState(),
|
|
1868
|
-
dependencies: /* @__PURE__ */ new Set(),
|
|
1869
|
-
isTracking: false
|
|
1870
|
-
};
|
|
1871
|
-
}
|
|
1872
|
-
/**
|
|
1873
|
-
* Start tracking on a tracking proxy.
|
|
1874
|
-
*/
|
|
1875
|
-
function startTracking(tracker) {
|
|
1876
|
-
tracker.isTracking = true;
|
|
1877
|
-
tracker.dependencies.clear();
|
|
1878
|
-
tracker.getterState.isTracking = true;
|
|
1879
|
-
tracker.getterState.externalDependencies.clear();
|
|
1880
|
-
startDependency(tracker.dependencyState);
|
|
1881
|
-
}
|
|
1882
|
-
/**
|
|
1883
|
-
* Stop tracking and collect all dependencies.
|
|
1884
|
-
*/
|
|
1885
|
-
function stopTracking(tracker, bloc) {
|
|
1886
|
-
tracker.isTracking = false;
|
|
1887
|
-
tracker.getterState.isTracking = false;
|
|
1888
|
-
capturePaths(tracker.dependencyState, bloc.state);
|
|
1889
|
-
commitTrackedGetters(tracker.getterState);
|
|
1890
|
-
const allDeps = new Set(tracker.dependencies);
|
|
1891
|
-
for (const dep of tracker.getterState.externalDependencies) allDeps.add(dep);
|
|
1892
|
-
tracker.getterState.externalDependencies.clear();
|
|
1893
|
-
return allDeps;
|
|
1894
|
-
}
|
|
1895
|
-
/**
|
|
1896
|
-
* Check if tracked state or getters have changed.
|
|
1897
|
-
*/
|
|
1898
|
-
function hasChanges(tracker, bloc) {
|
|
1899
|
-
invalidateRenderCache(tracker.getterState);
|
|
1900
|
-
const stateChanged = hasDependencyChanges(tracker.dependencyState, bloc.state);
|
|
1901
|
-
const getterChanged = hasGetterChanges(bloc, tracker.getterState);
|
|
1902
|
-
return stateChanged || getterChanged;
|
|
1903
|
-
}
|
|
1904
|
-
const MAX_GETTER_DEPTH = BLAC_DEFAULTS.MAX_GETTER_DEPTH;
|
|
1905
|
-
/**
|
|
1906
|
-
* Create a tracking proxy for a bloc instance.
|
|
1907
|
-
* Tracks both state property access and getter access.
|
|
1908
|
-
*/
|
|
1909
|
-
function createTrackingProxy(bloc, tracker) {
|
|
1910
|
-
tracker.dependencies.add(bloc);
|
|
1911
|
-
const stateProxyCache = /* @__PURE__ */ new WeakMap();
|
|
1912
|
-
return new Proxy(bloc, { get(target, prop, receiver) {
|
|
1913
|
-
if (prop === "state") {
|
|
1914
|
-
if (!tracker.isTracking) return target.state;
|
|
1915
|
-
const rawState = target.state;
|
|
1916
|
-
if (rawState === null || typeof rawState !== "object") return rawState;
|
|
1917
|
-
if (stateProxyCache.has(rawState)) return stateProxyCache.get(rawState);
|
|
1918
|
-
const stateProxy = createDependencyProxy(tracker.dependencyState, rawState);
|
|
1919
|
-
stateProxyCache.set(rawState, stateProxy);
|
|
1920
|
-
return stateProxy;
|
|
1921
|
-
}
|
|
1922
|
-
if (typeof prop === "symbol") return Reflect.get(target, prop, receiver);
|
|
1923
|
-
const value = Reflect.get(target, prop, receiver);
|
|
1924
|
-
if (typeof value === "function") return value.bind(target);
|
|
1925
|
-
if (tracker.isTracking && isGetter(target, prop)) {
|
|
1926
|
-
tracker.getterState.currentlyAccessing.add(prop);
|
|
1927
|
-
if (tracker.getterState.cacheValid && tracker.getterState.renderCache.has(prop)) {
|
|
1928
|
-
const cachedValue = tracker.getterState.renderCache.get(prop);
|
|
1929
|
-
tracker.getterState.trackedValues.set(prop, cachedValue);
|
|
1930
|
-
return cachedValue;
|
|
1931
|
-
}
|
|
1932
|
-
const context = getGetterExecutionContext();
|
|
1933
|
-
if (context.depth >= MAX_GETTER_DEPTH) {
|
|
1934
|
-
console.warn(`${BLAC_ERROR_PREFIX} Maximum getter depth (${MAX_GETTER_DEPTH}) exceeded. Possible circular dependency in getter "${String(prop)}" on ${target.constructor.name}.`);
|
|
1935
|
-
return;
|
|
1936
|
-
}
|
|
1937
|
-
if (context.visitedBlocs.has(target)) {
|
|
1938
|
-
console.warn(`${BLAC_ERROR_PREFIX} Circular dependency detected: getter "${String(prop)}" on ${target.constructor.name}.`);
|
|
1939
|
-
return;
|
|
1940
|
-
}
|
|
1941
|
-
const prevContext = {
|
|
1942
|
-
tracker: context.tracker,
|
|
1943
|
-
currentBloc: context.currentBloc,
|
|
1944
|
-
depth: context.depth,
|
|
1945
|
-
visitedBlocs: new Set(context.visitedBlocs)
|
|
1946
|
-
};
|
|
1947
|
-
context.tracker = tracker.getterState;
|
|
1948
|
-
context.currentBloc = target;
|
|
1949
|
-
context.depth++;
|
|
1950
|
-
context.visitedBlocs.add(target);
|
|
1951
|
-
try {
|
|
1952
|
-
const getterValue = getDescriptor(target, prop).get.call(target);
|
|
1953
|
-
tracker.getterState.trackedValues.set(prop, getterValue);
|
|
1954
|
-
return getterValue;
|
|
1955
|
-
} catch (error$1) {
|
|
1956
|
-
tracker.getterState.currentlyAccessing.delete(prop);
|
|
1957
|
-
throw error$1;
|
|
1958
|
-
} finally {
|
|
1959
|
-
context.tracker = prevContext.tracker;
|
|
1960
|
-
context.currentBloc = prevContext.currentBloc;
|
|
1961
|
-
context.depth = prevContext.depth;
|
|
1962
|
-
context.visitedBlocs = prevContext.visitedBlocs;
|
|
1963
|
-
}
|
|
1964
|
-
}
|
|
1965
|
-
return value;
|
|
1966
|
-
} });
|
|
1967
|
-
}
|
|
1968
|
-
|
|
1969
|
-
//#endregion
|
|
1970
|
-
//#region src/tracking/dependency-manager.ts
|
|
1971
|
-
/**
|
|
1972
|
-
* Manages subscriptions to state container dependencies.
|
|
1973
|
-
* Provides efficient sync mechanism to add/remove subscriptions
|
|
1974
|
-
* as dependencies change between callback invocations.
|
|
1975
|
-
*/
|
|
1976
|
-
var DependencyManager = class {
|
|
1977
|
-
constructor() {
|
|
1978
|
-
this.subscriptions = /* @__PURE__ */ new Map();
|
|
1979
|
-
this.currentDeps = /* @__PURE__ */ new Set();
|
|
1980
|
-
}
|
|
1981
|
-
/**
|
|
1982
|
-
* Sync subscriptions with a new set of dependencies.
|
|
1983
|
-
* Adds subscriptions for new deps, removes subscriptions for stale deps.
|
|
1984
|
-
*
|
|
1985
|
-
* @param newDeps - The new set of dependencies to subscribe to
|
|
1986
|
-
* @param onChange - Callback to invoke when any dependency changes
|
|
1987
|
-
* @param exclude - Optional instance to exclude from subscriptions (e.g., primary bloc)
|
|
1988
|
-
* @returns true if the dependency set changed, false if unchanged
|
|
1989
|
-
*/
|
|
1990
|
-
sync(newDeps, onChange, exclude) {
|
|
1991
|
-
const filteredNewDeps = /* @__PURE__ */ new Set();
|
|
1992
|
-
for (const dep of newDeps) if (dep !== exclude && !dep.isDisposed) filteredNewDeps.add(dep);
|
|
1993
|
-
if (this.areSetsEqual(this.currentDeps, filteredNewDeps)) return false;
|
|
1994
|
-
for (const dep of this.currentDeps) if (!filteredNewDeps.has(dep)) {
|
|
1995
|
-
const unsub = this.subscriptions.get(dep);
|
|
1996
|
-
if (unsub) {
|
|
1997
|
-
unsub();
|
|
1998
|
-
this.subscriptions.delete(dep);
|
|
1999
|
-
}
|
|
2000
|
-
}
|
|
2001
|
-
for (const dep of filteredNewDeps) if (!this.currentDeps.has(dep) && !this.subscriptions.has(dep)) {
|
|
2002
|
-
const unsub = dep.subscribe(onChange);
|
|
2003
|
-
this.subscriptions.set(dep, unsub);
|
|
2004
|
-
}
|
|
2005
|
-
this.currentDeps = filteredNewDeps;
|
|
2006
|
-
return true;
|
|
2007
|
-
}
|
|
2008
|
-
/**
|
|
2009
|
-
* Add a single dependency subscription.
|
|
2010
|
-
*/
|
|
2011
|
-
add(dep, onChange) {
|
|
2012
|
-
if (this.subscriptions.has(dep) || dep.isDisposed) return;
|
|
2013
|
-
const unsub = dep.subscribe(onChange);
|
|
2014
|
-
this.subscriptions.set(dep, unsub);
|
|
2015
|
-
this.currentDeps.add(dep);
|
|
2016
|
-
}
|
|
2017
|
-
/**
|
|
2018
|
-
* Check if a dependency is currently subscribed.
|
|
2019
|
-
*/
|
|
2020
|
-
has(dep) {
|
|
2021
|
-
return this.currentDeps.has(dep);
|
|
2022
|
-
}
|
|
2023
|
-
/**
|
|
2024
|
-
* Get the current set of dependencies.
|
|
2025
|
-
*/
|
|
2026
|
-
getDependencies() {
|
|
2027
|
-
return new Set(this.currentDeps);
|
|
2028
|
-
}
|
|
2029
|
-
/**
|
|
2030
|
-
* Clean up all active subscriptions.
|
|
2031
|
-
*/
|
|
2032
|
-
cleanup() {
|
|
2033
|
-
for (const unsub of this.subscriptions.values()) unsub();
|
|
2034
|
-
this.subscriptions.clear();
|
|
2035
|
-
this.currentDeps.clear();
|
|
2036
|
-
}
|
|
2037
|
-
areSetsEqual(a, b) {
|
|
2038
|
-
if (a.size !== b.size) return false;
|
|
2039
|
-
for (const item of a) if (!b.has(item)) return false;
|
|
2040
|
-
return true;
|
|
2041
|
-
}
|
|
2042
|
-
};
|
|
2043
|
-
|
|
2044
|
-
//#endregion
|
|
2045
|
-
//#region src/tracking/tracked.ts
|
|
2046
|
-
/**
|
|
2047
|
-
* Run a callback while tracking all bloc dependencies accessed.
|
|
2048
|
-
* Returns both the result and the set of discovered dependencies.
|
|
2049
|
-
*
|
|
2050
|
-
* @example
|
|
2051
|
-
* ```ts
|
|
2052
|
-
* const { result, dependencies } = tracked(() => {
|
|
2053
|
-
* const user = ensure(UserBloc);
|
|
2054
|
-
* return user.fullName; // getter that may access other blocs
|
|
2055
|
-
* });
|
|
2056
|
-
* // dependencies contains UserBloc + any blocs accessed in fullName getter
|
|
2057
|
-
* ```
|
|
2058
|
-
*/
|
|
2059
|
-
function tracked(callback, options) {
|
|
2060
|
-
const tracker = createState();
|
|
2061
|
-
startTracking(tracker);
|
|
2062
|
-
let result;
|
|
2063
|
-
try {
|
|
2064
|
-
result = callback();
|
|
2065
|
-
} finally {
|
|
2066
|
-
stopTracking(tracker, { state: null });
|
|
2067
|
-
}
|
|
2068
|
-
const dependencies = new Set(tracker.dependencies);
|
|
2069
|
-
for (const dep of tracker.getterState.externalDependencies) dependencies.add(dep);
|
|
2070
|
-
if (options?.exclude) dependencies.delete(options.exclude);
|
|
2071
|
-
return {
|
|
2072
|
-
result,
|
|
2073
|
-
dependencies
|
|
2074
|
-
};
|
|
2075
|
-
}
|
|
2076
|
-
/**
|
|
2077
|
-
* Context for running tracked callbacks with bloc proxies.
|
|
2078
|
-
* Provides methods to create proxies and check for changes.
|
|
2079
|
-
*/
|
|
2080
|
-
var TrackedContext = class {
|
|
2081
|
-
constructor() {
|
|
2082
|
-
this.proxiedBlocs = /* @__PURE__ */ new WeakMap();
|
|
2083
|
-
this.primaryBlocs = /* @__PURE__ */ new Set();
|
|
2084
|
-
this.tracker = createState();
|
|
2085
|
-
}
|
|
2086
|
-
/**
|
|
2087
|
-
* Get a tracking proxy for a bloc instance.
|
|
2088
|
-
* The proxy will track state and getter accesses.
|
|
2089
|
-
*/
|
|
2090
|
-
proxy(bloc) {
|
|
2091
|
-
const cached = this.proxiedBlocs.get(bloc);
|
|
2092
|
-
if (cached) return cached;
|
|
2093
|
-
const proxied = createTrackingProxy(bloc, this.tracker);
|
|
2094
|
-
this.proxiedBlocs.set(bloc, proxied);
|
|
2095
|
-
this.primaryBlocs.add(bloc);
|
|
2096
|
-
return proxied;
|
|
2097
|
-
}
|
|
2098
|
-
/**
|
|
2099
|
-
* Start tracking for a new callback execution.
|
|
2100
|
-
*/
|
|
2101
|
-
start() {
|
|
2102
|
-
startTracking(this.tracker);
|
|
2103
|
-
}
|
|
2104
|
-
/**
|
|
2105
|
-
* Stop tracking and get discovered dependencies.
|
|
2106
|
-
* Excludes primary blocs (those explicitly proxied via proxy()).
|
|
2107
|
-
*/
|
|
2108
|
-
stop() {
|
|
2109
|
-
const allDeps = /* @__PURE__ */ new Set();
|
|
2110
|
-
for (const bloc of this.primaryBlocs) {
|
|
2111
|
-
const deps = stopTracking(this.tracker, bloc);
|
|
2112
|
-
for (const dep of deps) if (!this.primaryBlocs.has(dep)) allDeps.add(dep);
|
|
2113
|
-
}
|
|
2114
|
-
for (const dep of this.tracker.getterState.externalDependencies) if (!this.primaryBlocs.has(dep)) allDeps.add(dep);
|
|
2115
|
-
return allDeps;
|
|
2116
|
-
}
|
|
2117
|
-
/**
|
|
2118
|
-
* Check if any tracked state or getters have changed.
|
|
2119
|
-
*/
|
|
2120
|
-
changed() {
|
|
2121
|
-
for (const bloc of this.primaryBlocs) if (hasChanges(this.tracker, bloc)) return true;
|
|
2122
|
-
return false;
|
|
2123
|
-
}
|
|
2124
|
-
/**
|
|
2125
|
-
* Get all primary blocs (those explicitly proxied).
|
|
2126
|
-
*/
|
|
2127
|
-
getPrimaryBlocs() {
|
|
2128
|
-
return new Set(this.primaryBlocs);
|
|
2129
|
-
}
|
|
2130
|
-
/**
|
|
2131
|
-
* Reset the context for reuse.
|
|
2132
|
-
*/
|
|
2133
|
-
reset() {
|
|
2134
|
-
this.tracker = createState();
|
|
2135
|
-
this.proxiedBlocs = /* @__PURE__ */ new WeakMap();
|
|
2136
|
-
this.primaryBlocs.clear();
|
|
2137
|
-
}
|
|
2138
|
-
};
|
|
2139
|
-
/**
|
|
2140
|
-
* Create a new tracked context for manual control over tracking.
|
|
2141
|
-
*/
|
|
2142
|
-
function createTrackedContext() {
|
|
2143
|
-
return new TrackedContext();
|
|
2144
|
-
}
|
|
2145
|
-
|
|
2146
|
-
//#endregion
|
|
2147
|
-
//#region src/adapter/framework-adapter.ts
|
|
2148
|
-
/**
|
|
2149
|
-
* Manages subscriptions to external bloc dependencies for getter tracking.
|
|
2150
|
-
* When a getter accesses another bloc's state, this manager ensures
|
|
2151
|
-
* re-renders occur when those external dependencies change.
|
|
2152
|
-
*
|
|
2153
|
-
* @deprecated Use DependencyManager from tracking module instead.
|
|
2154
|
-
* This class is kept for backward compatibility.
|
|
2155
|
-
*/
|
|
2156
|
-
var ExternalDepsManager = class {
|
|
2157
|
-
constructor() {
|
|
2158
|
-
this.manager = new DependencyManager();
|
|
2159
|
-
}
|
|
2160
|
-
/**
|
|
2161
|
-
* Update subscriptions to external bloc dependencies.
|
|
2162
|
-
* Creates subscriptions to blocs accessed via getters.
|
|
2163
|
-
* @param getterTracker - The getter tracker state with external dependencies
|
|
2164
|
-
* @param rawInstance - The primary bloc instance (excluded from subscriptions)
|
|
2165
|
-
* @param onGetterChange - Callback to invoke when external dependency changes
|
|
2166
|
-
* @returns true if subscriptions were updated, false if unchanged
|
|
2167
|
-
*/
|
|
2168
|
-
updateSubscriptions(getterState, rawInstance, onGetterChange) {
|
|
2169
|
-
if (!getterState?.externalDependencies) return false;
|
|
2170
|
-
const currentDeps = getterState.externalDependencies;
|
|
2171
|
-
const onExternalChange = () => {
|
|
2172
|
-
invalidateRenderCache(getterState);
|
|
2173
|
-
if (hasGetterChanges(rawInstance, getterState)) onGetterChange();
|
|
2174
|
-
};
|
|
2175
|
-
const changed = this.manager.sync(currentDeps, onExternalChange, rawInstance);
|
|
2176
|
-
clearExternalDependencies(getterState);
|
|
2177
|
-
return changed;
|
|
2178
|
-
}
|
|
2179
|
-
/**
|
|
2180
|
-
* Clean up all active subscriptions
|
|
2181
|
-
*/
|
|
2182
|
-
cleanup() {
|
|
2183
|
-
this.manager.cleanup();
|
|
2184
|
-
}
|
|
2185
|
-
};
|
|
2186
|
-
/**
|
|
2187
|
-
* Create a subscribe function for auto-tracking mode.
|
|
2188
|
-
* Only triggers callback when tracked properties change.
|
|
2189
|
-
* @param instance - The state container instance
|
|
2190
|
-
* @param adapterState - The adapter state for tracking
|
|
2191
|
-
* @returns Subscribe function for use with useSyncExternalStore
|
|
2192
|
-
*/
|
|
2193
|
-
function autoTrackSubscribe(instance$1, adapterState) {
|
|
2194
|
-
return (callback) => {
|
|
2195
|
-
return instance$1.subscribe(() => {
|
|
2196
|
-
const depState = adapterState.dependencyState || (adapterState.dependencyState = createDependencyState());
|
|
2197
|
-
const hasStateDeps = depState.pathCache && depState.pathCache.size > 0;
|
|
2198
|
-
const hasGetterDeps = adapterState.getterState && adapterState.getterState.trackedGetters.size > 0;
|
|
2199
|
-
const isPrimitiveState = instance$1.state !== null && typeof instance$1.state !== "object" && typeof instance$1.state !== "function";
|
|
2200
|
-
if (!hasStateDeps && !hasGetterDeps && !isPrimitiveState) return;
|
|
2201
|
-
let stateChanged = hasDependencyChanges(depState, instance$1.state);
|
|
2202
|
-
if (!hasStateDeps && hasGetterDeps) stateChanged = false;
|
|
2203
|
-
if (stateChanged) {
|
|
2204
|
-
callback();
|
|
2205
|
-
return;
|
|
2206
|
-
}
|
|
2207
|
-
if (hasGetterChanges(instance$1, adapterState.getterState)) callback();
|
|
2208
|
-
});
|
|
2209
|
-
};
|
|
2210
|
-
}
|
|
2211
|
-
/**
|
|
2212
|
-
* Create a subscribe function for manual dependency tracking mode.
|
|
2213
|
-
* Only triggers callback when dependencies array changes.
|
|
2214
|
-
* @param instance - The state container instance
|
|
2215
|
-
* @param adapterState - The adapter state for caching
|
|
2216
|
-
* @param config - Configuration with dependencies function
|
|
2217
|
-
* @returns Subscribe function for use with useSyncExternalStore
|
|
2218
|
-
*/
|
|
2219
|
-
function manualDepsSubscribe(instance$1, adapterState, config) {
|
|
2220
|
-
return (callback) => {
|
|
2221
|
-
return instance$1.subscribe(() => {
|
|
2222
|
-
const newDeps = config.dependencies(instance$1.state, instance$1);
|
|
2223
|
-
if (!adapterState.manualDepsCache || !shallowEqual(adapterState.manualDepsCache, newDeps)) {
|
|
2224
|
-
adapterState.manualDepsCache = newDeps;
|
|
2225
|
-
callback();
|
|
2226
|
-
}
|
|
2227
|
-
});
|
|
2228
|
-
};
|
|
2229
|
-
}
|
|
2230
|
-
/**
|
|
2231
|
-
* Create a subscribe function for no-tracking mode.
|
|
2232
|
-
* Triggers callback on every state change.
|
|
2233
|
-
* @param instance - The state container instance
|
|
2234
|
-
* @returns Subscribe function for use with useSyncExternalStore
|
|
2235
|
-
*/
|
|
2236
|
-
function noTrackSubscribe(instance$1) {
|
|
2237
|
-
return (callback) => instance$1.subscribe(callback);
|
|
2238
|
-
}
|
|
2239
|
-
/**
|
|
2240
|
-
* Create a snapshot function for auto-tracking mode.
|
|
2241
|
-
* Returns a proxied state that tracks property access.
|
|
2242
|
-
* @param instance - The state container instance
|
|
2243
|
-
* @param adapterState - The adapter state for tracking
|
|
2244
|
-
* @returns Snapshot function for use with useSyncExternalStore
|
|
2245
|
-
*/
|
|
2246
|
-
function autoTrackSnapshot(instance$1, adapterState) {
|
|
2247
|
-
return () => {
|
|
2248
|
-
const depState = adapterState.dependencyState || (adapterState.dependencyState = createDependencyState());
|
|
2249
|
-
if (hasTrackedData(depState)) capturePaths(depState, instance$1.state);
|
|
2250
|
-
if (adapterState.getterState) {
|
|
2251
|
-
invalidateRenderCache(adapterState.getterState);
|
|
2252
|
-
commitTrackedGetters(adapterState.getterState);
|
|
2253
|
-
adapterState.getterState.isTracking = true;
|
|
2254
|
-
setActiveTracker(instance$1, adapterState.getterState);
|
|
2255
|
-
}
|
|
2256
|
-
startDependency(depState);
|
|
2257
|
-
return createDependencyProxy(depState, instance$1.state);
|
|
2258
|
-
};
|
|
2259
|
-
}
|
|
2260
|
-
/**
|
|
2261
|
-
* Create a snapshot function for manual dependency tracking mode.
|
|
2262
|
-
* Caches dependencies for comparison on next render.
|
|
2263
|
-
* @param instance - The state container instance
|
|
2264
|
-
* @param adapterState - The adapter state for caching
|
|
2265
|
-
* @param config - Configuration with dependencies function
|
|
2266
|
-
* @returns Snapshot function for use with useSyncExternalStore
|
|
2267
|
-
*/
|
|
2268
|
-
function manualDepsSnapshot(instance$1, adapterState, config) {
|
|
2269
|
-
return () => {
|
|
2270
|
-
adapterState.manualDepsCache = config.dependencies(instance$1.state, instance$1);
|
|
2271
|
-
return instance$1.state;
|
|
2272
|
-
};
|
|
2273
|
-
}
|
|
2274
|
-
/**
|
|
2275
|
-
* Create a snapshot function for no-tracking mode.
|
|
2276
|
-
* Returns the raw state directly.
|
|
2277
|
-
* @param instance - The state container instance
|
|
2278
|
-
* @returns Snapshot function for use with useSyncExternalStore
|
|
2279
|
-
*/
|
|
2280
|
-
function noTrackSnapshot(instance$1) {
|
|
2281
|
-
return () => instance$1.state;
|
|
2282
|
-
}
|
|
2283
|
-
/**
|
|
2284
|
-
* Initialize adapter state for auto-tracking mode.
|
|
2285
|
-
* Creates getter tracker and proxied bloc instance.
|
|
2286
|
-
* @param instance - The state container instance
|
|
2287
|
-
* @returns Initialized adapter state
|
|
2288
|
-
*/
|
|
2289
|
-
function autoTrackInit(instance$1) {
|
|
2290
|
-
return {
|
|
2291
|
-
dependencyState: null,
|
|
2292
|
-
manualDepsCache: null,
|
|
2293
|
-
getterState: createGetterState(),
|
|
2294
|
-
proxiedBloc: createBlocProxy(instance$1)
|
|
2295
|
-
};
|
|
2296
|
-
}
|
|
2297
|
-
/**
|
|
2298
|
-
* Initialize adapter state for manual dependency tracking mode.
|
|
2299
|
-
* No proxy is created; bloc is used directly.
|
|
2300
|
-
* @param instance - The state container instance
|
|
2301
|
-
* @returns Initialized adapter state
|
|
2302
|
-
*/
|
|
2303
|
-
function manualDepsInit(instance$1) {
|
|
2304
|
-
return {
|
|
2305
|
-
dependencyState: null,
|
|
2306
|
-
manualDepsCache: null,
|
|
2307
|
-
getterState: null,
|
|
2308
|
-
proxiedBloc: instance$1
|
|
2309
|
-
};
|
|
2310
|
-
}
|
|
2311
|
-
/**
|
|
2312
|
-
* Initialize adapter state for no-tracking mode.
|
|
2313
|
-
* No tracking or proxy is created.
|
|
2314
|
-
* @param instance - The state container instance
|
|
2315
|
-
* @returns Initialized adapter state
|
|
2316
|
-
*/
|
|
2317
|
-
function noTrackInit(instance$1) {
|
|
2318
|
-
return {
|
|
2319
|
-
dependencyState: null,
|
|
2320
|
-
manualDepsCache: null,
|
|
2321
|
-
getterState: null,
|
|
2322
|
-
proxiedBloc: instance$1
|
|
2323
|
-
};
|
|
2324
|
-
}
|
|
2325
|
-
/**
|
|
2326
|
-
* Disable getter tracking after render phase completes.
|
|
2327
|
-
* Clears the active tracker to prevent tracking outside of render.
|
|
2328
|
-
* @param adapterState - The adapter state
|
|
2329
|
-
* @param rawInstance - The raw bloc instance
|
|
2330
|
-
*/
|
|
2331
|
-
function disableGetterTracking(adapterState, rawInstance) {
|
|
2332
|
-
if (adapterState.getterState) {
|
|
2333
|
-
adapterState.getterState.isTracking = false;
|
|
2334
|
-
clearActiveTracker(rawInstance);
|
|
2335
|
-
}
|
|
2336
|
-
}
|
|
2337
|
-
|
|
2338
|
-
//#endregion
|
|
2339
|
-
//#region src/logging/Logger.ts
|
|
2340
|
-
/**
|
|
2341
|
-
* Log severity levels (lower number = higher severity)
|
|
2342
|
-
*/
|
|
2343
|
-
let LogLevel = /* @__PURE__ */ function(LogLevel$1) {
|
|
2344
|
-
/** Critical errors that may cause application failure */
|
|
2345
|
-
LogLevel$1[LogLevel$1["ERROR"] = 0] = "ERROR";
|
|
2346
|
-
/** Warning conditions that should be addressed */
|
|
2347
|
-
LogLevel$1[LogLevel$1["WARN"] = 1] = "WARN";
|
|
2348
|
-
/** Informational messages about application state */
|
|
2349
|
-
LogLevel$1[LogLevel$1["INFO"] = 2] = "INFO";
|
|
2350
|
-
/** Detailed debugging information */
|
|
2351
|
-
LogLevel$1[LogLevel$1["DEBUG"] = 3] = "DEBUG";
|
|
2352
|
-
return LogLevel$1;
|
|
2353
|
-
}({});
|
|
2354
|
-
/**
|
|
2355
|
-
* Creates a logger instance with given configuration
|
|
2356
|
-
*
|
|
2357
|
-
* @param config - Logger configuration
|
|
2358
|
-
* @returns Logger instance with debug, info, warn, error, and configure methods
|
|
2359
|
-
*
|
|
2360
|
-
* @example
|
|
2361
|
-
* ```ts
|
|
2362
|
-
* const logger = createLogger({
|
|
2363
|
-
* enabled: true,
|
|
2364
|
-
* level: LogLevel.DEBUG,
|
|
2365
|
-
* output: (entry) => console.log(JSON.stringify(entry))
|
|
2366
|
-
* });
|
|
2367
|
-
* logger.debug('MyComponent', 'Rendering', { props: { foo: 'bar' } });
|
|
2368
|
-
* ```
|
|
2369
|
-
*/
|
|
2370
|
-
function createLogger(config) {
|
|
2371
|
-
const shouldLog = (level) => {
|
|
2372
|
-
return config.enabled && config.level >= level;
|
|
2373
|
-
};
|
|
2374
|
-
const log = (level, context, message, data) => {
|
|
2375
|
-
try {
|
|
2376
|
-
const entry = {
|
|
2377
|
-
level,
|
|
2378
|
-
context,
|
|
2379
|
-
message,
|
|
2380
|
-
timestamp: Date.now(),
|
|
2381
|
-
...data !== void 0 && { data: serialize(data) }
|
|
2382
|
-
};
|
|
2383
|
-
config.output(entry);
|
|
2384
|
-
} catch (e) {
|
|
2385
|
-
console.error("[BlacLogger] Error logging:", e);
|
|
2386
|
-
}
|
|
2387
|
-
};
|
|
2388
|
-
const serialize = (data) => {
|
|
2389
|
-
try {
|
|
2390
|
-
return JSON.parse(JSON.stringify(data));
|
|
2391
|
-
} catch {
|
|
2392
|
-
return String(data);
|
|
2393
|
-
}
|
|
2394
|
-
};
|
|
2395
|
-
return {
|
|
2396
|
-
debug: (context, message, data) => {
|
|
2397
|
-
if (shouldLog(LogLevel.DEBUG)) log("DEBUG", context, message, data);
|
|
2398
|
-
},
|
|
2399
|
-
info: (context, message, data) => {
|
|
2400
|
-
if (shouldLog(LogLevel.INFO)) log("INFO", context, message, data);
|
|
2401
|
-
},
|
|
2402
|
-
warn: (context, message, data) => {
|
|
2403
|
-
if (shouldLog(LogLevel.WARN)) log("WARN", context, message, data);
|
|
2404
|
-
},
|
|
2405
|
-
error: (context, message, data) => {
|
|
2406
|
-
if (shouldLog(LogLevel.ERROR)) log("ERROR", context, message, data);
|
|
2407
|
-
},
|
|
2408
|
-
configure: (opts) => {
|
|
2409
|
-
Object.assign(config, opts);
|
|
2410
|
-
}
|
|
2411
|
-
};
|
|
2412
|
-
}
|
|
2413
|
-
let currentConfig = {
|
|
2414
|
-
enabled: false,
|
|
2415
|
-
level: LogLevel.INFO,
|
|
2416
|
-
output: (entry) => console.log(JSON.stringify(entry))
|
|
2417
|
-
};
|
|
2418
|
-
let defaultLogger = createLogger(currentConfig);
|
|
2419
|
-
/**
|
|
2420
|
-
* Log a debug message (tree-shakeable export)
|
|
2421
|
-
* @param context - Context identifier (module or component name)
|
|
2422
|
-
* @param message - Log message
|
|
2423
|
-
* @param data - Optional structured data
|
|
2424
|
-
*/
|
|
2425
|
-
const debug = (context, message, data) => defaultLogger.debug(context, message, data);
|
|
2426
|
-
/**
|
|
2427
|
-
* Log an info message (tree-shakeable export)
|
|
2428
|
-
* @param context - Context identifier (module or component name)
|
|
2429
|
-
* @param message - Log message
|
|
2430
|
-
* @param data - Optional structured data
|
|
2431
|
-
*/
|
|
2432
|
-
const info = (context, message, data) => defaultLogger.info(context, message, data);
|
|
2433
|
-
/**
|
|
2434
|
-
* Log a warning message (tree-shakeable export)
|
|
2435
|
-
* @param context - Context identifier (module or component name)
|
|
2436
|
-
* @param message - Log message
|
|
2437
|
-
* @param data - Optional structured data
|
|
2438
|
-
*/
|
|
2439
|
-
const warn = (context, message, data) => defaultLogger.warn(context, message, data);
|
|
2440
|
-
/**
|
|
2441
|
-
* Log an error message (tree-shakeable export)
|
|
2442
|
-
* @param context - Context identifier (module or component name)
|
|
2443
|
-
* @param message - Log message
|
|
2444
|
-
* @param data - Optional structured data
|
|
2445
|
-
*/
|
|
2446
|
-
const error = (context, message, data) => defaultLogger.error(context, message, data);
|
|
2447
|
-
/**
|
|
2448
|
-
* Configuration function that recreates the default logger
|
|
2449
|
-
*
|
|
2450
|
-
* @param opts - Partial logger configuration
|
|
2451
|
-
*
|
|
2452
|
-
* @example
|
|
2453
|
-
* ```ts
|
|
2454
|
-
* configureLogger({ enabled: true, level: LogLevel.DEBUG });
|
|
2455
|
-
* ```
|
|
2456
|
-
*/
|
|
2457
|
-
function configureLogger(opts) {
|
|
2458
|
-
currentConfig = {
|
|
2459
|
-
...currentConfig,
|
|
2460
|
-
...opts
|
|
2461
|
-
};
|
|
2462
|
-
defaultLogger = createLogger(currentConfig);
|
|
2463
|
-
}
|
|
2464
|
-
|
|
2465
|
-
//#endregion
|
|
2466
|
-
//#region src/config.ts
|
|
2467
|
-
const defaultConfig = {
|
|
2468
|
-
devMode: process.env.NODE_ENV !== "production",
|
|
2469
|
-
logger: {
|
|
2470
|
-
enabled: false,
|
|
2471
|
-
level: LogLevel.INFO
|
|
2472
|
-
}
|
|
2473
|
-
};
|
|
2474
|
-
let globalConfig = { ...defaultConfig };
|
|
2475
|
-
/**
|
|
2476
|
-
* Configure global defaults for @blac/core.
|
|
2477
|
-
*
|
|
2478
|
-
* @example
|
|
2479
|
-
* ```ts
|
|
2480
|
-
* import { configureBlac, LogLevel } from '@blac/core';
|
|
2481
|
-
*
|
|
2482
|
-
* configureBlac({
|
|
2483
|
-
* devMode: true,
|
|
2484
|
-
* logger: {
|
|
2485
|
-
* enabled: true,
|
|
2486
|
-
* level: LogLevel.DEBUG
|
|
2487
|
-
* }
|
|
2488
|
-
* });
|
|
2489
|
-
* ```
|
|
2490
|
-
*
|
|
2491
|
-
* @param config - Partial configuration to merge with defaults
|
|
2492
|
-
*/
|
|
2493
|
-
function configureBlac(config) {
|
|
2494
|
-
globalConfig = {
|
|
2495
|
-
...globalConfig,
|
|
2496
|
-
...config,
|
|
2497
|
-
logger: {
|
|
2498
|
-
...globalConfig.logger,
|
|
2499
|
-
...config.logger
|
|
2500
|
-
}
|
|
2501
|
-
};
|
|
2502
|
-
if (config.logger) configureLogger(config.logger);
|
|
2503
|
-
}
|
|
2504
|
-
/**
|
|
2505
|
-
* Get the current global configuration.
|
|
2506
|
-
* @internal
|
|
2507
|
-
*/
|
|
2508
|
-
function getBlacConfig() {
|
|
2509
|
-
return globalConfig;
|
|
2510
|
-
}
|
|
2511
|
-
/**
|
|
2512
|
-
* Check if development mode is enabled.
|
|
2513
|
-
*/
|
|
2514
|
-
function isDevMode() {
|
|
2515
|
-
return globalConfig.devMode;
|
|
2516
|
-
}
|
|
2517
|
-
/**
|
|
2518
|
-
* Reset configuration to defaults (useful for testing).
|
|
2519
|
-
* @internal
|
|
2520
|
-
*/
|
|
2521
|
-
function resetBlacConfig() {
|
|
2522
|
-
globalConfig = { ...defaultConfig };
|
|
2523
|
-
configureLogger(defaultConfig.logger);
|
|
2524
|
-
}
|
|
2525
|
-
|
|
2526
|
-
//#endregion
|
|
2527
|
-
//#region src/decorators/blac.ts
|
|
274
|
+
|
|
275
|
+
//#endregion
|
|
276
|
+
//#region src/decorators/blac.ts
|
|
2528
277
|
/**
|
|
2529
278
|
* Decorator to configure StateContainer classes.
|
|
2530
279
|
*
|
|
@@ -2557,328 +306,5 @@ function blac(options) {
|
|
|
2557
306
|
}
|
|
2558
307
|
|
|
2559
308
|
//#endregion
|
|
2560
|
-
|
|
2561
|
-
function acquire(BlocClass, instanceKey, options) {
|
|
2562
|
-
return globalRegistry.acquire(BlocClass, instanceKey, {
|
|
2563
|
-
canCreate: true,
|
|
2564
|
-
countRef: true,
|
|
2565
|
-
props: options?.props
|
|
2566
|
-
});
|
|
2567
|
-
}
|
|
2568
|
-
|
|
2569
|
-
//#endregion
|
|
2570
|
-
//#region src/registry/borrow.ts
|
|
2571
|
-
function borrow(BlocClass, instanceKey) {
|
|
2572
|
-
return globalRegistry.borrow(BlocClass, instanceKey);
|
|
2573
|
-
}
|
|
2574
|
-
function borrowSafe(BlocClass, instanceKey) {
|
|
2575
|
-
return globalRegistry.borrowSafe(BlocClass, instanceKey);
|
|
2576
|
-
}
|
|
2577
|
-
|
|
2578
|
-
//#endregion
|
|
2579
|
-
//#region src/registry/ensure.ts
|
|
2580
|
-
function ensure(BlocClass, instanceKey) {
|
|
2581
|
-
return globalRegistry.ensure(BlocClass, instanceKey);
|
|
2582
|
-
}
|
|
2583
|
-
|
|
2584
|
-
//#endregion
|
|
2585
|
-
//#region src/registry/release.ts
|
|
2586
|
-
function release(BlocClass, instanceKey, forceDispose = false) {
|
|
2587
|
-
globalRegistry.release(BlocClass, instanceKey, forceDispose);
|
|
2588
|
-
}
|
|
2589
|
-
|
|
2590
|
-
//#endregion
|
|
2591
|
-
//#region src/registry/queries.ts
|
|
2592
|
-
function hasInstance(BlocClass, instanceKey) {
|
|
2593
|
-
return globalRegistry.hasInstance(BlocClass, instanceKey);
|
|
2594
|
-
}
|
|
2595
|
-
function getRefCount(BlocClass, instanceKey) {
|
|
2596
|
-
return globalRegistry.getRefCount(BlocClass, instanceKey);
|
|
2597
|
-
}
|
|
2598
|
-
function getAll(BlocClass) {
|
|
2599
|
-
return globalRegistry.getAll(BlocClass);
|
|
2600
|
-
}
|
|
2601
|
-
function forEach(BlocClass, callback) {
|
|
2602
|
-
globalRegistry.forEach(BlocClass, callback);
|
|
2603
|
-
}
|
|
2604
|
-
|
|
2605
|
-
//#endregion
|
|
2606
|
-
//#region src/registry/management.ts
|
|
2607
|
-
function clear(BlocClass) {
|
|
2608
|
-
globalRegistry.clear(BlocClass);
|
|
2609
|
-
}
|
|
2610
|
-
function clearAll() {
|
|
2611
|
-
globalRegistry.clearAll();
|
|
2612
|
-
}
|
|
2613
|
-
function register(BlocClass, isolated = false) {
|
|
2614
|
-
globalRegistry.register(BlocClass, isolated);
|
|
2615
|
-
}
|
|
2616
|
-
|
|
2617
|
-
//#endregion
|
|
2618
|
-
//#region src/registry/config.ts
|
|
2619
|
-
let _registry = globalRegistry;
|
|
2620
|
-
function getRegistry() {
|
|
2621
|
-
return _registry;
|
|
2622
|
-
}
|
|
2623
|
-
function setRegistry(registry) {
|
|
2624
|
-
_registry.clearAll();
|
|
2625
|
-
_registry = registry;
|
|
2626
|
-
}
|
|
2627
|
-
function getStats() {
|
|
2628
|
-
return _registry.getStats();
|
|
2629
|
-
}
|
|
2630
|
-
|
|
2631
|
-
//#endregion
|
|
2632
|
-
//#region src/waitUntil/errors.ts
|
|
2633
|
-
var WaitUntilTimeoutError = class extends Error {
|
|
2634
|
-
constructor(timeout) {
|
|
2635
|
-
super(`waitUntil timed out after ${timeout}ms`);
|
|
2636
|
-
this.name = "WaitUntilTimeoutError";
|
|
2637
|
-
}
|
|
2638
|
-
};
|
|
2639
|
-
var WaitUntilAbortedError = class extends Error {
|
|
2640
|
-
constructor() {
|
|
2641
|
-
super("waitUntil was aborted");
|
|
2642
|
-
this.name = "WaitUntilAbortedError";
|
|
2643
|
-
}
|
|
2644
|
-
};
|
|
2645
|
-
var WaitUntilDisposedError = class extends Error {
|
|
2646
|
-
constructor(blocName) {
|
|
2647
|
-
super(`waitUntil failed: ${blocName} was disposed while waiting`);
|
|
2648
|
-
this.name = "WaitUntilDisposedError";
|
|
2649
|
-
}
|
|
2650
|
-
};
|
|
2651
|
-
|
|
2652
|
-
//#endregion
|
|
2653
|
-
//#region src/waitUntil/waitUntil.ts
|
|
2654
|
-
function waitUntil(BlocClass, selectorOrPredicate, predicateOrOptions, maybeOptions) {
|
|
2655
|
-
if (typeof predicateOrOptions !== "function") return waitUntilSimple(BlocClass, selectorOrPredicate, predicateOrOptions);
|
|
2656
|
-
else return waitUntilSelector(BlocClass, selectorOrPredicate, predicateOrOptions, maybeOptions);
|
|
2657
|
-
}
|
|
2658
|
-
function waitUntilSimple(BlocClass, predicate, options) {
|
|
2659
|
-
const { instanceId = BLAC_DEFAULTS.DEFAULT_INSTANCE_KEY, timeout, signal } = options ?? {};
|
|
2660
|
-
return new Promise((resolve, reject) => {
|
|
2661
|
-
if (signal?.aborted) {
|
|
2662
|
-
reject(new WaitUntilAbortedError());
|
|
2663
|
-
return;
|
|
2664
|
-
}
|
|
2665
|
-
const bloc = ensure(BlocClass, instanceId);
|
|
2666
|
-
if (bloc.isDisposed) {
|
|
2667
|
-
reject(new WaitUntilDisposedError(BlocClass.name));
|
|
2668
|
-
return;
|
|
2669
|
-
}
|
|
2670
|
-
const tracker = createState();
|
|
2671
|
-
const proxiedBloc = createTrackingProxy(bloc, tracker);
|
|
2672
|
-
const externalDepsManager = new DependencyManager();
|
|
2673
|
-
const runPredicate = () => {
|
|
2674
|
-
startTracking(tracker);
|
|
2675
|
-
try {
|
|
2676
|
-
return predicate(proxiedBloc);
|
|
2677
|
-
} finally {
|
|
2678
|
-
const deps = stopTracking(tracker, bloc);
|
|
2679
|
-
for (const dep of tracker.getterState.externalDependencies) deps.add(dep);
|
|
2680
|
-
deps.delete(bloc);
|
|
2681
|
-
externalDepsManager.sync(deps, checkCondition, bloc);
|
|
2682
|
-
}
|
|
2683
|
-
};
|
|
2684
|
-
if (runPredicate()) {
|
|
2685
|
-
resolve(bloc);
|
|
2686
|
-
return;
|
|
2687
|
-
}
|
|
2688
|
-
let unsubscribe = null;
|
|
2689
|
-
let unsubscribeDispose = null;
|
|
2690
|
-
let timeoutId = null;
|
|
2691
|
-
let abortHandler = null;
|
|
2692
|
-
const cleanup = () => {
|
|
2693
|
-
unsubscribe?.();
|
|
2694
|
-
unsubscribeDispose?.();
|
|
2695
|
-
externalDepsManager.cleanup();
|
|
2696
|
-
if (timeoutId) clearTimeout(timeoutId);
|
|
2697
|
-
if (abortHandler && signal) signal.removeEventListener("abort", abortHandler);
|
|
2698
|
-
};
|
|
2699
|
-
function checkCondition() {
|
|
2700
|
-
try {
|
|
2701
|
-
if (runPredicate()) {
|
|
2702
|
-
cleanup();
|
|
2703
|
-
resolve(bloc);
|
|
2704
|
-
}
|
|
2705
|
-
} catch (error$1) {
|
|
2706
|
-
cleanup();
|
|
2707
|
-
reject(error$1);
|
|
2708
|
-
}
|
|
2709
|
-
}
|
|
2710
|
-
unsubscribe = bloc.subscribe(checkCondition);
|
|
2711
|
-
unsubscribeDispose = getRegistry().on("disposed", (instance$1) => {
|
|
2712
|
-
if (instance$1 === bloc) {
|
|
2713
|
-
cleanup();
|
|
2714
|
-
reject(new WaitUntilDisposedError(BlocClass.name));
|
|
2715
|
-
}
|
|
2716
|
-
});
|
|
2717
|
-
if (timeout !== void 0) timeoutId = setTimeout(() => {
|
|
2718
|
-
cleanup();
|
|
2719
|
-
reject(new WaitUntilTimeoutError(timeout));
|
|
2720
|
-
}, timeout);
|
|
2721
|
-
if (signal) {
|
|
2722
|
-
abortHandler = () => {
|
|
2723
|
-
cleanup();
|
|
2724
|
-
reject(new WaitUntilAbortedError());
|
|
2725
|
-
};
|
|
2726
|
-
signal.addEventListener("abort", abortHandler);
|
|
2727
|
-
}
|
|
2728
|
-
});
|
|
2729
|
-
}
|
|
2730
|
-
function waitUntilSelector(BlocClass, selector, predicate, options) {
|
|
2731
|
-
const { instanceId = BLAC_DEFAULTS.DEFAULT_INSTANCE_KEY, timeout, signal } = options ?? {};
|
|
2732
|
-
return new Promise((resolve, reject) => {
|
|
2733
|
-
if (signal?.aborted) {
|
|
2734
|
-
reject(new WaitUntilAbortedError());
|
|
2735
|
-
return;
|
|
2736
|
-
}
|
|
2737
|
-
const bloc = ensure(BlocClass, instanceId);
|
|
2738
|
-
if (bloc.isDisposed) {
|
|
2739
|
-
reject(new WaitUntilDisposedError(BlocClass.name));
|
|
2740
|
-
return;
|
|
2741
|
-
}
|
|
2742
|
-
const tracker = createState();
|
|
2743
|
-
const proxiedBloc = createTrackingProxy(bloc, tracker);
|
|
2744
|
-
const externalDepsManager = new DependencyManager();
|
|
2745
|
-
const runSelector = () => {
|
|
2746
|
-
startTracking(tracker);
|
|
2747
|
-
try {
|
|
2748
|
-
const value = selector(proxiedBloc);
|
|
2749
|
-
return {
|
|
2750
|
-
value,
|
|
2751
|
-
matches: predicate(value)
|
|
2752
|
-
};
|
|
2753
|
-
} finally {
|
|
2754
|
-
const deps = stopTracking(tracker, bloc);
|
|
2755
|
-
for (const dep of tracker.getterState.externalDependencies) deps.add(dep);
|
|
2756
|
-
deps.delete(bloc);
|
|
2757
|
-
externalDepsManager.sync(deps, checkCondition, bloc);
|
|
2758
|
-
}
|
|
2759
|
-
};
|
|
2760
|
-
const initial = runSelector();
|
|
2761
|
-
if (initial.matches) {
|
|
2762
|
-
resolve(initial.value);
|
|
2763
|
-
return;
|
|
2764
|
-
}
|
|
2765
|
-
let unsubscribe = null;
|
|
2766
|
-
let unsubscribeDispose = null;
|
|
2767
|
-
let timeoutId = null;
|
|
2768
|
-
let abortHandler = null;
|
|
2769
|
-
const cleanup = () => {
|
|
2770
|
-
unsubscribe?.();
|
|
2771
|
-
unsubscribeDispose?.();
|
|
2772
|
-
externalDepsManager.cleanup();
|
|
2773
|
-
if (timeoutId) clearTimeout(timeoutId);
|
|
2774
|
-
if (abortHandler && signal) signal.removeEventListener("abort", abortHandler);
|
|
2775
|
-
};
|
|
2776
|
-
function checkCondition() {
|
|
2777
|
-
try {
|
|
2778
|
-
const result = runSelector();
|
|
2779
|
-
if (result.matches) {
|
|
2780
|
-
cleanup();
|
|
2781
|
-
resolve(result.value);
|
|
2782
|
-
}
|
|
2783
|
-
} catch (error$1) {
|
|
2784
|
-
cleanup();
|
|
2785
|
-
reject(error$1);
|
|
2786
|
-
}
|
|
2787
|
-
}
|
|
2788
|
-
unsubscribe = bloc.subscribe(checkCondition);
|
|
2789
|
-
unsubscribeDispose = getRegistry().on("disposed", (instance$1) => {
|
|
2790
|
-
if (instance$1 === bloc) {
|
|
2791
|
-
cleanup();
|
|
2792
|
-
reject(new WaitUntilDisposedError(BlocClass.name));
|
|
2793
|
-
}
|
|
2794
|
-
});
|
|
2795
|
-
if (timeout !== void 0) timeoutId = setTimeout(() => {
|
|
2796
|
-
cleanup();
|
|
2797
|
-
reject(new WaitUntilTimeoutError(timeout));
|
|
2798
|
-
}, timeout);
|
|
2799
|
-
if (signal) {
|
|
2800
|
-
abortHandler = () => {
|
|
2801
|
-
cleanup();
|
|
2802
|
-
reject(new WaitUntilAbortedError());
|
|
2803
|
-
};
|
|
2804
|
-
signal.addEventListener("abort", abortHandler);
|
|
2805
|
-
}
|
|
2806
|
-
});
|
|
2807
|
-
}
|
|
2808
|
-
|
|
2809
|
-
//#endregion
|
|
2810
|
-
//#region src/watch/watch.ts
|
|
2811
|
-
const STOP = Symbol("watch.STOP");
|
|
2812
|
-
const BLOC_REF_MARKER = Symbol("BlocRef");
|
|
2813
|
-
/**
|
|
2814
|
-
* Create a reference to a specific bloc instance.
|
|
2815
|
-
*
|
|
2816
|
-
* @example
|
|
2817
|
-
* ```ts
|
|
2818
|
-
* watch(instance(UserBloc, 'user-123'), (userBloc) => {
|
|
2819
|
-
* console.log(userBloc.state.name);
|
|
2820
|
-
* });
|
|
2821
|
-
* ```
|
|
2822
|
-
*/
|
|
2823
|
-
function instance(BlocClass, instanceId) {
|
|
2824
|
-
return {
|
|
2825
|
-
[BLOC_REF_MARKER]: true,
|
|
2826
|
-
blocClass: BlocClass,
|
|
2827
|
-
instanceId
|
|
2828
|
-
};
|
|
2829
|
-
}
|
|
2830
|
-
function isBlocRef(input) {
|
|
2831
|
-
return typeof input === "object" && input !== null && BLOC_REF_MARKER in input;
|
|
2832
|
-
}
|
|
2833
|
-
function resolveBloc(input) {
|
|
2834
|
-
if (isBlocRef(input)) return ensure(input.blocClass, input.instanceId);
|
|
2835
|
-
return ensure(input, BLAC_DEFAULTS.DEFAULT_INSTANCE_KEY);
|
|
2836
|
-
}
|
|
2837
|
-
function isArray(input) {
|
|
2838
|
-
return Array.isArray(input);
|
|
2839
|
-
}
|
|
2840
|
-
function watchImpl(blocsOrBloc, callback) {
|
|
2841
|
-
const isSingle = !isArray(blocsOrBloc);
|
|
2842
|
-
const instances = (isSingle ? [blocsOrBloc] : blocsOrBloc).map(resolveBloc);
|
|
2843
|
-
const tracker = createState();
|
|
2844
|
-
const proxiedInstances = instances.map((inst) => createTrackingProxy(inst, tracker));
|
|
2845
|
-
const externalDepsManager = new DependencyManager();
|
|
2846
|
-
let disposed = false;
|
|
2847
|
-
const primarySubscriptions = [];
|
|
2848
|
-
const cleanup = () => {
|
|
2849
|
-
if (disposed) return;
|
|
2850
|
-
disposed = true;
|
|
2851
|
-
primarySubscriptions.forEach((unsub) => unsub());
|
|
2852
|
-
externalDepsManager.cleanup();
|
|
2853
|
-
};
|
|
2854
|
-
const runCallback = () => {
|
|
2855
|
-
if (disposed) return;
|
|
2856
|
-
startTracking(tracker);
|
|
2857
|
-
let result;
|
|
2858
|
-
try {
|
|
2859
|
-
result = callback(isSingle ? proxiedInstances[0] : proxiedInstances);
|
|
2860
|
-
} finally {
|
|
2861
|
-
const externalDeps = /* @__PURE__ */ new Set();
|
|
2862
|
-
for (const inst of instances) {
|
|
2863
|
-
const deps = stopTracking(tracker, inst);
|
|
2864
|
-
for (const dep of deps) externalDeps.add(dep);
|
|
2865
|
-
}
|
|
2866
|
-
for (const dep of tracker.getterState.externalDependencies) externalDeps.add(dep);
|
|
2867
|
-
for (const inst of instances) externalDeps.delete(inst);
|
|
2868
|
-
externalDepsManager.sync(externalDeps, runCallback);
|
|
2869
|
-
}
|
|
2870
|
-
if (result === STOP) cleanup();
|
|
2871
|
-
};
|
|
2872
|
-
const onChange = () => {
|
|
2873
|
-
if (disposed) return;
|
|
2874
|
-
runCallback();
|
|
2875
|
-
};
|
|
2876
|
-
for (const inst of instances) primarySubscriptions.push(inst.subscribe(onChange));
|
|
2877
|
-
runCallback();
|
|
2878
|
-
return cleanup;
|
|
2879
|
-
}
|
|
2880
|
-
const watch = Object.assign(watchImpl, { STOP });
|
|
2881
|
-
|
|
2882
|
-
//#endregion
|
|
2883
|
-
export { BLAC_DEFAULTS, BLAC_ERROR_PREFIX, BLAC_ID_PATTERNS, BLAC_STATIC_PROPS, Cubit, DependencyManager, ExternalDepsManager, LogLevel, PluginManager, STATELESS_MARKER, StateContainer, StateContainerRegistry, StatelessCubit, StatelessVertex, TrackedContext, Vertex, WaitUntilAbortedError, WaitUntilDisposedError, WaitUntilTimeoutError, __resetIdCounters, acquire, autoTrackInit, autoTrackSnapshot, autoTrackSubscribe, blac, borrow, borrowSafe, capturePaths, clear, clearActiveTracker, clearAll, clearExternalDependencies, commitTrackedGetters, configureBlac, configureLogger, createArrayProxy, createBlocProxy, createDependencyProxy, createDependencyState, createForTarget, createGetterState, createIdGenerator, createInternal, createLogger, createPluginManager, createProxyState, createTrackedContext, createTrackingProxy, createState as createTrackingProxyState, debug, disableGetterTracking, ensure, error, forEach, generateId, generateIsolatedKey, generateSimpleId, getActiveTracker, getAll, getBlacConfig, getDescriptor, getGetterExecutionContext, getPluginManager, getRefCount, getRegistry, getStaticProp, getStats, getValueAtPath, globalRegistry, hasDependencyChanges, hasGetterChanges, hasInitHook, hasInstance, hasTrackedData, hasChanges as hasTrackingProxyChanges, info, instance, invalidateRenderCache, isDevMode, isExcludedFromDevTools, isGetter, isIsolatedClass, isIsolatedKey, isKeepAliveClass, isProxyable, isStatelessClass, manualDepsInit, manualDepsSnapshot, manualDepsSubscribe, noTrackInit, noTrackSnapshot, noTrackSubscribe, parsePath, register, release, resetBlacConfig, resetGetterState, setActiveTracker, setRegistry, shallowEqual, startDependency, startProxy, startTracking as startTrackingProxy, stopProxy, stopTracking as stopTrackingProxy, tracked, waitUntil, warn, watch };
|
|
309
|
+
export { Cubit, DependencyManager, PluginManager, StateContainer, acquire, blac, borrow, borrowSafe, capturePaths, clear, clearActiveTracker, clearAll, clearExternalDependencies, commitTrackedGetters, createBlocProxy, createDependencyProxy, createDependencyState, createGetterState, ensure, forEach, generateIsolatedKey, getAll, getPluginManager, getRefCount, globalRegistry, hasDependencyChanges, hasGetterChanges, hasInstance, hasTrackedData, instance, invalidateRenderCache, isIsolatedClass, release, setActiveTracker, shallowEqual, startDependency, watch };
|
|
2884
310
|
//# sourceMappingURL=index.js.map
|