@memberjunction/react-runtime 2.70.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.
Files changed (58) hide show
  1. package/.turbo/turbo-build.log +4 -0
  2. package/CHANGELOG.md +3 -0
  3. package/README.md +224 -0
  4. package/dist/compiler/babel-config.d.ts +40 -0
  5. package/dist/compiler/babel-config.d.ts.map +1 -0
  6. package/dist/compiler/babel-config.js +52 -0
  7. package/dist/compiler/component-compiler.d.ts +22 -0
  8. package/dist/compiler/component-compiler.d.ts.map +1 -0
  9. package/dist/compiler/component-compiler.js +188 -0
  10. package/dist/compiler/index.d.ts +3 -0
  11. package/dist/compiler/index.d.ts.map +1 -0
  12. package/dist/compiler/index.js +13 -0
  13. package/dist/index.d.ts +41 -0
  14. package/dist/index.d.ts.map +1 -0
  15. package/dist/index.js +95 -0
  16. package/dist/registry/component-registry.d.ts +32 -0
  17. package/dist/registry/component-registry.d.ts.map +1 -0
  18. package/dist/registry/component-registry.js +197 -0
  19. package/dist/registry/component-resolver.d.ts +29 -0
  20. package/dist/registry/component-resolver.d.ts.map +1 -0
  21. package/dist/registry/component-resolver.js +112 -0
  22. package/dist/registry/index.d.ts +3 -0
  23. package/dist/registry/index.d.ts.map +1 -0
  24. package/dist/registry/index.js +7 -0
  25. package/dist/runtime/component-hierarchy.d.ts +44 -0
  26. package/dist/runtime/component-hierarchy.d.ts.map +1 -0
  27. package/dist/runtime/component-hierarchy.js +162 -0
  28. package/dist/runtime/component-wrapper.d.ts +18 -0
  29. package/dist/runtime/component-wrapper.d.ts.map +1 -0
  30. package/dist/runtime/component-wrapper.js +108 -0
  31. package/dist/runtime/error-boundary.d.ts +6 -0
  32. package/dist/runtime/error-boundary.d.ts.map +1 -0
  33. package/dist/runtime/error-boundary.js +139 -0
  34. package/dist/runtime/index.d.ts +5 -0
  35. package/dist/runtime/index.d.ts.map +1 -0
  36. package/dist/runtime/index.js +31 -0
  37. package/dist/runtime/prop-builder.d.ts +16 -0
  38. package/dist/runtime/prop-builder.d.ts.map +1 -0
  39. package/dist/runtime/prop-builder.js +161 -0
  40. package/dist/types/index.d.ts +98 -0
  41. package/dist/types/index.d.ts.map +1 -0
  42. package/dist/types/index.js +2 -0
  43. package/package.json +36 -0
  44. package/src/compiler/babel-config.ts +97 -0
  45. package/src/compiler/component-compiler.ts +366 -0
  46. package/src/compiler/index.ts +15 -0
  47. package/src/index.ts +125 -0
  48. package/src/registry/component-registry.ts +379 -0
  49. package/src/registry/component-resolver.ts +275 -0
  50. package/src/registry/index.ts +7 -0
  51. package/src/runtime/component-hierarchy.ts +346 -0
  52. package/src/runtime/component-wrapper.ts +249 -0
  53. package/src/runtime/error-boundary.ts +242 -0
  54. package/src/runtime/index.ts +45 -0
  55. package/src/runtime/prop-builder.ts +290 -0
  56. package/src/types/index.ts +226 -0
  57. package/tsconfig.json +37 -0
  58. package/tsconfig.tsbuildinfo +1 -0
