satteri 0.10.5 → 0.11.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.
Files changed (89) hide show
  1. package/README.md +11 -0
  2. package/dist/arena-reader.d.ts +13 -0
  3. package/dist/arena-reader.js +104 -0
  4. package/dist/binding.browser.d.ts +1 -1
  5. package/dist/binding.browser.js +1 -1
  6. package/dist/binding.d.ts +1 -1
  7. package/dist/binding.js +1 -1
  8. package/dist/byte-writer.d.ts +0 -18
  9. package/dist/byte-writer.js +6 -30
  10. package/dist/child-stub.d.ts +0 -17
  11. package/dist/child-stub.js +6 -22
  12. package/dist/command-buffer.d.ts +5 -26
  13. package/dist/command-buffer.js +28 -39
  14. package/dist/compile-options.d.ts +11 -0
  15. package/dist/compile-options.js +99 -0
  16. package/dist/compile-types.d.ts +330 -0
  17. package/dist/compile-types.js +1 -0
  18. package/dist/compile.d.ts +22 -328
  19. package/dist/compile.js +129 -379
  20. package/dist/directive-types.d.ts +1 -0
  21. package/dist/directive-types.js +1 -6
  22. package/dist/generated/arena-layout.d.ts +6 -4
  23. package/dist/generated/arena-layout.js +6 -7
  24. package/dist/generated/fused-wire.d.ts +3 -0
  25. package/dist/generated/fused-wire.js +229 -0
  26. package/dist/generated/parse-options.js +0 -5
  27. package/dist/generated/wire-constants.d.ts +3 -0
  28. package/dist/generated/wire-constants.js +33 -42
  29. package/dist/handles.d.ts +0 -7
  30. package/dist/hast/child-stub.d.ts +1 -8
  31. package/dist/hast/child-stub.js +14 -13
  32. package/dist/hast/element-props.d.ts +13 -0
  33. package/dist/hast/element-props.js +52 -6
  34. package/dist/hast/generated/node-types.d.ts +1 -9
  35. package/dist/hast/generated/node-types.js +1 -9
  36. package/dist/hast/generated/walk-decode.d.ts +10 -0
  37. package/dist/hast/generated/walk-decode.js +61 -0
  38. package/dist/hast/hast-materializer.d.ts +3 -10
  39. package/dist/hast/hast-materializer.js +73 -47
  40. package/dist/hast/hast-reader.d.ts +9 -38
  41. package/dist/hast/hast-reader.js +82 -271
  42. package/dist/hast/hast-visitor.d.ts +29 -16
  43. package/dist/hast/hast-visitor.js +197 -508
  44. package/dist/hast/hast-walk.d.ts +18 -0
  45. package/dist/hast/hast-walk.js +264 -0
  46. package/dist/index.d.ts +5 -5
  47. package/dist/index.js +4 -12
  48. package/dist/lazy-child-resolver.d.ts +1 -21
  49. package/dist/lazy-child-resolver.js +11 -32
  50. package/dist/materializer-cache.d.ts +5 -29
  51. package/dist/materializer-cache.js +29 -101
  52. package/dist/mdast/child-stub.d.ts +1 -12
  53. package/dist/mdast/child-stub.js +18 -28
  54. package/dist/mdast/column-align.d.ts +1 -1
  55. package/dist/mdast/column-align.js +0 -2
  56. package/dist/mdast/generated/layout.d.ts +0 -16
  57. package/dist/mdast/generated/layout.js +6 -47
  58. package/dist/mdast/generated/node-types.d.ts +1 -9
  59. package/dist/mdast/generated/node-types.js +3 -9
  60. package/dist/mdast/mdast-materializer.d.ts +3 -11
  61. package/dist/mdast/mdast-materializer.js +91 -63
  62. package/dist/mdast/mdast-reader.d.ts +15 -63
  63. package/dist/mdast/mdast-reader.js +132 -269
  64. package/dist/mdast/mdast-visitor.d.ts +23 -15
  65. package/dist/mdast/mdast-visitor.js +188 -294
  66. package/dist/mdast/mdast-walk.d.ts +13 -0
  67. package/dist/mdast/mdast-walk.js +144 -0
  68. package/dist/mdx-attr.js +0 -3
  69. package/dist/mdx-types.js +1 -6
  70. package/dist/op-stream.d.ts +1 -15
  71. package/dist/op-stream.js +3 -19
  72. package/dist/phantom.d.ts +0 -1
  73. package/dist/phantom.js +1 -9
  74. package/dist/plugin-pipeline.d.ts +32 -0
  75. package/dist/plugin-pipeline.js +177 -0
  76. package/dist/plugin.d.ts +8 -7
  77. package/dist/plugin.js +3 -9
  78. package/dist/types.d.ts +0 -1
  79. package/dist/visitor-node.d.ts +12 -0
  80. package/dist/visitor-node.js +23 -0
  81. package/dist/visitor-shared.d.ts +26 -30
  82. package/dist/visitor-shared.js +34 -36
  83. package/dist/wire-read.d.ts +0 -22
  84. package/dist/wire-read.js +3 -24
  85. package/index.d.ts +19 -10
  86. package/index.js +56 -53
  87. package/package.json +3 -12
  88. package/dist/string-pool.d.ts +0 -13
  89. package/dist/string-pool.js +0 -91
package/README.md CHANGED
@@ -41,6 +41,17 @@ import { markdownToJs } from "satteri";
41
41
  const { code } = markdownToJs("# Hello\n\n{not an expression}");
