@snteam/amplify-angular-core 1.0.36 → 1.0.38

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,2084 @@ 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 itself (this is important for validation)
651
+ selectors.push(fieldName);
652
+ // Add the relationship field with its ID
653
+ selectors.push(`${fieldName}.id`);
654
+ // Add common display fields for the relationship
655
+ for (const displayField of this.COMMON_DISPLAY_FIELDS) {
656
+ selectors.push(`${fieldName}.${displayField}`);
657
+ }
658
+ return selectors;
659
+ }
660
+ /**
661
+ * Parse and validate nested relationship paths
662
+ *
663
+ * Handles complex relationship configurations like "formView.user.profile"
664
+ * and validates for circular references
665
+ */
666
+ parseNestedRelationshipPath(fieldName, maxDepth = 3) {
667
+ if (!fieldName) {
668
+ return [];
669
+ }
670
+ // Split the field name by dots to handle nested paths
671
+ const pathSegments = fieldName.split('.');
672
+ // Limit nesting depth to prevent overly complex queries
673
+ if (pathSegments.length > maxDepth) {
674
+ console.warn(`ConfigurationAnalyzer: Nested relationship path "${fieldName}" exceeds maximum depth of ${maxDepth}. Truncating.`);
675
+ pathSegments.splice(maxDepth);
676
+ }
677
+ // Check for circular references (same segment appearing multiple times)
678
+ const uniqueSegments = new Set(pathSegments);
679
+ if (uniqueSegments.size !== pathSegments.length) {
680
+ console.warn(`ConfigurationAnalyzer: Circular reference detected in path "${fieldName}". Using only unique segments.`);
681
+ return Array.from(uniqueSegments);
682
+ }
683
+ return pathSegments;
684
+ }
685
+ /**
686
+ * Generate field selectors for nested relationship paths
687
+ *
688
+ * Creates GraphQL selectors that can handle nested relationships
689
+ * like "formView.user.profile.name"
690
+ */
691
+ generateNestedFieldSelectors(fieldName, targetModel) {
692
+ const pathSegments = this.parseNestedRelationshipPath(fieldName);
693
+ const selectors = [];
694
+ // Always include the base record ID
695
+ selectors.push('id');
696
+ if (pathSegments.length === 0) {
697
+ return selectors;
698
+ }
699
+ // For single-level relationships, use the existing logic
700
+ if (pathSegments.length === 1) {
701
+ const camelCaseField = this.toCamelCase(pathSegments[0]);
702
+ selectors.push(`${camelCaseField}.id`);
703
+ // Add common display fields
704
+ for (const displayField of this.COMMON_DISPLAY_FIELDS) {
705
+ selectors.push(`${camelCaseField}.${displayField}`);
706
+ }
707
+ return selectors;
708
+ }
709
+ // For nested relationships, build the path progressively
710
+ let currentPath = '';
711
+ for (let i = 0; i < pathSegments.length; i++) {
712
+ const segment = this.toCamelCase(pathSegments[i]);
713
+ if (i === 0) {
714
+ currentPath = segment;
715
+ }
716
+ else {
717
+ currentPath += `.${segment}`;
718
+ }
719
+ // Add ID for each level
720
+ selectors.push(`${currentPath}.id`);
721
+ // Add display fields only for the final level to avoid overly complex queries
722
+ if (i === pathSegments.length - 1) {
723
+ for (const displayField of this.COMMON_DISPLAY_FIELDS) {
724
+ selectors.push(`${currentPath}.${displayField}`);
725
+ }
726
+ }
727
+ }
728
+ return selectors;
729
+ }
730
+ /**
731
+ * Enhanced field selector determination that supports nested paths and handles different relationship types
732
+ */
733
+ determineFieldSelectorsEnhanced(targetModel, fieldName) {
734
+ if (!targetModel) {
735
+ return [];
736
+ }
737
+ // If no specific field name provided, use the target model name
738
+ const effectiveFieldName = fieldName || this.toCamelCase(targetModel);
739
+ // Check if this is a nested relationship path
740
+ if (effectiveFieldName.includes('.')) {
741
+ return this.generateNestedFieldSelectors(effectiveFieldName, targetModel);
742
+ }
743
+ // Generate selectors for the relationship
744
+ const selectors = [];
745
+ // Always include the base record ID
746
+ selectors.push('id');
747
+ // For the relationship field, we need to be more flexible about how we include it
748
+ // Some relationships might need the field itself, others might need nested access
749
+ // Strategy 1: Include the relationship field directly
750
+ selectors.push(effectiveFieldName);
751
+ // Strategy 2: Include nested access to common fields
752
+ selectors.push(`${effectiveFieldName}.id`);
753
+ // Add common display fields for the relationship
754
+ for (const displayField of this.COMMON_DISPLAY_FIELDS) {
755
+ selectors.push(`${effectiveFieldName}.${displayField}`);
756
+ }
757
+ // Strategy 3: For some relationship types, we might need to include additional metadata
758
+ // Add some common relationship metadata fields
759
+ const metadataFields = ['createdAt', 'updatedAt'];
760
+ for (const metadataField of metadataFields) {
761
+ selectors.push(`${effectiveFieldName}.${metadataField}`);
762
+ }
763
+ console.log(`ConfigurationAnalyzer: Generated ${selectors.length} field selectors for ${targetModel} with field ${effectiveFieldName}:`, selectors);
764
+ return selectors;
765
+ }
766
+ /**
767
+ * Convert a string to PascalCase (first letter uppercase, rest camelCase)
768
+ */
769
+ toPascalCase(str) {
770
+ if (!str)
771
+ return '';
772
+ // Handle camelCase to PascalCase
773
+ return str.charAt(0).toUpperCase() + str.slice(1);
774
+ }
775
+ /**
776
+ * Convert a string to camelCase (first letter lowercase)
777
+ */
778
+ toCamelCase(str) {
779
+ if (!str)
780
+ return '';
781
+ // Handle PascalCase to camelCase
782
+ return str.charAt(0).toLowerCase() + str.slice(1);
783
+ }
784
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: ConfigurationAnalyzerService, deps: [{ token: ErrorHandlerService }], target: i0.ɵɵFactoryTarget.Injectable });
785
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: ConfigurationAnalyzerService, providedIn: 'root' });
786
+ }
787
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: ConfigurationAnalyzerService, decorators: [{
788
+ type: Injectable,
789
+ args: [{
790
+ providedIn: 'root'
791
+ }]
792
+ }], ctorParameters: () => [{ type: ErrorHandlerService }] });
793
+
794
+ /**
795
+ * Service for introspecting Amplify schema data to validate and enhance selection sets
796
+ *
797
+ * This service leverages Amplify's schema introspection data to validate field existence,
798
+ * extract model information, and provide schema-aware field selection for GraphQL queries.
799
+ */
800
+ class SchemaIntrospectorService {
801
+ errorHandler;
802
+ amplifyOutputs = null;
803
+ /**
804
+ * Common display field names that are typically used for showing model data
805
+ */
806
+ COMMON_DISPLAY_FIELDS = ['name', 'title', 'label', 'displayName', 'description'];
807
+ /**
808
+ * System fields that are typically not used for display
809
+ */
810
+ SYSTEM_FIELDS = ['id', 'createdAt', 'updatedAt', 'owner'];
811
+ constructor(errorHandler) {
812
+ this.errorHandler = errorHandler;
813
+ }
814
+ /**
815
+ * Initialize the service with Amplify outputs data
816
+ * Enhanced with error handling and validation
817
+ * This should be called by consuming applications to provide schema data
818
+ */
819
+ initializeSchema(amplifyOutputs) {
820
+ try {
821
+ if (!amplifyOutputs) {
822
+ this.errorHandler.logSchemaError('', undefined, {
823
+ method: 'initializeSchema',
824
+ error: 'amplifyOutputs is null or undefined'
825
+ });
826
+ console.warn('SchemaIntrospectorService: Cannot initialize with null or undefined amplifyOutputs');
827
+ return;
828
+ }
829
+ // Validate schema structure
830
+ if (!amplifyOutputs.data || !amplifyOutputs.data.model_introspection || !amplifyOutputs.data.model_introspection.models) {
831
+ this.errorHandler.logSchemaError('', undefined, {
832
+ method: 'initializeSchema',
833
+ error: 'Invalid schema structure',
834
+ hasData: !!amplifyOutputs.data,
835
+ hasModelIntrospection: !!(amplifyOutputs.data?.model_introspection),
836
+ hasModels: !!(amplifyOutputs.data?.model_introspection?.models)
837
+ });
838
+ console.warn('SchemaIntrospectorService: Invalid schema structure in amplifyOutputs');
839
+ return;
840
+ }
841
+ this.amplifyOutputs = amplifyOutputs;
842
+ const modelCount = Object.keys(amplifyOutputs.data.model_introspection.models).length;
843
+ console.log(`SchemaIntrospectorService: Successfully initialized with schema data (${modelCount} models)`);
844
+ }
845
+ catch (error) {
846
+ this.errorHandler.logSelectionSetError(SelectionSetErrorType.SCHEMA_UNAVAILABLE, `Error initializing schema: ${error instanceof Error ? error.message : String(error)}`, undefined, undefined, {
847
+ method: 'initializeSchema',
848
+ originalError: {
849
+ message: error instanceof Error ? error.message : String(error),
850
+ stack: error instanceof Error ? error.stack : undefined
851
+ }
852
+ });
853
+ console.error('SchemaIntrospectorService: Error initializing schema:', error);
854
+ }
855
+ }
856
+ /**
857
+ * Get field information for a specific model from the schema
858
+ * Enhanced with comprehensive error handling and logging
859
+ */
860
+ getModelFields(modelName) {
861
+ try {
862
+ if (!this.isSchemaAvailable()) {
863
+ this.errorHandler.logSchemaError(modelName, undefined, {
864
+ method: 'getModelFields',
865
+ error: 'Schema data not available'
866
+ });
867
+ console.warn('SchemaIntrospectorService: Schema data not available');
868
+ return [];
869
+ }
870
+ if (!modelName) {
871
+ this.errorHandler.logSchemaError('', undefined, {
872
+ method: 'getModelFields',
873
+ error: 'Model name is empty or undefined'
874
+ });
875
+ return [];
876
+ }
877
+ const models = this.amplifyOutputs.data.model_introspection.models;
878
+ const modelData = models[modelName];
879
+ if (!modelData) {
880
+ this.errorHandler.logSchemaError(modelName, undefined, {
881
+ method: 'getModelFields',
882
+ error: 'Model not found in schema',
883
+ availableModels: Object.keys(models)
884
+ });
885
+ console.warn(`SchemaIntrospectorService: Model "${modelName}" not found in schema`);
886
+ return [];
887
+ }
888
+ if (!modelData.fields) {
889
+ this.errorHandler.logSchemaError(modelName, undefined, {
890
+ method: 'getModelFields',
891
+ error: 'Model has no fields property',
892
+ modelData: Object.keys(modelData)
893
+ });
894
+ console.warn(`SchemaIntrospectorService: Model "${modelName}" has no fields`);
895
+ return [];
896
+ }
897
+ const fields = [];
898
+ const fieldValues = Object.values(modelData.fields);
899
+ for (const field of fieldValues) {
900
+ try {
901
+ const fieldData = field;
902
+ if (!fieldData.name) {
903
+ console.warn(`SchemaIntrospectorService: Field in model "${modelName}" has no name property`);
904
+ continue;
905
+ }
906
+ fields.push({
907
+ name: fieldData.name,
908
+ type: this.determineFieldType(fieldData.type),
909
+ isRequired: fieldData.isRequired || false,
910
+ isRelationship: this.isRelationshipField(fieldData)
911
+ });
912
+ }
913
+ catch (fieldError) {
914
+ console.warn(`SchemaIntrospectorService: Error processing field in model "${modelName}":`, fieldError);
915
+ // Continue processing other fields
916
+ }
917
+ }
918
+ console.log(`SchemaIntrospectorService: Retrieved ${fields.length} fields for model "${modelName}"`);
919
+ return fields;
920
+ }
921
+ catch (error) {
922
+ this.errorHandler.logSelectionSetError(SelectionSetErrorType.MODEL_NOT_FOUND, `Error getting fields for model "${modelName}": ${error instanceof Error ? error.message : String(error)}`, undefined, undefined, {
923
+ method: 'getModelFields',
924
+ modelName,
925
+ originalError: {
926
+ message: error instanceof Error ? error.message : String(error),
927
+ stack: error instanceof Error ? error.stack : undefined
928
+ }
929
+ });
930
+ console.error(`SchemaIntrospectorService: Error getting fields for model "${modelName}":`, error);
931
+ return [];
932
+ }
933
+ }
934
+ /**
935
+ * Validate that a specific field exists in a model
936
+ */
937
+ validateFieldExists(modelName, fieldName) {
938
+ if (!this.isSchemaAvailable() || !modelName || !fieldName) {
939
+ return false;
940
+ }
941
+ try {
942
+ const models = this.amplifyOutputs.data.model_introspection.models;
943
+ const modelData = models[modelName];
944
+ if (!modelData || !modelData.fields) {
945
+ return false;
946
+ }
947
+ return fieldName in modelData.fields;
948
+ }
949
+ catch (error) {
950
+ console.error(`SchemaIntrospectorService: Error validating field "${fieldName}" in model "${modelName}":`, error);
951
+ return false;
952
+ }
953
+ }
954
+ /**
955
+ * Get common display fields for a model (e.g., name, title, label)
956
+ */
957
+ getCommonDisplayFields(modelName) {
958
+ if (!this.isSchemaAvailable()) {
959
+ // Fallback: return conservative display fields when schema is unavailable
960
+ return this.getConservativeDisplayFields(modelName);
961
+ }
962
+ const modelFields = this.getModelFields(modelName);
963
+ const displayFields = [];
964
+ // Always include id for relationships
965
+ if (this.validateFieldExists(modelName, 'id')) {
966
+ displayFields.push('id');
967
+ }
968
+ // Look for common display fields in the model
969
+ for (const commonField of this.COMMON_DISPLAY_FIELDS) {
970
+ const field = modelFields.find(f => f.name === commonField);
971
+ if (field && !field.isRelationship) {
972
+ displayFields.push(commonField);
973
+ }
974
+ }
975
+ // If no common display fields found, include the first non-system, non-relationship field
976
+ if (displayFields.length <= 1) { // Only id was added
977
+ const firstDisplayField = modelFields.find(f => !this.SYSTEM_FIELDS.includes(f.name) &&
978
+ !f.isRelationship &&
979
+ f.type === 'string');
980
+ if (firstDisplayField && !displayFields.includes(firstDisplayField.name)) {
981
+ displayFields.push(firstDisplayField.name);
982
+ }
983
+ }
984
+ return displayFields;
985
+ }
986
+ /**
987
+ * Check if schema introspection data is available
988
+ */
989
+ isSchemaAvailable() {
990
+ return !!(this.amplifyOutputs &&
991
+ this.amplifyOutputs.data &&
992
+ this.amplifyOutputs.data.model_introspection &&
993
+ this.amplifyOutputs.data.model_introspection.models);
994
+ }
995
+ /**
996
+ * Get conservative display fields when schema data is unavailable
997
+ * This provides a safe fallback that works with most Amplify models
998
+ */
999
+ getConservativeDisplayFields(modelName) {
1000
+ console.warn(`SchemaIntrospectorService: Schema unavailable, using conservative display fields for model "${modelName}"`);
1001
+ // Return a conservative set of fields that are commonly available
1002
+ const conservativeFields = ['id'];
1003
+ // Add all common display field names - the GraphQL query will simply ignore non-existent fields
1004
+ conservativeFields.push(...this.COMMON_DISPLAY_FIELDS);
1005
+ return conservativeFields;
1006
+ }
1007
+ /**
1008
+ * Get model fields with fallback behavior when schema is unavailable
1009
+ */
1010
+ getModelFieldsWithFallback(modelName) {
1011
+ if (this.isSchemaAvailable()) {
1012
+ return this.getModelFields(modelName);
1013
+ }
1014
+ // Fallback: return conservative field set
1015
+ console.warn(`SchemaIntrospectorService: Schema unavailable, using conservative field set for model "${modelName}"`);
1016
+ const conservativeFields = [
1017
+ { name: 'id', type: 'id', isRequired: true, isRelationship: false }
1018
+ ];
1019
+ // Add common display fields as optional string fields
1020
+ for (const fieldName of this.COMMON_DISPLAY_FIELDS) {
1021
+ conservativeFields.push({
1022
+ name: fieldName,
1023
+ type: 'string',
1024
+ isRequired: false,
1025
+ isRelationship: false
1026
+ });
1027
+ }
1028
+ return conservativeFields;
1029
+ }
1030
+ /**
1031
+ * Validate field existence with fallback behavior
1032
+ * When schema is unavailable, assumes common fields exist
1033
+ */
1034
+ validateFieldExistsWithFallback(modelName, fieldName) {
1035
+ if (this.isSchemaAvailable()) {
1036
+ return this.validateFieldExists(modelName, fieldName);
1037
+ }
1038
+ // Fallback: assume common fields exist
1039
+ const commonFields = ['id', ...this.COMMON_DISPLAY_FIELDS];
1040
+ const fieldExists = commonFields.includes(fieldName);
1041
+ if (!fieldExists) {
1042
+ console.warn(`SchemaIntrospectorService: Schema unavailable, cannot validate field "${fieldName}" in model "${modelName}". Assuming it does not exist.`);
1043
+ }
1044
+ return fieldExists;
1045
+ }
1046
+ /**
1047
+ * Get safe field selectors that work even when schema data is unavailable
1048
+ * This method prioritizes reliability over completeness
1049
+ */
1050
+ getSafeFieldSelectors(modelName, fieldName) {
1051
+ const selectors = ['id']; // Always safe to include
1052
+ if (!fieldName) {
1053
+ return selectors;
1054
+ }
1055
+ // Add the relationship field with id (safe even if field doesn't exist)
1056
+ selectors.push(`${fieldName}.id`);
1057
+ if (this.isSchemaAvailable()) {
1058
+ // Use schema-aware field selection
1059
+ const displayFields = this.getCommonDisplayFields(modelName);
1060
+ for (const displayField of displayFields) {
1061
+ if (displayField !== 'id') { // Already added
1062
+ selectors.push(`${fieldName}.${displayField}`);
1063
+ }
1064
+ }
1065
+ }
1066
+ else {
1067
+ // Use conservative field selection
1068
+ const conservativeFields = this.getConservativeDisplayFields(modelName);
1069
+ for (const conservativeField of conservativeFields) {
1070
+ if (conservativeField !== 'id') { // Already added
1071
+ selectors.push(`${fieldName}.${conservativeField}`);
1072
+ }
1073
+ }
1074
+ }
1075
+ return selectors;
1076
+ }
1077
+ /**
1078
+ * Get schema status information for debugging
1079
+ */
1080
+ getSchemaStatus() {
1081
+ const status = {
1082
+ isAvailable: this.isSchemaAvailable(),
1083
+ modelCount: 0,
1084
+ availableModels: [],
1085
+ lastError: undefined
1086
+ };
1087
+ if (status.isAvailable) {
1088
+ try {
1089
+ status.availableModels = this.getAvailableModels();
1090
+ status.modelCount = status.availableModels.length;
1091
+ }
1092
+ catch (error) {
1093
+ status.lastError = error instanceof Error ? error.message : String(error);
1094
+ }
1095
+ }
1096
+ else {
1097
+ status.lastError = 'Schema data not available or invalid structure';
1098
+ }
1099
+ return status;
1100
+ }
1101
+ /**
1102
+ * Get all available model names from the schema
1103
+ */
1104
+ getAvailableModels() {
1105
+ if (!this.isSchemaAvailable()) {
1106
+ return [];
1107
+ }
1108
+ try {
1109
+ return Object.keys(this.amplifyOutputs.data.model_introspection.models);
1110
+ }
1111
+ catch (error) {
1112
+ console.error('SchemaIntrospectorService: Error getting available models:', error);
1113
+ return [];
1114
+ }
1115
+ }
1116
+ /**
1117
+ * Get detailed information about a specific model
1118
+ */
1119
+ getModelInfo(modelName) {
1120
+ if (!this.isSchemaAvailable() || !modelName) {
1121
+ return null;
1122
+ }
1123
+ try {
1124
+ const models = this.amplifyOutputs.data.model_introspection.models;
1125
+ return models[modelName] || null;
1126
+ }
1127
+ catch (error) {
1128
+ console.error(`SchemaIntrospectorService: Error getting model info for "${modelName}":`, error);
1129
+ return null;
1130
+ }
1131
+ }
1132
+ /**
1133
+ * Determine the simplified type of a field from Amplify schema data
1134
+ */
1135
+ determineFieldType(fieldType) {
1136
+ if (typeof fieldType === 'string') {
1137
+ return fieldType.toLowerCase();
1138
+ }
1139
+ if (typeof fieldType === 'object') {
1140
+ if (fieldType.model) {
1141
+ return 'relationship';
1142
+ }
1143
+ if (fieldType.enum) {
1144
+ return 'enum';
1145
+ }
1146
+ }
1147
+ return 'unknown';
1148
+ }
1149
+ /**
1150
+ * Check if a field represents a relationship to another model
1151
+ */
1152
+ isRelationshipField(fieldData) {
1153
+ return !!(fieldData.type &&
1154
+ typeof fieldData.type === 'object' &&
1155
+ fieldData.type.model);
1156
+ }
1157
+ /**
1158
+ * Get relationship information for a field
1159
+ */
1160
+ getRelationshipInfo(modelName, fieldName) {
1161
+ if (!this.validateFieldExists(modelName, fieldName)) {
1162
+ return null;
1163
+ }
1164
+ try {
1165
+ const models = this.amplifyOutputs.data.model_introspection.models;
1166
+ const modelData = models[modelName];
1167
+ const fieldData = modelData.fields[fieldName];
1168
+ if (!this.isRelationshipField(fieldData)) {
1169
+ return null;
1170
+ }
1171
+ return {
1172
+ targetModel: fieldData.type.model,
1173
+ connectionType: fieldData.association?.connectionType,
1174
+ associatedWith: fieldData.association?.associatedWith,
1175
+ targetNames: fieldData.association?.targetNames
1176
+ };
1177
+ }
1178
+ catch (error) {
1179
+ console.error(`SchemaIntrospectorService: Error getting relationship info for "${modelName}.${fieldName}":`, error);
1180
+ return null;
1181
+ }
1182
+ }
1183
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: SchemaIntrospectorService, deps: [{ token: ErrorHandlerService }], target: i0.ɵɵFactoryTarget.Injectable });
1184
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: SchemaIntrospectorService, providedIn: 'root' });
1185
+ }
1186
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: SchemaIntrospectorService, decorators: [{
1187
+ type: Injectable,
1188
+ args: [{
1189
+ providedIn: 'root'
1190
+ }]
1191
+ }], ctorParameters: () => [{ type: ErrorHandlerService }] });
1192
+
1193
+ /**
1194
+ * Service for generating dynamic GraphQL selection sets for Amplify relationships
1195
+ *
1196
+ * This service replaces hardcoded selection set logic with dynamic generation
1197
+ * based on relationship configurations, schema introspection, and intelligent caching.
1198
+ */
1199
+ class SelectionSetGeneratorService {
1200
+ configurationAnalyzer;
1201
+ schemaIntrospector;
1202
+ errorHandler;
1203
+ /**
1204
+ * Cache for generated selection sets to improve performance
1205
+ */
1206
+ selectionSetCache = new Map();
1207
+ /**
1208
+ * Default configuration for selection set generation
1209
+ */
1210
+ DEFAULT_CONFIG = {
1211
+ pattern: SelectionSetPattern.SELECTIVE,
1212
+ maxDepth: 3,
1213
+ maxFields: 10,
1214
+ validateWithSchema: true,
1215
+ requiredFields: ['id'],
1216
+ complexityLimits: {
1217
+ maxNestingDepth: 3,
1218
+ maxFieldCount: 20,
1219
+ maxWildcardSelections: 2,
1220
+ preventCircularReferences: true,
1221
+ maxSelectorLength: 100
1222
+ }
1223
+ };
1224
+ /**
1225
+ * Default display optimization configuration
1226
+ */
1227
+ DEFAULT_DISPLAY_CONFIG = {
1228
+ enabled: true,
1229
+ commonDisplayFields: ['id', 'name', 'title', 'label', 'displayName', 'description'],
1230
+ excludeFromOptimization: ['id', 'createdAt', 'updatedAt'],
1231
+ prioritizeDisplayFields: true,
1232
+ maxNonDisplayFields: 5
1233
+ };
1234
+ constructor(configurationAnalyzer, schemaIntrospector, errorHandler) {
1235
+ this.configurationAnalyzer = configurationAnalyzer;
1236
+ this.schemaIntrospector = schemaIntrospector;
1237
+ this.errorHandler = errorHandler;
1238
+ }
1239
+ /**
1240
+ * Generate a selection set for a given relationship configuration
1241
+ * Enhanced with comprehensive error handling and logging
1242
+ */
1243
+ generateSelectionSet(config) {
1244
+ try {
1245
+ // Validate input configuration
1246
+ if (!config) {
1247
+ const error = this.errorHandler.logSelectionSetError(SelectionSetErrorType.INVALID_CONFIGURATION, 'Configuration is null or undefined', config);
1248
+ return this.handleGenerationError(error, '');
1249
+ }
1250
+ // Check cache first
1251
+ const cacheKey = this.generateCacheKey(config);
1252
+ const cached = this.getCachedSelectionSet(cacheKey);
1253
+ if (cached) {
1254
+ console.log('SelectionSetGenerator: Using cached selection set for config:', config.fieldName);
1255
+ return cached.selectionSet;
1256
+ }
1257
+ // Analyze the configuration
1258
+ const analysisResult = this.configurationAnalyzer.analyzeConfiguration(config);
1259
+ if (!analysisResult || !analysisResult.isValid) {
1260
+ const errors = analysisResult?.errors || ['Invalid configuration'];
1261
+ const error = this.errorHandler.logConfigurationError(config, errors, {
1262
+ analysisResult,
1263
+ cacheKey
1264
+ });
1265
+ return this.handleGenerationError(error, config.fieldName);
1266
+ }
1267
+ // Generate selection set based on analysis and schema availability
1268
+ const selectionSet = this.generateSelectionSetFromAnalysis(config, analysisResult);
1269
+ // Validate the generated selection set
1270
+ if (!this.validateSelectionSet(selectionSet)) {
1271
+ const error = this.errorHandler.logSelectionSetError(SelectionSetErrorType.INVALID_CONFIGURATION, 'Generated selection set failed validation', config, selectionSet, { analysisResult });
1272
+ return this.handleGenerationError(error, config.fieldName);
1273
+ }
1274
+ // Cache the result
1275
+ this.cacheSelectionSet(cacheKey, selectionSet);
1276
+ console.log('SelectionSetGenerator: Successfully generated selection set:', {
1277
+ config: config.fieldName,
1278
+ selectionSetLength: selectionSet.length,
1279
+ selectionSet
1280
+ });
1281
+ return selectionSet;
1282
+ }
1283
+ catch (error) {
1284
+ const selectionSetError = this.errorHandler.logSelectionSetError(SelectionSetErrorType.GRAPHQL_ERROR, `Unexpected error during selection set generation: ${error instanceof Error ? error.message : String(error)}`, config, undefined, {
1285
+ originalError: {
1286
+ message: error instanceof Error ? error.message : String(error),
1287
+ stack: error instanceof Error ? error.stack : undefined,
1288
+ type: typeof error
1289
+ }
1290
+ });
1291
+ return this.handleGenerationError(selectionSetError, config?.fieldName || '');
1292
+ }
1293
+ }
1294
+ /**
1295
+ * Generate a fallback selection set when primary generation fails
1296
+ * Uses progressive fallback strategies: comprehensive → selective → minimal
1297
+ * Enhanced with comprehensive error handling and logging
1298
+ */
1299
+ generateFallbackSelectionSet(fieldName) {
1300
+ try {
1301
+ if (!fieldName) {
1302
+ const error = this.errorHandler.logSelectionSetError(SelectionSetErrorType.INVALID_CONFIGURATION, 'Field name is empty or undefined for fallback generation', undefined, undefined, { fieldName });
1303
+ return this.generateMinimalFallbackSelectionSet();
1304
+ }
1305
+ console.warn(`SelectionSetGenerator: Using fallback selection set for field "${fieldName}"`);
1306
+ // Try progressive fallback strategies
1307
+ try {
1308
+ // Strategy 1: Comprehensive fallback (if schema is available)
1309
+ if (this.schemaIntrospector.isSchemaAvailable()) {
1310
+ const result = this.generateComprehensiveFallbackSelectionSet(fieldName);
1311
+ console.log(`SelectionSetGenerator: Comprehensive fallback successful for field "${fieldName}"`);
1312
+ return result;
1313
+ }
1314
+ // Strategy 2: Selective fallback (configuration-based)
1315
+ const result = this.generateSelectiveFallbackSelectionSet(fieldName);
1316
+ console.log(`SelectionSetGenerator: Selective fallback successful for field "${fieldName}"`);
1317
+ return result;
1318
+ }
1319
+ catch (error) {
1320
+ const selectionSetError = this.errorHandler.logSelectionSetError(SelectionSetErrorType.GRAPHQL_ERROR, `Error in fallback generation, using minimal fallback: ${error instanceof Error ? error.message : String(error)}`, undefined, undefined, {
1321
+ fieldName,
1322
+ originalError: {
1323
+ message: error instanceof Error ? error.message : String(error),
1324
+ stack: error instanceof Error ? error.stack : undefined
1325
+ },
1326
+ schemaAvailable: this.schemaIntrospector.isSchemaAvailable()
1327
+ });
1328
+ // Strategy 3: Minimal fallback (last resort)
1329
+ return this.generateMinimalFallbackSelectionSet(fieldName);
1330
+ }
1331
+ }
1332
+ catch (error) {
1333
+ // Absolute last resort - return basic selection set
1334
+ const selectionSetError = this.errorHandler.logSelectionSetError(SelectionSetErrorType.GRAPHQL_ERROR, `Critical error in fallback generation: ${error instanceof Error ? error.message : String(error)}`, undefined, undefined, {
1335
+ fieldName,
1336
+ criticalError: {
1337
+ message: error instanceof Error ? error.message : String(error),
1338
+ stack: error instanceof Error ? error.stack : undefined
1339
+ }
1340
+ });
1341
+ console.error('SelectionSetGenerator: Critical error in fallback generation, returning absolute minimal fallback');
1342
+ return ['id'];
1343
+ }
1344
+ }
1345
+ /**
1346
+ * Generate comprehensive fallback selection set using schema data
1347
+ * This is the first fallback strategy when schema is available
1348
+ */
1349
+ generateComprehensiveFallbackSelectionSet(fieldName) {
1350
+ console.log(`SelectionSetGenerator: Using comprehensive fallback strategy for field "${fieldName}"`);
1351
+ const selectionSet = ['id'];
1352
+ try {
1353
+ // Use schema introspector to get safe field selectors
1354
+ const safeSelectors = this.schemaIntrospector.getSafeFieldSelectors('', fieldName);
1355
+ // Add all safe selectors
1356
+ for (const selector of safeSelectors) {
1357
+ if (!selectionSet.includes(selector)) {
1358
+ selectionSet.push(selector);
1359
+ }
1360
+ }
1361
+ // Apply display optimization for fallback
1362
+ const mockConfig = {
1363
+ relationshipModelName: '',
1364
+ baseModelName: '',
1365
+ fieldName,
1366
+ associatedWith: ''
1367
+ };
1368
+ const optimizedSet = this.applyDisplayOptimization(selectionSet, mockConfig);
1369
+ // Apply complexity limits to prevent overly expensive queries
1370
+ return this.applyComplexityLimits(optimizedSet);
1371
+ }
1372
+ catch (error) {
1373
+ console.warn('SelectionSetGenerator: Comprehensive fallback failed, falling back to selective:', error);
1374
+ return this.generateSelectiveFallbackSelectionSet(fieldName);
1375
+ }
1376
+ }
1377
+ /**
1378
+ * Generate selective fallback selection set using common field patterns
1379
+ * This is the second fallback strategy when schema is unavailable
1380
+ */
1381
+ generateSelectiveFallbackSelectionSet(fieldName) {
1382
+ console.log(`SelectionSetGenerator: Using selective fallback strategy for field "${fieldName}"`);
1383
+ const selectionSet = ['id'];
1384
+ // Add the relationship field with id (always safe)
1385
+ selectionSet.push(`${fieldName}.id`);
1386
+ // Add common display fields that are likely to exist
1387
+ const commonDisplayFields = ['name', 'title', 'label', 'displayName'];
1388
+ for (const field of commonDisplayFields) {
1389
+ selectionSet.push(`${fieldName}.${field}`);
1390
+ }
1391
+ // Add some additional fields that might be useful for display
1392
+ const additionalFields = ['description', 'status', 'type'];
1393
+ for (const field of additionalFields) {
1394
+ selectionSet.push(`${fieldName}.${field}`);
1395
+ }
1396
+ return selectionSet;
1397
+ }
1398
+ /**
1399
+ * Generate minimal fallback selection set for critical error cases
1400
+ * This is the last resort fallback strategy
1401
+ */
1402
+ generateMinimalFallbackSelectionSet(fieldName) {
1403
+ console.log(`SelectionSetGenerator: Using minimal fallback strategy${fieldName ? ` for field "${fieldName}"` : ''}`);
1404
+ const selectionSet = ['id'];
1405
+ if (fieldName) {
1406
+ // Include only the most basic relationship data
1407
+ selectionSet.push(`${fieldName}.id`);
1408
+ // Add one common field that's most likely to exist
1409
+ selectionSet.push(`${fieldName}.name`);
1410
+ }
1411
+ return selectionSet;
1412
+ }
1413
+ /**
1414
+ * Generate fallback selection set with retry logic
1415
+ * Attempts multiple fallback strategies and validates each one
1416
+ * Enhanced with comprehensive error handling and recovery tracking
1417
+ */
1418
+ generateFallbackSelectionSetWithRetry(fieldName, previousErrors = []) {
1419
+ try {
1420
+ if (!fieldName) {
1421
+ const error = this.errorHandler.logSelectionSetError(SelectionSetErrorType.INVALID_CONFIGURATION, 'Field name is empty for fallback retry generation', undefined, undefined, { previousErrors });
1422
+ return ['id'];
1423
+ }
1424
+ console.log(`SelectionSetGenerator: Generating fallback with retry for field "${fieldName}", previous errors: ${previousErrors.length}`);
1425
+ const strategies = [
1426
+ () => this.generateComprehensiveFallbackSelectionSet(fieldName),
1427
+ () => this.generateSelectiveFallbackSelectionSet(fieldName),
1428
+ () => this.generateMinimalFallbackSelectionSet(fieldName)
1429
+ ];
1430
+ // Skip strategies that have already failed
1431
+ const availableStrategies = strategies.slice(previousErrors.length);
1432
+ for (let i = 0; i < availableStrategies.length; i++) {
1433
+ try {
1434
+ const strategy = availableStrategies[i];
1435
+ const selectionSet = strategy();
1436
+ // Validate the selection set
1437
+ if (this.validateSelectionSet(selectionSet)) {
1438
+ const strategyName = ['comprehensive', 'selective', 'minimal'][previousErrors.length + i];
1439
+ console.log(`SelectionSetGenerator: Successfully generated ${strategyName} fallback selection set`);
1440
+ // Log successful recovery if this was after previous failures
1441
+ if (previousErrors.length > 0) {
1442
+ this.errorHandler.logSuccessfulRecovery(this.errorHandler.logSelectionSetError(SelectionSetErrorType.GRAPHQL_ERROR, `Previous errors: ${previousErrors.join(', ')}`, undefined, undefined, { fieldName, previousErrors }), strategyName, selectionSet);
1443
+ }
1444
+ return selectionSet;
1445
+ }
1446
+ else {
1447
+ const strategyName = ['comprehensive', 'selective', 'minimal'][previousErrors.length + i];
1448
+ const validationError = `Generated selection set failed validation for ${strategyName} strategy`;
1449
+ console.warn(`SelectionSetGenerator: ${validationError}`);
1450
+ previousErrors.push(validationError);
1451
+ }
1452
+ }
1453
+ catch (error) {
1454
+ const strategyName = ['comprehensive', 'selective', 'minimal'][previousErrors.length + i];
1455
+ const errorMessage = error instanceof Error ? error.message : String(error);
1456
+ this.errorHandler.logSelectionSetError(SelectionSetErrorType.GRAPHQL_ERROR, `${strategyName} fallback strategy failed: ${errorMessage}`, undefined, undefined, {
1457
+ fieldName,
1458
+ strategyName,
1459
+ strategyIndex: previousErrors.length + i,
1460
+ originalError: {
1461
+ message: errorMessage,
1462
+ stack: error instanceof Error ? error.stack : undefined
1463
+ }
1464
+ });
1465
+ console.warn(`SelectionSetGenerator: ${strategyName} fallback strategy failed:`, error);
1466
+ previousErrors.push(errorMessage);
1467
+ }
1468
+ }
1469
+ // If all strategies fail, log the complete failure and return absolute minimal fallback
1470
+ 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'));
1471
+ console.error('SelectionSetGenerator: All fallback strategies failed, using absolute minimal fallback');
1472
+ return ['id'];
1473
+ }
1474
+ catch (error) {
1475
+ // Critical error in retry logic itself
1476
+ this.errorHandler.logSelectionSetError(SelectionSetErrorType.GRAPHQL_ERROR, `Critical error in fallback retry logic: ${error instanceof Error ? error.message : String(error)}`, undefined, undefined, {
1477
+ fieldName,
1478
+ previousErrors,
1479
+ criticalError: {
1480
+ message: error instanceof Error ? error.message : String(error),
1481
+ stack: error instanceof Error ? error.stack : undefined
1482
+ }
1483
+ });
1484
+ console.error('SelectionSetGenerator: Critical error in fallback retry logic:', error);
1485
+ return ['id'];
1486
+ }
1487
+ }
1488
+ /**
1489
+ * Handle generation errors by attempting fallback with comprehensive logging
1490
+ */
1491
+ handleGenerationError(error, fieldName) {
1492
+ try {
1493
+ const errorMessage = error instanceof Error ? error.message : String(error);
1494
+ console.warn(`SelectionSetGenerator: Handling generation error for field "${fieldName}":`, errorMessage);
1495
+ // Attempt fallback with retry logic
1496
+ const fallbackResult = this.generateFallbackSelectionSetWithRetry(fieldName, [errorMessage]);
1497
+ // Log the fallback attempt
1498
+ console.log(`SelectionSetGenerator: Fallback generated for field "${fieldName}":`, fallbackResult);
1499
+ return fallbackResult;
1500
+ }
1501
+ catch (fallbackError) {
1502
+ // Even fallback failed - return absolute minimal
1503
+ this.errorHandler.logSelectionSetError(SelectionSetErrorType.GRAPHQL_ERROR, `Both primary generation and fallback failed: ${fallbackError instanceof Error ? fallbackError.message : String(fallbackError)}`, undefined, undefined, {
1504
+ fieldName,
1505
+ originalError: error,
1506
+ fallbackError: {
1507
+ message: fallbackError instanceof Error ? fallbackError.message : String(fallbackError),
1508
+ stack: fallbackError instanceof Error ? fallbackError.stack : undefined
1509
+ }
1510
+ });
1511
+ console.error('SelectionSetGenerator: Both primary generation and fallback failed, returning minimal selection set');
1512
+ return ['id'];
1513
+ }
1514
+ }
1515
+ /**
1516
+ * Validate a selection set for basic correctness
1517
+ */
1518
+ validateSelectionSet(selectionSet) {
1519
+ if (!Array.isArray(selectionSet) || selectionSet.length === 0) {
1520
+ return false;
1521
+ }
1522
+ // Must include 'id' field
1523
+ if (!selectionSet.includes('id')) {
1524
+ return false;
1525
+ }
1526
+ // Check for obviously invalid selectors
1527
+ for (const selector of selectionSet) {
1528
+ if (typeof selector !== 'string' || selector.trim() === '') {
1529
+ return false;
1530
+ }
1531
+ // Check for dangerous patterns
1532
+ if (selector.includes('..') || selector.includes('*') || selector.length > 100) {
1533
+ return false;
1534
+ }
1535
+ }
1536
+ return true;
1537
+ }
1538
+ /**
1539
+ * Clear the internal cache of generated selection sets
1540
+ */
1541
+ clearCache() {
1542
+ this.selectionSetCache.clear();
1543
+ console.log('SelectionSetGenerator: Cache cleared');
1544
+ }
1545
+ /**
1546
+ * Generate selection set from configuration analysis result
1547
+ */
1548
+ generateSelectionSetFromAnalysis(config, analysisResult) {
1549
+ const selectionSet = [];
1550
+ // Always include the base record ID
1551
+ selectionSet.push('id');
1552
+ if (this.schemaIntrospector.isSchemaAvailable()) {
1553
+ // Use schema-aware generation
1554
+ return this.generateSchemaAwareSelectionSet(config, analysisResult);
1555
+ }
1556
+ else {
1557
+ // Use configuration-based generation
1558
+ return this.generateConfigurationBasedSelectionSet(config, analysisResult);
1559
+ }
1560
+ }
1561
+ /**
1562
+ * Generate selection set using schema introspection data
1563
+ */
1564
+ generateSchemaAwareSelectionSet(config, analysisResult) {
1565
+ const selectionSet = ['id'];
1566
+ // Get safe field selectors from schema introspector
1567
+ const safeSelectors = this.schemaIntrospector.getSafeFieldSelectors(analysisResult.targetModelName, config.fieldName);
1568
+ // Add schema-validated selectors
1569
+ for (const selector of safeSelectors) {
1570
+ if (!selectionSet.includes(selector)) {
1571
+ selectionSet.push(selector);
1572
+ }
1573
+ }
1574
+ // Apply display optimization if enabled
1575
+ const optimizedSet = this.applyDisplayOptimization(selectionSet, config);
1576
+ // Apply complexity limits
1577
+ return this.applyComplexityLimits(optimizedSet);
1578
+ }
1579
+ /**
1580
+ * Generate selection set using configuration analysis only
1581
+ */
1582
+ generateConfigurationBasedSelectionSet(config, analysisResult) {
1583
+ const selectionSet = ['id'];
1584
+ console.log('SelectionSetGenerator: Generating configuration-based selection set:', {
1585
+ config: {
1586
+ relationshipModelName: config.relationshipModelName,
1587
+ fieldName: config.fieldName,
1588
+ baseModelName: config.baseModelName,
1589
+ associatedWith: config.associatedWith
1590
+ },
1591
+ analysisResult: {
1592
+ targetModelName: analysisResult.targetModelName,
1593
+ fieldSelectorsCount: analysisResult.fieldSelectors?.length || 0,
1594
+ fieldSelectors: analysisResult.fieldSelectors
1595
+ }
1596
+ });
1597
+ // Use the field selectors from configuration analysis
1598
+ for (const selector of analysisResult.fieldSelectors) {
1599
+ if (!selectionSet.includes(selector)) {
1600
+ selectionSet.push(selector);
1601
+ }
1602
+ }
1603
+ console.log('SelectionSetGenerator: Configuration-based selection set before optimization:', selectionSet);
1604
+ // Apply display optimization if enabled
1605
+ const optimizedSet = this.applyDisplayOptimization(selectionSet, config);
1606
+ console.log('SelectionSetGenerator: Selection set after display optimization:', optimizedSet);
1607
+ // Apply complexity limits
1608
+ const finalSet = this.applyComplexityLimits(optimizedSet);
1609
+ console.log('SelectionSetGenerator: Final configuration-based selection set:', finalSet);
1610
+ return finalSet;
1611
+ }
1612
+ /**
1613
+ * Apply display optimization to prioritize display-relevant fields
1614
+ */
1615
+ applyDisplayOptimization(selectionSet, config) {
1616
+ const displayConfig = this.DEFAULT_DISPLAY_CONFIG;
1617
+ if (!displayConfig.enabled) {
1618
+ return selectionSet;
1619
+ }
1620
+ // Analyze each field for display relevance
1621
+ const fieldAnalyses = selectionSet.map(selector => this.analyzeFieldForDisplay(selector, config));
1622
+ // Sort by priority (higher priority first)
1623
+ fieldAnalyses.sort((a, b) => b.priority - a.priority);
1624
+ // Build optimized selection set
1625
+ const optimizedSet = [];
1626
+ const essentialFields = [];
1627
+ const displayFields = [];
1628
+ const otherFields = [];
1629
+ // Categorize fields
1630
+ for (const analysis of fieldAnalyses) {
1631
+ switch (analysis.classification) {
1632
+ case FieldClassification.ESSENTIAL:
1633
+ essentialFields.push(analysis.selector);
1634
+ break;
1635
+ case FieldClassification.DISPLAY:
1636
+ displayFields.push(analysis.selector);
1637
+ break;
1638
+ default:
1639
+ otherFields.push(analysis.selector);
1640
+ break;
1641
+ }
1642
+ }
1643
+ // Add essential fields first (always included)
1644
+ optimizedSet.push(...essentialFields);
1645
+ // Add display fields (prioritized)
1646
+ optimizedSet.push(...displayFields);
1647
+ // Add other fields up to the limit
1648
+ const remainingSlots = Math.max(0, displayConfig.maxNonDisplayFields - (optimizedSet.length - essentialFields.length));
1649
+ optimizedSet.push(...otherFields.slice(0, remainingSlots));
1650
+ console.log(`SelectionSetGenerator: Display optimization reduced selection set from ${selectionSet.length} to ${optimizedSet.length} fields`);
1651
+ return optimizedSet;
1652
+ }
1653
+ /**
1654
+ * Analyze a field selector for display optimization
1655
+ */
1656
+ analyzeFieldForDisplay(selector, config) {
1657
+ const displayConfig = this.DEFAULT_DISPLAY_CONFIG;
1658
+ const analysis = {
1659
+ selector,
1660
+ classification: FieldClassification.UNKNOWN,
1661
+ priority: 0,
1662
+ cost: 1,
1663
+ isDisplayRelevant: false
1664
+ };
1665
+ // Essential fields (always required)
1666
+ if (displayConfig.excludeFromOptimization.some(field => selector.includes(field))) {
1667
+ analysis.classification = FieldClassification.ESSENTIAL;
1668
+ analysis.priority = 100;
1669
+ analysis.isDisplayRelevant = true;
1670
+ return analysis;
1671
+ }
1672
+ // Check if it's a common display field
1673
+ const isDisplayField = displayConfig.commonDisplayFields.some(field => selector.endsWith(`.${field}`) || selector === field);
1674
+ if (isDisplayField) {
1675
+ analysis.classification = FieldClassification.DISPLAY;
1676
+ analysis.priority = 80;
1677
+ analysis.isDisplayRelevant = true;
1678
+ }
1679
+ else if (this.isRelationshipField(selector)) {
1680
+ analysis.classification = FieldClassification.RELATIONSHIP;
1681
+ analysis.priority = 60;
1682
+ analysis.cost = 2; // Relationships are more expensive
1683
+ analysis.isDisplayRelevant = this.isDisplayRelevantRelationship(selector);
1684
+ }
1685
+ else if (this.isMetadataField(selector)) {
1686
+ analysis.classification = FieldClassification.METADATA;
1687
+ analysis.priority = 40;
1688
+ analysis.isDisplayRelevant = false;
1689
+ }
1690
+ else if (this.isSystemField(selector)) {
1691
+ analysis.classification = FieldClassification.SYSTEM;
1692
+ analysis.priority = 20;
1693
+ analysis.isDisplayRelevant = false;
1694
+ }
1695
+ else {
1696
+ analysis.classification = FieldClassification.UNKNOWN;
1697
+ analysis.priority = 30;
1698
+ analysis.isDisplayRelevant = false;
1699
+ }
1700
+ // Adjust priority based on field depth (deeper fields are less likely to be displayed)
1701
+ const depth = (selector.match(/\./g) || []).length;
1702
+ analysis.priority -= depth * 5;
1703
+ analysis.cost += depth;
1704
+ // Boost priority for fields that match the relationship field name
1705
+ if (selector.includes(config.fieldName)) {
1706
+ analysis.priority += 10;
1707
+ analysis.isDisplayRelevant = true;
1708
+ }
1709
+ return analysis;
1710
+ }
1711
+ /**
1712
+ * Check if a selector represents a relationship field
1713
+ */
1714
+ isRelationshipField(selector) {
1715
+ // Relationship fields typically have dots (nested access) or end with common relationship suffixes
1716
+ return selector.includes('.') ||
1717
+ selector.endsWith('Id') ||
1718
+ selector.endsWith('Ids') ||
1719
+ selector.endsWith('Connection');
1720
+ }
1721
+ /**
1722
+ * Check if a relationship field is likely to be displayed
1723
+ */
1724
+ isDisplayRelevantRelationship(selector) {
1725
+ // Display-relevant relationships typically access display fields of related models
1726
+ const displayFieldPatterns = ['name', 'title', 'label', 'displayName', 'description'];
1727
+ return displayFieldPatterns.some(pattern => selector.includes(pattern));
1728
+ }
1729
+ /**
1730
+ * Check if a selector represents a metadata field
1731
+ */
1732
+ isMetadataField(selector) {
1733
+ const metadataPatterns = ['createdAt', 'updatedAt', 'version', 'owner', 'lastModified'];
1734
+ return metadataPatterns.some(pattern => selector.includes(pattern));
1735
+ }
1736
+ /**
1737
+ * Check if a selector represents a system field
1738
+ */
1739
+ isSystemField(selector) {
1740
+ const systemPatterns = ['__typename', '_version', '_deleted', '_lastChangedAt'];
1741
+ return systemPatterns.some(pattern => selector.includes(pattern));
1742
+ }
1743
+ /**
1744
+ * Apply complexity limits to prevent overly expensive GraphQL queries
1745
+ * Enhanced with comprehensive error handling and logging
1746
+ */
1747
+ applyComplexityLimits(selectionSet) {
1748
+ try {
1749
+ // First validate the selection set for complexity
1750
+ const validationResult = this.validateSelectionSetComplexity(selectionSet);
1751
+ if (validationResult.isValid) {
1752
+ console.log('SelectionSetGenerator: Selection set passed complexity validation');
1753
+ return selectionSet;
1754
+ }
1755
+ // Log validation issues
1756
+ if (validationResult.errors.length > 0) {
1757
+ this.errorHandler.logComplexityError(selectionSet, validationResult.errors);
1758
+ console.warn('SelectionSetGenerator: Complexity validation errors:', validationResult.errors);
1759
+ }
1760
+ if (validationResult.warnings.length > 0) {
1761
+ console.warn('SelectionSetGenerator: Complexity validation warnings:', validationResult.warnings);
1762
+ }
1763
+ // Apply fixes to make the selection set compliant
1764
+ const fixedSet = this.fixComplexityIssues(selectionSet, validationResult);
1765
+ // Validate the fixed set
1766
+ const revalidationResult = this.validateSelectionSetComplexity(fixedSet);
1767
+ if (!revalidationResult.isValid) {
1768
+ this.errorHandler.logSelectionSetError(SelectionSetErrorType.COMPLEXITY_EXCEEDED, 'Selection set still exceeds complexity limits after fixes', undefined, fixedSet, {
1769
+ originalSelectionSet: selectionSet,
1770
+ validationResult,
1771
+ revalidationResult
1772
+ });
1773
+ // Return minimal fallback if fixes didn't work
1774
+ console.warn('SelectionSetGenerator: Complexity fixes failed, returning minimal selection set');
1775
+ return ['id'];
1776
+ }
1777
+ console.log(`SelectionSetGenerator: Applied complexity fixes, reduced from ${selectionSet.length} to ${fixedSet.length} fields`);
1778
+ return fixedSet;
1779
+ }
1780
+ catch (error) {
1781
+ this.errorHandler.logSelectionSetError(SelectionSetErrorType.COMPLEXITY_EXCEEDED, `Error applying complexity limits: ${error instanceof Error ? error.message : String(error)}`, undefined, selectionSet, {
1782
+ originalError: {
1783
+ message: error instanceof Error ? error.message : String(error),
1784
+ stack: error instanceof Error ? error.stack : undefined
1785
+ }
1786
+ });
1787
+ console.error('SelectionSetGenerator: Error applying complexity limits:', error);
1788
+ // Return original set if complexity checking fails
1789
+ return selectionSet;
1790
+ }
1791
+ }
1792
+ /**
1793
+ * Validate selection set complexity against configured limits
1794
+ */
1795
+ validateSelectionSetComplexity(selectionSet) {
1796
+ const limits = this.DEFAULT_CONFIG.complexityLimits;
1797
+ const result = {
1798
+ isValid: true,
1799
+ errors: [],
1800
+ warnings: [],
1801
+ complexityScore: 0,
1802
+ circularReferences: []
1803
+ };
1804
+ // Track field paths for circular reference detection
1805
+ const fieldPaths = new Set();
1806
+ const pathComponents = new Map();
1807
+ let wildcardCount = 0;
1808
+ let maxDepth = 0;
1809
+ for (const selector of selectionSet) {
1810
+ // Check selector length
1811
+ if (selector.length > limits.maxSelectorLength) {
1812
+ result.errors.push(`Selector "${selector}" exceeds maximum length of ${limits.maxSelectorLength}`);
1813
+ result.isValid = false;
1814
+ }
1815
+ // Check for wildcard selections
1816
+ if (selector.includes('*')) {
1817
+ wildcardCount++;
1818
+ if (wildcardCount > limits.maxWildcardSelections) {
1819
+ result.errors.push(`Too many wildcard selections (${wildcardCount}), maximum allowed: ${limits.maxWildcardSelections}`);
1820
+ result.isValid = false;
1821
+ }
1822
+ }
1823
+ // Calculate nesting depth
1824
+ const depth = (selector.match(/\./g) || []).length;
1825
+ maxDepth = Math.max(maxDepth, depth);
1826
+ if (depth > limits.maxNestingDepth) {
1827
+ result.errors.push(`Selector "${selector}" exceeds maximum nesting depth of ${limits.maxNestingDepth}`);
1828
+ result.isValid = false;
1829
+ }
1830
+ // Track field paths for circular reference detection
1831
+ if (limits.preventCircularReferences && depth > 0) {
1832
+ const components = selector.split('.');
1833
+ pathComponents.set(selector, components);
1834
+ // Check for potential circular references
1835
+ const circularRef = this.detectCircularReference(components, pathComponents);
1836
+ if (circularRef) {
1837
+ result.circularReferences.push(circularRef);
1838
+ result.warnings.push(`Potential circular reference detected: ${circularRef}`);
1839
+ }
1840
+ }
1841
+ fieldPaths.add(selector);
1842
+ }
1843
+ // Check field count limit
1844
+ if (selectionSet.length > limits.maxFieldCount) {
1845
+ result.errors.push(`Selection set has ${selectionSet.length} fields, maximum allowed: ${limits.maxFieldCount}`);
1846
+ result.isValid = false;
1847
+ }
1848
+ // Calculate complexity score
1849
+ result.complexityScore = this.calculateComplexityScore(selectionSet, maxDepth, wildcardCount);
1850
+ // Add warnings for high complexity
1851
+ if (result.complexityScore > 50) {
1852
+ result.warnings.push(`High complexity score: ${result.complexityScore}`);
1853
+ }
1854
+ return result;
1855
+ }
1856
+ /**
1857
+ * Detect circular references in field paths
1858
+ */
1859
+ detectCircularReference(components, allPaths) {
1860
+ // Simple circular reference detection: check if any component appears multiple times in the path
1861
+ const componentCounts = new Map();
1862
+ for (const component of components) {
1863
+ const count = componentCounts.get(component) || 0;
1864
+ componentCounts.set(component, count + 1);
1865
+ if (count > 0) {
1866
+ return components.join('.');
1867
+ }
1868
+ }
1869
+ return null;
1870
+ }
1871
+ /**
1872
+ * Calculate complexity score for a selection set
1873
+ */
1874
+ calculateComplexityScore(selectionSet, maxDepth, wildcardCount) {
1875
+ let score = 0;
1876
+ // Base score from field count
1877
+ score += selectionSet.length;
1878
+ // Penalty for depth
1879
+ score += maxDepth * 5;
1880
+ // Heavy penalty for wildcards
1881
+ score += wildcardCount * 10;
1882
+ // Penalty for complex selectors
1883
+ for (const selector of selectionSet) {
1884
+ if (selector.includes('*')) {
1885
+ score += 5;
1886
+ }
1887
+ if (selector.split('.').length > 2) {
1888
+ score += 3;
1889
+ }
1890
+ }
1891
+ return score;
1892
+ }
1893
+ /**
1894
+ * Fix complexity issues in a selection set
1895
+ */
1896
+ fixComplexityIssues(selectionSet, validationResult) {
1897
+ const limits = this.DEFAULT_CONFIG.complexityLimits;
1898
+ let fixedSet = [...selectionSet];
1899
+ // Remove selectors that are too long
1900
+ fixedSet = fixedSet.filter(selector => {
1901
+ if (selector.length > limits.maxSelectorLength) {
1902
+ console.warn(`SelectionSetGenerator: Removing selector "${selector}" due to excessive length`);
1903
+ return false;
1904
+ }
1905
+ return true;
1906
+ });
1907
+ // Remove selectors that are too deeply nested
1908
+ fixedSet = fixedSet.filter(selector => {
1909
+ const depth = (selector.match(/\./g) || []).length;
1910
+ if (depth > limits.maxNestingDepth) {
1911
+ console.warn(`SelectionSetGenerator: Removing selector "${selector}" due to excessive nesting depth`);
1912
+ return false;
1913
+ }
1914
+ return true;
1915
+ });
1916
+ // Limit wildcard selections
1917
+ let wildcardCount = 0;
1918
+ fixedSet = fixedSet.filter(selector => {
1919
+ if (selector.includes('*')) {
1920
+ wildcardCount++;
1921
+ if (wildcardCount > limits.maxWildcardSelections) {
1922
+ console.warn(`SelectionSetGenerator: Removing wildcard selector "${selector}" due to limit`);
1923
+ return false;
1924
+ }
1925
+ }
1926
+ return true;
1927
+ });
1928
+ // Remove circular references
1929
+ if (limits.preventCircularReferences && validationResult.circularReferences.length > 0) {
1930
+ const circularSelectors = new Set(validationResult.circularReferences);
1931
+ fixedSet = fixedSet.filter(selector => {
1932
+ if (circularSelectors.has(selector)) {
1933
+ console.warn(`SelectionSetGenerator: Removing selector "${selector}" due to circular reference`);
1934
+ return false;
1935
+ }
1936
+ return true;
1937
+ });
1938
+ }
1939
+ // Truncate to field count limit
1940
+ if (fixedSet.length > limits.maxFieldCount) {
1941
+ console.warn(`SelectionSetGenerator: Truncating selection set from ${fixedSet.length} to ${limits.maxFieldCount} fields`);
1942
+ fixedSet = fixedSet.slice(0, limits.maxFieldCount);
1943
+ }
1944
+ // Ensure required fields are still present
1945
+ for (const requiredField of this.DEFAULT_CONFIG.requiredFields) {
1946
+ if (!fixedSet.includes(requiredField)) {
1947
+ fixedSet.unshift(requiredField);
1948
+ }
1949
+ }
1950
+ return fixedSet;
1951
+ }
1952
+ /**
1953
+ * Generate a cache key for a relationship configuration
1954
+ */
1955
+ generateCacheKey(config) {
1956
+ const key = {
1957
+ relationshipModel: config.relationshipModelName,
1958
+ fieldName: config.fieldName,
1959
+ targetModel: this.configurationAnalyzer.extractTargetModelName(config),
1960
+ schemaVersion: this.getSchemaVersion()
1961
+ };
1962
+ return JSON.stringify(key);
1963
+ }
1964
+ /**
1965
+ * Get cached selection set if available
1966
+ */
1967
+ getCachedSelectionSet(cacheKey) {
1968
+ const cached = this.selectionSetCache.get(cacheKey);
1969
+ if (cached) {
1970
+ // Update access count
1971
+ cached.accessCount++;
1972
+ return cached;
1973
+ }
1974
+ return null;
1975
+ }
1976
+ /**
1977
+ * Cache a generated selection set
1978
+ */
1979
+ cacheSelectionSet(cacheKey, selectionSet) {
1980
+ const cached = {
1981
+ key: JSON.parse(cacheKey),
1982
+ selectionSet: [...selectionSet],
1983
+ createdAt: new Date(),
1984
+ accessCount: 1
1985
+ };
1986
+ this.selectionSetCache.set(cacheKey, cached);
1987
+ // Implement simple cache size limit
1988
+ if (this.selectionSetCache.size > 100) {
1989
+ this.evictOldestCacheEntries();
1990
+ }
1991
+ }
1992
+ /**
1993
+ * Evict oldest cache entries when cache gets too large
1994
+ */
1995
+ evictOldestCacheEntries() {
1996
+ const entries = Array.from(this.selectionSetCache.entries());
1997
+ entries.sort((a, b) => a[1].createdAt.getTime() - b[1].createdAt.getTime());
1998
+ // Remove oldest 20% of entries
1999
+ const toRemove = Math.floor(entries.length * 0.2);
2000
+ for (let i = 0; i < toRemove; i++) {
2001
+ this.selectionSetCache.delete(entries[i][0]);
2002
+ }
2003
+ console.log(`SelectionSetGenerator: Evicted ${toRemove} old cache entries`);
2004
+ }
2005
+ /**
2006
+ * Get schema version for cache invalidation
2007
+ */
2008
+ getSchemaVersion() {
2009
+ // In a real implementation, this could be based on schema hash or version
2010
+ // For now, we'll use schema availability as a simple version indicator
2011
+ return this.schemaIntrospector.isSchemaAvailable() ? 'schema-available' : 'schema-unavailable';
2012
+ }
2013
+ /**
2014
+ * Get cache statistics for debugging
2015
+ */
2016
+ getCacheStats() {
2017
+ const entries = Array.from(this.selectionSetCache.values());
2018
+ const totalAccesses = entries.reduce((sum, entry) => sum + entry.accessCount, 0);
2019
+ return {
2020
+ size: this.selectionSetCache.size,
2021
+ totalAccesses,
2022
+ entries: entries.map(entry => ({
2023
+ key: entry.key,
2024
+ selectionSet: [...entry.selectionSet],
2025
+ createdAt: entry.createdAt,
2026
+ accessCount: entry.accessCount
2027
+ }))
2028
+ };
2029
+ }
2030
+ /**
2031
+ * Get optimization statistics for debugging
2032
+ */
2033
+ getOptimizationStats() {
2034
+ return {
2035
+ displayOptimizationEnabled: this.DEFAULT_DISPLAY_CONFIG.enabled,
2036
+ complexityLimits: { ...this.DEFAULT_CONFIG.complexityLimits },
2037
+ displayConfig: { ...this.DEFAULT_DISPLAY_CONFIG }
2038
+ };
2039
+ }
2040
+ /**
2041
+ * Analyze a selection set for optimization opportunities
2042
+ */
2043
+ analyzeSelectionSetOptimization(selectionSet) {
2044
+ const mockConfig = {
2045
+ relationshipModelName: '',
2046
+ baseModelName: '',
2047
+ fieldName: 'mock',
2048
+ associatedWith: ''
2049
+ };
2050
+ const analyses = selectionSet.map(selector => this.analyzeFieldForDisplay(selector, mockConfig));
2051
+ const displayRelevantCount = analyses.filter(a => a.isDisplayRelevant).length;
2052
+ const complexityScore = this.calculateComplexityScore(selectionSet, Math.max(...selectionSet.map(s => (s.match(/\./g) || []).length)), selectionSet.filter(s => s.includes('*')).length);
2053
+ const opportunities = [];
2054
+ // Identify optimization opportunities
2055
+ if (displayRelevantCount < selectionSet.length * 0.5) {
2056
+ opportunities.push('Many non-display fields could be removed');
2057
+ }
2058
+ if (complexityScore > 30) {
2059
+ opportunities.push('High complexity score suggests simplification needed');
2060
+ }
2061
+ const deepFields = selectionSet.filter(s => (s.match(/\./g) || []).length > 2);
2062
+ if (deepFields.length > 0) {
2063
+ opportunities.push(`${deepFields.length} deeply nested fields could be simplified`);
2064
+ }
2065
+ const wildcardFields = selectionSet.filter(s => s.includes('*'));
2066
+ if (wildcardFields.length > 1) {
2067
+ opportunities.push(`${wildcardFields.length} wildcard selections could be made more specific`);
2068
+ }
2069
+ return {
2070
+ totalFields: selectionSet.length,
2071
+ displayRelevantFields: displayRelevantCount,
2072
+ complexityScore,
2073
+ optimizationOpportunities: opportunities
2074
+ };
2075
+ }
2076
+ /**
2077
+ * Initialize the service with schema data
2078
+ */
2079
+ initializeWithSchema(amplifyOutputs) {
2080
+ this.schemaIntrospector.initializeSchema(amplifyOutputs);
2081
+ this.clearCache(); // Clear cache when schema changes
2082
+ console.log('SelectionSetGenerator: Initialized with schema data');
2083
+ }
2084
+ 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 });
2085
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: SelectionSetGeneratorService, providedIn: 'root' });
2086
+ }
2087
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: SelectionSetGeneratorService, decorators: [{
2088
+ type: Injectable,
2089
+ args: [{
2090
+ providedIn: 'root'
2091
+ }]
2092
+ }], ctorParameters: () => [{ type: ConfigurationAnalyzerService }, { type: SchemaIntrospectorService }, { type: ErrorHandlerService }] });
2093
+
37
2094
  class AmplifyAngularCore {
38
2095
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: AmplifyAngularCore, deps: [], target: i0.ɵɵFactoryTarget.Component });
39
2096
  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 +2101,7 @@ class AmplifyAngularCore {
44
2101
  }
