@blac/core 2.0.0-rc-8 → 2.0.0-rc-11
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 +27 -0
- package/dist/index.cjs.js +2 -2
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.d.ts +224 -14
- package/dist/index.es.js +1122 -556
- package/dist/index.es.js.map +1 -1
- package/package.json +5 -4
- package/src/Blac.ts +109 -47
- package/src/BlacObserver.ts +58 -24
- package/src/Bloc.ts +120 -160
- package/src/BlocBase.ts +266 -90
- package/src/Cubit.ts +15 -1
- package/src/__tests__/Blac.config.test.ts +86 -0
- package/src/__tests__/Bloc.event.test.ts +419 -0
- package/src/__tests__/BlocBase.lifecycle.test.ts +441 -0
- package/src/__tests__/Cubit.test.ts +473 -0
- package/src/__tests__/props.test.ts +256 -0
- package/src/adapter/BlacAdapter.ts +327 -0
- package/src/adapter/ConsumerTracker.ts +241 -0
- package/src/adapter/ProxyFactory.ts +308 -0
- package/src/adapter/__tests__/BlacAdapter.test.ts +561 -0
- package/src/adapter/__tests__/ConsumerTracker.test.ts +562 -0
- package/src/adapter/__tests__/ProxyFactory.test.ts +655 -0
- package/src/adapter/index.ts +3 -0
- package/src/events.ts +3 -0
- package/src/index.ts +4 -1
- package/src/testing.ts +60 -47
- package/src/types.ts +12 -13
- package/src/utils/shallowEqual.ts +40 -0
- package/src/utils/uuid.ts +4 -4
- package/src/BlacEvent.ts +0 -5
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
export interface DependencyArray {
|
|
2
|
+
statePaths: string[];
|
|
3
|
+
classPaths: string[];
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
export interface TrackedValue {
|
|
7
|
+
value: any;
|
|
8
|
+
lastAccessTime: number;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface ConsumerInfo {
|
|
12
|
+
id: string;
|
|
13
|
+
lastNotified: number;
|
|
14
|
+
hasRendered: boolean;
|
|
15
|
+
// Dependency tracking
|
|
16
|
+
stateAccesses: Set<string>;
|
|
17
|
+
classAccesses: Set<string>;
|
|
18
|
+
stateValues: Map<string, TrackedValue>;
|
|
19
|
+
classValues: Map<string, TrackedValue>;
|
|
20
|
+
accessCount: number;
|
|
21
|
+
lastAccessTime: number;
|
|
22
|
+
firstAccessTime: number;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* ConsumerTracker manages both consumer registration and dependency tracking.
|
|
27
|
+
* It uses a WeakMap for automatic garbage collection when consumers are no longer referenced.
|
|
28
|
+
*/
|
|
29
|
+
export class ConsumerTracker {
|
|
30
|
+
private consumers = new WeakMap<object, ConsumerInfo>();
|
|
31
|
+
private registrationCount = 0;
|
|
32
|
+
private activeConsumers = 0;
|
|
33
|
+
|
|
34
|
+
register(consumerRef: object, consumerId: string): void {
|
|
35
|
+
this.registrationCount++;
|
|
36
|
+
|
|
37
|
+
const existingConsumer = this.consumers.get(consumerRef);
|
|
38
|
+
if (!existingConsumer) {
|
|
39
|
+
this.activeConsumers++;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
this.consumers.set(consumerRef, {
|
|
43
|
+
id: consumerId,
|
|
44
|
+
lastNotified: Date.now(),
|
|
45
|
+
hasRendered: false,
|
|
46
|
+
stateAccesses: new Set(),
|
|
47
|
+
classAccesses: new Set(),
|
|
48
|
+
stateValues: new Map(),
|
|
49
|
+
classValues: new Map(),
|
|
50
|
+
accessCount: 0,
|
|
51
|
+
lastAccessTime: 0,
|
|
52
|
+
firstAccessTime: 0,
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
unregister(consumerRef: object): void {
|
|
57
|
+
if (consumerRef) {
|
|
58
|
+
const consumerInfo = this.consumers.get(consumerRef);
|
|
59
|
+
if (consumerInfo) {
|
|
60
|
+
this.consumers.delete(consumerRef);
|
|
61
|
+
this.activeConsumers = Math.max(0, this.activeConsumers - 1);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
trackAccess(
|
|
67
|
+
consumerRef: object,
|
|
68
|
+
type: 'state' | 'class',
|
|
69
|
+
path: string,
|
|
70
|
+
value?: any,
|
|
71
|
+
): void {
|
|
72
|
+
const consumerInfo = this.consumers.get(consumerRef);
|
|
73
|
+
if (!consumerInfo) return;
|
|
74
|
+
|
|
75
|
+
const now = Date.now();
|
|
76
|
+
|
|
77
|
+
if (!consumerInfo.firstAccessTime) {
|
|
78
|
+
consumerInfo.firstAccessTime = now;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
consumerInfo.accessCount++;
|
|
82
|
+
consumerInfo.lastAccessTime = now;
|
|
83
|
+
|
|
84
|
+
if (type === 'state') {
|
|
85
|
+
consumerInfo.stateAccesses.add(path);
|
|
86
|
+
if (value !== undefined) {
|
|
87
|
+
consumerInfo.stateValues.set(path, { value, lastAccessTime: now });
|
|
88
|
+
}
|
|
89
|
+
} else {
|
|
90
|
+
consumerInfo.classAccesses.add(path);
|
|
91
|
+
if (value !== undefined) {
|
|
92
|
+
consumerInfo.classValues.set(path, { value, lastAccessTime: now });
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
resetTracking(consumerRef: object): void {
|
|
98
|
+
const consumerInfo = this.consumers.get(consumerRef);
|
|
99
|
+
if (!consumerInfo) return;
|
|
100
|
+
|
|
101
|
+
consumerInfo.stateAccesses.clear();
|
|
102
|
+
consumerInfo.classAccesses.clear();
|
|
103
|
+
consumerInfo.stateValues.clear();
|
|
104
|
+
consumerInfo.classValues.clear();
|
|
105
|
+
consumerInfo.accessCount = 0;
|
|
106
|
+
consumerInfo.firstAccessTime = 0;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
getDependencies(consumerRef: object): DependencyArray | null {
|
|
110
|
+
const consumerInfo = this.consumers.get(consumerRef);
|
|
111
|
+
if (!consumerInfo) return null;
|
|
112
|
+
|
|
113
|
+
return {
|
|
114
|
+
statePaths: Array.from(consumerInfo.stateAccesses),
|
|
115
|
+
classPaths: Array.from(consumerInfo.classAccesses),
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
hasValuesChanged(
|
|
120
|
+
consumerRef: object,
|
|
121
|
+
newState: any,
|
|
122
|
+
newBlocInstance: any,
|
|
123
|
+
): boolean {
|
|
124
|
+
const consumerInfo = this.consumers.get(consumerRef);
|
|
125
|
+
if (!consumerInfo) return true;
|
|
126
|
+
|
|
127
|
+
let hasChanged = false;
|
|
128
|
+
const now = Date.now();
|
|
129
|
+
|
|
130
|
+
// If we haven't tracked any values yet, consider it changed to establish baseline
|
|
131
|
+
if (
|
|
132
|
+
consumerInfo.stateValues.size === 0 &&
|
|
133
|
+
consumerInfo.classValues.size === 0 &&
|
|
134
|
+
(consumerInfo.stateAccesses.size > 0 ||
|
|
135
|
+
consumerInfo.classAccesses.size > 0)
|
|
136
|
+
) {
|
|
137
|
+
return true;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// Check state values
|
|
141
|
+
for (const [path, trackedValue] of consumerInfo.stateValues) {
|
|
142
|
+
try {
|
|
143
|
+
const currentValue = this.getValueAtPath(newState, path);
|
|
144
|
+
if (currentValue !== trackedValue.value) {
|
|
145
|
+
consumerInfo.stateValues.set(path, {
|
|
146
|
+
value: currentValue,
|
|
147
|
+
lastAccessTime: now,
|
|
148
|
+
});
|
|
149
|
+
hasChanged = true;
|
|
150
|
+
}
|
|
151
|
+
} catch (error) {
|
|
152
|
+
hasChanged = true;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// Check class getter values
|
|
157
|
+
for (const [path, trackedValue] of consumerInfo.classValues) {
|
|
158
|
+
try {
|
|
159
|
+
const currentValue = this.getValueAtPath(newBlocInstance, path);
|
|
160
|
+
if (currentValue !== trackedValue.value) {
|
|
161
|
+
consumerInfo.classValues.set(path, {
|
|
162
|
+
value: currentValue,
|
|
163
|
+
lastAccessTime: now,
|
|
164
|
+
});
|
|
165
|
+
hasChanged = true;
|
|
166
|
+
}
|
|
167
|
+
} catch (error) {
|
|
168
|
+
hasChanged = true;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
return hasChanged;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
shouldNotifyConsumer(
|
|
176
|
+
consumerRef: object,
|
|
177
|
+
changedPaths: Set<string>,
|
|
178
|
+
): boolean {
|
|
179
|
+
const consumerInfo = this.consumers.get(consumerRef);
|
|
180
|
+
if (!consumerInfo) return false;
|
|
181
|
+
|
|
182
|
+
// Check if any changed paths match tracked dependencies
|
|
183
|
+
for (const changedPath of changedPaths) {
|
|
184
|
+
if (consumerInfo.stateAccesses.has(changedPath)) return true;
|
|
185
|
+
if (consumerInfo.classAccesses.has(changedPath)) return true;
|
|
186
|
+
|
|
187
|
+
// Check for nested path changes
|
|
188
|
+
for (const trackedPath of consumerInfo.stateAccesses) {
|
|
189
|
+
if (
|
|
190
|
+
changedPath.startsWith(trackedPath + '.') ||
|
|
191
|
+
trackedPath.startsWith(changedPath + '.')
|
|
192
|
+
) {
|
|
193
|
+
return true;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
return false;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
updateLastNotified(consumerRef: object): void {
|
|
202
|
+
const consumerInfo = this.consumers.get(consumerRef);
|
|
203
|
+
if (consumerInfo) {
|
|
204
|
+
consumerInfo.lastNotified = Date.now();
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
getConsumerInfo(consumerRef: object): ConsumerInfo | undefined {
|
|
209
|
+
return this.consumers.get(consumerRef);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
setHasRendered(consumerRef: object, hasRendered: boolean): void {
|
|
213
|
+
const consumerInfo = this.consumers.get(consumerRef);
|
|
214
|
+
if (consumerInfo) {
|
|
215
|
+
consumerInfo.hasRendered = hasRendered;
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
hasConsumer(consumerRef: object): boolean {
|
|
220
|
+
return this.consumers.has(consumerRef);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
getStats() {
|
|
224
|
+
return {
|
|
225
|
+
totalRegistrations: this.registrationCount,
|
|
226
|
+
activeConsumers: this.activeConsumers,
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
private getValueAtPath(obj: any, path: string): any {
|
|
231
|
+
const parts = path.split('.');
|
|
232
|
+
let current = obj;
|
|
233
|
+
|
|
234
|
+
for (const part of parts) {
|
|
235
|
+
if (current == null) return undefined;
|
|
236
|
+
current = current[part];
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
return current;
|
|
240
|
+
}
|
|
241
|
+
}
|
|
@@ -0,0 +1,308 @@
|
|
|
1
|
+
import { BlocBase } from '../BlocBase';
|
|
2
|
+
import { BlocState } from '../types';
|
|
3
|
+
|
|
4
|
+
interface ConsumerTracker {
|
|
5
|
+
trackAccess: (
|
|
6
|
+
consumerRef: object,
|
|
7
|
+
type: 'state' | 'class',
|
|
8
|
+
path: string,
|
|
9
|
+
value?: any,
|
|
10
|
+
) => void;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
// Cache for proxies to ensure consistent object identity
|
|
14
|
+
const proxyCache = new WeakMap<object, WeakMap<object, any>>();
|
|
15
|
+
|
|
16
|
+
// Statistics tracking
|
|
17
|
+
let proxyStats = {
|
|
18
|
+
stateProxiesCreated: 0,
|
|
19
|
+
classProxiesCreated: 0,
|
|
20
|
+
cacheHits: 0,
|
|
21
|
+
cacheMisses: 0,
|
|
22
|
+
propertyAccesses: 0,
|
|
23
|
+
nestedProxiesCreated: 0,
|
|
24
|
+
totalProxiesCreated: 0,
|
|
25
|
+
createdAt: Date.now(),
|
|
26
|
+
};
|
|
27
|
+
|
|
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
|
+
}
|
|
44
|
+
|
|
45
|
+
// Check cache to ensure consistent proxy identity
|
|
46
|
+
let refCache = proxyCache.get(target);
|
|
47
|
+
if (!refCache) {
|
|
48
|
+
refCache = new WeakMap();
|
|
49
|
+
proxyCache.set(target, refCache);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const existingProxy = refCache.get(consumerRef);
|
|
53
|
+
if (existingProxy) {
|
|
54
|
+
proxyStats.cacheHits++;
|
|
55
|
+
return existingProxy;
|
|
56
|
+
}
|
|
57
|
+
|
|
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);
|
|
126
|
+
refCache.set(consumerRef, proxy);
|
|
127
|
+
|
|
128
|
+
proxyStats.stateProxiesCreated++;
|
|
129
|
+
proxyStats.totalProxiesCreated++;
|
|
130
|
+
|
|
131
|
+
return proxy;
|
|
132
|
+
}
|
|
133
|
+
|
|
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
|
+
}
|
|
192
|
+
|
|
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
|
+
*/
|
|
205
|
+
|
|
206
|
+
// Return the value directly if it's not a getter or method
|
|
207
|
+
return value;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// For getters, track access and value
|
|
211
|
+
proxyStats.propertyAccesses++;
|
|
212
|
+
|
|
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;
|
|
218
|
+
|
|
219
|
+
// Track access with value for primitives
|
|
220
|
+
consumerTracker.trackAccess(
|
|
221
|
+
consumerRef,
|
|
222
|
+
'class',
|
|
223
|
+
String(prop),
|
|
224
|
+
trackValue,
|
|
225
|
+
);
|
|
226
|
+
return value;
|
|
227
|
+
},
|
|
228
|
+
|
|
229
|
+
has(obj: T, prop: string | symbol): boolean {
|
|
230
|
+
return prop in obj;
|
|
231
|
+
},
|
|
232
|
+
|
|
233
|
+
ownKeys(obj: T): (string | symbol)[] {
|
|
234
|
+
return Reflect.ownKeys(obj);
|
|
235
|
+
},
|
|
236
|
+
|
|
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++;
|
|
250
|
+
|
|
251
|
+
return proxy;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
static getProxyState<B extends BlocBase<any>>(options: {
|
|
255
|
+
state: BlocState<B>;
|
|
256
|
+
consumerRef: object;
|
|
257
|
+
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: {
|
|
267
|
+
blocInstance: B;
|
|
268
|
+
consumerRef: object;
|
|
269
|
+
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
|
+
}
|