fable 3.1.64 → 3.1.66
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/docs/services/expression-parser-functions/README.md +9 -0
- package/docs/services/expression-parser-functions/beziercurvefit.md +57 -0
- package/docs/services/expression-parser-functions/bezierpoint.md +55 -0
- package/docs/services/expression-parser-functions/intercept.md +59 -0
- package/docs/services/expression-parser-functions/setvalue.md +55 -0
- package/docs/services/expression-parser-functions/slope.md +51 -0
- package/docs/services/expression-parser-functions/ternary.md +180 -0
- package/docs/services/expression-parser.md +72 -0
- package/example_applications/mathematical_playground/Math-Solver-Harness.js +108 -0
- package/example_applications/mathematical_playground/TestSuite-AcidTest.json +178 -0
- package/example_applications/mathematical_playground/TestSuite-Identities.json +147 -0
- package/example_applications/mathematical_playground/TestSuite-Precision.json +127 -0
- package/example_applications/mathematical_playground/TestSuite-Spreadsheet.json +198 -0
- package/package.json +3 -3
- package/source/services/Fable-Service-ExpressionParser/Fable-Service-ExpressionParser-ExpressionTokenizer-DirectiveMutation.js +143 -0
- package/source/services/Fable-Service-ExpressionParser/Fable-Service-ExpressionParser-ExpressionTokenizer.js +1 -1
- package/source/services/Fable-Service-ExpressionParser/Fable-Service-ExpressionParser-FunctionMap.json +5 -4
- package/source/services/Fable-Service-ExpressionParser/Fable-Service-ExpressionParser-TokenMap.json +65 -1
- package/source/services/Fable-Service-ExpressionParser.js +1 -0
- package/source/services/Fable-Service-Logic.js +33 -0
- package/source/services/Fable-Service-Math.js +78 -0
- package/source/services/Fable-Service-RestClient.js +105 -0
- package/test/ExpressionParser_tests.js +290 -0
- package/test/RestClientBinaryUpload_test.js +537 -0
|
@@ -339,10 +339,153 @@ class ExpressionTokenizerDirectiveMutation extends libExpressionParserOperationB
|
|
|
339
339
|
return tmpNewDirectiveDescription;
|
|
340
340
|
}
|
|
341
341
|
|
|
342
|
+
rewriteTernaryOperators(pResultObject)
|
|
343
|
+
{
|
|
344
|
+
let tmpTokens = pResultObject.RawTokens;
|
|
345
|
+
|
|
346
|
+
// Scan right-to-left so nested ternaries (innermost first) are handled correctly.
|
|
347
|
+
for (let i = tmpTokens.length - 1; i >= 0; i--)
|
|
348
|
+
{
|
|
349
|
+
if (tmpTokens[i] !== '?')
|
|
350
|
+
{
|
|
351
|
+
continue;
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
// Found a ? token at index i.
|
|
355
|
+
// Walk left to find the start of the condition expression.
|
|
356
|
+
// The condition starts after the previous comma, open parenthesis, assignment operator, or beginning of tokens.
|
|
357
|
+
let tmpConditionStart = 0;
|
|
358
|
+
let tmpParenDepth = 0;
|
|
359
|
+
for (let j = i - 1; j >= 0; j--)
|
|
360
|
+
{
|
|
361
|
+
if (tmpTokens[j] === ')')
|
|
362
|
+
{
|
|
363
|
+
tmpParenDepth++;
|
|
364
|
+
}
|
|
365
|
+
else if (tmpTokens[j] === '(')
|
|
366
|
+
{
|
|
367
|
+
if (tmpParenDepth > 0)
|
|
368
|
+
{
|
|
369
|
+
tmpParenDepth--;
|
|
370
|
+
}
|
|
371
|
+
else
|
|
372
|
+
{
|
|
373
|
+
// We hit an unmatched open paren — condition starts after it
|
|
374
|
+
tmpConditionStart = j + 1;
|
|
375
|
+
break;
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
else if (tmpParenDepth === 0)
|
|
379
|
+
{
|
|
380
|
+
let tmpTokenDescriptor = this.ExpressionParser.tokenMap[tmpTokens[j]];
|
|
381
|
+
if (tmpTokenDescriptor && (tmpTokenDescriptor.Type === 'Assignment' || tmpTokens[j] === ','))
|
|
382
|
+
{
|
|
383
|
+
tmpConditionStart = j + 1;
|
|
384
|
+
break;
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
// Walk right from ? to find the matching :: at the same parenthesis depth.
|
|
390
|
+
let tmpSeparatorIndex = -1;
|
|
391
|
+
tmpParenDepth = 0;
|
|
392
|
+
for (let j = i + 1; j < tmpTokens.length; j++)
|
|
393
|
+
{
|
|
394
|
+
if (tmpTokens[j] === '(')
|
|
395
|
+
{
|
|
396
|
+
tmpParenDepth++;
|
|
397
|
+
}
|
|
398
|
+
else if (tmpTokens[j] === ')')
|
|
399
|
+
{
|
|
400
|
+
if (tmpParenDepth > 0)
|
|
401
|
+
{
|
|
402
|
+
tmpParenDepth--;
|
|
403
|
+
}
|
|
404
|
+
else
|
|
405
|
+
{
|
|
406
|
+
// Hit closing paren at our depth — no :: found in this group
|
|
407
|
+
break;
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
else if (tmpTokens[j] === '::' && tmpParenDepth === 0)
|
|
411
|
+
{
|
|
412
|
+
tmpSeparatorIndex = j;
|
|
413
|
+
break;
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
if (tmpSeparatorIndex === -1)
|
|
418
|
+
{
|
|
419
|
+
pResultObject.ExpressionParserLog.push(`ExpressionParser.rewriteTernaryOperators found a ? at token index ${i} with no matching :: separator.`);
|
|
420
|
+
this.log.warn(pResultObject.ExpressionParserLog[pResultObject.ExpressionParserLog.length - 1]);
|
|
421
|
+
continue;
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
// Walk right from :: to find the end of the false branch.
|
|
425
|
+
// The false branch ends at the next comma, close parenthesis, or end of tokens at the same depth.
|
|
426
|
+
let tmpFalseBranchEnd = tmpTokens.length;
|
|
427
|
+
tmpParenDepth = 0;
|
|
428
|
+
for (let j = tmpSeparatorIndex + 1; j < tmpTokens.length; j++)
|
|
429
|
+
{
|
|
430
|
+
if (tmpTokens[j] === '(')
|
|
431
|
+
{
|
|
432
|
+
tmpParenDepth++;
|
|
433
|
+
}
|
|
434
|
+
else if (tmpTokens[j] === ')')
|
|
435
|
+
{
|
|
436
|
+
if (tmpParenDepth > 0)
|
|
437
|
+
{
|
|
438
|
+
tmpParenDepth--;
|
|
439
|
+
}
|
|
440
|
+
else
|
|
441
|
+
{
|
|
442
|
+
// Unmatched close paren — false branch ends here
|
|
443
|
+
tmpFalseBranchEnd = j;
|
|
444
|
+
break;
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
else if (tmpParenDepth === 0)
|
|
448
|
+
{
|
|
449
|
+
if (tmpTokens[j] === ',')
|
|
450
|
+
{
|
|
451
|
+
tmpFalseBranchEnd = j;
|
|
452
|
+
break;
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
// Extract the three segments
|
|
458
|
+
let tmpConditionTokens = tmpTokens.slice(tmpConditionStart, i);
|
|
459
|
+
let tmpTrueBranchTokens = tmpTokens.slice(i + 1, tmpSeparatorIndex);
|
|
460
|
+
let tmpFalseBranchTokens = tmpTokens.slice(tmpSeparatorIndex + 1, tmpFalseBranchEnd);
|
|
461
|
+
|
|
462
|
+
// Build the replacement: ternary((condition), trueBranch, falseBranch)
|
|
463
|
+
// The condition is wrapped in its own parentheses to ensure correct
|
|
464
|
+
// precedence grouping when arithmetic appears on both sides of a comparison.
|
|
465
|
+
let tmpReplacementTokens = ['ternary', '(', '('];
|
|
466
|
+
tmpReplacementTokens = tmpReplacementTokens.concat(tmpConditionTokens);
|
|
467
|
+
tmpReplacementTokens.push(')');
|
|
468
|
+
tmpReplacementTokens.push(',');
|
|
469
|
+
tmpReplacementTokens = tmpReplacementTokens.concat(tmpTrueBranchTokens);
|
|
470
|
+
tmpReplacementTokens.push(',');
|
|
471
|
+
tmpReplacementTokens = tmpReplacementTokens.concat(tmpFalseBranchTokens);
|
|
472
|
+
tmpReplacementTokens.push(')');
|
|
473
|
+
|
|
474
|
+
// Splice the replacement into the token array
|
|
475
|
+
tmpTokens.splice(tmpConditionStart, tmpFalseBranchEnd - tmpConditionStart, ...tmpReplacementTokens);
|
|
476
|
+
|
|
477
|
+
// Adjust i to re-scan from the start of what we just inserted (in case of nested ternaries in the condition)
|
|
478
|
+
i = tmpConditionStart;
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
|
|
342
482
|
parseDirectives(pResultObject)
|
|
343
483
|
{
|
|
344
484
|
let tmpResults = (typeof(pResultObject) === 'object') ? pResultObject : { ExpressionParserLog: [] };
|
|
345
485
|
|
|
486
|
+
// Rewrite ternary operators before directive parsing
|
|
487
|
+
this.rewriteTernaryOperators(tmpResults);
|
|
488
|
+
|
|
346
489
|
tmpResults.SolverDirectives = this.defaultDirective;
|
|
347
490
|
tmpResults.SolverDirectiveTokens = [];
|
|
348
491
|
|
|
@@ -215,7 +215,7 @@ class ExpressionTokenizer extends libExpressionParserOperationBase
|
|
|
215
215
|
{
|
|
216
216
|
if (tmpCurrentToken.length > 0)
|
|
217
217
|
{
|
|
218
|
-
tmpResults.RawTokens.push(
|
|
218
|
+
tmpResults.RawTokens.push(tmpCurrentToken);
|
|
219
219
|
}
|
|
220
220
|
tmpCurrentToken = '';
|
|
221
221
|
tmpCurrentTokenType = false;
|
|
@@ -285,6 +285,11 @@
|
|
|
285
285
|
"Address": "fable.Logic.when"
|
|
286
286
|
},
|
|
287
287
|
|
|
288
|
+
"ternary": {
|
|
289
|
+
"Name": "numeric-aware ternary selection (used by ? :: operator desugaring)",
|
|
290
|
+
"Address": "fable.Logic.ternary"
|
|
291
|
+
},
|
|
292
|
+
|
|
288
293
|
"entryinset": {
|
|
289
294
|
"Name": "Entry in Set",
|
|
290
295
|
"Address": "fable.Math.entryInSet"
|
|
@@ -465,10 +470,6 @@
|
|
|
465
470
|
"Address": "fable.Math.interceptPrecise"
|
|
466
471
|
},
|
|
467
472
|
|
|
468
|
-
"polynomialregression": {
|
|
469
|
-
"Name": "Perform an nth degree Polynomial Regression on a Set of X and Y Values",
|
|
470
|
-
"Address": "fable.Math.polynomialRegression"
|
|
471
|
-
},
|
|
472
473
|
"leastsquares": {
|
|
473
474
|
"Name": "Perform a Least Squares Regression on a Set of Independent Variable Vectors and a Dependent Variable Vector",
|
|
474
475
|
"Address": "fable.Math.leastSquares"
|
package/source/services/Fable-Service-ExpressionParser/Fable-Service-ExpressionParser-TokenMap.json
CHANGED
|
@@ -46,7 +46,7 @@
|
|
|
46
46
|
"Name": "Set Concatenate",
|
|
47
47
|
"Token": ",",
|
|
48
48
|
"Function": "fable.Math.setConcatenate",
|
|
49
|
-
"Precedence":
|
|
49
|
+
"Precedence": 6,
|
|
50
50
|
"Type": "Operator"
|
|
51
51
|
},
|
|
52
52
|
|
|
@@ -99,5 +99,69 @@
|
|
|
99
99
|
"Function": "fable.Math.subtractPrecise",
|
|
100
100
|
"Precedence": 4,
|
|
101
101
|
"Type": "Operator"
|
|
102
|
+
},
|
|
103
|
+
|
|
104
|
+
">":
|
|
105
|
+
{
|
|
106
|
+
"Name": "Greater Than",
|
|
107
|
+
"Token": ">",
|
|
108
|
+
"Function": "fable.Math.greaterThanOperator",
|
|
109
|
+
"Precedence": 5,
|
|
110
|
+
"Type": "Operator"
|
|
111
|
+
},
|
|
112
|
+
">=":
|
|
113
|
+
{
|
|
114
|
+
"Name": "Greater Than or Equal",
|
|
115
|
+
"Token": ">=",
|
|
116
|
+
"Function": "fable.Math.greaterThanOrEqualOperator",
|
|
117
|
+
"Precedence": 5,
|
|
118
|
+
"Type": "Operator"
|
|
119
|
+
},
|
|
120
|
+
"<":
|
|
121
|
+
{
|
|
122
|
+
"Name": "Less Than",
|
|
123
|
+
"Token": "<",
|
|
124
|
+
"Function": "fable.Math.lessThanOperator",
|
|
125
|
+
"Precedence": 5,
|
|
126
|
+
"Type": "Operator"
|
|
127
|
+
},
|
|
128
|
+
"<=":
|
|
129
|
+
{
|
|
130
|
+
"Name": "Less Than or Equal",
|
|
131
|
+
"Token": "<=",
|
|
132
|
+
"Function": "fable.Math.lessThanOrEqualOperator",
|
|
133
|
+
"Precedence": 5,
|
|
134
|
+
"Type": "Operator"
|
|
135
|
+
},
|
|
136
|
+
"==":
|
|
137
|
+
{
|
|
138
|
+
"Name": "Equal",
|
|
139
|
+
"Token": "==",
|
|
140
|
+
"Function": "fable.Math.equalOperator",
|
|
141
|
+
"Precedence": 5,
|
|
142
|
+
"Type": "Operator"
|
|
143
|
+
},
|
|
144
|
+
"!=":
|
|
145
|
+
{
|
|
146
|
+
"Name": "Not Equal",
|
|
147
|
+
"Token": "!=",
|
|
148
|
+
"Function": "fable.Math.notEqualOperator",
|
|
149
|
+
"Precedence": 5,
|
|
150
|
+
"Type": "Operator"
|
|
151
|
+
},
|
|
152
|
+
|
|
153
|
+
"?":
|
|
154
|
+
{
|
|
155
|
+
"Name": "Ternary Condition",
|
|
156
|
+
"Token": "?",
|
|
157
|
+
"Precedence": 0,
|
|
158
|
+
"Type": "Ternary"
|
|
159
|
+
},
|
|
160
|
+
"::":
|
|
161
|
+
{
|
|
162
|
+
"Name": "Ternary Separator",
|
|
163
|
+
"Token": "::",
|
|
164
|
+
"Precedence": 0,
|
|
165
|
+
"Type": "Ternary"
|
|
102
166
|
}
|
|
103
167
|
}
|
|
@@ -112,6 +112,7 @@ class FableServiceExpressionParser extends libFableServiceBase
|
|
|
112
112
|
|
|
113
113
|
// Wire each sub service into this instance of the solver.
|
|
114
114
|
this.Tokenizer.connectExpressionParser(this);
|
|
115
|
+
this.Tokenizer.TokenizerDirectiveMutation.connectExpressionParser(this);
|
|
115
116
|
this.Linter.connectExpressionParser(this);
|
|
116
117
|
this.Postfix.connectExpressionParser(this);
|
|
117
118
|
this.ValueMarshal.connectExpressionParser(this);
|
|
@@ -126,6 +126,39 @@ class FableServiceLogic extends libFableServiceBase
|
|
|
126
126
|
}
|
|
127
127
|
return pOnTrue;
|
|
128
128
|
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Numeric-aware ternary selection for the expression parser.
|
|
132
|
+
* Treats 0, "0", "", null, undefined, false, NaN, empty arrays, and empty objects as falsy.
|
|
133
|
+
* Used by the ternary operator desugaring (condition ? trueVal :: falseVal).
|
|
134
|
+
*
|
|
135
|
+
* @param {any} pCondition - The condition to evaluate
|
|
136
|
+
* @param {any} pOnTrue - The value to return if the condition is truthy
|
|
137
|
+
* @param {any} [pOnFalse = ''] - The value to return if the condition is falsy
|
|
138
|
+
* @return {any} - The selected value
|
|
139
|
+
*/
|
|
140
|
+
ternary(pCondition, pOnTrue, pOnFalse = '')
|
|
141
|
+
{
|
|
142
|
+
// Standard JS falsy check
|
|
143
|
+
if (!pCondition)
|
|
144
|
+
{
|
|
145
|
+
return pOnFalse;
|
|
146
|
+
}
|
|
147
|
+
// Numeric zero as a string (from comparison operators returning "0")
|
|
148
|
+
if (pCondition === '0')
|
|
149
|
+
{
|
|
150
|
+
return pOnFalse;
|
|
151
|
+
}
|
|
152
|
+
if (Array.isArray(pCondition) && pCondition.length < 1)
|
|
153
|
+
{
|
|
154
|
+
return pOnFalse;
|
|
155
|
+
}
|
|
156
|
+
if (typeof pCondition === 'object' && Object.keys(pCondition).length < 1)
|
|
157
|
+
{
|
|
158
|
+
return pOnFalse;
|
|
159
|
+
}
|
|
160
|
+
return pOnTrue;
|
|
161
|
+
}
|
|
129
162
|
}
|
|
130
163
|
|
|
131
164
|
module.exports = FableServiceLogic;
|
|
@@ -460,6 +460,84 @@ class FableServiceMath extends libFableServiceBase
|
|
|
460
460
|
return tmpLeftArbitraryValue.lte(tmpRightValue);
|
|
461
461
|
}
|
|
462
462
|
|
|
463
|
+
/**
|
|
464
|
+
* Comparison operator: returns "1" if left > right, "0" otherwise.
|
|
465
|
+
* For use as an infix operator in the expression parser.
|
|
466
|
+
*
|
|
467
|
+
* @param {number|string} pLeftValue - The left value to compare.
|
|
468
|
+
* @param {number|string} pRightValue - The right value to compare.
|
|
469
|
+
* @returns {string} - "1" if left > right, "0" otherwise.
|
|
470
|
+
*/
|
|
471
|
+
greaterThanOperator(pLeftValue, pRightValue)
|
|
472
|
+
{
|
|
473
|
+
return this.gtPrecise(pLeftValue, pRightValue) ? '1' : '0';
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
/**
|
|
477
|
+
* Comparison operator: returns "1" if left >= right, "0" otherwise.
|
|
478
|
+
* For use as an infix operator in the expression parser.
|
|
479
|
+
*
|
|
480
|
+
* @param {number|string} pLeftValue - The left value to compare.
|
|
481
|
+
* @param {number|string} pRightValue - The right value to compare.
|
|
482
|
+
* @returns {string} - "1" if left >= right, "0" otherwise.
|
|
483
|
+
*/
|
|
484
|
+
greaterThanOrEqualOperator(pLeftValue, pRightValue)
|
|
485
|
+
{
|
|
486
|
+
return this.gtePrecise(pLeftValue, pRightValue) ? '1' : '0';
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
/**
|
|
490
|
+
* Comparison operator: returns "1" if left < right, "0" otherwise.
|
|
491
|
+
* For use as an infix operator in the expression parser.
|
|
492
|
+
*
|
|
493
|
+
* @param {number|string} pLeftValue - The left value to compare.
|
|
494
|
+
* @param {number|string} pRightValue - The right value to compare.
|
|
495
|
+
* @returns {string} - "1" if left < right, "0" otherwise.
|
|
496
|
+
*/
|
|
497
|
+
lessThanOperator(pLeftValue, pRightValue)
|
|
498
|
+
{
|
|
499
|
+
return this.ltPrecise(pLeftValue, pRightValue) ? '1' : '0';
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
/**
|
|
503
|
+
* Comparison operator: returns "1" if left <= right, "0" otherwise.
|
|
504
|
+
* For use as an infix operator in the expression parser.
|
|
505
|
+
*
|
|
506
|
+
* @param {number|string} pLeftValue - The left value to compare.
|
|
507
|
+
* @param {number|string} pRightValue - The right value to compare.
|
|
508
|
+
* @returns {string} - "1" if left <= right, "0" otherwise.
|
|
509
|
+
*/
|
|
510
|
+
lessThanOrEqualOperator(pLeftValue, pRightValue)
|
|
511
|
+
{
|
|
512
|
+
return this.ltePrecise(pLeftValue, pRightValue) ? '1' : '0';
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
/**
|
|
516
|
+
* Comparison operator: returns "1" if left == right, "0" otherwise.
|
|
517
|
+
* For use as an infix operator in the expression parser.
|
|
518
|
+
*
|
|
519
|
+
* @param {number|string} pLeftValue - The left value to compare.
|
|
520
|
+
* @param {number|string} pRightValue - The right value to compare.
|
|
521
|
+
* @returns {string} - "1" if left == right, "0" otherwise.
|
|
522
|
+
*/
|
|
523
|
+
equalOperator(pLeftValue, pRightValue)
|
|
524
|
+
{
|
|
525
|
+
return this.comparePrecise(pLeftValue, pRightValue) == 0 ? '1' : '0';
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
/**
|
|
529
|
+
* Comparison operator: returns "1" if left != right, "0" otherwise.
|
|
530
|
+
* For use as an infix operator in the expression parser.
|
|
531
|
+
*
|
|
532
|
+
* @param {number|string} pLeftValue - The left value to compare.
|
|
533
|
+
* @param {number|string} pRightValue - The right value to compare.
|
|
534
|
+
* @returns {string} - "1" if left != right, "0" otherwise.
|
|
535
|
+
*/
|
|
536
|
+
notEqualOperator(pLeftValue, pRightValue)
|
|
537
|
+
{
|
|
538
|
+
return this.comparePrecise(pLeftValue, pRightValue) != 0 ? '1' : '0';
|
|
539
|
+
}
|
|
540
|
+
|
|
463
541
|
/**
|
|
464
542
|
* Converts degrees to radians with arbitrary precision.
|
|
465
543
|
*
|
|
@@ -305,6 +305,111 @@ class FableServiceRestClient extends libFableServiceBase
|
|
|
305
305
|
return this.executeJSONRequest(pOptions, fCallback);
|
|
306
306
|
}
|
|
307
307
|
|
|
308
|
+
/**
|
|
309
|
+
* Upload binary data via POST.
|
|
310
|
+
*
|
|
311
|
+
* Accepts Buffer, Blob, or File as the body. In the browser, Blob/File
|
|
312
|
+
* bodies are converted to Buffer (via ArrayBuffer) before being passed
|
|
313
|
+
* to simple-get so the stream-http shim can send them correctly.
|
|
314
|
+
*
|
|
315
|
+
* The response body is read as a string (servers typically return JSON
|
|
316
|
+
* status for upload endpoints).
|
|
317
|
+
*
|
|
318
|
+
* @param {Record<string, any>} pOptions - Request options (url, body, headers, method)
|
|
319
|
+
* @param {(pError?: Error, pResponse: any, pBody?: any) => void} fCallback - Callback (pError, pResponse, pBody)
|
|
320
|
+
* @param {(pProgress: number) => void} [fOnProgress] - Optional progress callback (0.0 to 1.0); called with 1.0 on completion
|
|
321
|
+
*/
|
|
322
|
+
executeBinaryUpload(pOptions, fCallback, fOnProgress)
|
|
323
|
+
{
|
|
324
|
+
// Blob/File → Buffer conversion for simple-get compatibility
|
|
325
|
+
let tmpBody = pOptions.body;
|
|
326
|
+
|
|
327
|
+
if (typeof Blob !== 'undefined' && tmpBody instanceof Blob)
|
|
328
|
+
{
|
|
329
|
+
let tmpSelf = this;
|
|
330
|
+
tmpBody.arrayBuffer()
|
|
331
|
+
.then(
|
|
332
|
+
(pArrayBuffer) =>
|
|
333
|
+
{
|
|
334
|
+
pOptions.body = Buffer.from(pArrayBuffer);
|
|
335
|
+
tmpSelf._executeBinaryUploadInternal(pOptions, fCallback, fOnProgress);
|
|
336
|
+
})
|
|
337
|
+
.catch(
|
|
338
|
+
(pError) =>
|
|
339
|
+
{
|
|
340
|
+
return fCallback(pError);
|
|
341
|
+
});
|
|
342
|
+
return;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
// Already a Buffer, string, or stream — proceed directly
|
|
346
|
+
return this._executeBinaryUploadInternal(pOptions, fCallback, fOnProgress);
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
/**
|
|
350
|
+
* Internal binary upload implementation using simple-get.
|
|
351
|
+
*
|
|
352
|
+
* @param {Record<string, any>} pOptions - Request options with body already as Buffer
|
|
353
|
+
* @param {(pError?: Error, pResponse: any, pBody?: any) => void} fCallback - Callback (pError, pResponse, pBody)
|
|
354
|
+
* @param {(pProgress: number) => void} [fOnProgress] - Optional progress callback (0.0 to 1.0); called with 1.0 on completion
|
|
355
|
+
* @private
|
|
356
|
+
*/
|
|
357
|
+
_executeBinaryUploadInternal(pOptions, fCallback, fOnProgress)
|
|
358
|
+
{
|
|
359
|
+
let tmpOptions = this.preRequest(pOptions);
|
|
360
|
+
|
|
361
|
+
tmpOptions.RequestStartTime = this.fable.log.getTimeStamp();
|
|
362
|
+
|
|
363
|
+
if (this.TraceLog)
|
|
364
|
+
{
|
|
365
|
+
this.fable.log.debug(`Beginning ${tmpOptions.method} binary upload to ${tmpOptions.url} at ${tmpOptions.RequestStartTime}`);
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
tmpOptions.json = false;
|
|
369
|
+
|
|
370
|
+
return libSimpleGet(tmpOptions,
|
|
371
|
+
(pError, pResponse) =>
|
|
372
|
+
{
|
|
373
|
+
if (pError)
|
|
374
|
+
{
|
|
375
|
+
return fCallback(pError, pResponse);
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
if (this.TraceLog)
|
|
379
|
+
{
|
|
380
|
+
let tmpConnectTime = this.fable.log.getTimeStamp();
|
|
381
|
+
this.fable.log.debug(`--> Binary upload ${tmpOptions.method} connected in ${this.dataFormat.formatTimeDelta(tmpOptions.RequestStartTime, tmpConnectTime)}ms code ${pResponse.statusCode}`);
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
let tmpData = '';
|
|
385
|
+
|
|
386
|
+
pResponse.on('data', (pChunk) =>
|
|
387
|
+
{
|
|
388
|
+
if (this.TraceLog)
|
|
389
|
+
{
|
|
390
|
+
let tmpChunkTime = this.fable.log.getTimeStamp();
|
|
391
|
+
this.fable.log.debug(`--> Binary upload ${tmpOptions.method} response chunk size ${pChunk.length}b received in ${this.dataFormat.formatTimeDelta(tmpOptions.RequestStartTime, tmpChunkTime)}ms`);
|
|
392
|
+
}
|
|
393
|
+
tmpData += pChunk;
|
|
394
|
+
});
|
|
395
|
+
|
|
396
|
+
pResponse.on('end', () =>
|
|
397
|
+
{
|
|
398
|
+
if (this.TraceLog)
|
|
399
|
+
{
|
|
400
|
+
let tmpCompletionTime = this.fable.log.getTimeStamp();
|
|
401
|
+
this.fable.log.debug(`==> Binary upload ${tmpOptions.method} completed in ${this.dataFormat.formatTimeDelta(tmpOptions.RequestStartTime, tmpCompletionTime)}ms`);
|
|
402
|
+
}
|
|
403
|
+
// Signal completion via progress callback
|
|
404
|
+
if (typeof fOnProgress === 'function')
|
|
405
|
+
{
|
|
406
|
+
fOnProgress(1.0);
|
|
407
|
+
}
|
|
408
|
+
return fCallback(pError, pResponse, tmpData);
|
|
409
|
+
});
|
|
410
|
+
});
|
|
411
|
+
}
|
|
412
|
+
|
|
308
413
|
getRawText(pOptionsOrURL, fCallback)
|
|
309
414
|
{
|
|
310
415
|
let tmpRequestOptions = (typeof(pOptionsOrURL) == 'object') ? pOptionsOrURL : {};
|