+86
-5
@@ -19,10 +19,86 @@ import type { MdastPluginInput, HastPluginInput } from "./plugin.js"; | ||
| } | ||
| /** | ||
| * Per-backref callback. Invoked once per anchor in the footnotes section | ||
| * with 1-based `referenceNumber` and `rerunIndex` (1 on the first backref | ||
| * to that definition, 2 on the second, and so on). Must return the final | ||
| * string used as the backref content or `aria-label`. | ||
| */ | ||
| export type FootnoteBackrefCallback = (referenceNumber: number, rerunIndex: number) => string; | ||
| /** | ||
| * i18n strings for the GFM footnotes section. Mirrors `footnoteLabel`, | ||
| * `footnoteBackLabel`, and `footnoteBackContent` from remark-rehype. | ||
| * | ||
| * `backContent` and `backLabel` each accept either a template string with | ||
| * the `{reference}` placeholder (substituted with `1` or `1-2` to match | ||
| * remark-rehype's default suffix), or a callback receiving the raw | ||
| * `(referenceNumber, rerunIndex)` pair. | ||
| * | ||
| * Passing this object enables footnotes. To turn them off, use | ||
| * `gfm: { footnotes: false }`. | ||
| */ | ||
| export interface FootnoteOptions { | ||
| /** `<h2>` label opening the footnotes section. Default: `"Footnotes"`. */ | ||
| label?: string; | ||
| /** | ||
| * Backref `<a>` content. Default: `"↩"`. | ||
| * | ||
| * Template form: the string is used as-is, and a `<sup>K</sup>` marker | ||
| * is auto-appended on reruns (k > 1). Callback form: returns the full | ||
| * content for each backref; no auto-sup is added. | ||
| */ | ||
| backContent?: string | FootnoteBackrefCallback; | ||
| /** | ||
| * Backref `aria-label`. Default: `"Back to reference {reference}"`. | ||
| * | ||
| * Template form: `{reference}` becomes `n` for the first backref, `n-K` | ||
| * for subsequent ones. Callback form: returns the `aria-label` string | ||
| * for each backref. | ||
| */ | ||
| backLabel?: string | FootnoteBackrefCallback; | ||
| } | ||
| /** Granular GFM toggles, nested under {@link Features.gfm}. */ | ||
| export interface GfmOptions { | ||
| /** | ||
| * Enable GFM footnotes (`[^id]`). Default: true. | ||
| * | ||
| * Pass `false` to drop footnote parsing while keeping the rest of GFM; | ||
| * pass an object to enable footnotes with custom i18n strings (see | ||
| * {@link FootnoteOptions}). | ||
| */ | ||
| footnotes?: boolean | FootnoteOptions; | ||
| } | ||
| /** Granular math toggles, nested under {@link Features.math}. */ | ||
| export interface MathOptions { | ||
| /** | ||
| * Treat single-dollar runs (`$ ... $`) as inline math. Default: true. | ||
| * | ||
| * Set `false` to keep single `$` as literal text while still parsing | ||
| * `$$ ... $$` display math. Mirrors `singleDollarTextMath` from | ||
| * remark-math. | ||
| */ | ||
| singleDollarTextMath?: boolean; | ||
| } | ||
| /** Parser feature toggles. All default to their documented value when omitted. */ | ||
| export interface Features { | ||
| /** GFM: tables, footnotes, strikethrough, task lists. Default: true. */ | ||
| gfm?: boolean; | ||
| /** | ||
| * GFM: tables, footnotes, strikethrough, task lists. Default: true. | ||
| * | ||
| * Pass an options object for granular control: | ||
| * ```ts | ||
| * gfm: { footnotes: false } // skip footnotes only | ||
| * gfm: { footnotes: { label: "Notes" } } // localize footnotes | ||
| * ``` | ||
| */ | ||
| gfm?: boolean | GfmOptions; | ||
| /** Frontmatter: YAML (`--- ... ---`) and TOML (`+++ ... +++`). Default: true. */ | ||
| frontmatter?: boolean; | ||
| /** Math blocks and inline math. Default: true. */ | ||
| math?: boolean; | ||
| /** | ||
| * Math blocks and inline math. Default: true. | ||
| * | ||
| * Pass an options object for granular control: | ||
| * ```ts | ||
| * math: { singleDollarTextMath: false } // $$..$$ only, $..$ literal | ||
| * ``` | ||
| */ | ||
| math?: boolean | MathOptions; | ||
| /** Heading attributes (`# text { #id .class }`). Default: true. */ | ||
@@ -52,3 +128,8 @@ headingAttributes?: boolean; | ||
| features?: Features; | ||
| filename?: string; | ||
| /** | ||
| * The document being processed, surfaced to plugins as `ctx.fileURL`. Must | ||
| * be a `URL` (e.g. Astro's `fileURL`); convert a filesystem path with Node's | ||
| * `pathToFileURL` before passing it. | ||
| */ | ||
| fileURL?: URL; | ||
| } | ||
@@ -55,0 +136,0 @@ /** |
+97
-52
@@ -8,12 +8,51 @@ import { visitHastHandle, resolveSubscriptions } from "./hast/hast-visitor.js"; | ||
| import { materializeHastTree } from "./hast/hast-materializer.js"; | ||
| /** | ||
| * Split the user-facing `Features` (with nested unions) into the flat napi | ||
| * `JsFeatures` shape plus the conversion-side `JsConvertOptions` carrying | ||
| * the footnote i18n strings. The public API only exposes `features`; the | ||
| * footnote strings are routed to napi internally. | ||
| */ | ||
| function featuresToNative(features) { | ||
| if (!features) | ||
| return undefined; | ||
| return { features: undefined, convertOptions: undefined }; | ||
| const result = {}; | ||
| if (features.gfm !== undefined) | ||
| result.gfm = features.gfm; | ||
| let convertOptions; | ||
| if (features.gfm !== undefined) { | ||
| if (typeof features.gfm === "object") { | ||
| const g = features.gfm; | ||
| const gfmOpts = {}; | ||
| if (g.footnotes !== undefined) { | ||
| if (typeof g.footnotes === "object") { | ||
| gfmOpts.footnotes = true; | ||
| convertOptions = convertOptions ?? {}; | ||
| if (g.footnotes.label !== undefined) | ||
| convertOptions.footnoteLabel = g.footnotes.label; | ||
| if (g.footnotes.backContent !== undefined) | ||
| convertOptions.footnoteBackContent = g.footnotes.backContent; | ||
| if (g.footnotes.backLabel !== undefined) | ||
| convertOptions.footnoteBackLabel = g.footnotes.backLabel; | ||
| } | ||
| else { | ||
| gfmOpts.footnotes = g.footnotes; | ||
| } | ||
| } | ||
| result.gfmOptions = gfmOpts; | ||
| } | ||
| else { | ||
| result.gfm = features.gfm; | ||
| } | ||
| } | ||
| if (features.frontmatter !== undefined) | ||
| result.frontmatter = features.frontmatter; | ||
| if (features.math !== undefined) | ||
| result.math = features.math; | ||
| if (features.math !== undefined) { | ||
| if (typeof features.math === "object") { | ||
| const mathOpts = {}; | ||
| if (features.math.singleDollarTextMath !== undefined) | ||
| mathOpts.singleDollarTextMath = features.math.singleDollarTextMath; | ||
| result.mathOptions = mathOpts; | ||
| } | ||
| else { | ||
| result.math = features.math; | ||
| } | ||
| } | ||
| if (features.headingAttributes !== undefined) | ||
@@ -37,37 +76,42 @@ result.headingAttributes = features.headingAttributes; | ||
| } | ||
| return result; | ||
| return { features: result, convertOptions }; | ||
| } | ||
| function runMdastPluginsOnHandle(handle, plugins, filename) { | ||
| let pendingCommands = null; | ||
| function warnDroppedTransforms(plugin, dropped) { | ||
| const name = plugin.name ?? "<anonymous>"; | ||
| const noun = dropped === 1 ? "transform" : "transforms"; | ||
| console.warn(`satteri: plugin "${name}" queued ${dropped} mdast ${noun} on node(s) that were removed or ` + | ||
| `replaced earlier in the same pass; ${dropped === 1 ? "it was" : "they were"} dropped.`); | ||
| } | ||
| function runMdastPluginsOnHandle(handle, plugins, fileURL) { | ||
| // Each plugin runs once over the tree. A transform that passes a child | ||
| // through (returning it inside the replacement) keeps that child's identity, | ||
| // so a patch the same pass queued on it still applies — nesting composes in | ||
| // one pass. A plugin's own freshly-built nodes are not re-walked; transform | ||
| // them up front, or hand off to a later plugin that sees the materialized tree. | ||
| const runPlugin = (plugin) => { | ||
| const subs = resolveMdastSubscriptions(plugin); | ||
| const result = visitMdastHandle(handle, plugin, subs, () => getHandleSource(handle), fileURL); | ||
| const apply = (r) => { | ||
| if (!r.hasMutations) | ||
| return; | ||
| const dropped = applyCommandsToMdastHandle(handle, r.commandBuffer); | ||
| if (dropped) | ||
| warnDroppedTransforms(plugin, dropped); | ||
| }; | ||
| return result instanceof Promise ? result.then(apply) : apply(result); | ||
| }; | ||
| let i = 0; | ||
| const runNext = () => { | ||
| while (i < plugins.length) { | ||
| const idx = i++; | ||
| const raw = plugins[idx]; | ||
| const raw = plugins[i++]; | ||
| const plugin = typeof raw === "function" ? raw() : raw; | ||
| const subs = resolveMdastSubscriptions(plugin); | ||
| const result = visitMdastHandle(handle, plugin, subs, () => getHandleSource(handle), filename); | ||
| if (result instanceof Promise) { | ||
| return result.then((r) => { | ||
| applyMdastResult(r, idx, plugins.length, handle); | ||
| return runNext(); | ||
| }); | ||
| } | ||
| applyMdastResult(result, idx, plugins.length, handle); | ||
| const r = runPlugin(plugin); | ||
| if (r instanceof Promise) | ||
| return r.then(runNext); | ||
| } | ||
| return { handle, pendingCommands }; | ||
| return { handle }; | ||
| }; | ||
| function applyMdastResult(result, idx, total, h) { | ||
| if (result.hasMutations) { | ||
| if (idx === total - 1) { | ||
| pendingCommands = result.commandBuffer; | ||
| } | ||
| else { | ||
| applyCommandsToMdastHandle(h, result.commandBuffer); | ||
| } | ||
| } | ||
| } | ||
| return runNext(); | ||
| } | ||
| function runHastPluginsOnHandle(handle, plugins, source, filename) { | ||
| function runHastPluginsOnHandle(handle, plugins, source, fileURL) { | ||
| if (plugins.length === 0) | ||
@@ -82,3 +126,3 @@ return; | ||
| const subs = resolveSubscriptions(plugin); | ||
| const result = visitHastHandle(handle, plugin, subs, source, filename); | ||
| const result = visitHastHandle(handle, plugin, subs, source, fileURL); | ||
| if (result instanceof Promise) { | ||
@@ -135,5 +179,5 @@ return result.then(runNext); | ||
| export function markdownToHtml(source, options = {}) { | ||
| const { mdastPlugins = [], hastPlugins = [], features, filename = "<unknown>" } = options; | ||
| const nativeFeatures = featuresToNative(features); | ||
| const result = createHastHandleFromMdast(source, mdastPlugins, false, filename, nativeFeatures); | ||
| const { mdastPlugins = [], hastPlugins = [], features, fileURL } = options; | ||
| const { features: nativeFeatures, convertOptions: nativeConvertOptions } = featuresToNative(features); | ||
| const result = createHastHandleFromMdast(source, mdastPlugins, false, fileURL, nativeFeatures, nativeConvertOptions); | ||
| const renderAndDrop = (h, frontmatter) => { | ||
@@ -151,3 +195,3 @@ try { | ||
| try { | ||
| hastResult = runHastPluginsOnHandle(r.hastHandle, hastPlugins, source, filename); | ||
| hastResult = runHastPluginsOnHandle(r.hastHandle, hastPlugins, source, fileURL); | ||
| } | ||
@@ -171,6 +215,6 @@ catch (err) { | ||
| export function mdxToJs(source, options = {}) { | ||
| const { mdastPlugins = [], hastPlugins = [], features, filename = "<unknown>", ...mdxFields } = options; | ||
| const { mdastPlugins = [], hastPlugins = [], features, fileURL, ...mdxFields } = options; | ||
| const mdxOptions = mdxOptionsToNative(mdxFields); | ||
| const nativeFeatures = featuresToNative(features); | ||
| const result = createHastHandleFromMdast(source, mdastPlugins, true, filename, nativeFeatures); | ||
| const { features: nativeFeatures, convertOptions: nativeConvertOptions } = featuresToNative(features); | ||
| const result = createHastHandleFromMdast(source, mdastPlugins, true, fileURL, nativeFeatures, nativeConvertOptions); | ||
| const compileAndDrop = (h, frontmatter) => { | ||
@@ -188,3 +232,3 @@ try { | ||
| try { | ||
| hastResult = runHastPluginsOnHandle(r.hastHandle, hastPlugins, source, filename); | ||
| hastResult = runHastPluginsOnHandle(r.hastHandle, hastPlugins, source, fileURL); | ||
| } | ||
@@ -234,5 +278,7 @@ catch (err) { | ||
| * the yaml/toml node are reflected in the returned value. */ | ||
| function createHastHandleFromMdast(source, mdastPlugins, mdx, filename, | ||
| function createHastHandleFromMdast(source, mdastPlugins, mdx, fileURL, | ||
| // eslint-disable-next-line @typescript-eslint/no-explicit-any | ||
| nativeFeatures) { | ||
| nativeFeatures, | ||
| // eslint-disable-next-line @typescript-eslint/no-explicit-any | ||
| nativeConvertOptions) { | ||
| const mdastHandle = mdx | ||
@@ -245,7 +291,4 @@ ? createMdxMdastHandle(source, nativeFeatures) | ||
| try { | ||
| if (r.pendingCommands) { | ||
| applyCommandsToMdastHandle(r.handle, r.pendingCommands); | ||
| } | ||
| const frontmatter = readFrontmatter(r.handle); | ||
| const hastHandle = convertMdastToHastHandle(r.handle); | ||
| const hastHandle = convertMdastToHastHandle(r.handle, nativeConvertOptions); | ||
| return { hastHandle, frontmatter }; | ||
@@ -259,5 +302,5 @@ } | ||
| if (mdastPlugins.length === 0) { | ||
| return finalize({ handle: mdastHandle, pendingCommands: null }); | ||
| return finalize({ handle: mdastHandle }); | ||
| } | ||
| const mdastResult = runMdastPluginsOnHandle(mdastHandle, mdastPlugins, filename); | ||
| const mdastResult = runMdastPluginsOnHandle(mdastHandle, mdastPlugins, fileURL); | ||
| if (mdastResult instanceof Promise) { | ||
@@ -279,3 +322,3 @@ return mdastResult.then(finalize, (err) => { | ||
| export function markdownToMdast(source, options = {}) { | ||
| const handle = createMdastHandle(source, featuresToNative(options.features)); | ||
| const handle = createMdastHandle(source, featuresToNative(options.features).features); | ||
| try { | ||
@@ -290,3 +333,3 @@ return materializeMdastTree(new MdastReader(serializeHandle(handle))); | ||
| export function mdxToMdast(source, options = {}) { | ||
| const handle = createMdxMdastHandle(source, featuresToNative(options.features)); | ||
| const handle = createMdxMdastHandle(source, featuresToNative(options.features).features); | ||
| try { | ||
@@ -301,3 +344,4 @@ return materializeMdastTree(new MdastReader(serializeHandle(handle))); | ||
| export function markdownToHast(source, options = {}) { | ||
| const handle = createHastHandle(source, featuresToNative(options.features)); | ||
| const { features: nativeFeatures, convertOptions } = featuresToNative(options.features); | ||
| const handle = createHastHandle(source, nativeFeatures, convertOptions); | ||
| try { | ||
@@ -312,3 +356,4 @@ return materializeHastTree(new HastReader(serializeHandle(handle))); | ||
| export function mdxToHast(source, options = {}) { | ||
| const handle = createMdxHastHandle(source, featuresToNative(options.features)); | ||
| const { features: nativeFeatures, convertOptions } = featuresToNative(options.features); | ||
| const handle = createMdxHastHandle(source, nativeFeatures, convertOptions); | ||
| try { | ||
@@ -315,0 +360,0 @@ return materializeHastTree(new HastReader(serializeHandle(handle))); |
@@ -18,3 +18,8 @@ import { type HastNode } from "./hast-materializer.js"; | ||
| readonly source: string; | ||
| readonly filename: string; | ||
| /** | ||
| * The URL of the document being processed (the compile `fileURL` option), | ||
| * or `undefined` when none was given. Use `fileURLToPath(ctx.fileURL)` for a | ||
| * decoded filesystem path. | ||
| */ | ||
| readonly fileURL: URL | undefined; | ||
| removeNode(node: Readonly<HastNode>): void; | ||
@@ -74,3 +79,3 @@ replaceNode(node: Readonly<HastNode>, newNode: HastNode): void; | ||
| */ | ||
| export declare function visitHastHandle(handle: HastHandle, plugin: HastVisitorInstance, subs: ResolvedSubscription[], source: string | (() => string), filename: string): void | Promise<void>; | ||
| export declare function visitHastHandle(handle: HastHandle, plugin: HastVisitorInstance, subs: ResolvedSubscription[], source: string | (() => string), fileURL: URL | undefined): void | Promise<void>; | ||
| export {}; |
+104
-75
@@ -24,20 +24,33 @@ import { materializeHastNode } from "./hast-materializer.js"; | ||
| } | ||
| /** Inject `_hast: true` marker on a HastNode and all its children for JSON serialization. */ | ||
| /** | ||
| * Serialize a HastNode for the command buffer. Marks each node `_hast: true`, | ||
| * and — except at the root, which is the new replacement content — emits a | ||
| * reused (materialized) node as a `{ _ref: id }` placeholder so the rebuild | ||
| * splices the original in place (preserving its id, applying any pending patch | ||
| * on it) instead of rebuilding it fresh. | ||
| */ | ||
| function markHast(node) { | ||
| const n = node; | ||
| return markHastNode(node, true); | ||
| } | ||
| function markHastNode(node, isRoot) { | ||
| if (!isRoot) { | ||
| const id = nodeIdMap.get(node) ?? node._nodeId; | ||
| if (typeof id === "number") | ||
| return { _ref: id }; | ||
| } | ||
| const obj = { _hast: true, type: node.type }; | ||
| if ("tagName" in node) | ||
| obj.tagName = n.tagName; | ||
| obj.tagName = node.tagName; | ||
| if ("properties" in node) | ||
| obj.properties = n.properties; | ||
| obj.properties = node.properties; | ||
| if ("value" in node) | ||
| obj.value = n.value; | ||
| obj.value = node.value; | ||
| if ("name" in node) | ||
| obj.name = n.name; | ||
| obj.name = node.name; | ||
| if ("attributes" in node) | ||
| obj.attributes = n.attributes; | ||
| if ("data" in node && n.data != null) | ||
| obj.data = n.data; | ||
| obj.attributes = node.attributes; | ||
| if ("data" in node && node.data != null) | ||
| obj.data = node.data; | ||
| if ("children" in node) { | ||
| obj.children = n.children.map(markHast); | ||
| obj.children = node.children.map((c) => markHastNode(c, false)); | ||
| } | ||
@@ -56,7 +69,7 @@ return obj; | ||
| #getSource; | ||
| filename; | ||
| constructor(handle, getSource, filename) { | ||
| fileURL; | ||
| constructor(handle, getSource, fileURL) { | ||
| this.#handle = handle; | ||
| this.#getSource = getSource; | ||
| this.filename = filename; | ||
| this.fileURL = fileURL; | ||
| } | ||
@@ -104,3 +117,4 @@ get source() { | ||
| // MDX JSX nodes use `attributes`, not `properties`, keep replaceNode path | ||
| const current = this.#pendingNodes.get(id) ?? node; | ||
| const pending = this.#pendingNodes.get(id); | ||
| const current = (pending ?? node); | ||
| const updated = { ...current }; | ||
@@ -115,3 +129,3 @@ const attrs = [...(updated.attributes ?? [])]; | ||
| ? value | ||
| : `${value}`; | ||
| : String(value); | ||
| attrs.push({ type: "mdxJsxAttribute", name: key, value: attrValue }); | ||
@@ -220,2 +234,17 @@ updated.attributes = attrs; | ||
| } | ||
| /** Decode the 24-byte position block written by `serialize_hast_node_inline`. */ | ||
| function readPositionPrefix(view, offset) { | ||
| const startOffset = view.getUint32(offset, true); | ||
| const endOffset = view.getUint32(offset + 4, true); | ||
| const startLine = view.getUint32(offset + 8, true); | ||
| const startColumn = view.getUint32(offset + 12, true); | ||
| const endLine = view.getUint32(offset + 16, true); | ||
| const endColumn = view.getUint32(offset + 20, true); | ||
| if (startLine === 0 && startOffset === 0) | ||
| return undefined; | ||
| return { | ||
| start: { offset: startOffset, line: startLine, column: startColumn }, | ||
| end: { offset: endOffset, line: endLine, column: endColumn }, | ||
| }; | ||
| } | ||
| /** | ||
@@ -236,4 +265,2 @@ * Walk-path element node: uses prototype getters instead of per-instance | ||
| /** @internal */ _resolver; | ||
| /** @internal */ _dataPos; | ||
| /** @internal */ _dataLen; | ||
| get properties() { | ||
@@ -259,18 +286,6 @@ const val = decodeProperties(this._view, this._buf, this._propsPos); | ||
| } | ||
| get data() { | ||
| if (this._dataPos < 0) | ||
| return undefined; | ||
| const val = JSON.parse(textDecoder.decode(this._buf.subarray(this._dataPos, this._dataPos + this._dataLen))); | ||
| Object.defineProperty(this, "data", { | ||
| value: val, | ||
| writable: true, | ||
| enumerable: true, | ||
| configurable: true, | ||
| }); | ||
| return val; | ||
| } | ||
| } | ||
| /** Read a matched element node from the binary data section into a HastNode. | ||
| * Only tagName is decoded eagerly; properties, children, and data are lazy. */ | ||
| function readElementFromBinary(view, buf, offset, nodeId, resolver) { | ||
| /** Read the tail of a matched element node (tag + properties). | ||
| * Common prelude (data/position/children) is already consumed by `readMatchedNode`. */ | ||
| function readElementFromBinary(view, buf, offset, nodeId, resolver, position, childIds, data) { | ||
| let pos = offset; | ||
@@ -282,33 +297,15 @@ // Eager: tagName (almost always accessed by visitors) | ||
| pos += tagLen; | ||
| // Pre-scan: find section byte offsets without decoding strings | ||
| const propsPos = pos; | ||
| const propCount = view.getUint16(pos, true); | ||
| pos += 2; | ||
| for (let i = 0; i < propCount; i++) { | ||
| const nLen = view.getUint16(pos, true); | ||
| pos += 2 + nLen + 1; // name + kind byte | ||
| const vLen = view.getUint16(pos, true); | ||
| pos += 2 + vLen; // value | ||
| } | ||
| const childCount = view.getUint16(pos, true); | ||
| pos += 2; | ||
| const childIdsPos = pos; | ||
| pos += childCount * 4; | ||
| const nodeDataLen = view.getUint32(pos, true); | ||
| pos += 4; | ||
| const nodeDataPos = nodeDataLen > 0 ? pos : -1; | ||
| // Collect child IDs for lazy materialization | ||
| const ids = []; | ||
| for (let i = 0; i < childCount; i++) | ||
| ids.push(view.getUint32(childIdsPos + i * 4, true)); | ||
| // Build node using class (prototype getters, no per-instance defineProperty) | ||
| const node = new WalkElement(); | ||
| node.tagName = tagName; | ||
| if (position !== undefined) | ||
| node.position = position; | ||
| if (data !== null) | ||
| node.data = data; | ||
| node._view = view; | ||
| node._buf = buf; | ||
| node._propsPos = propsPos; | ||
| node._childIds = ids; | ||
| node._childIds = childIds; | ||
| node._resolver = resolver; | ||
| node._dataPos = nodeDataPos; | ||
| node._dataLen = nodeDataLen; | ||
| nodeIdMap.set(node, nodeId); | ||
@@ -326,6 +323,11 @@ return node; | ||
| }; | ||
| function readTextFromBinary(view, buf, offset, nodeId, nodeType) { | ||
| function readTextFromBinary(view, buf, offset, nodeId, nodeType, position, data) { | ||
| const valLen = view.getUint32(offset, true); | ||
| const value = textDecoder.decode(buf.subarray(offset + 4, offset + 4 + valLen)); | ||
| const node = { type: TEXT_NODE_TYPES[nodeType], value }; | ||
| const base = { type: TEXT_NODE_TYPES[nodeType], value }; | ||
| if (position !== undefined) | ||
| base.position = position; | ||
| if (data !== null) | ||
| base.data = data; | ||
| const node = base; | ||
| nodeIdMap.set(node, nodeId); | ||
@@ -341,3 +343,3 @@ if (nodeType === HAST_MDX_FLOW_EXPRESSION || nodeType === HAST_MDX_TEXT_EXPRESSION) { | ||
| /** Read an MDX JSX element from the binary data section. */ | ||
| function readMdxJsxFromBinary(view, buf, offset, nodeId, nodeType, resolver) { | ||
| function readMdxJsxFromBinary(view, buf, offset, nodeId, nodeType, resolver, position, childIds, data) { | ||
| let pos = offset; | ||
@@ -349,3 +351,3 @@ // Name | ||
| pos += nameLen; | ||
| // Attributes: [kind: u8][nameLen: u16][name][valLen: u16][val] | ||
| // Attributes: [kind: u8][nameLen: u16][name][valLen: u32][val] | ||
| const attrCount = view.getUint16(pos, true); | ||
@@ -384,3 +386,32 @@ pos += 2; | ||
| } | ||
| // Child IDs | ||
| const typeName = nodeType === HAST_MDX_JSX_ELEMENT ? "mdxJsxFlowElement" : "mdxJsxTextElement"; | ||
| const base = { type: typeName, name, attributes }; | ||
| if (position !== undefined) | ||
| base.position = position; | ||
| if (data !== null) | ||
| base.data = data; | ||
| nodeIdMap.set(base, nodeId); | ||
| makeLazyChildren(base, childIds, resolver); | ||
| return base; | ||
| } | ||
| function readMatchedNode(view, buf, offset, nodeId, nodeType, resolver) { | ||
| let pos = offset; | ||
| // Shared prelude (matches serialize_hast_node_inline / serialize_mdast_node_inline): | ||
| // [data_len: u32][data_bytes][position: 24B][child_count: u16][child_ids: N×u32] | ||
| // Data (JSON), eagerly parsed | ||
| const dataLen = view.getUint32(pos, true); | ||
| pos += 4; | ||
| let data = null; | ||
| if (dataLen > 0) { | ||
| const jsonStr = textDecoder.decode(buf.subarray(pos, pos + dataLen)); | ||
| try { | ||
| data = JSON.parse(jsonStr); | ||
| } | ||
| catch { | ||
| /* ignore malformed JSON */ | ||
| } | ||
| pos += dataLen; | ||
| } | ||
| const position = readPositionPrefix(view, pos); | ||
| pos += 24; | ||
| const childCount = view.getUint16(pos, true); | ||
@@ -393,12 +424,5 @@ pos += 2; | ||
| } | ||
| const typeName = nodeType === HAST_MDX_JSX_ELEMENT ? "mdxJsxFlowElement" : "mdxJsxTextElement"; | ||
| const node = { type: typeName, name, attributes }; | ||
| nodeIdMap.set(node, nodeId); | ||
| makeLazyChildren(node, childIds, resolver); | ||
| resolver.attachLazyData(node, nodeId); | ||
| return node; | ||
| } | ||
| function readMatchedNode(view, buf, offset, nodeId, nodeType, resolver) { | ||
| // Dispatch to type-specific tail (pos now sits at the type-specific section) | ||
| if (nodeType === HAST_ELEMENT) { | ||
| return readElementFromBinary(view, buf, offset, nodeId, resolver); | ||
| return readElementFromBinary(view, buf, pos, nodeId, resolver, position, childIds, data); | ||
| } | ||
@@ -411,9 +435,14 @@ else if (nodeType === HAST_TEXT || | ||
| nodeType === HAST_MDX_ESM) { | ||
| return readTextFromBinary(view, buf, offset, nodeId, nodeType); | ||
| return readTextFromBinary(view, buf, pos, nodeId, nodeType, position, data); | ||
| } | ||
| else if (nodeType === HAST_MDX_JSX_ELEMENT || nodeType === HAST_MDX_JSX_TEXT_ELEMENT) { | ||
| return readMdxJsxFromBinary(view, buf, offset, nodeId, nodeType, resolver); | ||
| return readMdxJsxFromBinary(view, buf, pos, nodeId, nodeType, resolver, position, childIds, data); | ||
| } | ||
| // Fallback: minimal node | ||
| const node = { type: `unknown(${nodeType})` }; | ||
| // Fallback: minimal node carrying whatever prelude data we found | ||
| const base = { type: `unknown(${nodeType})` }; | ||
| if (position !== undefined) | ||
| base.position = position; | ||
| if (data !== null) | ||
| base.data = data; | ||
| const node = base; | ||
| nodeIdMap.set(node, nodeId); | ||
@@ -546,5 +575,5 @@ return node; | ||
| */ | ||
| export function visitHastHandle(handle, plugin, subs, source, filename) { | ||
| export function visitHastHandle(handle, plugin, subs, source, fileURL) { | ||
| const getSource = typeof source === "function" ? source : () => source; | ||
| const ctx = new HastVisitorContextImpl(handle, getSource, filename); | ||
| const ctx = new HastVisitorContextImpl(handle, getSource, fileURL); | ||
| const returnBuffer = new CommandBuffer(); | ||
@@ -551,0 +580,0 @@ const resolver = new LazyChildResolver(handle); |
@@ -16,4 +16,9 @@ import { CommandBuffer } from "../command-buffer.js"; | ||
| #private; | ||
| readonly filename: string; | ||
| constructor(handle: MdastHandle, getSource: () => string, filename: string); | ||
| /** | ||
| * The URL of the document being processed (the compile `fileURL` option), | ||
| * or `undefined` when none was given. Use `fileURLToPath(ctx.fileURL)` for a | ||
| * decoded filesystem path. | ||
| */ | ||
| readonly fileURL: URL | undefined; | ||
| constructor(handle: MdastHandle, getSource: () => string, fileURL: URL | undefined); | ||
| get source(): string; | ||
@@ -106,3 +111,3 @@ removeNode(node: Readonly<MdastNode>): void; | ||
| */ | ||
| export declare function visitMdastHandle(handle: MdastHandle, plugin: MdastPluginInstance, subs: MdastSubscription[], source: string | (() => string), filename: string): MdastVisitResult | Promise<MdastVisitResult>; | ||
| export declare function visitMdastHandle(handle: MdastHandle, plugin: MdastPluginInstance, subs: MdastSubscription[], source: string | (() => string), fileURL: URL | undefined): MdastVisitResult | Promise<MdastVisitResult>; | ||
| export {}; |
@@ -63,7 +63,12 @@ import { materializeNode, TYPE_NAMES } from "./mdast-materializer.js"; | ||
| #getSource; | ||
| filename; | ||
| constructor(handle, getSource, filename) { | ||
| /** | ||
| * The URL of the document being processed (the compile `fileURL` option), | ||
| * or `undefined` when none was given. Use `fileURLToPath(ctx.fileURL)` for a | ||
| * decoded filesystem path. | ||
| */ | ||
| fileURL; | ||
| constructor(handle, getSource, fileURL) { | ||
| this.#handle = handle; | ||
| this.#getSource = getSource; | ||
| this.filename = filename; | ||
| this.fileURL = fileURL; | ||
| } | ||
@@ -505,2 +510,32 @@ get source() { | ||
| * so that context mutations (e.g. setProperty) are not clobbered. */ | ||
| /** The arena id of a node if it is an existing (materialized) node, else | ||
| * undefined for a freshly-built one. */ | ||
| function reusedId(node) { | ||
| if (node === null || typeof node !== "object") | ||
| return undefined; | ||
| const id = nid(node); | ||
| return typeof id === "number" ? id : undefined; | ||
| } | ||
| /** | ||
| * Rewrite a returned replacement tree so every *reused* node (one that came | ||
| * from the arena, at any depth) becomes a `{ _ref: id }` placeholder. The | ||
| * rebuild splices those originals back in place, preserving their ids — so a | ||
| * patch a nested visitor queued on a passed-through child still lands, in the | ||
| * same pass. Freshly-built nodes serialize as before. The root is never reffed: | ||
| * it is the new shape replacing the visited node. | ||
| */ | ||
| function refifyReusedNodes(node, isRoot) { | ||
| if (node === null || typeof node !== "object") | ||
| return node; | ||
| if (!isRoot) { | ||
| const id = reusedId(node); | ||
| if (id !== undefined) | ||
| return { _ref: id }; | ||
| } | ||
| const children = node.children; | ||
| if (Array.isArray(children)) { | ||
| return { ...node, children: children.map((c) => refifyReusedNodes(c, false)) }; | ||
| } | ||
| return node; | ||
| } | ||
| function applyMdastVisitResult(result, nodeId, returnBuffer, originalNode) { | ||
@@ -520,3 +555,3 @@ if (result === undefined || result === null) | ||
| case "structured_node": | ||
| returnBuffer.replace(nodeId, result); | ||
| returnBuffer.replace(nodeId, refifyReusedNodes(result, true)); | ||
| break; | ||
@@ -532,5 +567,5 @@ } | ||
| */ | ||
| export function visitMdastHandle(handle, plugin, subs, source, filename) { | ||
| export function visitMdastHandle(handle, plugin, subs, source, fileURL) { | ||
| const getSource = typeof source === "function" ? source : () => source; | ||
| const context = new MdastVisitorContext(handle, getSource, filename); | ||
| const context = new MdastVisitorContext(handle, getSource, fileURL); | ||
| const returnBuffer = new CommandBuffer(); | ||
@@ -537,0 +572,0 @@ const resolver = new MdastLazyChildResolver(handle); |
+59
-8
@@ -7,3 +7,3 @@ /* auto-generated by NAPI-RS */ | ||
| */ | ||
| export declare function applyCommandsAndConvertToHastHandle(handle: MdastHandle, commandBuf: Uint8Array): HastHandle | ||
| export declare function applyCommandsAndConvertToHastHandle(handle: MdastHandle, commandBuf: Uint8Array, convertOptions?: JsConvertOptions | undefined | null): HastHandle | ||
@@ -13,4 +13,8 @@ /** Apply a command buffer to a HAST handle's arena in-place. */ | ||
| /** Apply a command buffer to an MDAST handle in-place. */ | ||
| export declare function applyCommandsToMdastHandle(handle: MdastHandle, commandBuf: Uint8Array): void | ||
| /** | ||
| * Apply a command buffer to an MDAST handle in-place. Returns how many patches | ||
| * were dropped because their target lived inside a subtree this pass removed or | ||
| * replaced (see the lenient note below); the JS pipeline warns when non-zero. | ||
| */ | ||
| export declare function applyCommandsToMdastHandle(handle: MdastHandle, commandBuf: Uint8Array): number | ||
@@ -21,6 +25,6 @@ /** Compile a HAST handle's arena to MDX JavaScript. Does not consume the handle. */ | ||
| /** Compile MDX source directly to JavaScript. */ | ||
| export declare function compileMdx(source: string, options?: JsMdxOptions | undefined | null, features?: JsFeatures | undefined | null): string | ||
| export declare function compileMdx(source: string, options?: JsMdxOptions | undefined | null, features?: JsFeatures | undefined | null, convertOptions?: JsConvertOptions | undefined | null): string | ||
| /** Convert an MDAST handle to a HAST handle. The MDAST handle is consumed (emptied). */ | ||
| export declare function convertMdastToHastHandle(handle: MdastHandle): HastHandle | ||
| export declare function convertMdastToHastHandle(handle: MdastHandle, convertOptions?: JsConvertOptions | undefined | null): HastHandle | ||
@@ -31,3 +35,3 @@ /** | ||
| */ | ||
| export declare function createHastHandle(source: string, features?: JsFeatures | undefined | null): HastHandle | ||
| export declare function createHastHandle(source: string, features?: JsFeatures | undefined | null, convertOptions?: JsConvertOptions | undefined | null): HastHandle | ||
@@ -38,3 +42,3 @@ /** Parse markdown source into an MDAST arena handle. */ | ||
| /** Parse MDX source and convert to HAST. Returns an opaque handle. */ | ||
| export declare function createMdxHastHandle(source: string, features?: JsFeatures | undefined | null): HastHandle | ||
| export declare function createMdxHastHandle(source: string, features?: JsFeatures | undefined | null, convertOptions?: JsConvertOptions | undefined | null): HastHandle | ||
@@ -68,2 +72,21 @@ /** Parse MDX source into an MDAST arena handle. */ | ||
| /** | ||
| * MDAST→HAST conversion options passed from JavaScript. | ||
| * | ||
| * Input-only: `object_to_js = false` because `FunctionRef` only crosses | ||
| * JS → Rust. A `JsConvertOptions` never gets serialized back to JS. | ||
| */ | ||
| export interface JsConvertOptions { | ||
| /** `<h2>` label opening the footnotes section. Default: `"Footnotes"`. */ | ||
| footnoteLabel?: string | ||
| /** Backref `<a>` content. Default: `"\u{21a9}"` (↩). */ | ||
| footnoteBackContent?: string | ((arg0: number, arg1: number) => string) | ||
| /** | ||
| * Backref `aria-label`. The token `{reference}` is replaced with the | ||
| * footnote number (`1`) or `number-K` (`1-2`) for repeated references. | ||
| * Default: `"Back to reference {reference}"`. | ||
| */ | ||
| footnoteBackLabel?: string | ((arg0: number, arg1: number) => string) | ||
| } | ||
| /** Feature toggles for the Markdown/MDX parser, passed from JavaScript. */ | ||
@@ -73,2 +96,4 @@ export interface JsFeatures { | ||
| gfm?: boolean | ||
| /** Granular GFM control (overrides `gfm`). */ | ||
| gfmOptions?: JsGfmOptions | ||
| /** Frontmatter: YAML (`--- ... ---`) and TOML (`+++ ... +++`). Default: true. */ | ||
@@ -78,2 +103,4 @@ frontmatter?: boolean | ||
| math?: boolean | ||
| /** Granular math control (overrides `math`). */ | ||
| mathOptions?: JsMathOptions | ||
| /** Heading attributes (`# text { #id .class }`). Default: true. */ | ||
@@ -103,2 +130,26 @@ headingAttributes?: boolean | ||
| /** | ||
| * Granular GFM toggles, nested under `features.gfm`. The footnote i18n | ||
| * strings (label, back-content, back-label) travel separately via the | ||
| * `JsConvertOptions` argument on conversion entry points; the JS package | ||
| * extracts them from `features.gfm.footnotes` before calling in. | ||
| */ | ||
| export interface JsGfmOptions { | ||
| /** | ||
| * Enable GFM footnotes (`[^id]`). Default: true. Set `false` to drop | ||
| * footnote parsing while keeping the rest of the GFM bundle. | ||
| */ | ||
| footnotes?: boolean | ||
| } | ||
| /** Granular math toggles, nested under `features.math`. */ | ||
| export interface JsMathOptions { | ||
| /** | ||
| * Treat single-dollar runs (`$ ... $`) as inline math. Default: true. | ||
| * Set `false` to keep single `$` as literal text (prose with currency) | ||
| * while still parsing double-dollar (`$$ ... $$`) display math. | ||
| */ | ||
| singleDollarTextMath?: boolean | ||
| } | ||
| /** MDX compile options passed from JavaScript. */ | ||
@@ -201,3 +252,3 @@ export interface JsMdxOptions { | ||
| /** Parse Markdown source and return HTML string directly. */ | ||
| export declare function parseToHtml(source: string, features?: JsFeatures | undefined | null): string | ||
| export declare function parseToHtml(source: string, features?: JsFeatures | undefined | null, convertOptions?: JsConvertOptions | undefined | null): string | ||
@@ -204,0 +255,0 @@ /** Render a HAST handle's arena to HTML. Does not consume the handle. */ |
+52
-52
@@ -84,4 +84,4 @@ // prettier-ignore | ||
| const bindingPackageVersion = require('@bruits/satteri-android-arm64/package.json').version | ||
| if (bindingPackageVersion !== '0.6.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { | ||
| throw new Error(`Native binding package version mismatch, expected 0.6.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) | ||
| if (bindingPackageVersion !== '0.6.3' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { | ||
| throw new Error(`Native binding package version mismatch, expected 0.6.3 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) | ||
| } | ||
@@ -101,4 +101,4 @@ return binding | ||
| const bindingPackageVersion = require('@bruits/satteri-android-arm-eabi/package.json').version | ||
| if (bindingPackageVersion !== '0.6.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { | ||
| throw new Error(`Native binding package version mismatch, expected 0.6.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) | ||
| if (bindingPackageVersion !== '0.6.3' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { | ||
| throw new Error(`Native binding package version mismatch, expected 0.6.3 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) | ||
| } | ||
@@ -123,4 +123,4 @@ return binding | ||
| const bindingPackageVersion = require('@bruits/satteri-win32-x64-gnu/package.json').version | ||
| if (bindingPackageVersion !== '0.6.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { | ||
| throw new Error(`Native binding package version mismatch, expected 0.6.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) | ||
| if (bindingPackageVersion !== '0.6.3' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { | ||
| throw new Error(`Native binding package version mismatch, expected 0.6.3 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) | ||
| } | ||
@@ -140,4 +140,4 @@ return binding | ||
| const bindingPackageVersion = require('@bruits/satteri-win32-x64-msvc/package.json').version | ||
| if (bindingPackageVersion !== '0.6.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { | ||
| throw new Error(`Native binding package version mismatch, expected 0.6.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) | ||
| if (bindingPackageVersion !== '0.6.3' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { | ||
| throw new Error(`Native binding package version mismatch, expected 0.6.3 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) | ||
| } | ||
@@ -158,4 +158,4 @@ return binding | ||
| const bindingPackageVersion = require('@bruits/satteri-win32-ia32-msvc/package.json').version | ||
| if (bindingPackageVersion !== '0.6.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { | ||
| throw new Error(`Native binding package version mismatch, expected 0.6.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) | ||
| if (bindingPackageVersion !== '0.6.3' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { | ||
| throw new Error(`Native binding package version mismatch, expected 0.6.3 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) | ||
| } | ||
@@ -175,4 +175,4 @@ return binding | ||
| const bindingPackageVersion = require('@bruits/satteri-win32-arm64-msvc/package.json').version | ||
| if (bindingPackageVersion !== '0.6.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { | ||
| throw new Error(`Native binding package version mismatch, expected 0.6.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) | ||
| if (bindingPackageVersion !== '0.6.3' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { | ||
| throw new Error(`Native binding package version mismatch, expected 0.6.3 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) | ||
| } | ||
@@ -195,4 +195,4 @@ return binding | ||
| const bindingPackageVersion = require('@bruits/satteri-darwin-universal/package.json').version | ||
| if (bindingPackageVersion !== '0.6.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { | ||
| throw new Error(`Native binding package version mismatch, expected 0.6.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) | ||
| if (bindingPackageVersion !== '0.6.3' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { | ||
| throw new Error(`Native binding package version mismatch, expected 0.6.3 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) | ||
| } | ||
@@ -212,4 +212,4 @@ return binding | ||
| const bindingPackageVersion = require('@bruits/satteri-darwin-x64/package.json').version | ||
| if (bindingPackageVersion !== '0.6.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { | ||
| throw new Error(`Native binding package version mismatch, expected 0.6.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) | ||
| if (bindingPackageVersion !== '0.6.3' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { | ||
| throw new Error(`Native binding package version mismatch, expected 0.6.3 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) | ||
| } | ||
@@ -229,4 +229,4 @@ return binding | ||
| const bindingPackageVersion = require('@bruits/satteri-darwin-arm64/package.json').version | ||
| if (bindingPackageVersion !== '0.6.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { | ||
| throw new Error(`Native binding package version mismatch, expected 0.6.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) | ||
| if (bindingPackageVersion !== '0.6.3' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { | ||
| throw new Error(`Native binding package version mismatch, expected 0.6.3 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) | ||
| } | ||
@@ -250,4 +250,4 @@ return binding | ||
| const bindingPackageVersion = require('@bruits/satteri-freebsd-x64/package.json').version | ||
| if (bindingPackageVersion !== '0.6.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { | ||
| throw new Error(`Native binding package version mismatch, expected 0.6.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) | ||
| if (bindingPackageVersion !== '0.6.3' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { | ||
| throw new Error(`Native binding package version mismatch, expected 0.6.3 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) | ||
| } | ||
@@ -267,4 +267,4 @@ return binding | ||
| const bindingPackageVersion = require('@bruits/satteri-freebsd-arm64/package.json').version | ||
| if (bindingPackageVersion !== '0.6.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { | ||
| throw new Error(`Native binding package version mismatch, expected 0.6.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) | ||
| if (bindingPackageVersion !== '0.6.3' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { | ||
| throw new Error(`Native binding package version mismatch, expected 0.6.3 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) | ||
| } | ||
@@ -289,4 +289,4 @@ return binding | ||
| const bindingPackageVersion = require('@bruits/satteri-linux-x64-musl/package.json').version | ||
| if (bindingPackageVersion !== '0.6.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { | ||
| throw new Error(`Native binding package version mismatch, expected 0.6.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) | ||
| if (bindingPackageVersion !== '0.6.3' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { | ||
| throw new Error(`Native binding package version mismatch, expected 0.6.3 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) | ||
| } | ||
@@ -306,4 +306,4 @@ return binding | ||
| const bindingPackageVersion = require('@bruits/satteri-linux-x64-gnu/package.json').version | ||
| if (bindingPackageVersion !== '0.6.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { | ||
| throw new Error(`Native binding package version mismatch, expected 0.6.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) | ||
| if (bindingPackageVersion !== '0.6.3' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { | ||
| throw new Error(`Native binding package version mismatch, expected 0.6.3 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) | ||
| } | ||
@@ -325,4 +325,4 @@ return binding | ||
| const bindingPackageVersion = require('@bruits/satteri-linux-arm64-musl/package.json').version | ||
| if (bindingPackageVersion !== '0.6.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { | ||
| throw new Error(`Native binding package version mismatch, expected 0.6.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) | ||
| if (bindingPackageVersion !== '0.6.3' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { | ||
| throw new Error(`Native binding package version mismatch, expected 0.6.3 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) | ||
| } | ||
@@ -342,4 +342,4 @@ return binding | ||
| const bindingPackageVersion = require('@bruits/satteri-linux-arm64-gnu/package.json').version | ||
| if (bindingPackageVersion !== '0.6.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { | ||
| throw new Error(`Native binding package version mismatch, expected 0.6.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) | ||
| if (bindingPackageVersion !== '0.6.3' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { | ||
| throw new Error(`Native binding package version mismatch, expected 0.6.3 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) | ||
| } | ||
@@ -361,4 +361,4 @@ return binding | ||
| const bindingPackageVersion = require('@bruits/satteri-linux-arm-musleabihf/package.json').version | ||
| if (bindingPackageVersion !== '0.6.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { | ||
| throw new Error(`Native binding package version mismatch, expected 0.6.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) | ||
| if (bindingPackageVersion !== '0.6.3' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { | ||
| throw new Error(`Native binding package version mismatch, expected 0.6.3 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) | ||
| } | ||
@@ -378,4 +378,4 @@ return binding | ||
| const bindingPackageVersion = require('@bruits/satteri-linux-arm-gnueabihf/package.json').version | ||
| if (bindingPackageVersion !== '0.6.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { | ||
| throw new Error(`Native binding package version mismatch, expected 0.6.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) | ||
| if (bindingPackageVersion !== '0.6.3' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { | ||
| throw new Error(`Native binding package version mismatch, expected 0.6.3 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) | ||
| } | ||
@@ -397,4 +397,4 @@ return binding | ||
| const bindingPackageVersion = require('@bruits/satteri-linux-loong64-musl/package.json').version | ||
| if (bindingPackageVersion !== '0.6.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { | ||
| throw new Error(`Native binding package version mismatch, expected 0.6.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) | ||
| if (bindingPackageVersion !== '0.6.3' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { | ||
| throw new Error(`Native binding package version mismatch, expected 0.6.3 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) | ||
| } | ||
@@ -414,4 +414,4 @@ return binding | ||
| const bindingPackageVersion = require('@bruits/satteri-linux-loong64-gnu/package.json').version | ||
| if (bindingPackageVersion !== '0.6.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { | ||
| throw new Error(`Native binding package version mismatch, expected 0.6.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) | ||
| if (bindingPackageVersion !== '0.6.3' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { | ||
| throw new Error(`Native binding package version mismatch, expected 0.6.3 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) | ||
| } | ||
@@ -433,4 +433,4 @@ return binding | ||
| const bindingPackageVersion = require('@bruits/satteri-linux-riscv64-musl/package.json').version | ||
| if (bindingPackageVersion !== '0.6.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { | ||
| throw new Error(`Native binding package version mismatch, expected 0.6.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) | ||
| if (bindingPackageVersion !== '0.6.3' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { | ||
| throw new Error(`Native binding package version mismatch, expected 0.6.3 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) | ||
| } | ||
@@ -450,4 +450,4 @@ return binding | ||
| const bindingPackageVersion = require('@bruits/satteri-linux-riscv64-gnu/package.json').version | ||
| if (bindingPackageVersion !== '0.6.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { | ||
| throw new Error(`Native binding package version mismatch, expected 0.6.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) | ||
| if (bindingPackageVersion !== '0.6.3' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { | ||
| throw new Error(`Native binding package version mismatch, expected 0.6.3 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) | ||
| } | ||
@@ -468,4 +468,4 @@ return binding | ||
| const bindingPackageVersion = require('@bruits/satteri-linux-ppc64-gnu/package.json').version | ||
| if (bindingPackageVersion !== '0.6.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { | ||
| throw new Error(`Native binding package version mismatch, expected 0.6.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) | ||
| if (bindingPackageVersion !== '0.6.3' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { | ||
| throw new Error(`Native binding package version mismatch, expected 0.6.3 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) | ||
| } | ||
@@ -485,4 +485,4 @@ return binding | ||
| const bindingPackageVersion = require('@bruits/satteri-linux-s390x-gnu/package.json').version | ||
| if (bindingPackageVersion !== '0.6.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { | ||
| throw new Error(`Native binding package version mismatch, expected 0.6.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) | ||
| if (bindingPackageVersion !== '0.6.3' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { | ||
| throw new Error(`Native binding package version mismatch, expected 0.6.3 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) | ||
| } | ||
@@ -506,4 +506,4 @@ return binding | ||
| const bindingPackageVersion = require('@bruits/satteri-openharmony-arm64/package.json').version | ||
| if (bindingPackageVersion !== '0.6.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { | ||
| throw new Error(`Native binding package version mismatch, expected 0.6.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) | ||
| if (bindingPackageVersion !== '0.6.3' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { | ||
| throw new Error(`Native binding package version mismatch, expected 0.6.3 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) | ||
| } | ||
@@ -523,4 +523,4 @@ return binding | ||
| const bindingPackageVersion = require('@bruits/satteri-openharmony-x64/package.json').version | ||
| if (bindingPackageVersion !== '0.6.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { | ||
| throw new Error(`Native binding package version mismatch, expected 0.6.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) | ||
| if (bindingPackageVersion !== '0.6.3' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { | ||
| throw new Error(`Native binding package version mismatch, expected 0.6.3 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) | ||
| } | ||
@@ -540,4 +540,4 @@ return binding | ||
| const bindingPackageVersion = require('@bruits/satteri-openharmony-arm/package.json').version | ||
| if (bindingPackageVersion !== '0.6.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { | ||
| throw new Error(`Native binding package version mismatch, expected 0.6.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) | ||
| if (bindingPackageVersion !== '0.6.3' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { | ||
| throw new Error(`Native binding package version mismatch, expected 0.6.3 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) | ||
| } | ||
@@ -544,0 +544,0 @@ return binding |
+8
-6
| { | ||
| "name": "satteri", | ||
| "version": "0.6.3", | ||
| "version": "0.7.0", | ||
| "description": "High-performance Markdown and MDX processing", | ||
@@ -83,7 +83,7 @@ "repository": { | ||
| "optionalDependencies": { | ||
| "@bruits/satteri-linux-x64-gnu": "0.6.3", | ||
| "@bruits/satteri-darwin-x64": "0.6.3", | ||
| "@bruits/satteri-darwin-arm64": "0.6.3", | ||
| "@bruits/satteri-win32-x64-msvc": "0.6.3", | ||
| "@bruits/satteri-wasm32-wasi": "0.6.3" | ||
| "@bruits/satteri-linux-x64-gnu": "0.7.0", | ||
| "@bruits/satteri-darwin-x64": "0.7.0", | ||
| "@bruits/satteri-darwin-arm64": "0.7.0", | ||
| "@bruits/satteri-win32-x64-msvc": "0.7.0", | ||
| "@bruits/satteri-wasm32-wasi": "0.7.0" | ||
| }, | ||
@@ -95,2 +95,4 @@ "scripts": { | ||
| "postbuild:binary:native": "node patch-binding.js", | ||
| "build:binary:native:lite": "napi build --manifest-path ../../crates/satteri-napi-binding/Cargo.toml --output-dir . --platform --release --esm --strip -- --no-default-features", | ||
| "postbuild:binary:native:lite": "node patch-binding.js", | ||
| "build:binary:native:cross": "napi build --manifest-path ../../crates/satteri-napi-binding/Cargo.toml --output-dir . --platform --release --esm --strip --cross-compile", | ||
@@ -97,0 +99,0 @@ "postbuild:binary:native:cross": "node patch-binding.js", |
+14
-1
@@ -166,2 +166,10 @@ # satteri | ||
| ### How transforms compose | ||
| 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: | ||
| - **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. | ||
| - **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. | ||
| - **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. | ||
| ### Async plugins | ||
@@ -293,3 +301,4 @@ | ||
| hastPlugins?: HastPluginDefinition[]; | ||
| filename?: string; | ||
| features?: Features; | ||
| fileURL?: URL; | ||
| } | ||
@@ -303,4 +312,8 @@ | ||
| `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)`. | ||
| `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. | ||
| ## License | ||
| MIT |
AI-detected potential code anomaly
Supply chain riskAI has identified unusual behaviors that may pose a security risk.
Found 3 instances
AI-detected potential code anomaly
Supply chain riskAI has identified unusual behaviors that may pose a security risk.
Found 3 instances
231815
6.1%5427
4.79%317
4.28%