42
42
  ```
43
43
 
44
+ ### HAST to HTML
45
+
46
+ `hastToHtml` serializes a HAST root, a single node, or an array of nodes without adding a trailing newline. Raw HTML nodes are emitted verbatim; MDX nodes are skipped.
47
+
48
+ ```ts
49
+ import { hastToHtml, markdownToHast } from "satteri";
50
+
51
+ const html = hastToHtml(markdownToHast("**Hello**"));
52
+ // <p><strong>Hello</strong></p>
53
+ ```
54
+
44
55
  ### With plugins
45
56
 
46
57
  All three functions accept `mdastPlugins` (operate on the Markdown AST) and `hastPlugins` (operate on the HTML AST). A plugin is an object with a `name` and a visitor per node type; `defineMdastPlugin` / `defineHastPlugin` add type inference.
@@ -0,0 +1,13 @@
1
+ import type { ArenaWire, BufferHeader } from "./types.js";
2
+ export declare class ArenaReader {
3
+ #private;
4
+ readonly u8: Uint8Array;
5
+ readonly u32: Uint32Array;
6
+ readonly header: BufferHeader;
7
+ constructor(buffer: ArrayBuffer | Uint8Array, kind: number);
8
+ getWire(): ArenaWire;
9
+ getNodeData(nodeId: number): string | null;
10
+ getNodeDataTable(): ReadonlyMap<number, string> | null;
11
+ getStringPool(): string;
12
+ getString(offset: number, len: number): string;
13
+ }
@@ -0,0 +1,104 @@
1
+ import { ARENA_MAGIC, HEADER, KIND_MDAST } from "./generated/arena-layout.js";
2
+ export class ArenaReader {
3
+ u8;
4
+ u32;
5
+ #view;
6
+ header;
7
+ #textDecoder = new TextDecoder("utf-8", { ignoreBOM: true });
8
+ #stringPoolCache = null;
9
+ constructor(buffer, kind) {
10
+ let u8 = buffer instanceof Uint8Array ? buffer : new Uint8Array(buffer);
11
+ // A foreign slice may not have the alignment required by a u32 view.
12
+ if ((u8.byteOffset & 3) !== 0)
13
+ u8 = u8.slice();
14
+ this.u8 = u8;
15
+ this.u32 = new Uint32Array(u8.buffer, u8.byteOffset, u8.byteLength >> 2);
16
+ this.#view = new DataView(u8.buffer, u8.byteOffset, u8.byteLength);
17
+ this.header = readHeader(this.#view, kind);
18
+ }
19
+ #nodeDataTable = null;
20
+ #wire = null;
21
+ // Memoize wire setup because the lazy materializer requests it for every node.
22
+ getWire() {
23
+ return (this.#wire ??= {
24
+ u8: this.u8,
25
+ u32: this.u32,
26
+ nodesB: this.header.nodesOffset,
27
+ nodesW: this.header.nodesOffset >> 2,
28
+ strideB: this.header.nodeStructSize,
29
+ strideW: this.header.nodeStructSize >> 2,
30
+ childrenW: this.header.childrenOffset >> 2,
31
+ typeDataB: this.header.typeDataOffset,
32
+ pool: this.getStringPool(),
33
+ });
34
+ }
35
+ // Index data blobs once to keep materialization O(nodes), rather than O(nodes × entries).
36
+ getNodeData(nodeId) {
37
+ const table = this.getNodeDataTable();
38
+ if (table === null)
39
+ return null;
40
+ return table.get(nodeId) ?? null;
41
+ }
42
+ getNodeDataTable() {
43
+ if (this.header.nodeDataCount === 0)
44
+ return null;
45
+ if (this.#nodeDataTable === null) {
46
+ this.#nodeDataTable = new Map();
47
+ const v = this.#view;
48
+ let pos = this.header.nodeDataOffset;
49
+ for (let i = 0; i < this.header.nodeDataCount; i++) {
50
+ const id = v.getUint32(pos, true);
51
+ pos += 4;
52
+ const len = v.getUint32(pos, true);
53
+ pos += 4;
54
+ const slice = this.u8.subarray(pos, pos + len);
55
+ this.#nodeDataTable.set(id, this.#textDecoder.decode(slice));
56
+ pos += len;
57
+ }
58
+ }
59
+ return this.#nodeDataTable;
60
+ }
61
+ // The pool includes interned strings; it is not the original document source.
62
+ getStringPool() {
63
+ return this.#stringPoolCache ?? this.#initPool();
64
+ }
65
+ #initPool() {
66
+ const { stringPoolOffset, stringPoolLen } = this.header;
67
+ const pool = this.#textDecoder.decode(this.u8.subarray(stringPoolOffset, stringPoolOffset + stringPoolLen));
68
+ this.#stringPoolCache = pool;
69
+ return pool;
70
+ }
71
+ // The serializer remaps string refs to UTF-16 offsets, matching substring indices.
72
+ getString(offset, len) {
73
+ if (len === 0)
74
+ return "";
75
+ const pool = this.#stringPoolCache ?? this.#initPool();
76
+ return pool.substring(offset, offset + len);
77
+ }
78
+ }
79
+ function readHeader(view, expectedKind) {
80
+ const magic = view.getUint32(HEADER.magic, true);
81
+ if (magic !== ARENA_MAGIC) {
82
+ throw new Error(expectedKind === KIND_MDAST
83
+ ? `Invalid buffer: bad magic 0x${magic.toString(16)}, expected 0x${ARENA_MAGIC.toString(16)}`
84
+ : `Invalid HAST buffer: bad magic 0x${magic.toString(16)}`);
85
+ }
86
+ const kind = view.getUint32(HEADER.kind, true);
87
+ if (kind !== expectedKind) {
88
+ throw new Error(`${expectedKind === KIND_MDAST ? "MdastReader" : "HastReader"} was handed a buffer of kind ${kind} (expected ${expectedKind}). ` +
89
+ `MDAST and HAST node types overlap; reading the wrong kind decodes garbage.`);
90
+ }
91
+ return {
92
+ nodeStructSize: view.getUint32(HEADER.node_struct_size, true),
93
+ nodeCount: view.getUint32(HEADER.node_count, true),
94
+ nodesOffset: view.getUint32(HEADER.nodes_offset, true),
95
+ childrenCount: view.getUint32(HEADER.children_count, true),
96
+ childrenOffset: view.getUint32(HEADER.children_offset, true),
97
+ typeDataLen: view.getUint32(HEADER.type_data_len, true),
98
+ typeDataOffset: view.getUint32(HEADER.type_data_offset, true),
99
+ stringPoolLen: view.getUint32(HEADER.string_pool_len, true),
100
+ stringPoolOffset: view.getUint32(HEADER.string_pool_offset, true),
101
+ nodeDataCount: view.getUint32(HEADER.node_data_count, true),
102
+ nodeDataOffset: view.getUint32(HEADER.node_data_offset, true),
103
+ };
104
+ }
@@ -1 +1 @@
1
- export { applyCommandsAndCompileHandle, applyCommandsAndConvertToHastHandle, applyCommandsAndRenderHandle, applyCommandsToHandle, applyCommandsToMdastHandle, applyMdastCommandsAndConvertAndCompile, applyMdastCommandsAndConvertAndRender, compileHandle, compileMdx, convertMdastToHastHandle, createHastHandle, createHastHandleFromHtml, createHastHandleWithFrontmatter, createMdastHandle, createMdxHastHandle, createMdxHastHandleWithFrontmatter, createMdxMdastHandle, dropHandle, getHandleSource, getMdastFrontmatter, getNodeData, markdownToHtmlFast, markdownToJsFast, mdastTextContentHandle, mdxToJsFast, parseEsm, parseExpression, parseToHtml, renderHandle, serializeHandle, setNodeData, textContentHandle, walkHandle, walkMdastHandle, } from "../browser.js";
1
+ export { applyCommandsAndCompileHandle, applyCommandsAndConvertToHastHandle, applyCommandsAndRenderHandle, applyCommandsToHandle, applyCommandsToMdastHandle, applyMdastCommandsAndConvertAndCompile, applyMdastCommandsAndConvertAndRender, compileHandle, compileMdx, convertMdastToHastHandle, createHastHandle, createHastHandleFromHtml, createHastHandleWithFrontmatter, createMdastHandle, createMdxHastHandle, createMdxHastHandleWithFrontmatter, createMdxMdastHandle, dropHandle, getHandleSource, getMdastFrontmatter, getNodeData, markdownToHtmlFast, markdownToJsFast, mdastTextContentHandle, mdxToJsFast, parseEsm, parseExpression, parseToHtml, renderHandle, renderHastOpstream, parseHastWire, parseMdastWire, serializeHandle, setNodeData, textContentHandle, walkHandle, walkMdastHandle, } from "../browser.js";
@@ -1,2 +1,2 @@
1
1
  // @ts-nocheck — WASM browser binding has no type declarations
2
- export { applyCommandsAndCompileHandle, applyCommandsAndConvertToHastHandle, applyCommandsAndRenderHandle, applyCommandsToHandle, applyCommandsToMdastHandle, applyMdastCommandsAndConvertAndCompile, applyMdastCommandsAndConvertAndRender, compileHandle, compileMdx, convertMdastToHastHandle, createHastHandle, createHastHandleFromHtml, createHastHandleWithFrontmatter, createMdastHandle, createMdxHastHandle, createMdxHastHandleWithFrontmatter, createMdxMdastHandle, dropHandle, getHandleSource, getMdastFrontmatter, getNodeData, markdownToHtmlFast, markdownToJsFast, mdastTextContentHandle, mdxToJsFast, parseEsm, parseExpression, parseToHtml, renderHandle, serializeHandle, setNodeData, textContentHandle, walkHandle, walkMdastHandle, } from "../browser.js";
2
+ export { applyCommandsAndCompileHandle, applyCommandsAndConvertToHastHandle, applyCommandsAndRenderHandle, applyCommandsToHandle, applyCommandsToMdastHandle, applyMdastCommandsAndConvertAndCompile, applyMdastCommandsAndConvertAndRender, compileHandle, compileMdx, convertMdastToHastHandle, createHastHandle, createHastHandleFromHtml, createHastHandleWithFrontmatter, createMdastHandle, createMdxHastHandle, createMdxHastHandleWithFrontmatter, createMdxMdastHandle, dropHandle, getHandleSource, getMdastFrontmatter, getNodeData, markdownToHtmlFast, markdownToJsFast, mdastTextContentHandle, mdxToJsFast, parseEsm, parseExpression, parseToHtml, renderHandle, renderHastOpstream, parseHastWire, parseMdastWire, serializeHandle, setNodeData, textContentHandle, walkHandle, walkMdastHandle, } from "../browser.js";
package/dist/binding.d.ts CHANGED
@@ -1 +1 @@
1
- export { applyCommandsAndCompileHandle, applyCommandsAndConvertToHastHandle, applyCommandsAndRenderHandle, applyCommandsToHandle, applyCommandsToMdastHandle, applyMdastCommandsAndConvertAndCompile, applyMdastCommandsAndConvertAndRender, compileHandle, compileMdx, convertMdastToHastHandle, createHastHandle, createHastHandleFromHtml, createHastHandleWithFrontmatter, createMdastHandle, createMdxHastHandle, createMdxHastHandleWithFrontmatter, createMdxMdastHandle, dropHandle, getHandleSource, getMdastFrontmatter, getNodeData, markdownToHtmlFast, markdownToJsFast, mdastTextContentHandle, mdxToJsFast, parseEsm, parseExpression, parseToHtml, renderHandle, serializeHandle, setNodeData, textContentHandle, walkHandle, walkMdastHandle, } from "../index.js";
1
+ export { applyCommandsAndCompileHandle, applyCommandsAndConvertToHastHandle, applyCommandsAndRenderHandle, applyCommandsToHandle, applyCommandsToMdastHandle, applyMdastCommandsAndConvertAndCompile, applyMdastCommandsAndConvertAndRender, compileHandle, compileMdx, convertMdastToHastHandle, createHastHandle, createHastHandleFromHtml, createHastHandleWithFrontmatter, createMdastHandle, createMdxHastHandle, createMdxHastHandleWithFrontmatter, createMdxMdastHandle, dropHandle, getHandleSource, getMdastFrontmatter, getNodeData, markdownToHtmlFast, markdownToJsFast, mdastTextContentHandle, mdxToJsFast, parseEsm, parseExpression, parseToHtml, renderHandle, renderHastOpstream, parseHastWire, parseMdastWire, serializeHandle, setNodeData, textContentHandle, walkHandle, walkMdastHandle, } from "../index.js";
package/dist/binding.js CHANGED
@@ -1 +1 @@
1
- export { applyCommandsAndCompileHandle, applyCommandsAndConvertToHastHandle, applyCommandsAndRenderHandle, applyCommandsToHandle, applyCommandsToMdastHandle, applyMdastCommandsAndConvertAndCompile, applyMdastCommandsAndConvertAndRender, compileHandle, compileMdx, convertMdastToHastHandle, createHastHandle, createHastHandleFromHtml, createHastHandleWithFrontmatter, createMdastHandle, createMdxHastHandle, createMdxHastHandleWithFrontmatter, createMdxMdastHandle, dropHandle, getHandleSource, getMdastFrontmatter, getNodeData, markdownToHtmlFast, markdownToJsFast, mdastTextContentHandle, mdxToJsFast, parseEsm, parseExpression, parseToHtml, renderHandle, serializeHandle, setNodeData, textContentHandle, walkHandle, walkMdastHandle, } from "../index.js";
1
+ export { applyCommandsAndCompileHandle, applyCommandsAndConvertToHastHandle, applyCommandsAndRenderHandle, applyCommandsToHandle, applyCommandsToMdastHandle, applyMdastCommandsAndConvertAndCompile, applyMdastCommandsAndConvertAndRender, compileHandle, compileMdx, convertMdastToHastHandle, createHastHandle, createHastHandleFromHtml, createHastHandleWithFrontmatter, createMdastHandle, createMdxHastHandle, createMdxHastHandleWithFrontmatter, createMdxMdastHandle, dropHandle, getHandleSource, getMdastFrontmatter, getNodeData, markdownToHtmlFast, markdownToJsFast, mdastTextContentHandle, mdxToJsFast, parseEsm, parseExpression, parseToHtml, renderHandle, renderHastOpstream, parseHastWire, parseMdastWire, serializeHandle, setNodeData, textContentHandle, walkHandle, walkMdastHandle, } from "../index.js";
@@ -1,35 +1,17 @@
1
- /**
2
- * Growable little-endian byte buffer shared by the op-stream and command-buffer
3
- * encoders — the two hottest write paths in the package.
4
- *
5
- * Subclasses write single bytes directly through the protected `buf`/`n`
6
- * fields after an `ensure` for the whole record (one bounds check per record,
7
- * not per byte, and no per-byte call in unoptimized tiers — CodSpeed's
8
- * Simulation mode runs too few iterations for V8 to inline method calls).
9
- * Only the non-trivial logic lives here: growth, u32 writes, and UTF-8
10
- * string encoding.
11
- */
12
1
  export declare class ByteWriter {
13
2
  #private;
14
3
  protected buf: Uint8Array;
15
4
  protected n: number;
16
5
  constructor(initialSize: number);
17
- /** Number of bytes written so far. */
18
6
  get length(): number;
19
7
  get capacity(): number;
20
- /** Reset for reuse; the grown buffer is retained so steady state is alloc-free. */
21
8
  reset(): void;
22
9
  /** View of the bytes written so far (no copy; valid until the next write or reset). */
23
10
  take(): Uint8Array;
24
- /** Release the backing (handed-out views stay intact); next write re-allocates. */
25
11
  protected release(): void;
26
- /** Grow (doubling) so `extra` more bytes fit; required before unchecked writes. */
27
12
  ensure(extra: number): void;
28
13
  /** Write a u32 (LE) at the cursor; caller must have `ensure`d 4 bytes. */
29
14
  protected writeU32(v: number): void;
30
- /** u32 byte length + UTF-8 bytes (self-ensuring). */
31
15
  protected utf8WithU32Len(s: string): void;
32
- /** Backpatch a u32 (LE) at `pos` in the already-written region; the cursor
33
- * is left untouched. */
34
16
  protected patchU32(pos: number, v: number): void;
35
17
  }
@@ -1,38 +1,23 @@
1
- /**
2
- * Growable little-endian byte buffer shared by the op-stream and command-buffer
3
- * encoders — the two hottest write paths in the package.
4
- *
5
- * Subclasses write single bytes directly through the protected `buf`/`n`
6
- * fields after an `ensure` for the whole record (one bounds check per record,
7
- * not per byte, and no per-byte call in unoptimized tiers — CodSpeed's
8
- * Simulation mode runs too few iterations for V8 to inline method calls).
9
- * Only the non-trivial logic lives here: growth, u32 writes, and UTF-8
10
- * string encoding.
11
- */
1
+ // Write bytes inline after reserving each record to avoid per-byte calls and bounds checks.
12
2
  const encoder = new TextEncoder();
13
3
  const EMPTY = new Uint8Array(0);
14
- /** Below this length the inline char-code copy beats `encodeInto`'s call
15
- * overhead; above it the native bulk path wins (and skips the ASCII scan).
16
- * Measured crossover ~16 bytes (Node 24: 8B 15 vs 41 ns, 16B 37 vs 43,
17
- * 32B 73 vs 52). */
4
+ // Below 16 bytes, inline ASCII writes beat encodeInto call overhead.
18
5
  const INLINE_STR_MAX = 16;
19
6
  export class ByteWriter {
20
- // Backing allocates on first write: visitor passes construct buffers that
21
- // often never receive a byte (read-only plugins).
7
+ // Read-only plugin passes should allocate no backing buffer.
22
8
  buf = EMPTY;
23
9
  n = 0;
24
10
  #initialSize;
25
11
  constructor(initialSize) {
26
12
  this.#initialSize = initialSize;
27
13
  }
28
- /** Number of bytes written so far. */
29
14
  get length() {
30
15
  return this.n;
31
16
  }
32
17
  get capacity() {
33
18
  return this.buf.length;
34
19
  }
35
- /** Reset for reuse; the grown buffer is retained so steady state is alloc-free. */
20
+ // Retain capacity so repeated plugin passes can reuse the buffer without allocating.
36
21
  reset() {
37
22
  this.n = 0;
38
23
  }
@@ -40,12 +25,11 @@ export class ByteWriter {
40
25
  take() {
41
26
  return this.n === 0 ? EMPTY : this.buf.subarray(0, this.n);
42
27
  }
43
- /** Release the backing (handed-out views stay intact); next write re-allocates. */
44
28
  release() {
45
29
  this.buf = EMPTY;
46
30
  this.n = 0;
47
31
  }
48
- /** Grow (doubling) so `extra` more bytes fit; required before unchecked writes. */
32
+ // Reserve capacity before unchecked writes.
49
33
  ensure(extra) {
50
34
  if (this.n + extra <= this.buf.length)
51
35
  return;
@@ -66,12 +50,9 @@ export class ByteWriter {
66
50
  buf[n++] = (v >>> 24) & 255;
67
51
  this.n = n;
68
52
  }
69
- /** u32 byte length + UTF-8 bytes (self-ensuring). */
70
53
  utf8WithU32Len(s) {
71
54
  const len = s.length;
72
55
  this.ensure(4 + len * 3); // worst-case UTF-8 is 3 bytes per UTF-16 unit
73
- // Short strings: inline char copy (cheaper than a native call), guarded by a
74
- // quick ASCII scan. Anything longer — or non-ASCII — goes to encodeInto.
75
56
  if (len <= INLINE_STR_MAX) {
76
57
  let ascii = true;
77
58
  for (let i = 0; i < len; i++) {
@@ -81,8 +62,7 @@ export class ByteWriter {
81
62
  }
82
63
  }
83
64
  if (ascii) {
84
- // Inline stores, not writeU32: a per-string method call in the
85
- // unoptimized tier is exactly what this class's header rules out.
65
+ // Inline stores avoid per-string method calls before V8 has optimized this path.
86
66
  const buf = this.buf;
87
67
  let n = this.n;
88
68
  buf[n++] = len & 255;
@@ -95,16 +75,12 @@ export class ByteWriter {
95
75
  return;
96
76
  }
97
77
  }
98
- // Bulk path: encodeInto writes UTF-8 straight into the buffer (no alloc, no
99
- // per-char loop); backpatch the byte length once it's known.
100
78
  const lenPos = this.n;
101
79
  this.n += 4;
102
80
  const written = encoder.encodeInto(s, this.buf.subarray(this.n)).written;
103
81
  this.patchU32(lenPos, written);
104
82
  this.n += written;
105
83
  }
106
- /** Backpatch a u32 (LE) at `pos` in the already-written region; the cursor
107
- * is left untouched. */
108
84
  patchU32(pos, v) {
109
85
  const buf = this.buf;
110
86
  buf[pos] = v & 255;
@@ -1,21 +1,4 @@
1
- /**
2
- * Shared machinery for walk-path child stubs (see `hast/child-stub.ts` and
3
- * `mdast/child-stub.ts`): lightweight stand-ins that carry arena id + type
4
- * eagerly and defer the full-arena snapshot until a plugin reads a real field.
5
- * Passthrough children (the common replaceNode shape) thus compile to one-word
6
- * refs without ever serializing the arena.
7
- */
8
1
  export type StubDescriptorEntry = readonly [string, PropertyDescriptor];
9
- /**
10
- * Descriptor entries for one node type's stub fields. Own enumerable getters
11
- * so a spread copy carries correct values. `position`/`data` ride on every
12
- * stub; they may yield `undefined` where a materialized node omits the key —
13
- * accepted drift, invisible to `toEqual`. Installed one `defineProperty` at a
14
- * time: `Object.defineProperties` re-validates the whole map per call and
15
- * costs nearly double.
16
- */
17
2
  export declare function stubDescriptors(fields: readonly string[]): readonly StubDescriptorEntry[];
18
3
  export declare function installStubDescriptors(host: object, entries: readonly StubDescriptorEntry[]): void;
19
- /** Node tags are dense small ints, so the per-child stub loop indexes flat
20
- * arrays instead of paying Map/dictionary lookups. */
21
4
  export declare function flatByTag<T>(table: Readonly<Record<number, T>>): readonly (T | undefined)[];
@@ -1,14 +1,6 @@
1
- /**
2
- * Shared machinery for walk-path child stubs (see `hast/child-stub.ts` and
3
- * `mdast/child-stub.ts`): lightweight stand-ins that carry arena id + type
4
- * eagerly and defer the full-arena snapshot until a plugin reads a real field.
5
- * Passthrough children (the common replaceNode shape) thus compile to one-word
6
- * refs without ever serializing the arena.
7
- */
8
- /** Stub → materialized arena node, filled on the first real-field read. */
1
+ // Child stubs let unchanged children pass through as arena references without serializing the tree.
9
2
  const REAL_NODES = new WeakMap();
10
- /** One shared memoizing getter per field name, so stubs install shared
11
- * descriptor templates instead of allocating per-field closures per node. */
3
+ // Share getters across stubs to avoid allocating a closure for every field of every node.
12
4
  const FIELD_GETTERS = new Map();
13
5
  function fieldGetter(key) {
14
6
  let getter = FIELD_GETTERS.get(key);
@@ -16,11 +8,11 @@ function fieldGetter(key) {
16
8
  getter = function () {
17
9
  let real = REAL_NODES.get(this);
18
10
  if (real === undefined) {
19
- real = this._resolver.materializeOne(this._id);
11
+ real = this._materialize();
20
12
  REAL_NODES.set(this, real);
21
13
  }
22
14
  const value = real[key];
23
- // Mirrors the deep-frozen node it forwards to; nothing redefines a stub field after this.
15
+ // Stub fields must remain as immutable as the frozen nodes they expose.
24
16
  Object.defineProperty(this, key, {
25
17
  value,
26
18
  writable: false,
@@ -33,14 +25,7 @@ function fieldGetter(key) {
33
25
  }
34
26
  return getter;
35
27
  }
36
- /**
37
- * Descriptor entries for one node type's stub fields. Own enumerable getters
38
- * so a spread copy carries correct values. `position`/`data` ride on every
39
- * stub; they may yield `undefined` where a materialized node omits the key —
40
- * accepted drift, invisible to `toEqual`. Installed one `defineProperty` at a
41
- * time: `Object.defineProperties` re-validates the whole map per call and
42
- * costs nearly double.
43
- */
28
+ // Individual defineProperty calls avoid the descriptor-map validation cost of defineProperties.
44
29
  export function stubDescriptors(fields) {
45
30
  const entries = [];
46
31
  for (const key of [...fields, "position", "data"]) {
@@ -56,8 +41,7 @@ export function installStubDescriptors(host, entries) {
56
41
  Object.defineProperty(host, e[0], e[1]);
57
42
  }
58
43
  }
59
- /** Node tags are dense small ints, so the per-child stub loop indexes flat
60
- * arrays instead of paying Map/dictionary lookups. */
44
+ // Dense node tags allow array indexing on the per-child hot path.
61
45
  export function flatByTag(table) {
62
46
  const flat = [];
63
47
  for (const tag of Object.keys(table)) {
@@ -1,19 +1,6 @@
1
- /**
2
- * Binary command buffer for efficient JS→Rust mutation serialization.
3
- *
4
- * Simple mutations (remove, setProperty) are encoded as compact binary commands.
5
- * Structural mutations (insert, replace, …) carry one of two payload kinds:
6
- * compiled op-streams (`PAYLOAD_OPSTREAM`, replayed straight into the arena —
7
- * see op-stream.ts) for declarative content, or raw markdown/HTML strings
8
- * (re-parsed by Rust) for the `{raw}`/`{rawHtml}` escape hatches.
9
- *
10
- * All multi-byte integers are little-endian to match native x86/ARM layout and
11
- * avoid byte-swapping on the Rust side.
12
- */
13
1
  import { OpWriter } from "./op-stream.js";
14
2
  import type { MdastNode } from "./types.js";
15
3
  type ReturnClass = "no_change" | "raw_markdown" | "raw_html" | "structured_node";
16
- /** Input to the structural mutators (`replace`, `insertBefore`, …). */
17
4
  type StructuralContent = MdastNode | {
18
5
  raw: string;
19
6
  mdxExpressions?: boolean;
@@ -25,36 +12,28 @@ export declare function classifyReturn(value: unknown): ReturnClass;
25
12
  export declare const COMMAND_BUFFER_RETAIN_MAX: number;
26
13
  export declare function acquireCommandBuffer(): CommandBuffer;
27
14
  export declare function releaseCommandBuffer(buf: CommandBuffer): void;
28
- /** Structural commands that carry a subtree payload emitted in place via `emitOpstreamCommand`. */
29
15
  export type StructuralOp = "replace" | "insertBefore" | "insertAfter" | "prependChild" | "appendChild" | "wrapNode";
30
16
  export declare const STRUCTURAL_CMD: Record<StructuralOp, number>;
31
17
  export declare class CommandBuffer extends OpWriter {
32
18
  #private;
33
19
  constructor();
34
20
  reset(): void;
35
- /** Emit a structural command whose opstream payload is written by `emit`
36
- * via the inherited op methods. If `emit` returns false or throws, the
37
- * buffer is rolled back to the command start so the Rust decoder never
38
- * sees a half-written command. Returns `emit`'s verdict. */
39
21
  emitOpstreamCommand(cmd: number, nodeId: number, emit: () => boolean): boolean;
40
22
  removeNode(nodeId: number): void;
41
- /** Unified set-property for both MDAST and HAST nodes.
42
- *
43
- * Hot path: uses `encodeInto` to write UTF-8 straight into the buffer (no
44
- * per-call `Uint8Array`), reserving the worst-case length up front and
45
- * backfilling the length prefix once the byte count is known. */
46
23
  setProperty(nodeId: number, key: string, value: unknown): void;
24
+ setField(nodeId: number, key: string, value: unknown): void;
25
+ setAttribute(nodeId: number, name: string, value: unknown): void;
26
+ /** Set a HAST element's list property from `encodeTokenList` bytes, whose
27
+ * separator the renderer picks from the element's schema. */
28
+ setTokenListProperty(nodeId: number, key: string, tokens: string): void;
47
29
  insertBefore(nodeId: number, newNode: StructuralContent): void;
48
30
  insertAfter(nodeId: number, newNode: StructuralContent): void;
49
31
  prependChild(nodeId: number, newNode: StructuralContent): void;
50
32
  appendChild(nodeId: number, newNode: StructuralContent): void;
51
33
  wrapNode(nodeId: number, parentNode: StructuralContent): void;
52
34
  replace(nodeId: number, newNode: StructuralContent): void;
53
- /** Header (cmd + nodeId + PAYLOAD_RAW + flags) followed by a length-prefixed string. */
54
35
  private writeRawCommand;
55
- /** Return a Uint8Array view of the written bytes (no copy). */
56
36
  getBuffer(): Uint8Array;
57
- /** Encode the raw-string escape hatch; declarative nodes use `*Opstream` instead. */
58
37
  private writeStructuralCommand;
59
38
  }
60
39
  export {};
@@ -1,18 +1,7 @@
1
- /**
2
- * Binary command buffer for efficient JS→Rust mutation serialization.
3
- *
4
- * Simple mutations (remove, setProperty) are encoded as compact binary commands.
5
- * Structural mutations (insert, replace, …) carry one of two payload kinds:
6
- * compiled op-streams (`PAYLOAD_OPSTREAM`, replayed straight into the arena —
7
- * see op-stream.ts) for declarative content, or raw markdown/HTML strings
8
- * (re-parsed by Rust) for the `{raw}`/`{rawHtml}` escape hatches.
9
- *
10
- * All multi-byte integers are little-endian to match native x86/ARM layout and
11
- * avoid byte-swapping on the Rust side.
12
- */
1
+ // Little-endian fields match the Rust wire protocol without byte swapping on x86/ARM.
13
2
  import { OpWriter } from "./op-stream.js";
14
- import { PROP_STRING, PROP_BOOL_TRUE, PROP_BOOL_FALSE, PROP_SPACE_SEP, PROP_INT, PROP_NULL, } from "./op-stream.js";
15
- import { CMD_REMOVE, CMD_INSERT_BEFORE, CMD_INSERT_AFTER, CMD_PREPEND_CHILD, CMD_APPEND_CHILD, CMD_WRAP, CMD_REPLACE, CMD_SET_PROPERTY, CMD_SET_CHILDREN, PAYLOAD_RAW, RAW_LITERAL_BRACES, PAYLOAD_OPSTREAM, } from "./generated/wire-constants.js";
3
+ import { PROP_STRING, PROP_BOOL_TRUE, PROP_BOOL_FALSE, PROP_SPACE_SEP, PROP_INT, PROP_NULL, PROP_TOKEN_LIST, } from "./op-stream.js";
4
+ import { CMD_REMOVE, CMD_INSERT_BEFORE, CMD_INSERT_AFTER, CMD_PREPEND_CHILD, CMD_APPEND_CHILD, CMD_WRAP, CMD_REPLACE, CMD_SET_PROPERTY, CMD_SET_FIELD, CMD_SET_ATTRIBUTE, PAYLOAD_RAW, RAW_LITERAL_BRACES, PAYLOAD_OPSTREAM, } from "./generated/wire-constants.js";
16
5
  export function classifyReturn(value) {
17
6
  if (value === undefined || value === null)
18
7
  return "no_change";
@@ -26,10 +15,7 @@ export function classifyReturn(value) {
26
15
  throw new Error("Invalid return value from visitor: must have raw, rawHtml, or type");
27
16
  }
28
17
  const INITIAL_SIZE = 4096;
29
- /** Free list recycling `CommandBuffer` instances (and their grown backings)
30
- * across plugin passes. Safe to retain the backing because `mergeAndReset`
31
- * copies the bytes out before a buffer is released, so no view outlives a
32
- * pass. Cap bounds the pool for processes that briefly burst high. */
18
+ // collectCommands copies bytes before release, so pooled buffers cannot invalidate returned commands.
33
19
  const COMMAND_BUFFER_POOL_MAX = 8;
34
20
  /** Backings above this are dropped on release so one huge compile does not retain megabytes forever. */
35
21
  export const COMMAND_BUFFER_RETAIN_MAX = 1 << 20;
@@ -54,8 +40,7 @@ export const STRUCTURAL_CMD = {
54
40
  wrapNode: CMD_WRAP,
55
41
  };
56
42
  export class CommandBuffer extends OpWriter {
57
- /** Set while a structural payload is being emitted in place; command
58
- * methods must not interleave bytes into it. */
43
+ // Commands must not interleave bytes while a structural payload is being emitted.
59
44
  #inOpstream = false;
60
45
  constructor() {
61
46
  super(INITIAL_SIZE);
@@ -69,14 +54,10 @@ export class CommandBuffer extends OpWriter {
69
54
  throw new Error("reentrant command emission: a node getter or toJSON invoked a context mutation while a structural payload was being encoded");
70
55
  }
71
56
  }
72
- /** Emit a structural command whose opstream payload is written by `emit`
73
- * via the inherited op methods. If `emit` returns false or throws, the
74
- * buffer is rolled back to the command start so the Rust decoder never
75
- * sees a half-written command. Returns `emit`'s verdict. */
57
+ // Roll back failed payloads so the Rust decoder never sees a partial command.
76
58
  emitOpstreamCommand(cmd, nodeId, emit) {
77
59
  const commandStart = this.n;
78
60
  const lenPos = this.#beginOpstream(cmd, nodeId);
79
- // ok starts false so a throwing emit still hits the abort in finally
80
61
  let ok = false;
81
62
  try {
82
63
  ok = emit();
@@ -89,9 +70,6 @@ export class CommandBuffer extends OpWriter {
89
70
  this.#endOpstream(lenPos);
90
71
  return ok;
91
72
  }
92
- /** Open a structural command whose opstream payload is emitted in place via
93
- * the inherited op methods; returns the backpatch position for
94
- * `#endOpstream`. Pair with `#abortOpstream` on failure. */
95
73
  #beginOpstream(cmd, nodeId) {
