satteri 0.10.5 → 0.11.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (90) hide show
  1. package/README.md +11 -0
  2. package/dist/arena-reader.d.ts +13 -0
  3. package/dist/arena-reader.js +104 -0
  4. package/dist/binding.browser.d.ts +1 -1
  5. package/dist/binding.browser.js +1 -1
  6. package/dist/binding.d.ts +1 -1
  7. package/dist/binding.js +1 -1
  8. package/dist/byte-writer.d.ts +0 -18
  9. package/dist/byte-writer.js +6 -30
  10. package/dist/child-stub.d.ts +0 -17
  11. package/dist/child-stub.js +6 -22
  12. package/dist/command-buffer.d.ts +5 -26
  13. package/dist/command-buffer.js +28 -39
  14. package/dist/compile-options.d.ts +11 -0
  15. package/dist/compile-options.js +101 -0
  16. package/dist/compile-types.d.ts +342 -0
  17. package/dist/compile-types.js +1 -0
  18. package/dist/compile.d.ts +22 -328
  19. package/dist/compile.js +133 -383
  20. package/dist/directive-types.d.ts +1 -0
  21. package/dist/directive-types.js +1 -6
  22. package/dist/generated/arena-layout.d.ts +6 -4
  23. package/dist/generated/arena-layout.js +6 -7
  24. package/dist/generated/fused-wire.d.ts +3 -0
  25. package/dist/generated/fused-wire.js +229 -0
  26. package/dist/generated/parse-options.d.ts +1 -0
  27. package/dist/generated/parse-options.js +1 -5
  28. package/dist/generated/wire-constants.d.ts +4 -0
  29. package/dist/generated/wire-constants.js +34 -42
  30. package/dist/handles.d.ts +0 -7
  31. package/dist/hast/child-stub.d.ts +1 -8
  32. package/dist/hast/child-stub.js +14 -13
  33. package/dist/hast/element-props.d.ts +13 -0
  34. package/dist/hast/element-props.js +52 -6
  35. package/dist/hast/generated/node-types.d.ts +1 -9
  36. package/dist/hast/generated/node-types.js +1 -9
  37. package/dist/hast/generated/walk-decode.d.ts +10 -0
  38. package/dist/hast/generated/walk-decode.js +61 -0
  39. package/dist/hast/hast-materializer.d.ts +3 -10
  40. package/dist/hast/hast-materializer.js +73 -47
  41. package/dist/hast/hast-reader.d.ts +9 -38
  42. package/dist/hast/hast-reader.js +82 -271
  43. package/dist/hast/hast-visitor.d.ts +29 -16
  44. package/dist/hast/hast-visitor.js +200 -508
  45. package/dist/hast/hast-walk.d.ts +18 -0
  46. package/dist/hast/hast-walk.js +264 -0
  47. package/dist/index.d.ts +5 -5
  48. package/dist/index.js +4 -12
  49. package/dist/lazy-child-resolver.d.ts +1 -21
  50. package/dist/lazy-child-resolver.js +11 -32
  51. package/dist/materializer-cache.d.ts +5 -29
  52. package/dist/materializer-cache.js +29 -101
  53. package/dist/mdast/child-stub.d.ts +1 -12
  54. package/dist/mdast/child-stub.js +18 -28
  55. package/dist/mdast/column-align.d.ts +1 -1
  56. package/dist/mdast/column-align.js +0 -2
  57. package/dist/mdast/generated/layout.d.ts +0 -16
  58. package/dist/mdast/generated/layout.js +6 -47
  59. package/dist/mdast/generated/node-types.d.ts +1 -9
  60. package/dist/mdast/generated/node-types.js +3 -9
  61. package/dist/mdast/mdast-materializer.d.ts +3 -11
  62. package/dist/mdast/mdast-materializer.js +91 -63
  63. package/dist/mdast/mdast-reader.d.ts +15 -63
  64. package/dist/mdast/mdast-reader.js +132 -269
  65. package/dist/mdast/mdast-visitor.d.ts +23 -15
  66. package/dist/mdast/mdast-visitor.js +189 -294
  67. package/dist/mdast/mdast-walk.d.ts +13 -0
  68. package/dist/mdast/mdast-walk.js +144 -0
  69. package/dist/mdx-attr.js +0 -3
  70. package/dist/mdx-types.js +1 -6
  71. package/dist/op-stream.d.ts +4 -15
  72. package/dist/op-stream.js +31 -20
  73. package/dist/phantom.d.ts +0 -1
  74. package/dist/phantom.js +1 -9
  75. package/dist/plugin-pipeline.d.ts +32 -0
  76. package/dist/plugin-pipeline.js +177 -0
  77. package/dist/plugin.d.ts +14 -8
  78. package/dist/plugin.js +5 -10
  79. package/dist/types.d.ts +0 -1
  80. package/dist/visitor-node.d.ts +12 -0
  81. package/dist/visitor-node.js +23 -0
  82. package/dist/visitor-shared.d.ts +26 -30
  83. package/dist/visitor-shared.js +34 -36
  84. package/dist/wire-read.d.ts +0 -22
  85. package/dist/wire-read.js +3 -24
  86. package/index.d.ts +19 -10
  87. package/index.js +56 -53
  88. package/package.json +6 -12
  89. package/dist/string-pool.d.ts +0 -13
  90. package/dist/string-pool.js +0 -91
