@memberjunction/react-runtime 2.98.0 → 2.99.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.
@@ -0,0 +1,481 @@
1
+ /**
2
+ * @fileoverview Utility functions for unwrapping exports (components, utilities, objects) from UMD-bundled libraries
3
+ * Handles various UMD bundle structures and module wrapping patterns
4
+ * Intelligently handles simple functions, complex objects, and nested exports
5
+ * @module @memberjunction/react-runtime/utilities
6
+ */
7
+
8
+ /**
9
+ * Core unwrapping logic - handles smart detection and various library patterns
10
+ * @param library - The library object (e.g., antd, MaterialUI, XLSX, or a simple function like ApexCharts)
11
+ * @param exportName - Name of the export to unwrap (e.g., 'Button', 'utils', 'DatePicker.RangePicker')
12
+ * @param debug - Whether to log debug information
13
+ * @returns The unwrapped export (component, utility object, function, etc.) or undefined if not found
14
+ */
15
+ function coreUnwrapLogic(library: any, exportName: string, debug: boolean = false): any {
16
+ // 1. Handle null/undefined/primitives - THROW EXCEPTION
17
+ if (library == null) {
18
+ throw new Error(`Cannot unwrap export '${exportName}' from null or undefined library`);
19
+ }
20
+
21
+ const libraryType = typeof library;
22
+ if (libraryType === 'string' || libraryType === 'number' || libraryType === 'boolean' || libraryType === 'symbol' || libraryType === 'bigint') {
23
+ throw new Error(`Cannot unwrap export '${exportName}' from ${libraryType} value. Library must be an object or function.`);
24
+ }
25
+
26
+ // 2. Handle simple function/class (like ApexCharts or a single exported component)
27
+ if (libraryType === 'function') {
28
+ if (debug) {
29
+ console.log(`📦 Library is a single function/class, returning it for '${exportName}'`);
30
+ console.log(` Function name: ${library.name || 'anonymous'}`);
31
+
32
+ // Check if it's a class with static methods
33
+ const hasStatics = Object.keys(library).some(key =>
34
+ key !== 'prototype' && key !== 'length' && key !== 'name' && key !== 'caller' && key !== 'arguments'
35
+ );
36
+ if (hasStatics) {
37
+ console.log(` Has static properties: ${Object.keys(library).filter(k =>
38
+ k !== 'prototype' && k !== 'length' && k !== 'name' && k !== 'caller' && k !== 'arguments'
39
+ ).join(', ')}`);
40
+ }
41
+ }
42
+ return library;
43
+ }
44
+
45
+ // 3. Handle object with single property (auto-detect pattern)
46
+ if (libraryType === 'object') {
47
+ const objectKeys = Object.keys(library).filter(key => key !== '__esModule' && key !== 'default');
48
+
49
+ if (objectKeys.length === 1 && typeof library[objectKeys[0]] === 'function') {
50
+ if (debug) {
51
+ console.log(`📦 Library has single exported function '${objectKeys[0]}', using it for '${exportName}'`);
52
+ }
53
+ return library[objectKeys[0]];
54
+ }
55
+
56
+ // Also check if library.default is the only meaningful export
57
+ if (objectKeys.length === 0 && library.default && typeof library.default === 'function') {
58
+ if (debug) {
59
+ console.log(`📦 Library only has default export (function), using it for '${exportName}'`);
60
+ }
61
+ return library.default;
62
+ }
63
+ }
64
+
65
+ // 4. Standard unwrapping for complex libraries
66
+ if (debug) {
67
+ console.log(`📦 Library is a complex object, attempting standard unwrapping for '${exportName}'`);
68
+ }
69
+
70
+ // Check if exportName contains dot notation (e.g., 'DatePicker.RangePicker')
71
+ if (exportName.includes('.')) {
72
+ const parts = exportName.split('.');
73
+ let current = library;
74
+ let pathTaken = 'library';
75
+
76
+ // Navigate through the path
77
+ for (let i = 0; i < parts.length; i++) {
78
+ const part = parts[i];
79
+
80
+ if (!current) {
81
+ if (debug) {
82
+ console.log(` ❌ Cannot access '${part}' - parent is null/undefined at path: ${pathTaken}`);
83
+ }
84
+ return undefined;
85
+ }
86
+
87
+ // For the first part, try various unwrapping strategies
88
+ if (i === 0) {
89
+ // Try different paths for the first component
90
+ if (debug) {
91
+ console.log(` Checking library.${part}...`);
92
+ }
93
+ const firstComponent = current[part];
94
+
95
+ if (debug) {
96
+ console.log(` Checking library.default.${part}...`);
97
+ }
98
+ const firstDefault = current.default?.[part];
99
+
100
+ if (firstComponent) {
101
+ current = firstComponent;
102
+ pathTaken = `library.${part}`;
103
+ if (debug) {
104
+ console.log(` ✅ Found via ${pathTaken}`);
105
+ }
106
+ } else if (firstDefault) {
107
+ current = firstDefault;
108
+ pathTaken = `library.default.${part}`;
109
+ if (debug) {
110
+ console.log(` ✅ Found via ${pathTaken}`);
111
+ }
112
+ } else {
113
+ if (debug) {
114
+ console.log(` ❌ Could not find '${part}' in library`);
115
+ }
116
+ return undefined;
117
+ }
118
+ } else {
119
+ // For nested parts, just access directly (they should be properties on the component object)
120
+ if (debug) {
121
+ console.log(` Checking ${pathTaken}.${part}...`);
122
+ }
123
+ if (current[part]) {
124
+ current = current[part];
125
+ pathTaken += `.${part}`;
126
+ if (debug) {
127
+ console.log(` ✅ Found via ${pathTaken}`);
128
+ }
129
+ } else if (current.default?.[part]) {
130
+ // Sometimes nested components are also wrapped in default
131
+ current = current.default[part];
132
+ pathTaken += `.default.${part}`;
133
+ if (debug) {
134
+ console.log(` ✅ Found via ${pathTaken}.default`);
135
+ }
136
+ } else {
137
+ if (debug) {
138
+ console.log(` ❌ Could not find '${part}' at path: ${pathTaken}`);
139
+ }
140
+ return undefined;
141
+ }
142
+ }
143
+ }
144
+
145
+ // Check if the final result is a valid export (component, utility, object, etc.)
146
+ // We accept anything except null/undefined
147
+ const isValidExport = (obj: any): boolean => {
148
+ return obj != null; // This checks for both null and undefined
149
+ };
150
+
151
+ if (isValidExport(current)) {
152
+ if (debug) {
153
+ console.log(`✅ Successfully unwrapped '${exportName}' via: ${pathTaken}`);
154
+ const exportType = typeof current === 'function' ?
155
+ (current.prototype?.isReactComponent ? 'React class component' : 'function/component') :
156
+ (typeof current === 'object' ? 'object/utility' : typeof current);
157
+ console.log(` Export type: ${exportType}`);
158
+ }
159
+ return current;
160
+ } else if (current?.default && isValidExport(current.default)) {
161
+ // One more check: the final export might have a default export
162
+ if (debug) {
163
+ console.log(`✅ Successfully unwrapped '${exportName}' via: ${pathTaken}.default`);
164
+ }
165
+ return current.default;
166
+ } else {
167
+ if (debug) {
168
+ console.log(` ❌ '${exportName}' at ${pathTaken} is null/undefined`);
169
+ }
170
+ return undefined;
171
+ }
172
+ }
173
+
174
+ // Original logic for non-dot notation components
175
+ // Track which path we used for debugging
176
+ let unwrapPath: string = '';
177
+ let component: any;
178
+
179
+ // Helper to check if something is a valid export (any non-null value)
180
+ // This allows components, utilities, objects, classes, etc.
181
+ const isValidExport = (obj: any): boolean => {
182
+ return obj != null; // Checks for both null and undefined
183
+ };
184
+
185
+ // Path 1: Direct access (library.Component)
186
+ if (debug) {
187
+ console.log(` Checking library.${exportName}...`);
188
+ }
189
+ const directComponent = library[exportName];
190
+
191
+ // Path 2: Module with default export (library.Component.default)
192
+ if (debug && directComponent) {
193
+ console.log(` Checking library.${exportName}.default...`);
194
+ }
195
+ const moduleDefault = directComponent?.default;
196
+
197
+ // Path 3: Library wrapped in default (library.default.Component)
198
+ if (debug && library.default) {
199
+ console.log(` Checking library.default.${exportName}...`);
200
+ }
201
+ const libraryDefault = library.default?.[exportName];
202
+
203
+ // Path 4: Library wrapped in default with module default (library.default.Component.default)
204
+ if (debug && libraryDefault) {
205
+ console.log(` Checking library.default.${exportName}.default...`);
206
+ }
207
+ const libraryDefaultModule = library.default?.[exportName]?.default;
208
+
209
+ // Check each path in order of likelihood
210
+ if (isValidExport(directComponent)) {
211
+ component = directComponent;
212
+ unwrapPath = `library.${exportName}`;
213
+ if (debug) {
214
+ console.log(` ✅ Found via ${unwrapPath}`);
215
+ const exportType = typeof directComponent === 'function' ? 'function' :
216
+ typeof directComponent === 'object' ? 'object' : typeof directComponent;
217
+ console.log(` Export type: ${exportType}`);
218
+ }
219
+ } else if (isValidExport(moduleDefault)) {
220
+ component = moduleDefault;
221
+ unwrapPath = `library.${exportName}.default`;
222
+ if (debug) {
223
+ console.log(` ✅ Found via ${unwrapPath}`);
224
+ }
225
+ } else if (isValidExport(libraryDefault)) {
226
+ component = libraryDefault;
227
+ unwrapPath = `library.default.${exportName}`;
228
+ if (debug) {
229
+ console.log(` ✅ Found via ${unwrapPath}`);
230
+ }
231
+ } else if (isValidExport(libraryDefaultModule)) {
232
+ component = libraryDefaultModule;
233
+ unwrapPath = `library.default.${exportName}.default`;
234
+ if (debug) {
235
+ console.log(` ✅ Found via ${unwrapPath}`);
236
+ }
237
+ }
238
+
239
+ // Debug logging for failure cases
240
+ if (debug && !component) {
241
+ console.log(` ❌ Could not unwrap '${exportName}' from library (all paths were null/undefined)`);
242
+ console.log(' Library structure:');
243
+ console.log(' - Top-level keys:', Object.keys(library).slice(0, 10).join(', '),
244
+ Object.keys(library).length > 10 ? '...' : '');
245
+
246
+ console.log(` - library.${exportName}:`, directComponent);
247
+
248
+ if (library.default) {
249
+ console.log(' - library.default exists, keys:',
250
+ Object.keys(library.default).slice(0, 10).join(', '));
251
+ }
252
+ }
253
+
254
+ return component;
255
+ }
256
+
257
+ /**
258
+ * Unwraps a single export from a UMD library, handling various bundle structures
259
+ * Works with components, utilities, objects, functions, and any other exports
260
+ * @param library - The library object (e.g., antd, MaterialUI, XLSX)
261
+ * @param exportName - Name of the export to unwrap (e.g., 'Button', 'utils', 'writeFile')
262
+ * @param debug - Whether to log debug information
263
+ * @returns The unwrapped export or undefined if not found
264
+ */
265
+ export function unwrapLibraryComponent(library: any, exportName: string, debug: boolean = false): any {
266
+ try {
267
+ return coreUnwrapLogic(library, exportName, debug);
268
+ } catch (error: any) {
269
+ if (debug) {
270
+ console.error(`🚫 Error unwrapping export '${exportName}':`, error.message);
271
+ }
272
+ throw error;
273
+ }
274
+ }
275
+
276
+ /**
277
+ * Unwraps multiple exports from a UMD library using varargs
278
+ * Works with any type of export: components, utilities, objects, functions, etc.
279
+ * @param library - The library object (e.g., antd, XLSX, ApexCharts)
280
+ * @param exportNames - Export names to unwrap (e.g., 'Button', 'utils', 'writeFile')
281
+ * @returns Object with export names as keys and unwrapped values as values
282
+ */
283
+ export function unwrapLibraryComponents(
284
+ library: any,
285
+ ...exportNames: string[]
286
+ ): Record<string, any> {
287
+ // Determine debug mode from the last parameter if it's a boolean
288
+ let debug = false;
289
+ let names = exportNames;
290
+
291
+ // Check if the last argument is a debug flag (for backward compatibility)
292
+ const lastArg = exportNames[exportNames.length - 1];
293
+ if (typeof lastArg === 'boolean') {
294
+ debug = lastArg;
295
+ names = exportNames.slice(0, -1);
296
+ }
297
+
298
+ const components: Record<string, any> = {};
299
+ const found: string[] = [];
300
+ const notFound: string[] = [];
301
+
302
+ // Check if library is a simple function/class that should be returned for all names
303
+ const libraryType = typeof library;
304
+
305
+ // Handle null/undefined/primitives early
306
+ if (library == null ||
307
+ libraryType === 'string' ||
308
+ libraryType === 'number' ||
309
+ libraryType === 'boolean' ||
310
+ libraryType === 'symbol' ||
311
+ libraryType === 'bigint') {
312
+ // Let the core logic throw the appropriate error
313
+ try {
314
+ coreUnwrapLogic(library, names[0] || 'unknown', debug);
315
+ } catch (error: any) {
316
+ if (debug) {
317
+ console.error(`🚫 ${error.message}`);
318
+ }
319
+ throw error;
320
+ }
321
+ }
322
+
323
+ // For simple functions/classes, return the same function for all requested names
324
+ if (libraryType === 'function') {
325
+ if (debug) {
326
+ console.log(`📦 Library is a single function/class, returning it for all ${names.length} requested names`);
327
+ }
328
+ for (const name of names) {
329
+ components[name] = library;
330
+ found.push(name);
331
+ }
332
+ } else {
333
+ // For complex objects, use the core unwrap logic for each component
334
+ for (const name of names) {
335
+ try {
336
+ const component = coreUnwrapLogic(library, name, false); // Use false to avoid verbose logging per component
337
+ if (component) {
338
+ components[name] = component;
339
+
340
+ // Also provide a convenience mapping for the last part of the name
341
+ // e.g., 'DatePicker.RangePicker' also available as 'RangePicker'
342
+ if (name.includes('.')) {
343
+ const lastPart = name.split('.').pop();
344
+ if (lastPart && !components[lastPart]) {
345
+ // Only add the short name if it doesn't conflict with another component
346
+ components[lastPart] = component;
347
+ }
348
+ }
349
+
350
+ found.push(name);
351
+ } else {
352
+ notFound.push(name);
353
+ }
354
+ } catch (error: any) {
355
+ // If core logic throws an error, it's a critical failure
356
+ throw error;
357
+ }
358
+ }
359
+ }
360
+
361
+ if (debug) {
362
+ if (found.length > 0) {
363
+ console.log(`📦 Successfully unwrapped ${found.length} components:`, found.join(', '));
364
+ }
365
+ if (notFound.length > 0) {
366
+ console.warn(`⚠️ Could not unwrap ${notFound.length} components:`, notFound.join(', '));
367
+ }
368
+ }
369
+
370
+ return components;
371
+ }
372
+
373
+ /**
374
+ * Auto-detects and unwraps all components from a library
375
+ * Uses PascalCase detection to identify likely component exports
376
+ * @param library - The library object
377
+ * @param debug - Whether to log debug information
378
+ * @returns Object with all detected components
379
+ */
380
+ export function unwrapAllLibraryComponents(library: any, debug: boolean = false): Record<string, any> {
381
+ const components: Record<string, any> = {};
382
+
383
+ // Handle simple function/class case
384
+ const libraryType = typeof library;
385
+
386
+ // Handle null/undefined/primitives early
387
+ if (library == null ||
388
+ libraryType === 'string' ||
389
+ libraryType === 'number' ||
390
+ libraryType === 'boolean' ||
391
+ libraryType === 'symbol' ||
392
+ libraryType === 'bigint') {
393
+ try {
394
+ coreUnwrapLogic(library, 'auto-detect', debug);
395
+ } catch (error: any) {
396
+ if (debug) {
397
+ console.error(`🚫 ${error.message}`);
398
+ }
399
+ throw error;
400
+ }
401
+ }
402
+
403
+ // For simple functions/classes, return it with its name
404
+ if (libraryType === 'function') {
405
+ const functionName = library.name || 'Component';
406
+ if (debug) {
407
+ console.log(`📦 Library is a single function/class '${functionName}', returning it as the only component`);
408
+ }
409
+ components[functionName] = library;
410
+ return components;
411
+ }
412
+
413
+ // Helper to check if a key looks like a component name (PascalCase)
414
+ const looksLikeComponentName = (key: string): boolean => {
415
+ if (!key || key.length === 0) return false;
416
+ // Must start with uppercase letter
417
+ if (key[0] !== key[0].toUpperCase()) return false;
418
+ // Exclude common non-component exports
419
+ if (['__esModule', 'VERSION', 'version', 'default'].includes(key)) return false;
420
+ // Should have at least one lowercase letter (to distinguish from CONSTANTS)
421
+ return /[a-z]/.test(key);
422
+ };
423
+
424
+ const checkedKeys = new Set<string>();
425
+
426
+ // Check direct properties
427
+ for (const key of Object.keys(library)) {
428
+ if (looksLikeComponentName(key)) {
429
+ try {
430
+ const component = coreUnwrapLogic(library, key, false);
431
+ if (component) {
432
+ components[key] = component;
433
+ checkedKeys.add(key);
434
+ }
435
+ } catch (error) {
436
+ // Skip components that can't be unwrapped
437
+ }
438
+ }
439
+ }
440
+
441
+ // Check library.default properties if it exists
442
+ if (library.default && typeof library.default === 'object') {
443
+ for (const key of Object.keys(library.default)) {
444
+ if (!checkedKeys.has(key) && looksLikeComponentName(key)) {
445
+ try {
446
+ const component = coreUnwrapLogic(library, key, false);
447
+ if (component) {
448
+ components[key] = component;
449
+ checkedKeys.add(key);
450
+ }
451
+ } catch (error) {
452
+ // Skip components that can't be unwrapped
453
+ }
454
+ }
455
+ }
456
+ }
457
+
458
+ if (debug) {
459
+ const exportNames = Object.keys(components);
460
+ if (exportNames.length > 0) {
461
+ console.log(`🔍 Auto-detected ${exportNames.length} components from library`);
462
+ console.log(' Components:', exportNames.slice(0, 20).join(', '),
463
+ exportNames.length > 20 ? `... and ${exportNames.length - 20} more` : '');
464
+ } else {
465
+ console.warn('⚠️ No components auto-detected from library');
466
+ console.log(' Library type:', typeof library);
467
+ if (library) {
468
+ console.log(' Top-level keys:', Object.keys(library).slice(0, 10).join(', '));
469
+ }
470
+ }
471
+ }
472
+
473
+ return components;
474
+ }
475
+
476
+ // Legacy exports for backward compatibility
477
+ export const unwrapComponent = unwrapLibraryComponent;
478
+ export const unwrapComponents = (library: any, exportNames: string[], debug: boolean = false) => {
479
+ return unwrapLibraryComponents(library, ...exportNames, debug as any);
480
+ };
481
+ export const unwrapAllComponents = unwrapAllLibraryComponents;
@@ -10,4 +10,5 @@ export * from './library-registry';
10
10
  export * from './library-dependency-resolver';