45
2102
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: AmplifyAngularCore, decorators: [{
46
2103
  type: Component,
47
- args: [{ selector: 'snteam-amplify-angular-core', imports: [], template: `
2104
+ args: [{ selector: 'snteam-amplify-angular-core', standalone: true, imports: [], template: `
48
2105
  <p>
49
2106
  amplify-angular-core works!
50
2107
  </p>
@@ -236,8 +2293,12 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImpo
236
2293
  }] });
237
2294
 
238
2295
  class AmplifyModelService {
2296
+ selectionSetGenerator;
2297
+ errorHandler;
239
2298
  client;
240
- constructor() {
2299
+ constructor(selectionSetGenerator, errorHandler) {
2300
+ this.selectionSetGenerator = selectionSetGenerator;
2301
+ this.errorHandler = errorHandler;
241
2302
  // Client will be initialized when Amplify is configured by the consuming application
242
2303
  }
243
2304
  /**
@@ -424,14 +2485,65 @@ class AmplifyModelService {
424
2485
  return condition;
425
2486
  }
426
2487
  }
2488
+ /**
2489
+ * Generate dynamic selection set for relationship queries
2490
+ * Replaces hardcoded selection set logic with dynamic generation
2491
+ * Enhanced with comprehensive error handling and logging
2492
+ * @param config Relationship configuration object
2493
+ * @param baseId Base record ID (for compatibility, not used in generation)
2494
+ * @returns Array of GraphQL field selectors
2495
+ */
427
2496
  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 [];
2497
+ try {
2498
+ if (!config) {
2499
+ const error = this.errorHandler.logSelectionSetError(SelectionSetErrorType.INVALID_CONFIGURATION, 'Configuration is null or undefined', config, undefined, {
2500
+ method: 'getAmplifySelectionSet',
2501
+ baseId,
2502
+ clientAvailable: !!this.client
2503
+ });
2504
+ // Return minimal fallback
2505
+ return ['id'];
2506
+ }
2507
+ console.log('AmplifyModelService: Generating dynamic selection set for config:', {
2508
+ relationshipModel: config.relationshipModelName,
2509
+ fieldName: config.fieldName,
2510
+ baseId
2511
+ });
2512
+ // Use the SelectionSetGenerator to create dynamic selection sets
2513
+ const selectionSet = this.selectionSetGenerator.generateSelectionSet(config);
2514
+ if (!selectionSet || selectionSet.length === 0) {
2515
+ const error = this.errorHandler.logSelectionSetError(SelectionSetErrorType.GRAPHQL_ERROR, 'Selection set generator returned empty or null result', config, selectionSet, {
2516
+ method: 'getAmplifySelectionSet',
2517
+ baseId,
2518
+ generatorAvailable: !!this.selectionSetGenerator
2519
+ });
2520
+ // Return minimal fallback
2521
+ return ['id'];
2522
+ }
2523
+ console.log('AmplifyModelService: Generated selection set:', {
2524
+ config: config.fieldName,
2525
+ selectionSetLength: selectionSet.length,
2526
+ selectionSet
2527
+ });
2528
+ return selectionSet;
2529
+ }
2530
+ catch (error) {
2531
+ const selectionSetError = this.errorHandler.logSelectionSetError(SelectionSetErrorType.GRAPHQL_ERROR, `Unexpected error generating selection set: ${error instanceof Error ? error.message : String(error)}`, config, undefined, {
2532
+ method: 'getAmplifySelectionSet',
2533
+ baseId,
2534
+ originalError: {
2535
+ message: error instanceof Error ? error.message : String(error),
2536
+ stack: error instanceof Error ? error.stack : undefined,
2537
+ type: typeof error
2538
+ },
2539
+ context: {
2540
+ clientAvailable: !!this.client,
2541
+ selectionSetGeneratorAvailable: !!this.selectionSetGenerator
2542
+ }
2543
+ });
2544
+ console.error('AmplifyModelService: Error generating selection set, using fallback:', error);
2545
+ // Use fallback selection set generation with retry logic
2546
+ return this.selectionSetGenerator.generateFallbackSelectionSetWithRetry(config?.fieldName || '', [error instanceof Error ? error.message : String(error)]);
435
2547
  }
436
2548
  }
437
2549
  setRelatedSub(config, baseId) {
@@ -445,32 +2557,389 @@ class AmplifyModelService {
445
2557
  return this.client.models[modelName].onCreate({ filter: filter });
446
2558
  }
447
2559
  else {
448
- console.error(`Relationship model ${modelName} not found in client`);
449
- return null;
2560
+ console.error(`Relationship model ${modelName} not found in client`);
2561
+ return null;
2562
+ }
2563
+ }
2564
+ /**
2565
+ * Get related items with enhanced error handling and retry logic
2566
+ * @param config Relationship configuration object
2567
+ * @param baseId Base record ID to filter relationships
2568
+ * @returns Observable of related items with retry logic on GraphQL errors
2569
+ */
2570
+ getRelatedItems(config, baseId) {
2571
+ if (!this.client) {
2572
+ console.error('AmplifyModelService: Client not initialized. Call initializeClient() first.');
2573
+ return null;
2574
+ }
2575
+ const filter = this.getRelationshipFilter(config, baseId);
2576
+ const modelName = config.relationshipModelName;
2577
+ if (!this.client.models[modelName]) {
2578
+ console.error(`AmplifyModelService: Relationship model ${modelName} not found in client`);
2579
+ return null;
2580
+ }
2581
+ // Attempt to get related items with progressive retry logic
2582
+ return this.getRelatedItemsWithRetry(config, baseId, filter, modelName);
2583
+ }
2584
+ /**
2585
+ * Get related items with progressive retry logic for GraphQL errors
2586
+ * Implements progressive fallback: original → comprehensive → selective → minimal
2587
+ */
2588
+ getRelatedItemsWithRetry(config, baseId, filter, modelName, retryAttempt = 0, previousErrors = []) {
2589
+ const maxRetries = 3;
2590
+ try {
2591
+ // Generate selection set based on retry attempt
2592
+ let selectionSet;
2593
+ if (retryAttempt === 0) {
2594
+ // First attempt: Use dynamic generation
2595
+ selectionSet = this.getAmplifySelectionSet(config, baseId);
2596
+ }
2597
+ else {
2598
+ // Subsequent attempts: Use progressive fallback
2599
+ console.log(`AmplifyModelService: Retry attempt ${retryAttempt} for ${modelName}, using fallback selection set`);
2600
+ selectionSet = this.selectionSetGenerator.generateFallbackSelectionSetWithRetry(config.fieldName, previousErrors);
2601
+ }
2602
+ // Log the attempt
2603
+ this.logQueryAttempt(config, selectionSet, retryAttempt, previousErrors);
2604
+ // Prepare query options
2605
+ const queryOptions = { filter: filter };
2606
+ // Only include selectionSet if it's not empty to avoid GraphQL syntax errors
2607
+ if (selectionSet && selectionSet.length > 0) {
2608
+ queryOptions.selectionSet = [...selectionSet];
2609
+ }
2610
+ // Execute the query
2611
+ const queryObservable = this.client.models[modelName].observeQuery(queryOptions);
2612
+ // Wrap the observable to handle GraphQL errors with retry logic
2613
+ return new Observable(subscriber => {
2614
+ const subscription = queryObservable.subscribe({
2615
+ next: (result) => {
2616
+ // Log successful query
2617
+ this.logQuerySuccess(config, selectionSet, retryAttempt, result);
2618
+ // Validate that relationship field data is populated
2619
+ if (this.validateRelationshipFieldPopulation(result, config)) {
2620
+ subscriber.next(result);
2621
+ }
2622
+ else {
2623
+ // Relationship field not populated, try retry if possible
2624
+ const error = new Error(`Relationship field '${config.fieldName}' not populated in query results`);
2625
+ this.handleQueryError(error, config, baseId, filter, modelName, retryAttempt, previousErrors, subscriber);
2626
+ }
2627
+ },
2628
+ error: (error) => {
2629
+ this.handleQueryError(error, config, baseId, filter, modelName, retryAttempt, previousErrors, subscriber);
2630
+ },
2631
+ complete: () => {
2632
+ subscriber.complete();
2633
+ }
2634
+ });
2635
+ // Return cleanup function
2636
+ return () => subscription.unsubscribe();
2637
+ });
2638
+ }
2639
+ catch (error) {
2640
+ console.error(`AmplifyModelService: Error in retry attempt ${retryAttempt}:`, error);
2641
+ // If we've exhausted retries, return null
2642
+ if (retryAttempt >= maxRetries) {
2643
+ this.logFinalFailure(config, previousErrors, error);
2644
+ return null;
2645
+ }
2646
+ // Try next retry level
2647
+ const newErrors = [...previousErrors, error instanceof Error ? error.message : String(error)];
2648
+ return this.getRelatedItemsWithRetry(config, baseId, filter, modelName, retryAttempt + 1, newErrors);
2649
+ }
2650
+ }
2651
+ /**
2652
+ * Handle GraphQL query errors with retry logic
2653
+ * Enhanced with comprehensive error logging and context
2654
+ */
2655
+ handleQueryError(error, config, baseId, filter, modelName, retryAttempt, previousErrors, subscriber) {
2656
+ const maxRetries = 3;
2657
+ const errorMessage = error instanceof Error ? error.message : String(error);
2658
+ // Log comprehensive error details using ErrorHandlerService
2659
+ const selectionSetError = this.errorHandler.logGraphQLError(error, config, [], // Selection set will be logged separately in retry attempts
2660
+ retryAttempt, previousErrors);
2661
+ console.error(`AmplifyModelService: GraphQL query error on attempt ${retryAttempt + 1}:`, error);
2662
+ // Check if we should retry
2663
+ if (retryAttempt < maxRetries && this.shouldRetryOnError(error)) {
2664
+ console.log(`AmplifyModelService: Retrying with simpler selection set (attempt ${retryAttempt + 1}/${maxRetries})`);
2665
+ // Add current error to the list
2666
+ const newErrors = [...previousErrors, errorMessage];
2667
+ // Attempt retry with next fallback level
2668
+ const retryObservable = this.getRelatedItemsWithRetry(config, baseId, filter, modelName, retryAttempt + 1, newErrors);
2669
+ if (retryObservable) {
2670
+ // Subscribe to retry attempt
2671
+ const retrySubscription = retryObservable.subscribe({
2672
+ next: (result) => {
2673
+ // Log successful recovery
2674
+ this.errorHandler.logSuccessfulRecovery(selectionSetError, `retry_attempt_${retryAttempt + 1}`, [] // Selection set would be determined in retry
2675
+ );
2676
+ subscriber.next(result);
2677
+ },
2678
+ error: (retryError) => subscriber.error(retryError),
2679
+ complete: () => subscriber.complete()
2680
+ });
2681
+ // Handle cleanup
2682
+ subscriber.add(() => retrySubscription.unsubscribe());
2683
+ }
2684
+ else {
2685
+ // No more retries possible
2686
+ this.errorHandler.logFailedRecovery(selectionSetError, newErrors, new Error('Retry observable creation failed'));
2687
+ subscriber.error(error);
2688
+ }
2689
+ }
2690
+ else {
2691
+ // No more retries or error is not retryable
2692
+ const finalErrors = [...previousErrors, errorMessage];
2693
+ this.errorHandler.logFailedRecovery(selectionSetError, finalErrors, error);
2694
+ console.error('AmplifyModelService: All retry attempts exhausted or error not retryable');
2695
+ subscriber.error(error);
450
2696
  }
451
2697
  }
452
2698
  /**
453
- * @param config {object} - see dynamic-relationship-builder.component getDetailsForManyToMany() for example of building config
2699
+ * Determine if an error should trigger a retry
2700
+ * Enhanced with comprehensive error pattern matching
454
2701
  */
455
- getRelatedItems(config, baseId) {
456
- if (!this.client) {
457
- console.error('AmplifyModelService: Client not initialized. Call initializeClient() first.');
458
- return null;
2702
+ shouldRetryOnError(error) {
2703
+ try {
2704
+ const errorMessage = error instanceof Error ? error.message : String(error);
2705
+ const errorString = errorMessage.toLowerCase();
2706
+ // Retry on GraphQL field errors, selection set errors, but not on auth errors
2707
+ const retryableErrors = [
2708
+ 'field',
2709
+ 'selection',
2710
+ 'cannot query',
2711
+ 'unknown field',
2712
+ 'syntax error',
2713
+ 'invalid selection set',
2714
+ 'validation error',
2715
+ 'parse error'
2716
+ ];
2717
+ const nonRetryableErrors = [
2718
+ 'unauthorized',
2719
+ 'forbidden',
2720
+ 'access denied',
2721
+ 'authentication',
2722
+ 'permission',
2723
+ 'not authorized',
2724
+ 'invalid credentials'
2725
+ ];
2726
+ // Don't retry on auth errors
2727
+ if (nonRetryableErrors.some(pattern => errorString.includes(pattern))) {
2728
+ console.log(`AmplifyModelService: Not retrying due to auth error: ${errorMessage}`);
2729
+ return false;
2730
+ }
2731
+ // Retry on field/selection errors
2732
+ const shouldRetry = retryableErrors.some(pattern => errorString.includes(pattern));
2733
+ console.log(`AmplifyModelService: Error retry decision: ${shouldRetry} for error: ${errorMessage}`);
2734
+ return shouldRetry;
2735
+ }
2736
+ catch (retryDecisionError) {
2737
+ this.errorHandler.logSelectionSetError(SelectionSetErrorType.GRAPHQL_ERROR, `Error determining retry eligibility: ${retryDecisionError instanceof Error ? retryDecisionError.message : String(retryDecisionError)}`, undefined, undefined, {
2738
+ method: 'shouldRetryOnError',
2739
+ originalError: error,
2740
+ retryDecisionError: {
2741
+ message: retryDecisionError instanceof Error ? retryDecisionError.message : String(retryDecisionError),
2742
+ stack: retryDecisionError instanceof Error ? retryDecisionError.stack : undefined
2743
+ }
2744
+ });
2745
+ // Default to not retrying if we can't determine
2746
+ return false;
459
2747
  }
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];
2748
+ }
2749
+ /**
2750
+ * Validate that relationship field data is populated in query results
2751
+ * Enhanced with comprehensive error logging and flexible validation
2752
+ */
2753
+ validateRelationshipFieldPopulation(result, config) {
2754
+ try {
2755
+ if (!result || !result.data) {
2756
+ this.errorHandler.logSelectionSetError(SelectionSetErrorType.GRAPHQL_ERROR, 'Query result is missing or has no data property', config, undefined, {
2757
+ method: 'validateRelationshipFieldPopulation',
2758
+ hasResult: !!result,
2759
+ hasData: !!(result?.data)
2760
+ });
2761
+ return false;
2762
+ }
2763
+ // Check if any records have the relationship field populated
2764
+ const records = Array.isArray(result.data) ? result.data : [result.data];
2765
+ let populatedCount = 0;
2766
+ let partiallyPopulatedCount = 0;
2767
+ for (const record of records) {
2768
+ if (!record)
2769
+ continue;
2770
+ const relationshipData = record[config.fieldName];
2771
+ // Check for fully populated relationship (object with data)
2772
+ if (relationshipData !== undefined && relationshipData !== null) {
2773
+ // If it's an object with properties, consider it populated
2774
+ if (typeof relationshipData === 'object' && Object.keys(relationshipData).length > 0) {
2775
+ populatedCount++;
2776
+ }
2777
+ // If it's a non-null primitive value, also consider it populated
2778
+ else if (typeof relationshipData !== 'object') {
2779
+ populatedCount++;
2780
+ }
2781
+ // If it's an empty object or array, consider it partially populated
2782
+ else {
2783
+ partiallyPopulatedCount++;
2784
+ }
2785
+ }
2786
+ // Also check if the record has any nested relationship data
2787
+ // This handles cases where the selection set includes nested fields like "table.id", "table.name"
2788
+ const hasNestedRelationshipData = this.hasNestedRelationshipData(record, config.fieldName);
2789
+ if (hasNestedRelationshipData) {
2790
+ populatedCount++;
2791
+ }
468
2792
  }
469
- return this.client.models[modelName].observeQuery(queryOptions);
2793
+ // Consider the validation successful if we have any populated or partially populated records
2794
+ const totalValidRecords = populatedCount + partiallyPopulatedCount;
2795
+ if (totalValidRecords === 0) {
2796
+ // Log detailed information about what we found
2797
+ this.errorHandler.logSelectionSetError(SelectionSetErrorType.FIELD_NOT_FOUND, `No records have relationship field '${config.fieldName}' populated`, config, undefined, {
2798
+ method: 'validateRelationshipFieldPopulation',
2799
+ recordCount: records.length,
2800
+ populatedCount,
2801
+ partiallyPopulatedCount,
2802
+ sampleRecord: records[0] ? Object.keys(records[0]) : [],
2803
+ sampleRecordData: records[0] ? records[0] : null,
2804
+ fieldName: config.fieldName,
2805
+ relationshipFieldValue: records[0] ? records[0][config.fieldName] : undefined
2806
+ });
2807
+ console.warn(`AmplifyModelService: No records have relationship field '${config.fieldName}' populated`, {
2808
+ config,
2809
+ recordCount: records.length,
2810
+ sampleRecord: records[0],
2811
+ sampleRelationshipValue: records[0] ? records[0][config.fieldName] : undefined
2812
+ });
2813
+ return false;
2814
+ }
2815
+ console.log(`AmplifyModelService: Relationship field validation successful: ${populatedCount}/${records.length} records fully populated, ${partiallyPopulatedCount} partially populated for '${config.fieldName}' field`);
2816
+ return true;
470
2817
  }
471
- else {
472
- console.error(`Relationship model ${modelName} not found in client`);
473
- return null;
2818
+ catch (error) {
2819
+ this.errorHandler.logSelectionSetError(SelectionSetErrorType.GRAPHQL_ERROR, `Error validating relationship field population: ${error instanceof Error ? error.message : String(error)}`, config, undefined, {
2820
+ method: 'validateRelationshipFieldPopulation',
2821
+ originalError: {
2822
+ message: error instanceof Error ? error.message : String(error),
2823
+ stack: error instanceof Error ? error.stack : undefined
2824
+ }
2825
+ });
2826
+ console.error('AmplifyModelService: Error validating relationship field population:', error);
2827
+ return false;
2828
+ }
2829
+ }
2830
+ /**
2831
+ * Check if a record has nested relationship data based on field patterns
2832
+ * This helps validate cases where selection sets use nested field access like "table.id", "table.name"
2833
+ */
2834
+ hasNestedRelationshipData(record, fieldName) {
2835
+ if (!record || typeof record !== 'object') {
2836
+ return false;
2837
+ }
2838
+ // Look for any fields that start with the relationship field name followed by a dot
2839
+ const nestedFieldPattern = `${fieldName}.`;
2840
+ for (const key of Object.keys(record)) {
2841
+ if (key.startsWith(nestedFieldPattern)) {
2842
+ const value = record[key];
2843
+ if (value !== undefined && value !== null) {
2844
+ return true;
2845
+ }
2846
+ }
2847
+ }
2848
+ return false;
2849
+ }
2850
+ /**
2851
+ * Log query attempt details
2852
+ */
2853
+ logQueryAttempt(config, selectionSet, retryAttempt, previousErrors) {
2854
+ console.log('AmplifyModelService: Query attempt details:', {
2855
+ attempt: retryAttempt + 1,
2856
+ config: {
2857
+ relationshipModelName: config.relationshipModelName,
2858
+ fieldName: config.fieldName
2859
+ },
2860
+ selectionSet,
2861
+ previousErrors: previousErrors.length,
2862
+ timestamp: new Date().toISOString()
2863
+ });
2864
+ }
2865
+ /**
2866
+ * Log successful query details
2867
+ */
2868
+ logQuerySuccess(config, selectionSet, retryAttempt, result) {
2869
+ const recordCount = result?.data ? (Array.isArray(result.data) ? result.data.length : 1) : 0;
2870
+ console.log('AmplifyModelService: Query successful:', {
2871
+ attempt: retryAttempt + 1,
2872
+ config: {
2873
+ relationshipModelName: config.relationshipModelName,
2874
+ fieldName: config.fieldName
2875
+ },
2876
+ selectionSet,
2877
+ recordCount,
2878
+ timestamp: new Date().toISOString()
2879
+ });
2880
+ }
2881
+ /**
2882
+ * Log query error details
2883
+ */
2884
+ logQueryError(config, error, retryAttempt) {
2885
+ console.error('AmplifyModelService: Query error details:', {
2886
+ attempt: retryAttempt + 1,
2887
+ config: {
2888
+ relationshipModelName: config.relationshipModelName,
2889
+ baseModelName: config.baseModelName,
2890
+ fieldName: config.fieldName,
2891
+ associatedWith: config.associatedWith
2892
+ },
2893
+ error: {
2894
+ message: error instanceof Error ? error.message : String(error),
2895
+ type: typeof error,
2896
+ name: error instanceof Error ? error.name : undefined
2897
+ },
2898
+ timestamp: new Date().toISOString()
2899
+ });
2900
+ }
2901
+ /**
2902
+ * Log final failure when all retries are exhausted
2903
+ * Enhanced with comprehensive error context and recovery suggestions
2904
+ */
2905
+ logFinalFailure(config, allErrors, finalError) {
2906
+ try {
2907
+ const errorSummary = {
2908
+ config: {
2909
+ relationshipModelName: config.relationshipModelName,
2910
+ baseModelName: config.baseModelName,
2911
+ fieldName: config.fieldName,
2912
+ associatedWith: config.associatedWith
2913
+ },
2914
+ totalAttempts: allErrors.length + 1,
2915
+ allErrors,
2916
+ finalError: {
2917
+ message: finalError instanceof Error ? finalError.message : String(finalError),
2918
+ type: typeof finalError,
2919
+ name: finalError instanceof Error ? finalError.name : undefined
2920
+ },
2921
+ timestamp: new Date().toISOString(),
2922
+ suggestions: [
2923
+ 'Check if the relationship model exists in your Amplify schema',
2924
+ 'Verify that the field name matches the relationship field in your model',
2925
+ 'Ensure the relationship is properly configured in your GraphQL schema',
2926
+ 'Check if there are any authorization rules preventing access to the relationship data'
2927
+ ]
2928
+ };
2929
+ // Log using ErrorHandlerService for comprehensive tracking
2930
+ this.errorHandler.logFailedRecovery(this.errorHandler.logSelectionSetError(SelectionSetErrorType.GRAPHQL_ERROR, 'All retry attempts failed for relationship query', config, undefined, {
2931
+ method: 'logFinalFailure',
2932
+ errorSummary
2933
+ }), allErrors, finalError);
2934
+ console.error('AmplifyModelService: All retry attempts failed:', errorSummary);
2935
+ }
2936
+ catch (loggingError) {
2937
+ // Even logging failed - use basic console error
2938
+ console.error('AmplifyModelService: Critical error - failed to log final failure:', {
2939
+ originalError: finalError,
2940
+ loggingError,
2941
+ config: config?.fieldName || 'unknown'
2942
+ });
474
2943
  }
475
2944
  }
476
2945
  createItemPromise(model, payload) {
@@ -559,7 +3028,7 @@ class AmplifyModelService {
559
3028
  return null;
560
3029
  }
561
3030
  }
562
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: AmplifyModelService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
3031
+ 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
3032
  static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: AmplifyModelService, providedIn: 'root' });
564
3033
  }
