@snteam/amplify-angular-core 1.0.35 → 1.0.37

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.
@@ -1,5 +1,5 @@
1
1
  import * as i0 from '@angular/core';
2
- import { Component, Injectable, Pipe, inject, Input, ChangeDetectionStrategy, model, signal, EventEmitter, Output } from '@angular/core';
2
+ import { Injectable, Component, Pipe, inject, Input, ChangeDetectionStrategy, model, signal, EventEmitter, Output } from '@angular/core';
3
3
  import * as i3$1 from '@angular/common';
4
4
  import { CommonModule } from '@angular/common';
5
5
  import * as i1 from '@angular/forms';
@@ -13,27 +13,2045 @@ import { MatSelectModule } from '@angular/material/select';
13
13
  import * as i6 from '@angular/material/datepicker';
14
14
  import { MatDatepickerModule } from '@angular/material/datepicker';
15
15
  import { provideNativeDateAdapter } from '@angular/material/core';
16
+ import { Observable } from 'rxjs';
16
17
  import * as i3$2 from '@angular/material/divider';
17
18
  import { MatDividerModule } from '@angular/material/divider';
18
- import * as i2 from '@angular/material/toolbar';
19
+ import * as i3$4 from '@angular/material/toolbar';
19
20
  import { MatToolbarModule } from '@angular/material/toolbar';
20
21
  import * as i3$3 from '@angular/material/button';
21
22
  import { MatButtonModule } from '@angular/material/button';
22
- import * as i4$1 from '@angular/material/icon';
23
+ import * as i5$1 from '@angular/material/icon';
23
24
  import { MatIconModule } from '@angular/material/icon';
24
- import * as i5$1 from '@angular/material/list';
25
+ import * as i6$1 from '@angular/material/list';
25
26
  import { MatListModule } from '@angular/material/list';
26
27
  import { MatDialogRef, MAT_DIALOG_DATA, MatDialogTitle, MatDialogContent, MatDialogActions, MatDialogClose, MatDialog } from '@angular/material/dialog';
27
28
  import { MatSnackBar, MatSnackBarModule } from '@angular/material/snack-bar';
28
- import * as i4$2 from '@angular/router';
29
+ import * as i4$1 from '@angular/router';
29
30
  import { ActivatedRoute, Router } from '@angular/router';
30
31
  import * as i5$2 from '@angular/material/tooltip';
31
32
  import { MatTooltipModule } from '@angular/material/tooltip';
32
33
  import * as i1$1 from '@angular/material/expansion';
33
34
  import { MatExpansionModule } from '@angular/material/expansion';
34
- import * as i3$4 from '@angular/material/card';
35
+ import * as i3$5 from '@angular/material/card';
35
36
  import { MatCardModule } from '@angular/material/card';
36
37
 
