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.
@@ -109,7 +109,7 @@ tmpAnticipate.anticipate(function (fCallback)
109
109
  ```
110
110
 
111
111
  The step function receives one parameter:
112
- - `fCallback` Callback to signal completion. Pass an error to bail out: `fCallback(new Error('something failed'))`
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 pipeline continues
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 create a new instance for each file you parse
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` sets `Environment` to `'node.js'`
27
- - **Browser**: `Fable-Service-EnvironmentData-Web.js` sets `Environment` to `'web'`
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
 
@@ -42,7 +42,7 @@ Inverse = MATRIXINVERSE(Matrix3x3)
42
42
  ### Solving Equations
43
43
 
44
44
  ```expression
45
- // Solve Ax = b x = A⁻¹b
45
+ // Solve Ax = b -> x = A⁻¹b
46
46
  Solution = MATRIXVECTORMULTIPLY(MATRIXINVERSE(A), B)
47
47
  ```
48
48
 
@@ -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 "19.90")
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** 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)
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` `'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'` |
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` `'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'` |
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` iteration counter (0, 1, 2, ...)
390
- - `rowIndex` actual array index of the current row
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) current row
395
- - `-1` previous row, `-2` two rows back, `-3` three back, etc.
396
- - `1` next row, `2` two ahead, etc.
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 the stored data
142
- // node.Hash the hash key
143
- // node.Metadata.Created timestamp (ms) when the entry was 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.68",
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
- "quackage": "^1.0.68"
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()
@@ -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',