565
3034
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: AmplifyModelService, decorators: [{
@@ -567,7 +3036,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImpo
567
3036
  args: [{
568
3037
  providedIn: 'root'
569
3038
  }]
570
- }], ctorParameters: () => [] });
3039
+ }], ctorParameters: () => [{ type: SelectionSetGeneratorService }, { type: ErrorHandlerService }] });
571
3040
 
572
3041
  class DynamicFormQuestionComponent {
573
3042
  rootFormGroup;
@@ -798,46 +3267,441 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImpo
798
3267
  // import outputs from '../../../../amplify_outputs.json';
799
3268
  class DynamicRelationshipBuilderComponent {
800
3269
  ams;
3270
+ errorHandler;
801
3271
  m2m;
802
3272
  item;
803
3273
  model;
804
- m2mItems;
805
- constructor(ams) {
3274
+ m2mItems = [];
3275
+ errorMessage = '';
3276
+ isLoading = false;
3277
+ constructor(ams, errorHandler) {
806
3278
  this.ams = ams;
3279
+ this.errorHandler = errorHandler;
807
3280
  }
808
3281
  modelItem; // = signal('');
809
3282
  dialog = inject(MatDialog);
810
3283
  ngOnInit() {
811
- console.log('DynamicRelationshipBuilderComponent model', this.m2m);
812
- this.listItems();
813
- this.setCreateSubscription();
3284
+ try {
3285
+ console.log('DynamicRelationshipBuilderComponent initializing with m2m config:', this.m2m);
3286
+ // Validate required inputs with comprehensive error logging
3287
+ if (!this.m2m) {
3288
+ const error = this.errorHandler.logSelectionSetError(SelectionSetErrorType.INVALID_CONFIGURATION, 'Relationship configuration (m2m) is missing', undefined, undefined, {
3289
+ component: 'DynamicRelationshipBuilderComponent',
3290
+ method: 'ngOnInit',
3291
+ hasItem: !!this.item,
3292
+ hasModel: !!this.model
3293
+ });
3294
+ this.setError('Relationship configuration is missing');
3295
+ return;
3296
+ }
3297
+ if (!this.item || !this.item.id) {
3298
+ const error = this.errorHandler.logSelectionSetError(SelectionSetErrorType.INVALID_CONFIGURATION, 'Item data is missing or invalid (no ID)', this.m2m, undefined, {
3299
+ component: 'DynamicRelationshipBuilderComponent',
3300
+ method: 'ngOnInit',
3301
+ hasItem: !!this.item,
3302
+ itemId: this.item?.id,
3303
+ hasModel: !!this.model
3304
+ });
3305
+ this.setError('Item data is missing or invalid');
3306
+ return;
3307
+ }
3308
+ // Validate m2m configuration structure
3309
+ const requiredM2MFields = ['relationshipModelName', 'fieldName', 'baseModelName', 'associatedWith'];
3310
+ const missingFields = requiredM2MFields.filter(field => !this.m2m[field]);
3311
+ if (missingFields.length > 0) {
3312
+ const error = this.errorHandler.logSelectionSetError(SelectionSetErrorType.INVALID_CONFIGURATION, `M2M configuration is missing required fields: ${missingFields.join(', ')}`, this.m2m, undefined, {
3313
+ component: 'DynamicRelationshipBuilderComponent',
3314
+ method: 'ngOnInit',
3315
+ missingFields,
3316
+ providedFields: Object.keys(this.m2m || {})
3317
+ });
3318
+ this.setError(`Configuration is incomplete: missing ${missingFields.join(', ')}`);
3319
+ return;
3320
+ }
3321
+ this.clearError();
3322
+ console.log('DynamicRelationshipBuilderComponent: Configuration validated successfully');
3323
+ // Initialize relationship data loading
3324
+ this.listItems();
3325
+ this.setCreateSubscription();
3326
+ }
3327
+ catch (error) {
3328
+ const componentError = this.errorHandler.logSelectionSetError(SelectionSetErrorType.GRAPHQL_ERROR, `Unexpected error during component initialization: ${error instanceof Error ? error.message : String(error)}`, this.m2m, undefined, {
3329
+ component: 'DynamicRelationshipBuilderComponent',
3330
+ method: 'ngOnInit',
3331
+ originalError: {
3332
+ message: error instanceof Error ? error.message : String(error),
3333
+ stack: error instanceof Error ? error.stack : undefined
3334
+ }
3335
+ });
3336
+ console.error('DynamicRelationshipBuilderComponent: Critical initialization error:', error);
3337
+ this.setError('Failed to initialize relationship component');
3338
+ }
3339
+ }
3340
+ /**
3341
+ * Set error message and clear loading state
3342
+ * Enhanced with comprehensive error context logging
3343
+ */
3344
+ setError(message, additionalContext) {
3345
+ try {
3346
+ this.errorMessage = message;
3347
+ this.isLoading = false;
3348
+ // Log error with context using ErrorHandlerService
3349
+ this.errorHandler.logSelectionSetError(SelectionSetErrorType.GRAPHQL_ERROR, `Component error: ${message}`, this.m2m, undefined, {
3350
+ component: 'DynamicRelationshipBuilderComponent',
3351
+ method: 'setError',
3352
+ userMessage: message,
3353
+ componentState: {
3354
+ hasM2M: !!this.m2m,
3355
+ hasItem: !!this.item,
3356
+ itemId: this.item?.id,
3357
+ m2mItemsCount: Array.isArray(this.m2mItems) ? this.m2mItems.length : 0,
3358
+ isLoading: this.isLoading
3359
+ },
3360
+ ...additionalContext
3361
+ });
3362
+ console.error('DynamicRelationshipBuilderComponent error:', {
3363
+ message,
3364
+ context: additionalContext,
3365
+ timestamp: new Date().toISOString()
3366
+ });
3367
+ }
3368
+ catch (loggingError) {
3369
+ // Fallback if even error logging fails
3370
+ console.error('DynamicRelationshipBuilderComponent: Critical error in setError:', {
3371
+ originalMessage: message,
3372
+ loggingError,
3373
+ timestamp: new Date().toISOString()
3374
+ });
3375
+ this.errorMessage = message;
3376
+ this.isLoading = false;
3377
+ }
3378
+ }
3379
+ /**
3380
+ * Clear error message and log recovery
3381
+ */
3382
+ clearError() {
3383
+ if (this.errorMessage) {
3384
+ console.log('DynamicRelationshipBuilderComponent: Clearing previous error:', this.errorMessage);
3385
+ }
3386
+ this.errorMessage = '';
3387
+ }
3388
+ /**
3389
+ * Get display name for a relationship item, handling various field names
3390
+ */
3391
+ getDisplayName(relationshipItem) {
3392
+ if (!relationshipItem) {
3393
+ return 'Unknown item';
3394
+ }
3395
+ // Try common display field names
3396
+ return relationshipItem.name ||
3397
+ relationshipItem.title ||
3398
+ relationshipItem.label ||
3399
+ relationshipItem.displayName ||
3400
+ `Item ${relationshipItem.id || 'Unknown'}`;
3401
+ }
3402
+ /**
3403
+ * Get display name for items with partial or missing relationship data
3404
+ */
3405
+ getPartialDisplayName(item) {
3406
+ if (!item) {
3407
+ return 'Unknown item';
3408
+ }
3409
+ // If we have an ID, show it
3410
+ if (item.id) {
3411
+ return `Item ${item.id} (incomplete data)`;
3412
+ }
3413
+ return 'Incomplete relationship data';
3414
+ }
3415
+ /**
3416
+ * Check if an item has complete relationship data
3417
+ */
3418
+ hasCompleteRelationshipData(item) {
3419
+ if (!item || !this.m2m?.fieldName) {
3420
+ return false;
3421
+ }
3422
+ const relationshipData = item[this.m2m.fieldName];
3423
+ return relationshipData && (relationshipData.name || relationshipData.title || relationshipData.id);
3424
+ }
3425
+ /**
3426
+ * Check if an item has partial relationship data (item exists but relationship data is incomplete)
3427
+ */
3428
+ hasPartialRelationshipData(item) {
3429
+ if (!item || !item.id) {
3430
+ return false;
3431
+ }
3432
+ return !this.hasCompleteRelationshipData(item);
3433
+ }
3434
+ /**
3435
+ * Get loading state indicator for relationship data
3436
+ */
3437
+ getRelationshipLoadingState(item) {
3438
+ if (!item) {
3439
+ return 'missing';
3440
+ }
3441
+ if (this.isLoading) {
3442
+ return 'loading';
3443
+ }
3444
+ if (this.hasCompleteRelationshipData(item)) {
3445
+ return 'complete';
3446
+ }
3447
+ if (item.id) {
3448
+ return 'partial';
3449
+ }
3450
+ return 'missing';
3451
+ }
3452
+ /**
3453
+ * Get appropriate CSS class for relationship item based on data completeness
3454
+ */
3455
+ getRelationshipItemClass(item) {
3456
+ const state = this.getRelationshipLoadingState(item);
3457
+ return `relationship-item relationship-item--${state}`;
3458
+ }
3459
+ /**
3460
+ * Get tooltip text for relationship items based on their data state
3461
+ */
3462
+ getRelationshipTooltip(item) {
3463
+ const state = this.getRelationshipLoadingState(item);
3464
+ switch (state) {
3465
+ case 'complete':
3466
+ return 'Complete relationship data loaded';
3467
+ case 'partial':
3468
+ return 'Relationship exists but some data is missing or failed to load';
3469
+ case 'loading':
3470
+ return 'Loading relationship data...';
3471
+ case 'missing':
3472
+ return 'No relationship data available';
3473
+ default:
3474
+ return '';
3475
+ }
3476
+ }
3477
+ /**
3478
+ * Get summary of data states for all relationship items
3479
+ */
3480
+ getDataStateSummary() {
3481
+ if (!Array.isArray(this.m2mItems)) {
3482
+ return {
3483
+ completeCount: 0,
3484
+ partialCount: 0,
3485
+ missingCount: 0,
3486
+ hasPartialOrMissing: false
3487
+ };
3488
+ }
3489
+ let completeCount = 0;
3490
+ let partialCount = 0;
3491
+ let missingCount = 0;
3492
+ this.m2mItems.forEach(item => {
3493
+ const state = this.getRelationshipLoadingState(item);
3494
+ switch (state) {
3495
+ case 'complete':
3496
+ completeCount++;
3497
+ break;
3498
+ case 'partial':
3499
+ partialCount++;
3500
+ break;
3501
+ case 'missing':
3502
+ missingCount++;
3503
+ break;
3504
+ }
3505
+ });
3506
+ return {
3507
+ completeCount,
3508
+ partialCount,
3509
+ missingCount,
3510
+ hasPartialOrMissing: partialCount > 0 || missingCount > 0
3511
+ };
814
3512
  }
815
3513
  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);
3514
+ try {
3515
+ if (!item || !item.id) {
3516
+ console.warn('Invalid item received in perhapsAddM2MItem:', item);
3517
+ return;
3518
+ }
3519
+ let m2mItem = { id: item.id };
3520
+ // Safely attempt to get relationship data
3521
+ if (this.m2m?.fieldName && typeof item[this.m2m.fieldName] === 'function') {
3522
+ try {
3523
+ const { data: retItem } = await item[this.m2m.fieldName]();
3524
+ m2mItem[this.m2m.fieldName] = retItem;
3525
+ }
3526
+ catch (relationshipError) {
3527
+ console.warn('Failed to load relationship data for item:', item.id, relationshipError);
3528
+ // Keep the item but mark it as having incomplete data
3529
+ m2mItem[this.m2m.fieldName] = null;
3530
+ }
824
3531
  }
3532
+ else {
3533
+ console.warn('Invalid fieldName or item method for relationship:', this.m2m?.fieldName);
3534
+ m2mItem[this.m2m.fieldName] = null;
3535
+ }
3536
+ // Ensure m2mItems is initialized as array
3537
+ if (!Array.isArray(this.m2mItems)) {
3538
+ this.m2mItems = [];
3539
+ }
3540
+ let existingInd = this.m2mItems.findIndex((x) => x?.id === item.id);
3541
+ if (existingInd > -1) {
3542
+ if (!this.m2mItems[existingInd][this.m2m.fieldName]) {
3543
+ console.log('Removing existing item with no relationship data', this.m2mItems[existingInd]);
3544
+ this.m2mItems.splice(existingInd, 1);
3545
+ }
3546
+ }
3547
+ this.m2mItems.push(m2mItem);
3548
+ }
3549
+ catch (error) {
3550
+ console.error('Error in perhapsAddM2MItem:', error);
3551
+ // Don't let individual item errors break the entire component
825
3552
  }
826
- this.m2mItems.push(m2mItem);
827
- //console.log('perhapsAddM2MItem', item, 'm2m', this.m2m);
828
3553
  }
829
3554
  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
- });
3555
+ try {
3556
+ if (!this.m2m || !this.item?.id) {
3557
+ console.warn('Cannot set subscription: missing m2m or item.id');
3558
+ return;
3559
+ }
3560
+ const subscription = this.ams.setRelatedSub(this.m2m, this.item.id);
3561
+ if (subscription) {
3562
+ subscription.subscribe({
3563
+ next: (data) => {
3564
+ this.perhapsAddM2MItem(data);
3565
+ },
3566
+ error: (error) => {
3567
+ console.warn('Subscription error for related items:', error);
3568
+ this.setError('Failed to receive real-time updates for relationships');
3569
+ },
3570
+ });
3571
+ }
3572
+ else {
3573
+ console.warn('Failed to create subscription for related items');
3574
+ }
3575
+ }
3576
+ catch (error) {
3577
+ console.error('Error setting up subscription:', error);
3578
+ this.setError('Failed to set up real-time updates');
3579
+ }
834
3580
  }
