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.
Files changed (62) hide show
  1. package/README.md +64 -55
  2. package/docs/README.md +24 -66
  3. package/docs/_cover.md +7 -7
  4. package/docs/_sidebar.md +24 -11
  5. package/docs/_topbar.md +2 -0
  6. package/docs/api/constructor.md +67 -0
  7. package/docs/api/initializeDataEndpoints.md +54 -0
  8. package/docs/api/initializePersistenceEngine.md +34 -0
  9. package/docs/api/initializeService.md +44 -0
  10. package/docs/api/onAfterInitialize.md +48 -0
  11. package/docs/api/onBeforeInitialize.md +45 -0
  12. package/docs/api/onInitialize.md +38 -0
  13. package/docs/api/reference.md +35 -0
  14. package/docs/api/stopService.md +34 -0
  15. package/docs/architecture.md +78 -14
  16. package/docs/behavior-injection.md +121 -78
  17. package/docs/css/docuserve.css +73 -0
  18. package/docs/dal-access.md +135 -56
  19. package/docs/index.html +1 -1
  20. package/docs/quick-start.md +169 -0
  21. package/docs/retold-catalog.json +144 -0
  22. package/docs/retold-keyword-index.json +3530 -0
  23. package/example_applications/data-cloner/data/cloned.sqlite +0 -0
  24. package/example_applications/data-cloner/data/cloned.sqlite-shm +0 -0
  25. package/example_applications/data-cloner/data/cloned.sqlite-wal +0 -0
  26. package/example_applications/data-cloner/data-cloner-web.html +935 -0
  27. package/example_applications/data-cloner/data-cloner.js +1047 -0
  28. package/example_applications/data-cloner/package.json +19 -0
  29. package/package.json +13 -9
  30. package/source/Retold-Data-Service.js +225 -73
  31. package/source/services/Retold-Data-Service-ConnectionManager.js +277 -0
  32. package/source/services/Retold-Data-Service-MeadowEndpoints.js +217 -0
  33. package/source/services/Retold-Data-Service-ModelManager.js +335 -0
  34. package/source/services/meadow-integration/MeadowIntegration-Command-CSVCheck.js +85 -0
  35. package/source/services/meadow-integration/MeadowIntegration-Command-CSVTransform.js +180 -0
  36. package/source/services/meadow-integration/MeadowIntegration-Command-ComprehensionIntersect.js +153 -0
  37. package/source/services/meadow-integration/MeadowIntegration-Command-ComprehensionPush.js +190 -0
  38. package/source/services/meadow-integration/MeadowIntegration-Command-ComprehensionToArray.js +113 -0
  39. package/source/services/meadow-integration/MeadowIntegration-Command-ComprehensionToCSV.js +211 -0
  40. package/source/services/meadow-integration/MeadowIntegration-Command-EntityFromTabularFolder.js +244 -0
  41. package/source/services/meadow-integration/MeadowIntegration-Command-JSONArrayTransform.js +213 -0
  42. package/source/services/meadow-integration/MeadowIntegration-Command-TSVCheck.js +80 -0
  43. package/source/services/meadow-integration/MeadowIntegration-Command-TSVTransform.js +166 -0
  44. package/source/services/meadow-integration/Retold-Data-Service-MeadowIntegration.js +113 -0
  45. package/source/services/migration-manager/MigrationManager-Command-Connections.js +220 -0
  46. package/source/services/migration-manager/MigrationManager-Command-DiffMigrate.js +169 -0
  47. package/source/services/migration-manager/MigrationManager-Command-Schemas.js +532 -0
  48. package/source/services/migration-manager/MigrationManager-Command-WebUI.js +123 -0
  49. package/source/services/migration-manager/Retold-Data-Service-MigrationManager.js +357 -0
  50. package/source/services/stricture/Retold-Data-Service-Stricture.js +303 -0
  51. package/source/services/stricture/Stricture-Command-Compile.js +39 -0
  52. package/source/services/stricture/Stricture-Command-Generate-AuthorizationChart.js +14 -0
  53. package/source/services/stricture/Stricture-Command-Generate-DictionaryCSV.js +14 -0
  54. package/source/services/stricture/Stricture-Command-Generate-LaTeX.js +14 -0
  55. package/source/services/stricture/Stricture-Command-Generate-Markdown.js +14 -0
  56. package/source/services/stricture/Stricture-Command-Generate-Meadow.js +14 -0
  57. package/source/services/stricture/Stricture-Command-Generate-ModelGraph.js +14 -0
  58. package/source/services/stricture/Stricture-Command-Generate-MySQL.js +14 -0
  59. package/source/services/stricture/Stricture-Command-Generate-MySQLMigrate.js +14 -0
  60. package/source/services/stricture/Stricture-Command-Generate-Pict.js +14 -0
  61. package/source/services/stricture/Stricture-Command-Generate-TestObjectContainers.js +14 -0
  62. 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
+ };