retold 4.0.2 → 4.0.4

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 (34) hide show
  1. package/.claude/settings.local.json +37 -1
  2. package/README.md +112 -2
  3. package/docs/_sidebar.md +4 -1
  4. package/docs/architecture/architecture.md +2 -2
  5. package/docs/architecture/comprehensions.md +282 -0
  6. package/docs/architecture/fluid-models.md +355 -0
  7. package/docs/architecture/modules.md +3 -4
  8. package/docs/contributing.md +50 -0
  9. package/docs/modules/orator.md +0 -7
  10. package/docs/retold-catalog.json +3944 -878
  11. package/docs/retold-keyword-index.json +174010 -144308
  12. package/docs/testing.md +122 -0
  13. package/modules/Include-Retold-Module-List.sh +1 -1
  14. package/package.json +7 -4
  15. package/source/retold-manager/package.json +23 -0
  16. package/source/retold-manager/retold-manager.js +65 -0
  17. package/source/retold-manager/source/Retold-Manager-App.js +1532 -0
  18. package/source/retold-manager/source/Retold-Manager-ModuleCatalog.js +75 -0
  19. package/source/retold-manager/source/Retold-Manager-ProcessRunner.js +706 -0
  20. package/source/retold-manager/source/views/PictView-TUI-Checkout.js +45 -0
  21. package/source/retold-manager/source/views/PictView-TUI-Header.js +41 -0
  22. package/source/retold-manager/source/views/PictView-TUI-Layout.js +53 -0
  23. package/source/retold-manager/source/views/PictView-TUI-Status.js +45 -0
  24. package/source/retold-manager/source/views/PictView-TUI-StatusBar.js +41 -0
  25. package/source/retold-manager/source/views/PictView-TUI-Update.js +45 -0
  26. package/examples/quickstart/layer1/package-lock.json +0 -344
  27. package/examples/quickstart/layer2/package-lock.json +0 -4468
  28. package/examples/quickstart/layer3/package-lock.json +0 -1936
  29. package/examples/quickstart/layer4/package-lock.json +0 -13206
  30. package/examples/quickstart/layer5/package-lock.json +0 -345
  31. package/examples/todo-list/cli-client/package-lock.json +0 -418
  32. package/examples/todo-list/console-client/package-lock.json +0 -426
  33. package/examples/todo-list/server/package-lock.json +0 -6113
  34. package/examples/todo-list/web-client/package-lock.json +0 -12030
@@ -78,7 +78,43 @@
78
78
  "Bash(/Users/stevenvelozo/Code/retold/examples/todo-list/model/data/fix_csv.py << 'PYEOF'\nimport re\n\nINPUT_FILE = '/Users/stevenvelozo/Code/retold/examples/todo-list/model/data/seeded_todo_events.csv'\n\nwith open\\(INPUT_FILE, 'r'\\) as f:\n lines = f.readlines\\(\\)\n\nheader = lines[0].strip\\(\\)\noutput_lines = [header]\n\n# Pattern for the last 3 fields: date, number, status\n# DueDate is YYYY-MM-DD, LengthInHours is a number \\(int or float\\), Status is one of three values\ntail_pattern = re.compile\\(r',\\(\\\\d{4}-\\\\d{2}-\\\\d{2}\\),\\([\\\\d.]+\\),\\(Pending|In Progress|Complete\\)\\\\s*\\)\n\nfor i, line in enumerate\\(lines[1:], start=2\\):\n line = line.strip\\(\\)\n if not line:\n continue\n\n # Find the tail \\(DueDate, LengthInHours, Status\\) from the right\n match = tail_pattern.search\\(line\\)\n if not match:\n print\\(f\"WARNING: Line {i} does not match expected tail pattern: {line[:80]}\"\\)\n output_lines.append\\(line\\)\n continue\n\n tail_start = match.start\\(\\)\n due_date = match.group\\(1\\)\n length = match.group\\(2\\)\n status = match.group\\(3\\)\n\n # Everything before the tail is \"Name,Description\" \\(with possible extra commas in Description\\)\n front = line[:tail_start]\n\n # Split front into Name and Description at the first comma\n first_comma = front.index\\(','\\)\n name = front[:first_comma]\n description = front[first_comma + 1:]\n\n # Strip any existing surrounding quotes from the description\n if description.startswith\\('\"'\\) and description.endswith\\('\"'\\):\n description = description[1:-1]\n\n # Escape any double quotes inside the description \\(double them per CSV standard\\)\n description = description.replace\\('\"', '\"\"'\\)\n\n # Reconstruct the line with the description properly quoted\n fixed_line = f'{name},\"{description}\",{due_date},{length},{status}'\n output_lines.append\\(fixed_line\\)\n\nwith open\\(INPUT_FILE, 'w'\\) as f:\n f.write\\('\\\\n'.join\\(output_lines\\) + '\\\\n'\\)\n\nprint\\(f\"Processed {len\\(output_lines\\) - 1} data rows \\(plus header\\).\"\\)\nprint\\(\"File written successfully.\"\\)\nPYEOF)",
