fable 3.1.71 → 3.1.73

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 (39) hide show
  1. package/docs/README.md +30 -6
  2. package/docs/_brand.json +18 -0
  3. package/docs/_playground.json +10 -0
  4. package/docs/_sidebar.md +2 -0
  5. package/docs/_version.json +3 -3
  6. package/docs/architecture.md +201 -39
  7. package/docs/index.html +6 -7
  8. package/docs/pict-docuserve.min.js +91 -0
  9. package/docs/pict-docuserve.min.js.map +1 -0
  10. package/docs/playground.md +38 -0
  11. package/docs/retold-catalog.json +1 -1
  12. package/docs/retold-keyword-index.json +8721 -8105
  13. package/docs/services/README.md +26 -9
  14. package/docs/services/anticipate.md +104 -40
  15. package/docs/services/csv-parser.md +63 -35
  16. package/docs/services/data-format.md +154 -49
  17. package/docs/services/data-generation.md +77 -16
  18. package/docs/services/dates.md +103 -36
  19. package/docs/services/environment-data.md +13 -2
  20. package/docs/services/expression-parser.md +280 -68
  21. package/docs/services/file-persistence.md +142 -150
  22. package/docs/services/logging.md +93 -37
  23. package/docs/services/logic.md +70 -22
  24. package/docs/services/manifest.md +114 -26
  25. package/docs/services/math.md +168 -63
  26. package/docs/services/meta-template.md +312 -158
  27. package/docs/services/object-cache.md +94 -11
  28. package/docs/services/operation.md +68 -6
  29. package/docs/services/progress-time.md +74 -13
  30. package/docs/services/progress-tracker-set.md +101 -3
  31. package/docs/services/rest-client.md +136 -104
  32. package/docs/services/settings-manager.md +133 -40
  33. package/docs/services/template.md +71 -22
  34. package/docs/services/utility.md +121 -29
  35. package/docs/services/uuid.md +58 -10
  36. package/package.json +4 -4
  37. package/source/services/Fable-Service-RestClient.js +204 -7
  38. package/test/RestClient_test.js +342 -0
  39. package/.claude/settings.local.json +0 -8
@@ -1,26 +1,54 @@
1
1
  # MetaTemplate Service
2
2
 
3
+ The MetaTemplate service is a **low-level pattern-replacement primitive**.
4
+ You register one or more `(start, end, parserFn)` patterns; `parseString()`
5
+ then walks the template string character by character, hands each
6
+ between-tag region to the matching parser function, and emits the
7
+ function's return value into the output.
8
+
9
+ It is intentionally minimal: there are no built-in tags. The `{~Name~}`
10
+ syntax shown below is just convention — every pattern is something you
11
+ wire up yourself with `addPattern()`. End markers are short by design
12
+ (typically 1–2 characters such as `~}`, `>>`, `}`); the engine's
13
+ character-by-character WordTree match is built around that.
14
+
3
15
  ## Access
4
16
 
5
17
  ```javascript
18
+ const libFable = require('fable');
19
+ const fable = new libFable({ Product: 'MetaTemplateDemo', ProductVersion: '1.0.0' });
20
+
6
21
  // On-demand service - instantiate when needed
7
22
  const metaTemplate = fable.instantiateServiceProvider('MetaTemplate');
23
+ console.log('parseString:', typeof metaTemplate.parseString);
24
+ console.log('addPattern:', typeof metaTemplate.addPattern);
8
25
  ```
9
26
 
10
27
  ## Basic Usage
11
28
 
12
- ### Simple Template
29
+ ### Register a Substitution Pattern
30
+
31
+ The minimum useful setup: one `{~name~}` pattern whose parser function
32
+ resolves `name` against the data object using dot/bracket notation.
13
33
 
