@fable-org/fable-library-ts 2.2.0 → 2.3.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.ts CHANGED
@@ -2,6 +2,7 @@ import { OperationCanceledException, Trampoline } from "./AsyncBuilder.ts";
2
2
  import { Continuation, Continuations } from "./AsyncBuilder.ts";
3
3
  import { Async, IAsyncContext, CancellationToken } from "./AsyncBuilder.ts";
4
4
  import { protectedCont, protectedBind, protectedReturn } from "./AsyncBuilder.ts";
5
+ import type { IEvent$2 } from "./Event.ts";
5
6
  import { FSharpChoice$2_$union, Choice_makeChoice1Of2, Choice_makeChoice2Of2 } from "./Choice.ts";
6
7
  import { TimeoutException_$ctor } from "./System.ts";
7
8
  import { Exception } from "./Util.ts";
@@ -86,6 +87,23 @@ export function awaitPromise<T>(p: Promise<T>) {
86
87
  ? conts[2] : conts[1])(err)));
87
88
  }
88
89
 
90
+ export function awaitEvent<Del extends Function, T>(event: IEvent$2<Del, T>, cancelAction?: () => void): Async<T> {
91
+ return protectedCont((ctx: IAsyncContext<T>) => {
92
+ let tokenId: number;
93
+ const handler = ((_sender: unknown, arg: T) => {
94
+ ctx.cancelToken.removeListener(tokenId);
95
+ event.RemoveHandler(handler as unknown as Del);
96
+ ctx.onSuccess(arg);
97
+ }) as unknown as Del;
98
+ tokenId = ctx.cancelToken.addListener(() => {
99
+ event.RemoveHandler(handler);
100
+ if (cancelAction != null) { cancelAction(); }
101
+ ctx.onCancel(new OperationCanceledException());
102
+ });
103
+ event.AddHandler(handler);
104
+ });
105
+ }
106
+
89
107
  export function cancellationToken() {
90
108
  return protectedCont((ctx: IAsyncContext<CancellationToken>) => ctx.onSuccess(ctx.cancelToken));
91
109
  }
package/CHANGELOG.md CHANGED
@@ -1,5 +1,5 @@
1
1
  ---
2
- last_commit_released: c9b3ee2429a4688946c1936e27df730837428070
2
+ last_commit_released: c977d78b39225a51c7bd051a1fe363ed0ccbe201
3
3
  updaters:
4
4
  - package.json:
5
5
  file: package.json
