retold-data-service 2.0.21 → 2.0.23

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 (37) hide show
  1. package/.quackage-comprehension-loader.json +19 -0
  2. package/bin/retold-data-service-clone.js +4 -1
  3. package/generate-bookstore-comprehension.js +645 -0
  4. package/package.json +7 -7
  5. package/source/Retold-Data-Service.js +30 -2
  6. package/source/services/comprehension-loader/ComprehensionLoader-Command-Load.js +345 -0
  7. package/source/services/comprehension-loader/ComprehensionLoader-Command-Schema.js +97 -0
  8. package/source/services/comprehension-loader/ComprehensionLoader-Command-Session.js +221 -0
  9. package/source/services/comprehension-loader/ComprehensionLoader-Command-WebUI.js +57 -0
  10. package/source/services/comprehension-loader/Retold-Data-Service-ComprehensionLoader.js +536 -0
  11. package/source/services/comprehension-loader/pict-app/Pict-Application-ComprehensionLoader-Configuration.json +9 -0
  12. package/source/services/comprehension-loader/pict-app/Pict-Application-ComprehensionLoader.js +86 -0
  13. package/source/services/comprehension-loader/pict-app/Pict-ComprehensionLoader-Bundle.js +6 -0
  14. package/source/services/comprehension-loader/pict-app/providers/Pict-Provider-ComprehensionLoader.js +760 -0
  15. package/source/services/comprehension-loader/pict-app/views/PictView-ComprehensionLoader-Layout.js +360 -0
  16. package/source/services/comprehension-loader/pict-app/views/PictView-ComprehensionLoader-Load.js +472 -0
  17. package/source/services/comprehension-loader/pict-app/views/PictView-ComprehensionLoader-Schema.js +119 -0
  18. package/source/services/comprehension-loader/pict-app/views/PictView-ComprehensionLoader-Session.js +269 -0
  19. package/source/services/comprehension-loader/pict-app/views/PictView-ComprehensionLoader-Source.js +330 -0
  20. package/source/services/comprehension-loader/web/comprehension-loader.js +6794 -0
  21. package/source/services/comprehension-loader/web/comprehension-loader.js.map +1 -0
  22. package/source/services/comprehension-loader/web/comprehension-loader.min.js +2 -0
  23. package/source/services/comprehension-loader/web/comprehension-loader.min.js.map +1 -0
  24. package/source/services/comprehension-loader/web/index.html +17 -0
  25. package/source/services/data-cloner/DataCloner-Command-Schema.js +407 -15
  26. package/source/services/data-cloner/Retold-Data-Service-DataCloner.js +59 -1
  27. package/source/services/data-cloner/pict-app/Pict-Application-DataCloner.js +1 -0
  28. package/source/services/data-cloner/pict-app/providers/Pict-Provider-DataCloner.js +125 -5
  29. package/source/services/data-cloner/pict-app/views/PictView-DataCloner-Connection.js +18 -8
  30. package/source/services/data-cloner/pict-app/views/PictView-DataCloner-Deploy.js +104 -1
  31. package/source/services/data-cloner/pict-app/views/PictView-DataCloner-Export.js +1 -1
  32. package/source/services/data-cloner/pict-app/views/PictView-DataCloner-Layout.js +12 -0
  33. package/source/services/data-cloner/web/data-cloner.js +201 -139
  34. package/source/services/data-cloner/web/data-cloner.js.map +1 -1
  35. package/source/services/data-cloner/web/data-cloner.min.js +1 -1
  36. package/source/services/data-cloner/web/data-cloner.min.js.map +1 -1
  37. package/test/RetoldDataService_tests.js +225 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "retold-data-service",
3
- "version": "2.0.21",
3
+ "version": "2.0.23",
4
4
  "description": "Serve up a whole model!",
5
5
  "main": "source/Retold-Data-Service.js",
