retold 4.0.2 → 4.0.3

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.
@@ -78,7 +78,18 @@
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:*)"
82
93
  ]
83
94
  }
84
95
  }
package/README.md CHANGED
@@ -1,2 +1,92 @@
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
+ ## Repository Structure
65
+
66
+ 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.
67
+
68
+ ```
69
+ retold/
70
+ ├── source/Retold.cjs
71
+ ├── test/
72
+ ├── docs/ # Documentation site (pict-docuserve)
73
+ └── modules/
74
+ ├── fable/ # Core ecosystem (~6 modules)
75
+ ├── meadow/ # Data access (~13 modules)
76
+ ├── orator/ # API server (~7 modules)
77
+ ├── pict/ # MVC tools (~15 modules)
78
+ └── utility/ # Build & docs (~10 modules)
79
+ ```
80
+
81
+ ## Documentation
82
+
83
+ Full documentation lives in the [`docs/`](docs/) folder and is served by pict-docuserve.
84
+
85
+ - [Architecture](docs/architecture/architecture.md) — Layer model, service provider pattern, component breakdown
86
+ - [Getting Started](docs/getting-started.md) — Building your first Retold application step by step
87
+ - [Examples](docs/examples/examples.md) — Complete runnable applications including a full-stack Todo List
88
+ - [All Modules](docs/architecture/modules.md) — Every repository in the suite with descriptions and links
89
+
90
+ ## License
91
+
92
+ MIT
package/docs/_sidebar.md CHANGED
@@ -3,6 +3,8 @@
3
3
  - [Getting Started](getting-started.md)
4
4
  - [Architecture](architecture/architecture.md)
5
5
  - [Ecosystem Architecture](architecture/module-architecture.md)
6
+ - [Fluid Models](architecture/fluid-models.md)
7
+ - [Comprehensions](architecture/comprehensions.md)
6
8
  - [All Modules](architecture/modules.md)
7
9
 
8
10
  - [Examples](examples/examples.md)
@@ -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.