fable 3.1.68 → 3.1.69
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/.claude/settings.local.json +8 -0
- package/docs/_version.json +7 -0
- package/docs/architecture.md +5 -5
- package/docs/css/docuserve.css +277 -23
- package/docs/index.html +2 -2
- package/docs/retold-catalog.json +19 -18
- package/docs/retold-keyword-index.json +14382 -12235
- package/docs/services/anticipate.md +2 -2
- package/docs/services/csv-parser.md +1 -1
- package/docs/services/environment-data.md +2 -2
- package/docs/services/expression-parser-functions/matrixinverse.md +1 -1
- package/docs/services/expression-parser-functions/tofixed.md +1 -1
- package/docs/services/expression-parser.md +22 -22
- package/docs/services/object-cache.md +3 -3
- package/package.json +3 -2
- package/source/services/Fable-Service-RestClient.js +40 -0
- package/test/RestClient_test.js +130 -0
|
@@ -109,7 +109,7 @@ tmpAnticipate.anticipate(function (fCallback)
|
|
|
109
109
|
```
|
|
110
110
|
|
|
111
111
|
The step function receives one parameter:
|
|
112
|
-
- `fCallback`
|
|
112
|
+
- `fCallback` -- Callback to signal completion. Pass an error to bail out: `fCallback(new Error('something failed'))`
|
|
113
113
|
|
|
114
114
|
### wait(fCallback)
|
|
115
115
|
|
|
@@ -185,7 +185,7 @@ tmpAnticipate.anticipate(function (fCallback)
|
|
|
185
185
|
catch (pError)
|
|
186
186
|
{
|
|
187
187
|
tmpRecoveredError = pError;
|
|
188
|
-
fCallback(); // No error passed
|
|
188
|
+
fCallback(); // No error passed -- pipeline continues
|
|
189
189
|
}
|
|
190
190
|
});
|
|
191
191
|
tmpAnticipate.anticipate(function (fCallback)
|
|
@@ -149,4 +149,4 @@ If a row has more columns than the header, extra columns are keyed by their nume
|
|
|
149
149
|
- All values are returned as strings
|
|
150
150
|
- The parser is designed for streaming/line-by-line use, not whole-file-at-once parsing
|
|
151
151
|
- Carriage returns (`\r`) are automatically stripped from fields
|
|
152
|
-
- The parser is stateful
|
|
152
|
+
- The parser is stateful -- create a new instance for each file you parse
|
|
@@ -23,8 +23,8 @@ fable.EnvironmentData.Environment
|
|
|
23
23
|
|
|
24
24
|
Fable automatically uses the appropriate implementation:
|
|
25
25
|
|
|
26
|
-
- **Node.js**: `Fable-Service-EnvironmentData.js`
|
|
27
|
-
- **Browser**: `Fable-Service-EnvironmentData-Web.js`
|
|
26
|
+
- **Node.js**: `Fable-Service-EnvironmentData.js` -- sets `Environment` to `'node.js'`
|
|
27
|
+
- **Browser**: `Fable-Service-EnvironmentData-Web.js` -- sets `Environment` to `'web'`
|
|
28
28
|
|
|
29
29
|
## Use Cases
|
|
30
30
|
|
|
@@ -70,6 +70,6 @@ FormattedPercent = TOFIXED(PERCENT(Completed, Total) * 100, 1)
|
|
|
70
70
|
## Notes
|
|
71
71
|
|
|
72
72
|
- Always returns specified number of decimal places
|
|
73
|
-
- Pads with zeros if necessary (19.9
|
|
73
|
+
- Pads with zeros if necessary (19.9 -> "19.90")
|
|
74
74
|
- Uses the Math service's `toFixedPrecise` method
|
|
75
75
|
- Returns result as string
|
|
@@ -28,11 +28,11 @@ parser.solve('2 ^ 10'); // Returns '1024'
|
|
|
28
28
|
parser.solve(expression, dataObject, resultObject, manifest, destinationObject);
|
|
29
29
|
```
|
|
30
30
|
|
|
31
|
-
- **expression**
|
|
32
|
-
- **dataObject**
|
|
33
|
-
- **resultObject**
|
|
34
|
-
- **manifest**
|
|
35
|
-
- **destinationObject**
|
|
31
|
+
- **expression** -- the expression string to evaluate
|
|
32
|
+
- **dataObject** -- object containing variable values (optional)
|
|
33
|
+
- **resultObject** -- object to store solver metadata/logs (optional)
|
|
34
|
+
- **manifest** -- a Manyfest instance for address resolution, or `false` (optional)
|
|
35
|
+
- **destinationObject** -- object where named results are written (optional)
|
|
36
36
|
|
|
37
37
|
### Assigning Results
|
|
38
38
|
|
|
@@ -59,12 +59,12 @@ parser.solve('Name ?= GETVALUE("AppData.Students[0]")', data, {}, false, dest);
|
|
|
59
59
|
|
|
60
60
|
| Operator | Description | Example |
|
|
61
61
|
|----------|-------------|---------|
|
|
62
|
-
| `+` | Addition | `5 + 3`
|
|
63
|
-
| `-` | Subtraction | `5 - 3`
|
|
64
|
-
| `*` | Multiplication | `5 * 3`
|
|
65
|
-
| `/` | Division | `15 / 3`
|
|
66
|
-
| `^` | Power | `2 ^ 3`
|
|
67
|
-
| `%` | Modulus | `10 % 3`
|
|
62
|
+
| `+` | Addition | `5 + 3` -> `'8'` |
|
|
63
|
+
| `-` | Subtraction | `5 - 3` -> `'2'` |
|
|
64
|
+
| `*` | Multiplication | `5 * 3` -> `'15'` |
|
|
65
|
+
| `/` | Division | `15 / 3` -> `'5'` |
|
|
66
|
+
| `^` | Power | `2 ^ 3` -> `'8'` |
|
|
67
|
+
| `%` | Modulus | `10 % 3` -> `'1'` |
|
|
68
68
|
|
|
69
69
|
### Comparison Operators
|
|
70
70
|
|
|
@@ -72,12 +72,12 @@ Comparison operators evaluate to `'1'` (true) or `'0'` (false). They bind looser
|
|
|
72
72
|
|
|
73
73
|
| Operator | Description | Example |
|
|
74
74
|
|----------|-------------|---------|
|
|
75
|
-
| `>` | Greater than | `5 > 3`
|
|
76
|
-
| `>=` | Greater than or equal | `5 >= 5`
|
|
77
|
-
| `<` | Less than | `3 < 5`
|
|
78
|
-
| `<=` | Less than or equal | `3 <= 3`
|
|
79
|
-
| `==` | Equal | `5 == 5`
|
|
80
|
-
| `!=` | Not equal | `5 != 3`
|
|
75
|
+
| `>` | Greater than | `5 > 3` -> `'1'` |
|
|
76
|
+
| `>=` | Greater than or equal | `5 >= 5` -> `'1'` |
|
|
77
|
+
| `<` | Less than | `3 < 5` -> `'1'` |
|
|
78
|
+
| `<=` | Less than or equal | `3 <= 3` -> `'1'` |
|
|
79
|
+
| `==` | Equal | `5 == 5` -> `'1'` |
|
|
80
|
+
| `!=` | Not equal | `5 != 3` -> `'1'` |
|
|
81
81
|
|
|
82
82
|
```javascript
|
|
83
83
|
parser.solve('Result = 5 > 3', {}, {}, false, dest);
|
|
@@ -386,14 +386,14 @@ parser.solve('Sampled = MULTIROWMAP ROWS FROM Rows SERIESSTEP 2 VAR v FROM Value
|
|
|
386
386
|
```
|
|
387
387
|
|
|
388
388
|
**Available variables in MULTIROWMAP expressions:**
|
|
389
|
-
- `stepIndex`
|
|
390
|
-
- `rowIndex`
|
|
389
|
+
- `stepIndex` -- iteration counter (0, 1, 2, ...)
|
|
390
|
+
- `rowIndex` -- actual array index of the current row
|
|
391
391
|
- Any VAR-mapped variable names
|
|
392
392
|
|
|
393
393
|
**OFFSET values:**
|
|
394
|
-
- `0` (default)
|
|
395
|
-
- `-1`
|
|
396
|
-
- `1`
|
|
394
|
+
- `0` (default) -- current row
|
|
395
|
+
- `-1` -- previous row, `-2` -- two rows back, `-3` -- three back, etc.
|
|
396
|
+
- `1` -- next row, `2` -- two ahead, etc.
|
|
397
397
|
|
|
398
398
|
### ITERATIVESERIES
|
|
399
399
|
|
|
@@ -138,9 +138,9 @@ Get the full linked list node (including metadata) for a hash:
|
|
|
138
138
|
|
|
139
139
|
```javascript
|
|
140
140
|
const node = cache.getNode('my-key');
|
|
141
|
-
// node.Datum
|
|
142
|
-
// node.Hash
|
|
143
|
-
// node.Metadata.Created
|
|
141
|
+
// node.Datum -- the stored data
|
|
142
|
+
// node.Hash -- the hash key
|
|
143
|
+
// node.Metadata.Created -- timestamp (ms) when the entry was created
|
|
144
144
|
```
|
|
145
145
|
|
|
146
146
|
### RecordMap
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "fable",
|
|
3
|
-
"version": "3.1.
|
|
3
|
+
"version": "3.1.69",
|
|
4
4
|
"description": "A service dependency injection, configuration and logging library.",
|
|
5
5
|
"main": "source/Fable.js",
|
|
6
6
|
"scripts": {
|
|
@@ -51,7 +51,8 @@
|
|
|
51
51
|
},
|
|
52
52
|
"homepage": "https://github.com/stevenvelozo/fable",
|
|
53
53
|
"devDependencies": {
|
|
54
|
-
"
|
|
54
|
+
"pict-docuserve": "^0.1.5",
|
|
55
|
+
"quackage": "^1.1.0"
|
|
55
56
|
},
|
|
56
57
|
"dependencies": {
|
|
57
58
|
"async.eachlimit": "^0.5.2",
|
|
@@ -2,6 +2,8 @@ const libFableServiceBase = require('fable-serviceproviderbase');
|
|
|
2
2
|
|
|
3
3
|
const libSimpleGet = require('simple-get');
|
|
4
4
|
const libCookie = require('cookie');
|
|
5
|
+
const libHttp = require('http');
|
|
6
|
+
const libHttps = require('https');
|
|
5
7
|
|
|
6
8
|
class FableServiceRestClient extends libFableServiceBase
|
|
7
9
|
{
|
|
@@ -24,6 +26,44 @@ class FableServiceRestClient extends libFableServiceBase
|
|
|
24
26
|
// This is a function that can be overridden, to allow the management
|
|
25
27
|
// of the request options before they are passed to the request library.
|
|
26
28
|
this.prepareRequestOptions = (pOptions) => { return pOptions; };
|
|
29
|
+
|
|
30
|
+
// Check for keep-alive configuration (from options or fable settings)
|
|
31
|
+
// Useful in environments where connections are slow to establish
|
|
32
|
+
// (e.g. inside poorly configured customer networks / VPNs)
|
|
33
|
+
let tmpKeepAlive = this.options.KeepAlive || this.fable.settings.RestClientKeepAlive;
|
|
34
|
+
if (tmpKeepAlive)
|
|
35
|
+
{
|
|
36
|
+
this.initializeKeepAliveAgent();
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Initialize HTTP keep-alive agents and wire them into prepareRequestOptions.
|
|
42
|
+
* Creates both an HTTP and HTTPS agent so the correct one is selected per-request
|
|
43
|
+
* based on the URL protocol.
|
|
44
|
+
*/
|
|
45
|
+
initializeKeepAliveAgent()
|
|
46
|
+
{
|
|
47
|
+
let tmpAgentOptions = { keepAlive: true };
|
|
48
|
+
|
|
49
|
+
this.httpAgent = new libHttp.Agent(tmpAgentOptions);
|
|
50
|
+
this.httpsAgent = new libHttps.Agent(tmpAgentOptions);
|
|
51
|
+
|
|
52
|
+
// Capture any previously set prepareRequestOptions so we can chain
|
|
53
|
+
let tmpPreviousPrepareRequestOptions = this.prepareRequestOptions;
|
|
54
|
+
|
|
55
|
+
this.prepareRequestOptions = (pOptions) =>
|
|
56
|
+
{
|
|
57
|
+
if (typeof pOptions.url === 'string' && pOptions.url.startsWith('http:'))
|
|
58
|
+
{
|
|
59
|
+
pOptions.agent = this.httpAgent;
|
|
60
|
+
}
|
|
61
|
+
else
|
|
62
|
+
{
|
|
63
|
+
pOptions.agent = this.httpsAgent;
|
|
64
|
+
}
|
|
65
|
+
return tmpPreviousPrepareRequestOptions(pOptions);
|
|
66
|
+
};
|
|
27
67
|
}
|
|
28
68
|
|
|
29
69
|
get simpleGet()
|
package/test/RestClient_test.js
CHANGED
|
@@ -146,6 +146,136 @@ suite
|
|
|
146
146
|
}
|
|
147
147
|
);
|
|
148
148
|
|
|
149
|
+
suite
|
|
150
|
+
(
|
|
151
|
+
'KeepAlive Agent',
|
|
152
|
+
function ()
|
|
153
|
+
{
|
|
154
|
+
test
|
|
155
|
+
(
|
|
156
|
+
'Initialize keep-alive agents via options.',
|
|
157
|
+
function ()
|
|
158
|
+
{
|
|
159
|
+
let testFable = new libFable();
|
|
160
|
+
let tmpRestClient = testFable.instantiateServiceProvider('RestClient', { KeepAlive: true }, 'RestClient-KeepAlive-Options');
|
|
161
|
+
|
|
162
|
+
Expect(tmpRestClient.httpAgent).to.be.an('object');
|
|
163
|
+
Expect(tmpRestClient.httpsAgent).to.be.an('object');
|
|
164
|
+
Expect(tmpRestClient.httpAgent.keepAlive).to.equal(true);
|
|
165
|
+
Expect(tmpRestClient.httpsAgent.keepAlive).to.equal(true);
|
|
166
|
+
}
|
|
167
|
+
);
|
|
168
|
+
test
|
|
169
|
+
(
|
|
170
|
+
'Initialize keep-alive agents via fable settings.',
|
|
171
|
+
function ()
|
|
172
|
+
{
|
|
173
|
+
let testFable = new libFable({ RestClientKeepAlive: true });
|
|
174
|
+
let tmpRestClient = testFable.instantiateServiceProvider('RestClient', {}, 'RestClient-KeepAlive-Settings');
|
|
175
|
+
|
|
176
|
+
Expect(tmpRestClient.httpAgent).to.be.an('object');
|
|
177
|
+
Expect(tmpRestClient.httpsAgent).to.be.an('object');
|
|
178
|
+
Expect(tmpRestClient.httpAgent.keepAlive).to.equal(true);
|
|
179
|
+
Expect(tmpRestClient.httpsAgent.keepAlive).to.equal(true);
|
|
180
|
+
}
|
|
181
|
+
);
|
|
182
|
+
test
|
|
183
|
+
(
|
|
184
|
+
'Do not create agents when KeepAlive is not set.',
|
|
185
|
+
function ()
|
|
186
|
+
{
|
|
187
|
+
let testFable = new libFable();
|
|
188
|
+
let tmpRestClient = testFable.instantiateServiceProvider('RestClient', {}, 'RestClient-NoKeepAlive');
|
|
189
|
+
|
|
190
|
+
Expect(tmpRestClient.httpAgent).to.equal(undefined);
|
|
191
|
+
Expect(tmpRestClient.httpsAgent).to.equal(undefined);
|
|
192
|
+
}
|
|
193
|
+
);
|
|
194
|
+
test
|
|
195
|
+
(
|
|
196
|
+
'Inject HTTP agent into request options for http URLs.',
|
|
197
|
+
function (fTestComplete)
|
|
198
|
+
{
|
|
199
|
+
var tmpServer = libHTTP.createServer(function (pReq, pRes)
|
|
200
|
+
{
|
|
201
|
+
pRes.writeHead(200, { 'Content-Type': 'application/json' });
|
|
202
|
+
pRes.end(JSON.stringify({ OK: true }));
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
tmpServer.listen(0, function ()
|
|
206
|
+
{
|
|
207
|
+
var tmpPort = tmpServer.address().port;
|
|
208
|
+
var testFable = new libFable();
|
|
209
|
+
var tmpRestClient = testFable.instantiateServiceProvider('RestClient', { KeepAlive: true }, 'RestClient-KeepAlive-HTTP');
|
|
210
|
+
|
|
211
|
+
// Wrap prepareRequestOptions to capture the final options
|
|
212
|
+
var tmpOriginalPrepare = tmpRestClient.prepareRequestOptions;
|
|
213
|
+
var tmpCapturedAgent = null;
|
|
214
|
+
tmpRestClient.prepareRequestOptions = function (pOptions)
|
|
215
|
+
{
|
|
216
|
+
let tmpResult = tmpOriginalPrepare(pOptions);
|
|
217
|
+
tmpCapturedAgent = tmpResult.agent;
|
|
218
|
+
return tmpResult;
|
|
219
|
+
};
|
|
220
|
+
|
|
221
|
+
tmpRestClient.getJSON('http://localhost:' + tmpPort + '/test',
|
|
222
|
+
function (pError, pResponse, pBody)
|
|
223
|
+
{
|
|
224
|
+
Expect(tmpCapturedAgent).to.equal(tmpRestClient.httpAgent);
|
|
225
|
+
Expect(pBody).to.be.an('object');
|
|
226
|
+
Expect(pBody.OK).to.equal(true);
|
|
227
|
+
tmpServer.close();
|
|
228
|
+
fTestComplete();
|
|
229
|
+
});
|
|
230
|
+
});
|
|
231
|
+
}
|
|
232
|
+
);
|
|
233
|
+
test
|
|
234
|
+
(
|
|
235
|
+
'Chain with previously set prepareRequestOptions.',
|
|
236
|
+
function (fTestComplete)
|
|
237
|
+
{
|
|
238
|
+
var tmpServer = libHTTP.createServer(function (pReq, pRes)
|
|
239
|
+
{
|
|
240
|
+
pRes.writeHead(200, { 'Content-Type': 'application/json' });
|
|
241
|
+
pRes.end(JSON.stringify({ CustomHeader: pReq.headers['x-custom'] || '' }));
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
tmpServer.listen(0, function ()
|
|
245
|
+
{
|
|
246
|
+
var tmpPort = tmpServer.address().port;
|
|
247
|
+
var testFable = new libFable();
|
|
248
|
+
var tmpRestClient = testFable.instantiateServiceProvider('RestClient', { KeepAlive: true }, 'RestClient-KeepAlive-Chain');
|
|
249
|
+
|
|
250
|
+
// Set a custom prepareRequestOptions AFTER keep-alive init to verify chaining
|
|
251
|
+
var tmpKeepAlivePrepare = tmpRestClient.prepareRequestOptions;
|
|
252
|
+
tmpRestClient.prepareRequestOptions = function (pOptions)
|
|
253
|
+
{
|
|
254
|
+
// Apply keep-alive first
|
|
255
|
+
let tmpResult = tmpKeepAlivePrepare(pOptions);
|
|
256
|
+
// Then add custom header
|
|
257
|
+
if (!tmpResult.headers)
|
|
258
|
+
{
|
|
259
|
+
tmpResult.headers = {};
|
|
260
|
+
}
|
|
261
|
+
tmpResult.headers['X-Custom'] = 'test-value';
|
|
262
|
+
return tmpResult;
|
|
263
|
+
};
|
|
264
|
+
|
|
265
|
+
tmpRestClient.getJSON('http://localhost:' + tmpPort + '/test',
|
|
266
|
+
function (pError, pResponse, pBody)
|
|
267
|
+
{
|
|
268
|
+
Expect(pBody).to.be.an('object');
|
|
269
|
+
Expect(pBody.CustomHeader).to.equal('test-value');
|
|
270
|
+
tmpServer.close();
|
|
271
|
+
fTestComplete();
|
|
272
|
+
});
|
|
273
|
+
});
|
|
274
|
+
}
|
|
275
|
+
);
|
|
276
|
+
}
|
|
277
|
+
);
|
|
278
|
+
|
|
149
279
|
suite
|
|
150
280
|
(
|
|
151
281
|
'Error Handling',
|