14
34
  ```javascript
35
+ const libFable = require('fable');
36
+ const fable = new libFable({ Product: 'MetaTemplateDemo', ProductVersion: '1.0.0' });
15
37
  const metaTemplate = fable.instantiateServiceProvider('MetaTemplate');
16
38
 
17
- const result = metaTemplate.render('Hello, {~Name~}!', { Name: 'World' });
18
- // Returns 'Hello, World!'
39
+ metaTemplate.addPattern('{~', '~}', (pInner, pData) => {
40
+ const value = fable.Utility.getValueByHash(pData, pInner.trim());
41
+ return value == null ? '' : String(value);
42
+ });
43
+
44
+ const result = metaTemplate.parseString('Hello, {~Name~}!', { Name: 'World' });
45
+ console.log(result); // 'Hello, World!'
19
46
  ```
20
47
 
21
- ### Template Syntax
48
+ ### Template Syntax (this convention)
22
49
 
23
- MetaTemplate uses a different syntax from the standard Template service:
50
+ With the `{~ ~}` pattern registered above, these all work because
51
+ `fable.Utility.getValueByHash` understands dot/bracket paths:
24
52
 
25
53
  | Pattern | Description | Example |
26
54
  |---------|-------------|---------|
@@ -28,125 +56,185 @@ MetaTemplate uses a different syntax from the standard Template service:
28
56
  | `{~Object.Property~}` | Nested property | `{~User.Name~}` |
29
57
  | `{~Array[0]~}` | Array access | `{~Items[0]~}` |
30
58
 
31
- ## Advanced Features
32
-
33
- ### Conditional Rendering
34
-
35
59
  ```javascript
36
- const template = `
37
- {~Begin:If:ShowGreeting~}
38
- Hello, {~Name~}!
39
- {~End:If:ShowGreeting~}
40
- `;
60
+ const libFable = require('fable');
61
+ const fable = new libFable({ Product: 'MetaTemplateDemo', ProductVersion: '1.0.0' });
62
+ const metaTemplate = fable.instantiateServiceProvider('MetaTemplate');
41
63
 
42
- metaTemplate.render(template, { ShowGreeting: true, Name: 'World' });
43
- // Returns 'Hello, World!'
64
+ metaTemplate.addPattern('{~', '~}', (pInner, pData) => {
65
+ const value = fable.Utility.getValueByHash(pData, pInner.trim());
66
+ return value == null ? '' : String(value);
67
+ });
44
68
 
45
- metaTemplate.render(template, { ShowGreeting: false, Name: 'World' });
46
- // Returns ''
69
+ const data = {
70
+ User: { Name: 'Alice' },
71
+ Items: ['first', 'second', 'third']
72
+ };
73
+
74
+ console.log(metaTemplate.parseString('Name: {~User.Name~}, Item 1: {~Items[0]~}, Item 3: {~Items[2]~}', data));
47
75
  ```
48
76
 
49
- ### Iteration
77
+ ## Iteration (via parser-function logic)
78
+
79
+ The parser function receives the iterator address and inline body
80
+ separated by `|`, looks up the array, and joins the rendered bodies:
50
81
 
51
82
  ```javascript
52
- const template = `
53
- {~Begin:Each:Items~}
54
- - {~Record.Name~}: {~Record.Value~}
55
- {~End:Each:Items~}
56
- `;
83
+ const libFable = require('fable');
84
+ const fable = new libFable({ Product: 'MetaTemplateDemo', ProductVersion: '1.0.0' });
85
+ const metaTemplate = fable.instantiateServiceProvider('MetaTemplate');
86
+
87
+ // {~each ItemsAddress|template-using-{{Record.Field}}~}
88
+ // Use {{ }} inside the body to reference per-row fields; the parser
89
+ // substitutes them per iteration.
90
+ metaTemplate.addPattern('{~each ', '~}', (pInner, pData) => {
91
+ const sepIndex = pInner.indexOf('|');
92
+ const address = pInner.slice(0, sepIndex).trim();
93
+ const body = pInner.slice(sepIndex + 1);
94
+ const list = fable.Utility.getValueByHash(pData, address) || [];
95
+
96
+ return list.map((Record) =>
97
+ body.replace(/\{\{([^}]+)\}\}/g, (_, hash) => {
98
+ const v = fable.Utility.getValueByHash({ Record }, hash.trim());
99
+ return v == null ? '' : String(v);
100
+ })
101
+ ).join('');
102
+ });
57
103
 
58
104
  const data = {
59
105
  Items: [
60
- { Name: 'Apple', Value: 1.50 },
106
+ { Name: 'Apple', Value: 1.50 },
61
107
  { Name: 'Banana', Value: 0.75 }
62
108
  ]
63
109
  };
64
110
 
65
- metaTemplate.render(template, data);
66
- // Returns:
67
- // - Apple: 1.50
68
- // - Banana: 0.75
111
+ const out = metaTemplate.parseString('{~each Items|- {{Record.Name}}: {{Record.Value}}\n~}', data);
112
+ console.log(out);
69
113
  ```
