@blac/core 2.0.0-rc-13 → 2.0.0-rc-14

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) hide show
  1. package/dist/index.cjs.js +2 -2
  2. package/dist/index.cjs.js.map +1 -1
  3. package/dist/index.d.ts +522 -297
  4. package/dist/index.es.js +1833 -1550
  5. package/dist/index.es.js.map +1 -1
  6. package/package.json +6 -7
  7. package/src/Blac.ts +38 -71
  8. package/src/Bloc.ts +8 -9
  9. package/src/BlocBase.ts +246 -540
  10. package/src/Cubit.ts +1 -16
  11. package/src/__tests__/Blac.config.test.ts +10 -17
  12. package/src/__tests__/Bloc.event.test.ts +27 -52
  13. package/src/__tests__/BlocBase.subscription.test.ts +349 -0
  14. package/src/__tests__/Cubit.test.ts +39 -50
  15. package/src/__tests__/memory-leaks.test.ts +383 -0
  16. package/src/__tests__/plugins.test.ts +107 -97
  17. package/src/adapter/BlacAdapter.ts +347 -207
  18. package/src/adapter/ProxyFactory.ts +196 -255
  19. package/src/adapter/__tests__/BlacAdapter.test.ts +71 -77
  20. package/src/adapter/__tests__/ProxyFactory.test.ts +1 -1
  21. package/src/adapter/index.ts +0 -1
  22. package/src/errors/BlacError.ts +69 -0
  23. package/src/errors/ErrorManager.ts +101 -0
  24. package/src/errors/handleError.ts +114 -0
  25. package/src/errors/index.ts +13 -0
  26. package/src/events.ts +7 -1
  27. package/src/index.ts +7 -1
  28. package/src/lifecycle/BlocLifecycle.ts +156 -0
  29. package/src/lifecycle/index.ts +1 -0
  30. package/src/plugins/BlocPluginRegistry.ts +71 -51
  31. package/src/plugins/SystemPluginRegistry.ts +63 -13
  32. package/src/plugins/__tests__/BlocPluginRegistry.test.ts +450 -0
  33. package/src/plugins/__tests__/SystemPluginRegistry.test.ts +404 -0
  34. package/src/plugins/types.ts +32 -3
  35. package/src/subscription/README.md +132 -0
  36. package/src/subscription/SubscriptionManager.ts +395 -0
  37. package/src/subscription/__tests__/SubscriptionManager.test.ts +355 -0
  38. package/src/subscription/index.ts +2 -0
  39. package/src/subscription/types.ts +90 -0
  40. package/src/testing.ts +31 -37
  41. package/src/types.ts +4 -21
  42. package/src/utils/BatchingManager.ts +59 -0
  43. package/src/utils/RerenderLogger.ts +265 -0
  44. package/src/utils/__tests__/generateInstanceId.test.ts +104 -0
  45. package/src/utils/__tests__/shallowEqual.test.ts +171 -0
  46. package/src/utils/__tests__/uuid.test.ts +144 -0
  47. package/src/utils/generateInstanceId.ts +54 -0
  48. package/src/utils/shallowEqual.ts +1 -1
  49. package/README.md +0 -329
  50. package/src/BlacObserver.ts +0 -189
  51. package/src/__tests__/BlocBase.lifecycle.test.ts +0 -441
  52. package/src/__tests__/props.test.ts +0 -256
  53. package/src/adapter/ConsumerTracker.ts +0 -241
  54. 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
- // Cache for proxies to ensure consistent object identity
12
+ // Minimal cache for backward compatibility
14
13
  const proxyCache = new WeakMap<object, WeakMap<object, any>>();
15
14
 