6
6
  "bin": {
@@ -61,24 +61,24 @@
61
61
  "devDependencies": {
62
62
  "meadow-connection-sqlite": "^1.0.18",
63
63
  "puppeteer": "^24.38.0",
64
- "quackage": "^1.0.64",
64
+ "quackage": "^1.0.65",
65
65
  "stricture": "^4.0.2",
66
66
  "supertest": "^7.2.2"
67
67
  },
68
68
  "dependencies": {
69
69
  "bibliograph": "^0.1.4",
70
- "fable": "^3.1.63",
70
+ "fable": "^3.1.67",
71
71
  "fable-serviceproviderbase": "^3.0.19",
72
72
  "meadow": "^2.0.33",
73
73
  "meadow-connection-mysql": "^1.0.14",
74
- "meadow-endpoints": "^4.0.14",
75
- "meadow-integration": "^1.0.18",
74
+ "meadow-endpoints": "^4.0.15",
75
+ "meadow-integration": "^1.0.20",
76
76
  "meadow-migrationmanager": "^0.0.4",
77
77
  "orator": "^6.0.4",
78
78
  "orator-http-proxy": "^1.0.5",
79
- "orator-serviceserver-restify": "^2.0.9",
79
+ "orator-serviceserver-restify": "^2.0.10",
80
80
  "orator-static-server": "^2.0.4",
81
- "pict": "^1.0.357",
81
+ "pict": "^1.0.361",
82
82
  "pict-section-histogram": "^1.0.0",
83
83
  "pict-sessionmanager": "^1.0.2",
84
84
  "stricture": "^4.0.2"
@@ -18,6 +18,7 @@ const libRetoldDataServiceMeadowIntegration = require('./services/meadow-integra
18
18
  const libRetoldDataServiceMigrationManager = require('./services/migration-manager/Retold-Data-Service-MigrationManager.js');
19
19
  const libRetoldDataServiceDataCloner = require('./services/data-cloner/Retold-Data-Service-DataCloner.js');
20
20
  const libRetoldDataServiceIntegrationTelemetry = require('./services/integration-telemetry/Retold-Data-Service-IntegrationTelemetry.js');
21
+ const libRetoldDataServiceComprehensionLoader = require('./services/comprehension-loader/Retold-Data-Service-ComprehensionLoader.js');
21
22
 
22
23
  const defaultDataServiceSettings = (
23
24
  {
@@ -53,7 +54,11 @@ const defaultDataServiceSettings = (
53
54
  // Data cloner web UI (GET /clone/)
54
55
  DataClonerWebUI: false,
55
56
  // Integration telemetry API endpoints (/telemetry/*)
56
- IntegrationTelemetry: false
57
+ IntegrationTelemetry: false,
58
+ // Comprehension loader API endpoints (/comprehension_load/*)
59
+ ComprehensionLoader: false,
60
+ // Comprehension loader web UI (GET /comprehension_load/)
61
+ ComprehensionLoaderWebUI: false
57
62
  },
58
63
 
59
64
  // Migration manager configuration
@@ -79,6 +84,13 @@ const defaultDataServiceSettings = (
79
84
  RoutePrefix: '/telemetry',
80
85
  // Default tenant identifier when none is provided
81
86
  DefaultTenantID: 'default'
87
+ },
88
+
89
+ // Comprehension loader configuration
90
+ ComprehensionLoader:
91
+ {
92
+ // Route prefix for all comprehension loader endpoints (API + web UI)
93
+ RoutePrefix: '/comprehension_load'
82
94
  }
83
95
  });
84
96
 
@@ -142,6 +154,10 @@ class RetoldDataService extends libFableServiceProviderBase
142
154
  this.fable.serviceManager.addServiceType('RetoldDataServiceIntegrationTelemetry', libRetoldDataServiceIntegrationTelemetry);
143
155
  this.fable.serviceManager.instantiateServiceProvider('RetoldDataServiceIntegrationTelemetry');
144
156
 
157
+ // Register and instantiate the ComprehensionLoader service
158
+ this.fable.serviceManager.addServiceType('RetoldDataServiceComprehensionLoader', libRetoldDataServiceComprehensionLoader);
159
+ this.fable.serviceManager.instantiateServiceProvider('RetoldDataServiceComprehensionLoader');
160
+
145
161
  // Expose the DAL and MeadowEndpoints from the service on this object and on fable for backward compatibility
146
162
  this._DAL = this.fable.RetoldDataServiceMeadowEndpoints._DAL;
147
163
  this._MeadowEndpoints = this.fable.RetoldDataServiceMeadowEndpoints._MeadowEndpoints;
@@ -235,7 +251,7 @@ class RetoldDataService extends libFableServiceProviderBase
235
251
  this.fable.log.info(`The Retold Data Service is initializing...`);
236
252
 
237
253
  // Log endpoint configuration
238
- let tmpGroupNames = ['ConnectionManager', 'ModelManagerWrite', 'Stricture', 'MeadowIntegration', 'MeadowEndpoints', 'MigrationManager', 'MigrationManagerWebUI', 'DataCloner', 'DataClonerWebUI', 'IntegrationTelemetry'];
254
+ let tmpGroupNames = ['ConnectionManager', 'ModelManagerWrite', 'Stricture', 'MeadowIntegration', 'MeadowEndpoints', 'MigrationManager', 'MigrationManagerWebUI', 'DataCloner', 'DataClonerWebUI', 'IntegrationTelemetry', 'ComprehensionLoader', 'ComprehensionLoaderWebUI'];
239
255
  let tmpEnabledGroups = [];
240
256
  let tmpDisabledGroups = [];
241
257
  for (let i = 0; i < tmpGroupNames.length; i++)
@@ -336,6 +352,18 @@ class RetoldDataService extends libFableServiceProviderBase
336
352
  this.fable.RetoldDataServiceIntegrationTelemetry.connectRoutes(this.fable.OratorServiceServer);
337
353
  }
338
354
 
355
+ // ComprehensionLoader API routes (/comprehension_load/*)
356
+ if (this.isEndpointGroupEnabled('ComprehensionLoader'))
357
+ {
358
+ this.fable.RetoldDataServiceComprehensionLoader.connectRoutes(this.fable.OratorServiceServer);
359
+ }
360
+
361
+ // ComprehensionLoader Web UI routes (GET /comprehension_load/)
362
+ if (this.isEndpointGroupEnabled('ComprehensionLoaderWebUI'))
363
+ {
364
+ this.fable.RetoldDataServiceComprehensionLoader.connectWebUIRoutes(this.fable.OratorServiceServer);
365
+ }
366
+
339
367
  return fInitCallback();
340
368
  });
