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,1042 @@
1
+ /**
2
+ * Retold Facto
3
+ *
4
+ * Data warehouse and knowledge graph storage service.
5
+ *
6
+ * @author Steven Velozo <steven@velozo.com>
7
+ */
8
+ const libFableServiceProviderBase = require('fable-serviceproviderbase');
9
+
10
+ const libOrator = require('orator');
11
+ const libOratorServiceServerRestify = require('orator-serviceserver-restify');
12
+ const libOratorStaticServer = require('orator-static-server');
13
+
14
+ const libMeadow = require('meadow');
15
+ const libMeadowEndpoints = require('meadow-endpoints');
16
+
17
+ const libPath = require('path');
18
+ const libFs = require('fs');
19
+
20
+ const libRetoldFactoSourceManager = require('./services/Retold-Facto-SourceManager.js');
21
+ const libRetoldFactoRecordManager = require('./services/Retold-Facto-RecordManager.js');
22
+ const libRetoldFactoDatasetManager = require('./services/Retold-Facto-DatasetManager.js');
23
+ const libRetoldFactoIngestEngine = require('./services/Retold-Facto-IngestEngine.js');
24
+ const libRetoldFactoProjectionEngine = require('./services/Retold-Facto-ProjectionEngine.js');
25
+ const libRetoldFactoCatalogManager = require('./services/Retold-Facto-CatalogManager.js');
26
+ const libRetoldFactoStoreConnectionManager = require('./services/Retold-Facto-StoreConnectionManager.js');
27
+ const libRetoldFactoDataLakeService = require('./services/Retold-Facto-DataLakeService.js');
28
+ const libRetoldFactoSourceFolderScanner = require('./services/Retold-Facto-SourceFolderScanner.js');
29
+ const libRetoldFactoSchemaManager = require('./services/Retold-Facto-SchemaManager.js');
30
+
31
+ const libMeadowIntegration = require('meadow-integration');
32
+ const libTabularTransform = require('meadow-integration/source/services/tabular/Service-TabularTransform.js');
33
+ const libCertaintyAccumulator = require('meadow-integration/source/services/certainty/Service-CertaintyAccumulator.js');
34
+ const libThroughputMonitor = require('./services/Retold-Facto-ThroughputMonitor.js');
35
+
36
+ // Embedded schema SQL for auto-creation when using SQLite
37
+ const FACTO_SCHEMA_SQL = `
38
+ CREATE TABLE IF NOT EXISTS Source (
39
+ IDSource INTEGER PRIMARY KEY AUTOINCREMENT,
40
+ GUIDSource TEXT,
41
+ CreateDate TEXT, CreatingIDUser INTEGER DEFAULT 0,
42
+ UpdateDate TEXT, UpdatingIDUser INTEGER DEFAULT 0,
43
+ Deleted INTEGER DEFAULT 0, DeleteDate TEXT, DeletingIDUser INTEGER DEFAULT 0,
44
+ Name TEXT, Hash TEXT DEFAULT '', Type TEXT, URL TEXT, Protocol TEXT,
45
+ Description TEXT, Configuration TEXT, Active INTEGER DEFAULT 0
46
+ );
47
+ CREATE TABLE IF NOT EXISTS SourceDocumentation (
48
+ IDSourceDocumentation INTEGER PRIMARY KEY AUTOINCREMENT,
49
+ GUIDSourceDocumentation TEXT,
50
+ CreateDate TEXT, CreatingIDUser INTEGER DEFAULT 0,
51
+ UpdateDate TEXT, UpdatingIDUser INTEGER DEFAULT 0,
52
+ Deleted INTEGER DEFAULT 0, DeleteDate TEXT, DeletingIDUser INTEGER DEFAULT 0,
53
+ IDSource INTEGER DEFAULT 0, Name TEXT, DocumentType TEXT,
54
+ MimeType TEXT, StorageKey TEXT, Description TEXT, Content TEXT
55
+ );
56
+ CREATE TABLE IF NOT EXISTS Dataset (
57
+ IDDataset INTEGER PRIMARY KEY AUTOINCREMENT,
58
+ GUIDDataset TEXT,
59
+ CreateDate TEXT, CreatingIDUser INTEGER DEFAULT 0,
60
+ UpdateDate TEXT, UpdatingIDUser INTEGER DEFAULT 0,
61
+ Deleted INTEGER DEFAULT 0, DeleteDate TEXT, DeletingIDUser INTEGER DEFAULT 0,
62
+ Name TEXT, Hash TEXT DEFAULT '', Type TEXT, Description TEXT,
63
+ SchemaHash TEXT, SchemaVersion INTEGER DEFAULT 0, SchemaDefinition TEXT,
64
+ VersionPolicy TEXT DEFAULT 'Append',
65
+ IDSchema INTEGER DEFAULT 0
66
+ );
67
+ CREATE TABLE IF NOT EXISTS DatasetSource (
68
+ IDDatasetSource INTEGER PRIMARY KEY AUTOINCREMENT,
69
+ GUIDDatasetSource TEXT,
70
+ CreateDate TEXT, CreatingIDUser INTEGER DEFAULT 0,
71
+ UpdateDate TEXT, UpdatingIDUser INTEGER DEFAULT 0,
72
+ Deleted INTEGER DEFAULT 0, DeleteDate TEXT, DeletingIDUser INTEGER DEFAULT 0,
73
+ IDDataset INTEGER DEFAULT 0, IDSource INTEGER DEFAULT 0,
74
+ ReliabilityWeight REAL DEFAULT 0
75
+ );
76
+ CREATE TABLE IF NOT EXISTS Record (
77
+ IDRecord INTEGER PRIMARY KEY AUTOINCREMENT,
78
+ GUIDRecord TEXT,
79
+ CreateDate TEXT, CreatingIDUser INTEGER DEFAULT 0,
80
+ UpdateDate TEXT, UpdatingIDUser INTEGER DEFAULT 0,
81
+ Deleted INTEGER DEFAULT 0, DeleteDate TEXT, DeletingIDUser INTEGER DEFAULT 0,
82
+ IDDataset INTEGER DEFAULT 0, IDSource INTEGER DEFAULT 0,
83
+ Type TEXT, SchemaHash TEXT, SchemaVersion INTEGER DEFAULT 0,
84
+ Version INTEGER DEFAULT 1, IDIngestJob INTEGER DEFAULT 0,
85
+ IngestDate TEXT, OriginCreateDate TEXT,
86
+ RepresentedTimeStampStart INTEGER DEFAULT 0,
87
+ RepresentedTimeStampStop INTEGER DEFAULT 0,
88
+ RepresentedDuration INTEGER DEFAULT 0, Content TEXT
89
+ );
90
+ CREATE TABLE IF NOT EXISTS RecordBinary (
91
+ IDRecordBinary INTEGER PRIMARY KEY AUTOINCREMENT,
92
+ GUIDRecordBinary TEXT,
93
+ CreateDate TEXT, CreatingIDUser INTEGER DEFAULT 0,
94
+ UpdateDate TEXT, UpdatingIDUser INTEGER DEFAULT 0,
95
+ Deleted INTEGER DEFAULT 0, DeleteDate TEXT, DeletingIDUser INTEGER DEFAULT 0,
96
+ IDRecord INTEGER DEFAULT 0, MimeType TEXT, StorageKey TEXT,
97
+ FileSize INTEGER DEFAULT 0
98
+ );
99
+ CREATE TABLE IF NOT EXISTS CertaintyIndex (
100
+ IDCertaintyIndex INTEGER PRIMARY KEY AUTOINCREMENT,
101
+ GUIDCertaintyIndex TEXT,
102
+ CreateDate TEXT, CreatingIDUser INTEGER DEFAULT 0,
103
+ UpdateDate TEXT, UpdatingIDUser INTEGER DEFAULT 0,
104
+ Deleted INTEGER DEFAULT 0, DeleteDate TEXT, DeletingIDUser INTEGER DEFAULT 0,
105
+ IDRecord INTEGER DEFAULT 0, CertaintyValue REAL DEFAULT 0.5,
106
+ Dimension TEXT, Justification TEXT
107
+ );
108
+ CREATE TABLE IF NOT EXISTS IngestJob (
109
+ IDIngestJob INTEGER PRIMARY KEY AUTOINCREMENT,
110
+ GUIDIngestJob TEXT,
111
+ CreateDate TEXT, CreatingIDUser INTEGER DEFAULT 0,
112
+ UpdateDate TEXT, UpdatingIDUser INTEGER DEFAULT 0,
113
+ Deleted INTEGER DEFAULT 0, DeleteDate TEXT, DeletingIDUser INTEGER DEFAULT 0,
114
+ IDSource INTEGER DEFAULT 0, IDDataset INTEGER DEFAULT 0,
115
+ Status TEXT, StartDate TEXT, EndDate TEXT,
116
+ RecordsProcessed INTEGER DEFAULT 0, RecordsCreated INTEGER DEFAULT 0,
117
+ RecordsUpdated INTEGER DEFAULT 0, RecordsErrored INTEGER DEFAULT 0,
118
+ Configuration TEXT, Log TEXT,
119
+ DatasetVersion INTEGER DEFAULT 0, ContentSignature TEXT DEFAULT ''
120
+ );
121
+ CREATE TABLE IF NOT EXISTS SourceCatalogEntry (
122
+ IDSourceCatalogEntry INTEGER PRIMARY KEY AUTOINCREMENT,
123
+ GUIDSourceCatalogEntry TEXT,
124
+ CreateDate TEXT, CreatingIDUser INTEGER DEFAULT 0,
125
+ UpdateDate TEXT, UpdatingIDUser INTEGER DEFAULT 0,
126
+ Deleted INTEGER DEFAULT 0, DeleteDate TEXT, DeletingIDUser INTEGER DEFAULT 0,
127
+ Agency TEXT, Name TEXT, Type TEXT, URL TEXT, Protocol TEXT,
128
+ Category TEXT, Region TEXT, UpdateFrequency TEXT,
129
+ Description TEXT, Notes TEXT, Verified INTEGER DEFAULT 0
130
+ );
131
+ CREATE TABLE IF NOT EXISTS CatalogDatasetDefinition (
132
+ IDCatalogDatasetDefinition INTEGER PRIMARY KEY AUTOINCREMENT,
133
+ GUIDCatalogDatasetDefinition TEXT,
134
+ CreateDate TEXT, CreatingIDUser INTEGER DEFAULT 0,
135
+ UpdateDate TEXT, UpdatingIDUser INTEGER DEFAULT 0,
136
+ Deleted INTEGER DEFAULT 0, DeleteDate TEXT, DeletingIDUser INTEGER DEFAULT 0,
137
+ IDSourceCatalogEntry INTEGER DEFAULT 0, Name TEXT, Format TEXT,
138
+ MimeType TEXT, EndpointURL TEXT, Description TEXT,
139
+ ParseOptions TEXT, AuthRequirements TEXT,
140
+ VersionPolicy TEXT DEFAULT 'Append',
141
+ Provisioned INTEGER DEFAULT 0,
142
+ IDSource INTEGER DEFAULT 0, IDDataset INTEGER DEFAULT 0
143
+ );
144
+ CREATE TABLE IF NOT EXISTS MultiSetProjection (
145
+ IDMultiSetProjection INTEGER PRIMARY KEY AUTOINCREMENT,
146
+ GUIDMultiSetProjection TEXT,
147
+ CreateDate TEXT, CreatingIDUser INTEGER DEFAULT 0,
148
+ UpdateDate TEXT, UpdatingIDUser INTEGER DEFAULT 0,
149
+ Deleted INTEGER DEFAULT 0, DeleteDate TEXT, DeletingIDUser INTEGER DEFAULT 0,
150
+ IDDataset INTEGER DEFAULT 0, IDProjectionStore INTEGER DEFAULT 0,
151
+ Name TEXT, Description TEXT, PipelineConfiguration TEXT,
152
+ Active INTEGER DEFAULT 1
153
+ );
154
+ CREATE TABLE IF NOT EXISTS ProjectionCertaintyLog (
155
+ IDProjectionCertaintyLog INTEGER PRIMARY KEY AUTOINCREMENT,
156
+ GUIDProjectionCertaintyLog TEXT,
157
+ CreateDate TEXT, CreatingIDUser INTEGER DEFAULT 0,
158
+ UpdateDate TEXT, UpdatingIDUser INTEGER DEFAULT 0,
159
+ Deleted INTEGER DEFAULT 0, DeleteDate TEXT, DeletingIDUser INTEGER DEFAULT 0,
160
+ IDMultiSetProjection INTEGER DEFAULT 0,
161
+ RecordGUID TEXT, CertaintyValue REAL DEFAULT 0.5,
162
+ SourceMappingLabel TEXT, IDProjectionMapping INTEGER DEFAULT 0,
163
+ Action TEXT, Details TEXT
164
+ );
165
+ CREATE TABLE IF NOT EXISTS StoreConnection (
166
+ IDStoreConnection INTEGER PRIMARY KEY AUTOINCREMENT,
167
+ GUIDStoreConnection TEXT,
168
+ CreateDate TEXT, CreatingIDUser INTEGER DEFAULT 0,
169
+ UpdateDate TEXT, UpdatingIDUser INTEGER DEFAULT 0,
170
+ Deleted INTEGER DEFAULT 0, DeleteDate TEXT, DeletingIDUser INTEGER DEFAULT 0,
171
+ Name TEXT, Type TEXT, Config TEXT,
172
+ Status TEXT DEFAULT 'Untested', LastTestedDate TEXT
173
+ );
174
+ CREATE TABLE IF NOT EXISTS ProjectionStore (
175
+ IDProjectionStore INTEGER PRIMARY KEY AUTOINCREMENT,
176
+ GUIDProjectionStore TEXT,
177
+ CreateDate TEXT, CreatingIDUser INTEGER DEFAULT 0,
178
+ UpdateDate TEXT, UpdatingIDUser INTEGER DEFAULT 0,
179
+ Deleted INTEGER DEFAULT 0, DeleteDate TEXT, DeletingIDUser INTEGER DEFAULT 0,
180
+ IDDataset INTEGER DEFAULT 0, IDStoreConnection INTEGER DEFAULT 0,
181
+ TargetTableName TEXT, Status TEXT DEFAULT 'Pending',
182
+ DeployedAt TEXT, DeployLog TEXT
183
+ );
184
+ CREATE TABLE IF NOT EXISTS ProjectionMapping (
185
+ IDProjectionMapping INTEGER PRIMARY KEY AUTOINCREMENT,
186
+ GUIDProjectionMapping TEXT,
187
+ CreateDate TEXT, CreatingIDUser INTEGER DEFAULT 0,
188
+ UpdateDate TEXT, UpdatingIDUser INTEGER DEFAULT 0,
189
+ Deleted INTEGER DEFAULT 0, DeleteDate TEXT, DeletingIDUser INTEGER DEFAULT 0,
190
+ IDDataset INTEGER DEFAULT 0,
191
+ IDSource INTEGER DEFAULT 0,
192
+ IDProjectionStore INTEGER DEFAULT 0,
193
+ Name TEXT,
194
+ SchemaVersion INTEGER DEFAULT 0,
195
+ MappingConfiguration TEXT,
196
+ FlowDiagramState TEXT,
197
+ Active INTEGER DEFAULT 1
198
+ );
199
+
200
+ CREATE TABLE IF NOT EXISTS FactoSchema (
201
+ IDSchema INTEGER PRIMARY KEY AUTOINCREMENT,
202
+ GUIDSchema TEXT,
203
+ CreateDate TEXT, CreatingIDUser INTEGER DEFAULT 0,
204
+ UpdateDate TEXT, UpdatingIDUser INTEGER DEFAULT 0,
205
+ Deleted INTEGER DEFAULT 0, DeleteDate TEXT, DeletingIDUser INTEGER DEFAULT 0,
206
+ Name TEXT, Hash TEXT DEFAULT '', Type TEXT DEFAULT 'Record',
207
+ Description TEXT, Version INTEGER DEFAULT 0,
208
+ SchemaDefinition TEXT, ManyfestDefinition TEXT,
209
+ SchemaHash TEXT DEFAULT '', Active INTEGER DEFAULT 1
210
+ );
211
+ CREATE TABLE IF NOT EXISTS SchemaDocumentation (
212
+ IDSchemaDocumentation INTEGER PRIMARY KEY AUTOINCREMENT,
213
+ GUIDSchemaDocumentation TEXT,
214
+ CreateDate TEXT, CreatingIDUser INTEGER DEFAULT 0,
215
+ UpdateDate TEXT, UpdatingIDUser INTEGER DEFAULT 0,
216
+ Deleted INTEGER DEFAULT 0, DeleteDate TEXT, DeletingIDUser INTEGER DEFAULT 0,
217
+ IDSchema INTEGER DEFAULT 0,
218
+ Name TEXT, DocumentType TEXT DEFAULT 'markdown',
219
+ MimeType TEXT DEFAULT 'text/markdown',
220
+ StorageKey TEXT, Description TEXT, Content TEXT
221
+ );
222
+ CREATE TABLE IF NOT EXISTS SchemaVersion (
223
+ IDSchemaVersion INTEGER PRIMARY KEY AUTOINCREMENT,
224
+ GUIDSchemaVersion TEXT,
225
+ CreateDate TEXT, CreatingIDUser INTEGER DEFAULT 0,
226
+ UpdateDate TEXT, UpdatingIDUser INTEGER DEFAULT 0,
227
+ Deleted INTEGER DEFAULT 0, DeleteDate TEXT, DeletingIDUser INTEGER DEFAULT 0,
228
+ IDSchema INTEGER DEFAULT 0,
229
+ Version INTEGER DEFAULT 0,
230
+ SchemaDefinition TEXT, ManyfestDefinition TEXT,
231
+ SchemaHash TEXT DEFAULT '', ChangeDescription TEXT
232
+ );
233
+ CREATE TABLE IF NOT EXISTS ThroughputEvent (
234
+ IDThroughputEvent INTEGER PRIMARY KEY AUTOINCREMENT,
235
+ RunLabel TEXT,
236
+ RunStartTime INTEGER DEFAULT 0,
237
+ Timestamp INTEGER DEFAULT 0,
238
+ Stage TEXT,
239
+ Count INTEGER DEFAULT 0,
240
+ Dataset TEXT
241
+ );
242
+ `;
243
+
244
+ const defaultFactoSettings = (
245
+ {
246
+ StorageProvider: false,
247
+ StorageProviderModule: false,
248
+
249
+ FullMeadowSchemaPath: `${process.cwd()}/model/`,
250
+ FullMeadowSchemaFilename: false,
251
+
252
+ AutoInitializeDataService: true,
253
+ AutoStartOrator: true,
254
+ AutoCreateSchema: false,
255
+
256
+ // Path to the web app folder for static serving; false to skip
257
+ WebAppPath: false,
258
+
259
+ // Endpoint allow-list. Only enabled groups have their routes wired.
260
+ Endpoints:
261
+ {
262
+ // Per-entity CRUD endpoints (e.g. /1.0/Source, /1.0/Record)
263
+ MeadowEndpoints: true,
264
+ // Source management API (/facto/source/*)
265
+ SourceManager: true,
266
+ // Record ingest/version/certainty API (/facto/record/*)
267
+ RecordManager: true,
268
+ // Dataset management API (/facto/dataset/*)
269
+ DatasetManager: true,
270
+ // Ingest engine API (/facto/ingest/*)
271
+ IngestEngine: true,
272
+ // Projection engine API (/facto/projection/*)
273
+ ProjectionEngine: true,
274
+ // Catalog manager API (/facto/catalog/*)
275
+ CatalogManager: true,
276
+ // Store connection manager API (/facto/connection/*)
277
+ StoreConnectionManager: true,
278
+ // Source folder scanner API (/facto/scanner/*)
279
+ SourceFolderScanner: true,
280
+ // Schema manager API (/facto/schema/*)
281
+ SchemaManager: true,
282
+ // Web UI
283
+ WebUI: true
284
+ },
285
+
286
+ Facto:
287
+ {
288
+ RoutePrefix: '/facto',
289
+ DefaultCertaintyValue: 0.5,
290
+ ScanPaths: [],
291
+ AutoProvision: false,
292
+ AutoIngest: false
293
+ }
294
+ });
295
+
296
+ class RetoldFacto extends libFableServiceProviderBase
297
+ {
298
+ constructor(pFable, pOptions, pServiceHash)
299
+ {
300
+ // Intersect default options, parent constructor, service information
301
+ let tmpOptions = Object.assign({}, JSON.parse(JSON.stringify(defaultFactoSettings)), pOptions);
302
+ super(pFable, tmpOptions, pServiceHash);
303
+
304
+ this.serviceType = 'RetoldFacto';
305
+
306
+ // Re-apply defaults without mutating the module-level defaultFactoSettings object.
307
+ this.options = Object.assign({}, JSON.parse(JSON.stringify(defaultFactoSettings)), this.options);
308
+
309
+ // Add the restify server provider and orator base class to fable
310
+ this.fable.serviceManager.addServiceType('OratorServiceServer', libOratorServiceServerRestify);
311
+ this.fable.serviceManager.addServiceType('Orator', libOrator);
312
+
313
+ // Initialize Restify
314
+ this.fable.serviceManager.instantiateServiceProvider('OratorServiceServer', this.options);
315
+
316
+ // Initialize Orator, which will automatically use the default OratorServiceServer
317
+ this.fable.serviceManager.instantiateServiceProvider('Orator', this.options);
318
+
319
+ // Initialize Meadow
320
+ this._Meadow = libMeadow.new(pFable);
321
+ this._DAL = {};
322
+ this._MeadowEndpoints = {};
323
+ this.models = {};
324
+ this.fullModel = false;
325
+ this.entityList = false;
326
+
327
+ // Register and instantiate sub-services
328
+ this.fable.serviceManager.addServiceType('RetoldFactoSourceManager', libRetoldFactoSourceManager);
329
+ this.fable.serviceManager.instantiateServiceProvider('RetoldFactoSourceManager',
330
+ {
331
+ RoutePrefix: this.options.Facto.RoutePrefix
332
+ });
333
+
334
+ this.fable.serviceManager.addServiceType('RetoldFactoRecordManager', libRetoldFactoRecordManager);
335
+ this.fable.serviceManager.instantiateServiceProvider('RetoldFactoRecordManager',
336
+ {
337
+ RoutePrefix: this.options.Facto.RoutePrefix,
338
+ DefaultCertaintyValue: this.options.Facto.DefaultCertaintyValue
339
+ });
340
+
341
+ this.fable.serviceManager.addServiceType('RetoldFactoDatasetManager', libRetoldFactoDatasetManager);
342
+ this.fable.serviceManager.instantiateServiceProvider('RetoldFactoDatasetManager',
343
+ {
344
+ RoutePrefix: this.options.Facto.RoutePrefix
345
+ });
346
+
347
+ this.fable.serviceManager.addServiceType('RetoldFactoIngestEngine', libRetoldFactoIngestEngine);
348
+ this.fable.serviceManager.instantiateServiceProvider('RetoldFactoIngestEngine',
349
+ {
350
+ RoutePrefix: this.options.Facto.RoutePrefix
351
+ });
352
+
353
+ this.fable.serviceManager.addServiceType('RetoldFactoProjectionEngine', libRetoldFactoProjectionEngine);
354
+ this.fable.serviceManager.instantiateServiceProvider('RetoldFactoProjectionEngine',
355
+ {
356
+ RoutePrefix: this.options.Facto.RoutePrefix
357
+ });
358
+
359
+ this.fable.serviceManager.addServiceType('RetoldFactoCatalogManager', libRetoldFactoCatalogManager);
360
+ this.fable.serviceManager.instantiateServiceProvider('RetoldFactoCatalogManager',
361
+ {
362
+ RoutePrefix: this.options.Facto.RoutePrefix
363
+ });
364
+
365
+ this.fable.serviceManager.addServiceType('RetoldFactoStoreConnectionManager', libRetoldFactoStoreConnectionManager);
366
+ this.fable.serviceManager.instantiateServiceProvider('RetoldFactoStoreConnectionManager',
367
+ {
368
+ RoutePrefix: this.options.Facto.RoutePrefix
369
+ });
370
+
371
+ this.fable.serviceManager.addServiceType('RetoldFactoDataLakeService', libRetoldFactoDataLakeService);
372
+ this.fable.serviceManager.instantiateServiceProvider('RetoldFactoDataLakeService',
373
+ {
374
+ CatalogPath: this.options.Facto.CatalogPath || null,
375
+ DataDir: this.options.Facto.DataDir || null
376
+ });
377
+
378
+ this.fable.serviceManager.addServiceType('RetoldFactoSourceFolderScanner', libRetoldFactoSourceFolderScanner);
379
+ this.fable.serviceManager.instantiateServiceProvider('RetoldFactoSourceFolderScanner',
380
+ {
381
+ RoutePrefix: this.options.Facto.RoutePrefix
382
+ });
383
+
384
+ this.fable.serviceManager.addServiceType('RetoldFactoSchemaManager', libRetoldFactoSchemaManager);
385
+ this.fable.serviceManager.instantiateServiceProvider('RetoldFactoSchemaManager',
386
+ {
387
+ RoutePrefix: this.options.Facto.RoutePrefix
388
+ });
389
+
390
+ // Register Meadow Integration services for projection mapping transforms
391
+ this.fable.serviceManager.addServiceType('TabularCheck', libMeadowIntegration.TabularCheck);
392
+ this.fable.serviceManager.instantiateServiceProvider('TabularCheck');
393
+ this.fable.serviceManager.addServiceType('TabularTransform', libTabularTransform);
394
+ this.fable.serviceManager.instantiateServiceProvider('TabularTransform');
395
+ this.fable.serviceManager.addServiceType('CertaintyAccumulator', libCertaintyAccumulator);
396
+ this.fable.serviceManager.instantiateServiceProvider('CertaintyAccumulator');
397
+ this.fable.serviceManager.addServiceType('ThroughputMonitor', libThroughputMonitor);
398
+ this.fable.serviceManager.instantiateServiceProvider('ThroughputMonitor');
399
+
400
+ // Expose DAL on fable for convenience
401
+ this.fable.DAL = this._DAL;
402
+ this.fable.MeadowEndpoints = this._MeadowEndpoints;
403
+
404
+ this.serviceInitialized = false;
405
+ }
406
+
407
+ /**
408
+ * Rebuild the merged fullModel and entityList from all loaded models.
409
+ */
410
+ rebuildFullModel()
411
+ {
412
+ let tmpModelNames = Object.keys(this.models);
413
+
414
+ if (tmpModelNames.length === 0)
415
+ {
416
+ this.fullModel = false;
417
+ this.entityList = false;
418
+ return;
419
+ }
420
+
421
+ let tmpMergedModel = (
422
+ {
423
+ Tables: {},
424
+ TablesSequence: [],
425
+ Authorization: {},
426
+ Endpoints: {},
427
+ Pict: {}
428
+ });
429
+
430
+ for (let i = 0; i < tmpModelNames.length; i++)
431
+ {
432
+ let tmpModel = this.models[tmpModelNames[i]];
433
+
434
+ if (tmpModel.Tables)
435
+ {
436
+ Object.assign(tmpMergedModel.Tables, tmpModel.Tables);
437
+ }
438
+ if (tmpModel.TablesSequence)
439
+ {
440
+ tmpMergedModel.TablesSequence = tmpMergedModel.TablesSequence.concat(tmpModel.TablesSequence);
441
+ }
442
+ if (tmpModel.Authorization)
443
+ {
444
+ Object.assign(tmpMergedModel.Authorization, tmpModel.Authorization);
445
+ }
446
+ if (tmpModel.Endpoints)
447
+ {
448
+ Object.assign(tmpMergedModel.Endpoints, tmpModel.Endpoints);
449
+ }
450
+ if (tmpModel.Pict)
451
+ {
452
+ Object.assign(tmpMergedModel.Pict, tmpModel.Pict);
453
+ }
454
+ }
455
+
456
+ this.fullModel = tmpMergedModel;
457
+ this.entityList = Object.keys(this._DAL);
458
+ }
459
+
460
+ /**
461
+ * Load a parsed model object and create DAL objects and Meadow Endpoints
462
+ * for each entity in it.
463
+ *
464
+ * @param {string} pModelName - A name to identify this model
465
+ * @param {Object} pModelObject - The parsed stricture model
466
+ * @param {string} [pStorageProvider] - Optional storage provider name override
467
+ * @param {function} fCallback - Callback
468
+ */
469
+ loadModel(pModelName, pModelObject, pStorageProvider, fCallback)
470
+ {
471
+ let tmpCallback = fCallback;
472
+ let tmpStorageProvider = pStorageProvider;
473
+ if (typeof(pStorageProvider) === 'function')
474
+ {
475
+ tmpCallback = pStorageProvider;
476
+ tmpStorageProvider = this.options.StorageProvider;
477
+ }
478
+
479
+ this.fable.log.info(`Retold Facto loading model [${pModelName}]...`);
480
+
481
+ if (this.models.hasOwnProperty(pModelName))
482
+ {
483
+ this.fable.log.warn(`Model [${pModelName}] is already loaded; overwriting.`);
484
+ }
485
+
486
+ this.models[pModelName] = pModelObject;
487
+
488
+ let tmpEntityList = Object.keys(pModelObject.Tables);
489
+
490
+ this.fable.log.info(`...initializing ${tmpEntityList.length} DAL objects and corresponding Meadow Endpoints for model [${pModelName}]...`);
491
+
492
+ for (let i = 0; i < tmpEntityList.length; i++)
493
+ {
494
+ let tmpDALEntityName = tmpEntityList[i];
495
+ let tmpRoutesAlreadyConnected = this._MeadowEndpoints.hasOwnProperty(tmpDALEntityName);
496
+
497
+ if (this._DAL.hasOwnProperty(tmpDALEntityName))
498
+ {
499
+ this.fable.log.warn(`Entity [${tmpDALEntityName}] already exists in the DAL; overwriting.`);
500
+ }
501
+
502
+ try
503
+ {
504
+ let tmpDALSchema = pModelObject.Tables[tmpDALEntityName];
505
+ let tmpDALMeadowSchema = tmpDALSchema.MeadowSchema;
506
+
507
+ this._DAL[tmpDALEntityName] = this._Meadow.loadFromPackageObject(tmpDALMeadowSchema);
508
+ this.fable.log.info(`...defaulting the ${tmpDALEntityName} DAL to use ${tmpStorageProvider}`);
509
+ this._DAL[tmpDALEntityName].setProvider(tmpStorageProvider);
510
+ this.fable.log.info(`...initializing the ${tmpDALEntityName} Meadow Endpoints`);
511
+ this._MeadowEndpoints[tmpDALEntityName] = libMeadowEndpoints.new(this._DAL[tmpDALEntityName]);
512
+
513
+ if (!tmpRoutesAlreadyConnected)
514
+ {
515
+ this.fable.log.info(`...mapping the ${tmpDALEntityName} Meadow Endpoints to Orator`);
516
+ this._MeadowEndpoints[tmpDALEntityName].connectRoutes(this.fable.OratorServiceServer);
517
+ }
518
+ else
519
+ {
520
+ this.fable.log.info(`...routes for ${tmpDALEntityName} already registered; skipping connectRoutes.`);
521
+ }
522
+ }
523
+ catch (pError)
524
+ {
525
+ this.fable.log.error(`Error initializing DAL and Endpoints for entity [${tmpDALEntityName}]: ${pError}`);
526
+ }
527
+ }
528
+
529
+ this.rebuildFullModel();
530
+
531
+ return tmpCallback();
532
+ }
533
+
534
+ /**
535
+ * Load a model from a JSON file on disk.
536
+ */
537
+ loadModelFromFile(pModelName, pModelPath, pModelFilename, fCallback)
538
+ {
539
+ this.fable.log.info(`...loading model [${pModelName}] from file [${pModelPath}${pModelFilename}]...`);
540
+
541
+ let tmpModelObject;
542
+ try
543
+ {
544
+ tmpModelObject = require(`${pModelPath}${pModelFilename}`);
545
+ }
546
+ catch (pError)
547
+ {
548
+ this.fable.log.error(`Error loading model file [${pModelPath}${pModelFilename}]: ${pError}`);
549
+ return fCallback(pError);
550
+ }
551
+
552
+ return this.loadModel(pModelName, tmpModelObject, fCallback);
553
+ }
554
+
555
+ /**
556
+ * Check if an endpoint group is enabled in the Endpoints configuration.
557
+ */
558
+ isEndpointGroupEnabled(pGroupName)
559
+ {
560
+ if (!this.options.Endpoints)
561
+ {
562
+ return false;
563
+ }
564
+ if (!this.options.Endpoints.hasOwnProperty(pGroupName))
565
+ {
566
+ return false;
567
+ }
568
+ return !!this.options.Endpoints[pGroupName];
569
+ }
570
+
571
+ /**
572
+ * Create the database schema using the embedded SQL.
573
+ * Uses the MeadowSQLiteProvider's db handle to execute DDL.
574
+ *
575
+ * @param {function} fCallback - Callback(pError)
576
+ */
577
+ createSchema(fCallback)
578
+ {
579
+ try
580
+ {
581
+ if (this.fable.MeadowSQLiteProvider && this.fable.MeadowSQLiteProvider.db)
582
+ {
583
+ this.fable.log.info('Creating Facto schema (CREATE TABLE IF NOT EXISTS)...');
584
+ this.fable.MeadowSQLiteProvider.db.exec(FACTO_SCHEMA_SQL);
585
+ this.fable.log.info('Facto schema created successfully.');
586
+ }
587
+ else
588
+ {
589
+ this.fable.log.warn('No SQLite provider available; skipping schema auto-creation.');
590
+ }
591
+ }
592
+ catch (pError)
593
+ {
594
+ this.fable.log.error(`Error creating Facto schema: ${pError}`);
595
+ return fCallback(pError);
596
+ }
597
+
598
+ return fCallback();
599
+ }
600
+
601
+ /**
602
+ * Generate a human-readable hash (slug) from a name string.
603
+ *
604
+ * Converts "US Census Bureau" → "US-Census-Bureau",
605
+ * "ISO 3166 Countries" → "ISO-3166-Countries", etc.
606
+ *
607
+ * All managers should call this method so hash generation stays consistent
608
+ * across the service. Collision handling (e.g. appending numeric suffixes
609
+ * when two distinct names produce the same slug) may be added here in the
610
+ * future.
611
+ *
612
+ * @param {string} pInput - The name to convert
613
+ * @returns {string} A clean kebab-case slug, max 128 characters
614
+ */
615
+ generateHash(pInput)
616
+ {
617
+ if (!pInput || typeof pInput !== 'string')
618
+ {
619
+ return '';
620
+ }
621
+ return pInput
622
+ .replace(/[^a-zA-Z0-9\s\-_]/g, '')
623
+ .replace(/\s+/g, '-')
624
+ .replace(/-+/g, '-')
625
+ .replace(/^-|-$/g, '')
626
+ .substring(0, 128);
627
+ }
628
+
629
+ onBeforeInitialize(fCallback)
630
+ {
631
+ return fCallback();
632
+ }
633
+
634
+ onInitialize(fCallback)
635
+ {
636
+ return fCallback();
637
+ }
638
+
639
+ onAfterInitialize(fCallback)
640
+ {
641
+ let tmpScanPaths = this.options.Facto.ScanPaths;
642
+ if (!Array.isArray(tmpScanPaths) || tmpScanPaths.length === 0)
643
+ {
644
+ return fCallback();
645
+ }
646
+
647
+ // Start the server immediately — scan in the background so
648
+ // slow/large folder trees on external drives don't block startup.
649
+ this.fable.log.info(`Retold Facto: Scanning ${tmpScanPaths.length} configured path(s) in background...`);
650
+
651
+ let tmpFable = this.fable;
652
+
653
+ // Use setImmediate so the initializeService chain completes first
654
+ setImmediate(
655
+ () =>
656
+ {
657
+ let tmpAnticipate = tmpFable.newAnticipate();
658
+
659
+ for (let i = 0; i < tmpScanPaths.length; i++)
660
+ {
661
+ let tmpPath = tmpScanPaths[i];
662
+ tmpAnticipate.anticipate(
663
+ (fStepCallback) =>
664
+ {
665
+ tmpFable.RetoldFactoSourceFolderScanner.addScanPath(tmpPath,
666
+ (pError) =>
667
+ {
668
+ if (pError)
669
+ {
670
+ tmpFable.log.error(`Error scanning path ${tmpPath}: ${pError}`);
671
+ }
672
+ return fStepCallback();
673
+ });
674
+ });
675
+ }
676
+
677
+ tmpAnticipate.wait(
678
+ (pError) =>
679
+ {
680
+ if (pError)
681
+ {
682
+ tmpFable.log.error(`Error adding scan paths: ${pError}`);
683
+ }
684
+ else
685
+ {
686
+ tmpFable.log.info(`Retold Facto: Background scan complete.`);
687
+ }
688
+ });
689
+ });
690
+
691
+ return fCallback();
692
+ }
693
+
694
+ initializePersistenceEngine(fCallback)
695
+ {
696
+ if (this.options.StorageProviderModule)
697
+ {
698
+ this.fable.serviceManager.addAndInstantiateServiceType(`Meadow${this.options.StorageProvider}Provider`, require(this.options.StorageProviderModule));
699
+ }
700
+ return fCallback();
701
+ }
702
+
703
+ initializeService(fCallback)
704
+ {
705
+ if (this.serviceInitialized)
706
+ {
707
+ return fCallback(new Error("Retold Facto is being initialized but has already been initialized..."));
708
+ }
709
+ else
710
+ {
711
+ let tmpAnticipate = this.fable.newAnticipate();
712
+
713
+ this.fable.log.info(`Retold Facto is initializing...`);
714
+
715
+ // Log endpoint configuration
716
+ let tmpGroupNames = ['MeadowEndpoints', 'SourceManager', 'RecordManager', 'DatasetManager', 'IngestEngine', 'ProjectionEngine', 'CatalogManager', 'StoreConnectionManager', 'SourceFolderScanner', 'SchemaManager', 'WebUI'];
717
+ let tmpEnabledGroups = [];
718
+ let tmpDisabledGroups = [];
719
+ for (let i = 0; i < tmpGroupNames.length; i++)
720
+ {
721
+ if (this.isEndpointGroupEnabled(tmpGroupNames[i]))
722
+ {
723
+ tmpEnabledGroups.push(tmpGroupNames[i]);
724
+ }
725
+ else
726
+ {
727
+ tmpDisabledGroups.push(tmpGroupNames[i]);
728
+ }
729
+ }
730
+ this.fable.log.info(`Endpoint groups enabled: [${tmpEnabledGroups.join(', ')}]`);
731
+ if (tmpDisabledGroups.length > 0)
732
+ {
733
+ this.fable.log.info(`Endpoint groups disabled: [${tmpDisabledGroups.join(', ')}]`);
734
+ }
735
+
736
+ tmpAnticipate.anticipate(this.onBeforeInitialize.bind(this));
737
+
738
+ tmpAnticipate.anticipate(
739
+ (fInitCallback) =>
740
+ {
741
+ if (this.options.AutoStartOrator)
742
+ {
743
+ this.fable.Orator.startWebServer(fInitCallback);
744
+ }
745
+ else
746
+ {
747
+ return fInitCallback();
748
+ }
749
+ });
750
+
751
+ // Enable JSON body parsing and query string parsing
752
+ tmpAnticipate.anticipate(
753
+ (fInitCallback) =>
754
+ {
755
+ this.fable.OratorServiceServer.server.use(this.fable.OratorServiceServer.bodyParser());
756
+ this.fable.OratorServiceServer.server.use(require('restify').plugins.queryParser());
757
+ return fInitCallback();
758
+ });
759
+
760
+ tmpAnticipate.anticipate(this.initializePersistenceEngine.bind(this));
761
+
762
+ // Auto-create schema if configured
763
+ tmpAnticipate.anticipate(
764
+ (fInitCallback) =>
765
+ {
766
+ if (this.options.AutoCreateSchema)
767
+ {
768
+ return this.createSchema(fInitCallback);
769
+ }
770
+ return fInitCallback();
771
+ });
772
+
773
+ tmpAnticipate.anticipate(this.onInitialize.bind(this));
774
+
775
+ // Wire endpoint routes based on the Endpoints allow-list
776
+ tmpAnticipate.anticipate(
777
+ (fInitCallback) =>
778
+ {
779
+ if (this.isEndpointGroupEnabled('SourceManager'))
780
+ {
781
+ this.fable.RetoldFactoSourceManager.connectRoutes(this.fable.OratorServiceServer);
782
+ }
783
+
784
+ if (this.isEndpointGroupEnabled('RecordManager'))
785
+ {
786
+ this.fable.RetoldFactoRecordManager.connectRoutes(this.fable.OratorServiceServer);
787
+ }
788
+
789
+ if (this.isEndpointGroupEnabled('DatasetManager'))
790
+ {
791
+ this.fable.RetoldFactoDatasetManager.connectRoutes(this.fable.OratorServiceServer);
792
+ }
793
+
794
+ if (this.isEndpointGroupEnabled('IngestEngine'))
795
+ {
796
+ this.fable.RetoldFactoIngestEngine.connectRoutes(this.fable.OratorServiceServer);
797
+ }
798
+
799
+ if (this.isEndpointGroupEnabled('ProjectionEngine'))
800
+ {
801
+ this.fable.RetoldFactoProjectionEngine.connectRoutes(this.fable.OratorServiceServer);
802
+ }
803
+
804
+ if (this.isEndpointGroupEnabled('CatalogManager'))
805
+ {
806
+ this.fable.RetoldFactoCatalogManager.connectRoutes(this.fable.OratorServiceServer);
807
+ }
808
+
809
+ if (this.isEndpointGroupEnabled('StoreConnectionManager'))
810
+ {
811
+ this.fable.RetoldFactoStoreConnectionManager.connectRoutes(this.fable.OratorServiceServer);
812
+ }
813
+
814
+ if (this.isEndpointGroupEnabled('SourceFolderScanner'))
815
+ {
816
+ this.fable.RetoldFactoSourceFolderScanner.connectRoutes(this.fable.OratorServiceServer);
817
+ }
818
+
819
+ if (this.isEndpointGroupEnabled('SchemaManager'))
820
+ {
821
+ this.fable.RetoldFactoSchemaManager.connectRoutes(this.fable.OratorServiceServer);
822
+ }
823
+
824
+ // Throughput monitoring endpoints
825
+ let tmpThroughputPrefix = this.options.Facto.RoutePrefix || '/facto';
826
+ let tmpSS = this.fable.OratorServiceServer;
827
+
828
+ tmpSS.get(`${tmpThroughputPrefix}/throughput`,
829
+ (pRequest, pResponse, fNext) =>
830
+ {
831
+ let tmpDuration = parseInt(pRequest.query && pRequest.query.duration, 10) || 60;
832
+ pResponse.send(this.fable.ThroughputMonitor.getBuckets(tmpDuration));
833
+ return fNext();
834
+ });
835
+
836
+ tmpSS.get(`${tmpThroughputPrefix}/throughput/totals`,
837
+ (pRequest, pResponse, fNext) =>
838
+ {
839
+ pResponse.send(this.fable.ThroughputMonitor.getTotals());
840
+ return fNext();
841
+ });
842
+
843
+ tmpSS.post(`${tmpThroughputPrefix}/throughput/event`,
844
+ (pRequest, pResponse, fNext) =>
845
+ {
846
+ let tmpBody = pRequest.body || {};
847
+ this.fable.ThroughputMonitor.recordEvent(
848
+ tmpBody.Stage || 'extracted',
849
+ tmpBody.Count || 0,
850
+ tmpBody.Dataset || '');
851
+ pResponse.send({ Success: true });
852
+ return fNext();
853
+ });
854
+
855
+ tmpSS.post(`${tmpThroughputPrefix}/throughput/run/start`,
856
+ (pRequest, pResponse, fNext) =>
857
+ {
858
+ let tmpBody = pRequest.body || {};
859
+ this.fable.ThroughputMonitor.startRun(tmpBody.Label || '');
860
+ pResponse.send({ Success: true });
861
+ return fNext();
862
+ });
863
+
864
+ tmpSS.post(`${tmpThroughputPrefix}/throughput/run/end`,
865
+ (pRequest, pResponse, fNext) =>
866
+ {
867
+ this.fable.ThroughputMonitor.endRun();
868
+ pResponse.send({ Success: true });
869
+ return fNext();
870
+ });
871
+
872
+ // Historical run endpoints
873
+ tmpSS.get(`${tmpThroughputPrefix}/throughput/runs`,
874
+ (pRequest, pResponse, fNext) =>
875
+ {
876
+ let tmpLimit = parseInt(pRequest.query && pRequest.query.limit, 10) || 20;
877
+ pResponse.send(this.fable.ThroughputMonitor.getPersistedRuns(tmpLimit));
878
+ return fNext();
879
+ });
880
+
881
+ tmpSS.get(`${tmpThroughputPrefix}/throughput/run/:label`,
882
+ (pRequest, pResponse, fNext) =>
883
+ {
884
+ let tmpDataset = pRequest.query && pRequest.query.dataset;
885
+ pResponse.send(this.fable.ThroughputMonitor.getPersistedRunBuckets(
886
+ pRequest.params.label, tmpDataset || null));
887
+ return fNext();
888
+ });
889
+
890
+ tmpSS.get(`${tmpThroughputPrefix}/throughput/run/:label/datasets`,
891
+ (pRequest, pResponse, fNext) =>
892
+ {
893
+ pResponse.send(this.fable.ThroughputMonitor.getPersistedRunDatasetBreakdown(
894
+ pRequest.params.label));
895
+ return fNext();
896
+ });
897
+
898
+ return fInitCallback();
899
+ });
900
+
901
+ // Load default model if MeadowEndpoints are enabled and a schema file is configured
902
+ tmpAnticipate.anticipate(
903
+ (fInitCallback) =>
904
+ {
905
+ if (!this.isEndpointGroupEnabled('MeadowEndpoints'))
906
+ {
907
+ this.fable.log.info('MeadowEndpoints are disabled; skipping data endpoint initialization.');
908
+ return fInitCallback();
909
+ }
910
+
911
+ if (this.options.FullMeadowSchemaFilename)
912
+ {
913
+ let tmpModelName = this.options.FullMeadowSchemaFilename.replace(/\.json$/i, '');
914
+ return this.loadModelFromFile(tmpModelName, this.options.FullMeadowSchemaPath, this.options.FullMeadowSchemaFilename, fInitCallback);
915
+ }
916
+ else
917
+ {
918
+ this.fable.log.info('No default model configured; skipping data endpoint initialization.');
919
+ return fInitCallback();
920
+ }
921
+ });
922
+
923
+ // Wire static file serving for web UI if enabled
924
+ tmpAnticipate.anticipate(
925
+ (fInitCallback) =>
926
+ {
927
+ if (!this.isEndpointGroupEnabled('WebUI'))
928
+ {
929
+ return fInitCallback();
930
+ }
931
+
932
+ let tmpWebAppPath = this.options.WebAppPath;
933
+ if (!tmpWebAppPath)
934
+ {
935
+ // Default to the bundled web app
936
+ tmpWebAppPath = libPath.join(__dirname, 'services', 'web-app', 'web');
937
+ }
938
+
939
+ this.fable.log.info(`Serving Facto web UI from ${tmpWebAppPath}`);
940
+
941
+ // Serve pict.min.js from the pict package's dist folder
942
+ let tmpPictMinJsPath;
943
+ try
944
+ {
945
+ tmpPictMinJsPath = require.resolve('pict/dist/pict.min.js');
946
+ }
947
+ catch (pResolveError)
948
+ {
949
+ this.fable.log.warn(`Could not resolve pict.min.js: ${pResolveError}`);
950
+ }
951
+
952
+ if (tmpPictMinJsPath)
953
+ {
954
+ this.fable.OratorServiceServer.doGet('/pict.min.js',
955
+ (pRequest, pResponse, fNext) =>
956
+ {
957
+ libFs.readFile(tmpPictMinJsPath, 'utf8',
958
+ (pError, pData) =>
959
+ {
960
+ if (pError)
961
+ {
962
+ pResponse.send(500, { Error: 'Could not read pict.min.js' });
963
+ return fNext();
964
+ }
965
+ pResponse.setHeader('Content-Type', 'application/javascript');
966
+ pResponse.sendRaw(200, pData);
967
+ return fNext();
968
+ });
969
+ });
970
+ }
971
+
972
+ this.fable.serviceManager.addServiceType('OratorStaticServer', libOratorStaticServer);
973
+ let tmpStaticServer = this.fable.serviceManager.instantiateServiceProvider('OratorStaticServer');
974
+
975
+ // Serve the accordion (simple) app at /simple/*
976
+ let tmpSimpleWebAppPath = libPath.join(tmpWebAppPath, 'simple');
977
+ tmpStaticServer.addStaticRoute(tmpSimpleWebAppPath, 'index.html', '/simple/*', '/simple/');
978
+
979
+ // Serve the full app at /* (registered after /simple/* so it doesn't shadow it)
980
+ tmpStaticServer.addStaticRoute(tmpWebAppPath, 'index.html', '/*', '/');
981
+
982
+ return fInitCallback();
983
+ });
984
+
985
+ // Warm up projection entities for deployed ProjectionStores
986
+ tmpAnticipate.anticipate(
987
+ (fInitCallback) =>
988
+ {
989
+ if (!this.isEndpointGroupEnabled('ProjectionEngine'))
990
+ {
991
+ return fInitCallback();
992
+ }
993
+ this.fable.RetoldFactoProjectionEngine._warmUpProjectionEntities(fInitCallback);
994
+ });
995
+
996
+ tmpAnticipate.anticipate(this.onAfterInitialize.bind(this));
997
+
998
+ tmpAnticipate.wait(
999
+ (pError) =>
1000
+ {
1001
+ if (pError)
1002
+ {
1003
+ this.log.error(`Error initializing Retold Facto: ${pError}`);
1004
+ return fCallback(pError);
1005
+ }
1006
+ this.serviceInitialized = true;
1007
+ return fCallback();
1008
+ });
1009
+ }
1010
+ }
1011
+
1012
+ stopService(fCallback)
1013
+ {
1014
+ if (!this.serviceInitialized)
1015
+ {
1016
+ return fCallback(new Error("Retold Facto is being stopped but is not initialized..."));
1017
+ }
1018
+ else
1019
+ {
1020
+ this.fable.log.info(`Retold Facto is stopping Orator`);
1021
+
1022
+ let tmpAnticipate = this.fable.newAnticipate();
1023
+
1024
+ tmpAnticipate.anticipate(this.fable.Orator.stopWebServer.bind(this.fable.Orator));
1025
+
1026
+ tmpAnticipate.wait(
1027
+ (pError) =>
1028
+ {
1029
+ if (pError)
1030
+ {
1031
+ this.log.error(`Error stopping Retold Facto: ${pError}`);
1032
+ return fCallback(pError);
1033
+ }
1034
+ this.serviceInitialized = false;
1035
+ return fCallback();
1036
+ });
1037
+ }
1038
+ }
1039
+ }
1040
+
1041
+ module.exports = RetoldFacto;
1042
+ module.exports.FACTO_SCHEMA_SQL = FACTO_SCHEMA_SQL;