79
79
  "Bash(for f in architecture.md fable.md meadow.md orator.md pict.md utility.md modules.md examples.md todo-list.md todo-list-model.md todo-list-server.md todo-list-web-client.md todo-list-console-client.md todo-list-cli-client.md)",
80
80
  "Bash(do [ -f \"/Users/stevenvelozo/Code/retold/docs/$f\" ])",
81
- "WebFetch(domain:registry.npmjs.org)"
81
+ "WebFetch(domain:registry.npmjs.org)",
82
+ "WebFetch(domain:data.jsdelivr.com)",
83
+ "WebFetch(domain:purge.jsdelivr.net)",
84
+ "WebFetch(domain:raw.githubusercontent.com)",
85
+ "Bash(npx quack prepare-docs:*)",
86
+ "Bash(xargs kill)",
87
+ "WebFetch(domain:cdn.jsdelivr.net)",
88
+ "Bash(git stash:*)",
89
+ "Bash(npx indoctrinate:*)",
90
+ "Bash(npm link)",
91
+ "Bash(npm link:*)",
92
+ "Bash(indoctrinate --version:*)",
93
+ "Bash(find /Users/stevenvelozo/Code/retold/modules -maxdepth 3 -name \"README.md\" -not -path \"*/node_modules/*\" -not -path \"*/.config/*\" -not -path \"*/example*/*\" -not -path \"*/test*/*\" -not -path \"*/debug/*\" -not -path \"*/retold-harness/*\" -not -path \"*/source/dialects/*\" -exec sh -c 'echo \"\"=== $1 ===\"\"; tail -5 \"\"$1\"\"' _ {} ;)",
94
+ "Bash(__NEW_LINE_374a0e31bacbf733__ echo \"=== Modules with ## License ===\")",
95
+ "Bash(__NEW_LINE_2ef0371971d8e4a5__ echo \"=== Modules with ## Contributing ===\")",
96
+ "Bash(__NEW_LINE_62e19d1668e76d3d__ echo \"=== Modules with CONTRIBUTING.md ===\")",
97
+ "Bash(__NEW_LINE_e21f9b2ee8c8ae4f__ echo \"=== Modules with LICENSE file ===\")",
98
+ "Bash(npx mocha -u tdd -R spec --grep \"Create a new Book\")",
99
+ "Bash(timeout 5 npm run harness:*)",
100
+ "Bash(node -e \"\nconst child = require\\(''child_process''\\).spawn\\(''node'', [''bin/retold-harness.js''], { stdio: ''pipe'' }\\);\nlet output = '''';\nchild.stdout.on\\(''data'', \\(d\\) => { output += d; }\\);\nchild.stderr.on\\(''data'', \\(d\\) => { output += d; }\\);\nsetTimeout\\(\\(\\) => {\n child.kill\\(\\);\n console.log\\(output\\);\n process.exit\\(0\\);\n}, 3000\\);\n\")",
101
+ "Bash(xargs kill -9)",
102
+ "Bash(/Users/stevenvelozo/Code/retold/modules/meadow/meadow/scripts/mysql-test-db.sh:*)",
103
+ "Bash(/Users/stevenvelozo/Code/retold/modules/meadow/meadow/scripts/mssql-test-db.sh:*)",
104
+ "Bash(docker exec:*)",
105
+ "Bash(npm run test-mysql:*)",
106
+ "Bash(./node_modules/mocha/bin/_mocha:*)",
107
+ "Bash(/Users/stevenvelozo/Code/retold/modules/meadow/meadow/scripts/meadow-test-cleanup.sh:*)",
108
+ "Bash(node --no-warnings:*)",
109
+ "Bash(/Users/stevenvelozo/Code/retold/modules/meadow/retold-harness/test_foxhound_tmp.js << 'ENDOFSCRIPT'\nconst libFoxHound = require\\('foxhound'\\);\nconst libFable = require\\('fable'\\);\nconst tmpFable = new libFable\\({}\\);\nconst tmpQuery = libFoxHound.new\\(tmpFable\\);\nconsole.log\\('Query created'\\);\ntmpQuery.setDialect\\('SQLite'\\);\nconsole.log\\('Dialect set to SQLite'\\);\ntmpQuery.setScope\\('Book'\\).addRecord\\({Title:'Test'}\\).buildCreateQuery\\(\\);\nconsole.log\\('Query body:', tmpQuery.query.body\\);\nENDOFSCRIPT)",
110
+ "Bash(/Users/stevenvelozo/Code/retold/modules/meadow/retold-harness/test_audit_tmp.js << 'ENDOFSCRIPT'\nconst libFable = require\\('fable'\\);\nconst libMeadowConnectionSQLite = require\\('meadow-connection-sqlite'\\);\nconst libRetoldDataService = require\\('retold-data-service'\\);\nconst libPath = require\\('path'\\);\nconst libFS = require\\('fs'\\);\n\nconst _Settings = require\\('./source/configuration-bookstore-serve-api.js'\\);\nconst _Fable = new libFable\\(_Settings\\);\n\n_Fable.serviceManager.addServiceType\\('MeadowSQLiteProvider', libMeadowConnectionSQLite\\);\n_Fable.serviceManager.instantiateServiceProvider\\('MeadowSQLiteProvider'\\);\n\n_Fable.MeadowSQLiteProvider.connectAsync\\(\\(pError\\) => {\n if \\(pError\\) { console.log\\('Connection error:', pError\\); return; }\n\n let tmpDB = _Fable.MeadowSQLiteProvider.db;\n let tmpCreateSQL = libFS.readFileSync\\(libPath.join\\(__dirname, 'source/model/sqlite_create/BookStore-CreateSQLiteTables.sql'\\), 'utf8'\\);\n tmpDB.exec\\(tmpCreateSQL\\);\n\n _Fable.serviceManager.addServiceType\\('RetoldDataService', libRetoldDataService\\);\n _Fable.serviceManager.instantiateServiceProvider\\('RetoldDataService', _Settings.RetoldDataServiceOptions\\);\n\n _Fable.RetoldDataService.initializeService\\(\\(pInitError\\) => {\n if \\(pInitError\\) { console.log\\('Init error:', pInitError\\); return; }\n\n let tmpBookDAL = _Fable.RetoldDataService._DAL.Book;\n\n // Test the full doCreate flow\n tmpBookDAL.doCreate\\(\n tmpBookDAL.query.addRecord\\({ Title: 'Audit Test Book', Genre: 'Testing' }\\),\n function\\(pError, pQuery, pQueryRead, pRecord\\) {\n if \\(pError\\) { console.log\\('Create error:', pError\\); }\n console.log\\('=== Created Record ==='\\);\n console.log\\(JSON.stringify\\(pRecord, null, 2\\)\\);\n\n // Also query raw\n let tmpRaw = tmpDB.prepare\\('SELECT * FROM Book WHERE Title = ?'\\).get\\('Audit Test Book'\\);\n console.log\\('\\\\n=== Raw SQLite Row ==='\\);\n console.log\\(JSON.stringify\\(tmpRaw, null, 2\\)\\);\n\n // Clean up\n tmpDB.prepare\\('DELETE FROM Book WHERE Title = ?'\\).run\\('Audit Test Book'\\);\n process.exit\\(0\\);\n }\n \\);\n }\\);\n}\\);\nENDOFSCRIPT)",
111
+ "Bash(npm run:*)",
112
+ "Bash(git log:*)",
113
+ "Bash(docker rm:*)",
114
+ "Bash(npm run docker-dev-build:*)",
115
+ "Bash(npm run docker-dev-run:*)",
116
+ "Bash(npm rebuild:*)",
117
+ "Bash(docker compose:*)"
82
118
  ]