96
74
  this.#assertNotEncoding();
97
75
  this.#inOpstream = true;
@@ -107,7 +85,6 @@ export class CommandBuffer extends OpWriter {
107
85
  this.#inOpstream = false;
108
86
  this.patchU32(lenPos, this.n - (lenPos + 4));
109
87
  }
110
- /** Roll back an in-progress opstream command (unencodable content). */
111
88
  #abortOpstream(commandStart) {
112
89
  this.#inOpstream = false;
113
90
  this.n = commandStart;
@@ -118,12 +95,16 @@ export class CommandBuffer extends OpWriter {
118
95
  this.buf[this.n++] = CMD_REMOVE;
119
96
  this.writeU32(nodeId);
120
97
  }
121
- /** Unified set-property for both MDAST and HAST nodes.
122
- *
123
- * Hot path: uses `encodeInto` to write UTF-8 straight into the buffer (no
124
- * per-call `Uint8Array`), reserving the worst-case length up front and
125
- * backfilling the length prefix once the byte count is known. */
126
98
  setProperty(nodeId, key, value) {
99
+ this.#writeNamedValue(CMD_SET_PROPERTY, nodeId, key, value);
100
+ }
101
+ setField(nodeId, key, value) {
102
+ this.#writeNamedValue(CMD_SET_FIELD, nodeId, key, value);
103
+ }
104
+ setAttribute(nodeId, name, value) {
105
+ this.#writeNamedValue(CMD_SET_ATTRIBUTE, nodeId, name, value);
106
+ }
107
+ #writeNamedValue(cmd, nodeId, key, value) {
127
108
  this.#assertNotEncoding();