835
3581
  listItems() {
836
- this.ams.getRelatedItems(this.m2m, this.item.id)?.subscribe({
837
- next: ({ items, isSynced }) => {
838
- this.m2mItems = items;
839
- },
840
- });
3582
+ try {
3583
+ if (!this.m2m || !this.item?.id) {
3584
+ const error = 'Cannot load relationships: missing configuration or item data';
3585
+ this.setError(error, {
3586
+ method: 'listItems',
3587
+ hasM2M: !!this.m2m,
3588
+ hasItem: !!this.item,
3589
+ itemId: this.item?.id
3590
+ });
3591
+ return;
3592
+ }
3593
+ this.isLoading = true;
3594
+ this.clearError();
3595
+ console.log('DynamicRelationshipBuilderComponent: Loading relationship items for:', {
3596
+ relationshipModel: this.m2m.relationshipModelName,
3597
+ fieldName: this.m2m.fieldName,
3598
+ itemId: this.item.id
3599
+ });
3600
+ const observable = this.ams.getRelatedItems(this.m2m, this.item.id);
3601
+ if (observable) {
3602
+ observable.subscribe({
3603
+ next: ({ items, isSynced }) => {
3604
+ try {
3605
+ this.isLoading = false;
3606
+ // Ensure items is an array
3607
+ if (Array.isArray(items)) {
3608
+ this.m2mItems = items;
3609
+ console.log('DynamicRelationshipBuilderComponent: Successfully loaded relationship items:', {
3610
+ count: items.length,
3611
+ isSynced,
3612
+ sampleItem: items[0] ? Object.keys(items[0]) : []
3613
+ });
3614
+ }
3615
+ else {
3616
+ console.warn('DynamicRelationshipBuilderComponent: Received non-array items:', items);
3617
+ this.m2mItems = [];
3618
+ this.errorHandler.logSelectionSetError(SelectionSetErrorType.GRAPHQL_ERROR, 'Received non-array items from relationship query', this.m2m, undefined, {
3619
+ component: 'DynamicRelationshipBuilderComponent',
3620
+ method: 'listItems',
3621
+ receivedType: typeof items,
3622
+ receivedValue: items,
3623
+ isSynced
3624
+ });
3625
+ }
3626
+ // Analyze data quality
3627
+ const dataStateSummary = this.getDataStateSummary();
3628
+ if (dataStateSummary.hasPartialOrMissing) {
3629
+ console.warn('DynamicRelationshipBuilderComponent: Some relationship data is incomplete:', dataStateSummary);
3630
+ }
3631
+ }
3632
+ catch (processingError) {
3633
+ this.isLoading = false;
3634
+ const error = `Error processing relationship data: ${processingError instanceof Error ? processingError.message : String(processingError)}`;
3635
+ this.setError('Failed to process relationship data', {
3636
+ method: 'listItems',
3637
+ step: 'data_processing',
3638
+ originalError: {
3639
+ message: processingError instanceof Error ? processingError.message : String(processingError),
3640
+ stack: processingError instanceof Error ? processingError.stack : undefined
3641
+ }
3642
+ });
3643
+ this.m2mItems = [];
3644
+ }
3645
+ },
3646
+ error: (error) => {
3647
+ this.isLoading = false;
3648
+ const errorMessage = error instanceof Error ? error.message : String(error);
3649
+ console.error('DynamicRelationshipBuilderComponent: Error loading relationship items:', error);
3650
+ // Log comprehensive error details
3651
+ this.errorHandler.logSelectionSetError(SelectionSetErrorType.GRAPHQL_ERROR, `Failed to load relationship data: ${errorMessage}`, this.m2m, undefined, {
3652
+ component: 'DynamicRelationshipBuilderComponent',
3653
+ method: 'listItems',
3654
+ step: 'observable_error',
3655
+ originalError: {
3656
+ message: errorMessage,
3657
+ stack: error instanceof Error ? error.stack : undefined,
3658
+ name: error instanceof Error ? error.name : undefined
3659
+ },
3660
+ itemId: this.item.id
3661
+ });
3662
+ this.setError('Failed to load relationship data. Please try refreshing the page.', {
3663
+ method: 'listItems',
3664
+ originalError: errorMessage
3665
+ });
3666
+ // Initialize empty array so component doesn't break
3667
+ this.m2mItems = [];
3668
+ }
3669
+ });
3670
+ }
3671
+ else {
3672
+ this.isLoading = false;
3673
+ this.errorHandler.logSelectionSetError(SelectionSetErrorType.GRAPHQL_ERROR, 'AmplifyModelService returned null observable for relationship query', this.m2m, undefined, {
3674
+ component: 'DynamicRelationshipBuilderComponent',
3675
+ method: 'listItems',
3676
+ step: 'observable_creation',
3677
+ itemId: this.item.id
3678
+ });
3679
+ this.setError('Unable to create relationship query', {
3680
+ method: 'listItems',
3681
+ step: 'observable_creation'
3682
+ });
3683
+ this.m2mItems = [];
3684
+ }
3685
+ }
3686
+ catch (error) {
3687
+ this.isLoading = false;
3688
+ const errorMessage = error instanceof Error ? error.message : String(error);
3689
+ console.error('DynamicRelationshipBuilderComponent: Critical error in listItems:', error);
3690
+ this.errorHandler.logSelectionSetError(SelectionSetErrorType.GRAPHQL_ERROR, `Critical error in listItems: ${errorMessage}`, this.m2m, undefined, {
3691
+ component: 'DynamicRelationshipBuilderComponent',
3692
+ method: 'listItems',
3693
+ step: 'critical_error',
3694
+ originalError: {
3695
+ message: errorMessage,
3696
+ stack: error instanceof Error ? error.stack : undefined
3697
+ }
3698
+ });
3699
+ this.setError('An unexpected error occurred while loading relationships', {
3700
+ method: 'listItems',
3701
+ originalError: errorMessage
3702
+ });
3703
+ this.m2mItems = [];
3704
+ }
841
3705
  }