38
+ // Dynamic Relationship Loader Interfaces
39
+
40
+ /**
41
+ * Selection set generation patterns
42
+ */
43
+ var SelectionSetPattern;
44
+ (function (SelectionSetPattern) {
45
+ /** Basic relationship loading with id and common display fields */
46
+ SelectionSetPattern["BASIC"] = "basic";
47
+ /** Comprehensive field loading using wildcard selection */
48
+ SelectionSetPattern["COMPREHENSIVE"] = "comprehensive";
49
+ /** Selective field loading based on schema analysis */
50
+ SelectionSetPattern["SELECTIVE"] = "selective";
51
+ /** Minimal fallback pattern for error cases */
52
+ SelectionSetPattern["FALLBACK"] = "fallback";
53
+ })(SelectionSetPattern || (SelectionSetPattern = {}));
54
+ /**
55
+ * Field classification for display optimization
56
+ */
57
+ var FieldClassification;
58
+ (function (FieldClassification) {
59
+ /** Essential fields that must always be included */
60
+ FieldClassification["ESSENTIAL"] = "essential";
61
+ /** Display fields commonly shown in UI */
62
+ FieldClassification["DISPLAY"] = "display";
63
+ /** Metadata fields that provide additional context */
64
+ FieldClassification["METADATA"] = "metadata";
65
+ /** Relationship fields that link to other models */
66
+ FieldClassification["RELATIONSHIP"] = "relationship";
67
+ /** System fields used internally */
68
+ FieldClassification["SYSTEM"] = "system";
69
+ /** Unknown or unclassified fields */
70
+ FieldClassification["UNKNOWN"] = "unknown";
71
+ })(FieldClassification || (FieldClassification = {}));
72
+
73
+ /**
74
+ * Types of errors that can occur during selection set generation
75
+ */
76
+ var SelectionSetErrorType;
77
+ (function (SelectionSetErrorType) {
78
+ /** Configuration is invalid or incomplete */
79
+ SelectionSetErrorType["INVALID_CONFIGURATION"] = "invalid_configuration";
80
+ /** Target model not found in schema */
81
+ SelectionSetErrorType["MODEL_NOT_FOUND"] = "model_not_found";
82
+ /** Field not found in target model */
83
+ SelectionSetErrorType["FIELD_NOT_FOUND"] = "field_not_found";
84
+ /** Schema introspection data unavailable */
85
+ SelectionSetErrorType["SCHEMA_UNAVAILABLE"] = "schema_unavailable";
86
+ /** Generated selection set exceeds complexity limits */
87
+ SelectionSetErrorType["COMPLEXITY_EXCEEDED"] = "complexity_exceeded";
88
+ /** Circular reference detected in relationship chain */
89
+ SelectionSetErrorType["CIRCULAR_REFERENCE"] = "circular_reference";
90
+ /** GraphQL query failed with generated selection set */
91
+ SelectionSetErrorType["GRAPHQL_ERROR"] = "graphql_error";
92
+ })(SelectionSetErrorType || (SelectionSetErrorType = {}));
93
+
94
+ // Dynamic Relationship Loader Types
95
+
96
+ /**
97
+ * Centralized error handling and logging service for the dynamic relationship loader system
98
+ *
99
+ * This service provides comprehensive error handling, logging, and recovery mechanisms
100
+ * for all relationship loading failures across the system.
101
+ */
102
+ class ErrorHandlerService {
103
+ /**
104
+ * Maximum number of error logs to keep in memory
105
+ */
106
+ MAX_ERROR_LOGS = 100;
107
+ /**
108
+ * In-memory error log storage for debugging
109
+ */
110
+ errorLogs = [];
111
+ /**
112
+ * Error statistics for monitoring
113
+ */
114
+ errorStats = {
115
+ totalErrors: 0,
116
+ errorsByType: new Map(),
117
+ lastError: null,
118
+ recoveryAttempts: 0,
119
+ successfulRecoveries: 0
120
+ };
121
+ /**
122
+ * Log a selection set generation error with comprehensive context
123
+ */
124
+ logSelectionSetError(type, message, config, selectionSet, additionalContext) {
125
+ const error = {
126
+ type,
127
+ message,
128
+ configuration: config ? { ...config } : undefined,
129
+ selectionSet: selectionSet ? [...selectionSet] : undefined,
130
+ context: {
131
+ ...additionalContext,
132
+ userAgent: typeof navigator !== 'undefined' ? navigator.userAgent : 'unknown',
133
+ timestamp: new Date().toISOString(),
134
+ stackTrace: new Error().stack
135
+ },
136
+ timestamp: new Date()
137
+ };
138
+ // Add to error logs
139
+ this.addErrorLog(error);
140
+ // Update statistics
141
+ this.updateErrorStats(error);
142
+ // Log to console with structured format
143
+ this.logToConsole(error);
144
+ return error;
145
+ }
146
+ /**
147
+ * Log a GraphQL query error with retry context
148
+ */
149
+ logGraphQLError(error, config, selectionSet, retryAttempt, previousErrors = []) {
150
+ const errorMessage = error instanceof Error ? error.message : String(error);
151
+ return this.logSelectionSetError(SelectionSetErrorType.GRAPHQL_ERROR, `GraphQL query failed: ${errorMessage}`, config, selectionSet, {
152
+ originalError: {
153
+ message: errorMessage,
154
+ name: error instanceof Error ? error.name : 'Unknown',
155
+ stack: error instanceof Error ? error.stack : undefined
156
+ },
157
+ retryAttempt,
158
+ previousErrors,
159
+ queryContext: {
160
+ modelName: config.relationshipModelName,
161
+ fieldName: config.fieldName,
162
+ selectionSetLength: selectionSet.length
163
+ }
164
+ });
165
+ }
166
+ /**
167
+ * Log a configuration analysis error
168
+ */
169
+ logConfigurationError(config, errors, additionalContext) {
170
+ return this.logSelectionSetError(SelectionSetErrorType.INVALID_CONFIGURATION, `Configuration analysis failed: ${errors.join(', ')}`, config || undefined, undefined, {
171
+ validationErrors: errors,
172
+ ...additionalContext
173
+ });
174
+ }
175
+ /**
176
+ * Log a schema introspection error
177
+ */
178
+ logSchemaError(modelName, fieldName, additionalContext) {
179
+ const message = fieldName
180
+ ? `Schema validation failed for field "${fieldName}" in model "${modelName}"`
181
+ : `Schema data unavailable or invalid for model "${modelName}"`;
182
+ return this.logSelectionSetError(fieldName ? SelectionSetErrorType.FIELD_NOT_FOUND : SelectionSetErrorType.SCHEMA_UNAVAILABLE, message, undefined, undefined, {
183
+ modelName,
184
+ fieldName,
185
+ ...additionalContext
186
+ });
187
+ }
188
+ /**
189
+ * Log a complexity validation error
190
+ */
191
+ logComplexityError(selectionSet, validationErrors, config) {
192
+ return this.logSelectionSetError(SelectionSetErrorType.COMPLEXITY_EXCEEDED, `Selection set complexity exceeded limits: ${validationErrors.join(', ')}`, config, selectionSet, {
193
+ complexityIssues: validationErrors,
194
+ selectionSetSize: selectionSet.length,
195
+ maxDepth: Math.max(...selectionSet.map(s => (s.match(/\./g) || []).length))
196
+ });
197
+ }
198
+ /**
199
+ * Log a circular reference error
200
+ */
201
+ logCircularReferenceError(circularPath, selectionSet, config) {
202
+ return this.logSelectionSetError(SelectionSetErrorType.CIRCULAR_REFERENCE, `Circular reference detected in path: ${circularPath}`, config, selectionSet, {
203
+ circularPath,
204
+ pathDepth: circularPath.split('.').length
205
+ });
206
+ }
207
+ /**
208
+ * Log a successful error recovery
209
+ */
210
+ logSuccessfulRecovery(originalError, recoveryMethod, finalSelectionSet) {
211
+ this.errorStats.recoveryAttempts++;
212
+ this.errorStats.successfulRecoveries++;
213
+ console.log('ErrorHandler: Successful error recovery:', {
214
+ originalError: {
215
+ type: originalError.type,
216
+ message: originalError.message
217
+ },
218
+ recoveryMethod,
219
+ finalSelectionSet,
220
+ recoveryRate: this.getRecoveryRate(),
221
+ timestamp: new Date().toISOString()
222
+ });
223
+ }
224
+ /**
225
+ * Log a failed error recovery attempt
226
+ */
227
+ logFailedRecovery(originalError, recoveryAttempts, finalError) {
228
+ this.errorStats.recoveryAttempts++;
229
+ const failureError = this.logSelectionSetError(SelectionSetErrorType.GRAPHQL_ERROR, `All recovery attempts failed: ${finalError instanceof Error ? finalError.message : String(finalError)}`, originalError.configuration, originalError.selectionSet, {
230
+ originalError: {
231
+ type: originalError.type,
232
+ message: originalError.message
233
+ },
234
+ recoveryAttempts,
235
+ finalError: {
236
+ message: finalError instanceof Error ? finalError.message : String(finalError),
237
+ type: typeof finalError
238
+ },
239
+ recoveryRate: this.getRecoveryRate()
240
+ });
241
+ console.error('ErrorHandler: All recovery attempts failed:', failureError);
242
+ }
243
+ /**
244
+ * Create a SelectionSetResult for a successful operation
245
+ */
246
+ createSuccessResult(selectionSet, patternUsed, usedFallback = false) {
247
+ return {
248
+ success: true,
249
+ selectionSet: [...selectionSet],
250
+ usedFallback,
251
+ patternUsed
252
+ };
253
+ }
254
+ /**
255
+ * Create a SelectionSetResult for a failed operation
256
+ */
257
+ createErrorResult(error) {
258
+ return {
259
+ success: false,
260
+ error,
261
+ usedFallback: false
262
+ };
263
+ }
264
+ /**
265
+ * Get error statistics for monitoring and debugging
266
+ */
267
+ getErrorStats() {
268
+ const errorsByType = {};
269
+ this.errorStats.errorsByType.forEach((count, type) => {
270
+ errorsByType[type] = count;
271
+ });
272
+ return {
273
+ totalErrors: this.errorStats.totalErrors,
274
+ errorsByType,
275
+ lastError: this.errorStats.lastError,
276
+ recoveryAttempts: this.errorStats.recoveryAttempts,
277
+ successfulRecoveries: this.errorStats.successfulRecoveries,
278
+ recoveryRate: this.getRecoveryRate(),
279
+ recentErrors: this.getRecentErrors(10)
280
+ };
281
+ }
282
+ /**
283
+ * Get recent error logs for debugging
284
+ */
285
+ getRecentErrors(count = 10) {
286
+ return this.errorLogs
287
+ .slice(-count)
288
+ .sort((a, b) => b.timestamp.getTime() - a.timestamp.getTime());
289
+ }
290
+ /**
291
+ * Clear all error logs and reset statistics
292
+ */
293
+ clearErrorLogs() {
294
+ this.errorLogs = [];
295
+ this.errorStats = {
296
+ totalErrors: 0,
297
+ errorsByType: new Map(),
298
+ lastError: null,
299
+ recoveryAttempts: 0,
300
+ successfulRecoveries: 0
301
+ };
302
+ console.log('ErrorHandler: Error logs and statistics cleared');
303
+ }
304
+ /**
305
+ * Check if the system is experiencing high error rates
306
+ */
307
+ isHighErrorRate(timeWindowMinutes = 5, threshold = 10) {
308
+ const cutoffTime = new Date(Date.now() - timeWindowMinutes * 60 * 1000);
309
+ const recentErrors = this.errorLogs.filter(error => error.timestamp > cutoffTime);
310
+ return recentErrors.length >= threshold;
311
+ }
312
+ /**
313
+ * Get error patterns for analysis
314
+ */
315
+ getErrorPatterns() {
316
+ if (this.errorLogs.length === 0) {
317
+ return {
318
+ mostCommonErrorType: null,
319
+ mostProblematicModel: null,
320
+ mostProblematicField: null,
321
+ errorTrends: []
322
+ };
323
+ }
324
+ // Find most common error type
325
+ let mostCommonErrorType = null;
326
+ let maxErrorCount = 0;
327
+ this.errorStats.errorsByType.forEach((count, type) => {
328
+ if (count > maxErrorCount) {
329
+ maxErrorCount = count;
330
+ mostCommonErrorType = type;
331
+ }
332
+ });
333
+ // Find most problematic model
334
+ const modelCounts = new Map();
335
+ this.errorLogs.forEach(error => {
336
+ if (error.configuration?.relationshipModelName) {
337
+ const count = modelCounts.get(error.configuration.relationshipModelName) || 0;
338
+ modelCounts.set(error.configuration.relationshipModelName, count + 1);
339
+ }
340
+ });
341
+ let mostProblematicModel = null;
342
+ let maxModelCount = 0;
343
+ modelCounts.forEach((count, model) => {
344
+ if (count > maxModelCount) {
345
+ maxModelCount = count;
346
+ mostProblematicModel = model;
347
+ }
348
+ });
349
+ // Find most problematic field
350
+ const fieldCounts = new Map();
351
+ this.errorLogs.forEach(error => {
352
+ if (error.configuration?.fieldName) {
353
+ const count = fieldCounts.get(error.configuration.fieldName) || 0;
354
+ fieldCounts.set(error.configuration.fieldName, count + 1);
355
+ }
356
+ });
357
+ let mostProblematicField = null;
358
+ let maxFieldCount = 0;
359
+ fieldCounts.forEach((count, field) => {
360
+ if (count > maxFieldCount) {
361
+ maxFieldCount = count;
362
+ mostProblematicField = field;
363
+ }
364
+ });
365
+ // Generate error trends by hour
366
+ const hourCounts = new Map();
367
+ this.errorLogs.forEach(error => {
368
+ const hour = error.timestamp.toISOString().substring(0, 13); // YYYY-MM-DDTHH
369
+ const count = hourCounts.get(hour) || 0;
370
+ hourCounts.set(hour, count + 1);
371
+ });
372
+ const errorTrends = Array.from(hourCounts.entries())
373
+ .map(([hour, errorCount]) => ({ hour, errorCount }))
374
+ .sort((a, b) => a.hour.localeCompare(b.hour));
375
+ return {
376
+ mostCommonErrorType,
377
+ mostProblematicModel,
378
+ mostProblematicField,
379
+ errorTrends
380
+ };
381
+ }
382
+ /**
383
+ * Add error to the log with size management
384
+ */
385
+ addErrorLog(error) {
386
+ this.errorLogs.push(error);
387
+ // Maintain maximum log size
388
+ if (this.errorLogs.length > this.MAX_ERROR_LOGS) {
389
+ this.errorLogs = this.errorLogs.slice(-this.MAX_ERROR_LOGS);
390
+ }
391
+ }
392
+ /**
393
+ * Update error statistics
394
+ */
395
+ updateErrorStats(error) {
396
+ this.errorStats.totalErrors++;
397
+ this.errorStats.lastError = error;
398
+ const currentCount = this.errorStats.errorsByType.get(error.type) || 0;
399
+ this.errorStats.errorsByType.set(error.type, currentCount + 1);
400
+ }
401
+ /**
402
+ * Calculate recovery rate percentage
403
+ */
404
+ getRecoveryRate() {
405
+ if (this.errorStats.recoveryAttempts === 0) {
406
+ return 0;
407
+ }
408
+ return Math.round((this.errorStats.successfulRecoveries / this.errorStats.recoveryAttempts) * 100);
409
+ }
410
+ /**
411
+ * Log error to console with structured format
412
+ */
413
+ logToConsole(error) {
414
+ const logData = {
415
+ type: error.type,
416
+ message: error.message,
417
+ timestamp: error.timestamp.toISOString(),
418
+ configuration: error.configuration ? {
419
+ relationshipModel: error.configuration.relationshipModelName,
420
+ baseModel: error.configuration.baseModelName,
421
+ fieldName: error.configuration.fieldName,
422
+ associatedWith: error.configuration.associatedWith
423
+ } : undefined,
424
+ selectionSet: error.selectionSet,
425
+ context: error.context
426
+ };
427
+ // Use appropriate console method based on error severity
428
+ switch (error.type) {
429
+ case SelectionSetErrorType.INVALID_CONFIGURATION:
430
+ case SelectionSetErrorType.GRAPHQL_ERROR:
431
+ console.error('ErrorHandler: Critical error:', logData);
432
+ break;
433
+ case SelectionSetErrorType.SCHEMA_UNAVAILABLE:
434
+ case SelectionSetErrorType.MODEL_NOT_FOUND:
435
+ case SelectionSetErrorType.FIELD_NOT_FOUND:
436
+ console.warn('ErrorHandler: Schema-related warning:', logData);
437
+ break;
438
+ case SelectionSetErrorType.COMPLEXITY_EXCEEDED:
439
+ case SelectionSetErrorType.CIRCULAR_REFERENCE:
440
+ console.warn('ErrorHandler: Complexity warning:', logData);
441
+ break;
442
+ default:
443
+ console.log('ErrorHandler: General error:', logData);
444
+ break;
445
+ }
446
+ }
447
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: ErrorHandlerService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
448
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: ErrorHandlerService, providedIn: 'root' });
449
+ }
450
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: ErrorHandlerService, decorators: [{
451
+ type: Injectable,
452
+ args: [{
453
+ providedIn: 'root'
454
+ }]
455
+ }] });
456
+
457
+ /**
458
+ * Service for analyzing relationship configurations to extract field mappings
459
+ *
460
+ * This service analyzes relationship configurations to determine target models
461
+ * and generate appropriate field selectors for GraphQL queries.
462
+ */
463
+ class ConfigurationAnalyzerService {
464
+ errorHandler;
465
+ /**
466
+ * Common display field names to look for in models
467
+ */
468
+ COMMON_DISPLAY_FIELDS = ['name', 'title', 'label', 'displayName', 'description'];
469
+ constructor(errorHandler) {
470
+ this.errorHandler = errorHandler;
471
+ }
472
+ /**
473
+ * Analyze a relationship configuration to extract field mappings and validate structure
474
+ * Enhanced with comprehensive error handling and logging
475
+ */
476
+ analyzeConfiguration(config) {
477
+ const errors = [];
478
+ try {
479
+ // Validate configuration completeness
480
+ if (!config) {
481
+ const error = 'Configuration is null or undefined';
482
+ errors.push(error);
483
+ this.errorHandler.logConfigurationError(null, [error], {
484
+ method: 'analyzeConfiguration',
485
+ step: 'initial_validation'
486
+ });
487
+ return {
488
+ targetModelName: '',
489
+ fieldSelectors: [],
490
+ isValid: false,
491
+ errors
492
+ };
493
+ }
494
+ // Validate required fields
495
+ const requiredFields = [
496
+ { field: 'relationshipModelName', value: config.relationshipModelName },
497
+ { field: 'baseModelName', value: config.baseModelName },
498
+ { field: 'fieldName', value: config.fieldName },
499
+ { field: 'associatedWith', value: config.associatedWith }
500
+ ];
501
+ for (const { field, value } of requiredFields) {
502
+ if (!value) {
503
+ errors.push(`${field} is required`);
504
+ }
505
+ }
506
+ if (errors.length > 0) {
507
+ this.errorHandler.logConfigurationError(config, errors, {
508
+ method: 'analyzeConfiguration',
509
+ step: 'field_validation',
510
+ providedFields: {
511
+ relationshipModelName: !!config.relationshipModelName,
512
+ baseModelName: !!config.baseModelName,
513
+ fieldName: !!config.fieldName,
514
+ associatedWith: !!config.associatedWith
515
+ }
516
+ });
517
+ return {
518
+ targetModelName: '',
519
+ fieldSelectors: [],
520
+ isValid: false,
521
+ errors
522
+ };
523
+ }
524
+ // Extract target model name from the configuration
525
+ const targetModelName = this.extractTargetModelName(config);
526
+ if (!targetModelName) {
527
+ const error = 'Could not determine target model name from configuration';
528
+ errors.push(error);
529
+ this.errorHandler.logConfigurationError(config, [error], {
530
+ method: 'analyzeConfiguration',
531
+ step: 'target_model_extraction',
532
+ fieldName: config.fieldName
533
+ });
534
+ return {
535
+ targetModelName: '',
536
+ fieldSelectors: [],
537
+ isValid: false,
538
+ errors
539
+ };
540
+ }
541
+ // Generate field selectors for the target model
542
+ const fieldSelectors = this.determineFieldSelectorsEnhanced(targetModelName, config.fieldName);
543
+ if (!fieldSelectors || fieldSelectors.length === 0) {
544
+ const error = 'Could not generate field selectors for target model';
545
+ errors.push(error);
546
+ this.errorHandler.logConfigurationError(config, [error], {
547
+ method: 'analyzeConfiguration',
548
+ step: 'field_selector_generation',
549
+ targetModelName,
550
+ fieldName: config.fieldName
551
+ });
552
+ return {
553
+ targetModelName,
554
+ fieldSelectors: [],
555
+ isValid: false,
556
+ errors
557
+ };
558
+ }
559
+ console.log('ConfigurationAnalyzer: Successfully analyzed configuration:', {
560
+ targetModelName,
561
+ fieldSelectorsCount: fieldSelectors.length,
562
+ fieldName: config.fieldName
563
+ });
564
+ return {
565
+ targetModelName,
566
+ fieldSelectors,
567
+ isValid: true,
568
+ errors: []
569
+ };
570
+ }
571
+ catch (error) {
572
+ const errorMessage = `Analysis failed: ${error instanceof Error ? error.message : String(error)}`;
573
+ errors.push(errorMessage);
574
+ this.errorHandler.logSelectionSetError(SelectionSetErrorType.INVALID_CONFIGURATION, errorMessage, config, undefined, {
575
+ method: 'analyzeConfiguration',
576
+ step: 'exception_handling',
577
+ originalError: {
578
+ message: error instanceof Error ? error.message : String(error),
579
+ stack: error instanceof Error ? error.stack : undefined,
580
+ type: typeof error
581
+ }
582
+ });
583
+ return {
584
+ targetModelName: '',
585
+ fieldSelectors: [],
586
+ isValid: false,
587
+ errors
588
+ };
589
+ }
590
+ }
591
+ /**
592
+ * Extract the target model name from a relationship configuration
593
+ * Enhanced with error handling and validation
594
+ *
595
+ * For a FormViewField relationship, this would extract the target model
596
+ * that we want to display (e.g., "Field" from the FormViewField junction)
597
+ */
598
+ extractTargetModelName(config) {
599
+ try {
600
+ if (!config || !config.fieldName) {
601
+ this.errorHandler.logSelectionSetError(SelectionSetErrorType.INVALID_CONFIGURATION, 'Cannot extract target model name: configuration or fieldName is missing', config, undefined, {
602
+ method: 'extractTargetModelName',
603
+ hasConfig: !!config,
604
+ hasFieldName: !!(config?.fieldName)
605
+ });
606
+ return '';
607
+ }
608
+ // The fieldName typically corresponds to the target model we want to access
609
+ // For example, if fieldName is "formView", the target model is "FormView"
610
+ // If fieldName is "service", the target model is "Service"
611
+ // Convert fieldName to PascalCase to match Amplify model naming conventions
612
+ const targetModelName = this.toPascalCase(config.fieldName);
613
+ if (!targetModelName) {
614
+ this.errorHandler.logSelectionSetError(SelectionSetErrorType.INVALID_CONFIGURATION, 'Failed to convert fieldName to PascalCase for target model name', config, undefined, {
615
+ method: 'extractTargetModelName',
616
+ fieldName: config.fieldName,
617
+ conversionResult: targetModelName
618
+ });
619
+ return '';
620
+ }
621
+ console.log(`ConfigurationAnalyzer: Extracted target model name "${targetModelName}" from field "${config.fieldName}"`);
622
+ return targetModelName;
623
+ }
624
+ catch (error) {
625
+ this.errorHandler.logSelectionSetError(SelectionSetErrorType.INVALID_CONFIGURATION, `Error extracting target model name: ${error instanceof Error ? error.message : String(error)}`, config, undefined, {
626
+ method: 'extractTargetModelName',
627
+ originalError: {
628
+ message: error instanceof Error ? error.message : String(error),
629
+ stack: error instanceof Error ? error.stack : undefined
630
+ }
631
+ });
632
+ return '';
633
+ }
634
+ }
635
+ /**
636
+ * Determine appropriate field selectors for a target model
637
+ *
638
+ * This generates GraphQL field selectors that include the relationship field
639
+ * and common display properties like id, name, title, etc.
640
+ */
641
+ determineFieldSelectors(targetModel) {
642
+ if (!targetModel) {
643
+ return [];
644
+ }
645
+ const selectors = [];
646
+ // Always include the base record ID
647
+ selectors.push('id');
648
+ // Convert target model to camelCase for field access
649
+ const fieldName = this.toCamelCase(targetModel);
650
+ // Add the relationship field with its ID
651
+ selectors.push(`${fieldName}.id`);
652
+ // Add common display fields for the relationship
653
+ for (const displayField of this.COMMON_DISPLAY_FIELDS) {
654
+ selectors.push(`${fieldName}.${displayField}`);
655
+ }
656
+ return selectors;
657
+ }
658
+ /**
659
+ * Parse and validate nested relationship paths
660
+ *
661
+ * Handles complex relationship configurations like "formView.user.profile"
662
+ * and validates for circular references
663
+ */
664
+ parseNestedRelationshipPath(fieldName, maxDepth = 3) {
665
+ if (!fieldName) {
666
+ return [];
667
+ }
668
+ // Split the field name by dots to handle nested paths
669
+ const pathSegments = fieldName.split('.');
670
+ // Limit nesting depth to prevent overly complex queries
671
+ if (pathSegments.length > maxDepth) {
672
+ console.warn(`ConfigurationAnalyzer: Nested relationship path "${fieldName}" exceeds maximum depth of ${maxDepth}. Truncating.`);
673
+ pathSegments.splice(maxDepth);
674
+ }
675
+ // Check for circular references (same segment appearing multiple times)
676
+ const uniqueSegments = new Set(pathSegments);
677
+ if (uniqueSegments.size !== pathSegments.length) {
678
+ console.warn(`ConfigurationAnalyzer: Circular reference detected in path "${fieldName}". Using only unique segments.`);
679
+ return Array.from(uniqueSegments);
680
+ }
681
+ return pathSegments;
682
+ }
683
+ /**
684
+ * Generate field selectors for nested relationship paths
685
+ *
686
+ * Creates GraphQL selectors that can handle nested relationships
687
+ * like "formView.user.profile.name"
688
+ */
689
+ generateNestedFieldSelectors(fieldName, targetModel) {
690
+ const pathSegments = this.parseNestedRelationshipPath(fieldName);
691
+ const selectors = [];
692
+ // Always include the base record ID
693
+ selectors.push('id');
694
+ if (pathSegments.length === 0) {
695
+ return selectors;
696
+ }
697
+ // For single-level relationships, use the existing logic
698
+ if (pathSegments.length === 1) {
699
+ const camelCaseField = this.toCamelCase(pathSegments[0]);
700
+ selectors.push(`${camelCaseField}.id`);
701
+ // Add common display fields
702
+ for (const displayField of this.COMMON_DISPLAY_FIELDS) {
703
+ selectors.push(`${camelCaseField}.${displayField}`);
704
+ }
705
+ return selectors;
706
+ }
707
+ // For nested relationships, build the path progressively
708
+ let currentPath = '';
709
+ for (let i = 0; i < pathSegments.length; i++) {
710
+ const segment = this.toCamelCase(pathSegments[i]);
711
+ if (i === 0) {
712
+ currentPath = segment;
713
+ }
714
+ else {
715
+ currentPath += `.${segment}`;
716
+ }
717
+ // Add ID for each level
718
+ selectors.push(`${currentPath}.id`);
719
+ // Add display fields only for the final level to avoid overly complex queries
720
+ if (i === pathSegments.length - 1) {
721
+ for (const displayField of this.COMMON_DISPLAY_FIELDS) {
722
+ selectors.push(`${currentPath}.${displayField}`);
723
+ }
724
+ }
725
+ }
726
+ return selectors;
727
+ }
728
+ /**
729
+ * Enhanced field selector determination that supports nested paths
730
+ */
731
+ determineFieldSelectorsEnhanced(targetModel, fieldName) {
732
+ if (!targetModel) {
733
+ return [];
734
+ }
735
+ // If no specific field name provided, use the target model name
736
+ const effectiveFieldName = fieldName || this.toCamelCase(targetModel);
737
+ // Check if this is a nested relationship path
738
+ if (effectiveFieldName.includes('.')) {
739
+ return this.generateNestedFieldSelectors(effectiveFieldName, targetModel);
740
+ }
741
+ // Use the standard field selector generation for simple relationships
742
+ return this.determineFieldSelectors(targetModel);
743
+ }
744
+ /**
745
+ * Convert a string to PascalCase (first letter uppercase, rest camelCase)
746
+ */
747
+ toPascalCase(str) {
748
+ if (!str)
749
+ return '';
750
+ // Handle camelCase to PascalCase
751
+ return str.charAt(0).toUpperCase() + str.slice(1);
752
+ }
753
+ /**
754
+ * Convert a string to camelCase (first letter lowercase)
755
+ */
756
+ toCamelCase(str) {
757
+ if (!str)
758
+ return '';
759
+ // Handle PascalCase to camelCase
760
+ return str.charAt(0).toLowerCase() + str.slice(1);
761
+ }
762
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: ConfigurationAnalyzerService, deps: [{ token: ErrorHandlerService }], target: i0.ɵɵFactoryTarget.Injectable });
763
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: ConfigurationAnalyzerService, providedIn: 'root' });
764
+ }
765
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: ConfigurationAnalyzerService, decorators: [{
766
+ type: Injectable,
767
+ args: [{
768
+ providedIn: 'root'
769
+ }]
770
+ }], ctorParameters: () => [{ type: ErrorHandlerService }] });
771
+
772
+ /**
773
+ * Service for introspecting Amplify schema data to validate and enhance selection sets
774
+ *
775
+ * This service leverages Amplify's schema introspection data to validate field existence,
776
+ * extract model information, and provide schema-aware field selection for GraphQL queries.
777
+ */
778
+ class SchemaIntrospectorService {
779
+ errorHandler;
780
+ amplifyOutputs = null;
781
+ /**
782
+ * Common display field names that are typically used for showing model data
783
+ */
784
+ COMMON_DISPLAY_FIELDS = ['name', 'title', 'label', 'displayName', 'description'];
785
+ /**
786
+ * System fields that are typically not used for display
787
+ */
788
+ SYSTEM_FIELDS = ['id', 'createdAt', 'updatedAt', 'owner'];
789
+ constructor(errorHandler) {
790
+ this.errorHandler = errorHandler;
791
+ }
792
+ /**
793
+ * Initialize the service with Amplify outputs data
794
+ * Enhanced with error handling and validation
795
+ * This should be called by consuming applications to provide schema data
796
+ */
797
+ initializeSchema(amplifyOutputs) {
798
+ try {
799
+ if (!amplifyOutputs) {
800
+ this.errorHandler.logSchemaError('', undefined, {
801
+ method: 'initializeSchema',
802
+ error: 'amplifyOutputs is null or undefined'
803
+ });
804
+ console.warn('SchemaIntrospectorService: Cannot initialize with null or undefined amplifyOutputs');
805
+ return;
806
+ }
807
+ // Validate schema structure
808
+ if (!amplifyOutputs.data || !amplifyOutputs.data.model_introspection || !amplifyOutputs.data.model_introspection.models) {
809
+ this.errorHandler.logSchemaError('', undefined, {
810
+ method: 'initializeSchema',
811
+ error: 'Invalid schema structure',
812
+ hasData: !!amplifyOutputs.data,
813
+ hasModelIntrospection: !!(amplifyOutputs.data?.model_introspection),
814
+ hasModels: !!(amplifyOutputs.data?.model_introspection?.models)
815
+ });
816
+ console.warn('SchemaIntrospectorService: Invalid schema structure in amplifyOutputs');
817
+ return;
818
+ }
819
+ this.amplifyOutputs = amplifyOutputs;
820
+ const modelCount = Object.keys(amplifyOutputs.data.model_introspection.models).length;
821
+ console.log(`SchemaIntrospectorService: Successfully initialized with schema data (${modelCount} models)`);
822
+ }
823
+ catch (error) {
824
+ this.errorHandler.logSelectionSetError(SelectionSetErrorType.SCHEMA_UNAVAILABLE, `Error initializing schema: ${error instanceof Error ? error.message : String(error)}`, undefined, undefined, {
825
+ method: 'initializeSchema',
826
+ originalError: {
827
+ message: error instanceof Error ? error.message : String(error),
828
+ stack: error instanceof Error ? error.stack : undefined
829
+ }
830
+ });
831
+ console.error('SchemaIntrospectorService: Error initializing schema:', error);
832
+ }
833
+ }
834
+ /**
835
+ * Get field information for a specific model from the schema
836
+ * Enhanced with comprehensive error handling and logging
837
+ */
838
+ getModelFields(modelName) {
839
+ try {
840
+ if (!this.isSchemaAvailable()) {
841
+ this.errorHandler.logSchemaError(modelName, undefined, {
842
+ method: 'getModelFields',
843
+ error: 'Schema data not available'
844
+ });
845
+ console.warn('SchemaIntrospectorService: Schema data not available');
846
+ return [];
847
+ }
848
+ if (!modelName) {
849
+ this.errorHandler.logSchemaError('', undefined, {
850
+ method: 'getModelFields',
851
+ error: 'Model name is empty or undefined'
852
+ });
853
+ return [];
854
+ }
855
+ const models = this.amplifyOutputs.data.model_introspection.models;
856
+ const modelData = models[modelName];
857
+ if (!modelData) {
858
+ this.errorHandler.logSchemaError(modelName, undefined, {
859
+ method: 'getModelFields',
860
+ error: 'Model not found in schema',
861
+ availableModels: Object.keys(models)
862
+ });
863
+ console.warn(`SchemaIntrospectorService: Model "${modelName}" not found in schema`);
864
+ return [];
865
+ }
866
+ if (!modelData.fields) {
867
+ this.errorHandler.logSchemaError(modelName, undefined, {
868
+ method: 'getModelFields',
869
+ error: 'Model has no fields property',
870
+ modelData: Object.keys(modelData)
871
+ });
872
+ console.warn(`SchemaIntrospectorService: Model "${modelName}" has no fields`);
873
+ return [];
874
+ }
875
+ const fields = [];
876
+ const fieldValues = Object.values(modelData.fields);
877
+ for (const field of fieldValues) {
878
+ try {
879
+ const fieldData = field;
880
+ if (!fieldData.name) {
881
+ console.warn(`SchemaIntrospectorService: Field in model "${modelName}" has no name property`);
882
+ continue;
883
+ }
884
+ fields.push({
885
+ name: fieldData.name,
886
+ type: this.determineFieldType(fieldData.type),
887
+ isRequired: fieldData.isRequired || false,
888
+ isRelationship: this.isRelationshipField(fieldData)
889
+ });
890
+ }
891
+ catch (fieldError) {
892
+ console.warn(`SchemaIntrospectorService: Error processing field in model "${modelName}":`, fieldError);
893
+ // Continue processing other fields
894
+ }
895
+ }
896
+ console.log(`SchemaIntrospectorService: Retrieved ${fields.length} fields for model "${modelName}"`);
897
+ return fields;
898
+ }
899
+ catch (error) {
900
+ this.errorHandler.logSelectionSetError(SelectionSetErrorType.MODEL_NOT_FOUND, `Error getting fields for model "${modelName}": ${error instanceof Error ? error.message : String(error)}`, undefined, undefined, {
901
+ method: 'getModelFields',
902
+ modelName,
903
+ originalError: {
904
+ message: error instanceof Error ? error.message : String(error),
905
+ stack: error instanceof Error ? error.stack : undefined
906
+ }
907
+ });
908
+ console.error(`SchemaIntrospectorService: Error getting fields for model "${modelName}":`, error);
909
+ return [];
910
+ }
911
+ }
912
+ /**
913
+ * Validate that a specific field exists in a model
914
+ */
915
+ validateFieldExists(modelName, fieldName) {
916
+ if (!this.isSchemaAvailable() || !modelName || !fieldName) {
917
+ return false;
918
+ }
919
+ try {
920
+ const models = this.amplifyOutputs.data.model_introspection.models;
921
+ const modelData = models[modelName];
922
+ if (!modelData || !modelData.fields) {
923
+ return false;
924
+ }
925
+ return fieldName in modelData.fields;
926
+ }
927
+ catch (error) {
928
+ console.error(`SchemaIntrospectorService: Error validating field "${fieldName}" in model "${modelName}":`, error);
929
+ return false;
930
+ }
931
+ }
932
+ /**
933
+ * Get common display fields for a model (e.g., name, title, label)
934
+ */
935
+ getCommonDisplayFields(modelName) {
936
+ if (!this.isSchemaAvailable()) {
937
+ // Fallback: return conservative display fields when schema is unavailable
938
+ return this.getConservativeDisplayFields(modelName);
939
+ }
940
+ const modelFields = this.getModelFields(modelName);
941
+ const displayFields = [];
942
+ // Always include id for relationships
943
+ if (this.validateFieldExists(modelName, 'id')) {
944
+ displayFields.push('id');
945
+ }
946
+ // Look for common display fields in the model
947
+ for (const commonField of this.COMMON_DISPLAY_FIELDS) {
948
+ const field = modelFields.find(f => f.name === commonField);
949
+ if (field && !field.isRelationship) {
950
+ displayFields.push(commonField);
951
+ }
952
+ }
953
+ // If no common display fields found, include the first non-system, non-relationship field
954
+ if (displayFields.length <= 1) { // Only id was added
955
+ const firstDisplayField = modelFields.find(f => !this.SYSTEM_FIELDS.includes(f.name) &&
956
+ !f.isRelationship &&
957
+ f.type === 'string');
958
+ if (firstDisplayField && !displayFields.includes(firstDisplayField.name)) {
959
+ displayFields.push(firstDisplayField.name);
960
+ }
961
+ }
962
+ return displayFields;
963
+ }
964
+ /**
965
+ * Check if schema introspection data is available
966
+ */
967
+ isSchemaAvailable() {
968
+ return !!(this.amplifyOutputs &&
969
+ this.amplifyOutputs.data &&
970
+ this.amplifyOutputs.data.model_introspection &&
971
+ this.amplifyOutputs.data.model_introspection.models);
972
+ }
973
+ /**
974
+ * Get conservative display fields when schema data is unavailable
975
+ * This provides a safe fallback that works with most Amplify models
976
+ */
977
+ getConservativeDisplayFields(modelName) {
978
+ console.warn(`SchemaIntrospectorService: Schema unavailable, using conservative display fields for model "${modelName}"`);
979
+ // Return a conservative set of fields that are commonly available
980
+ const conservativeFields = ['id'];
981
+ // Add all common display field names - the GraphQL query will simply ignore non-existent fields
982
+ conservativeFields.push(...this.COMMON_DISPLAY_FIELDS);
983
+ return conservativeFields;
984
+ }
985
+ /**
986
+ * Get model fields with fallback behavior when schema is unavailable
987
+ */
988
+ getModelFieldsWithFallback(modelName) {
989
+ if (this.isSchemaAvailable()) {
990
+ return this.getModelFields(modelName);
991
+ }
992
+ // Fallback: return conservative field set
993
+ console.warn(`SchemaIntrospectorService: Schema unavailable, using conservative field set for model "${modelName}"`);
994
+ const conservativeFields = [
995
+ { name: 'id', type: 'id', isRequired: true, isRelationship: false }
996
+ ];
997
+ // Add common display fields as optional string fields
998
+ for (const fieldName of this.COMMON_DISPLAY_FIELDS) {
999
+ conservativeFields.push({
1000
+ name: fieldName,
1001
+ type: 'string',
1002
+ isRequired: false,
1003
+ isRelationship: false
1004
+ });
1005
+ }
1006
+ return conservativeFields;
1007
+ }
1008
+ /**
1009
+ * Validate field existence with fallback behavior
1010
+ * When schema is unavailable, assumes common fields exist
1011
+ */
1012
+ validateFieldExistsWithFallback(modelName, fieldName) {
1013
+ if (this.isSchemaAvailable()) {
1014
+ return this.validateFieldExists(modelName, fieldName);
1015
+ }
1016
+ // Fallback: assume common fields exist
1017
+ const commonFields = ['id', ...this.COMMON_DISPLAY_FIELDS];
1018
+ const fieldExists = commonFields.includes(fieldName);
1019
+ if (!fieldExists) {
1020
+ console.warn(`SchemaIntrospectorService: Schema unavailable, cannot validate field "${fieldName}" in model "${modelName}". Assuming it does not exist.`);
1021
+ }
1022
+ return fieldExists;
1023
+ }
1024
+ /**
1025
+ * Get safe field selectors that work even when schema data is unavailable
1026
+ * This method prioritizes reliability over completeness
1027
+ */
1028
+ getSafeFieldSelectors(modelName, fieldName) {
1029
+ const selectors = ['id']; // Always safe to include
1030
+ if (!fieldName) {
1031
+ return selectors;
1032
+ }
1033
+ // Add the relationship field with id (safe even if field doesn't exist)
1034
+ selectors.push(`${fieldName}.id`);
1035
+ if (this.isSchemaAvailable()) {
1036
+ // Use schema-aware field selection
1037
+ const displayFields = this.getCommonDisplayFields(modelName);
1038
+ for (const displayField of displayFields) {
1039
+ if (displayField !== 'id') { // Already added
1040
+ selectors.push(`${fieldName}.${displayField}`);
1041
+ }
1042
+ }
1043
+ }
1044
+ else {
1045
+ // Use conservative field selection
1046
+ const conservativeFields = this.getConservativeDisplayFields(modelName);
1047
+ for (const conservativeField of conservativeFields) {
1048
+ if (conservativeField !== 'id') { // Already added
1049
+ selectors.push(`${fieldName}.${conservativeField}`);
1050
+ }
1051
+ }
1052
+ }
1053
+ return selectors;
1054
+ }
1055
+ /**
1056
+ * Get schema status information for debugging
1057
+ */
1058
+ getSchemaStatus() {
1059
+ const status = {
1060
+ isAvailable: this.isSchemaAvailable(),
1061
+ modelCount: 0,
1062
+ availableModels: [],
1063
+ lastError: undefined
1064
+ };
1065
+ if (status.isAvailable) {
1066
+ try {
1067
+ status.availableModels = this.getAvailableModels();
1068
+ status.modelCount = status.availableModels.length;
1069
+ }
1070
+ catch (error) {
1071
+ status.lastError = error instanceof Error ? error.message : String(error);
1072
+ }
1073
+ }
1074
+ else {
1075
+ status.lastError = 'Schema data not available or invalid structure';
1076
+ }
1077
+ return status;
1078
+ }
1079
+ /**
1080
+ * Get all available model names from the schema
1081
+ */
1082
+ getAvailableModels() {
1083
+ if (!this.isSchemaAvailable()) {
1084
+ return [];
1085
+ }
1086
+ try {
1087
+ return Object.keys(this.amplifyOutputs.data.model_introspection.models);
1088
+ }
1089
+ catch (error) {
1090
+ console.error('SchemaIntrospectorService: Error getting available models:', error);
1091
+ return [];
1092
+ }
1093
+ }
1094
+ /**
1095
+ * Get detailed information about a specific model
1096
+ */
1097
+ getModelInfo(modelName) {
1098
+ if (!this.isSchemaAvailable() || !modelName) {
1099
+ return null;
1100
+ }
1101
+ try {
1102
+ const models = this.amplifyOutputs.data.model_introspection.models;
1103
+ return models[modelName] || null;
1104
+ }
1105
+ catch (error) {
1106
+ console.error(`SchemaIntrospectorService: Error getting model info for "${modelName}":`, error);
1107
+ return null;
1108
+ }
1109
+ }
1110
+ /**
1111
+ * Determine the simplified type of a field from Amplify schema data
1112
+ */
1113
+ determineFieldType(fieldType) {
1114
+ if (typeof fieldType === 'string') {
1115
+ return fieldType.toLowerCase();
1116
+ }
1117
+ if (typeof fieldType === 'object') {
1118
+ if (fieldType.model) {
1119
+ return 'relationship';
1120
+ }
1121
+ if (fieldType.enum) {
1122
+ return 'enum';
1123
+ }
1124
+ }
1125
+ return 'unknown';
1126
+ }
1127
+ /**
1128
+ * Check if a field represents a relationship to another model
1129
+ */
1130
+ isRelationshipField(fieldData) {
1131
+ return !!(fieldData.type &&
1132
+ typeof fieldData.type === 'object' &&
1133
+ fieldData.type.model);
1134
+ }
1135
+ /**
1136
+ * Get relationship information for a field
1137
+ */
1138
+ getRelationshipInfo(modelName, fieldName) {
1139
+ if (!this.validateFieldExists(modelName, fieldName)) {
1140
+ return null;
1141
+ }
1142
+ try {
1143
+ const models = this.amplifyOutputs.data.model_introspection.models;
1144
+ const modelData = models[modelName];
1145
+ const fieldData = modelData.fields[fieldName];
1146
+ if (!this.isRelationshipField(fieldData)) {
1147
+ return null;
1148
+ }
1149
+ return {
1150
+ targetModel: fieldData.type.model,
1151
+ connectionType: fieldData.association?.connectionType,
1152
+ associatedWith: fieldData.association?.associatedWith,
1153
+ targetNames: fieldData.association?.targetNames
1154
+ };
1155
+ }
1156
+ catch (error) {
1157
+ console.error(`SchemaIntrospectorService: Error getting relationship info for "${modelName}.${fieldName}":`, error);
1158
+ return null;
1159
+ }
1160
+ }
1161
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: SchemaIntrospectorService, deps: [{ token: ErrorHandlerService }], target: i0.ɵɵFactoryTarget.Injectable });
1162
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: SchemaIntrospectorService, providedIn: 'root' });
1163
+ }
1164
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: SchemaIntrospectorService, decorators: [{
1165
+ type: Injectable,
1166
+ args: [{
1167
+ providedIn: 'root'
1168
+ }]
1169
+ }], ctorParameters: () => [{ type: ErrorHandlerService }] });
1170
+
1171
+ /**
1172
+ * Service for generating dynamic GraphQL selection sets for Amplify relationships
1173
+ *
1174
+ * This service replaces hardcoded selection set logic with dynamic generation
1175
+ * based on relationship configurations, schema introspection, and intelligent caching.
1176
+ */
1177
+ class SelectionSetGeneratorService {
1178
+ configurationAnalyzer;
1179
+ schemaIntrospector;
1180
+ errorHandler;
1181
+ /**
1182
+ * Cache for generated selection sets to improve performance
1183
+ */
1184
+ selectionSetCache = new Map();
1185
+ /**
1186
+ * Default configuration for selection set generation
1187
+ */
1188
+ DEFAULT_CONFIG = {
1189
+ pattern: SelectionSetPattern.SELECTIVE,
1190
+ maxDepth: 3,
1191
+ maxFields: 10,
1192
+ validateWithSchema: true,
1193
+ requiredFields: ['id'],
1194
+ complexityLimits: {
1195
+ maxNestingDepth: 3,
1196
+ maxFieldCount: 20,
1197
+ maxWildcardSelections: 2,
1198
+ preventCircularReferences: true,
1199
+ maxSelectorLength: 100
1200
+ }
1201
+ };
1202
+ /**
1203
+ * Default display optimization configuration
1204
+ */
1205
+ DEFAULT_DISPLAY_CONFIG = {
1206
+ enabled: true,
1207
+ commonDisplayFields: ['id', 'name', 'title', 'label', 'displayName', 'description'],
1208
+ excludeFromOptimization: ['id', 'createdAt', 'updatedAt'],
1209
+ prioritizeDisplayFields: true,
1210
+ maxNonDisplayFields: 5
1211
+ };
1212
+ constructor(configurationAnalyzer, schemaIntrospector, errorHandler) {
1213
+ this.configurationAnalyzer = configurationAnalyzer;
1214
+ this.schemaIntrospector = schemaIntrospector;
1215
+ this.errorHandler = errorHandler;
1216
+ }
1217
+ /**
1218
+ * Generate a selection set for a given relationship configuration
1219
+ * Enhanced with comprehensive error handling and logging
1220
+ */
1221
+ generateSelectionSet(config) {
1222
+ try {
1223
+ // Validate input configuration
1224
+ if (!config) {
1225
+ const error = this.errorHandler.logSelectionSetError(SelectionSetErrorType.INVALID_CONFIGURATION, 'Configuration is null or undefined', config);
1226
+ return this.handleGenerationError(error, '');
1227
+ }
1228
+ // Check cache first
1229
+ const cacheKey = this.generateCacheKey(config);
1230
+ const cached = this.getCachedSelectionSet(cacheKey);
1231
+ if (cached) {
1232
+ console.log('SelectionSetGenerator: Using cached selection set for config:', config.fieldName);
1233
+ return cached.selectionSet;
1234
+ }
1235
+ // Analyze the configuration
1236
+ const analysisResult = this.configurationAnalyzer.analyzeConfiguration(config);
1237
+ if (!analysisResult || !analysisResult.isValid) {
1238
+ const errors = analysisResult?.errors || ['Invalid configuration'];
1239
+ const error = this.errorHandler.logConfigurationError(config, errors, {
1240
+ analysisResult,
1241
+ cacheKey
1242
+ });
1243
+ return this.handleGenerationError(error, config.fieldName);
1244
+ }
1245
+ // Generate selection set based on analysis and schema availability
1246
+ const selectionSet = this.generateSelectionSetFromAnalysis(config, analysisResult);
1247
+ // Validate the generated selection set
1248
+ if (!this.validateSelectionSet(selectionSet)) {
1249
+ const error = this.errorHandler.logSelectionSetError(SelectionSetErrorType.INVALID_CONFIGURATION, 'Generated selection set failed validation', config, selectionSet, { analysisResult });
1250
+ return this.handleGenerationError(error, config.fieldName);
1251
+ }
1252
+ // Cache the result
1253
+ this.cacheSelectionSet(cacheKey, selectionSet);
1254
+ console.log('SelectionSetGenerator: Successfully generated selection set:', {
1255
+ config: config.fieldName,
1256
+ selectionSetLength: selectionSet.length,
1257
+ selectionSet
1258
+ });
1259
+ return selectionSet;
1260
+ }
1261
+ catch (error) {
1262
+ const selectionSetError = this.errorHandler.logSelectionSetError(SelectionSetErrorType.GRAPHQL_ERROR, `Unexpected error during selection set generation: ${error instanceof Error ? error.message : String(error)}`, config, undefined, {
1263
+ originalError: {
1264
+ message: error instanceof Error ? error.message : String(error),
1265
+ stack: error instanceof Error ? error.stack : undefined,
1266
+ type: typeof error
1267
+ }
1268
+ });
1269
+ return this.handleGenerationError(selectionSetError, config?.fieldName || '');
1270
+ }
1271
+ }
1272
+ /**
1273
+ * Generate a fallback selection set when primary generation fails
1274
+ * Uses progressive fallback strategies: comprehensive → selective → minimal
1275
+ * Enhanced with comprehensive error handling and logging
1276
+ */
1277
+ generateFallbackSelectionSet(fieldName) {
1278
+ try {
1279
+ if (!fieldName) {
1280
+ const error = this.errorHandler.logSelectionSetError(SelectionSetErrorType.INVALID_CONFIGURATION, 'Field name is empty or undefined for fallback generation', undefined, undefined, { fieldName });
1281
+ return this.generateMinimalFallbackSelectionSet();
1282
+ }
1283
+ console.warn(`SelectionSetGenerator: Using fallback selection set for field "${fieldName}"`);
1284
+ // Try progressive fallback strategies
1285
+ try {
1286
+ // Strategy 1: Comprehensive fallback (if schema is available)
1287
+ if (this.schemaIntrospector.isSchemaAvailable()) {
1288
+ const result = this.generateComprehensiveFallbackSelectionSet(fieldName);
1289
+ console.log(`SelectionSetGenerator: Comprehensive fallback successful for field "${fieldName}"`);
1290
+ return result;
1291
+ }
1292
+ // Strategy 2: Selective fallback (configuration-based)
1293
+ const result = this.generateSelectiveFallbackSelectionSet(fieldName);
1294
+ console.log(`SelectionSetGenerator: Selective fallback successful for field "${fieldName}"`);
1295
+ return result;
1296
+ }
1297
+ catch (error) {
1298
+ const selectionSetError = this.errorHandler.logSelectionSetError(SelectionSetErrorType.GRAPHQL_ERROR, `Error in fallback generation, using minimal fallback: ${error instanceof Error ? error.message : String(error)}`, undefined, undefined, {
1299
+ fieldName,
1300
+ originalError: {
1301
+ message: error instanceof Error ? error.message : String(error),
1302
+ stack: error instanceof Error ? error.stack : undefined
1303
+ },
1304
+ schemaAvailable: this.schemaIntrospector.isSchemaAvailable()
1305
+ });
1306
+ // Strategy 3: Minimal fallback (last resort)
1307
+ return this.generateMinimalFallbackSelectionSet(fieldName);
1308
+ }
1309
+ }
1310
+ catch (error) {
1311
+ // Absolute last resort - return basic selection set
1312
+ const selectionSetError = this.errorHandler.logSelectionSetError(SelectionSetErrorType.GRAPHQL_ERROR, `Critical error in fallback generation: ${error instanceof Error ? error.message : String(error)}`, undefined, undefined, {
1313
+ fieldName,
1314
+ criticalError: {
1315
+ message: error instanceof Error ? error.message : String(error),
1316
+ stack: error instanceof Error ? error.stack : undefined
1317
+ }
1318
+ });
1319
+ console.error('SelectionSetGenerator: Critical error in fallback generation, returning absolute minimal fallback');
1320
+ return ['id'];
1321
+ }
1322
+ }
1323
+ /**
1324
+ * Generate comprehensive fallback selection set using schema data
1325
+ * This is the first fallback strategy when schema is available
1326
+ */
1327
+ generateComprehensiveFallbackSelectionSet(fieldName) {
1328
+ console.log(`SelectionSetGenerator: Using comprehensive fallback strategy for field "${fieldName}"`);
1329
+ const selectionSet = ['id'];
1330
+ try {
1331
+ // Use schema introspector to get safe field selectors
1332
+ const safeSelectors = this.schemaIntrospector.getSafeFieldSelectors('', fieldName);
1333
+ // Add all safe selectors
1334
+ for (const selector of safeSelectors) {
1335
+ if (!selectionSet.includes(selector)) {
1336
+ selectionSet.push(selector);
1337
+ }
1338
+ }
1339
+ // Apply display optimization for fallback
1340
+ const mockConfig = {
1341
+ relationshipModelName: '',
1342
+ baseModelName: '',
1343
+ fieldName,
1344
+ associatedWith: ''
1345
+ };
1346
+ const optimizedSet = this.applyDisplayOptimization(selectionSet, mockConfig);
1347
+ // Apply complexity limits to prevent overly expensive queries
1348
+ return this.applyComplexityLimits(optimizedSet);
1349
+ }
1350
+ catch (error) {
1351
+ console.warn('SelectionSetGenerator: Comprehensive fallback failed, falling back to selective:', error);
1352
+ return this.generateSelectiveFallbackSelectionSet(fieldName);
1353
+ }
1354
+ }
1355
+ /**
1356
+ * Generate selective fallback selection set using common field patterns
1357
+ * This is the second fallback strategy when schema is unavailable
1358
+ */
1359
+ generateSelectiveFallbackSelectionSet(fieldName) {
1360
+ console.log(`SelectionSetGenerator: Using selective fallback strategy for field "${fieldName}"`);
1361
+ const selectionSet = ['id'];
1362
+ // Add the relationship field with id (always safe)
1363
+ selectionSet.push(`${fieldName}.id`);
1364
+ // Add common display fields that are likely to exist
1365
+ const commonDisplayFields = ['name', 'title', 'label', 'displayName'];
1366
+ for (const field of commonDisplayFields) {
1367
+ selectionSet.push(`${fieldName}.${field}`);
1368
+ }
1369
+ // Add some additional fields that might be useful for display
1370
+ const additionalFields = ['description', 'status', 'type'];
1371
+ for (const field of additionalFields) {
1372
+ selectionSet.push(`${fieldName}.${field}`);
1373
+ }
1374
+ return selectionSet;
1375
+ }
1376
+ /**
1377
+ * Generate minimal fallback selection set for critical error cases
1378
+ * This is the last resort fallback strategy
1379
+ */
1380
+ generateMinimalFallbackSelectionSet(fieldName) {
1381
+ console.log(`SelectionSetGenerator: Using minimal fallback strategy${fieldName ? ` for field "${fieldName}"` : ''}`);
1382
+ const selectionSet = ['id'];
1383
+ if (fieldName) {
1384
+ // Include only the most basic relationship data
1385
+ selectionSet.push(`${fieldName}.id`);
1386
+ // Add one common field that's most likely to exist
1387
+ selectionSet.push(`${fieldName}.name`);
1388
+ }
1389
+ return selectionSet;
1390
+ }
1391
+ /**
1392
+ * Generate fallback selection set with retry logic
1393
+ * Attempts multiple fallback strategies and validates each one
1394
+ * Enhanced with comprehensive error handling and recovery tracking
1395
+ */
1396
+ generateFallbackSelectionSetWithRetry(fieldName, previousErrors = []) {
1397
+ try {
1398
+ if (!fieldName) {
1399
+ const error = this.errorHandler.logSelectionSetError(SelectionSetErrorType.INVALID_CONFIGURATION, 'Field name is empty for fallback retry generation', undefined, undefined, { previousErrors });
1400
+ return ['id'];
1401
+ }
1402
+ console.log(`SelectionSetGenerator: Generating fallback with retry for field "${fieldName}", previous errors: ${previousErrors.length}`);
1403
+ const strategies = [
1404
+ () => this.generateComprehensiveFallbackSelectionSet(fieldName),
1405
+ () => this.generateSelectiveFallbackSelectionSet(fieldName),
1406
+ () => this.generateMinimalFallbackSelectionSet(fieldName)
1407
+ ];
1408
+ // Skip strategies that have already failed
1409
+ const availableStrategies = strategies.slice(previousErrors.length);
1410
+ for (let i = 0; i < availableStrategies.length; i++) {
1411
+ try {
1412
+ const strategy = availableStrategies[i];
1413
+ const selectionSet = strategy();
1414
+ // Validate the selection set
1415
+ if (this.validateSelectionSet(selectionSet)) {
1416
+ const strategyName = ['comprehensive', 'selective', 'minimal'][previousErrors.length + i];
1417
+ console.log(`SelectionSetGenerator: Successfully generated ${strategyName} fallback selection set`);
1418
+ // Log successful recovery if this was after previous failures
1419
+ if (previousErrors.length > 0) {
1420
+ this.errorHandler.logSuccessfulRecovery(this.errorHandler.logSelectionSetError(SelectionSetErrorType.GRAPHQL_ERROR, `Previous errors: ${previousErrors.join(', ')}`, undefined, undefined, { fieldName, previousErrors }), strategyName, selectionSet);
1421
+ }
1422
+ return selectionSet;
1423
+ }
1424
+ else {
1425
+ const strategyName = ['comprehensive', 'selective', 'minimal'][previousErrors.length + i];
1426
+ const validationError = `Generated selection set failed validation for ${strategyName} strategy`;
1427
+ console.warn(`SelectionSetGenerator: ${validationError}`);
1428
+ previousErrors.push(validationError);
1429
+ }
1430
+ }
1431
+ catch (error) {
1432
+ const strategyName = ['comprehensive', 'selective', 'minimal'][previousErrors.length + i];
1433
+ const errorMessage = error instanceof Error ? error.message : String(error);
1434
+ this.errorHandler.logSelectionSetError(SelectionSetErrorType.GRAPHQL_ERROR, `${strategyName} fallback strategy failed: ${errorMessage}`, undefined, undefined, {
1435
+ fieldName,
1436
+ strategyName,
1437
+ strategyIndex: previousErrors.length + i,
1438
+ originalError: {
1439
+ message: errorMessage,
1440
+ stack: error instanceof Error ? error.stack : undefined
1441
+ }
1442
+ });
1443
+ console.warn(`SelectionSetGenerator: ${strategyName} fallback strategy failed:`, error);
1444
+ previousErrors.push(errorMessage);
1445
+ }
1446
+ }
1447
+ // If all strategies fail, log the complete failure and return absolute minimal fallback
1448
+ this.errorHandler.logFailedRecovery(this.errorHandler.logSelectionSetError(SelectionSetErrorType.GRAPHQL_ERROR, 'All fallback strategies failed', undefined, undefined, { fieldName, previousErrors }), ['comprehensive', 'selective', 'minimal'], new Error('All fallback strategies exhausted'));
1449
+ console.error('SelectionSetGenerator: All fallback strategies failed, using absolute minimal fallback');
1450
+ return ['id'];
1451
+ }
1452
+ catch (error) {
1453
+ // Critical error in retry logic itself
1454
+ this.errorHandler.logSelectionSetError(SelectionSetErrorType.GRAPHQL_ERROR, `Critical error in fallback retry logic: ${error instanceof Error ? error.message : String(error)}`, undefined, undefined, {
1455
+ fieldName,
1456
+ previousErrors,
1457
+ criticalError: {
1458
+ message: error instanceof Error ? error.message : String(error),
1459
+ stack: error instanceof Error ? error.stack : undefined
1460
+ }
1461
+ });
1462
+ console.error('SelectionSetGenerator: Critical error in fallback retry logic:', error);
1463
+ return ['id'];
1464
+ }
1465
+ }
1466
+ /**
1467
+ * Handle generation errors by attempting fallback with comprehensive logging
1468
+ */
1469
+ handleGenerationError(error, fieldName) {
1470
+ try {
1471
+ const errorMessage = error instanceof Error ? error.message : String(error);
1472
+ console.warn(`SelectionSetGenerator: Handling generation error for field "${fieldName}":`, errorMessage);
1473
+ // Attempt fallback with retry logic
1474
+ const fallbackResult = this.generateFallbackSelectionSetWithRetry(fieldName, [errorMessage]);
1475
+ // Log the fallback attempt
1476
+ console.log(`SelectionSetGenerator: Fallback generated for field "${fieldName}":`, fallbackResult);
1477
+ return fallbackResult;
1478
+ }
1479
+ catch (fallbackError) {
1480
+ // Even fallback failed - return absolute minimal
1481
+ this.errorHandler.logSelectionSetError(SelectionSetErrorType.GRAPHQL_ERROR, `Both primary generation and fallback failed: ${fallbackError instanceof Error ? fallbackError.message : String(fallbackError)}`, undefined, undefined, {
1482
+ fieldName,
1483
+ originalError: error,
1484
+ fallbackError: {
1485
+ message: fallbackError instanceof Error ? fallbackError.message : String(fallbackError),
1486
+ stack: fallbackError instanceof Error ? fallbackError.stack : undefined
1487
+ }
1488
+ });
1489
+ console.error('SelectionSetGenerator: Both primary generation and fallback failed, returning minimal selection set');
1490
+ return ['id'];
1491
+ }
1492
+ }
1493
+ /**
1494
+ * Validate a selection set for basic correctness
1495
+ */
1496
+ validateSelectionSet(selectionSet) {
1497
+ if (!Array.isArray(selectionSet) || selectionSet.length === 0) {
1498
+ return false;
1499
+ }
1500
+ // Must include 'id' field
1501
+ if (!selectionSet.includes('id')) {
1502
+ return false;
1503
+ }
1504
+ // Check for obviously invalid selectors
1505
+ for (const selector of selectionSet) {
1506
+ if (typeof selector !== 'string' || selector.trim() === '') {
1507
+ return false;
1508
+ }
1509
+ // Check for dangerous patterns
1510
+ if (selector.includes('..') || selector.includes('*') || selector.length > 100) {
1511
+ return false;
1512
+ }
1513
+ }
1514
+ return true;
1515
+ }
1516
+ /**
1517
+ * Clear the internal cache of generated selection sets
1518
+ */
1519
+ clearCache() {
1520
+ this.selectionSetCache.clear();
1521
+ console.log('SelectionSetGenerator: Cache cleared');
1522
+ }
1523
+ /**
1524
+ * Generate selection set from configuration analysis result
1525
+ */
1526
+ generateSelectionSetFromAnalysis(config, analysisResult) {
1527
+ const selectionSet = [];
1528
+ // Always include the base record ID
1529
+ selectionSet.push('id');
1530
+ if (this.schemaIntrospector.isSchemaAvailable()) {
1531
+ // Use schema-aware generation
1532
+ return this.generateSchemaAwareSelectionSet(config, analysisResult);
1533
+ }
1534
+ else {
1535
+ // Use configuration-based generation
1536
+ return this.generateConfigurationBasedSelectionSet(config, analysisResult);
1537
+ }
1538
+ }
1539
+ /**
1540
+ * Generate selection set using schema introspection data
1541
+ */
1542
+ generateSchemaAwareSelectionSet(config, analysisResult) {
1543
+ const selectionSet = ['id'];
1544
+ // Get safe field selectors from schema introspector
1545
+ const safeSelectors = this.schemaIntrospector.getSafeFieldSelectors(analysisResult.targetModelName, config.fieldName);
1546
+ // Add schema-validated selectors
1547
+ for (const selector of safeSelectors) {
1548
+ if (!selectionSet.includes(selector)) {
1549
+ selectionSet.push(selector);
1550
+ }
1551
+ }
1552
+ // Apply display optimization if enabled
1553
+ const optimizedSet = this.applyDisplayOptimization(selectionSet, config);
1554
+ // Apply complexity limits
1555
+ return this.applyComplexityLimits(optimizedSet);
1556
+ }
1557
+ /**
1558
+ * Generate selection set using configuration analysis only
1559
+ */
1560
+ generateConfigurationBasedSelectionSet(config, analysisResult) {
1561
+ const selectionSet = ['id'];
1562
+ // Use the field selectors from configuration analysis
1563
+ for (const selector of analysisResult.fieldSelectors) {
1564
+ if (!selectionSet.includes(selector)) {
1565
+ selectionSet.push(selector);
1566
+ }
1567
+ }
1568
+ // Apply display optimization if enabled
1569
+ const optimizedSet = this.applyDisplayOptimization(selectionSet, config);
1570
+ // Apply complexity limits
1571
+ return this.applyComplexityLimits(optimizedSet);
1572
+ }
1573
+ /**
1574
+ * Apply display optimization to prioritize display-relevant fields
1575
+ */
1576
+ applyDisplayOptimization(selectionSet, config) {
1577
+ const displayConfig = this.DEFAULT_DISPLAY_CONFIG;
1578
+ if (!displayConfig.enabled) {
1579
+ return selectionSet;
1580
+ }
1581
+ // Analyze each field for display relevance
1582
+ const fieldAnalyses = selectionSet.map(selector => this.analyzeFieldForDisplay(selector, config));
1583
+ // Sort by priority (higher priority first)
1584
+ fieldAnalyses.sort((a, b) => b.priority - a.priority);
1585
+ // Build optimized selection set
1586
+ const optimizedSet = [];
1587
+ const essentialFields = [];
1588
+ const displayFields = [];
1589
+ const otherFields = [];
1590
+ // Categorize fields
1591
+ for (const analysis of fieldAnalyses) {
1592
+ switch (analysis.classification) {
1593
+ case FieldClassification.ESSENTIAL:
1594
+ essentialFields.push(analysis.selector);
1595
+ break;
1596
+ case FieldClassification.DISPLAY:
1597
+ displayFields.push(analysis.selector);
1598
+ break;
1599
+ default:
1600
+ otherFields.push(analysis.selector);
1601
+ break;
1602
+ }
1603
+ }
1604
+ // Add essential fields first (always included)
1605
+ optimizedSet.push(...essentialFields);
1606
+ // Add display fields (prioritized)
1607
+ optimizedSet.push(...displayFields);
1608
+ // Add other fields up to the limit
1609
+ const remainingSlots = Math.max(0, displayConfig.maxNonDisplayFields - (optimizedSet.length - essentialFields.length));
1610
+ optimizedSet.push(...otherFields.slice(0, remainingSlots));
1611
+ console.log(`SelectionSetGenerator: Display optimization reduced selection set from ${selectionSet.length} to ${optimizedSet.length} fields`);
1612
+ return optimizedSet;
1613
+ }
1614
+ /**
1615
+ * Analyze a field selector for display optimization
1616
+ */
1617
+ analyzeFieldForDisplay(selector, config) {
1618
+ const displayConfig = this.DEFAULT_DISPLAY_CONFIG;
1619
+ const analysis = {
1620
+ selector,
1621
+ classification: FieldClassification.UNKNOWN,
1622
+ priority: 0,
1623
+ cost: 1,
1624
+ isDisplayRelevant: false
1625
+ };
1626
+ // Essential fields (always required)
1627
+ if (displayConfig.excludeFromOptimization.some(field => selector.includes(field))) {
1628
+ analysis.classification = FieldClassification.ESSENTIAL;
1629
+ analysis.priority = 100;
1630
+ analysis.isDisplayRelevant = true;
1631
+ return analysis;
1632
+ }
1633
+ // Check if it's a common display field
1634
+ const isDisplayField = displayConfig.commonDisplayFields.some(field => selector.endsWith(`.${field}`) || selector === field);
1635
+ if (isDisplayField) {
1636
+ analysis.classification = FieldClassification.DISPLAY;
1637
+ analysis.priority = 80;
1638
+ analysis.isDisplayRelevant = true;
1639
+ }
1640
+ else if (this.isRelationshipField(selector)) {
1641
+ analysis.classification = FieldClassification.RELATIONSHIP;
1642
+ analysis.priority = 60;
1643
+ analysis.cost = 2; // Relationships are more expensive
1644
+ analysis.isDisplayRelevant = this.isDisplayRelevantRelationship(selector);
1645
+ }
1646
+ else if (this.isMetadataField(selector)) {
1647
+ analysis.classification = FieldClassification.METADATA;
1648
+ analysis.priority = 40;
1649
+ analysis.isDisplayRelevant = false;
1650
+ }
1651
+ else if (this.isSystemField(selector)) {
1652
+ analysis.classification = FieldClassification.SYSTEM;
1653
+ analysis.priority = 20;
1654
+ analysis.isDisplayRelevant = false;
1655
+ }
1656
+ else {
1657
+ analysis.classification = FieldClassification.UNKNOWN;
1658
+ analysis.priority = 30;
1659
+ analysis.isDisplayRelevant = false;
1660
+ }
1661
+ // Adjust priority based on field depth (deeper fields are less likely to be displayed)
1662
+ const depth = (selector.match(/\./g) || []).length;
1663
+ analysis.priority -= depth * 5;
1664
+ analysis.cost += depth;
1665
+ // Boost priority for fields that match the relationship field name
1666
+ if (selector.includes(config.fieldName)) {
1667
+ analysis.priority += 10;
1668
+ analysis.isDisplayRelevant = true;
1669
+ }
1670
+ return analysis;
1671
+ }
1672
+ /**
1673
+ * Check if a selector represents a relationship field
1674
+ */
1675
+ isRelationshipField(selector) {
1676
+ // Relationship fields typically have dots (nested access) or end with common relationship suffixes
1677
+ return selector.includes('.') ||
1678
+ selector.endsWith('Id') ||
1679
+ selector.endsWith('Ids') ||
1680
+ selector.endsWith('Connection');
1681
+ }
1682
+ /**
1683
+ * Check if a relationship field is likely to be displayed
1684
+ */
1685
+ isDisplayRelevantRelationship(selector) {
1686
+ // Display-relevant relationships typically access display fields of related models
1687
+ const displayFieldPatterns = ['name', 'title', 'label', 'displayName', 'description'];
1688
+ return displayFieldPatterns.some(pattern => selector.includes(pattern));
1689
+ }
1690
+ /**
1691
+ * Check if a selector represents a metadata field
1692
+ */
1693
+ isMetadataField(selector) {
1694
+ const metadataPatterns = ['createdAt', 'updatedAt', 'version', 'owner', 'lastModified'];
1695
+ return metadataPatterns.some(pattern => selector.includes(pattern));
1696
+ }
1697
+ /**
1698
+ * Check if a selector represents a system field
1699
+ */
1700
+ isSystemField(selector) {
1701
+ const systemPatterns = ['__typename', '_version', '_deleted', '_lastChangedAt'];
1702
+ return systemPatterns.some(pattern => selector.includes(pattern));
1703
+ }
1704
+ /**
1705
+ * Apply complexity limits to prevent overly expensive GraphQL queries
1706
+ * Enhanced with comprehensive error handling and logging
1707
+ */
1708
+ applyComplexityLimits(selectionSet) {
1709
+ try {
1710
+ // First validate the selection set for complexity
1711
+ const validationResult = this.validateSelectionSetComplexity(selectionSet);
1712
+ if (validationResult.isValid) {
1713
+ console.log('SelectionSetGenerator: Selection set passed complexity validation');
1714
+ return selectionSet;
1715
+ }
1716
+ // Log validation issues
1717
+ if (validationResult.errors.length > 0) {
1718
+ this.errorHandler.logComplexityError(selectionSet, validationResult.errors);
1719
+ console.warn('SelectionSetGenerator: Complexity validation errors:', validationResult.errors);
1720
+ }
1721
+ if (validationResult.warnings.length > 0) {
1722
+ console.warn('SelectionSetGenerator: Complexity validation warnings:', validationResult.warnings);
1723
+ }
1724
+ // Apply fixes to make the selection set compliant
1725
+ const fixedSet = this.fixComplexityIssues(selectionSet, validationResult);
1726
+ // Validate the fixed set
1727
+ const revalidationResult = this.validateSelectionSetComplexity(fixedSet);
1728
+ if (!revalidationResult.isValid) {
1729
+ this.errorHandler.logSelectionSetError(SelectionSetErrorType.COMPLEXITY_EXCEEDED, 'Selection set still exceeds complexity limits after fixes', undefined, fixedSet, {
1730
+ originalSelectionSet: selectionSet,
1731
+ validationResult,
1732
+ revalidationResult
1733
+ });
1734
+ // Return minimal fallback if fixes didn't work
1735
+ console.warn('SelectionSetGenerator: Complexity fixes failed, returning minimal selection set');
1736
+ return ['id'];
1737
+ }
1738
+ console.log(`SelectionSetGenerator: Applied complexity fixes, reduced from ${selectionSet.length} to ${fixedSet.length} fields`);
1739
+ return fixedSet;
1740
+ }
1741
+ catch (error) {
1742
+ this.errorHandler.logSelectionSetError(SelectionSetErrorType.COMPLEXITY_EXCEEDED, `Error applying complexity limits: ${error instanceof Error ? error.message : String(error)}`, undefined, selectionSet, {
1743
+ originalError: {
1744
+ message: error instanceof Error ? error.message : String(error),
1745
+ stack: error instanceof Error ? error.stack : undefined
1746
+ }
1747
+ });
1748
+ console.error('SelectionSetGenerator: Error applying complexity limits:', error);
1749
+ // Return original set if complexity checking fails
1750
+ return selectionSet;
1751
+ }
1752
+ }
1753
+ /**
1754
+ * Validate selection set complexity against configured limits
1755
+ */
1756
+ validateSelectionSetComplexity(selectionSet) {
1757
+ const limits = this.DEFAULT_CONFIG.complexityLimits;
1758
+ const result = {
1759
+ isValid: true,
1760
+ errors: [],
1761
+ warnings: [],
1762
+ complexityScore: 0,
1763
+ circularReferences: []
1764
+ };
1765
+ // Track field paths for circular reference detection
1766
+ const fieldPaths = new Set();
1767
+ const pathComponents = new Map();
1768
+ let wildcardCount = 0;
1769
+ let maxDepth = 0;
1770
+ for (const selector of selectionSet) {
1771
+ // Check selector length
1772
+ if (selector.length > limits.maxSelectorLength) {
1773
+ result.errors.push(`Selector "${selector}" exceeds maximum length of ${limits.maxSelectorLength}`);
1774
+ result.isValid = false;
1775
+ }
1776
+ // Check for wildcard selections
1777
+ if (selector.includes('*')) {
1778
+ wildcardCount++;
1779
+ if (wildcardCount > limits.maxWildcardSelections) {
1780
+ result.errors.push(`Too many wildcard selections (${wildcardCount}), maximum allowed: ${limits.maxWildcardSelections}`);
1781
+ result.isValid = false;
1782
+ }
1783
+ }
1784
+ // Calculate nesting depth
1785
+ const depth = (selector.match(/\./g) || []).length;
1786
+ maxDepth = Math.max(maxDepth, depth);
1787
+ if (depth > limits.maxNestingDepth) {
1788
+ result.errors.push(`Selector "${selector}" exceeds maximum nesting depth of ${limits.maxNestingDepth}`);
1789
+ result.isValid = false;
1790
+ }
1791
+ // Track field paths for circular reference detection
1792
+ if (limits.preventCircularReferences && depth > 0) {
1793
+ const components = selector.split('.');
1794
+ pathComponents.set(selector, components);
1795
+ // Check for potential circular references
1796
+ const circularRef = this.detectCircularReference(components, pathComponents);
1797
+ if (circularRef) {
1798
+ result.circularReferences.push(circularRef);
1799
+ result.warnings.push(`Potential circular reference detected: ${circularRef}`);
1800
+ }
1801
+ }
1802
+ fieldPaths.add(selector);
1803
+ }
1804
+ // Check field count limit
1805
+ if (selectionSet.length > limits.maxFieldCount) {
1806
+ result.errors.push(`Selection set has ${selectionSet.length} fields, maximum allowed: ${limits.maxFieldCount}`);
1807
+ result.isValid = false;
1808
+ }
1809
+ // Calculate complexity score
1810
+ result.complexityScore = this.calculateComplexityScore(selectionSet, maxDepth, wildcardCount);
1811
+ // Add warnings for high complexity
1812
+ if (result.complexityScore > 50) {
1813
+ result.warnings.push(`High complexity score: ${result.complexityScore}`);
1814
+ }
1815
+ return result;
1816
+ }
1817
+ /**
1818
+ * Detect circular references in field paths
1819
+ */
1820
+ detectCircularReference(components, allPaths) {
1821
+ // Simple circular reference detection: check if any component appears multiple times in the path
1822
+ const componentCounts = new Map();
1823
+ for (const component of components) {
1824
+ const count = componentCounts.get(component) || 0;
1825
+ componentCounts.set(component, count + 1);
1826
+ if (count > 0) {
1827
+ return components.join('.');
1828
+ }
1829
+ }
1830
+ return null;
1831
+ }
1832
+ /**
1833
+ * Calculate complexity score for a selection set
1834
+ */
1835
+ calculateComplexityScore(selectionSet, maxDepth, wildcardCount) {
1836
+ let score = 0;
1837
+ // Base score from field count
1838
+ score += selectionSet.length;
1839
+ // Penalty for depth
1840
+ score += maxDepth * 5;
1841
+ // Heavy penalty for wildcards
1842
+ score += wildcardCount * 10;
1843
+ // Penalty for complex selectors
1844
+ for (const selector of selectionSet) {
1845
+ if (selector.includes('*')) {
1846
+ score += 5;
1847
+ }
1848
+ if (selector.split('.').length > 2) {
1849
+ score += 3;
1850
+ }
1851
+ }
1852
+ return score;
1853
+ }
1854
+ /**
1855
+ * Fix complexity issues in a selection set
1856
+ */
1857
+ fixComplexityIssues(selectionSet, validationResult) {
1858
+ const limits = this.DEFAULT_CONFIG.complexityLimits;
1859
+ let fixedSet = [...selectionSet];
1860
+ // Remove selectors that are too long
1861
+ fixedSet = fixedSet.filter(selector => {
1862
+ if (selector.length > limits.maxSelectorLength) {
1863
+ console.warn(`SelectionSetGenerator: Removing selector "${selector}" due to excessive length`);
1864
+ return false;
1865
+ }
1866
+ return true;
1867
+ });
1868
+ // Remove selectors that are too deeply nested
1869
+ fixedSet = fixedSet.filter(selector => {
1870
+ const depth = (selector.match(/\./g) || []).length;
1871
+ if (depth > limits.maxNestingDepth) {
1872
+ console.warn(`SelectionSetGenerator: Removing selector "${selector}" due to excessive nesting depth`);
1873
+ return false;
1874
+ }
1875
+ return true;
1876
+ });
1877
+ // Limit wildcard selections
1878
+ let wildcardCount = 0;
1879
+ fixedSet = fixedSet.filter(selector => {
1880
+ if (selector.includes('*')) {
1881
+ wildcardCount++;
1882
+ if (wildcardCount > limits.maxWildcardSelections) {
1883
+ console.warn(`SelectionSetGenerator: Removing wildcard selector "${selector}" due to limit`);
1884
+ return false;
1885
+ }
1886
+ }
1887
+ return true;
1888
+ });
1889
+ // Remove circular references
1890
+ if (limits.preventCircularReferences && validationResult.circularReferences.length > 0) {
1891
+ const circularSelectors = new Set(validationResult.circularReferences);
1892
+ fixedSet = fixedSet.filter(selector => {
1893
+ if (circularSelectors.has(selector)) {
1894
+ console.warn(`SelectionSetGenerator: Removing selector "${selector}" due to circular reference`);
1895
+ return false;
1896
+ }
1897
+ return true;
1898
+ });
1899
+ }
1900
+ // Truncate to field count limit
1901
+ if (fixedSet.length > limits.maxFieldCount) {
1902
+ console.warn(`SelectionSetGenerator: Truncating selection set from ${fixedSet.length} to ${limits.maxFieldCount} fields`);
1903
+ fixedSet = fixedSet.slice(0, limits.maxFieldCount);
1904
+ }
1905
+ // Ensure required fields are still present
1906
+ for (const requiredField of this.DEFAULT_CONFIG.requiredFields) {
1907
+ if (!fixedSet.includes(requiredField)) {
1908
+ fixedSet.unshift(requiredField);
1909
+ }
1910
+ }
1911
+ return fixedSet;
1912
+ }
1913
+ /**
1914
+ * Generate a cache key for a relationship configuration
1915
+ */
1916
+ generateCacheKey(config) {
1917
+ const key = {
1918
+ relationshipModel: config.relationshipModelName,
1919
+ fieldName: config.fieldName,
1920
+ targetModel: this.configurationAnalyzer.extractTargetModelName(config),
1921
+ schemaVersion: this.getSchemaVersion()
1922
+ };
1923
+ return JSON.stringify(key);
1924
+ }
1925
+ /**
1926
+ * Get cached selection set if available
1927
+ */
1928
+ getCachedSelectionSet(cacheKey) {
1929
+ const cached = this.selectionSetCache.get(cacheKey);
1930
+ if (cached) {
1931
+ // Update access count
1932
+ cached.accessCount++;
1933
+ return cached;
1934
+ }
1935
+ return null;
1936
+ }
1937
+ /**
1938
+ * Cache a generated selection set
1939
+ */
1940
+ cacheSelectionSet(cacheKey, selectionSet) {
1941
+ const cached = {
1942
+ key: JSON.parse(cacheKey),
1943
+ selectionSet: [...selectionSet],
1944
+ createdAt: new Date(),
1945
+ accessCount: 1
1946
+ };
1947
+ this.selectionSetCache.set(cacheKey, cached);
1948
+ // Implement simple cache size limit
1949
+ if (this.selectionSetCache.size > 100) {
1950
+ this.evictOldestCacheEntries();
1951
+ }
1952
+ }
1953
+ /**
1954
+ * Evict oldest cache entries when cache gets too large
1955
+ */
1956
+ evictOldestCacheEntries() {
1957
+ const entries = Array.from(this.selectionSetCache.entries());
1958
+ entries.sort((a, b) => a[1].createdAt.getTime() - b[1].createdAt.getTime());
1959
+ // Remove oldest 20% of entries
1960
+ const toRemove = Math.floor(entries.length * 0.2);
1961
+ for (let i = 0; i < toRemove; i++) {
1962
+ this.selectionSetCache.delete(entries[i][0]);
1963
+ }
1964
+ console.log(`SelectionSetGenerator: Evicted ${toRemove} old cache entries`);
1965
+ }
1966
+ /**
1967
+ * Get schema version for cache invalidation
1968
+ */
1969
+ getSchemaVersion() {
1970
+ // In a real implementation, this could be based on schema hash or version
1971
+ // For now, we'll use schema availability as a simple version indicator
1972
+ return this.schemaIntrospector.isSchemaAvailable() ? 'schema-available' : 'schema-unavailable';
1973
+ }
1974
+ /**
1975
+ * Get cache statistics for debugging
1976
+ */
1977
+ getCacheStats() {
1978
+ const entries = Array.from(this.selectionSetCache.values());
1979
+ const totalAccesses = entries.reduce((sum, entry) => sum + entry.accessCount, 0);
1980
+ return {
1981
+ size: this.selectionSetCache.size,
1982
+ totalAccesses,
1983
+ entries: entries.map(entry => ({
1984
+ key: entry.key,
1985
+ selectionSet: [...entry.selectionSet],
1986
+ createdAt: entry.createdAt,
1987
+ accessCount: entry.accessCount
1988
+ }))
1989
+ };
1990
+ }
1991
+ /**
1992
+ * Get optimization statistics for debugging
1993
+ */
1994
+ getOptimizationStats() {
1995
+ return {
1996
+ displayOptimizationEnabled: this.DEFAULT_DISPLAY_CONFIG.enabled,
1997
+ complexityLimits: { ...this.DEFAULT_CONFIG.complexityLimits },
1998
+ displayConfig: { ...this.DEFAULT_DISPLAY_CONFIG }
1999
+ };
2000
+ }
2001
+ /**
2002
+ * Analyze a selection set for optimization opportunities
2003
+ */
2004
+ analyzeSelectionSetOptimization(selectionSet) {
2005
+ const mockConfig = {
2006
+ relationshipModelName: '',
2007
+ baseModelName: '',
2008
+ fieldName: 'mock',
2009
+ associatedWith: ''
2010
+ };
2011
+ const analyses = selectionSet.map(selector => this.analyzeFieldForDisplay(selector, mockConfig));
2012
+ const displayRelevantCount = analyses.filter(a => a.isDisplayRelevant).length;
2013
+ const complexityScore = this.calculateComplexityScore(selectionSet, Math.max(...selectionSet.map(s => (s.match(/\./g) || []).length)), selectionSet.filter(s => s.includes('*')).length);
2014
+ const opportunities = [];
2015
+ // Identify optimization opportunities
2016
+ if (displayRelevantCount < selectionSet.length * 0.5) {
2017
+ opportunities.push('Many non-display fields could be removed');
2018
+ }
2019
+ if (complexityScore > 30) {
2020
+ opportunities.push('High complexity score suggests simplification needed');
2021
+ }
2022
+ const deepFields = selectionSet.filter(s => (s.match(/\./g) || []).length > 2);
2023
+ if (deepFields.length > 0) {
2024
+ opportunities.push(`${deepFields.length} deeply nested fields could be simplified`);
2025
+ }
2026
+ const wildcardFields = selectionSet.filter(s => s.includes('*'));
2027
+ if (wildcardFields.length > 1) {
2028
+ opportunities.push(`${wildcardFields.length} wildcard selections could be made more specific`);
2029
+ }
2030
+ return {
2031
+ totalFields: selectionSet.length,
2032
+ displayRelevantFields: displayRelevantCount,
2033
+ complexityScore,
2034
+ optimizationOpportunities: opportunities
2035
+ };
2036
+ }
2037
+ /**
2038
+ * Initialize the service with schema data
2039
+ */
2040
+ initializeWithSchema(amplifyOutputs) {
2041
+ this.schemaIntrospector.initializeSchema(amplifyOutputs);
2042
+ this.clearCache(); // Clear cache when schema changes
2043
+ console.log('SelectionSetGenerator: Initialized with schema data');
2044
+ }
2045
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: SelectionSetGeneratorService, deps: [{ token: ConfigurationAnalyzerService }, { token: SchemaIntrospectorService }, { token: ErrorHandlerService }], target: i0.ɵɵFactoryTarget.Injectable });
2046
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: SelectionSetGeneratorService, providedIn: 'root' });
2047
+ }
2048
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: SelectionSetGeneratorService, decorators: [{
2049
+ type: Injectable,
2050
+ args: [{
2051
+ providedIn: 'root'
2052
+ }]
2053
+ }], ctorParameters: () => [{ type: ConfigurationAnalyzerService }, { type: SchemaIntrospectorService }, { type: ErrorHandlerService }] });
2054
+
37
2055
  class AmplifyAngularCore {
38
2056
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: AmplifyAngularCore, deps: [], target: i0.ɵɵFactoryTarget.Component });
39
2057
  static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.15", type: AmplifyAngularCore, isStandalone: true, selector: "snteam-amplify-angular-core", ngImport: i0, template: `
