meadow-integration 1.0.20 → 1.0.23
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/example-applications/mapping-demo/.quackage.json +10 -0
- package/example-applications/mapping-demo/README.md +99 -0
- package/example-applications/mapping-demo/data/books-sample.csv +21 -0
- package/example-applications/mapping-demo/generate-build-config.js +44 -0
- package/example-applications/mapping-demo/mappings/books-to-book.json +14 -0
- package/example-applications/mapping-demo/package.json +14 -0
- package/example-applications/mapping-demo/server.js +814 -0
- package/example-applications/mapping-demo/source/MappingDemoApp.js +52 -0
- package/example-applications/mapping-demo/source/views/MappingDemoEditorView.js +186 -0
- package/example-applications/mapping-demo/web/index.html +892 -0
- package/example-applications/mapping-demo/web/mapping-demo-editor.js +3195 -0
- package/example-applications/mapping-demo/web/mapping-demo-editor.js.map +1 -0
- package/example-applications/mapping-demo/web/mapping-demo-editor.min.js +2 -0
- package/example-applications/mapping-demo/web/mapping-demo-editor.min.js.map +1 -0
- package/example-applications/mapping-demo/web/pict.min.js +12 -0
- package/package.json +8 -4
- package/source/Meadow-Integration-Browser.js +31 -0
- package/source/Meadow-Integration.js +16 -1
- package/source/services/certainty/Service-CertaintyAccumulator.js +402 -0
- package/source/services/clone/Meadow-Service-Sync-Entity-Initial.js +16 -3
- package/source/services/clone/Meadow-Service-Sync-Entity-Ongoing.js +15 -2
- package/source/services/clone/Meadow-Service-Sync.js +21 -0
- package/source/views/MappingEditor-SchemaUtils.js +71 -0
- package/source/views/PictView-MeadowMappingEditor.js +1299 -0
- package/source/views/flow-cards/FlowCard-MappingSource.js +50 -0
- package/source/views/flow-cards/FlowCard-MappingTarget.js +49 -0
- package/source/views/flow-cards/FlowCard-SolverExpression.js +78 -0
- package/source/views/flow-cards/FlowCard-TemplateExpression.js +77 -0
- package/test/Meadow-Integration-BisectionSync_test.js +1601 -0
- package/test/Meadow-Integration-CloneDeleteSync_test.js +809 -0
|
@@ -0,0 +1,814 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Mapping Demo Server
|
|
3
|
+
*
|
|
4
|
+
* Demonstrates the full meadow-integration pipeline:
|
|
5
|
+
* Parse → Map → Comprehension → Load → Verify
|
|
6
|
+
*
|
|
7
|
+
* Starts a single Orator server on port 8092 that:
|
|
8
|
+
* - Serves the demo web UI at http://localhost:8092/
|
|
9
|
+
* - Exposes a Book entity via meadow-endpoints (SQLite in-memory)
|
|
10
|
+
* - Provides demo pipeline REST endpoints under /1.0/Demo/
|
|
11
|
+
*
|
|
12
|
+
* Run from this directory:
|
|
13
|
+
* node server.js
|
|
14
|
+
*/
|
|
15
|
+
'use strict';
|
|
16
|
+
|
|
17
|
+
const libPath = require('path');
|
|
18
|
+
const libFS = require('fs');
|
|
19
|
+
const libReadline = require('readline');
|
|
20
|
+
|
|
21
|
+
const libPict = require('pict');
|
|
22
|
+
const libOrator = require('orator');
|
|
23
|
+
const libOratorRestify = require('orator-serviceserver-restify');
|
|
24
|
+
const libMeadow = require('meadow');
|
|
25
|
+
const libMeadowEndpoints = require('meadow-endpoints');
|
|
26
|
+
const libMeadowConnectionSQLite = require('meadow-connection-sqlite');
|
|
27
|
+
|
|
28
|
+
// meadow-integration's own pipeline components (resolved relative to module root)
|
|
29
|
+
const libIntegrationAdapter = require('../../source/Meadow-Service-Integration-Adapter.js');
|
|
30
|
+
const libGUIDMap = require('../../source/Meadow-Service-Integration-GUIDMap.js');
|
|
31
|
+
const libRestClient = require('../../source/services/clone/Meadow-Service-RestClient.js');
|
|
32
|
+
const libTabularTransform = require('../../source/services/tabular/Service-TabularTransform.js');
|
|
33
|
+
|
|
34
|
+
// ── Constants ──────────────────────────────────────────────────────────────────
|
|
35
|
+
|
|
36
|
+
const PORT = 8092;
|
|
37
|
+
const SERVER_URL = `http://localhost:${PORT}/1.0/`;
|
|
38
|
+
const DATA_FILE = libPath.join(__dirname, 'data', 'books-sample.csv');
|
|
39
|
+
const MAPPING_FILE = libPath.join(__dirname, 'mappings', 'books-to-book.json');
|
|
40
|
+
const WEB_DIR = libPath.join(__dirname, 'web');
|
|
41
|
+
|
|
42
|
+
// Book schema as micro-DDL — used by the visual mapping editor TGT node.
|
|
43
|
+
// Only includes user-editable fields (not system/audit columns).
|
|
44
|
+
const BOOK_TARGET_SCHEMA_DDL = `!Book
|
|
45
|
+
$Title 200
|
|
46
|
+
$Type 32
|
|
47
|
+
$Genre 128
|
|
48
|
+
$ISBN 64
|
|
49
|
+
$Language 12
|
|
50
|
+
$ImageURL 254
|
|
51
|
+
#PublicationYear`;
|
|
52
|
+
|
|
53
|
+
// ── Book entity schema (inlined from retold-harness bookstore schema) ──────────
|
|
54
|
+
|
|
55
|
+
const _BookMeadowSchema =
|
|
56
|
+
{
|
|
57
|
+
Scope: 'Book',
|
|
58
|
+
DefaultIdentifier: 'IDBook',
|
|
59
|
+
Domain: 'Default',
|
|
60
|
+
Schema:
|
|
61
|
+
[
|
|
62
|
+
{ Column: 'IDBook', Type: 'AutoIdentity', Size: 'Default' },
|
|
63
|
+
{ Column: 'GUIDBook', Type: 'AutoGUID', Size: '128' },
|
|
64
|
+
{ Column: 'CreateDate', Type: 'CreateDate', Size: 'Default' },
|
|
65
|
+
{ Column: 'CreatingIDUser', Type: 'CreateIDUser', Size: 'int' },
|
|
66
|
+
{ Column: 'UpdateDate', Type: 'UpdateDate', Size: 'Default' },
|
|
67
|
+
{ Column: 'UpdatingIDUser', Type: 'UpdateIDUser', Size: 'int' },
|
|
68
|
+
{ Column: 'Deleted', Type: 'Deleted', Size: 'Default' },
|
|
69
|
+
{ Column: 'DeleteDate', Type: 'DeleteDate', Size: 'Default' },
|
|
70
|
+
{ Column: 'DeletingIDUser', Type: 'DeleteIDUser', Size: 'int' },
|
|
71
|
+
{ Column: 'Title', Type: 'String', Size: '200' },
|
|
72
|
+
{ Column: 'Type', Type: 'String', Size: '32' },
|
|
73
|
+
{ Column: 'Genre', Type: 'String', Size: '128' },
|
|
74
|
+
{ Column: 'ISBN', Type: 'String', Size: '64' },
|
|
75
|
+
{ Column: 'Language', Type: 'String', Size: '12' },
|
|
76
|
+
{ Column: 'ImageURL', Type: 'String', Size: '254' },
|
|
77
|
+
{ Column: 'PublicationYear', Type: 'Integer', Size: 'int' }
|
|
78
|
+
],
|
|
79
|
+
DefaultObject:
|
|
80
|
+
{
|
|
81
|
+
IDBook: 0, GUIDBook: '', CreateDate: null, CreatingIDUser: 0,
|
|
82
|
+
UpdateDate: null, UpdatingIDUser: 0, Deleted: false,
|
|
83
|
+
DeleteDate: null, DeletingIDUser: 0,
|
|
84
|
+
Title: '', Type: '', Genre: '', ISBN: '',
|
|
85
|
+
Language: '', ImageURL: '', PublicationYear: 0
|
|
86
|
+
},
|
|
87
|
+
// JsonSchema.properties is required by meadow-endpoints for field filtering
|
|
88
|
+
JsonSchema:
|
|
89
|
+
{
|
|
90
|
+
title: 'Book',
|
|
91
|
+
type: 'object',
|
|
92
|
+
properties:
|
|
93
|
+
{
|
|
94
|
+
IDBook: { type: 'integer', size: 'Default' },
|
|
95
|
+
GUIDBook: { type: 'string', size: '128' },
|
|
96
|
+
CreateDate: { type: 'string', size: 'Default' },
|
|
97
|
+
CreatingIDUser: { type: 'integer', size: 'int' },
|
|
98
|
+
UpdateDate: { type: 'string', size: 'Default' },
|
|
99
|
+
UpdatingIDUser: { type: 'integer', size: 'int' },
|
|
100
|
+
Deleted: { type: 'boolean', size: 'Default' },
|
|
101
|
+
DeleteDate: { type: 'string', size: 'Default' },
|
|
102
|
+
DeletingIDUser: { type: 'integer', size: 'int' },
|
|
103
|
+
Title: { type: 'string', size: '200' },
|
|
104
|
+
Type: { type: 'string', size: '32' },
|
|
105
|
+
Genre: { type: 'string', size: '128' },
|
|
106
|
+
ISBN: { type: 'string', size: '64' },
|
|
107
|
+
Language: { type: 'string', size: '12' },
|
|
108
|
+
ImageURL: { type: 'string', size: '254' },
|
|
109
|
+
PublicationYear: { type: 'integer', size: 'int' }
|
|
110
|
+
},
|
|
111
|
+
required: ['IDBook']
|
|
112
|
+
}
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
// ── Fable / service setup ──────────────────────────────────────────────────────
|
|
116
|
+
|
|
117
|
+
let _Fable = new libPict(
|
|
118
|
+
{
|
|
119
|
+
Product: 'MappingDemo',
|
|
120
|
+
ProductVersion: '1.0.0',
|
|
121
|
+
APIServerPort: PORT,
|
|
122
|
+
SQLite: { SQLiteFilePath: ':memory:' },
|
|
123
|
+
LogStreams: [ { streamtype: 'console', level: 'warn' } ]
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
_Fable.serviceManager.addServiceType('OratorServiceServer', libOratorRestify);
|
|
127
|
+
_Fable.serviceManager.addServiceType('MeadowSQLiteProvider', libMeadowConnectionSQLite);
|
|
128
|
+
_Fable.serviceManager.addServiceType('MeadowIntegrationTabularTransform', libTabularTransform);
|
|
129
|
+
_Fable.serviceManager.addServiceType('IntegrationAdapter', libIntegrationAdapter);
|
|
130
|
+
_Fable.serviceManager.addServiceType('MeadowGUIDMap', libGUIDMap);
|
|
131
|
+
_Fable.serviceManager.addServiceType('MeadowCloneRestClient', libRestClient);
|
|
132
|
+
|
|
133
|
+
let _Orator = new libOrator(_Fable, {});
|
|
134
|
+
|
|
135
|
+
// ── In-memory pipeline state ────────────────────────────────────────────────────
|
|
136
|
+
|
|
137
|
+
let _RawRecords = null; // Parsed CSV records
|
|
138
|
+
let _MappingConfig = null; // Loaded mapping JSON (used by Transform step)
|
|
139
|
+
let _LastComprehension = {}; // Most recent comprehension from Transform step
|
|
140
|
+
|
|
141
|
+
// Visual mapping store — persisted in memory.
|
|
142
|
+
// Initialized from books-to-book.json; updated when user saves via the editor.
|
|
143
|
+
let _VisualMappingStore = null; // { IDProjectionMapping, Name, IDSource, MappingConfiguration, FlowDiagramState, Active }
|
|
144
|
+
let _VisualMappingNextID = 2; // Auto-increment for create operations
|
|
145
|
+
|
|
146
|
+
// ── Startup: load static data ───────────────────────────────────────────────────
|
|
147
|
+
|
|
148
|
+
function loadStaticData()
|
|
149
|
+
{
|
|
150
|
+
let tmpCSVContent = libFS.readFileSync(DATA_FILE, 'utf8');
|
|
151
|
+
let tmpLines = tmpCSVContent.split('\n');
|
|
152
|
+
let tmpHeaders = null;
|
|
153
|
+
let tmpRecords = [];
|
|
154
|
+
|
|
155
|
+
for (let i = 0; i < tmpLines.length; i++)
|
|
156
|
+
{
|
|
157
|
+
let tmpLine = tmpLines[i].trim();
|
|
158
|
+
if (!tmpLine)
|
|
159
|
+
{
|
|
160
|
+
continue;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
let tmpValues = tmpLine.split(',');
|
|
164
|
+
|
|
165
|
+
if (!tmpHeaders)
|
|
166
|
+
{
|
|
167
|
+
tmpHeaders = tmpValues.map(function(v) { return v.trim(); });
|
|
168
|
+
continue;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
let tmpRecord = {};
|
|
172
|
+
for (let j = 0; j < tmpHeaders.length; j++)
|
|
173
|
+
{
|
|
174
|
+
tmpRecord[tmpHeaders[j]] = (tmpValues[j] || '').trim();
|
|
175
|
+
}
|
|
176
|
+
tmpRecords.push(tmpRecord);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
_RawRecords = tmpRecords;
|
|
180
|
+
_MappingConfig = JSON.parse(libFS.readFileSync(MAPPING_FILE, 'utf8'));
|
|
181
|
+
|
|
182
|
+
// Seed the visual mapping store from the JSON file so the editor starts
|
|
183
|
+
// with the existing mapping pre-loaded. Inject sourceColumns from the
|
|
184
|
+
// parsed CSV headers so the SRC flow node shows all fields immediately.
|
|
185
|
+
let tmpSourceColumns = tmpHeaders || [];
|
|
186
|
+
let tmpConfigWithColumns = Object.assign({}, _MappingConfig, { sourceColumns: tmpSourceColumns });
|
|
187
|
+
_VisualMappingStore =
|
|
188
|
+
{
|
|
189
|
+
IDProjectionMapping: 1,
|
|
190
|
+
Name: 'Books to Book',
|
|
191
|
+
IDSource: 1,
|
|
192
|
+
IDProjectionStore: 0,
|
|
193
|
+
MappingConfiguration: JSON.stringify(tmpConfigWithColumns),
|
|
194
|
+
FlowDiagramState: null,
|
|
195
|
+
Active: 1
|
|
196
|
+
};
|
|
197
|
+
|
|
198
|
+
console.log(`Loaded ${_RawRecords.length} sample records and mapping configuration.`);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// ── Startup: database initialization ────────────────────────────────────────────
|
|
202
|
+
|
|
203
|
+
function initializeDatabase(fCallback)
|
|
204
|
+
{
|
|
205
|
+
_Fable.serviceManager.instantiateServiceProvider('MeadowSQLiteProvider');
|
|
206
|
+
_Fable.serviceManager.instantiateServiceProvider('MeadowIntegrationTabularTransform');
|
|
207
|
+
|
|
208
|
+
_Fable.MeadowSQLiteProvider.connectAsync(
|
|
209
|
+
function(pError)
|
|
210
|
+
{
|
|
211
|
+
if (pError)
|
|
212
|
+
{
|
|
213
|
+
return fCallback(pError);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
let tmpDB = _Fable.MeadowSQLiteProvider.db;
|
|
217
|
+
|
|
218
|
+
tmpDB.exec(`
|
|
219
|
+
CREATE TABLE IF NOT EXISTS User (
|
|
220
|
+
IDUser INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
221
|
+
GUIDUser TEXT DEFAULT '',
|
|
222
|
+
LoginID TEXT DEFAULT '',
|
|
223
|
+
Password TEXT DEFAULT '',
|
|
224
|
+
NameFirst TEXT DEFAULT '',
|
|
225
|
+
NameLast TEXT DEFAULT '',
|
|
226
|
+
FullName TEXT DEFAULT '',
|
|
227
|
+
Config TEXT DEFAULT ''
|
|
228
|
+
);
|
|
229
|
+
CREATE TABLE IF NOT EXISTS Book (
|
|
230
|
+
IDBook INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
231
|
+
GUIDBook TEXT DEFAULT '',
|
|
232
|
+
CreateDate TEXT DEFAULT '',
|
|
233
|
+
CreatingIDUser INTEGER DEFAULT 0,
|
|
234
|
+
UpdateDate TEXT DEFAULT '',
|
|
235
|
+
UpdatingIDUser INTEGER DEFAULT 0,
|
|
236
|
+
Deleted INTEGER DEFAULT 0,
|
|
237
|
+
DeleteDate TEXT DEFAULT '',
|
|
238
|
+
DeletingIDUser INTEGER DEFAULT 0,
|
|
239
|
+
Title TEXT DEFAULT '',
|
|
240
|
+
Type TEXT DEFAULT '',
|
|
241
|
+
Genre TEXT DEFAULT '',
|
|
242
|
+
ISBN TEXT DEFAULT '',
|
|
243
|
+
Language TEXT DEFAULT '',
|
|
244
|
+
ImageURL TEXT DEFAULT '',
|
|
245
|
+
PublicationYear INTEGER DEFAULT 0
|
|
246
|
+
);
|
|
247
|
+
`);
|
|
248
|
+
|
|
249
|
+
// Seed the system user so meadow audit fields don't fail
|
|
250
|
+
tmpDB.prepare(
|
|
251
|
+
`INSERT OR IGNORE INTO User
|
|
252
|
+
(IDUser, GUIDUser, LoginID, Password, NameFirst, NameLast, FullName, Config)
|
|
253
|
+
VALUES (1, 'system-user', 'system', '', 'System', 'User', 'System User', '{}')`
|
|
254
|
+
).run();
|
|
255
|
+
|
|
256
|
+
return fCallback();
|
|
257
|
+
});
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
// ── Startup: Meadow DAL and endpoints ───────────────────────────────────────────
|
|
261
|
+
|
|
262
|
+
function initializeMeadowEndpoints()
|
|
263
|
+
{
|
|
264
|
+
let tmpMeadow = libMeadow.new(_Fable);
|
|
265
|
+
let tmpBookDAL = tmpMeadow.loadFromPackageObject(_BookMeadowSchema);
|
|
266
|
+
tmpBookDAL.setProvider('SQLite');
|
|
267
|
+
|
|
268
|
+
let tmpBookEndpoints = libMeadowEndpoints.new(tmpBookDAL);
|
|
269
|
+
tmpBookEndpoints.connectRoutes(_Fable.OratorServiceServer);
|
|
270
|
+
|
|
271
|
+
console.log(`Book entity endpoints registered at /1.0/Book(s).`);
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
// ── Route: static web UI and pict bundles ────────────────────────────────────
|
|
275
|
+
|
|
276
|
+
function registerStaticRoutes(pOrator)
|
|
277
|
+
{
|
|
278
|
+
pOrator.serviceServer.get('/',
|
|
279
|
+
function(pRequest, pResponse, fNext)
|
|
280
|
+
{
|
|
281
|
+
let tmpHTML = libFS.readFileSync(libPath.join(WEB_DIR, 'index.html'), 'utf8');
|
|
282
|
+
pResponse.setHeader('Content-Type', 'text/html; charset=utf-8');
|
|
283
|
+
pResponse.end(tmpHTML);
|
|
284
|
+
return fNext();
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
// Serve pict.min.js (copied to web/ by the build step)
|
|
288
|
+
pOrator.serviceServer.get('/pict.min.js',
|
|
289
|
+
function(pRequest, pResponse, fNext)
|
|
290
|
+
{
|
|
291
|
+
let tmpFile = libPath.join(WEB_DIR, 'pict.min.js');
|
|
292
|
+
if (!libFS.existsSync(tmpFile))
|
|
293
|
+
{
|
|
294
|
+
pResponse.send(404, 'pict.min.js not found — run: npm run build');
|
|
295
|
+
return fNext();
|
|
296
|
+
}
|
|
297
|
+
let tmpContent = libFS.readFileSync(tmpFile);
|
|
298
|
+
pResponse.setHeader('Content-Type', 'application/javascript; charset=utf-8');
|
|
299
|
+
pResponse.end(tmpContent);
|
|
300
|
+
return fNext();
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
// Serve the mapping-demo editor bundle (built by quackage)
|
|
304
|
+
pOrator.serviceServer.get('/mapping-demo-editor.min.js',
|
|
305
|
+
function(pRequest, pResponse, fNext)
|
|
306
|
+
{
|
|
307
|
+
let tmpFile = libPath.join(WEB_DIR, 'mapping-demo-editor.min.js');
|
|
308
|
+
if (!libFS.existsSync(tmpFile))
|
|
309
|
+
{
|
|
310
|
+
// Return a stub so the page still loads without a build
|
|
311
|
+
pResponse.setHeader('Content-Type', 'application/javascript; charset=utf-8');
|
|
312
|
+
pResponse.end('/* mapping-demo-editor.min.js not built — run: npm run build */');
|
|
313
|
+
return fNext();
|
|
314
|
+
}
|
|
315
|
+
let tmpContent = libFS.readFileSync(tmpFile);
|
|
316
|
+
pResponse.setHeader('Content-Type', 'application/javascript; charset=utf-8');
|
|
317
|
+
pResponse.end(tmpContent);
|
|
318
|
+
return fNext();
|
|
319
|
+
});
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
// ── Route: GET /1.0/Demo/SourceSchema ────────────────────────────────────────
|
|
323
|
+
// Returns the CSV column names for the visual editor's SRC node ports.
|
|
324
|
+
|
|
325
|
+
function registerDemoSourceSchemaRoute(pOrator)
|
|
326
|
+
{
|
|
327
|
+
pOrator.serviceServer.get('/1.0/Demo/SourceSchema',
|
|
328
|
+
function(pRequest, pResponse, fNext)
|
|
329
|
+
{
|
|
330
|
+
let tmpHeaders = (_RawRecords && _RawRecords.length > 0)
|
|
331
|
+
? Object.keys(_RawRecords[0])
|
|
332
|
+
: [];
|
|
333
|
+
|
|
334
|
+
pResponse.send(200,
|
|
335
|
+
{
|
|
336
|
+
Headers: tmpHeaders,
|
|
337
|
+
SampleSize: _RawRecords ? _RawRecords.length : 0
|
|
338
|
+
});
|
|
339
|
+
return fNext();
|
|
340
|
+
});
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
// ── Route: GET /1.0/Demo/Sources ─────────────────────────────────────────────
|
|
344
|
+
// Returns the list of available source datasets with their column names.
|
|
345
|
+
// The Columns array lets the mapping editor SRC node show fields without
|
|
346
|
+
// requiring a separate "Discover Fields" API call.
|
|
347
|
+
|
|
348
|
+
function registerDemoSourcesRoute(pOrator)
|
|
349
|
+
{
|
|
350
|
+
pOrator.serviceServer.get('/1.0/Demo/Sources',
|
|
351
|
+
function(pRequest, pResponse, fNext)
|
|
352
|
+
{
|
|
353
|
+
let tmpColumns = (_RawRecords && _RawRecords.length > 0)
|
|
354
|
+
? Object.keys(_RawRecords[0])
|
|
355
|
+
: [];
|
|
356
|
+
pResponse.send(200, [{ IDSource: 1, Name: 'books-sample.csv', Columns: tmpColumns }]);
|
|
357
|
+
return fNext();
|
|
358
|
+
});
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
// ── Route: GET /1.0/Demo/TargetSchema ────────────────────────────────────────
|
|
362
|
+
// Returns the Book entity schema in micro-DDL format for the TGT node.
|
|
363
|
+
|
|
364
|
+
function registerDemoTargetSchemaRoute(pOrator)
|
|
365
|
+
{
|
|
366
|
+
pOrator.serviceServer.get('/1.0/Demo/TargetSchema',
|
|
367
|
+
function(pRequest, pResponse, fNext)
|
|
368
|
+
{
|
|
369
|
+
pResponse.send(200, { SchemaDefinition: BOOK_TARGET_SCHEMA_DDL });
|
|
370
|
+
return fNext();
|
|
371
|
+
});
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
// ── Routes: GET|POST|PUT|DELETE /1.0/Demo/VisualMapping[/:id] ────────────────
|
|
375
|
+
// CRUD for the in-memory visual mapping. The demo keeps a single mapping
|
|
376
|
+
// (ID=1) seeded from books-to-book.json; saving updates both the store and
|
|
377
|
+
// _MappingConfig so the Transform step uses the new wiring immediately.
|
|
378
|
+
|
|
379
|
+
function registerDemoVisualMappingRoutes(pOrator)
|
|
380
|
+
{
|
|
381
|
+
// GET /1.0/Demo/VisualMapping — list
|
|
382
|
+
pOrator.serviceServer.get('/1.0/Demo/VisualMapping',
|
|
383
|
+
function(pRequest, pResponse, fNext)
|
|
384
|
+
{
|
|
385
|
+
let tmpMappings = _VisualMappingStore ? [ _VisualMappingStore ] : [];
|
|
386
|
+
pResponse.send(200, { Mappings: tmpMappings });
|
|
387
|
+
return fNext();
|
|
388
|
+
});
|
|
389
|
+
|
|
390
|
+
// GET /1.0/Demo/VisualMapping/:id — single
|
|
391
|
+
pOrator.serviceServer.get('/1.0/Demo/VisualMapping/:id',
|
|
392
|
+
function(pRequest, pResponse, fNext)
|
|
393
|
+
{
|
|
394
|
+
let tmpID = parseInt(pRequest.params.id, 10);
|
|
395
|
+
if (!_VisualMappingStore || _VisualMappingStore.IDProjectionMapping !== tmpID)
|
|
396
|
+
{
|
|
397
|
+
pResponse.send(404, { Error: 'Mapping not found.' });
|
|
398
|
+
return fNext();
|
|
399
|
+
}
|
|
400
|
+
pResponse.send(200, { Mapping: _VisualMappingStore });
|
|
401
|
+
return fNext();
|
|
402
|
+
});
|
|
403
|
+
|
|
404
|
+
// POST /1.0/Demo/VisualMapping — create
|
|
405
|
+
pOrator.serviceServer.postWithBodyParser('/1.0/Demo/VisualMapping',
|
|
406
|
+
function(pRequest, pResponse, fNext)
|
|
407
|
+
{
|
|
408
|
+
let tmpData = pRequest.body || pRequest.params || {};
|
|
409
|
+
let tmpNewMapping =
|
|
410
|
+
{
|
|
411
|
+
IDProjectionMapping: _VisualMappingNextID++,
|
|
412
|
+
Name: tmpData.Name || 'New Mapping',
|
|
413
|
+
IDSource: tmpData.IDSource || 1,
|
|
414
|
+
IDProjectionStore: tmpData.IDProjectionStore || 0,
|
|
415
|
+
MappingConfiguration: tmpData.MappingConfiguration || '{}',
|
|
416
|
+
FlowDiagramState: tmpData.FlowDiagramState || null,
|
|
417
|
+
Active: tmpData.Active !== undefined ? tmpData.Active : 1
|
|
418
|
+
};
|
|
419
|
+
|
|
420
|
+
// Replace the single-slot store with the new mapping
|
|
421
|
+
_VisualMappingStore = tmpNewMapping;
|
|
422
|
+
|
|
423
|
+
// Apply to the live transform pipeline
|
|
424
|
+
_applyVisualMappingConfig(tmpNewMapping.MappingConfiguration);
|
|
425
|
+
|
|
426
|
+
pResponse.send(200, { Mapping: _VisualMappingStore });
|
|
427
|
+
return fNext();
|
|
428
|
+
});
|
|
429
|
+
|
|
430
|
+
// PUT /1.0/Demo/VisualMapping/:id — update
|
|
431
|
+
pOrator.serviceServer.putWithBodyParser('/1.0/Demo/VisualMapping/:id',
|
|
432
|
+
function(pRequest, pResponse, fNext)
|
|
433
|
+
{
|
|
434
|
+
let tmpID = parseInt(pRequest.params.id, 10);
|
|
435
|
+
let tmpData = pRequest.body || pRequest.params || {};
|
|
436
|
+
|
|
437
|
+
if (!_VisualMappingStore || _VisualMappingStore.IDProjectionMapping !== tmpID)
|
|
438
|
+
{
|
|
439
|
+
// Auto-create if not found (handles the first save after loading)
|
|
440
|
+
_VisualMappingStore =
|
|
441
|
+
{
|
|
442
|
+
IDProjectionMapping: tmpID,
|
|
443
|
+
Name: tmpData.Name || 'Books to Book',
|
|
444
|
+
IDSource: tmpData.IDSource || 1,
|
|
445
|
+
IDProjectionStore: tmpData.IDProjectionStore || 0,
|
|
446
|
+
MappingConfiguration: tmpData.MappingConfiguration || '{}',
|
|
447
|
+
FlowDiagramState: tmpData.FlowDiagramState || null,
|
|
448
|
+
Active: tmpData.Active !== undefined ? tmpData.Active : 1
|
|
449
|
+
};
|
|
450
|
+
}
|
|
451
|
+
else
|
|
452
|
+
{
|
|
453
|
+
if (tmpData.Name !== undefined) _VisualMappingStore.Name = tmpData.Name;
|
|
454
|
+
if (tmpData.IDSource !== undefined) _VisualMappingStore.IDSource = tmpData.IDSource;
|
|
455
|
+
if (tmpData.IDProjectionStore !== undefined) _VisualMappingStore.IDProjectionStore = tmpData.IDProjectionStore;
|
|
456
|
+
if (tmpData.MappingConfiguration !== undefined) _VisualMappingStore.MappingConfiguration = tmpData.MappingConfiguration;
|
|
457
|
+
if (tmpData.FlowDiagramState !== undefined) _VisualMappingStore.FlowDiagramState = tmpData.FlowDiagramState;
|
|
458
|
+
if (tmpData.Active !== undefined) _VisualMappingStore.Active = tmpData.Active;
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
// Apply to the live transform pipeline
|
|
462
|
+
_applyVisualMappingConfig(_VisualMappingStore.MappingConfiguration);
|
|
463
|
+
|
|
464
|
+
pResponse.send(200, { Mapping: _VisualMappingStore });
|
|
465
|
+
return fNext();
|
|
466
|
+
});
|
|
467
|
+
|
|
468
|
+
// DELETE /1.0/Demo/VisualMapping/:id
|
|
469
|
+
pOrator.serviceServer.del('/1.0/Demo/VisualMapping/:id',
|
|
470
|
+
function(pRequest, pResponse, fNext)
|
|
471
|
+
{
|
|
472
|
+
let tmpID = parseInt(pRequest.params.id, 10);
|
|
473
|
+
if (_VisualMappingStore && _VisualMappingStore.IDProjectionMapping === tmpID)
|
|
474
|
+
{
|
|
475
|
+
_VisualMappingStore = null;
|
|
476
|
+
}
|
|
477
|
+
pResponse.send(200, { Deleted: tmpID });
|
|
478
|
+
return fNext();
|
|
479
|
+
});
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
// ── Helper: apply a saved MappingConfiguration JSON string to the live pipeline
|
|
483
|
+
|
|
484
|
+
function _applyVisualMappingConfig(pMappingConfigJSON)
|
|
485
|
+
{
|
|
486
|
+
try
|
|
487
|
+
{
|
|
488
|
+
let tmpParsed = JSON.parse(pMappingConfigJSON || '{}');
|
|
489
|
+
if (!tmpParsed.Entity)
|
|
490
|
+
{
|
|
491
|
+
tmpParsed.Entity = 'Book';
|
|
492
|
+
}
|
|
493
|
+
_MappingConfig = tmpParsed;
|
|
494
|
+
}
|
|
495
|
+
catch (e)
|
|
496
|
+
{
|
|
497
|
+
console.warn('Could not parse MappingConfiguration:', e.message);
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
// ── Route: GET /1.0/Demo/SampleData ─────────────────────────────────────────────
|
|
502
|
+
// Returns the raw parsed records from books-sample.csv
|
|
503
|
+
|
|
504
|
+
function registerDemoSampleDataRoute(pOrator)
|
|
505
|
+
{
|
|
506
|
+
pOrator.serviceServer.get('/1.0/Demo/SampleData',
|
|
507
|
+
function(pRequest, pResponse, fNext)
|
|
508
|
+
{
|
|
509
|
+
pResponse.send(200,
|
|
510
|
+
{
|
|
511
|
+
Count: _RawRecords.length,
|
|
512
|
+
Headers: Object.keys(_RawRecords[0] || {}),
|
|
513
|
+
Records: _RawRecords
|
|
514
|
+
});
|
|
515
|
+
return fNext();
|
|
516
|
+
});
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
// ── Route: GET /1.0/Demo/Mapping ────────────────────────────────────────────────
|
|
520
|
+
// Returns the current mapping configuration
|
|
521
|
+
|
|
522
|
+
function registerDemoMappingRoute(pOrator)
|
|
523
|
+
{
|
|
524
|
+
pOrator.serviceServer.get('/1.0/Demo/Mapping',
|
|
525
|
+
function(pRequest, pResponse, fNext)
|
|
526
|
+
{
|
|
527
|
+
pResponse.send(200,
|
|
528
|
+
{
|
|
529
|
+
MappingFile: 'mappings/books-to-book.json',
|
|
530
|
+
Configuration: _MappingConfig
|
|
531
|
+
});
|
|
532
|
+
return fNext();
|
|
533
|
+
});
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
// ── Route: POST /1.0/Demo/Transform ─────────────────────────────────────────────
|
|
537
|
+
// Runs TabularTransform on the sample data with the mapping config → comprehension
|
|
538
|
+
|
|
539
|
+
function registerDemoTransformRoute(pOrator)
|
|
540
|
+
{
|
|
541
|
+
pOrator.serviceServer.post('/1.0/Demo/Transform',
|
|
542
|
+
function(pRequest, pResponse, fNext)
|
|
543
|
+
{
|
|
544
|
+
let tmpTransform = _Fable.MeadowIntegrationTabularTransform;
|
|
545
|
+
let tmpOutcome = tmpTransform.newMappingOutcomeObject();
|
|
546
|
+
|
|
547
|
+
tmpOutcome.ExplicitConfiguration = JSON.parse(JSON.stringify(_MappingConfig));
|
|
548
|
+
|
|
549
|
+
for (let i = 0; i < _RawRecords.length; i++)
|
|
550
|
+
{
|
|
551
|
+
let tmpRecord = _RawRecords[i];
|
|
552
|
+
|
|
553
|
+
// Initialize on first record
|
|
554
|
+
if (!tmpOutcome.ImplicitConfiguration)
|
|
555
|
+
{
|
|
556
|
+
tmpOutcome.ImplicitConfiguration = tmpTransform.generateMappingConfigurationPrototype(
|
|
557
|
+
'books-sample', tmpRecord);
|
|
558
|
+
tmpOutcome.Configuration = Object.assign(
|
|
559
|
+
{}, tmpOutcome.ImplicitConfiguration,
|
|
560
|
+
tmpOutcome.ExplicitConfiguration,
|
|
561
|
+
tmpOutcome.UserConfiguration);
|
|
562
|
+
|
|
563
|
+
if (!('GUIDName' in tmpOutcome.Configuration))
|
|
564
|
+
{
|
|
565
|
+
tmpOutcome.Configuration.GUIDName = `GUID${tmpOutcome.Configuration.Entity}`;
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
if (!(tmpOutcome.Configuration.Entity in tmpOutcome.Comprehension))
|
|
569
|
+
{
|
|
570
|
+
tmpOutcome.Comprehension[tmpOutcome.Configuration.Entity] = {};
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
tmpOutcome.ParsedRowCount++;
|
|
575
|
+
|
|
576
|
+
let tmpSolution =
|
|
577
|
+
{
|
|
578
|
+
IncomingRecord: tmpRecord,
|
|
579
|
+
MappingConfiguration: tmpOutcome.Configuration,
|
|
580
|
+
MappingOutcome: tmpOutcome,
|
|
581
|
+
RowIndex: tmpOutcome.ParsedRowCount,
|
|
582
|
+
NewRecordsGUIDUniqueness: [],
|
|
583
|
+
NewRecordPrototype: {},
|
|
584
|
+
Fable: _Fable,
|
|
585
|
+
Pict: _Fable,
|
|
586
|
+
AppData: _Fable.AppData
|
|
587
|
+
};
|
|
588
|
+
|
|
589
|
+
if (tmpOutcome.Configuration.Solvers && Array.isArray(tmpOutcome.Configuration.Solvers))
|
|
590
|
+
{
|
|
591
|
+
let tmpSolverResults = {};
|
|
592
|
+
for (let s = 0; s < tmpOutcome.Configuration.Solvers.length; s++)
|
|
593
|
+
{
|
|
594
|
+
_Fable.ExpressionParser.solve(
|
|
595
|
+
tmpOutcome.Configuration.Solvers[s],
|
|
596
|
+
tmpSolution, tmpSolverResults,
|
|
597
|
+
_Fable.manifest, tmpSolution);
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
tmpTransform.addRecordToComprehension(tmpRecord, tmpOutcome, tmpSolution.NewRecordPrototype);
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
// Cache for the Load step
|
|
605
|
+
_LastComprehension = tmpOutcome.Comprehension;
|
|
606
|
+
|
|
607
|
+
let tmpEntityName = tmpOutcome.Configuration ? tmpOutcome.Configuration.Entity : 'Book';
|
|
608
|
+
let tmpRecords = tmpOutcome.Comprehension[tmpEntityName] || {};
|
|
609
|
+
let tmpKeys = Object.keys(tmpRecords);
|
|
610
|
+
let tmpSample = {};
|
|
611
|
+
for (let k = 0; k < Math.min(3, tmpKeys.length); k++)
|
|
612
|
+
{
|
|
613
|
+
tmpSample[tmpKeys[k]] = tmpRecords[tmpKeys[k]];
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
pResponse.send(200,
|
|
617
|
+
{
|
|
618
|
+
Entity: tmpEntityName,
|
|
619
|
+
TotalRecords: tmpKeys.length,
|
|
620
|
+
BadRecords: tmpOutcome.BadRecords.length,
|
|
621
|
+
SampleRecords: tmpSample,
|
|
622
|
+
Comprehension: tmpOutcome.Comprehension
|
|
623
|
+
});
|
|
624
|
+
return fNext();
|
|
625
|
+
});
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
// ── Route: POST /1.0/Demo/Load ──────────────────────────────────────────────────
|
|
629
|
+
// Pushes the last comprehension into the bookstore via IntegrationAdapter
|
|
630
|
+
|
|
631
|
+
function registerDemoLoadRoute(pOrator)
|
|
632
|
+
{
|
|
633
|
+
pOrator.serviceServer.post('/1.0/Demo/Load',
|
|
634
|
+
function(pRequest, pResponse, fNext)
|
|
635
|
+
{
|
|
636
|
+
let tmpEntityKeys = Object.keys(_LastComprehension);
|
|
637
|
+
|
|
638
|
+
if (tmpEntityKeys.length === 0)
|
|
639
|
+
{
|
|
640
|
+
pResponse.send(400,
|
|
641
|
+
{ Error: 'No comprehension available. Run the Transform step first.' });
|
|
642
|
+
return fNext();
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
// Create a fresh REST client pointing to ourselves
|
|
646
|
+
let tmpRestClient = _Fable.serviceManager.instantiateServiceProviderWithoutRegistration(
|
|
647
|
+
'MeadowCloneRestClient',
|
|
648
|
+
{ ServerURL: SERVER_URL });
|
|
649
|
+
|
|
650
|
+
// Create adapter for the Book entity
|
|
651
|
+
let tmpAdapter = _Fable.serviceManager.instantiateServiceProviderWithoutRegistration(
|
|
652
|
+
'IntegrationAdapter',
|
|
653
|
+
{
|
|
654
|
+
Entity: 'Book',
|
|
655
|
+
AdapterSetGUIDMarshalPrefix: 'DEMO',
|
|
656
|
+
EntityGUIDMarshalPrefix: 'BK',
|
|
657
|
+
ForceMarshal: true
|
|
658
|
+
});
|
|
659
|
+
|
|
660
|
+
tmpAdapter.setRestClient(tmpRestClient);
|
|
661
|
+
|
|
662
|
+
// Add each record from the comprehension
|
|
663
|
+
let tmpEntityName = tmpEntityKeys[0];
|
|
664
|
+
let tmpDataMap = _LastComprehension[tmpEntityName];
|
|
665
|
+
let tmpGUIDs = Object.keys(tmpDataMap);
|
|
666
|
+
|
|
667
|
+
for (let i = 0; i < tmpGUIDs.length; i++)
|
|
668
|
+
{
|
|
669
|
+
tmpAdapter.addSourceRecord(tmpDataMap[tmpGUIDs[i]]);
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
tmpAdapter.integrateRecords(
|
|
673
|
+
function(pError)
|
|
674
|
+
{
|
|
675
|
+
if (pError)
|
|
676
|
+
{
|
|
677
|
+
pResponse.send(500,
|
|
678
|
+
{ Error: `Integration failed: ${pError.message || pError}` });
|
|
679
|
+
return fNext();
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
pResponse.send(200,
|
|
683
|
+
{
|
|
684
|
+
Success: true,
|
|
685
|
+
Entity: tmpEntityName,
|
|
686
|
+
RecordsPushed: tmpGUIDs.length,
|
|
687
|
+
Message: `Pushed ${tmpGUIDs.length} ${tmpEntityName} records into the bookstore database.`
|
|
688
|
+
});
|
|
689
|
+
return fNext();
|
|
690
|
+
});
|
|
691
|
+
});
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
// ── Route: GET /1.0/Demo/Books ──────────────────────────────────────────────────
|
|
695
|
+
// Reads books back from the in-memory database via the meadow-endpoints API
|
|
696
|
+
|
|
697
|
+
function registerDemoBooksRoute(pOrator)
|
|
698
|
+
{
|
|
699
|
+
pOrator.serviceServer.get('/1.0/Demo/Books',
|
|
700
|
+
function(pRequest, pResponse, fNext)
|
|
701
|
+
{
|
|
702
|
+
// Query directly from SQLite for the demo read-back
|
|
703
|
+
let tmpDB = _Fable.MeadowSQLiteProvider.db;
|
|
704
|
+
let tmpBooks = tmpDB.prepare(
|
|
705
|
+
`SELECT IDBook, GUIDBook, Title, Genre, Type, Language, ISBN, PublicationYear
|
|
706
|
+
FROM Book WHERE Deleted = 0 ORDER BY IDBook`
|
|
707
|
+
).all();
|
|
708
|
+
|
|
709
|
+
pResponse.send(200,
|
|
710
|
+
{
|
|
711
|
+
Count: tmpBooks.length,
|
|
712
|
+
Books: tmpBooks
|
|
713
|
+
});
|
|
714
|
+
return fNext();
|
|
715
|
+
});
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
// ── Route: GET /1.0/Demo/Status ─────────────────────────────────────────────────
|
|
719
|
+
|
|
720
|
+
function registerDemoStatusRoute(pOrator)
|
|
721
|
+
{
|
|
722
|
+
pOrator.serviceServer.get('/1.0/Demo/Status',
|
|
723
|
+
function(pRequest, pResponse, fNext)
|
|
724
|
+
{
|
|
725
|
+
pResponse.send(200,
|
|
726
|
+
{
|
|
727
|
+
Product: 'Mapping Demo',
|
|
728
|
+
Status: 'Running',
|
|
729
|
+
Port: PORT,
|
|
730
|
+
SampleDataFile: 'data/books-sample.csv',
|
|
731
|
+
MappingFile: 'mappings/books-to-book.json',
|
|
732
|
+
RecordsLoaded: _RawRecords ? _RawRecords.length : 0,
|
|
733
|
+
Pipeline:
|
|
734
|
+
[
|
|
735
|
+
'GET /1.0/Demo/SampleData – raw parsed CSV records',
|
|
736
|
+
'GET /1.0/Demo/Mapping – static mapping config (JSON file)',
|
|
737
|
+
'GET /1.0/Demo/SourceSchema – CSV column names for visual editor',
|
|
738
|
+
'GET /1.0/Demo/TargetSchema – Book schema as micro-DDL',
|
|
739
|
+
'GET /1.0/Demo/VisualMapping – visual mapping list',
|
|
740
|
+
'GET /1.0/Demo/VisualMapping/:id – single visual mapping',
|
|
741
|
+
'POST /1.0/Demo/VisualMapping – save new visual mapping',
|
|
742
|
+
'PUT /1.0/Demo/VisualMapping/:id – update visual mapping',
|
|
743
|
+
'POST /1.0/Demo/Transform – run mapping → comprehension',
|
|
744
|
+
'POST /1.0/Demo/Load – push comprehension to bookstore',
|
|
745
|
+
'GET /1.0/Demo/Books – read books from bookstore',
|
|
746
|
+
'GET /1.0/Books/0/20 – Meadow-Endpoints book list'
|
|
747
|
+
]
|
|
748
|
+
});
|
|
749
|
+
return fNext();
|
|
750
|
+
});
|
|
751
|
+
}
|
|
752
|
+
|
|
753
|
+
// ── Main startup sequence ────────────────────────────────────────────────────────
|
|
754
|
+
|
|
755
|
+
loadStaticData();
|
|
756
|
+
|
|
757
|
+
initializeDatabase(
|
|
758
|
+
function(pError)
|
|
759
|
+
{
|
|
760
|
+
if (pError)
|
|
761
|
+
{
|
|
762
|
+
console.error('Failed to initialize database:', pError);
|
|
763
|
+
process.exit(1);
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
_Orator.initialize(
|
|
767
|
+
function(pInitError)
|
|
768
|
+
{
|
|
769
|
+
if (pInitError)
|
|
770
|
+
{
|
|
771
|
+
console.error('Failed to initialize Orator:', pInitError);
|
|
772
|
+
process.exit(1);
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
// Register Meadow Book endpoints
|
|
776
|
+
initializeMeadowEndpoints();
|
|
777
|
+
|
|
778
|
+
// Register demo pipeline routes
|
|
779
|
+
registerDemoStatusRoute(_Orator);
|
|
780
|
+
registerDemoSampleDataRoute(_Orator);
|
|
781
|
+
registerDemoMappingRoute(_Orator);
|
|
782
|
+
registerDemoTransformRoute(_Orator);
|
|
783
|
+
registerDemoLoadRoute(_Orator);
|
|
784
|
+
registerDemoBooksRoute(_Orator);
|
|
785
|
+
|
|
786
|
+
// Register visual mapping editor API routes
|
|
787
|
+
registerDemoSourcesRoute(_Orator);
|
|
788
|
+
registerDemoSourceSchemaRoute(_Orator);
|
|
789
|
+
registerDemoTargetSchemaRoute(_Orator);
|
|
790
|
+
registerDemoVisualMappingRoutes(_Orator);
|
|
791
|
+
|
|
792
|
+
// Serve the web UI and pict bundles (register last — catch-all)
|
|
793
|
+
registerStaticRoutes(_Orator);
|
|
794
|
+
|
|
795
|
+
_Orator.startService(
|
|
796
|
+
function(pStartError)
|
|
797
|
+
{
|
|
798
|
+
if (pStartError)
|
|
799
|
+
{
|
|
800
|
+
console.error('Failed to start server:', pStartError);
|
|
801
|
+
process.exit(1);
|
|
802
|
+
}
|
|
803
|
+
|
|
804
|
+
console.log('');
|
|
805
|
+
console.log(' Meadow-Integration Mapping Demo');
|
|
806
|
+
console.log(' ─────────────────────────────────────────────');
|
|
807
|
+
console.log(` Web UI: http://localhost:${PORT}/`);
|
|
808
|
+
console.log(` Status: http://localhost:${PORT}/1.0/Demo/Status`);
|
|
809
|
+
console.log(` Books API: http://localhost:${PORT}/1.0/Books/0/20`);
|
|
810
|
+
console.log(' ─────────────────────────────────────────────');
|
|
811
|
+
console.log('');
|
|
812
|
+
});
|
|
813
|
+
});
|
|
814
|
+
});
|