@fable-org/fable-library-js 2.8.1 → 2.10.0

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/Async.js CHANGED
@@ -2,8 +2,8 @@ import { OperationCanceledException, Trampoline } from "./AsyncBuilder.js";
2
2
  import { CancellationToken } from "./AsyncBuilder.js";
3
3
  import { protectedCont, protectedBind, protectedReturn } from "./AsyncBuilder.js";
4
4
  import { Choice_makeChoice1Of2, Choice_makeChoice2Of2 } from "./Choice.js";
5
- import { TimeoutException_$ctor } from "./System.js";
6
- import { Exception } from "./Util.js";
5
+ import { ArgumentException_$ctor_Z384F8060, InvalidOperationException_$ctor_Z721C83C5, TimeoutException_$ctor } from "./System.js";
6
+ import { Exception, ensureErrorOrException } from "./Util.js";
7
7
  function emptyContinuation(_x) {
8
8
  // NOP
9
9
  }
@@ -44,27 +44,43 @@ export function throwIfCancellationRequested(token) {
44
44
  throw new Exception("Operation is cancelled");
45
45
  }
46
46
  }
47
+ // The parent keeps the listener until unlink is called
48
+ function createLinkedToken(parent) {
49
+ const token = new CancellationToken();
50
+ const id = parent.addListener(() => token.cancel());
51
+ return [token, () => { parent.removeListener(id); }];
52
+ }
47
53
  export function startChild(computation, ms) {
48
54
  return protectedCont((ctx) => {
49
- // Share the parent's cancellation token so cancelling the parent
50
- // cancels the child, like .NET Async.StartChild
51
- const promise = startAsPromise(computation, ctx.cancelToken);
52
- let promiseToRun = promise;
53
- if (ms) {
54
- // Race the computation against a timeout: whichever settles first wins.
55
- promiseToRun = new Promise((resolve, reject) => {
56
- const timeoutId = setTimeout(() => reject(TimeoutException_$ctor()), ms);
57
- promise.then(value => { clearTimeout(timeoutId); resolve(value); }, error => { clearTimeout(timeoutId); reject(error); });
58
- });
59
- }
60
- // Prevent an unhandled rejection when the child is cancelled or fails
61
- // while nobody awaits it (in F# an unobserved child's cancellation or
62
- // failure is simply not propagated). awaitPromise attaches its own
63
- // handlers to the same promise, so awaiting still observes the result.
64
- promiseToRun.catch(emptyContinuation);
55
+ const timeout = ms == null || ms === -1 ? undefined : ms;
56
+ const [childToken, unlink] = timeout === undefined
57
+ ? [ctx.cancelToken, () => { }]
58
+ : createLinkedToken(ctx.cancelToken);
59
+ const promise = startAsPromise(computation, childToken);
60
+ let settled = false;
61
+ const onSettled = () => { settled = true; unlink(); };
62
+ // Without a rejection handler an unawaited child's failure is an unhandled rejection
63
+ promise.then(onSettled, onSettled);
64
+ // Like .NET, the timeout only runs while the result is awaited, not from the start of the child
65
+ const awaitResult = () => timeout === undefined || settled ? promise : new Promise((resolve, reject) => {
66
+ const timeoutId = setTimeout(() => {
67
+ // .NET doesn't cancel the child when the timeout is 0
68
+ if (timeout !== 0) {
69
+ try {
70
+ childToken.cancel();
71
+ }
72
+ catch (error) {
73
+ reject(error);
74
+ return;
75
+ }
76
+ }
77
+ reject(TimeoutException_$ctor());
78
+ }, timeout);
79
+ promise.then(value => { clearTimeout(timeoutId); resolve(value); }, error => { clearTimeout(timeoutId); reject(error); });
80
+ });
65
81
  // JS Promises are hot, computation has already started
66
82
  // but we delay returning the result
67
- protectedReturn(awaitPromise(promiseToRun))(ctx);
83
+ protectedReturn(protectedCont((awaitCtx) => awaitPromise(awaitResult())(awaitCtx)))(ctx);
68
84
  });
69
85
  }
70
86
  export function awaitPromise(p) {
@@ -107,15 +123,179 @@ export function catchAsync(work) {
107
123
  });
108
124
  });
109
125
  }
126
+ export function tryCancelled(computation, compensation) {
127
+ return (ctx) => {
128
+ // invariant: install the handler without adding a cancellation checkpoint, including for nested handlers
129
+ computation({
130
+ ...ctx,
131
+ onCancel: (error) => {
132
+ // decision: preserve the original cancellation even when compensation throws, matching .NET
133
+ try {
134
+ compensation(error);
135
+ }
136
+ catch { /* Cancellation takes precedence. */ }
137
+ if (ctx.trampoline.incrementAndCheck()) {
138
+ ctx.trampoline.hijack(() => ctx.onCancel(error));
139
+ }
140
+ else {
141
+ ctx.onCancel(error);
142
+ }
143
+ },
144
+ });
145
+ };
146
+ }
110
147
  export function fromContinuations(f) {
111
- return protectedCont((ctx) => f([ctx.onSuccess, ctx.onError, ctx.onCancel]));
148
+ return (ctx) => {
149
+ let settled = false;
150
+ const claim = () => {
151
+ if (settled) {
152
+ throw InvalidOperationException_$ctor_Z721C83C5("A continuation was invoked more than once");
153
+ }
154
+ settled = true;
155
+ };
156
+ const success = (x) => { claim(); ctx.onSuccess(x); };
157
+ const error = (x) => { claim(); ctx.onError(x); };
158
+ const cancelled = (x) => { claim(); ctx.onCancel(x); };
159
+ protectedCont(() => f([success, error, cancelled]))({
160
+ onSuccess: success,
161
+ onError: (x) => { if (settled) {
162
+ throw x;
163
+ } error(x); },
164
+ onCancel: cancelled,
165
+ cancelToken: ctx.cancelToken,
166
+ trampoline: ctx.trampoline,
167
+ });
168
+ };
112
169
  }
113
170
  export function ignore(computation) {
114
171
  return protectedBind(computation, (_x) => protectedReturn(void 0));
115
172
  }
