teptris 0.3.23 → 0.3.25

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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/src/index.js +106 -18
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "teptris",
3
- "version": "0.3.23",
3
+ "version": "0.3.25",
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.js CHANGED
@@ -3,6 +3,19 @@
3
3
 
4
4
  const KIND_OFFSET = 4, KIND_LOCAL_DT = 5, KIND_DATE = 6, KIND_TIME = 7;
5
5
 
6
+ // Date instances are the JS ecosystem's datetime shape (smol-toml
7
+ // hands them back) - they re-enter as the engine's datetime op. The
8
+ // fields come straight off the Date (toISOString + the parse regex
9
+ // per value measured as the datetime-heavy hot cost); Date carries
10
+ // millisecond precision, so nanoseconds = ms * 1e6.
11
+ const dateToDt = (d) => ({
12
+ kind: KIND_OFFSET, /* toISOString always yields Z */
13
+ year: d.getUTCFullYear(), month: d.getUTCMonth() + 1,
14
+ day: d.getUTCDate(), hour: d.getUTCHours(),
15
+ minute: d.getUTCMinutes(), second: d.getUTCSeconds(),
16
+ nanosecond: d.getUTCMilliseconds() * 1e6, offset_seconds: 0,
17
+ });
18
+
6
19
  function cstr(mod, s) {
7
20
  const bytes = mod.lengthBytesUTF8(s) + 1;
8
21
  const ptr = mod._teptris_wasm_alloc(bytes);
@@ -131,6 +144,15 @@ function buildValue(mod, b, key, v, errs) {
131
144
  default:
132
145
  break;
133
146
  }
147
+ if (v instanceof Date) {
148
+ // the JS ecosystem's datetime shape re-enters as the engine's
149
+ // datetime op (same mapping as the RFC 3339 string form)
150
+ const dt = dateToDt(v);
151
+ mod._teptris_wasm_builder_put_datetime(
152
+ b, kp, dt.kind, dt.year, dt.month, dt.day, dt.hour,
153
+ dt.minute, dt.second, dt.nanosecond, dt.offset_seconds);
154
+ return;
155
+ }
134
156
  if (Array.isArray(v)) {
135
157
  mod._teptris_wasm_builder_open_array(b, kp);
136
158
  for (const item of v) buildValue(mod, b, null, item, errs);
@@ -278,9 +300,73 @@ export function initApi(mod) {
278
300
  n += 1 + written;
279
301
  }
280
302
  };
303
+ // the datetime record is value-only (the key op precedes it)
304
+ const writeDatetime = (k, dt) => {
305
+ if (k !== null) wkey(k);
306
+ ensure(27);
307
+ buf[n++] = 7;
308
+ buf[n++] = dt.kind;
309
+ i64(dt.year);
310
+ buf[n++] = dt.month; buf[n++] = dt.day; buf[n++] = dt.hour;
311
+ buf[n++] = dt.minute; buf[n++] = dt.second;
312
+ u32(dt.nanosecond);
313
+ i64(dt.offset_seconds);
314
+ };
281
315
  const scalar = (k, v) => {
282
316
  // array ELEMENTS carry no key op (writer grammar v0.3.11): the
283
317
  // old klen-0 marker is now a genuine empty key
318
+ if (v instanceof Date) {
319
+ writeDatetime(k, dateToDt(v));
320
+ return;
321
+ }
322
+ // HOT PATH: keyed int/bool scalars (the mega-flat shapes are
323
+ // exactly this) - ONE growth check for key + value together,
324
+ // wkey inlined, direct DataView writes. The profiled dump cost
325
+ // was 91% JS walk: per-byte closure calls dominated.
326
+ if (k !== null && typeof v === "number" && !Object.is(v, -0) &&
327
+ Number.isInteger(v) && v >= -I64_BOUND && v < I64_BOUND) {
328
+ ensure(4 + k.length * 3 + 8);
329
+ buf[n++] = 1; /* the KEY op */
330
+ let ascii = true;
331
+ for (let i = 0; i < k.length; i++) {
332
+ const c = k.charCodeAt(i);
333
+ if (c > 127) { ascii = false; break; }
334
+ buf[n + 1 + i] = c;
335
+ }
336
+ if (ascii) {
337
+ buf[n] = k.length; n += 1 + k.length;
338
+ } else {
339
+ const { written } = utf8enc.encodeInto(k, buf.subarray(n + 1));
340
+ buf[n] = written; n += 1 + written;
341
+ }
342
+ buf[n++] = 3;
343
+ if (v >= -2147483648 && v <= 2147483647) {
344
+ dv.setInt32(n, v, true);
345
+ dv.setInt32(n + 4, v < 0 ? -1 : 0, true);
346
+ } else {
347
+ dv.setBigInt64(n, BigInt(v), true);
348
+ }
349
+ n += 8;
350
+ return;
351
+ }
352
+ if (k !== null && typeof v === "boolean") {
353
+ ensure(3 + k.length * 3 + 1);
354
+ buf[n++] = 1; /* the KEY op */
355
+ let ascii = true;
356
+ for (let i = 0; i < k.length; i++) {
357
+ const c = k.charCodeAt(i);
358
+ if (c > 127) { ascii = false; break; }
359
+ buf[n + 1 + i] = c;
360
+ }
361
+ if (ascii) {
362
+ buf[n] = k.length; n += 1 + k.length;
363
+ } else {
364
+ const { written } = utf8enc.encodeInto(k, buf.subarray(n + 1));
365
+ buf[n] = written; n += 1 + written;
366
+ }
367
+ buf[n++] = v ? 6 : 5;
368
+ return;
369
+ }
284
370
  if (k !== null) wkey(k);
285
371
  if (typeof v === "boolean") { byte(v ? 6 : 5); return; }
286
372
  if (typeof v === "number") {
@@ -304,18 +390,7 @@ export function initApi(mod) {
304
390
  }
305
391
  if (typeof v === "string") {
306
392
  if (maybeRfc3339(v) && RFC3339.test(v)) {
307
- const dt = parseDateTime(v);
308
- // the key prefix is already written (top of scalar()); the
309
- // datetime record is value-only - re-sending the key here
310
- // crashed bare array elements (k === null)
311
- ensure(27);
312
- buf[n++] = 7;
313
- buf[n++] = dt.kind;
314
- i64(dt.year);
315
- buf[n++] = dt.month; buf[n++] = dt.day; buf[n++] = dt.hour;
316
- buf[n++] = dt.minute; buf[n++] = dt.second;
317
- u32(dt.nanosecond);
318
- i64(dt.offset_seconds);
393
+ writeDatetime(null, parseDateTime(v));
319
394
  return;
320
395
  }
321
396
  ensure(5 + v.length * 3);
@@ -341,7 +416,8 @@ export function initApi(mod) {
341
416
  const isScalar = (v) =>
342
417
  v !== null && (typeof v !== "object" || Array.isArray(v) === false &&
343
418
  (typeof v === "string" || typeof v === "number" ||
344
- typeof v === "boolean" || typeof v === "bigint"));
419
+ typeof v === "boolean" || typeof v === "bigint")) ||
420
+ v instanceof Date;
345
421
  let depth = 0;
346
422
  const members = (o, inline) => {
347
423
  // name the engine's 512 ceiling: without this the JS stack
@@ -351,13 +427,25 @@ export function initApi(mod) {
351
427
  }
352
428
  try {
353
429
  const keys = Object.keys(o);
430
+ // ONE pass: scalars emit immediately (canonical order - scalar
431
+ // ops precede table ops per table); container keys collect and
432
+ // walk after. The inlined type test replaces the per-key
433
+ // isScalar closure call x2 the two-pass structure paid.
434
+ const containers = [];
354
435
  for (let i = 0; i < keys.length; i++) {
355
436
  const k = keys[i], v = o[k];
356
- if (isScalar(v)) scalar(k, v);
437
+ const tv = typeof v;
438
+ if (v instanceof Date ||
439
+ (v !== null && (tv === "string" || tv === "number" ||
440
+ tv === "boolean" || tv === "bigint"))) {
441
+ scalar(k, v);
442
+ } else {
443
+ containers.push(k);
444
+ }
357
445
  }
358
- for (let i = 0; i < keys.length; i++) {
359
- const k = keys[i], v = o[k];
360
- if (isScalar(v)) continue;
446
+ for (let i = 0; i < containers.length; i++) {
447
+ const k = containers[i];
448
+ const v = o[k];
361
449
  if (v === null || v === undefined) {
362
450
  throw new Error("null/undefined cannot round-trip through TOML (nan/inf parse to null in loads() and cannot re-enter)");
363
451
  }
@@ -365,7 +453,7 @@ export function initApi(mod) {
365
453
  if (Array.isArray(v)) {
366
454
  const aot = v.length > 0 && !inline && v.every(
367
455
  (x) => x !== null && typeof x === "object" &&
368
- !Array.isArray(x));
456
+ !Array.isArray(x) && !(x instanceof Date));
369
457
  if (aot) {
370
458
  // element bodies are EXPLICIT (the element opens with the
371
459
  // section op): nested tables promote to [path.sub]