fable 3.1.50 → 3.1.52

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 (148) hide show
  1. package/README.md +87 -2
  2. package/dist/fable.js +74 -46
  3. package/dist/fable.js.map +1 -1
  4. package/dist/fable.min.js +2 -2
  5. package/dist/fable.min.js.map +1 -1
  6. package/docs/.nojekyll +0 -0
  7. package/docs/README.md +95 -0
  8. package/docs/_sidebar.md +42 -0
  9. package/docs/architecture.md +326 -0
  10. package/docs/cover.md +11 -0
  11. package/docs/index.html +51 -0
  12. package/docs/services/README.md +76 -0
  13. package/docs/services/anticipate.md +331 -0
  14. package/docs/services/csv-parser.md +152 -0
  15. package/docs/services/data-format.md +277 -0
  16. package/docs/services/data-generation.md +142 -0
  17. package/docs/services/dates.md +216 -0
  18. package/docs/services/environment-data.md +44 -0
  19. package/docs/services/expression-parser-functions/README.md +178 -0
  20. package/docs/services/expression-parser-functions/abs.md +84 -0
  21. package/docs/services/expression-parser-functions/aggregationhistogram.md +83 -0
  22. package/docs/services/expression-parser-functions/aggregationhistogrambyobject.md +64 -0
  23. package/docs/services/expression-parser-functions/arrayconcat.md +64 -0
  24. package/docs/services/expression-parser-functions/avg.md +81 -0
  25. package/docs/services/expression-parser-functions/bucketset.md +69 -0
  26. package/docs/services/expression-parser-functions/ceil.md +70 -0
  27. package/docs/services/expression-parser-functions/cleanvaluearray.md +66 -0
  28. package/docs/services/expression-parser-functions/cleanvalueobject.md +68 -0
  29. package/docs/services/expression-parser-functions/compare.md +72 -0
  30. package/docs/services/expression-parser-functions/concat.md +73 -0
  31. package/docs/services/expression-parser-functions/concatraw.md +73 -0
  32. package/docs/services/expression-parser-functions/cos.md +75 -0
  33. package/docs/services/expression-parser-functions/count.md +73 -0
  34. package/docs/services/expression-parser-functions/countset.md +65 -0
  35. package/docs/services/expression-parser-functions/countsetelements.md +63 -0
  36. package/docs/services/expression-parser-functions/createarrayfromabsolutevalues.md +63 -0
  37. package/docs/services/expression-parser-functions/createvalueobjectbyhashes.md +69 -0
  38. package/docs/services/expression-parser-functions/cumulativesummation.md +96 -0
  39. package/docs/services/expression-parser-functions/dateadddays.md +79 -0
  40. package/docs/services/expression-parser-functions/dateaddhours.md +74 -0
  41. package/docs/services/expression-parser-functions/dateaddmilliseconds.md +65 -0
  42. package/docs/services/expression-parser-functions/dateaddminutes.md +72 -0
  43. package/docs/services/expression-parser-functions/dateaddmonths.md +74 -0
  44. package/docs/services/expression-parser-functions/dateaddseconds.md +66 -0
  45. package/docs/services/expression-parser-functions/dateaddweeks.md +73 -0
  46. package/docs/services/expression-parser-functions/dateaddyears.md +74 -0
  47. package/docs/services/expression-parser-functions/datedaydifference.md +84 -0
  48. package/docs/services/expression-parser-functions/datefromparts.md +81 -0
  49. package/docs/services/expression-parser-functions/datehourdifference.md +64 -0
  50. package/docs/services/expression-parser-functions/datemathadd.md +72 -0
  51. package/docs/services/expression-parser-functions/datemilliseconddifference.md +64 -0
  52. package/docs/services/expression-parser-functions/dateminutedifference.md +64 -0
  53. package/docs/services/expression-parser-functions/datemonthdifference.md +66 -0
  54. package/docs/services/expression-parser-functions/dateseconddifference.md +64 -0
  55. package/docs/services/expression-parser-functions/dateweekdifference.md +65 -0
  56. package/docs/services/expression-parser-functions/dateyeardifference.md +64 -0
  57. package/docs/services/expression-parser-functions/distributionhistogram.md +96 -0
  58. package/docs/services/expression-parser-functions/distributionhistogrambyobject.md +64 -0
  59. package/docs/services/expression-parser-functions/entryinset.md +72 -0
  60. package/docs/services/expression-parser-functions/euler.md +77 -0
  61. package/docs/services/expression-parser-functions/exp.md +74 -0
  62. package/docs/services/expression-parser-functions/findfirstvaluebyexactmatch.md +67 -0
  63. package/docs/services/expression-parser-functions/findfirstvaluebystringincludes.md +67 -0
  64. package/docs/services/expression-parser-functions/flatten.md +76 -0
  65. package/docs/services/expression-parser-functions/floor.md +70 -0
  66. package/docs/services/expression-parser-functions/gaussianelimination.md +75 -0
  67. package/docs/services/expression-parser-functions/generatearrayofobjectsfromsets.md +70 -0
  68. package/docs/services/expression-parser-functions/getvalue.md +90 -0
  69. package/docs/services/expression-parser-functions/getvaluearray.md +64 -0
  70. package/docs/services/expression-parser-functions/getvalueobject.md +67 -0
  71. package/docs/services/expression-parser-functions/if.md +109 -0
  72. package/docs/services/expression-parser-functions/iterativeseries.md +107 -0
  73. package/docs/services/expression-parser-functions/join.md +75 -0
  74. package/docs/services/expression-parser-functions/joinraw.md +64 -0
  75. package/docs/services/expression-parser-functions/largestinset.md +63 -0
  76. package/docs/services/expression-parser-functions/leastsquares.md +66 -0
  77. package/docs/services/expression-parser-functions/linest.md +58 -0
  78. package/docs/services/expression-parser-functions/log.md +74 -0
  79. package/docs/services/expression-parser-functions/match.md +71 -0
  80. package/docs/services/expression-parser-functions/matrixinverse.md +67 -0
  81. package/docs/services/expression-parser-functions/matrixmultiply.md +71 -0
  82. package/docs/services/expression-parser-functions/matrixtranspose.md +72 -0
  83. package/docs/services/expression-parser-functions/matrixvectormultiply.md +69 -0
  84. package/docs/services/expression-parser-functions/max.md +73 -0
  85. package/docs/services/expression-parser-functions/mean.md +63 -0
  86. package/docs/services/expression-parser-functions/median.md +79 -0
  87. package/docs/services/expression-parser-functions/min.md +73 -0
  88. package/docs/services/expression-parser-functions/mode.md +66 -0
  89. package/docs/services/expression-parser-functions/objectkeystoarray.md +66 -0
  90. package/docs/services/expression-parser-functions/objectvaluessortbyexternalobjectarray.md +65 -0
  91. package/docs/services/expression-parser-functions/objectvaluestoarray.md +67 -0
  92. package/docs/services/expression-parser-functions/percent.md +75 -0
  93. package/docs/services/expression-parser-functions/pi.md +77 -0
  94. package/docs/services/expression-parser-functions/polynomialregression.md +69 -0
  95. package/docs/services/expression-parser-functions/predict.md +71 -0
  96. package/docs/services/expression-parser-functions/rad.md +85 -0
  97. package/docs/services/expression-parser-functions/randomfloat.md +63 -0
  98. package/docs/services/expression-parser-functions/randomfloatbetween.md +72 -0
  99. package/docs/services/expression-parser-functions/randomfloatupto.md +65 -0
  100. package/docs/services/expression-parser-functions/randominteger.md +56 -0
  101. package/docs/services/expression-parser-functions/randomintegerbetween.md +72 -0
  102. package/docs/services/expression-parser-functions/randomintegerupto.md +64 -0
  103. package/docs/services/expression-parser-functions/resolvehtmlentities.md +64 -0
  104. package/docs/services/expression-parser-functions/round.md +111 -0
  105. package/docs/services/expression-parser-functions/setconcatenate.md +64 -0
  106. package/docs/services/expression-parser-functions/sin.md +83 -0
  107. package/docs/services/expression-parser-functions/slice.md +80 -0
  108. package/docs/services/expression-parser-functions/smallestinset.md +63 -0
  109. package/docs/services/expression-parser-functions/sorthistogram.md +70 -0
  110. package/docs/services/expression-parser-functions/sorthistogrambykeys.md +69 -0
  111. package/docs/services/expression-parser-functions/sortset.md +75 -0
  112. package/docs/services/expression-parser-functions/sqrt.md +85 -0
  113. package/docs/services/expression-parser-functions/stdev.md +81 -0
  114. package/docs/services/expression-parser-functions/stdeva.md +58 -0
  115. package/docs/services/expression-parser-functions/stdevp.md +83 -0
  116. package/docs/services/expression-parser-functions/stringcountsegments.md +66 -0
  117. package/docs/services/expression-parser-functions/stringgetsegments.md +74 -0
  118. package/docs/services/expression-parser-functions/subtractingsummation.md +66 -0
  119. package/docs/services/expression-parser-functions/sum.md +78 -0
  120. package/docs/services/expression-parser-functions/tan.md +78 -0
  121. package/docs/services/expression-parser-functions/tofixed.md +75 -0
  122. package/docs/services/expression-parser-functions/var.md +67 -0
  123. package/docs/services/expression-parser-functions/vara.md +58 -0
  124. package/docs/services/expression-parser-functions/varp.md +66 -0
  125. package/docs/services/expression-parser-functions/when.md +98 -0
  126. package/docs/services/expression-parser.md +314 -0
  127. package/docs/services/file-persistence.md +279 -0
  128. package/docs/services/logging.md +237 -0
  129. package/docs/services/logic.md +166 -0
  130. package/docs/services/manifest.md +256 -0
  131. package/docs/services/math.md +279 -0
  132. package/docs/services/meta-template.md +268 -0
  133. package/docs/services/object-cache.md +171 -0
  134. package/docs/services/operation.md +207 -0
  135. package/docs/services/progress-time.md +167 -0
  136. package/docs/services/progress-tracker-set.md +222 -0
  137. package/docs/services/rest-client.md +296 -0
  138. package/docs/services/settings-manager.md +265 -0
  139. package/docs/services/template.md +233 -0
  140. package/docs/services/utility.md +304 -0
  141. package/docs/services/uuid.md +162 -0
  142. package/package.json +1 -1
  143. package/source/services/Fable-Service-DataFormat.js +1 -0
  144. package/source/services/Fable-Service-ExpressionParser/Fable-Service-ExpressionParser-FunctionMap.json +24 -0
  145. package/source/services/Fable-Service-ExpressionParser/Fable-Service-ExpressionParser-ValueMarshal.js +6 -4
  146. package/source/services/Fable-Service-Math.js +65 -3
  147. package/test/ExpressionParser_tests.js +5 -0
  148. package/test/Math_test.js +29 -0