116
- export function parallel(computations) {
117
- // Children share the parent's cancellation token, like .NET Async.Parallel
118
- return protectedCont((ctx) => awaitPromise(Promise.all(Array.from(computations, (w) => startAsPromise(w, ctx.cancelToken))))(ctx));
173
+ export function parallel(computations, maxDegreeOfParallelism) {
174
+ if (maxDegreeOfParallelism != null && maxDegreeOfParallelism < 1) {
175
+ throw ArgumentException_$ctor_Z384F8060(`maxDegreeOfParallelism must be positive, was ${maxDegreeOfParallelism}`, "maxDegreeOfParallelism");
176
+ }
177
+ return protectedCont((ctx) => awaitPromise(runParallel(Array.from(computations), maxDegreeOfParallelism, ctx.cancelToken))(ctx));
178
+ }
179
+ function runParallel(computations, limit, parentToken) {
180
+ return new Promise((resolve, reject) => {
181
+ const count = computations.length;
182
+ const results = new Array(count);
183
+ const [token, unlink] = createLinkedToken(parentToken);
184
+ let next = 0;
185
+ let running = 0;
186
+ let failed = false;
187
+ let failure;
188
+ const startNext = () => {
189
+ const i = next++;
190
+ running++;
191
+ startAsPromise(computations[i], token)
192
+ .then((x) => { results[i] = x; }, (err) => {
193
+ if (!failed) {
194
+ failed = true;
195
+ failure = err;
196
+ try {
197
+ token.cancel();
198
+ }
199
+ catch (error) {
200
+ failure = error;
201
+ }
202
+ }
203
+ })
204
+ .then(() => {
205
+ running--;
206
+ if (!failed && next < count) {
207
+ startNext();
208
+ }
209
+ else if (running === 0) {
210
+ unlink();
211
+ if (failed) {
212
+ reject(failure);
213
+ }
214
+ else {
215
+ resolve(results);
216
+ }
217
+ }
218
+ });
219
+ };
220
+ if (count === 0) {
221
+ unlink();
222
+ resolve(results);
223
+ }
224
+ const initial = Math.min(limit ?? count, count);
225
+ while (next < initial) {
226
+ startNext();
227
+ }
228
+ });
229
+ }
230
+ export function choice(computations) {
231
+ return protectedCont((ctx) => {
232
+ const works = Array.from(computations);
233
+ let remaining = works.length;
234
+ if (remaining === 0) {
235
+ ctx.onSuccess(undefined);
236
+ return;
237
+ }
238
+ const [token, unlink] = createLinkedToken(ctx.cancelToken);
239
+ let done = false;
240
+ const finish = (report) => {
241
+ if (done) {
242
+ return;
243
+ }
244
+ done = true;
245
+ unlink();
246
+ try {
247
+ token.cancel();
248
+ }
249
+ catch (error) {
250
+ ctx.onError(ensureErrorOrException(error));
251
+ return;
252
+ }
253
+ report();
254
+ };
255
+ for (const work of works) {
256
+ startWithContinuations(work, (x) => {
257
+ if (x != null) {
258
+ finish(() => ctx.onSuccess(x));
259
+ }
260
+ else if (--remaining === 0) {
261
+ finish(() => ctx.onSuccess(undefined));
262
+ }
263
+ }, (err) => finish(() => ctx.onError(err)), (err) => finish(() => ctx.onCancel(err)), token);
264
+ if (done) {
265
+ break;
266
+ }
267
+ }
268
+ });
269
+ }
270
+ export function onCancel(interruption) {
271
+ return protectedCont((ctx) => {
272
+ const token = ctx.cancelToken;
273
+ let interrupted = false;
274
+ const interrupt = () => {
275
+ if (!interrupted) {
276
+ interrupted = true;
277
+ try {
278
+ interruption();
279
+ }
280
+ catch { /* Ignored, like .NET Async.OnCancel */ }
281
+ }
282
+ };
283
+ const id = token.addListener(interrupt);
284
+ let disposed = false;
285
+ ctx.onSuccess({
286
+ Dispose() {
287
+ if (disposed) {
288
+ return;
289
+ }
290
+ disposed = true;
291
+ token.removeListener(id);
292
+ // With use! Dispose runs inside cancel() before this listener gets its turn
293
+ if (token.isCancelled) {
294
+ interrupt();
295
+ }
296
+ },
297
+ });
298
+ });
119
299
  }
120
300
  export function sequential(computations) {
121
301
  function _sequential(computations, cancelToken) {
@@ -175,3 +355,9 @@ export function startWithContinuations(computation, continuation, exceptionConti
175
355
  export function startAsPromise(computation, cancellationToken) {
176
356
  return new Promise((resolve, reject) => startWithContinuations(computation, resolve, reject, reject, cancellationToken ? cancellationToken : defaultCancellationToken));
177
357
  }
358
+ export function startAsTask(computation, _taskCreationOptions, cancellationToken) {
359
+ return startAsPromise(computation, cancellationToken);
360
+ }
361
+ export function startImmediateAsTask(computation, cancellationToken) {
362
+ return startAsPromise(computation, cancellationToken);
363
+ }
package/AsyncBuilder.js CHANGED
@@ -174,9 +174,15 @@ export class AsyncBuilder {
174
174
  return computation;
175
175
  }
176
176
  TryFinally(computation, compensation) {
177
- return protectedCont((ctx) => {
178
- computation({
177
+ // The cancellation check in protectedCont must see the cleanup continuations
178
+ return (ctx) => {
179
+ let settled = false;
180
+ protectedCont(computation)({
179
181
  onSuccess: (x) => {
182
+ if (settled) {
183
+ return;
184
+ }
185
+ settled = true;
180
186
  try {
181
187
  compensation();
182
188
  }
@@ -187,6 +193,10 @@ export class AsyncBuilder {
187
193
  ctx.onSuccess(x);
188
194
  },
189
195
  onError: (x) => {
196
+ if (settled) {
197
+ throw x;
198
+ }
199
+ settled = true;
190
200
  try {
191
201
  compensation();
192
202
  }
@@ -197,6 +207,10 @@ export class AsyncBuilder {
197
207
  ctx.onError(x);
198
208
  },
199
209
  onCancel: (x) => {
210
+ if (settled) {
211
+ return;
212
+ }
213
+ settled = true;
200
214
  // decision: cancellation remains terminal when compensation throws, matching .NET Async.TryFinally
201
215
  try {
202
216
  compensation();
@@ -207,7 +221,7 @@ export class AsyncBuilder {
207
221
  cancelToken: ctx.cancelToken,
208
222
  trampoline: ctx.trampoline,
209
223
  });
210
- });
224
+ };
211
225
  }
212
226
  TryWith(computation, catchHandler) {
213
227
  return protectedCont((ctx) => {
@@ -232,7 +246,8 @@ export class AsyncBuilder {
232
246
  });
233
247
  }
234
248
  Using(resource, binder) {
235
- return this.TryFinally(binder(resource), () => resource.Dispose());
249
+ // Without Delay the binder runs before TryFinally installs Dispose
250
+ return this.TryFinally(this.Delay(() => binder(resource)), () => resource.Dispose());
236
251
  }
237
252
  While(guard, computation) {
238
253
  if (guard()) {
package/CHANGELOG.md CHANGED
@@ -1,5 +1,5 @@
1
1
  ---
2
- last_commit_released: 148f989e1b37b3f6560c3dda615b6a3a8efffcb8
2
+ last_commit_released: 2098f64a25e96385306ef19ed8c289f3b6c84db9
3
3
  updaters:
4
4
  - package.json:
5
5
  file: package.json
@@ -15,6 +15,38 @@ All notable changes to this project will be documented in this file.
15
15
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
16
16
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
17
17
 
18
+ ## 2.10.0 - 2026-10-22
19
+
20
+ ### 🚀 Features
21
+
22
+ * *(js/ts)* Support System.Diagnostics.Stopwatch (#5100) ([eb68225f](https://github.com/fable-compiler/Fable/commit/eb68225ff74d1b455765d99998faa6fd3be32383))
23
+
24
+ ### 🐞 Bug Fixes
25
+
26
+ * *(js/ts)* Round decimals below 0.1 correctly ([fca4c408](https://github.com/fable-compiler/Fable/commit/fca4c408a64e33c58293f12869975e5b61bcb96a))
27
+ * *(js/ts)* Keep decimal zero scale without leading zeros ([3076b45a](https://github.com/fable-compiler/Fable/commit/3076b45acacf39b8c99ec57025560ad355ab74db))
28
+ * *(js/ts)* Hash decimals by value in collections ([32a69e0d](https://github.com/fable-compiler/Fable/commit/32a69e0d00f567c9a838307e9ed51b3b1d519473))
29
+ * *(js/ts)* Keep .NET decimal scale through arithmetic (#5126) ([bdf0a6c8](https://github.com/fable-compiler/Fable/commit/bdf0a6c8cb56151a5431b5e3703f7ee165b26dba))
30
+ * *(js/ts)* Accept decimal integers up to 2^96 ([c9feb339](https://github.com/fable-compiler/Fable/commit/c9feb339fb8a4e98bebf17b3facdc42ecd22d628))
31
+ * *(js/ts)* Align decimal conversion, parsing and G format ([6430cdac](https://github.com/fable-compiler/Fable/commit/6430cdacec34e6327434af649eb0c9427d65608c))
32
+ * *(js/ts)* Add missing Async and Task members, cancel timed-out StartChild, bind Async in task { } (#5097) ([d063630d](https://github.com/fable-compiler/Fable/commit/d063630d58d62db21603d45a6bf54b172378c66e))
33
+ * *(js/ts)* Respect maxDegreeOfParallelism and cancel on failure in Async.Parallel (#5098) ([5de3271d](https://github.com/fable-compiler/Fable/commit/5de3271d8b47f1752c4e74c010e4314cf40bbcbe))
34
+
35
+ <strong><small>[View changes on Github](https://github.com/fable-compiler/Fable/compare/e33c57870454fea189a3eb33519f583a5003f8c3..2098f64a25e96385306ef19ed8c289f3b6c84db9)</small></strong>
36
+
37
+ ## 2.9.0 - 2026-10-16
38
+
39
+ ### 🚀 Features
40
+
41
+ * *(js/ts/python/beam)* Support Async.TryCancelled (#5083) ([1643297d](https://github.com/fable-compiler/Fable/commit/1643297d9297fef536c8d10091d5b0d1fad77af7))
42
+
43
+ ### 🐞 Bug Fixes
44
+
45
+ * *(js/ts)* Avoid full array copy in Encoding.GetString (#5069) ([e6b72ec7](https://github.com/fable-compiler/Fable/commit/e6b72ec73be454bd3b3233167542a65fc6553c8a))
46
+ * *(js/ts/python)* Preserve cancelled cleanup and generic unit callbacks (#5062) ([5dd6b497](https://github.com/fable-compiler/Fable/commit/5dd6b4979e79c8f7c4632cbfb5254d6cf0d2e8d2))
47
+
48
+ <strong><small>[View changes on Github](https://github.com/fable-compiler/Fable/compare/148f989e1b37b3f6560c3dda615b6a3a8efffcb8..e33c57870454fea189a3eb33519f583a5003f8c3)</small></strong>
49
+
18
50
  ## 2.8.1 - 2026-10-12
19
51
 
20
52
  ### 🐞 Bug Fixes
package/Decimal.js CHANGED
@@ -1,11 +1,18 @@
1
1
  import Decimal from "./lib/big.js";
2
2
  import { symbol } from "./Numeric.js";
3
- import { FSharpRef } from "./Types.js";
4
3
  import { Exception, combineHashCodes } from "./Util.js";
5
4
  import { fromDecimal } from "./BigInt.js";
6
5
  import * as bigInt from "./BigInt.js";
7
6
  Decimal.prototype.GetHashCode = function () {
8
- return combineHashCodes([this.s, this.e].concat(this.c));
7
+ const digits = this.c;
8
+ if (digits[0] === 0) {
9
+ return combineHashCodes([1, 0, 0]);
10
+ }
11
+ let end = digits.length;
12
+ while (digits[end - 1] === 0) {
13
+ end--;
14
+ }
15
+ return combineHashCodes([this.s, this.e].concat(digits.slice(0, end)));
9
16
  };
10
17
  Decimal.prototype.Equals = function (x) {
11
18
  return !this.cmp(x);
@@ -37,7 +44,7 @@ export function equals(x, y) {
37
44
  }
38
45
  export function abs(x) { return x.abs(); }
39
46
  export function sign(x) { return x.lt(get_Zero) ? -1 : x.gt(get_Zero) ? 1 : 0; }
40
- export function max(x, y) { return x.gt(y) ? x : y; }
47
+ export function max(x, y) { return x.lt(y) ? y : x; }
41
48
  export function min(x, y) { return x.lt(y) ? x : y; }
42
49
  export function maxMagnitude(x, y) { return abs(x).gt(abs(y)) ? x : y; }
43
50
  export function minMagnitude(x, y) { return abs(x).lt(abs(y)) ? x : y; }
@@ -94,23 +101,79 @@ export const negate = op_UnaryNegation;
94
101
  export function toString(x) {
95
102
  return x.toString();
96
103
  }
97
- export function tryParse(str, defValue) {
104
+ const NumberStyles = {
105
+ AllowLeadingWhite: 1,
106
+ AllowTrailingWhite: 2,
107
+ AllowLeadingSign: 4,
108
+ AllowTrailingSign: 8,
109
+ AllowParentheses: 16,
110
+ AllowDecimalPoint: 32,
111
+ AllowThousands: 64,
112
+ AllowExponent: 128,
113
+ AllowHexSpecifier: 512,
114
+ Number: 111,
115
+ };
116
+ // The digits of a decimal string once whitespace, sign and parentheses are stripped
117
+ function digitsPattern(style) {
118
+ const integral = style & NumberStyles.AllowThousands ? "\\d+(?:,\\d+)*" : "\\d+";
119
+ const point = style & NumberStyles.AllowDecimalPoint ? "(?:\\.\\d*)?" : "";
120
+ const leadingPoint = style & NumberStyles.AllowDecimalPoint ? "|\\.\\d+" : "";
121
+ const exponent = style & NumberStyles.AllowExponent ? "(?:[eE][+-]?\\d+)?" : "";
122
+ return new RegExp(`^(?:${integral}${point}${leadingPoint})${exponent}$`);
123
+ }
124
+ // Returns undefined when the string does not have the format allowed by the style
125
+ function parseWithStyle(str, style) {
126
+ if (style & NumberStyles.AllowHexSpecifier) {
127
+ throw new Exception("The number style AllowHexSpecifier is not supported on floating point data types.");
128
+ }
129
+ let s = str;
130
+ if (style & NumberStyles.AllowLeadingWhite) {
131
+ s = s.replace(/^\s+/, "");
132
+ }
133
+ if (style & NumberStyles.AllowTrailingWhite) {
134
+ s = s.replace(/\s+$/, "");
135
+ }
136
+ let negative = false;
137
+ if (style & NumberStyles.AllowParentheses && s.startsWith("(") && s.endsWith(")")) {
138
+ s = s.slice(1, -1);
139
+ negative = true;
140
+ }
141
+ if (style & NumberStyles.AllowLeadingSign && /^[+-]/.test(s)) {
142
+ negative = s[0] === "-" ? !negative : negative;
143
+ s = s.slice(1);
144
+ }
145
+ else if (style & NumberStyles.AllowTrailingSign && /[+-]$/.test(s)) {
146
+ negative = s[s.length - 1] === "-" ? !negative : negative;
147
+ s = s.slice(0, -1);
148
+ }
149
+ if (!digitsPattern(style).test(s)) {
150
+ return undefined;
151
+ }
152
+ const d = new Decimal(s.replace(/,/g, ""));
153
+ if (negative) {
154
+ d.s = -1;
155
+ }
156
+ return d;
157
+ }
158
+ export function tryParse(str, style, defValue) {
98
159
  try {
99
- defValue.contents = new Decimal(str.trim());
160
+ const d = parseWithStyle(str, style);
161
+ if (d === undefined) {
162
+ return false;
163
+ }
164
+ defValue.contents = d;
100
165
  return true;
101
166
  }
102
167
  catch {
103
168
  return false;
104
169
  }
105
170
  }
106
- export function parse(str) {
107
- const defValue = new FSharpRef(get_Zero);
108
- if (tryParse(str, defValue)) {
109
- return defValue.contents;
110
- }
111
- else {
171
+ export function parse(str, style = NumberStyles.Number) {
172
+ const d = parseWithStyle(str, style);
173
+ if (d === undefined) {
112
174
  throw new Exception(`The input string ${str} was not in a correct format.`);
113
175
  }
176
+ return d;
114
177
  }
115
178
  export function toNumber(x) {
116
179
  return +x;
@@ -182,6 +245,32 @@ function getInt32Bits(hexDigits, offset) {
182
245
  }
183
246
  return bits;
184
247
  }
248
+ const OVERFLOW = "Value was either too large or too small for a Decimal.";
249
+ // The first double outside the decimal range
250
+ const MAX_DOUBLE = 2 ** 96;
251
+ function fromFloat(n, significantDigits) {
252
+ if (!isFinite(n) || Math.abs(n) >= MAX_DOUBLE) {
253
+ throw new Exception(OVERFLOW);
254
+ }
255
+ const exact = Math.abs(n) >= 1e21 ? BigInt(n).toString() : n.toFixed(100);
256
+ const d = new Decimal(exact).prec(significantDigits, 2);
257
+ const digits = d.c;
258
+ let end = digits.length;
259
+ while (end > 1 && digits[end - 1] === 0) {
260
+ end--;
261
+ }
262
+ d.c = digits.slice(0, end);
263
+ if (d.c[0] === 0) {
264
+ d.e = 0;
265
+ }
266
+ return d;
267
+ }
268
+ export function fromFloat64(n) {
269
+ return fromFloat(n, 15);
270
+ }
271
+ export function fromFloat32(n) {
272
+ return fromFloat(n, 7);
273
+ }
185
274
  export function fromIntArray(bits) {
186
275
  return fromInts(bits[0], bits[1], bits[2], bits[3]);
187
276
  }
package/Encoding.js CHANGED
@@ -90,6 +90,27 @@ function utf8_decode(bytes) {
90
90
  }
91
91
  return chars.join("");
92
92
  }
93
+ function toUint8Array(bytes, index, count) {
94
+ if (ArrayBuffer.isView(bytes)) {
95
+ const buffer = new Uint8Array(bytes.buffer, bytes.byteOffset, bytes.byteLength);
96
+ if (index != null && count != null) {
97
+ return buffer.subarray(index, index + count);
98
+ }
99
+ else if (index != null) {
100
+ return buffer.subarray(index);
101
+ }
102
+ return buffer;
103
+ }
104
+ else {
105
+ if (index != null && count != null) {
106
+ return Uint8Array.from(Array.prototype.slice.call(bytes, index, index + count));
107
+ }
108
+ else if (index != null) {
109
+ return Uint8Array.from(Array.prototype.slice.call(bytes, index));
110
+ }
111
+ return Uint8Array.from(bytes);
112
+ }
113
+ }
93
114
  class UTF16LE {
94
115
  getBytes(str, index, count) {
95
116
  str = Array.isArray(str) ? str.join("") : str;
@@ -111,14 +132,7 @@ class UTF16LE {
111
132
  }
112
133
  }
113
134
  getString(bytes, index, count) {
114
- const array = ArrayBuffer.isView(bytes) ? bytes : Uint8Array.from(bytes);
115
- let buffer = new Uint8Array(array.buffer, array.byteOffset, array.byteLength);
116
- if (index != null && count != null) {
117
- buffer = buffer.subarray(index, index + count);
118
- }
119
- else if (index != null) {
120
- buffer = buffer.subarray(index);
121
- }
135
+ const buffer = toUint8Array(bytes, index, count);
122
136
  // Allow to compile for Browser environment without @types/node being installed
123
137
  // See https://github.com/fable-compiler/Fable/issues/4368
124
138
  const g = globalThis;
@@ -157,14 +171,7 @@ class UTF8 {
157
171
  }
158
172
  }
159
173
  getString(bytes, index, count) {
160
- const array = ArrayBuffer.isView(bytes) ? bytes : Uint8Array.from(bytes);
161
- let buffer = new Uint8Array(array.buffer, array.byteOffset, array.byteLength);
162
- if (index != null && count != null) {
163
- buffer = buffer.subarray(index, index + count);
164
- }
165
- else if (index != null) {
166
- buffer = buffer.subarray(index);
167
- }
174
+ const buffer = toUint8Array(bytes, index, count);
168
175
  // Allow to compile for Browser environment without @types/node being installed
169
176
  // See https://github.com/fable-compiler/Fable/issues/4368
170
177
  const g = globalThis;
@@ -96,6 +96,10 @@ export function postAndAsyncReply($this, buildMessage) {
96
96
  }
97
97
  const reply = {
98
98
  reply: (res) => {
99
+ // .NET AsyncReplyChannel.Reply ignores a second reply
100
+ if (replied) {
101
+ return;
102
+ }
99
103
  result = res;
100
104
  replied = true;
101
105
  checkCompletion();
package/Stopwatch.js ADDED
@@ -0,0 +1,65 @@
1
+ // Timestamps are in microseconds, the resolution performance.now() can offer
2
+ const ticksPerMillisecond = 1000;
3
+ export class Stopwatch {
4
+ constructor() {
5
+ // Milliseconds measured before the last Start
6
+ this.elapsed = 0;
7
+ // performance.now() at the last Start, while running
8
+ this.startedAt = undefined;
9
+ }
10
+ }
11
+ export function create() {
12
+ return new Stopwatch();
13
+ }
14
+ export function startNew() {
15
+ const sw = new Stopwatch();
16
+ start(sw);
17
+ return sw;
18
+ }
19
+ export function start(sw) {
20
+ if (sw.startedAt === undefined) {
21
+ sw.startedAt = performance.now();
22
+ }
23
+ }
24
+ export function stop(sw) {
25
+ if (sw.startedAt !== undefined) {
26
+ sw.elapsed += performance.now() - sw.startedAt;
27
+ sw.startedAt = undefined;
28
+ }
29
+ }
30
+ export function reset(sw) {
31
+ sw.elapsed = 0;
32
+ sw.startedAt = undefined;
33
+ }
34
+ export function restart(sw) {
35
+ sw.elapsed = 0;
36
+ sw.startedAt = performance.now();
37
+ }
38
+ export function isRunning(sw) {
39
+ return sw.startedAt !== undefined;
40
+ }
41
+ function rawElapsedMilliseconds(sw) {
42
+ return sw.elapsed + (sw.startedAt === undefined ? 0 : performance.now() - sw.startedAt);
43
+ }
44
+ // In units of Frequency, like .NET (not TimeSpan ticks)
45
+ export function elapsedTicks(sw) {
46
+ return BigInt(Math.trunc(rawElapsedMilliseconds(sw) * ticksPerMillisecond));
47
+ }
48
+ export function elapsedMilliseconds(sw) {
49
+ return elapsedTicks(sw) / BigInt(ticksPerMillisecond);
50
+ }
51
+ export function elapsedTotalMilliseconds(sw) {
52
+ return Number(elapsedTicks(sw)) / ticksPerMillisecond;
53
+ }
54
+ export function frequency() {
55
+ return BigInt(ticksPerMillisecond * 1000);
56
+ }
57
+ export function isHighResolution() {
58
+ return true;
59
+ }
60
+ export function getTimestamp() {
61
+ return BigInt(Math.trunc(performance.now() * ticksPerMillisecond));
62
+ }
63
+ export function getElapsedTotalMilliseconds(startingTimestamp, endingTimestamp) {
64
+ return Number((endingTimestamp ?? getTimestamp()) - startingTimestamp) / ticksPerMillisecond;
65
+ }
package/String.js CHANGED
@@ -426,6 +426,10 @@ export function format(str, ...args) {
426
426
  break;
427
427
  case "g":
428
428
  case "G": {
429
+ if (precision == null && typeof rep !== "number" && typeof rep !== "bigint") {
430
+ rep = String(rep);
431
+ break;
432
+ }
429
433
  rep = precision != null ? toPrecision(rep, precision) : toPrecision(rep);
430
434
  // Handle exponential notation: only trim trailing zeros from mantissa, not from exponent.
431
435
  // .NET G format guarantees an exponent of at least 2 digits with an explicit sign (e.g. "E-07").
package/Task.js CHANGED
@@ -17,6 +17,28 @@ export function fromResult(value) {
17
17
  export function zero() {
18
18
  return Promise.resolve();
19
19
  }
20
+ // Like .NET, -1 waits until the token is cancelled
21
+ export function delay(millisecondsDelay, cancellationToken) {
22
+ return new Promise((resolve, reject) => {
23
+ if (cancellationToken?.isCancelled) {
24
+ reject(new OperationCanceledException());
25
+ return;
26
+ }
27
+ let tokenId;
28
+ const timeoutId = millisecondsDelay === -1 ? undefined : setTimeout(() => {
29
+ if (tokenId !== undefined) {
30
+ cancellationToken.removeListener(tokenId);
31
+ }
32
+ resolve();
33
+ }, millisecondsDelay);
34
+ if (cancellationToken != null) {
35
+ tokenId = cancellationToken.addListener(() => {
36
+ clearTimeout(timeoutId);
37
+ reject(new OperationCanceledException());
38
+ });
39
+ }
40
+ });
41
+ }
20
42
  // Task<T> = Promise<T> in JS/TS. GetAwaiter/GetResult return the Promise itself;
21
43
  // callers should use Async.AwaitTask to extract the value in an async context.
22
44
  export function getAwaiter(t) {
package/big.d.ts CHANGED
@@ -202,6 +202,7 @@ export interface Big extends CustomNumeric {
202
202
  * @throws `!Big.RM!` if rm is invalid.
203
203
  */
204
204
  round(dp?: number, rm?: RoundingMode): Big;
205
+ prec(sd: number, rm?: RoundingMode): Big;
205
206
  /**
206
207
  * Returns a Big number whose value is the square root of this Big number.
207
208
  *
package/lib/big.js CHANGED
@@ -67,7 +67,6 @@ function _Big_() {
67
67
  x.s = n.s;
68
68
  x.e = n.e;
69
69
  x.c = n.c.slice();
70
- normalize(x);
71
70
  }
72
71
  else {
73
72
  if (typeof n !== 'string') {
@@ -91,14 +90,6 @@ function _Big_() {
91
90
  Big.strict = STRICT;
92
91
  return Big;
93
92
  }
94
- function normalize(x) {
95
- // x = round(x, DP, 0);
96
- if (x.c.length > 1 && !x.c[0]) {
97
- let i = x.c.findIndex(x => x);
98
- x.c = x.c.slice(i);
99
- x.e = x.e - i;
100
- }
101
- }
102
93
  /*
103
94
  * Parse the number or string value passed to a Big constructor.
104
95
  *
@@ -128,15 +119,13 @@ function parse(x, n) {
128
119
  e = n.length;
129
120
  }
130
121
  nl = n.length;
131
- // Determine leading zeros before decimal point.
132
- for (i = 0; i < e && i < nl && n.charAt(i) == '0';)
122
+ // Determine leading zeros.
123
+ for (i = 0; i < nl && n.charAt(i) == '0';)
133
124
  ++i;
134
- // original version (ignores decimal point).
135
- // // Determine leading zeros.
136
- // for (i = 0; i < nl && n.charAt(i) == '0';) ++i;
137
125
  if (i == nl) {
138
- // Zero.
139
- x.c = [x.e = 0];
126
+ // Zero, keeping the scale: 0.00 is c = [0], e = -2.
127
+ x.c = [0];
128
+ x.e = nl > e ? e - nl : 0;
140
129
  }
141
130
  else {
142
131
  x.e = e - i - 1;
@@ -150,8 +139,7 @@ function parse(x, n) {
150
139
  // // Convert string to array of digits without leading/trailing zeros.
151
140
  // for (e = 0; i <= nl;) x.c[e++] = +n.charAt(i++);
152
141
  }
153
- x = round(x, Big.DP + 1, Big.RM);
154
- return x;
142
+ return fit(x);
155
143
  }
156
144
  /*
157
145
  * Round Big x to a maximum of sd significant digits using rounding mode rm.
@@ -171,17 +159,11 @@ function round(x, sd, rm, more) {
171
159
  if (sd < 1) {
172
160
  more =
173
161
  rm === 3 && (more || !!xc[0]) || sd === 0 && (rm === 1 && xc[0] >= 5 ||
174
- rm === 2 && (xc[0] > 5 || xc[0] === 5 && (more || xc[1] !== UNDEFINED)));
162
+ rm === 2 && (xc[0] > 5 || xc[0] === 5 && (more || xc.some((d, i) => i > 0 && d))));
175
163
  xc.length = 1;
176
- if (more) {
177
- // 1, 0.1, 0.01, 0.001, 0.0001 etc.
178
- x.e = x.e - sd + 1;
179
- xc[0] = 1;
180
- }
181
- else {
182
- // Zero.
183
- xc[0] = x.e = 0;
184
- }
164
+ // 1, 0.1, 0.01, 0.001, 0.0001 etc., or zero at the same place.
165
+ x.e = x.e - sd + 1;
166
+ xc[0] = more ? 1 : 0;
185
167
  }
186
168
  else if (sd < xc.length) {
187
169
  // xc[sd] is the digit after the digit that may be rounded up.
@@ -189,7 +171,7 @@ function round(x, sd, rm, more) {
189
171
  more =
190
172
  rm === 1 && xc[sd] >= 5 ||
191
173
  rm === 2 && (xc[sd] > 5 || xc[sd] === 5 &&
192
- (more || xc[sd + 1] !== UNDEFINED || xc[sd - 1] & 1)) ||
174
+ (more || xc.some((d, i) => i > sd && d) || xc[sd - 1] & 1)) ||
193
175
  rm === 3 && (more || !isZero);
194
176
  // Remove any digits after the required precision.
195
177
  xc.length = sd--;
@@ -204,14 +186,59 @@ function round(x, sd, rm, more) {
204
186
  }
205
187
  }
206
188
  }
207
- // Remove trailing zeros.
208
- for (sd = xc.length; !xc[--sd];)
209
- xc.pop();
210
189
  }
211
190
  return x;
212
191
  }
213
- function hasNonzeroDigit(x) {
214
- return x.c.some(d => d !== 0);
192
+ // Largest System.Decimal mantissa, 2^96 - 1.
193
+ var MAX_MANTISSA = '79228162514264337593543950335'.split('').map(Number), OVERFLOW = 'Value was either too large or too small for a Decimal.';
194
+ /*
195
+ * Return the number of decimal places of Big x.
196
+ */
197
+ function scaleOf(x) {
198
+ return x.c.length - x.e - 1;
199
+ }
200
+ /*
201
+ * Pad Big x with trailing zeros up to dp decimal places.
202
+ */
203
+ function padScale(x, dp) {
204
+ if (!x.c[0]) {
205
+ x.e = Math.min(x.e, -dp);
206
+ }
207
+ else {
208
+ for (; scaleOf(x) < dp;)
209
+ x.c.push(0);
210
+ }
211
+ return x;
212
+ }
213
+ /*
214
+ * Return true if the digits c, padded with zeros to the length of MAX_MANTISSA, exceed it.
215
+ */
216
+ function exceedsMax(c) {
217
+ for (var i = 0; i < MAX_MANTISSA.length; i++) {
218
+ var d = i < c.length ? c[i] : 0;
219
+ if (d !== MAX_MANTISSA[i])
220
+ return d > MAX_MANTISSA[i];
221
+ }
222
+ return false;
223
+ }
224
+ /*
225
+ * Round Big x half to even so that it fits a System.Decimal: at most Big.DP decimal places
226
+ * and a mantissa no larger than MAX_MANTISSA. Throws when no decimal place is left to drop.
227
+ */
228
+ function fit(x) {
229
+ var scale = scaleOf(x), dp = Math.min(scale, Big.DP, Big.DP - x.e);
230
+ if (x.e > Big.DP)
231
+ throw Error(OVERFLOW);
232
+ if (dp < scale)
233
+ round(x, x.e + 1 + dp, 2);
234
+ if (x.e > Big.DP)
235
+ throw Error(OVERFLOW);
236
+ // Both the value and the mantissa itself must not exceed MAX_MANTISSA.
237
+ if (x.e === Big.DP && exceedsMax(x.c))
238
+ throw Error(OVERFLOW);
239
+ if (x.c.length === MAX_MANTISSA.length && exceedsMax(x.c))
240
+ round(x, x.c.length - 1, 2);
241
+ return x;
215
242
  }
216
243
  /*
217
244
  * Return a string representing the value of Big x in normal or exponential notation.
@@ -221,6 +248,8 @@ function stringify(x, doExponential, isNonzero) {
221
248
  var e = x.e, s = x.c.join(''), n = s.length;
222
249
  // Exponential notation?
223
250
  if (doExponential) {
251
+ if (!x.c[0])
252
+ e = 0;
224
253
  s = s.charAt(0) + (n > 1 ? '.' + s.slice(1) : '') + (e < 0 ? 'e' : 'e+') + e;
225
254
  // Normal notation.
226
255
  }
@@ -292,20 +321,31 @@ P.cmp = function (y) {
292
321
  * if necessary, to a maximum of Big.DP decimal places using rounding mode Big.RM.
293
322
  */
294
323
  P.div = function (y) {
295
- var Big = this.constructor, x = new Big(this), y = new Big(y), a = x.c, // dividend
296
- b = y.c, // divisor
297
- k = x.s == y.s ? 1 : -1, dp = Big.DP;
324
+ var Big = this.constructor, dp = Big.DP;
298
325
  if (dp !== ~~dp || dp < 0 || dp > MAX_DP) {
299
326
  throw Error(INVALID_DP);
300
327
  }
328
+ return divide(new Big(this), new Big(y), dp, 2);
329
+ };
330
+ /*
331
+ * Return Big x divided by Big y to dp decimal places using rounding mode rm.
332
+ * Both are modified.
333
+ */
334
+ function divide(x, y, dp, rm) {
335
+ var a = x.c, // dividend
336
+ b = y.c, // divisor
337
+ k = x.s == y.s ? 1 : -1;
301
338
  // Divisor is zero?
302
339
  if (!b[0]) {
303
340
  throw Error(DIV_BY_ZERO);
304
341
  }
342
+ // Scale of the exact quotient is at least the scale of x less the scale of y.
343
+ var minScale = Math.max(0, scaleOf(x) - scaleOf(y));
305
344
  // Dividend is 0? Return +-0.
306
345
  if (!a[0]) {
307
346
  y.s = k;
308
- y.c = [y.e = 0];
347
+ y.c = [0];
348
+ y.e = -minScale;
309
349
  return y;
310
350
  }
311
351
  var bl, bt, n, cmp, ri, bz = b.slice(), ai = bl = b.length, al = a.length, r = a.slice(0, bl), // remainder
@@ -369,11 +409,20 @@ P.div = function (y) {
369
409
  q.e--;
370
410
  p--;
371
411
  }
412
+ var exact = r[0] === UNDEFINED;
372
413
  // Round?
373
414
  if (qi > p)
374
- round(q, p, Big.RM, r[0] !== UNDEFINED);
415
+ round(q, p, rm, !exact);
416
+ fit(q);
417
+ if (!q.c[0]) {
418
+ q.e = 0;
419
+ }
420
+ else if (exact) {
421
+ for (; scaleOf(q) > minScale && !q.c[q.c.length - 1];)
422
+ q.c.pop();
423
+ }
375
424
  return q;
376
- };
425
+ }
377
426
  /*
378
427
  * Return true if the value of this Big is equal to the value of Big y, otherwise return false.
379
428
  */
@@ -420,6 +469,7 @@ P.minus = P.sub = function (y) {
420
469
  var xc = x.c.slice(), xe = x.e, yc = y.c, ye = y.e;
421
470
  // Either zero?
422
471
  if (!xc[0] || !yc[0]) {
472
+ var dp = Math.max(scaleOf(x), scaleOf(y));
423
473
  if (yc[0]) {
424
474
  y.s = -b;
425
475
  }
@@ -429,7 +479,7 @@ P.minus = P.sub = function (y) {
429
479
  else {
430
480
  y.s = 1;
431
481
  }
432
- return y;
482
+ return padScale(y, dp);
433
483
  }
434
484
  // Determine which is the bigger number. Prepend zeros to equalise exponents.
435
485
  if (a = xe - ye) {
@@ -480,9 +530,8 @@ P.minus = P.sub = function (y) {
480
530
  }
481
531
  xc[j] -= yc[j];
482
532
  }
483
- // Remove trailing zeros.
484
- for (; xc[--b] === 0;)
485
- xc.pop();
533
+ // The result keeps the larger scale.
534
+ var dp = xc.length - ye - 1;
486
535
  // Remove leading zeros and adjust exponent accordingly.
487
536
  for (; xc[0] === 0;) {
488
537
  xc.shift();
@@ -492,11 +541,12 @@ P.minus = P.sub = function (y) {
492
541
  // n - n = +0
493
542
  y.s = 1;
494
543
  // Result must be zero.
495
- xc = [ye = 0];
544
+ xc = [0];
545
+ ye = -dp;
496
546
  }
497
547
  y.c = xc;
498
548
  y.e = ye;
499
- return y;
549
+ return fit(y);
500
550
  };
501
551
  /*
502
552
  * Return a new Big whose value is the value of this Big modulo the value of Big y.
@@ -512,12 +562,7 @@ P.mod = function (y) {
512
562
  y.s = b;
513
563
  if (ygtx)
514
564
  return new Big(x);
515
- a = Big.DP;
516
- b = Big.RM;
517
- Big.DP = Big.RM = 0;
518
- x = x.div(y);
519
- Big.DP = a;
520
- Big.RM = b;
565
+ x = divide(x, new Big(y), 0, 0);
521
566
  return this.minus(x.times(y));
522
567
  };
523
568
  /*
@@ -533,6 +578,7 @@ P.plus = P.add = function (y) {
533
578
  var xe = x.e, xc = x.c, ye = y.e, yc = y.c;
534
579
  // Either zero?
535
580
  if (!xc[0] || !yc[0]) {
581
+ var dp = Math.max(scaleOf(x), scaleOf(y));
536
582
  if (!yc[0]) {
537
583
  if (xc[0]) {
538
584
  y = new Big(x);
@@ -541,7 +587,7 @@ P.plus = P.add = function (y) {
541
587
  y.s = x.s;
542
588
  }
543
589
  }
544
- return y;
590
+ return padScale(y, dp);
545
591
  }
546
592
  xc = xc.slice();
547
593
  // Prepend zeros to equalise exponents.
@@ -575,12 +621,9 @@ P.plus = P.add = function (y) {
575
621
  xc.unshift(k);
576
622
  ++ye;
577
623
  }
578
- // Remove trailing zeros.
579
- for (e = xc.length; xc[--e] === 0;)
580
- xc.pop();
581
624
  y.c = xc;
582
625
  y.e = ye;
583
- return y;
626
+ return fit(y);
584
627
  };
585
628
  /*
586
629
  * Return a Big whose value is the value of this Big raised to the power n.
@@ -681,7 +724,9 @@ P.times = P.mul = function (y) {
681
724
  y.s = x.s == y.s ? 1 : -1;
682
725
  // Return signed 0 if either 0.
683
726
  if (!xc[0] || !yc[0]) {
684
- y.c = [y.e = 0];
727
+ var dp = scaleOf(x) + scaleOf(y);
728
+ y.c = [0];
729
+ y.e = -dp;
685
730
  return y;
686
731
  }
687
732
  // Initialise exponent of result as x.e + y.e.
@@ -717,11 +762,8 @@ P.times = P.mul = function (y) {
717
762
  ++y.e;
718
763
  else
719
764
  c.shift();
720
- // Remove trailing zeros.
721
- for (i = c.length; !c[--i];)
722
- c.pop();
723
765
  y.c = c;
724
- return y;
766
+ return fit(y);
725
767
  };
726
768
  /*
727
769
  * Return a string representing the value of this Big in exponential notation rounded to dp fixed
@@ -731,7 +773,7 @@ P.times = P.mul = function (y) {
731
773
  * rm? {number} Rounding mode: 0 (down), 1 (half-up), 2 (half-even) or 3 (up).
732
774
  */
733
775
  P.toExponential = function (dp, rm) {
734
- var x = this, n = hasNonzeroDigit(x);
776
+ var x = this, n = x.c[0];
735
777
  if (dp !== UNDEFINED) {
736
778
  if (dp !== ~~dp || dp < 0 || dp > MAX_DP) {
737
779
  throw Error(INVALID_DP);
@@ -740,7 +782,7 @@ P.toExponential = function (dp, rm) {
740
782
  for (; x.c.length < dp;)
741
783
  x.c.push(0);
742
784
  }
743
- return stringify(x, true, n);
785
+ return stringify(x, true, !!n);
744
786
  };
745
787
  /*
746
788
  * Return a string representing the value of this Big in normal notation rounded to dp fixed
@@ -758,13 +800,12 @@ P.toFixed = function (dp, rm) {
758
800
  if (dp !== ~~dp || dp < 0 || dp > MAX_DP) {
759
801
  throw Error(INVALID_DP);
760
802
  }
761
- x = new x.constructor(x);
762
- x = round(x, dp + x.e + 1, rm);
803
+ x = round(new x.constructor(x), dp + x.e + 1, rm);
763
804
  // x.e may have changed if the value is rounded up.
764
805
  for (dp = dp + x.e + 1; x.c.length < dp;)
765
806
  x.c.push(0);
766
807
  }
767
- return stringify(x, false, hasNonzeroDigit(x));
808
+ return stringify(x, false, !!x.c[0]);
768
809
  };
769
810
  /*
770
811
  * Return a string representing the value of this Big.
@@ -774,7 +815,7 @@ P.toFixed = function (dp, rm) {
774
815
  */
775
816
  P.toJSON = P.toString = function () {
776
817
  var x = this, Big = x.constructor;
777
- return stringify(x, x.e <= Big.NE || x.e >= Big.PE, hasNonzeroDigit(x));
818
+ return stringify(x, x.e <= Big.NE || x.e >= Big.PE, !!x.c[0]);
778
819
  };
779
820
  /*
780
821
  * Return the value of this Big as a primitve number.
@@ -796,16 +837,18 @@ P.toNumber = function () {
796
837
  * rm? {number} Rounding mode: 0 (down), 1 (half-up), 2 (half-even) or 3 (up).
797
838
  */
798
839
  P.toPrecision = function (sd, rm) {
799
- var x = this, Big = x.constructor, n = hasNonzeroDigit(x);
840
+ var x = this, Big = x.constructor, n = x.c[0];
800
841
  if (sd !== UNDEFINED) {
801
842
  if (sd !== ~~sd || sd < 1 || sd > MAX_DP) {
802
843
  throw Error(INVALID + 'precision');
803
844
  }
804
845
  x = round(new Big(x), sd, rm);
846
+ if (!x.c[0])
847
+ x.e = 0;
805
848
  for (; x.c.length < sd;)
806
849
  x.c.push(0);
807
850
  }
808
- return stringify(x, sd <= x.e || x.e <= Big.NE || x.e >= Big.PE, n);
851
+ return stringify(x, sd <= x.e || x.e <= Big.NE || x.e >= Big.PE, !!n);
809
852
  };
810
853
  /*
811
854
  * Return a string representing the value of this Big.
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "private": false,
4
4
  "type": "module",
5
5
  "name": "@fable-org/fable-library-js",
6
- "version": "2.8.1",
6
+ "version": "2.10.0",
7
7
  "description": "Core library used by F# projects compiled with fable.io",
8
8
  "author": "Fable Contributors",
9
9
  "license": "MIT",