retold-facto 0.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 (92) hide show
  1. package/.claude/launch.json +11 -0
  2. package/.dockerignore +8 -0
  3. package/.quackage.json +19 -0
  4. package/Dockerfile +26 -0
  5. package/bin/retold-facto.js +909 -0
  6. package/examples/facto-government-data.sqlite +0 -0
  7. package/examples/government-data-catalog.json +137 -0
  8. package/examples/government-data-loader.js +1432 -0
  9. package/package.json +91 -0
  10. package/scripts/facto-download.js +425 -0
  11. package/source/Retold-Facto.js +1042 -0
  12. package/source/services/Retold-Facto-BeaconProvider.js +511 -0
  13. package/source/services/Retold-Facto-CatalogManager.js +1252 -0
  14. package/source/services/Retold-Facto-DataLakeService.js +1642 -0
  15. package/source/services/Retold-Facto-DatasetManager.js +417 -0
  16. package/source/services/Retold-Facto-IngestEngine.js +1315 -0
  17. package/source/services/Retold-Facto-ProjectionEngine.js +3960 -0
  18. package/source/services/Retold-Facto-RecordManager.js +360 -0
  19. package/source/services/Retold-Facto-SchemaManager.js +1110 -0
  20. package/source/services/Retold-Facto-SourceFolderScanner.js +2243 -0
  21. package/source/services/Retold-Facto-SourceManager.js +730 -0
  22. package/source/services/Retold-Facto-StoreConnectionManager.js +441 -0
  23. package/source/services/Retold-Facto-ThroughputMonitor.js +478 -0
  24. package/source/services/web-app/codemirror-entry.js +7 -0
  25. package/source/services/web-app/pict-app/Pict-Application-Facto-Configuration.json +9 -0
  26. package/source/services/web-app/pict-app/Pict-Application-Facto.js +70 -0
  27. package/source/services/web-app/pict-app/Pict-Facto-Bundle.js +11 -0
  28. package/source/services/web-app/pict-app/providers/Pict-Provider-Facto-UI.js +66 -0
  29. package/source/services/web-app/pict-app/providers/Pict-Provider-Facto.js +69 -0
  30. package/source/services/web-app/pict-app/providers/facto-api/Facto-API-Catalog.js +93 -0
  31. package/source/services/web-app/pict-app/providers/facto-api/Facto-API-Connections.js +42 -0
  32. package/source/services/web-app/pict-app/providers/facto-api/Facto-API-Datasets.js +605 -0
  33. package/source/services/web-app/pict-app/providers/facto-api/Facto-API-Projections.js +188 -0
  34. package/source/services/web-app/pict-app/providers/facto-api/Facto-API-Scanner.js +80 -0
  35. package/source/services/web-app/pict-app/providers/facto-api/Facto-API-Schema.js +116 -0
  36. package/source/services/web-app/pict-app/providers/facto-api/Facto-API-Sources.js +104 -0
  37. package/source/services/web-app/pict-app/views/PictView-Facto-Catalog.js +526 -0
  38. package/source/services/web-app/pict-app/views/PictView-Facto-Datasets.js +173 -0
  39. package/source/services/web-app/pict-app/views/PictView-Facto-Ingest.js +259 -0
  40. package/source/services/web-app/pict-app/views/PictView-Facto-Layout.js +191 -0
  41. package/source/services/web-app/pict-app/views/PictView-Facto-Projections.js +231 -0
  42. package/source/services/web-app/pict-app/views/PictView-Facto-Records.js +326 -0
  43. package/source/services/web-app/pict-app/views/PictView-Facto-Scanner.js +624 -0
  44. package/source/services/web-app/pict-app/views/PictView-Facto-Sources.js +201 -0
  45. package/source/services/web-app/pict-app/views/PictView-Facto-Throughput.js +456 -0
  46. package/source/services/web-app/pict-app-full/Pict-Application-Facto-Full-Configuration.json +14 -0
  47. package/source/services/web-app/pict-app-full/Pict-Application-Facto-Full.js +391 -0
  48. package/source/services/web-app/pict-app-full/providers/PictRouter-Facto-Configuration.json +56 -0
  49. package/source/services/web-app/pict-app-full/views/PictView-Facto-Full-BottomBar.js +68 -0
  50. package/source/services/web-app/pict-app-full/views/PictView-Facto-Full-Connections.js +340 -0
  51. package/source/services/web-app/pict-app-full/views/PictView-Facto-Full-Dashboard.js +149 -0
  52. package/source/services/web-app/pict-app-full/views/PictView-Facto-Full-Dashboards.js +819 -0
  53. package/source/services/web-app/pict-app-full/views/PictView-Facto-Full-Datasets.js +178 -0
  54. package/source/services/web-app/pict-app-full/views/PictView-Facto-Full-IngestJobs.js +99 -0
  55. package/source/services/web-app/pict-app-full/views/PictView-Facto-Full-Layout.js +62 -0
  56. package/source/services/web-app/pict-app-full/views/PictView-Facto-Full-MappingEditor.js +158 -0
  57. package/source/services/web-app/pict-app-full/views/PictView-Facto-Full-ProjectionDetail.js +1120 -0
  58. package/source/services/web-app/pict-app-full/views/PictView-Facto-Full-Projections.js +172 -0
  59. package/source/services/web-app/pict-app-full/views/PictView-Facto-Full-QueryPanel.js +119 -0
  60. package/source/services/web-app/pict-app-full/views/PictView-Facto-Full-RecordViewer.js +663 -0
  61. package/source/services/web-app/pict-app-full/views/PictView-Facto-Full-Records.js +648 -0
  62. package/source/services/web-app/pict-app-full/views/PictView-Facto-Full-Scanner.js +1017 -0
  63. package/source/services/web-app/pict-app-full/views/PictView-Facto-Full-SchemaDetail.js +1404 -0
  64. package/source/services/web-app/pict-app-full/views/PictView-Facto-Full-SchemaDocEditor.js +1036 -0
  65. package/source/services/web-app/pict-app-full/views/PictView-Facto-Full-SchemaEditor.js +636 -0
  66. package/source/services/web-app/pict-app-full/views/PictView-Facto-Full-SchemaResearch.js +357 -0
  67. package/source/services/web-app/pict-app-full/views/PictView-Facto-Full-SourceDetail.js +822 -0
  68. package/source/services/web-app/pict-app-full/views/PictView-Facto-Full-SourceEditor.js +1036 -0
  69. package/source/services/web-app/pict-app-full/views/PictView-Facto-Full-SourceResearch.js +487 -0
  70. package/source/services/web-app/pict-app-full/views/PictView-Facto-Full-Sources.js +165 -0
  71. package/source/services/web-app/pict-app-full/views/PictView-Facto-Full-Throughput.js +439 -0
  72. package/source/services/web-app/pict-app-full/views/PictView-Facto-Full-TopBar.js +335 -0
  73. package/source/services/web-app/pict-app-full/views/projections/Facto-Projections-Constants.js +71 -0
  74. package/source/services/web-app/web/chart.min.js +20 -0
  75. package/source/services/web-app/web/codemirror-bundle.js +30099 -0
  76. package/source/services/web-app/web/css/facto-themes.css +467 -0
  77. package/source/services/web-app/web/css/facto.css +502 -0
  78. package/source/services/web-app/web/index.html +28 -0
  79. package/source/services/web-app/web/retold-facto.js +12138 -0
  80. package/source/services/web-app/web/retold-facto.js.map +1 -0
  81. package/source/services/web-app/web/retold-facto.min.js +2 -0
  82. package/source/services/web-app/web/retold-facto.min.js.map +1 -0
  83. package/source/services/web-app/web/simple/index.html +17 -0
  84. package/test/Facto_Browser_Integration_tests.js +798 -0
  85. package/test/RetoldFacto_tests.js +4117 -0
  86. package/test/fixtures/weather-readings.csv +17 -0
  87. package/test/fixtures/weather-stations.csv +9 -0
  88. package/test/model/MeadowModel-Extended.json +8497 -0
  89. package/test/model/MeadowModel-PICT.json +1 -0
  90. package/test/model/MeadowModel.json +1355 -0
  91. package/test/model/ddl/Facto.ddl +225 -0
  92. package/test/model/fable-configuration.json +14 -0