128
109
  let valueType;
129
110
  let str;
@@ -151,14 +132,24 @@ export class CommandBuffer extends OpWriter {
151
132
  valueType = PROP_STRING;
152
133
  str = String(value);
153
134
  }
154
- // 1(cmd) + 4(nodeId) + 1(valueType); name and value are length-prefixed strings
155
135
  this.ensure(6);
156
- this.buf[this.n++] = CMD_SET_PROPERTY;
136
+ this.buf[this.n++] = cmd;
157
137
  this.writeU32(nodeId);
158
138
  this.buf[this.n++] = valueType;
159
139
  this.utf8WithU32Len(key);
160
140
  this.utf8WithU32Len(str);
161
141
  }
142
+ /** Set a HAST element's list property from `encodeTokenList` bytes, whose
143
+ * separator the renderer picks from the element's schema. */
144
+ setTokenListProperty(nodeId, key, tokens) {
145
+ this.#assertNotEncoding();
146
+ this.ensure(6);
147
+ this.buf[this.n++] = CMD_SET_PROPERTY;
148
+ this.writeU32(nodeId);
149
+ this.buf[this.n++] = PROP_TOKEN_LIST;
150
+ this.utf8WithU32Len(key);
151
+ this.utf8WithU32Len(tokens);
152
+ }
162
153
  insertBefore(nodeId, newNode) {
163
154
  this.writeStructuralCommand(CMD_INSERT_BEFORE, nodeId, newNode);
164
155
  }
