@fable-org/fable-library-ts 2.2.0 → 2.4.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 +18 -0
- package/CHANGELOG.md +30 -1
- package/Date.ts +1 -1
- package/Environment.ts +7 -0
- package/File.ts +35 -0
- package/Path.ts +37 -0
- package/Reflection.ts +21 -4
- package/String.ts +26 -0
- package/Task.ts +37 -0
- package/TaskBuilder.ts +85 -0
- package/package.json +1 -1
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:
|
|
2
|
+
last_commit_released: 7f915f1dd66b9a5fbbd56f858b07d39b98519b65
|
|
3
3
|
updaters:
|
|
4
4
|
- package.json:
|
|
5
5
|
file: package.json
|
|
@@ -15,6 +15,35 @@ 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.4.0 - 2026-07-05
|
|
19
|
+
|
|
20
|
+
### 🚀 Features
|
|
21
|
+
|
|
22
|
+
* *(js/ts)* Add `System.IO.Path` and `System.IO.File` API support ([ec99fac5](https://github.com/fable-compiler/Fable/commit/ec99fac5fd7a9428ee54f66f878ef4edad0904cb))
|
|
23
|
+
* *(js/ts/python)* Add Environment and Console.Error support ([6e082f95](https://github.com/fable-compiler/Fable/commit/6e082f955140676da3388a43b55378ea4ad20f1c))
|
|
24
|
+
|
|
25
|
+
### 🐞 Bug Fixes
|
|
26
|
+
|
|
27
|
+
* *(js/ts)* DateTime.ToString() and %A are now consistent across all DateTimeKind values (#4714) ([de98b0e4](https://github.com/fable-compiler/Fable/commit/de98b0e466c8824b0ba8b941d20efae70d232abb))
|
|
28
|
+
|
|
29
|
+
<strong><small>[View changes on Github](https://github.com/fable-compiler/Fable/compare/c977d78b39225a51c7bd051a1fe363ed0ccbe201..7f915f1dd66b9a5fbbd56f858b07d39b98519b65)</small></strong>
|
|
30
|
+
|
|
31
|
+
## 2.3.0 - 2026-06-30
|
|
32
|
+
|
|
33
|
+
### 🚀 Features
|
|
34
|
+
|
|
35
|
+
* *(js/ts)* Map task { } to Promise<T> ([97f54d36](https://github.com/fable-compiler/Fable/commit/97f54d3692e5ba881c249b289c20b4fad5ac27e2))
|
|
36
|
+
* *(js/ts/python/beam)* Add support for `Async.AwaitEvent` (#4693) ([71c98179](https://github.com/fable-compiler/Fable/commit/71c981792b6ff99cd417bb2e70a13a9d55ed72cf))
|
|
37
|
+
|
|
38
|
+
### 🐞 Bug Fixes
|
|
39
|
+
|
|
40
|
+
* *(js)* Respect StringComparison in String.IndexOf/LastIndexOf (#4681) ([7cb92d52](https://github.com/fable-compiler/Fable/commit/7cb92d522e5dc8dfe51351565504c59a94c2f408))
|
|
41
|
+
* *(js/ts)* Int list is not a union type (#4698) ([ecbecbe7](https://github.com/fable-compiler/Fable/commit/ecbecbe756610fd83a621c005e97eda92bbc332a))
|
|
42
|
+
* *(js/ts)* Pass TypeInfo to getRecordFields to handle None fields in anonymous records (#4704) ([c977d78b](https://github.com/fable-compiler/Fable/commit/c977d78b39225a51c7bd051a1fe363ed0ccbe201))
|
|
43
|
+
* *(ts)* Widen isEnumDefined value parameter to accept any value (#4690) ([0d6498dd](https://github.com/fable-compiler/Fable/commit/0d6498ddf65e0e18918690633da23b04b78479f6))
|
|
44
|
+
|
|
45
|
+
<strong><small>[View changes on Github](https://github.com/fable-compiler/Fable/compare/c9b3ee2429a4688946c1936e27df730837428070..c977d78b39225a51c7bd051a1fe363ed0ccbe201)</small></strong>
|
|
46
|
+
|
|
18
47
|
## 2.2.0 - 2026-06-24
|
|
19
48
|
|
|
20
49
|
### 🚀 Features
|
package/Date.ts
CHANGED
|
@@ -509,7 +509,7 @@ function dateToString_Y(date: IDateTime) {
|
|
|
509
509
|
function dateToStringWithKind(date: IDateTime, format?: string) {
|
|
510
510
|
const utc = date.kind === DateTimeKind.Utc;
|
|
511
511
|
if (typeof format !== "string") {
|
|
512
|
-
return
|
|
512
|
+
return dateToString_d(date) + " " + dateToString_T(date);
|
|
513
513
|
} else if (format.length === 1) {
|
|
514
514
|
switch (format) {
|
|
515
515
|
case "D": return dateToString_D(date);
|
package/Environment.ts
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
// process.env is Node-specific; there is no browser equivalent for reading
|
|
2
|
+
// environment variables, so this only works when running under Node.
|
|
3
|
+
declare const process: { env: Record<string, string | undefined> };
|
|
4
|
+
|
|
5
|
+
export function getEnvironmentVariable(name: string): string {
|
|
6
|
+
return process.env[name] as string;
|
|
7
|
+
}
|
package/File.ts
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
// This library doesn't depend on @types/node (to stay browser-compatible),
|
|
2
|
+
// so this Node.js built-in import can't be type-checked; the exported
|
|
3
|
+
// functions below still declare their own types for callers to rely on.
|
|
4
|
+
// @ts-ignore
|
|
5
|
+
import { readFileSync, writeFileSync } from "node:fs";
|
|
6
|
+
import { MutableArray } from "./Util.ts";
|
|
7
|
+
|
|
8
|
+
// byte[] is represented as a plain JS/TS array of numbers, not a typed array
|
|
9
|
+
// (see e.g. BitConverter.ts), so convert the Buffer returned by readFileSync.
|
|
10
|
+
export function readAllBytes(path: string): number[] {
|
|
11
|
+
return Array.from(readFileSync(path));
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function readAllLines(path: string): string[] {
|
|
15
|
+
const lines = readFileSync(path, "utf8").split(/\r\n|\r|\n/);
|
|
16
|
+
|
|
17
|
+
if (lines[lines.length - 1] === "") {
|
|
18
|
+
lines.pop();
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
return lines;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function readAllText(path: string): string {
|
|
25
|
+
return readFileSync(path, "utf8");
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function writeAllBytes(path: string, bytes: MutableArray<number>): void {
|
|
29
|
+
// Convert back from the plain array representation to a typed array Node's fs accepts.
|
|
30
|
+
writeFileSync(path, Uint8Array.from(bytes));
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function writeAllLines(path: string, lines: MutableArray<string>): void {
|
|
34
|
+
writeFileSync(path, lines.join("\n"));
|
|
35
|
+
}
|
package/Path.ts
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
// This library doesn't depend on @types/node (to stay browser-compatible),
|
|
2
|
+
// so these Node.js built-in imports can't be type-checked; the exported
|
|
3
|
+
// functions below still declare their own types for callers to rely on.
|
|
4
|
+
// @ts-ignore
|
|
5
|
+
import { basename, extname, join } from "node:path";
|
|
6
|
+
// @ts-ignore
|
|
7
|
+
import { tmpdir } from "node:os";
|
|
8
|
+
// @ts-ignore
|
|
9
|
+
import { writeFileSync } from "node:fs";
|
|
10
|
+
|
|
11
|
+
// Mirrors the format of .NET's Path.GetRandomFileName(): 8 random hex chars,
|
|
12
|
+
// a dot, then 3 more random hex chars.
|
|
13
|
+
export function getRandomFileName(): string {
|
|
14
|
+
const nameBytes = new Uint8Array(4);
|
|
15
|
+
crypto.getRandomValues(nameBytes);
|
|
16
|
+
const name = Array.from(nameBytes, (b) => b.toString(16).padStart(2, "0")).join("");
|
|
17
|
+
|
|
18
|
+
const extBytes = new Uint8Array(2);
|
|
19
|
+
crypto.getRandomValues(extBytes);
|
|
20
|
+
const ext = Array.from(extBytes, (b) => b.toString(16).padStart(2, "0")).join("").slice(0, 3);
|
|
21
|
+
|
|
22
|
+
return `${name}.${ext}`;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function getFileNameWithoutExtension(path: string): string {
|
|
26
|
+
return basename(path, extname(path));
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function hasExtension(path: string): boolean {
|
|
30
|
+
return extname(path) !== "";
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function getTempFileName(): string {
|
|
34
|
+
const path = join(tmpdir(), getRandomFileName());
|
|
35
|
+
writeFileSync(path, "");
|
|
36
|
+
return path;
|
|
37
|
+
}
|
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
|
-
|
|
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:
|
|
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
|
-
//
|
|
481
|
-
|
|
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