@@ -0,0 +1,511 @@
1
+ /**
2
+ * Retold Facto -- Beacon Provider
3
+ *
4
+ * Fable service that registers retold-facto as a beacon in the
5
+ * Ultravisor mesh. This exposes facto's data access, transform,
6
+ * and deployment capabilities so other nodes in the mesh can
7
+ * interact with the facto data pipeline remotely.
8
+ *
9
+ * Three capabilities are registered:
10
+ * - FactoData: CRUD operations against facto's database
11
+ * - FactoTransform: TabularTransform execution (pure function)
12
+ * - FactoDeploy: Schema deployment to projection stores
13
+ *
14
+ * @license MIT
15
+ */
16
+ const libFableServiceProviderBase = require('fable-serviceproviderbase');
17
+ const libBeaconService = require('ultravisor-beacon');
18
+
19
+ class RetoldFactoBeaconProvider extends libFableServiceProviderBase
20
+ {
21
+ constructor(pFable, pOptions, pServiceHash)
22
+ {
23
+ super(pFable, pOptions, pServiceHash);
24
+
25
+ this.serviceType = 'RetoldFactoBeaconProvider';
26
+
27
+ // Beacon service reference (created on connectBeacon)
28
+ this._BeaconService = null;
29
+ }
30
+
31
+ /**
32
+ * Connect to an Ultravisor coordinator as a beacon, registering
33
+ * facto's data, transform, and deployment capabilities.
34
+ *
35
+ * @param {object} pBeaconConfig Beacon configuration:
36
+ * - ServerURL {string} Ultravisor server URL (required)
37
+ * - Name {string} Beacon name (default: 'retold-facto')
38
+ * - Password {string} Auth password (default: '')
39
+ * - MaxConcurrent {number} Max concurrent work items (default: 1)
40
+ * - StagingPath {string} Local staging directory (default: cwd)
41
+ * - Tags {object} Beacon tags (default: {})
42
+ * - BindAddresses {array} Addresses to bind (default: [])
43
+ * @param {Function} fCallback Called with (pError, pBeaconInfo)
44
+ */
45
+ connectBeacon(pBeaconConfig, fCallback)
46
+ {
47
+ if (!pBeaconConfig || !pBeaconConfig.ServerURL)
48
+ {
49
+ return fCallback(new Error('connectBeacon requires a ServerURL in the config.'));
50
+ }
51
+
52
+ if (this._BeaconService && this._BeaconService.isEnabled())
53
+ {
54
+ this.log.warn('RetoldFactoBeaconProvider: beacon already connected.');
55
+ return fCallback(null);
56
+ }
57
+
58
+ // Register the beacon service type with fable if not already present
59
+ this.fable.addServiceTypeIfNotExists('UltravisorBeacon', libBeaconService);
60
+
61
+ // Instantiate the beacon service with the provided config
62
+ this._BeaconService = this.fable.instantiateServiceProviderWithoutRegistration('UltravisorBeacon',
63
+ {
64
+ ServerURL: pBeaconConfig.ServerURL,
65
+ Name: pBeaconConfig.Name || 'retold-facto',
66
+ Password: pBeaconConfig.Password || '',
67
+ MaxConcurrent: pBeaconConfig.MaxConcurrent || 1,
68
+ StagingPath: pBeaconConfig.StagingPath || process.cwd(),
69
+ Tags: pBeaconConfig.Tags || {},
70
+ BindAddresses: pBeaconConfig.BindAddresses || []
71
+ });
72
+
73
+ // Capture a reference to the fable instance for use in
74
+ // capability handler closures (which run in a different scope).
75
+ let tmpFable = this.fable;
76
+ let tmpLog = this.log;
77
+
78
+ // ---------------------------------------------------------
79
+ // Capability: FactoData
80
+ // CRUD operations against facto's database via DAL entities
81
+ // ---------------------------------------------------------
82
+ this._BeaconService.registerCapability(
83
+ {
84
+ Capability: 'FactoData',
85
+ Name: 'FactoDataProvider',
86
+ actions:
87
+ {
88
+ 'CreateSource':
89
+ {
90
+ Description: 'Create a new Source record',
91
+ SettingsSchema:
92
+ [
93
+ { Name: 'Name', DataType: 'String', Required: true },
94
+ { Name: 'Type', DataType: 'String', Required: true },
95
+ { Name: 'URL', DataType: 'String', Required: false }
96
+ ],
97
+ Handler: function (pWorkItem, pContext, fCallback)
98
+ {
99
+ let tmpSettings = pWorkItem.Settings || {};
100
+ let tmpQuery = tmpFable.DAL.Source.query.clone();
101
+ tmpFable.DAL.Source.doCreate(tmpQuery,
102
+ {
103
+ Name: tmpSettings.Name,
104
+ Type: tmpSettings.Type,
105
+ URL: tmpSettings.URL
106
+ },
107
+ (pError, pQuery, pRecord) =>
108
+ {
109
+ if (pError)
110
+ {
111
+ tmpLog.error(`FactoData.CreateSource failed: ${pError.message}`);
112
+ return fCallback(pError);
113
+ }
114
+ return fCallback(null, { Outputs: { Created: pRecord } });
115
+ });
116
+ }
117
+ },
118
+
119
+ 'CreateDataset':
120
+ {
121
+ Description: 'Create a new Dataset record',
122
+ SettingsSchema:
123
+ [
124
+ { Name: 'Name', DataType: 'String', Required: true },
125
+ { Name: 'Type', DataType: 'String', Required: true }
126
+ ],
127
+ Handler: function (pWorkItem, pContext, fCallback)
128
+ {
129
+ let tmpSettings = pWorkItem.Settings || {};
130
+ let tmpQuery = tmpFable.DAL.Dataset.query.clone();
131
+ tmpFable.DAL.Dataset.doCreate(tmpQuery,
132
+ {
133
+ Name: tmpSettings.Name,
134
+ Type: tmpSettings.Type
135
+ },
136
+ (pError, pQuery, pRecord) =>
137
+ {
138
+ if (pError)
139
+ {
140
+ tmpLog.error(`FactoData.CreateDataset failed: ${pError.message}`);
141
+ return fCallback(pError);
142
+ }
143
+ return fCallback(null, { Outputs: { Created: pRecord } });
144
+ });
145
+ }
146
+ },
147
+
148
+ 'CreateIngestJob':
149
+ {
150
+ Description: 'Create a new IngestJob record',
151
+ SettingsSchema:
152
+ [
153
+ { Name: 'IDSource', DataType: 'Integer', Required: true },
154
+ { Name: 'IDDataset', DataType: 'Integer', Required: true },
155
+ { Name: 'Status', DataType: 'String', Required: false },
156
+ { Name: 'StartDate', DataType: 'DateTime', Required: false }
157
+ ],
158
+ Handler: function (pWorkItem, pContext, fCallback)
159
+ {
160
+ let tmpSettings = pWorkItem.Settings || {};
161
+ let tmpQuery = tmpFable.DAL.IngestJob.query.clone();
162
+ tmpFable.DAL.IngestJob.doCreate(tmpQuery,
163
+ {
164
+ IDSource: tmpSettings.IDSource,
165
+ IDDataset: tmpSettings.IDDataset,
166
+ Status: tmpSettings.Status,
167
+ StartDate: tmpSettings.StartDate
168
+ },
169
+ (pError, pQuery, pRecord) =>
170
+ {
171
+ if (pError)
172
+ {
173
+ tmpLog.error(`FactoData.CreateIngestJob failed: ${pError.message}`);
174
+ return fCallback(pError);
175
+ }
176
+ return fCallback(null, { Outputs: { Created: pRecord } });
177
+ });
178
+ }
179
+ },
180
+
181
+ 'CreateRecord':
182
+ {
183
+ Description: 'Create a new Record entry',
184
+ SettingsSchema:
185
+ [
186
+ { Name: 'IDDataset', DataType: 'Integer', Required: true },
187
+ { Name: 'IDSource', DataType: 'Integer', Required: true },
188
+ { Name: 'IDIngestJob', DataType: 'Integer', Required: true },
189
+ { Name: 'Content', DataType: 'String', Required: true },
190
+ { Name: 'Type', DataType: 'String', Required: false },
191
+ { Name: 'IngestDate', DataType: 'DateTime', Required: false },
192
+ { Name: 'Version', DataType: 'Integer', Required: false }
193
+ ],
194
+ Handler: function (pWorkItem, pContext, fCallback)
195
+ {
196
+ let tmpSettings = pWorkItem.Settings || {};
197
+ let tmpQuery = tmpFable.DAL.Record.query.clone();
198
+ tmpFable.DAL.Record.doCreate(tmpQuery,
199
+ {
200
+ IDDataset: tmpSettings.IDDataset,
201
+ IDSource: tmpSettings.IDSource,
202
+ IDIngestJob: tmpSettings.IDIngestJob,
203
+ Content: tmpSettings.Content,
204
+ Type: tmpSettings.Type,
205
+ IngestDate: tmpSettings.IngestDate,
206
+ Version: tmpSettings.Version
207
+ },
208
+ (pError, pQuery, pRecord) =>
209
+ {
210
+ if (pError)
211
+ {
212
+ tmpLog.error(`FactoData.CreateRecord failed: ${pError.message}`);
213
+ return fCallback(pError);
214
+ }
215
+ return fCallback(null, { Outputs: { Created: pRecord } });
216
+ });
217
+ }
218
+ },
219
+
220
+ 'ReadRecords':
221
+ {
222
+ Description: 'Read Records with optional filter and cap',
223
+ SettingsSchema:
224
+ [
225
+ { Name: 'Filter', DataType: 'Object', Required: false },
226
+ { Name: 'Cap', DataType: 'Integer', Required: false }
227
+ ],
228
+ Handler: function (pWorkItem, pContext, fCallback)
229
+ {
230
+ let tmpSettings = pWorkItem.Settings || {};
231
+ let tmpFilter = tmpSettings.Filter || {};
232
+ let tmpCap = tmpSettings.Cap || 100;
233
+ let tmpQuery = tmpFable.DAL.Record.query.clone();
234
+ tmpQuery.setCap(tmpCap);
235
+
236
+ // Apply filter fields to the query
237
+ let tmpFilterKeys = Object.keys(tmpFilter);
238
+ for (let i = 0; i < tmpFilterKeys.length; i++)
239
+ {
240
+ tmpQuery.addFilter(tmpFilterKeys[i], tmpFilter[tmpFilterKeys[i]]);
241
+ }
242
+
243
+ tmpFable.DAL.Record.doReads(tmpQuery,
244
+ (pError, pQuery, pRecords) =>
245
+ {
246
+ if (pError)
247
+ {
248
+ tmpLog.error(`FactoData.ReadRecords failed: ${pError.message}`);
249
+ return fCallback(pError);
250
+ }
251
+ return fCallback(null, { Outputs: { Records: pRecords } });
252
+ });
253
+ }
254
+ },
255
+
256
+ 'UpdateIngestJob':
257
+ {
258
+ Description: 'Update an existing IngestJob record',
259
+ SettingsSchema:
260
+ [
261
+ { Name: 'IDIngestJob', DataType: 'Integer', Required: true },
262
+ { Name: 'Status', DataType: 'String', Required: false },
263
+ { Name: 'RecordsProcessed', DataType: 'Integer', Required: false },
264
+ { Name: 'RecordsCreated', DataType: 'Integer', Required: false },
265
+ { Name: 'EndDate', DataType: 'DateTime', Required: false }
266
+ ],
267
+ Handler: function (pWorkItem, pContext, fCallback)
268
+ {
269
+ let tmpSettings = pWorkItem.Settings || {};
270
+ let tmpQuery = tmpFable.DAL.IngestJob.query.clone();
271
+ tmpQuery.addFilter('IDIngestJob', tmpSettings.IDIngestJob);
272
+
273
+ let tmpUpdateRecord = {};
274
+ if (tmpSettings.Status !== undefined)
275
+ {
276
+ tmpUpdateRecord.Status = tmpSettings.Status;
277
+ }
278
+ if (tmpSettings.RecordsProcessed !== undefined)
279
+ {
280
+ tmpUpdateRecord.RecordsProcessed = tmpSettings.RecordsProcessed;
281
+ }
282
+ if (tmpSettings.RecordsCreated !== undefined)
283
+ {
284
+ tmpUpdateRecord.RecordsCreated = tmpSettings.RecordsCreated;
285
+ }
286
+ if (tmpSettings.EndDate !== undefined)
287
+ {
288
+ tmpUpdateRecord.EndDate = tmpSettings.EndDate;
289
+ }
290
+
291
+ tmpFable.DAL.IngestJob.doUpdate(tmpQuery, tmpUpdateRecord,
292
+ (pError, pQuery, pRecord) =>
293
+ {
294
+ if (pError)
295
+ {
296
+ tmpLog.error(`FactoData.UpdateIngestJob failed: ${pError.message}`);
297
+ return fCallback(pError);
298
+ }
299
+ return fCallback(null, { Outputs: { Updated: pRecord } });
300
+ });
301
+ }
302
+ },
303
+
304
+ 'CreateProjectionStore':
305
+ {
306
+ Description: 'Create a new ProjectionStore record',
307
+ SettingsSchema:
308
+ [
309
+ { Name: 'IDDataset', DataType: 'Integer', Required: true },
310
+ { Name: 'IDStoreConnection', DataType: 'Integer', Required: true },
311
+ { Name: 'TargetTableName', DataType: 'String', Required: true },
312
+ { Name: 'Status', DataType: 'String', Required: false }
313
+ ],
314
+ Handler: function (pWorkItem, pContext, fCallback)
315
+ {
316
+ let tmpSettings = pWorkItem.Settings || {};
317
+ let tmpQuery = tmpFable.DAL.ProjectionStore.query.clone();
318
+ tmpFable.DAL.ProjectionStore.doCreate(tmpQuery,
319
+ {
320
+ IDDataset: tmpSettings.IDDataset,
321
+ IDStoreConnection: tmpSettings.IDStoreConnection,
322
+ TargetTableName: tmpSettings.TargetTableName,
323
+ Status: tmpSettings.Status
324
+ },
325
+ (pError, pQuery, pRecord) =>
326
+ {
327
+ if (pError)
328
+ {
329
+ tmpLog.error(`FactoData.CreateProjectionStore failed: ${pError.message}`);
330
+ return fCallback(pError);
331
+ }
332
+ return fCallback(null, { Outputs: { Created: pRecord } });
333
+ });
334
+ }
335
+ }
336
+ }
337
+ });
338
+
339
+ // ---------------------------------------------------------
340
+ // Capability: FactoTransform
341
+ // TabularTransform execution (pure data transformation)
342
+ // ---------------------------------------------------------
343
+ this._BeaconService.registerCapability(
344
+ {
345
+ Capability: 'FactoTransform',
346
+ Name: 'FactoTransformProvider',
347
+ actions:
348
+ {
349
+ 'ApplyMapping':
350
+ {
351
+ Description: 'Apply a TabularTransform mapping to an array of records',
352
+ SettingsSchema:
353
+ [
354
+ { Name: 'Records', DataType: 'Array', Required: true },
355
+ { Name: 'MappingConfiguration', DataType: 'Object', Required: true }
356
+ ],
357
+ Handler: function (pWorkItem, pContext, fCallback)
358
+ {
359
+ let tmpSettings = pWorkItem.Settings || {};
360
+ let tmpRecords = tmpSettings.Records || [];
361
+ let tmpMappingConfig = tmpSettings.MappingConfiguration || {};
362
+ let tmpTabularTransform = tmpFable.services.TabularTransform;
363
+
364
+ if (!tmpTabularTransform)
365
+ {
366
+ return fCallback(new Error('TabularTransform service is not available on the fable instance.'));
367
+ }
368
+
369
+ let tmpMappingOutcome = tmpTabularTransform.newMappingOutcomeObject();
370
+ tmpMappingOutcome.ExplicitConfiguration = tmpMappingConfig;
371
+ tmpMappingOutcome.ImplicitConfiguration = tmpMappingConfig;
372
+
373
+ for (let i = 0; i < tmpRecords.length; i++)
374
+ {
375
+ try
376
+ {
377
+ tmpTabularTransform.transformRecord(tmpRecords[i], tmpMappingOutcome);
378
+ }
379
+ catch (pTransformError)
380
+ {
381
+ tmpLog.error(`FactoTransform.ApplyMapping error on record ${i}: ${pTransformError.message}`);
382
+ }
383
+ }
384
+
385
+ let tmpEntityName = tmpMappingConfig.Entity || Object.keys(tmpMappingOutcome.Comprehension)[0] || 'Unknown';
386
+ let tmpComprehension = tmpMappingOutcome.Comprehension[tmpEntityName] || {};
387
+
388
+ return fCallback(null,
389
+ {
390
+ Outputs:
391
+ {
392
+ Comprehension: tmpComprehension,
393
+ BadRecords: tmpMappingOutcome.BadRecords,
394
+ ParsedRowCount: tmpMappingOutcome.ParsedRowCount,
395
+ UniqueCount: Object.keys(tmpComprehension).length
396
+ }
397
+ });
398
+ }
399
+ }
400
+ }
401
+ });
402
+
403
+ // ---------------------------------------------------------
404
+ // Capability: FactoDeploy
405
+ // Schema deployment to projection stores
406
+ // ---------------------------------------------------------
407
+ this._BeaconService.registerCapability(
408
+ {
409
+ Capability: 'FactoDeploy',
410
+ Name: 'FactoDeployProvider',
411
+ actions:
412
+ {
413
+ 'DeploySchema':
414
+ {
415
+ Description: 'Deploy a dataset schema to a projection store',
416
+ SettingsSchema:
417
+ [
418
+ { Name: 'IDDataset', DataType: 'Integer', Required: true },
419
+ { Name: 'IDStoreConnection', DataType: 'Integer', Required: true },
420
+ { Name: 'TargetTableName', DataType: 'String', Required: true }
421
+ ],
422
+ Handler: function (pWorkItem, pContext, fCallback)
423
+ {
424
+ let tmpSettings = pWorkItem.Settings || {};
425
+ let tmpProjectionEngine = tmpFable.services.RetoldFactoProjectionEngine;
426
+
427
+ if (!tmpProjectionEngine)
428
+ {
429
+ return fCallback(new Error('ProjectionEngine service is not available on the fable instance.'));
430
+ }
431
+
432
+ tmpProjectionEngine.deploySchema(
433
+ tmpSettings.IDDataset,
434
+ tmpSettings.IDStoreConnection,
435
+ tmpSettings.TargetTableName,
436
+ (pError, pResult) =>
437
+ {
438
+ if (pError)
439
+ {
440
+ tmpLog.error(`FactoDeploy.DeploySchema failed: ${pError.message}`);
441
+ return fCallback(pError);
442
+ }
443
+ return fCallback(null, { Outputs: { DeployResult: pResult } });
444
+ });
445
+ }
446
+ }
447
+ }
448
+ });
449
+
450
+ this.log.info('RetoldFactoBeaconProvider: registered capabilities [FactoData, FactoTransform, FactoDeploy]');
451
+
452
+ // Enable the beacon -- authenticate, register, begin polling.
453
+ this._BeaconService.enable(
454
+ (pEnableError, pBeaconInfo) =>
455
+ {
456
+ if (pEnableError)
457
+ {
458
+ this.log.error(`RetoldFactoBeaconProvider: beacon enable failed: ${pEnableError.message}`);
459
+ this._BeaconService = null;
460
+ return fCallback(pEnableError);
461
+ }
462
+
463
+ this.log.info(`RetoldFactoBeaconProvider: beacon connected as ${pBeaconInfo.BeaconID}`);
464
+ return fCallback(null, pBeaconInfo);
465
+ });
466
+ }
467
+
468
+ /**
469
+ * Disconnect the beacon from the Ultravisor coordinator.
470
+ *
471
+ * @param {Function} fCallback Called with (pError)
472
+ */
473
+ disconnectBeacon(fCallback)
474
+ {
475
+ if (!this._BeaconService || !this._BeaconService.isEnabled())
476
+ {
477
+ if (this.log)
478
+ {
479
+ this.log.info('RetoldFactoBeaconProvider: beacon not connected, nothing to disconnect.');
480
+ }
481
+ return fCallback(null);
482
+ }
483
+
484
+ this._BeaconService.disable(
485
+ (pError) =>
486
+ {
487
+ if (pError)
488
+ {
489
+ this.log.warn(`RetoldFactoBeaconProvider: beacon disconnect warning: ${pError.message}`);
490
+ }
491
+ else
492
+ {
493
+ this.log.info('RetoldFactoBeaconProvider: beacon disconnected.');
494
+ }
495
+ this._BeaconService = null;
496
+ return fCallback(pError || null);
497
+ });
498
+ }
499
+
500
+ /**
501
+ * Check if beacon mode is currently enabled.
502
+ *
503
+ * @returns {boolean}
504
+ */
505
+ isEnabled()
506
+ {
507
+ return this._BeaconService && this._BeaconService.isEnabled();
508
+ }
509
+ }
510
+
511
+ module.exports = RetoldFactoBeaconProvider;