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,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;
|