smol-toml 1.3.3 → 1.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/README.md CHANGED
@@ -16,6 +16,9 @@ parser didn't feel too out of place.
16
16
 
17
17
  *[insert xkcd 927]*
18
18
 
19
+ Nowadays, smol-toml is the most downloaded TOML parser on npm thanks to its quality. From frameworks to tooling, it
20
+ has been battle-tested and is actively used in production systems.
21
+
19
22
  smol-toml passes most of the tests from the [`toml-test` suite](https://github.com/toml-lang/toml-test); use the
20
23
  `run-toml-test.bash` script to run the tests. Due to the nature of JavaScript and the limits of the language,
21
24
  it doesn't pass certain tests, namely:
@@ -24,7 +27,9 @@ it doesn't pass certain tests, namely:
24
27
  - Certain invalid dates are not rejected
25
28
  - For instance, `2023-02-30` would be accepted and parsed as `2023-03-02`. While additional checks could be performed
26
29
  to reject these, they've not been added for performance reasons.
27
- - smol-toml doesn't preserve type information between integers and floats (in JS, everything is a float)
30
+
31
+ Please also note that by default, the behavior regarding integers doesn't preserve type information, nor does it allow
32
+ deserializing integers larger than 53 bits. See [Integers](#integers).
28
33
 
29
34
  You can see a list of all tests smol-toml fails (and the reason why it fails these) in the list of skipped tests in
30
35
  `run-toml-test.bash`. Note that some failures are *not* specification violations per-se. For instance, the TOML spec
@@ -59,11 +64,42 @@ A few notes on the `stringify` function:
59
64
  - `undefined` and `null` values on objects are ignored (does not produce a key/value).
60
65
  - `undefined` and `null` values in arrays are **rejected**.
61
66
  - Functions, classes and symbols are **rejected**.
62
- - floats will be serialized as integers if they don't have a decimal part.
67
+ - By default, floats will be serialized as integers if they don't have a decimal part. See [Integers](#integers)
63
68
  - `stringify(parse('a = 1.0')) === 'a = 1'`
64
69
  - JS `Date` will be serialized as Offset Date Time
65
70
  - Use the [`TomlDate` object](#dates) for representing other types.
66
71
 
72
+ ### Integers
73
+ When parsing, both integers and floats are read as plain JavaScript numbers, which essentially are floats. This means
74
+ loss of type information, and makes it impossible to safely represent integers beyond 53 bits.
75
+
76
+ When serializing, numbers without a decimal part are serialized as integers. This allows in most cases to preserve
77
+ whether a number is an integer or not, but fails to preserve type information for numbers like `1.0`.
78
+
79
+ #### Enabling BigInt support and type preservation
80
+ To parse integers beyond 53 bits, it's possible to tell the parser to return all integers as BigInt. This will
81
+ therefore preserve the type information at the cost of using a slightly more expensive container.
82
+
83
+ ```js
84
+ import { parse } from 'smol-toml'
85
+
86
+ const doc = '...'
87
+ const parsed = parse(doc, { integersAsBigInt: true })
88
+ ```
89
+
90
+ If you want to keep numbers for integers that can safely be represented as a JavaScript number, you can pass
91
+ `"asNeeded"` instead.
92
+
93
+ To get end-to-end type preservation, you can tell the serializer to always treat numbers as floating point numbers.
94
+ Then, only BigInts will be serialized as integers and numbers without a decimal part will still be serialized as float.
95
+
96
+ ```js
97
+ import { stringify } from 'smol-toml'
98
+
99
+ const obj = { ... }
100
+ const toml = stringify(obj, { numbersAsFloat: true })
101
+ ```
102
+
67
103
  ### Dates
68
104
  `smol-toml` uses an extended `Date` object to represent all types of TOML Dates. In the future, `smol-toml` will use
69
105
  objects from the Temporal proposal, but for now we're stuck with the legacy Date object.
@@ -107,6 +143,10 @@ const localTime = TomlDate.wrapAsLocalTime(jsDate)
107
143
  ```
108
144
 
109
145
  ## Performance
146
+ > [!NOTE]
147
+ > These benchmarks are starting to get a bit old. They will be updated in the (hopefully near) future to better
148
+ > reflect numbers of the latest version of smol-toml on the latest version of Node.js.
149
+
110
150
  A note on these performance numbers: in some highly synthetic tests, other parsers such as `fast-toml` greatly
111
151
  outperform other parsers, mostly due to their lack of compliance with the spec. For example, to parse a string,
112
152
  `fast-toml` skips the entire string while `smol-toml` does validate the string, costing a fair share of performance.
@@ -187,7 +227,7 @@ I initially tried to benchmark `toml-nodejs`, but the 0.3.0 package is broken.
187
227
  I initially reported this to the library author, but the author decided to
188
228
  - a) advise to use a custom loader (via *experimental* flag) to circumvent the invalid imports.
189
229
  - Said flag, `--experimental-specifier-resolution`, has been removed in Node v20.
190
- - b) [delete the issue](https://github.com/huan231/toml-nodejs/issues/12) when pointed out links to the NodeJS
230
+ - b) [delete the issue](https://github.com/huan231/toml-nodejs/issues/12) when pointed out links to the Node.js
191
231
  documentation about the flag removal and standard resolution algorithm.
192
232
 
193
233
  For the reference anyway, `toml-nodejs` (with proper imports) is ~8x slower on both parse benchmark with:
package/dist/date.js CHANGED
@@ -42,7 +42,9 @@ export class TomlDate extends Date {
42
42
  date = `0000-01-01T${date}`;
43
43
  }
44
44
  hasTime = !!match[2];
45
- // Do not allow rollover hours
45
+ // Make sure to use T instead of a space. Breaks in case of extreme values otherwise.
46
+ hasTime && date[10] === ' ' && (date = date.replace(' ', 'T'));
47
+ // Do not allow rollover hours.
46
48
  if (match[2] && +match[2] > 23) {
47
49
  date = '';
48
50
  }
package/dist/extract.d.ts CHANGED
@@ -25,5 +25,6 @@
25
25
  * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
26
26
  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27
27
  */
28
- import { type TomlPrimitive } from './util.js';
29
- export declare function extractValue(str: string, ptr: number, end?: string | undefined, depth?: number): [TomlPrimitive, number];
28
+ import { type IntegersAsBigInt } from './primitive.js';
29
+ import { type TomlValue } from './util.js';
30
+ export declare function extractValue(str: string, ptr: number, end: string | undefined, depth: number, integersAsBigInt: IntegersAsBigInt): [TomlValue, number];
package/dist/extract.js CHANGED
@@ -50,7 +50,7 @@ function sliceAndTrimEndOf(str, startPtr, endPtr, allowNewLines) {
50
50
  }
51
51
  return [trimmed, commentIdx];
52
52
  }
53
- export function extractValue(str, ptr, end, depth = -1) {
53
+ export function extractValue(str, ptr, end, depth, integersAsBigInt) {
54
54
  if (depth === 0) {
55
55
  throw new TomlError('document contains excessively nested structures. aborting.', {
56
56
  toml: str,
@@ -60,10 +60,10 @@ export function extractValue(str, ptr, end, depth = -1) {
60
60
  let c = str[ptr];
61
61
  if (c === '[' || c === '{') {
62
62
  let [value, endPtr] = c === '['
63
- ? parseArray(str, ptr, depth)
64
- : parseInlineTable(str, ptr, depth);
65
- let newPtr = skipUntil(str, endPtr, ',', end);
66
- if (end === '}') {
63
+ ? parseArray(str, ptr, depth, integersAsBigInt)
64
+ : parseInlineTable(str, ptr, depth, integersAsBigInt);
65
+ let newPtr = end ? skipUntil(str, endPtr, ',', end) : endPtr;
66
+ if (endPtr - newPtr && end === '}') {
67
67
  let nextNewLine = indexOfNewline(str, endPtr, newPtr);
68
68
  if (nextNewLine > -1) {
69
69
  throw new TomlError('newlines are not allowed in inline tables', {
@@ -103,7 +103,7 @@ export function extractValue(str, ptr, end, depth = -1) {
103
103
  endPtr += +(str[endPtr] === ',');
104
104
  }
105
105
  return [
106
- parseValue(slice[0], str, ptr),
106
+ parseValue(slice[0], str, ptr, integersAsBigInt),
107
107
  endPtr,
108
108
  ];
109
109
  }
package/dist/index.cjs CHANGED
@@ -108,9 +108,7 @@ function skipUntil(str, ptr, sep, end, banNewLines = false) {
108
108
  i = indexOfNewline(str, i);
109
109
  } else if (c === sep) {
110
110
  return i + 1;
111
- } else if (c === end) {
112
- return i;
113
- } else if (banNewLines && (c === "\n" || c === "\r" && str[i + 1] === "\n")) {
111
+ } else if (c === end || banNewLines && (c === "\n" || c === "\r" && str[i + 1] === "\n")) {
114
112
  return i;
115
113
  }
116
114
  }
@@ -156,6 +154,7 @@ var TomlDate = class _TomlDate extends Date {
156
154
  date = `0000-01-01T${date}`;
157
155
  }
158
156
  hasTime = !!match[2];
157
+ hasTime && date[10] === " " && (date = date.replace(" ", "T"));
159
158
  if (match[2] && +match[2] > 23) {
160
159
  date = "";
161
160
  } else {
@@ -316,7 +315,7 @@ function parseString(str, ptr = 0, endPtr = str.length) {
316
315
  }
317
316
  return parsed + str.slice(sliceStart, endPtr - 1);
318
317
  }
319
- function parseValue(value, toml, ptr) {
318
+ function parseValue(value, toml, ptr, integersAsBigInt) {
320
319
  if (value === "true")
321
320
  return true;
322
321
  if (value === "false")
@@ -328,31 +327,36 @@ function parseValue(value, toml, ptr) {
328
327
  if (value === "nan" || value === "+nan" || value === "-nan")
329
328
  return NaN;
330
329
  if (value === "-0")
331
- return 0;
332
- let isInt;
333
- if ((isInt = INT_REGEX.test(value)) || FLOAT_REGEX.test(value)) {
330
+ return integersAsBigInt ? 0n : 0;
331
+ let isInt = INT_REGEX.test(value);
332
+ if (isInt || FLOAT_REGEX.test(value)) {
334
333
  if (LEADING_ZERO.test(value)) {
335
334
  throw new TomlError("leading zeroes are not allowed", {
336
335
  toml,
337
336
  ptr
338
337
  });
339
338
  }
340
- let numeric = +value.replace(/_/g, "");
339
+ value = value.replace(/_/g, "");
340
+ let numeric = +value;
341
341
  if (isNaN(numeric)) {
342
342
  throw new TomlError("invalid number", {
343
343
  toml,
344
344
  ptr
345
345
  });
346
346
  }
347
- if (isInt && !Number.isSafeInteger(numeric)) {
348
- throw new TomlError("integer value cannot be represented losslessly", {
349
- toml,
350
- ptr
351
- });
347
+ if (isInt) {
348
+ if ((isInt = !Number.isSafeInteger(numeric)) && !integersAsBigInt) {
349
+ throw new TomlError("integer value cannot be represented losslessly", {
350
+ toml,
351
+ ptr
352
+ });
353
+ }
354
+ if (isInt || integersAsBigInt)
355
+ numeric = BigInt(value);
352
356
  }
353
357
  return numeric;
354
358
  }
355
- let date = new TomlDate(value);
359
+ const date = new TomlDate(value);
356
360
  if (!date.isValid()) {
357
361
  throw new TomlError("invalid value", {
358
362
  toml,
@@ -382,7 +386,7 @@ function sliceAndTrimEndOf(str, startPtr, endPtr, allowNewLines) {
382
386
  }
383
387
  return [trimmed, commentIdx];
384
388
  }
385
- function extractValue(str, ptr, end, depth = -1) {
389
+ function extractValue(str, ptr, end, depth, integersAsBigInt) {
386
390
  if (depth === 0) {
387
391
  throw new TomlError("document contains excessively nested structures. aborting.", {
388
392
  toml: str,
@@ -391,9 +395,9 @@ function extractValue(str, ptr, end, depth = -1) {
391
395
  }
392
396
  let c = str[ptr];
393
397
  if (c === "[" || c === "{") {
394
- let [value, endPtr2] = c === "[" ? parseArray(str, ptr, depth) : parseInlineTable(str, ptr, depth);
395
- let newPtr = skipUntil(str, endPtr2, ",", end);
396
- if (end === "}") {
398
+ let [value, endPtr2] = c === "[" ? parseArray(str, ptr, depth, integersAsBigInt) : parseInlineTable(str, ptr, depth, integersAsBigInt);
399
+ let newPtr = end ? skipUntil(str, endPtr2, ",", end) : endPtr2;
400
+ if (endPtr2 - newPtr && end === "}") {
397
401
  let nextNewLine = indexOfNewline(str, endPtr2, newPtr);
398
402
  if (nextNewLine > -1) {
399
403
  throw new TomlError("newlines are not allowed in inline tables", {
@@ -433,7 +437,7 @@ function extractValue(str, ptr, end, depth = -1) {
433
437
  endPtr += +(str[endPtr] === ",");
434
438
  }
435
439
  return [
436
- parseValue(slice[0], str, ptr),
440
+ parseValue(slice[0], str, ptr, integersAsBigInt),
437
441
  endPtr
438
442
  ];
439
443
  }
@@ -507,28 +511,20 @@ function parseKey(str, ptr, end = "=") {
507
511
  } while (dot + 1 && dot < endPtr);
508
512
  return [parsed, skipVoid(str, endPtr + 1, true, true)];
509
513
  }
510
- function parseInlineTable(str, ptr, depth = -1) {
514
+ function parseInlineTable(str, ptr, depth, integersAsBigInt) {
511
515
  let res = {};
512
516
  let seen = /* @__PURE__ */ new Set();
513
517
  let c;
514
518
  let comma = 0;
515
519
  ptr++;
516
520
  while ((c = str[ptr++]) !== "}" && c) {
521
+ let err = { toml: str, ptr: ptr - 1 };
517
522
  if (c === "\n") {
518
- throw new TomlError("newlines are not allowed in inline tables", {
519
- toml: str,
520
- ptr: ptr - 1
521
- });
523
+ throw new TomlError("newlines are not allowed in inline tables", err);
522
524
  } else if (c === "#") {
523
- throw new TomlError("inline tables cannot contain comments", {
524
- toml: str,
525
- ptr: ptr - 1
526
- });
525
+ throw new TomlError("inline tables cannot contain comments", err);
527
526
  } else if (c === ",") {
528
- throw new TomlError("expected key-value, found comma", {
529
- toml: str,
530
- ptr: ptr - 1
531
- });
527
+ throw new TomlError("expected key-value, found comma", err);
532
528
  } else if (c !== " " && c !== " ") {
533
529
  let k;
534
530
  let t = res;
@@ -554,7 +550,7 @@ function parseInlineTable(str, ptr, depth = -1) {
554
550
  ptr
555
551
  });
556
552
  }
557
- let [value, valueEndPtr] = extractValue(str, keyEndPtr, "}", depth - 1);
553
+ let [value, valueEndPtr] = extractValue(str, keyEndPtr, "}", depth - 1, integersAsBigInt);
558
554
  seen.add(value);
559
555
  t[k] = value;
560
556
  ptr = valueEndPtr;
@@ -575,7 +571,7 @@ function parseInlineTable(str, ptr, depth = -1) {
575
571
  }
576
572
  return [res, ptr];
577
573
  }
578
- function parseArray(str, ptr, depth = -1) {
574
+ function parseArray(str, ptr, depth, integersAsBigInt) {
579
575
  let res = [];
580
576
  let c;
581
577
  ptr++;
@@ -588,7 +584,7 @@ function parseArray(str, ptr, depth = -1) {
588
584
  } else if (c === "#")
589
585
  ptr = skipComment(str, ptr);
590
586
  else if (c !== " " && c !== " " && c !== "\n" && c !== "\r") {
591
- let e = extractValue(str, ptr - 1, "]", depth - 1);
587
+ let e = extractValue(str, ptr - 1, "]", depth - 1, integersAsBigInt);
592
588
  res.push(e[0]);
593
589
  ptr = e[1];
594
590
  }
@@ -662,8 +658,7 @@ function peekTable(key, table, meta, type) {
662
658
  }
663
659
  return [k, t, state.c];
664
660
  }
665
- function parse(toml, opts) {
666
- let maxDepth = opts?.maxDepth ?? 1e3;
661
+ function parse(toml, { maxDepth = 1e3, integersAsBigInt } = {}) {
667
662
  let res = {};
668
663
  let meta = {};
669
664
  let tbl = res;
@@ -712,7 +707,7 @@ function parse(toml, opts) {
712
707
  ptr
713
708
  });
714
709
  }
715
- let v = extractValue(toml, k[1], void 0, maxDepth);
710
+ let v = extractValue(toml, k[1], void 0, maxDepth, integersAsBigInt);
716
711
  p[1][p[0]] = v[0];
717
712
  ptr = v[1];
718
713
  }
@@ -750,7 +745,7 @@ function isArrayOfTables(obj) {
750
745
  function formatString(s) {
751
746
  return JSON.stringify(s).replace(/\x7f/g, "\\u007f");
752
747
  }
753
- function stringifyValue(val, type, depth) {
748
+ function stringifyValue(val, type, depth, numberAsFloat) {
754
749
  if (depth === 0) {
755
750
  throw new Error("Could not stringify the object: maximum object depth exceeded");
756
751
  }
@@ -761,6 +756,8 @@ function stringifyValue(val, type, depth) {
761
756
  return "inf";
762
757
  if (val === -Infinity)
763
758
  return "-inf";
759
+ if (numberAsFloat && Number.isInteger(val))
760
+ return val.toFixed(1);
764
761
  return val.toString();
765
762
  }
766
763
  if (type === "bigint" || type === "boolean") {
@@ -776,13 +773,13 @@ function stringifyValue(val, type, depth) {
776
773
  return val.toISOString();
777
774
  }
778
775
  if (type === "object") {
779
- return stringifyInlineTable(val, depth);
776
+ return stringifyInlineTable(val, depth, numberAsFloat);
780
777
  }
781
778
  if (type === "array") {
782
- return stringifyArray(val, depth);
779
+ return stringifyArray(val, depth, numberAsFloat);
783
780
  }
784
781
  }
785
- function stringifyInlineTable(obj, depth) {
782
+ function stringifyInlineTable(obj, depth, numberAsFloat) {
786
783
  let keys = Object.keys(obj);
787
784
  if (keys.length === 0)
788
785
  return "{}";
@@ -793,11 +790,11 @@ function stringifyInlineTable(obj, depth) {
793
790
  res += ", ";
794
791
  res += BARE_KEY.test(k) ? k : formatString(k);
795
792
  res += " = ";
796
- res += stringifyValue(obj[k], extendedTypeOf(obj[k]), depth - 1);
793
+ res += stringifyValue(obj[k], extendedTypeOf(obj[k]), depth - 1, numberAsFloat);
797
794
  }
798
795
  return res + " }";
799
796
  }
800
- function stringifyArray(array, depth) {
797
+ function stringifyArray(array, depth, numberAsFloat) {
801
798
  if (array.length === 0)
802
799
  return "[]";
803
800
  let res = "[ ";
@@ -807,11 +804,11 @@ function stringifyArray(array, depth) {
807
804
  if (array[i] === null || array[i] === void 0) {
808
805
  throw new TypeError("arrays cannot contain null or undefined values");
809
806
  }
810
- res += stringifyValue(array[i], extendedTypeOf(array[i]), depth - 1);
807
+ res += stringifyValue(array[i], extendedTypeOf(array[i]), depth - 1, numberAsFloat);
811
808
  }
812
809
  return res + " ]";
813
810
  }
814
- function stringifyArrayTable(array, key, depth) {
811
+ function stringifyArrayTable(array, key, depth, numberAsFloat) {
815
812
  if (depth === 0) {
816
813
  throw new Error("Could not stringify the object: maximum object depth exceeded");
817
814
  }
@@ -819,12 +816,12 @@ function stringifyArrayTable(array, key, depth) {
819
816
  for (let i = 0; i < array.length; i++) {
820
817
  res += `[[${key}]]
821
818
  `;
822
- res += stringifyTable(array[i], key, depth);
819
+ res += stringifyTable(array[i], key, depth, numberAsFloat);
823
820
  res += "\n\n";
824
821
  }
825
822
  return res;
826
823
  }
827
- function stringifyTable(obj, prefix, depth) {
824
+ function stringifyTable(obj, prefix, depth, numberAsFloat) {
828
825
  if (depth === 0) {
829
826
  throw new Error("Could not stringify the object: maximum object depth exceeded");
830
827
  }
@@ -840,17 +837,17 @@ function stringifyTable(obj, prefix, depth) {
840
837
  }
841
838
  let key = BARE_KEY.test(k) ? k : formatString(k);
842
839
  if (type === "array" && isArrayOfTables(obj[k])) {
843
- tables += stringifyArrayTable(obj[k], prefix ? `${prefix}.${key}` : key, depth - 1);
840
+ tables += stringifyArrayTable(obj[k], prefix ? `${prefix}.${key}` : key, depth - 1, numberAsFloat);
844
841
  } else if (type === "object") {
845
842
  let tblKey = prefix ? `${prefix}.${key}` : key;
846
843
  tables += `[${tblKey}]
847
844
  `;
848
- tables += stringifyTable(obj[k], tblKey, depth - 1);
845
+ tables += stringifyTable(obj[k], tblKey, depth - 1, numberAsFloat);
849
846
  tables += "\n\n";
850
847
  } else {
851
848
  preamble += key;
852
849
  preamble += " = ";
853
- preamble += stringifyValue(obj[k], type, depth);
850
+ preamble += stringifyValue(obj[k], type, depth, numberAsFloat);
854
851
  preamble += "\n";
855
852
  }
856
853
  }
@@ -858,12 +855,11 @@ function stringifyTable(obj, prefix, depth) {
858
855
  return `${preamble}
859
856
  ${tables}`.trim();
860
857
  }
861
- function stringify(obj, opts) {
858
+ function stringify(obj, { maxDepth = 1e3, numbersAsFloat = false } = {}) {
862
859
  if (extendedTypeOf(obj) !== "object") {
863
860
  throw new TypeError("stringify can only be called with an object");
864
861
  }
865
- let maxDepth = opts?.maxDepth ?? 1e3;
866
- return stringifyTable(obj, "", maxDepth);
862
+ return stringifyTable(obj, "", maxDepth, numbersAsFloat);
867
863
  }
868
864
 
869
865
  // dist/index.js
package/dist/index.d.ts CHANGED
@@ -29,7 +29,7 @@ import { parse } from './parse.js';
29
29
  import { stringify } from './stringify.js';
30
30
  import { TomlDate } from './date.js';
31
31
  import { TomlError } from './error.js';
32
- export type { TomlPrimitive } from './util.js';
32
+ export type { TomlValue, TomlTable, TomlValueWithoutBigInt, TomlTableWithoutBigInt } from './util.js';
33
33
  declare const _default: {
34
34
  parse: typeof parse;
35
35
  stringify: typeof stringify;
@@ -38,3 +38,6 @@ declare const _default: {
38
38
  };
39
39
  export default _default;
40
40
  export { parse, stringify, TomlDate, TomlError };
41
+ export type {
42
+ /** @deprecated use TomlValue instead */
43
+ TomlValue as TomlPrimitive } from './util.js';
package/dist/parse.d.ts CHANGED
@@ -25,7 +25,13 @@
25
25
  * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
26
26
  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27
27
  */
28
- import { type TomlPrimitive } from './util.js';
29
- export declare function parse(toml: string, opts?: {
30
- maxDepth: number;
31
- }): Record<string, TomlPrimitive>;
28
+ import type { IntegersAsBigInt } from './primitive.js';
29
+ import { type TomlTable, type TomlTableWithoutBigInt } from './util.js';
30
+ export interface ParseOptions {
31
+ maxDepth?: number;
32
+ integersAsBigInt?: IntegersAsBigInt;
33
+ }
34
+ export declare function parse(toml: string, options?: ParseOptions & {
35
+ integersAsBigInt: Exclude<IntegersAsBigInt, undefined | false>;
36
+ }): TomlTable;
37
+ export declare function parse(toml: string, options?: ParseOptions): TomlTableWithoutBigInt;
package/dist/parse.js CHANGED
@@ -93,8 +93,7 @@ function peekTable(key, table, meta, type) {
93
93
  }
94
94
  return [k, t, state.c];
95
95
  }
96
- export function parse(toml, opts) {
97
- let maxDepth = opts?.maxDepth ?? 1000;
96
+ export function parse(toml, { maxDepth = 1000, integersAsBigInt } = {}) {
98
97
  let res = {};
99
98
  let meta = {};
100
99
  let tbl = res;
@@ -132,7 +131,7 @@ export function parse(toml, opts) {
132
131
  ptr: ptr,
133
132
  });
134
133
  }
135
- let v = extractValue(toml, k[1], void 0, maxDepth);
134
+ let v = extractValue(toml, k[1], void 0, maxDepth, integersAsBigInt);
136
135
  p[1][p[0]] = v[0];
137
136
  ptr = v[1];
138
137
  }
@@ -27,4 +27,5 @@
27
27
  */
28
28
  import { TomlDate } from './date.js';
29
29
  export declare function parseString(str: string, ptr?: number, endPtr?: number): string;
30
- export declare function parseValue(value: string, toml: string, ptr: number): boolean | number | TomlDate;
30
+ export type IntegersAsBigInt = undefined | boolean | 'asNeeded';
31
+ export declare function parseValue(value: string, toml: string, ptr: number, integersAsBigInt: IntegersAsBigInt): boolean | number | bigint | TomlDate;
package/dist/primitive.js CHANGED
@@ -42,7 +42,7 @@ let ESC_MAP = {
42
42
  '\\': '\\',
43
43
  };
44
44
  export function parseString(str, ptr = 0, endPtr = str.length) {
45
- let isLiteral = str[ptr] === "'";
45
+ let isLiteral = str[ptr] === '\'';
46
46
  let isMultiline = str[ptr++] === str[ptr] && str[ptr] === str[ptr + 1];
47
47
  if (isMultiline) {
48
48
  endPtr -= 2;
@@ -61,14 +61,14 @@ export function parseString(str, ptr = 0, endPtr = str.length) {
61
61
  if (!isMultiline) {
62
62
  throw new TomlError('newlines are not allowed in strings', {
63
63
  toml: str,
64
- ptr: ptr - 1
64
+ ptr: ptr - 1,
65
65
  });
66
66
  }
67
67
  }
68
68
  else if ((c < '\x20' && c !== '\t') || c === '\x7f') {
69
69
  throw new TomlError('control characters are not allowed in strings', {
70
70
  toml: str,
71
- ptr: ptr - 1
71
+ ptr: ptr - 1,
72
72
  });
73
73
  }
74
74
  if (isEscape) {
@@ -79,7 +79,7 @@ export function parseString(str, ptr = 0, endPtr = str.length) {
79
79
  if (!ESCAPE_REGEX.test(code)) {
80
80
  throw new TomlError('invalid unicode escape', {
81
81
  toml: str,
82
- ptr: tmp
82
+ ptr: tmp,
83
83
  });
84
84
  }
85
85
  try {
@@ -88,7 +88,7 @@ export function parseString(str, ptr = 0, endPtr = str.length) {
88
88
  catch {
89
89
  throw new TomlError('invalid unicode escape', {
90
90
  toml: str,
91
- ptr: tmp
91
+ ptr: tmp,
92
92
  });
93
93
  }
94
94
  }
@@ -98,7 +98,7 @@ export function parseString(str, ptr = 0, endPtr = str.length) {
98
98
  if (str[ptr] !== '\n' && str[ptr] !== '\r') {
99
99
  throw new TomlError('invalid escape: only line-ending whitespace may be escaped', {
100
100
  toml: str,
101
- ptr: tmp
101
+ ptr: tmp,
102
102
  });
103
103
  }
104
104
  ptr = skipVoid(str, ptr);
@@ -110,7 +110,7 @@ export function parseString(str, ptr = 0, endPtr = str.length) {
110
110
  else {
111
111
  throw new TomlError('unrecognized escape sequence', {
112
112
  toml: str,
113
- ptr: tmp
113
+ ptr: tmp,
114
114
  });
115
115
  }
116
116
  sliceStart = ptr;
@@ -123,7 +123,7 @@ export function parseString(str, ptr = 0, endPtr = str.length) {
123
123
  }
124
124
  return parsed + str.slice(sliceStart, endPtr - 1);
125
125
  }
126
- export function parseValue(value, toml, ptr) {
126
+ export function parseValue(value, toml, ptr, integersAsBigInt) {
127
127
  // Constant values
128
128
  if (value === 'true')
129
129
  return true;
@@ -135,37 +135,43 @@ export function parseValue(value, toml, ptr) {
135
135
  return Infinity;
136
136
  if (value === 'nan' || value === '+nan' || value === '-nan')
137
137
  return NaN;
138
+ // Avoid FP representation of -0
138
139
  if (value === '-0')
139
- return 0; // Avoid FP representation of -0
140
+ return integersAsBigInt ? 0n : 0;
140
141
  // Numbers
141
- let isInt;
142
- if ((isInt = INT_REGEX.test(value)) || FLOAT_REGEX.test(value)) {
142
+ let isInt = INT_REGEX.test(value);
143
+ if (isInt || FLOAT_REGEX.test(value)) {
143
144
  if (LEADING_ZERO.test(value)) {
144
145
  throw new TomlError('leading zeroes are not allowed', {
145
146
  toml: toml,
146
- ptr: ptr
147
+ ptr: ptr,
147
148
  });
148
149
  }
149
- let numeric = +(value.replace(/_/g, ''));
150
+ value = value.replace(/_/g, '');
151
+ let numeric = +value;
150
152
  if (isNaN(numeric)) {
151
153
  throw new TomlError('invalid number', {
152
154
  toml: toml,
153
- ptr: ptr
155
+ ptr: ptr,
154
156
  });
155
157
  }
156
- if (isInt && !Number.isSafeInteger(numeric)) {
157
- throw new TomlError('integer value cannot be represented losslessly', {
158
- toml: toml,
159
- ptr: ptr
160
- });
158
+ if (isInt) {
159
+ if ((isInt = !Number.isSafeInteger(numeric)) && !integersAsBigInt) {
160
+ throw new TomlError('integer value cannot be represented losslessly', {
161
+ toml: toml,
162
+ ptr: ptr,
163
+ });
164
+ }
165
+ if (isInt || integersAsBigInt)
166
+ numeric = BigInt(value);
161
167
  }
162
168
  return numeric;
163
169
  }
164
- let date = new TomlDate(value);
170
+ const date = new TomlDate(value);
165
171
  if (!date.isValid()) {
166
172
  throw new TomlError('invalid value', {
167
173
  toml: toml,
168
- ptr: ptr
174
+ ptr: ptr,
169
175
  });
170
176
  }
171
177
  return date;
@@ -25,6 +25,7 @@
25
25
  * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
26
26
  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27
27
  */
28
- export declare function stringify(obj: any, opts?: {
28
+ export declare function stringify(obj: any, { maxDepth, numbersAsFloat }?: {
29
29
  maxDepth?: number;
30
+ numbersAsFloat?: boolean;
30
31
  }): string;
package/dist/stringify.js CHANGED
@@ -46,9 +46,9 @@ function isArrayOfTables(obj) {
46
46
  function formatString(s) {
47
47
  return JSON.stringify(s).replace(/\x7f/g, '\\u007f');
48
48
  }
49
- function stringifyValue(val, type, depth) {
49
+ function stringifyValue(val, type, depth, numberAsFloat) {
50
50
  if (depth === 0) {
51
- throw new Error("Could not stringify the object: maximum object depth exceeded");
51
+ throw new Error('Could not stringify the object: maximum object depth exceeded');
52
52
  }
53
53
  if (type === 'number') {
54
54
  if (isNaN(val))
@@ -57,6 +57,8 @@ function stringifyValue(val, type, depth) {
57
57
  return 'inf';
58
58
  if (val === -Infinity)
59
59
  return '-inf';
60
+ if (numberAsFloat && Number.isInteger(val))
61
+ return val.toFixed(1);
60
62
  return val.toString();
61
63
  }
62
64
  if (type === 'bigint' || type === 'boolean') {
@@ -72,13 +74,13 @@ function stringifyValue(val, type, depth) {
72
74
  return val.toISOString();
73
75
  }
74
76
  if (type === 'object') {
75
- return stringifyInlineTable(val, depth);
77
+ return stringifyInlineTable(val, depth, numberAsFloat);
76
78
  }
77
79
  if (type === 'array') {
78
- return stringifyArray(val, depth);
80
+ return stringifyArray(val, depth, numberAsFloat);
79
81
  }
80
82
  }
81
- function stringifyInlineTable(obj, depth) {
83
+ function stringifyInlineTable(obj, depth, numberAsFloat) {
82
84
  let keys = Object.keys(obj);
83
85
  if (keys.length === 0)
84
86
  return '{}';
@@ -89,11 +91,11 @@ function stringifyInlineTable(obj, depth) {
89
91
  res += ', ';
90
92
  res += BARE_KEY.test(k) ? k : formatString(k);
91
93
  res += ' = ';
92
- res += stringifyValue(obj[k], extendedTypeOf(obj[k]), depth - 1);
94
+ res += stringifyValue(obj[k], extendedTypeOf(obj[k]), depth - 1, numberAsFloat);
93
95
  }
94
96
  return res + ' }';
95
97
  }
96
- function stringifyArray(array, depth) {
98
+ function stringifyArray(array, depth, numberAsFloat) {
97
99
  if (array.length === 0)
98
100
  return '[]';
99
101
  let res = '[ ';
@@ -103,25 +105,25 @@ function stringifyArray(array, depth) {
103
105
  if (array[i] === null || array[i] === void 0) {
104
106
  throw new TypeError('arrays cannot contain null or undefined values');
105
107
  }
106
- res += stringifyValue(array[i], extendedTypeOf(array[i]), depth - 1);
108
+ res += stringifyValue(array[i], extendedTypeOf(array[i]), depth - 1, numberAsFloat);
107
109
  }
108
110
  return res + ' ]';
109
111
  }
110
- function stringifyArrayTable(array, key, depth) {
112
+ function stringifyArrayTable(array, key, depth, numberAsFloat) {
111
113
  if (depth === 0) {
112
- throw new Error("Could not stringify the object: maximum object depth exceeded");
114
+ throw new Error('Could not stringify the object: maximum object depth exceeded');
113
115
  }
114
116
  let res = '';
115
117
  for (let i = 0; i < array.length; i++) {
116
118
  res += `[[${key}]]\n`;
117
- res += stringifyTable(array[i], key, depth);
119
+ res += stringifyTable(array[i], key, depth, numberAsFloat);
118
120
  res += '\n\n';
119
121
  }
120
122
  return res;
121
123
  }
122
- function stringifyTable(obj, prefix, depth) {
124
+ function stringifyTable(obj, prefix, depth, numberAsFloat) {
123
125
  if (depth === 0) {
124
- throw new Error("Could not stringify the object: maximum object depth exceeded");
126
+ throw new Error('Could not stringify the object: maximum object depth exceeded');
125
127
  }
126
128
  let preamble = '';
127
129
  let tables = '';
@@ -135,28 +137,27 @@ function stringifyTable(obj, prefix, depth) {
135
137
  }
136
138
  let key = BARE_KEY.test(k) ? k : formatString(k);
137
139
  if (type === 'array' && isArrayOfTables(obj[k])) {
138
- tables += stringifyArrayTable(obj[k], prefix ? `${prefix}.${key}` : key, depth - 1);
140
+ tables += stringifyArrayTable(obj[k], prefix ? `${prefix}.${key}` : key, depth - 1, numberAsFloat);
139
141
  }
140
142
  else if (type === 'object') {
141
143
  let tblKey = prefix ? `${prefix}.${key}` : key;
142
144
  tables += `[${tblKey}]\n`;
143
- tables += stringifyTable(obj[k], tblKey, depth - 1);
145
+ tables += stringifyTable(obj[k], tblKey, depth - 1, numberAsFloat);
144
146
  tables += '\n\n';
145
147
  }
146
148
  else {
147
149
  preamble += key;
148
150
  preamble += ' = ';
149
- preamble += stringifyValue(obj[k], type, depth);
151
+ preamble += stringifyValue(obj[k], type, depth, numberAsFloat);
150
152
  preamble += '\n';
151
153
  }
152
154
  }
153
155
  }
154
156
  return `${preamble}\n${tables}`.trim();
155
157
  }
156
- export function stringify(obj, opts) {
158
+ export function stringify(obj, { maxDepth = 1000, numbersAsFloat = false } = {}) {
157
159
  if (extendedTypeOf(obj) !== 'object') {
158
160
  throw new TypeError('stringify can only be called with an object');
159
161
  }
160
- let maxDepth = opts?.maxDepth ?? 1000;
161
- return stringifyTable(obj, '', maxDepth);
162
+ return stringifyTable(obj, '', maxDepth, numbersAsFloat);
162
163
  }
package/dist/struct.d.ts CHANGED
@@ -25,7 +25,8 @@
25
25
  * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
26
26
  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27
27
  */
28
- import { type TomlPrimitive } from './util.js';
28
+ import { type IntegersAsBigInt } from './primitive.js';
29
+ import { type TomlTable, type TomlValue } from './util.js';
29
30
  export declare function parseKey(str: string, ptr: number, end?: string): [string[], number];
30
- export declare function parseInlineTable(str: string, ptr: number, depth?: number): [Record<string, TomlPrimitive>, number];
31
- export declare function parseArray(str: string, ptr: number, depth?: number): [TomlPrimitive[], number];
31
+ export declare function parseInlineTable(str: string, ptr: number, depth: number, integersAsBigInt: IntegersAsBigInt): [TomlTable, number];
32
+ export declare function parseArray(str: string, ptr: number, depth: number, integersAsBigInt: IntegersAsBigInt): [TomlValue[], number];
package/dist/struct.js CHANGED
@@ -27,7 +27,7 @@
27
27
  */
28
28
  import { parseString } from './primitive.js';
29
29
  import { extractValue } from './extract.js';
30
- import { skipComment, indexOfNewline, getStringEnd, skipVoid } from './util.js';
30
+ import { getStringEnd, indexOfNewline, skipComment, skipVoid } from './util.js';
31
31
  import { TomlError } from './error.js';
32
32
  let KEY_PART_RE = /^[a-zA-Z0-9-_]+[ \t]*$/;
33
33
  export function parseKey(str, ptr, end = '=') {
@@ -37,7 +37,7 @@ export function parseKey(str, ptr, end = '=') {
37
37
  if (endPtr < 0) {
38
38
  throw new TomlError('incomplete key-value: cannot find end of key', {
39
39
  toml: str,
40
- ptr: ptr
40
+ ptr: ptr,
41
41
  });
42
42
  }
43
43
  do {
@@ -45,7 +45,7 @@ export function parseKey(str, ptr, end = '=') {
45
45
  // If it's whitespace, ignore
46
46
  if (c !== ' ' && c !== '\t') {
47
47
  // If it's a string
48
- if (c === '"' || c === "'") {
48
+ if (c === '"' || c === '\'') {
49
49
  if (c === str[ptr + 1] && c === str[ptr + 2]) {
50
50
  throw new TomlError('multiline strings are not allowed in keys', {
51
51
  toml: str,
@@ -102,30 +102,22 @@ export function parseKey(str, ptr, end = '=') {
102
102
  } while (dot + 1 && dot < endPtr);
103
103
  return [parsed, skipVoid(str, endPtr + 1, true, true)];
104
104
  }
105
- export function parseInlineTable(str, ptr, depth = -1) {
105
+ export function parseInlineTable(str, ptr, depth, integersAsBigInt) {
106
106
  let res = {};
107
107
  let seen = new Set();
108
108
  let c;
109
109
  let comma = 0;
110
110
  ptr++;
111
111
  while ((c = str[ptr++]) !== '}' && c) {
112
+ let err = { toml: str, ptr: ptr - 1 };
112
113
  if (c === '\n') {
113
- throw new TomlError('newlines are not allowed in inline tables', {
114
- toml: str,
115
- ptr: ptr - 1
116
- });
114
+ throw new TomlError('newlines are not allowed in inline tables', err);
117
115
  }
118
116
  else if (c === '#') {
119
- throw new TomlError('inline tables cannot contain comments', {
120
- toml: str,
121
- ptr: ptr - 1
122
- });
117
+ throw new TomlError('inline tables cannot contain comments', err);
123
118
  }
124
119
  else if (c === ',') {
125
- throw new TomlError('expected key-value, found comma', {
126
- toml: str,
127
- ptr: ptr - 1
128
- });
120
+ throw new TomlError('expected key-value, found comma', err);
129
121
  }
130
122
  else if (c !== ' ' && c !== '\t') {
131
123
  let k;
@@ -139,7 +131,7 @@ export function parseInlineTable(str, ptr, depth = -1) {
139
131
  if ((hasOwn = Object.hasOwn(t, k)) && (typeof t[k] !== 'object' || seen.has(t[k]))) {
140
132
  throw new TomlError('trying to redefine an already defined value', {
141
133
  toml: str,
142
- ptr: ptr
134
+ ptr: ptr,
143
135
  });
144
136
  }
145
137
  if (!hasOwn && k === '__proto__') {
@@ -149,10 +141,10 @@ export function parseInlineTable(str, ptr, depth = -1) {
149
141
  if (hasOwn) {
150
142
  throw new TomlError('trying to redefine an already defined value', {
151
143
  toml: str,
152
- ptr: ptr
144
+ ptr: ptr,
153
145
  });
154
146
  }
155
- let [value, valueEndPtr] = extractValue(str, keyEndPtr, '}', depth - 1);
147
+ let [value, valueEndPtr] = extractValue(str, keyEndPtr, '}', depth - 1, integersAsBigInt);
156
148
  seen.add(value);
157
149
  t[k] = value;
158
150
  ptr = valueEndPtr;
@@ -162,18 +154,18 @@ export function parseInlineTable(str, ptr, depth = -1) {
162
154
  if (comma) {
163
155
  throw new TomlError('trailing commas are not allowed in inline tables', {
164
156
  toml: str,
165
- ptr: comma
157
+ ptr: comma,
166
158
  });
167
159
  }
168
160
  if (!c) {
169
161
  throw new TomlError('unfinished table encountered', {
170
162
  toml: str,
171
- ptr: ptr
163
+ ptr: ptr,
172
164
  });
173
165
  }
174
166
  return [res, ptr];
175
167
  }
176
- export function parseArray(str, ptr, depth = -1) {
168
+ export function parseArray(str, ptr, depth, integersAsBigInt) {
177
169
  let res = [];
178
170
  let c;
179
171
  ptr++;
@@ -181,13 +173,13 @@ export function parseArray(str, ptr, depth = -1) {
181
173
  if (c === ',') {
182
174
  throw new TomlError('expected value, found comma', {
183
175
  toml: str,
184
- ptr: ptr - 1
176
+ ptr: ptr - 1,
185
177
  });
186
178
  }
187
179
  else if (c === '#')
188
180
  ptr = skipComment(str, ptr);
189
181
  else if (c !== ' ' && c !== '\t' && c !== '\n' && c !== '\r') {
190
- let e = extractValue(str, ptr - 1, ']', depth - 1);
182
+ let e = extractValue(str, ptr - 1, ']', depth - 1, integersAsBigInt);
191
183
  res.push(e[0]);
192
184
  ptr = e[1];
193
185
  }
@@ -195,7 +187,7 @@ export function parseArray(str, ptr, depth = -1) {
195
187
  if (!c) {
196
188
  throw new TomlError('unfinished array encountered', {
197
189
  toml: str,
198
- ptr: ptr
190
+ ptr: ptr,
199
191
  });
200
192
  }
201
193
  return [res, ptr];
package/dist/util.d.ts CHANGED
@@ -26,9 +26,15 @@
26
26
  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27
27
  */
28
28
  import type { TomlDate } from './date.js';
29
- export type TomlPrimitive = string | number | boolean | TomlDate | {
30
- [key: string]: TomlPrimitive;
31
- } | TomlPrimitive[];
29
+ export type TomlPrimitive = string | number | bigint | boolean | TomlDate;
30
+ export type TomlTable = {
31
+ [key: string]: TomlValue;
32
+ };
33
+ export type TomlValue = TomlPrimitive | TomlValue[] | TomlTable;
34
+ export type TomlTableWithoutBigInt = {
35
+ [key: string]: TomlValueWithoutBigInt;
36
+ };
37
+ export type TomlValueWithoutBigInt = Exclude<TomlPrimitive, bigint> | TomlValueWithoutBigInt[] | TomlTableWithoutBigInt;
32
38
  export declare function indexOfNewline(str: string, start?: number, end?: number): number;
33
39
  export declare function skipComment(str: string, ptr: number): number;
34
40
  export declare function skipVoid(str: string, ptr: number, banNewLines?: boolean, banComments?: boolean): number;
package/dist/util.js CHANGED
@@ -69,10 +69,7 @@ export function skipUntil(str, ptr, sep, end, banNewLines = false) {
69
69
  else if (c === sep) {
70
70
  return i + 1;
71
71
  }
72
- else if (c === end) {
73
- return i;
74
- }
75
- else if (banNewLines && (c === '\n' || c === '\r' && str[i + 1] === '\n')) {
72
+ else if (c === end || (banNewLines && (c === '\n' || (c === '\r' && str[i + 1] === '\n')))) {
76
73
  return i;
77
74
  }
78
75
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "smol-toml",
3
3
  "license": "BSD-3-Clause",
4
- "version": "1.3.3",
4
+ "version": "1.4.0",
5
5
  "description": "A small, fast, and correct TOML parser/serializer",
6
6
  "author": "Cynthia <cyyynthia@borkenware.com>",
7
7
  "repository": "github:squirrelchat/smol-toml",
@@ -19,15 +19,15 @@
19
19
  "devDependencies": {
20
20
  "@iarna/toml": "3.0.0",
21
21
  "@ltd/j-toml": "^1.38.0",
22
- "@tsconfig/node-lts": "^22.0.1",
22
+ "@tsconfig/node-lts": "^22.0.2",
23
23
  "@tsconfig/strictest": "^2.0.5",
24
- "@types/node": "^22.14.1",
25
- "@vitest/ui": "^3.1.1",
26
- "esbuild": "^0.25.2",
24
+ "@types/node": "^24.0.7",
25
+ "@vitest/ui": "^3.2.4",
26
+ "esbuild": "^0.25.5",
27
27
  "fast-toml": "^0.5.4",
28
- "pin-github-action": "^3.1.2",
28
+ "pin-github-action": "^3.4.0",
29
29
  "typescript": "^5.8.3",
30
- "vitest": "^3.1.1"
30
+ "vitest": "^3.2.4"
31
31
  },
32
32
  "main": "./dist/index.cjs",
33
33
  "module": "./dist/index.js",