70
114
 
71
- ### Nested Templates
115
+ ## Nested Templates (two-pass rendering)
116
+
117
+ `parseString` is sync when no callback is provided. Render the inner
118
+ fragment first, then drop the result into the outer template's data:
72
119
 
73
120
  ```javascript
74
- const outerTemplate = `
75
- <div class="container">
76
- {~InnerContent~}
77
- </div>
78
- `;
121
+ const libFable = require('fable');
122
+ const fable = new libFable({ Product: 'MetaTemplateDemo', ProductVersion: '1.0.0' });
123
+ const metaTemplate = fable.instantiateServiceProvider('MetaTemplate');
79
124
 
80
- const innerContent = '<p>Hello, {~Name~}!</p>';
125
+ metaTemplate.addPattern('{~', '~}', (pInner, pData) => {
126
+ const value = fable.Utility.getValueByHash(pData, pInner.trim());
127
+ return value == null ? '' : String(value);
128
+ });
81
129
 
82
- // First render inner
83
- const inner = metaTemplate.render(innerContent, { Name: 'World' });
130
+ const innerContent = '<p>Hello, {~Name~}!</p>';
131
+ const outerTemplate = '<div class="container">\n{~InnerContent~}\n</div>';
84
132
 
85
- // Then render outer
86
- const outer = metaTemplate.render(outerTemplate, { InnerContent: inner });
133
+ const inner = metaTemplate.parseString(innerContent, { Name: 'World' });
134
+ const outer = metaTemplate.parseString(outerTemplate, { InnerContent: inner });
135
+ console.log(outer);
87
136
  ```
88
137
 
89
- ## Template Inheritance
138
+ ## Template Inheritance (compose strings, render once)
90
139
 
91
- ### Define Base Template
140
+ There is no native template-inheritance system — the same effect is
141
+ achieved by composing the final template string in JS, then calling
142
+ `parseString` once.
143
+
144
+ ### Define a Base Template
92
145
 
93
146
  ```javascript
94
- const baseTemplate = `
95
- <!DOCTYPE html>
96
- <html>
97
- <head><title>{~Title~}</title></head>
98
- <body>
99
- <header>{~Header~}</header>
100
- <main>{~Content~}</main>
101
- <footer>{~Footer~}</footer>
102
- </body>
103
- </html>
104
- `;
147
+ const baseTemplate = [
148
+ '<!DOCTYPE html>',
149
+ '<html>',
150
+ '<head><title>{~Title~}</title></head>',
151
+ '<body>',
152
+ ' <header>{~Header~}</header>',
153
+ ' <main>{~Content~}</main>',
154
+ ' <footer>{~Footer~}</footer>',
155
+ '</body>',
156
+ '</html>'
157
+ ].join('\n');
158
+
159
+ console.log('baseTemplate length:', baseTemplate.length, 'chars');
105
160
  ```
106
161
 
107
- ### Extend Template
162
+ ### Use the Base Template
108
163
 