11
11
  export * from './component-error-analyzer';
12
12
  export * from './resource-manager';
13
- export * from './cache-manager';
13
+ export * from './cache-manager';
14
+ export * from './component-unwrapper';
@@ -476,6 +476,17 @@ export class LibraryLoader {
476
476
  continue;
477
477
  }
478
478
 
479
+ // Check library status
480
+ if (library.Status) {
481
+ if (library.Status === 'Disabled') {
482
+ console.error(`🚫 ERROR: Library '${library.Name}' is DISABLED and should not be used`);
483
+ // Continue loading anyway per requirements
484
+ } else if (library.Status === 'Deprecated') {
485
+ console.warn(`⚠️ WARNING: Library '${library.Name}' is DEPRECATED. Consider using an alternative.`);
486
+ }
487
+ // Active status is fine, no message needed
488
+ }
489
+
479
490
  if (debug) {
480
491
  console.log(`📥 Loading '${library.Name}@${library.Version}'`);
481
492
  }
@@ -604,6 +615,17 @@ export class LibraryLoader {
604
615
  continue;
605
616
  }
606
617
 
618
+ // Check library status
619
+ if (library.Status) {
620
+ if (library.Status === 'Disabled') {
621
+ console.error(`🚫 ERROR: Library '${library.Name}' is DISABLED and should not be used`);
622
+ // Continue loading anyway per requirements
623
+ } else if (library.Status === 'Deprecated') {
624
+ console.warn(`⚠️ WARNING: Library '${library.Name}' is DEPRECATED. Consider using an alternative.`);
625
+ }
626
+ // Active status is fine, no message needed
627
+ }
628
+
607
629
  if (debug) {
608
630
  console.log(`📥 Loading '${library.Name}@${library.Version}'`);
609
631
  }