satteri 0.8.0 → 0.8.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.
@@ -52,6 +52,8 @@ export declare class CommandBuffer {
52
52
  }): void;
53
53
  /** Write a REPLACE command with a pre-serialized JSON payload. */
54
54
  replaceRawJson(nodeId: number, json: string): void;
55
+ /** Replace a node's child list (Root-wrapped `json`) while keeping the node. */
56
+ setChildren(nodeId: number, json: string): void;
55
57
  /** Write any structural command with a pre-serialized JSON payload. */
56
58
  insertBeforeRawJson(nodeId: number, json: string): void;
57
59
  insertAfterRawJson(nodeId: number, json: string): void;
@@ -17,6 +17,7 @@ const CMD_APPEND_CHILD = 0x08;
17
17
  const CMD_WRAP = 0x09;
18
18
  const CMD_REPLACE = 0x0b;
19
19
  const CMD_SET_PROPERTY = 0x0c;
20
+ const CMD_SET_CHILDREN = 0x0d;
20
21
  // Payload types (0x10+, distinct range from commands)
21
22
  const PAYLOAD_RAW_MARKDOWN = 0x10;
22
23
  const PAYLOAD_RAW_HTML = 0x11;
@@ -119,6 +120,10 @@ export class CommandBuffer {
119
120
  replaceRawJson(nodeId, json) {
120
121
  this.writeRawJsonCommand(CMD_REPLACE, nodeId, json);
121
122
  }
123
+ /** Replace a node's child list (Root-wrapped `json`) while keeping the node. */
124
+ setChildren(nodeId, json) {
125
+ this.writeRawJsonCommand(CMD_SET_CHILDREN, nodeId, json);
126
+ }
122
127
  /** Write any structural command with a pre-serialized JSON payload. */
123
128
  insertBeforeRawJson(nodeId, json) {
124
129
  this.writeRawJsonCommand(CMD_INSERT_BEFORE, nodeId, json);
@@ -23,16 +23,20 @@ export interface HastVisitorContext {
23
23
  readonly fileURL: URL | undefined;
24
24
  removeNode(node: Readonly<HastNode>): void;
25
25
  replaceNode(node: Readonly<HastNode>, newNode: HastNode): void;
26
- insertBefore(node: Readonly<HastNode>, newNode: HastNode): void;
27
- insertAfter(node: Readonly<HastNode>, newNode: HastNode): void;
26
+ insertBefore(node: Readonly<HastNode>, newNode: HastNode | HastNode[]): void;
27
+ insertAfter(node: Readonly<HastNode>, newNode: HastNode | HastNode[]): void;
28
28
  /**
29
29
  * Wrap `node` in `parentNode`, making it `parentNode`'s first child. Any
30
30
  * children `parentNode` declares are kept after it, so a `div` with an anchor
31
31
  * child wraps a heading as `div > [heading, anchor]`.
32
32
  */
33
33
  wrapNode(node: Readonly<HastNode>, parentNode: HastNode): void;
34
- prependChild(node: Readonly<HastNode>, childNode: HastNode): void;
35
- appendChild(node: Readonly<HastNode>, childNode: HastNode): void;
34
+ prependChild(node: Readonly<HastNode>, childNode: HastNode | HastNode[]): void;
35
+ appendChild(node: Readonly<HastNode>, childNode: HastNode | HastNode[]): void;
36
+ /** Insert one node or an array at `index`; clamps (`0` or less prepends, past the end appends). */
37
+ insertChildAt(node: Readonly<HastNode>, index: number, childNode: HastNode | HastNode[]): void;
38
+ /** Remove the `index`-th child of `node`; a no-op when there is no such child. */
39
+ removeChildAt(node: Readonly<HastNode>, index: number): void;
36
40
  setProperty(node: Readonly<HastNode>, key: string, value: unknown): void;
37
41
  /** Collect the concatenated text of all descendant text nodes (like DOM textContent). */
38
42
  textContent(node: Readonly<HastNode>): string;
@@ -58,6 +58,9 @@ function markHastNode(node, isRoot) {
58
58
  function nid(node) {
59
59
  return nodeIdMap.get(node) ?? node._nodeId;
60
60
  }
61
+ function asArray(value) {
62
+ return Array.isArray(value) ? value : [value];
63
+ }
61
64
  class HastVisitorContextImpl {
62
65
  #commandBuffer = new CommandBuffer();
63
66
  #diagnostics = [];
@@ -85,22 +88,62 @@ class HastVisitorContextImpl {
85
88
  this.#pendingNodes.set(id, newNode);
86
89
  }
87
90
  insertBefore(node, newNode) {
88
- this.#commandBuffer.insertBeforeRawJson(nid(node), JSON.stringify(markHast(newNode)));
91
+ const id = nid(node);
92
+ for (const n of asArray(newNode)) {
93
+ this.#commandBuffer.insertBeforeRawJson(id, JSON.stringify(markHast(n)));
94
+ }
89
95
  }
90
96
  insertAfter(node, newNode) {
91
- this.#commandBuffer.insertAfterRawJson(nid(node), JSON.stringify(markHast(newNode)));
97
+ const id = nid(node);
98
+ for (const n of asArray(newNode)) {
99
+ this.#commandBuffer.insertAfterRawJson(id, JSON.stringify(markHast(n)));
100
+ }
92
101
  }
93
102
  wrapNode(node, parentNode) {
94
103
  this.#commandBuffer.wrapNodeRawJson(nid(node), JSON.stringify(markHast(parentNode)));
95
104
  }
96
105
  prependChild(node, childNode) {
97
- this.#commandBuffer.prependChildRawJson(nid(node), JSON.stringify(markHast(childNode)));
106
+ const id = nid(node);
107
+ for (const n of asArray(childNode)) {
108
+ this.#commandBuffer.prependChildRawJson(id, JSON.stringify(markHast(n)));
109
+ }
98
110
  }
99
111
  appendChild(node, childNode) {
100
- this.#commandBuffer.appendChildRawJson(nid(node), JSON.stringify(markHast(childNode)));
112
+ const id = nid(node);
113
+ for (const n of asArray(childNode)) {
114
+ this.#commandBuffer.appendChildRawJson(id, JSON.stringify(markHast(n)));
115
+ }
116
+ }
117
+ insertChildAt(node, index, childNode) {
118
+ const children = "children" in node ? node.children : [];
119
+ if (index <= 0 || children.length === 0) {
120
+ this.prependChild(node, childNode);
121
+ }
122
+ else if (index >= children.length) {
123
+ this.appendChild(node, childNode);
124
+ }
125
+ else {
126
+ this.insertBefore(children[index], childNode);
127
+ }
128
+ }
129
+ removeChildAt(node, index) {
130
+ const child = "children" in node ? node.children[index] : undefined;
131
+ if (child)
132
+ this.removeNode(child);
101
133
  }
102
134
  setProperty(node, key, value) {
103
135
  const id = nid(node);
136
+ if (key === "children") {
137
+ // children is structural: set-children keeps the node and swaps only its
138
+ // child list (reused children keep their id).
139
+ const wrapper = {
140
+ _hast: true,
141
+ type: "root",
142
+ children: value.map((child) => markHastNode(child, false)),
143
+ };
144
+ this.#commandBuffer.setChildren(id, JSON.stringify(wrapper));
145
+ return;
146
+ }
104
147
  if (key === "data") {
105
148
  this.#commandBuffer.setProperty(id, key, value != null ? JSON.stringify(value) : null);
106
149
  return;
@@ -22,15 +22,19 @@ export declare class MdastVisitorContext {
22
22
  constructor(handle: MdastHandle, getSource: () => string, fileURL: URL | undefined);
23
23
  get source(): string;
24
24
  removeNode(node: Readonly<MdastNode>): void;
25
- insertBefore(node: Readonly<MdastNode>, newNode: MdastNode): void;
26
- insertAfter(node: Readonly<MdastNode>, newNode: MdastNode): void;
25
+ insertBefore(node: Readonly<MdastNode>, newNode: MdastNode | MdastNode[]): void;
26
+ insertAfter(node: Readonly<MdastNode>, newNode: MdastNode | MdastNode[]): void;
27
27
  /**
28
28
  * Wrap `node` in `parentNode`, making it `parentNode`'s first child. Any
29
29
  * children `parentNode` declares are kept after it.
30
30
  */
31
31
  wrapNode(node: Readonly<MdastNode>, parentNode: MdastNode): void;
32
- prependChild(node: Readonly<MdastNode>, childNode: MdastNode): void;
33
- appendChild(node: Readonly<MdastNode>, childNode: MdastNode): void;
32
+ prependChild(node: Readonly<MdastNode>, childNode: MdastNode | MdastNode[]): void;
33
+ appendChild(node: Readonly<MdastNode>, childNode: MdastNode | MdastNode[]): void;
34
+ /** Insert one node or an array at `index`; clamps (`0` or less prepends, past the end appends). */
35
+ insertChildAt(node: Readonly<MdastNode>, index: number, childNode: MdastNode | MdastNode[]): void;
36
+ /** Remove the `index`-th child of `node`; a no-op when there is no such child. */
37
+ removeChildAt(node: Readonly<MdastNode>, index: number): void;
34
38
  replaceNode(node: Readonly<MdastNode>, newNode: MdastNode): void;
35
39
  setProperty<N extends MdastNode, K extends keyof N & string>(node: Readonly<N>, key: K, value: N[K]): void;
36
40
  /** Collect the concatenated text of all descendant text nodes (like mdast-util-to-string). */
@@ -55,6 +55,9 @@ const mdastNodeIdMap = new WeakMap();
55
55
  function nid(node) {
56
56
  return mdastNodeIdMap.get(node) ?? node._nodeId;
57
57
  }
58
+ function asArray(value) {
59
+ return Array.isArray(value) ? value : [value];
60
+ }
58
61
  export class MdastVisitorContext {
59
62
  #commandBuffer = new CommandBuffer();
60
63
  #diagnostics = [];
@@ -80,10 +83,14 @@ export class MdastVisitorContext {
80
83
  this.#commandBuffer.removeNode(nid(node));
81
84
  }
82
85
  insertBefore(node, newNode) {
83
- this.#commandBuffer.insertBefore(nid(node), newNode);
86
+ const id = nid(node);
87
+ for (const n of asArray(newNode))
88
+ this.#commandBuffer.insertBefore(id, n);
84
89
  }
85
90
  insertAfter(node, newNode) {
86
- this.#commandBuffer.insertAfter(nid(node), newNode);
91
+ const id = nid(node);
92
+ for (const n of asArray(newNode))
93
+ this.#commandBuffer.insertAfter(id, n);
87
94
  }
88
95
  /**
89
96
  * Wrap `node` in `parentNode`, making it `parentNode`'s first child. Any
@@ -93,15 +100,45 @@ export class MdastVisitorContext {
93
100
  this.#commandBuffer.wrapNode(nid(node), parentNode);
94
101
  }
95
102
  prependChild(node, childNode) {
96
- this.#commandBuffer.prependChild(nid(node), childNode);
103
+ const id = nid(node);
104
+ for (const n of asArray(childNode))
105
+ this.#commandBuffer.prependChild(id, n);
97
106
  }
98
107
  appendChild(node, childNode) {
99
- this.#commandBuffer.appendChild(nid(node), childNode);
108
+ const id = nid(node);
109
+ for (const n of asArray(childNode))
110
+ this.#commandBuffer.appendChild(id, n);
111
+ }
112
+ /** Insert one node or an array at `index`; clamps (`0` or less prepends, past the end appends). */
113
+ insertChildAt(node, index, childNode) {
114
+ const children = "children" in node ? node.children : [];
115
+ if (index <= 0 || children.length === 0) {
116
+ this.prependChild(node, childNode);
117
+ }
118
+ else if (index >= children.length) {
119
+ this.appendChild(node, childNode);
120
+ }
121
+ else {
122
+ this.insertBefore(children[index], childNode);
123
+ }
124
+ }
125
+ /** Remove the `index`-th child of `node`; a no-op when there is no such child. */
126
+ removeChildAt(node, index) {
127
+ const child = "children" in node ? node.children[index] : undefined;
128
+ if (child)
129
+ this.removeNode(child);
100
130
  }
101
131
  replaceNode(node, newNode) {
102
132
  this.#commandBuffer.replace(nid(node), newNode);
103
133
  }
104
134
  setProperty(node, key, value) {
135
+ if (key === "children") {
136
+ // children is structural: set-children keeps the node and swaps only its
137
+ // child list (reused children keep their id).
138
+ const wrapper = refifyReusedNodes({ type: "root", children: value }, true);
139
+ this.#commandBuffer.setChildren(nid(node), JSON.stringify(wrapper));
140
+ return;
141
+ }
105
142
  if (key === "data") {
106
143
  // data is stored as JSON in the arena, serialize it for the command buffer
107
144
  this.#commandBuffer.setProperty(nid(node), key, value != null ? JSON.stringify(value) : null);
@@ -396,11 +433,18 @@ function readMdastMatchedNode(view, buf, dataOffset, nodeId, nodeType, resolver)
396
433
  node.label = rstr(buf, pos, labelLen);
397
434
  pos += labelLen;
398
435
  const kind = buf[pos];
436
+ pos += 1;
399
437
  // Only link/image references carry `referenceType`; the mdast spec
400
438
  // defines it for those two, not for `footnoteReference`.
401
439
  if (nodeType !== 20) {
402
440
  node.referenceType = ["shortcut", "collapsed", "full"][kind] ?? "shortcut";
403
441
  }
442
+ // imageReference also carries `alt` (the serializer appends it).
443
+ if (nodeType === 18) {
444
+ const altLen = ru16(view, pos);
445
+ pos += 2;
446
+ node.alt = rstr(buf, pos, altLen);
447
+ }
404
448
  break;
405
449
  }
406
450
  case 19: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "satteri",
3
- "version": "0.8.0",
3
+ "version": "0.8.1",
4
4
  "description": "High-performance Markdown and MDX processing",
5
5
  "repository": {
6
6
  "type": "git",
@@ -80,11 +80,11 @@
80
80
  ]
81
81
  },
82
82
  "optionalDependencies": {
83
- "@bruits/satteri-linux-x64-gnu": "0.8.0",
84
- "@bruits/satteri-darwin-x64": "0.8.0",
85
- "@bruits/satteri-darwin-arm64": "0.8.0",
86
- "@bruits/satteri-win32-x64-msvc": "0.8.0",
87
- "@bruits/satteri-wasm32-wasi": "0.8.0"
83
+ "@bruits/satteri-linux-x64-gnu": "0.8.1",
84
+ "@bruits/satteri-darwin-x64": "0.8.1",
85
+ "@bruits/satteri-darwin-arm64": "0.8.1",
86
+ "@bruits/satteri-win32-x64-msvc": "0.8.1",
87
+ "@bruits/satteri-wasm32-wasi": "0.8.1"
88
88
  },
89
89
  "scripts": {
90
90
  "build:binary:wasm": "napi build --manifest-path ../../crates/satteri-napi-binding/Cargo.toml --output-dir . --platform --release --esm --strip --target wasm32-wasip1-threads",