meadow-integration 1.0.20 → 1.0.21

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 (29) hide show
  1. package/example-applications/mapping-demo/.quackage.json +10 -0
  2. package/example-applications/mapping-demo/README.md +99 -0
  3. package/example-applications/mapping-demo/data/books-sample.csv +21 -0
  4. package/example-applications/mapping-demo/generate-build-config.js +44 -0
  5. package/example-applications/mapping-demo/mappings/books-to-book.json +14 -0
  6. package/example-applications/mapping-demo/package.json +14 -0
  7. package/example-applications/mapping-demo/server.js +814 -0
  8. package/example-applications/mapping-demo/source/MappingDemoApp.js +52 -0
  9. package/example-applications/mapping-demo/source/views/MappingDemoEditorView.js +186 -0
  10. package/example-applications/mapping-demo/web/index.html +892 -0
  11. package/example-applications/mapping-demo/web/mapping-demo-editor.js +3195 -0
  12. package/example-applications/mapping-demo/web/mapping-demo-editor.js.map +1 -0
  13. package/example-applications/mapping-demo/web/mapping-demo-editor.min.js +2 -0
  14. package/example-applications/mapping-demo/web/mapping-demo-editor.min.js.map +1 -0
  15. package/example-applications/mapping-demo/web/pict.min.js +12 -0
  16. package/package.json +8 -4
  17. package/source/Meadow-Integration-Browser.js +31 -0
  18. package/source/Meadow-Integration.js +16 -1
  19. package/source/services/certainty/Service-CertaintyAccumulator.js +402 -0
  20. package/source/services/clone/Meadow-Service-Sync-Entity-Initial.js +16 -3
  21. package/source/services/clone/Meadow-Service-Sync-Entity-Ongoing.js +15 -2
  22. package/source/services/clone/Meadow-Service-Sync.js +21 -0
  23. package/source/views/MappingEditor-SchemaUtils.js +71 -0
  24. package/source/views/PictView-MeadowMappingEditor.js +1299 -0
  25. package/source/views/flow-cards/FlowCard-MappingSource.js +50 -0
  26. package/source/views/flow-cards/FlowCard-MappingTarget.js +49 -0
  27. package/source/views/flow-cards/FlowCard-SolverExpression.js +78 -0
  28. package/source/views/flow-cards/FlowCard-TemplateExpression.js +77 -0
  29. 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
+ });