16
- // Statistics tracking
17
- let proxyStats = {
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
- export class ProxyFactory {
29
- static createStateProxy<T extends object>(options: {
30
- target: T;
31
- consumerRef: object;
32
- consumerTracker: ConsumerTracker;
33
- path?: string;
34
- }): T {
35
- const { target, consumerRef, consumerTracker, path = '' } = options;
36
-
37
- if (!consumerRef || !consumerTracker) {
38
- return target;
39
- }
40
-
41
- if (typeof target !== 'object' || target === null) {
42
- return target;
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
- // Check cache to ensure consistent proxy identity
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 existingProxy = refCache.get(consumerRef);
53
- if (existingProxy) {
54
- proxyStats.cacheHits++;
55
- return existingProxy;
51
+ const cached = refCache.get(consumerRef);
52
+ if (cached) {
53
+ stats.cacheHits++;
54
+ return cached;
56
55
  }
56
+ stats.cacheMisses++;
57
+ }
57
58
 
58
- proxyStats.cacheMisses++;
59
-
60
- const handler: ProxyHandler<T> = {
61
- get(obj: T, prop: string | symbol): any {
62
- // Handle symbols and special properties
63
- if (typeof prop === 'symbol' || prop === 'constructor') {
64
- return Reflect.get(obj, prop);
65
- }
66
-
67
- const fullPath = path ? `${path}.${prop}` : prop;
68
- proxyStats.propertyAccesses++;
69
-
70
- const value = Reflect.get(obj, prop);
71
- const valueType = typeof value;
72
- const isObject = value && valueType === 'object' && value !== null;
73
-
74
- // Track the access with value (only for primitives at root level)
75
- const trackValue = !isObject ? value : undefined;
76
- consumerTracker.trackAccess(consumerRef, 'state', fullPath, trackValue);
77
-
78
- // Recursively proxy nested objects and arrays
79
- if (value && typeof value === 'object' && value !== null) {
80
- // Support arrays, plain objects, and other object types
81
- const isPlainObject =
82
- Object.getPrototypeOf(value) === Object.prototype;
83
- const isArray = Array.isArray(value);
84
-
85
- if (isPlainObject || isArray) {
86
- proxyStats.nestedProxiesCreated++;
87
- return ProxyFactory.createStateProxy({
88
- target: value,
89
- consumerRef,
90
- consumerTracker,
91
- path: fullPath,
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
- proxyStats.stateProxiesCreated++;
129
- proxyStats.totalProxiesCreated++;
130
-
131
- return proxy;
98
+ stats.stateProxiesCreated++;
99
+ stats.totalProxiesCreated++;
132
100
  }
133
101
 
134
- static createClassProxy<T extends object>(options: {
135
- target: T;
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
- if (!isGetter) {
194
- // bind methods to the object if they are functions
195
- /*
196
- if (typeof value === 'function') {
197
- proxyStats.propertyAccesses++;
198
- console.log(
199
- `🏭 [ProxyFactory] 🔧 Method accessed: ${String(prop)}`,
200
- );
201
- console.log(`🏭 [ProxyFactory] Binding method to object instance`);
202
- return value.bind(obj);
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
- // Return the value directly if it's not a getter or method
207
- return value;
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
- // For getters, track access and value
211
- proxyStats.propertyAccesses++;
124
+ const cached = refCache.get(consumerRef);
125
+ if (cached) {
126
+ stats.cacheHits++;
127
+ return cached;
128
+ }
129
+ stats.cacheMisses++;
212
130
 
213
- // Track the getter value if it's a primitive
214
- const getterValue = value;
215
- const isGetterValuePrimitive =
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
- // Track access with value for primitives
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
- trackValue,
144
+ isPrimitive ? value : undefined,
225
145
  );
226
- return value;
227
- },
146
+ }
228
147
 
229
- has(obj: T, prop: string | symbol): boolean {
230
- return prop in obj;
231
- },
148
+ return value;
149
+ },
150
+ });
232
151
 
233
- ownKeys(obj: T): (string | symbol)[] {
234
- return Reflect.ownKeys(obj);
235
- },
152
+ // Cache proxy
153
+ refCache.set(consumerRef, proxy);
154
+ stats.classProxiesCreated++;
155
+ stats.totalProxiesCreated++;
236
156
 
237
- getOwnPropertyDescriptor(
238
- obj: T,
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
- return proxy;
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
- static getProxyState<B extends BlocBase<any>>(options: {
255
- state: BlocState<B>;
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
- }): BlocState<B> {
259
- return ProxyFactory.createStateProxy({
260
- target: options.state,
261
- consumerRef: options.consumerRef,
262
- consumerTracker: options.consumerTracker,
263
- });
264
- }
265
-
266
- static getProxyBlocInstance<B extends BlocBase<any>>(options: {
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
- }): B {
271
- return ProxyFactory.createClassProxy({
272
- target: options.blocInstance,
273
- consumerRef: options.consumerRef,
274
- consumerTracker: options.consumerTracker,
275
- });
276
- }
277
-
278
- static getStats() {
279
- const lifetime = Date.now() - proxyStats.createdAt;
280
- return {
281
- ...proxyStats,
282
- totalProxies:
283
- proxyStats.stateProxiesCreated + proxyStats.classProxiesCreated,
284
- cacheEfficiency:
285
- proxyStats.cacheHits + proxyStats.cacheMisses > 0
286
- ? `${((proxyStats.cacheHits / (proxyStats.cacheHits + proxyStats.cacheMisses)) * 100).toFixed(1)}%`
287
- : 'N/A',
288
- lifetime: `${lifetime}ms`,
289
- proxiesPerSecond:
290
- lifetime > 0
291
- ? (proxyStats.totalProxiesCreated / (lifetime / 1000)).toFixed(2)
292
- : 'N/A',
293
- };
294
- }
295
-
296
- static resetStats() {
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
+ };