@@ -177,7 +168,6 @@ export class CommandBuffer extends OpWriter {
177
168
  replace(nodeId, newNode) {
178
169
  this.writeStructuralCommand(CMD_REPLACE, nodeId, newNode);
179
170
  }
180
- /** Header (cmd + nodeId + PAYLOAD_RAW + flags) followed by a length-prefixed string. */
181
171
  writeRawCommand(cmd, nodeId, flags, s) {
182
172
  this.#assertNotEncoding();
183
173
  this.ensure(7);
@@ -187,11 +177,10 @@ export class CommandBuffer extends OpWriter {
187
177
  this.buf[this.n++] = flags;
188
178
  this.utf8WithU32Len(s);
189
179
  }
190
- /** Return a Uint8Array view of the written bytes (no copy). */
180
+ // This view aliases the buffer and must be copied before it is released to the pool.
191
181
  getBuffer() {
192
182
  return this.take();
193
183
  }
194
- /** Encode the raw-string escape hatch; declarative nodes use `*Opstream` instead. */
195
184
  writeStructuralCommand(cmd, nodeId, node) {
196
185
  const v = node;
197
186
  if (typeof v.raw === "string") {
@@ -0,0 +1,11 @@
1
+ import type { markdownToHtmlFast } from "#binding";
2
+ import type { Features, MdxOnlyOptions } from "./compile-types.js";
3
+ export declare const DEFAULT_PARSE_OPTIONS: number;
4
+ export type NativeConvertOptions = NonNullable<Parameters<typeof markdownToHtmlFast>[2]>;
5
+ type NativeCompileOptions = {
6
+ parseOptions: number;
7
+ convertOptions: NativeConvertOptions | undefined;
8
+ };
9
+ export declare function featuresToNative(features: Features | undefined): NativeCompileOptions;
10
+ export declare function mdxOptionsToNative(opts: MdxOnlyOptions): MdxOnlyOptions | undefined;
11
+ export {};