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.
- package/.claude/launch.json +11 -0
- package/.dockerignore +8 -0
- package/.quackage.json +19 -0
- package/Dockerfile +26 -0
- package/bin/retold-facto.js +909 -0
- package/examples/facto-government-data.sqlite +0 -0
- package/examples/government-data-catalog.json +137 -0
- package/examples/government-data-loader.js +1432 -0
- package/package.json +91 -0
- package/scripts/facto-download.js +425 -0
- package/source/Retold-Facto.js +1042 -0
- package/source/services/Retold-Facto-BeaconProvider.js +511 -0
- package/source/services/Retold-Facto-CatalogManager.js +1252 -0
- package/source/services/Retold-Facto-DataLakeService.js +1642 -0
- package/source/services/Retold-Facto-DatasetManager.js +417 -0
- package/source/services/Retold-Facto-IngestEngine.js +1315 -0
- package/source/services/Retold-Facto-ProjectionEngine.js +3960 -0
- package/source/services/Retold-Facto-RecordManager.js +360 -0
- package/source/services/Retold-Facto-SchemaManager.js +1110 -0
- package/source/services/Retold-Facto-SourceFolderScanner.js +2243 -0
- package/source/services/Retold-Facto-SourceManager.js +730 -0
- package/source/services/Retold-Facto-StoreConnectionManager.js +441 -0
- package/source/services/Retold-Facto-ThroughputMonitor.js +478 -0
- package/source/services/web-app/codemirror-entry.js +7 -0
- package/source/services/web-app/pict-app/Pict-Application-Facto-Configuration.json +9 -0
- package/source/services/web-app/pict-app/Pict-Application-Facto.js +70 -0
- package/source/services/web-app/pict-app/Pict-Facto-Bundle.js +11 -0
- package/source/services/web-app/pict-app/providers/Pict-Provider-Facto-UI.js +66 -0
- package/source/services/web-app/pict-app/providers/Pict-Provider-Facto.js +69 -0
- package/source/services/web-app/pict-app/providers/facto-api/Facto-API-Catalog.js +93 -0
- package/source/services/web-app/pict-app/providers/facto-api/Facto-API-Connections.js +42 -0
- package/source/services/web-app/pict-app/providers/facto-api/Facto-API-Datasets.js +605 -0
- package/source/services/web-app/pict-app/providers/facto-api/Facto-API-Projections.js +188 -0
- package/source/services/web-app/pict-app/providers/facto-api/Facto-API-Scanner.js +80 -0
- package/source/services/web-app/pict-app/providers/facto-api/Facto-API-Schema.js +116 -0
- package/source/services/web-app/pict-app/providers/facto-api/Facto-API-Sources.js +104 -0
- package/source/services/web-app/pict-app/views/PictView-Facto-Catalog.js +526 -0
- package/source/services/web-app/pict-app/views/PictView-Facto-Datasets.js +173 -0
- package/source/services/web-app/pict-app/views/PictView-Facto-Ingest.js +259 -0
- package/source/services/web-app/pict-app/views/PictView-Facto-Layout.js +191 -0
- package/source/services/web-app/pict-app/views/PictView-Facto-Projections.js +231 -0
- package/source/services/web-app/pict-app/views/PictView-Facto-Records.js +326 -0
- package/source/services/web-app/pict-app/views/PictView-Facto-Scanner.js +624 -0
- package/source/services/web-app/pict-app/views/PictView-Facto-Sources.js +201 -0
- package/source/services/web-app/pict-app/views/PictView-Facto-Throughput.js +456 -0
- package/source/services/web-app/pict-app-full/Pict-Application-Facto-Full-Configuration.json +14 -0
- package/source/services/web-app/pict-app-full/Pict-Application-Facto-Full.js +391 -0
- package/source/services/web-app/pict-app-full/providers/PictRouter-Facto-Configuration.json +56 -0
- package/source/services/web-app/pict-app-full/views/PictView-Facto-Full-BottomBar.js +68 -0
- package/source/services/web-app/pict-app-full/views/PictView-Facto-Full-Connections.js +340 -0
- package/source/services/web-app/pict-app-full/views/PictView-Facto-Full-Dashboard.js +149 -0
- package/source/services/web-app/pict-app-full/views/PictView-Facto-Full-Dashboards.js +819 -0
- package/source/services/web-app/pict-app-full/views/PictView-Facto-Full-Datasets.js +178 -0
- package/source/services/web-app/pict-app-full/views/PictView-Facto-Full-IngestJobs.js +99 -0
- package/source/services/web-app/pict-app-full/views/PictView-Facto-Full-Layout.js +62 -0
- package/source/services/web-app/pict-app-full/views/PictView-Facto-Full-MappingEditor.js +158 -0
- package/source/services/web-app/pict-app-full/views/PictView-Facto-Full-ProjectionDetail.js +1120 -0
- package/source/services/web-app/pict-app-full/views/PictView-Facto-Full-Projections.js +172 -0
- package/source/services/web-app/pict-app-full/views/PictView-Facto-Full-QueryPanel.js +119 -0
- package/source/services/web-app/pict-app-full/views/PictView-Facto-Full-RecordViewer.js +663 -0
- package/source/services/web-app/pict-app-full/views/PictView-Facto-Full-Records.js +648 -0
- package/source/services/web-app/pict-app-full/views/PictView-Facto-Full-Scanner.js +1017 -0
- package/source/services/web-app/pict-app-full/views/PictView-Facto-Full-SchemaDetail.js +1404 -0
- package/source/services/web-app/pict-app-full/views/PictView-Facto-Full-SchemaDocEditor.js +1036 -0
- package/source/services/web-app/pict-app-full/views/PictView-Facto-Full-SchemaEditor.js +636 -0
- package/source/services/web-app/pict-app-full/views/PictView-Facto-Full-SchemaResearch.js +357 -0
- package/source/services/web-app/pict-app-full/views/PictView-Facto-Full-SourceDetail.js +822 -0
- package/source/services/web-app/pict-app-full/views/PictView-Facto-Full-SourceEditor.js +1036 -0
- package/source/services/web-app/pict-app-full/views/PictView-Facto-Full-SourceResearch.js +487 -0
- package/source/services/web-app/pict-app-full/views/PictView-Facto-Full-Sources.js +165 -0
- package/source/services/web-app/pict-app-full/views/PictView-Facto-Full-Throughput.js +439 -0
- package/source/services/web-app/pict-app-full/views/PictView-Facto-Full-TopBar.js +335 -0
- package/source/services/web-app/pict-app-full/views/projections/Facto-Projections-Constants.js +71 -0
- package/source/services/web-app/web/chart.min.js +20 -0
- package/source/services/web-app/web/codemirror-bundle.js +30099 -0
- package/source/services/web-app/web/css/facto-themes.css +467 -0
- package/source/services/web-app/web/css/facto.css +502 -0
- package/source/services/web-app/web/index.html +28 -0
- package/source/services/web-app/web/retold-facto.js +12138 -0
- package/source/services/web-app/web/retold-facto.js.map +1 -0
- package/source/services/web-app/web/retold-facto.min.js +2 -0
- package/source/services/web-app/web/retold-facto.min.js.map +1 -0
- package/source/services/web-app/web/simple/index.html +17 -0
- package/test/Facto_Browser_Integration_tests.js +798 -0
- package/test/RetoldFacto_tests.js +4117 -0
- package/test/fixtures/weather-readings.csv +17 -0
- package/test/fixtures/weather-stations.csv +9 -0
- package/test/model/MeadowModel-Extended.json +8497 -0
- package/test/model/MeadowModel-PICT.json +1 -0
- package/test/model/MeadowModel.json +1355 -0
- package/test/model/ddl/Facto.ddl +225 -0
- 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;
|