842
3706
  /**
843
3707
  Customize this function to include all desired models from your file
@@ -848,44 +3712,111 @@ class DynamicRelationshipBuilderComponent {
848
3712
  this.modelItem = signal(this.ams.getEmptyObjectForModel(mdl), ...(ngDevMode ? [{ debugName: "modelItem" }] : []));
849
3713
  }
850
3714
  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);
3715
+ try {
3716
+ if (!item || !item.id) {
3717
+ console.warn('Cannot delete item: invalid item data', item);
3718
+ this.setError('Cannot delete item: invalid data');
3719
+ return;
3720
+ }
3721
+ if (!this.m2m?.relationshipModelName) {
3722
+ console.warn('Cannot delete item: missing relationship model name');
3723
+ this.setError('Cannot delete item: missing configuration');
3724
+ return;
3725
+ }
3726
+ console.log('deleteRelatedItem for item', item, 'm2m', this.m2m);
3727
+ await this.ams.deleteRelationship(this.m2m.relationshipModelName, item.id);
3728
+ // Remove item from local array on successful deletion
3729
+ if (Array.isArray(this.m2mItems)) {
3730
+ const index = this.m2mItems.findIndex(x => x?.id === item.id);
3731
+ if (index > -1) {
3732
+ this.m2mItems.splice(index, 1);
3733
+ }
3734
+ }
3735
+ }
3736
+ catch (error) {
3737
+ console.error('Error deleting relationship item:', error);
3738
+ this.setError('Failed to delete relationship. Please try again.');
3739
+ }
854
3740
  }
855
3741
  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
- });
3742
+ try {
3743
+ if (!rel) {
3744
+ console.warn('Cannot open dialog: missing relationship configuration');
3745
+ this.setError('Cannot add relationship: missing configuration');
3746
+ return;
3747
+ }
3748
+ console.log('openDialog clicked for rel', rel);
3749
+ this.setModelItem(rel.partnerModelName);
3750
+ const dialogRef = this.dialog.open(AddRelationshipDialogComponent, {
3751
+ data: { rel: rel, modelItem: this.modelItem(), selectedItems: this.m2mItems || [] },
3752
+ });
3753
+ dialogRef.afterClosed().subscribe({
3754
+ next: (result) => {
3755
+ if (result) {
3756
+ this.handleDialogResult(result, rel);
3757
+ }
3758
+ },
3759
+ error: (error) => {
3760
+ console.error('Dialog error:', error);
3761
+ this.setError('An error occurred while adding the relationship');
3762
+ }
3763
+ });
3764
+ }
3765
+ catch (error) {
3766
+ console.error('Error opening dialog:', error);
3767
+ this.setError('Failed to open relationship dialog');
3768
+ }
865
3769
  }
866
3770
  handleDialogResult(result, rel) {
867
- if (!result)
868
- return;
869
- console.log('handleDialogResult', result, 'rel', rel);
870
- if (result !== undefined) {
871
- this.modelItem.set(result);
3771
+ try {
3772
+ if (!result) {
3773
+ return;
3774
+ }
3775
+ if (!rel || !this.item?.id) {
3776
+ console.warn('Cannot handle dialog result: missing relationship or item data');
3777
+ this.setError('Cannot create relationship: missing data');
3778
+ return;
3779
+ }
3780
+ console.log('handleDialogResult', result, 'rel', rel);
3781
+ if (result !== undefined) {
3782
+ this.modelItem.set(result);
3783
+ }
3784
+ let relIdName = rel.partnerModelName?.toLowerCase() + 'Id';
3785
+ let thisIdName = rel.baseModelName?.toLowerCase() + 'Id';
3786
+ if (!relIdName || !thisIdName) {
3787
+ console.warn('Cannot determine relationship field names');
3788
+ this.setError('Cannot create relationship: invalid configuration');
3789
+ return;
3790
+ }
3791
+ let newRelObj = {};
3792
+ newRelObj[relIdName] = result.id;
3793
+ newRelObj[thisIdName] = this.item.id;
3794
+ const createPromise = this.ams.createRelationship(rel.relationshipModelName, newRelObj);
3795
+ if (createPromise) {
3796
+ createPromise.catch((error) => {
3797
+ console.error('Error creating relationship:', error);
3798
+ this.setError('Failed to create relationship. Please try again.');
3799
+ });
3800
+ }
3801
+ else {
3802
+ console.warn('Failed to create relationship: service returned null');
3803
+ this.setError('Failed to create relationship. Please try again.');
3804
+ }
3805
+ }
3806
+ catch (error) {
3807
+ console.error('Error handling dialog result:', error);
3808
+ this.setError('An error occurred while creating the relationship');
872
3809
  }
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
3810
  }
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 }] });
3811
+ 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 });
3812
+ 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
3813
  }
883
3814
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: DynamicRelationshipBuilderComponent, decorators: [{
884
3815
  type: Component,
885
3816
  args: [{ selector: 'snteam-dynamic-relationship-builder', standalone: true, imports: [MatToolbarModule, MatButtonModule, MatIconModule, MatFormFieldModule, MatInputModule, MatListModule,
886
3817
  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: [{
3818
+ 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"] }]
3819
+ }], ctorParameters: () => [{ type: AmplifyModelService }, { type: ErrorHandlerService }], propDecorators: { m2m: [{
889
3820
  type: Input
890
3821
  }], item: [{
891
3822
  type: Input
@@ -2036,7 +4967,7 @@ class DynamicFormComponent {
2036
4967
  return this.form.get(question.key);
2037
4968
  return true;
2038
4969
  }
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 });
4970
+ 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
4971
  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
4972
  }
2042
4973
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: DynamicFormComponent, decorators: [{
@@ -2055,7 +4986,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImpo
2055
4986
  MatDividerModule,
2056
4987
  MatButtonModule
2057
4988
  ], 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: [{
4989
+ }], ctorParameters: () => [{ type: QuestionControlService }, { type: AmplifyFormBuilderService }, { type: i3$1.Location }, { type: i4$1.ActivatedRoute }], propDecorators: { model: [{
2059
4990
  type: Input
2060
4991
  }], itemId: [{
2061
4992
  type: Input
@@ -2194,7 +5125,7 @@ class ListViewComponent {
2194
5125
  this.listItems();
2195
5126
  }
2196
5127
  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" }] });
5128
+ 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
5129
  }
2199
5130
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: ListViewComponent, decorators: [{
2200
5131
  type: Component,
@@ -2528,7 +5459,7 @@ return fields;
2528
5459
  return 'text'; // Default fallback
2529
5460
  }
2530
5461
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: ConfigurationsComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
2531
- 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: 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"] }] });
5462
+ 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"] }] });
2532
5463
  }
2533
5464
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: ConfigurationsComponent, decorators: [{
2534
5465
  type: Component,
@@ -2554,5 +5485,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImpo
2554
5485
  * Generated bundle index. Do not edit.
2555
5486
  */
2556
5487
 
2557
- 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 };
5488
+ 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 };
2558
5489
  //# sourceMappingURL=snteam-amplify-angular-core.mjs.map