@@ -44,7 +2062,7 @@ class AmplifyAngularCore {
44
2062
  }
45
2063
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: AmplifyAngularCore, decorators: [{
46
2064
  type: Component,
47
- args: [{ selector: 'snteam-amplify-angular-core', imports: [], template: `
2065
+ args: [{ selector: 'snteam-amplify-angular-core', standalone: true, imports: [], template: `
48
2066
  <p>
49
2067
  amplify-angular-core works!
50
2068
  </p>
@@ -236,8 +2254,12 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImpo
236
2254
  }] });
237
2255
 
238
2256
  class AmplifyModelService {
2257
+ selectionSetGenerator;
2258
+ errorHandler;
239
2259
  client;
240
- constructor() {
2260
+ constructor(selectionSetGenerator, errorHandler) {
2261
+ this.selectionSetGenerator = selectionSetGenerator;
2262
+ this.errorHandler = errorHandler;
241
2263
  // Client will be initialized when Amplify is configured by the consuming application
242
2264
  }
243
2265
  /**
@@ -424,14 +2446,65 @@ class AmplifyModelService {
424
2446
  return condition;
425
2447
  }
426
2448
  }
2449
+ /**
2450
+ * Generate dynamic selection set for relationship queries
2451
+ * Replaces hardcoded selection set logic with dynamic generation
2452
+ * Enhanced with comprehensive error handling and logging
2453
+ * @param config Relationship configuration object
2454
+ * @param baseId Base record ID (for compatibility, not used in generation)
2455
+ * @returns Array of GraphQL field selectors
2456
+ */
427
2457
  getAmplifySelectionSet(config, baseId) {
428
- switch (config.fieldName) {
429
- case 'service':
430
- return ['id', 'service.title', 'service.price', 'service.id'];
431
- case 'customer':
432
- return ['id', 'customer.*'];
433
- default:
434
- return [];
2458
+ try {
2459
+ if (!config) {
2460
+ const error = this.errorHandler.logSelectionSetError(SelectionSetErrorType.INVALID_CONFIGURATION, 'Configuration is null or undefined', config, undefined, {
2461
+ method: 'getAmplifySelectionSet',
2462
+ baseId,
2463
+ clientAvailable: !!this.client
2464
+ });
2465
+ // Return minimal fallback
2466
+ return ['id'];
2467
+ }
2468
+ console.log('AmplifyModelService: Generating dynamic selection set for config:', {
2469
+ relationshipModel: config.relationshipModelName,
2470
+ fieldName: config.fieldName,
2471
+ baseId
2472
+ });
2473
+ // Use the SelectionSetGenerator to create dynamic selection sets
2474
+ const selectionSet = this.selectionSetGenerator.generateSelectionSet(config);
2475
+ if (!selectionSet || selectionSet.length === 0) {
2476
+ const error = this.errorHandler.logSelectionSetError(SelectionSetErrorType.GRAPHQL_ERROR, 'Selection set generator returned empty or null result', config, selectionSet, {
2477
+ method: 'getAmplifySelectionSet',
2478
+ baseId,
2479
+ generatorAvailable: !!this.selectionSetGenerator
2480
+ });
2481
+ // Return minimal fallback
2482
+ return ['id'];
2483
+ }
2484
+ console.log('AmplifyModelService: Generated selection set:', {
2485
+ config: config.fieldName,
2486
+ selectionSetLength: selectionSet.length,
2487
+ selectionSet
2488
+ });
2489
+ return selectionSet;
2490
+ }
2491
+ catch (error) {
2492
+ const selectionSetError = this.errorHandler.logSelectionSetError(SelectionSetErrorType.GRAPHQL_ERROR, `Unexpected error generating selection set: ${error instanceof Error ? error.message : String(error)}`, config, undefined, {
2493
+ method: 'getAmplifySelectionSet',
2494
+ baseId,
2495
+ originalError: {
2496
+ message: error instanceof Error ? error.message : String(error),
2497
+ stack: error instanceof Error ? error.stack : undefined,
2498
+ type: typeof error
2499
+ },
2500
+ context: {
2501
+ clientAvailable: !!this.client,
2502
+ selectionSetGeneratorAvailable: !!this.selectionSetGenerator
2503
+ }
2504
+ });
2505
+ console.error('AmplifyModelService: Error generating selection set, using fallback:', error);
2506
+ // Use fallback selection set generation with retry logic
2507
+ return this.selectionSetGenerator.generateFallbackSelectionSetWithRetry(config?.fieldName || '', [error instanceof Error ? error.message : String(error)]);
435
2508
  }
436
2509
  }
437
2510
  setRelatedSub(config, baseId) {
@@ -450,27 +2523,336 @@ class AmplifyModelService {
450
2523
  }
451
2524
  }
452
2525
  /**
453
- * @param config {object} - see dynamic-relationship-builder.component getDetailsForManyToMany() for example of building config
2526
+ * Get related items with enhanced error handling and retry logic
2527
+ * @param config Relationship configuration object
2528
+ * @param baseId Base record ID to filter relationships
2529
+ * @returns Observable of related items with retry logic on GraphQL errors
2530
+ */
2531
+ getRelatedItems(config, baseId) {
2532
+ if (!this.client) {
2533
+ console.error('AmplifyModelService: Client not initialized. Call initializeClient() first.');
2534
+ return null;
2535
+ }
2536
+ const filter = this.getRelationshipFilter(config, baseId);
2537
+ const modelName = config.relationshipModelName;
2538
+ if (!this.client.models[modelName]) {
2539
+ console.error(`AmplifyModelService: Relationship model ${modelName} not found in client`);
2540
+ return null;
2541
+ }
2542
+ // Attempt to get related items with progressive retry logic
2543
+ return this.getRelatedItemsWithRetry(config, baseId, filter, modelName);
2544
+ }
2545
+ /**
2546
+ * Get related items with progressive retry logic for GraphQL errors
2547
+ * Implements progressive fallback: original → comprehensive → selective → minimal
2548
+ */
2549
+ getRelatedItemsWithRetry(config, baseId, filter, modelName, retryAttempt = 0, previousErrors = []) {
2550
+ const maxRetries = 3;
2551
+ try {
2552
+ // Generate selection set based on retry attempt
2553
+ let selectionSet;
2554
+ if (retryAttempt === 0) {
2555
+ // First attempt: Use dynamic generation
2556
+ selectionSet = this.getAmplifySelectionSet(config, baseId);
2557
+ }
2558
+ else {
2559
+ // Subsequent attempts: Use progressive fallback
2560
+ console.log(`AmplifyModelService: Retry attempt ${retryAttempt} for ${modelName}, using fallback selection set`);
2561
+ selectionSet = this.selectionSetGenerator.generateFallbackSelectionSetWithRetry(config.fieldName, previousErrors);
2562
+ }
2563
+ // Log the attempt
2564
+ this.logQueryAttempt(config, selectionSet, retryAttempt, previousErrors);
2565
+ // Prepare query options
2566
+ const queryOptions = { filter: filter };
2567
+ // Only include selectionSet if it's not empty to avoid GraphQL syntax errors
2568
+ if (selectionSet && selectionSet.length > 0) {
2569
+ queryOptions.selectionSet = [...selectionSet];
2570
+ }
2571
+ // Execute the query
2572
+ const queryObservable = this.client.models[modelName].observeQuery(queryOptions);
2573
+ // Wrap the observable to handle GraphQL errors with retry logic
2574
+ return new Observable(subscriber => {
2575
+ const subscription = queryObservable.subscribe({
2576
+ next: (result) => {
2577
+ // Log successful query
2578
+ this.logQuerySuccess(config, selectionSet, retryAttempt, result);
2579
+ // Validate that relationship field data is populated
2580
+ if (this.validateRelationshipFieldPopulation(result, config)) {
2581
+ subscriber.next(result);
2582
+ }
2583
+ else {
2584
+ // Relationship field not populated, try retry if possible
2585
+ const error = new Error(`Relationship field '${config.fieldName}' not populated in query results`);
2586
+ this.handleQueryError(error, config, baseId, filter, modelName, retryAttempt, previousErrors, subscriber);
2587
+ }
2588
+ },
2589
+ error: (error) => {
2590
+ this.handleQueryError(error, config, baseId, filter, modelName, retryAttempt, previousErrors, subscriber);
2591
+ },
2592
+ complete: () => {
2593
+ subscriber.complete();
2594
+ }
2595
+ });
2596
+ // Return cleanup function
2597
+ return () => subscription.unsubscribe();
2598
+ });
2599
+ }
2600
+ catch (error) {
2601
+ console.error(`AmplifyModelService: Error in retry attempt ${retryAttempt}:`, error);
2602
+ // If we've exhausted retries, return null
2603
+ if (retryAttempt >= maxRetries) {
2604
+ this.logFinalFailure(config, previousErrors, error);
2605
+ return null;
2606
+ }
2607
+ // Try next retry level
2608
+ const newErrors = [...previousErrors, error instanceof Error ? error.message : String(error)];
2609
+ return this.getRelatedItemsWithRetry(config, baseId, filter, modelName, retryAttempt + 1, newErrors);
2610
+ }
2611
+ }
2612
+ /**
2613
+ * Handle GraphQL query errors with retry logic
2614
+ * Enhanced with comprehensive error logging and context
2615
+ */
2616
+ handleQueryError(error, config, baseId, filter, modelName, retryAttempt, previousErrors, subscriber) {
2617
+ const maxRetries = 3;
2618
+ const errorMessage = error instanceof Error ? error.message : String(error);
2619
+ // Log comprehensive error details using ErrorHandlerService
2620
+ const selectionSetError = this.errorHandler.logGraphQLError(error, config, [], // Selection set will be logged separately in retry attempts
2621
+ retryAttempt, previousErrors);
2622
+ console.error(`AmplifyModelService: GraphQL query error on attempt ${retryAttempt + 1}:`, error);
2623
+ // Check if we should retry
2624
+ if (retryAttempt < maxRetries && this.shouldRetryOnError(error)) {
2625
+ console.log(`AmplifyModelService: Retrying with simpler selection set (attempt ${retryAttempt + 1}/${maxRetries})`);
2626
+ // Add current error to the list
2627
+ const newErrors = [...previousErrors, errorMessage];
2628
+ // Attempt retry with next fallback level
2629
+ const retryObservable = this.getRelatedItemsWithRetry(config, baseId, filter, modelName, retryAttempt + 1, newErrors);
2630
+ if (retryObservable) {
2631
+ // Subscribe to retry attempt
2632
+ const retrySubscription = retryObservable.subscribe({
2633
+ next: (result) => {
2634
+ // Log successful recovery
2635
+ this.errorHandler.logSuccessfulRecovery(selectionSetError, `retry_attempt_${retryAttempt + 1}`, [] // Selection set would be determined in retry
2636
+ );
2637
+ subscriber.next(result);
2638
+ },
2639
+ error: (retryError) => subscriber.error(retryError),
2640
+ complete: () => subscriber.complete()
2641
+ });
2642
+ // Handle cleanup
2643
+ subscriber.add(() => retrySubscription.unsubscribe());
2644
+ }
2645
+ else {
2646
+ // No more retries possible
2647
+ this.errorHandler.logFailedRecovery(selectionSetError, newErrors, new Error('Retry observable creation failed'));
2648
+ subscriber.error(error);
2649
+ }
2650
+ }
2651
+ else {
2652
+ // No more retries or error is not retryable
2653
+ const finalErrors = [...previousErrors, errorMessage];
2654
+ this.errorHandler.logFailedRecovery(selectionSetError, finalErrors, error);
2655
+ console.error('AmplifyModelService: All retry attempts exhausted or error not retryable');
2656
+ subscriber.error(error);
2657
+ }
2658
+ }
2659
+ /**
2660
+ * Determine if an error should trigger a retry
2661
+ * Enhanced with comprehensive error pattern matching
2662
+ */
2663
+ shouldRetryOnError(error) {
2664
+ try {
2665
+ const errorMessage = error instanceof Error ? error.message : String(error);
2666
+ const errorString = errorMessage.toLowerCase();
2667
+ // Retry on GraphQL field errors, selection set errors, but not on auth errors
2668
+ const retryableErrors = [
2669
+ 'field',
2670
+ 'selection',
2671
+ 'cannot query',
2672
+ 'unknown field',
2673
+ 'syntax error',
2674
+ 'invalid selection set',
2675
+ 'validation error',
2676
+ 'parse error'
2677
+ ];
2678
+ const nonRetryableErrors = [
2679
+ 'unauthorized',
2680
+ 'forbidden',
2681
+ 'access denied',
2682
+ 'authentication',
2683
+ 'permission',
2684
+ 'not authorized',
2685
+ 'invalid credentials'
2686
+ ];
2687
+ // Don't retry on auth errors
2688
+ if (nonRetryableErrors.some(pattern => errorString.includes(pattern))) {
2689
+ console.log(`AmplifyModelService: Not retrying due to auth error: ${errorMessage}`);
2690
+ return false;
2691
+ }
2692
+ // Retry on field/selection errors
2693
+ const shouldRetry = retryableErrors.some(pattern => errorString.includes(pattern));
2694
+ console.log(`AmplifyModelService: Error retry decision: ${shouldRetry} for error: ${errorMessage}`);
2695
+ return shouldRetry;
2696
+ }
2697
+ catch (retryDecisionError) {
2698
+ this.errorHandler.logSelectionSetError(SelectionSetErrorType.GRAPHQL_ERROR, `Error determining retry eligibility: ${retryDecisionError instanceof Error ? retryDecisionError.message : String(retryDecisionError)}`, undefined, undefined, {
2699
+ method: 'shouldRetryOnError',
2700
+ originalError: error,
2701
+ retryDecisionError: {
2702
+ message: retryDecisionError instanceof Error ? retryDecisionError.message : String(retryDecisionError),
2703
+ stack: retryDecisionError instanceof Error ? retryDecisionError.stack : undefined
2704
+ }
2705
+ });
2706
+ // Default to not retrying if we can't determine
2707
+ return false;
2708
+ }
2709
+ }
2710
+ /**
2711
+ * Validate that relationship field data is populated in query results
2712
+ * Enhanced with comprehensive error logging
2713
+ */
2714
+ validateRelationshipFieldPopulation(result, config) {
2715
+ try {
2716
+ if (!result || !result.data) {
2717
+ this.errorHandler.logSelectionSetError(SelectionSetErrorType.GRAPHQL_ERROR, 'Query result is missing or has no data property', config, undefined, {
2718
+ method: 'validateRelationshipFieldPopulation',
2719
+ hasResult: !!result,
2720
+ hasData: !!(result?.data)
2721
+ });
2722
+ return false;
2723
+ }
2724
+ // Check if any records have the relationship field populated
2725
+ const records = Array.isArray(result.data) ? result.data : [result.data];
2726
+ let populatedCount = 0;
2727
+ for (const record of records) {
2728
+ if (record && record[config.fieldName] !== undefined && record[config.fieldName] !== null) {
2729
+ populatedCount++;
2730
+ }
2731
+ }
2732
+ if (populatedCount === 0) {
2733
+ // Log warning if no relationship fields are populated
2734
+ this.errorHandler.logSelectionSetError(SelectionSetErrorType.FIELD_NOT_FOUND, `No records have relationship field '${config.fieldName}' populated`, config, undefined, {
2735
+ method: 'validateRelationshipFieldPopulation',
2736
+ recordCount: records.length,
2737
+ populatedCount,
2738
+ sampleRecord: records[0] ? Object.keys(records[0]) : [],
2739
+ fieldName: config.fieldName
2740
+ });
2741
+ console.warn(`AmplifyModelService: No records have relationship field '${config.fieldName}' populated`, {
2742
+ config,
2743
+ recordCount: records.length,
2744
+ sampleRecord: records[0]
2745
+ });
2746
+ return false;
2747
+ }
2748
+ console.log(`AmplifyModelService: Relationship field validation successful: ${populatedCount}/${records.length} records have populated '${config.fieldName}' field`);
2749
+ return true;
2750
+ }
2751
+ catch (error) {
2752
+ this.errorHandler.logSelectionSetError(SelectionSetErrorType.GRAPHQL_ERROR, `Error validating relationship field population: ${error instanceof Error ? error.message : String(error)}`, config, undefined, {
2753
+ method: 'validateRelationshipFieldPopulation',
2754
+ originalError: {
2755
+ message: error instanceof Error ? error.message : String(error),
2756
+ stack: error instanceof Error ? error.stack : undefined
2757
+ }
2758
+ });
2759
+ console.error('AmplifyModelService: Error validating relationship field population:', error);
2760
+ return false;
2761
+ }
2762
+ }
2763
+ /**
2764
+ * Log query attempt details
2765
+ */
2766
+ logQueryAttempt(config, selectionSet, retryAttempt, previousErrors) {
2767
+ console.log('AmplifyModelService: Query attempt details:', {
2768
+ attempt: retryAttempt + 1,
2769
+ config: {
2770
+ relationshipModelName: config.relationshipModelName,
2771
+ fieldName: config.fieldName
2772
+ },
2773
+ selectionSet,
2774
+ previousErrors: previousErrors.length,
2775
+ timestamp: new Date().toISOString()
2776
+ });
2777
+ }
2778
+ /**
2779
+ * Log successful query details
454
2780
  */
455
- getRelatedItems(config, baseId) {
456
- if (!this.client) {
457
- console.error('AmplifyModelService: Client not initialized. Call initializeClient() first.');
458
- return null;
459
- }
460
- let filter = this.getRelationshipFilter(config, baseId);
461
- let selectionSet = this.getAmplifySelectionSet(config, baseId);
462
- const modelName = config.relationshipModelName;
463
- if (this.client.models[modelName]) {
464
- // Only include selectionSet if it's not empty to avoid GraphQL syntax errors
465
- const queryOptions = { filter: filter };
466
- if (selectionSet && selectionSet.length > 0) {
467
- queryOptions.selectionSet = [...selectionSet];
468
- }
469
- return this.client.models[modelName].observeQuery(queryOptions);
470
- }
471
- else {
472
- console.error(`Relationship model ${modelName} not found in client`);
473
- return null;
2781
+ logQuerySuccess(config, selectionSet, retryAttempt, result) {
2782
+ const recordCount = result?.data ? (Array.isArray(result.data) ? result.data.length : 1) : 0;
2783
+ console.log('AmplifyModelService: Query successful:', {
2784
+ attempt: retryAttempt + 1,
2785
+ config: {
2786
+ relationshipModelName: config.relationshipModelName,
2787
+ fieldName: config.fieldName
2788
+ },
2789
+ selectionSet,
2790
+ recordCount,
2791
+ timestamp: new Date().toISOString()
2792
+ });
2793
+ }
2794
+ /**
2795
+ * Log query error details
2796
+ */
2797
+ logQueryError(config, error, retryAttempt) {
2798
+ console.error('AmplifyModelService: Query error details:', {
2799
+ attempt: retryAttempt + 1,
2800
+ config: {
2801
+ relationshipModelName: config.relationshipModelName,
2802
+ baseModelName: config.baseModelName,
2803
+ fieldName: config.fieldName,
2804
+ associatedWith: config.associatedWith
2805
+ },
2806
+ error: {
2807
+ message: error instanceof Error ? error.message : String(error),
2808
+ type: typeof error,
2809
+ name: error instanceof Error ? error.name : undefined
2810
+ },
2811
+ timestamp: new Date().toISOString()
2812
+ });
2813
+ }
2814
+ /**
2815
+ * Log final failure when all retries are exhausted
2816
+ * Enhanced with comprehensive error context and recovery suggestions
2817
+ */
2818
+ logFinalFailure(config, allErrors, finalError) {
2819
+ try {
2820
+ const errorSummary = {
2821
+ config: {
2822
+ relationshipModelName: config.relationshipModelName,
2823
+ baseModelName: config.baseModelName,
2824
+ fieldName: config.fieldName,
2825
+ associatedWith: config.associatedWith
2826
+ },
2827
+ totalAttempts: allErrors.length + 1,
2828
+ allErrors,
2829
+ finalError: {
2830
+ message: finalError instanceof Error ? finalError.message : String(finalError),
2831
+ type: typeof finalError,
2832
+ name: finalError instanceof Error ? finalError.name : undefined
2833
+ },
2834
+ timestamp: new Date().toISOString(),
2835
+ suggestions: [
2836
+ 'Check if the relationship model exists in your Amplify schema',
2837
+ 'Verify that the field name matches the relationship field in your model',
2838
+ 'Ensure the relationship is properly configured in your GraphQL schema',
2839
+ 'Check if there are any authorization rules preventing access to the relationship data'
2840
+ ]
2841
+ };
2842
+ // Log using ErrorHandlerService for comprehensive tracking
2843
+ this.errorHandler.logFailedRecovery(this.errorHandler.logSelectionSetError(SelectionSetErrorType.GRAPHQL_ERROR, 'All retry attempts failed for relationship query', config, undefined, {
2844
+ method: 'logFinalFailure',
2845
+ errorSummary
2846
+ }), allErrors, finalError);
2847
+ console.error('AmplifyModelService: All retry attempts failed:', errorSummary);
2848
+ }
2849
+ catch (loggingError) {
2850
+ // Even logging failed - use basic console error
2851
+ console.error('AmplifyModelService: Critical error - failed to log final failure:', {
2852
+ originalError: finalError,
2853
+ loggingError,
2854
+ config: config?.fieldName || 'unknown'
2855
+ });
474
2856
  }
475
2857
  }
476
2858
  createItemPromise(model, payload) {
@@ -559,7 +2941,7 @@ class AmplifyModelService {
559
2941
  return null;
560
2942
  }
561
2943
  }
562
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: AmplifyModelService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
2944
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: AmplifyModelService, deps: [{ token: SelectionSetGeneratorService }, { token: ErrorHandlerService }], target: i0.ɵɵFactoryTarget.Injectable });
563
2945
  static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: AmplifyModelService, providedIn: 'root' });
564
2946
  }
