@blac/core 2.0.0-rc-13 → 2.0.0-rc-16
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/package.json +10 -15
- package/src/Blac.ts +38 -71
- package/src/Bloc.ts +8 -9
- package/src/BlocBase.ts +246 -540
- package/src/Cubit.ts +1 -16
- package/src/__tests__/Blac.config.test.ts +10 -17
- package/src/__tests__/Bloc.event.test.ts +27 -52
- package/src/__tests__/BlocBase.subscription.test.ts +349 -0
- package/src/__tests__/Cubit.test.ts +39 -50
- package/src/__tests__/memory-leaks.test.ts +383 -0
- package/src/__tests__/plugins.test.ts +107 -97
- package/src/adapter/BlacAdapter.ts +347 -207
- package/src/adapter/ProxyFactory.ts +196 -255
- package/src/adapter/__tests__/BlacAdapter.test.ts +71 -77
- package/src/adapter/__tests__/ProxyFactory.test.ts +1 -1
- package/src/adapter/index.ts +0 -1
- package/src/errors/BlacError.ts +69 -0
- package/src/errors/ErrorManager.ts +101 -0
- package/src/errors/handleError.ts +114 -0
- package/src/errors/index.ts +13 -0
- package/src/events.ts +7 -1
- package/src/index.ts +7 -1
- package/src/lifecycle/BlocLifecycle.ts +156 -0
- package/src/lifecycle/index.ts +1 -0
- package/src/plugins/BlocPluginRegistry.ts +71 -51
- package/src/plugins/SystemPluginRegistry.ts +63 -13
- package/src/plugins/__tests__/BlocPluginRegistry.test.ts +450 -0
- package/src/plugins/__tests__/SystemPluginRegistry.test.ts +404 -0
- package/src/plugins/types.ts +32 -3
- package/src/subscription/README.md +132 -0
- package/src/subscription/SubscriptionManager.ts +395 -0
- package/src/subscription/__tests__/SubscriptionManager.test.ts +355 -0
- package/src/subscription/index.ts +2 -0
- package/src/subscription/types.ts +90 -0
- package/src/testing.ts +31 -37
- package/src/types.ts +4 -21
- package/src/utils/BatchingManager.ts +59 -0
- package/src/utils/RerenderLogger.ts +265 -0
- package/src/utils/__tests__/generateInstanceId.test.ts +104 -0
- package/src/utils/__tests__/shallowEqual.test.ts +171 -0
- package/src/utils/__tests__/uuid.test.ts +144 -0
- package/src/utils/generateInstanceId.ts +54 -0
- package/src/utils/shallowEqual.ts +1 -1
- package/README.md +0 -329
- package/dist/index.cjs.js +0 -11
- package/dist/index.cjs.js.map +0 -1
- package/dist/index.d.ts +0 -1078
- package/dist/index.es.js +0 -1992
- package/dist/index.es.js.map +0 -1
- package/src/BlacObserver.ts +0 -189
- package/src/__tests__/BlocBase.lifecycle.test.ts +0 -441
- package/src/__tests__/props.test.ts +0 -256
- package/src/adapter/ConsumerTracker.ts +0 -241
- package/src/adapter/__tests__/ConsumerTracker.test.ts +0 -562
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { BlocBase } from '../BlocBase';
|
|
2
|
-
import { BlocState } from '../types';
|
|
3
2
|
|
|
4
3
|
interface ConsumerTracker {
|
|
5
4
|
trackAccess: (
|
|
@@ -10,299 +9,241 @@ interface ConsumerTracker {
|
|
|
10
9
|
) => void;
|
|
11
10
|
}
|
|
12
11
|
|
|
13
|
-
//
|
|
12
|
+
// Minimal cache for backward compatibility
|
|
14
13
|
const proxyCache = new WeakMap<object, WeakMap<object, any>>();
|
|
15
14
|
|
|
16
|
-
//
|
|
17
|
-
|
|
15
|
+
// Minimal stats for backward compatibility
|
|
16
|
+
const stats = {
|
|
18
17
|
stateProxiesCreated: 0,
|
|
19
18
|
classProxiesCreated: 0,
|
|
20
19
|
cacheHits: 0,
|
|
21
20
|
cacheMisses: 0,
|
|
22
|
-
propertyAccesses: 0,
|
|
23
|
-
nestedProxiesCreated: 0,
|
|
24
21
|
totalProxiesCreated: 0,
|
|
25
|
-
createdAt: Date.now(),
|
|
26
22
|
};
|
|
27
23
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
24
|
+
/**
|
|
25
|
+
* Creates a proxy for state objects that tracks property access
|
|
26
|
+
*/
|
|
27
|
+
export const createStateProxy = <T extends object>(
|
|
28
|
+
target: T,
|
|
29
|
+
consumerRef: object,
|
|
30
|
+
consumerTracker: ConsumerTracker,
|
|
31
|
+
path = '',
|
|
32
|
+
): T => {
|
|
33
|
+
if (
|
|
34
|
+
!consumerRef ||
|
|
35
|
+
!consumerTracker ||
|
|
36
|
+
typeof target !== 'object' ||
|
|
37
|
+
target === null
|
|
38
|
+
) {
|
|
39
|
+
return target;
|
|
40
|
+
}
|
|
44
41
|
|
|
45
|
-
|
|
42
|
+
// Check cache for backward compatibility with tests
|
|
43
|
+
if (!path) {
|
|
44
|
+
// Only cache root objects
|
|
46
45
|
let refCache = proxyCache.get(target);
|
|
47
46
|
if (!refCache) {
|
|
48
47
|
refCache = new WeakMap();
|
|
49
48
|
proxyCache.set(target, refCache);
|
|
50
49
|
}
|
|
51
50
|
|
|
52
|
-
const
|
|
53
|
-
if (
|
|
54
|
-
|
|
55
|
-
return
|
|
51
|
+
const cached = refCache.get(consumerRef);
|
|
52
|
+
if (cached) {
|
|
53
|
+
stats.cacheHits++;
|
|
54
|
+
return cached;
|
|
56
55
|
}
|
|
56
|
+
stats.cacheMisses++;
|
|
57
|
+
}
|
|
57
58
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
return value;
|
|
97
|
-
},
|
|
98
|
-
|
|
99
|
-
has(obj: T, prop: string | symbol): boolean {
|
|
100
|
-
return prop in obj;
|
|
101
|
-
},
|
|
102
|
-
|
|
103
|
-
ownKeys(obj: T): (string | symbol)[] {
|
|
104
|
-
return Reflect.ownKeys(obj);
|
|
105
|
-
},
|
|
106
|
-
|
|
107
|
-
getOwnPropertyDescriptor(
|
|
108
|
-
obj: T,
|
|
109
|
-
prop: string | symbol,
|
|
110
|
-
): PropertyDescriptor | undefined {
|
|
111
|
-
return Reflect.getOwnPropertyDescriptor(obj, prop);
|
|
112
|
-
},
|
|
113
|
-
|
|
114
|
-
set(): boolean {
|
|
115
|
-
// State should not be mutated directly. Use emit() or patch() methods.
|
|
116
|
-
return false;
|
|
117
|
-
},
|
|
118
|
-
|
|
119
|
-
deleteProperty(): boolean {
|
|
120
|
-
// State properties should not be deleted directly.
|
|
121
|
-
return false;
|
|
122
|
-
},
|
|
123
|
-
};
|
|
124
|
-
|
|
125
|
-
const proxy = new Proxy(target, handler);
|
|
59
|
+
const proxy = new Proxy(target, {
|
|
60
|
+
get(obj: T, prop: string | symbol): any {
|
|
61
|
+
// Handle symbols and special properties
|
|
62
|
+
if (typeof prop === 'symbol' || prop === 'constructor') {
|
|
63
|
+
return Reflect.get(obj, prop);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const fullPath = path ? `${path}.${prop}` : prop;
|
|
67
|
+
const value = Reflect.get(obj, prop);
|
|
68
|
+
const isObject = value && typeof value === 'object';
|
|
69
|
+
|
|
70
|
+
// Track access with value for primitives
|
|
71
|
+
consumerTracker.trackAccess(
|
|
72
|
+
consumerRef,
|
|
73
|
+
'state',
|
|
74
|
+
fullPath,
|
|
75
|
+
isObject ? undefined : value,
|
|
76
|
+
);
|
|
77
|
+
|
|
78
|
+
// Recursively proxy nested objects and arrays
|
|
79
|
+
if (
|
|
80
|
+
isObject &&
|
|
81
|
+
(Array.isArray(value) ||
|
|
82
|
+
Object.getPrototypeOf(value) === Object.prototype)
|
|
83
|
+
) {
|
|
84
|
+
return createStateProxy(value, consumerRef, consumerTracker, fullPath);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return value;
|
|
88
|
+
},
|
|
89
|
+
|
|
90
|
+
set: () => false, // State should be immutable
|
|
91
|
+
deleteProperty: () => false, // State properties should not be deleted
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
// Cache root proxies
|
|
95
|
+
if (!path) {
|
|
96
|
+
const refCache = proxyCache.get(target)!;
|
|
126
97
|
refCache.set(consumerRef, proxy);
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
proxyStats.totalProxiesCreated++;
|
|
130
|
-
|
|
131
|
-
return proxy;
|
|
98
|
+
stats.stateProxiesCreated++;
|
|
99
|
+
stats.totalProxiesCreated++;
|
|
132
100
|
}
|
|
133
101
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
consumerRef: object;
|
|
137
|
-
consumerTracker: ConsumerTracker;
|
|
138
|
-
}): T {
|
|
139
|
-
const { target, consumerRef, consumerTracker } = options;
|
|
140
|
-
|
|
141
|
-
if (!consumerRef || !consumerTracker) {
|
|
142
|
-
return target;
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
// Check cache for class proxies
|
|
146
|
-
let refCache = proxyCache.get(target);
|
|
147
|
-
if (!refCache) {
|
|
148
|
-
refCache = new WeakMap();
|
|
149
|
-
proxyCache.set(target, refCache);
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
const existingProxy = refCache.get(consumerRef);
|
|
153
|
-
if (existingProxy) {
|
|
154
|
-
proxyStats.cacheHits++;
|
|
155
|
-
return existingProxy;
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
proxyStats.cacheMisses++;
|
|
159
|
-
|
|
160
|
-
const handler: ProxyHandler<T> = {
|
|
161
|
-
get(obj: T, prop: string | symbol): any {
|
|
162
|
-
const value = Reflect.get(obj, prop);
|
|
163
|
-
// Check for getter on the prototype chain with safety limits
|
|
164
|
-
let isGetter = false;
|
|
165
|
-
let currentObj: any = obj;
|
|
166
|
-
const visitedPrototypes = new WeakSet();
|
|
167
|
-
const MAX_PROTOTYPE_DEPTH = 10; // Reasonable depth limit
|
|
168
|
-
let depth = 0;
|
|
169
|
-
|
|
170
|
-
while (currentObj && !isGetter && depth < MAX_PROTOTYPE_DEPTH) {
|
|
171
|
-
// Check for circular references
|
|
172
|
-
if (visitedPrototypes.has(currentObj)) {
|
|
173
|
-
break;
|
|
174
|
-
}
|
|
175
|
-
visitedPrototypes.add(currentObj);
|
|
176
|
-
|
|
177
|
-
try {
|
|
178
|
-
const descriptor = Object.getOwnPropertyDescriptor(
|
|
179
|
-
currentObj,
|
|
180
|
-
prop,
|
|
181
|
-
);
|
|
182
|
-
if (descriptor && descriptor.get) {
|
|
183
|
-
isGetter = true;
|
|
184
|
-
break;
|
|
185
|
-
}
|
|
186
|
-
currentObj = Object.getPrototypeOf(currentObj);
|
|
187
|
-
depth++;
|
|
188
|
-
} catch (error) {
|
|
189
|
-
break;
|
|
190
|
-
}
|
|
191
|
-
}
|
|
102
|
+
return proxy;
|
|
103
|
+
};
|
|
192
104
|
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
*/
|
|
105
|
+
/**
|
|
106
|
+
* Creates a proxy for bloc instances that tracks getter access
|
|
107
|
+
*/
|
|
108
|
+
export const createBlocProxy = <T extends object>(
|
|
109
|
+
target: T,
|
|
110
|
+
consumerRef: object,
|
|
111
|
+
consumerTracker: ConsumerTracker,
|
|
112
|
+
): T => {
|
|
113
|
+
if (!consumerRef || !consumerTracker) {
|
|
114
|
+
return target;
|
|
115
|
+
}
|
|
205
116
|
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
117
|
+
// Check cache
|
|
118
|
+
let refCache = proxyCache.get(target);
|
|
119
|
+
if (!refCache) {
|
|
120
|
+
refCache = new WeakMap();
|
|
121
|
+
proxyCache.set(target, refCache);
|
|
122
|
+
}
|
|
209
123
|
|
|
210
|
-
|
|
211
|
-
|
|
124
|
+
const cached = refCache.get(consumerRef);
|
|
125
|
+
if (cached) {
|
|
126
|
+
stats.cacheHits++;
|
|
127
|
+
return cached;
|
|
128
|
+
}
|
|
129
|
+
stats.cacheMisses++;
|
|
212
130
|
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
getterValue !== null && typeof getterValue !== 'object';
|
|
217
|
-
const trackValue = isGetterValuePrimitive ? getterValue : undefined;
|
|
131
|
+
const proxy = new Proxy(target, {
|
|
132
|
+
get(obj: T, prop: string | symbol): any {
|
|
133
|
+
const value = Reflect.get(obj, prop);
|
|
218
134
|
|
|
219
|
-
|
|
135
|
+
// Only track getters, not methods or regular properties
|
|
136
|
+
const descriptor = findPropertyDescriptor(obj, prop);
|
|
137
|
+
if (descriptor?.get) {
|
|
138
|
+
// Track getter access with value for primitives
|
|
139
|
+
const isPrimitive = value !== null && typeof value !== 'object';
|
|
220
140
|
consumerTracker.trackAccess(
|
|
221
141
|
consumerRef,
|
|
222
142
|
'class',
|
|
223
143
|
String(prop),
|
|
224
|
-
|
|
144
|
+
isPrimitive ? value : undefined,
|
|
225
145
|
);
|
|
226
|
-
|
|
227
|
-
},
|
|
146
|
+
}
|
|
228
147
|
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
148
|
+
return value;
|
|
149
|
+
},
|
|
150
|
+
});
|
|
232
151
|
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
152
|
+
// Cache proxy
|
|
153
|
+
refCache.set(consumerRef, proxy);
|
|
154
|
+
stats.classProxiesCreated++;
|
|
155
|
+
stats.totalProxiesCreated++;
|
|
236
156
|
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
prop: string | symbol,
|
|
240
|
-
): PropertyDescriptor | undefined {
|
|
241
|
-
return Reflect.getOwnPropertyDescriptor(obj, prop);
|
|
242
|
-
},
|
|
243
|
-
};
|
|
244
|
-
|
|
245
|
-
const proxy = new Proxy(target, handler);
|
|
246
|
-
refCache.set(consumerRef, proxy);
|
|
247
|
-
|
|
248
|
-
proxyStats.classProxiesCreated++;
|
|
249
|
-
proxyStats.totalProxiesCreated++;
|
|
157
|
+
return proxy;
|
|
158
|
+
};
|
|
250
159
|
|
|
251
|
-
|
|
160
|
+
/**
|
|
161
|
+
* Helper to find property descriptor in prototype chain
|
|
162
|
+
*/
|
|
163
|
+
const findPropertyDescriptor = (
|
|
164
|
+
obj: any,
|
|
165
|
+
prop: string | symbol,
|
|
166
|
+
maxDepth = 10,
|
|
167
|
+
): PropertyDescriptor | undefined => {
|
|
168
|
+
let current = obj;
|
|
169
|
+
let depth = 0;
|
|
170
|
+
|
|
171
|
+
while (current && depth < maxDepth) {
|
|
172
|
+
const descriptor = Object.getOwnPropertyDescriptor(current, prop);
|
|
173
|
+
if (descriptor) return descriptor;
|
|
174
|
+
|
|
175
|
+
current = Object.getPrototypeOf(current);
|
|
176
|
+
depth++;
|
|
252
177
|
}
|
|
253
178
|
|
|
254
|
-
|
|
255
|
-
|
|
179
|
+
return undefined;
|
|
180
|
+
};
|
|
181
|
+
|
|
182
|
+
// Export compatibility functions for easier migration
|
|
183
|
+
export const ProxyFactory = {
|
|
184
|
+
createStateProxy: (options: {
|
|
185
|
+
target: any;
|
|
256
186
|
consumerRef: object;
|
|
257
187
|
consumerTracker: ConsumerTracker;
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
188
|
+
path?: string;
|
|
189
|
+
}) =>
|
|
190
|
+
createStateProxy(
|
|
191
|
+
options.target,
|
|
192
|
+
options.consumerRef,
|
|
193
|
+
options.consumerTracker,
|
|
194
|
+
options.path,
|
|
195
|
+
),
|
|
196
|
+
|
|
197
|
+
createClassProxy: (options: {
|
|
198
|
+
target: any;
|
|
199
|
+
consumerRef: object;
|
|
200
|
+
consumerTracker: ConsumerTracker;
|
|
201
|
+
}) =>
|
|
202
|
+
createBlocProxy(
|
|
203
|
+
options.target,
|
|
204
|
+
options.consumerRef,
|
|
205
|
+
options.consumerTracker,
|
|
206
|
+
),
|
|
207
|
+
|
|
208
|
+
getProxyState: <B extends BlocBase<any>>(options: {
|
|
209
|
+
state: any;
|
|
210
|
+
consumerRef: object;
|
|
211
|
+
consumerTracker: ConsumerTracker;
|
|
212
|
+
}) =>
|
|
213
|
+
createStateProxy(
|
|
214
|
+
options.state,
|
|
215
|
+
options.consumerRef,
|
|
216
|
+
options.consumerTracker,
|
|
217
|
+
),
|
|
218
|
+
|
|
219
|
+
getProxyBlocInstance: <B extends BlocBase<any>>(options: {
|
|
267
220
|
blocInstance: B;
|
|
268
221
|
consumerRef: object;
|
|
269
222
|
consumerTracker: ConsumerTracker;
|
|
270
|
-
})
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
proxyStats = {
|
|
298
|
-
stateProxiesCreated: 0,
|
|
299
|
-
classProxiesCreated: 0,
|
|
300
|
-
cacheHits: 0,
|
|
301
|
-
cacheMisses: 0,
|
|
302
|
-
propertyAccesses: 0,
|
|
303
|
-
nestedProxiesCreated: 0,
|
|
304
|
-
totalProxiesCreated: 0,
|
|
305
|
-
createdAt: Date.now(),
|
|
306
|
-
};
|
|
307
|
-
}
|
|
308
|
-
}
|
|
223
|
+
}) =>
|
|
224
|
+
createBlocProxy(
|
|
225
|
+
options.blocInstance,
|
|
226
|
+
options.consumerRef,
|
|
227
|
+
options.consumerTracker,
|
|
228
|
+
),
|
|
229
|
+
|
|
230
|
+
// Compatibility functions that return expected structure
|
|
231
|
+
getStats: () => ({
|
|
232
|
+
...stats,
|
|
233
|
+
propertyAccesses: 0,
|
|
234
|
+
nestedProxiesCreated: 0,
|
|
235
|
+
cacheEfficiency:
|
|
236
|
+
stats.cacheHits + stats.cacheMisses > 0
|
|
237
|
+
? `${((stats.cacheHits / (stats.cacheHits + stats.cacheMisses)) * 100).toFixed(1)}%`
|
|
238
|
+
: 'N/A',
|
|
239
|
+
lifetime: '0ms',
|
|
240
|
+
proxiesPerSecond: 'N/A',
|
|
241
|
+
}),
|
|
242
|
+
resetStats: () => {
|
|
243
|
+
stats.stateProxiesCreated = 0;
|
|
244
|
+
stats.classProxiesCreated = 0;
|
|
245
|
+
stats.cacheHits = 0;
|
|
246
|
+
stats.cacheMisses = 0;
|
|
247
|
+
stats.totalProxiesCreated = 0;
|
|
248
|
+
},
|
|
249
|
+
};
|