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.
- package/README.md +87 -2
- package/dist/fable.js +74 -46
- package/dist/fable.js.map +1 -1
- package/dist/fable.min.js +2 -2
- package/dist/fable.min.js.map +1 -1
- package/docs/.nojekyll +0 -0
- package/docs/README.md +95 -0
- package/docs/_sidebar.md +42 -0
- package/docs/architecture.md +326 -0
- package/docs/cover.md +11 -0
- package/docs/index.html +51 -0
- package/docs/services/README.md +76 -0
- package/docs/services/anticipate.md +331 -0
- package/docs/services/csv-parser.md +152 -0
- package/docs/services/data-format.md +277 -0
- package/docs/services/data-generation.md +142 -0
- package/docs/services/dates.md +216 -0
- package/docs/services/environment-data.md +44 -0
- package/docs/services/expression-parser-functions/README.md +178 -0
- package/docs/services/expression-parser-functions/abs.md +84 -0
- package/docs/services/expression-parser-functions/aggregationhistogram.md +83 -0
- package/docs/services/expression-parser-functions/aggregationhistogrambyobject.md +64 -0
- package/docs/services/expression-parser-functions/arrayconcat.md +64 -0
- package/docs/services/expression-parser-functions/avg.md +81 -0
- package/docs/services/expression-parser-functions/bucketset.md +69 -0
- package/docs/services/expression-parser-functions/ceil.md +70 -0
- package/docs/services/expression-parser-functions/cleanvaluearray.md +66 -0
- package/docs/services/expression-parser-functions/cleanvalueobject.md +68 -0
- package/docs/services/expression-parser-functions/compare.md +72 -0
- package/docs/services/expression-parser-functions/concat.md +73 -0
- package/docs/services/expression-parser-functions/concatraw.md +73 -0
- package/docs/services/expression-parser-functions/cos.md +75 -0
- package/docs/services/expression-parser-functions/count.md +73 -0
- package/docs/services/expression-parser-functions/countset.md +65 -0
- package/docs/services/expression-parser-functions/countsetelements.md +63 -0
- package/docs/services/expression-parser-functions/createarrayfromabsolutevalues.md +63 -0
- package/docs/services/expression-parser-functions/createvalueobjectbyhashes.md +69 -0
- package/docs/services/expression-parser-functions/cumulativesummation.md +96 -0
- package/docs/services/expression-parser-functions/dateadddays.md +79 -0
- package/docs/services/expression-parser-functions/dateaddhours.md +74 -0
- package/docs/services/expression-parser-functions/dateaddmilliseconds.md +65 -0
- package/docs/services/expression-parser-functions/dateaddminutes.md +72 -0
- package/docs/services/expression-parser-functions/dateaddmonths.md +74 -0
- package/docs/services/expression-parser-functions/dateaddseconds.md +66 -0
- package/docs/services/expression-parser-functions/dateaddweeks.md +73 -0
- package/docs/services/expression-parser-functions/dateaddyears.md +74 -0
- package/docs/services/expression-parser-functions/datedaydifference.md +84 -0
- package/docs/services/expression-parser-functions/datefromparts.md +81 -0
- package/docs/services/expression-parser-functions/datehourdifference.md +64 -0
- package/docs/services/expression-parser-functions/datemathadd.md +72 -0
- package/docs/services/expression-parser-functions/datemilliseconddifference.md +64 -0
- package/docs/services/expression-parser-functions/dateminutedifference.md +64 -0
- package/docs/services/expression-parser-functions/datemonthdifference.md +66 -0
- package/docs/services/expression-parser-functions/dateseconddifference.md +64 -0
- package/docs/services/expression-parser-functions/dateweekdifference.md +65 -0
- package/docs/services/expression-parser-functions/dateyeardifference.md +64 -0
- package/docs/services/expression-parser-functions/distributionhistogram.md +96 -0
- package/docs/services/expression-parser-functions/distributionhistogrambyobject.md +64 -0
- package/docs/services/expression-parser-functions/entryinset.md +72 -0
- package/docs/services/expression-parser-functions/euler.md +77 -0
- package/docs/services/expression-parser-functions/exp.md +74 -0
- package/docs/services/expression-parser-functions/findfirstvaluebyexactmatch.md +67 -0
- package/docs/services/expression-parser-functions/findfirstvaluebystringincludes.md +67 -0
- package/docs/services/expression-parser-functions/flatten.md +76 -0
- package/docs/services/expression-parser-functions/floor.md +70 -0
- package/docs/services/expression-parser-functions/gaussianelimination.md +75 -0
- package/docs/services/expression-parser-functions/generatearrayofobjectsfromsets.md +70 -0
- package/docs/services/expression-parser-functions/getvalue.md +90 -0
- package/docs/services/expression-parser-functions/getvaluearray.md +64 -0
- package/docs/services/expression-parser-functions/getvalueobject.md +67 -0
- package/docs/services/expression-parser-functions/if.md +109 -0
- package/docs/services/expression-parser-functions/iterativeseries.md +107 -0
- package/docs/services/expression-parser-functions/join.md +75 -0
- package/docs/services/expression-parser-functions/joinraw.md +64 -0
- package/docs/services/expression-parser-functions/largestinset.md +63 -0
- package/docs/services/expression-parser-functions/leastsquares.md +66 -0
- package/docs/services/expression-parser-functions/linest.md +58 -0
- package/docs/services/expression-parser-functions/log.md +74 -0
- package/docs/services/expression-parser-functions/match.md +71 -0
- package/docs/services/expression-parser-functions/matrixinverse.md +67 -0
- package/docs/services/expression-parser-functions/matrixmultiply.md +71 -0
- package/docs/services/expression-parser-functions/matrixtranspose.md +72 -0
- package/docs/services/expression-parser-functions/matrixvectormultiply.md +69 -0
- package/docs/services/expression-parser-functions/max.md +73 -0
- package/docs/services/expression-parser-functions/mean.md +63 -0
- package/docs/services/expression-parser-functions/median.md +79 -0
- package/docs/services/expression-parser-functions/min.md +73 -0
- package/docs/services/expression-parser-functions/mode.md +66 -0
- package/docs/services/expression-parser-functions/objectkeystoarray.md +66 -0
- package/docs/services/expression-parser-functions/objectvaluessortbyexternalobjectarray.md +65 -0
- package/docs/services/expression-parser-functions/objectvaluestoarray.md +67 -0
- package/docs/services/expression-parser-functions/percent.md +75 -0
- package/docs/services/expression-parser-functions/pi.md +77 -0
- package/docs/services/expression-parser-functions/polynomialregression.md +69 -0
- package/docs/services/expression-parser-functions/predict.md +71 -0
- package/docs/services/expression-parser-functions/rad.md +85 -0
- package/docs/services/expression-parser-functions/randomfloat.md +63 -0
- package/docs/services/expression-parser-functions/randomfloatbetween.md +72 -0
- package/docs/services/expression-parser-functions/randomfloatupto.md +65 -0
- package/docs/services/expression-parser-functions/randominteger.md +56 -0
- package/docs/services/expression-parser-functions/randomintegerbetween.md +72 -0
- package/docs/services/expression-parser-functions/randomintegerupto.md +64 -0
- package/docs/services/expression-parser-functions/resolvehtmlentities.md +64 -0
- package/docs/services/expression-parser-functions/round.md +111 -0
- package/docs/services/expression-parser-functions/setconcatenate.md +64 -0
- package/docs/services/expression-parser-functions/sin.md +83 -0
- package/docs/services/expression-parser-functions/slice.md +80 -0
- package/docs/services/expression-parser-functions/smallestinset.md +63 -0
- package/docs/services/expression-parser-functions/sorthistogram.md +70 -0
- package/docs/services/expression-parser-functions/sorthistogrambykeys.md +69 -0
- package/docs/services/expression-parser-functions/sortset.md +75 -0
- package/docs/services/expression-parser-functions/sqrt.md +85 -0
- package/docs/services/expression-parser-functions/stdev.md +81 -0
- package/docs/services/expression-parser-functions/stdeva.md +58 -0
- package/docs/services/expression-parser-functions/stdevp.md +83 -0
- package/docs/services/expression-parser-functions/stringcountsegments.md +66 -0
- package/docs/services/expression-parser-functions/stringgetsegments.md +74 -0
- package/docs/services/expression-parser-functions/subtractingsummation.md +66 -0
- package/docs/services/expression-parser-functions/sum.md +78 -0
- package/docs/services/expression-parser-functions/tan.md +78 -0
- package/docs/services/expression-parser-functions/tofixed.md +75 -0
- package/docs/services/expression-parser-functions/var.md +67 -0
- package/docs/services/expression-parser-functions/vara.md +58 -0
- package/docs/services/expression-parser-functions/varp.md +66 -0
- package/docs/services/expression-parser-functions/when.md +98 -0
- package/docs/services/expression-parser.md +314 -0
- package/docs/services/file-persistence.md +279 -0
- package/docs/services/logging.md +237 -0
- package/docs/services/logic.md +166 -0
- package/docs/services/manifest.md +256 -0
- package/docs/services/math.md +279 -0
- package/docs/services/meta-template.md +268 -0
- package/docs/services/object-cache.md +171 -0
- package/docs/services/operation.md +207 -0
- package/docs/services/progress-time.md +167 -0
- package/docs/services/progress-tracker-set.md +222 -0
- package/docs/services/rest-client.md +296 -0
- package/docs/services/settings-manager.md +265 -0
- package/docs/services/template.md +233 -0
- package/docs/services/utility.md +304 -0
- package/docs/services/uuid.md +162 -0
- package/package.json +1 -1
- package/source/services/Fable-Service-DataFormat.js +1 -0
- package/source/services/Fable-Service-ExpressionParser/Fable-Service-ExpressionParser-FunctionMap.json +24 -0
- package/source/services/Fable-Service-ExpressionParser/Fable-Service-ExpressionParser-ValueMarshal.js +6 -4
- package/source/services/Fable-Service-Math.js +65 -3
- package/test/ExpressionParser_tests.js +5 -0
- 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>© 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
|