341
369
 
@@ -0,0 +1,345 @@
1
+ /**
2
+ * ComprehensionLoader Load Management Routes
3
+ *
4
+ * Registers /comprehension_load/comprehension/* and /comprehension_load/load/*
5
+ * endpoints for receiving comprehension data, starting/stopping the push,
6
+ * and polling progress.
7
+ *
8
+ * @param {Object} pComprehensionLoaderService - The RetoldDataServiceComprehensionLoader instance
9
+ * @param {Object} pOratorServiceServer - The Orator ServiceServer instance
10
+ */
11
+ module.exports = (pComprehensionLoaderService, pOratorServiceServer) =>
12
+ {
13
+ let tmpFable = pComprehensionLoaderService.fable;
14
+ let tmpLoadState = pComprehensionLoaderService.loadState;
15
+ let tmpPict = pComprehensionLoaderService.pict;
16
+ let tmpPrefix = pComprehensionLoaderService.routePrefix;
17
+
18
+ // POST /comprehension_load/comprehension/receive
19
+ pOratorServiceServer.postWithBodyParser(`${tmpPrefix}/comprehension/receive`,
20
+ (pRequest, pResponse, fNext) =>
21
+ {
22
+ let tmpBody = pRequest.body || {};
23
+
24
+ if (!tmpBody.Comprehension || (typeof tmpBody.Comprehension !== 'object'))
25
+ {
26
+ pResponse.send(400, { Success: false, Error: 'No valid Comprehension object provided in request body.' });
27
+ return fNext();
28
+ }
29
+
30
+ let tmpSummary = pComprehensionLoaderService.loadComprehension(tmpBody.Comprehension);
31
+
32
+ tmpFable.log.info(`Comprehension Loader: Received comprehension with ${tmpSummary.EntityList.length} entities, ${tmpSummary.TotalRecords} total records.`);
33
+
34
+ pResponse.send(200,
35
+ {
36
+ Success: true,
37
+ EntityCount: tmpSummary.EntityList.length,
38
+ EntityList: tmpSummary.EntityList,
39
+ RecordCounts: tmpSummary.RecordCounts,
40
+ TotalRecords: tmpSummary.TotalRecords
41
+ });
42
+ return fNext();
43
+ });
44
+
45
+ // POST /comprehension_load/comprehension/proxy-fetch
46
+ pOratorServiceServer.postWithBodyParser(`${tmpPrefix}/comprehension/proxy-fetch`,
47
+ (pRequest, pResponse, fNext) =>
48
+ {
49
+ let tmpBody = pRequest.body || {};
50
+ let tmpURL = tmpBody.URL;
51
+
52
+ if (!tmpURL || typeof tmpURL !== 'string')
53
+ {
54
+ pResponse.send(400, { Success: false, Error: 'URL is required.' });
55
+ return fNext();
56
+ }
57
+
58
+ tmpFable.log.info(`Comprehension Loader: Proxy-fetching comprehension from ${tmpURL}...`);
59
+
60
+ tmpPict.RestClient.getJSON(tmpURL,
61
+ (pError, pHTTPResponse, pData) =>
62
+ {
63
+ if (pError)
64
+ {
65
+ tmpFable.log.error(`Comprehension Loader: Proxy fetch error: ${pError.message || pError}`);
66
+ pResponse.send(500, { Success: false, Error: `Proxy fetch error: ${pError.message || pError}` });
67
+ return fNext();
68
+ }
69
+
70
+ if (!pHTTPResponse || pHTTPResponse.statusCode !== 200)
71
+ {
72
+ let tmpStatus = pHTTPResponse ? pHTTPResponse.statusCode : 'unknown';
73
+ pResponse.send(500, { Success: false, Error: `Proxy fetch returned HTTP ${tmpStatus}` });
74
+ return fNext();
75
+ }
76
+
77
+ if (!pData || typeof pData !== 'object')
78
+ {
79
+ pResponse.send(400, { Success: false, Error: 'Fetched content is not valid JSON.' });
80
+ return fNext();
81
+ }
82
+
83
+ let tmpSummary = pComprehensionLoaderService.loadComprehension(pData);
84
+
85
+ tmpFable.log.info(`Comprehension Loader: Proxy-fetched comprehension with ${tmpSummary.EntityList.length} entities, ${tmpSummary.TotalRecords} total records.`);
86
+
87
+ pResponse.send(200,
88
+ {
89
+ Success: true,
90
+ EntityCount: tmpSummary.EntityList.length,
91
+ EntityList: tmpSummary.EntityList,
92
+ RecordCounts: tmpSummary.RecordCounts,
93
+ TotalRecords: tmpSummary.TotalRecords
94
+ });
95
+ return fNext();
96
+ });
97
+ });
98
+
99
+ // GET /comprehension_load/comprehension
100
+ pOratorServiceServer.get(`${tmpPrefix}/comprehension`,
101
+ (pRequest, pResponse, fNext) =>
102
+ {
103
+ pResponse.send(200,
104
+ {
105
+ Loaded: tmpLoadState.ComprehensionData !== null,
106
+ EntityCount: tmpLoadState.ComprehensionEntityList.length,
107
+ EntityList: tmpLoadState.ComprehensionEntityList,
108
+ RecordCounts: tmpLoadState.ComprehensionRecordCounts,
109
+ TotalRecords: tmpLoadState.ComprehensionTotalRecords
110
+ });
111
+ return fNext();
112
+ });
113
+
114
+ // POST /comprehension_load/comprehension/clear
115
+ pOratorServiceServer.post(`${tmpPrefix}/comprehension/clear`,
116
+ (pRequest, pResponse, fNext) =>
117
+ {
118
+ pComprehensionLoaderService.clearComprehension();
119
+
120
+ tmpFable.log.info('Comprehension Loader: Comprehension data cleared.');
121
+
122
+ pResponse.send(200, { Success: true });
123
+ return fNext();
124
+ });
125
+
126
+ // POST /comprehension_load/load/start
127
+ pOratorServiceServer.post(`${tmpPrefix}/load/start`,
128
+ (pRequest, pResponse, fNext) =>
129
+ {
130
+ if (tmpLoadState.LoadRunning)
131
+ {
132
+ pResponse.send(409, { Success: false, Error: 'A load is already running.' });
133
+ return fNext();
134
+ }
135
+
136
+ if (!tmpLoadState.ComprehensionData)
137
+ {
138
+ pResponse.send(400, { Success: false, Error: 'No comprehension data loaded. Send data first via /comprehension/receive.' });
139
+ return fNext();
140
+ }
141
+
142
+ if (!tmpLoadState.SessionAuthenticated)
143
+ {
144
+ pResponse.send(400, { Success: false, Error: 'Session not authenticated. Configure and authenticate a session first.' });
145
+ return fNext();
146
+ }
147
+
148
+ let tmpBody = pRequest.body || {};
149
+ let tmpEntityList = tmpBody.Entities || tmpLoadState.ComprehensionEntityList;
150
+
151
+ // Reset progress
152
+ tmpLoadState.LoadProgress = {};
153
+
154
+ tmpFable.log.info(`Comprehension Loader: Starting load for ${tmpEntityList.length} entities...`);
155
+
156
+ // Start push asynchronously
157
+ pComprehensionLoaderService.pushEntities(tmpEntityList);
158
+
159
+ pResponse.send(200,
160
+ {
161
+ Success: true,
162
+ Entities: tmpEntityList,
163
+ TotalRecords: tmpLoadState.ComprehensionTotalRecords
164
+ });
165
+ return fNext();
166
+ });
167
+
168
+ // POST /comprehension_load/load/stop
169
+ pOratorServiceServer.post(`${tmpPrefix}/load/stop`,
170
+ (pRequest, pResponse, fNext) =>
171
+ {
172
+ if (!tmpLoadState.LoadRunning)
173
+ {
174
+ pResponse.send(200, { Success: true, Message: 'No load is running.' });
175
+ return fNext();
176
+ }
177
+
178
+ tmpLoadState.LoadStopping = true;
179
+ tmpFable.log.info('Comprehension Loader: Stop requested.');
180
+
181
+ pResponse.send(200, { Success: true, Message: 'Stop signal sent.' });
182
+ return fNext();
183
+ });
184
+
185
+ // GET /comprehension_load/load/status
186
+ pOratorServiceServer.get(`${tmpPrefix}/load/status`,
187
+ (pRequest, pResponse, fNext) =>
188
+ {
189
+ pResponse.send(200,
190
+ {
191
+ Running: tmpLoadState.LoadRunning,
192
+ Entities: tmpLoadState.LoadProgress
193
+ });
194
+ return fNext();
195
+ });
196
+
197
+ // GET /comprehension_load/load/live-status
198
+ pOratorServiceServer.get(`${tmpPrefix}/load/live-status`,
199
+ (pRequest, pResponse, fNext) =>
200
+ {
201
+ let tmpEntityNames = Object.keys(tmpLoadState.LoadProgress);
202
+ let tmpTotalPushed = 0;
203
+ let tmpTotalRecords = tmpLoadState.ComprehensionTotalRecords;
204
+ let tmpCompleted = 0;
205
+ let tmpErrors = 0;
206
+ let tmpPending = 0;
207
+ let tmpActiveEntity = null;
208
+ let tmpActiveProgress = null;
209
+
210
+ for (let i = 0; i < tmpEntityNames.length; i++)
211
+ {
212
+ let tmpName = tmpEntityNames[i];
213
+ let tmpP = tmpLoadState.LoadProgress[tmpName];
214
+ tmpTotalPushed += (tmpP.Pushed || 0);
215
+
216
+ if (tmpP.Status === 'Complete') tmpCompleted++;
217
+ else if (tmpP.Status === 'Error') { tmpErrors++; tmpCompleted++; }
218
+ else if (tmpP.Status === 'Pushing')
219
+ {
220
+ tmpActiveEntity = tmpName;
221
+ tmpActiveProgress = { Total: tmpP.Total, Pushed: tmpP.Pushed };
222
+ }
223
+ else tmpPending++;
224
+ }
225
+
226
+ // Determine phase
227
+ let tmpPhase = 'idle';
228
+ let tmpMessage = 'Idle';
229
+
230
+ if (!tmpLoadState.ComprehensionData && !tmpLoadState.LoadRunning)
231
+ {
232
+ tmpPhase = 'idle';
233
+ tmpMessage = 'No comprehension data loaded';
234
+ }
235
+ else if (tmpLoadState.ComprehensionData && !tmpLoadState.LoadRunning && tmpEntityNames.length === 0)
236
+ {
237
+ tmpPhase = 'ready';
238
+ tmpMessage = `Ready to load ${tmpLoadState.ComprehensionEntityList.length} entities (${tmpTotalRecords} records)`;
239
+ }
240
+ else if (tmpLoadState.LoadStopping)
241
+ {
242
+ tmpPhase = 'stopping';
243
+ tmpMessage = 'Stopping...';
244
+ }
245
+ else if (tmpLoadState.LoadRunning)
246
+ {
247
+ tmpPhase = 'loading';
248
+ if (tmpActiveEntity)
249
+ {
250
+ tmpMessage = `Pushing ${tmpActiveEntity}: ${tmpActiveProgress.Pushed} / ${tmpActiveProgress.Total} records`;
251
+ }
252
+ else
253
+ {
254
+ tmpMessage = `Loading ${tmpEntityNames.length} entities`;
255
+ }
256
+ }
257
+ else if (tmpEntityNames.length > 0 && !tmpLoadState.LoadRunning)
258
+ {
259
+ tmpPhase = 'complete';
260
+ tmpMessage = `Load complete: ${tmpCompleted} entities, ${tmpTotalPushed} records pushed`;
261
+ }
262
+
263
+ // Calculate elapsed time and ETA
264
+ let tmpElapsed = null;
265
+ let tmpETA = null;
266
+ if (tmpLoadState.LoadStartTime)
267
+ {
268
+ let tmpEndTime = tmpLoadState.LoadEndTime ? new Date(tmpLoadState.LoadEndTime).getTime() : Date.now();
269
+ let tmpElapsedMs = tmpEndTime - new Date(tmpLoadState.LoadStartTime).getTime();
270
+ let tmpElapsedSec = Math.round(tmpElapsedMs / 1000);
271
+
272
+ if (tmpElapsedSec < 60)
273
+ {
274
+ tmpElapsed = tmpElapsedSec + 's';
275
+ }
276
+ else
277
+ {
278
+ let tmpMin = Math.floor(tmpElapsedSec / 60);
279
+ let tmpSec = tmpElapsedSec % 60;
280
+ tmpElapsed = tmpMin + ':' + (tmpSec < 10 ? '0' : '') + tmpSec;
281
+ }
282
+
283
+ // ETA based on record-weighted progress
284
+ if (tmpLoadState.LoadRunning && tmpTotalPushed > 0 && tmpTotalRecords > 0)
285
+ {
286
+ let tmpRemainingRecords = tmpTotalRecords - tmpTotalPushed;
287
+ let tmpMsPerRecord = tmpElapsedMs / tmpTotalPushed;
288
+ let tmpETAMs = tmpRemainingRecords * tmpMsPerRecord;
289
+ let tmpETASec = Math.round(tmpETAMs / 1000);
290
+
291
+ if (tmpETASec < 60)
292
+ {
293
+ tmpETA = tmpETASec + 's';
294
+ }
295
+ else
296
+ {
297
+ let tmpETAMin = Math.floor(tmpETASec / 60);
298
+ let tmpETAS = tmpETASec % 60;
299
+ tmpETA = tmpETAMin + ':' + (tmpETAS < 10 ? '0' : '') + tmpETAS;
300
+ }
301
+ }
302
+ }
303
+
304
+ pResponse.send(200,
305
+ {
306
+ Phase: tmpPhase,
307
+ Message: tmpMessage,
308
+ TotalEntities: tmpEntityNames.length,
309
+ Completed: tmpCompleted,
310
+ Errors: tmpErrors,
311
+ Pending: tmpPending,
312
+ TotalPushed: tmpTotalPushed,
313
+ TotalRecords: tmpTotalRecords,
314
+ ActiveEntity: tmpActiveEntity,
315
+ ActiveProgress: tmpActiveProgress,
316
+ Elapsed: tmpElapsed,
317
+ ETA: tmpETA,
318
+ ThroughputSamples: tmpLoadState.ThroughputSamples || []
319
+ });
320
+ return fNext();
321
+ });
322
+
323
+ // GET /comprehension_load/load/report
324
+ pOratorServiceServer.get(`${tmpPrefix}/load/report`,
325
+ (pRequest, pResponse, fNext) =>
326
+ {
327
+ if (!tmpLoadState.LoadReport)
328
+ {
329
+ if (tmpLoadState.LoadStartTime && !tmpLoadState.LoadRunning)
330
+ {
331
+ pComprehensionLoaderService.generateLoadReport();
332
+ }
333
+ }
334
+
335
+ if (tmpLoadState.LoadReport)
336
+ {
337
+ pResponse.send(200, tmpLoadState.LoadReport);
338
+ }
339
+ else
340
+ {
341
+ pResponse.send(200, { ReportVersion: null, Message: 'No load report available.' });
342
+ }
343
+ return fNext();
344
+ });
345
+ };
@@ -0,0 +1,97 @@
1
+ /**
2
+ * ComprehensionLoader Schema Management Routes
3
+ *
4
+ * Registers /comprehension_load/schema/* endpoints for fetching remote
5
+ * schemas to discover valid entities on the target server.
6
+ *
7
+ * @param {Object} pComprehensionLoaderService - The RetoldDataServiceComprehensionLoader instance
8
+ * @param {Object} pOratorServiceServer - The Orator ServiceServer instance
9
+ */
10
+ module.exports = (pComprehensionLoaderService, pOratorServiceServer) =>
11
+ {
12
+ let tmpFable = pComprehensionLoaderService.fable;
13
+ let tmpLoadState = pComprehensionLoaderService.loadState;
14
+ let tmpPict = pComprehensionLoaderService.pict;
15
+ let tmpPrefix = pComprehensionLoaderService.routePrefix;
16
+
17
+ // POST /comprehension_load/schema/fetch
18
+ pOratorServiceServer.post(`${tmpPrefix}/schema/fetch`,
19
+ (pRequest, pResponse, fNext) =>
20
+ {
21
+ let tmpBody = pRequest.body || {};
22
+ let tmpSchemaURL = tmpBody.SchemaURL;
23
+
24
+ if (!tmpSchemaURL)
25
+ {
26
+ if (tmpLoadState.RemoteServerURL)
27
+ {
28
+ tmpSchemaURL = tmpLoadState.RemoteServerURL + 'Retold/Models';
29
+ }
30
+ else
31
+ {
32
+ pResponse.send(400, { Success: false, Error: 'SchemaURL is required (or configure a session first).' });
33
+ return fNext();
34
+ }
35
+ }
36
+
37
+ tmpFable.log.info(`Comprehension Loader: Fetching remote schema from ${tmpSchemaURL}...`);
38
+
39
+ tmpPict.RestClient.getJSON(tmpSchemaURL,
40
+ (pError, pHTTPResponse, pData) =>
41
+ {
42
+ if (pError)
43
+ {
44
+ tmpFable.log.error(`Comprehension Loader: Schema fetch error: ${pError.message || pError}`);
45
+ pResponse.send(500, { Success: false, Error: `Schema fetch error: ${pError.message || pError}` });
46
+ return fNext();
47
+ }
48
+
49
+ if (!pHTTPResponse || pHTTPResponse.statusCode !== 200)
50
+ {
51
+ let tmpStatus = pHTTPResponse ? pHTTPResponse.statusCode : 'unknown';
52
+ tmpFable.log.error(`Comprehension Loader: Schema fetch returned HTTP ${tmpStatus}`);
53
+ pResponse.send(500, { Success: false, Error: `Schema fetch returned HTTP ${tmpStatus}` });
54
+ return fNext();
55
+ }
56
+
57
+ tmpLoadState.RemoteSchema = pData;
58
+
59
+ let tmpEntityNames = [];
60
+ if (pData && pData.Tables)
61
+ {
62
+ tmpEntityNames = Object.keys(pData.Tables);
63
+ }
64
+ tmpLoadState.RemoteEntityList = tmpEntityNames;
65
+
66
+ tmpFable.log.info(`Comprehension Loader: Fetched schema with ${tmpEntityNames.length} entities: [${tmpEntityNames.join(', ')}]`);
67
+
68
+ pResponse.send(200,
69
+ {
70
+ Success: true,
71
+ SchemaURL: tmpSchemaURL,
72
+ EntityCount: tmpEntityNames.length,
73
+ Entities: tmpEntityNames
74
+ });
75
+ return fNext();
76
+ });
77
+ });
78
+
79
+ // GET /comprehension_load/schema
80
+ pOratorServiceServer.get(`${tmpPrefix}/schema`,
81
+ (pRequest, pResponse, fNext) =>
82
+ {
83
+ if (!tmpLoadState.RemoteSchema)
84
+ {
85
+ pResponse.send(200, { Fetched: false, Entities: [] });
86
+ return fNext();
87
+ }
88
+
89
+ pResponse.send(200,
90
+ {
91
+ Fetched: true,
92
+ EntityCount: tmpLoadState.RemoteEntityList.length,
93
+ Entities: tmpLoadState.RemoteEntityList
94
+ });
95
+ return fNext();
96
+ });
97
+ };