109
164
  ```javascript
165
+ const libFable = require('fable');
166
+ const fable = new libFable({ Product: 'MetaTemplateDemo', ProductVersion: '1.0.0' });
167
+ const metaTemplate = fable.instantiateServiceProvider('MetaTemplate');
168
+
169
+ metaTemplate.addPattern('{~', '~}', (pInner, pData) => {
170
+ const value = fable.Utility.getValueByHash(pData, pInner.trim());
171
+ return value == null ? '' : String(value);
172
+ });
173
+
174
+ const baseTemplate = [
175
+ '<!DOCTYPE html>',
176
+ '<html>',
177
+ '<head><title>{~Title~}</title></head>',
178
+ '<body>',
179
+ ' <header>{~Header~}</header>',
180
+ ' <main>{~Content~}</main>',
181
+ ' <footer>{~Footer~}</footer>',
182
+ '</body>',
183
+ '</html>'
184
+ ].join('\n');
185
+
110
186
  const pageData = {
111
- Title: 'My Page',
112
- Header: '<h1>Welcome</h1>',
187
+ Title: 'My Page',
188
+ Header: '<h1>Welcome</h1>',
113
189
  Content: '<p>Main content here</p>',
114
- Footer: '<p>&copy; 2024</p>'
190
+ Footer: '<p>&copy; 2024</p>'
115
191
  };
116
192
 
117
- const page = metaTemplate.render(baseTemplate, pageData);
193
+ const page = metaTemplate.parseString(baseTemplate, pageData);
194
+ console.log(page);
118
195
  ```
119
196
 
120
197
  ## Use Cases
121
198
 
122
- ### Email Templates
199
+ ### Email Templates (substitution + iteration patterns)
123
200
 
124
201
  ```javascript
125
- const emailTemplate = `
126
- Dear {~Recipient.Name~},
127
-
128
- {~Begin:If:HasOrder~}
129
- Your order #{~Order.Number~} has been {~Order.Status~}.
130
-
131
- Items:
132
- {~Begin:Each:Order.Items~}
133
- - {~Record.Name~} x {~Record.Quantity~}: ${~Record.Price~}
134
- {~End:Each:Order.Items~}
135
-
136
- Total: ${~Order.Total~}
137
- {~End:If:HasOrder~}
202
+ const libFable = require('fable');
203
+ const fable = new libFable({ Product: 'MetaTemplateDemo', ProductVersion: '1.0.0' });
204
+ const metaTemplate = fable.instantiateServiceProvider('MetaTemplate');
138
205
 
139
- {~Begin:If:IsPromotion~}
140
- Special offer: {~Promotion.Message~}
141
- {~End:If:IsPromotion~}
206
+ metaTemplate.addPattern('{~', '~}', (pInner, pData) => {
207
+ const value = fable.Utility.getValueByHash(pData, pInner.trim());
208
+ return value == null ? '' : String(value);
209
+ });
210
+ metaTemplate.addPattern('{~each ', '~}', (pInner, pData) => {
211
+ const sepIndex = pInner.indexOf('|');
212
+ const address = pInner.slice(0, sepIndex).trim();
213
+ const body = pInner.slice(sepIndex + 1);
214
+ const list = fable.Utility.getValueByHash(pData, address) || [];
215
+ return list.map((Record) =>
216
+ body.replace(/\{\{([^}]+)\}\}/g, (_, hash) => {
217
+ const v = fable.Utility.getValueByHash({ Record }, hash.trim());
218
+ return v == null ? '' : String(v);
219
+ })
220
+ ).join('');
221
+ });
142
222
 
143
- Best regards,
144
- {~Sender.Name~}
145
- `;
223
+ const emailTemplate = [
224
+ 'Dear {~Recipient.Name~},',
225
+ '',
226
+ 'Your order #{~Order.Number~} has been {~Order.Status~}.',
227
+ '',
228
+ 'Items:',
229
+ '{~each Order.Items|- {{Record.Name}} x {{Record.Quantity}}: ${{Record.Price}}\n~}',
230
+ 'Total: ${~Order.Total~}',
231
+ '',
232
+ 'Best regards,',
233
+ '{~Sender.Name~}'
234
+ ].join('\n');
146
235
 
147
236
  const emailData = {
148
237
  Recipient: { Name: 'John' },
149
- HasOrder: true,
150
238
  Order: {
151
239
  Number: '12345',
152
240
  Status: 'shipped',
@@ -156,113 +244,179 @@ const emailData = {
156
244
  ],
157
245
  Total: '109.97'
158
246
  },
159
- IsPromotion: false,
160
247
  Sender: { Name: 'Support Team' }
161
248
  };
162
249
 
163
- const email = metaTemplate.render(emailTemplate, emailData);
250
+ console.log(metaTemplate.parseString(emailTemplate, emailData));
164
251
  ```
