meadow-integration 1.0.1 → 1.0.4

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 (52) hide show
  1. package/CONTRIBUTING.md +50 -0
  2. package/README.md +223 -7
  3. package/docs/README.md +107 -7
  4. package/docs/_sidebar.md +38 -0
  5. package/docs/_topbar.md +7 -0
  6. package/docs/cli-reference.md +242 -0
  7. package/docs/comprehensions.md +98 -0
  8. package/docs/cover.md +11 -0
  9. package/docs/css/docuserve.css +73 -0
  10. package/docs/examples-walkthrough.md +138 -0
  11. package/docs/index.html +37 -20
  12. package/docs/integration-adapter.md +109 -0
  13. package/docs/mapping-files.md +140 -0
  14. package/docs/programmatic-api.md +173 -0
  15. package/docs/rest-api-reference.md +731 -0
  16. package/docs/retold-catalog.json +153 -0
  17. package/docs/retold-keyword-index.json +4828 -0
  18. package/examples/Example-001-CSV-Check.sh +29 -0
  19. package/examples/Example-002-CSV-Transform-Implicit.sh +31 -0
  20. package/examples/Example-003-CSV-Transform-CLI-Options.sh +39 -0
  21. package/examples/Example-004-CSV-Transform-Mapping-File.sh +41 -0
  22. package/examples/Example-005-Multi-Entity-Bookstore.sh +60 -0
  23. package/examples/Example-006-Multi-CSV-Intersect.sh +74 -0
  24. package/examples/Example-007-Comprehension-To-Array.sh +41 -0
  25. package/examples/Example-008-Comprehension-To-CSV.sh +51 -0
  26. package/examples/Example-009-JSON-Array-Transform.sh +46 -0
  27. package/examples/Example-010-Programmatic-API.js +138 -0
  28. package/examples/README.md +44 -0
  29. package/examples/output/.gitignore +2 -0
  30. package/package.json +7 -4
  31. package/source/Meadow-Integration.js +3 -1
  32. package/source/cli/Meadow-Integration-CLI-Program.js +4 -1
  33. package/source/cli/commands/Meadow-Integration-Command-ObjectArrayToCSV.js +49 -32
  34. package/source/cli/commands/Meadow-Integration-Command-Serve.js +51 -0
  35. package/source/restserver/Meadow-Integration-Server-Endpoints.js +83 -0
  36. package/source/restserver/Meadow-Integration-Server.js +86 -0
  37. package/source/restserver/endpoints/Endpoint-CSVCheck.js +91 -0
  38. package/source/restserver/endpoints/Endpoint-CSVTransform.js +189 -0
  39. package/source/restserver/endpoints/Endpoint-ComprehensionArray.js +121 -0
  40. package/source/restserver/endpoints/Endpoint-ComprehensionIntersect.js +166 -0
  41. package/source/restserver/endpoints/Endpoint-ComprehensionPush.js +209 -0
  42. package/source/restserver/endpoints/Endpoint-EntityFromTabularFolder.js +252 -0
  43. package/source/restserver/endpoints/Endpoint-JSONArrayTransform.js +238 -0
  44. package/source/restserver/endpoints/Endpoint-ObjectArrayToCSV.js +231 -0
  45. package/source/restserver/endpoints/Endpoint-TSVCheck.js +93 -0
  46. package/source/restserver/endpoints/Endpoint-TSVTransform.js +191 -0
  47. package/test/Meadow-Integration-Server_test.js +1170 -0
  48. package/test/data/test-comprehension-secondary.json +8 -0
  49. package/test/data/test-comprehension.json +8 -0
  50. package/test/data/test-small.csv +6 -0
  51. package/test/data/test-small.json +7 -0
  52. package/test/data/test-small.tsv +6 -0
