retold-data-service 2.0.12 → 2.0.14
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/README.md +64 -55
- package/docs/README.md +24 -66
- package/docs/_cover.md +7 -7
- package/docs/_sidebar.md +24 -11
- package/docs/_topbar.md +2 -0
- package/docs/api/constructor.md +67 -0
- package/docs/api/initializeDataEndpoints.md +54 -0
- package/docs/api/initializePersistenceEngine.md +34 -0
- package/docs/api/initializeService.md +44 -0
- package/docs/api/onAfterInitialize.md +48 -0
- package/docs/api/onBeforeInitialize.md +45 -0
- package/docs/api/onInitialize.md +38 -0
- package/docs/api/reference.md +35 -0
- package/docs/api/stopService.md +34 -0
- package/docs/architecture.md +78 -14
- package/docs/behavior-injection.md +121 -78
- package/docs/css/docuserve.css +73 -0
- package/docs/dal-access.md +135 -56
- package/docs/index.html +1 -1
- package/docs/quick-start.md +169 -0
- package/docs/retold-catalog.json +144 -0
- package/docs/retold-keyword-index.json +3530 -0
- package/example_applications/data-cloner/data/cloned.sqlite +0 -0
- package/example_applications/data-cloner/data/cloned.sqlite-shm +0 -0
- package/example_applications/data-cloner/data/cloned.sqlite-wal +0 -0
- package/example_applications/data-cloner/data-cloner-web.html +935 -0
- package/example_applications/data-cloner/data-cloner.js +1047 -0
- package/example_applications/data-cloner/package.json +19 -0
- package/package.json +13 -9
- package/source/Retold-Data-Service.js +225 -73
- package/source/services/Retold-Data-Service-ConnectionManager.js +277 -0
- package/source/services/Retold-Data-Service-MeadowEndpoints.js +217 -0
- package/source/services/Retold-Data-Service-ModelManager.js +335 -0
- package/source/services/meadow-integration/MeadowIntegration-Command-CSVCheck.js +85 -0
- package/source/services/meadow-integration/MeadowIntegration-Command-CSVTransform.js +180 -0
- package/source/services/meadow-integration/MeadowIntegration-Command-ComprehensionIntersect.js +153 -0
- package/source/services/meadow-integration/MeadowIntegration-Command-ComprehensionPush.js +190 -0
- package/source/services/meadow-integration/MeadowIntegration-Command-ComprehensionToArray.js +113 -0
- package/source/services/meadow-integration/MeadowIntegration-Command-ComprehensionToCSV.js +211 -0
- package/source/services/meadow-integration/MeadowIntegration-Command-EntityFromTabularFolder.js +244 -0
- package/source/services/meadow-integration/MeadowIntegration-Command-JSONArrayTransform.js +213 -0
- package/source/services/meadow-integration/MeadowIntegration-Command-TSVCheck.js +80 -0
- package/source/services/meadow-integration/MeadowIntegration-Command-TSVTransform.js +166 -0
- package/source/services/meadow-integration/Retold-Data-Service-MeadowIntegration.js +113 -0
- package/source/services/migration-manager/MigrationManager-Command-Connections.js +220 -0
- package/source/services/migration-manager/MigrationManager-Command-DiffMigrate.js +169 -0
- package/source/services/migration-manager/MigrationManager-Command-Schemas.js +532 -0
- package/source/services/migration-manager/MigrationManager-Command-WebUI.js +123 -0
- package/source/services/migration-manager/Retold-Data-Service-MigrationManager.js +357 -0
- package/source/services/stricture/Retold-Data-Service-Stricture.js +303 -0
- package/source/services/stricture/Stricture-Command-Compile.js +39 -0
- package/source/services/stricture/Stricture-Command-Generate-AuthorizationChart.js +14 -0
- package/source/services/stricture/Stricture-Command-Generate-DictionaryCSV.js +14 -0
- package/source/services/stricture/Stricture-Command-Generate-LaTeX.js +14 -0
- package/source/services/stricture/Stricture-Command-Generate-Markdown.js +14 -0
- package/source/services/stricture/Stricture-Command-Generate-Meadow.js +14 -0
- package/source/services/stricture/Stricture-Command-Generate-ModelGraph.js +14 -0
- package/source/services/stricture/Stricture-Command-Generate-MySQL.js +14 -0
- package/source/services/stricture/Stricture-Command-Generate-MySQLMigrate.js +14 -0
- package/source/services/stricture/Stricture-Command-Generate-Pict.js +14 -0
- package/source/services/stricture/Stricture-Command-Generate-TestObjectContainers.js +14 -0
- package/test/RetoldDataService_tests.js +161 -1
|
@@ -0,0 +1,357 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Retold Data Service - Migration Manager Service
|
|
3
|
+
*
|
|
4
|
+
* Fable service that exposes meadow-migrationmanager capabilities via REST
|
|
5
|
+
* endpoints. Creates an isolated MeadowMigrationManager instance (extends
|
|
6
|
+
* Pict) and instantiates all migration-manager services on it.
|
|
7
|
+
*
|
|
8
|
+
* Two route groups:
|
|
9
|
+
* connectRoutes() — JSON API endpoints under /api/*
|
|
10
|
+
* connectWebUIRoutes() — Web UI HTML + /lib/* static JS libraries
|
|
11
|
+
*
|
|
12
|
+
* @author Steven Velozo <steven@velozo.com>
|
|
13
|
+
*/
|
|
14
|
+
const libFableServiceProviderBase = require('fable-serviceproviderbase');
|
|
15
|
+
|
|
16
|
+
const libFs = require('fs');
|
|
17
|
+
const libPath = require('path');
|
|
18
|
+
|
|
19
|
+
const libMeadowMigrationManager = require('meadow-migrationmanager');
|
|
20
|
+
|
|
21
|
+
class RetoldDataServiceMigrationManager extends libFableServiceProviderBase
|
|
22
|
+
{
|
|
23
|
+
constructor(pFable, pOptions, pServiceHash)
|
|
24
|
+
{
|
|
25
|
+
super(pFable, pOptions, pServiceHash);
|
|
26
|
+
|
|
27
|
+
this.serviceType = 'RetoldDataServiceMigrationManager';
|
|
28
|
+
|
|
29
|
+
// Create an isolated MeadowMigrationManager instance (extends Pict).
|
|
30
|
+
// This keeps migration-manager state completely separate from the
|
|
31
|
+
// main Fable context.
|
|
32
|
+
this._MM = new libMeadowMigrationManager(
|
|
33
|
+
{
|
|
34
|
+
Product: 'RetoldDataServiceMigrationManager',
|
|
35
|
+
LogStreams: pFable.settings.LogStreams || [{ streamtype: 'console' }]
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
// Instantiate migration-manager services on the isolated context
|
|
39
|
+
this._schemaLibrary = this._MM.instantiateServiceProvider('SchemaLibrary');
|
|
40
|
+
this._strictureAdapter = this._MM.instantiateServiceProvider('StrictureAdapter');
|
|
41
|
+
this._schemaVisualizer = this._MM.instantiateServiceProvider('SchemaVisualizer');
|
|
42
|
+
this._schemaDiff = this._MM.instantiateServiceProvider('SchemaDiff');
|
|
43
|
+
this._migrationGenerator = this._MM.instantiateServiceProvider('MigrationGenerator');
|
|
44
|
+
this._flowDataBuilder = this._MM.instantiateServiceProvider('FlowDataBuilder');
|
|
45
|
+
this._connectionLibrary = this._MM.instantiateServiceProvider('ConnectionLibrary');
|
|
46
|
+
this._databaseProviderFactory = this._MM.instantiateServiceProvider('DatabaseProviderFactory');
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* The underlying MeadowMigrationManager (Pict) instance.
|
|
51
|
+
*/
|
|
52
|
+
get migrationManager()
|
|
53
|
+
{
|
|
54
|
+
return this._MM;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* The route prefix for all migration manager endpoints.
|
|
59
|
+
*/
|
|
60
|
+
get routePrefix()
|
|
61
|
+
{
|
|
62
|
+
let tmpConfig = this.fable.RetoldDataService.options.MigrationManager || {};
|
|
63
|
+
return tmpConfig.RoutePrefix || '';
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* The model path for DDL files.
|
|
68
|
+
*/
|
|
69
|
+
get modelPath()
|
|
70
|
+
{
|
|
71
|
+
let tmpMigrationManagerConfig = this.fable.RetoldDataService.options.MigrationManager || {};
|
|
72
|
+
return tmpMigrationManagerConfig.ModelPath || false;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// ================================================================
|
|
76
|
+
// Helper functions (from MigrationManager-Server-Setup.js)
|
|
77
|
+
// ================================================================
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Scan a directory for DDL files (.mddl and .ddl) non-recursively.
|
|
81
|
+
*
|
|
82
|
+
* @param {string} pDirPath - Directory path to scan
|
|
83
|
+
* @return {Array<string>} Array of absolute file paths
|
|
84
|
+
*/
|
|
85
|
+
scanForDDLFiles(pDirPath)
|
|
86
|
+
{
|
|
87
|
+
let tmpFiles = [];
|
|
88
|
+
|
|
89
|
+
try
|
|
90
|
+
{
|
|
91
|
+
let tmpEntries = libFs.readdirSync(pDirPath);
|
|
92
|
+
|
|
93
|
+
for (let i = 0; i < tmpEntries.length; i++)
|
|
94
|
+
{
|
|
95
|
+
let tmpEntry = tmpEntries[i];
|
|
96
|
+
|
|
97
|
+
if (tmpEntry.endsWith('.mddl') || tmpEntry.endsWith('.ddl'))
|
|
98
|
+
{
|
|
99
|
+
tmpFiles.push(libPath.join(pDirPath, tmpEntry));
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
catch (pError)
|
|
104
|
+
{
|
|
105
|
+
// Directory read failed; return empty
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
return tmpFiles;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Normalize a compiled schema's Tables property from hash to array.
|
|
113
|
+
*
|
|
114
|
+
* @param {Object} pCompiledSchema - A compiled schema object
|
|
115
|
+
* @return {Object} Schema with Tables normalized to array format
|
|
116
|
+
*/
|
|
117
|
+
normalizeSchemaForDiff(pCompiledSchema)
|
|
118
|
+
{
|
|
119
|
+
if (!pCompiledSchema || !pCompiledSchema.Tables)
|
|
120
|
+
{
|
|
121
|
+
return { Tables: [] };
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
if (Array.isArray(pCompiledSchema.Tables))
|
|
125
|
+
{
|
|
126
|
+
return pCompiledSchema;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
let tmpNormalized = Object.assign({}, pCompiledSchema);
|
|
130
|
+
let tmpTables = [];
|
|
131
|
+
let tmpTableKeys = Object.keys(pCompiledSchema.Tables);
|
|
132
|
+
|
|
133
|
+
for (let i = 0; i < tmpTableKeys.length; i++)
|
|
134
|
+
{
|
|
135
|
+
tmpTables.push(pCompiledSchema.Tables[tmpTableKeys[i]]);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
tmpNormalized.Tables = tmpTables;
|
|
139
|
+
return tmpNormalized;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Recursively discover all DDL files referenced by [Include ...] directives.
|
|
144
|
+
*
|
|
145
|
+
* @param {string} pFilePath - Absolute path to the DDL file to parse
|
|
146
|
+
* @param {string} pBaseDir - The base model directory for relative path calculation
|
|
147
|
+
* @param {Set} [pVisited] - Set of already-visited absolute paths
|
|
148
|
+
* @param {string} [pIncludedBy] - Relative path of the file that included this one
|
|
149
|
+
* @param {number} [pDepth] - Include depth level
|
|
150
|
+
* @return {Array<Object>} Array of file descriptor objects
|
|
151
|
+
*/
|
|
152
|
+
discoverIncludedFiles(pFilePath, pBaseDir, pVisited, pIncludedBy, pDepth)
|
|
153
|
+
{
|
|
154
|
+
let tmpVisited = pVisited || new Set();
|
|
155
|
+
let tmpResults = [];
|
|
156
|
+
let tmpDepth = pDepth || 0;
|
|
157
|
+
|
|
158
|
+
let tmpAbsPath = libPath.resolve(pFilePath);
|
|
159
|
+
let tmpAbsBase = libPath.resolve(pBaseDir);
|
|
160
|
+
|
|
161
|
+
// Prevent directory traversal outside base dir
|
|
162
|
+
if (!tmpAbsPath.startsWith(tmpAbsBase))
|
|
163
|
+
{
|
|
164
|
+
return tmpResults;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// Prevent circular includes
|
|
168
|
+
if (tmpVisited.has(tmpAbsPath))
|
|
169
|
+
{
|
|
170
|
+
return tmpResults;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
tmpVisited.add(tmpAbsPath);
|
|
174
|
+
|
|
175
|
+
let tmpContent = '';
|
|
176
|
+
try
|
|
177
|
+
{
|
|
178
|
+
tmpContent = libFs.readFileSync(tmpAbsPath, 'utf8');
|
|
179
|
+
}
|
|
180
|
+
catch (pError)
|
|
181
|
+
{
|
|
182
|
+
return tmpResults;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
let tmpRelPath = libPath.relative(tmpAbsBase, tmpAbsPath);
|
|
186
|
+
|
|
187
|
+
tmpResults.push(
|
|
188
|
+
{
|
|
189
|
+
RelativePath: tmpRelPath,
|
|
190
|
+
AbsolutePath: tmpAbsPath,
|
|
191
|
+
Content: tmpContent,
|
|
192
|
+
IsMain: (tmpVisited.size === 1),
|
|
193
|
+
IncludedBy: pIncludedBy || null,
|
|
194
|
+
Depth: tmpDepth
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
// Parse [Include ...] directives
|
|
198
|
+
let tmpLines = tmpContent.split('\n');
|
|
199
|
+
let tmpFileDir = libPath.dirname(tmpAbsPath);
|
|
200
|
+
|
|
201
|
+
for (let i = 0; i < tmpLines.length; i++)
|
|
202
|
+
{
|
|
203
|
+
let tmpLine = tmpLines[i].trim();
|
|
204
|
+
let tmpMatch = tmpLine.match(/^\[Include\s+(.+)\]$/i);
|
|
205
|
+
|
|
206
|
+
if (tmpMatch)
|
|
207
|
+
{
|
|
208
|
+
let tmpIncludePath = tmpMatch[1].trim();
|
|
209
|
+
let tmpIncludeAbs = libPath.resolve(tmpFileDir, tmpIncludePath);
|
|
210
|
+
|
|
211
|
+
let tmpChildFiles = this.discoverIncludedFiles(tmpIncludeAbs, tmpAbsBase, tmpVisited, tmpRelPath, tmpDepth + 1);
|
|
212
|
+
|
|
213
|
+
for (let j = 0; j < tmpChildFiles.length; j++)
|
|
214
|
+
{
|
|
215
|
+
tmpResults.push(tmpChildFiles[j]);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
return tmpResults;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// ================================================================
|
|
224
|
+
// Initialization
|
|
225
|
+
// ================================================================
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* Initialize the migration manager: scan ModelPath for DDL files,
|
|
229
|
+
* import them into the SchemaLibrary, and auto-compile.
|
|
230
|
+
*
|
|
231
|
+
* @param {function} fCallback - Callback(pError)
|
|
232
|
+
*/
|
|
233
|
+
initializeMigrationManager(fCallback)
|
|
234
|
+
{
|
|
235
|
+
let tmpModelPath = this.modelPath;
|
|
236
|
+
|
|
237
|
+
if (!tmpModelPath)
|
|
238
|
+
{
|
|
239
|
+
this.fable.log.info('MigrationManager: No ModelPath configured; schema library is empty.');
|
|
240
|
+
return fCallback();
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// Scan for DDL files
|
|
244
|
+
let tmpDDLFiles = this.scanForDDLFiles(tmpModelPath);
|
|
245
|
+
let tmpImportCount = 0;
|
|
246
|
+
|
|
247
|
+
for (let i = 0; i < tmpDDLFiles.length; i++)
|
|
248
|
+
{
|
|
249
|
+
this._schemaLibrary.importSchemaFromFile(tmpDDLFiles[i],
|
|
250
|
+
(pError, pEntry) =>
|
|
251
|
+
{
|
|
252
|
+
if (!pError && pEntry)
|
|
253
|
+
{
|
|
254
|
+
tmpImportCount++;
|
|
255
|
+
}
|
|
256
|
+
});
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
this.fable.log.info(`MigrationManager: Imported ${tmpImportCount} schema(s) from ${tmpModelPath}`);
|
|
260
|
+
|
|
261
|
+
// Auto-compile all imported schemas
|
|
262
|
+
let tmpSchemaNames = this._schemaLibrary.listSchemas();
|
|
263
|
+
let tmpCompilesPending = tmpSchemaNames.length;
|
|
264
|
+
let tmpCompilesDone = 0;
|
|
265
|
+
|
|
266
|
+
let fOnAllCompiled = () =>
|
|
267
|
+
{
|
|
268
|
+
this.fable.log.info(`MigrationManager: Compiled ${tmpCompilesDone} of ${tmpSchemaNames.length} schema(s).`);
|
|
269
|
+
return fCallback();
|
|
270
|
+
};
|
|
271
|
+
|
|
272
|
+
if (tmpSchemaNames.length === 0)
|
|
273
|
+
{
|
|
274
|
+
return fCallback();
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
for (let i = 0; i < tmpSchemaNames.length; i++)
|
|
278
|
+
{
|
|
279
|
+
let tmpSchemaName = tmpSchemaNames[i];
|
|
280
|
+
let tmpSchemaEntry = this._schemaLibrary.getSchema(tmpSchemaName);
|
|
281
|
+
|
|
282
|
+
if (!tmpSchemaEntry || !tmpSchemaEntry.DDL)
|
|
283
|
+
{
|
|
284
|
+
tmpCompilesPending--;
|
|
285
|
+
if (tmpCompilesPending <= 0)
|
|
286
|
+
{
|
|
287
|
+
fOnAllCompiled();
|
|
288
|
+
}
|
|
289
|
+
continue;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
let tmpCompileCallback = (pError, pCompiledSchema, pMeadowPackages) =>
|
|
293
|
+
{
|
|
294
|
+
if (!pError && pCompiledSchema)
|
|
295
|
+
{
|
|
296
|
+
tmpSchemaEntry.CompiledSchema = pCompiledSchema;
|
|
297
|
+
tmpSchemaEntry.MeadowPackages = pMeadowPackages;
|
|
298
|
+
tmpSchemaEntry.LastCompiled = new Date().toJSON();
|
|
299
|
+
tmpCompilesDone++;
|
|
300
|
+
}
|
|
301
|
+
else if (pError)
|
|
302
|
+
{
|
|
303
|
+
this._MM.log.warn(`MigrationManager: Failed to compile schema [${tmpSchemaName}]: ${pError.message || pError}`);
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
tmpCompilesPending--;
|
|
307
|
+
if (tmpCompilesPending <= 0)
|
|
308
|
+
{
|
|
309
|
+
fOnAllCompiled();
|
|
310
|
+
}
|
|
311
|
+
};
|
|
312
|
+
|
|
313
|
+
if (tmpSchemaEntry.SourceFilePath)
|
|
314
|
+
{
|
|
315
|
+
this._strictureAdapter.compileFileAndGenerate(tmpSchemaEntry.SourceFilePath, tmpCompileCallback);
|
|
316
|
+
}
|
|
317
|
+
else
|
|
318
|
+
{
|
|
319
|
+
this._strictureAdapter.compileAndGenerate(tmpSchemaEntry.DDL, tmpCompileCallback);
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
// ================================================================
|
|
325
|
+
// Route registration
|
|
326
|
+
// ================================================================
|
|
327
|
+
|
|
328
|
+
/**
|
|
329
|
+
* Register all migration manager API routes on the Orator service server.
|
|
330
|
+
*
|
|
331
|
+
* @param {Object} pOratorServiceServer - The Orator ServiceServer instance
|
|
332
|
+
*/
|
|
333
|
+
connectRoutes(pOratorServiceServer)
|
|
334
|
+
{
|
|
335
|
+
require('./MigrationManager-Command-Schemas.js')(this, pOratorServiceServer);
|
|
336
|
+
require('./MigrationManager-Command-Connections.js')(this, pOratorServiceServer);
|
|
337
|
+
require('./MigrationManager-Command-DiffMigrate.js')(this, pOratorServiceServer);
|
|
338
|
+
|
|
339
|
+
this.fable.log.info('Retold Data Service MigrationManager API routes registered.');
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
/**
|
|
343
|
+
* Register the web UI and static library routes on the Orator service server.
|
|
344
|
+
*
|
|
345
|
+
* @param {Object} pOratorServiceServer - The Orator ServiceServer instance
|
|
346
|
+
*/
|
|
347
|
+
connectWebUIRoutes(pOratorServiceServer)
|
|
348
|
+
{
|
|
349
|
+
require('./MigrationManager-Command-WebUI.js')(this, pOratorServiceServer);
|
|
350
|
+
|
|
351
|
+
this.fable.log.info('Retold Data Service MigrationManager Web UI routes registered.');
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
module.exports = RetoldDataServiceMigrationManager;
|
|
356
|
+
module.exports.serviceType = 'RetoldDataServiceMigrationManager';
|
|
357
|
+
module.exports.default_configuration = {};
|
|
@@ -0,0 +1,303 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Retold Data Service - Stricture Service
|
|
3
|
+
*
|
|
4
|
+
* Fable service that exposes stricture compilation and generation
|
|
5
|
+
* capabilities via REST endpoints under /1.0/Retold/Stricture/.
|
|
6
|
+
*
|
|
7
|
+
* Stricture is a procedural CLI tool (not a service provider), so this
|
|
8
|
+
* service calls its internal compiler and generator functions directly,
|
|
9
|
+
* creating fresh Fable contexts for each operation to avoid state leakage.
|
|
10
|
+
*
|
|
11
|
+
* @author Steven Velozo <steven@velozo.com>
|
|
12
|
+
*/
|
|
13
|
+
const libFableServiceProviderBase = require('fable-serviceproviderbase');
|
|
14
|
+
|
|
15
|
+
const libFable = require('fable');
|
|
16
|
+
const libFS = require('fs');
|
|
17
|
+
const libPath = require('path');
|
|
18
|
+
const libOS = require('os');
|
|
19
|
+
|
|
20
|
+
// Require stricture internals directly — these are plain functions, not service providers
|
|
21
|
+
const libStrictureCompile = require('stricture/source/Stricture-Compile.js');
|
|
22
|
+
const libStricturePrepare = require('stricture/source/Stricture-Run-Prepare.js');
|
|
23
|
+
|
|
24
|
+
class RetoldDataServiceStricture extends libFableServiceProviderBase
|
|
25
|
+
{
|
|
26
|
+
constructor(pFable, pOptions, pServiceHash)
|
|
27
|
+
{
|
|
28
|
+
super(pFable, pOptions, pServiceHash);
|
|
29
|
+
|
|
30
|
+
this.serviceType = 'RetoldDataServiceStricture';
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Create a fresh Fable instance configured for a stricture operation.
|
|
35
|
+
*
|
|
36
|
+
* Each operation gets its own isolated context to avoid state leakage
|
|
37
|
+
* between concurrent requests.
|
|
38
|
+
*
|
|
39
|
+
* @param {Object} pSettings - Settings to merge into the Fable instance
|
|
40
|
+
* @return {Object} A Fable instance ready for stricture operations
|
|
41
|
+
*/
|
|
42
|
+
_createStrictureContext(pSettings)
|
|
43
|
+
{
|
|
44
|
+
let tmpSettings = Object.assign(
|
|
45
|
+
{
|
|
46
|
+
Product: 'RetoldDataServiceStricture',
|
|
47
|
+
LogStreams: [{ streamtype: 'console', level: 'fatal' }]
|
|
48
|
+
}, pSettings);
|
|
49
|
+
|
|
50
|
+
let tmpFable = libFable.new(tmpSettings);
|
|
51
|
+
|
|
52
|
+
// Set up stricture extensions (LoadJSON, GenerateIndexedTables)
|
|
53
|
+
libStricturePrepare(tmpFable, () => {});
|
|
54
|
+
|
|
55
|
+
return tmpFable;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Create a temporary directory for stricture I/O.
|
|
60
|
+
*
|
|
61
|
+
* @return {string} Absolute path to the temp directory
|
|
62
|
+
*/
|
|
63
|
+
createTempDir()
|
|
64
|
+
{
|
|
65
|
+
return libFS.mkdtempSync(libPath.join(libOS.tmpdir(), 'retold-stricture-'));
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Recursively remove a temporary directory and all its contents.
|
|
70
|
+
*
|
|
71
|
+
* @param {string} pDir - Absolute path to the temp directory
|
|
72
|
+
*/
|
|
73
|
+
cleanupTempDir(pDir)
|
|
74
|
+
{
|
|
75
|
+
try
|
|
76
|
+
{
|
|
77
|
+
libFS.rmSync(pDir, { recursive: true, force: true });
|
|
78
|
+
}
|
|
79
|
+
catch (pError)
|
|
80
|
+
{
|
|
81
|
+
this.fable.log.warn(`Failed to clean up temp directory [${pDir}]: ${pError}`);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Read all files from a directory and return them as a map of filename → content.
|
|
87
|
+
*
|
|
88
|
+
* @param {string} pDir - Directory to read
|
|
89
|
+
* @return {Object} Map of filename → file content (string)
|
|
90
|
+
*/
|
|
91
|
+
readAllFiles(pDir)
|
|
92
|
+
{
|
|
93
|
+
let tmpFiles = {};
|
|
94
|
+
let tmpEntries = libFS.readdirSync(pDir, { withFileTypes: true });
|
|
95
|
+
|
|
96
|
+
for (let i = 0; i < tmpEntries.length; i++)
|
|
97
|
+
{
|
|
98
|
+
if (tmpEntries[i].isFile())
|
|
99
|
+
{
|
|
100
|
+
tmpFiles[tmpEntries[i].name] = libFS.readFileSync(libPath.join(pDir, tmpEntries[i].name), 'utf8');
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return tmpFiles;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Compile DDL content to a stricture model.
|
|
109
|
+
*
|
|
110
|
+
* Accepts either:
|
|
111
|
+
* - { DDL: "string content" } for a single DDL file
|
|
112
|
+
* - { Files: { "name.ddl": "content", ... }, EntryPoint: "name.ddl" } for multi-file
|
|
113
|
+
*
|
|
114
|
+
* @param {Object} pInput - The DDL input
|
|
115
|
+
* @param {function} fCallback - Callback as fCallback(pError, pCompiledModel)
|
|
116
|
+
*/
|
|
117
|
+
compileFromDDL(pInput, fCallback)
|
|
118
|
+
{
|
|
119
|
+
let tmpInputDir = this.createTempDir();
|
|
120
|
+
let tmpOutputDir = this.createTempDir();
|
|
121
|
+
|
|
122
|
+
let tmpEntryPoint;
|
|
123
|
+
|
|
124
|
+
try
|
|
125
|
+
{
|
|
126
|
+
if (pInput.DDL && typeof(pInput.DDL) === 'string')
|
|
127
|
+
{
|
|
128
|
+
// Single DDL string shorthand
|
|
129
|
+
tmpEntryPoint = 'Model.ddl';
|
|
130
|
+
libFS.writeFileSync(libPath.join(tmpInputDir, tmpEntryPoint), pInput.DDL, 'utf8');
|
|
131
|
+
}
|
|
132
|
+
else if (pInput.Files && typeof(pInput.Files) === 'object')
|
|
133
|
+
{
|
|
134
|
+
// Multi-file virtual filesystem
|
|
135
|
+
let tmpFileNames = Object.keys(pInput.Files);
|
|
136
|
+
for (let i = 0; i < tmpFileNames.length; i++)
|
|
137
|
+
{
|
|
138
|
+
let tmpFilePath = libPath.join(tmpInputDir, tmpFileNames[i]);
|
|
139
|
+
// Create subdirectories for paths like "entities/Author.ddl"
|
|
140
|
+
let tmpFileDir = libPath.dirname(tmpFilePath);
|
|
141
|
+
if (!libFS.existsSync(tmpFileDir))
|
|
142
|
+
{
|
|
143
|
+
libFS.mkdirSync(tmpFileDir, { recursive: true });
|
|
144
|
+
}
|
|
145
|
+
libFS.writeFileSync(tmpFilePath, pInput.Files[tmpFileNames[i]], 'utf8');
|
|
146
|
+
}
|
|
147
|
+
tmpEntryPoint = pInput.EntryPoint || tmpFileNames[0];
|
|
148
|
+
}
|
|
149
|
+
else
|
|
150
|
+
{
|
|
151
|
+
this.cleanupTempDir(tmpInputDir);
|
|
152
|
+
this.cleanupTempDir(tmpOutputDir);
|
|
153
|
+
return fCallback(new Error('Request body must include DDL (string) or Files (object).'));
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
let tmpEntryPointPath = libPath.join(tmpInputDir, tmpEntryPoint);
|
|
157
|
+
|
|
158
|
+
// Create a fresh stricture context for this compilation
|
|
159
|
+
let tmpStrictureContext = this._createStrictureContext(
|
|
160
|
+
{
|
|
161
|
+
InputFileName: tmpEntryPointPath,
|
|
162
|
+
OutputLocation: tmpOutputDir + '/',
|
|
163
|
+
OutputFileName: 'Model'
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
// Call the compiler directly — it reads DDL from InputFileName,
|
|
167
|
+
// writes JSON to OutputLocation, and stores the model in tmpStrictureContext.Stricture
|
|
168
|
+
libStrictureCompile(tmpStrictureContext,
|
|
169
|
+
(pError) =>
|
|
170
|
+
{
|
|
171
|
+
let tmpResult = null;
|
|
172
|
+
|
|
173
|
+
if (!pError)
|
|
174
|
+
{
|
|
175
|
+
// The compiler stores the compiled model in the fable context's Stricture property
|
|
176
|
+
tmpResult = tmpStrictureContext.Stricture;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
this.cleanupTempDir(tmpInputDir);
|
|
180
|
+
this.cleanupTempDir(tmpOutputDir);
|
|
181
|
+
return fCallback(pError, tmpResult);
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
catch (pError)
|
|
185
|
+
{
|
|
186
|
+
this.cleanupTempDir(tmpInputDir);
|
|
187
|
+
this.cleanupTempDir(tmpOutputDir);
|
|
188
|
+
return fCallback(pError);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Run a stricture generator function against a model and return the output files.
|
|
194
|
+
*
|
|
195
|
+
* Creates a fresh Fable context, sets up the model and indices,
|
|
196
|
+
* runs the generator, reads the output files, and cleans up.
|
|
197
|
+
*
|
|
198
|
+
* @param {function} pGeneratorFunction - The stricture generator function (e.g. require('stricture/source/Stricture-Generate-MySQL.js'))
|
|
199
|
+
* @param {Object} pModel - The compiled stricture model (Extended JSON format)
|
|
200
|
+
* @param {function} fCallback - Callback as fCallback(pError, pFiles)
|
|
201
|
+
*/
|
|
202
|
+
generateFromModel(pGeneratorFunction, pModel, fCallback)
|
|
203
|
+
{
|
|
204
|
+
if (!pModel || !pModel.Tables)
|
|
205
|
+
{
|
|
206
|
+
return fCallback(new Error('Model must include a Tables object.'));
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
let tmpOutputDir = this.createTempDir();
|
|
210
|
+
|
|
211
|
+
try
|
|
212
|
+
{
|
|
213
|
+
// Create a fresh context with output settings
|
|
214
|
+
let tmpStrictureContext = this._createStrictureContext(
|
|
215
|
+
{
|
|
216
|
+
InputFileName: 'unused',
|
|
217
|
+
OutputLocation: tmpOutputDir + '/',
|
|
218
|
+
OutputFileName: 'Model'
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
// Set up the model data that generators read from
|
|
222
|
+
tmpStrictureContext.Model = pModel;
|
|
223
|
+
tmpStrictureContext.ModelIndices = tmpStrictureContext.StrictureExtensions.GenerateIndexedTables(pModel);
|
|
224
|
+
tmpStrictureContext.settings.ExtendedModel = pModel.hasOwnProperty('Authorization');
|
|
225
|
+
|
|
226
|
+
// Generators are synchronous — they write files directly to OutputLocation
|
|
227
|
+
pGeneratorFunction(tmpStrictureContext);
|
|
228
|
+
|
|
229
|
+
let tmpFiles = this.readAllFiles(tmpOutputDir);
|
|
230
|
+
this.cleanupTempDir(tmpOutputDir);
|
|
231
|
+
return fCallback(null, tmpFiles);
|
|
232
|
+
}
|
|
233
|
+
catch (pError)
|
|
234
|
+
{
|
|
235
|
+
this.cleanupTempDir(tmpOutputDir);
|
|
236
|
+
return fCallback(pError);
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/**
|
|
241
|
+
* Helper to register a generator endpoint route.
|
|
242
|
+
*
|
|
243
|
+
* Creates a POST endpoint at /1.0/Retold/Stricture/Generate/{pRouteSuffix}
|
|
244
|
+
* that accepts a model in the body and returns generated files.
|
|
245
|
+
*
|
|
246
|
+
* @param {Object} pOratorServiceServer - The Orator ServiceServer instance
|
|
247
|
+
* @param {string} pRouteSuffix - Route suffix (e.g. 'MySQL', 'Meadow')
|
|
248
|
+
* @param {function} pGeneratorFunction - The stricture generator function
|
|
249
|
+
*/
|
|
250
|
+
registerGenerateEndpoint(pOratorServiceServer, pRouteSuffix, pGeneratorFunction)
|
|
251
|
+
{
|
|
252
|
+
let tmpSelf = this;
|
|
253
|
+
|
|
254
|
+
pOratorServiceServer.postWithBodyParser(`/1.0/Retold/Stricture/Generate/${pRouteSuffix}`,
|
|
255
|
+
(pRequest, pResponse, fNext) =>
|
|
256
|
+
{
|
|
257
|
+
if (!pRequest.body || !pRequest.body.Model)
|
|
258
|
+
{
|
|
259
|
+
pResponse.send(400, { Error: 'Request body must include Model.' });
|
|
260
|
+
return fNext();
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
tmpSelf.generateFromModel(pGeneratorFunction, pRequest.body.Model,
|
|
264
|
+
(pError, pFiles) =>
|
|
265
|
+
{
|
|
266
|
+
if (pError)
|
|
267
|
+
{
|
|
268
|
+
pResponse.send(500, { Error: pError.message });
|
|
269
|
+
return fNext();
|
|
270
|
+
}
|
|
271
|
+
pResponse.send(200, { Files: pFiles });
|
|
272
|
+
return fNext();
|
|
273
|
+
});
|
|
274
|
+
});
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
/**
|
|
278
|
+
* Register all stricture REST routes on the Orator service server.
|
|
279
|
+
*
|
|
280
|
+
* @param {Object} pOratorServiceServer - The Orator ServiceServer instance
|
|
281
|
+
*/
|
|
282
|
+
connectRoutes(pOratorServiceServer)
|
|
283
|
+
{
|
|
284
|
+
// Load and register each command module
|
|
285
|
+
require('./Stricture-Command-Compile.js')(this, pOratorServiceServer);
|
|
286
|
+
require('./Stricture-Command-Generate-MySQL.js')(this, pOratorServiceServer);
|
|
287
|
+
require('./Stricture-Command-Generate-MySQLMigrate.js')(this, pOratorServiceServer);
|
|
288
|
+
require('./Stricture-Command-Generate-Meadow.js')(this, pOratorServiceServer);
|
|
289
|
+
require('./Stricture-Command-Generate-Markdown.js')(this, pOratorServiceServer);
|
|
290
|
+
require('./Stricture-Command-Generate-LaTeX.js')(this, pOratorServiceServer);
|
|
291
|
+
require('./Stricture-Command-Generate-DictionaryCSV.js')(this, pOratorServiceServer);
|
|
292
|
+
require('./Stricture-Command-Generate-ModelGraph.js')(this, pOratorServiceServer);
|
|
293
|
+
require('./Stricture-Command-Generate-AuthorizationChart.js')(this, pOratorServiceServer);
|
|
294
|
+
require('./Stricture-Command-Generate-Pict.js')(this, pOratorServiceServer);
|
|
295
|
+
require('./Stricture-Command-Generate-TestObjectContainers.js')(this, pOratorServiceServer);
|
|
296
|
+
|
|
297
|
+
this.fable.log.info('Retold Data Service Stricture routes registered.');
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
module.exports = RetoldDataServiceStricture;
|
|
302
|
+
module.exports.serviceType = 'RetoldDataServiceStricture';
|
|
303
|
+
module.exports.default_configuration = {};
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Stricture Command - Compile DDL
|
|
3
|
+
*
|
|
4
|
+
* POST /1.0/Retold/Stricture/Compile
|
|
5
|
+
*
|
|
6
|
+
* Accepts MicroDDL content and returns the compiled stricture model
|
|
7
|
+
* (MeadowModel-Extended.json format).
|
|
8
|
+
*
|
|
9
|
+
* Body formats:
|
|
10
|
+
* Single file: { "DDL": "!Book\n@IDBook\n..." }
|
|
11
|
+
* Multi-file: { "Files": { "Main.ddl": "...", "entities/Author.ddl": "..." }, "EntryPoint": "Main.ddl" }
|
|
12
|
+
*
|
|
13
|
+
* @param {Object} pStrictureService - The RetoldDataServiceStricture instance
|
|
14
|
+
* @param {Object} pOratorServiceServer - The Orator ServiceServer instance
|
|
15
|
+
*/
|
|
16
|
+
module.exports = function(pStrictureService, pOratorServiceServer)
|
|
17
|
+
{
|
|
18
|
+
pOratorServiceServer.postWithBodyParser('/1.0/Retold/Stricture/Compile',
|
|
19
|
+
(pRequest, pResponse, fNext) =>
|
|
20
|
+
{
|
|
21
|
+
if (!pRequest.body)
|
|
22
|
+
{
|
|
23
|
+
pResponse.send(400, { Error: 'Request body must include DDL (string) or Files (object).' });
|
|
24
|
+
return fNext();
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
pStrictureService.compileFromDDL(pRequest.body,
|
|
28
|
+
(pError, pCompiledModel) =>
|
|
29
|
+
{
|
|
30
|
+
if (pError)
|
|
31
|
+
{
|
|
32
|
+
pResponse.send(500, { Error: pError.message });
|
|
33
|
+
return fNext();
|
|
34
|
+
}
|
|
35
|
+
pResponse.send(200, pCompiledModel);
|
|
36
|
+
return fNext();
|
|
37
|
+
});
|
|
38
|
+
});
|
|
39
|
+
};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Stricture Command - Generate Authorization Chart
|
|
3
|
+
*
|
|
4
|
+
* POST /1.0/Retold/Stricture/Generate/AuthorizationChart
|
|
5
|
+
*
|
|
6
|
+
* Accepts a compiled stricture model and returns an authorization chart document.
|
|
7
|
+
*
|
|
8
|
+
* Body: { "Model": { "Tables": { ... }, "Authorization": { ... }, ... } }
|
|
9
|
+
* Returns: { "Files": { ... } }
|
|
10
|
+
*/
|
|
11
|
+
module.exports = function(pStrictureService, pOratorServiceServer)
|
|
12
|
+
{
|
|
13
|
+
pStrictureService.registerGenerateEndpoint(pOratorServiceServer, 'AuthorizationChart', require('stricture/source/Stricture-Generate-Authorization-Chart.js'));
|
|
14
|
+
};
|