165
252
 
166
- ### Report Generation
253
+ ### Report Generation (substitution + iteration)
167
254
 
168
255
  ```javascript
169
- const reportTemplate = `
170
- # {~Report.Title~}
171
- Generated: {~Report.Date~}
172
-
173
- ## Summary
174
- - Total Records: {~Summary.TotalRecords~}
175
- - Processed: {~Summary.Processed~}
176
- - Errors: {~Summary.Errors~}
177
-
178
- ## Details
179
- {~Begin:Each:Details~}
180
- ### {~Record.Category~}
181
- {~Record.Description~}
182
- Count: {~Record.Count~}
183
- {~End:Each:Details~}
184
-
185
- {~Begin:If:HasWarnings~}
186
- ## Warnings
187
- {~Begin:Each:Warnings~}
188
- - {~Record~}
189
- {~End:Each:Warnings~}
190
- {~End:If:HasWarnings~}
191
- `;
256
+ const libFable = require('fable');
257
+ const fable = new libFable({ Product: 'MetaTemplateDemo', ProductVersion: '1.0.0' });
258
+ const metaTemplate = fable.instantiateServiceProvider('MetaTemplate');
259
+
260
+ metaTemplate.addPattern('{~', '~}', (pInner, pData) => {
261
+ const value = fable.Utility.getValueByHash(pData, pInner.trim());
262
+ return value == null ? '' : String(value);
263
+ });
264
+ metaTemplate.addPattern('{~each ', '~}', (pInner, pData) => {
265
+ const sepIndex = pInner.indexOf('|');
266
+ const address = pInner.slice(0, sepIndex).trim();
267
+ const body = pInner.slice(sepIndex + 1);
268
+ const list = fable.Utility.getValueByHash(pData, address) || [];
269
+ return list.map((Record) =>
270
+ body.replace(/\{\{([^}]+)\}\}/g, (_, hash) => {
271
+ const v = fable.Utility.getValueByHash({ Record }, hash.trim());
272
+ return v == null ? '' : String(v);
273
+ })
274
+ ).join('');
275
+ });
276
+
277
+ const reportTemplate = [
278
+ '# {~Report.Title~}',
279
+ 'Generated: {~Report.Date~}',
280
+ '',
281
+ '## Summary',
282
+ '- Total Records: {~Summary.TotalRecords~}',
283
+ '- Processed: {~Summary.Processed~}',
284
+ '- Errors: {~Summary.Errors~}',
285
+ '',
286
+ '## Details',
287
+ '{~each Details|### {{Record.Category}}\n{{Record.Description}}\nCount: {{Record.Count}}\n\n~}'
288
+ ].join('\n');
289
+
290
+ const reportData = {
291
+ Report: { Title: 'Sales Report', Date: '2024-01-15' },
292
+ Summary: { TotalRecords: 1000, Processed: 980, Errors: 20 },
293
+ Details: [
294
+ { Category: 'Q1', Description: 'First-quarter results', Count: 240 },
295
+ { Category: 'Q2', Description: 'Second-quarter results', Count: 260 }
296
+ ]
297
+ };
298
+
299
+ console.log(metaTemplate.parseString(reportTemplate, reportData));
192
300
  ```
193
301
 
194
302
  ### Configuration File Generation
195
303
 