83
119
  }
84
120
  }
package/README.md CHANGED
@@ -1,2 +1,112 @@
1
- # retold
2
- A story-obsessed application suite.
1
+ # Retold
2
+
3
+ > A story-obsessed application suite.
4
+
5
+ Retold is a collection of ~50 JavaScript/Node.js modules for building web applications and APIs. The modules span five groups — from core dependency injection up through data access, API serving, and full MVC — all designed to compose together through a shared service provider pattern. Plain JavaScript, no TypeScript. MIT licensed.
6
+
7
+ ## Module Groups
8
+
9
+ | Group | Purpose |
10
+ |-------|---------|
11
+ | **Fable** | Core ecosystem: dependency injection, configuration, logging, UUID generation, expression parsing, REST client, template engine |
12
+ | **Meadow** | Data access layer: provider-agnostic ORM, query generation (FoxHound), schema definitions (Stricture), database connectors (MySQL, MSSQL, SQLite), auto-generated REST endpoints |
13
+ | **Orator** | API server: HTTP server abstraction over Restify, static file serving, reverse proxy, WebSocket reporting |
14
+ | **Pict** | MVC tools: views, templates, providers, application lifecycle — for browser, terminal, or any text-based UI |
15
+ | **Utility** | Build tools (Quackage), manifest management (Manyfest), documentation generation (Indoctrinate), process supervision (Ultravisor) |
16
+
17
+ ## The Service Provider Pattern
18
+
19
+ Every Retold module extends `fable-serviceproviderbase` and registers with a Fable instance. That instance provides dependency injection, logging, UUID generation, and shared configuration. Any registered service can reach any other through `this.fable`, so modules are loosely coupled — you can swap database providers, change server implementations, or add custom services without modifying existing code.
20
+
21
+ ```javascript
22
+ const libFable = require('fable');
23
+ const libMeadow = require('meadow');
24
+ const libOrator = require('orator');
25
+
26
+ let _Fable = new libFable({ Product: 'MyApp', LogLevel: 3 });
27
+
28
+ // Services register with the Fable instance
29
+ let _Meadow = _Fable.instantiateServiceProvider('Meadow');
30
+ let _Orator = _Fable.instantiateServiceProvider('Orator');
31
+
32
+ // Every service can reach every other service
33
+ // _Meadow.fable.Orator, _Orator.fable.Meadow, etc.
34
+ ```
35
+
36
+ ## Quick Start
37
+
38
+ ```bash
39
+ # Core foundation
40
+ npm install fable
41
+
42
+ # Data access
43
+ npm install meadow foxhound stricture
44
+
45
+ # API server
46
+ npm install orator orator-serviceserver-restify meadow-endpoints
47
+
48
+ # Browser MVC
49
+ npm install pict
50
+ ```
51
+
52
+ ```javascript
53
+ const libFable = require('fable');
54
+
55
+ let _Fable = new libFable({
56
+ Product: 'MyApp',
57
+ ProductVersion: '1.0.0',
58
+ LogLevel: 3
59
+ });
60
+
61
+ _Fable.log.info('Retold application started.');
62
+ ```
63
+
64
+ ## Testing
65
+
66
+ Each module has its own test suite: `npm test` from any module directory. Most modules only need Node.js, but the Meadow data access modules require MySQL and MSSQL for their full test suites.
67
+
68
+ Docker scripts in `modules/meadow/meadow/scripts/` manage disposable test containers on non-standard ports (MySQL 33306, MSSQL 31433) so they won't conflict with local databases.
69
+
70
+ ```bash
71
+ cd modules/meadow/meadow
72
+
73
+ # Start databases, seed data, and run tests
74
+ npm run test-mysql # MySQL tests only
75
+ npm run test-mssql # MSSQL tests only
76
+ npm run test-all-providers # Both
77
+
78
+ # Tear down when done
79
+ npm run docker-cleanup
80
+ ```
81
+
82
+ See [docs/testing.md](docs/testing.md) for full details on ports, connection settings, and managing containers.
83
+
84
+ ## Repository Structure
85
+
86
+ Each module is its own git repo, cloned into a category folder under `modules/`. The root repo tracks module organization — individual module code lives in their respective repos.
87
+
88
+ ```
89
+ retold/
90
+ ├── source/Retold.cjs
91
+ ├── test/
92
+ ├── docs/ # Documentation site (pict-docuserve)
93
+ └── modules/
94
+ ├── fable/ # Core ecosystem (~6 modules)
95
+ ├── meadow/ # Data access (~13 modules)
96
+ ├── orator/ # API server (~7 modules)
97
+ ├── pict/ # MVC tools (~15 modules)
98
+ └── utility/ # Build & docs (~10 modules)
99
+ ```
100
+
101
+ ## Documentation
102
+
103
+ Full documentation lives in the [`docs/`](docs/) folder and is served by pict-docuserve.
104
+
105
+ - [Architecture](docs/architecture/architecture.md) — Layer model, service provider pattern, component breakdown
106
+ - [Getting Started](docs/getting-started.md) — Building your first Retold application step by step
107
+ - [Examples](docs/examples/examples.md) — Complete runnable applications including a full-stack Todo List
108
+ - [All Modules](docs/architecture/modules.md) — Every repository in the suite with descriptions and links
109
+
110
+ ## License
111
+
112
+ MIT
package/docs/_sidebar.md CHANGED
@@ -1,8 +1,12 @@
1
1
  - Getting Started
