@fable-org/fable-library-js 2.7.1 → 2.8.1
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 +21 -7
- package/AsyncBuilder.js +51 -6
- package/CHANGELOG.md +23 -1
- package/CollectionUtil.js +10 -16
- package/MailboxProcessor.js +19 -3
- package/MutableMap.js +5 -1
- package/MutableSet.js +5 -1
- package/Util.js +15 -3
- package/package.json +1 -1
package/Async.js
CHANGED
|
@@ -86,6 +86,9 @@ export function awaitEvent(event, cancelAction) {
|
|
|
86
86
|
}
|
|
87
87
|
ctx.onCancel(new OperationCanceledException());
|
|
88
88
|
});
|
|
89
|
+
if (ctx.cancelToken.isCancelled) {
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
89
92
|
event.AddHandler(handler);
|
|
90
93
|
});
|
|
91
94
|
}
|
|
@@ -127,15 +130,26 @@ export function sequential(computations) {
|
|
|
127
130
|
}
|
|
128
131
|
export function sleep(millisecondsDueTime) {
|
|
129
132
|
return protectedCont((ctx) => {
|
|
133
|
+
let completed = false;
|
|
130
134
|
let tokenId;
|
|
131
|
-
const
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
135
|
+
const settle = (cancelled) => {
|
|
136
|
+
if (completed) {
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
completed = true;
|
|
136
140
|
clearTimeout(timeoutId);
|
|
137
|
-
|
|
138
|
-
|
|
141
|
+
if (tokenId !== undefined) {
|
|
142
|
+
ctx.cancelToken.removeListener(tokenId);
|
|
143
|
+
}
|
|
144
|
+
if (cancelled) {
|
|
145
|
+
ctx.onCancel(new OperationCanceledException());
|
|
146
|
+
}
|
|
147
|
+
else {
|
|
148
|
+
ctx.onSuccess(void 0);
|
|
149
|
+
}
|
|
150
|
+
};
|
|
151
|
+
const timeoutId = setTimeout(() => settle(false), millisecondsDueTime);
|
|
152
|
+
tokenId = ctx.cancelToken.addListener(() => settle(true));
|
|
139
153
|
});
|
|
140
154
|
}
|
|
141
155
|
export function start(computation, cancellationToken) {
|
package/AsyncBuilder.js
CHANGED
|
@@ -1,4 +1,12 @@
|
|
|
1
1
|
import { Exception, ensureErrorOrException } from "./Util.js";
|
|
2
|
+
// AggregateError requires ES2021; the runtime also supports ES2020 hosts.
|
|
3
|
+
export class CancellationCallbackError extends Exception {
|
|
4
|
+
constructor(errors) {
|
|
5
|
+
// invariant: F# InnerException exposes the first failure without discarding the remaining errors
|
|
6
|
+
super("Cancellation callbacks failed", errors[0]);
|
|
7
|
+
this.errors = errors;
|
|
8
|
+
}
|
|
9
|
+
}
|
|
2
10
|
export class CancellationToken {
|
|
3
11
|
constructor(cancelled = false) {
|
|
4
12
|
this._id = 0;
|
|
@@ -11,14 +19,35 @@ export class CancellationToken {
|
|
|
11
19
|
cancel() {
|
|
12
20
|
if (!this._cancelled) {
|
|
13
21
|
this._cancelled = true;
|
|
14
|
-
|
|
15
|
-
|
|
22
|
+
// decision: visit newest registrations first, matching .NET; disposal can skip pending callbacks
|
|
23
|
+
const pending = Array.from(this._listeners.keys()).reverse();
|
|
24
|
+
const errors = [];
|
|
25
|
+
for (const id of pending) {
|
|
26
|
+
const listener = this._listeners.get(id);
|
|
27
|
+
this._listeners.delete(id);
|
|
28
|
+
if (listener) {
|
|
29
|
+
try {
|
|
30
|
+
listener();
|
|
31
|
+
}
|
|
32
|
+
catch (error) {
|
|
33
|
+
errors.push(error);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
if (errors.length > 0) {
|
|
38
|
+
throw new CancellationCallbackError(errors);
|
|
16
39
|
}
|
|
17
40
|
}
|
|
18
41
|
}
|
|
19
42
|
addListener(f) {
|
|
20
43
|
const id = this._id;
|
|
21
|
-
this.
|
|
44
|
+
this._id++;
|
|
45
|
+
if (this._cancelled) {
|
|
46
|
+
f();
|
|
47
|
+
}
|
|
48
|
+
else {
|
|
49
|
+
this._listeners.set(id, f);
|
|
50
|
+
}
|
|
22
51
|
return id;
|
|
23
52
|
}
|
|
24
53
|
removeListener(id) {
|
|
@@ -148,15 +177,31 @@ export class AsyncBuilder {
|
|
|
148
177
|
return protectedCont((ctx) => {
|
|
149
178
|
computation({
|
|
150
179
|
onSuccess: (x) => {
|
|
151
|
-
|
|
180
|
+
try {
|
|
181
|
+
compensation();
|
|
182
|
+
}
|
|
183
|
+
catch (error) {
|
|
184
|
+
ctx.onError(ensureErrorOrException(error));
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
152
187
|
ctx.onSuccess(x);
|
|
153
188
|
},
|
|
154
189
|
onError: (x) => {
|
|
155
|
-
|
|
190
|
+
try {
|
|
191
|
+
compensation();
|
|
192
|
+
}
|
|
193
|
+
catch (error) {
|
|
194
|
+
ctx.onError(ensureErrorOrException(error));
|
|
195
|
+
return;
|
|
196
|
+
}
|
|
156
197
|
ctx.onError(x);
|
|
157
198
|
},
|
|
158
199
|
onCancel: (x) => {
|
|
159
|
-
compensation
|
|
200
|
+
// decision: cancellation remains terminal when compensation throws, matching .NET Async.TryFinally
|
|
201
|
+
try {
|
|
202
|
+
compensation();
|
|
203
|
+
}
|
|
204
|
+
catch { /* Cancellation takes precedence. */ }
|
|
160
205
|
ctx.onCancel(x);
|
|
161
206
|
},
|
|
162
207
|
cancelToken: ctx.cancelToken,
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
---
|
|
2
|
-
last_commit_released:
|
|
2
|
+
last_commit_released: 148f989e1b37b3f6560c3dda615b6a3a8efffcb8
|
|
3
3
|
updaters:
|
|
4
4
|
- package.json:
|
|
5
5
|
file: package.json
|
|
@@ -15,6 +15,28 @@ 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.8.1 - 2026-10-12
|
|
19
|
+
|
|
20
|
+
### 🐞 Bug Fixes
|
|
21
|
+
|
|
22
|
+
* *(js/ts/python)* Synchronize cancellation registrations (#5036) ([07db576c](https://github.com/fable-compiler/Fable/commit/07db576c4ae16dcef9d1773ac4d6c70b2a223f0a))
|
|
23
|
+
* *(js/ts/python)* Settle cancelled delays and finalizers once (#5038) ([a9802484](https://github.com/fable-compiler/Fable/commit/a9802484c8cdeadd5203123d76f42e375c1c3159))
|
|
24
|
+
|
|
25
|
+
<strong><small>[View changes on Github](https://github.com/fable-compiler/Fable/compare/2369fbd24fafe851544c1ed6eb54f0bfc2b75f36..148f989e1b37b3f6560c3dda615b6a3a8efffcb8)</small></strong>
|
|
26
|
+
|
|
27
|
+
## 2.8.0 - 2026-09-25
|
|
28
|
+
|
|
29
|
+
### 🐞 Bug Fixes
|
|
30
|
+
|
|
31
|
+
* *(js/ts)* Fix Dictionary.Remove and HashSet.Remove to remove empty buckets (#4985) ([aea61b56](https://github.com/fable-compiler/Fable/commit/aea61b562c461a9b383e805f06f68a3b21ed45c2))
|
|
32
|
+
* *(js/ts)* Clear custom System.Collections.IList implementations ([2369fbd2](https://github.com/fable-compiler/Fable/commit/2369fbd24fafe851544c1ed6eb54f0bfc2b75f36))
|
|
33
|
+
|
|
34
|
+
### ⚡ Performance Improvements
|
|
35
|
+
|
|
36
|
+
* *(js/ts)* Faster `resizeArray.Clear()` in JS/TS ([32d85add](https://github.com/fable-compiler/Fable/commit/32d85add01422547dba03b15e0a0787d8afb031b))
|
|
37
|
+
|
|
38
|
+
<strong><small>[View changes on Github](https://github.com/fable-compiler/Fable/compare/4a441b1926387a42db6a3a4294acd97b02760ca3..2369fbd24fafe851544c1ed6eb54f0bfc2b75f36)</small></strong>
|
|
39
|
+
|
|
18
40
|
## 2.7.1 - 2026-09-11
|
|
19
41
|
|
|
20
42
|
### 🐞 Bug Fixes
|
package/CollectionUtil.js
CHANGED
|
@@ -161,22 +161,16 @@ export function clear(col) {
|
|
|
161
161
|
if (typeof col["System.Collections.Generic.ICollection`1.Clear"] === "function") {
|
|
162
162
|
return col["System.Collections.Generic.ICollection`1.Clear"](); // collection
|
|
163
163
|
}
|
|
164
|
+
else if (Array.isArray(col)) {
|
|
165
|
+
col.length = 0;
|
|
166
|
+
}
|
|
167
|
+
else if (ArrayBuffer.isView(col)) {
|
|
168
|
+
// TODO: throw for typed arrays?
|
|
169
|
+
}
|
|
170
|
+
else if (typeof col.clear === "function") {
|
|
171
|
+
col.clear(); // map, set
|
|
172
|
+
}
|
|
164
173
|
else {
|
|
165
|
-
|
|
166
|
-
if (ArrayBuffer.isView(col)) {
|
|
167
|
-
// TODO: throw for typed arrays?
|
|
168
|
-
}
|
|
169
|
-
else {
|
|
170
|
-
col.splice(0); // array, resize array
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
|
-
else {
|
|
174
|
-
if (typeof col.clear === "function") {
|
|
175
|
-
col.clear(); // map, set
|
|
176
|
-
}
|
|
177
|
-
else {
|
|
178
|
-
// TODO: throw for other collections?
|
|
179
|
-
}
|
|
180
|
-
}
|
|
174
|
+
// TODO: throw for other collections?
|
|
181
175
|
}
|
|
182
176
|
}
|
package/MailboxProcessor.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { defaultCancellationToken } from "./Async.js";
|
|
2
2
|
import { fromContinuations } from "./Async.js";
|
|
3
3
|
import { startImmediate } from "./Async.js";
|
|
4
|
+
import { OperationCanceledException, protectedCont } from "./AsyncBuilder.js";
|
|
4
5
|
import { Exception } from "./Util.js";
|
|
5
6
|
class QueueCell {
|
|
6
7
|
constructor(message) {
|
|
@@ -55,12 +56,27 @@ export function startInstance($this) {
|
|
|
55
56
|
startImmediate($this.body($this), $this.cancellationToken);
|
|
56
57
|
}
|
|
57
58
|
export function receive($this) {
|
|
58
|
-
return
|
|
59
|
+
return protectedCont((ctx) => {
|
|
59
60
|
if ($this.continuation) {
|
|
60
61
|
throw new Exception("Receive can only be called once!");
|
|
61
62
|
}
|
|
62
|
-
|
|
63
|
-
|
|
63
|
+
let tokenId;
|
|
64
|
+
const continuation = (message) => {
|
|
65
|
+
if (tokenId !== undefined) {
|
|
66
|
+
ctx.cancelToken.removeListener(tokenId);
|
|
67
|
+
}
|
|
68
|
+
ctx.onSuccess(message);
|
|
69
|
+
};
|
|
70
|
+
$this.continuation = continuation;
|
|
71
|
+
tokenId = ctx.cancelToken.addListener(() => {
|
|
72
|
+
if ($this.continuation === continuation) {
|
|
73
|
+
delete $this.continuation;
|
|
74
|
+
ctx.onCancel(new OperationCanceledException());
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
if ($this.continuation === continuation) {
|
|
78
|
+
__processEvents($this);
|
|
79
|
+
}
|
|
64
80
|
});
|
|
65
81
|
}
|
|
66
82
|
export function post($this, message) {
|
package/MutableMap.js
CHANGED
|
@@ -330,7 +330,11 @@ export function Dictionary__Remove_2B595(this$, k) {
|
|
|
330
330
|
}
|
|
331
331
|
switch (matchResult) {
|
|
332
332
|
case 0: {
|
|
333
|
-
|
|
333
|
+
const h_1 = matchValue[1] | 0;
|
|
334
|
+
getItemFromDict(this$.hashMap, h_1).splice(matchValue[2], 1);
|
|
335
|
+
if (getItemFromDict(this$.hashMap, h_1).length === 0) {
|
|
336
|
+
this$.hashMap.delete(h_1);
|
|
337
|
+
}
|
|
334
338
|
return true;
|
|
335
339
|
}
|
|
336
340
|
default:
|
package/MutableSet.js
CHANGED
|
@@ -224,7 +224,11 @@ export function HashSet__Remove_2B595(this$, k) {
|
|
|
224
224
|
}
|
|
225
225
|
switch (matchResult) {
|
|
226
226
|
case 0: {
|
|
227
|
-
|
|
227
|
+
const h_1 = matchValue[1] | 0;
|
|
228
|
+
getItemFromDict(this$.hashMap, h_1).splice(matchValue[2], 1);
|
|
229
|
+
if (getItemFromDict(this$.hashMap, h_1).length === 0) {
|
|
230
|
+
this$.hashMap.delete(h_1);
|
|
231
|
+
}
|
|
228
232
|
return true;
|
|
229
233
|
}
|
|
230
234
|
default:
|
package/Util.js
CHANGED
|
@@ -559,11 +559,23 @@ export function count(col) {
|
|
|
559
559
|
}
|
|
560
560
|
}
|
|
561
561
|
export function clear(col) {
|
|
562
|
-
if (
|
|
563
|
-
col.
|
|
562
|
+
if (Array.isArray(col)) {
|
|
563
|
+
col.length = 0;
|
|
564
|
+
}
|
|
565
|
+
else if (ArrayBuffer.isView(col)) {
|
|
566
|
+
// TODO: throw for typed arrays?
|
|
567
|
+
}
|
|
568
|
+
else if (typeof col.clear === "function") {
|
|
569
|
+
col.clear(); // map, set
|
|
570
|
+
}
|
|
571
|
+
else if (typeof col["System.Collections.Generic.ICollection`1.Clear"] === "function") {
|
|
572
|
+
col["System.Collections.Generic.ICollection`1.Clear"](); // collection
|
|
573
|
+
}
|
|
574
|
+
else if (typeof col["System.Collections.IList.Clear"] === "function") {
|
|
575
|
+
col["System.Collections.IList.Clear"](); // collection
|
|
564
576
|
}
|
|
565
577
|
else {
|
|
566
|
-
|
|
578
|
+
// TODO: throw for other collections?
|
|
567
579
|
}
|
|
568
580
|
}
|
|
569
581
|
const curried = new WeakMap();
|
package/package.json
CHANGED