196
304
  ```javascript
197
- const configTemplate = `
198
- # Application Configuration
199
- # Generated for {~Environment~}
200
-
201
- server:
202
- host: {~Server.Host~}
203
- port: {~Server.Port~}
204
-
205
- database:
206
- host: {~Database.Host~}
207
- name: {~Database.Name~}
208
- {~Begin:If:Database.UseSSL~}
209
- ssl: true
210
- {~End:If:Database.UseSSL~}
211
-
212
- {~Begin:Each:Features~}
213
- {~Record.Name~}:
214
- enabled: {~Record.Enabled~}
215
- {~End:Each:Features~}
216
- `;
217
-
218
- const config = metaTemplate.render(configTemplate, {
305
+ const libFable = require('fable');
306
+ const fable = new libFable({ Product: 'MetaTemplateDemo', ProductVersion: '1.0.0' });
307
+ const metaTemplate = fable.instantiateServiceProvider('MetaTemplate');
308
+
309
+ metaTemplate.addPattern('{~', '~}', (pInner, pData) => {
310
+ const value = fable.Utility.getValueByHash(pData, pInner.trim());
311
+ return value == null ? '' : String(value);
312
+ });
313
+ metaTemplate.addPattern('{~each ', '~}', (pInner, pData) => {
314
+ const sepIndex = pInner.indexOf('|');
315
+ const address = pInner.slice(0, sepIndex).trim();
316
+ const body = pInner.slice(sepIndex + 1);
317
+ const list = fable.Utility.getValueByHash(pData, address) || [];
318
+ return list.map((Record) =>
319
+ body.replace(/\{\{([^}]+)\}\}/g, (_, hash) => {
320
+ const v = fable.Utility.getValueByHash({ Record }, hash.trim());
321
+ return v == null ? '' : String(v);
322
+ })
323
+ ).join('');
324
+ });
325
+
326
+ const configTemplate = [
327
+ '# Application Configuration',
328
+ '# Generated for {~Environment~}',
329
+ '',
330
+ 'server:',
331
+ ' host: {~Server.Host~}',
332
+ ' port: {~Server.Port~}',
333
+ '',
334
+ 'database:',
335
+ ' host: {~Database.Host~}',
336
+ ' name: {~Database.Name~}',
337
+ '',
338
+ '{~each Features|{{Record.Name}}:\n enabled: {{Record.Enabled}}\n\n~}'
339
+ ].join('\n');
340
+
341
+ const config = metaTemplate.parseString(configTemplate, {
219
342
  Environment: 'production',
220
- Server: { Host: '0.0.0.0', Port: 8080 },
221
- Database: { Host: 'db.example.com', Name: 'myapp', UseSSL: true },
343
+ Server: { Host: '0.0.0.0', Port: 8080 },
344
+ Database: { Host: 'db.example.com', Name: 'myapp' },
222
345
  Features: [
223
346
  { Name: 'cache', Enabled: true },
224
347
  { Name: 'debug', Enabled: false }
225
348
  ]
226
349
  });
350
+
351
+ console.log(config);
227
352
  ```
228
353
 
229
354
  ### Dynamic Forms
230
355
 
