angular-data-mapper 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (64) hide show
  1. package/.claude/settings.local.json +10 -0
  2. package/LICENSE +190 -0
  3. package/PUBLISHING.md +75 -0
  4. package/README.md +214 -0
  5. package/angular.json +121 -0
  6. package/package.json +67 -0
  7. package/projects/demo-app/public/favicon.ico +0 -0
  8. package/projects/demo-app/src/app/app.config.ts +12 -0
  9. package/projects/demo-app/src/app/app.html +36 -0
  10. package/projects/demo-app/src/app/app.routes.ts +62 -0
  11. package/projects/demo-app/src/app/app.scss +65 -0
  12. package/projects/demo-app/src/app/app.ts +11 -0
  13. package/projects/demo-app/src/app/layout/app-layout.component.ts +294 -0
  14. package/projects/demo-app/src/app/pages/mapper-page/mapper-page.component.html +87 -0
  15. package/projects/demo-app/src/app/pages/mapper-page/mapper-page.component.scss +202 -0
  16. package/projects/demo-app/src/app/pages/mapper-page/mapper-page.component.ts +192 -0
  17. package/projects/demo-app/src/app/pages/mappings-page/add-mapping-dialog.component.ts +163 -0
  18. package/projects/demo-app/src/app/pages/mappings-page/mappings-page.component.ts +306 -0
  19. package/projects/demo-app/src/app/pages/schema-creator-page/schema-creator-page.component.ts +88 -0
  20. package/projects/demo-app/src/app/pages/schema-editor-page/schema-editor-page.component.html +108 -0
  21. package/projects/demo-app/src/app/pages/schema-editor-page/schema-editor-page.component.scss +317 -0
  22. package/projects/demo-app/src/app/pages/schema-editor-page/schema-editor-page.component.ts +129 -0
  23. package/projects/demo-app/src/app/services/app-state.service.ts +233 -0
  24. package/projects/demo-app/src/app/services/sample-data.service.ts +228 -0
  25. package/projects/demo-app/src/index.html +15 -0
  26. package/projects/demo-app/src/main.ts +6 -0
  27. package/projects/demo-app/src/styles.scss +54 -0
  28. package/projects/demo-app/tsconfig.app.json +13 -0
  29. package/projects/ngx-data-mapper/ng-package.json +7 -0
  30. package/projects/ngx-data-mapper/package.json +40 -0
  31. package/projects/ngx-data-mapper/src/lib/components/array-filter-modal/array-filter-modal.component.html +183 -0
  32. package/projects/ngx-data-mapper/src/lib/components/array-filter-modal/array-filter-modal.component.scss +352 -0
  33. package/projects/ngx-data-mapper/src/lib/components/array-filter-modal/array-filter-modal.component.ts +277 -0
  34. package/projects/ngx-data-mapper/src/lib/components/array-selector-modal/array-selector-modal.component.html +174 -0
  35. package/projects/ngx-data-mapper/src/lib/components/array-selector-modal/array-selector-modal.component.scss +357 -0
  36. package/projects/ngx-data-mapper/src/lib/components/array-selector-modal/array-selector-modal.component.ts +258 -0
  37. package/projects/ngx-data-mapper/src/lib/components/condition-builder/condition-builder.component.html +139 -0
  38. package/projects/ngx-data-mapper/src/lib/components/condition-builder/condition-builder.component.scss +213 -0
  39. package/projects/ngx-data-mapper/src/lib/components/condition-builder/condition-builder.component.ts +261 -0
  40. package/projects/ngx-data-mapper/src/lib/components/data-mapper/data-mapper.component.html +199 -0
  41. package/projects/ngx-data-mapper/src/lib/components/data-mapper/data-mapper.component.scss +321 -0
  42. package/projects/ngx-data-mapper/src/lib/components/data-mapper/data-mapper.component.ts +618 -0
  43. package/projects/ngx-data-mapper/src/lib/components/default-value-popover/default-value-popover.component.html +67 -0
  44. package/projects/ngx-data-mapper/src/lib/components/default-value-popover/default-value-popover.component.scss +97 -0
  45. package/projects/ngx-data-mapper/src/lib/components/default-value-popover/default-value-popover.component.ts +105 -0
  46. package/projects/ngx-data-mapper/src/lib/components/schema-editor/schema-editor.component.html +552 -0
  47. package/projects/ngx-data-mapper/src/lib/components/schema-editor/schema-editor.component.scss +824 -0
  48. package/projects/ngx-data-mapper/src/lib/components/schema-editor/schema-editor.component.ts +730 -0
  49. package/projects/ngx-data-mapper/src/lib/components/schema-tree/schema-tree.component.html +82 -0
  50. package/projects/ngx-data-mapper/src/lib/components/schema-tree/schema-tree.component.scss +352 -0
  51. package/projects/ngx-data-mapper/src/lib/components/schema-tree/schema-tree.component.ts +225 -0
  52. package/projects/ngx-data-mapper/src/lib/components/transformation-popover/transformation-popover.component.html +346 -0
  53. package/projects/ngx-data-mapper/src/lib/components/transformation-popover/transformation-popover.component.scss +511 -0
  54. package/projects/ngx-data-mapper/src/lib/components/transformation-popover/transformation-popover.component.ts +368 -0
  55. package/projects/ngx-data-mapper/src/lib/models/json-schema.model.ts +164 -0
  56. package/projects/ngx-data-mapper/src/lib/models/schema.model.ts +173 -0
  57. package/projects/ngx-data-mapper/src/lib/services/mapping.service.ts +615 -0
  58. package/projects/ngx-data-mapper/src/lib/services/schema-parser.service.ts +270 -0
  59. package/projects/ngx-data-mapper/src/lib/services/svg-connector.service.ts +135 -0
  60. package/projects/ngx-data-mapper/src/lib/services/transformation.service.ts +453 -0
  61. package/projects/ngx-data-mapper/src/public-api.ts +22 -0
  62. package/projects/ngx-data-mapper/tsconfig.lib.json +13 -0
  63. package/projects/ngx-data-mapper/tsconfig.lib.prod.json +9 -0
  64. package/tsconfig.json +28 -0
