teptris 0.3.22 → 0.3.24

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 +113 -22
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "teptris",
3
- "version": "0.3.22",
3
+ "version": "0.3.24",
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);
@@ -10,6 +23,21 @@ function cstr(mod, s) {
10
23
  return ptr;
11
24
  }
12
25
 
26
+ // The len-bounded fast path: TextEncoder.encodeInto writes UTF-8
27
+ // STRAIGHT into the wasm heap with the native encoder, replacing
28
+ // emscripten's two JS-loop passes (lengthBytesUTF8 + stringToUTF8) -
29
+ // the measured biggest JS-side cost of the loads crossing. Only for
30
+ // entry points whose C side takes an explicit length (no NUL needed);
31
+ // NUL-terminated destinations keep cstr.
32
+ const utf8Encoder = new TextEncoder();
33
+
34
+ function cstrLen(mod, s) {
35
+ const cap = s.length * 3 + 4; /* UTF-8 worst case: 3 bytes/unit */
36
+ const ptr = mod._teptris_wasm_alloc(cap);
37
+ const { written } = utf8Encoder.encodeInto(s, mod.HEAPU8.subarray(ptr, ptr + cap));
38
+ return { ptr, len: written };
39
+ }
40
+
13
41
  function freeCstr(mod, ptr) { mod._teptris_wasm_free(ptr); }
14
42
 
15
43
  // RFC 3339-ish (the four TOML shapes) back into builder fields
@@ -116,6 +144,15 @@ function buildValue(mod, b, key, v, errs) {
116
144
  default:
117
145
  break;
118
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
+ }
119
156
  if (Array.isArray(v)) {
120
157
  mod._teptris_wasm_builder_open_array(b, kp);
121
158
  for (const item of v) buildValue(mod, b, null, item, errs);
@@ -148,8 +185,8 @@ export function initApi(mod) {
148
185
  if (typeof toml !== "string") {
149
186
  throw new TypeError("loads expects a string");
150
187
  }
151
- const ptr = cstr(mod, toml);
152
- const st = mod._teptris_wasm_load(ptr, lengthBytesUTF8(toml));
188
+ const { ptr, len } = cstrLen(mod, toml);
189
+ const st = mod._teptris_wasm_load(ptr, len);
153
190
  let value = null, error = null;
154
191
  if (st === 0) {
155
192
  const jptr = mod._teptris_wasm_json();
@@ -263,9 +300,73 @@ export function initApi(mod) {
263
300
  n += 1 + written;
264
301
  }
265
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
+ };
266
315
  const scalar = (k, v) => {
267
316
  // array ELEMENTS carry no key op (writer grammar v0.3.11): the
268
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
+ }
269
370
  if (k !== null) wkey(k);
270
371
  if (typeof v === "boolean") { byte(v ? 6 : 5); return; }
271
372
  if (typeof v === "number") {
@@ -289,18 +390,7 @@ export function initApi(mod) {
289
390
  }
290
391
  if (typeof v === "string") {
291
392
  if (maybeRfc3339(v) && RFC3339.test(v)) {
292
- const dt = parseDateTime(v);
293
- // the key prefix is already written (top of scalar()); the
294
- // datetime record is value-only - re-sending the key here
295
- // crashed bare array elements (k === null)
296
- ensure(27);
297
- buf[n++] = 7;
298
- buf[n++] = dt.kind;
299
- i64(dt.year);
300
- buf[n++] = dt.month; buf[n++] = dt.day; buf[n++] = dt.hour;
301
- buf[n++] = dt.minute; buf[n++] = dt.second;
302
- u32(dt.nanosecond);
303
- i64(dt.offset_seconds);
393
+ writeDatetime(null, parseDateTime(v));
304
394
  return;
305
395
  }
306
396
  ensure(5 + v.length * 3);
@@ -326,7 +416,8 @@ export function initApi(mod) {
326
416
  const isScalar = (v) =>
327
417
  v !== null && (typeof v !== "object" || Array.isArray(v) === false &&
328
418
  (typeof v === "string" || typeof v === "number" ||
329
- typeof v === "boolean" || typeof v === "bigint"));
419
+ typeof v === "boolean" || typeof v === "bigint")) ||
420
+ v instanceof Date;
330
421
  let depth = 0;
331
422
  const members = (o, inline) => {
332
423
  // name the engine's 512 ceiling: without this the JS stack
@@ -350,7 +441,7 @@ export function initApi(mod) {
350
441
  if (Array.isArray(v)) {
351
442
  const aot = v.length > 0 && !inline && v.every(
352
443
  (x) => x !== null && typeof x === "object" &&
353
- !Array.isArray(x));
444
+ !Array.isArray(x) && !(x instanceof Date));
354
445
  if (aot) {
355
446
  // element bodies are EXPLICIT (the element opens with the
356
447
  // section op): nested tables promote to [path.sub]
@@ -428,8 +519,8 @@ export function initApi(mod) {
428
519
  if (typeof toml !== "string") {
429
520
  throw new TypeError("loadsDoc expects a string");
430
521
  }
431
- const ptr = cstr(mod, toml);
432
- const st = mod._teptris_wasm_load_doc(ptr, lengthBytesUTF8(toml));
522
+ const { ptr, len } = cstrLen(mod, toml);
523
+ const st = mod._teptris_wasm_load_doc(ptr, len);
433
524
  freeCstr(mod, ptr);
434
525
  if (st !== 0) {
435
526
  docHeld = false;
@@ -567,8 +658,8 @@ export function initApi(mod) {
567
658
  if (typeof toml !== "string") {
568
659
  throw new TypeError("planWalk expects a TOML string");
569
660
  }
570
- const p = cstr(mod, toml);
571
- const st = mod._teptris_wasm_plan_walk(plan, p, lengthBytesUTF8(toml));
661
+ const { ptr: p, len: plen } = cstrLen(mod, toml);
662
+ const st = mod._teptris_wasm_plan_walk(plan, p, plen);
572
663
  freeCstr(mod, p);
573
664
  if (st !== 0) {
574
665
  throw planError();
@@ -594,11 +685,11 @@ export function initApi(mod) {
594
685
  throw new TypeError("edit expects three strings");
595
686
  }
596
687
  }
597
- const sp = cstr(mod, source);
688
+ const { ptr: sp, len: slen } = cstrLen(mod, source);
598
689
  const kp = cstr(mod, keyPath);
599
690
  const rp = cstr(mod, replacement);
600
691
  let out = null, error = null;
601
- const st = mod._teptris_wasm_edit(sp, lengthBytesUTF8(source), kp, rp);
692
+ const st = mod._teptris_wasm_edit(sp, slen, kp, rp);
602
693
  if (st === 0) {
603
694
  const optr = mod._teptris_wasm_json();
604
695
  const olen = mod._teptris_wasm_json_len();