231
356
  ```javascript
232
- const formTemplate = `
233
- <form action="{~Form.Action~}" method="{~Form.Method~}">
234
- {~Begin:Each:Form.Fields~}
235
- <div class="field">
236
- <label for="{~Record.Id~}">{~Record.Label~}</label>
237
- <input type="{~Record.Type~}" id="{~Record.Id~}" name="{~Record.Name~}"
238
- {~Begin:If:Record.Required~}required{~End:If:Record.Required~}>
239
- </div>
240
- {~End:Each:Form.Fields~}
241
- <button type="submit">{~Form.SubmitText~}</button>
242
- </form>
243
- `;
357
+ const libFable = require('fable');
358
+ const fable = new libFable({ Product: 'MetaTemplateDemo', ProductVersion: '1.0.0' });
359
+ const metaTemplate = fable.instantiateServiceProvider('MetaTemplate');
360
+
361
+ metaTemplate.addPattern('{~', '~}', (pInner, pData) => {
362
+ const value = fable.Utility.getValueByHash(pData, pInner.trim());
363
+ return value == null ? '' : String(value);
364
+ });
365
+ metaTemplate.addPattern('{~each ', '~}', (pInner, pData) => {
366
+ const sepIndex = pInner.indexOf('|');
367
+ const address = pInner.slice(0, sepIndex).trim();
368
+ const body = pInner.slice(sepIndex + 1);
369
+ const list = fable.Utility.getValueByHash(pData, address) || [];
370
+ return list.map((Record) =>
371
+ body.replace(/\{\{([^}]+)\}\}/g, (_, hash) => {
372
+ const v = fable.Utility.getValueByHash({ Record }, hash.trim());
373
+ return v == null ? '' : String(v);
374
+ })
375
+ ).join('');
376
+ });
377
+
378
+ const formTemplate = [
379
+ '<form action="{~Form.Action~}" method="{~Form.Method~}">',
380
+ '{~each Form.Fields| <div class="field"><label for="{{Record.Id}}">{{Record.Label}}</label><input type="{{Record.Type}}" id="{{Record.Id}}" name="{{Record.Name}}"></div>\n~}',
381
+ ' <button type="submit">{~Form.SubmitText~}</button>',
382
+ '</form>'
383
+ ].join('\n');
384
+
385
+ console.log(metaTemplate.parseString(formTemplate, {
386
+ Form: {
387
+ Action: '/signup',
388
+ Method: 'POST',
389
+ SubmitText: 'Sign Up',
390
+ Fields: [
391
+ { Id: 'email', Name: 'email', Label: 'Email', Type: 'email' },
392
+ { Id: 'name', Name: 'name', Label: 'Name', Type: 'text' }
393
+ ]
394
+ }
395
+ }));
244
396
  ```
245
397
 
246
- ## Comparison with Template Service
398
+ ## Async Mode
247
399
 
248
- | Feature | Template | MetaTemplate |
249
- |---------|----------|--------------|
250
- | Syntax | `<%= %>` / `<% %>` | `{~ ~}` |
251
- | Conditionals | JavaScript `if` | `{~Begin:If~}` |
252
- | Loops | JavaScript `for` | `{~Begin:Each~}` |
253
- | JavaScript execution | Yes | Limited |
254
- | Use case | Code-heavy templates | Data-driven templates |
400
+ Pass a callback to `parseString` to run asynchronously — parser
401
+ functions can complete on their own schedule (via the WordTree's
402
+ `addPatternBoth` async-parser slot). The sync mode used above is
403
+ selected automatically when no callback is supplied.
255
404
 
256
- ## Best Practices
405
+ ## Comparison with Template Service
257
406
 
258
- 1. **Use MetaTemplate for data-driven content**: When templates are mostly about inserting values
259
- 2. **Use Template for logic-heavy content**: When you need complex JavaScript logic
260
- 3. **Keep templates readable**: Use clear section names
261
- 4. **Validate data before rendering**: Ensure required properties exist
407
+ | Feature | Template (`fable.Utility.template`) | MetaTemplate |
408
+ |---------|-------------------------------------|--------------|
409
+ | Syntax | `<%= %>` / `<% %>` (underscore) | Whatever you register via `addPattern` |
410
+ | Control flow | JavaScript `<% if %>` / `<% for %>` | None built in; write parser-function logic for what you need |
411
+ | JavaScript execution | Yes (compiled function body) | No — pure callbacks |
412
+ | Use case | Code-heavy templates | Custom tag syntaxes, replacement-only templates |
262
413
 
263
414
  ## Notes
264
415
 
265
- - MetaTemplate is designed for safer, more declarative templates
266
- - Less JavaScript execution means less risk of injection
267
- - Templates can be stored in files and loaded dynamically
268
- - The `Record` variable is special within `Each` blocks
416
+ - MetaTemplate has no built-in tag vocabulary; everything is `addPattern`.
417
+ - `parseString` is synchronous unless you pass a callback.
418
+ - The `Record` variable convention used in the iteration examples above
419
+ is a JS-side helper inside the iteration parser function there is
420
+ nothing in MetaTemplate that names it.
421
+ - Keep end markers short (1–2 characters); the WordTree match is built
422
+ around that.