@@ -0,0 +1,346 @@
1
+ /**
2
+ * @fileoverview Component hierarchy registration utilities for MemberJunction React Runtime.
3
+ * Provides functionality to register a hierarchy of components from Skip component specifications.
4
+ * @module @memberjunction/react-runtime/hierarchy
5
+ */
6
+
7
+ import {
8
+ CompilationResult,
9
+ CompileOptions,
10
+ ComponentStyles,
11
+ RuntimeContext
12
+ } from '../types';
13
+ import { ComponentCompiler } from '../compiler';
14
+ import { ComponentRegistry } from '../registry';
15
+
16
+ import { ComponentSpec } from '../registry/component-resolver';
17
+
18
+ /**
19
+ * Result of a hierarchy registration operation
20
+ */
21
+ export interface HierarchyRegistrationResult {
22
+ success: boolean;
23
+ registeredComponents: string[];
24
+ errors: ComponentRegistrationError[];
25
+ warnings: string[];
26
+ }
27
+
28
+ /**
29
+ * Error information for component registration
30
+ */
31
+ export interface ComponentRegistrationError {
32
+ componentName: string;
33
+ error: string;
34
+ phase: 'compilation' | 'registration' | 'validation';
35
+ }
36
+
37
+ /**
38
+ * Options for hierarchy registration
39
+ */
40
+ export interface HierarchyRegistrationOptions {
41
+ /** Component styles to apply to all components */
42
+ styles?: ComponentStyles;
43
+ /** Namespace for component registration */
44
+ namespace?: string;
45
+ /** Version for component registration */
46
+ version?: string;
47
+ /** Whether to continue on errors */
48
+ continueOnError?: boolean;
49
+ /** Whether to override existing components */
50
+ allowOverride?: boolean;
51
+ }
52
+
53
+ /**
54
+ * Utility class for registering component hierarchies
55
+ */
56
+ export class ComponentHierarchyRegistrar {
57
+ constructor(
58
+ private compiler: ComponentCompiler,
59
+ private registry: ComponentRegistry,
60
+ private runtimeContext: RuntimeContext
61
+ ) {}
62
+
63
+ /**
64
+ * Registers a complete component hierarchy from a root specification
65
+ * @param rootSpec - The root component specification
66
+ * @param options - Registration options
67
+ * @returns Registration result with details about success/failures
68
+ */
69
+ async registerHierarchy(
70
+ rootSpec: ComponentSpec,
71
+ options: HierarchyRegistrationOptions = {}
72
+ ): Promise<HierarchyRegistrationResult> {
73
+ const {
74
+ styles,
75
+ namespace = 'Global',
76
+ version = 'v1',
77
+ continueOnError = true,
78
+ allowOverride = true
79
+ } = options;
80
+
81
+ const registeredComponents: string[] = [];
82
+ const errors: ComponentRegistrationError[] = [];
83
+ const warnings: string[] = [];
84
+
85
+ // Register the root component
86
+ const rootResult = await this.registerSingleComponent(
87
+ rootSpec,
88
+ { styles, namespace, version, allowOverride }
89
+ );
90
+
91
+ if (rootResult.success) {
92
+ registeredComponents.push(rootSpec.componentName);
93
+ } else {
94
+ errors.push(rootResult.error!);
95
+ if (!continueOnError) {
96
+ return { success: false, registeredComponents, errors, warnings };
97
+ }
98
+ }
99
+
100
+ // Register child components recursively
101
+ const childComponents = rootSpec.childComponents || rootSpec.components || [];
102
+ if (childComponents.length > 0) {
103
+ const childResult = await this.registerChildComponents(
104
+ childComponents,
105
+ { styles, namespace, version, continueOnError, allowOverride },
106
+ registeredComponents,
107
+ errors,
108
+ warnings
109
+ );
110
+ }
111
+
112
+ return {
113
+ success: errors.length === 0,
114
+ registeredComponents,
115
+ errors,
116
+ warnings
117
+ };
118
+ }
119
+
120
+ /**
121
+ * Registers a single component from a specification
122
+ * @param spec - Component specification
123
+ * @param options - Registration options
124
+ * @returns Registration result for this component
125
+ */
126
+ async registerSingleComponent(
127
+ spec: ComponentSpec,
128
+ options: {
129
+ styles?: ComponentStyles;
130
+ namespace?: string;
131
+ version?: string;
132
+ allowOverride?: boolean;
133
+ }
134
+ ): Promise<{ success: boolean; error?: ComponentRegistrationError }> {
135
+ const { styles, namespace = 'Global', version = 'v1', allowOverride = true } = options;
136
+
137
+ try {
138
+ // Skip if no component code
139
+ if (!spec.componentCode) {
140
+ return {
141
+ success: true,
142
+ error: undefined
143
+ };
144
+ }
145
+
146
+ // Check if component already exists
147
+ const existingComponent = this.registry.get(spec.componentName, namespace, version);
148
+ if (existingComponent && !allowOverride) {
149
+ return {
150
+ success: false,
151
+ error: {
152
+ componentName: spec.componentName,
153
+ error: `Component already registered in ${namespace}/${version}`,
154
+ phase: 'registration'
155
+ }
156
+ };
157
+ }
158
+
159
+ // Compile the component
160
+ const compileOptions: CompileOptions = {
161
+ componentName: spec.componentName,
162
+ componentCode: spec.componentCode,
163
+ styles
164
+ };
165
+
166
+ const compilationResult = await this.compiler.compile(compileOptions);
167
+
168
+ if (!compilationResult.success) {
169
+ return {
170
+ success: false,
171
+ error: {
172
+ componentName: spec.componentName,
173
+ error: compilationResult.error?.message || 'Unknown compilation error',
174
+ phase: 'compilation'
175
+ }
176
+ };
177
+ }
178
+
179
+ // Create component factory
180
+ const componentFactory = compilationResult.component!.component(this.runtimeContext, styles);
181
+
182
+ // Register the component
183
+ this.registry.register(
184
+ spec.componentName,
185
+ componentFactory.component,
186
+ namespace,
187
+ version
188
+ );
189
+
190
+ return { success: true };
191
+
192
+ } catch (error) {
193
+ return {
194
+ success: false,
195
+ error: {
196
+ componentName: spec.componentName,
197
+ error: error instanceof Error ? error.message : String(error),
198
+ phase: 'registration'
199
+ }
200
+ };
201
+ }
202
+ }
203
+
204
+ /**
205
+ * Recursively registers child components
206
+ * @param children - Array of child component specifications
207
+ * @param options - Registration options
208
+ * @param registeredComponents - Array to track registered components
209
+ * @param errors - Array to collect errors
210
+ * @param warnings - Array to collect warnings
211
+ */
212
+ private async registerChildComponents(
213
+ children: ComponentSpec[],
214
+ options: HierarchyRegistrationOptions,
215
+ registeredComponents: string[],
216
+ errors: ComponentRegistrationError[],
217
+ warnings: string[]
218
+ ): Promise<void> {
219
+ for (const child of children) {
220
+ // Register this child
221
+ const childResult = await this.registerSingleComponent(child, {
222
+ styles: options.styles,
223
+ namespace: options.namespace,
224
+ version: options.version,
225
+ allowOverride: options.allowOverride
226
+ });
227
+
228
+ if (childResult.success) {
229
+ if (child.componentCode) {
230
+ registeredComponents.push(child.componentName);
231
+ }
232
+ } else {
233
+ errors.push(childResult.error!);
234
+ if (!options.continueOnError) {
235
+ return;
236
+ }
237
+ }
238
+
239
+ // Register nested children recursively
240
+ const nestedChildren = child.childComponents || child.components || [];
241
+ if (nestedChildren.length > 0) {
242
+ await this.registerChildComponents(
243
+ nestedChildren,
244
+ options,
245
+ registeredComponents,
246
+ errors,
247
+ warnings
248
+ );
249
+ }
250
+ }
251
+ }
252
+ }
253
+
254
+ /**
255
+ * Convenience function to register a component hierarchy
256
+ * @param rootSpec - The root component specification
257
+ * @param compiler - Component compiler instance
258
+ * @param registry - Component registry instance
259
+ * @param runtimeContext - Runtime context with React and other libraries
260
+ * @param options - Registration options
261
+ * @returns Registration result
262
+ */
263
+ export async function registerComponentHierarchy(
264
+ rootSpec: ComponentSpec,
265
+ compiler: ComponentCompiler,
266
+ registry: ComponentRegistry,
267
+ runtimeContext: RuntimeContext,
268
+ options: HierarchyRegistrationOptions = {}
269
+ ): Promise<HierarchyRegistrationResult> {
270
+ const registrar = new ComponentHierarchyRegistrar(compiler, registry, runtimeContext);
271
+ return registrar.registerHierarchy(rootSpec, options);
272
+ }
273
+
274
+ /**
275
+ * Validates a component specification before registration
276
+ * @param spec - Component specification to validate
277
+ * @returns Array of validation errors (empty if valid)
278
+ */
279
+ export function validateComponentSpec(spec: ComponentSpec): string[] {
280
+ const errors: string[] = [];
281
+
282
+ if (!spec.componentName) {
283
+ errors.push('Component specification must have a componentName');
284
+ }
285
+
286
+ // If componentCode is provided, do basic validation
287
+ if (spec.componentCode) {
288
+ if (typeof spec.componentCode !== 'string') {
289
+ errors.push(`Component code for ${spec.componentName} must be a string`);
290
+ }
291
+ if (spec.componentCode.trim().length === 0) {
292
+ errors.push(`Component code for ${spec.componentName} cannot be empty`);
293
+ }
294
+ }
295
+
296
+ // Validate child components recursively
297
+ const children = spec.childComponents || spec.components || [];
298
+ children.forEach((child, index) => {
299
+ const childErrors = validateComponentSpec(child);
300
+ childErrors.forEach(error => {
301
+ errors.push(`Child ${index} (${child.componentName || 'unnamed'}): ${error}`);
302
+ });
303
+ });
304
+
305
+ return errors;
306
+ }
307
+
308
+ /**
309
+ * Flattens a component hierarchy into a list of all components
310
+ * @param rootSpec - The root component specification
311
+ * @returns Array of all component specifications in the hierarchy
312
+ */
313
+ export function flattenComponentHierarchy(rootSpec: ComponentSpec): ComponentSpec[] {
314
+ const components: ComponentSpec[] = [rootSpec];
315
+
316
+ const children = rootSpec.childComponents || rootSpec.components || [];
317
+ children.forEach(child => {
318
+ components.push(...flattenComponentHierarchy(child));
319
+ });
320
+
321
+ return components;
322
+ }
323
+
324
+ /**
325
+ * Counts the total number of components in a hierarchy
326
+ * @param rootSpec - The root component specification
327
+ * @param includeEmpty - Whether to include components without code
328
+ * @returns Total component count
329
+ */
330
+ export function countComponentsInHierarchy(
331
+ rootSpec: ComponentSpec,
332
+ includeEmpty: boolean = false
333
+ ): number {
334
+ let count = 0;
335
+
336
+ if (includeEmpty || rootSpec.componentCode) {
337
+ count = 1;
338
+ }
339
+
340
+ const children = rootSpec.childComponents || rootSpec.components || [];
341
+ children.forEach(child => {
342
+ count += countComponentsInHierarchy(child, includeEmpty);
343
+ });
344
+
345
+ return count;
346
+ }
@@ -0,0 +1,249 @@
1
+ /**
2
+ * @fileoverview React component wrapper utilities.
3
+ * Provides HOCs and utilities for wrapping components with additional functionality.
4
+ * @module @memberjunction/react-runtime/runtime
5
+ */
6
+
7
+ import { ComponentProps, ComponentLifecycle } from '../types';
8
+
9
+ /**
10
+ * Options for wrapping a component
11
+ */
12
+ export interface WrapperOptions {
13
+ /** Component display name */
14
+ displayName?: string;
15
+ /** Enable performance monitoring */
16
+ enableProfiling?: boolean;
17
+ /** Enable props logging */
18
+ logProps?: boolean;
19
+ /** Lifecycle hooks */
20
+ lifecycle?: ComponentLifecycle;
21
+ /** Default props */
22
+ defaultProps?: Partial<ComponentProps>;
23
+ }
24
+
25
+ /**
26
+ * Wraps a React component with additional functionality
27
+ * @param React - React library instance
28
+ * @param Component - Component to wrap
29
+ * @param options - Wrapper options
30
+ * @returns Wrapped component
31
+ */
32
+ export function wrapComponent(React: any, Component: any, options: WrapperOptions = {}): any {
33
+ const {
34
+ displayName,
35
+ enableProfiling = false,
36
+ logProps = false,
37
+ lifecycle = {},
38
+ defaultProps = {}
39
+ } = options;
40
+
41
+ // Create wrapper component
42
+ const WrappedComponent = React.forwardRef((props: any, ref: any) => {
43
+ const mergedProps = { ...defaultProps, ...props };
44
+
45
+ // Log props if enabled
46
+ React.useEffect(() => {
47
+ if (logProps) {
48
+ console.log(`[${displayName || Component.name}] Props:`, mergedProps);
49
+ }
50
+ }, [mergedProps]);
51
+
52
+ // Lifecycle: beforeMount
53
+ React.useEffect(() => {
54
+ if (lifecycle.beforeMount) {
55
+ lifecycle.beforeMount();
56
+ }
57
+
58
+ // Lifecycle: afterMount
59
+ if (lifecycle.afterMount) {
60
+ lifecycle.afterMount();
61
+ }
62
+
63
+ // Lifecycle: beforeUnmount
64
+ return () => {
65
+ if (lifecycle.beforeUnmount) {
66
+ lifecycle.beforeUnmount();
67
+ }
68
+ };
69
+ }, []);
70
+
71
+ // Lifecycle: beforeUpdate/afterUpdate
72
+ const prevPropsRef = React.useRef(mergedProps);
73
+ React.useEffect(() => {
74
+ const prevProps = prevPropsRef.current;
75
+
76
+ if (lifecycle.beforeUpdate) {
77
+ lifecycle.beforeUpdate(prevProps, mergedProps);
78
+ }
79
+
80
+ // Schedule afterUpdate
81
+ if (lifecycle.afterUpdate) {
82
+ Promise.resolve().then(() => {
83
+ lifecycle.afterUpdate!(prevProps, mergedProps);
84
+ });
85
+ }
86
+
87
+ prevPropsRef.current = mergedProps;
88
+ });
89
+
90
+ // Render with profiling if enabled
91
+ if (enableProfiling && React.Profiler) {
92
+ return React.createElement(
93
+ React.Profiler,
94
+ {
95
+ id: displayName || Component.name || 'WrappedComponent',
96
+ onRender: (id: string, phase: string, actualDuration: number) => {
97
+ console.log(`[Profiler] ${id} (${phase}): ${actualDuration.toFixed(2)}ms`);
98
+ }
99
+ },
100
+ React.createElement(Component, { ...mergedProps, ref })
101
+ );
102
+ }
103
+
104
+ return React.createElement(Component, { ...mergedProps, ref });
105
+ });
106
+
107
+ // Set display name
108
+ WrappedComponent.displayName = displayName || `Wrapped(${Component.displayName || Component.name || 'Component'})`;
109
+
110
+ return WrappedComponent;
111
+ }
112
+
113
+ /**
114
+ * Creates a memoized version of a component
115
+ * @param React - React library instance
116
+ * @param Component - Component to memoize
117
+ * @param propsAreEqual - Optional comparison function
118
+ * @returns Memoized component
119
+ */
120
+ export function memoizeComponent(
121
+ React: any,
122
+ Component: any,
123
+ propsAreEqual?: (prevProps: any, nextProps: any) => boolean
124
+ ): any {
125
+ return React.memo(Component, propsAreEqual);
126
+ }
127
+
128
+ /**
129
+ * Creates a lazy-loaded component
130
+ * @param React - React library instance
131
+ * @param loader - Function that returns a promise resolving to the component
132
+ * @param fallback - Optional loading fallback
133
+ * @returns Lazy component
134
+ */
135
+ export function lazyComponent(
136
+ React: any,
137
+ loader: () => Promise<{ default: any }>,
138
+ fallback?: any
139
+ ): any {
140
+ const LazyComponent = React.lazy(loader);
141
+
142
+ if (fallback) {
143
+ return (props: any) => React.createElement(
144
+ React.Suspense,
145
+ { fallback },
146
+ React.createElement(LazyComponent, props)
147
+ );
148
+ }
149
+
150
+ return LazyComponent;
151
+ }
152
+
153
+ /**
154
+ * Injects additional props into a component
155
+ * @param React - React library instance
156
+ * @param Component - Component to inject props into
157
+ * @param injectedProps - Props to inject
158
+ * @returns Component with injected props
159
+ */
160
+ export function injectProps(React: any, Component: any, injectedProps: any): any {
161
+ return React.forwardRef((props: any, ref: any) => {
162
+ const mergedProps = { ...injectedProps, ...props };
163
+ return React.createElement(Component, { ...mergedProps, ref });
164
+ });
165
+ }
166
+
167
+ /**
168
+ * Creates a component that renders conditionally
169
+ * @param React - React library instance
170
+ * @param Component - Component to render conditionally
171
+ * @param condition - Condition function or boolean
172
+ * @param fallback - Optional fallback component
173
+ * @returns Conditional component
174
+ */
175
+ export function conditionalComponent(
176
+ React: any,
177
+ Component: any,
178
+ condition: boolean | ((props: any) => boolean),
179
+ fallback?: any
180
+ ): any {
181
+ return (props: any) => {
182
+ const shouldRender = typeof condition === 'function' ? condition(props) : condition;
183
+
184
+ if (shouldRender) {
185
+ return React.createElement(Component, props);
186
+ }
187
+
188
+ return fallback || null;
189
+ };
190
+ }
191
+
192
+ /**
193
+ * Creates a component with default error handling
194
+ * @param React - React library instance
195
+ * @param Component - Component to wrap
196
+ * @param errorHandler - Error handling function
197
+ * @returns Component with error handling
198
+ */
199
+ export function withErrorHandler(
200
+ React: any,
201
+ Component: any,
202
+ errorHandler: (error: Error) => void
203
+ ): any {
204
+ return class extends React.Component {
205
+ componentDidCatch(error: Error, errorInfo: any) {
206
+ errorHandler(error);
207
+ }
208
+
209
+ render() {
210
+ return React.createElement(Component, this.props);
211
+ }
212
+ };
213
+ }
214
+
215
+ /**
216
+ * Creates a portal wrapper component
217
+ * @param React - React library instance
218
+ * @param ReactDOM - ReactDOM library instance
219
+ * @param Component - Component to render in portal
220
+ * @param container - DOM container element or selector
221
+ * @returns Portal component
222
+ */
223
+ export function portalComponent(
224
+ React: any,
225
+ ReactDOM: any,
226
+ Component: any,
227
+ container: Element | string
228
+ ): any {
229
+ return (props: any) => {
230
+ const [mountNode, setMountNode] = React.useState(null as Element | null);
231
+
232
+ React.useEffect(() => {
233
+ const node = typeof container === 'string'
234
+ ? document.querySelector(container)
235
+ : container;
236
+
237
+ setMountNode(node);
238
+ }, []);
239
+
240
+ if (!mountNode || !ReactDOM.createPortal) {
241
+ return null;
242
+ }
243
+
244
+ return ReactDOM.createPortal(
245
+ React.createElement(Component, props),
246
+ mountNode
247
+ );
248
+ };
249
+ }