@@ -15,6 +15,22 @@ 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.3.0 - 2026-06-30
19
+
20
+ ### 🚀 Features
21
+
22
+ * *(js/ts)* Map task { } to Promise<T> ([97f54d36](https://github.com/fable-compiler/Fable/commit/97f54d3692e5ba881c249b289c20b4fad5ac27e2))
23
+ * *(js/ts/python/beam)* Add support for `Async.AwaitEvent` (#4693) ([71c98179](https://github.com/fable-compiler/Fable/commit/71c981792b6ff99cd417bb2e70a13a9d55ed72cf))
24
+
25
+ ### 🐞 Bug Fixes
26
+
27
+ * *(js)* Respect StringComparison in String.IndexOf/LastIndexOf (#4681) ([7cb92d52](https://github.com/fable-compiler/Fable/commit/7cb92d522e5dc8dfe51351565504c59a94c2f408))
28
+ * *(js/ts)* Int list is not a union type (#4698) ([ecbecbe7](https://github.com/fable-compiler/Fable/commit/ecbecbe756610fd83a621c005e97eda92bbc332a))
29
+ * *(js/ts)* Pass TypeInfo to getRecordFields to handle None fields in anonymous records (#4704) ([c977d78b](https://github.com/fable-compiler/Fable/commit/c977d78b39225a51c7bd051a1fe363ed0ccbe201))
30
+ * *(ts)* Widen isEnumDefined value parameter to accept any value (#4690) ([0d6498dd](https://github.com/fable-compiler/Fable/commit/0d6498ddf65e0e18918690633da23b04b78479f6))
31
+
32
+ <strong><small>[View changes on Github](https://github.com/fable-compiler/Fable/compare/c9b3ee2429a4688946c1936e27df730837428070..c977d78b39225a51c7bd051a1fe363ed0ccbe201)</small></strong>
33
+
18
34
  ## 2.2.0 - 2026-06-24
19
35
 
20
36
  ### 🚀 Features
package/Reflection.ts CHANGED
@@ -171,7 +171,18 @@ export function option_type(generic: TypeInfo): TypeInfo {
171
171
  }
172
172
 
173
173
  export function list_type(generic: TypeInfo): TypeInfo {
174
- return new TypeInfo("Microsoft.FSharp.Collections.FSharpList`1", [generic]);
174
+ const t: TypeInfo = new TypeInfo(
175
+ "Microsoft.FSharp.Collections.FSharpList`1",
176
+ [generic],
177
+ undefined,
178
+ undefined,
179
+ undefined,
180
+ () => [
181
+ new CaseInfo(t, 0, "Empty"),
182
+ new CaseInfo(t, 1, "Cons", [["Head", generic], ["Tail", t]])
183
+ ]
184
+ );
185
+ return t;
175
186
  }
176
187
 
177
188
  export function array_type(generic: TypeInfo): TypeInfo {
@@ -389,7 +400,7 @@ export function getEnumName(t: TypeInfo, v: number): string {
389
400
  return getEnumCase(t, v)[0];
390
401
  }
391
402
 
392
- export function isEnumDefined(t: TypeInfo, v: string | number): boolean {
403
+ export function isEnumDefined(t: TypeInfo, v: any): boolean {
393
404
  try {
394
405
  const kv = getEnumCase(t, v);
395
406
  return kv[0] != null && kv[0] !== "";
@@ -477,8 +488,14 @@ export function getUnionCaseFields(uci: CaseInfo): FieldInfo[] {
477
488
 
478
489
  // This is used as replacement of `FSharpValue.GetRecordFields`
479
490
  // For `FSharpTypes.GetRecordFields` see `getRecordElements`
480
- // Object.keys returns keys in the order they were added to the object
481
- export function getRecordFields(v: any): MutableArray<any> {
491
+ // TypeInfo is used when available to enumerate fields by name: anonymous record None fields
492
+ // are omitted from the JS object, so Object.keys alone would miss them.
493
+ // Boxed anonymous record would are still not covered but this is the best we can do for now
494
+ // without adding a __fields__ to every anonymous record being created
495
+ export function getRecordFields(v: any, t: TypeInfo): MutableArray<any> {
496
+ if (t.fields != null) {
497
+ return t.fields().map(([key, _]) => v[key]);
498
+ }
482
499
  return Object.keys(v).map((k) => v[k]);
483
500
  }
484
501
 
package/String.ts CHANGED
@@ -99,6 +99,32 @@ export function contains(str: string, pattern: string, ic: boolean | StringCompa
99
99
  return false;
100
100
  }
101
101
 
102
+ export function indexOf(str: string, searchValue: string, comparison: StringComparison, startIndex: number = 0): number {
103
+ if (comparison === StringComparison.Ordinal) { // fast path
104
+ return str.indexOf(searchValue, startIndex);
105
+ }
106
+ const len = searchValue.length;
107
+ for (let i = Math.max(startIndex, 0); i <= str.length - len; i++) {
108
+ if (cmp(str.slice(i, i + len), searchValue, comparison) === 0) {
109
+ return i;
110
+ }
111
+ }
112
+ return -1;
113
+ }
114
+
115
+ export function lastIndexOf(str: string, searchValue: string, comparison: StringComparison, startIndex: number = str.length - 1): number {
116
+ if (comparison === StringComparison.Ordinal) { // fast path
117
+ return str.lastIndexOf(searchValue, startIndex);
118
+ }
119
+ const len = searchValue.length;
120
+ for (let i = Math.min(startIndex, str.length - len); i >= 0; i--) {
121
+ if (cmp(str.slice(i, i + len), searchValue, comparison) === 0) {
122
+ return i;
123
+ }
124
+ }
125
+ return -1;
126
+ }
127
+
102
128
  export function indexOfAny(str: string, anyOf: string[], ...args: number[]) {
103
129
  if (str == null || str === "") {
104
130
  return -1;
package/Task.ts ADDED
@@ -0,0 +1,37 @@
1
+ import { OperationCanceledException } from "./AsyncBuilder.ts";
2
+
3
+ export class TaskCompletionSource<T> {
4
+ private _resolve!: (value: T) => void;
5
+ private _reject!: (reason?: unknown) => void;
6
+ public task: Promise<T>;
7
+
8
+ constructor() {
9
+ this.task = new Promise<T>((resolve, reject) => {
10
+ this._resolve = resolve;
11
+ this._reject = reject;
12
+ });
13
+ }
14
+
15
+ SetResult(value: T): void { this._resolve(value); }
16
+ SetException(error: unknown): void { this._reject(error); }
17
+ SetCancelled(): void { this._reject(new OperationCanceledException()); }
18
+ get_Task(): Promise<T> { return this.task; }
19
+ }
20
+
21
+ export function fromResult<T>(value: T): Promise<T> {
22
+ return Promise.resolve(value);
23
+ }
24
+
25
+ export function zero(): Promise<void> {
26
+ return Promise.resolve();
27
+ }
28
+
29
+ // Task<T> = Promise<T> in JS/TS. GetAwaiter/GetResult return the Promise itself;
30
+ // callers should use Async.AwaitTask to extract the value in an async context.
31
+ export function getAwaiter<T>(t: Promise<T>): Promise<T> {
32
+ return t;
33
+ }
34
+
35
+ export function getResult<T>(t: Promise<T>): Promise<T> {
36
+ return t;
37
+ }
package/TaskBuilder.ts ADDED
@@ -0,0 +1,85 @@
1
+ import { IDisposable } from "./Util.ts";
2
+ import { zero } from "./Task.ts";
3
+
4
+ export class TaskBuilder {
5
+ public Bind<T, U>(computation: Promise<T>, binder: (x: T) => Promise<U>): Promise<U> {
6
+ return computation.then(binder);
7
+ }
8
+
9
+ public Combine<T>(computation1: Promise<void>, computation2: () => Promise<T>): Promise<T> {
10
+ return computation1.then(computation2);
11
+ }
12
+
13
+ public Delay<T>(generator: () => Promise<T>): () => Promise<T> {
14
+ return generator;
15
+ }
16
+
17
+ public For<T>(sequence: Iterable<T>, body: (x: T) => Promise<void>): Promise<void> {
18
+ const iter = sequence[Symbol.iterator]();
19
+ let cur = iter.next();
20
+ return this.While(
21
+ () => !cur.done,
22
+ this.Delay(() => {
23
+ const res = body(cur.value!);
24
+ cur = iter.next();
25
+ return res;
26
+ })
27
+ );
28
+ }
29
+
30
+ public Return<T>(value?: T): Promise<T | undefined> {
31
+ return Promise.resolve(value);
32
+ }
33
+
34
+ public ReturnFrom<T>(computation: Promise<T>): Promise<T> {
35
+ return computation;
36
+ }
37
+
38
+ public TryFinally<T>(computation: () => Promise<T>, compensation: () => void): Promise<T> {
39
+ try {
40
+ return computation().finally(compensation);
41
+ } catch (e) {
42
+ compensation();
43
+ return Promise.reject(e);
44
+ }
45
+ }
46
+
47
+ public TryWith<T>(computation: () => Promise<T>, catchHandler: (e: unknown) => Promise<T>): Promise<T> {
48
+ try {
49
+ return computation().catch(catchHandler);
50
+ } catch (e) {
51
+ try {
52
+ return catchHandler(e);
53
+ } catch (e2) {
54
+ return Promise.reject(e2);
55
+ }
56
+ }
57
+ }
58
+
59
+ public Using<T extends IDisposable, U>(resource: T, binder: (x: T) => Promise<U>): Promise<U> {
60
+ return this.TryFinally(() => binder(resource), () => resource.Dispose());
61
+ }
62
+
63
+ public While(guard: () => boolean, computation: () => Promise<void>): Promise<void> {
64
+ return (async () => {
65
+ while (guard()) {
66
+ await computation();
67
+ }
68
+ })();
69
+ }
70
+
71
+ public Zero(): Promise<void> {
72
+ return zero();
73
+ }
74
+
75
+ public Run<T>(computation: () => Promise<T>): Promise<T> {
76
+ try {
77
+ return computation();
78
+ } catch (e) {
79
+ return Promise.reject(e);
80
+ }
81
+ }
82
+ }
83
+
84
+ export const singleton = new TaskBuilder();
85
+ export function task(): TaskBuilder { return singleton; }
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "private": false,
4
4
  "type": "module",
5
5
  "name": "@fable-org/fable-library-ts",
6
- "version": "2.2.0",
6
+ "version": "2.3.0",
7
7
  "description": "Core library used by F# projects compiled with fable.io",
8
8
  "author": "Fable Contributors",
9
9
  "license": "MIT",