@memberjunction/react-runtime 2.75.0 → 2.77.0
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/.turbo/turbo-build.log +1 -1
- package/CHANGELOG.md +23 -0
- package/dist/compiler/component-compiler.d.ts +0 -1
- package/dist/compiler/component-compiler.d.ts.map +1 -1
- package/dist/compiler/component-compiler.js +34 -25
- package/dist/index.d.ts +4 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +10 -2
- package/dist/registry/component-registry.d.ts +1 -0
- package/dist/registry/component-registry.d.ts.map +1 -1
- package/dist/registry/component-registry.js +6 -3
- package/dist/runtime/index.d.ts +2 -1
- package/dist/runtime/index.d.ts.map +1 -1
- package/dist/runtime/index.js +4 -2
- package/dist/runtime/prop-builder.d.ts +1 -2
- package/dist/runtime/prop-builder.d.ts.map +1 -1
- package/dist/runtime/prop-builder.js +4 -76
- package/dist/runtime/react-root-manager.d.ts +26 -0
- package/dist/runtime/react-root-manager.d.ts.map +1 -0
- package/dist/runtime/react-root-manager.js +122 -0
- package/dist/types/index.d.ts +1 -0
- package/dist/types/index.d.ts.map +1 -1
- package/dist/utilities/cache-manager.d.ts +38 -0
- package/dist/utilities/cache-manager.d.ts.map +1 -0
- package/dist/utilities/cache-manager.js +156 -0
- package/dist/utilities/core-libraries.d.ts +5 -0
- package/dist/utilities/core-libraries.d.ts.map +1 -0
- package/dist/utilities/core-libraries.js +52 -0
- package/dist/utilities/index.d.ts +2 -0
- package/dist/utilities/index.d.ts.map +1 -1
- package/dist/utilities/index.js +2 -0
- package/dist/utilities/library-loader.d.ts +2 -2
- package/dist/utilities/library-loader.d.ts.map +1 -1
- package/dist/utilities/library-loader.js +52 -24
- package/dist/utilities/resource-manager.d.ts +34 -0
- package/dist/utilities/resource-manager.d.ts.map +1 -0
- package/dist/utilities/resource-manager.js +174 -0
- package/package.json +4 -4
- package/samples/entities-1.js +493 -0
- package/src/compiler/component-compiler.ts +64 -35
- package/src/index.ts +18 -1
- package/src/registry/component-registry.ts +14 -4
- package/src/runtime/index.ts +7 -2
- package/src/runtime/prop-builder.ts +5 -112
- package/src/runtime/react-root-manager.ts +218 -0
- package/src/types/index.ts +2 -0
- package/src/utilities/cache-manager.ts +253 -0
- package/src/utilities/core-libraries.ts +61 -0
- package/src/utilities/index.ts +3 -1
- package/src/utilities/library-loader.ts +111 -47
- package/src/utilities/resource-manager.ts +305 -0
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -0,0 +1,305 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Centralized resource management for React Runtime
|
|
3
|
+
* Handles timers, DOM elements, event listeners, and other resources that need cleanup
|
|
4
|
+
* @module @memberjunction/react-runtime/utilities
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
export interface ManagedResource {
|
|
8
|
+
type: 'timer' | 'interval' | 'animationFrame' | 'eventListener' | 'domElement' | 'observable' | 'reactRoot';
|
|
9
|
+
id: string | number;
|
|
10
|
+
cleanup: () => void;
|
|
11
|
+
metadata?: Record<string, any>;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* ResourceManager provides centralized management of resources that need cleanup.
|
|
16
|
+
* This prevents memory leaks by ensuring all resources are properly disposed.
|
|
17
|
+
*/
|
|
18
|
+
export class ResourceManager {
|
|
19
|
+
private resources = new Map<string, Set<ManagedResource>>();
|
|
20
|
+
private globalResources = new Set<ManagedResource>();
|
|
21
|
+
private cleanupCallbacks = new Map<string, (() => void)[]>();
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Register a timeout with automatic cleanup
|
|
25
|
+
*/
|
|
26
|
+
setTimeout(
|
|
27
|
+
componentId: string,
|
|
28
|
+
callback: () => void,
|
|
29
|
+
delay: number,
|
|
30
|
+
metadata?: Record<string, any>
|
|
31
|
+
): number {
|
|
32
|
+
const id = window.setTimeout(() => {
|
|
33
|
+
this.removeResource(componentId, 'timer', id);
|
|
34
|
+
callback();
|
|
35
|
+
}, delay);
|
|
36
|
+
|
|
37
|
+
this.addResource(componentId, {
|
|
38
|
+
type: 'timer',
|
|
39
|
+
id,
|
|
40
|
+
cleanup: () => window.clearTimeout(id),
|
|
41
|
+
metadata
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
return id;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Register an interval with automatic cleanup
|
|
49
|
+
*/
|
|
50
|
+
setInterval(
|
|
51
|
+
componentId: string,
|
|
52
|
+
callback: () => void,
|
|
53
|
+
delay: number,
|
|
54
|
+
metadata?: Record<string, any>
|
|
55
|
+
): number {
|
|
56
|
+
const id = window.setInterval(callback, delay);
|
|
57
|
+
|
|
58
|
+
this.addResource(componentId, {
|
|
59
|
+
type: 'interval',
|
|
60
|
+
id,
|
|
61
|
+
cleanup: () => window.clearInterval(id),
|
|
62
|
+
metadata
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
return id;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Register an animation frame with automatic cleanup
|
|
70
|
+
*/
|
|
71
|
+
requestAnimationFrame(
|
|
72
|
+
componentId: string,
|
|
73
|
+
callback: FrameRequestCallback,
|
|
74
|
+
metadata?: Record<string, any>
|
|
75
|
+
): number {
|
|
76
|
+
const id = window.requestAnimationFrame((time) => {
|
|
77
|
+
this.removeResource(componentId, 'animationFrame', id);
|
|
78
|
+
callback(time);
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
this.addResource(componentId, {
|
|
82
|
+
type: 'animationFrame',
|
|
83
|
+
id,
|
|
84
|
+
cleanup: () => window.cancelAnimationFrame(id),
|
|
85
|
+
metadata
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
return id;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Clear a specific timeout
|
|
93
|
+
*/
|
|
94
|
+
clearTimeout(componentId: string, id: number): void {
|
|
95
|
+
window.clearTimeout(id);
|
|
96
|
+
this.removeResource(componentId, 'timer', id);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Clear a specific interval
|
|
101
|
+
*/
|
|
102
|
+
clearInterval(componentId: string, id: number): void {
|
|
103
|
+
window.clearInterval(id);
|
|
104
|
+
this.removeResource(componentId, 'interval', id);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Cancel a specific animation frame
|
|
109
|
+
*/
|
|
110
|
+
cancelAnimationFrame(componentId: string, id: number): void {
|
|
111
|
+
window.cancelAnimationFrame(id);
|
|
112
|
+
this.removeResource(componentId, 'animationFrame', id);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Register an event listener with automatic cleanup
|
|
117
|
+
*/
|
|
118
|
+
addEventListener(
|
|
119
|
+
componentId: string,
|
|
120
|
+
target: EventTarget,
|
|
121
|
+
type: string,
|
|
122
|
+
listener: EventListener,
|
|
123
|
+
options?: AddEventListenerOptions
|
|
124
|
+
): void {
|
|
125
|
+
target.addEventListener(type, listener, options);
|
|
126
|
+
|
|
127
|
+
const resourceId = `${type}-${Date.now()}-${Math.random()}`;
|
|
128
|
+
this.addResource(componentId, {
|
|
129
|
+
type: 'eventListener',
|
|
130
|
+
id: resourceId,
|
|
131
|
+
cleanup: () => target.removeEventListener(type, listener, options),
|
|
132
|
+
metadata: { target, type, options }
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Register a DOM element that needs cleanup
|
|
138
|
+
*/
|
|
139
|
+
registerDOMElement(
|
|
140
|
+
componentId: string,
|
|
141
|
+
element: HTMLElement,
|
|
142
|
+
cleanup?: () => void
|
|
143
|
+
): void {
|
|
144
|
+
const resourceId = `dom-${Date.now()}-${Math.random()}`;
|
|
145
|
+
this.addResource(componentId, {
|
|
146
|
+
type: 'domElement',
|
|
147
|
+
id: resourceId,
|
|
148
|
+
cleanup: () => {
|
|
149
|
+
if (cleanup) {
|
|
150
|
+
cleanup();
|
|
151
|
+
}
|
|
152
|
+
if (element.parentNode) {
|
|
153
|
+
element.parentNode.removeChild(element);
|
|
154
|
+
}
|
|
155
|
+
},
|
|
156
|
+
metadata: { element }
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Register a React root for cleanup
|
|
162
|
+
*/
|
|
163
|
+
registerReactRoot(
|
|
164
|
+
componentId: string,
|
|
165
|
+
root: any,
|
|
166
|
+
unmountFn: () => void
|
|
167
|
+
): void {
|
|
168
|
+
this.addResource(componentId, {
|
|
169
|
+
type: 'reactRoot',
|
|
170
|
+
id: `react-root-${componentId}`,
|
|
171
|
+
cleanup: unmountFn,
|
|
172
|
+
metadata: { root }
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Register a generic cleanup callback for a component
|
|
178
|
+
*/
|
|
179
|
+
registerCleanup(componentId: string, cleanup: () => void): void {
|
|
180
|
+
if (!this.cleanupCallbacks.has(componentId)) {
|
|
181
|
+
this.cleanupCallbacks.set(componentId, []);
|
|
182
|
+
}
|
|
183
|
+
this.cleanupCallbacks.get(componentId)!.push(cleanup);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Register a global resource (not tied to a specific component)
|
|
188
|
+
*/
|
|
189
|
+
registerGlobalResource(resource: ManagedResource): void {
|
|
190
|
+
this.globalResources.add(resource);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Add a resource to be managed
|
|
195
|
+
*/
|
|
196
|
+
private addResource(componentId: string, resource: ManagedResource): void {
|
|
197
|
+
if (!this.resources.has(componentId)) {
|
|
198
|
+
this.resources.set(componentId, new Set());
|
|
199
|
+
}
|
|
200
|
+
this.resources.get(componentId)!.add(resource);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* Remove a specific resource
|
|
205
|
+
*/
|
|
206
|
+
private removeResource(
|
|
207
|
+
componentId: string,
|
|
208
|
+
type: ManagedResource['type'],
|
|
209
|
+
id: string | number
|
|
210
|
+
): void {
|
|
211
|
+
const componentResources = this.resources.get(componentId);
|
|
212
|
+
if (componentResources) {
|
|
213
|
+
const toRemove = Array.from(componentResources).find(
|
|
214
|
+
r => r.type === type && r.id === id
|
|
215
|
+
);
|
|
216
|
+
if (toRemove) {
|
|
217
|
+
componentResources.delete(toRemove);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* Clean up all resources for a specific component
|
|
224
|
+
*/
|
|
225
|
+
cleanupComponent(componentId: string): void {
|
|
226
|
+
// Clean up tracked resources
|
|
227
|
+
const componentResources = this.resources.get(componentId);
|
|
228
|
+
if (componentResources) {
|
|
229
|
+
componentResources.forEach(resource => {
|
|
230
|
+
try {
|
|
231
|
+
resource.cleanup();
|
|
232
|
+
} catch (error) {
|
|
233
|
+
console.error(`Error cleaning up ${resource.type} resource:`, error);
|
|
234
|
+
}
|
|
235
|
+
});
|
|
236
|
+
this.resources.delete(componentId);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// Execute cleanup callbacks
|
|
240
|
+
const callbacks = this.cleanupCallbacks.get(componentId);
|
|
241
|
+
if (callbacks) {
|
|
242
|
+
callbacks.forEach(callback => {
|
|
243
|
+
try {
|
|
244
|
+
callback();
|
|
245
|
+
} catch (error) {
|
|
246
|
+
console.error('Error executing cleanup callback:', error);
|
|
247
|
+
}
|
|
248
|
+
});
|
|
249
|
+
this.cleanupCallbacks.delete(componentId);
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
/**
|
|
254
|
+
* Clean up all global resources
|
|
255
|
+
*/
|
|
256
|
+
cleanupGlobal(): void {
|
|
257
|
+
this.globalResources.forEach(resource => {
|
|
258
|
+
try {
|
|
259
|
+
resource.cleanup();
|
|
260
|
+
} catch (error) {
|
|
261
|
+
console.error(`Error cleaning up global ${resource.type} resource:`, error);
|
|
262
|
+
}
|
|
263
|
+
});
|
|
264
|
+
this.globalResources.clear();
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* Clean up all resources (components and global)
|
|
269
|
+
*/
|
|
270
|
+
cleanupAll(): void {
|
|
271
|
+
// Clean up all component resources
|
|
272
|
+
for (const componentId of this.resources.keys()) {
|
|
273
|
+
this.cleanupComponent(componentId);
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
// Clean up global resources
|
|
277
|
+
this.cleanupGlobal();
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
/**
|
|
281
|
+
* Get resource statistics for debugging
|
|
282
|
+
*/
|
|
283
|
+
getStats(): {
|
|
284
|
+
componentCount: number;
|
|
285
|
+
resourceCounts: Record<string, number>;
|
|
286
|
+
globalResourceCount: number;
|
|
287
|
+
} {
|
|
288
|
+
const resourceCounts: Record<string, number> = {};
|
|
289
|
+
|
|
290
|
+
for (const resources of this.resources.values()) {
|
|
291
|
+
resources.forEach(resource => {
|
|
292
|
+
resourceCounts[resource.type] = (resourceCounts[resource.type] || 0) + 1;
|
|
293
|
+
});
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
return {
|
|
297
|
+
componentCount: this.resources.size,
|
|
298
|
+
resourceCounts,
|
|
299
|
+
globalResourceCount: this.globalResources.size
|
|
300
|
+
};
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
// Singleton instance
|
|
305
|
+
export const resourceManager = new ResourceManager();
|