retold-data-service 2.0.12 → 2.0.14

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 (62) hide show
  1. package/README.md +64 -55
  2. package/docs/README.md +24 -66
  3. package/docs/_cover.md +7 -7
  4. package/docs/_sidebar.md +24 -11
  5. package/docs/_topbar.md +2 -0
  6. package/docs/api/constructor.md +67 -0
  7. package/docs/api/initializeDataEndpoints.md +54 -0
  8. package/docs/api/initializePersistenceEngine.md +34 -0
  9. package/docs/api/initializeService.md +44 -0
  10. package/docs/api/onAfterInitialize.md +48 -0
  11. package/docs/api/onBeforeInitialize.md +45 -0
  12. package/docs/api/onInitialize.md +38 -0
  13. package/docs/api/reference.md +35 -0
  14. package/docs/api/stopService.md +34 -0
  15. package/docs/architecture.md +78 -14
  16. package/docs/behavior-injection.md +121 -78
  17. package/docs/css/docuserve.css +73 -0
  18. package/docs/dal-access.md +135 -56
  19. package/docs/index.html +1 -1
  20. package/docs/quick-start.md +169 -0
  21. package/docs/retold-catalog.json +144 -0
  22. package/docs/retold-keyword-index.json +3530 -0
  23. package/example_applications/data-cloner/data/cloned.sqlite +0 -0
  24. package/example_applications/data-cloner/data/cloned.sqlite-shm +0 -0
  25. package/example_applications/data-cloner/data/cloned.sqlite-wal +0 -0
  26. package/example_applications/data-cloner/data-cloner-web.html +935 -0
  27. package/example_applications/data-cloner/data-cloner.js +1047 -0
  28. package/example_applications/data-cloner/package.json +19 -0
  29. package/package.json +13 -9
  30. package/source/Retold-Data-Service.js +225 -73
  31. package/source/services/Retold-Data-Service-ConnectionManager.js +277 -0
  32. package/source/services/Retold-Data-Service-MeadowEndpoints.js +217 -0
  33. package/source/services/Retold-Data-Service-ModelManager.js +335 -0
  34. package/source/services/meadow-integration/MeadowIntegration-Command-CSVCheck.js +85 -0
  35. package/source/services/meadow-integration/MeadowIntegration-Command-CSVTransform.js +180 -0
  36. package/source/services/meadow-integration/MeadowIntegration-Command-ComprehensionIntersect.js +153 -0
  37. package/source/services/meadow-integration/MeadowIntegration-Command-ComprehensionPush.js +190 -0
  38. package/source/services/meadow-integration/MeadowIntegration-Command-ComprehensionToArray.js +113 -0
  39. package/source/services/meadow-integration/MeadowIntegration-Command-ComprehensionToCSV.js +211 -0
  40. package/source/services/meadow-integration/MeadowIntegration-Command-EntityFromTabularFolder.js +244 -0
  41. package/source/services/meadow-integration/MeadowIntegration-Command-JSONArrayTransform.js +213 -0
  42. package/source/services/meadow-integration/MeadowIntegration-Command-TSVCheck.js +80 -0
  43. package/source/services/meadow-integration/MeadowIntegration-Command-TSVTransform.js +166 -0
  44. package/source/services/meadow-integration/Retold-Data-Service-MeadowIntegration.js +113 -0
  45. package/source/services/migration-manager/MigrationManager-Command-Connections.js +220 -0
  46. package/source/services/migration-manager/MigrationManager-Command-DiffMigrate.js +169 -0
  47. package/source/services/migration-manager/MigrationManager-Command-Schemas.js +532 -0
  48. package/source/services/migration-manager/MigrationManager-Command-WebUI.js +123 -0
  49. package/source/services/migration-manager/Retold-Data-Service-MigrationManager.js +357 -0
  50. package/source/services/stricture/Retold-Data-Service-Stricture.js +303 -0
  51. package/source/services/stricture/Stricture-Command-Compile.js +39 -0
  52. package/source/services/stricture/Stricture-Command-Generate-AuthorizationChart.js +14 -0
  53. package/source/services/stricture/Stricture-Command-Generate-DictionaryCSV.js +14 -0
  54. package/source/services/stricture/Stricture-Command-Generate-LaTeX.js +14 -0
  55. package/source/services/stricture/Stricture-Command-Generate-Markdown.js +14 -0
  56. package/source/services/stricture/Stricture-Command-Generate-Meadow.js +14 -0
  57. package/source/services/stricture/Stricture-Command-Generate-ModelGraph.js +14 -0
  58. package/source/services/stricture/Stricture-Command-Generate-MySQL.js +14 -0
  59. package/source/services/stricture/Stricture-Command-Generate-MySQLMigrate.js +14 -0
  60. package/source/services/stricture/Stricture-Command-Generate-Pict.js +14 -0
  61. package/source/services/stricture/Stricture-Command-Generate-TestObjectContainers.js +14 -0
  62. package/test/RetoldDataService_tests.js +161 -1