565
2947
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: AmplifyModelService, decorators: [{
@@ -567,7 +2949,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImpo
567
2949
  args: [{
568
2950
  providedIn: 'root'
569
2951
  }]
570
- }], ctorParameters: () => [] });
2952
+ }], ctorParameters: () => [{ type: SelectionSetGeneratorService }, { type: ErrorHandlerService }] });
571
2953
 
572
2954
  class DynamicFormQuestionComponent {
573
2955
  rootFormGroup;
@@ -798,46 +3180,441 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImpo
798
3180
  // import outputs from '../../../../amplify_outputs.json';
799
3181
  class DynamicRelationshipBuilderComponent {
800
3182
  ams;
3183
+ errorHandler;
801
3184
  m2m;
802
3185
  item;
803
3186
  model;
804
- m2mItems;
805
- constructor(ams) {
3187
+ m2mItems = [];
3188
+ errorMessage = '';
3189
+ isLoading = false;
3190
+ constructor(ams, errorHandler) {
806
3191
  this.ams = ams;
3192
+ this.errorHandler = errorHandler;
807
3193
  }
808
3194
  modelItem; // = signal('');
809
3195
  dialog = inject(MatDialog);
810
3196
  ngOnInit() {
811
- console.log('DynamicRelationshipBuilderComponent model', this.m2m);
812
- this.listItems();
813
- this.setCreateSubscription();
3197
+ try {
3198
+ console.log('DynamicRelationshipBuilderComponent initializing with m2m config:', this.m2m);
3199
+ // Validate required inputs with comprehensive error logging
3200
+ if (!this.m2m) {
3201
+ const error = this.errorHandler.logSelectionSetError(SelectionSetErrorType.INVALID_CONFIGURATION, 'Relationship configuration (m2m) is missing', undefined, undefined, {
3202
+ component: 'DynamicRelationshipBuilderComponent',
3203
+ method: 'ngOnInit',
3204
+ hasItem: !!this.item,
3205
+ hasModel: !!this.model
3206
+ });
3207
+ this.setError('Relationship configuration is missing');
3208
+ return;
3209
+ }
3210
+ if (!this.item || !this.item.id) {
3211
+ const error = this.errorHandler.logSelectionSetError(SelectionSetErrorType.INVALID_CONFIGURATION, 'Item data is missing or invalid (no ID)', this.m2m, undefined, {
3212
+ component: 'DynamicRelationshipBuilderComponent',
3213
+ method: 'ngOnInit',
3214
+ hasItem: !!this.item,
3215
+ itemId: this.item?.id,
3216
+ hasModel: !!this.model
3217
+ });
3218
+ this.setError('Item data is missing or invalid');
3219
+ return;
3220
+ }
3221
+ // Validate m2m configuration structure
3222
+ const requiredM2MFields = ['relationshipModelName', 'fieldName', 'baseModelName', 'associatedWith'];
3223
+ const missingFields = requiredM2MFields.filter(field => !this.m2m[field]);
3224
+ if (missingFields.length > 0) {
3225
+ const error = this.errorHandler.logSelectionSetError(SelectionSetErrorType.INVALID_CONFIGURATION, `M2M configuration is missing required fields: ${missingFields.join(', ')}`, this.m2m, undefined, {
3226
+ component: 'DynamicRelationshipBuilderComponent',
3227
+ method: 'ngOnInit',
3228
+ missingFields,
3229
+ providedFields: Object.keys(this.m2m || {})
3230
+ });
3231
+ this.setError(`Configuration is incomplete: missing ${missingFields.join(', ')}`);
3232
+ return;
3233
+ }
3234
+ this.clearError();
3235
+ console.log('DynamicRelationshipBuilderComponent: Configuration validated successfully');
3236
+ // Initialize relationship data loading
3237
+ this.listItems();
3238
+ this.setCreateSubscription();
3239
+ }
3240
+ catch (error) {
3241
+ const componentError = this.errorHandler.logSelectionSetError(SelectionSetErrorType.GRAPHQL_ERROR, `Unexpected error during component initialization: ${error instanceof Error ? error.message : String(error)}`, this.m2m, undefined, {
3242
+ component: 'DynamicRelationshipBuilderComponent',
3243
+ method: 'ngOnInit',
3244
+ originalError: {
3245
+ message: error instanceof Error ? error.message : String(error),
3246
+ stack: error instanceof Error ? error.stack : undefined
3247
+ }
3248
+ });
3249
+ console.error('DynamicRelationshipBuilderComponent: Critical initialization error:', error);
3250
+ this.setError('Failed to initialize relationship component');
3251
+ }
3252
+ }
3253
+ /**
3254
+ * Set error message and clear loading state
3255
+ * Enhanced with comprehensive error context logging
3256
+ */
3257
+ setError(message, additionalContext) {
3258
+ try {
3259
+ this.errorMessage = message;
3260
+ this.isLoading = false;
3261
+ // Log error with context using ErrorHandlerService
3262
+ this.errorHandler.logSelectionSetError(SelectionSetErrorType.GRAPHQL_ERROR, `Component error: ${message}`, this.m2m, undefined, {
3263
+ component: 'DynamicRelationshipBuilderComponent',
3264
+ method: 'setError',
3265
+ userMessage: message,
3266
+ componentState: {
3267
+ hasM2M: !!this.m2m,
3268
+ hasItem: !!this.item,
3269
+ itemId: this.item?.id,
3270
+ m2mItemsCount: Array.isArray(this.m2mItems) ? this.m2mItems.length : 0,
3271
+ isLoading: this.isLoading
3272
+ },
3273
+ ...additionalContext
3274
+ });
3275
+ console.error('DynamicRelationshipBuilderComponent error:', {
3276
+ message,
3277
+ context: additionalContext,
3278
+ timestamp: new Date().toISOString()
3279
+ });
3280
+ }
3281
+ catch (loggingError) {
3282
+ // Fallback if even error logging fails
3283
+ console.error('DynamicRelationshipBuilderComponent: Critical error in setError:', {
3284
+ originalMessage: message,
3285
+ loggingError,
3286
+ timestamp: new Date().toISOString()
3287
+ });
3288
+ this.errorMessage = message;
3289
+ this.isLoading = false;
3290
+ }
3291
+ }
3292
+ /**
3293
+ * Clear error message and log recovery
3294
+ */
3295
+ clearError() {
3296
+ if (this.errorMessage) {
3297
+ console.log('DynamicRelationshipBuilderComponent: Clearing previous error:', this.errorMessage);
3298
+ }
3299
+ this.errorMessage = '';
3300
+ }
3301
+ /**
3302
+ * Get display name for a relationship item, handling various field names
3303
+ */
3304
+ getDisplayName(relationshipItem) {
3305
+ if (!relationshipItem) {
3306
+ return 'Unknown item';
3307
+ }
3308
+ // Try common display field names
3309
+ return relationshipItem.name ||
3310
+ relationshipItem.title ||
3311
+ relationshipItem.label ||
3312
+ relationshipItem.displayName ||
3313
+ `Item ${relationshipItem.id || 'Unknown'}`;
3314
+ }
3315
+ /**
3316
+ * Get display name for items with partial or missing relationship data
3317
+ */
3318
+ getPartialDisplayName(item) {
3319
+ if (!item) {
3320
+ return 'Unknown item';
3321
+ }
3322
+ // If we have an ID, show it
3323
+ if (item.id) {
3324
+ return `Item ${item.id} (incomplete data)`;
3325
+ }
3326
+ return 'Incomplete relationship data';
3327
+ }
3328
+ /**
3329
+ * Check if an item has complete relationship data
3330
+ */
3331
+ hasCompleteRelationshipData(item) {
3332
+ if (!item || !this.m2m?.fieldName) {
3333
+ return false;
3334
+ }
3335
+ const relationshipData = item[this.m2m.fieldName];
3336
+ return relationshipData && (relationshipData.name || relationshipData.title || relationshipData.id);
3337
+ }
3338
+ /**
3339
+ * Check if an item has partial relationship data (item exists but relationship data is incomplete)
3340
+ */
3341
+ hasPartialRelationshipData(item) {
3342
+ if (!item || !item.id) {
3343
+ return false;
3344
+ }
3345
+ return !this.hasCompleteRelationshipData(item);
3346
+ }
3347
+ /**
3348
+ * Get loading state indicator for relationship data
3349
+ */
3350
+ getRelationshipLoadingState(item) {
3351
+ if (!item) {
3352
+ return 'missing';
3353
+ }
3354
+ if (this.isLoading) {
3355
+ return 'loading';
3356
+ }
3357
+ if (this.hasCompleteRelationshipData(item)) {
3358
+ return 'complete';
3359
+ }
3360
+ if (item.id) {
3361
+ return 'partial';
3362
+ }
3363
+ return 'missing';
3364
+ }
3365
+ /**
3366
+ * Get appropriate CSS class for relationship item based on data completeness
3367
+ */
3368
+ getRelationshipItemClass(item) {
3369
+ const state = this.getRelationshipLoadingState(item);
3370
+ return `relationship-item relationship-item--${state}`;
3371
+ }
3372
+ /**
3373
+ * Get tooltip text for relationship items based on their data state
3374
+ */
3375
+ getRelationshipTooltip(item) {
3376
+ const state = this.getRelationshipLoadingState(item);
3377
+ switch (state) {
3378
+ case 'complete':
3379
+ return 'Complete relationship data loaded';
3380
+ case 'partial':
3381
+ return 'Relationship exists but some data is missing or failed to load';
3382
+ case 'loading':
3383
+ return 'Loading relationship data...';
3384
+ case 'missing':
3385
+ return 'No relationship data available';
3386
+ default:
3387
+ return '';
3388
+ }
3389
+ }
3390
+ /**
3391
+ * Get summary of data states for all relationship items
3392
+ */
3393
+ getDataStateSummary() {
3394
+ if (!Array.isArray(this.m2mItems)) {
3395
+ return {
3396
+ completeCount: 0,
3397
+ partialCount: 0,
3398
+ missingCount: 0,
3399
+ hasPartialOrMissing: false
3400
+ };
3401
+ }
3402
+ let completeCount = 0;
3403
+ let partialCount = 0;
3404
+ let missingCount = 0;
3405
+ this.m2mItems.forEach(item => {
3406
+ const state = this.getRelationshipLoadingState(item);
3407
+ switch (state) {
3408
+ case 'complete':
3409
+ completeCount++;
3410
+ break;
3411
+ case 'partial':
3412
+ partialCount++;
3413
+ break;
3414
+ case 'missing':
3415
+ missingCount++;
3416
+ break;
3417
+ }
3418
+ });
3419
+ return {
3420
+ completeCount,
3421
+ partialCount,
3422
+ missingCount,
3423
+ hasPartialOrMissing: partialCount > 0 || missingCount > 0
3424
+ };
814
3425
  }
815
3426
  async perhapsAddM2MItem(item) {
816
- let m2mItem = { id: item.id };
817
- const { data: retItem } = await item[this.m2m.fieldName]();
818
- m2mItem[this.m2m.fieldName] = retItem;
819
- let existingInd = this.m2mItems.findIndex((x) => x.id == item.id);
820
- if (existingInd > -1) {
821
- if (!this.m2mItems[existingInd][this.m2m.fieldName]) {
822
- console.log('would delete existing', this.m2mItems[existingInd]);
823
- this.m2mItems.splice(existingInd, 1);
3427
+ try {
3428
+ if (!item || !item.id) {
3429
+ console.warn('Invalid item received in perhapsAddM2MItem:', item);
3430
+ return;
3431
+ }
3432
+ let m2mItem = { id: item.id };
3433
+ // Safely attempt to get relationship data
3434
+ if (this.m2m?.fieldName && typeof item[this.m2m.fieldName] === 'function') {
3435
+ try {
3436
+ const { data: retItem } = await item[this.m2m.fieldName]();
3437
+ m2mItem[this.m2m.fieldName] = retItem;
3438
+ }
3439
+ catch (relationshipError) {
3440
+ console.warn('Failed to load relationship data for item:', item.id, relationshipError);
3441
+ // Keep the item but mark it as having incomplete data
3442
+ m2mItem[this.m2m.fieldName] = null;
3443
+ }
824
3444
  }
3445
+ else {
3446
+ console.warn('Invalid fieldName or item method for relationship:', this.m2m?.fieldName);
3447
+ m2mItem[this.m2m.fieldName] = null;
3448
+ }
3449
+ // Ensure m2mItems is initialized as array
3450
+ if (!Array.isArray(this.m2mItems)) {
3451
+ this.m2mItems = [];
3452
+ }
3453
+ let existingInd = this.m2mItems.findIndex((x) => x?.id === item.id);
3454
+ if (existingInd > -1) {
3455
+ if (!this.m2mItems[existingInd][this.m2m.fieldName]) {
3456
+ console.log('Removing existing item with no relationship data', this.m2mItems[existingInd]);
3457
+ this.m2mItems.splice(existingInd, 1);
3458
+ }
3459
+ }
3460
+ this.m2mItems.push(m2mItem);
3461
+ }
3462
+ catch (error) {
3463
+ console.error('Error in perhapsAddM2MItem:', error);
3464
+ // Don't let individual item errors break the entire component
825
3465
  }
826
- this.m2mItems.push(m2mItem);
827
- //console.log('perhapsAddM2MItem', item, 'm2m', this.m2m);
828
3466
  }
829
3467
  setCreateSubscription() {
830
- this.ams.setRelatedSub(this.m2m, this.item.id)?.subscribe({
831
- next: (data) => this.perhapsAddM2MItem(data), //(console.log('setRelatedSub m2mItems', this.m2mItems, 'data', data),
832
- error: (error) => console.warn(error),
833
- });
3468
+ try {
3469
+ if (!this.m2m || !this.item?.id) {
3470
+ console.warn('Cannot set subscription: missing m2m or item.id');
3471
+ return;
3472
+ }
3473
+ const subscription = this.ams.setRelatedSub(this.m2m, this.item.id);
3474
+ if (subscription) {
3475
+ subscription.subscribe({
3476
+ next: (data) => {
3477
+ this.perhapsAddM2MItem(data);
3478
+ },
3479
+ error: (error) => {
3480
+ console.warn('Subscription error for related items:', error);
3481
+ this.setError('Failed to receive real-time updates for relationships');
3482
+ },
3483
+ });
3484
+ }
3485
+ else {
3486
+ console.warn('Failed to create subscription for related items');
3487
+ }
3488
+ }
3489
+ catch (error) {
3490
+ console.error('Error setting up subscription:', error);
3491
+ this.setError('Failed to set up real-time updates');
3492
+ }
834
3493
  }
835
3494
  listItems() {
836
- this.ams.getRelatedItems(this.m2m, this.item.id)?.subscribe({
837
- next: ({ items, isSynced }) => {
838
- this.m2mItems = items;
839
- },
840
- });
3495
+ try {
3496
+ if (!this.m2m || !this.item?.id) {
3497
+ const error = 'Cannot load relationships: missing configuration or item data';
3498
+ this.setError(error, {
3499
+ method: 'listItems',
3500
+ hasM2M: !!this.m2m,
3501
+ hasItem: !!this.item,
3502
+ itemId: this.item?.id
3503
+ });
3504
+ return;
3505
+ }
3506
+ this.isLoading = true;
3507
+ this.clearError();
3508
+ console.log('DynamicRelationshipBuilderComponent: Loading relationship items for:', {
3509
+ relationshipModel: this.m2m.relationshipModelName,
3510
+ fieldName: this.m2m.fieldName,
3511
+ itemId: this.item.id
3512
+ });
3513
+ const observable = this.ams.getRelatedItems(this.m2m, this.item.id);
3514
+ if (observable) {
3515
+ observable.subscribe({
3516
+ next: ({ items, isSynced }) => {
3517
+ try {
3518
+ this.isLoading = false;
3519
+ // Ensure items is an array
3520
+ if (Array.isArray(items)) {
3521
+ this.m2mItems = items;
3522
+ console.log('DynamicRelationshipBuilderComponent: Successfully loaded relationship items:', {
3523
+ count: items.length,
3524
+ isSynced,
3525
+ sampleItem: items[0] ? Object.keys(items[0]) : []
3526
+ });
3527
+ }
3528
+ else {
3529
+ console.warn('DynamicRelationshipBuilderComponent: Received non-array items:', items);
3530
+ this.m2mItems = [];
3531
+ this.errorHandler.logSelectionSetError(SelectionSetErrorType.GRAPHQL_ERROR, 'Received non-array items from relationship query', this.m2m, undefined, {
3532
+ component: 'DynamicRelationshipBuilderComponent',
3533
+ method: 'listItems',
3534
+ receivedType: typeof items,
3535
+ receivedValue: items,
3536
+ isSynced
3537
+ });
3538
+ }
3539
+ // Analyze data quality
3540
+ const dataStateSummary = this.getDataStateSummary();
3541
+ if (dataStateSummary.hasPartialOrMissing) {
3542
+ console.warn('DynamicRelationshipBuilderComponent: Some relationship data is incomplete:', dataStateSummary);
3543
+ }
3544
+ }
3545
+ catch (processingError) {
3546
+ this.isLoading = false;
3547
+ const error = `Error processing relationship data: ${processingError instanceof Error ? processingError.message : String(processingError)}`;
3548
+ this.setError('Failed to process relationship data', {
3549
+ method: 'listItems',
3550
+ step: 'data_processing',
3551
+ originalError: {
3552
+ message: processingError instanceof Error ? processingError.message : String(processingError),
3553
+ stack: processingError instanceof Error ? processingError.stack : undefined
3554
+ }
3555
+ });
3556
+ this.m2mItems = [];
3557
+ }
3558
+ },
3559
+ error: (error) => {
3560
+ this.isLoading = false;
3561
+ const errorMessage = error instanceof Error ? error.message : String(error);
3562
+ console.error('DynamicRelationshipBuilderComponent: Error loading relationship items:', error);
3563
+ // Log comprehensive error details
3564
+ this.errorHandler.logSelectionSetError(SelectionSetErrorType.GRAPHQL_ERROR, `Failed to load relationship data: ${errorMessage}`, this.m2m, undefined, {
3565
+ component: 'DynamicRelationshipBuilderComponent',
3566
+ method: 'listItems',
3567
+ step: 'observable_error',
3568
+ originalError: {
3569
+ message: errorMessage,
3570
+ stack: error instanceof Error ? error.stack : undefined,
3571
+ name: error instanceof Error ? error.name : undefined
3572
+ },
3573
+ itemId: this.item.id
3574
+ });
3575
+ this.setError('Failed to load relationship data. Please try refreshing the page.', {
3576
+ method: 'listItems',
3577
+ originalError: errorMessage
3578
+ });
3579
+ // Initialize empty array so component doesn't break
3580
+ this.m2mItems = [];
3581
+ }
3582
+ });
3583
+ }
3584
+ else {
3585
+ this.isLoading = false;
3586
+ this.errorHandler.logSelectionSetError(SelectionSetErrorType.GRAPHQL_ERROR, 'AmplifyModelService returned null observable for relationship query', this.m2m, undefined, {
3587
+ component: 'DynamicRelationshipBuilderComponent',
3588
+ method: 'listItems',
3589
+ step: 'observable_creation',
3590
+ itemId: this.item.id
3591
+ });
3592
+ this.setError('Unable to create relationship query', {
3593
+ method: 'listItems',
3594
+ step: 'observable_creation'
3595
+ });
3596
+ this.m2mItems = [];
3597
+ }
3598
+ }
3599
+ catch (error) {
3600
+ this.isLoading = false;
3601
+ const errorMessage = error instanceof Error ? error.message : String(error);
3602
+ console.error('DynamicRelationshipBuilderComponent: Critical error in listItems:', error);
3603
+ this.errorHandler.logSelectionSetError(SelectionSetErrorType.GRAPHQL_ERROR, `Critical error in listItems: ${errorMessage}`, this.m2m, undefined, {
3604
+ component: 'DynamicRelationshipBuilderComponent',
3605
+ method: 'listItems',
3606
+ step: 'critical_error',
3607
+ originalError: {
3608
+ message: errorMessage,
3609
+ stack: error instanceof Error ? error.stack : undefined
3610
+ }
3611
+ });
3612
+ this.setError('An unexpected error occurred while loading relationships', {
3613
+ method: 'listItems',
3614
+ originalError: errorMessage
3615
+ });
3616
+ this.m2mItems = [];
3617
+ }
841
3618
  }
842
3619
  /**
843
3620
  Customize this function to include all desired models from your file
@@ -848,44 +3625,111 @@ class DynamicRelationshipBuilderComponent {
848
3625
  this.modelItem = signal(this.ams.getEmptyObjectForModel(mdl), ...(ngDevMode ? [{ debugName: "modelItem" }] : []));
849
3626
  }
850
3627
  async deleteRelatedItem(item) {
851
- console.log('deleteRelatedItem for item', item, 'm2m', this.m2m);
852
- //this.ams.deleteRelationship(this.m2m.relationshipModelName, item.id);
853
- await this.ams.deleteRelationship(this.m2m.relationshipModelName, item.id);
3628
+ try {
3629
+ if (!item || !item.id) {
3630
+ console.warn('Cannot delete item: invalid item data', item);
3631
+ this.setError('Cannot delete item: invalid data');
3632
+ return;
3633
+ }
3634
+ if (!this.m2m?.relationshipModelName) {
3635
+ console.warn('Cannot delete item: missing relationship model name');
3636
+ this.setError('Cannot delete item: missing configuration');
3637
+ return;
3638
+ }
3639
+ console.log('deleteRelatedItem for item', item, 'm2m', this.m2m);
3640
+ await this.ams.deleteRelationship(this.m2m.relationshipModelName, item.id);
3641
+ // Remove item from local array on successful deletion
3642
+ if (Array.isArray(this.m2mItems)) {
3643
+ const index = this.m2mItems.findIndex(x => x?.id === item.id);
3644
+ if (index > -1) {
3645
+ this.m2mItems.splice(index, 1);
3646
+ }
3647
+ }
3648
+ }
3649
+ catch (error) {
3650
+ console.error('Error deleting relationship item:', error);
3651
+ this.setError('Failed to delete relationship. Please try again.');
3652
+ }
854
3653
  }
855
3654
  openDialog(rel) {
856
- console.log('openDialog clicked for rel', rel);
857
- this.setModelItem(rel.partnerModelName);
858
- const dialogRef = this.dialog.open(AddRelationshipDialogComponent, {
859
- data: { rel: rel, modelItem: this.modelItem(), selectedItems: this.m2mItems },
860
- });
861
- dialogRef.afterClosed().subscribe(result => {
862
- if (result)
863
- this.handleDialogResult(result, rel);
864
- });
3655
+ try {
3656
+ if (!rel) {
3657
+ console.warn('Cannot open dialog: missing relationship configuration');
3658
+ this.setError('Cannot add relationship: missing configuration');
3659
+ return;
3660
+ }
3661
+ console.log('openDialog clicked for rel', rel);
3662
+ this.setModelItem(rel.partnerModelName);
3663
+ const dialogRef = this.dialog.open(AddRelationshipDialogComponent, {
3664
+ data: { rel: rel, modelItem: this.modelItem(), selectedItems: this.m2mItems || [] },
3665
+ });
3666
+ dialogRef.afterClosed().subscribe({
3667
+ next: (result) => {
3668
+ if (result) {
3669
+ this.handleDialogResult(result, rel);
3670
+ }
3671
+ },
3672
+ error: (error) => {
3673
+ console.error('Dialog error:', error);
3674
+ this.setError('An error occurred while adding the relationship');
3675
+ }
3676
+ });
3677
+ }
3678
+ catch (error) {
3679
+ console.error('Error opening dialog:', error);
3680
+ this.setError('Failed to open relationship dialog');
3681
+ }
865
3682
  }
866
3683
  handleDialogResult(result, rel) {
867
- if (!result)
868
- return;
869
- console.log('handleDialogResult', result, 'rel', rel);
870
- if (result !== undefined) {
871
- this.modelItem.set(result);
3684
+ try {
3685
+ if (!result) {
3686
+ return;
3687
+ }
3688
+ if (!rel || !this.item?.id) {
3689
+ console.warn('Cannot handle dialog result: missing relationship or item data');
3690
+ this.setError('Cannot create relationship: missing data');
3691
+ return;
3692
+ }
3693
+ console.log('handleDialogResult', result, 'rel', rel);
3694
+ if (result !== undefined) {
3695
+ this.modelItem.set(result);
3696
+ }
3697
+ let relIdName = rel.partnerModelName?.toLowerCase() + 'Id';
3698
+ let thisIdName = rel.baseModelName?.toLowerCase() + 'Id';
3699
+ if (!relIdName || !thisIdName) {
3700
+ console.warn('Cannot determine relationship field names');
3701
+ this.setError('Cannot create relationship: invalid configuration');
3702
+ return;
3703
+ }
3704
+ let newRelObj = {};
3705
+ newRelObj[relIdName] = result.id;
3706
+ newRelObj[thisIdName] = this.item.id;
3707
+ const createPromise = this.ams.createRelationship(rel.relationshipModelName, newRelObj);
3708
+ if (createPromise) {
3709
+ createPromise.catch((error) => {
3710
+ console.error('Error creating relationship:', error);
3711
+ this.setError('Failed to create relationship. Please try again.');
3712
+ });
3713
+ }
3714
+ else {
3715
+ console.warn('Failed to create relationship: service returned null');
3716
+ this.setError('Failed to create relationship. Please try again.');
3717
+ }
3718
+ }
3719
+ catch (error) {
3720
+ console.error('Error handling dialog result:', error);
3721
+ this.setError('An error occurred while creating the relationship');
872
3722
  }
873
- let relIdName = rel.partnerModelName.toLowerCase() + 'Id';
874
- let thisIdName = rel.baseModelName.toLowerCase() + 'Id';
875
- let newRelObj = {};
876
- newRelObj[relIdName] = result.id;
877
- newRelObj[thisIdName] = this.item.id;
878
- this.ams.createRelationship(rel.relationshipModelName, newRelObj);
879
3723
  }
880
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: DynamicRelationshipBuilderComponent, deps: [{ token: AmplifyModelService }], target: i0.ɵɵFactoryTarget.Component });
881
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.15", type: DynamicRelationshipBuilderComponent, isStandalone: true, selector: "snteam-dynamic-relationship-builder", inputs: { m2m: "m2m", item: "item", model: "model" }, ngImport: i0, template: "<div class=\"record-relationship-holder\">\n <div class=\"relationship-type\">\n <mat-toolbar>\n <span>{{m2m.partnerModelName}} Management</span>\n <span class=\"rel-title-spacer\"></span>\n <button mat-icon-button attr.aria-label=\"Add {{m2m.name}}\" (click)=\"openDialog(m2m)\">\n <mat-icon>add</mat-icon>\n </button>\n </mat-toolbar>\n @if(m2mItems && m2mItems.length > 0){\n <mat-list>\n @for (item of m2mItems; track item?.id) {\n @if(item[m2m.fieldName]){\n <mat-list-item>\n <span matListItemTitle>{{item[m2m.fieldName].name || item[m2m.fieldName].title}}</span>\n <button mat-icon-button (click)=\"deleteRelatedItem(item)\" matListItemMeta>\n <mat-icon>delete</mat-icon>\n </button>\n </mat-list-item>\n }\n }\n </mat-list>\n }\n @else {\n Add your first {{m2m.partnerModelName}}\n }\n </div>\n</div>", styles: [".relationship-type{margin-top:20px}.rel-title-spacer{flex:1 1 auto}mat-toolbar button{background:transparent;color:#000}\n"], dependencies: [{ kind: "ngmodule", type: MatToolbarModule }, { kind: "component", type: i2.MatToolbar, selector: "mat-toolbar", inputs: ["color"], exportAs: ["matToolbar"] }, { kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i3$3.MatIconButton, selector: "button[mat-icon-button], a[mat-icon-button], button[matIconButton], a[matIconButton]", exportAs: ["matButton", "matAnchor"] }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i4$1.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "ngmodule", type: MatFormFieldModule }, { kind: "ngmodule", type: MatInputModule }, { kind: "ngmodule", type: MatListModule }, { kind: "component", type: i5$1.MatList, selector: "mat-list", exportAs: ["matList"] }, { kind: "component", type: i5$1.MatListItem, selector: "mat-list-item, a[mat-list-item], button[mat-list-item]", inputs: ["activated"], exportAs: ["matListItem"] }, { kind: "directive", type: i5$1.MatListItemTitle, selector: "[matListItemTitle]" }, { kind: "directive", type: i5$1.MatListItemMeta, selector: "[matListItemMeta]" }, { kind: "ngmodule", type: FormsModule }, { kind: "ngmodule", type: CommonModule }] });
3724
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: DynamicRelationshipBuilderComponent, deps: [{ token: AmplifyModelService }, { token: ErrorHandlerService }], target: i0.ɵɵFactoryTarget.Component });
3725
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.15", type: DynamicRelationshipBuilderComponent, isStandalone: true, selector: "snteam-dynamic-relationship-builder", inputs: { m2m: "m2m", item: "item", model: "model" }, ngImport: i0, template: "<div class=\"record-relationship-holder\">\n <div class=\"relationship-type\">\n <mat-toolbar>\n <span>{{m2m?.partnerModelName || 'Unknown'}} Management</span>\n <span class=\"rel-title-spacer\"></span>\n <button mat-icon-button [attr.aria-label]=\"'Add ' + (m2m?.name || 'item')\" (click)=\"openDialog(m2m)\" [disabled]=\"!m2m\">\n <mat-icon>add</mat-icon>\n </button>\n </mat-toolbar>\n \n <!-- Error message display -->\n @if (errorMessage) {\n <div class=\"error-message\" role=\"alert\">\n <mat-icon>error</mat-icon>\n <span>{{ errorMessage }}</span>\n </div>\n }\n \n <!-- Loading state -->\n @if (isLoading) {\n <div class=\"loading-message\">\n <mat-icon>hourglass_empty</mat-icon>\n <span>Loading relationships...</span>\n </div>\n }\n \n <!-- Relationship items display -->\n @if (m2mItems && m2mItems.length > 0) {\n <mat-list>\n @for (item of m2mItems; track item?.id) {\n <mat-list-item [class]=\"getRelationshipItemClass(item)\" [title]=\"getRelationshipTooltip(item)\">\n @if (hasCompleteRelationshipData(item)) {\n <!-- Complete relationship data -->\n <span matListItemTitle>{{ getDisplayName(item[m2m.fieldName]) }}</span>\n <span matListItemLine class=\"relationship-status complete\">Complete data</span>\n <button mat-icon-button (click)=\"deleteRelatedItem(item)\" matListItemMeta [attr.aria-label]=\"'Delete ' + getDisplayName(item[m2m.fieldName])\">\n <mat-icon>delete</mat-icon>\n </button>\n } @else if (hasPartialRelationshipData(item)) {\n <!-- Partial relationship data -->\n <span matListItemTitle class=\"partial-data\">\n {{ getPartialDisplayName(item) }}\n <mat-icon class=\"warning-icon\" title=\"Incomplete relationship data\">warning</mat-icon>\n </span>\n <span matListItemLine class=\"relationship-status partial\">Partial data - some information may be missing</span>\n <button mat-icon-button (click)=\"deleteRelatedItem(item)\" matListItemMeta [attr.aria-label]=\"'Delete incomplete item'\">\n <mat-icon>delete</mat-icon>\n </button>\n } @else {\n <!-- Missing or invalid relationship data -->\n <span matListItemTitle class=\"missing-data\">\n Invalid relationship data\n <mat-icon class=\"error-icon\" title=\"Invalid or missing data\">error</mat-icon>\n </span>\n <span matListItemLine class=\"relationship-status missing\">Data could not be loaded</span>\n <button mat-icon-button (click)=\"deleteRelatedItem(item)\" matListItemMeta [attr.aria-label]=\"'Delete invalid item'\">\n <mat-icon>delete</mat-icon>\n </button>\n }\n </mat-list-item>\n }\n </mat-list>\n \n <!-- Summary of data states -->\n @if (getDataStateSummary().hasPartialOrMissing) {\n <div class=\"data-summary\">\n <mat-icon>info</mat-icon>\n <span>\n {{ getDataStateSummary().completeCount }} of {{ m2mItems.length }} relationships loaded completely.\n @if (getDataStateSummary().partialCount > 0) {\n {{ getDataStateSummary().partialCount }} have partial data.\n }\n @if (getDataStateSummary().missingCount > 0) {\n {{ getDataStateSummary().missingCount }} failed to load.\n }\n </span>\n </div>\n }\n } @else if (!isLoading && !errorMessage) {\n <div class=\"empty-state\">\n Add your first {{ m2m?.partnerModelName || 'item' }}\n </div>\n }\n </div>\n</div>", styles: [".relationship-type{margin-top:20px}.rel-title-spacer{flex:1 1 auto}mat-toolbar button{background:transparent;color:#000}.error-message{display:flex;align-items:center;gap:8px;padding:12px;background-color:#ffebee;color:#c62828;border-left:4px solid #c62828;margin:8px 0}.loading-message{display:flex;align-items:center;gap:8px;padding:12px;background-color:#f5f5f5;color:#666;margin:8px 0}.partial-data{display:flex;align-items:center;gap:4px;color:#ff9800}.missing-data{display:flex;align-items:center;gap:4px;color:#f44336}.warning-icon{font-size:16px;width:16px;height:16px;color:#ff9800}.error-icon{font-size:16px;width:16px;height:16px;color:#f44336}.empty-state{padding:20px;text-align:center;color:#666;font-style:italic}.relationship-status{font-size:12px;opacity:.7}.relationship-status.complete{color:#4caf50}.relationship-status.partial{color:#ff9800}.relationship-status.missing{color:#f44336}.relationship-item--complete{border-left:3px solid #4caf50}.relationship-item--partial{border-left:3px solid #ff9800;background-color:#fff8e1}.relationship-item--missing{border-left:3px solid #f44336;background-color:#ffebee}.relationship-item--loading{border-left:3px solid #2196f3;background-color:#e3f2fd}.data-summary{display:flex;align-items:center;gap:8px;padding:12px;background-color:#e8f5e8;color:#2e7d32;border-radius:4px;margin:8px 0;font-size:14px}\n"], dependencies: [{ kind: "ngmodule", type: MatToolbarModule }, { kind: "component", type: i3$4.MatToolbar, selector: "mat-toolbar", inputs: ["color"], exportAs: ["matToolbar"] }, { kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i3$3.MatIconButton, selector: "button[mat-icon-button], a[mat-icon-button], button[matIconButton], a[matIconButton]", exportAs: ["matButton", "matAnchor"] }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i5$1.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "ngmodule", type: MatFormFieldModule }, { kind: "ngmodule", type: MatInputModule }, { kind: "ngmodule", type: MatListModule }, { kind: "component", type: i6$1.MatList, selector: "mat-list", exportAs: ["matList"] }, { kind: "component", type: i6$1.MatListItem, selector: "mat-list-item, a[mat-list-item], button[mat-list-item]", inputs: ["activated"], exportAs: ["matListItem"] }, { kind: "directive", type: i6$1.MatListItemLine, selector: "[matListItemLine]" }, { kind: "directive", type: i6$1.MatListItemTitle, selector: "[matListItemTitle]" }, { kind: "directive", type: i6$1.MatListItemMeta, selector: "[matListItemMeta]" }, { kind: "ngmodule", type: FormsModule }, { kind: "ngmodule", type: CommonModule }] });
882
3726
  }
883
3727
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: DynamicRelationshipBuilderComponent, decorators: [{
884
3728
  type: Component,
885
3729
  args: [{ selector: 'snteam-dynamic-relationship-builder', standalone: true, imports: [MatToolbarModule, MatButtonModule, MatIconModule, MatFormFieldModule, MatInputModule, MatListModule,
886
3730
  FormsModule,
887
- CommonModule], template: "<div class=\"record-relationship-holder\">\n <div class=\"relationship-type\">\n <mat-toolbar>\n <span>{{m2m.partnerModelName}} Management</span>\n <span class=\"rel-title-spacer\"></span>\n <button mat-icon-button attr.aria-label=\"Add {{m2m.name}}\" (click)=\"openDialog(m2m)\">\n <mat-icon>add</mat-icon>\n </button>\n </mat-toolbar>\n @if(m2mItems && m2mItems.length > 0){\n <mat-list>\n @for (item of m2mItems; track item?.id) {\n @if(item[m2m.fieldName]){\n <mat-list-item>\n <span matListItemTitle>{{item[m2m.fieldName].name || item[m2m.fieldName].title}}</span>\n <button mat-icon-button (click)=\"deleteRelatedItem(item)\" matListItemMeta>\n <mat-icon>delete</mat-icon>\n </button>\n </mat-list-item>\n }\n }\n </mat-list>\n }\n @else {\n Add your first {{m2m.partnerModelName}}\n }\n </div>\n</div>", styles: [".relationship-type{margin-top:20px}.rel-title-spacer{flex:1 1 auto}mat-toolbar button{background:transparent;color:#000}\n"] }]
888
- }], ctorParameters: () => [{ type: AmplifyModelService }], propDecorators: { m2m: [{
3731
+ CommonModule], template: "<div class=\"record-relationship-holder\">\n <div class=\"relationship-type\">\n <mat-toolbar>\n <span>{{m2m?.partnerModelName || 'Unknown'}} Management</span>\n <span class=\"rel-title-spacer\"></span>\n <button mat-icon-button [attr.aria-label]=\"'Add ' + (m2m?.name || 'item')\" (click)=\"openDialog(m2m)\" [disabled]=\"!m2m\">\n <mat-icon>add</mat-icon>\n </button>\n </mat-toolbar>\n \n <!-- Error message display -->\n @if (errorMessage) {\n <div class=\"error-message\" role=\"alert\">\n <mat-icon>error</mat-icon>\n <span>{{ errorMessage }}</span>\n </div>\n }\n \n <!-- Loading state -->\n @if (isLoading) {\n <div class=\"loading-message\">\n <mat-icon>hourglass_empty</mat-icon>\n <span>Loading relationships...</span>\n </div>\n }\n \n <!-- Relationship items display -->\n @if (m2mItems && m2mItems.length > 0) {\n <mat-list>\n @for (item of m2mItems; track item?.id) {\n <mat-list-item [class]=\"getRelationshipItemClass(item)\" [title]=\"getRelationshipTooltip(item)\">\n @if (hasCompleteRelationshipData(item)) {\n <!-- Complete relationship data -->\n <span matListItemTitle>{{ getDisplayName(item[m2m.fieldName]) }}</span>\n <span matListItemLine class=\"relationship-status complete\">Complete data</span>\n <button mat-icon-button (click)=\"deleteRelatedItem(item)\" matListItemMeta [attr.aria-label]=\"'Delete ' + getDisplayName(item[m2m.fieldName])\">\n <mat-icon>delete</mat-icon>\n </button>\n } @else if (hasPartialRelationshipData(item)) {\n <!-- Partial relationship data -->\n <span matListItemTitle class=\"partial-data\">\n {{ getPartialDisplayName(item) }}\n <mat-icon class=\"warning-icon\" title=\"Incomplete relationship data\">warning</mat-icon>\n </span>\n <span matListItemLine class=\"relationship-status partial\">Partial data - some information may be missing</span>\n <button mat-icon-button (click)=\"deleteRelatedItem(item)\" matListItemMeta [attr.aria-label]=\"'Delete incomplete item'\">\n <mat-icon>delete</mat-icon>\n </button>\n } @else {\n <!-- Missing or invalid relationship data -->\n <span matListItemTitle class=\"missing-data\">\n Invalid relationship data\n <mat-icon class=\"error-icon\" title=\"Invalid or missing data\">error</mat-icon>\n </span>\n <span matListItemLine class=\"relationship-status missing\">Data could not be loaded</span>\n <button mat-icon-button (click)=\"deleteRelatedItem(item)\" matListItemMeta [attr.aria-label]=\"'Delete invalid item'\">\n <mat-icon>delete</mat-icon>\n </button>\n }\n </mat-list-item>\n }\n </mat-list>\n \n <!-- Summary of data states -->\n @if (getDataStateSummary().hasPartialOrMissing) {\n <div class=\"data-summary\">\n <mat-icon>info</mat-icon>\n <span>\n {{ getDataStateSummary().completeCount }} of {{ m2mItems.length }} relationships loaded completely.\n @if (getDataStateSummary().partialCount > 0) {\n {{ getDataStateSummary().partialCount }} have partial data.\n }\n @if (getDataStateSummary().missingCount > 0) {\n {{ getDataStateSummary().missingCount }} failed to load.\n }\n </span>\n </div>\n }\n } @else if (!isLoading && !errorMessage) {\n <div class=\"empty-state\">\n Add your first {{ m2m?.partnerModelName || 'item' }}\n </div>\n }\n </div>\n</div>", styles: [".relationship-type{margin-top:20px}.rel-title-spacer{flex:1 1 auto}mat-toolbar button{background:transparent;color:#000}.error-message{display:flex;align-items:center;gap:8px;padding:12px;background-color:#ffebee;color:#c62828;border-left:4px solid #c62828;margin:8px 0}.loading-message{display:flex;align-items:center;gap:8px;padding:12px;background-color:#f5f5f5;color:#666;margin:8px 0}.partial-data{display:flex;align-items:center;gap:4px;color:#ff9800}.missing-data{display:flex;align-items:center;gap:4px;color:#f44336}.warning-icon{font-size:16px;width:16px;height:16px;color:#ff9800}.error-icon{font-size:16px;width:16px;height:16px;color:#f44336}.empty-state{padding:20px;text-align:center;color:#666;font-style:italic}.relationship-status{font-size:12px;opacity:.7}.relationship-status.complete{color:#4caf50}.relationship-status.partial{color:#ff9800}.relationship-status.missing{color:#f44336}.relationship-item--complete{border-left:3px solid #4caf50}.relationship-item--partial{border-left:3px solid #ff9800;background-color:#fff8e1}.relationship-item--missing{border-left:3px solid #f44336;background-color:#ffebee}.relationship-item--loading{border-left:3px solid #2196f3;background-color:#e3f2fd}.data-summary{display:flex;align-items:center;gap:8px;padding:12px;background-color:#e8f5e8;color:#2e7d32;border-radius:4px;margin:8px 0;font-size:14px}\n"] }]
3732
+ }], ctorParameters: () => [{ type: AmplifyModelService }, { type: ErrorHandlerService }], propDecorators: { m2m: [{
889
3733
  type: Input
890
3734
  }], item: [{
891
3735
  type: Input
@@ -2036,7 +4880,7 @@ class DynamicFormComponent {
2036
4880
  return this.form.get(question.key);
2037
4881
  return true;
2038
4882
  }
2039
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: DynamicFormComponent, deps: [{ token: QuestionControlService }, { token: AmplifyFormBuilderService }, { token: i3$1.Location }, { token: i4$2.ActivatedRoute }], target: i0.ɵɵFactoryTarget.Component });
4883
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: DynamicFormComponent, deps: [{ token: QuestionControlService }, { token: AmplifyFormBuilderService }, { token: i3$1.Location }, { token: i4$1.ActivatedRoute }], target: i0.ɵɵFactoryTarget.Component });
2040
4884
  static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.15", type: DynamicFormComponent, isStandalone: true, selector: "snteam-dynamic-form", inputs: { model: "model", itemId: "itemId", valueMap: "valueMap", hideRelationships: "hideRelationships", hideSaveButton: "hideSaveButton", amplifyOutputs: "amplifyOutputs", formViewId: "formViewId" }, outputs: { itemOutput: "itemOutput", submitOutput: "submitOutput", formLoaded: "formLoaded" }, ngImport: i0, template: "<div class=\"dynamic-form-holder\">\n @if (form && questions) {\n <form (ngSubmit)=\"onSubmit()\" [formGroup]=\"form\">\n @for (question of questions; track question) {\n @if (this.switchInputs.includes(question.controlType) || this.materialInputs.includes(question.controlType) || this.refInputs.includes(question.controlType)) {\n <div class=\"form-row\">\n <snteam-dynamic-form-question [question]=\"question\" [formGroup]=\"form\"></snteam-dynamic-form-question>\n </div>\n }\n @if (this.formGroupInputs.includes(question.controlType)) {\n <div class=\"group-row\">\n <snteam-dynamic-form-group [question]=\"question\" [formGroup]=\"form\" [formGroupName]=\"question.key\"></snteam-dynamic-form-group>\n </div>\n }\n }\n @if (!hideSaveButton) {\n <div class=\"form-row\">\n <button mat-raised-button color=\"primary\" type=\"submit\" [disabled]=\"!form.valid\">Save</button>\n </div>\n }\n </form>\n } @else {\n <div class=\"loading\">Loading...</div>\n }\n\n @if(item && !hideRelationships){\n <div class=\"relationship-holder\">\n <snteam-record-relationships [model]=\"mdl\" [id]=\"id\" [item]=\"item\" [amplifyOutputs]=\"outputs\"></snteam-record-relationships>\n </div>\n }\n</div>", styles: [".dynamic-form-holder{padding:20px;width:100%;display:flex;justify-content:space-between;gap:20px}form{width:100%;flex-basis:40%}.relationship-holder{flex-basis:40%}.full-width{width:100%}.form-row,.group-row{margin-top:20px}.loading{padding:20px;text-align:center;color:#666}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: DynamicFormQuestionComponent, selector: "snteam-dynamic-form-question", inputs: ["question", "formGroup"] }, { kind: "component", type: DynamicFormGroupComponent, selector: "snteam-dynamic-form-group", inputs: ["formGroupName", "question", "formGroup"] }, { kind: "component", type: RecordRelationshipsComponent, selector: "snteam-record-relationships", inputs: ["model", "id", "item", "amplifyOutputs"] }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i1.FormGroupName, selector: "[formGroupName]", inputs: ["formGroupName"] }, { kind: "ngmodule", type: MatFormFieldModule }, { kind: "ngmodule", type: MatInputModule }, { kind: "ngmodule", type: MatSelectModule }, { kind: "ngmodule", type: MatSnackBarModule }, { kind: "ngmodule", type: MatDividerModule }, { kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i3$3.MatButton, selector: " button[matButton], a[matButton], button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button], a[mat-button], a[mat-raised-button], a[mat-flat-button], a[mat-stroked-button] ", inputs: ["matButton"], exportAs: ["matButton", "matAnchor"] }] });
2041
4885
  }
2042
4886
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: DynamicFormComponent, decorators: [{
@@ -2055,7 +4899,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImpo
2055
4899
  MatDividerModule,
2056
4900
  MatButtonModule
2057
4901
  ], template: "<div class=\"dynamic-form-holder\">\n @if (form && questions) {\n <form (ngSubmit)=\"onSubmit()\" [formGroup]=\"form\">\n @for (question of questions; track question) {\n @if (this.switchInputs.includes(question.controlType) || this.materialInputs.includes(question.controlType) || this.refInputs.includes(question.controlType)) {\n <div class=\"form-row\">\n <snteam-dynamic-form-question [question]=\"question\" [formGroup]=\"form\"></snteam-dynamic-form-question>\n </div>\n }\n @if (this.formGroupInputs.includes(question.controlType)) {\n <div class=\"group-row\">\n <snteam-dynamic-form-group [question]=\"question\" [formGroup]=\"form\" [formGroupName]=\"question.key\"></snteam-dynamic-form-group>\n </div>\n }\n }\n @if (!hideSaveButton) {\n <div class=\"form-row\">\n <button mat-raised-button color=\"primary\" type=\"submit\" [disabled]=\"!form.valid\">Save</button>\n </div>\n }\n </form>\n } @else {\n <div class=\"loading\">Loading...</div>\n }\n\n @if(item && !hideRelationships){\n <div class=\"relationship-holder\">\n <snteam-record-relationships [model]=\"mdl\" [id]=\"id\" [item]=\"item\" [amplifyOutputs]=\"outputs\"></snteam-record-relationships>\n </div>\n }\n</div>", styles: [".dynamic-form-holder{padding:20px;width:100%;display:flex;justify-content:space-between;gap:20px}form{width:100%;flex-basis:40%}.relationship-holder{flex-basis:40%}.full-width{width:100%}.form-row,.group-row{margin-top:20px}.loading{padding:20px;text-align:center;color:#666}\n"] }]
2058
- }], ctorParameters: () => [{ type: QuestionControlService }, { type: AmplifyFormBuilderService }, { type: i3$1.Location }, { type: i4$2.ActivatedRoute }], propDecorators: { model: [{
4902
+ }], ctorParameters: () => [{ type: QuestionControlService }, { type: AmplifyFormBuilderService }, { type: i3$1.Location }, { type: i4$1.ActivatedRoute }], propDecorators: { model: [{
2059
4903
  type: Input
2060
4904
  }], itemId: [{
2061
4905
  type: Input
@@ -2194,7 +5038,7 @@ class ListViewComponent {
2194
5038
  this.listItems();
2195
5039
  }
2196
5040
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: ListViewComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
2197
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.15", type: ListViewComponent, isStandalone: true, selector: "snteam-list-view", inputs: { modelName: "modelName", customItemTemplate: "customItemTemplate", hideNewButton: "hideNewButton", title: "title", useRouter: "useRouter", showRowActions: "showRowActions", showDeleteAction: "showDeleteAction", customRowActions: "customRowActions" }, outputs: { itemClick: "itemClick", newClick: "newClick", itemsLoaded: "itemsLoaded", itemDeleted: "itemDeleted" }, ngImport: i0, template: "<div class=\"list-view-container\">\n <div class=\"header\">\n <h2>{{ title || modelName }}</h2>\n @if (!hideNewButton) {\n <button mat-raised-button color=\"primary\" (click)=\"onNewClick()\">\n <mat-icon>add</mat-icon>\n New {{ modelName }}\n </button>\n }\n </div>\n\n @if (loading) {\n <div class=\"loading\">Loading {{ modelName }}...</div>\n }\n\n @if (error) {\n <div class=\"error\">\n {{ error }}\n <button mat-button (click)=\"refresh()\">Retry</button>\n </div>\n }\n\n @if (!loading && !error && itemsArr.length === 0) {\n <div class=\"empty-state\">\n <p>No {{ modelName }} found</p>\n @if (!hideNewButton) {\n <button mat-raised-button color=\"primary\" (click)=\"onNewClick()\">\n Create First {{ modelName }}\n </button>\n }\n </div>\n }\n\n @if (!loading && !error && itemsArr.length > 0) {\n <mat-list class=\"items-list\">\n @for (item of itemsArr; track item.id) {\n <mat-list-item (click)=\"onItemClick(item, $event)\" class=\"clickable-item\">\n @if (customItemTemplate) {\n <ng-container [ngTemplateOutlet]=\"customItemTemplate\" [ngTemplateOutletContext]=\"{ $implicit: item }\"></ng-container>\n } @else {\n <div class=\"item-content\">\n <div class=\"item-text\">\n <span matListItemTitle>{{ getDisplayText(item) }}</span>\n <span matListItemLine>{{ item.description || (item.createdAt | date) }}</span>\n </div>\n @if (showRowActions || showDeleteAction || customRowActions.length > 0) {\n <div class=\"row-actions\">\n @if (showDeleteAction) {\n <button mat-icon-button color=\"warn\" (click)=\"onDeleteItem(item, $event)\" \n matTooltip=\"Delete\" class=\"action-button\">\n <mat-icon>delete</mat-icon>\n </button>\n }\n @for (action of customRowActions; track action.label) {\n <button mat-icon-button [color]=\"action.color || 'primary'\" \n (click)=\"onCustomAction(action, item, $event)\"\n [matTooltip]=\"action.label\" class=\"action-button\">\n <mat-icon>{{ action.icon }}</mat-icon>\n </button>\n }\n </div>\n }\n </div>\n }\n </mat-list-item>\n }\n </mat-list>\n }\n</div>", styles: [".list-view-container{padding:20px;width:100%}.header{display:flex;justify-content:space-between;align-items:center;margin-bottom:20px}.header h2{margin:0;color:#333}.loading,.error,.empty-state{padding:40px 20px;text-align:center;color:#666}.error{color:#d32f2f}.empty-state p{margin-bottom:20px;font-size:16px}.items-list{border:1px solid #e0e0e0;border-radius:4px}.clickable-item{cursor:pointer;transition:background-color .2s}.clickable-item:hover{background-color:#f5f5f5}.clickable-item:not(:last-child){border-bottom:1px solid #e0e0e0}.item-content{display:flex;justify-content:space-between;align-items:center;width:100%;padding:8px 0}.item-text{flex:1;display:flex;flex-direction:column}.row-actions{display:flex;gap:4px;margin-left:16px}.action-button{width:32px;height:32px;line-height:32px}.action-button mat-icon{font-size:18px;width:18px;height:18px}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i3$1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "ngmodule", type: MatListModule }, { kind: "component", type: i5$1.MatList, selector: "mat-list", exportAs: ["matList"] }, { kind: "component", type: i5$1.MatListItem, selector: "mat-list-item, a[mat-list-item], button[mat-list-item]", inputs: ["activated"], exportAs: ["matListItem"] }, { kind: "directive", type: i5$1.MatListItemLine, selector: "[matListItemLine]" }, { kind: "directive", type: i5$1.MatListItemTitle, selector: "[matListItemTitle]" }, { kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i3$3.MatButton, selector: " button[matButton], a[matButton], button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button], a[mat-button], a[mat-raised-button], a[mat-flat-button], a[mat-stroked-button] ", inputs: ["matButton"], exportAs: ["matButton", "matAnchor"] }, { kind: "component", type: i3$3.MatIconButton, selector: "button[mat-icon-button], a[mat-icon-button], button[matIconButton], a[matIconButton]", exportAs: ["matButton", "matAnchor"] }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i4$1.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "ngmodule", type: MatSnackBarModule }, { kind: "ngmodule", type: MatTooltipModule }, { kind: "directive", type: i5$2.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }, { kind: "pipe", type: i3$1.DatePipe, name: "date" }] });
5041
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.15", type: ListViewComponent, isStandalone: true, selector: "snteam-list-view", inputs: { modelName: "modelName", customItemTemplate: "customItemTemplate", hideNewButton: "hideNewButton", title: "title", useRouter: "useRouter", showRowActions: "showRowActions", showDeleteAction: "showDeleteAction", customRowActions: "customRowActions" }, outputs: { itemClick: "itemClick", newClick: "newClick", itemsLoaded: "itemsLoaded", itemDeleted: "itemDeleted" }, ngImport: i0, template: "<div class=\"list-view-container\">\n <div class=\"header\">\n <h2>{{ title || modelName }}</h2>\n @if (!hideNewButton) {\n <button mat-raised-button color=\"primary\" (click)=\"onNewClick()\">\n <mat-icon>add</mat-icon>\n New {{ modelName }}\n </button>\n }\n </div>\n\n @if (loading) {\n <div class=\"loading\">Loading {{ modelName }}...</div>\n }\n\n @if (error) {\n <div class=\"error\">\n {{ error }}\n <button mat-button (click)=\"refresh()\">Retry</button>\n </div>\n }\n\n @if (!loading && !error && itemsArr.length === 0) {\n <div class=\"empty-state\">\n <p>No {{ modelName }} found</p>\n @if (!hideNewButton) {\n <button mat-raised-button color=\"primary\" (click)=\"onNewClick()\">\n Create First {{ modelName }}\n </button>\n }\n </div>\n }\n\n @if (!loading && !error && itemsArr.length > 0) {\n <mat-list class=\"items-list\">\n @for (item of itemsArr; track item.id) {\n <mat-list-item (click)=\"onItemClick(item, $event)\" class=\"clickable-item\">\n @if (customItemTemplate) {\n <ng-container [ngTemplateOutlet]=\"customItemTemplate\" [ngTemplateOutletContext]=\"{ $implicit: item }\"></ng-container>\n } @else {\n <div class=\"item-content\">\n <div class=\"item-text\">\n <span matListItemTitle>{{ getDisplayText(item) }}</span>\n <span matListItemLine>{{ item.description || (item.createdAt | date) }}</span>\n </div>\n @if (showRowActions || showDeleteAction || customRowActions.length > 0) {\n <div class=\"row-actions\">\n @if (showDeleteAction) {\n <button mat-icon-button color=\"warn\" (click)=\"onDeleteItem(item, $event)\" \n matTooltip=\"Delete\" class=\"action-button\">\n <mat-icon>delete</mat-icon>\n </button>\n }\n @for (action of customRowActions; track action.label) {\n <button mat-icon-button [color]=\"action.color || 'primary'\" \n (click)=\"onCustomAction(action, item, $event)\"\n [matTooltip]=\"action.label\" class=\"action-button\">\n <mat-icon>{{ action.icon }}</mat-icon>\n </button>\n }\n </div>\n }\n </div>\n }\n </mat-list-item>\n }\n </mat-list>\n }\n</div>", styles: [".list-view-container{padding:20px;width:100%}.header{display:flex;justify-content:space-between;align-items:center;margin-bottom:20px}.header h2{margin:0;color:#333}.loading,.error,.empty-state{padding:40px 20px;text-align:center;color:#666}.error{color:#d32f2f}.empty-state p{margin-bottom:20px;font-size:16px}.items-list{border:1px solid #e0e0e0;border-radius:4px}.clickable-item{cursor:pointer;transition:background-color .2s}.clickable-item:hover{background-color:#f5f5f5}.clickable-item:not(:last-child){border-bottom:1px solid #e0e0e0}.item-content{display:flex;justify-content:space-between;align-items:center;width:100%;padding:8px 0}.item-text{flex:1;display:flex;flex-direction:column}.row-actions{display:flex;gap:4px;margin-left:16px}.action-button{width:32px;height:32px;line-height:32px}.action-button mat-icon{font-size:18px;width:18px;height:18px}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i3$1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "ngmodule", type: MatListModule }, { kind: "component", type: i6$1.MatList, selector: "mat-list", exportAs: ["matList"] }, { kind: "component", type: i6$1.MatListItem, selector: "mat-list-item, a[mat-list-item], button[mat-list-item]", inputs: ["activated"], exportAs: ["matListItem"] }, { kind: "directive", type: i6$1.MatListItemLine, selector: "[matListItemLine]" }, { kind: "directive", type: i6$1.MatListItemTitle, selector: "[matListItemTitle]" }, { kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i3$3.MatButton, selector: " button[matButton], a[matButton], button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button], a[mat-button], a[mat-raised-button], a[mat-flat-button], a[mat-stroked-button] ", inputs: ["matButton"], exportAs: ["matButton", "matAnchor"] }, { kind: "component", type: i3$3.MatIconButton, selector: "button[mat-icon-button], a[mat-icon-button], button[matIconButton], a[matIconButton]", exportAs: ["matButton", "matAnchor"] }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i5$1.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "ngmodule", type: MatSnackBarModule }, { kind: "ngmodule", type: MatTooltipModule }, { kind: "directive", type: i5$2.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }, { kind: "pipe", type: i3$1.DatePipe, name: "date" }] });
2198
5042
  }
2199
5043
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: ListViewComponent, decorators: [{
2200
5044
  type: Component,
@@ -2259,13 +5103,6 @@ class ConfigurationsComponent {
2259
5103
  FieldConfig: this.hasFieldConfig
2260
5104
  });
2261
5105
  }
2262
- formatModelLabel(modelName) {
2263
- // Convert PascalCase to readable format
2264
- return modelName
2265
- .replace(/([A-Z])/g, ' $1')
2266
- .trim()
2267
- .replace(/^./, str => str.toUpperCase());
2268
- }
2269
5106
  async populateDefaultFieldConfigs() {
2270
5107
  try {
2271
5108
  console.log('ConfigurationsComponent: Starting populateDefaultFieldConfigs');
@@ -2390,8 +5227,152 @@ return fields;
2390
5227
  async deleteFieldConfigs() {
2391
5228
  await this.deleteAllConfigs('FieldConfig');
2392
5229
  }
5230
+ async populateDefaultTableConfigs() {
5231
+ try {
5232
+ console.log('ConfigurationsComponent: Starting populateDefaultTableConfigs');
5233
+ // Get all available models from the schema
5234
+ const models = this.outputs.data.model_introspection.models;
5235
+ const modelNames = Object.keys(models);
5236
+ let totalCreated = 0;
5237
+ let createdTableConfigs = [];
5238
+ let createdFormViews = [];
5239
+ let createdFormViewFields = [];
5240
+ // Step 1: Create TableConfig records for each model
5241
+ console.log('ConfigurationsComponent: Creating TableConfig records for', modelNames.length, 'models');
5242
+ for (const modelName of modelNames) {
5243
+ const tableConfig = {
5244
+ name: modelName,
5245
+ description: `Configuration for ${this.formatModelLabel(modelName)} model`,
5246
+ label: this.formatModelLabel(modelName),
5247
+ hide_in_menu: false
5248
+ };
5249
+ const tableConfigResult = await this.ams.createItemPromise('TableConfig', tableConfig);
5250
+ if (tableConfigResult && tableConfigResult.data) {
5251
+ createdTableConfigs.push(tableConfigResult.data);
5252
+ totalCreated++;
5253
+ console.log('ConfigurationsComponent: Created TableConfig for', modelName);
5254
+ }
5255
+ }
5256
+ // Step 2: Create FormView records for each model and link to TableConfig
5257
+ console.log('ConfigurationsComponent: Creating FormView records');
5258
+ for (let i = 0; i < modelNames.length; i++) {
5259
+ const modelName = modelNames[i];
5260
+ const tableConfig = createdTableConfigs[i];
5261
+ if (tableConfig) {
5262
+ const formView = {
5263
+ label: `Default ${this.formatModelLabel(modelName)} Form`,
5264
+ tableConfigId: tableConfig.id
5265
+ };
5266
+ const formViewResult = await this.ams.createItemPromise('FormView', formView);
5267
+ if (formViewResult && formViewResult.data) {
5268
+ createdFormViews.push(formViewResult.data);
5269
+ totalCreated++;
5270
+ console.log('ConfigurationsComponent: Created FormView for', modelName);
5271
+ // Step 3: Update TableConfig to set this as the default FormView
5272
+ const updatedTableConfig = {
5273
+ id: tableConfig.id,
5274
+ defaultFormViewId: formViewResult.data.id
5275
+ };
5276
+ await this.ams.updateItemForModel('TableConfig', updatedTableConfig);
5277
+ console.log('ConfigurationsComponent: Updated TableConfig with default FormView for', modelName);
5278
+ }
5279
+ }
5280
+ }
5281
+ // Step 4: Create FormViewField records for each field in each model
5282
+ console.log('ConfigurationsComponent: Creating FormViewField records');
5283
+ for (let i = 0; i < modelNames.length; i++) {
5284
+ const modelName = modelNames[i];
5285
+ const formView = createdFormViews[i];
5286
+ const modelData = models[modelName];
5287
+ if (formView && modelData && modelData.fields) {
5288
+ const fields = Object.values(modelData.fields);
5289
+ const skipFields = ['id', 'createdAt', 'updatedAt']; // Skip system fields
5290
+ let fieldOrder = 0;
5291
+ for (const field of fields) {
5292
+ const fieldData = field;
5293
+ // Skip system fields and relationship fields
5294
+ if (skipFields.includes(fieldData.name) ||
5295
+ (fieldData.association && fieldData.association.connectionType)) {
5296
+ continue;
5297
+ }
5298
+ const formViewField = {
5299
+ field_name: fieldData.name,
5300
+ display_label: this.formatFieldLabel(fieldData.name),
5301
+ field_type: this.mapFieldType(fieldData.type),
5302
+ required: fieldData.isRequired || false,
5303
+ order: fieldOrder++,
5304
+ formViewId: formView.id
5305
+ };
5306
+ const formViewFieldResult = await this.ams.createItemPromise('FormViewField', formViewField);
5307
+ if (formViewFieldResult && formViewFieldResult.data) {
5308
+ createdFormViewFields.push(formViewFieldResult.data);
5309
+ totalCreated++;
5310
+ }
5311
+ }
5312
+ console.log('ConfigurationsComponent: Created FormViewFields for', modelName, 'with', fieldOrder, 'fields');
5313
+ }
5314
+ }
5315
+ console.log('ConfigurationsComponent: Successfully created', totalCreated, 'total records');
5316
+ console.log('ConfigurationsComponent: Created', createdTableConfigs.length, 'TableConfigs');
5317
+ console.log('ConfigurationsComponent: Created', createdFormViews.length, 'FormViews');
5318
+ console.log('ConfigurationsComponent: Created', createdFormViewFields.length, 'FormViewFields');
5319
+ this.snackBar.open(`Created ${totalCreated} configuration records: ${createdTableConfigs.length} TableConfigs, ${createdFormViews.length} FormViews, ${createdFormViewFields.length} FormViewFields`, 'Dismiss', { duration: 5000 });
5320
+ }
5321
+ catch (error) {
5322
+ console.error('ConfigurationsComponent: Error in populateDefaultTableConfigs:', error);
5323
+ this.snackBar.open('Error creating default table configurations: ' + error, 'Dismiss', {
5324
+ duration: 5000
5325
+ });
5326
+ }
5327
+ }
5328
+ formatModelLabel(modelName) {
5329
+ // Convert PascalCase to readable format
5330
+ return modelName
5331
+ .replace(/([A-Z])/g, ' $1')
5332
+ .trim()
5333
+ .replace(/^./, str => str.toUpperCase());
5334
+ }
5335
+ formatFieldLabel(fieldName) {
5336
+ // Convert snake_case or camelCase to readable format
5337
+ return fieldName
5338
+ .replace(/_/g, ' ')
5339
+ .replace(/([A-Z])/g, ' $1')
5340
+ .trim()
5341
+ .replace(/^./, str => str.toUpperCase());
5342
+ }
5343
+ mapFieldType(fieldType) {
5344
+ // Map Amplify field types to form field types
5345
+ if (typeof fieldType === 'string') {
5346
+ switch (fieldType.toLowerCase()) {
5347
+ case 'string':
5348
+ case 'awsurl':
5349
+ case 'awsemail':
5350
+ case 'awsphone':
5351
+ return 'text';
5352
+ case 'int':
5353
+ case 'integer':
5354
+ case 'float':
5355
+ return 'number';
5356
+ case 'boolean':
5357
+ return 'checkbox';
5358
+ case 'awsdate':
5359
+ return 'date';
5360
+ case 'awsdatetime':
5361
+ return 'datetime-local';
5362
+ case 'awstime':
5363
+ return 'time';
5364
+ default:
5365
+ return 'text';
5366
+ }
5367
+ }
5368
+ // Handle complex types (relationships, etc.)
5369
+ if (typeof fieldType === 'object' && fieldType.model) {
5370
+ return 'select'; // Relationship fields become select dropdowns
5371
+ }
5372
+ return 'text'; // Default fallback
5373
+ }
2393
5374
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: ConfigurationsComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
2394
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.15", type: ConfigurationsComponent, isStandalone: true, selector: "snteam-configurations", inputs: { amplifyOutputs: "amplifyOutputs" }, ngImport: i0, template: "<div class=\"configurations-container\">\n <h2>System Configurations</h2>\n \n <mat-accordion multi=\"true\">\n \n <!-- Table Configs Section -->\n <mat-expansion-panel>\n <mat-expansion-panel-header>\n <mat-panel-title>\n <mat-icon>table_chart</mat-icon>\n <span class=\"panel-title\">Table Configs</span>\n </mat-panel-title>\n <mat-panel-description>\n Manage table configurations and settings\n </mat-panel-description>\n </mat-expansion-panel-header>\n \n <div class=\"panel-content\">\n @if (hasTableConfig) {\n <div class=\"section-actions\">\n <button mat-raised-button color=\"warn\" (click)=\"deleteTableConfigs()\" class=\"delete-button\">\n <mat-icon>delete</mat-icon>\n Delete All Table Configs\n </button>\n </div>\n <snteam-list-view \n [modelName]=\"'TableConfig'\"\n [useRouter]=\"true\"\n [showDeleteAction]=\"true\">\n </snteam-list-view>\n } @else {\n <mat-card class=\"error-card\">\n <mat-card-content>\n <mat-icon class=\"error-icon\">error_outline</mat-icon>\n <h3>TableConfig Model Not Found</h3>\n <p>The TableConfig model is not available in your Amplify schema. Please add it to your data model to manage table configurations.</p>\n <button mat-raised-button color=\"primary\" class=\"help-button\">\n <mat-icon>help</mat-icon>\n View Documentation\n </button>\n </mat-card-content>\n </mat-card>\n }\n </div>\n </mat-expansion-panel>\n\n <!-- Form Views Section -->\n <mat-expansion-panel>\n <mat-expansion-panel-header>\n <mat-panel-title>\n <mat-icon>dynamic_form</mat-icon>\n <span class=\"panel-title\">Form Views</span>\n </mat-panel-title>\n <mat-panel-description>\n Configure custom form layouts and field selections\n </mat-panel-description>\n </mat-expansion-panel-header>\n \n <div class=\"panel-content\">\n @if (hasFormView) {\n <div class=\"section-actions\">\n <button mat-raised-button color=\"warn\" (click)=\"deleteFormViews()\" class=\"delete-button\">\n <mat-icon>delete</mat-icon>\n Delete All Form Views\n </button>\n </div>\n <snteam-list-view \n [modelName]=\"'FormView'\"\n [useRouter]=\"true\"\n [showDeleteAction]=\"true\">\n </snteam-list-view>\n } @else {\n <mat-card class=\"error-card\">\n <mat-card-content>\n <mat-icon class=\"error-icon\">error_outline</mat-icon>\n <h3>FormView Model Not Found</h3>\n <p>The FormView model is not available in your Amplify schema. Please add it to your data model to create custom form configurations.</p>\n <button mat-raised-button color=\"primary\" class=\"help-button\">\n <mat-icon>help</mat-icon>\n View Documentation\n </button>\n </mat-card-content>\n </mat-card>\n }\n </div>\n </mat-expansion-panel>\n\n <!-- List Views Section -->\n <mat-expansion-panel>\n <mat-expansion-panel-header>\n <mat-panel-title>\n <mat-icon>view_list</mat-icon>\n <span class=\"panel-title\">List Views</span>\n </mat-panel-title>\n <mat-panel-description>\n Customize list displays and column configurations\n </mat-panel-description>\n </mat-expansion-panel-header>\n \n <div class=\"panel-content\">\n @if (hasListView) {\n <div class=\"section-actions\">\n <button mat-raised-button color=\"warn\" (click)=\"deleteListViews()\" class=\"delete-button\">\n <mat-icon>delete</mat-icon>\n Delete All List Views\n </button>\n </div>\n <snteam-list-view \n [modelName]=\"'ListView'\"\n [useRouter]=\"true\"\n [showDeleteAction]=\"true\">\n </snteam-list-view>\n } @else {\n <mat-card class=\"error-card\">\n <mat-card-content>\n <mat-icon class=\"error-icon\">error_outline</mat-icon>\n <h3>ListView Model Not Found</h3>\n <p>The ListView model is not available in your Amplify schema. Please add it to your data model to create custom list view configurations.</p>\n <button mat-raised-button color=\"primary\" class=\"help-button\">\n <mat-icon>help</mat-icon>\n View Documentation\n </button>\n </mat-card-content>\n </mat-card>\n }\n </div>\n </mat-expansion-panel>\n\n <!-- Field Configs Section -->\n <mat-expansion-panel>\n <mat-expansion-panel-header>\n <mat-panel-title>\n <mat-icon>settings</mat-icon>\n <span class=\"panel-title\">Field Configs</span>\n </mat-panel-title>\n <mat-panel-description>\n Configure field behavior, validation, and choices\n </mat-panel-description>\n </mat-expansion-panel-header>\n \n <div class=\"panel-content\">\n @if (hasFieldConfig) {\n <div class=\"section-actions\">\n <button mat-raised-button color=\"accent\" (click)=\"populateDefaultFieldConfigs()\" class=\"populate-button\">\n <mat-icon>auto_fix_high</mat-icon>\n Populate Default Field Configs\n </button>\n <button mat-raised-button color=\"warn\" (click)=\"deleteFieldConfigs()\" class=\"delete-button\">\n <mat-icon>delete</mat-icon>\n Delete All Field Configs\n </button>\n </div>\n <snteam-list-view \n [modelName]=\"'FieldConfig'\"\n [useRouter]=\"true\"\n [showDeleteAction]=\"true\">\n </snteam-list-view>\n } @else {\n <mat-card class=\"error-card\">\n <mat-card-content>\n <mat-icon class=\"error-icon\">error_outline</mat-icon>\n <h3>FieldConfig Model Not Found</h3>\n <p>The FieldConfig model is not available in your Amplify schema. Please add it to your data model to configure field behavior and validation.</p>\n <button mat-raised-button color=\"primary\" class=\"help-button\">\n <mat-icon>help</mat-icon>\n View Documentation\n </button>\n </mat-card-content>\n </mat-card>\n }\n </div>\n </mat-expansion-panel>\n\n </mat-accordion>\n</div>", styles: [".configurations-container{padding:20px;max-width:1200px;margin:0 auto}.configurations-container h2{margin-bottom:24px;color:#333;font-weight:500}.mat-expansion-panel{margin-bottom:16px;border-radius:8px;box-shadow:0 2px 4px #0000001a}.mat-expansion-panel-header{padding:16px 24px}.panel-title{margin-left:12px;font-weight:500;font-size:16px}.mat-panel-description{color:#666;font-size:14px}.panel-content{padding:16px 24px 24px;background-color:#fafafa}.error-card{text-align:center;padding:24px;background-color:#fff;border:2px dashed #ddd;border-radius:8px}.error-card mat-card-content{padding:0}.error-icon{font-size:48px;width:48px;height:48px;color:#ff9800;margin-bottom:16px}.error-card h3{margin:0 0 12px;color:#333;font-weight:500}.error-card p{color:#666;line-height:1.5;max-width:500px;margin:0 auto 20px}.help-button{margin-top:8px}.help-button mat-icon{margin-right:8px}.section-actions{margin-bottom:16px;padding-bottom:16px;border-bottom:1px solid #e0e0e0;display:flex;gap:12px;flex-wrap:wrap}.generate-button{background-color:#2196f3;color:#fff}.generate-button mat-icon{margin-right:8px}.delete-button{background-color:#f44336;color:#fff}.delete-button mat-icon{margin-right:8px}.populate-button mat-icon{margin-right:8px}@media (max-width: 768px){.configurations-container{padding:16px}.panel-content{padding:12px 16px 16px}.error-card{padding:16px}.error-icon{font-size:36px;width:36px;height:36px}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: MatExpansionModule }, { kind: "directive", type: i1$1.MatAccordion, selector: "mat-accordion", inputs: ["hideToggle", "displayMode", "togglePosition"], exportAs: ["matAccordion"] }, { kind: "component", type: i1$1.MatExpansionPanel, selector: "mat-expansion-panel", inputs: ["hideToggle", "togglePosition"], outputs: ["afterExpand", "afterCollapse"], exportAs: ["matExpansionPanel"] }, { kind: "component", type: i1$1.MatExpansionPanelHeader, selector: "mat-expansion-panel-header", inputs: ["expandedHeight", "collapsedHeight", "tabIndex"] }, { kind: "directive", type: i1$1.MatExpansionPanelTitle, selector: "mat-panel-title" }, { kind: "directive", type: i1$1.MatExpansionPanelDescription, selector: "mat-panel-description" }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i4$1.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "ngmodule", type: MatCardModule }, { kind: "component", type: i3$4.MatCard, selector: "mat-card", inputs: ["appearance"], exportAs: ["matCard"] }, { kind: "directive", type: i3$4.MatCardContent, selector: "mat-card-content" }, { kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i3$3.MatButton, selector: " button[matButton], a[matButton], button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button], a[mat-button], a[mat-raised-button], a[mat-flat-button], a[mat-stroked-button] ", inputs: ["matButton"], exportAs: ["matButton", "matAnchor"] }, { kind: "ngmodule", type: MatSnackBarModule }, { kind: "component", type: ListViewComponent, selector: "snteam-list-view", inputs: ["modelName", "customItemTemplate", "hideNewButton", "title", "useRouter", "showRowActions", "showDeleteAction", "customRowActions"], outputs: ["itemClick", "newClick", "itemsLoaded", "itemDeleted"] }] });
5375
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.15", type: ConfigurationsComponent, isStandalone: true, selector: "snteam-configurations", inputs: { amplifyOutputs: "amplifyOutputs" }, ngImport: i0, template: "<div class=\"configurations-container\">\n <h2>System Configurations</h2>\n \n <mat-accordion multi=\"true\">\n \n <!-- Table Configs Section -->\n <mat-expansion-panel>\n <mat-expansion-panel-header>\n <mat-panel-title>\n <mat-icon>table_chart</mat-icon>\n <span class=\"panel-title\">Table Configs</span>\n </mat-panel-title>\n <mat-panel-description>\n Manage table configurations and settings\n </mat-panel-description>\n </mat-expansion-panel-header>\n \n <div class=\"panel-content\">\n @if (hasTableConfig) {\n <div class=\"section-actions\">\n <button mat-raised-button color=\"accent\" (click)=\"populateDefaultTableConfigs()\" class=\"populate-button\">\n <mat-icon>auto_fix_high</mat-icon>\n Populate Default Table Configs\n </button>\n <button mat-raised-button color=\"warn\" (click)=\"deleteTableConfigs()\" class=\"delete-button\">\n <mat-icon>delete</mat-icon>\n Delete All Table Configs\n </button>\n </div>\n <snteam-list-view \n [modelName]=\"'TableConfig'\"\n [useRouter]=\"true\"\n [showDeleteAction]=\"true\">\n </snteam-list-view>\n } @else {\n <mat-card class=\"error-card\">\n <mat-card-content>\n <mat-icon class=\"error-icon\">error_outline</mat-icon>\n <h3>TableConfig Model Not Found</h3>\n <p>The TableConfig model is not available in your Amplify schema. Please add it to your data model to manage table configurations.</p>\n <button mat-raised-button color=\"primary\" class=\"help-button\">\n <mat-icon>help</mat-icon>\n View Documentation\n </button>\n </mat-card-content>\n </mat-card>\n }\n </div>\n </mat-expansion-panel>\n\n <!-- Form Views Section -->\n <mat-expansion-panel>\n <mat-expansion-panel-header>\n <mat-panel-title>\n <mat-icon>dynamic_form</mat-icon>\n <span class=\"panel-title\">Form Views</span>\n </mat-panel-title>\n <mat-panel-description>\n Configure custom form layouts and field selections\n </mat-panel-description>\n </mat-expansion-panel-header>\n \n <div class=\"panel-content\">\n @if (hasFormView) {\n <div class=\"section-actions\">\n <button mat-raised-button color=\"warn\" (click)=\"deleteFormViews()\" class=\"delete-button\">\n <mat-icon>delete</mat-icon>\n Delete All Form Views\n </button>\n </div>\n <snteam-list-view \n [modelName]=\"'FormView'\"\n [useRouter]=\"true\"\n [showDeleteAction]=\"true\">\n </snteam-list-view>\n } @else {\n <mat-card class=\"error-card\">\n <mat-card-content>\n <mat-icon class=\"error-icon\">error_outline</mat-icon>\n <h3>FormView Model Not Found</h3>\n <p>The FormView model is not available in your Amplify schema. Please add it to your data model to create custom form configurations.</p>\n <button mat-raised-button color=\"primary\" class=\"help-button\">\n <mat-icon>help</mat-icon>\n View Documentation\n </button>\n </mat-card-content>\n </mat-card>\n }\n </div>\n </mat-expansion-panel>\n\n <!-- List Views Section -->\n <mat-expansion-panel>\n <mat-expansion-panel-header>\n <mat-panel-title>\n <mat-icon>view_list</mat-icon>\n <span class=\"panel-title\">List Views</span>\n </mat-panel-title>\n <mat-panel-description>\n Customize list displays and column configurations\n </mat-panel-description>\n </mat-expansion-panel-header>\n \n <div class=\"panel-content\">\n @if (hasListView) {\n <div class=\"section-actions\">\n <button mat-raised-button color=\"warn\" (click)=\"deleteListViews()\" class=\"delete-button\">\n <mat-icon>delete</mat-icon>\n Delete All List Views\n </button>\n </div>\n <snteam-list-view \n [modelName]=\"'ListView'\"\n [useRouter]=\"true\"\n [showDeleteAction]=\"true\">\n </snteam-list-view>\n } @else {\n <mat-card class=\"error-card\">\n <mat-card-content>\n <mat-icon class=\"error-icon\">error_outline</mat-icon>\n <h3>ListView Model Not Found</h3>\n <p>The ListView model is not available in your Amplify schema. Please add it to your data model to create custom list view configurations.</p>\n <button mat-raised-button color=\"primary\" class=\"help-button\">\n <mat-icon>help</mat-icon>\n View Documentation\n </button>\n </mat-card-content>\n </mat-card>\n }\n </div>\n </mat-expansion-panel>\n\n <!-- Field Configs Section -->\n <mat-expansion-panel>\n <mat-expansion-panel-header>\n <mat-panel-title>\n <mat-icon>settings</mat-icon>\n <span class=\"panel-title\">Field Configs</span>\n </mat-panel-title>\n <mat-panel-description>\n Configure field behavior, validation, and choices\n </mat-panel-description>\n </mat-expansion-panel-header>\n \n <div class=\"panel-content\">\n @if (hasFieldConfig) {\n <div class=\"section-actions\">\n <button mat-raised-button color=\"accent\" (click)=\"populateDefaultFieldConfigs()\" class=\"populate-button\">\n <mat-icon>auto_fix_high</mat-icon>\n Populate Default Field Configs\n </button>\n <button mat-raised-button color=\"warn\" (click)=\"deleteFieldConfigs()\" class=\"delete-button\">\n <mat-icon>delete</mat-icon>\n Delete All Field Configs\n </button>\n </div>\n <snteam-list-view \n [modelName]=\"'FieldConfig'\"\n [useRouter]=\"true\"\n [showDeleteAction]=\"true\">\n </snteam-list-view>\n } @else {\n <mat-card class=\"error-card\">\n <mat-card-content>\n <mat-icon class=\"error-icon\">error_outline</mat-icon>\n <h3>FieldConfig Model Not Found</h3>\n <p>The FieldConfig model is not available in your Amplify schema. Please add it to your data model to configure field behavior and validation.</p>\n <button mat-raised-button color=\"primary\" class=\"help-button\">\n <mat-icon>help</mat-icon>\n View Documentation\n </button>\n </mat-card-content>\n </mat-card>\n }\n </div>\n </mat-expansion-panel>\n\n </mat-accordion>\n</div>", styles: [".configurations-container{padding:20px;max-width:1200px;margin:0 auto}.configurations-container h2{margin-bottom:24px;color:#333;font-weight:500}.mat-expansion-panel{margin-bottom:16px;border-radius:8px;box-shadow:0 2px 4px #0000001a}.mat-expansion-panel-header{padding:16px 24px}.panel-title{margin-left:12px;font-weight:500;font-size:16px}.mat-panel-description{color:#666;font-size:14px}.panel-content{padding:16px 24px 24px;background-color:#fafafa}.error-card{text-align:center;padding:24px;background-color:#fff;border:2px dashed #ddd;border-radius:8px}.error-card mat-card-content{padding:0}.error-icon{font-size:48px;width:48px;height:48px;color:#ff9800;margin-bottom:16px}.error-card h3{margin:0 0 12px;color:#333;font-weight:500}.error-card p{color:#666;line-height:1.5;max-width:500px;margin:0 auto 20px}.help-button{margin-top:8px}.help-button mat-icon{margin-right:8px}.section-actions{margin-bottom:16px;padding-bottom:16px;border-bottom:1px solid #e0e0e0;display:flex;gap:12px;flex-wrap:wrap}.generate-button{background-color:#2196f3;color:#fff}.generate-button mat-icon{margin-right:8px}.delete-button{background-color:#f44336;color:#fff}.delete-button mat-icon{margin-right:8px}.populate-button mat-icon{margin-right:8px}@media (max-width: 768px){.configurations-container{padding:16px}.panel-content{padding:12px 16px 16px}.error-card{padding:16px}.error-icon{font-size:36px;width:36px;height:36px}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: MatExpansionModule }, { kind: "directive", type: i1$1.MatAccordion, selector: "mat-accordion", inputs: ["hideToggle", "displayMode", "togglePosition"], exportAs: ["matAccordion"] }, { kind: "component", type: i1$1.MatExpansionPanel, selector: "mat-expansion-panel", inputs: ["hideToggle", "togglePosition"], outputs: ["afterExpand", "afterCollapse"], exportAs: ["matExpansionPanel"] }, { kind: "component", type: i1$1.MatExpansionPanelHeader, selector: "mat-expansion-panel-header", inputs: ["expandedHeight", "collapsedHeight", "tabIndex"] }, { kind: "directive", type: i1$1.MatExpansionPanelTitle, selector: "mat-panel-title" }, { kind: "directive", type: i1$1.MatExpansionPanelDescription, selector: "mat-panel-description" }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i5$1.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "ngmodule", type: MatCardModule }, { kind: "component", type: i3$5.MatCard, selector: "mat-card", inputs: ["appearance"], exportAs: ["matCard"] }, { kind: "directive", type: i3$5.MatCardContent, selector: "mat-card-content" }, { kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i3$3.MatButton, selector: " button[matButton], a[matButton], button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button], a[mat-button], a[mat-raised-button], a[mat-flat-button], a[mat-stroked-button] ", inputs: ["matButton"], exportAs: ["matButton", "matAnchor"] }, { kind: "ngmodule", type: MatSnackBarModule }, { kind: "component", type: ListViewComponent, selector: "snteam-list-view", inputs: ["modelName", "customItemTemplate", "hideNewButton", "title", "useRouter", "showRowActions", "showDeleteAction", "customRowActions"], outputs: ["itemClick", "newClick", "itemsLoaded", "itemDeleted"] }] });
2395
5376
  }
2396
5377
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: ConfigurationsComponent, decorators: [{
2397
5378
  type: Component,
@@ -2403,7 +5384,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImpo
2403
5384
  MatButtonModule,
2404
5385
  MatSnackBarModule,
2405
5386
  ListViewComponent
2406
- ], template: "<div class=\"configurations-container\">\n <h2>System Configurations</h2>\n \n <mat-accordion multi=\"true\">\n \n <!-- Table Configs Section -->\n <mat-expansion-panel>\n <mat-expansion-panel-header>\n <mat-panel-title>\n <mat-icon>table_chart</mat-icon>\n <span class=\"panel-title\">Table Configs</span>\n </mat-panel-title>\n <mat-panel-description>\n Manage table configurations and settings\n </mat-panel-description>\n </mat-expansion-panel-header>\n \n <div class=\"panel-content\">\n @if (hasTableConfig) {\n <div class=\"section-actions\">\n <button mat-raised-button color=\"warn\" (click)=\"deleteTableConfigs()\" class=\"delete-button\">\n <mat-icon>delete</mat-icon>\n Delete All Table Configs\n </button>\n </div>\n <snteam-list-view \n [modelName]=\"'TableConfig'\"\n [useRouter]=\"true\"\n [showDeleteAction]=\"true\">\n </snteam-list-view>\n } @else {\n <mat-card class=\"error-card\">\n <mat-card-content>\n <mat-icon class=\"error-icon\">error_outline</mat-icon>\n <h3>TableConfig Model Not Found</h3>\n <p>The TableConfig model is not available in your Amplify schema. Please add it to your data model to manage table configurations.</p>\n <button mat-raised-button color=\"primary\" class=\"help-button\">\n <mat-icon>help</mat-icon>\n View Documentation\n </button>\n </mat-card-content>\n </mat-card>\n }\n </div>\n </mat-expansion-panel>\n\n <!-- Form Views Section -->\n <mat-expansion-panel>\n <mat-expansion-panel-header>\n <mat-panel-title>\n <mat-icon>dynamic_form</mat-icon>\n <span class=\"panel-title\">Form Views</span>\n </mat-panel-title>\n <mat-panel-description>\n Configure custom form layouts and field selections\n </mat-panel-description>\n </mat-expansion-panel-header>\n \n <div class=\"panel-content\">\n @if (hasFormView) {\n <div class=\"section-actions\">\n <button mat-raised-button color=\"warn\" (click)=\"deleteFormViews()\" class=\"delete-button\">\n <mat-icon>delete</mat-icon>\n Delete All Form Views\n </button>\n </div>\n <snteam-list-view \n [modelName]=\"'FormView'\"\n [useRouter]=\"true\"\n [showDeleteAction]=\"true\">\n </snteam-list-view>\n } @else {\n <mat-card class=\"error-card\">\n <mat-card-content>\n <mat-icon class=\"error-icon\">error_outline</mat-icon>\n <h3>FormView Model Not Found</h3>\n <p>The FormView model is not available in your Amplify schema. Please add it to your data model to create custom form configurations.</p>\n <button mat-raised-button color=\"primary\" class=\"help-button\">\n <mat-icon>help</mat-icon>\n View Documentation\n </button>\n </mat-card-content>\n </mat-card>\n }\n </div>\n </mat-expansion-panel>\n\n <!-- List Views Section -->\n <mat-expansion-panel>\n <mat-expansion-panel-header>\n <mat-panel-title>\n <mat-icon>view_list</mat-icon>\n <span class=\"panel-title\">List Views</span>\n </mat-panel-title>\n <mat-panel-description>\n Customize list displays and column configurations\n </mat-panel-description>\n </mat-expansion-panel-header>\n \n <div class=\"panel-content\">\n @if (hasListView) {\n <div class=\"section-actions\">\n <button mat-raised-button color=\"warn\" (click)=\"deleteListViews()\" class=\"delete-button\">\n <mat-icon>delete</mat-icon>\n Delete All List Views\n </button>\n </div>\n <snteam-list-view \n [modelName]=\"'ListView'\"\n [useRouter]=\"true\"\n [showDeleteAction]=\"true\">\n </snteam-list-view>\n } @else {\n <mat-card class=\"error-card\">\n <mat-card-content>\n <mat-icon class=\"error-icon\">error_outline</mat-icon>\n <h3>ListView Model Not Found</h3>\n <p>The ListView model is not available in your Amplify schema. Please add it to your data model to create custom list view configurations.</p>\n <button mat-raised-button color=\"primary\" class=\"help-button\">\n <mat-icon>help</mat-icon>\n View Documentation\n </button>\n </mat-card-content>\n </mat-card>\n }\n </div>\n </mat-expansion-panel>\n\n <!-- Field Configs Section -->\n <mat-expansion-panel>\n <mat-expansion-panel-header>\n <mat-panel-title>\n <mat-icon>settings</mat-icon>\n <span class=\"panel-title\">Field Configs</span>\n </mat-panel-title>\n <mat-panel-description>\n Configure field behavior, validation, and choices\n </mat-panel-description>\n </mat-expansion-panel-header>\n \n <div class=\"panel-content\">\n @if (hasFieldConfig) {\n <div class=\"section-actions\">\n <button mat-raised-button color=\"accent\" (click)=\"populateDefaultFieldConfigs()\" class=\"populate-button\">\n <mat-icon>auto_fix_high</mat-icon>\n Populate Default Field Configs\n </button>\n <button mat-raised-button color=\"warn\" (click)=\"deleteFieldConfigs()\" class=\"delete-button\">\n <mat-icon>delete</mat-icon>\n Delete All Field Configs\n </button>\n </div>\n <snteam-list-view \n [modelName]=\"'FieldConfig'\"\n [useRouter]=\"true\"\n [showDeleteAction]=\"true\">\n </snteam-list-view>\n } @else {\n <mat-card class=\"error-card\">\n <mat-card-content>\n <mat-icon class=\"error-icon\">error_outline</mat-icon>\n <h3>FieldConfig Model Not Found</h3>\n <p>The FieldConfig model is not available in your Amplify schema. Please add it to your data model to configure field behavior and validation.</p>\n <button mat-raised-button color=\"primary\" class=\"help-button\">\n <mat-icon>help</mat-icon>\n View Documentation\n </button>\n </mat-card-content>\n </mat-card>\n }\n </div>\n </mat-expansion-panel>\n\n </mat-accordion>\n</div>", styles: [".configurations-container{padding:20px;max-width:1200px;margin:0 auto}.configurations-container h2{margin-bottom:24px;color:#333;font-weight:500}.mat-expansion-panel{margin-bottom:16px;border-radius:8px;box-shadow:0 2px 4px #0000001a}.mat-expansion-panel-header{padding:16px 24px}.panel-title{margin-left:12px;font-weight:500;font-size:16px}.mat-panel-description{color:#666;font-size:14px}.panel-content{padding:16px 24px 24px;background-color:#fafafa}.error-card{text-align:center;padding:24px;background-color:#fff;border:2px dashed #ddd;border-radius:8px}.error-card mat-card-content{padding:0}.error-icon{font-size:48px;width:48px;height:48px;color:#ff9800;margin-bottom:16px}.error-card h3{margin:0 0 12px;color:#333;font-weight:500}.error-card p{color:#666;line-height:1.5;max-width:500px;margin:0 auto 20px}.help-button{margin-top:8px}.help-button mat-icon{margin-right:8px}.section-actions{margin-bottom:16px;padding-bottom:16px;border-bottom:1px solid #e0e0e0;display:flex;gap:12px;flex-wrap:wrap}.generate-button{background-color:#2196f3;color:#fff}.generate-button mat-icon{margin-right:8px}.delete-button{background-color:#f44336;color:#fff}.delete-button mat-icon{margin-right:8px}.populate-button mat-icon{margin-right:8px}@media (max-width: 768px){.configurations-container{padding:16px}.panel-content{padding:12px 16px 16px}.error-card{padding:16px}.error-icon{font-size:36px;width:36px;height:36px}}\n"] }]
5387
+ ], template: "<div class=\"configurations-container\">\n <h2>System Configurations</h2>\n \n <mat-accordion multi=\"true\">\n \n <!-- Table Configs Section -->\n <mat-expansion-panel>\n <mat-expansion-panel-header>\n <mat-panel-title>\n <mat-icon>table_chart</mat-icon>\n <span class=\"panel-title\">Table Configs</span>\n </mat-panel-title>\n <mat-panel-description>\n Manage table configurations and settings\n </mat-panel-description>\n </mat-expansion-panel-header>\n \n <div class=\"panel-content\">\n @if (hasTableConfig) {\n <div class=\"section-actions\">\n <button mat-raised-button color=\"accent\" (click)=\"populateDefaultTableConfigs()\" class=\"populate-button\">\n <mat-icon>auto_fix_high</mat-icon>\n Populate Default Table Configs\n </button>\n <button mat-raised-button color=\"warn\" (click)=\"deleteTableConfigs()\" class=\"delete-button\">\n <mat-icon>delete</mat-icon>\n Delete All Table Configs\n </button>\n </div>\n <snteam-list-view \n [modelName]=\"'TableConfig'\"\n [useRouter]=\"true\"\n [showDeleteAction]=\"true\">\n </snteam-list-view>\n } @else {\n <mat-card class=\"error-card\">\n <mat-card-content>\n <mat-icon class=\"error-icon\">error_outline</mat-icon>\n <h3>TableConfig Model Not Found</h3>\n <p>The TableConfig model is not available in your Amplify schema. Please add it to your data model to manage table configurations.</p>\n <button mat-raised-button color=\"primary\" class=\"help-button\">\n <mat-icon>help</mat-icon>\n View Documentation\n </button>\n </mat-card-content>\n </mat-card>\n }\n </div>\n </mat-expansion-panel>\n\n <!-- Form Views Section -->\n <mat-expansion-panel>\n <mat-expansion-panel-header>\n <mat-panel-title>\n <mat-icon>dynamic_form</mat-icon>\n <span class=\"panel-title\">Form Views</span>\n </mat-panel-title>\n <mat-panel-description>\n Configure custom form layouts and field selections\n </mat-panel-description>\n </mat-expansion-panel-header>\n \n <div class=\"panel-content\">\n @if (hasFormView) {\n <div class=\"section-actions\">\n <button mat-raised-button color=\"warn\" (click)=\"deleteFormViews()\" class=\"delete-button\">\n <mat-icon>delete</mat-icon>\n Delete All Form Views\n </button>\n </div>\n <snteam-list-view \n [modelName]=\"'FormView'\"\n [useRouter]=\"true\"\n [showDeleteAction]=\"true\">\n </snteam-list-view>\n } @else {\n <mat-card class=\"error-card\">\n <mat-card-content>\n <mat-icon class=\"error-icon\">error_outline</mat-icon>\n <h3>FormView Model Not Found</h3>\n <p>The FormView model is not available in your Amplify schema. Please add it to your data model to create custom form configurations.</p>\n <button mat-raised-button color=\"primary\" class=\"help-button\">\n <mat-icon>help</mat-icon>\n View Documentation\n </button>\n </mat-card-content>\n </mat-card>\n }\n </div>\n </mat-expansion-panel>\n\n <!-- List Views Section -->\n <mat-expansion-panel>\n <mat-expansion-panel-header>\n <mat-panel-title>\n <mat-icon>view_list</mat-icon>\n <span class=\"panel-title\">List Views</span>\n </mat-panel-title>\n <mat-panel-description>\n Customize list displays and column configurations\n </mat-panel-description>\n </mat-expansion-panel-header>\n \n <div class=\"panel-content\">\n @if (hasListView) {\n <div class=\"section-actions\">\n <button mat-raised-button color=\"warn\" (click)=\"deleteListViews()\" class=\"delete-button\">\n <mat-icon>delete</mat-icon>\n Delete All List Views\n </button>\n </div>\n <snteam-list-view \n [modelName]=\"'ListView'\"\n [useRouter]=\"true\"\n [showDeleteAction]=\"true\">\n </snteam-list-view>\n } @else {\n <mat-card class=\"error-card\">\n <mat-card-content>\n <mat-icon class=\"error-icon\">error_outline</mat-icon>\n <h3>ListView Model Not Found</h3>\n <p>The ListView model is not available in your Amplify schema. Please add it to your data model to create custom list view configurations.</p>\n <button mat-raised-button color=\"primary\" class=\"help-button\">\n <mat-icon>help</mat-icon>\n View Documentation\n </button>\n </mat-card-content>\n </mat-card>\n }\n </div>\n </mat-expansion-panel>\n\n <!-- Field Configs Section -->\n <mat-expansion-panel>\n <mat-expansion-panel-header>\n <mat-panel-title>\n <mat-icon>settings</mat-icon>\n <span class=\"panel-title\">Field Configs</span>\n </mat-panel-title>\n <mat-panel-description>\n Configure field behavior, validation, and choices\n </mat-panel-description>\n </mat-expansion-panel-header>\n \n <div class=\"panel-content\">\n @if (hasFieldConfig) {\n <div class=\"section-actions\">\n <button mat-raised-button color=\"accent\" (click)=\"populateDefaultFieldConfigs()\" class=\"populate-button\">\n <mat-icon>auto_fix_high</mat-icon>\n Populate Default Field Configs\n </button>\n <button mat-raised-button color=\"warn\" (click)=\"deleteFieldConfigs()\" class=\"delete-button\">\n <mat-icon>delete</mat-icon>\n Delete All Field Configs\n </button>\n </div>\n <snteam-list-view \n [modelName]=\"'FieldConfig'\"\n [useRouter]=\"true\"\n [showDeleteAction]=\"true\">\n </snteam-list-view>\n } @else {\n <mat-card class=\"error-card\">\n <mat-card-content>\n <mat-icon class=\"error-icon\">error_outline</mat-icon>\n <h3>FieldConfig Model Not Found</h3>\n <p>The FieldConfig model is not available in your Amplify schema. Please add it to your data model to configure field behavior and validation.</p>\n <button mat-raised-button color=\"primary\" class=\"help-button\">\n <mat-icon>help</mat-icon>\n View Documentation\n </button>\n </mat-card-content>\n </mat-card>\n }\n </div>\n </mat-expansion-panel>\n\n </mat-accordion>\n</div>", styles: [".configurations-container{padding:20px;max-width:1200px;margin:0 auto}.configurations-container h2{margin-bottom:24px;color:#333;font-weight:500}.mat-expansion-panel{margin-bottom:16px;border-radius:8px;box-shadow:0 2px 4px #0000001a}.mat-expansion-panel-header{padding:16px 24px}.panel-title{margin-left:12px;font-weight:500;font-size:16px}.mat-panel-description{color:#666;font-size:14px}.panel-content{padding:16px 24px 24px;background-color:#fafafa}.error-card{text-align:center;padding:24px;background-color:#fff;border:2px dashed #ddd;border-radius:8px}.error-card mat-card-content{padding:0}.error-icon{font-size:48px;width:48px;height:48px;color:#ff9800;margin-bottom:16px}.error-card h3{margin:0 0 12px;color:#333;font-weight:500}.error-card p{color:#666;line-height:1.5;max-width:500px;margin:0 auto 20px}.help-button{margin-top:8px}.help-button mat-icon{margin-right:8px}.section-actions{margin-bottom:16px;padding-bottom:16px;border-bottom:1px solid #e0e0e0;display:flex;gap:12px;flex-wrap:wrap}.generate-button{background-color:#2196f3;color:#fff}.generate-button mat-icon{margin-right:8px}.delete-button{background-color:#f44336;color:#fff}.delete-button mat-icon{margin-right:8px}.populate-button mat-icon{margin-right:8px}@media (max-width: 768px){.configurations-container{padding:16px}.panel-content{padding:12px 16px 16px}.error-card{padding:16px}.error-icon{font-size:36px;width:36px;height:36px}}\n"] }]
2407
5388
  }], propDecorators: { amplifyOutputs: [{
2408
5389
  type: Input
2409
5390
  }] } });
@@ -2417,5 +5398,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImpo
2417
5398
  * Generated bundle index. Do not edit.
2418
5399
  */
2419
5400
 
2420
- export { AddRelationshipDialogComponent, AmplifyAngularCore, AmplifyFormBuilderService, AmplifyModelService, AsyncDropdownQuestion, ConfigurationsComponent, DatePickerQuestion, DateTimePickerQuestion, DropdownQuestion, DynamicFormComponent, DynamicFormGroupComponent, DynamicFormQuestionComponent, DynamicNestedFormQuestionComponent, DynamicRelationshipBuilderComponent, EmailQuestion, FormGroupQuestion, ListViewComponent, MyErrorStateMatcher, NumberQuestion, PhoneQuestion, QuestionBase, QuestionControlService, RecordRelationshipsComponent, SliderQuestion, TextboxQuestion, TimePickerQuestion, ValToTitlePipe, phoneNumberValidator };
5401
+ export { AddRelationshipDialogComponent, AmplifyAngularCore, AmplifyFormBuilderService, AmplifyModelService, AsyncDropdownQuestion, ConfigurationAnalyzerService, ConfigurationsComponent, DatePickerQuestion, DateTimePickerQuestion, DropdownQuestion, DynamicFormComponent, DynamicFormGroupComponent, DynamicFormQuestionComponent, DynamicNestedFormQuestionComponent, DynamicRelationshipBuilderComponent, EmailQuestion, FieldClassification, FormGroupQuestion, ListViewComponent, MyErrorStateMatcher, NumberQuestion, PhoneQuestion, QuestionBase, QuestionControlService, RecordRelationshipsComponent, SchemaIntrospectorService, SelectionSetErrorType, SelectionSetGeneratorService, SelectionSetPattern, SliderQuestion, TextboxQuestion, TimePickerQuestion, ValToTitlePipe, phoneNumberValidator };
2421
5402
  //# sourceMappingURL=snteam-amplify-angular-core.mjs.map