@@ -0,0 +1,231 @@
1
+ /**
2
+ * POST /1.0/Comprehension/ToCSV
3
+ *
4
+ * Convert a comprehension or array of objects to CSV format.
5
+ *
6
+ * Request body (JSON):
7
+ * {
8
+ * "Records": [ {...}, {...}, ... ], // Array of record objects to convert
9
+ * "Entity": "MyEntity" // (optional) Entity name if passing a comprehension
10
+ * }
11
+ *
12
+ * OR comprehension-based:
13
+ * {
14
+ * "Comprehension": { ... }, // Comprehension object
15
+ * "Entity": "MyEntity" // (optional) Entity; auto-inferred if single
16
+ * }
17
+ *
18
+ * OR file-based:
19
+ *
20
+ * POST /1.0/Comprehension/ToCSVFromFile
21
+ * {
22
+ * "File": "/path/to/records.json", // JSON file (array or comprehension)
23
+ * "Entity": "MyEntity" // (optional) Entity name
24
+ * }
25
+ *
26
+ * Response: CSV text (Content-Type: text/csv)
27
+ */
28
+ module.exports = function(pFable, pOrator)
29
+ {
30
+ // In-memory conversion
31
+ pOrator.serviceServer.postWithBodyParser('/1.0/Comprehension/ToCSV',
32
+ (pRequest, pResponse, fNext) =>
33
+ {
34
+ let tmpBody = pRequest.body || {};
35
+
36
+ let tmpRecordArray = extractRecordArray(pFable, tmpBody);
37
+ if (tmpRecordArray.Error)
38
+ {
39
+ pResponse.send(400, tmpRecordArray);
40
+ return fNext();
41
+ }
42
+
43
+ let tmpCSV = generateCSV(tmpRecordArray.Records);
44
+ pResponse.setHeader('Content-Type', 'text/csv');
45
+ pResponse.setHeader('Content-Disposition', 'attachment; filename="export.csv"');
46
+ pResponse.sendRaw(200, tmpCSV);
47
+ return fNext();
48
+ });
49
+
50
+ // File-based conversion
51
+ pOrator.serviceServer.postWithBodyParser('/1.0/Comprehension/ToCSVFromFile',
52
+ (pRequest, pResponse, fNext) =>
53
+ {
54
+ let tmpBody = pRequest.body || {};
55
+
56
+ if (!tmpBody.File || (typeof(tmpBody.File) !== 'string'))
57
+ {
58
+ pResponse.send(400, { Error: 'No valid File path provided in request body.' });
59
+ return fNext();
60
+ }
61
+
62
+ pFable.instantiateServiceProvider('FilePersistence');
63
+
64
+ let tmpFilePath = pFable.FilePersistence.resolvePath(tmpBody.File);
65
+
66
+ if (!pFable.FilePersistence.existsSync(tmpFilePath))
67
+ {
68
+ pResponse.send(404, { Error: `File [${tmpFilePath}] does not exist.` });
69
+ return fNext();
70
+ }
71
+
72
+ let tmpRawRecordSet;
73
+ try
74
+ {
75
+ tmpRawRecordSet = JSON.parse(pFable.FilePersistence.readFileSync(tmpFilePath));
76
+ }
77
+ catch (pError)
78
+ {
79
+ pResponse.send(400, { Error: `Error parsing JSON file: ${pError.message}` });
80
+ return fNext();
81
+ }
82
+
83
+ // Build a body-like object for extraction
84
+ let tmpExtractBody = { Entity: tmpBody.Entity };
85
+ if (Array.isArray(tmpRawRecordSet))
86
+ {
87
+ tmpExtractBody.Records = tmpRawRecordSet;
88
+ }
89
+ else if (typeof(tmpRawRecordSet) === 'object')
90
+ {
91
+ tmpExtractBody.Comprehension = tmpRawRecordSet;
92
+ }
93
+
94
+ let tmpRecordArray = extractRecordArray(pFable, tmpExtractBody);
95
+ if (tmpRecordArray.Error)
96
+ {
97
+ pResponse.send(400, tmpRecordArray);
98
+ return fNext();
99
+ }
100
+
101
+ let tmpCSV = generateCSV(tmpRecordArray.Records);
102
+ pResponse.setHeader('Content-Type', 'text/csv');
103
+ pResponse.setHeader('Content-Disposition', 'attachment; filename="export.csv"');
104
+ pResponse.sendRaw(200, tmpCSV);
105
+ return fNext();
106
+ });
107
+ };
108
+
109
+ function extractRecordArray(pFable, pBody)
110
+ {
111
+ // Direct array
112
+ if (pBody.Records && Array.isArray(pBody.Records))
113
+ {
114
+ if (pBody.Records.length < 1)
115
+ {
116
+ return { Error: 'Records array is empty.' };
117
+ }
118
+ return { Records: pBody.Records };
119
+ }
120
+
121
+ // Comprehension
122
+ if (pBody.Comprehension && (typeof(pBody.Comprehension) === 'object'))
123
+ {
124
+ let tmpEntity = pBody.Entity;
125
+
126
+ if (tmpEntity)
127
+ {
128
+ if (pBody.Comprehension[tmpEntity])
129
+ {
130
+ let tmpRecords;
131
+ if (Array.isArray(pBody.Comprehension[tmpEntity]))
132
+ {
133
+ tmpRecords = pBody.Comprehension[tmpEntity];
134
+ }
135
+ else
136
+ {
137
+ tmpRecords = Object.values(pBody.Comprehension[tmpEntity]);
138
+ }
139
+ if (tmpRecords.length < 1)
140
+ {
141
+ return { Error: 'No records found for the specified entity.' };
142
+ }
143
+ return { Records: tmpRecords };
144
+ }
145
+ else
146
+ {
147
+ return { Error: `Entity [${tmpEntity}] not found in comprehension.` };
148
+ }
149
+ }
150
+ else
151
+ {
152
+ // Auto-detect single entity
153
+ let tmpKeys = Object.keys(pBody.Comprehension);
154
+ if (tmpKeys.length === 1 && typeof(pBody.Comprehension[tmpKeys[0]]) === 'object' && !Array.isArray(pBody.Comprehension[tmpKeys[0]]))
155
+ {
156
+ pFable.log.info(`Auto-detected entity [${tmpKeys[0]}] from comprehension.`);
157
+ let tmpRecords = Object.values(pBody.Comprehension[tmpKeys[0]]);
158
+ if (tmpRecords.length < 1)
159
+ {
160
+ return { Error: 'No records found in the auto-detected entity.' };
161
+ }
162
+ return { Records: tmpRecords };
163
+ }
164
+ else if (tmpKeys.length > 1)
165
+ {
166
+ return { Error: `Multiple entities found [${tmpKeys.join(', ')}]. Please specify an Entity.` };
167
+ }
168
+ else
169
+ {
170
+ return { Error: 'No entities found in the comprehension.' };
171
+ }
172
+ }
173
+ }
174
+
175
+ return { Error: 'No valid Records array or Comprehension object provided.' };
176
+ }
177
+
178
+ function flattenObject(pObject, pAddressPrefix)
179
+ {
180
+ let tmpPrefix = pAddressPrefix || '';
181
+ let tmpFlattenedObject = {};
182
+ for (const [pKey, pValue] of Object.entries(pObject))
183
+ {
184
+ const pPropertyPath = tmpPrefix ? `${tmpPrefix}.${pKey}` : pKey;
185
+ if (pValue && typeof pValue === 'object' && !Array.isArray(pValue))
186
+ {
187
+ Object.assign(tmpFlattenedObject, flattenObject(pValue, pPropertyPath));
188
+ }
189
+ else
190
+ {
191
+ tmpFlattenedObject[pPropertyPath] = pValue;
192
+ }
193
+ }
194
+ return tmpFlattenedObject;
195
+ }
196
+
197
+ function escapeCSVValue(pValue)
198
+ {
199
+ if (pValue === null || pValue === undefined) return '';
200
+ const str = String(pValue);
201
+ return /[",\n]/.test(str) ? `"${str.replace(/"/g, '""')}"` : str;
202
+ }
203
+
204
+ function generateCSV(pRecords)
205
+ {
206
+ const tmpAllKeysSet = new Set();
207
+ const tmpFlattenedRecords = [];
208
+
209
+ // First pass: flatten and collect all keys
210
+ for (const tmpRecord of pRecords)
211
+ {
212
+ const tmpFlattenedObject = flattenObject(tmpRecord);
213
+ tmpFlattenedRecords.push(tmpFlattenedObject);
214
+ for (const tmpKey of Object.keys(tmpFlattenedObject))
215
+ {
216
+ tmpAllKeysSet.add(tmpKey);
217
+ }
218
+ }
219
+
220
+ const tmpAllObjectKeys = Array.from(tmpAllKeysSet).sort();
221
+
222
+ // Build CSV string
223
+ let tmpCSV = tmpAllObjectKeys.join(',') + '\n';
224
+ for (const tmpFlatRecord of tmpFlattenedRecords)
225
+ {
226
+ const tmpRow = tmpAllObjectKeys.map((pKey) => escapeCSVValue(tmpFlatRecord[pKey])).join(',');
227
+ tmpCSV += tmpRow + '\n';
228
+ }
229
+
230
+ return tmpCSV;
231
+ }
@@ -0,0 +1,93 @@
1
+ const libFS = require('fs');
2
+ const libPath = require('path');
3
+ const libReadline = require('readline');
4
+
5
+ /**
6
+ * POST /1.0/TSV/Check
7
+ *
8
+ * Analyze a TSV file for statistics.
9
+ *
10
+ * Request body (JSON):
11
+ * {
12
+ * "File": "/absolute/path/to/file.tsv", // The TSV file to analyze
13
+ * "Records": false, // (optional) Include full record dump
14
+ * "QuoteDelimiter": "\"" // (optional) Quote delimiter character
15
+ * }
16
+ *
17
+ * Response: JSON statistics object
18
+ */
19
+ module.exports = function(pFable, pOrator)
20
+ {
21
+ pOrator.serviceServer.postWithBodyParser('/1.0/TSV/Check',
22
+ (pRequest, pResponse, fNext) =>
23
+ {
24
+ let tmpBody = pRequest.body || {};
25
+
26
+ if (!tmpBody.File || (typeof(tmpBody.File) !== 'string'))
27
+ {
28
+ pResponse.send(400, { Error: 'No valid File path provided in request body.' });
29
+ return fNext();
30
+ }
31
+
32
+ pFable.instantiateServiceProvider('FilePersistence');
33
+ pFable.addAndInstantiateServiceTypeIfNotExists('MeadowIntegrationTabularCheck', require('../../services/tabular/Service-TabularCheck.js'));
34
+
35
+ // Create a fresh CSVParser for each request to reset header state
36
+ let tmpCSVParser = pFable.instantiateServiceProviderWithoutRegistration('CSVParser');
37
+
38
+ // Configure for TSV
39
+ tmpCSVParser.Delimiter = '\t';
40
+ if (tmpBody.QuoteDelimiter)
41
+ {
42
+ tmpCSVParser.QuoteCharacter = tmpBody.QuoteDelimiter;
43
+ }
44
+
45
+ let tmpInputFilePath = pFable.FilePersistence.resolvePath(tmpBody.File);
46
+
47
+ if (!pFable.FilePersistence.existsSync(tmpInputFilePath))
48
+ {
49
+ pResponse.send(404, { Error: `File [${tmpInputFilePath}] does not exist.` });
50
+ return fNext();
51
+ }
52
+
53
+ let tmpStatistics = pFable.MeadowIntegrationTabularCheck.newStatisticsObject(tmpInputFilePath);
54
+ let tmpStoreFullRecord = (tmpBody.Records === true);
55
+
56
+ if (tmpStoreFullRecord)
57
+ {
58
+ tmpStatistics.Records = [];
59
+ }
60
+
61
+ const tmpReadline = libReadline.createInterface(
62
+ {
63
+ input: libFS.createReadStream(tmpInputFilePath),
64
+ crlfDelay: Infinity,
65
+ });
66
+
67
+ tmpReadline.on('line',
68
+ (pLine) =>
69
+ {
70
+ const tmpRecord = tmpCSVParser.parseCSVLine(pLine);
71
+ if (tmpRecord)
72
+ {
73
+ pFable.MeadowIntegrationTabularCheck.collectStatistics(tmpRecord, tmpStatistics, tmpStoreFullRecord);
74
+ }
75
+ });
76
+
77
+ tmpReadline.on('close',
78
+ () =>
79
+ {
80
+ pFable.log.info(`TSV Check: ${tmpStatistics.RowCount} rows, ${tmpStatistics.ColumnCount} columns in [${tmpInputFilePath}].`);
81
+ pResponse.send(200, tmpStatistics);
82
+ return fNext();
83
+ });
84
+
85
+ tmpReadline.on('error',
86
+ (pError) =>
87
+ {
88
+ pFable.log.error(`TSV Check error reading file [${tmpInputFilePath}]: ${pError}`, pError);
89
+ pResponse.send(500, { Error: `Error reading TSV file: ${pError.message}` });
90
+ return fNext();
91
+ });
92
+ });
93
+ };
@@ -0,0 +1,191 @@
1
+ const libFS = require('fs');
2
+ const libPath = require('path');
3
+ const libReadline = require('readline');
4
+
5
+ /**
6
+ * POST /1.0/TSV/Transform
7
+ *
8
+ * Transform a TSV file into a comprehension.
9
+ *
10
+ * Request body (JSON):
11
+ * {
12
+ * "File": "/absolute/path/to/file.tsv", // The TSV file to transform
13
+ * "Entity": "MyEntity", // (optional) Entity name
14
+ * "GUIDName": "GUIDMyEntity", // (optional) GUID column name
15
+ * "GUIDTemplate": "{~D:Record.id~}", // (optional) Pict template for GUID
16
+ * "Mappings": { "Col1": "{~D:Record.col1~}" }, // (optional) Column mappings object
17
+ * "MappingConfiguration": { ... }, // (optional) Full explicit mapping config
18
+ * "IncomingComprehension": { ... }, // (optional) Existing comprehension to merge into
19
+ * "Extended": false, // (optional) Return full operation state
20
+ * "QuoteDelimiter": "\"" // (optional) Quote delimiter character
21
+ * }
22
+ *
23
+ * Response: Comprehension JSON (or extended state if Extended=true)
24
+ */
25
+ module.exports = function(pFable, pOrator)
26
+ {
27
+ pOrator.serviceServer.postWithBodyParser('/1.0/TSV/Transform',
28
+ (pRequest, pResponse, fNext) =>
29
+ {
30
+ let tmpBody = pRequest.body || {};
31
+
32
+ if (!tmpBody.File || (typeof(tmpBody.File) !== 'string'))
33
+ {
34
+ pResponse.send(400, { Error: 'No valid File path provided in request body.' });
35
+ return fNext();
36
+ }
37
+
38
+ pFable.instantiateServiceProvider('FilePersistence');
39
+ pFable.addAndInstantiateServiceTypeIfNotExists('MeadowIntegrationTabularTransform', require('../../services/tabular/Service-TabularTransform.js'));
40
+
41
+ // Create a fresh CSVParser for each request to reset header state
42
+ let tmpCSVParser = pFable.instantiateServiceProviderWithoutRegistration('CSVParser');
43
+
44
+ // Configure for TSV
45
+ tmpCSVParser.Delimiter = '\t';
46
+ if (tmpBody.QuoteDelimiter)
47
+ {
48
+ tmpCSVParser.QuoteCharacter = tmpBody.QuoteDelimiter;
49
+ }
50
+
51
+ let tmpInputFilePath = pFable.FilePersistence.resolvePath(tmpBody.File);
52
+
53
+ if (!pFable.FilePersistence.existsSync(tmpInputFilePath))
54
+ {
55
+ pResponse.send(404, { Error: `File [${tmpInputFilePath}] does not exist.` });
56
+ return fNext();
57
+ }
58
+
59
+ let tmpMappingOutcome = pFable.MeadowIntegrationTabularTransform.newMappingOutcomeObject();
60
+
61
+ // Apply user configuration from request body
62
+ if (tmpBody.Entity)
63
+ {
64
+ tmpMappingOutcome.UserConfiguration.Entity = tmpBody.Entity;
65
+ }
66
+ if (tmpBody.GUIDName)
67
+ {
68
+ tmpMappingOutcome.UserConfiguration.GUIDName = tmpBody.GUIDName;
69
+ }
70
+ if (tmpBody.GUIDTemplate)
71
+ {
72
+ tmpMappingOutcome.UserConfiguration.GUIDTemplate = tmpBody.GUIDTemplate;
73
+ }
74
+ if (tmpBody.Mappings && (typeof(tmpBody.Mappings) === 'object'))
75
+ {
76
+ tmpMappingOutcome.UserConfiguration.Mappings = tmpBody.Mappings;
77
+ }
78
+
79
+ // Apply explicit mapping configuration
80
+ if (tmpBody.MappingConfiguration && (typeof(tmpBody.MappingConfiguration) === 'object'))
81
+ {
82
+ tmpMappingOutcome.ExplicitConfiguration = tmpBody.MappingConfiguration;
83
+ }
84
+
85
+ // Apply incoming comprehension
86
+ if (tmpBody.IncomingComprehension && (typeof(tmpBody.IncomingComprehension) === 'object'))
87
+ {
88
+ tmpMappingOutcome.ExistingComprehension = tmpBody.IncomingComprehension;
89
+ tmpMappingOutcome.Comprehension = JSON.parse(JSON.stringify(tmpBody.IncomingComprehension));
90
+ }
91
+
92
+ const tmpReadline = libReadline.createInterface(
93
+ {
94
+ input: libFS.createReadStream(tmpInputFilePath),
95
+ crlfDelay: Infinity,
96
+ });
97
+
98
+ tmpReadline.on('line',
99
+ (pLine) =>
100
+ {
101
+ const tmpIncomingRecord = tmpCSVParser.parseCSVLine(pLine);
102
+ tmpMappingOutcome.ParsedRowCount++;
103
+
104
+ if (tmpIncomingRecord)
105
+ {
106
+ if (!tmpMappingOutcome.ImplicitConfiguration)
107
+ {
108
+ tmpMappingOutcome.ImplicitConfiguration = pFable.MeadowIntegrationTabularTransform.generateMappingConfigurationPrototype(libPath.basename(tmpInputFilePath), tmpIncomingRecord);
109
+
110
+ if ((!tmpMappingOutcome.ExplicitConfiguration) || (typeof(tmpMappingOutcome.ExplicitConfiguration) != 'object'))
111
+ {
112
+ tmpMappingOutcome.Configuration = Object.assign({}, tmpMappingOutcome.ImplicitConfiguration, tmpMappingOutcome.UserConfiguration);
113
+ }
114
+ else
115
+ {
116
+ tmpMappingOutcome.Configuration = Object.assign({}, tmpMappingOutcome.ImplicitConfiguration, tmpMappingOutcome.ExplicitConfiguration, tmpMappingOutcome.UserConfiguration);
117
+ }
118
+
119
+ if (!('GUIDName' in tmpMappingOutcome.Configuration))
120
+ {
121
+ tmpMappingOutcome.Configuration.GUIDName = `GUID${tmpMappingOutcome.Configuration.Entity}`;
122
+ }
123
+
124
+ if (!(tmpMappingOutcome.Configuration.Entity in tmpMappingOutcome.Comprehension))
125
+ {
126
+ tmpMappingOutcome.Comprehension[tmpMappingOutcome.Configuration.Entity] = {};
127
+ }
128
+ }
129
+
130
+ let tmpMappingRecordSolution = (
131
+ {
132
+ IncomingRecord: tmpIncomingRecord,
133
+ MappingConfiguration: tmpMappingOutcome.Configuration,
134
+ MappingOutcome: tmpMappingOutcome,
135
+ RowIndex: tmpMappingOutcome.ParsedRowCount,
136
+ NewRecordsGUIDUniqueness: [],
137
+ NewRecordPrototype: {},
138
+ Fable: pFable,
139
+ Pict: pFable,
140
+ AppData: pFable.AppData
141
+ });
142
+
143
+ // Run the solvers for this record
144
+ let tmpSolverResultsObject = {};
145
+ if (tmpMappingOutcome.Configuration.Solvers && Array.isArray(tmpMappingOutcome.Configuration.Solvers))
146
+ {
147
+ for (let i = 0; i < tmpMappingOutcome.Configuration.Solvers.length; i++)
148
+ {
149
+ let tmpSolver = tmpMappingOutcome.Configuration.Solvers[i];
150
+ pFable.ExpressionParser.solve(tmpSolver, tmpMappingRecordSolution, tmpSolverResultsObject, pFable.manifest, tmpMappingRecordSolution);
151
+ }
152
+ }
153
+
154
+ if (tmpMappingOutcome.Configuration.MultipleGUIDUniqueness && tmpMappingRecordSolution.NewRecordsGUIDUniqueness.length > 0)
155
+ {
156
+ for (let i = 0; i < tmpMappingRecordSolution.NewRecordsGUIDUniqueness.length; i++)
157
+ {
158
+ pFable.MeadowIntegrationTabularTransform.addRecordToComprehension(tmpIncomingRecord, tmpMappingOutcome, tmpMappingRecordSolution.NewRecordPrototype, tmpMappingRecordSolution.NewRecordsGUIDUniqueness[i]);
159
+ }
160
+ }
161
+ else if (!tmpMappingOutcome.Configuration.MultipleGUIDUniqueness)
162
+ {
163
+ pFable.MeadowIntegrationTabularTransform.addRecordToComprehension(tmpIncomingRecord, tmpMappingOutcome, tmpMappingRecordSolution.NewRecordPrototype);
164
+ }
165
+ }
166
+ });
167
+
168
+ tmpReadline.on('close',
169
+ () =>
170
+ {
171
+ pFable.log.info(`TSV Transform: Parsed ${tmpMappingOutcome.ParsedRowCount} rows from [${tmpInputFilePath}].`);
172
+ if (tmpBody.Extended)
173
+ {
174
+ pResponse.send(200, tmpMappingOutcome);
175
+ }
176
+ else
177
+ {
178
+ pResponse.send(200, tmpMappingOutcome.Comprehension);
179
+ }
180
+ return fNext();
181
+ });
182
+
183
+ tmpReadline.on('error',
184
+ (pError) =>
185
+ {
186
+ pFable.log.error(`TSV Transform error reading file [${tmpInputFilePath}]: ${pError}`, pError);
187
+ pResponse.send(500, { Error: `Error reading TSV file: ${pError.message}` });
188
+ return fNext();
189
+ });
190
+ });
191
+ };