@@ -0,0 +1,335 @@
1
+ /**
2
+ * Retold Data Service - Model Manager
3
+ *
4
+ * Fable service that manages named model definitions and provides
5
+ * REST endpoints for uploading models, inspecting schemas, and
6
+ * connecting models to database connections.
7
+ *
8
+ * Routes are split into read (schema inspection, always available)
9
+ * and write (model upload/delete/connect, configurable) groups.
10
+ *
11
+ * Delegates to RetoldDataServiceMeadowEndpoints for DAL/endpoint
12
+ * creation and RetoldDataServiceConnectionManager for connection lookups.
13
+ *
14
+ * @author Steven Velozo <steven@velozo.com>
15
+ */
16
+ const libFableServiceProviderBase = require('fable-serviceproviderbase');
17
+
18
+ class RetoldDataServiceModelManager extends libFableServiceProviderBase
19
+ {
20
+ constructor(pFable, pOptions, pServiceHash)
21
+ {
22
+ super(pFable, pOptions, pServiceHash);
23
+
24
+ this.serviceType = 'RetoldDataServiceModelManager';
25
+
26
+ // Named model storage: { modelName: { Name, Model, ConnectionName, Initialized } }
27
+ this.modelDefinitions = {};
28
+ }
29
+
30
+ /**
31
+ * Add a named model definition. This stores the model but does NOT
32
+ * create DAL objects or endpoints. Use connectModelToConnection()
33
+ * to activate a model's endpoints.
34
+ *
35
+ * @param {string} pName - Model name
36
+ * @param {Object} pModelObject - Parsed stricture model (MeadowModel-Extended.json format)
37
+ * @param {function} fCallback - Callback invoked as fCallback(pError)
38
+ */
39
+ addModel(pName, pModelObject, fCallback)
40
+ {
41
+ if (!pName || typeof(pName) !== 'string')
42
+ {
43
+ return fCallback(new Error('Model name is required and must be a string.'));
44
+ }
45
+ if (!pModelObject || !pModelObject.Tables || typeof(pModelObject.Tables) !== 'object')
46
+ {
47
+ return fCallback(new Error('Model must include a Tables object.'));
48
+ }
49
+
50
+ if (this.modelDefinitions.hasOwnProperty(pName))
51
+ {
52
+ this.fable.log.warn(`Model [${pName}] already exists; overwriting.`);
53
+ }
54
+
55
+ this.modelDefinitions[pName] = (
56
+ {
57
+ Name: pName,
58
+ Model: pModelObject,
59
+ ConnectionName: false,
60
+ Initialized: false
61
+ });
62
+
63
+ this.fable.log.info(`Model [${pName}] added with ${Object.keys(pModelObject.Tables).length} entities.`);
64
+ return fCallback();
65
+ }
66
+
67
+ /**
68
+ * Remove a named model definition.
69
+ *
70
+ * @param {string} pName - Model name
71
+ * @param {function} fCallback - Callback invoked as fCallback(pError)
72
+ */
73
+ removeModel(pName, fCallback)
74
+ {
75
+ if (!this.modelDefinitions.hasOwnProperty(pName))
76
+ {
77
+ return fCallback(new Error(`Model [${pName}] does not exist.`));
78
+ }
79
+
80
+ delete this.modelDefinitions[pName];
81
+ this.fable.log.info(`Model [${pName}] removed.`);
82
+ return fCallback();
83
+ }
84
+
85
+ /**
86
+ * Get a single model's metadata.
87
+ *
88
+ * @param {string} pName - Model name
89
+ * @return {Object|false} Model metadata or false if not found
90
+ */
91
+ getModel(pName)
92
+ {
93
+ if (!this.modelDefinitions.hasOwnProperty(pName))
94
+ {
95
+ return false;
96
+ }
97
+
98
+ return this.modelDefinitions[pName];
99
+ }
100
+
101
+ /**
102
+ * Get summary metadata for all models.
103
+ *
104
+ * @return {Array} Array of model summaries
105
+ */
106
+ getModels()
107
+ {
108
+ let tmpModelList = [];
109
+ let tmpModelNames = Object.keys(this.modelDefinitions);
110
+
111
+ for (let i = 0; i < tmpModelNames.length; i++)
112
+ {
113
+ let tmpModelDef = this.modelDefinitions[tmpModelNames[i]];
114
+ tmpModelList.push(
115
+ {
116
+ Name: tmpModelDef.Name,
117
+ EntityCount: Object.keys(tmpModelDef.Model.Tables).length,
118
+ ConnectionName: tmpModelDef.ConnectionName,
119
+ Initialized: tmpModelDef.Initialized
120
+ });
121
+ }
122
+
123
+ return tmpModelList;
124
+ }
125
+
126
+ /**
127
+ * Connect a named model to a named connection, activating its
128
+ * DAL objects and CRUD endpoints.
129
+ *
130
+ * @param {string} pModelName - Model name (must exist in modelDefinitions)
131
+ * @param {string} pConnectionName - Connection name (must exist in ConnectionManager)
132
+ * @param {function} fCallback - Callback invoked as fCallback(pError)
133
+ */
134
+ connectModelToConnection(pModelName, pConnectionName, fCallback)
135
+ {
136
+ if (!this.modelDefinitions.hasOwnProperty(pModelName))
137
+ {
138
+ return fCallback(new Error(`Model [${pModelName}] does not exist.`));
139
+ }
140
+
141
+ let tmpConnectionManager = this.fable.RetoldDataServiceConnectionManager;
142
+ let tmpConnection = tmpConnectionManager.getConnection(pConnectionName);
143
+
144
+ if (!tmpConnection)
145
+ {
146
+ return fCallback(new Error(`Connection [${pConnectionName}] does not exist.`));
147
+ }
148
+ if (!tmpConnection.Active)
149
+ {
150
+ return fCallback(new Error(`Connection [${pConnectionName}] is not active.`));
151
+ }
152
+
153
+ let tmpModelDef = this.modelDefinitions[pModelName];
154
+
155
+ this.fable.log.info(`Connecting model [${pModelName}] to connection [${pConnectionName}] (provider: ${tmpConnection.Provider})...`);
156
+
157
+ // Delegate to MeadowEndpoints to create DAL objects and wire routes
158
+ this.fable.RetoldDataServiceMeadowEndpoints.loadModel(pModelName, tmpModelDef.Model, tmpConnection.Provider,
159
+ (pError) =>
160
+ {
161
+ if (pError)
162
+ {
163
+ return fCallback(pError);
164
+ }
165
+
166
+ tmpModelDef.ConnectionName = pConnectionName;
167
+ tmpModelDef.Initialized = true;
168
+ this.fable.log.info(`Model [${pModelName}] connected to [${pConnectionName}] and endpoints activated.`);
169
+ return fCallback();
170
+ });
171
+ }
172
+
173
+ /**
174
+ * Register schema READ routes on the Orator service server.
175
+ * These routes are always available regardless of endpoint configuration.
176
+ *
177
+ * @param {Object} pOratorServiceServer - The Orator ServiceServer instance
178
+ */
179
+ connectReadRoutes(pOratorServiceServer)
180
+ {
181
+ let tmpSelf = this;
182
+
183
+ // GET /1.0/Retold/Models — list all models
184
+ pOratorServiceServer.get('/1.0/Retold/Models',
185
+ (pRequest, pResponse, fNext) =>
186
+ {
187
+ pResponse.send(200, tmpSelf.getModels());
188
+ return fNext();
189
+ });
190
+
191
+ // GET /1.0/Retold/Model/:Name — get a single model's full definition
192
+ pOratorServiceServer.get('/1.0/Retold/Model/:Name',
193
+ (pRequest, pResponse, fNext) =>
194
+ {
195
+ let tmpModel = tmpSelf.getModel(pRequest.params.Name);
196
+ if (!tmpModel)
197
+ {
198
+ pResponse.send(404, { Error: `Model [${pRequest.params.Name}] not found.` });
199
+ return fNext();
200
+ }
201
+ pResponse.send(200, tmpModel);
202
+ return fNext();
203
+ });
204
+
205
+ // GET /1.0/Retold/Model/:Name/Entities — list entities in a model
206
+ pOratorServiceServer.get('/1.0/Retold/Model/:Name/Entities',
207
+ (pRequest, pResponse, fNext) =>
208
+ {
209
+ let tmpModel = tmpSelf.getModel(pRequest.params.Name);
210
+ if (!tmpModel)
211
+ {
212
+ pResponse.send(404, { Error: `Model [${pRequest.params.Name}] not found.` });
213
+ return fNext();
214
+ }
215
+ let tmpEntities = Object.keys(tmpModel.Model.Tables);
216
+ pResponse.send(200, { Name: pRequest.params.Name, Entities: tmpEntities });
217
+ return fNext();
218
+ });
219
+
220
+ // GET /1.0/Retold/Model/:Name/Entity/:EntityName — entity detail
221
+ pOratorServiceServer.get('/1.0/Retold/Model/:Name/Entity/:EntityName',
222
+ (pRequest, pResponse, fNext) =>
223
+ {
224
+ let tmpModel = tmpSelf.getModel(pRequest.params.Name);
225
+ if (!tmpModel)
226
+ {
227
+ pResponse.send(404, { Error: `Model [${pRequest.params.Name}] not found.` });
228
+ return fNext();
229
+ }
230
+
231
+ let tmpEntityName = pRequest.params.EntityName;
232
+ if (!tmpModel.Model.Tables.hasOwnProperty(tmpEntityName))
233
+ {
234
+ pResponse.send(404, { Error: `Entity [${tmpEntityName}] not found in model [${pRequest.params.Name}].` });
235
+ return fNext();
236
+ }
237
+
238
+ let tmpEntity = tmpModel.Model.Tables[tmpEntityName];
239
+ pResponse.send(200, tmpEntity);
240
+ return fNext();
241
+ });
242
+
243
+ this.fable.log.info('Retold Data Service ModelManager read routes registered.');
244
+ }
245
+
246
+ /**
247
+ * Register model management WRITE routes on the Orator service server.
248
+ * These routes allow runtime model upload, deletion, and activation.
249
+ *
250
+ * @param {Object} pOratorServiceServer - The Orator ServiceServer instance
251
+ */
252
+ connectWriteRoutes(pOratorServiceServer)
253
+ {
254
+ let tmpSelf = this;
255
+
256
+ // POST /1.0/Retold/Model — upload a named model
257
+ pOratorServiceServer.postWithBodyParser('/1.0/Retold/Model',
258
+ (pRequest, pResponse, fNext) =>
259
+ {
260
+ let tmpBody = pRequest.body;
261
+ if (!tmpBody || !tmpBody.Name || !tmpBody.Model)
262
+ {
263
+ pResponse.send(400, { Error: 'Request body must include Name and Model.' });
264
+ return fNext();
265
+ }
266
+
267
+ tmpSelf.addModel(tmpBody.Name, tmpBody.Model,
268
+ (pError) =>
269
+ {
270
+ if (pError)
271
+ {
272
+ pResponse.send(500, { Error: pError.message });
273
+ return fNext();
274
+ }
275
+
276
+ let tmpModelDef = tmpSelf.modelDefinitions[tmpBody.Name];
277
+ pResponse.send(200,
278
+ {
279
+ Name: tmpModelDef.Name,
280
+ EntityCount: Object.keys(tmpModelDef.Model.Tables).length,
281
+ ConnectionName: tmpModelDef.ConnectionName,
282
+ Initialized: tmpModelDef.Initialized
283
+ });
284
+ return fNext();
285
+ });
286
+ });
287
+
288
+ // DEL /1.0/Retold/Model/:Name — remove a model
289
+ pOratorServiceServer.del('/1.0/Retold/Model/:Name',
290
+ (pRequest, pResponse, fNext) =>
291
+ {
292
+ tmpSelf.removeModel(pRequest.params.Name,
293
+ (pError) =>
294
+ {
295
+ if (pError)
296
+ {
297
+ pResponse.send(404, { Error: pError.message });
298
+ return fNext();
299
+ }
300
+ pResponse.send(200, { Message: `Model [${pRequest.params.Name}] removed.` });
301
+ return fNext();
302
+ });
303
+ });
304
+
305
+ // POST /1.0/Retold/Model/:Name/Connect/:ConnectionName — connect model to connection
306
+ pOratorServiceServer.postWithBodyParser('/1.0/Retold/Model/:Name/Connect/:ConnectionName',
307
+ (pRequest, pResponse, fNext) =>
308
+ {
309
+ tmpSelf.connectModelToConnection(pRequest.params.Name, pRequest.params.ConnectionName,
310
+ (pError) =>
311
+ {
312
+ if (pError)
313
+ {
314
+ pResponse.send(500, { Error: pError.message });
315
+ return fNext();
316
+ }
317
+ let tmpModelDef = tmpSelf.modelDefinitions[pRequest.params.Name];
318
+ pResponse.send(200,
319
+ {
320
+ Name: tmpModelDef.Name,
321
+ EntityCount: Object.keys(tmpModelDef.Model.Tables).length,
322
+ ConnectionName: tmpModelDef.ConnectionName,
323
+ Initialized: tmpModelDef.Initialized
324
+ });
325
+ return fNext();
326
+ });
327
+ });
328
+
329
+ this.fable.log.info('Retold Data Service ModelManager write routes registered.');
330
+ }
331
+ }
332
+
333
+ module.exports = RetoldDataServiceModelManager;
334
+ module.exports.serviceType = 'RetoldDataServiceModelManager';
335
+ module.exports.default_configuration = {};
@@ -0,0 +1,85 @@
1
+ /**
2
+ * MeadowIntegration Command - CSV Check
3
+ *
4
+ * POST /1.0/Retold/MeadowIntegration/CSV/Check
5
+ *
6
+ * Analyze a CSV file for statistics.
7
+ *
8
+ * Body: { "File": "/path/to/file.csv", "Records": false, "QuoteDelimiter": "\"" }
9
+ * Returns: Statistics JSON
10
+ */
11
+ const libFS = require('fs');
12
+ const libReadline = require('readline');
13
+
14
+ module.exports = function(pIntegrationService, pOratorServiceServer)
15
+ {
16
+ let tmpPict = pIntegrationService.pict;
17
+
18
+ pOratorServiceServer.postWithBodyParser(`${pIntegrationService.routePrefix}/CSV/Check`,
19
+ (pRequest, pResponse, fNext) =>
20
+ {
21
+ let tmpBody = pRequest.body || {};
22
+
23
+ if (!tmpBody.File || (typeof(tmpBody.File) !== 'string'))
24
+ {
25
+ pResponse.send(400, { Error: 'No valid File path provided in request body.' });
26
+ return fNext();
27
+ }
28
+
29
+ let tmpInputFilePath = tmpPict.FilePersistence.resolvePath(tmpBody.File);
30
+
31
+ if (!tmpPict.FilePersistence.existsSync(tmpInputFilePath))
32
+ {
33
+ pResponse.send(404, { Error: `File [${tmpInputFilePath}] does not exist.` });
34
+ return fNext();
35
+ }
36
+
37
+ // Create a fresh CSVParser for each request to reset header state
38
+ let tmpCSVParser = tmpPict.instantiateServiceProviderWithoutRegistration('CSVParser');
39
+
40
+ if (tmpBody.QuoteDelimiter)
41
+ {
42
+ tmpCSVParser.QuoteCharacter = tmpBody.QuoteDelimiter;
43
+ }
44
+
45
+ let tmpStatistics = tmpPict.MeadowIntegrationTabularCheck.newStatisticsObject(tmpInputFilePath);
46
+ let tmpStoreFullRecord = (tmpBody.Records === true);
47
+
48
+ if (tmpStoreFullRecord)
49
+ {
50
+ tmpStatistics.Records = [];
51
+ }
52
+
53
+ const tmpReadline = libReadline.createInterface(
54
+ {
55
+ input: libFS.createReadStream(tmpInputFilePath),
56
+ crlfDelay: Infinity,
57
+ });
58
+
59
+ tmpReadline.on('line',
60
+ (pLine) =>
61
+ {
62
+ const tmpRecord = tmpCSVParser.parseCSVLine(pLine);
63
+ if (tmpRecord)
64
+ {
65
+ tmpPict.MeadowIntegrationTabularCheck.collectStatistics(tmpRecord, tmpStatistics, tmpStoreFullRecord);
66
+ }
67
+ });
68
+
69
+ tmpReadline.on('close',
70
+ () =>
71
+ {
72
+ tmpPict.log.info(`CSV Check: ${tmpStatistics.RowCount} rows, ${tmpStatistics.ColumnCount} columns in [${tmpInputFilePath}].`);
73
+ pResponse.send(200, tmpStatistics);
74
+ return fNext();
75
+ });
76
+
77
+ tmpReadline.on('error',
78
+ (pError) =>
79
+ {
80
+ tmpPict.log.error(`CSV Check error reading file [${tmpInputFilePath}]: ${pError}`, pError);
81
+ pResponse.send(500, { Error: `Error reading CSV file: ${pError.message}` });
82
+ return fNext();
83
+ });
84
+ });
85
+ };
@@ -0,0 +1,180 @@
1
+ /**
2
+ * MeadowIntegration Command - CSV Transform
3
+ *
4
+ * POST /1.0/Retold/MeadowIntegration/CSV/Transform
5
+ *
6
+ * Transform a CSV file into a comprehension using mapping configuration.
7
+ *
8
+ * Body: {
9
+ * "File": "/path/to/file.csv",
10
+ * "Entity": "MyEntity",
11
+ * "GUIDTemplate": "{~D:Record.id~}",
12
+ * "Mappings": { "Col1": "{~D:Record.col1~}" },
13
+ * "MappingConfiguration": { ... },
14
+ * "IncomingComprehension": { ... },
15
+ * "Extended": false,
16
+ * "QuoteDelimiter": "\""
17
+ * }
18
+ * Returns: Comprehension JSON (or extended state if Extended=true)
19
+ */
20
+ const libFS = require('fs');
21
+ const libPath = require('path');
22
+ const libReadline = require('readline');
23
+
24
+ module.exports = function(pIntegrationService, pOratorServiceServer)
25
+ {
26
+ let tmpPict = pIntegrationService.pict;
27
+
28
+ pOratorServiceServer.postWithBodyParser(`${pIntegrationService.routePrefix}/CSV/Transform`,
29
+ (pRequest, pResponse, fNext) =>
30
+ {
31
+ let tmpBody = pRequest.body || {};
32
+
33
+ if (!tmpBody.File || (typeof(tmpBody.File) !== 'string'))
34
+ {
35
+ pResponse.send(400, { Error: 'No valid File path provided in request body.' });
36
+ return fNext();
37
+ }
38
+
39
+ let tmpInputFilePath = tmpPict.FilePersistence.resolvePath(tmpBody.File);
40
+
41
+ if (!tmpPict.FilePersistence.existsSync(tmpInputFilePath))
42
+ {
43
+ pResponse.send(404, { Error: `File [${tmpInputFilePath}] does not exist.` });
44
+ return fNext();
45
+ }
46
+
47
+ let tmpCSVParser = tmpPict.instantiateServiceProviderWithoutRegistration('CSVParser');
48
+
49
+ if (tmpBody.QuoteDelimiter)
50
+ {
51
+ tmpCSVParser.QuoteCharacter = tmpBody.QuoteDelimiter;
52
+ }
53
+
54
+ let tmpMappingOutcome = tmpPict.MeadowIntegrationTabularTransform.newMappingOutcomeObject();
55
+
56
+ // Apply user configuration from request body
57
+ if (tmpBody.Entity)
58
+ {
59
+ tmpMappingOutcome.UserConfiguration.Entity = tmpBody.Entity;
60
+ }
61
+ if (tmpBody.GUIDName)
62
+ {
63
+ tmpMappingOutcome.UserConfiguration.GUIDName = tmpBody.GUIDName;
64
+ }
65
+ if (tmpBody.GUIDTemplate)
66
+ {
67
+ tmpMappingOutcome.UserConfiguration.GUIDTemplate = tmpBody.GUIDTemplate;
68
+ }
69
+ if (tmpBody.Mappings && (typeof(tmpBody.Mappings) === 'object'))
70
+ {
71
+ tmpMappingOutcome.UserConfiguration.Mappings = tmpBody.Mappings;
72
+ }
73
+ if (tmpBody.MappingConfiguration && (typeof(tmpBody.MappingConfiguration) === 'object'))
74
+ {
75
+ tmpMappingOutcome.ExplicitConfiguration = tmpBody.MappingConfiguration;
76
+ }
77
+ if (tmpBody.IncomingComprehension && (typeof(tmpBody.IncomingComprehension) === 'object'))
78
+ {
79
+ tmpMappingOutcome.ExistingComprehension = tmpBody.IncomingComprehension;
80
+ tmpMappingOutcome.Comprehension = JSON.parse(JSON.stringify(tmpBody.IncomingComprehension));
81
+ }
82
+
83
+ const tmpReadline = libReadline.createInterface(
84
+ {
85
+ input: libFS.createReadStream(tmpInputFilePath),
86
+ crlfDelay: Infinity,
87
+ });
88
+
89
+ tmpReadline.on('line',
90
+ (pLine) =>
91
+ {
92
+ const tmpIncomingRecord = tmpCSVParser.parseCSVLine(pLine);
93
+ tmpMappingOutcome.ParsedRowCount++;
94
+
95
+ if (tmpIncomingRecord)
96
+ {
97
+ if (!tmpMappingOutcome.ImplicitConfiguration)
98
+ {
99
+ tmpMappingOutcome.ImplicitConfiguration = tmpPict.MeadowIntegrationTabularTransform.generateMappingConfigurationPrototype(libPath.basename(tmpInputFilePath), tmpIncomingRecord);
100
+
101
+ if ((!tmpMappingOutcome.ExplicitConfiguration) || (typeof(tmpMappingOutcome.ExplicitConfiguration) != 'object'))
102
+ {
103
+ tmpMappingOutcome.Configuration = Object.assign({}, tmpMappingOutcome.ImplicitConfiguration, tmpMappingOutcome.UserConfiguration);
104
+ }
105
+ else
106
+ {
107
+ tmpMappingOutcome.Configuration = Object.assign({}, tmpMappingOutcome.ImplicitConfiguration, tmpMappingOutcome.ExplicitConfiguration, tmpMappingOutcome.UserConfiguration);
108
+ }
109
+
110
+ if (!('GUIDName' in tmpMappingOutcome.Configuration))
111
+ {
112
+ tmpMappingOutcome.Configuration.GUIDName = `GUID${tmpMappingOutcome.Configuration.Entity}`;
113
+ }
114
+
115
+ if (!(tmpMappingOutcome.Configuration.Entity in tmpMappingOutcome.Comprehension))
116
+ {
117
+ tmpMappingOutcome.Comprehension[tmpMappingOutcome.Configuration.Entity] = {};
118
+ }
119
+ }
120
+
121
+ let tmpMappingRecordSolution = (
122
+ {
123
+ IncomingRecord: tmpIncomingRecord,
124
+ MappingConfiguration: tmpMappingOutcome.Configuration,
125
+ MappingOutcome: tmpMappingOutcome,
126
+ RowIndex: tmpMappingOutcome.ParsedRowCount,
127
+ NewRecordsGUIDUniqueness: [],
128
+ NewRecordPrototype: {},
129
+ Fable: tmpPict,
130
+ Pict: tmpPict,
131
+ AppData: tmpPict.AppData
132
+ });
133
+
134
+ let tmpSolverResultsObject = {};
135
+ if (tmpMappingOutcome.Configuration.Solvers && Array.isArray(tmpMappingOutcome.Configuration.Solvers))
136
+ {
137
+ for (let i = 0; i < tmpMappingOutcome.Configuration.Solvers.length; i++)
138
+ {
139
+ tmpPict.ExpressionParser.solve(tmpMappingOutcome.Configuration.Solvers[i], tmpMappingRecordSolution, tmpSolverResultsObject, tmpPict.manifest, tmpMappingRecordSolution);
140
+ }
141
+ }
142
+
143
+ if (tmpMappingOutcome.Configuration.MultipleGUIDUniqueness && tmpMappingRecordSolution.NewRecordsGUIDUniqueness.length > 0)
144
+ {
145
+ for (let i = 0; i < tmpMappingRecordSolution.NewRecordsGUIDUniqueness.length; i++)
146
+ {
147
+ tmpPict.MeadowIntegrationTabularTransform.addRecordToComprehension(tmpIncomingRecord, tmpMappingOutcome, tmpMappingRecordSolution.NewRecordPrototype, tmpMappingRecordSolution.NewRecordsGUIDUniqueness[i]);
148
+ }
149
+ }
150
+ else if (!tmpMappingOutcome.Configuration.MultipleGUIDUniqueness)
151
+ {
152
+ tmpPict.MeadowIntegrationTabularTransform.addRecordToComprehension(tmpIncomingRecord, tmpMappingOutcome, tmpMappingRecordSolution.NewRecordPrototype);
153
+ }
154
+ }
155
+ });
156
+
157
+ tmpReadline.on('close',
158
+ () =>
159
+ {
160
+ tmpPict.log.info(`CSV Transform: Parsed ${tmpMappingOutcome.ParsedRowCount} rows from [${tmpInputFilePath}].`);
161
+ if (tmpBody.Extended)
162
+ {
163
+ pResponse.send(200, tmpMappingOutcome);
164
+ }
165
+ else
166
+ {
167
+ pResponse.send(200, tmpMappingOutcome.Comprehension);
168
+ }
169
+ return fNext();
170
+ });
171
+
172
+ tmpReadline.on('error',
173
+ (pError) =>
174
+ {
175
+ tmpPict.log.error(`CSV Transform error reading file [${tmpInputFilePath}]: ${pError}`, pError);
176
+ pResponse.send(500, { Error: `Error reading CSV file: ${pError.message}` });
177
+ return fNext();
178
+ });
179
+ });
180
+ };