@@ -0,0 +1,368 @@
1
+ import {
2
+ Component,
3
+ Input,
4
+ Output,
5
+ EventEmitter,
6
+ OnInit,
7
+ OnChanges,
8
+ SimpleChanges,
9
+ inject,
10
+ } from '@angular/core';
11
+ import { CommonModule } from '@angular/common';
12
+ import { FormsModule } from '@angular/forms';
13
+ import { MatIconModule } from '@angular/material/icon';
14
+ import { MatButtonModule } from '@angular/material/button';
15
+ import { MatSelectModule } from '@angular/material/select';
16
+ import { MatInputModule } from '@angular/material/input';
17
+ import { MatFormFieldModule } from '@angular/material/form-field';
18
+ import { MatTooltipModule } from '@angular/material/tooltip';
19
+ import { MatCheckboxModule } from '@angular/material/checkbox';
20
+ import { CdkDragDrop, DragDropModule, moveItemInArray } from '@angular/cdk/drag-drop';
21
+ import {
22
+ FieldMapping,
23
+ FilterGroup,
24
+ TransformationConfig,
25
+ TransformationType,
26
+ } from '../../models/schema.model';
27
+ import { ConditionBuilderComponent } from '../condition-builder/condition-builder.component';
28
+ import { TransformationService } from '../../services/transformation.service';
29
+
30
+ @Component({
31
+ selector: 'transformation-popover',
32
+ standalone: true,
33
+ imports: [
34
+ CommonModule,
35
+ FormsModule,
36
+ MatIconModule,
37
+ MatButtonModule,
38
+ MatSelectModule,
39
+ MatInputModule,
40
+ MatFormFieldModule,
41
+ MatTooltipModule,
42
+ MatCheckboxModule,
43
+ DragDropModule,
44
+ ConditionBuilderComponent,
45
+ ],
46
+ templateUrl: './transformation-popover.component.html',
47
+ styleUrl: './transformation-popover.component.scss',
48
+ })
49
+ export class TransformationPopoverComponent implements OnInit, OnChanges {
50
+ @Input() mapping!: FieldMapping;
51
+ @Input() position: { x: number; y: number } = { x: 0, y: 0 };
52
+ @Input() sampleData: Record<string, unknown> = {};
53
+
54
+ @Output() save = new EventEmitter<TransformationConfig[]>();
55
+ @Output() delete = new EventEmitter<void>();
56
+ @Output() close = new EventEmitter<void>();
57
+
58
+ private transformationService = inject(TransformationService);
59
+
60
+ // Array of transformation steps
61
+ steps: TransformationConfig[] = [];
62
+
63
+ // Preview results for each step
64
+ stepPreviews: string[] = [];
65
+ // Input values for each step (to show input → output)
66
+ stepInputs: string[] = [];
67
+ finalPreview: string = '';
68
+
69
+ // Track which step is expanded (-1 means none, used in single-step mode)
70
+ expandedStepIndex: number = -1;
71
+
72
+ availableTransformations = this.transformationService.getAvailableTransformations();
73
+
74
+ ngOnInit(): void {
75
+ this.initFromMapping();
76
+ }
77
+
78
+ ngOnChanges(changes: SimpleChanges): void {
79
+ if (changes['mapping'] || changes['sampleData']) {
80
+ this.initFromMapping();
81
+ }
82
+ }
83
+
84
+ private initFromMapping(): void {
85
+ if (this.mapping) {
86
+ // Copy transformations array, with fallback for empty/undefined
87
+ if (this.mapping.transformations && this.mapping.transformations.length > 0) {
88
+ this.steps = this.mapping.transformations.map(t => ({ ...t }));
89
+ } else {
90
+ // Fallback to a single direct transformation
91
+ this.steps = [{ type: 'direct' }];
92
+ }
93
+ this.updatePreview();
94
+ }
95
+ }
96
+
97
+ // Check if we're in multi-step mode (more than one step)
98
+ get isMultiStep(): boolean {
99
+ return this.steps.length > 1;
100
+ }
101
+
102
+ onStepTypeChange(index: number): void {
103
+ const step = this.steps[index];
104
+
105
+ // Set defaults based on type
106
+ switch (step.type) {
107
+ case 'concat':
108
+ step.separator = step.separator ?? ' ';
109
+ step.template = step.template ?? this.getDefaultTemplate();
110
+ break;
111
+ case 'substring':
112
+ step.startIndex = step.startIndex ?? 0;
113
+ step.endIndex = step.endIndex ?? 10;
114
+ break;
115
+ case 'replace':
116
+ step.searchValue = step.searchValue ?? '';
117
+ step.replaceValue = step.replaceValue ?? '';
118
+ break;
119
+ case 'dateFormat':
120
+ step.outputFormat = step.outputFormat ?? 'YYYY-MM-DD';
121
+ break;
122
+ case 'numberFormat':
123
+ step.decimalPlaces = step.decimalPlaces ?? 2;
124
+ break;
125
+ case 'mask':
126
+ step.pattern = step.pattern ?? '(###) ###-####';
127
+ break;
128
+ }
129
+
130
+ this.updatePreview();
131
+ }
132
+
133
+ private getDefaultTemplate(): string {
134
+ return this.mapping.sourceFields.map((_, i) => `{${i}}`).join(' ');
135
+ }
136
+
137
+ addStep(): void {
138
+ this.steps.push({ type: 'direct' });
139
+ // Expand the newly added step
140
+ this.expandedStepIndex = this.steps.length - 1;
141
+ this.updatePreview();
142
+ }
143
+
144
+ removeStep(index: number): void {
145
+ if (this.steps.length > 1) {
146
+ this.steps.splice(index, 1);
147
+ // Collapse after deletion
148
+ this.expandedStepIndex = -1;
149
+ this.updatePreview();
150
+ }
151
+ }
152
+
153
+ onStepDrop(event: CdkDragDrop<TransformationConfig[]>): void {
154
+ if (event.previousIndex !== event.currentIndex) {
155
+ moveItemInArray(this.steps, event.previousIndex, event.currentIndex);
156
+ // Move expanded index with the step
157
+ if (this.expandedStepIndex === event.previousIndex) {
158
+ this.expandedStepIndex = event.currentIndex;
159
+ } else if (
160
+ this.expandedStepIndex > event.previousIndex &&
161
+ this.expandedStepIndex <= event.currentIndex
162
+ ) {
163
+ this.expandedStepIndex--;
164
+ } else if (
165
+ this.expandedStepIndex < event.previousIndex &&
166
+ this.expandedStepIndex >= event.currentIndex
167
+ ) {
168
+ this.expandedStepIndex++;
169
+ }
170
+ this.updatePreview();
171
+ }
172
+ }
173
+
174
+ toggleStep(index: number): void {
175
+ if (this.expandedStepIndex === index) {
176
+ this.expandedStepIndex = -1; // Collapse
177
+ } else {
178
+ this.expandedStepIndex = index; // Expand
179
+ }
180
+ }
181
+
182
+ isStepExpanded(index: number): boolean {
183
+ return this.expandedStepIndex === index;
184
+ }
185
+
186
+ updatePreview(): void {
187
+ if (!this.mapping || !this.sampleData) {
188
+ this.stepPreviews = [];
189
+ this.stepInputs = [];
190
+ this.finalPreview = '';
191
+ return;
192
+ }
193
+
194
+ this.stepPreviews = [];
195
+ this.stepInputs = [];
196
+ let currentValue: unknown = null;
197
+
198
+ // Get initial input from source fields
199
+ const initialValues = this.mapping.sourceFields
200
+ .map(f => this.getValueByPath(this.sampleData, f.path))
201
+ .map(v => String(v ?? ''));
202
+ const initialInput = initialValues.join(', ');
203
+
204
+ // For the first step, use the source fields from sample data
205
+ for (let i = 0; i < this.steps.length; i++) {
206
+ const step = this.steps[i];
207
+
208
+ if (i === 0) {
209
+ // First step: input is from source fields
210
+ this.stepInputs.push(initialInput);
211
+ // Check condition before applying transformation
212
+ if (this.transformationService.isConditionMet(initialInput, step)) {
213
+ currentValue = this.transformationService.applyTransformation(
214
+ this.sampleData,
215
+ this.mapping.sourceFields,
216
+ step
217
+ );
218
+ } else {
219
+ currentValue = initialInput; // Pass through if condition not met
220
+ }
221
+ } else {
222
+ // Subsequent steps: input is previous output
223
+ this.stepInputs.push(this.stepPreviews[i - 1]);
224
+ // Check condition before applying transformation
225
+ if (this.transformationService.isConditionMet(currentValue, step)) {
226
+ currentValue = this.transformationService.applyTransformationToValue(
227
+ currentValue,
228
+ step
229
+ );
230
+ }
231
+ // If condition not met, currentValue passes through unchanged
232
+ }
233
+
234
+ this.stepPreviews.push(String(currentValue ?? ''));
235
+ }
236
+
237
+ this.finalPreview = this.stepPreviews[this.stepPreviews.length - 1] || '';
238
+ }
239
+
240
+ private getValueByPath(obj: Record<string, unknown>, path: string): unknown {
241
+ return path.split('.').reduce((acc: unknown, part) => {
242
+ if (acc && typeof acc === 'object') {
243
+ return (acc as Record<string, unknown>)[part];
244
+ }
245
+ return undefined;
246
+ }, obj);
247
+ }
248
+
249
+ onConfigChange(): void {
250
+ this.updatePreview();
251
+ }
252
+
253
+ onSave(): void {
254
+ // Filter out 'direct' transformations if they're not the only one
255
+ const cleanedSteps = this.steps.length === 1
256
+ ? this.steps
257
+ : this.steps.filter(s => s.type !== 'direct');
258
+
259
+ // If all filtered out, keep at least one direct
260
+ const finalSteps = cleanedSteps.length > 0 ? cleanedSteps : [{ type: 'direct' as TransformationType }];
261
+
262
+ this.save.emit(finalSteps);
263
+ }
264
+
265
+ onDelete(): void {
266
+ this.delete.emit();
267
+ }
268
+
269
+ onClose(): void {
270
+ this.close.emit();
271
+ }
272
+
273
+ getSourceFieldNames(): string {
274
+ return this.mapping.sourceFields.map((f) => f.name).join(', ');
275
+ }
276
+
277
+ getPopoverStyle(): Record<string, string> {
278
+ return {
279
+ left: `${this.position.x}px`,
280
+ top: `${this.position.y}px`,
281
+ };
282
+ }
283
+
284
+ getStepTypeLabel(type: TransformationType): string {
285
+ const t = this.availableTransformations.find(t => t.type === type);
286
+ return t?.label || type;
287
+ }
288
+
289
+ // Condition methods
290
+ hasCondition(step: TransformationConfig): boolean {
291
+ return step.condition?.enabled === true;
292
+ }
293
+
294
+ toggleCondition(step: TransformationConfig, enabled: boolean): void {
295
+ if (enabled) {
296
+ if (!step.condition) {
297
+ step.condition = {
298
+ enabled: true,
299
+ root: this.createEmptyConditionGroup(),
300
+ };
301
+ } else {
302
+ step.condition.enabled = true;
303
+ }
304
+ } else {
305
+ if (step.condition) {
306
+ step.condition.enabled = false;
307
+ }
308
+ }
309
+ }
310
+
311
+ onConditionChange(step: TransformationConfig, group: FilterGroup): void {
312
+ if (step.condition) {
313
+ step.condition.root = group;
314
+ }
315
+ }
316
+
317
+ private createEmptyConditionGroup(): FilterGroup {
318
+ return {
319
+ id: `cond-${Date.now()}`,
320
+ type: 'group',
321
+ logic: 'and',
322
+ children: [],
323
+ };
324
+ }
325
+
326
+ getConditionSummary(step: TransformationConfig): string {
327
+ if (!step.condition?.enabled || !step.condition.root) return '';
328
+ return this.summarizeConditionGroup(step.condition.root);
329
+ }
330
+
331
+ private summarizeConditionGroup(group: FilterGroup): string {
332
+ if (group.children.length === 0) return '';
333
+
334
+ const parts = group.children.map(child => {
335
+ if (child.type === 'condition') {
336
+ const opLabel = this.getOperatorLabel(child.operator);
337
+ if (['isEmpty', 'isNotEmpty', 'isTrue', 'isFalse'].includes(child.operator)) {
338
+ return opLabel;
339
+ }
340
+ return `${opLabel} "${child.value}"`;
341
+ } else {
342
+ return `(${this.summarizeConditionGroup(child)})`;
343
+ }
344
+ });
345
+
346
+ return parts.join(` ${group.logic.toUpperCase()} `);
347
+ }
348
+
349
+ private getOperatorLabel(operator: string): string {
350
+ const labels: Record<string, string> = {
351
+ equals: '=',
352
+ notEquals: '!=',
353
+ contains: 'contains',
354
+ notContains: 'not contain',
355
+ startsWith: 'starts with',
356
+ endsWith: 'ends with',
357
+ isEmpty: 'is empty',
358
+ isNotEmpty: 'is not empty',
359
+ greaterThan: '>',
360
+ lessThan: '<',
361
+ greaterThanOrEqual: '>=',
362
+ lessThanOrEqual: '<=',
363
+ isTrue: 'is true',
364
+ isFalse: 'is false',
365
+ };
366
+ return labels[operator] || operator;
367
+ }
368
+ }
@@ -0,0 +1,164 @@
1
+ /**
2
+ * Standard JSON Schema (draft-07) TypeScript interfaces
3
+ */
4
+
5
+ export interface JsonSchema {
6
+ $schema?: string;
7
+ $id?: string;
8
+ title?: string;
9
+ description?: string;
10
+ type?: JsonSchemaType | JsonSchemaType[];
11
+ properties?: Record<string, JsonSchema>;
12
+ items?: JsonSchema;
13
+ required?: string[];
14
+ enum?: (string | number | boolean | null)[];
15
+ const?: unknown;
16
+ default?: unknown;
17
+
18
+ // String validations
19
+ minLength?: number;
20
+ maxLength?: number;
21
+ pattern?: string;
22
+ format?: string;
23
+
24
+ // Number validations
25
+ minimum?: number;
26
+ maximum?: number;
27
+ exclusiveMinimum?: number;
28
+ exclusiveMaximum?: number;
29
+ multipleOf?: number;
30
+
31
+ // Array validations
32
+ minItems?: number;
33
+ maxItems?: number;
34
+ uniqueItems?: boolean;
35
+
36
+ // Object validations
37
+ minProperties?: number;
38
+ maxProperties?: number;
39
+ additionalProperties?: boolean | JsonSchema;
40
+
41
+ // Combining schemas
42
+ allOf?: JsonSchema[];
43
+ anyOf?: JsonSchema[];
44
+ oneOf?: JsonSchema[];
45
+ not?: JsonSchema;
46
+
47
+ // References
48
+ $ref?: string;
49
+ definitions?: Record<string, JsonSchema>;
50
+ }
51
+
52
+ export type JsonSchemaType = 'string' | 'number' | 'integer' | 'boolean' | 'object' | 'array' | 'null';
53
+
54
+ /**
55
+ * Helper type for working with JSON schemas in the UI
56
+ */
57
+ export interface JsonSchemaField {
58
+ name: string;
59
+ path: string;
60
+ schema: JsonSchema;
61
+ children?: JsonSchemaField[];
62
+ expanded?: boolean;
63
+ }
64
+
65
+ /**
66
+ * Convert JSON Schema to flat field list for UI rendering
67
+ */
68
+ export function schemaToFields(schema: JsonSchema, parentPath: string = ''): JsonSchemaField[] {
69
+ const fields: JsonSchemaField[] = [];
70
+
71
+ if (schema.type === 'object' && schema.properties) {
72
+ for (const [name, propSchema] of Object.entries(schema.properties)) {
73
+ const path = parentPath ? `${parentPath}.${name}` : name;
74
+ const field: JsonSchemaField = {
75
+ name,
76
+ path,
77
+ schema: propSchema,
78
+ expanded: false,
79
+ };
80
+
81
+ if (propSchema.type === 'object' && propSchema.properties) {
82
+ field.children = schemaToFields(propSchema, path);
83
+ } else if (propSchema.type === 'array' && propSchema.items) {
84
+ field.children = schemaToFields(propSchema.items, `${path}[]`);
85
+ }
86
+
87
+ fields.push(field);
88
+ }
89
+ }
90
+
91
+ return fields;
92
+ }
93
+
94
+ /**
95
+ * Get the simple type for display purposes
96
+ */
97
+ export function getSchemaType(schema: JsonSchema): string {
98
+ if (Array.isArray(schema.type)) {
99
+ return schema.type.filter(t => t !== 'null').join(' | ');
100
+ }
101
+ if (schema.type === 'integer') {
102
+ return 'number';
103
+ }
104
+ if (schema.format === 'date' || schema.format === 'date-time') {
105
+ return 'date';
106
+ }
107
+ return schema.type || 'any';
108
+ }
109
+
110
+ /**
111
+ * Create an empty JSON Schema for a new schema definition
112
+ */
113
+ export function createEmptySchema(title: string = 'New Schema'): JsonSchema {
114
+ return {
115
+ $schema: 'http://json-schema.org/draft-07/schema#',
116
+ title,
117
+ type: 'object',
118
+ properties: {},
119
+ required: [],
120
+ };
121
+ }
122
+
123
+ /**
124
+ * Add a property to a schema
125
+ */
126
+ export function addProperty(
127
+ schema: JsonSchema,
128
+ name: string,
129
+ type: JsonSchemaType,
130
+ options?: { description?: string; required?: boolean }
131
+ ): JsonSchema {
132
+ const newSchema = { ...schema };
133
+ newSchema.properties = { ...newSchema.properties };
134
+
135
+ const propSchema: JsonSchema = { type };
136
+ if (options?.description) {
137
+ propSchema.description = options.description;
138
+ }
139
+
140
+ if (type === 'object') {
141
+ propSchema.properties = {};
142
+ } else if (type === 'array') {
143
+ propSchema.items = { type: 'string' };
144
+ }
145
+
146
+ newSchema.properties[name] = propSchema;
147
+
148
+ if (options?.required) {
149
+ newSchema.required = [...(newSchema.required || []), name];
150
+ }
151
+
152
+ return newSchema;
153
+ }
154
+
155
+ /**
156
+ * Remove a property from a schema
157
+ */
158
+ export function removeProperty(schema: JsonSchema, name: string): JsonSchema {
159
+ const newSchema = { ...schema };
160
+ newSchema.properties = { ...newSchema.properties };
161
+ delete newSchema.properties[name];
162
+ newSchema.required = (newSchema.required || []).filter(r => r !== name);
163
+ return newSchema;
164
+ }
@@ -0,0 +1,173 @@
1
+ export interface SchemaField {
2
+ id: string;
3
+ name: string;
4
+ type: 'string' | 'number' | 'boolean' | 'object' | 'array' | 'date';
5
+ path: string;
6
+ children?: SchemaField[];
7
+ expanded?: boolean;
8
+ description?: string;
9
+ isArrayItem?: boolean; // Marks fields that are children of an array
10
+ parentArrayPath?: string; // Path to parent array for context
11
+ }
12
+
13
+ export interface SchemaDefinition {
14
+ name: string;
15
+ fields: SchemaField[];
16
+ }
17
+
18
+ export type TransformationType =
19
+ | 'direct'
20
+ | 'concat'
21
+ | 'substring'
22
+ | 'replace'
23
+ | 'uppercase'
24
+ | 'lowercase'
25
+ | 'trim'
26
+ | 'mask'
27
+ | 'dateFormat'
28
+ | 'extractYear'
29
+ | 'extractMonth'
30
+ | 'extractDay'
31
+ | 'extractHour'
32
+ | 'extractMinute'
33
+ | 'extractSecond'
34
+ | 'numberFormat'
35
+ | 'template';
36
+
37
+ export interface TransformationCondition {
38
+ enabled: boolean;
39
+ root: FilterGroup;
40
+ }
41
+
42
+ export interface TransformationConfig {
43
+ type: TransformationType;
44
+ // For concat
45
+ separator?: string;
46
+ template?: string;
47
+ // For substring
48
+ startIndex?: number;
49
+ endIndex?: number;
50
+ // For replace
51
+ searchValue?: string;
52
+ replaceValue?: string;
53
+ // For date format
54
+ inputFormat?: string;
55
+ outputFormat?: string;
56
+ // For number format
57
+ decimalPlaces?: number;
58
+ prefix?: string;
59
+ suffix?: string;
60
+ // For mask
61
+ pattern?: string;
62
+ // Optional condition - transformation only applies if condition is met
63
+ condition?: TransformationCondition;
64
+ }
65
+
66
+ export interface FieldMapping {
67
+ id: string;
68
+ sourceFields: SchemaField[];
69
+ targetField: SchemaField;
70
+ transformations: TransformationConfig[]; // Array of transformation steps applied in sequence
71
+ // For array-to-array mappings
72
+ isArrayMapping?: boolean;
73
+ arrayMappingId?: string; // Reference to parent array mapping
74
+ // For array-to-object mappings
75
+ isArrayToObjectMapping?: boolean;
76
+ arrayToObjectMappingId?: string; // Reference to parent array-to-object mapping
77
+ }
78
+
79
+ export type FilterOperator =
80
+ | 'equals'
81
+ | 'notEquals'
82
+ | 'contains'
83
+ | 'notContains'
84
+ | 'startsWith'
85
+ | 'endsWith'
86
+ | 'greaterThan'
87
+ | 'lessThan'
88
+ | 'greaterThanOrEqual'
89
+ | 'lessThanOrEqual'
90
+ | 'isEmpty'
91
+ | 'isNotEmpty'
92
+ | 'isTrue'
93
+ | 'isFalse';
94
+
95
+ export interface FilterCondition {
96
+ id: string;
97
+ type: 'condition';
98
+ field: string; // Field path within the array item
99
+ fieldName: string; // Display name
100
+ operator: FilterOperator;
101
+ value: string | number | boolean;
102
+ valueType: 'string' | 'number' | 'boolean';
103
+ }
104
+
105
+ export interface FilterGroup {
106
+ id: string;
107
+ type: 'group';
108
+ logic: 'and' | 'or';
109
+ children: FilterItem[];
110
+ }
111
+
112
+ export type FilterItem = FilterCondition | FilterGroup;
113
+
114
+ export interface ArrayFilterConfig {
115
+ enabled: boolean;
116
+ root: FilterGroup;
117
+ }
118
+
119
+ export interface ArrayMapping {
120
+ id: string;
121
+ sourceArray: SchemaField;
122
+ targetArray: SchemaField;
123
+ itemMappings: FieldMapping[]; // Mappings for fields within the array items
124
+ filter?: ArrayFilterConfig; // Optional filter on source array
125
+ }
126
+
127
+ // Selection mode for array-to-object mapping
128
+ export type ArraySelectionMode = 'first' | 'last' | 'condition';
129
+
130
+ export interface ArraySelectorConfig {
131
+ mode: ArraySelectionMode;
132
+ condition?: FilterGroup; // Reuse filter group for condition mode
133
+ }
134
+
135
+ export interface ArrayToObjectMapping {
136
+ id: string;
137
+ sourceArray: SchemaField;
138
+ targetObject: SchemaField;
139
+ selector: ArraySelectorConfig; // How to select the single item
140
+ itemMappings: FieldMapping[]; // Mappings for fields within the selected item
141
+ }
142
+
143
+ export interface ConnectionPoint {
144
+ fieldId: string;
145
+ side: 'source' | 'target';
146
+ x: number;
147
+ y: number;
148
+ }
149
+
150
+ export interface Connection {
151
+ id: string;
152
+ mappingId: string;
153
+ sourcePoints: ConnectionPoint[];
154
+ targetPoint: ConnectionPoint;
155
+ transformations: TransformationConfig[];
156
+ }
157
+
158
+ export interface DragState {
159
+ isDragging: boolean;
160
+ sourceField: SchemaField | null;
161
+ startPoint: { x: number; y: number } | null;
162
+ currentPoint: { x: number; y: number } | null;
163
+ // For endpoint dragging (moving existing connection)
164
+ dragMode: 'new' | 'move-source' | 'move-target';
165
+ mappingId?: string; // The mapping being modified when moving endpoint
166
+ sourceFieldIndex?: number; // For multi-source mappings, which source is being moved
167
+ }
168
+
169
+ export interface DefaultValue {
170
+ id: string;
171
+ targetField: SchemaField;
172
+ value: string | number | boolean | Date | null;
173
+ }