2
2
 
3
3
  - [Getting Started](getting-started.md)
4
+ - [Testing](testing.md)
5
+ - [Contributing](contributing.md)
4
6
  - [Architecture](architecture/architecture.md)
5
7
  - [Ecosystem Architecture](architecture/module-architecture.md)
8
+ - [Fluid Models](architecture/fluid-models.md)
9
+ - [Comprehensions](architecture/comprehensions.md)
6
10
  - [All Modules](architecture/modules.md)
7
11
 
8
12
  - [Examples](examples/examples.md)
@@ -45,7 +49,6 @@
45
49
  - [orator-static-server](/orator/orator-static-server/)
46
50
  - [orator-http-proxy](/orator/orator-http-proxy/)
47
51
  - [tidings](/orator/tidings/)
48
- - [orator-endpoint](/orator/orator-endpoint/)
49
52
  - [orator-conversion](/orator/orator-conversion/)
50
53
 
51
54
  - [Pict — MVC Tools](modules/pict.md)
@@ -8,7 +8,7 @@ A fully-realized Retold application assembles five layers, from infrastructure a
8
8
 
9
9
  ```mermaid
10
10
  graph TB
11
- L5["<b>Layer 5</b> — Your Application / Mid-Tier Service<br/><i>Authentication, business logic, custom endpoints</i>"]
11
+ L5["<b>Layer 5</b> — Pict + Your Application / Mid-Tier Service<br/><i>MVC tools, authentication, business logic, custom endpoints</i>"]
12
12
  L4["<b>Layer 4</b> — Orator (API Server)<br/><i>HTTP lifecycle, middleware, static files, proxy</i>"]
13
13
  L3["<b>Layer 3</b> — Meadow-Endpoints<br/><i>Auto-generated CRUD routes, behavior hooks</i>"]
14
14
  L2["<b>Layer 2</b> — Meadow + FoxHound + Stricture<br/><i>Data broker, SQL generation, schema definitions</i>"]
@@ -188,7 +188,7 @@ graph TB
188
188
 
189
189
  Orator is deliberately thin. It provides a consistent interface regardless of the underlying server, so you can swap Restify for another implementation or use IPC mode for testing — without changing your application code.
190
190
 
191
- ## Pict — MVC Tools
191
+ ## Layer 5: Pict — MVC Tools
192
192
 
193
193
  Pict sits alongside the server stack, providing Model-View-Controller tools for any text-based UI: browser DOM, terminal, or rendered strings.
194
194
 
@@ -0,0 +1,282 @@
1
+ # Comprehensions
2
+
3
+ Comprehensions are the intermediate data format that Retold uses for data integration pipelines. When you need to ingest records from external systems — CSV files, JSON feeds, other databases — comprehensions provide a consistent structure for staging, deduplicating, merging, and cross-referencing that data before it reaches Meadow.
4
+
5
+ The modules [bibliograph](/meadow/bibliograph/) and [meadow-integration](/meadow/meadow-integration/) both work with comprehensions. Bibliograph provides key-value record comprehension for change tracking. Meadow-integration provides the full transformation and integration pipeline — mapping source data into comprehensions and pushing them into Meadow entities through the integration adapter.
6
+
7
+ ## The Object Format
8
+
9
+ A comprehension is traditionally a JSON object where entity records are keyed by their GUID. This is the primary format and the one the integration pipeline works with internally.
10
+
11
+ ```json
12
+ {
13
+ "Book": {
14
+ "Book_1": { "GUIDBook": "Book_1", "Title": "The Hunger Games", "ISBN": "9780439023481" },
15
+ "Book_2": { "GUIDBook": "Book_2", "Title": "Dune", "ISBN": "9780441172719" }
16
+ },
17
+ "Author": {
18
+ "Author_SuzanneCollins": { "GUIDAuthor": "Author_SuzanneCollins", "Name": "Suzanne Collins" },
19
+ "Author_FrankHerbert": { "GUIDAuthor": "Author_FrankHerbert", "Name": "Frank Herbert" }
20
+ }
21
+ }
22
+ ```
23
+
24
+ Each top-level key is an entity name. Within each entity, records are stored as properties keyed by their GUID value. This gives you:
25
+
26
+ - **O(1) lookup** — find any record by GUID without scanning
27
+ - **Natural deduplication** — writing the same GUID twice merges rather than duplicates
28
+ - **Easy merging** — `Object.assign()` combines records from multiple sources
29
+ - **Multi-entity support** — a single comprehension can hold Books, Authors, and join records together
30
+
31
+ ## The Array Format
32
+
33
+ Comprehensions can also be arrays of records. This format is useful for export, for consumption by tools that expect flat record lists, or for feeding data to systems and softwares that do not benefit from GUID-keyed lookup.
34
+
35
+ ```json
36
+ [
37
+ { "GUIDBook": "Book_1", "Title": "The Hunger Games", "ISBN": "9780439023481" },
38
+ { "GUIDBook": "Book_2", "Title": "Dune", "ISBN": "9780441172719" }
39
+ ]
40
+ ```
41
+
42
+ The array format loses the O(1) lookup performance and the automatic deduplication of the object format. You convert between the two formats using the `comprehensionarray` command or the `/1.0/Comprehension/ToArray` endpoint.
43
+
44
+ ## How Data Flows Through Comprehensions
45
+
46
+ Source data enters the integration pipeline through mapping files, gets staged as a comprehension, and then flows through the integration adapter into Meadow entities.
47
+
48
+ ```mermaid
49
+ graph LR
50
+ source["Source Data<br/><i>CSV, JSON, TSV</i>"]
51
+ mapping["Mapping File<br/><i>GUIDTemplate,<br/>field mappings,<br/>solvers</i>"]
52
+ comp["Comprehension<br/><i>GUID-keyed<br/>entity records</i>"]
53
+ adapter["Integration<br/>Adapter<br/><i>Marshal, upsert</i>"]
54
+ meadow["Meadow<br/>Entities<br/><i>Database records</i>"]
55
+
56
+ source --> mapping
57
+ mapping --> comp
58
+ comp --> adapter
59
+ adapter --> meadow
60
+
61
+ style source fill:#f5f5f5,stroke:#bdbdbd,color:#333
62
+ style mapping fill:#fff3e0,stroke:#ffa726,color:#333
63
+ style comp fill:#fff3e0,stroke:#ffa726,color:#333
64
+ style adapter fill:#fff3e0,stroke:#ffa726,color:#333
65
+ style meadow fill:#fff3e0,stroke:#ff9800,color:#333
66
+ ```
67
+
68
+ Mapping files control the transformation from source columns to comprehension fields. They define the entity name, the GUID template, and the field-by-field mappings using Pict template expressions.
69
+
70
+ ## GUID Design
71
+
72
+ GUIDs are the primary key for comprehension records. Good GUID design ensures three things:
73
+
74
+ - **Uniqueness** — each record gets a distinct key
75
+ - **Determinism** — the same source data always generates the same GUID
76
+ - **Mergeability** — related data from different sources can be matched by GUID
77
+
78
+ GUID templates use Pict's jellyfish template syntax (`{~D:...~}`) to pull values from the source record:
79
+
80
+ ```json
81
+ {
82
+ "Entity": "Book",
83
+ "GUIDTemplate": "Book_{~D:Record.id~}",
84
+ "Mappings": {
85
+ "Title": "{~D:Record.title~}",
86
+ "ISBN": "{~D:Record.isbn~}"
87
+ }
88
+ }
89
+ ```
90
+
91
+ This produces records keyed by `Book_1`, `Book_2`, etc. When the same GUID template is used across multiple transform runs on different source files, records with matching GUIDs merge automatically in the comprehension.
92
+
93
+ ## Combinatorial Keys
94
+
95
+ When no single source column provides a natural unique key, you build a combinatorial GUID from multiple columns.
96
+
97
+ ```json
98
+ {
99
+ "Entity": "Transaction",
100
+ "GUIDTemplate": "TXN_{~D:Record.date~}_{~D:Record.account_id~}_{~D:Record.seq~}",
101
+ "Mappings": {
102
+ "Amount": "{~D:Record.amount~}",
103
+ "AccountID": "{~D:Record.account_id~}",
104
+ "TransactionDate": "{~D:Record.date~}"
105
+ }
106
+ }
107
+ ```
108
+
109
+ This produces GUIDs like `TXN_2025-02-17_12345_001` — unique across the combination of date, account, and sequence number. The composite key ensures that two transactions on the same day for the same account are distinguishable, while the determinism means re-running the transform on the same source data produces the same GUIDs (merging cleanly rather than creating duplicates).
110
+
111
+ Format modifiers like `{~PascalCaseIdentifier:Record.name~}` are useful in combinatorial keys when the source values contain spaces or special characters that would make messy GUIDs.
112
+
113
+ ## The `_GUID` Prefix — Bypassing Magic Marshaling
114
+
115
+ When the integration adapter pushes comprehension records into Meadow, places a prefix on GUID fields based on the integration being run. This can signify the source system, data set or anything else the developer wants to connote in the GUID string itself. The underscore prefix controls which code path a GUID field takes.
116
+
117
+ **`GUIDBook`** — a field starting with `GUID` is treated as an **external system GUID**. The adapter runs it through the full marshaling pipeline: it looks up the external GUID in the mapping table to find the corresponding Meadow numeric ID, and writes that ID into the output record as `IDBook`.
118
+
119
+ **`_GUIDBook`** — a field starting with `_GUID` is treated as a **Meadow GUID** that already exists in the system. The adapter skips the external-to-internal translation and does a direct lookup from Meadow GUID to numeric ID. No prefix magic is applied.
120
+
121
+ ```json
122
+ {
123
+ "BookReview": {
124
+ "Review_1": {
125
+ "GUIDBookReview": "Review_1",
126
+ "GUIDBook": "Book_1",
127
+ "_GUIDUser": "0x01234567"
128
+ }
129
+ }
130
+ }
131
+ ```
132
+
133
+ In this example, `GUIDBook` with value `Book_1` is an external key from the comprehension — the adapter will look up what Meadow ID corresponds to external GUID `Book_1`. But `_GUIDUser` with value `0x01234567` is already a Meadow GUID — the adapter looks it up directly without applying the integration prefix.
134
+
135
+ The distinction matters when you are integrating data that references both external records (from the same import batch) and existing Meadow records (already in the database). Use `GUID` for references within the comprehension. Use `_GUID` when pointing at records that already exist in Meadow.
136
+
137
+ ## Cross-Connecting Recordsets
138
+
139
+ Comprehensions handle relationships between entities through GUID cross-references. A record in one entity can reference records in other entities by including `GUID`-prefixed fields that match the target entity's GUID values.
140
+
141
+ ### Join Tables from a Single Source
142
+
143
+ A common pattern is generating join table records from a single source that contains embedded relationships. For example, a books CSV where the `authors` column contains comma-separated names:
144
+
145
+ ```
146
+ id,title,authors
147
+ 1,The Hunger Games,"Suzanne Collins"
148
+ 2,Dune,"Frank Herbert"
149
+ 3,Good Omens,"Terry Pratchett,Neil Gaiman"
150
+ ```
151
+
152
+ Three mapping files transform this one CSV into three related entity sets:
153
+
154
+ **Books:**
155
+ ```json
156
+ {
157
+ "Entity": "Book",
158
+ "GUIDTemplate": "Book_{~D:Record.id~}",
159
+ "Mappings": { "Title": "{~D:Record.title~}" }
160
+ }
161
+ ```
162
+
163
+ **Authors** (one source row can produce multiple records):
164
+ ```json
165
+ {
166
+ "Entity": "Author",
167
+ "MultipleGUIDUniqueness": true,
168
+ "Solvers": [
169
+ "NewRecordsGUIDUniqueness = STRINGGETSEGMENTS(IncomingRecord.authors,\",\")"
170
+ ],
171
+ "GUIDTemplate": "Author_{~PascalCaseIdentifier:Record._GUIDUniqueness~}",
172
+ "Mappings": { "Name": "{~D:Record._GUIDUniqueness~}" }
173
+ }
174
+ ```
175
+
176
+ **BookAuthorJoin** (cross-references both entities):
177
+ ```json
178
+ {
179
+ "Entity": "BookAuthorJoin",
180
+ "MultipleGUIDUniqueness": true,
181
+ "Solvers": [
182
+ "NewRecordsGUIDUniqueness = STRINGGETSEGMENTS(IncomingRecord.authors,\",\")"
183
+ ],
184
+ "GUIDTemplate": "BAJ_A_{~PascalCaseIdentifier:Record._GUIDUniqueness~}_B_{~D:Record.id~}",
185
+ "Mappings": {
186
+ "GUIDBook": "Book_{~D:Record.id~}",
187
+ "GUIDAuthor": "Author_{~PascalCaseIdentifier:Record._GUIDUniqueness~}"
188
+ }
189
+ }
190
+ ```
191
+
192
+ The `MultipleGUIDUniqueness` flag combined with a Solver expression splits the comma-separated authors into individual entries. For each entry, the system creates a separate record with `_GUIDUniqueness` set to that entry's value. The GUID template and mappings use `_GUIDUniqueness` to build unique keys and cross-references.
193
+
194
+ For "Good Omens" with two authors, this produces:
195
+
196
+ ```mermaid
197
+ graph TB
198
+ subgraph Comprehension["Resulting Comprehension"]
199
+ direction TB
200
+ subgraph Books["Book"]
201
+ b3["<b>Book_3</b><br/>Title: Good Omens"]
202
+ end
203
+ subgraph Authors["Author"]
204
+ a1["<b>Author_TerryPratchett</b><br/>Name: Terry Pratchett"]
205
+ a2["<b>Author_NeilGaiman</b><br/>Name: Neil Gaiman"]
206
+ end
207
+ subgraph Joins["BookAuthorJoin"]
208
+ j1["<b>BAJ_A_TerryPratchett_B_3</b>"]
209
+ j2["<b>BAJ_A_NeilGaiman_B_3</b>"]
210
+ end
211
+ end
212
+
213
+ j1 -. "GUIDBook" .-> b3
214
+ j1 -. "GUIDAuthor" .-> a1
215
+ j2 -. "GUIDBook" .-> b3
216
+ j2 -. "GUIDAuthor" .-> a2
217
+
218
+ style Comprehension fill:#fff8e1,stroke:#ffcc80,color:#333
219
+ style Books fill:#fff3e0,stroke:#ffa726,color:#333
220
+ style Authors fill:#fff3e0,stroke:#ffa726,color:#333
221
+ style Joins fill:#fff3e0,stroke:#ffa726,color:#333
222
+ style b3 fill:#fff,stroke:#ffcc80,color:#333
223
+ style a1 fill:#fff,stroke:#ffcc80,color:#333
224
+ style a2 fill:#fff,stroke:#ffcc80,color:#333
225
+ style j1 fill:#fff,stroke:#ffcc80,color:#333
226
+ style j2 fill:#fff,stroke:#ffcc80,color:#333
227
+ ```
228
+
229
+ The join records contain `GUIDBook` and `GUIDAuthor` fields whose values match the GUIDs of the Book and Author entities. When the integration adapter pushes these records to Meadow, it resolves each GUID cross-reference to the corresponding numeric ID, producing proper foreign key relationships in the database.
230
+
231
+ ### Merging Across Sources
232
+
233
+ When the same entities have data spread across multiple source files, comprehension merging combines them by GUID. The `comprehensionintersect` command (or `/1.0/Comprehension/Intersect` endpoint) takes two comprehensions and merges records with matching GUIDs:
234
+
235
+ ```json
236
+ // Primary: population data
237
+ {
238
+ "Neighborhood": {
239
+ "SEATTLE_BALLARD": { "GUIDNeighborhood": "SEATTLE_BALLARD", "Name": "Ballard", "Population": 50000 }
240
+ }
241
+ }
242
+
243
+ // Secondary: housing data
244
+ {
245
+ "Neighborhood": {
246
+ "SEATTLE_BALLARD": { "GUIDNeighborhood": "SEATTLE_BALLARD", "MedianHomePrice": 750000 }
247
+ }
248
+ }
249
+
250
+ // Merged result
251
+ {
252
+ "Neighborhood": {
253
+ "SEATTLE_BALLARD": { "GUIDNeighborhood": "SEATTLE_BALLARD", "Name": "Ballard", "Population": 50000, "MedianHomePrice": 750000 }
254
+ }
255
+ }
256
+ ```
257
+
258
+ This is why deterministic GUID design matters — when two sources use the same GUID template for the same logical entity, their data merges cleanly.
259
+
260
+ ## The Integration Adapter: GUID to Database ID
261
+
262
+ When comprehension records are pushed into Meadow through the integration adapter, a three-layer GUID transformation maps external identifiers to database IDs:
263
+
264
+ ```mermaid
265
+ graph LR
266
+ ext["External GUID<br/><code>Book_1</code><br/><i>from comprehension</i>"]
267
+ mguid["Meadow GUID<br/><code>INTG-DEF-E-Book-Book_1</code><br/><i>prefixed, unique</i>"]
268
+ mid["Meadow ID<br/><code>42</code><br/><i>database primary key</i>"]
269
+
270
+ ext -- "adapter prefixing" --> mguid
271
+ mguid -- "upsert returns ID" --> mid
272
+
273
+ style ext fill:#f5f5f5,stroke:#bdbdbd,color:#333
274
+ style mguid fill:#fff3e0,stroke:#ffa726,color:#333
275
+ style mid fill:#fff3e0,stroke:#ff9800,color:#333
276
+ ```
277
+
278
+ 1. The external GUID from the comprehension (e.g. `Book_1`) gets a configurable prefix applied by the adapter, producing a Meadow GUID (e.g. `INTG-DEF-E-Book-Book_1`)
279
+ 2. The adapter upserts the record to the Meadow API. The server returns the numeric database ID
280
+ 3. The GUIDMap service tracks the bidirectional mapping: external GUID to Meadow GUID to database ID
281
+
282
+ This mapping persists across the entire integration run. When later entities reference `GUIDBook: "Book_1"`, the adapter looks up the mapping and resolves it to the correct numeric `IDBook` value. Entity integration order matters — referenced entities must be pushed before the entities that reference them.