meadow-integration 1.0.19 → 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 (35) 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 +11 -5
  17. package/source/Meadow-Integration-Browser.js +31 -0
  18. package/source/Meadow-Integration.js +30 -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/services/parser/Service-FileParser-CSV.js +263 -0
  24. package/source/services/parser/Service-FileParser-FixedWidth.js +158 -0
  25. package/source/services/parser/Service-FileParser-JSON.js +255 -0
  26. package/source/services/parser/Service-FileParser-XLSX.js +194 -0
  27. package/source/services/parser/Service-FileParser-XML.js +190 -0
  28. package/source/services/parser/Service-FileParser.js +142 -0
  29. package/source/views/MappingEditor-SchemaUtils.js +71 -0
  30. package/source/views/PictView-MeadowMappingEditor.js +1299 -0
  31. package/source/views/flow-cards/FlowCard-MappingSource.js +50 -0
  32. package/source/views/flow-cards/FlowCard-MappingTarget.js +49 -0
  33. package/source/views/flow-cards/FlowCard-SolverExpression.js +78 -0
  34. package/source/views/flow-cards/FlowCard-TemplateExpression.js +77 -0
  35. package/test/Meadow-Integration-CloneDeleteSync_test.js +809 -0
@@ -0,0 +1,892 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Meadow-Integration Pipeline Demo</title>
7
+ <style id="PICT-CSS"></style>
8
+ <style>
9
+ :root {
10
+ --brand: #18a5a0;
11
+ --brand-dark: #127b77;
12
+ --bg: #f5efe4;
13
+ --bg-surface: #fdf8f0;
14
+ --bg-card: #ffffff;
15
+ --text: #3a3020;
16
+ --text-mid: #6b5c45;
17
+ --text-dim: #a09070;
18
+ --border: #d6c8ae;
19
+ --border-sub: #ece3d0;
20
+ --success: #2ecc71;
21
+ --error: #e74c3c;
22
+ --warn: #f39c12;
23
+ --step-1: #18a5a0;
24
+ --step-2: #3498db;
25
+ --step-3: #c0392b;
26
+ --step-4: #9b59b6;
27
+ --step-5: #e67e22;
28
+ --step-6: #2ecc71;
29
+ }
30
+
31
+ /* ── Mapping editor integration ── */
32
+ .meadow-mapping-editor {
33
+ display: none;
34
+ }
35
+ .meadow-mapping-editor.active {
36
+ display: block;
37
+ }
38
+ /* Override facto-specific CSS vars with demo theme equivalents */
39
+ .meadow-mapping-editor,
40
+ .meadow-flow-container,
41
+ .meadow-mapping-btn,
42
+ .meadow-mapping-json-editor {
43
+ --facto-brand: var(--brand);
44
+ --facto-border: var(--border);
45
+ --facto-border-subtle: var(--border-sub);
46
+ --facto-bg-surface: var(--bg-surface);
47
+ --facto-bg-input: var(--bg-card);
48
+ --facto-text: var(--text);
49
+ --facto-text-tertiary: var(--text-dim);
50
+ }
51
+
52
+ * { box-sizing: border-box; margin: 0; padding: 0; }
53
+
54
+ body {
55
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
56
+ background: var(--bg);
57
+ color: var(--text);
58
+ min-height: 100vh;
59
+ }
60
+
61
+ /* ── Header ── */
62
+ .demo-header {
63
+ background: var(--text);
64
+ color: #fff;
65
+ padding: 1.25rem 2rem;
66
+ display: flex;
67
+ align-items: center;
68
+ gap: 1rem;
69
+ }
70
+ .demo-header h1 {
71
+ font-size: 1.25rem;
72
+ font-weight: 600;
73
+ letter-spacing: -0.02em;
74
+ }
75
+ .demo-header .subtitle {
76
+ font-size: 0.8rem;
77
+ opacity: 0.6;
78
+ font-weight: 400;
79
+ }
80
+ .demo-badge {
81
+ margin-left: auto;
82
+ background: var(--brand);
83
+ color: #fff;
84
+ padding: 0.25em 0.7em;
85
+ border-radius: 999px;
86
+ font-size: 0.72rem;
87
+ font-weight: 600;
88
+ letter-spacing: 0.04em;
89
+ }
90
+
91
+ /* ── Pipeline flow bar ── */
92
+ .pipeline-bar {
93
+ background: var(--bg-surface);
94
+ border-bottom: 1px solid var(--border);
95
+ padding: 0.75rem 2rem;
96
+ display: flex;
97
+ align-items: center;
98
+ gap: 0;
99
+ overflow-x: auto;
100
+ }
101
+ .pipeline-step {
102
+ display: flex;
103
+ align-items: center;
104
+ gap: 0.5rem;
105
+ padding: 0.4em 0.9em;
106
+ border-radius: 4px;
107
+ font-size: 0.8rem;
108
+ font-weight: 600;
109
+ color: var(--text-dim);
110
+ cursor: pointer;
111
+ white-space: nowrap;
112
+ transition: background 0.15s, color 0.15s;
113
+ }
114
+ .pipeline-step:hover { background: var(--border-sub); color: var(--text); }
115
+ .pipeline-step.done { color: var(--text-mid); }
116
+ .pipeline-step.done .step-num { background: var(--success); color: #fff; }
117
+ .pipeline-step .step-num {
118
+ width: 1.5em; height: 1.5em;
119
+ border-radius: 50%;
120
+ background: var(--border);
121
+ color: var(--text-dim);
122
+ display: flex; align-items: center; justify-content: center;
123
+ font-size: 0.75em;
124
+ font-weight: 700;
125
+ transition: background 0.2s, color 0.2s;
126
+ }
127
+ .pipeline-arrow {
128
+ color: var(--border);
129
+ font-size: 0.9rem;
130
+ padding: 0 0.2rem;
131
+ user-select: none;
132
+ }
133
+
134
+ /* ── Main layout ── */
135
+ .demo-main {
136
+ max-width: 960px;
137
+ margin: 2rem auto;
138
+ padding: 0 1.5rem;
139
+ display: flex;
140
+ flex-direction: column;
141
+ gap: 1.25rem;
142
+ }
143
+
144
+ /* ── Step card ── */
145
+ .step-card {
146
+ background: var(--bg-card);
147
+ border: 1px solid var(--border);
148
+ border-radius: 8px;
149
+ overflow: hidden;
150
+ }
151
+ .step-card-header {
152
+ display: flex;
153
+ align-items: center;
154
+ gap: 0.75rem;
155
+ padding: 0.9rem 1.25rem;
156
+ border-bottom: 1px solid var(--border-sub);
157
+ }
158
+ .step-color-bar {
159
+ width: 4px;
160
+ height: 2rem;
161
+ border-radius: 2px;
162
+ flex-shrink: 0;
163
+ }
164
+ .step-card-header h2 {
165
+ font-size: 1rem;
166
+ font-weight: 700;
167
+ flex: 1;
168
+ }
169
+ .step-card-header .step-desc {
170
+ font-size: 0.78rem;
171
+ color: var(--text-dim);
172
+ display: block;
173
+ margin-top: 0.15em;
174
+ font-weight: 400;
175
+ }
176
+ .step-card-body {
177
+ padding: 1.1rem 1.25rem;
178
+ }
179
+
180
+ /* ── Run button ── */
181
+ .run-btn {
182
+ display: inline-flex;
183
+ align-items: center;
184
+ gap: 0.4em;
185
+ padding: 0.45em 1.1em;
186
+ font-size: 0.85rem;
187
+ font-weight: 600;
188
+ border: none;
189
+ border-radius: 5px;
190
+ cursor: pointer;
191
+ transition: opacity 0.15s, transform 0.1s;
192
+ color: #fff;
193
+ }
194
+ .run-btn:hover { opacity: 0.88; }
195
+ .run-btn:active { transform: scale(0.98); }
196
+ .run-btn:disabled { opacity: 0.45; cursor: not-allowed; }
197
+ .run-btn .icon { font-size: 1em; }
198
+
199
+ /* ── Result panel ── */
200
+ .result-panel {
201
+ margin-top: 0.9rem;
202
+ border: 1px solid var(--border-sub);
203
+ border-radius: 6px;
204
+ overflow: hidden;
205
+ display: none;
206
+ }
207
+ .result-panel.visible { display: block; }
208
+ .result-panel-header {
209
+ background: var(--bg);
210
+ border-bottom: 1px solid var(--border-sub);
211
+ padding: 0.4em 0.75em;
212
+ font-size: 0.7rem;
213
+ font-weight: 700;
214
+ text-transform: uppercase;
215
+ letter-spacing: 0.06em;
216
+ color: var(--text-dim);
217
+ display: flex;
218
+ align-items: center;
219
+ gap: 0.5em;
220
+ }
221
+ .result-badge {
222
+ padding: 0.1em 0.5em;
223
+ border-radius: 3px;
224
+ font-size: 0.68rem;
225
+ font-weight: 700;
226
+ }
227
+ .result-badge.ok { background: #d4edda; color: #155724; }
228
+ .result-badge.err { background: #f8d7da; color: #721c24; }
229
+ .result-badge.info { background: #d1ecf1; color: #0c5460; }
230
+
231
+ /* ── Data table ── */
232
+ .data-table-wrap {
233
+ overflow-x: auto;
234
+ padding: 0.5rem 0.75rem;
235
+ }
236
+ .data-table {
237
+ width: 100%;
238
+ border-collapse: collapse;
239
+ font-size: 0.8rem;
240
+ }
241
+ .data-table th {
242
+ text-align: left;
243
+ color: var(--text-dim);
244
+ font-weight: 600;
245
+ font-size: 0.7rem;
246
+ text-transform: uppercase;
247
+ letter-spacing: 0.04em;
248
+ padding: 0.35em 0.6em;
249
+ border-bottom: 1px solid var(--border);
250
+ white-space: nowrap;
251
+ }
252
+ .data-table td {
253
+ padding: 0.35em 0.6em;
254
+ border-bottom: 1px solid var(--border-sub);
255
+ color: var(--text);
256
+ max-width: 200px;
257
+ overflow: hidden;
258
+ text-overflow: ellipsis;
259
+ white-space: nowrap;
260
+ }
261
+ .data-table tr:last-child td { border-bottom: none; }
262
+ .data-table tr:hover td { background: var(--bg); }
263
+
264
+ /* ── JSON viewer ── */
265
+ .json-viewer {
266
+ padding: 0.75rem;
267
+ font-family: 'SF Mono', 'Cascadia Code', 'Fira Code', monospace;
268
+ font-size: 0.78rem;
269
+ line-height: 1.6;
270
+ color: var(--text);
271
+ white-space: pre-wrap;
272
+ word-break: break-all;
273
+ background: var(--bg);
274
+ max-height: 320px;
275
+ overflow-y: auto;
276
+ }
277
+ .json-key { color: #2980b9; }
278
+ .json-string { color: #27ae60; }
279
+ .json-number { color: #8e44ad; }
280
+ .json-bool { color: var(--warn); }
281
+
282
+ /* ── Stats row ── */
283
+ .stats-row {
284
+ display: flex;
285
+ gap: 0.75rem;
286
+ padding: 0.6rem 0.75rem;
287
+ flex-wrap: wrap;
288
+ }
289
+ .stat-chip {
290
+ padding: 0.3em 0.75em;
291
+ background: var(--bg);
292
+ border: 1px solid var(--border);
293
+ border-radius: 4px;
294
+ font-size: 0.78rem;
295
+ }
296
+ .stat-chip strong { color: var(--brand-dark); }
297
+
298
+ /* ── Spinner ── */
299
+ .spinner {
300
+ display: inline-block;
301
+ width: 0.9em; height: 0.9em;
302
+ border: 2px solid rgba(255,255,255,0.4);
303
+ border-top-color: #fff;
304
+ border-radius: 50%;
305
+ animation: spin 0.6s linear infinite;
306
+ }
307
+ @keyframes spin { to { transform: rotate(360deg); } }
308
+
309
+ /* ── Footer ── */
310
+ .demo-footer {
311
+ text-align: center;
312
+ padding: 2rem;
313
+ font-size: 0.75rem;
314
+ color: var(--text-dim);
315
+ }
316
+ .demo-footer a { color: var(--brand); text-decoration: none; }
317
+ </style>
318
+ </head>
319
+ <body>
320
+
321
+ <header class="demo-header">
322
+ <div>
323
+ <h1>Meadow-Integration Pipeline Demo</h1>
324
+ <span class="subtitle">meadow-integration &rsaquo; example-applications/mapping-demo</span>
325
+ </div>
326
+ <span class="demo-badge">LIVE</span>
327
+ </header>
328
+
329
+ <nav class="pipeline-bar">
330
+ <div class="pipeline-step" id="nav-1" onclick="scrollToStep(1)">
331
+ <span class="step-num">1</span> Parse
332
+ </div>
333
+ <span class="pipeline-arrow">&#8594;</span>
334
+ <div class="pipeline-step" id="nav-2" onclick="scrollToStep(2)">
335
+ <span class="step-num">2</span> Map
336
+ </div>
337
+ <span class="pipeline-arrow">&#8594;</span>
338
+ <div class="pipeline-step" id="nav-3" onclick="scrollToStep(3)">
339
+ <span class="step-num">3</span> Visual Map
340
+ </div>
341
+ <span class="pipeline-arrow">&#8594;</span>
342
+ <div class="pipeline-step" id="nav-4" onclick="scrollToStep(4)">
343
+ <span class="step-num">4</span> Transform
344
+ </div>
345
+ <span class="pipeline-arrow">&#8594;</span>
346
+ <div class="pipeline-step" id="nav-5" onclick="scrollToStep(5)">
347
+ <span class="step-num">5</span> Load
348
+ </div>
349
+ <span class="pipeline-arrow">&#8594;</span>
350
+ <div class="pipeline-step" id="nav-6" onclick="scrollToStep(6)">
351
+ <span class="step-num">6</span> Verify
352
+ </div>
353
+ </nav>
354
+
355
+ <main class="demo-main">
356
+
357
+ <!-- Step 1: Parse -->
358
+ <section class="step-card" id="step-1">
359
+ <div class="step-card-header">
360
+ <div class="step-color-bar" style="background: var(--step-1);"></div>
361
+ <div>
362
+ <h2>Step 1 &mdash; Parse</h2>
363
+ <span class="step-desc">Read <code>data/books-sample.csv</code> and parse into raw records</span>
364
+ </div>
365
+ <button class="run-btn" id="btn-1" style="background: var(--step-1);" onclick="runParse()">
366
+ <span class="icon">&#9654;</span> Run Parse
367
+ </button>
368
+ </div>
369
+ <div class="step-card-body">
370
+ <p style="font-size:0.82rem; color:var(--text-mid); margin-bottom:0.75rem;">
371
+ The first stage reads a CSV file and parses it into an array of plain JavaScript objects
372
+ keyed by column header. No transformation is applied &mdash; this is the raw source data.
373
+ </p>
374
+ <div class="result-panel" id="result-1">
375
+ <div class="result-panel-header">
376
+ <span>Result</span>
377
+ <span class="result-badge info" id="badge-1"></span>
378
+ </div>
379
+ <div id="output-1"></div>
380
+ </div>
381
+ </div>
382
+ </section>
383
+
384
+ <!-- Step 2: Map -->
385
+ <section class="step-card" id="step-2">
386
+ <div class="step-card-header">
387
+ <div class="step-color-bar" style="background: var(--step-2);"></div>
388
+ <div>
389
+ <h2>Step 2 &mdash; Map</h2>
390
+ <span class="step-desc">Load <code>mappings/books-to-book.json</code> &mdash; defines how CSV columns become entity fields</span>
391
+ </div>
392
+ <button class="run-btn" id="btn-2" style="background: var(--step-2);" onclick="runMap()">
393
+ <span class="icon">&#9654;</span> Load Mapping
394
+ </button>
395
+ </div>
396
+ <div class="step-card-body">
397
+ <p style="font-size:0.82rem; color:var(--text-mid); margin-bottom:0.75rem;">
398
+ A mapping file declares the target <strong>Entity</strong>, a <strong>GUIDTemplate</strong>
399
+ (unique stable key per record), and a <strong>Mappings</strong> object where each value is a
400
+ Pict template expression that produces the target field value from the source record.
401
+ Literal values like <code>"Classic"</code> are valid alongside template expressions.
402
+ </p>
403
+ <div class="result-panel" id="result-2">
404
+ <div class="result-panel-header">
405
+ <span>Result</span>
406
+ <span class="result-badge info" id="badge-2"></span>
407
+ </div>
408
+ <div id="output-2"></div>
409
+ </div>
410
+ </div>
411
+ </section>
412
+
413
+ <!-- Step 3: Visual Map -->
414
+ <section class="step-card" id="step-3">
415
+ <div class="step-card-header">
416
+ <div class="step-color-bar" style="background: var(--step-3);"></div>
417
+ <div>
418
+ <h2>Step 3 &mdash; Visual Mapping Editor</h2>
419
+ <span class="step-desc">Wire CSV source fields to Book entity columns using the flow canvas</span>
420
+ </div>
421
+ <button class="run-btn" id="btn-3" style="background: var(--step-3);" onclick="openVisualEditor()">
422
+ <span class="icon">&#9654;</span> Open Editor
423
+ </button>
424
+ </div>
425
+ <div class="step-card-body">
426
+ <p style="font-size:0.82rem; color:var(--text-mid); margin-bottom:0.75rem;">
427
+ The <strong>Visual Mapping Editor</strong> lets you draw connections between source fields
428
+ (CSV columns) and target columns (Book entity schema) on an interactive flow canvas.
429
+ Add <strong>TPL</strong> nodes (purple) to apply Pict template expressions like
430
+ <code>{~D:Record.title~}</code>, or <strong>SOL</strong> nodes (orange) for conditional
431
+ solver expressions. Click <em>Save Mapping</em> to apply the wiring to the pipeline &mdash;
432
+ the Transform step will use your visual mapping.
433
+ </p>
434
+
435
+ <!-- Placeholder shown before the editor is opened -->
436
+ <div id="mapping-editor-placeholder" style="padding:1.5rem; text-align:center; border:2px dashed var(--border); border-radius:6px; color:var(--text-dim); font-size:0.82rem;">
437
+ Click <strong>Open Editor</strong> to launch the visual flow mapper.<br>
438
+ <span style="font-size:0.75em; margin-top:0.5em; display:block;">
439
+ Requires a built bundle &mdash; run <code>npm run build</code> in this directory first.
440
+ </span>
441
+ </div>
442
+
443
+ <!-- Pict renders the mapping editor view into this container -->
444
+ <div id="MeadowMap-Editor-Container"></div>
445
+
446
+ <div class="result-panel" id="result-3">
447
+ <div class="result-panel-header">
448
+ <span>Result</span>
449
+ <span class="result-badge info" id="badge-3"></span>
450
+ </div>
451
+ <div id="output-3"></div>
452
+ </div>
453
+ </div>
454
+ </section>
455
+
456
+ <!-- Step 4: Transform -->
457
+ <section class="step-card" id="step-4">
458
+ <div class="step-card-header">
459
+ <div class="step-color-bar" style="background: var(--step-4);"></div>
460
+ <div>
461
+ <h2>Step 4 &mdash; Transform (Comprehension)</h2>
462
+ <span class="step-desc">Run <strong>TabularTransform</strong> to produce a comprehension from the mapped records</span>
463
+ </div>
464
+ <button class="run-btn" id="btn-4" style="background: var(--step-4);" onclick="runTransform()">
465
+ <span class="icon">&#9654;</span> Transform
466
+ </button>
467
+ </div>
468
+ <div class="step-card-body">
469
+ <p style="font-size:0.82rem; color:var(--text-mid); margin-bottom:0.75rem;">
470
+ <strong>TabularTransform</strong> applies the mapping configuration to each raw record and
471
+ builds a <em>comprehension</em> &mdash; a deduplicated dictionary keyed by GUID. The comprehension
472
+ is the canonical intermediate format in the meadow-integration pipeline.
473
+ Duplicate GUIDs are merged; bad records (empty GUIDs) are tracked separately.
474
+ If you saved a visual mapping in Step 3, it will be used here instead of the static JSON config.
475
+ </p>
476
+ <div class="result-panel" id="result-4">
477
+ <div class="result-panel-header">
478
+ <span>Result</span>
479
+ <span class="result-badge info" id="badge-4"></span>
480
+ </div>
481
+ <div id="output-4"></div>
482
+ </div>
483
+ </div>
484
+ </section>
485
+
486
+ <!-- Step 5: Load -->
487
+ <section class="step-card" id="step-5">
488
+ <div class="step-card-header">
489
+ <div class="step-color-bar" style="background: var(--step-5);"></div>
490
+ <div>
491
+ <h2>Step 5 &mdash; Load (IntegrationAdapter)</h2>
492
+ <span class="step-desc">Push the comprehension into the bookstore database via <strong>IntegrationAdapter</strong></span>
493
+ </div>
494
+ <button class="run-btn" id="btn-5" style="background: var(--step-5);" onclick="runLoad()">
495
+ <span class="icon">&#9654;</span> Load to DB
496
+ </button>
497
+ </div>
498
+ <div class="step-card-body">
499
+ <p style="font-size:0.82rem; color:var(--text-mid); margin-bottom:0.75rem;">
500
+ <strong>IntegrationAdapter</strong> iterates the comprehension and calls the Meadow REST API
501
+ for each record: GET by GUID to check existence, then POST (create) or PUT (update).
502
+ GUIDs are tracked in the <strong>GUIDMap</strong> so re-runs are idempotent upserts.
503
+ The bookstore API is powered by <code>meadow-endpoints</code> on an in-memory SQLite database.
504
+ </p>
505
+ <div class="result-panel" id="result-5">
506
+ <div class="result-panel-header">
507
+ <span>Result</span>
508
+ <span class="result-badge info" id="badge-5"></span>
509
+ </div>
510
+ <div id="output-5"></div>
511
+ </div>
512
+ </div>
513
+ </section>
514
+
515
+ <!-- Step 6: Verify -->
516
+ <section class="step-card" id="step-6">
517
+ <div class="step-card-header">
518
+ <div class="step-color-bar" style="background: var(--step-6);"></div>
519
+ <div>
520
+ <h2>Step 6 &mdash; Verify</h2>
521
+ <span class="step-desc">Read the loaded books back from the bookstore database</span>
522
+ </div>
523
+ <button class="run-btn" id="btn-6" style="background: var(--step-6);" onclick="runVerify()">
524
+ <span class="icon">&#9654;</span> Read Books
525
+ </button>
526
+ </div>
527
+ <div class="step-card-body">
528
+ <p style="font-size:0.82rem; color:var(--text-mid); margin-bottom:0.75rem;">
529
+ After loading, query the database directly to confirm the records are persisted.
530
+ You can also hit the live Meadow-Endpoints API at
531
+ <a href="/1.0/Books/0/20" target="_blank"><code>GET /1.0/Books/0/20</code></a>
532
+ to see the full record structure with auto-generated IDs, audit timestamps, and GUIDs.
533
+ </p>
534
+ <div class="result-panel" id="result-6">
535
+ <div class="result-panel-header">
536
+ <span>Result</span>
537
+ <span class="result-badge info" id="badge-6"></span>
538
+ </div>
539
+ <div id="output-6"></div>
540
+ </div>
541
+ </div>
542
+ </section>
543
+
544
+ </main>
545
+
546
+ <footer class="demo-footer">
547
+ <a href="/1.0/Demo/Status" target="_blank">API Status</a>
548
+ &nbsp;&middot;&nbsp;
549
+ <a href="/1.0/Books/0/20" target="_blank">Books Endpoint</a>
550
+ &nbsp;&middot;&nbsp;
551
+ meadow-integration &mdash; MIT License
552
+ </footer>
553
+
554
+ <script>
555
+ // ── Utilities ──────────────────────────────────────────────────────────────────
556
+
557
+ function scrollToStep(n)
558
+ {
559
+ document.getElementById('step-' + n).scrollIntoView({ behavior: 'smooth', block: 'start' });
560
+ }
561
+
562
+ function showSpinner(btnEl)
563
+ {
564
+ let tmpSpan = btnEl.querySelector('.icon');
565
+ tmpSpan.innerHTML = '<span class="spinner"></span>';
566
+ btnEl.disabled = true;
567
+ }
568
+
569
+ function resetBtn(btnEl, icon, label)
570
+ {
571
+ let tmpSpan = btnEl.querySelector('.icon');
572
+ tmpSpan.innerHTML = icon;
573
+ btnEl.disabled = false;
574
+ }
575
+
576
+ function showResult(n, content, badgeText, badgeClass)
577
+ {
578
+ let tmpPanel = document.getElementById('result-' + n);
579
+ let tmpBadge = document.getElementById('badge-' + n);
580
+ let tmpOutput = document.getElementById('output-' + n);
581
+
582
+ tmpPanel.classList.add('visible');
583
+ tmpBadge.textContent = badgeText;
584
+ tmpBadge.className = 'result-badge ' + (badgeClass || 'info');
585
+ tmpOutput.innerHTML = content;
586
+ }
587
+
588
+ function markNavDone(n)
589
+ {
590
+ document.getElementById('nav-' + n).classList.add('done');
591
+ }
592
+
593
+ function syntaxHighlight(obj)
594
+ {
595
+ let tmpJSON = JSON.stringify(obj, null, 2);
596
+ tmpJSON = tmpJSON.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
597
+ return tmpJSON.replace(
598
+ /("(\\u[a-zA-Z0-9]{4}|\\[^u]|[^\\"])*"(\s*:)?|\b(true|false|null)\b|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?)/g,
599
+ function(match)
600
+ {
601
+ let cls = 'json-number';
602
+ if (/^"/.test(match))
603
+ {
604
+ cls = (/:$/.test(match)) ? 'json-key' : 'json-string';
605
+ }
606
+ else if (/true|false/.test(match))
607
+ {
608
+ cls = 'json-bool';
609
+ }
610
+ return '<span class="' + cls + '">' + match + '</span>';
611
+ });
612
+ }
613
+
614
+ function buildTable(records, columns)
615
+ {
616
+ if (!records || records.length === 0)
617
+ {
618
+ return '<p style="padding:0.75rem; font-size:0.8rem; color:var(--text-dim);">No records.</p>';
619
+ }
620
+
621
+ let tmpCols = columns || Object.keys(records[0]);
622
+ let tmpHTML = '<div class="data-table-wrap"><table class="data-table"><thead><tr>';
623
+
624
+ for (let i = 0; i < tmpCols.length; i++)
625
+ {
626
+ tmpHTML += '<th>' + tmpCols[i] + '</th>';
627
+ }
628
+ tmpHTML += '</tr></thead><tbody>';
629
+
630
+ for (let r = 0; r < records.length; r++)
631
+ {
632
+ tmpHTML += '<tr>';
633
+ for (let c = 0; c < tmpCols.length; c++)
634
+ {
635
+ let tmpVal = records[r][tmpCols[c]];
636
+ if (tmpVal === null || tmpVal === undefined) { tmpVal = ''; }
637
+ tmpHTML += '<td title="' + String(tmpVal).replace(/"/g, '&quot;') + '">'
638
+ + String(tmpVal).replace(/</g, '&lt;') + '</td>';
639
+ }
640
+ tmpHTML += '</tr>';
641
+ }
642
+
643
+ tmpHTML += '</tbody></table></div>';
644
+ return tmpHTML;
645
+ }
646
+
647
+ function buildStats(chips)
648
+ {
649
+ let tmpHTML = '<div class="stats-row">';
650
+ for (let i = 0; i < chips.length; i++)
651
+ {
652
+ tmpHTML += '<div class="stat-chip">' + chips[i][0] + ': <strong>' + chips[i][1] + '</strong></div>';
653
+ }
654
+ tmpHTML += '</div>';
655
+ return tmpHTML;
656
+ }
657
+
658
+ async function apiCall(method, path, body)
659
+ {
660
+ let tmpOpts = { method: method, headers: { 'Content-Type': 'application/json' } };
661
+ if (body)
662
+ {
663
+ tmpOpts.body = JSON.stringify(body);
664
+ }
665
+ let tmpResp = await fetch(path, tmpOpts);
666
+ return await tmpResp.json();
667
+ }
668
+
669
+ // ── Step 1: Parse ──────────────────────────────────────────────────────────────
670
+
671
+ async function runParse()
672
+ {
673
+ let tmpBtn = document.getElementById('btn-1');
674
+ showSpinner(tmpBtn);
675
+
676
+ try
677
+ {
678
+ let tmpData = await apiCall('GET', '/1.0/Demo/SampleData');
679
+ let tmpHTML = buildStats([
680
+ ['File', 'data/books-sample.csv'],
681
+ ['Records', tmpData.Count],
682
+ ['Columns', tmpData.Headers.length]
683
+ ]);
684
+ tmpHTML += buildTable(tmpData.Records, ['id', 'title', 'original_publication_year', 'isbn', 'language_code']);
685
+ showResult(1, tmpHTML, tmpData.Count + ' records', 'ok');
686
+ markNavDone(1);
687
+ }
688
+ catch (e)
689
+ {
690
+ showResult(1, '<p style="padding:0.75rem;color:var(--error);">' + e.message + '</p>', 'Error', 'err');
691
+ }
692
+
693
+ resetBtn(tmpBtn, '&#10003;', 'Done');
694
+ }
695
+
696
+ // ── Step 2: Map ───────────────────────────────────────────────────────────────
697
+
698
+ async function runMap()
699
+ {
700
+ let tmpBtn = document.getElementById('btn-2');
701
+ showSpinner(tmpBtn);
702
+
703
+ try
704
+ {
705
+ let tmpData = await apiCall('GET', '/1.0/Demo/Mapping');
706
+ let tmpCfg = tmpData.Configuration;
707
+
708
+ let tmpHTML = buildStats([
709
+ ['File', tmpData.MappingFile],
710
+ ['Entity', tmpCfg.Entity],
711
+ ['GUID Template', tmpCfg.GUIDTemplate],
712
+ ['Fields', Object.keys(tmpCfg.Mappings || {}).length]
713
+ ]);
714
+
715
+ let tmpMappingRows = [];
716
+ let tmpKeys = Object.keys(tmpCfg.Mappings || {});
717
+ for (let i = 0; i < tmpKeys.length; i++)
718
+ {
719
+ tmpMappingRows.push({ 'Target Field': tmpKeys[i], 'Expression': tmpCfg.Mappings[tmpKeys[i]] });
720
+ }
721
+ tmpHTML += buildTable(tmpMappingRows, ['Target Field', 'Expression']);
722
+ tmpHTML += '<div class="json-viewer">' + syntaxHighlight(tmpCfg) + '</div>';
723
+
724
+ showResult(2, tmpHTML, tmpCfg.Entity + ' mapping', 'ok');
725
+ markNavDone(2);
726
+ }
727
+ catch (e)
728
+ {
729
+ showResult(2, '<p style="padding:0.75rem;color:var(--error);">' + e.message + '</p>', 'Error', 'err');
730
+ }
731
+
732
+ resetBtn(tmpBtn, '&#10003;', 'Done');
733
+ }
734
+
735
+ // ── Step 3: Visual Mapping Editor ─────────────────────────────────────────────
736
+
737
+ function openVisualEditor()
738
+ {
739
+ if (typeof window.openMappingEditor === 'function')
740
+ {
741
+ window.openMappingEditor();
742
+ markNavDone(3);
743
+ }
744
+ else
745
+ {
746
+ showResult(3,
747
+ '<p style="padding:0.75rem;color:var(--error);">Editor bundle not loaded. Run <code>npm run build</code> in the mapping-demo directory first, then restart the server.</p>',
748
+ 'Build needed', 'err');
749
+ let tmpPanel = document.getElementById('result-3');
750
+ if (tmpPanel) tmpPanel.classList.add('visible');
751
+ }
752
+ }
753
+
754
+ // Listen for the editor closed event to refresh the mapping display
755
+ document.addEventListener('mapping-editor-closed', function()
756
+ {
757
+ showResult(3,
758
+ '<div style="padding:0.75rem; font-size:0.82rem; color:var(--text-mid);">Mapping saved. Run <strong>Step 4 &mdash; Transform</strong> to apply your new wiring.</div>',
759
+ 'Mapping saved', 'ok');
760
+ });
761
+
762
+ // ── Step 4: Transform ─────────────────────────────────────────────────────────
763
+
764
+ async function runTransform()
765
+ {
766
+ let tmpBtn = document.getElementById('btn-4');
767
+ showSpinner(tmpBtn);
768
+
769
+ try
770
+ {
771
+ let tmpData = await apiCall('POST', '/1.0/Demo/Transform');
772
+
773
+ let tmpHTML = buildStats([
774
+ ['Entity', tmpData.Entity],
775
+ ['Records in comprehension', tmpData.TotalRecords],
776
+ ['Bad records', tmpData.BadRecords],
777
+ ['Format', 'GUID-keyed dictionary']
778
+ ]);
779
+
780
+ // Show a few sample transformed records as a table
781
+ let tmpSampleKeys = Object.keys(tmpData.SampleRecords);
782
+ let tmpSampleArr = [];
783
+ for (let i = 0; i < tmpSampleKeys.length; i++)
784
+ {
785
+ tmpSampleArr.push(tmpData.SampleRecords[tmpSampleKeys[i]]);
786
+ }
787
+
788
+ if (tmpSampleArr.length > 0)
789
+ {
790
+ tmpHTML += '<div style="padding:0 0.75rem 0.4rem; font-size:0.7rem; font-weight:700; text-transform:uppercase; letter-spacing:0.04em; color:var(--text-dim); margin-top:0.5rem;">Sample Comprehension Records</div>';
791
+ tmpHTML += buildTable(tmpSampleArr, ['GUIDBook', 'Title', 'Language', 'PublicationYear', 'ISBN', 'Genre']);
792
+ }
793
+
794
+ tmpHTML += '<div style="padding:0 0.75rem 0.4rem; font-size:0.7rem; font-weight:700; text-transform:uppercase; letter-spacing:0.04em; color:var(--text-dim); margin-top:0.5rem;">Full Comprehension (JSON)</div>';
795
+ tmpHTML += '<div class="json-viewer">' + syntaxHighlight(tmpData.Comprehension) + '</div>';
796
+
797
+ showResult(4, tmpHTML, tmpData.TotalRecords + ' records', 'ok');
798
+ markNavDone(4);
799
+ }
800
+ catch (e)
801
+ {
802
+ showResult(4, '<p style="padding:0.75rem;color:var(--error);">' + e.message + '</p>', 'Error', 'err');
803
+ }
804
+
805
+ resetBtn(tmpBtn, '&#10003;', 'Done');
806
+ }
807
+
808
+ // ── Step 5: Load ──────────────────────────────────────────────────────────────
809
+
810
+ async function runLoad()
811
+ {
812
+ let tmpBtn = document.getElementById('btn-5');
813
+ showSpinner(tmpBtn);
814
+
815
+ try
816
+ {
817
+ let tmpData = await apiCall('POST', '/1.0/Demo/Load');
818
+
819
+ if (tmpData.Error)
820
+ {
821
+ showResult(5, '<p style="padding:0.75rem;color:var(--error);">' + tmpData.Error + '</p>', 'Error', 'err');
822
+ }
823
+ else
824
+ {
825
+ let tmpHTML = buildStats([
826
+ ['Entity', tmpData.Entity],
827
+ ['Records pushed', tmpData.RecordsPushed],
828
+ ['Target', 'SQLite in-memory bookstore'],
829
+ ['Method', 'IntegrationAdapter (upsert via Meadow REST API)']
830
+ ]);
831
+ tmpHTML += '<div class="json-viewer">' + syntaxHighlight(tmpData) + '</div>';
832
+ showResult(5, tmpHTML, tmpData.RecordsPushed + ' loaded', 'ok');
833
+ markNavDone(5);
834
+ }
835
+ }
836
+ catch (e)
837
+ {
838
+ showResult(5, '<p style="padding:0.75rem;color:var(--error);">' + e.message + '</p>', 'Error', 'err');
839
+ }
840
+
841
+ resetBtn(tmpBtn, '&#10003;', 'Done');
842
+ }
843
+
844
+ // ── Step 6: Verify ────────────────────────────────────────────────────────────
845
+
846
+ async function runVerify()
847
+ {
848
+ let tmpBtn = document.getElementById('btn-6');
849
+ showSpinner(tmpBtn);
850
+
851
+ try
852
+ {
853
+ let tmpData = await apiCall('GET', '/1.0/Demo/Books');
854
+
855
+ let tmpHTML = buildStats([
856
+ ['Books in database', tmpData.Count],
857
+ ['Storage', 'SQLite in-memory'],
858
+ ['Live endpoint', '/1.0/Books/0/20']
859
+ ]);
860
+
861
+ tmpHTML += buildTable(tmpData.Books, ['IDBook', 'Title', 'Genre', 'Language', 'PublicationYear', 'ISBN']);
862
+
863
+ tmpHTML += '<div style="padding:0.6rem 0.75rem; font-size:0.78rem; color:var(--text-mid);">'
864
+ + 'Also try the live Meadow-Endpoints API: '
865
+ + '<a href="/1.0/Books/0/20" target="_blank" style="color:var(--brand);">/1.0/Books/0/20</a>'
866
+ + '</div>';
867
+
868
+ showResult(6, tmpHTML, tmpData.Count + ' books verified', 'ok');
869
+ markNavDone(6);
870
+ }
871
+ catch (e)
872
+ {
873
+ showResult(6, '<p style="padding:0.75rem;color:var(--error);">' + e.message + '</p>', 'Error', 'err');
874
+ }
875
+
876
+ resetBtn(tmpBtn, '&#10003;', 'Done');
877
+ }
878
+ </script>
879
+
880
+ <!-- Pict runtime + mapping editor bundle (built by: npm run build) -->
881
+ <script src="/pict.min.js" type="text/javascript"></script>
882
+ <script src="/mapping-demo-editor.min.js" type="text/javascript"></script>
883
+ <script type="text/javascript">
884
+ // Initialize the pict mapping editor application after DOM is ready.
885
+ // Pict.safeLoadPictApplication waits for DOMContentLoaded automatically.
886
+ if (typeof Pict !== 'undefined' && typeof MappingDemoApplication !== 'undefined')
887
+ {
888
+ Pict.safeLoadPictApplication(MappingDemoApplication, 2);
889
+ }
890
+ </script>
891
+ </body>
892
+ </html>