@@ -0,0 +1,268 @@
1
+ # MetaTemplate Service
2
+
3
+ ## Access
4
+
5
+ ```javascript
6
+ // On-demand service - instantiate when needed
7
+ const metaTemplate = fable.instantiateServiceProvider('MetaTemplate');
8
+ ```
9
+
10
+ ## Basic Usage
11
+
12
+ ### Simple Template
13
+
14
+ ```javascript
15
+ const metaTemplate = fable.instantiateServiceProvider('MetaTemplate');
16
+
17
+ const result = metaTemplate.render('Hello, {~Name~}!', { Name: 'World' });
18
+ // Returns 'Hello, World!'
19
+ ```
20
+
21
+ ### Template Syntax
22
+
23
+ MetaTemplate uses a different syntax from the standard Template service:
24
+
25
+ | Pattern | Description | Example |
26
+ |---------|-------------|---------|
27
+ | `{~Property~}` | Simple substitution | `{~Name~}` |
28
+ | `{~Object.Property~}` | Nested property | `{~User.Name~}` |
29
+ | `{~Array[0]~}` | Array access | `{~Items[0]~}` |
30
+
31
+ ## Advanced Features
32
+
33
+ ### Conditional Rendering
34
+
35
+ ```javascript
36
+ const template = `
37
+ {~Begin:If:ShowGreeting~}
38
+ Hello, {~Name~}!
39
+ {~End:If:ShowGreeting~}
40
+ `;
41
+
42
+ metaTemplate.render(template, { ShowGreeting: true, Name: 'World' });
43
+ // Returns 'Hello, World!'
44
+
45
+ metaTemplate.render(template, { ShowGreeting: false, Name: 'World' });
46
+ // Returns ''
47
+ ```
48
+
49
+ ### Iteration
50
+
51
+ ```javascript
52
+ const template = `
53
+ {~Begin:Each:Items~}
54
+ - {~Record.Name~}: {~Record.Value~}
55
+ {~End:Each:Items~}
56
+ `;
57
+
58
+ const data = {
59
+ Items: [
60
+ { Name: 'Apple', Value: 1.50 },
61
+ { Name: 'Banana', Value: 0.75 }
62
+ ]
63
+ };
64
+
65
+ metaTemplate.render(template, data);
66
+ // Returns:
67
+ // - Apple: 1.50
68
+ // - Banana: 0.75
69
+ ```
70
+
71
+ ### Nested Templates
72
+
73
+ ```javascript
74
+ const outerTemplate = `
75
+ <div class="container">
76
+ {~InnerContent~}
77
+ </div>
78
+ `;
79
+
80
+ const innerContent = '<p>Hello, {~Name~}!</p>';
81
+
82
+ // First render inner
83
+ const inner = metaTemplate.render(innerContent, { Name: 'World' });
84
+
85
+ // Then render outer
86
+ const outer = metaTemplate.render(outerTemplate, { InnerContent: inner });
87
+ ```
88
+
89
+ ## Template Inheritance
90
+
91
+ ### Define Base Template
92
+
93
+ ```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
+ `;
105
+ ```
106
+
107
+ ### Extend Template
108
+
109
+ ```javascript
110
+ const pageData = {
111
+ Title: 'My Page',
112
+ Header: '<h1>Welcome</h1>',
113
+ Content: '<p>Main content here</p>',
114
+ Footer: '<p>&copy; 2024</p>'
115
+ };
116
+
117
+ const page = metaTemplate.render(baseTemplate, pageData);
118
+ ```
119
+
120
+ ## Use Cases
121
+
122
+ ### Email Templates
123
+
124
+ ```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~}
138
+
139
+ {~Begin:If:IsPromotion~}
140
+ Special offer: {~Promotion.Message~}
141
+ {~End:If:IsPromotion~}
142
+
143
+ Best regards,
144
+ {~Sender.Name~}
145
+ `;
146
+
147
+ const emailData = {
148
+ Recipient: { Name: 'John' },
149
+ HasOrder: true,
150
+ Order: {
151
+ Number: '12345',
152
+ Status: 'shipped',
153
+ Items: [
154
+ { Name: 'Widget', Quantity: 2, Price: '29.99' },
155
+ { Name: 'Gadget', Quantity: 1, Price: '49.99' }
156
+ ],
157
+ Total: '109.97'
158
+ },
159
+ IsPromotion: false,
160
+ Sender: { Name: 'Support Team' }
161
+ };
162
+
163
+ const email = metaTemplate.render(emailTemplate, emailData);
164
+ ```
165
+
166
+ ### Report Generation
167
+
168
+ ```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
+ `;
192
+ ```
193
+
194
+ ### Configuration File Generation
195
+
196
+ ```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, {
219
+ Environment: 'production',
220
+ Server: { Host: '0.0.0.0', Port: 8080 },
221
+ Database: { Host: 'db.example.com', Name: 'myapp', UseSSL: true },
222
+ Features: [
223
+ { Name: 'cache', Enabled: true },
224
+ { Name: 'debug', Enabled: false }
225
+ ]
226
+ });
227
+ ```
228
+
229
+ ### Dynamic Forms
230
+
231
+ ```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
+ `;
244
+ ```
245
+
246
+ ## Comparison with Template Service
247
+
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 |
255
+
256
+ ## Best Practices
257
+
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
262
+
263
+ ## Notes
264
+
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
@@ -0,0 +1,171 @@
1
+ # ObjectCache Service
2
+
3
+ The ObjectCache service (powered by [cachetrax](https://github.com/stevenvelozo/cachetrax)) provides in-memory object caching with size-based and time-based expiration, backed by a linked list for efficient eviction.
4
+
5
+ ## Access
6
+
7
+ ```javascript
8
+ // On-demand service - instantiate when needed
9
+ const cache = fable.instantiateServiceProvider('ObjectCache');
10
+
11
+ // Create named caches for different purposes
12
+ const userCache = fable.instantiateServiceProvider('ObjectCache', {}, 'user-cache');
13
+ const sessionCache = fable.instantiateServiceProvider('ObjectCache', {}, 'session-cache');
14
+ ```
15
+
16
+ ## Basic Operations
17
+
18
+ ### Put (Add or Update)
19
+
20
+ ```javascript
21
+ cache.put('some-data', 'my-key');
22
+ // Stores 'some-data' with hash 'my-key'
23
+ // If 'my-key' already exists, updates the stored datum
24
+
25
+ cache.put({ name: 'John', age: 30 }, 'user-123');
26
+ // Stores an object with hash 'user-123'
27
+ ```
28
+
29
+ ### Read
30
+
31
+ ```javascript
32
+ const data = cache.read('my-key');
33
+ // Returns the stored datum, or false if not found
34
+ ```
35
+
36
+ ### Expire (Remove)
37
+
38
+ ```javascript
39
+ cache.expire('my-key');
40
+ // Removes the entry from the cache and returns the removed node
41
+ // Returns false if the key doesn't exist
42
+ ```
43
+
44
+ ### Touch (Refresh)
45
+
46
+ ```javascript
47
+ cache.touch('my-key');
48
+ // Moves the entry to the tail of the list and resets its timestamp
49
+ // Useful for keeping frequently accessed items fresh
50
+ ```
51
+
52
+ ## Size-Based Expiration
53
+
54
+ ### maxLength
55
+
56
+ Set `maxLength` to automatically evict the oldest entry when the cache exceeds the limit:
57
+
58
+ ```javascript
59
+ cache.maxLength = 2;
60
+
61
+ cache.put('A', 'ABC');
62
+ cache.put('D', 'DEF');
63
+ // Cache: [ABC, DEF] (length 2)
64
+
65
+ cache.put('G', 'GHI');
66
+ // ABC is automatically evicted
67
+ // Cache: [DEF, GHI] (length 2)
68
+ ```
69
+
70
+ Setting `maxLength` to `0` (the default) disables automatic size-based eviction on insert. To enforce a new smaller `maxLength` on existing entries, call `prune()`.
71
+
72
+ ## Time-Based Expiration
73
+
74
+ ### maxAge
75
+
76
+ Set `maxAge` (in milliseconds) to expire entries older than the specified age when `prune()` is called:
77
+
78
+ ```javascript
79
+ cache.maxAge = 60000; // 1 minute
80
+ ```
81
+
82
+ ## Pruning
83
+
84
+ ### prune(callback)
85
+
86
+ Prune the cache based on both `maxAge` and `maxLength` rules. Expired entries are removed first, then size limits are enforced:
87
+
88
+ ```javascript
89
+ cache.maxLength = 2;
90
+
91
+ // After adding many entries...
92
+ cache.prune((removedRecords) => {
93
+ console.log(`Pruned ${removedRecords.length} entries`);
94
+ // Cache is now within maxLength
95
+ });
96
+ ```
97
+
98
+ ### pruneBasedOnExpiration(callback, removedRecords)
99
+
100
+ Prune only entries older than `maxAge`:
101
+
102
+ ```javascript
103
+ cache.maxAge = 30000; // 30 seconds
104
+ cache.pruneBasedOnExpiration((removed) => {
105
+ console.log(`Expired ${removed.length} old entries`);
106
+ });
107
+ ```
108
+
109
+ ### pruneBasedOnLength(callback, removedRecords)
110
+
111
+ Prune only based on `maxLength`, popping entries from the head (oldest) of the list:
112
+
113
+ ```javascript
114
+ cache.pruneBasedOnLength((removed) => {
115
+ console.log(`Evicted ${removed.length} entries for length`);
116
+ });
117
+ ```
118
+
119
+ ### pruneCustom(callback, pruneFunction, removedRecords)
120
+
121
+ Prune entries using a custom function. The function receives `(datum, hash, node)` and should return `true` to expire the entry:
122
+
123
+ ```javascript
124
+ cache.pruneCustom(
125
+ (removed) => { console.log(`Custom pruned ${removed.length}`); },
126
+ (datum, hash, node) => {
127
+ // Expire entries where datum starts with 'temp'
128
+ return typeof datum === 'string' && datum.startsWith('temp');
129
+ }
130
+ );
131
+ ```
132
+
133
+ ## Low-Level Access
134
+
135
+ ### getNode(hash)
136
+
137
+ Get the full linked list node (including metadata) for a hash:
138
+
139
+ ```javascript
140
+ const node = cache.getNode('my-key');
141
+ // node.Datum — the stored data
142
+ // node.Hash — the hash key
143
+ // node.Metadata.Created — timestamp (ms) when the entry was created
144
+ ```
145
+
146
+ ### RecordMap
147
+
148
+ Access the record map directly (a plain object mapping hashes to data):
149
+
150
+ ```javascript
151
+ const allRecords = cache.RecordMap;
152
+ // { 'my-key': 'some-data', 'user-123': { name: 'John', age: 30 } }
153
+ ```
154
+
155
+ ### Internal List
156
+
157
+ The cache is backed by a linked list accessible via `cache._List`:
158
+
159
+ ```javascript
160
+ cache._List.length // Number of entries in the cache
161
+ cache._List.head // First (oldest) node
162
+ cache._List.tail // Last (newest) node
163
+ ```
164
+
165
+ ## Notes
166
+
167
+ - Cache is in-memory only; data is lost on restart
168
+ - `maxLength` of `0` means no automatic size limit
169
+ - `maxAge` of `0` means no automatic time-based expiration
170
+ - Automatic eviction on `put()` only removes one entry at a time; use `prune()` for bulk cleanup
171
+ - Each node tracks its creation time in `node.Metadata.Created`
@@ -0,0 +1,207 @@
1
+ # Operation Service
2
+
3
+ The Operation service provides phased step execution with built-in progress tracking and logging, designed for complex multi-step workflows.
4
+
5
+ ## Access
6
+
7
+ ```javascript
8
+ // On-demand service - instantiate when needed
9
+ const operation = fable.instantiateServiceProvider('Operation', { Name: 'My Operation' }, 'MY-OP-1');
10
+ ```
11
+
12
+ ## Basic Usage
13
+
14
+ ### Create an Operation
15
+
16
+ ```javascript
17
+ const operation = fable.instantiateServiceProvider('Operation', {
18
+ Name: 'Data Import'
19
+ }, 'IMPORT-123');
20
+ ```
21
+
22
+ ### Add Steps
23
+
24
+ Use `addStep()` to register sequential steps. Each step function receives a completion callback and runs with a bound context:
25
+
26
+ ```javascript
27
+ operation.addStep(
28
+ function (fStepComplete) {
29
+ this.log.info('Validating input...');
30
+ // ... validation logic ...
31
+ fStepComplete();
32
+ },
33
+ {}, // Step metadata (accessible as this.metadata / this.options)
34
+ 'Validate', // Step name
35
+ 'Validate input data', // Step description
36
+ 'VALIDATE-STEP' // Step GUID (optional)
37
+ );
38
+
39
+ operation.addStep(
40
+ function (fStepComplete) {
41
+ this.log.info('Processing records...');
42
+ // ... processing logic ...
43
+ fStepComplete();
44
+ },
45
+ {},
46
+ 'Process',
47
+ 'Process all records',
48
+ 'PROCESS-STEP'
49
+ );
50
+ ```
51
+
52
+ ### Execute the Operation
53
+
54
+ Steps execute sequentially in the order they were added:
55
+
56
+ ```javascript
57
+ operation.execute((pError) => {
58
+ if (pError) {
59
+ fable.log.error('Operation failed', { error: pError.message });
60
+ } else {
61
+ fable.log.info('Operation completed successfully');
62
+ }
63
+ });
64
+ ```
65
+
66
+ ## Step Context
67
+
68
+ Inside a step function, `this` is bound to a context object with these properties:
69
+
70
+ | Property | Description |
71
+ |----------|-------------|
72
+ | `this.log` | Logger (writes to both fable.log and operation state log) |
73
+ | `this.fable` | Reference to the Fable instance |
74
+ | `this.options` | Step metadata object (same as `this.metadata`) |
75
+ | `this.metadata` | Step metadata object |
76
+ | `this.ProgressTracker` | Progress tracker for this step |
77
+ | `this.logProgressTrackerStatus()` | Log the current progress status |
78
+ | `this.OperationState` | The full operation state object |
79
+ | `this.StepState` | This step's state entry |
80
+
81
+ ## Progress Tracking
82
+
83
+ ### Set Total Operations for a Step
84
+
85
+ ```javascript
86
+ operation.addStep(
87
+ function (fStepComplete) {
88
+ // ... step work ...
89
+ fStepComplete();
90
+ },
91
+ {}, 'Process Records', 'Process all records', 'PROCESS-STEP'
92
+ );
93
+
94
+ // Set expected total operations for the step
95
+ operation.setStepTotalOperations('PROCESS-STEP', 100);
96
+ ```
97
+
98
+ ### Increment Progress Within a Step
99
+
100
+ ```javascript
101
+ operation.addStep(
102
+ function (fStepComplete) {
103
+ this.ProgressTracker.setProgressTrackerTotalOperations(items.length);
104
+
105
+ let tmpAnticipate = this.fable.newAnticipate();
106
+
107
+ for (let i = 0; i < items.length; i++) {
108
+ tmpAnticipate.anticipate((fWorkComplete) => {
109
+ processItem(items[i]);
110
+ this.ProgressTracker.incrementProgressTracker(1);
111
+ this.logProgressTrackerStatus();
112
+ fWorkComplete();
113
+ });
114
+ }
115
+
116
+ tmpAnticipate.wait(fStepComplete);
117
+ },
118
+ {}, 'Process Items', 'Process each item with tracking', 'ITEMS-STEP'
119
+ );
120
+ ```
121
+
122
+ ## Operation State and Logging
123
+
124
+ The operation maintains a structured state object:
125
+
126
+ ```javascript
127
+ operation.state.Metadata.UUID // Unique identifier
128
+ operation.state.Metadata.Name // Operation name
129
+ operation.state.Status.StepCount // Number of registered steps
130
+ operation.state.Steps // Array of step state entries
131
+ operation.state.Log // Array of log strings
132
+ operation.state.Errors // Array of error strings
133
+ ```
134
+
135
+ ### Built-in Log Methods
136
+
137
+ The operation provides logging methods that write to both fable.log and the operation's internal log:
138
+
139
+ ```javascript
140
+ operation.log.trace('Trace message');
141
+ operation.log.debug('Debug message');
142
+ operation.log.info('Info message');
143
+ operation.log.warn('Warning message');
144
+ operation.log.error('Error message'); // Also writes to state.Errors
145
+ operation.log.fatal('Fatal message'); // Also writes to state.Errors
146
+ ```
147
+
148
+ ### Logging with Data
149
+
150
+ ```javascript
151
+ operation.log.debug('Processing', { TestData: 'Ignition Complete' });
152
+ // Appends JSON stringified data to the log
153
+ ```
154
+
155
+ ## Use Cases
156
+
157
+ ### Multi-Step Async Workflow
158
+
159
+ ```javascript
160
+ function createImportOperation(fable, records) {
161
+ const operation = fable.instantiateServiceProvider('Operation', {
162
+ Name: 'Record Import'
163
+ });
164
+
165
+ operation.addStep(
166
+ function (fStepComplete) {
167
+ this.log.info(`Importing ${records.length} records...`);
168
+
169
+ this.ProgressTracker.setProgressTrackerTotalOperations(records.length);
170
+
171
+ let tmpAnticipate = this.fable.newAnticipate();
172
+
173
+ for (let i = 0; i < records.length; i++) {
174
+ tmpAnticipate.anticipate((fWorkComplete) => {
175
+ importRecord(records[i], () => {
176
+ this.ProgressTracker.incrementProgressTracker(1);
177
+ this.logProgressTrackerStatus();
178
+ fWorkComplete();
179
+ });
180
+ });
181
+ }
182
+
183
+ tmpAnticipate.wait(fStepComplete);
184
+ },
185
+ {}, 'Import', 'Import all records', 'IMPORT'
186
+ );
187
+
188
+ operation.addStep(
189
+ function (fStepComplete) {
190
+ this.log.info('Finalizing...');
191
+ finalizeImport(fStepComplete);
192
+ },
193
+ {}, 'Finalize', 'Finalize the import'
194
+ );
195
+
196
+ return operation;
197
+ }
198
+ ```
199
+
200
+ ## Notes
201
+
202
+ - Steps execute sequentially in the order they were added
203
+ - An operation can only be executed once; calling `execute()` again returns an error
204
+ - Step functions are bound to a custom context (not the operation itself)
205
+ - The service type is `'PhasedOperation'`
206
+ - Each step gets its own progress tracker automatically
207
+ - The operation tracks an overall progress tracker across all steps