teptris 0.3.13 → 0.3.14

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/dist/teptris.wasm CHANGED
Binary file
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "teptris",
3
- "version": "0.3.13",
3
+ "version": "0.3.14",
4
4
  "description": "TOML for JavaScript/TypeScript at libteptris speed (WebAssembly, no fallback) - version tracks the engine ({engine}.{iteration}; npm semver is 3-part)",
5
5
  "type": "module",
6
6
  "main": "src/index.js",
package/src/index.d.ts CHANGED
@@ -32,7 +32,13 @@ export interface TeptrisError extends Error {
32
32
  export interface TeptrisApi {
33
33
  /** Parse TOML into a JS object (natural-JSON view: real numbers,
34
34
  * booleans, RFC 3339 datetime strings, non-finite floats as
35
- * null). Throws TeptrisError on invalid input. */
35
+ * null). Throws TeptrisError on invalid input.
36
+ *
37
+ * Precision floor: JS numbers lose precision past 2^53, so
38
+ * integer literals beyond that round (9223372036854775807 reads
39
+ * back as 9223372036854776000). dump() never wraps: values past
40
+ * the int64 bounds emit as floats, and bigints outside int64
41
+ * throw. */
36
42
  loads(toml: string): TomlTable;
37
43
 
38
44
  /** Serialize a table to canonical TOML via the engine builder.
package/src/index.js CHANGED
@@ -53,6 +53,16 @@ function parseDateTime(str) {
53
53
  const RFC3339 =
54
54
  /^\d{4}-\d{2}-\d{2}([T ]\d{2}:\d{2}:\d{2}(\.\d+)?(Z|[+-]\d{2}:\d{2})?)?$|^\d{2}:\d{2}:\d{2}(\.\d+)?$/;
55
55
 
56
+ // int64 bounds for the dump integer path: Number.isInteger is true for
57
+ // values like 1e21, and the op-stream's int64 encode wraps silently
58
+ // past these bounds. The bound is 2^63 EXCLUSIVE (exactly representable
59
+ // as a number; 2^63-1 is not - its literal rounds up to 2^63).
60
+ // BigInt(number) of an integral in-range f64 is exact, so anything
61
+ // between 2^53 and 2^63 is still lossless.
62
+ const I64_BOUND = 9223372036854775808; /* 2 ** 63 */
63
+ const I64_MIN_BIG = -9223372036854775808n;
64
+ const I64_MAX_BIG = 9223372036854775807n;
65
+
56
66
  // cheap charset pre-check before the RFC 3339 regex: datetimes start
57
67
  // with a digit and only ever contain digits, date/time punctuation
58
68
  // and TZ marks - rejects nearly all plain strings without the regex.
@@ -70,11 +80,17 @@ function buildValue(mod, b, key, v, errs) {
70
80
  const kp = key == null ? 0 : cstr(mod, key);
71
81
  try {
72
82
  if (v === null || v === undefined) {
73
- throw new Error("null/undefined cannot round-trip through TOML");
83
+ throw new Error("null/undefined cannot round-trip through TOML (nan/inf parse to null in loads() and cannot re-enter)");
74
84
  }
75
85
  switch (typeof v) {
76
86
  case "number":
77
- if (Number.isInteger(v)) {
87
+ // same int64-exactness guard as the op-stream path: put_integer
88
+ // truncates BigInts past the int64 bounds, and -0.0 keeps its
89
+ // sign only as a float
90
+ if (Object.is(v, -0)) {
91
+ mod._teptris_wasm_builder_put_float(b, kp, v);
92
+ } else if (Number.isInteger(v) &&
93
+ v >= -I64_BOUND && v < I64_BOUND) {
78
94
  mod._teptris_wasm_builder_put_integer(b, kp, BigInt(v));
79
95
  } else {
80
96
  mod._teptris_wasm_builder_put_float(b, kp, v);
@@ -252,11 +268,24 @@ export function initApi(mod) {
252
268
  else wkey(k);
253
269
  if (typeof v === "boolean") { byte(v ? 6 : 5); return; }
254
270
  if (typeof v === "number") {
255
- if (Number.isInteger(v)) { byte(3); i64(v); }
256
- else { byte(4); f64(v); }
271
+ // the integer path only for numbers the int64 op can carry
272
+ // EXACTLY: Number.isInteger alone is true for 1e21 and for
273
+ // loads-rounded values past i64max, and setBigInt64 silently
274
+ // WRAPS out-of-range values (9223372036854775807 dumped as
275
+ // INT64_MIN); -0.0 must keep its sign, so it goes to the
276
+ // float path too
277
+ if (Object.is(v, -0)) { byte(4); f64(v); return; }
278
+ if (Number.isInteger(v) &&
279
+ v >= -I64_BOUND && v < I64_BOUND) { byte(3); i64(v); return; }
280
+ byte(4); f64(v);
257
281
  return;
258
282
  }
259
- if (typeof v === "bigint") { byte(3); i64(v); return; }
283
+ if (typeof v === "bigint") {
284
+ if (v < I64_MIN_BIG || v > I64_MAX_BIG) {
285
+ throw new Error(`bigint out of int64 range: ${v}`);
286
+ }
287
+ byte(3); i64(v); return;
288
+ }
260
289
  if (typeof v === "string") {
261
290
  if (maybeRfc3339(v) && RFC3339.test(v)) {
262
291
  const dt = parseDateTime(v);
@@ -291,7 +320,7 @@ export function initApi(mod) {
291
320
  }
292
321
  return;
293
322
  }
294
- throw new Error("null/undefined cannot round-trip through TOML");
323
+ throw new Error("null/undefined cannot round-trip through TOML (nan/inf parse to null in loads() and cannot re-enter)");
295
324
  };
296
325
  const isScalar = (v) =>
297
326
  v !== null && (typeof v !== "object" || Array.isArray(v) === false &&
@@ -307,7 +336,7 @@ export function initApi(mod) {
307
336
  const k = keys[i], v = o[k];
308
337
  if (isScalar(v)) continue;
309
338
  if (v === null || v === undefined) {
310
- throw new Error("null/undefined cannot round-trip through TOML");
339
+ throw new Error("null/undefined cannot round-trip through TOML (nan/inf parse to null in loads() and cannot re-enter)");
311
340
  }
312
341
  wkey(k);
313
342
  if (Array.isArray(v)) {
@@ -339,7 +368,7 @@ export function initApi(mod) {
339
368
  return;
340
369
  }
341
370
  if (el === null || el === undefined) {
342
- throw new Error("null/undefined cannot round-trip through TOML");
371
+ throw new Error("null/undefined cannot round-trip through TOML (nan/inf parse to null in loads() and cannot re-enter)");
343
372
  }
344
373
  byte(0x0B);
345
374
  members(el, true);