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,909 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Retold Facto — CLI Entry Point
4
+ *
5
+ * A data warehouse and knowledge graph storage system.
6
+ *
7
+ * Subcommands:
8
+ * serve Start the API server with web UI (default)
9
+ * init Initialize the database schema
10
+ * ingest <file> Ingest a CSV or JSON file into a dataset
11
+ * source list List all registered sources
12
+ * source add <name> Register a new data source
13
+ * dataset list List all datasets
14
+ * dataset add <name> Create a new dataset
15
+ *
16
+ * @author Steven Velozo <steven@velozo.com>
17
+ */
18
+ const libFable = require('pict');
19
+ const libMeadowConnectionManager = require('meadow-connection-manager');
20
+ const libRetoldFacto = require('../source/Retold-Facto.js');
21
+
22
+ const libFs = require('fs');
23
+ const libPath = require('path');
24
+
25
+ // ================================================================
26
+ // CLI Argument Parsing
27
+ // ================================================================
28
+
29
+ let _CLIConfig = null;
30
+ let _CLILogPath = null;
31
+ let _CLIPort = null;
32
+ let _CLIDBPath = null;
33
+ let _CLIScanPaths = null;
34
+ let _CLICommand = 'serve';
35
+ let _CLIArgs = [];
36
+
37
+ // Parse arguments
38
+ let tmpArgs = process.argv.slice(2);
39
+ let tmpPositionalIndex = 0;
40
+
41
+ for (let i = 0; i < tmpArgs.length; i++)
42
+ {
43
+ let tmpArg = tmpArgs[i];
44
+
45
+ if (tmpArg === '--config' || tmpArg === '-c')
46
+ {
47
+ if (tmpArgs[i + 1])
48
+ {
49
+ let tmpConfigPath = libPath.resolve(tmpArgs[i + 1]);
50
+ try
51
+ {
52
+ let tmpRaw = libFs.readFileSync(tmpConfigPath, 'utf8');
53
+ _CLIConfig = JSON.parse(tmpRaw);
54
+ console.log(`Retold Facto: Loaded config from ${tmpConfigPath}`);
55
+ }
56
+ catch (pConfigError)
57
+ {
58
+ console.error(`Retold Facto: Failed to load config from ${tmpConfigPath}: ${pConfigError.message}`);
59
+ process.exit(1);
60
+ }
61
+ i++;
62
+ }
63
+ }
64
+ else if (tmpArg === '--port' || tmpArg === '-p')
65
+ {
66
+ if (tmpArgs[i + 1])
67
+ {
68
+ _CLIPort = parseInt(tmpArgs[i + 1], 10);
69
+ i++;
70
+ }
71
+ }
72
+ else if (tmpArg === '--db' || tmpArg === '-d')
73
+ {
74
+ if (tmpArgs[i + 1])
75
+ {
76
+ _CLIDBPath = libPath.resolve(tmpArgs[i + 1]);
77
+ i++;
78
+ }
79
+ }
80
+ else if (tmpArg === '--log' || tmpArg === '-l')
81
+ {
82
+ if (tmpArgs[i + 1] && !tmpArgs[i + 1].startsWith('-'))
83
+ {
84
+ _CLILogPath = libPath.resolve(tmpArgs[i + 1]);
85
+ i++;
86
+ }
87
+ else
88
+ {
89
+ _CLILogPath = `${process.cwd()}/Facto-Run-${Date.now()}.log`;
90
+ }
91
+ }
92
+ else if (tmpArg === '--scan-path' || tmpArg === '-s')
93
+ {
94
+ if (tmpArgs[i + 1])
95
+ {
96
+ if (!_CLIScanPaths)
97
+ {
98
+ _CLIScanPaths = [];
99
+ }
100
+ _CLIScanPaths.push(libPath.resolve(tmpArgs[i + 1]));
101
+ i++;
102
+ }
103
+ }
104
+ else if (tmpArg === '--help' || tmpArg === '-h')
105
+ {
106
+ printHelp();
107
+ process.exit(0);
108
+ }
109
+ else if (!tmpArg.startsWith('-'))
110
+ {
111
+ // Positional argument
112
+ if (tmpPositionalIndex === 0)
113
+ {
114
+ _CLICommand = tmpArg;
115
+ }
116
+ else
117
+ {
118
+ _CLIArgs.push(tmpArg);
119
+ }
120
+ tmpPositionalIndex++;
121
+ }
122
+ }
123
+
124
+ function printHelp()
125
+ {
126
+ console.log(`
127
+ Retold Facto — Data Warehouse and Knowledge Graph Storage
128
+
129
+ Usage:
130
+ retold-facto [command] [options]
131
+
132
+ Commands:
133
+ serve Start the API server with web UI (default)
134
+ init Initialize/create the database schema
135
+ ingest <file> Ingest a CSV or JSON file into a dataset
136
+ source list List all registered sources
137
+ source add <name> [options] Register a new data source
138
+ dataset list List all datasets
139
+ dataset add <name> [options] Create a new dataset
140
+ scan <folder> Scan a folder tree and list discovered datasets
141
+ scan provision <folder> [name] Provision one or all discovered datasets
142
+ scan ingest <folder> [name] Ingest one or all discovered datasets
143
+
144
+ Options:
145
+ --config, -c <path> Path to a JSON config file
146
+ --port, -p <port> Override the API server port (default: 8386)
147
+ --db, -d <path> Path to SQLite database file (default: ./data/facto.sqlite)
148
+ --scan-path, -s <path> Add a scan path for serve mode (repeatable)
149
+ --log, -l [path] Write log output to a file
150
+ --help, -h Show this help
151
+
152
+ Ingest Options (positional after file path):
153
+ retold-facto ingest <file> <dataset-id> <source-id> [type]
154
+
155
+ Source Add Options:
156
+ retold-facto source add <name> <type> [url]
157
+
158
+ Dataset Add Options:
159
+ retold-facto dataset add <name> <type> [description]
160
+
161
+ Examples:
162
+ retold-facto Start server on default port
163
+ retold-facto serve --port 9000 Start server on port 9000
164
+ retold-facto init Create database tables
165
+ retold-facto ingest data.csv 1 1 Ingest CSV into dataset 1 from source 1
166
+ retold-facto source add "Census API" API Register an API source
167
+ retold-facto dataset add "Pop 2020" Raw Create a Raw dataset
168
+ `);
169
+ }
170
+
171
+ // ================================================================
172
+ // Configuration
173
+ // ================================================================
174
+
175
+ let _Settings = (
176
+ {
177
+ Product: 'RetoldFacto',
178
+ ProductVersion: '0.0.1',
179
+ APIServerPort: _CLIPort || parseInt(process.env.PORT, 10) || 8386,
180
+ LogStreams:
181
+ [
182
+ {
183
+ streamtype: 'console'
184
+ }
185
+ ],
186
+
187
+ SQLite:
188
+ {
189
+ SQLiteFilePath: _CLIDBPath || libPath.join(process.cwd(), 'data', 'facto.sqlite')
190
+ }
191
+ });
192
+
193
+ // Merge CLI config if provided
194
+ if (_CLIConfig)
195
+ {
196
+ Object.assign(_Settings, _CLIConfig);
197
+ }
198
+
199
+ if (_CLILogPath)
200
+ {
201
+ _Settings.LogStreams.push(
202
+ {
203
+ loggertype: 'simpleflatfile',
204
+ outputloglinestoconsole: false,
205
+ showtimestamps: true,
206
+ formattedtimestamps: true,
207
+ level: 'trace',
208
+ path: _CLILogPath
209
+ });
210
+ }
211
+
212
+ // For non-serve commands, use quieter logging
213
+ if (_CLICommand !== 'serve')
214
+ {
215
+ _Settings.LogStreams = [{ streamtype: 'console', level: 'warn' }];
216
+ }
217
+
218
+ // Ensure the data directory exists
219
+ let _DataDir = libPath.dirname(_Settings.SQLite.SQLiteFilePath);
220
+ if (_DataDir !== ':memory:' && !libFs.existsSync(_DataDir))
221
+ {
222
+ libFs.mkdirSync(_DataDir, { recursive: true });
223
+ }
224
+
225
+ // ================================================================
226
+ // Bootstrap
227
+ // ================================================================
228
+
229
+ let _Fable = new libFable(_Settings);
230
+
231
+ _Fable.serviceManager.addServiceType('MeadowConnectionManager', libMeadowConnectionManager);
232
+ _Fable.serviceManager.instantiateServiceProvider('MeadowConnectionManager');
233
+
234
+ _Fable.MeadowConnectionManager.connect('facto',
235
+ {
236
+ Type: 'SQLite',
237
+ SQLiteFilePath: _Settings.SQLite.SQLiteFilePath
238
+ },
239
+ (pError, pConnection) =>
240
+ {
241
+ if (pError)
242
+ {
243
+ console.error(`SQLite connection error: ${pError}`);
244
+ process.exit(1);
245
+ }
246
+
247
+ // Bridge: Meadow DAL providers look up fable.MeadowSQLiteProvider
248
+ _Fable.MeadowSQLiteProvider = pConnection.instance;
249
+ _Fable.settings.MeadowProvider = 'SQLite';
250
+
251
+ switch (_CLICommand)
252
+ {
253
+ case 'serve':
254
+ commandServe();
255
+ break;
256
+ case 'init':
257
+ commandInit();
258
+ break;
259
+ case 'ingest':
260
+ commandIngest();
261
+ break;
262
+ case 'source':
263
+ commandSource();
264
+ break;
265
+ case 'dataset':
266
+ commandDataset();
267
+ break;
268
+ case 'scan':
269
+ commandScan();
270
+ break;
271
+ default:
272
+ console.error(`Unknown command: ${_CLICommand}`);
273
+ printHelp();
274
+ process.exit(1);
275
+ }
276
+ });
277
+
278
+ // ================================================================
279
+ // Command: serve
280
+ // ================================================================
281
+ function commandServe()
282
+ {
283
+ let tmpFactoConfig = {
284
+ RoutePrefix: '/facto',
285
+ DefaultCertaintyValue: 0.5,
286
+ ScanPaths: _CLIScanPaths || [],
287
+ AutoProvision: false,
288
+ AutoIngest: false
289
+ };
290
+
291
+ _Fable.serviceManager.addServiceType('RetoldFacto', libRetoldFacto);
292
+ let tmpFactoService = _Fable.serviceManager.instantiateServiceProvider('RetoldFacto',
293
+ {
294
+ StorageProvider: 'SQLite',
295
+ AutoCreateSchema: true,
296
+
297
+ FullMeadowSchemaPath: libPath.join(__dirname, '..', 'test', 'model') + '/',
298
+ FullMeadowSchemaFilename: 'MeadowModel-Extended.json',
299
+
300
+ Endpoints:
301
+ {
302
+ MeadowEndpoints: true,
303
+ SourceManager: true,
304
+ RecordManager: true,
305
+ DatasetManager: true,
306
+ IngestEngine: true,
307
+ ProjectionEngine: true,
308
+ CatalogManager: true,
309
+ StoreConnectionManager: true,
310
+ SourceFolderScanner: true,
311
+ SchemaManager: true,
312
+ WebUI: true
313
+ },
314
+
315
+ Facto: tmpFactoConfig
316
+ });
317
+
318
+ tmpFactoService.initializeService(
319
+ (pInitError) =>
320
+ {
321
+ if (pInitError)
322
+ {
323
+ _Fable.log.error(`Initialization error: ${pInitError}`);
324
+ process.exit(1);
325
+ }
326
+ _Fable.log.info(`Retold Facto running on port ${_Settings.APIServerPort}`);
327
+ _Fable.log.info(`API: http://localhost:${_Settings.APIServerPort}/1.0/`);
328
+ _Fable.log.info(`Facto: http://localhost:${_Settings.APIServerPort}/facto/`);
329
+ _Fable.log.info(`Web UI: http://localhost:${_Settings.APIServerPort}/`);
330
+ });
331
+ }
332
+
333
+ // ================================================================
334
+ // Command: init
335
+ // ================================================================
336
+ function commandInit()
337
+ {
338
+ console.log('Initializing Facto database schema...');
339
+ try
340
+ {
341
+ _Fable.MeadowSQLiteProvider.db.exec(libRetoldFacto.FACTO_SCHEMA_SQL);
342
+ console.log('Schema created successfully.');
343
+ console.log(`Database: ${_Settings.SQLite.SQLiteFilePath}`);
344
+ }
345
+ catch (pError)
346
+ {
347
+ console.error(`Error creating schema: ${pError.message}`);
348
+ process.exit(1);
349
+ }
350
+ process.exit(0);
351
+ }
352
+
353
+ // ================================================================
354
+ // Command: ingest <file> [dataset-id] [source-id] [type]
355
+ // ================================================================
356
+ function commandIngest()
357
+ {
358
+ let tmpFilePath = _CLIArgs[0];
359
+ if (!tmpFilePath)
360
+ {
361
+ console.error('Error: File path is required for ingest command.');
362
+ console.error('Usage: retold-facto ingest <file> [dataset-id] [source-id] [type]');
363
+ process.exit(1);
364
+ }
365
+
366
+ tmpFilePath = libPath.resolve(tmpFilePath);
367
+ if (!libFs.existsSync(tmpFilePath))
368
+ {
369
+ console.error(`Error: File not found: ${tmpFilePath}`);
370
+ process.exit(1);
371
+ }
372
+
373
+ let tmpIDDataset = parseInt(_CLIArgs[1], 10) || 1;
374
+ let tmpIDSource = parseInt(_CLIArgs[2], 10) || 1;
375
+ let tmpRecordType = _CLIArgs[3] || 'file-ingest';
376
+
377
+ // Create schema, set up service, then ingest
378
+ _Fable.MeadowSQLiteProvider.db.exec(libRetoldFacto.FACTO_SCHEMA_SQL);
379
+
380
+ _Fable.serviceManager.addServiceType('RetoldFacto', libRetoldFacto);
381
+ let tmpFactoService = _Fable.serviceManager.instantiateServiceProvider('RetoldFacto',
382
+ {
383
+ StorageProvider: 'SQLite',
384
+ AutoStartOrator: false,
385
+
386
+ FullMeadowSchemaPath: libPath.join(__dirname, '..', 'test', 'model') + '/',
387
+ FullMeadowSchemaFilename: 'MeadowModel-Extended.json',
388
+
389
+ Endpoints:
390
+ {
391
+ MeadowEndpoints: true,
392
+ SourceManager: false,
393
+ RecordManager: false,
394
+ DatasetManager: false,
395
+ IngestEngine: true,
396
+ ProjectionEngine: false,
397
+ WebUI: false
398
+ }
399
+ });
400
+
401
+ tmpFactoService.initializeService(
402
+ (pInitError) =>
403
+ {
404
+ if (pInitError)
405
+ {
406
+ console.error(`Initialization error: ${pInitError}`);
407
+ process.exit(1);
408
+ }
409
+
410
+ console.log(`Ingesting file: ${tmpFilePath}`);
411
+ console.log(` Dataset ID: ${tmpIDDataset}, Source ID: ${tmpIDSource}, Type: ${tmpRecordType}`);
412
+
413
+ _Fable.RetoldFactoIngestEngine.ingestFile(tmpFilePath, tmpIDDataset, tmpIDSource,
414
+ { type: tmpRecordType },
415
+ (pIngestError, pResult) =>
416
+ {
417
+ if (pIngestError)
418
+ {
419
+ console.error(`Ingest error: ${pIngestError.message}`);
420
+ process.exit(1);
421
+ }
422
+ console.log(`Ingest complete:`);
423
+ console.log(` Format: ${pResult.Format}`);
424
+ console.log(` Total: ${pResult.Total}`);
425
+ console.log(` Ingested: ${pResult.Ingested}`);
426
+ console.log(` Errors: ${pResult.Errors}`);
427
+ process.exit(0);
428
+ });
429
+ });
430
+ }
431
+
432
+ // ================================================================
433
+ // Command: source [list|add]
434
+ // ================================================================
435
+ function commandSource()
436
+ {
437
+ let tmpSubCommand = _CLIArgs[0] || 'list';
438
+
439
+ // Create schema and set up service
440
+ _Fable.MeadowSQLiteProvider.db.exec(libRetoldFacto.FACTO_SCHEMA_SQL);
441
+
442
+ _Fable.serviceManager.addServiceType('RetoldFacto', libRetoldFacto);
443
+ let tmpFactoService = _Fable.serviceManager.instantiateServiceProvider('RetoldFacto',
444
+ {
445
+ StorageProvider: 'SQLite',
446
+ AutoStartOrator: false,
447
+
448
+ FullMeadowSchemaPath: libPath.join(__dirname, '..', 'test', 'model') + '/',
449
+ FullMeadowSchemaFilename: 'MeadowModel-Extended.json',
450
+
451
+ Endpoints:
452
+ {
453
+ MeadowEndpoints: true,
454
+ SourceManager: false,
455
+ RecordManager: false,
456
+ DatasetManager: false,
457
+ IngestEngine: false,
458
+ ProjectionEngine: false,
459
+ WebUI: false
460
+ }
461
+ });
462
+
463
+ tmpFactoService.initializeService(
464
+ (pInitError) =>
465
+ {
466
+ if (pInitError)
467
+ {
468
+ console.error(`Initialization error: ${pInitError}`);
469
+ process.exit(1);
470
+ }
471
+
472
+ if (tmpSubCommand === 'list')
473
+ {
474
+ let tmpQuery = _Fable.DAL.Source.query.clone()
475
+ .addFilter('Deleted', 0)
476
+ .setCap(1000);
477
+
478
+ _Fable.DAL.Source.doReads(tmpQuery,
479
+ (pError, pQuery, pRecords) =>
480
+ {
481
+ if (pError)
482
+ {
483
+ console.error(`Error listing sources: ${pError}`);
484
+ process.exit(1);
485
+ }
486
+ if (pRecords.length === 0)
487
+ {
488
+ console.log('No sources registered.');
489
+ }
490
+ else
491
+ {
492
+ console.log(`\n ID | Name | Type | Active`);
493
+ console.log(`------+--------------------------------+----------+-------`);
494
+ for (let i = 0; i < pRecords.length; i++)
495
+ {
496
+ let tmpS = pRecords[i];
497
+ let tmpName = (tmpS.Name || '').substring(0, 30).padEnd(30);
498
+ let tmpType = (tmpS.Type || '').substring(0, 8).padEnd(8);
499
+ let tmpActive = tmpS.Active ? 'Yes' : 'No';
500
+ console.log(` ${String(tmpS.IDSource).padStart(3)} | ${tmpName} | ${tmpType} | ${tmpActive}`);
501
+ }
502
+ console.log(`\n Total: ${pRecords.length} source(s)`);
503
+ }
504
+ process.exit(0);
505
+ });
506
+ }
507
+ else if (tmpSubCommand === 'add')
508
+ {
509
+ let tmpName = _CLIArgs[1];
510
+ if (!tmpName)
511
+ {
512
+ console.error('Error: Name is required for source add.');
513
+ console.error('Usage: retold-facto source add <name> [type] [url]');
514
+ process.exit(1);
515
+ }
516
+
517
+ let tmpType = _CLIArgs[2] || 'Manual';
518
+ let tmpURL = _CLIArgs[3] || '';
519
+
520
+ let tmpQuery = _Fable.DAL.Source.query.clone()
521
+ .addRecord(
522
+ {
523
+ Name: tmpName,
524
+ Type: tmpType,
525
+ URL: tmpURL,
526
+ Active: 1
527
+ });
528
+
529
+ _Fable.DAL.Source.doCreate(tmpQuery,
530
+ (pError, pQuery, pQueryRead, pRecord) =>
531
+ {
532
+ if (pError)
533
+ {
534
+ console.error(`Error creating source: ${pError}`);
535
+ process.exit(1);
536
+ }
537
+ console.log(`Source created: #${pRecord.IDSource} "${pRecord.Name}" (${pRecord.Type})`);
538
+ process.exit(0);
539
+ });
540
+ }
541
+ else
542
+ {
543
+ console.error(`Unknown source subcommand: ${tmpSubCommand}`);
544
+ console.error('Usage: retold-facto source [list|add]');
545
+ process.exit(1);
546
+ }
547
+ });
548
+ }
549
+
550
+ // ================================================================
551
+ // Command: dataset [list|add]
552
+ // ================================================================
553
+ function commandDataset()
554
+ {
555
+ let tmpSubCommand = _CLIArgs[0] || 'list';
556
+
557
+ // Create schema and set up service
558
+ _Fable.MeadowSQLiteProvider.db.exec(libRetoldFacto.FACTO_SCHEMA_SQL);
559
+
560
+ _Fable.serviceManager.addServiceType('RetoldFacto', libRetoldFacto);
561
+ let tmpFactoService = _Fable.serviceManager.instantiateServiceProvider('RetoldFacto',
562
+ {
563
+ StorageProvider: 'SQLite',
564
+ AutoStartOrator: false,
565
+
566
+ FullMeadowSchemaPath: libPath.join(__dirname, '..', 'test', 'model') + '/',
567
+ FullMeadowSchemaFilename: 'MeadowModel-Extended.json',
568
+
569
+ Endpoints:
570
+ {
571
+ MeadowEndpoints: true,
572
+ SourceManager: false,
573
+ RecordManager: false,
574
+ DatasetManager: false,
575
+ IngestEngine: false,
576
+ ProjectionEngine: false,
577
+ WebUI: false
578
+ }
579
+ });
580
+
581
+ tmpFactoService.initializeService(
582
+ (pInitError) =>
583
+ {
584
+ if (pInitError)
585
+ {
586
+ console.error(`Initialization error: ${pInitError}`);
587
+ process.exit(1);
588
+ }
589
+
590
+ if (tmpSubCommand === 'list')
591
+ {
592
+ let tmpQuery = _Fable.DAL.Dataset.query.clone()
593
+ .addFilter('Deleted', 0)
594
+ .setCap(1000);
595
+
596
+ _Fable.DAL.Dataset.doReads(tmpQuery,
597
+ (pError, pQuery, pRecords) =>
598
+ {
599
+ if (pError)
600
+ {
601
+ console.error(`Error listing datasets: ${pError}`);
602
+ process.exit(1);
603
+ }
604
+ if (pRecords.length === 0)
605
+ {
606
+ console.log('No datasets created.');
607
+ }
608
+ else
609
+ {
610
+ console.log(`\n ID | Name | Type | Description`);
611
+ console.log(`------+--------------------------------+----------------+----------------------------`);
612
+ for (let i = 0; i < pRecords.length; i++)
613
+ {
614
+ let tmpD = pRecords[i];
615
+ let tmpName = (tmpD.Name || '').substring(0, 30).padEnd(30);
616
+ let tmpType = (tmpD.Type || '').substring(0, 14).padEnd(14);
617
+ let tmpDesc = (tmpD.Description || '').substring(0, 26);
618
+ console.log(` ${String(tmpD.IDDataset).padStart(3)} | ${tmpName} | ${tmpType} | ${tmpDesc}`);
619
+ }
620
+ console.log(`\n Total: ${pRecords.length} dataset(s)`);
621
+ }
622
+ process.exit(0);
623
+ });
624
+ }
625
+ else if (tmpSubCommand === 'add')
626
+ {
627
+ let tmpName = _CLIArgs[1];
628
+ if (!tmpName)
629
+ {
630
+ console.error('Error: Name is required for dataset add.');
631
+ console.error('Usage: retold-facto dataset add <name> [type] [description]');
632
+ process.exit(1);
633
+ }
634
+
635
+ let tmpType = _CLIArgs[2] || 'Raw';
636
+ let tmpDescription = _CLIArgs[3] || '';
637
+
638
+ let tmpValidTypes = ['Raw', 'Compositional', 'Projection', 'Derived'];
639
+ if (tmpValidTypes.indexOf(tmpType) < 0)
640
+ {
641
+ console.error(`Error: Invalid dataset type "${tmpType}". Valid types: ${tmpValidTypes.join(', ')}`);
642
+ process.exit(1);
643
+ }
644
+
645
+ let tmpQuery = _Fable.DAL.Dataset.query.clone()
646
+ .addRecord(
647
+ {
648
+ Name: tmpName,
649
+ Type: tmpType,
650
+ Description: tmpDescription
651
+ });
652
+
653
+ _Fable.DAL.Dataset.doCreate(tmpQuery,
654
+ (pError, pQuery, pQueryRead, pRecord) =>
655
+ {
656
+ if (pError)
657
+ {
658
+ console.error(`Error creating dataset: ${pError}`);
659
+ process.exit(1);
660
+ }
661
+ console.log(`Dataset created: #${pRecord.IDDataset} "${pRecord.Name}" (${pRecord.Type})`);
662
+ process.exit(0);
663
+ });
664
+ }
665
+ else
666
+ {
667
+ console.error(`Unknown dataset subcommand: ${tmpSubCommand}`);
668
+ console.error('Usage: retold-facto dataset [list|add]');
669
+ process.exit(1);
670
+ }
671
+ });
672
+ }
673
+
674
+ // ================================================================
675
+ // Command: scan <folder> | scan provision <folder> [name] | scan ingest <folder> [name]
676
+ // ================================================================
677
+ function commandScan()
678
+ {
679
+ let tmpSubCommand = _CLIArgs[0];
680
+ if (!tmpSubCommand)
681
+ {
682
+ console.error('Error: Folder path is required for scan command.');
683
+ console.error('Usage: retold-facto scan <folder>');
684
+ console.error(' retold-facto scan provision <folder> [dataset-name]');
685
+ console.error(' retold-facto scan ingest <folder> [dataset-name]');
686
+ process.exit(1);
687
+ }
688
+
689
+ // Determine if this is a subcommand or a folder path
690
+ let tmpIsSubCommand = (tmpSubCommand === 'provision' || tmpSubCommand === 'ingest');
691
+ let tmpScanFolder;
692
+ let tmpDatasetName;
693
+
694
+ if (tmpIsSubCommand)
695
+ {
696
+ tmpScanFolder = _CLIArgs[1];
697
+ tmpDatasetName = _CLIArgs[2];
698
+ if (!tmpScanFolder)
699
+ {
700
+ console.error(`Error: Folder path is required for scan ${tmpSubCommand}.`);
701
+ process.exit(1);
702
+ }
703
+ }
704
+ else
705
+ {
706
+ // First arg is the folder path, treat as a plain scan
707
+ tmpScanFolder = tmpSubCommand;
708
+ tmpSubCommand = 'list';
709
+ }
710
+
711
+ tmpScanFolder = libPath.resolve(tmpScanFolder);
712
+
713
+ if (!libFs.existsSync(tmpScanFolder))
714
+ {
715
+ console.error(`Error: Folder not found: ${tmpScanFolder}`);
716
+ process.exit(1);
717
+ }
718
+
719
+ // Determine if provision/ingest need the full service (with DB)
720
+ let tmpNeedsDB = (tmpSubCommand === 'provision' || tmpSubCommand === 'ingest');
721
+
722
+ if (tmpNeedsDB)
723
+ {
724
+ _Fable.MeadowSQLiteProvider.db.exec(libRetoldFacto.FACTO_SCHEMA_SQL);
725
+ }
726
+
727
+ _Fable.serviceManager.addServiceType('RetoldFacto', libRetoldFacto);
728
+ let tmpFactoService = _Fable.serviceManager.instantiateServiceProvider('RetoldFacto',
729
+ {
730
+ StorageProvider: 'SQLite',
731
+ AutoStartOrator: false,
732
+ AutoCreateSchema: tmpNeedsDB,
733
+
734
+ FullMeadowSchemaPath: libPath.join(__dirname, '..', 'test', 'model') + '/',
735
+ FullMeadowSchemaFilename: 'MeadowModel-Extended.json',
736
+
737
+ Endpoints:
738
+ {
739
+ MeadowEndpoints: tmpNeedsDB,
740
+ SourceManager: false,
741
+ RecordManager: false,
742
+ DatasetManager: false,
743
+ IngestEngine: tmpNeedsDB,
744
+ ProjectionEngine: false,
745
+ CatalogManager: tmpNeedsDB,
746
+ SourceFolderScanner: false,
747
+ WebUI: false
748
+ }
749
+ });
750
+
751
+ tmpFactoService.initializeService(
752
+ (pInitError) =>
753
+ {
754
+ if (pInitError)
755
+ {
756
+ console.error(`Initialization error: ${pInitError}`);
757
+ process.exit(1);
758
+ }
759
+
760
+ let tmpScanner = _Fable.RetoldFactoSourceFolderScanner;
761
+
762
+ tmpScanner.addScanPath(tmpScanFolder,
763
+ (pScanError) =>
764
+ {
765
+ if (pScanError)
766
+ {
767
+ console.error(`Scan error: ${pScanError}`);
768
+ process.exit(1);
769
+ }
770
+
771
+ if (tmpSubCommand === 'list')
772
+ {
773
+ let tmpDatasets = tmpScanner.getDiscoveredDatasets();
774
+ if (tmpDatasets.length === 0)
775
+ {
776
+ console.log('No datasets discovered.');
777
+ process.exit(0);
778
+ }
779
+
780
+ console.log(`\nDiscovered ${tmpDatasets.length} dataset(s) in ${tmpScanFolder}\n`);
781
+ console.log(` ${'Name'.padEnd(40)} | ${'Status'.padEnd(12)} | ${'Data Files'.padEnd(10)} | Title`);
782
+ console.log(` ${''.padEnd(40, '-')}+${''.padEnd(14, '-')}+${''.padEnd(12, '-')}+${''.padEnd(30, '-')}`);
783
+ for (let i = 0; i < tmpDatasets.length; i++)
784
+ {
785
+ let tmpDS = tmpDatasets[i];
786
+ let tmpName = (tmpDS.FolderName || '').substring(0, 38).padEnd(40);
787
+ let tmpStatus = (tmpDS.Status || '').padEnd(12);
788
+ let tmpFiles = String(tmpDS.DataFiles ? tmpDS.DataFiles.length : 0).padEnd(10);
789
+ let tmpTitle = (tmpDS.Title || '').substring(0, 40);
790
+ console.log(` ${tmpName}| ${tmpStatus}| ${tmpFiles}| ${tmpTitle}`);
791
+ }
792
+ console.log('');
793
+ process.exit(0);
794
+ }
795
+ else if (tmpSubCommand === 'provision')
796
+ {
797
+ if (tmpDatasetName)
798
+ {
799
+ // Provision a single dataset
800
+ let tmpDS = tmpScanner.getDiscoveredDatasetByName(tmpDatasetName);
801
+ if (!tmpDS)
802
+ {
803
+ console.error(`Dataset not found: ${tmpDatasetName}`);
804
+ process.exit(1);
805
+ }
806
+ tmpScanner.provisionDataset(tmpDS.FolderPath,
807
+ (pProvError) =>
808
+ {
809
+ if (pProvError)
810
+ {
811
+ console.error(`Provision error: ${pProvError}`);
812
+ process.exit(1);
813
+ }
814
+ console.log(`Provisioned: ${tmpDS.FolderName} (Source #${tmpDS.IDSource}, Dataset #${tmpDS.IDDataset})`);
815
+ process.exit(0);
816
+ });
817
+ }
818
+ else
819
+ {
820
+ // Provision all
821
+ let tmpAllDatasets = tmpScanner.getDiscoveredDatasets();
822
+ let tmpCount = 0;
823
+ let tmpErrors = 0;
824
+
825
+ let tmpProvisionNext = () =>
826
+ {
827
+ if (tmpCount >= tmpAllDatasets.length)
828
+ {
829
+ console.log(`\nProvisioned ${tmpCount - tmpErrors} of ${tmpAllDatasets.length} dataset(s) (${tmpErrors} error(s))`);
830
+ process.exit(tmpErrors > 0 ? 1 : 0);
831
+ }
832
+
833
+ let tmpDS = tmpAllDatasets[tmpCount];
834
+ tmpScanner.provisionDataset(tmpDS.FolderPath,
835
+ (pProvError) =>
836
+ {
837
+ if (pProvError)
838
+ {
839
+ console.error(` Error provisioning ${tmpDS.FolderName}: ${pProvError}`);
840
+ tmpErrors++;
841
+ }
842
+ else
843
+ {
844
+ console.log(` Provisioned: ${tmpDS.FolderName}`);
845
+ }
846
+ tmpCount++;
847
+ tmpProvisionNext();
848
+ });
849
+ };
850
+ console.log(`Provisioning ${tmpAllDatasets.length} dataset(s)...`);
851
+ tmpProvisionNext();
852
+ }
853
+ }
854
+ else if (tmpSubCommand === 'ingest')
855
+ {
856
+ if (tmpDatasetName)
857
+ {
858
+ // Ingest a single dataset
859
+ let tmpDS = tmpScanner.getDiscoveredDatasetByName(tmpDatasetName);
860
+ if (!tmpDS)
861
+ {
862
+ console.error(`Dataset not found: ${tmpDatasetName}`);
863
+ process.exit(1);
864
+ }
865
+ // Provision first if needed
866
+ let tmpDoIngest = () =>
867
+ {
868
+ tmpScanner.ingestDataset(tmpDS.FolderPath, {},
869
+ (pIngestError) =>
870
+ {
871
+ if (pIngestError)
872
+ {
873
+ console.error(`Ingest error: ${pIngestError}`);
874
+ process.exit(1);
875
+ }
876
+ console.log(`Ingested: ${tmpDS.FolderName}`);
877
+ process.exit(0);
878
+ });
879
+ };
880
+
881
+ if (tmpDS.Status === 'Discovered')
882
+ {
883
+ tmpScanner.provisionDataset(tmpDS.FolderPath,
884
+ (pProvError) =>
885
+ {
886
+ if (pProvError)
887
+ {
888
+ console.error(`Provision error: ${pProvError}`);
889
+ process.exit(1);
890
+ }
891
+ console.log(` Provisioned: ${tmpDS.FolderName}`);
892
+ tmpDoIngest();
893
+ });
894
+ }
895
+ else
896
+ {
897
+ tmpDoIngest();
898
+ }
899
+ }
900
+ else
901
+ {
902
+ console.error('Error: Dataset name is required for scan ingest.');
903
+ console.error('Usage: retold-facto scan ingest <folder> <dataset-name>');
904
+ process.exit(1);
905
+ }
906
+ }
907
+ });
908
+ });
909
+ }