@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,242 @@
1
+ /**
2
+ * @fileoverview React error boundary creation utilities.
3
+ * Provides platform-agnostic error boundary components for React applications.
4
+ * @module @memberjunction/react-runtime/runtime
5
+ */
6
+
7
+ import { ErrorBoundaryOptions, ComponentError } from '../types';
8
+
9
+ /**
10
+ * Creates a React error boundary component class
11
+ * @param React - React library instance
12
+ * @param options - Error boundary options
13
+ * @returns Error boundary component class
14
+ */
15
+ export function createErrorBoundary(React: any, options: ErrorBoundaryOptions = {}): any {
16
+ const {
17
+ onError,
18
+ fallback,
19
+ logErrors = true,
20
+ recovery = 'none'
21
+ } = options;
22
+
23
+ /**
24
+ * Error boundary component class
25
+ */
26
+ return class ErrorBoundary extends React.Component {
27
+ state: { hasError: boolean; error: Error | null; errorInfo: any; retryCount: number };
28
+
29
+ constructor(props: any) {
30
+ super(props);
31
+ this.state = {
32
+ hasError: false,
33
+ error: null,
34
+ errorInfo: null,
35
+ retryCount: 0
36
+ };
37
+ }
38
+
39
+ static getDerivedStateFromError(error: Error): any {
40
+ // Update state to trigger fallback UI
41
+ return { hasError: true, error };
42
+ }
43
+
44
+ componentDidCatch(error: Error, errorInfo: any) {
45
+ // Log error if enabled
46
+ if (logErrors) {
47
+ console.error('React Error Boundary caught error:', error);
48
+ console.error('Error Info:', errorInfo);
49
+ }
50
+
51
+ // Call custom error handler if provided
52
+ if (onError) {
53
+ try {
54
+ onError(error, errorInfo);
55
+ } catch (handlerError) {
56
+ console.error('Error in custom error handler:', handlerError);
57
+ }
58
+ }
59
+
60
+ // Update state with error details
61
+ this.setState({ errorInfo });
62
+ }
63
+
64
+ handleRetry = () => {
65
+ this.setState((prevState: any) => ({
66
+ hasError: false,
67
+ error: null,
68
+ errorInfo: null,
69
+ retryCount: prevState.retryCount + 1
70
+ }));
71
+ };
72
+
73
+ handleReset = () => {
74
+ this.setState({
75
+ hasError: false,
76
+ error: null,
77
+ errorInfo: null,
78
+ retryCount: 0
79
+ });
80
+ };
81
+
82
+ render() {
83
+ if (this.state.hasError) {
84
+ // Use custom fallback if provided
85
+ if (fallback) {
86
+ if (typeof fallback === 'function') {
87
+ return fallback({
88
+ error: this.state.error,
89
+ errorInfo: this.state.errorInfo,
90
+ retry: this.handleRetry,
91
+ reset: this.handleReset,
92
+ retryCount: this.state.retryCount
93
+ });
94
+ }
95
+ return fallback;
96
+ }
97
+
98
+ // Default error UI
99
+ const showRetry = recovery === 'retry' && this.state.retryCount < 3;
100
+ const showReset = recovery === 'reset';
101
+
102
+ return React.createElement(
103
+ 'div',
104
+ {
105
+ style: {
106
+ padding: '20px',
107
+ backgroundColor: '#f8f8f8',
108
+ border: '1px solid #ddd',
109
+ borderRadius: '4px',
110
+ margin: '10px'
111
+ }
112
+ },
113
+ React.createElement('h2', { style: { color: '#d32f2f' } }, 'Component Error'),
114
+ React.createElement(
115
+ 'p',
116
+ { style: { color: '#666' } },
117
+ 'An error occurred while rendering this component.'
118
+ ),
119
+ this.state.error && React.createElement(
120
+ 'details',
121
+ { style: { marginTop: '10px' } },
122
+ React.createElement(
123
+ 'summary',
124
+ { style: { cursor: 'pointer', color: '#333' } },
125
+ 'Error Details'
126
+ ),
127
+ React.createElement(
128
+ 'pre',
129
+ {
130
+ style: {
131
+ backgroundColor: '#f0f0f0',
132
+ padding: '10px',
133
+ marginTop: '10px',
134
+ overflow: 'auto',
135
+ fontSize: '12px'
136
+ }
137
+ },
138
+ this.state.error.toString(),
139
+ '\n\n',
140
+ this.state.error.stack
141
+ )
142
+ ),
143
+ (showRetry || showReset) && React.createElement(
144
+ 'div',
145
+ { style: { marginTop: '10px' } },
146
+ showRetry && React.createElement(
147
+ 'button',
148
+ {
149
+ onClick: this.handleRetry,
150
+ style: {
151
+ padding: '8px 16px',
152
+ marginRight: '10px',
153
+ backgroundColor: '#1976d2',
154
+ color: 'white',
155
+ border: 'none',
156
+ borderRadius: '4px',
157
+ cursor: 'pointer'
158
+ }
159
+ },
160
+ `Retry (${3 - this.state.retryCount} attempts left)`
161
+ ),
162
+ showReset && React.createElement(
163
+ 'button',
164
+ {
165
+ onClick: this.handleReset,
166
+ style: {
167
+ padding: '8px 16px',
168
+ backgroundColor: '#757575',
169
+ color: 'white',
170
+ border: 'none',
171
+ borderRadius: '4px',
172
+ cursor: 'pointer'
173
+ }
174
+ },
175
+ 'Reset Component'
176
+ )
177
+ )
178
+ );
179
+ }
180
+
181
+ return this.props.children;
182
+ }
183
+ };
184
+ }
185
+
186
+ /**
187
+ * Creates a functional error boundary wrapper using React hooks
188
+ * @param React - React library instance
189
+ * @param Component - Component to wrap
190
+ * @param options - Error boundary options
191
+ * @returns Wrapped component with error boundary
192
+ */
193
+ export function withErrorBoundary(React: any, Component: any, options: ErrorBoundaryOptions = {}): any {
194
+ const ErrorBoundaryComponent = createErrorBoundary(React, options);
195
+
196
+ return (props: any) => {
197
+ return React.createElement(
198
+ ErrorBoundaryComponent,
199
+ null,
200
+ React.createElement(Component, props)
201
+ );
202
+ };
203
+ }
204
+
205
+ /**
206
+ * Formats a component error for display or logging
207
+ * @param error - Error to format
208
+ * @param componentName - Name of the component where error occurred
209
+ * @param phase - Phase when error occurred
210
+ * @returns Formatted component error
211
+ */
212
+ export function formatComponentError(
213
+ error: Error,
214
+ componentName: string,
215
+ phase: ComponentError['phase']
216
+ ): ComponentError {
217
+ return {
218
+ message: error.message || 'Unknown error',
219
+ stack: error.stack,
220
+ componentName,
221
+ phase,
222
+ details: {
223
+ name: error.name,
224
+ timestamp: new Date().toISOString()
225
+ }
226
+ };
227
+ }
228
+
229
+ /**
230
+ * Creates a simple error logger for error boundaries
231
+ * @param componentName - Name of the component
232
+ * @returns Error logging function
233
+ */
234
+ export function createErrorLogger(componentName: string): (error: Error, errorInfo: any) => void {
235
+ return (error: Error, errorInfo: any) => {
236
+ console.group(`🚨 React Component Error: ${componentName}`);
237
+ console.error('Error:', error);
238
+ console.error('Component Stack:', errorInfo.componentStack);
239
+ console.error('Props:', errorInfo.props);
240
+ console.groupEnd();
241
+ };
242
+ }
@@ -0,0 +1,45 @@
1
+ /**
2
+ * @fileoverview Runtime module exports
3
+ * @module @memberjunction/react-runtime/runtime
4
+ */
5
+
6
+ export {
7
+ createErrorBoundary,
8
+ withErrorBoundary,
9
+ formatComponentError,
10
+ createErrorLogger
11
+ } from './error-boundary';
12
+
13
+ export {
14
+ wrapComponent,
15
+ memoizeComponent,
16
+ lazyComponent,
17
+ injectProps,
18
+ conditionalComponent,
19
+ withErrorHandler,
20
+ portalComponent,
21
+ WrapperOptions
22
+ } from './component-wrapper';
23
+
24
+ export {
25
+ buildComponentProps,
26
+ normalizeCallbacks,
27
+ normalizeStyles,
28
+ validateComponentProps,
29
+ mergeProps,
30
+ createPropsTransformer,
31
+ wrapCallbacksWithLogging,
32
+ extractPropPaths,
33
+ PropBuilderOptions
34
+ } from './prop-builder';
35
+
36
+ export {
37
+ ComponentHierarchyRegistrar,
38
+ registerComponentHierarchy,
39
+ validateComponentSpec,
40
+ flattenComponentHierarchy,
41
+ countComponentsInHierarchy,
42
+ HierarchyRegistrationResult,
43
+ ComponentRegistrationError,
44
+ HierarchyRegistrationOptions
45
+ } from './component-hierarchy';
@@ -0,0 +1,290 @@
1
+ /**
2
+ * @fileoverview Props builder utilities for React components.
3
+ * Provides utilities for constructing and validating component props.
4
+ * @module @memberjunction/react-runtime/runtime
5
+ */
6
+
7
+ import { ComponentProps, ComponentCallbacks, ComponentStyles } from '../types';
8
+
9
+ /**
10
+ * Options for building component props
11
+ */
12
+ export interface PropBuilderOptions {
13
+ /** Validate props before building */
14
+ validate?: boolean;
15
+ /** Merge with existing props */
16
+ merge?: boolean;
17
+ /** Transform data before passing to component */
18
+ transformData?: (data: any) => any;
19
+ /** Transform state before passing to component */
20
+ transformState?: (state: any) => any;
21
+ }
22
+
23
+ /**
24
+ * Builds component props from various sources
25
+ * @param data - Component data
26
+ * @param userState - User state object
27
+ * @param utilities - Utility functions
28
+ * @param callbacks - Component callbacks
29
+ * @param components - Child components
30
+ * @param styles - Component styles
31
+ * @param options - Builder options
32
+ * @returns Built component props
33
+ */
34
+ export function buildComponentProps(
35
+ data: any = {},
36
+ userState: any = {},
37
+ utilities: any = {},
38
+ callbacks: ComponentCallbacks = {},
39
+ components: Record<string, any> = {},
40
+ styles?: ComponentStyles,
41
+ options: PropBuilderOptions = {}
42
+ ): ComponentProps {
43
+ const {
44
+ validate = true,
45
+ transformData,
46
+ transformState
47
+ } = options;
48
+
49
+ // Transform data if transformer provided
50
+ const transformedData = transformData ? transformData(data) : data;
51
+ const transformedState = transformState ? transformState(userState) : userState;
52
+
53
+ // Build props object
54
+ const props: ComponentProps = {
55
+ data: transformedData,
56
+ userState: transformedState,
57
+ utilities,
58
+ callbacks: normalizeCallbacks(callbacks),
59
+ components,
60
+ styles: normalizeStyles(styles)
61
+ };
62
+
63
+ // Validate if enabled
64
+ if (validate) {
65
+ validateComponentProps(props);
66
+ }
67
+
68
+ return props;
69
+ }
70
+
71
+ /**
72
+ * Normalizes component callbacks
73
+ * @param callbacks - Raw callbacks object
74
+ * @returns Normalized callbacks
75
+ */
76
+ export function normalizeCallbacks(callbacks: any): ComponentCallbacks {
77
+ const normalized: ComponentCallbacks = {};
78
+
79
+ // Ensure all callbacks are functions
80
+ if (callbacks.RefreshData && typeof callbacks.RefreshData === 'function') {
81
+ normalized.RefreshData = callbacks.RefreshData;
82
+ }
83
+
84
+ if (callbacks.OpenEntityRecord && typeof callbacks.OpenEntityRecord === 'function') {
85
+ normalized.OpenEntityRecord = callbacks.OpenEntityRecord;
86
+ }
87
+
88
+ if (callbacks.UpdateUserState && typeof callbacks.UpdateUserState === 'function') {
89
+ normalized.UpdateUserState = callbacks.UpdateUserState;
90
+ }
91
+
92
+ if (callbacks.NotifyEvent && typeof callbacks.NotifyEvent === 'function') {
93
+ normalized.NotifyEvent = callbacks.NotifyEvent;
94
+ }
95
+
96
+ return normalized;
97
+ }
98
+
99
+ /**
100
+ * Normalizes component styles
101
+ * @param styles - Raw styles object
102
+ * @returns Normalized styles
103
+ */
104
+ export function normalizeStyles(styles?: any): any {
105
+ // Pass through the full styles object as-is
106
+ // This allows Skip components to access their full style structure
107
+ // including colors, typography, borders, etc.
108
+ return styles;
109
+ }
110
+
111
+ /**
112
+ * Validates component props
113
+ * @param props - Props to validate
114
+ * @throws Error if validation fails
115
+ */
116
+ export function validateComponentProps(props: ComponentProps): void {
117
+ // Validate data
118
+ if (props.data === null || props.data === undefined) {
119
+ throw new Error('Component props.data cannot be null or undefined');
120
+ }
121
+
122
+ // Validate userState
123
+ if (props.userState === null) {
124
+ throw new Error('Component props.userState cannot be null');
125
+ }
126
+
127
+ // Validate utilities
128
+ if (props.utilities === null) {
129
+ throw new Error('Component props.utilities cannot be null');
130
+ }
131
+
132
+ // Validate callbacks
133
+ if (!props.callbacks || typeof props.callbacks !== 'object') {
134
+ throw new Error('Component props.callbacks must be an object');
135
+ }
136
+
137
+ // Validate callback functions
138
+ for (const [key, value] of Object.entries(props.callbacks)) {
139
+ if (value !== undefined && typeof value !== 'function') {
140
+ throw new Error(`Component callback "${key}" must be a function`);
141
+ }
142
+ }
143
+ }
144
+
145
+ /**
146
+ * Merges multiple prop objects
147
+ * @param propsList - Array of props to merge
148
+ * @returns Merged props
149
+ */
150
+ export function mergeProps(...propsList: Partial<ComponentProps>[]): ComponentProps {
151
+ const merged: ComponentProps = {
152
+ data: {},
153
+ userState: {},
154
+ utilities: {},
155
+ callbacks: {},
156
+ components: {},
157
+ styles: {}
158
+ };
159
+
160
+ for (const props of propsList) {
161
+ if (props.data) {
162
+ merged.data = { ...merged.data, ...props.data };
163
+ }
164
+
165
+ if (props.userState) {
166
+ merged.userState = { ...merged.userState, ...props.userState };
167
+ }
168
+
169
+ if (props.utilities) {
170
+ merged.utilities = { ...merged.utilities, ...props.utilities };
171
+ }
172
+
173
+ if (props.callbacks) {
174
+ merged.callbacks = { ...merged.callbacks, ...props.callbacks };
175
+ }
176
+
177
+ if (props.components) {
178
+ merged.components = { ...merged.components, ...props.components };
179
+ }
180
+
181
+ if (props.styles) {
182
+ merged.styles = { ...merged.styles, ...props.styles };
183
+ }
184
+ }
185
+
186
+ return merged;
187
+ }
188
+
189
+ /**
190
+ * Creates a props transformer function
191
+ * @param transformations - Map of prop paths to transformer functions
192
+ * @returns Props transformer
193
+ */
194
+ export function createPropsTransformer(
195
+ transformations: Record<string, (value: any) => any>
196
+ ): (props: ComponentProps) => ComponentProps {
197
+ return (props: ComponentProps) => {
198
+ const transformed = { ...props };
199
+
200
+ for (const [path, transformer] of Object.entries(transformations)) {
201
+ const pathParts = path.split('.');
202
+ let current: any = transformed;
203
+
204
+ // Navigate to the parent of the target property
205
+ for (let i = 0; i < pathParts.length - 1; i++) {
206
+ if (!current[pathParts[i]]) {
207
+ current[pathParts[i]] = {};
208
+ }
209
+ current = current[pathParts[i]];
210
+ }
211
+
212
+ // Apply transformation
213
+ const lastPart = pathParts[pathParts.length - 1];
214
+ if (current[lastPart] !== undefined) {
215
+ current[lastPart] = transformer(current[lastPart]);
216
+ }
217
+ }
218
+
219
+ return transformed;
220
+ };
221
+ }
222
+
223
+ /**
224
+ * Creates a callback wrapper that adds logging
225
+ * @param callbacks - Original callbacks
226
+ * @param componentName - Component name for logging
227
+ * @returns Wrapped callbacks
228
+ */
229
+ export function wrapCallbacksWithLogging(
230
+ callbacks: ComponentCallbacks,
231
+ componentName: string
232
+ ): ComponentCallbacks {
233
+ const wrapped: ComponentCallbacks = {};
234
+
235
+ if (callbacks.RefreshData) {
236
+ wrapped.RefreshData = () => {
237
+ console.log(`[${componentName}] RefreshData called`);
238
+ callbacks.RefreshData!();
239
+ };
240
+ }
241
+
242
+ if (callbacks.OpenEntityRecord) {
243
+ wrapped.OpenEntityRecord = (entityName: string, key: any) => {
244
+ console.log(`[${componentName}] OpenEntityRecord called:`, { entityName, key });
245
+ callbacks.OpenEntityRecord!(entityName, key);
246
+ };
247
+ }
248
+
249
+ if (callbacks.UpdateUserState) {
250
+ wrapped.UpdateUserState = (state: any) => {
251
+ console.log(`[${componentName}] UpdateUserState called:`, state);
252
+ callbacks.UpdateUserState!(state);
253
+ };
254
+ }
255
+
256
+ if (callbacks.NotifyEvent) {
257
+ wrapped.NotifyEvent = (event: string, data: any) => {
258
+ console.log(`[${componentName}] NotifyEvent called:`, { event, data });
259
+ callbacks.NotifyEvent!(event, data);
260
+ };
261
+ }
262
+
263
+ return wrapped;
264
+ }
265
+
266
+ /**
267
+ * Extracts props paths used by a component
268
+ * @param componentCode - Component source code
269
+ * @returns Array of prop paths
270
+ */
271
+ export function extractPropPaths(componentCode: string): string[] {
272
+ const paths: string[] = [];
273
+
274
+ // Simple regex patterns to find prop access
275
+ const patterns = [
276
+ /props\.data\.(\w+)/g,
277
+ /props\.userState\.(\w+)/g,
278
+ /props\.utilities\.(\w+)/g,
279
+ /props\.callbacks\.(\w+)/g
280
+ ];
281
+
282
+ for (const pattern of patterns) {
283
+ let match;
284
+ while ((match = pattern.exec(componentCode)) !== null) {
285
+ paths.push(match[0]);
286
+ }
287
+ }
288
+
289
+ return [...new Set(paths)]; // Remove duplicates
290
+ }