@@ -0,0 +1,18 @@
1
+ import { LazyChildResolver, type EpochCache } from "../lazy-child-resolver.js";
2
+ import { type NodeRefs } from "../visitor-shared.js";
3
+ import { type HastNode } from "./hast-materializer.js";
4
+ import { HastReader } from "./hast-reader.js";
5
+ export declare function getNodeId(node: HastNode, refs: NodeRefs): number | undefined;
6
+ export interface WalkWire {
7
+ view: DataView;
8
+ buf: Uint8Array;
9
+ resolver: HastLazyChildResolver;
10
+ }
11
+ export declare function readMatchedNode(wire: WalkWire, offset: number, nodeId: number, nodeType: number): HastNode;
12
+ export declare class HastLazyChildResolver extends LazyChildResolver<HastReader, HastNode> {
13
+ protected cacheSlot(): WeakMap<globalThis.AnyHandle, EpochCache<HastReader>>;
14
+ protected createReader(wire: Uint8Array): HastReader;
15
+ protected materializeNode(reader: HastReader, nodeId: number, refs: NodeRefs): HastNode;
16
+ protected readParentId(reader: HastReader, nodeId: number): number;
17
+ protected readChildIds(reader: HastReader, nodeId: number): number[];
18
+ }
@@ -0,0 +1,264 @@
1
+ import { parseEsm as napiParseEsm, parseExpression as napiParseExpression } from "#binding";
2
+ import { LazyChildResolver, registerEpochCacheSlot, } from "../lazy-child-resolver.js";
3
+ import { VisitorNode } from "../visitor-node.js";
4
+ import { crossPipelineForeign, FOREIGN_REF } from "../visitor-shared.js";
5
+ import { readPosition, rstr } from "../wire-read.js";
6
+ import { HastChildStub } from "./child-stub.js";
7
+ import { TYPE_NAMES } from "./generated/node-types.js";
8
+ import { decodeWalkElementProps, readWalkElementTag, readWalkHastValue, readWalkMdxJsx, walkElementPropCount, walkElementPropsAt, } from "./generated/walk-decode.js";
9
+ import { materializeHastNode } from "./hast-materializer.js";
10
+ import { HAST_COMMENT, HAST_ELEMENT, HAST_MDX_ESM, HAST_MDX_FLOW_EXPRESSION, HAST_MDX_JSX_ELEMENT, HAST_MDX_JSX_TEXT_ELEMENT, HAST_MDX_TEXT_EXPRESSION, HAST_RAW, HAST_ROOT, HAST_TEXT, HastReader, } from "./hast-reader.js";
11
+ function attachParseExpression(node, parseFn) {
12
+ Object.defineProperty(node, "parseExpression", {
13
+ value() {
14
+ const value = this.value;
15
+ if (typeof value !== "string")
16
+ return null;
17
+ const json = parseFn(value);
18
+ if (json == null)
19
+ return null;
20
+ return JSON.parse(json);
21
+ },
22
+ writable: false,
23
+ enumerable: false,
24
+ configurable: true,
25
+ });
26
+ }
27
+ // Spread copies must be treated as new content, not as references to the original arena node.
28
+ export function getNodeId(node, refs) {
29
+ if (node instanceof WalkElement)
30
+ return node._refs === refs ? node._nid : FOREIGN_REF;
31
+ if (node instanceof HastChildStub)
32
+ return node._refs === refs ? node._id : FOREIGN_REF;
33
+ const id = VisitorNode.getNodeId(node, refs) ?? refs.get(node);
34
+ if (id !== undefined)
35
+ return id;
36
+ const d = Object.getOwnPropertyDescriptor(node, "_nodeId");
37
+ if (d !== undefined && !d.enumerable)
38
+ return FOREIGN_REF;
39
+ return crossPipelineForeign(node);
40
+ }
41
+ function readChildStubs(view, buf, idsPos, typesPos, count, resolver) {
42
+ // With a hot snapshot a stub's deferral buys nothing; real nodes skip its per-field getters.
43
+ if (resolver.hasHotSnapshot()) {
44
+ const nodes = new Array(count);
45
+ for (let i = 0; i < count; i++) {
46
+ nodes[i] = resolver.materializeOne(view.getUint32(idsPos + i * 4, true));
47
+ }
48
+ return nodes;
49
+ }
50
+ const stubs = new Array(count);
51
+ for (let i = 0; i < count; i++) {
52
+ stubs[i] = new HastChildStub(resolver, view.getUint32(idsPos + i * 4, true), buf[typesPos + i]);
53
+ }
54
+ return stubs;
55
+ }
56
+ // Shared getters need a static block to access private wire fields.
57
+ let WALK_PROPS_DESC;
58
+ let WALK_CHILDREN_DESC;
59
+ // Private wire fields preserve spread identity without the GC cost of a WeakMap entry per element.
60
+ class WalkElement {
61
+ type = "element";
62
+ tagName;
63
+ #nodeId;
64
+ #wire;
65
+ #propsPos;
66
+ #childIdsPos;
67
+ #childTypesPos;
68
+ #childCount;
69
+ constructor(tagName, nodeId, wire, propsPos, propCount, childIdsPos, childTypesPos, childCount) {
70
+ this.tagName = tagName;
71
+ this.#nodeId = nodeId;
72
+ this.#wire = wire;
73
+ this.#propsPos = propsPos;
74
+ this.#childIdsPos = childIdsPos;
75
+ this.#childTypesPos = childTypesPos;
76
+ this.#childCount = childCount;
77
+ if (propCount === 0) {
78
+ this.properties = {};
79
+ }
80
+ else {
81
+ Object.defineProperty(this, "properties", WALK_PROPS_DESC);
82
+ }
83
+ if (childCount === 0) {
84
+ this.children = [];
85
+ }
86
+ else {
87
+ Object.defineProperty(this, "children", WALK_CHILDREN_DESC);
88
+ }
89
+ }
90
+ /** @internal */
91
+ get _nid() {
92
+ return this.#nodeId;
93
+ }
94
+ /** @internal */
95
+ get _refs() {
96
+ return this.#wire.resolver.refs;
97
+ }
98
+ static {
99
+ WALK_PROPS_DESC = {
100
+ enumerable: true,
101
+ configurable: true,
102
+ get() {
103
+ const w = this.#wire;
104
+ const val = decodeWalkElementProps(w.view, w.buf, this.#propsPos);
105
+ Object.defineProperty(this, "properties", {
106
+ value: val,
107
+ writable: true,
108
+ enumerable: true,
109
+ configurable: true,
110
+ });
111
+ return val;
112
+ },
113
+ };
114
+ WALK_CHILDREN_DESC = {
115
+ enumerable: true,
116
+ configurable: true,
117
+ get() {
118
+ const w = this.#wire;
119
+ const val = readChildStubs(w.view, w.buf, this.#childIdsPos, this.#childTypesPos, this.#childCount, w.resolver);
120
+ Object.defineProperty(this, "children", {
121
+ value: val,
122
+ writable: true,
123
+ enumerable: true,
124
+ configurable: true,
125
+ });
126
+ return val;
127
+ },
128
+ };
129
+ }
130
+ }
131
+ function readElementFromBinary(wire, offset, nodeId, position, childIdsPos, childTypesPos, childCount, data) {
132
+ // Visitors almost always read tagName, so decode it eagerly.
133
+ const tagName = readWalkElementTag(wire.view, wire.buf, offset);
134
+ const pos = walkElementPropsAt(wire.view, offset);
135
+ const propCount = walkElementPropCount(wire.view, pos);
136
+ const node = new WalkElement(tagName, nodeId, wire, pos, propCount, childIdsPos, childTypesPos, childCount);
137
+ if (position !== undefined)
138
+ node.position = position;
139
+ if (data !== null)
140
+ node.data = data;
141
+ return node;
142
+ }
143
+ function readTextFromBinary(view, buf, offset, nodeId, nodeType, position, data, refs) {
144
+ const value = readWalkHastValue(view, buf, offset, nodeType);
145
+ const base = new VisitorNode(TYPE_NAMES[nodeType] ?? `unknown(${nodeType})`, refs, nodeId);
146
+ base.value = value;
147
+ if (position !== undefined)
148
+ base.position = position;
149
+ if (data !== null)
150
+ base.data = data;
151
+ const node = base;
152
+ if (nodeType === HAST_MDX_FLOW_EXPRESSION || nodeType === HAST_MDX_TEXT_EXPRESSION) {
153
+ attachParseExpression(node, napiParseExpression);
154
+ }
155
+ else if (nodeType === HAST_MDX_ESM) {
156
+ attachParseExpression(node, napiParseEsm);
157
+ }
158
+ return node;
159
+ }
160
+ function readMdxJsxFromBinary(view, buf, offset, nodeId, nodeType, resolver, position, childIdsPos, childTypesPos, childCount, data) {
161
+ const { name, attributes } = readWalkMdxJsx(view, buf, offset);
162
+ const typeName = nodeType === HAST_MDX_JSX_ELEMENT ? "mdxJsxFlowElement" : "mdxJsxTextElement";
163
+ const base = new VisitorNode(typeName, resolver.refs, nodeId);
164
+ base.name = name;
165
+ base.attributes = attributes;
166
+ if (position !== undefined)
167
+ base.position = position;
168
+ if (data !== null)
169
+ base.data = data;
170
+ makeLazyChildren(base, view, buf, childIdsPos, childTypesPos, childCount, resolver);
171
+ return base;
172
+ }
173
+ export function readMatchedNode(wire, offset, nodeId, nodeType) {
174
+ const { view, buf, resolver } = wire;
175
+ let pos = offset;
176
+ const dataLen = view.getUint32(pos, true);
177
+ pos += 4;
178
+ let data = null;
179
+ if (dataLen > 0) {
180
+ const jsonStr = rstr(buf, pos, dataLen);
181
+ try {
182
+ data = JSON.parse(jsonStr);
183
+ }
184
+ catch (err) {
185
+ if (process.env.NODE_ENV !== "production") {
186
+ console.warn(`readMatchedNode: malformed node_data for nodeId=${nodeId}`, err);
187
+ }
188
+ }
189
+ pos += dataLen;
190
+ }
191
+ const position = readPosition(view, pos);
192
+ pos += 24;
193
+ const childCount = view.getUint32(pos, true);
194
+ pos += 4;
195
+ // Ids/types decode lazily with `.children` — most matched nodes never read them.
196
+ const childIdsPos = pos;
197
+ pos += childCount * 4;
198
+ const childTypesPos = pos;
199
+ pos += childCount;
200
+ if (nodeType === HAST_ELEMENT) {
201
+ return readElementFromBinary(wire, pos, nodeId, position, childIdsPos, childTypesPos, childCount, data);
202
+ }
203
+ else if (nodeType === HAST_TEXT ||
204
+ nodeType === HAST_COMMENT ||
205
+ nodeType === HAST_RAW ||
206
+ nodeType === HAST_MDX_FLOW_EXPRESSION ||
207
+ nodeType === HAST_MDX_TEXT_EXPRESSION ||
208
+ nodeType === HAST_MDX_ESM) {
209
+ return readTextFromBinary(view, buf, pos, nodeId, nodeType, position, data, resolver.refs);
210
+ }
211
+ else if (nodeType === HAST_MDX_JSX_ELEMENT || nodeType === HAST_MDX_JSX_TEXT_ELEMENT) {
212
+ return readMdxJsxFromBinary(view, buf, pos, nodeId, nodeType, resolver, position, childIdsPos, childTypesPos, childCount, data);
213
+ }
214
+ const base = new VisitorNode(TYPE_NAMES[nodeType] ?? `unknown(${nodeType})`, resolver.refs, nodeId);
215
+ if (position !== undefined)
216
+ base.position = position;
217
+ if (data !== null)
218
+ base.data = data;
219
+ if (nodeType === HAST_ROOT) {
220
+ if (childCount > 0) {
221
+ makeLazyChildren(base, view, buf, childIdsPos, childTypesPos, childCount, resolver);
222
+ }
223
+ else {
224
+ base.children = [];
225
+ }
226
+ }
227
+ const node = base;
228
+ return node;
229
+ }
230
+ const HAST_EPOCH_CACHE = registerEpochCacheSlot(new WeakMap());
231
+ export class HastLazyChildResolver extends LazyChildResolver {
232
+ cacheSlot() {
233
+ return HAST_EPOCH_CACHE;
234
+ }
235
+ createReader(wire) {
236
+ return new HastReader(wire);
237
+ }
238
+ materializeNode(reader, nodeId, refs) {
239
+ return materializeHastNode(reader, nodeId, true, refs);
240
+ }
241
+ readParentId(reader, nodeId) {
242
+ return reader.getParentId(nodeId);
243
+ }
244
+ readChildIds(reader, nodeId) {
245
+ return reader.getChildIds(nodeId);
246
+ }
247
+ }
248
+ // Own enumerable getters preserve children in spreads; closures avoid costly per-node wire slots.
249
+ function makeLazyChildren(node, view, buf, childIdsPos, childTypesPos, childCount, resolver) {
250
+ Object.defineProperty(node, "children", {
251
+ get() {
252
+ const val = readChildStubs(view, buf, childIdsPos, childTypesPos, childCount, resolver);
253
+ Object.defineProperty(this, "children", {
254
+ value: val,
255
+ writable: true,
256
+ enumerable: true,
257
+ configurable: true,
258
+ });
259
+ return val;
260
+ },
261
+ enumerable: true,
262
+ configurable: true,
263
+ });
264
+ }
package/dist/index.d.ts CHANGED
@@ -1,12 +1,12 @@
1
- export { markdownToHtml, markdownToJs, mdxToJs, evaluate, markdownToMdast, mdxToMdast, markdownToHast, mdxToHast, htmlToHast, } from "./compile.js";
2
- export type { CompileOptions, MdxCompileOptions, MdxOnlyOptions, MarkdownToJsOptions, EvaluateOptions, OptimizeStaticConfig, Features, TreeOptions, HtmlToHastOptions, SmartPunctuationOptions, Frontmatter, MarkdownToHtmlResult, MarkdownToJsResult, MdxToJsResult, } from "./compile.js";
1
+ export { markdownToHtml, markdownToJs, mdxToJs, evaluate, markdownToMdast, mdxToMdast, markdownToHast, mdxToHast, htmlToHast, hastToHtml, } from "./compile.js";
2
+ export type { CompileOptions, MdxCompileOptions, MdxOnlyOptions, MarkdownToJsOptions, EvaluateOptions, OptimizeStaticConfig, Features, TreeOptions, MdastTreeOptions, HastTreeOptions, HtmlToHastOptions, SmartPunctuationOptions, Frontmatter, MarkdownToHtmlResult, MarkdownToJsResult, MdxToJsResult, } from "./compile.js";
3
3
  export { defineMdastPlugin, defineHastPlugin } from "./plugin.js";
4
4
  export type { MdastPluginDefinition, HastPluginDefinition, MdastPluginInput, HastPluginInput, MdastPluginEntry, HastPluginEntry, MdastPluginList, HastPluginList, PluginFactoryContext, } from "./plugin.js";
5
- export type { HastVisitorInstance, HastVisitorContext, HastFilteredVisitor, HastContent, HastParentContent, RawHastContent, RawHtmlHastContent, EstreeProgram, } from "./hast/hast-visitor.js";
6
- export type { MdastNode, HastNode, Custom, DataMap, Data, SourceFormat, Position, Point, MdxJsxAttributeNode, MdxJsxExpressionAttributeNode, MdxJsxAttributeValueExpressionNode, MdxJsxAttributeUnion, MdxJsxFlowElement, MdxJsxTextElement, MdxFlowExpression, MdxTextExpression, MdxjsEsm, MdxJsxFlowElementHast, MdxJsxTextElementHast, MdxFlowExpressionHast, MdxTextExpressionHast, MdxjsEsmHast, } from "./types.js";
5
+ export type { HastVisitorInstance, HastVisitorContext, HastFilteredVisitor, HastVisitorFn, HastContent, HastParentContent, RawHastContent, RawHtmlHastContent, EstreeProgram, } from "./hast/hast-visitor.js";
6
+ export type { MdastNode, HastNode, Custom, DataMap, Data, SourceFormat, Position, Point, MdxJsxAttributeNode, MdxJsxExpressionAttributeNode, MdxJsxAttributeValueExpressionNode, MdxJsxAttributeUnion, MdxJsxFlowElement, MdxJsxTextElement, MdxFlowExpression, MdxTextExpression, MdxjsEsm, MdxJsxFlowElementHast, MdxJsxTextElementHast, MdxFlowExpressionHast, MdxTextExpressionHast, MdxjsEsmHast, ContainerDirective, LeafDirective, TextDirective, DirectiveAttributes, Toml, MathNode, InlineMath, Superscript, Subscript, DescriptionList, DescriptionTerm, DescriptionDetails, HastRaw, } from "./types.js";
7
7
  export { normalizePlugins } from "./plugin.js";
8
8
  export { visitMdastHandle, visitMdastHook, resolveMdastSubscriptions, } from "./mdast/mdast-visitor.js";
9
- export type { MdastPluginInstance, MdastVisitorContext, MdastContent, MdastTarget, MdastParentContent, MdastDiagnostic, MdastHookFn, RawMdastContent, RawHtmlMdastContent, } from "./mdast/mdast-visitor.js";
9
+ export type { MdastPluginInstance, MdastVisitorContext, MdastVisitorFn, MdastContent, MdastTarget, MdastParentContent, MdastDiagnostic, MdastHookFn, RawMdastContent, RawHtmlMdastContent, } from "./mdast/mdast-visitor.js";
10
10
  export { visitHastHandle, visitHastHook, resolveSubscriptions as resolveHastSubscriptions, } from "./hast/hast-visitor.js";
11
11
  export type { HastDiagnostic, HastHookFn } from "./hast/hast-visitor.js";
12
12
  export { MdastReader } from "./mdast/mdast-reader.js";
package/dist/index.js CHANGED
@@ -1,12 +1,8 @@
1
- // Public API: compile functions
2
- export { markdownToHtml, markdownToJs, mdxToJs, evaluate, markdownToMdast, mdxToMdast, markdownToHast, mdxToHast, htmlToHast, } from "./compile.js";
3
- // Plugin definitions
1
+ export { markdownToHtml, markdownToJs, mdxToJs, evaluate, markdownToMdast, mdxToMdast, markdownToHast, mdxToHast, htmlToHast, hastToHtml, } from "./compile.js";
4
2
  export { defineMdastPlugin, defineHastPlugin } from "./plugin.js";
5
- // Visitor pipeline (for manual plugin execution)
6
3
  export { normalizePlugins } from "./plugin.js";
7
4
  export { visitMdastHandle, visitMdastHook, resolveMdastSubscriptions, } from "./mdast/mdast-visitor.js";
8
5
  export { visitHastHandle, visitHastHook, resolveSubscriptions as resolveHastSubscriptions, } from "./hast/hast-visitor.js";
9
- // Step-by-step API: readers, materializers, and handle functions
10
6
  export { MdastReader } from "./mdast/mdast-reader.js";
11
7
  export { materializeMdastTree } from "./mdast/mdast-materializer.js";
12
8
  export { HastReader } from "./hast/hast-reader.js";
@@ -14,8 +10,7 @@ export { materializeHastTree } from "./hast/hast-materializer.js";
14
10
  export { serializeHandle, renderHandle, compileHandle, getHandleSource } from "#binding";
15
11
  import { applyCommandsToMdastHandle as napiApplyCommandsToMdastHandle, applyCommandsAndConvertToHastHandle as napiApplyCommandsAndConvertToHastHandle, convertMdastToHastHandle as napiConvertMdastToHastHandle, createHastHandle as napiCreateHastHandle, createMdastHandle as napiCreateMdastHandle, createMdxHastHandle as napiCreateMdxHastHandle, createMdxMdastHandle as napiCreateMdxMdastHandle, dropHandle as napiDropHandle, } from "#binding";
16
12
  import { featuresToNative } from "./compile.js";
17
- import { markHandleMutated } from "./lazy-child-resolver.js";
18
- // The napi creators take pre-packed parser bits; these keep `Features` the public shape.
13
+ import { markHandleMutated, releaseHandleReferences } from "./lazy-child-resolver.js";
19
14
  export function createMdastHandle(source, features, trackPositions) {
20
15
  return napiCreateMdastHandle(source, featuresToNative(features).parseOptions, trackPositions);
21
16
  }
@@ -37,10 +32,7 @@ function mergeConvertOptions(fromFeatures, explicit) {
37
32
  return fromFeatures;
38
33
  return { ...fromFeatures, ...explicit };
39
34
  }
40
- // The raw NAPI mutators renumber or empty the arena; without the epoch bump a
41
- // child stub retained past a manual-pipeline pass would silently snapshot the
42
- // changed arena (or die with an opaque RangeError) instead of hitting the
43
- // retention error.
35
+ // Arena mutations invalidate retained child stubs because node IDs can be reassigned or freed.
44
36
  export function applyCommandsToMdastHandle(handle, commandBuf) {
45
37
  markHandleMutated(handle);
46
38
  return napiApplyCommandsToMdastHandle(handle, commandBuf);
@@ -50,7 +42,7 @@ export function convertMdastToHastHandle(handle, convertOptions) {
50
42
  return napiConvertMdastToHastHandle(handle, convertOptions);
51
43
  }
52
44
  export function dropHandle(handle) {
53
- markHandleMutated(handle);
45
+ releaseHandleReferences(handle);
54
46
  napiDropHandle(handle);
55
47
  }
56
48
  export function applyCommandsAndConvertToHastHandle(handle, commandBuf, convertOptions) {
@@ -1,23 +1,12 @@
1
1
  import type { NodeRefs } from "./visitor-shared.js";
2
2
  import type { AnyHandle } from "./handles.js";
3
3
  export declare function registerEpochCacheSlot<T extends object>(slot: WeakMap<AnyHandle, T>): WeakMap<AnyHandle, T>;
4
- /** Record that `handle`'s arena was rebuilt. Resolvers created before the bump
5
- * refuse to take a fresh snapshot afterwards (their ids are stale). */
6
4
  export declare function markHandleMutated(handle: AnyHandle): void;
7
- /** Snapshot for one `(handle, epoch)`. Immutable once built, so it is shared
8
- * by every resolver of the same pass chain: nested matched containers dedup
9
- * by id, and later read-only passes reuse the whole cache instead of
10
- * re-serializing and re-materializing. */
5
+ export declare function releaseHandleReferences(handle: AnyHandle): void;
11
6
  export interface EpochCache<TReader> {
12
7
  epoch: number;
13
8
  reader: TReader;
14
9
  }
15
- /**
16
- * Lazy node materializer for the walk paths: serializes the handle's arena
17
- * once, on the first stub materialization, then materializes nodes from that
18
- * snapshot. Subclasses supply reader construction and per-node materialization
19
- * so the hot path stays free of per-node closures.
20
- */
21
10
  export declare abstract class LazyChildResolver<TReader, TNode> {
22
11
  #private;
23
12
  readonly refs: NodeRefs;
@@ -26,18 +15,9 @@ export declare abstract class LazyChildResolver<TReader, TNode> {
26
15
  protected abstract materializeNode(reader: TReader, nodeId: number, refs: NodeRefs): TNode;
27
16
  protected abstract readParentId(reader: TReader, nodeId: number): number;
28
17
  protected abstract readChildIds(reader: TReader, nodeId: number): number[];
29
- /** Kind-specific `(handle → cache)` slot, supplied as a module-level WeakMap
30
- * by each subclass so MDAST and HAST snapshots never collide. */
31
18
  protected abstract cacheSlot(): WeakMap<AnyHandle, EpochCache<TReader>>;
32
- /** Whether this pass's snapshot already exists; never takes one itself. */
33
19
  hasHotSnapshot(): boolean;
34
- /** Materialize one node for a child stub's first real-field read. The
35
- * materializers memoize per `(reader, id)`, so overlapping subtrees and
36
- * later passes share the same materialized objects. */
37
20
  materializeOne(nodeId: number): TNode;
38
- /** Arena id of `nodeId`'s parent in the pass snapshot, or undefined at the root. */
39
21
  parentIdOf(nodeId: number): number | undefined;
40
- /** Index of `nodeId` within its parent's children in the pass snapshot,
41
- * or undefined at the root. */
42
22
  indexInParent(nodeId: number): number | undefined;
43
23
  }
@@ -1,14 +1,13 @@
1
1
  import { serializeHandle } from "#binding";
2
- /** Rebuild count per handle: bumped whenever a command buffer lands and
3
- * renumbers the arena, invalidating ids captured before it. */
2
+ // Arena rebuilds can renumber nodes, invalidating IDs captured by earlier passes.
4
3
  const HANDLE_EPOCHS = new WeakMap();
5
- /** Per-flavor `(handle → cache)` slots, registered so a mutation can evict a snapshot the handle would otherwise pin. */
4
+ // Registered cache slots let mutations evict snapshots that handles would otherwise retain.
6
5
  const EPOCH_CACHE_SLOTS = [];
7
6
  export function registerEpochCacheSlot(slot) {
8
7
  EPOCH_CACHE_SLOTS.push(slot);
9
8
  return slot;
10
9
  }
11
- /** Per handle, not per epoch: an id stale within its own handle is the epoch machinery's to drop with a warning. */
10
+ // Ownership is per handle; epoch checks separately reject stale IDs within the same handle.
12
11
  const HANDLE_NODE_REFS = new WeakMap();
13
12
  function nodeRefsOfHandle(handle) {
14
13
  let refs = HANDLE_NODE_REFS.get(handle);
@@ -18,23 +17,19 @@ function nodeRefsOfHandle(handle) {
18
17
  }
19
18
  return refs;
20
19
  }
21
- /** Record that `handle`'s arena was rebuilt. Resolvers created before the bump
22
- * refuse to take a fresh snapshot afterwards (their ids are stale). */
23
20
  export function markHandleMutated(handle) {
24
21
  HANDLE_EPOCHS.set(handle, (HANDLE_EPOCHS.get(handle) ?? 0) + 1);
25
- // Safe to evict: a stale-epoch entry can never be adopted, only pinned by existing resolvers
26
22
  for (const slot of EPOCH_CACHE_SLOTS) {
27
23
  slot.delete(handle);
28
24
  }
29
25
  }
30
- /** Arena sentinel in the node struct's parent field: the node has no parent. */
26
+ export function releaseHandleReferences(handle) {
27
+ markHandleMutated(handle);
28
+ // Retained contexts keep their own refs; do not keep the table alive until native handle GC.
29
+ HANDLE_NODE_REFS.delete(handle);
30
+ }
31
31
  const NO_PARENT = 0xffffffff;
32
- /**
33
- * Lazy node materializer for the walk paths: serializes the handle's arena
34
- * once, on the first stub materialization, then materializes nodes from that
35
- * snapshot. Subclasses supply reader construction and per-node materialization
36
- * so the hot path stays free of per-node closures.
37
- */
32
+ // Subclasses keep the per-node materialization path free of closures.
38
33
  export class LazyChildResolver {
39
34
  #handle;
40
35
  #epoch;
@@ -56,21 +51,13 @@ export class LazyChildResolver {
56
51
  this.#cache = cache;
57
52
  return cache;
58
53
  }
59
- // A node id proves the tree was read in-pass, so a deferred snapshot is
60
- // still faithful as long as no command buffer mutated the arena since
61
- // match time. An existing same-epoch cache is always safe: the snapshot
62
- // is an immutable copy.
54
+ // Snapshot only within the captured epoch; later arenas may assign these IDs to different nodes.
63
55
  if ((HANDLE_EPOCHS.get(this.#handle) ?? 0) !== this.#epoch) {
64
56
  throw new Error("Cannot read node content: this node was retained past its visitor pass " +
65
57
  "and the tree has changed since. Reading a child node's field (or calling " +
66
58
  "ctx.parent()) during the pass pins the pass snapshot; eager fields like " +
67
59
  "tagName or properties do not. Or copy the data you need before the pass ends.");
68
60
  }
69
- // The serialized buffer already carries each node's `data` blob (read
70
- // eagerly by the materializer), and the arena isn't mutated mid-visit,
71
- // so no separate lazy NAPI fetch is needed. This also keeps walk-path
72
- // children consistent with the fully materialized tree (no `data` key
73
- // when a node has none).
74
61
  cache = {
75
62
  epoch: this.#epoch,
76
63
  reader: this.createReader(serializeHandle(this.#handle)),
@@ -82,29 +69,21 @@ export class LazyChildResolver {
82
69
  #ensureReader() {
83
70
  return this.#ensureCache().reader;
84
71
  }
85
- /** Whether this pass's snapshot already exists; never takes one itself. */
86
72
  hasHotSnapshot() {
87
73
  if (this.#cache !== undefined)
88
74
  return true;
89
75
  const cache = this.cacheSlot().get(this.#handle);
90
76
  return cache !== undefined && cache.epoch === this.#epoch;
91
77
  }
92
- /** Materialize one node for a child stub's first real-field read. The
93
- * materializers memoize per `(reader, id)`, so overlapping subtrees and
94
- * later passes share the same materialized objects. */
95
78
  materializeOne(nodeId) {
96
79
  return this.materializeNode(this.#ensureCache().reader, nodeId, this.refs);
97
80
  }
98
- /** Arena id of `nodeId`'s parent in the pass snapshot, or undefined at the root. */
99
81
  parentIdOf(nodeId) {
100
82
  const parentId = this.readParentId(this.#ensureReader(), nodeId);
101
83
  return parentId === NO_PARENT ? undefined : parentId;
102
84
  }
103
- /** Per-parent child-id→index maps, built lazily: null until a plugin calls
104
- * `indexInParent` (most never do). Cache-safe because the snapshot is immutable. */
85
+ // Immutable snapshots make cached child indices safe across repeated parent lookups.
105
86
  #childIndexByParent = null;
106
- /** Index of `nodeId` within its parent's children in the pass snapshot,
107
- * or undefined at the root. */
108
87
  indexInParent(nodeId) {
109
88
  const reader = this.#ensureReader();
110
89
  const parentId = this.readParentId(reader, nodeId);
@@ -1,42 +1,18 @@
1
- /**
2
- * Shared materializer machinery for the HAST and MDAST flavors: per-reader
3
- * node memo, lazy `children` descriptors, and the frozen-mode (plugin walk
4
- * path) freeze rules.
5
- */
6
- import type { Position } from "unist";
1
+ import type { Node } from "unist";
7
2
  import type { NodeRefs } from "./visitor-shared.js";
8
- /** The reader surface the shared machinery needs; both `HastReader` and `MdastReader` satisfy it. */
9
3
  export interface MaterializerReader {
10
4
  getNodeType(nodeId: number): number;
11
5
  getChildIds(nodeId: number): number[];
12
- getPosition(nodeId: number): Position | undefined;
6
+ getChildrenCount(nodeId: number): number;
13
7
  getNodeData(nodeId: number): string | null;
14
8
  }
15
- export interface MaterializerSpec<TReader extends MaterializerReader, TNode extends object> {
16
- /** Function name used in error/warning messages (e.g. "materializeHastNode"). */
9
+ export interface MaterializerSpec<TReader extends MaterializerReader, TNode extends Node> {
17
10
  label: string;
18
- /** Node-type tag -> canonical AST name (the generated `TYPE_NAMES`). */
19
11
  typeNames: Readonly<Record<number, string>>;
20
- /** Whether `node` carries `children`. Takes the built node because mdast
21
- * `custom` decides leafness per node rather than per type. */
22
12
  hasChildren(nodeType: number, node: TNode, reader: TReader, nodeId: number): boolean;
23
- /**
24
- * Install the type-specific eager fields on `node`. Must not install
25
- * `children`, `position`, `data`, or `_nodeId`, and must not freeze:
26
- * the shared machinery owns all of those.
27
- */
28
13
  populate(node: TNode, reader: TReader, nodeId: number, nodeType: number): void;
29
14
  }
30
- /**
31
- * Build a memoizing materializer, memoized per `(reader, id)`.
32
- *
33
- * `node` materializes one node with lazy `children`; `frozen` (the plugin walk
34
- * path) deep-freezes every node at construction so plugins cannot corrupt the
35
- * shared cache. `tree` materializes a whole tree eagerly, which is what the
36
- * step-by-step API wants: it asked for the tree, so laziness would only add
37
- * per-node accessor overhead.
38
- */
39
- export declare function createMaterializer<TReader extends MaterializerReader, TNode extends object>(spec: MaterializerSpec<TReader, TNode>): {
15
+ export declare function installNodeData(node: Node, rawData: string | null, label: string, nodeId: number): void;
16
+ export declare function createMaterializer<TReader extends MaterializerReader, TNode extends Node>(spec: MaterializerSpec<TReader, TNode>): {
40
17
  node: (reader: TReader, nodeId: number, frozen?: boolean, refs?: NodeRefs) => TNode;
41
- tree: (reader: TReader, rootId: number) => TNode;
42
18
  };