@fable-org/fable-library-ts 2.0.0 → 2.1.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/CHANGELOG.md CHANGED
@@ -1,3 +1,13 @@
1
+ ---
2
+ last_commit_released: 15eb83ed36657f75073fb0e1b4cac677e24fc9bb
3
+ updaters:
4
+ - package.json:
5
+ file: package.json
6
+ - regex:
7
+ file: ./../Fable.Transforms/Global/Compiler.fs
8
+ pattern: (?<=let JS_LIBRARY_VERSION = ").*(?=")
9
+ ---
10
+
1
11
  # Changelog
2
12
 
3
13
  All notable changes to this project will be documented in this file.
@@ -5,7 +15,33 @@ All notable changes to this project will be documented in this file.
5
15
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
16
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
17
 
8
- ## Unreleased
18
+ ## 2.1.1 - 2026-06-10
19
+
20
+ ### 🐞 Bug Fixes
21
+
22
+ * *(js/ts)* Reject JS-permissive date strings that .NET TryParse rejects (#4588) ([09003de](https://github.com/fable-compiler/Fable/commit/09003de8bc72e4f73281828d905d49aab7db3f55))
23
+ * *(js/ts)* Make Exception.ToString() return the message instead of "Exception" (#4635) ([4c36f76](https://github.com/fable-compiler/Fable/commit/4c36f761bd378da800e30657c58355bd5324a653))
24
+
25
+ <strong><small>[View changes on Github](https://github.com/fable-compiler/Fable/compare/63bcd3d90f37cb3934edcc59b5f54f49ffab3896..15eb83ed36657f75073fb0e1b4cac677e24fc9bb)</small></strong>
26
+
27
+ ## 2.1.0 - 2026-05-28
28
+
29
+ ### 🚀 Features
30
+
31
+ * *(js/ts/python)* Add missing standard DateTime format specifiers for JS/TS and Python (#4547) ([90f5eb7](https://github.com/fable-compiler/Fable/commit/90f5eb7f61710bc679f6fdec30ff5d864984ac08))
32
+ * *(js/ts/python)* Add missing StringBuilder.Append overloads for numeric types (#4568) ([b402c30](https://github.com/fable-compiler/Fable/commit/b402c30d04bfca8644314ba4b21894abfff6a713))
33
+
34
+ ### 🐞 Bug Fixes
35
+
36
+ * [JS/TS] Fix DatetimeOffset.ToString("s") (#4596) ([3ce6f3f](https://github.com/fable-compiler/Fable/commit/3ce6f3fd88cc7f67e34e2bbc311e722c55ffbf45))
37
+ * *(js/ts)* Fix datetime custom format off by one year (#4558) ([83bdbb5](https://github.com/fable-compiler/Fable/commit/83bdbb5b34e70eae203831a3a442d477d15911e1))
38
+ * *(js/ts)* Hex format specifier uses no padding unless precision is specified (#4603) ([ba9857a](https://github.com/fable-compiler/Fable/commit/ba9857a4dcbf7ab1f936241f6619fda43e039040))
39
+ * *(js/ts)* Fix Decimal.GetBits returning incorrect mantissa after round arithmetic result (#4561) ([345bcd4](https://github.com/fable-compiler/Fable/commit/345bcd4b99417b304c71059966126fad74cf3a97))
40
+ * *(js/ts)* Fix G/g format specifier corrupting exponential notation when trimming trailing zeros (#4587) ([773c098](https://github.com/fable-compiler/Fable/commit/773c098a8f7ac7e8db1d4e490dec1294a80321ee))
41
+ * *(js/ts/python)* Fix FSharpOption not recognized as union type in F# reflection (#4529) ([d78a37d](https://github.com/fable-compiler/Fable/commit/d78a37db9f4c25eb51fac8afcd320b4ea36c60a7))
42
+ * *(ts)* Enforce browser-only compatibility in fable-library-ts tsconfig (#4563) ([10c81c1](https://github.com/fable-compiler/Fable/commit/10c81c1361208eb61f5fa78c6704e6fccc068fb1))
43
+
44
+ <strong><small>[View changes on Github](https://github.com/fable-compiler/Fable/compare/b471dc16fc3b5132af77b5974d1669c9b8220cca..63bcd3d90f37cb3934edcc59b5f54f49ffab3896)</small></strong>
9
45
 
10
46
  ## 2.0.0 - 2026-04-21
11
47
 
package/Date.ts CHANGED
@@ -273,13 +273,13 @@ function dateToStringWithCustomFormat(date: Date, format: string, utc: boolean)
273
273
  cursorPos += tokenLength;
274
274
  switch (tokenLength) {
275
275
  case 1:
276
- result += localizedDate.getFullYear() % 100;
276
+ result += year(localizedDate) % 100;
277
277
  break;
278
278
  case 2:
279
- result += padWithZeros(localizedDate.getFullYear() % 100, 2);
279
+ result += padWithZeros(year(localizedDate) % 100, 2);
280
280
  break;
281
281
  default:
282
- result += padWithZeros(localizedDate.getFullYear(), tokenLength);
282
+ result += padWithZeros(year(localizedDate), tokenLength);
283
283
  break;
284
284
  }
285
285
  break;
@@ -410,9 +410,28 @@ function dateToStringWithOffset(date: IDateTimeOffset, format?: string) {
410
410
  switch (format) {
411
411
  case "D": return dateToString_D(d);
412
412
  case "d": return dateToString_d(d);
413
+ case "F": return dateToString_D(d) + " " + dateToString_T(d);
414
+ case "f": return dateToString_D(d) + " " + dateToString_t(d);
415
+ case "G": return dateToString_d(d) + " " + dateToString_T(d);
416
+ case "g": return dateToString_d(d) + " " + dateToString_t(d);
417
+ case "M": case "m": return dateToString_M(d);
418
+ case "O": case "o": return dateToISOStringWithOffset(d, (date.offset ?? 0));
419
+ case "R": case "r": {
420
+ const utcDate = DateTime(date.getTime(), DateTimeKind.Utc);
421
+ return dateToString_R(utcDate);
422
+ }
423
+ case "s": return dateToString_s(toUniversalTime(d));
413
424
  case "T": return dateToString_T(toUniversalTime(d));
414
425
  case "t": return dateToString_t(toUniversalTime(d));
415
- case "O": case "o": return dateToISOStringWithOffset(d, (date.offset ?? 0));
426
+ case "u": {
427
+ const utcDate = DateTime(date.getTime(), DateTimeKind.Utc);
428
+ return dateToString_u(utcDate);
429
+ }
430
+ case "U": {
431
+ const utcDate = DateTime(date.getTime(), DateTimeKind.Utc);
432
+ return dateToString_D(utcDate) + " " + dateToString_T(utcDate);
433
+ }
434
+ case "Y": case "y": return dateToString_Y(d);
416
435
  default: throw new Exception("Unrecognized Date print format");
417
436
  }
418
437
  } else {
@@ -444,6 +463,49 @@ function dateToString_t(date: IDateTime) {
444
463
  + ":" + padWithZeros(minute(date), 2);
445
464
  }
446
465
 
466
+ // RFC 1123: "Thu, 01 Jan 2009 00:00:00 GMT" — always UTC
467
+ function dateToString_R(date: IDateTime) {
468
+ const utcDate = toUniversalTime(date);
469
+ return shortDays[dayOfWeek(utcDate)] + ", "
470
+ + padWithZeros(day(utcDate), 2) + " "
471
+ + shortMonths[month(utcDate) - 1] + " "
472
+ + year(utcDate) + " "
473
+ + padWithZeros(hour(utcDate), 2) + ":"
474
+ + padWithZeros(minute(utcDate), 2) + ":"
475
+ + padWithZeros(second(utcDate), 2) + " GMT";
476
+ }
477
+
478
+ // Sortable ISO 8601, no timezone: "2009-06-15T13:45:30"
479
+ function dateToString_s(date: IDateTime) {
480
+ return padWithZeros(year(date), 4) + "-"
481
+ + padWithZeros(month(date), 2) + "-"
482
+ + padWithZeros(day(date), 2) + "T"
483
+ + padWithZeros(hour(date), 2) + ":"
484
+ + padWithZeros(minute(date), 2) + ":"
485
+ + padWithZeros(second(date), 2);
486
+ }
487
+
488
+ // Universal sortable: "2009-06-15 13:45:30Z" — always UTC
489
+ function dateToString_u(date: IDateTime) {
490
+ const utcDate = toUniversalTime(date);
491
+ return padWithZeros(year(utcDate), 4) + "-"
492
+ + padWithZeros(month(utcDate), 2) + "-"
493
+ + padWithZeros(day(utcDate), 2) + " "
494
+ + padWithZeros(hour(utcDate), 2) + ":"
495
+ + padWithZeros(minute(utcDate), 2) + ":"
496
+ + padWithZeros(second(utcDate), 2) + "Z";
497
+ }
498
+
499
+ // Month/day (InvariantCulture "MMMM dd"): "June 15"
500
+ function dateToString_M(date: IDateTime) {
501
+ return longMonths[month(date) - 1] + " " + padWithZeros(day(date), 2);
502
+ }
503
+
504
+ // Year/month (InvariantCulture "yyyy MMMM"): "2009 June"
505
+ function dateToString_Y(date: IDateTime) {
506
+ return year(date) + " " + longMonths[month(date) - 1];
507
+ }
508
+
447
509
  function dateToStringWithKind(date: IDateTime, format?: string) {
448
510
  const utc = date.kind === DateTimeKind.Utc;
449
511
  if (typeof format !== "string") {
@@ -452,10 +514,19 @@ function dateToStringWithKind(date: IDateTime, format?: string) {
452
514
  switch (format) {
453
515
  case "D": return dateToString_D(date);
454
516
  case "d": return dateToString_d(date);
517
+ case "F": return dateToString_D(date) + " " + dateToString_T(date);
518
+ case "f": return dateToString_D(date) + " " + dateToString_t(date);
519
+ case "G": return dateToString_d(date) + " " + dateToString_T(date);
520
+ case "g": return dateToString_d(date) + " " + dateToString_t(date);
521
+ case "M": case "m": return dateToString_M(date);
522
+ case "O": case "o": return dateToISOString(date, utc);
523
+ case "R": case "r": return dateToString_R(date);
524
+ case "s": return dateToString_s(date);
455
525
  case "T": return dateToString_T(date);
456
526
  case "t": return dateToString_t(date);
457
- case "O": case "o":
458
- return dateToISOString(date, utc);
527
+ case "u": return dateToString_u(date);
528
+ case "U": return dateToString_D(toUniversalTime(date)) + " " + dateToString_T(toUniversalTime(date));
529
+ case "Y": case "y": return dateToString_Y(date);
459
530
  default:
460
531
  throw new Exception("Unrecognized Date print format");
461
532
  }
@@ -519,6 +590,18 @@ export function maxValue() {
519
590
  return DateTime(253402300799999, DateTimeKind.Utc);
520
591
  }
521
592
 
593
+ // The only date words .NET's invariant parser recognises: month names, weekday names,
594
+ // meridiem designators and zone markers (plus the ISO "T" separator). Anything else is
595
+ // rejected. Used to reject JS-permissive inputs (see `parseRaw`).
596
+ const recognizedDateWords = new Set([
597
+ "january", "february", "march", "april", "may", "june",
598
+ "july", "august", "september", "october", "november", "december",
599
+ "jan", "feb", "mar", "apr", "jun", "jul", "aug", "sep", "sept", "oct", "nov", "dec",
600
+ "monday", "tuesday", "wednesday", "thursday", "friday", "saturday", "sunday",
601
+ "mon", "tue", "wed", "thu", "fri", "sat", "sun",
602
+ "am", "pm", "gmt", "utc", "ut", "t", "z",
603
+ ]);
604
+
522
605
  export function parseRaw(input: string): [Date, Offset] {
523
606
  function fail() {
524
607
  throw new Exception(`The string is not a valid Date: ${input}`);
@@ -528,6 +611,10 @@ export function parseRaw(input: string): [Date, Offset] {
528
611
  fail();
529
612
  }
530
613
 
614
+ if ((input.match(/[a-z]+/gi) ?? []).some(word => !recognizedDateWords.has(word.toLowerCase()))) {
615
+ fail();
616
+ }
617
+
531
618
  // ISO dates without TZ are parsed as UTC. Adding time without TZ keeps them local.
532
619
  if (input.length === 10 && input[4] === "-" && input[7] === "-") {
533
620
  input += "T00:00:00";
package/Decimal.ts CHANGED
@@ -248,14 +248,16 @@ export function fromParts(low: number, mid: number, high: number, isNegative: bo
248
248
 
249
249
  export function getBits(d: Decimal) {
250
250
  const bitSize = 96;
251
- const decDigits = Uint8Array.from(d.c);
251
+ const decStr = d.toString();
252
+ const absStr = decStr[0] === "-" ? decStr.slice(1) : decStr;
253
+ const dotPos = absStr.indexOf(".");
254
+ const scale = dotPos < 0 ? 0 : absStr.length - dotPos - 1;
255
+ const mantissaStr = absStr.replace(".", "");
256
+ const decDigits = Uint8Array.from(mantissaStr, ch => ch.charCodeAt(0) - 48);
252
257
  const hexDigits = decimalToHex(decDigits, bitSize);
253
258
  const low = getInt32Bits(hexDigits, 0);
254
259
  const mid = getInt32Bits(hexDigits, 8);
255
260
  const high = getInt32Bits(hexDigits, 16);
256
- const decStr = d.toString();
257
- const dotPos = decStr.indexOf(".");
258
- const scale = dotPos < 0 ? 0 : decStr.length - dotPos - 1;
259
261
  const signExp = ((scale & 0x7F) << 16) | (d.s < 0 ? 0x80000000 : 0);
260
262
  return [low, mid, high, signExp];
261
263
  }
package/Reflection.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  import { FSharpRef, Record, Union } from "./Types.ts";
2
2
  import { Exception, MutableArray, combineHashCodes, equalArraysWith, IEquatable, stringHash } from "./Util.ts";
3
3
  import Decimal from "./Decimal.ts";
4
+ import { Some, some } from "./Option.ts";
4
5
 
5
6
  export type FieldInfo = [string, TypeInfo];
6
7
  export type PropertyInfo = FieldInfo;
@@ -155,7 +156,18 @@ export function lambda_type(argType: TypeInfo, returnType: TypeInfo): TypeInfo {
155
156
  }
156
157
 
157
158
  export function option_type(generic: TypeInfo): TypeInfo {
158
- return new TypeInfo("Microsoft.FSharp.Core.FSharpOption`1", [generic]);
159
+ const t: TypeInfo = new TypeInfo(
160
+ "Microsoft.FSharp.Core.FSharpOption`1",
161
+ [generic],
162
+ undefined,
163
+ undefined,
164
+ undefined,
165
+ () => [
166
+ new CaseInfo(t, 0, "None"),
167
+ new CaseInfo(t, 1, "Some", [["value", generic]])
168
+ ]
169
+ );
170
+ return t;
159
171
  }
160
172
 
161
173
  export function list_type(generic: TypeInfo): TypeInfo {
@@ -443,6 +455,15 @@ export function isFunction(t: TypeInfo): boolean {
443
455
 
444
456
  export function getUnionFields(v: any, t: TypeInfo): [CaseInfo, any[]] {
445
457
  const cases = getUnionCases(t);
458
+ // Special handling for option types (None is undefined, Some is the value or a Some wrapper)
459
+ if (t.fullname === "Microsoft.FSharp.Core.FSharpOption`1") {
460
+ if (v == null) {
461
+ return [cases[0], []]; // None case
462
+ } else {
463
+ const innerValue = v instanceof Some ? v.value : v;
464
+ return [cases[1], [innerValue]]; // Some case
465
+ }
466
+ }
446
467
  const case_ = cases[v.tag];
447
468
  if (case_ == null) {
448
469
  throw new Exception(`Cannot find case ${v.name} in union type`);
@@ -478,6 +499,10 @@ export function makeUnion(uci: CaseInfo, values: MutableArray<any>): any {
478
499
  if (values.length !== expectedLength) {
479
500
  throw new Exception(`Expected an array of length ${expectedLength} but got ${values.length}`);
480
501
  }
502
+ // Special handling for option types
503
+ if (uci.declaringType.fullname === "Microsoft.FSharp.Core.FSharpOption`1") {
504
+ return uci.tag === 0 ? undefined : some(values[0]);
505
+ }
481
506
  const construct = uci.declaringType.construct;
482
507
  if (construct == null) {
483
508
  return {};
package/String.ts CHANGED
@@ -390,11 +390,23 @@ export function format(str: string | object, ...args: any[]) {
390
390
  rep = parts.integral + "." + padRight(parts.decimal, precision, "0");
391
391
  }
392
392
  break;
393
- case "g": case "G":
393
+ case "g": case "G": {
394
394
  rep = precision != null ? toPrecision(rep, precision) : toPrecision(rep);
395
- // TODO: Check why some numbers are formatted with decimal part
396
- rep = trimEnd(trimEnd(rep, "0"), ".");
395
+ // Handle exponential notation: only trim trailing zeros from mantissa, not from exponent.
396
+ // .NET G format guarantees an exponent of at least 2 digits with an explicit sign (e.g. "E-07").
397
+ const eIdx = rep.indexOf("e");
398
+ if (eIdx >= 0) {
399
+ const mantissa = trimEnd(trimEnd(rep.slice(0, eIdx), "0"), ".");
400
+ const expSign = rep[eIdx + 1]; // toPrecision always emits "+" or "-"
401
+ const expDigits = rep.slice(eIdx + 2);
402
+ const paddedExpDigits = expDigits.length < 2 ? "0" + expDigits : expDigits;
403
+ const eChar = format === "G" ? "E" : "e";
404
+ rep = mantissa + eChar + expSign + paddedExpDigits;
405
+ } else {
406
+ rep = trimEnd(trimEnd(rep, "0"), ".");
407
+ }
397
408
  break;
409
+ }
398
410
  case "n": case "N":
399
411
  precision = precision != null ? precision : 2;
400
412
  rep = toFixed(rep, precision);
@@ -421,7 +433,7 @@ export function format(str: string | object, ...args: any[]) {
421
433
  if (!isIntegral(rep)) {
422
434
  throw new Exception("Format specifier was invalid.");
423
435
  }
424
- precision = precision != null ? precision : 2;
436
+ precision = precision != null ? precision : 1;
425
437
  rep = padLeft(toHex(rep), precision, "0");
426
438
  if (format === "X") {
427
439
  rep = rep.toUpperCase();
package/System.Text.ts CHANGED
@@ -1,9 +1,10 @@
1
1
 
2
2
  import { replace, format, replicate, substring, isNullOrEmpty, join } from "./String.ts";
3
- import { float64, int32 } from "./Int32.ts";
3
+ import { float64, float32, uint32, uint16, int16, uint8, int8, int32 } from "./Int32.ts";
4
4
  import { class_type, TypeInfo } from "./Reflection.ts";
5
5
  import { toString } from "./Types.ts";
6
- import { Exception, clear, MutableArray, int32ToString } from "./Util.ts";
6
+ import { Exception, clear, MutableArray, int64ToString, int32ToString, int16ToString } from "./Util.ts";
7
+ import { uint64, int64 } from "./BigInt.ts";
7
8
  import { setItem, item } from "./Array.ts";
8
9
 
9
10
  export class StringBuilder {
@@ -61,11 +62,51 @@ export function StringBuilder__Append_61B1CA(x: StringBuilder, c: string, repeat
61
62
  return x;
62
63
  }
63
64
 
65
+ export function StringBuilder__Append_Z510FF069(x: StringBuilder, o: int8): StringBuilder {
66
+ void (x.buf.push(o.toString()));
67
+ return x;
68
+ }
69
+
70
+ export function StringBuilder__Append_244D3E44(x: StringBuilder, o: uint8): StringBuilder {
71
+ void (x.buf.push(o.toString()));
72
+ return x;
73
+ }
74
+
75
+ export function StringBuilder__Append_Z524259E6(x: StringBuilder, o: int16): StringBuilder {
76
+ void (x.buf.push(int16ToString(o)));
77
+ return x;
78
+ }
79
+
80
+ export function StringBuilder__Append_Z6EF82811(x: StringBuilder, o: uint16): StringBuilder {
81
+ void (x.buf.push(o.toString()));
82
+ return x;
83
+ }
84
+
64
85
  export function StringBuilder__Append_Z524259A4(x: StringBuilder, o: int32): StringBuilder {
65
86
  void (x.buf.push(int32ToString(o)));
66
87
  return x;
67
88
  }
68
89
 
90
+ export function StringBuilder__Append_Z6EF827D7(x: StringBuilder, o: uint32): StringBuilder {
91
+ void (x.buf.push(o.toString()));
92
+ return x;
93
+ }
94
+
95
+ export function StringBuilder__Append_Z524259C1(x: StringBuilder, o: int64): StringBuilder {
96
+ void (x.buf.push(int64ToString(o)));
97
+ return x;
98
+ }
99
+
100
+ export function StringBuilder__Append_Z6EF827B6(x: StringBuilder, o: uint64): StringBuilder {
101
+ void (x.buf.push(o.toString()));
102
+ return x;
103
+ }
104
+
105
+ export function StringBuilder__Append_Z7138B98C(x: StringBuilder, o: float32): StringBuilder {
106
+ void (x.buf.push(o.toString()));
107
+ return x;
108
+ }
109
+
69
110
  export function StringBuilder__Append_5E38073B(x: StringBuilder, o: float64): StringBuilder {
70
111
  void (x.buf.push(o.toString()));
71
112
  return x;
package/Util.ts CHANGED
@@ -84,6 +84,10 @@ export class Exception {
84
84
  constructor(msg?: string) {
85
85
  this.message = msg ?? "";
86
86
  }
87
+
88
+ toString() {
89
+ return this.message;
90
+ }
87
91
  }
88
92
 
89
93
  export function isException(x: any) {
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.0.0",
6
+ "version": "2.1.1",
7
7
  "description": "Core library used by F# projects compiled with fable.io",
8
8
  "author": "Fable Contributors",
9
9
  "license": "MIT",
package/tsconfig.json CHANGED
@@ -34,7 +34,7 @@
34
34
  // "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */
35
35
  // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */
36
36
  // "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */
37
- // "types": [], /* Specify type package names to be included without being referenced in a source file. */
37
+ "types": [], /* Exclude all @types/* packages (e.g. @types/node) to enforce browser-only compatibility. */
38
38
  // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
39
39
  // "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */
40
40
  // "allowImportingTsExtensions": true, /* Allow imports to include TypeScript file extensions. Requires '--moduleResolution bundler' and either '--noEmit' or '--emitDeclarationOnly' to be set. */