satteri 0.9.2 → 0.9.4

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/README.md CHANGED
@@ -2,6 +2,8 @@
2
2
 
3
3
  Native-enhanced Markdown parsing and processing for JavaScript. Parse and compile in Rust, create flexible plugins in JavaScript.
4
4
 
5
+ Check out the [documentation](https://satteri.bruits.org/docs/) for [installation instructions](https://satteri.bruits.org/docs/installation/), the [API reference](https://satteri.bruits.org/docs/entry-points/), and [usage examples](https://satteri.bruits.org/docs/quick-start/), try it online on the [playground](https://satteri.bruits.org/playground), or join us on [Discord](https://discord.com/invite/84pd4QtmzA)!
6
+
5
7
  ## Install
6
8
 
7
9
  ```sh
@@ -17,7 +19,7 @@ pnpm add satteri
17
19
  ```ts
18
20
  import { markdownToHtml } from "satteri";
19
21
 
20
- const html = markdownToHtml("# Hello\n\nWorld");
22
+ const { html } = markdownToHtml("# Hello\n\nWorld");
21
23
  // <h1>Hello</h1>\n<p>World</p>
22
24
  ```
23
25
 
@@ -26,345 +28,31 @@ const html = markdownToHtml("# Hello\n\nWorld");
26
28
  ```ts
27
29
  import { mdxToJs } from "satteri";
28
30
 
29
- const js = mdxToJs("# Hello\n\n<MyComponent />");
31
+ const { code } = mdxToJs("# Hello\n\n<MyComponent />");
30
32
  ```
31
33
 
32
34
  ### With plugins
33
35
 
34
- Both functions accept `mdastPlugins` (operate on the Markdown AST before conversion to HAST) and `hastPlugins` (operate on the HAST before output).
35
-
36
- ```ts
37
- import { markdownToHtml } from "satteri";
38
- import { removeHeadings } from "./my-mdast-plugins.js";
39
- import { addLinkClasses } from "./my-hast-plugins.js";
40
-
41
- const html = markdownToHtml("# Hello\n\n[link](https://example.com)", {
42
- mdastPlugins: [removeHeadings],
43
- hastPlugins: [addLinkClasses],
44
- });
45
- ```
46
-
47
- If you're familiar with the unified ecosystem, mdast and hast plugins would be similar to remark and rehype plugins, respectively, and re-uses the same AST shape for both. This project does not currently have an equivalent of micromark or recma plugins.
48
-
49
- ## Plugins
50
-
51
- ### MDAST plugins
52
-
53
- MDAST plugins run on the Markdown syntax tree, allowing you to do things like replace emoji shortcodes, unwrap images from paragraphs, or collect headings for a table of contents before Markdown is transformed to HTML / JS. Define visitor methods named after node types (`heading`, `code`, `link`, `image`, etc.). Each visitor receives the node and a context object for mutations.
54
-
55
- ```ts
56
- const emojis = defineMdastPlugin({
57
- name: "emojis",
58
- text(node, ctx) {
59
- if (node.value.includes(":wave:")) {
60
- ctx.setProperty(node, "value", node.value.replaceAll(":wave:", "\u{1F44B}"));
61
- }
62
- },
63
- });
64
- ```
65
-
66
- Visitors can alternatively return a replacement node, raw Markdown, or raw HTML. This is useful when the replacement can't be expressed as property changes on the original node:
67
-
68
- ```ts
69
- const highlightCode = defineMdastPlugin({
70
- name: "highlight-code",
71
- code(node) {
72
- return { rawHtml: `<pre class="highlighted">${escape(node.value)}</pre>` };
73
- },
74
- });
75
- ```
76
-
77
- All standard mdast node types are supported, plus GFM extensions (`table`, `tableRow`, `tableCell`, `delete`, `footnoteDefinition`, `footnoteReference`) and MDX nodes (`mdxJsxFlowElement`, `mdxJsxTextElement`, `mdxFlowExpression`, `mdxTextExpression`, `mdxjsEsm`) if enabled. For more information on the AST shape and node types, see the [mdast spec](https://github.com/syntax-tree/mdast).
78
-
79
- ### HAST plugins
80
-
81
- HAST plugins run on the HTML syntax tree after mdast-to-hast conversion, allowing you to do things like add classes to elements, set attributes on links, or wrap HTML elements with other elements, etc. Element visitors use a `filter` array to specify which tag names (or component names for MDX) to match.
82
-
83
- ```ts
84
- const addLinkClasses = defineHastPlugin({
85
- name: "add-link-classes",
86
- element: {
87
- filter: ["a"],
88
- visit(node, ctx) {
89
- ctx.setProperty(node, "class", "link");
90
- ctx.setProperty(node, "target", "_blank");
91
- },
92
- },
93
- });
94
- ```
95
-
96
- Multiple filter groups on the same node type:
97
-
98
- ```ts
99
- const multiFilter = defineHastPlugin({
100
- name: "multi-filter",
101
- element: [
102
- {
103
- filter: ["h1", "h2", "h3"],
104
- visit(node, ctx) {
105
- ctx.setProperty(node, "class", "heading");
106
- },
107
- },
108
- {
109
- filter: ["a"],
110
- visit(node, ctx) {
111
- ctx.setProperty(node, "target", "_blank");
112
- },
113
- },
114
- ],
115
- });
116
- ```
117
-
118
- An empty filter matches all elements, but can quickly become expensive when used on large documents, so use with caution:
119
-
120
- ```ts
121
- const allElements = defineHastPlugin({
122
- name: "all-elements",
123
- element: {
124
- filter: [],
125
- visit(node, ctx) {
126
- ctx.setProperty(node, "data-visited", "true");
127
- },
128
- },
129
- });
130
- ```
131
-
132
- Non-element visitors (`text`, `comment`, `raw`, `doctype`, MDX expression types) use bare functions instead of filter objects:
133
-
134
- ```ts
135
- const uppercaseText = defineHastPlugin({
136
- name: "uppercase-text",
137
- text(node, ctx) {
138
- ctx.setProperty(node, "value", node.value.toUpperCase());
139
- },
140
- });
141
- ```
142
-
143
- For more information on the AST shape and node types, see the [hast spec](https://github.com/syntax-tree/hast).
144
-
145
- ### Mutating nodes
146
-
147
- Unlike remark and rehype plugins, nodes in Sätteri inside plugins are read-only. The AST lives in Rust memory and JavaScript only has a "view" over the different nodes, so direct mutations like `node.value = "new text"` have no effect. Use the context methods (`ctx.setProperty`, `ctx.removeNode`, `ctx.replaceNode`, etc.) instead, which send changes back to Rust in an efficient way.
148
-
149
- ```ts
150
- // Won't work
151
- heading(node, ctx) {
152
- node.depth = 2; // no effect, TypeScript will also complain that the node is readonly
153
- }
154
-
155
- // Do this instead
156
- heading(node, ctx) {
157
- ctx.setProperty(node, "depth", 2);
158
- }
159
-
160
- // Or return a new node to replace it entirely, but this is less efficient and generally not recommended
161
- heading(node) {
162
- return { ..node, depth: 2 };
163
- }
164
- ```
165
-
166
- ### Sharing data between plugins
167
-
168
- Each context exposes a `data` object, a document-scoped grab bag shared across every visitor in the compile. Writes from one plugin are visible to later plugins, and the bag persists across the mdast→hast boundary, so hast plugins can read what mdast plugins wrote. After compilation the final state is returned on `result.data`, making it suitable for things like extracting a table of contents.
169
-
170
- ```ts
171
- const collectHeadings = defineMdastPlugin({
172
- name: "collect-headings",
173
- heading(node, ctx) {
174
- const list = (ctx.data.headings as string[]) ?? [];
175
- const first = node.children[0];
176
- if (first && "value" in first) list.push(first.value as string);
177
- ctx.data.headings = list;
178
- },
179
- });
180
-
181
- const { html, data } = markdownToHtml("# A\n\n# B", { mdastPlugins: [collectHeadings] });
182
- console.log(data?.headings); // ["A", "B"]
183
- ```
184
-
185
- The bag lives entirely on the JS side, so any value is allowed, including functions, class instances, and `Map`/`Set`. References are preserved across plugins and across the mdast→hast boundary. A fresh, empty bag is created for every compile unless you seed one with the `data` option.
186
-
187
- To seed data into the bag before plugins run, pass an object to the `data` option. The same object is available in all plugins and returned as `result.data` after compilation, so you can pre-populate it with values or methods that plugins can read and update.
188
-
189
- ```ts
190
- const data = { title: "Hello" };
191
- const rewriteTitle = defineMdastPlugin({
192
- name: "rewrite-title",
193
- heading(_node, ctx) {
194
- ctx.data.title = "Updated";
195
- },
196
- });
197
-
198
- const result = markdownToHtml("# A", { mdastPlugins: [rewriteTitle], data });
199
- console.log(result.data === data); // true, the seeded object is returned
200
- console.log(data.title); // "Updated"
201
- ```
202
-
203
- By default keys are typed as `unknown`. To give a key a type, augment the `DataMap` interface:
204
-
205
- ```ts
206
- declare module "satteri" {
207
- interface DataMap {
208
- headings: string[];
209
- }
210
- }
211
-
212
- // now ctx.data.headings and result.data.headings are typed as string[] | undefined
213
- ```
214
-
215
- Unregistered keys stay `unknown`, so the bag remains open-ended.
216
-
217
- ### How transforms compose
218
-
219
- Unlike remark and rehype, which re-walk the tree until it stops changing, each Sätteri plugin walks the tree **once**. Within that single pass:
220
-
221
- - **Passed-through children keep their identity.** When a visitor returns a replacement that reuses the original node's children (e.g. `{ ...node, children: [...node.children] }`), those children are spliced back unchanged — so a transform the same pass queues on a nested one still applies. This is what lets a `containerDirective` visitor turn both an outer `:::note` and a nested `:::tip` into asides in one go.
222
- - **A plugin's own freshly-built nodes are not re-walked by that plugin.** If a visitor returns a brand-new node (one that didn't come from the tree), the same plugin won't visit it. Produce its final shape directly, or hand it off to a later plugin — every plugin runs over the fully materialized output of the ones before it.
223
- - **Dropping a subtree drops transforms queued inside it.** If one visitor removes or replaces a node while another (in the same pass) had queued a transform on something inside that subtree, the orphaned transform is dropped and a warning is logged. This is usually intentional — you discarded that subtree on purpose — but the warning helps catch the cases where it isn't.
224
-
225
- ### Async plugins
226
-
227
- Visitors can optionally be async. When any visitor is async, `markdownToHtml` and `mdxToJs` return a `Promise<string>` instead of `string`. For performance reasons, it is typically best to avoid async visitors, especially if your visitor matches a large number of nodes.
228
-
229
- ````ts
230
- const highlighter = await createHighlighter({ themes: ["github-dark"], langs: ["js", "ts"] });
231
-
232
- const asyncHighlight = defineMdastPlugin({
233
- name: "async-highlight",
234
- async code(node) {
235
- const html = await highlighter.codeToHtml(node.value, {
236
- lang: node.lang,
237
- theme: "github-dark",
238
- });
239
- return { rawHtml: html };
240
- },
241
- });
242
-
243
- // Returns Promise<string> when async plugins are used
244
- const html = await markdownToHtml("```js\ncode\n```", {
245
- mdastPlugins: [asyncHighlight],
246
- });
247
- ````
248
-
249
- ## API
250
-
251
- ### `markdownToHtml(source: string, options?: CompileOptions)`
252
-
253
- Parse Markdown and compile to HTML. Returns `string` if all plugins are sync, `Promise<string>` if any are async.
36
+ Both 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.
254
37
 
255
38
  ```ts
256
- const html = markdownToHtml("# Hello\n\nWorld");
257
- // <h1>Hello</h1>\n<p>World</p>
258
- ```
259
-
260
- ### `mdxToJs(source: string, options?: MdxCompileOptions)`
39
+ import { markdownToHtml, defineMdastPlugin } from "satteri";
261
40
 
262
- Parse MDX and compile to JavaScript module code. Same sync/async return behavior.
263
-
264
- ```ts
265
- const js = mdxToJs("# Hello\n\n<MyComponent />");
266
- ```
267
-
268
- #### Static optimization
269
-
270
- The `optimizeStatic` option for MDX collapses static subtrees into pre-rendered HTML strings, reducing the number of JSX element calls in the output and increasing rendering performance. Dynamic content (JSX components, expressions) is preserved as normal JSX calls.
271
-
272
- ```ts
273
- // Astro-style: wraps static HTML in <Fragment set:html="...">
274
- const js = mdxToJs("# Hello\n\nWorld", {
275
- optimizeStatic: {
276
- component: "Fragment",
277
- prop: "set:html",
41
+ const stripInlineCode = defineMdastPlugin({
42
+ name: "strip-inline-code",
43
+ inlineCode(node, ctx) {
44
+ ctx.replaceNode(node, { type: "text", value: node.value });
278
45
  },
279
46
  });
280
47
 
281
- // React-style: wraps in <div dangerouslySetInnerHTML={{ __html: "..." }}>
282
- const js = mdxToJs("# Hello\n\nWorld", {
283
- optimizeStatic: {
284
- component: "div",
285
- prop: "dangerouslySetInnerHTML",
286
- wrapPropValue: true,
287
- },
48
+ const { html } = markdownToHtml("Use `let` instead of `var`.", {
49
+ mdastPlugins: [stripInlineCode],
288
50
  });
51
+ // <p>Use let instead of var.</p>
289
52
  ```
290
53
 
291
- The `ignoreElements` option can be used to exclude specific elements from collapsing.
292
-
293
- Shoutout to [Bjorn Lu](https://bjornlu.com) for originally developing this optimization for [Astro](https://astro.build/).
294
-
295
- ### `markdownToMdast(source: string)`
296
-
297
- Parse Markdown and return a complete mdast tree. This can be useful if you wanted to benefit from the fast native parsing of Sätteri, but ultimately wanted another pipeline to handle transformations and compilation, e.g. using remark plugins and `remark-stringify` to convert back to Markdown after processing.
298
-
299
- ```ts
300
- import { markdownToMdast } from "satteri";
301
-
302
- const tree = markdownToMdast("# Hello\n\nWorld");
303
- // tree.children[0].type === "heading"
304
- // tree.children[0].depth === 1
305
- ```
306
-
307
- ### `mdxToMdast(source: string)`
308
-
309
- Parse MDX and return a complete mdast tree.
310
-
311
- ```ts
312
- const tree = mdxToMdast('<Component foo="bar" />');
313
- // tree.children[0].type === "mdxJsxFlowElement"
314
- // tree.children[0].name === "Component"
315
- ```
316
-
317
- ### `markdownToHast(source: string)`
318
-
319
- Parse Markdown, convert to hast, and return a complete hast tree.
320
-
321
- ```ts
322
- const tree = markdownToHast("# Hello\n\nWorld");
323
- // tree.children[0].type === "element"
324
- // tree.children[0].tagName === "h1"
325
- ```
326
-
327
- ### `mdxToHast(source: string)`
328
-
329
- Parse MDX, convert to hast, and return a complete hast tree.
330
-
331
- ```ts
332
- const tree = mdxToHast("<MyComponent />");
333
- // tree.children[0].type === "mdxJsxFlowElement"
334
- // tree.children[0].name === "MyComponent"
335
- ```
336
-
337
- ### `defineMdastPlugin(definition: MdastPluginDefinition)`
338
-
339
- Type-safe wrapper for MDAST plugin definitions.
340
-
341
- ### `defineHastPlugin(definition: HastPluginDefinition)`
342
-
343
- Type-safe wrapper for HAST plugin definitions.
344
-
345
- ### `CompileOptions`
346
-
347
- ```ts
348
- interface CompileOptions {
349
- mdastPlugins?: MdastPluginDefinition[];
350
- hastPlugins?: HastPluginDefinition[];
351
- features?: Features;
352
- fileURL?: URL;
353
- data?: Data;
354
- }
355
-
356
- // mdxToJs accepts MdxCompileOptions, which extends CompileOptions
357
- interface MdxCompileOptions extends CompileOptions {
358
- optimizeStatic?: OptimizeStaticConfig;
359
- }
360
- ```
361
-
362
- `fileURL` is the `URL` of the document being processed, surfaced to plugins as `ctx.fileURL` (a `URL`, or `undefined` when omitted). Pass a file URL such as Astro's `fileURL`, or convert a filesystem path with Node's `pathToFileURL`; read it back with `fileURLToPath(ctx.fileURL)`.
363
-
364
- `data` seeds the document-level data bag before plugins run, surfaced as `ctx.data` and returned as `result.data`. See [Sharing data between plugins](#sharing-data-between-plugins).
365
-
366
- `Features` controls the Markdown extensions Sätteri's parser recognizes: `gfm`, `frontmatter`, `math`, `directive`, `smartPunctuation`, etc. `gfm`, `math`, and `smartPunctuation` also accept granular options. For example, `math: { singleDollarTextMath: false }` keeps single `$` as literal text. See the [Features reference](https://satteri.dev/docs/features/) for the full list.
54
+ If you're familiar with the unified ecosystem, mdast and hast plugins are similar to remark and rehype plugins, respectively, reusing the same AST shapes.
367
55
 
368
- ## License
56
+ ## Development
369
57
 
370
- MIT
58
+ Refer to [CONTRIBUTING.md](https://github.com/bruits/satteri/blob/main/CONTRIBUTING.md) for development setup and workflow details.
@@ -1 +1 @@
1
- export { applyCommandsAndConvertToHastHandle, applyCommandsToHandle, applyCommandsToMdastHandle, compileHandle, compileMdx, convertMdastToHastHandle, createHastHandle, createMdastHandle, createMdxHastHandle, createMdxMdastHandle, dropHandle, getHandleSource, getMdastFrontmatter, getNodeData, mdastTextContentHandle, parseEsm, parseExpression, parseToHtml, renderHandle, serializeHandle, setNodeData, textContentHandle, walkHandle, walkMdastHandle, } from "../satteri_napi.wasi-browser.js";
1
+ export { applyCommandsAndConvertToHastHandle, applyCommandsToHandle, applyCommandsToMdastHandle, compileHandle, compileMdx, convertMdastToHastHandle, createHastHandle, createMdastHandle, createMdxHastHandle, createMdxMdastHandle, dropHandle, getHandleSource, getMdastFrontmatter, getNodeData, mdastTextContentHandle, parseEsm, parseExpression, parseToHtml, renderHandle, 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 { applyCommandsAndConvertToHastHandle, applyCommandsToHandle, applyCommandsToMdastHandle, compileHandle, compileMdx, convertMdastToHastHandle, createHastHandle, createMdastHandle, createMdxHastHandle, createMdxMdastHandle, dropHandle, getHandleSource, getMdastFrontmatter, getNodeData, mdastTextContentHandle, parseEsm, parseExpression, parseToHtml, renderHandle, serializeHandle, setNodeData, textContentHandle, walkHandle, walkMdastHandle, } from "../satteri_napi.wasi-browser.js";
2
+ export { applyCommandsAndConvertToHastHandle, applyCommandsToHandle, applyCommandsToMdastHandle, compileHandle, compileMdx, convertMdastToHastHandle, createHastHandle, createMdastHandle, createMdxHastHandle, createMdxMdastHandle, dropHandle, getHandleSource, getMdastFrontmatter, getNodeData, mdastTextContentHandle, parseEsm, parseExpression, parseToHtml, renderHandle, serializeHandle, setNodeData, textContentHandle, walkHandle, walkMdastHandle, } from "../browser.js";
@@ -30,8 +30,8 @@ export declare const HEADER: {
30
30
  readonly children_offset: 24;
31
31
  readonly type_data_len: 28;
32
32
  readonly type_data_offset: 32;
33
- readonly source_len: 36;
34
- readonly source_offset: 40;
33
+ readonly string_pool_len: 36;
34
+ readonly string_pool_offset: 40;
35
35
  readonly node_data_count: 44;
36
36
  readonly node_data_offset: 48;
37
37
  };
@@ -34,8 +34,8 @@ export const HEADER = {
34
34
  children_offset: 24,
35
35
  type_data_len: 28,
36
36
  type_data_offset: 32,
37
- source_len: 36,
38
- source_offset: 40,
37
+ string_pool_len: 36,
38
+ string_pool_offset: 40,
39
39
  node_data_count: 44,
40
40
  node_data_offset: 48,
41
41
  };
@@ -29,8 +29,9 @@ export declare class HastReader {
29
29
  getNodeData(nodeId: number): string | null;
30
30
  get nodeCount(): number;
31
31
  get header(): BufferHeader;
32
- /** Get the full source string (string pool). */
33
- getSource(): string;
32
+ /** The full string pool (original input + interning heap). Not the document
33
+ * source as written; for that, read `ctx.source` from a plugin. */
34
+ getStringPool(): string;
34
35
  /** Read a substring from the string pool by byte offset and length. */
35
36
  getString(offset: number, len: number): string;
36
37
  /** Get position data for a node. */
@@ -19,7 +19,7 @@ export class HastReader {
19
19
  #view;
20
20
  #header;
21
21
  #textDecoder;
22
- #sourceCache = null;
22
+ #stringPoolCache = null;
23
23
  constructor(buffer) {
24
24
  if (buffer instanceof Uint8Array) {
25
25
  this.#view = new DataView(buffer.buffer, buffer.byteOffset, buffer.byteLength);
@@ -49,8 +49,8 @@ export class HastReader {
49
49
  childrenOffset: v.getUint32(HEADER.children_offset, true),
50
50
  typeDataLen: v.getUint32(HEADER.type_data_len, true),
51
51
  typeDataOffset: v.getUint32(HEADER.type_data_offset, true),
52
- sourceLen: v.getUint32(HEADER.source_len, true),
53
- sourceOffset: v.getUint32(HEADER.source_offset, true),
52
+ stringPoolLen: v.getUint32(HEADER.string_pool_len, true),
53
+ stringPoolOffset: v.getUint32(HEADER.string_pool_offset, true),
54
54
  nodeDataCount: v.getUint32(HEADER.node_data_count, true),
55
55
  nodeDataOffset: v.getUint32(HEADER.node_data_offset, true),
56
56
  };
@@ -87,21 +87,22 @@ export class HastReader {
87
87
  get header() {
88
88
  return { ...this.#header };
89
89
  }
90
- /** Get the full source string (string pool). */
91
- getSource() {
92
- if (this.#sourceCache === null) {
93
- const { sourceOffset, sourceLen } = this.#header;
94
- const bytes = new Uint8Array(this.#view.buffer, this.#view.byteOffset + sourceOffset, sourceLen);
95
- this.#sourceCache = this.#textDecoder.decode(bytes);
90
+ /** The full string pool (original input + interning heap). Not the document
91
+ * source as written; for that, read `ctx.source` from a plugin. */
92
+ getStringPool() {
93
+ if (this.#stringPoolCache === null) {
94
+ const { stringPoolOffset, stringPoolLen } = this.#header;
95
+ const bytes = new Uint8Array(this.#view.buffer, this.#view.byteOffset + stringPoolOffset, stringPoolLen);
96
+ this.#stringPoolCache = this.#textDecoder.decode(bytes);
96
97
  }
97
- return this.#sourceCache;
98
+ return this.#stringPoolCache;
98
99
  }
99
100
  /** Read a substring from the string pool by byte offset and length. */
100
101
  getString(offset, len) {
101
102
  if (len === 0)
102
103
  return "";
103
- const { sourceOffset } = this.#header;
104
- const bytes = new Uint8Array(this.#view.buffer, this.#view.byteOffset + sourceOffset + offset, len);
104
+ const { stringPoolOffset } = this.#header;
105
+ const bytes = new Uint8Array(this.#view.buffer, this.#view.byteOffset + stringPoolOffset + offset, len);
105
106
  return this.#textDecoder.decode(bytes);
106
107
  }
107
108
  /** Get position data for a node. */
package/dist/index.d.ts CHANGED
@@ -5,7 +5,7 @@ export type { MdastPluginDefinition, HastPluginDefinition, MdastPluginInput, Has
5
5
  export type { HastVisitorInstance, HastVisitorContext, HastFilteredVisitor, HastContent, EstreeProgram, } from "./hast/hast-visitor.js";
6
6
  export type { MdastNode, HastNode, DataMap, Data, Position, Point, MdxJsxAttributeNode, MdxJsxExpressionAttributeNode, MdxJsxAttributeValueExpressionNode, MdxJsxAttributeUnion, MdxJsxFlowElement, MdxJsxTextElement, MdxFlowExpression, MdxTextExpression, MdxjsEsm, MdxJsxFlowElementHast, MdxJsxTextElementHast, MdxFlowExpressionHast, MdxTextExpressionHast, MdxjsEsmHast, } from "./types.js";
7
7
  export { visitMdastHandle, resolveMdastSubscriptions } from "./mdast/mdast-visitor.js";
8
- export type { MdastPluginInstance, MdastContent } from "./mdast/mdast-visitor.js";
8
+ export type { MdastPluginInstance, MdastVisitorContext, MdastContent, } from "./mdast/mdast-visitor.js";
9
9
  export { visitHastHandle, resolveSubscriptions as resolveHastSubscriptions, } from "./hast/hast-visitor.js";
10
10
  export { MdastReader } from "./mdast/mdast-reader.js";
11
11
  export { materializeMdastTree } from "./mdast/mdast-materializer.js";
@@ -9,7 +9,9 @@ export declare class MdastReader {
9
9
  getNodeData(nodeId: number): string | null;
10
10
  get nodeCount(): number;
11
11
  get header(): BufferHeader;
12
- getSource(): string;
12
+ /** The full string pool (original input + interning heap). Not the document
13
+ * source as written; for that, read `ctx.source` from a plugin. */
14
+ getStringPool(): string;
13
15
  getString(offset: number, len: number): string;
14
16
  getNode(nodeId: number): MdastNodeRaw;
15
17
  /** Fast path: read only the type byte for a node. */
@@ -8,7 +8,7 @@ export class MdastReader {
8
8
  #view;
9
9
  #header;
10
10
  #textDecoder;
11
- #sourceCache = null;
11
+ #stringPoolCache = null;
12
12
  constructor(buffer) {
13
13
  if (buffer instanceof Uint8Array) {
14
14
  this.#view = new DataView(buffer.buffer, buffer.byteOffset, buffer.byteLength);
@@ -38,8 +38,8 @@ export class MdastReader {
38
38
  childrenOffset: v.getUint32(HEADER.children_offset, true),
39
39
  typeDataLen: v.getUint32(HEADER.type_data_len, true),
40
40
  typeDataOffset: v.getUint32(HEADER.type_data_offset, true),
41
- sourceLen: v.getUint32(HEADER.source_len, true),
42
- sourceOffset: v.getUint32(HEADER.source_offset, true),
41
+ stringPoolLen: v.getUint32(HEADER.string_pool_len, true),
42
+ stringPoolOffset: v.getUint32(HEADER.string_pool_offset, true),
43
43
  nodeDataCount: v.getUint32(HEADER.node_data_count, true),
44
44
  nodeDataOffset: v.getUint32(HEADER.node_data_offset, true),
45
45
  };
@@ -73,19 +73,21 @@ export class MdastReader {
73
73
  get header() {
74
74
  return { ...this.#header };
75
75
  }
76
- getSource() {
77
- if (this.#sourceCache === null) {
78
- const { sourceOffset, sourceLen } = this.#header;
79
- const bytes = new Uint8Array(this.#view.buffer, this.#view.byteOffset + sourceOffset, sourceLen);
80
- this.#sourceCache = this.#textDecoder.decode(bytes);
76
+ /** The full string pool (original input + interning heap). Not the document
77
+ * source as written; for that, read `ctx.source` from a plugin. */
78
+ getStringPool() {
79
+ if (this.#stringPoolCache === null) {
80
+ const { stringPoolOffset, stringPoolLen } = this.#header;
81
+ const bytes = new Uint8Array(this.#view.buffer, this.#view.byteOffset + stringPoolOffset, stringPoolLen);
82
+ this.#stringPoolCache = this.#textDecoder.decode(bytes);
81
83
  }
82
- return this.#sourceCache;
84
+ return this.#stringPoolCache;
83
85
  }
84
86
  getString(offset, len) {
85
87
  if (len === 0)
86
88
  return "";
87
- const { sourceOffset } = this.#header;
88
- const bytes = new Uint8Array(this.#view.buffer, this.#view.byteOffset + sourceOffset + offset, len);
89
+ const { stringPoolOffset } = this.#header;
90
+ const bytes = new Uint8Array(this.#view.buffer, this.#view.byteOffset + stringPoolOffset + offset, len);
89
91
  return this.#textDecoder.decode(bytes);
90
92
  }
91
93
  getNode(nodeId) {
package/dist/types.d.ts CHANGED
@@ -109,8 +109,8 @@ export interface BufferHeader {
109
109
  childrenOffset: number;
110
110
  typeDataLen: number;
111
111
  typeDataOffset: number;
112
- sourceLen: number;
113
- sourceOffset: number;
112
+ stringPoolLen: number;
113
+ stringPoolOffset: number;
114
114
  /** Number of nodes that carry an extra JSON `data` blob. */
115
115
  nodeDataCount: number;
116
116
  /** Offset of the node-data section: `[id u32][len u32][bytes...]` repeated. */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "satteri",
3
- "version": "0.9.2",
3
+ "version": "0.9.4",
4
4
  "description": "High-performance Markdown and MDX processing",
5
5
  "repository": {
6
6
  "type": "git",
@@ -84,15 +84,15 @@
84
84
  ]
85
85
  },
86
86
  "optionalDependencies": {
87
- "@bruits/satteri-linux-x64-gnu": "0.9.2",
88
- "@bruits/satteri-linux-arm64-gnu": "0.9.2",
89
- "@bruits/satteri-linux-x64-musl": "0.9.2",
90
- "@bruits/satteri-linux-arm64-musl": "0.9.2",
91
- "@bruits/satteri-darwin-x64": "0.9.2",
92
- "@bruits/satteri-darwin-arm64": "0.9.2",
93
- "@bruits/satteri-win32-x64-msvc": "0.9.2",
94
- "@bruits/satteri-win32-arm64-msvc": "0.9.2",
95
- "@bruits/satteri-wasm32-wasi": "0.9.2"
87
+ "@bruits/satteri-linux-x64-gnu": "0.9.4",
88
+ "@bruits/satteri-linux-arm64-gnu": "0.9.4",
89
+ "@bruits/satteri-linux-x64-musl": "0.9.4",
90
+ "@bruits/satteri-linux-arm64-musl": "0.9.4",
91
+ "@bruits/satteri-darwin-x64": "0.9.4",
92
+ "@bruits/satteri-darwin-arm64": "0.9.4",
93
+ "@bruits/satteri-win32-x64-msvc": "0.9.4",
94
+ "@bruits/satteri-win32-arm64-msvc": "0.9.4",
95
+ "@bruits/satteri-wasm32-wasi": "0.9.4"
96
96
  },
97
97
  "scripts": {
98
98
  "build:binary:wasm": "napi build --manifest-path ../../crates/satteri-napi-binding/Cargo.toml --output-dir . --platform --release --esm --strip --target wasm32-wasip1-threads",