@effected/markdown 0.18.0 → 0.19.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/Markdown.js CHANGED
@@ -2,7 +2,7 @@ import { Root } from "./MarkdownNode.js";
2
2
  import { isGuardExceeded, isRawMarkdownError } from "./internal/carriers.js";
3
3
  import { parseBlocks } from "./internal/blockParser.js";
4
4
  import { parsePhrasingText } from "./internal/phrasing.js";
5
- import { stringifyTree } from "./internal/stringify.js";
5
+ import { stringifyBlocks } from "./internal/stringify.js";
6
6
  import { MarkdownDiagnostic } from "./MarkdownDiagnostic.js";
7
7
  import { Effect, Result, Schema, SchemaIssue, SchemaTransformation } from "effect";
8
8
 
@@ -256,11 +256,20 @@ var Markdown = class Markdown {
256
256
  */
257
257
  static parsePhrasing = Effect.fn("Markdown.parsePhrasing")((text, options) => Effect.fromResult(Markdown.parsePhrasingResult(text, options)));
258
258
  /**
259
- * Serialize a {@link Root} tree to canonical markdown, synchronously, as a
260
- * `Result`. The pure primitive twin of {@link Markdown.stringify}, on the
261
- * same terms as {@link Markdown.parseResult}.
259
+ * Serialize a {@link Root} tree, or a single {@link (FlowContent:type)} node, to
260
+ * canonical markdown, synchronously, as a `Result`. The pure primitive twin
261
+ * of {@link Markdown.stringify}, on the same terms as
262
+ * {@link Markdown.parseResult}.
262
263
  *
263
264
  * @remarks
265
+ * **A single flow node is defined as its one-child document**: passing
266
+ * `node` is exactly `stringifyResult(Root.make({ children: [node] }))` —
267
+ * the same code path, the same trailing `\n`, the same canonical choices.
268
+ * This lets a caller serialize one block (a table, a section's paragraph,
269
+ * an MDX flow element) without building the wrapper by hand.
270
+ * `Frontmatter` and `MdxjsEsm` are deliberately not admitted alone — each
271
+ * is meaningful only at a document's root — so wrap them in a `Root`.
272
+ *
264
273
  * Canonical serialization: fidelity fields (marker characters, fence
265
274
  * style, heading spelling) win when present; documented canonical
266
275
  * defaults apply when absent. One default surprises: a language-less
@@ -343,13 +352,13 @@ var Markdown = class Markdown {
343
352
  * trip on a tree nesting past the depth cap fails, and only synthesized
344
353
  * or decoded trees can nest that far.
345
354
  *
346
- * @param root - The document tree to serialize.
355
+ * @param node - The document tree, or a single flow node, to serialize.
347
356
  * @returns A `Result` succeeding with markdown source, or failing with
348
357
  * {@link MarkdownStringifyError}.
349
358
  */
350
- static stringifyResult(root) {
359
+ static stringifyResult(node) {
351
360
  try {
352
- return Result.succeed(stringifyTree(root));
361
+ return Result.succeed(stringifyBlocks(node.type === "root" ? node.children : [node]));
353
362
  } catch (caught) {
354
363
  if (isGuardExceeded(caught)) return Result.fail(new MarkdownStringifyError({ diagnostic: MarkdownDiagnostic.make({
355
364
  code: caught.reason,
@@ -363,9 +372,9 @@ var Markdown = class Markdown {
363
372
  }
364
373
  }
365
374
  /**
366
- * Serialize a {@link Root} tree to canonical markdown. Defined in terms of
367
- * {@link Markdown.stringifyResult} — synchronous callers can use that
368
- * variant directly.
375
+ * Serialize a {@link Root} tree, or a single {@link (FlowContent:type)} node, to
376
+ * canonical markdown. Defined in terms of {@link Markdown.stringifyResult}
377
+ * — synchronous callers can use that variant directly.
369
378
  *
370
379
  * @remarks
371
380
  * The canonical form is a **stability commitment** and safe to assert
@@ -381,11 +390,14 @@ var Markdown = class Markdown {
381
390
  * `escapeStyle`, an emitter instruction rather than a source spelling,
382
391
  * which the boundary admits.
383
392
  *
384
- * @param root - The document tree to serialize.
393
+ * A single flow node serializes exactly as its one-child document does;
394
+ * see {@link Markdown.stringifyResult}.
395
+ *
396
+ * @param node - The document tree, or a single flow node, to serialize.
385
397
  * @returns An `Effect` that succeeds with markdown source, or fails with
386
398
  * {@link MarkdownStringifyError}.
387
399
  */
388
- static stringify = Effect.fn("Markdown.stringify")((root) => Effect.fromResult(Markdown.stringifyResult(root)));
400
+ static stringify = Effect.fn("Markdown.stringify")((node) => Effect.fromResult(Markdown.stringifyResult(node)));
389
401
  /**
390
402
  * A `Schema<Root, string>` decoding markdown source into a {@link Root}
391
403
  * tree and encoding a tree back to canonical markdown via
@@ -5,6 +5,7 @@ import { preprocessLines } from "./internal/preprocess.js";
5
5
  import { normalizeLabelText } from "./internal/references.js";
6
6
  import { MarkdownDiagnostic } from "./MarkdownDiagnostic.js";
7
7
  import { parsePassResult } from "./Markdown.js";
8
+ import { rootHeadingBounds } from "./internal/sectionBounds.js";
8
9
  import { Effect, Result, Schema } from "effect";
9
10
 
10
11
  //#region src/MarkdownDocument.ts
@@ -239,43 +240,9 @@ var MarkdownDocument = class MarkdownDocument extends Schema.Class("MarkdownDocu
239
240
  get sections() {
240
241
  const blocks = this.root.children;
241
242
  const sections = [];
242
- const headings = [];
243
- for (let index = 0; index < blocks.length; index += 1) {
244
- const block = blocks[index];
245
- if (block !== void 0 && block.type === "heading") headings.push({
246
- index,
247
- node: block
248
- });
249
- }
250
- const nextByDepth = [
251
- void 0,
252
- void 0,
253
- void 0,
254
- void 0,
255
- void 0,
256
- void 0,
257
- void 0
258
- ];
259
- const boundaryByHeading = new Array(headings.length);
260
- for (let index = headings.length - 1; index >= 0; index -= 1) {
261
- const current = headings[index]?.node;
262
- if (current === void 0) continue;
263
- let boundary;
264
- for (let depth = 1; depth <= current.depth; depth += 1) {
265
- const candidate = nextByDepth[depth];
266
- if (candidate !== void 0 && (boundary === void 0 || candidate < boundary)) boundary = candidate;
267
- }
268
- boundaryByHeading[index] = boundary;
269
- nextByDepth[current.depth] = index;
270
- }
271
- for (let index = 0; index < headings.length; index += 1) {
272
- const current = headings[index];
273
- if (current === void 0) continue;
274
- const block = current.node;
275
- const boundaryHeading = boundaryByHeading[index];
276
- const boundaryNode = boundaryHeading === void 0 ? void 0 : headings[boundaryHeading]?.node;
243
+ for (const { heading: block, index, end } of rootHeadingBounds(blocks)) {
244
+ const boundaryNode = blocks[end];
277
245
  const boundary = boundaryNode === void 0 ? this.source.length : boundaryNode.position.start.offset;
278
- const end = boundaryHeading === void 0 ? blocks.length : headings[boundaryHeading]?.index ?? blocks.length;
279
246
  const start = block.position.start.offset;
280
247
  const bodyStart = block.position.end.offset;
281
248
  sections.push(new DocumentSection({
@@ -285,7 +252,7 @@ var MarkdownDocument = class MarkdownDocument extends Schema.Class("MarkdownDocu
285
252
  offset: start,
286
253
  length: boundary - start
287
254
  }),
288
- children: blocks.slice(current.index + 1, end).filter((child) => child.type !== "frontmatter" && child.type !== "mdxjsEsm"),
255
+ children: blocks.slice(index + 1, end).filter((child) => child.type !== "frontmatter" && child.type !== "mdxjsEsm"),
289
256
  text: phrasingText(block.children),
290
257
  bodyRange: MarkdownRange.make({
291
258
  offset: bodyStart,
@@ -0,0 +1,167 @@
1
+ import { rootHeadingBounds } from "./internal/sectionBounds.js";
2
+
3
+ //#region src/MarkdownTransform.ts
4
+ /**
5
+ * Pure, total tree-to-tree transforms over the markdown node classes. Not
6
+ * instantiable.
7
+ *
8
+ * @remarks
9
+ * Every method returns a plain value — none has an error channel. They work
10
+ * on a tree, never on source text, so they compose with
11
+ * {@link Markdown.stringify} for output and with each other; offset-splice
12
+ * editing stays the job of `MarkdownFormat` and `MarkdownEdit`.
13
+ *
14
+ * @example
15
+ * ```ts
16
+ * import { LinkReference, Markdown, MarkdownTransform } from "@effected/markdown";
17
+ * import { Result } from "effect";
18
+ *
19
+ * const parsed = Markdown.parseResult("# Title\n\nSee [docs](https://example.com).\n");
20
+ * if (Result.isSuccess(parsed)) {
21
+ * const shifted = MarkdownTransform.shiftHeadings(parsed.success, 1);
22
+ * const rewritten = MarkdownTransform.map(shifted, {
23
+ * link: (link) =>
24
+ * LinkReference.make({ identifier: "docs", referenceType: "full", children: link.children }),
25
+ * });
26
+ * Markdown.stringifyResult(rewritten); // "## Title\n\nSee [docs][docs].\n"
27
+ * }
28
+ * ```
29
+ *
30
+ * @public
31
+ */
32
+ var MarkdownTransform = class {
33
+ constructor() {}
34
+ static map(node, handlers) {
35
+ return mapNode(node, handlers);
36
+ }
37
+ /**
38
+ * Shift every heading's depth by `by`, clamped into `[min, max]`.
39
+ *
40
+ * @remarks
41
+ * The new depth is `clamp(depth + by, min ?? 1, max ?? 6)`. A heading
42
+ * whose new depth exceeds 2 loses `headingStyle: "setext"` (the key is
43
+ * omitted), since setext spells depths 1 and 2 only; it then serializes
44
+ * as ATX. A heading whose depth does not change, and needs no setext
45
+ * drop, keeps its identity; structural sharing follows
46
+ * {@link MarkdownTransform.(map:1)}, on which this is built. Accepts any node,
47
+ * including a lone {@link Heading}.
48
+ *
49
+ * Built on `map`, it recurses the same way: a hand-built tree nested far
50
+ * deeper than anything `Markdown.parse` or `Mdast.fromMdast` produces can
51
+ * overflow the call stack.
52
+ *
53
+ * @param node - The node whose headings to shift.
54
+ * @param by - The signed depth offset; must be an integer.
55
+ * @param options - Optional {@link ShiftHeadingsOptions} clamp bounds.
56
+ * @returns The node with headings shifted.
57
+ * @throws `RangeError` when `by` is not an integer, or `min` / `max` is not
58
+ * a heading depth or `min > max` — a wiring error, not a data error.
59
+ */
60
+ static shiftHeadings(node, by, options) {
61
+ if (!Number.isInteger(by)) throw new RangeError(`MarkdownTransform.shiftHeadings: by must be an integer, got ${by}`);
62
+ const min = options?.min ?? 1;
63
+ const max = options?.max ?? 6;
64
+ if (!isHeadingDepth(min) || !isHeadingDepth(max) || min > max) throw new RangeError(`MarkdownTransform.shiftHeadings: invalid depth bounds min=${min} max=${max}`);
65
+ return mapNode(node, { heading: (heading) => {
66
+ const depth = Math.min(max, Math.max(min, heading.depth + by));
67
+ const dropSetext = depth > 2 && heading.headingStyle === "setext";
68
+ if (depth === heading.depth && !dropSetext) return heading;
69
+ const { headingStyle, ...rest } = heading;
70
+ const ctor = heading.constructor;
71
+ return new ctor({
72
+ ...rest,
73
+ depth,
74
+ ...headingStyle !== void 0 && !dropSetext ? { headingStyle } : {}
75
+ }, { disableChecks: true });
76
+ } });
77
+ }
78
+ /**
79
+ * Locate the root-level sections of one heading depth by child index —
80
+ * no source text needed, so it works on a tree a
81
+ * `MarkdownTransform.map` has reshaped.
82
+ *
83
+ * @remarks
84
+ * Returns the root-level headings of exactly `depth` whose index lies in
85
+ * `[start, end)`, in document order. Each section ends at the next
86
+ * root-level heading of equal or shallower depth, or at `end` — the same
87
+ * boundary rule as `MarkdownDocument.sections`, from one shared
88
+ * implementation. The window is what lets a caller nest: find the depth-2
89
+ * blocks, then the depth-3 sections inside one block's
90
+ * `[headingIndex + 1, end)`.
91
+ *
92
+ * **A shallower heading ends a section too.** Querying depth 2 on
93
+ * `## Features`, a list, a stray `# Top` and another list yields one span
94
+ * whose `end` is the `# Top` heading's index, so neither `# Top` nor the
95
+ * list after it belongs to the section. That is the outline reading. A
96
+ * caller that wants each section to run to the next heading of the SAME
97
+ * depth (slicing a changeset or changelog by entry, where a stray H1 is
98
+ * content) computes each body's end from the next span's `headingIndex`
99
+ * instead, or the window's end for the last span, as in the example.
100
+ *
101
+ * @example
102
+ * Bodies that run to the next same-depth heading:
103
+ *
104
+ * ```ts
105
+ * import { Markdown, MarkdownTransform } from "@effected/markdown";
106
+ * import { Result } from "effect";
107
+ *
108
+ * const root = Result.getOrThrow(Markdown.parseResult("## Features\n\n- a\n\n# Top\n\n- b\n"));
109
+ * const spans = MarkdownTransform.sections(root, { depth: 2 });
110
+ * // spans[0].end === 2: the outline section stops at `# Top`
111
+ * const bodies = spans.map((span, index) =>
112
+ * root.children.slice(span.headingIndex + 1, spans[index + 1]?.headingIndex ?? root.children.length),
113
+ * );
114
+ * // bodies[0] holds the first list, `# Top` and the second list
115
+ * ```
116
+ *
117
+ * @param root - The tree whose root-level headings delimit sections.
118
+ * @param options - The {@link SectionSpanOptions} depth and window.
119
+ * @returns The matching {@link SectionSpan}s, in document order.
120
+ * @throws `RangeError` when `depth` is not an integer from 1 to 6, when
121
+ * `start` or `end` is not an integer in `[0, root.children.length]`, or
122
+ * when `start > end`.
123
+ */
124
+ static sections(root, options) {
125
+ if (!isHeadingDepth(options.depth)) throw new RangeError(`MarkdownTransform.sections: depth must be a heading depth 1-6, got ${options.depth}`);
126
+ const length = root.children.length;
127
+ const start = options.start ?? 0;
128
+ const end = options.end ?? length;
129
+ if (!Number.isInteger(start) || !Number.isInteger(end) || start < 0 || end > length || start > end) throw new RangeError(`MarkdownTransform.sections: invalid window start=${start} end=${end} for ${length} children`);
130
+ const spans = [];
131
+ for (const bound of rootHeadingBounds(root.children)) {
132
+ if (bound.index < start || bound.index >= end || bound.heading.depth !== options.depth) continue;
133
+ spans.push({
134
+ heading: bound.heading,
135
+ headingIndex: bound.index,
136
+ end: Math.min(bound.end, end)
137
+ });
138
+ }
139
+ return spans;
140
+ }
141
+ };
142
+ const isHeadingDepth = (value) => Number.isInteger(value) && value >= 1 && value <= 6;
143
+ function mapNode(node, handlers) {
144
+ let current = node;
145
+ if ("children" in node) {
146
+ const children = node.children;
147
+ let next;
148
+ for (let index = 0; index < children.length; index += 1) {
149
+ const child = children[index];
150
+ const mapped = mapNode(child, handlers);
151
+ if (next === void 0 && mapped !== child) next = children.slice(0, index);
152
+ next?.push(mapped);
153
+ }
154
+ if (next !== void 0) {
155
+ const ctor = node.constructor;
156
+ current = new ctor({
157
+ ...node,
158
+ children: next
159
+ }, { disableChecks: true });
160
+ }
161
+ }
162
+ const handler = handlers[current.type];
163
+ return handler === void 0 ? current : handler(current);
164
+ }
165
+
166
+ //#endregion
167
+ export { MarkdownTransform };
package/Mdast.js CHANGED
@@ -469,7 +469,56 @@ var Mdast = class Mdast {
469
469
  * fails with {@link MdastDecodeError}.
470
470
  */
471
471
  static fromMdast = Effect.fn("Mdast.fromMdast")((input) => Effect.fromResult(Mdast.fromMdastResult(input)));
472
+ /**
473
+ * The plain-text content of a node, defined as `mdast-util-to-string`
474
+ * applied to the node's {@link Mdast.toMdast} projection.
475
+ *
476
+ * @remarks
477
+ * A node carrying a string `value` yields it (an `html` node only when
478
+ * `includeHtml`); an `image` or `imageReference` yields its non-empty
479
+ * `alt` when `includeImageAlt`; a node with children yields the
480
+ * concatenation of its children's text; anything else — a `break`, a
481
+ * `thematicBreak`, a `definition` — yields `""`. Both options default to
482
+ * `true`. Pure and total.
483
+ *
484
+ * **Values are read as the projection spells them.** The one value the
485
+ * projection changes is a `code` node's: this package keeps the final line
486
+ * ending of a code block's content, plain mdast does not, so `toText`
487
+ * strips it exactly as {@link Mdast.toMdast} does — `"let x"`, not
488
+ * `"let x\n"`, for a fenced block holding one line.
489
+ *
490
+ * This is the remark-ecosystem derivation, and it deliberately differs
491
+ * from the display derivation behind `DocumentHeading.text` and
492
+ * `DocumentSection.text`, which render a hard break as a space and drop
493
+ * raw HTML. Reach for `toText` when matching what remark tooling computes
494
+ * (slugs, search indexes); reach for the document accessors for display.
495
+ *
496
+ * The walk recurses once per nesting level. Every tree `Markdown.parse` or
497
+ * `Mdast.fromMdast` produces stays within the package's nesting cap, but a
498
+ * hand-built tree nested far deeper than that can overflow the call stack.
499
+ *
500
+ * @param node - The node to flatten.
501
+ * @param options - Optional {@link MdastToTextOptions}.
502
+ * @returns The node's text content.
503
+ */
504
+ static toText(node, options) {
505
+ return nodeText(node, options?.includeImageAlt ?? true, options?.includeHtml ?? true);
506
+ }
472
507
  };
508
+ /** `mdast-util-to-string`'s `one`, over the node classes as `toMdast` projects them. */
509
+ function nodeText(node, includeImageAlt, includeHtml) {
510
+ if ("value" in node) {
511
+ if (node.type === "html") return includeHtml ? node.value : "";
512
+ return node.type === "code" ? stripFinalLineEnding(node.value) : node.value;
513
+ }
514
+ if (includeImageAlt && "alt" in node && node.alt) return node.alt;
515
+ if ("children" in node) {
516
+ let text = "";
517
+ for (const child of node.children) text += nodeText(child, includeImageAlt, includeHtml);
518
+ return text;
519
+ }
520
+ return "";
521
+ }
473
522
 
474
523
  //#endregion
475
524
  export { Mdast, MdastDecodeError };
package/index.d.ts CHANGED
@@ -1204,11 +1204,20 @@ export declare class Markdown {
1204
1204
  */
1205
1205
  static readonly parsePhrasing: (text: string, options?: MarkdownParseOptions | undefined) => Effect.Effect<readonly PhrasingContent[], MarkdownParseError, never>;
1206
1206
  /**
1207
- * Serialize a {@link Root} tree to canonical markdown, synchronously, as a
1208
- * `Result`. The pure primitive twin of {@link Markdown.stringify}, on the
1209
- * same terms as {@link Markdown.parseResult}.
1207
+ * Serialize a {@link Root} tree, or a single {@link (FlowContent:type)} node, to
1208
+ * canonical markdown, synchronously, as a `Result`. The pure primitive twin
1209
+ * of {@link Markdown.stringify}, on the same terms as
1210
+ * {@link Markdown.parseResult}.
1210
1211
  *
1211
1212
  * @remarks
1213
+ * **A single flow node is defined as its one-child document**: passing
1214
+ * `node` is exactly `stringifyResult(Root.make({ children: [node] }))` —
1215
+ * the same code path, the same trailing `\n`, the same canonical choices.
1216
+ * This lets a caller serialize one block (a table, a section's paragraph,
1217
+ * an MDX flow element) without building the wrapper by hand.
1218
+ * `Frontmatter` and `MdxjsEsm` are deliberately not admitted alone — each
1219
+ * is meaningful only at a document's root — so wrap them in a `Root`.
1220
+ *
1212
1221
  * Canonical serialization: fidelity fields (marker characters, fence
1213
1222
  * style, heading spelling) win when present; documented canonical
1214
1223
  * defaults apply when absent. One default surprises: a language-less
@@ -1291,15 +1300,15 @@ export declare class Markdown {
1291
1300
  * trip on a tree nesting past the depth cap fails, and only synthesized
1292
1301
  * or decoded trees can nest that far.
1293
1302
  *
1294
- * @param root - The document tree to serialize.
1303
+ * @param node - The document tree, or a single flow node, to serialize.
1295
1304
  * @returns A `Result` succeeding with markdown source, or failing with
1296
1305
  * {@link MarkdownStringifyError}.
1297
1306
  */
1298
- static stringifyResult(root: Root): Result.Result<string, MarkdownStringifyError>;
1307
+ static stringifyResult(node: Root | FlowContent): Result.Result<string, MarkdownStringifyError>;
1299
1308
  /**
1300
- * Serialize a {@link Root} tree to canonical markdown. Defined in terms of
1301
- * {@link Markdown.stringifyResult} — synchronous callers can use that
1302
- * variant directly.
1309
+ * Serialize a {@link Root} tree, or a single {@link (FlowContent:type)} node, to
1310
+ * canonical markdown. Defined in terms of {@link Markdown.stringifyResult}
1311
+ * — synchronous callers can use that variant directly.
1303
1312
  *
1304
1313
  * @remarks
1305
1314
  * The canonical form is a **stability commitment** and safe to assert
@@ -1315,11 +1324,14 @@ export declare class Markdown {
1315
1324
  * `escapeStyle`, an emitter instruction rather than a source spelling,
1316
1325
  * which the boundary admits.
1317
1326
  *
1318
- * @param root - The document tree to serialize.
1327
+ * A single flow node serializes exactly as its one-child document does;
1328
+ * see {@link Markdown.stringifyResult}.
1329
+ *
1330
+ * @param node - The document tree, or a single flow node, to serialize.
1319
1331
  * @returns An `Effect` that succeeds with markdown source, or fails with
1320
1332
  * {@link MarkdownStringifyError}.
1321
1333
  */
1322
- static readonly stringify: (root: Root) => Effect.Effect<string, MarkdownStringifyError, never>;
1334
+ static readonly stringify: (node: Root | FlowContent) => Effect.Effect<string, MarkdownStringifyError, never>;
1323
1335
  /**
1324
1336
  * A `Schema<Root, string>` decoding markdown source into a {@link Root}
1325
1337
  * tree and encoding a tree back to canonical markdown via
@@ -2661,6 +2673,251 @@ export declare class MarkdownFormat {
2661
2673
  static readonly modifyToString: (document: MarkdownDocument, target: MarkdownNode, replacement: string | MarkdownNode) => Effect.Effect<string, MarkdownModificationError, never>;
2662
2674
  }
2663
2675
  //#endregion
2676
+ //#region src/MarkdownTransform.d.ts
2677
+ /**
2678
+ * The node `type` tags whose handler may change the node's type — the
2679
+ * phrasing-only types, i.e. every member of `PhrasingContent` that is
2680
+ * not also flow content.
2681
+ *
2682
+ * @remarks
2683
+ * `html` is excluded: an `html` node is both phrasing and flow content, so it
2684
+ * can sit in a `Root`, `Blockquote` or `ListItem` child list, where only flow
2685
+ * content is valid. Its handler must therefore return an `Html`.
2686
+ *
2687
+ * @public
2688
+ */
2689
+ type MarkdownPhrasingOnlyType = Exclude<PhrasingContent, FlowContent>["type"];
2690
+ /**
2691
+ * The handler table for `MarkdownTransform.map`: at most one function
2692
+ * per node `type` tag, each receiving the node narrowed to its class.
2693
+ *
2694
+ * @remarks
2695
+ * A handler for a phrasing-only type (see {@link MarkdownPhrasingOnlyType})
2696
+ * may return any {@link (PhrasingContent:type)} — turning a `link` into a
2697
+ * `linkReference`, say. That is sound because every parent of a phrasing
2698
+ * node holds `PhrasingContent` children, so any phrasing node is valid in its
2699
+ * place. Every other handler must return a node of its own type, which keeps
2700
+ * `map` total: no handler can put a node where its parent forbids it.
2701
+ *
2702
+ * @public
2703
+ */
2704
+ type MarkdownTransformHandlers = { readonly [T in MarkdownNodeType]?: (node: MarkdownNodeOfType<T>) => T extends MarkdownPhrasingOnlyType ? PhrasingContent : MarkdownNodeOfType<T>; };
2705
+ /**
2706
+ * What `MarkdownTransform.map` returns for an input of type `N`: any
2707
+ * `PhrasingContent` when `N` is phrasing-only (its handler may change
2708
+ * its type), otherwise `N` itself.
2709
+ *
2710
+ * @public
2711
+ */
2712
+ type MarkdownTransformResult<N extends MarkdownNode> = N extends Exclude<PhrasingContent, FlowContent> ? PhrasingContent : N;
2713
+ /**
2714
+ * Options for {@link MarkdownTransform.shiftHeadings}: the inclusive depth
2715
+ * range shifted headings are clamped into.
2716
+ *
2717
+ * @public
2718
+ */
2719
+ interface ShiftHeadingsOptions {
2720
+ /** The shallowest depth a shifted heading may take. Defaults to `1`. */
2721
+ readonly min?: HeadingDepth;
2722
+ /** The deepest depth a shifted heading may take. Defaults to `6`. */
2723
+ readonly max?: HeadingDepth;
2724
+ }
2725
+ /**
2726
+ * Options for {@link MarkdownTransform.sections}: the heading depth to match
2727
+ * and the window of `root.children` indexes to search.
2728
+ *
2729
+ * @public
2730
+ */
2731
+ interface SectionSpanOptions {
2732
+ /** The exact heading depth to match. */
2733
+ readonly depth: HeadingDepth;
2734
+ /** The first child index searched, inclusive. Defaults to `0`. */
2735
+ readonly start?: number;
2736
+ /** The child index the search stops at, exclusive. Defaults to `root.children.length`. */
2737
+ readonly end?: number;
2738
+ }
2739
+ /**
2740
+ * A root-level section located by index: the heading, its index in
2741
+ * `root.children`, and the exclusive index its section ends at. The body is
2742
+ * the children at `[headingIndex + 1, end)`.
2743
+ *
2744
+ * @public
2745
+ */
2746
+ interface SectionSpan {
2747
+ readonly heading: Heading;
2748
+ readonly headingIndex: number;
2749
+ readonly end: number;
2750
+ }
2751
+ /**
2752
+ * Pure, total tree-to-tree transforms over the markdown node classes. Not
2753
+ * instantiable.
2754
+ *
2755
+ * @remarks
2756
+ * Every method returns a plain value — none has an error channel. They work
2757
+ * on a tree, never on source text, so they compose with
2758
+ * {@link Markdown.stringify} for output and with each other; offset-splice
2759
+ * editing stays the job of `MarkdownFormat` and `MarkdownEdit`.
2760
+ *
2761
+ * @example
2762
+ * ```ts
2763
+ * import { LinkReference, Markdown, MarkdownTransform } from "@effected/markdown";
2764
+ * import { Result } from "effect";
2765
+ *
2766
+ * const parsed = Markdown.parseResult("# Title\n\nSee [docs](https://example.com).\n");
2767
+ * if (Result.isSuccess(parsed)) {
2768
+ * const shifted = MarkdownTransform.shiftHeadings(parsed.success, 1);
2769
+ * const rewritten = MarkdownTransform.map(shifted, {
2770
+ * link: (link) =>
2771
+ * LinkReference.make({ identifier: "docs", referenceType: "full", children: link.children }),
2772
+ * });
2773
+ * Markdown.stringifyResult(rewritten); // "## Title\n\nSee [docs][docs].\n"
2774
+ * }
2775
+ * ```
2776
+ *
2777
+ * @public
2778
+ */
2779
+ export declare class MarkdownTransform {
2780
+ private constructor();
2781
+ /**
2782
+ * Rebuild a tree by applying `handlers` to its nodes, bottom-up.
2783
+ *
2784
+ * @remarks
2785
+ * **Walk order is a contract:** post-order, children left to right. A
2786
+ * handler sees its node with its children ALREADY transformed, and
2787
+ * handlers are called in document order of the nodes' closing positions,
2788
+ * so a stateful handler can rely on it — deduplicating while keeping the
2789
+ * first occurrence, or remembering the last paragraph seen. The input
2790
+ * node itself is visited last.
2791
+ *
2792
+ * **Structural sharing:** a subtree no handler changed is returned as the
2793
+ * same instance (`===`), so `map(node, {})` returns `node`. A parent whose
2794
+ * children changed is rebuilt with only `children` swapped — every other
2795
+ * field, including `position`, kept as it was. A rebuilt parent therefore
2796
+ * carries its ORIGINAL `position`: offsets into the pre-transform source,
2797
+ * which no longer describe the transformed tree.
2798
+ *
2799
+ * **Handler typing keeps the map total.** A handler for a phrasing-only
2800
+ * type ({@link MarkdownPhrasingOnlyType}) may return any phrasing node —
2801
+ * a `link` may become a `linkReference` — because every parent of a
2802
+ * phrasing node holds phrasing children. Every other handler must return a
2803
+ * node of its own type. `html` is the one phrasing type excluded from the
2804
+ * type-changing set: it is a member of BOTH the phrasing and the flow
2805
+ * unions, so an `html` node can be a child of a `Root`, `Blockquote` or
2806
+ * `ListItem`, where only flow content is valid — its handler must return
2807
+ * an `Html`, and `map` of an `Html` returns an `Html`. See
2808
+ * {@link MarkdownTransformHandlers}. A handler that throws propagates the
2809
+ * exception unchanged; `map` has no error channel of its own.
2810
+ *
2811
+ * The walk recurses once per nesting level. Every tree `Markdown.parse` or
2812
+ * `Mdast.fromMdast` produces stays within the package's nesting cap, but a
2813
+ * hand-built tree nested far deeper than that can overflow the call stack.
2814
+ *
2815
+ * **Two signatures.** This one covers every node whose handler cannot
2816
+ * change its type — a `Root`, any flow node, a list item, a table row or
2817
+ * cell, an `html` node — and returns `N` itself, so a generic caller
2818
+ * constrained to, say, `Root | FlowContent` gets its own type parameter
2819
+ * back. A lone phrasing-only node resolves to the second signature, whose
2820
+ * result widens to `PhrasingContent`.
2821
+ *
2822
+ * @param node - The node to transform — typically a {@link Root}.
2823
+ * @param handlers - At most one function per node `type` tag.
2824
+ * @returns The transformed node, of the input's own type: `node` itself
2825
+ * when nothing changed.
2826
+ */
2827
+ static map<N extends Exclude<MarkdownNode, Exclude<PhrasingContent, FlowContent>>>(node: N, handlers: MarkdownTransformHandlers): N;
2828
+ /**
2829
+ * Rebuild a tree by applying `handlers` to its nodes, bottom-up — the
2830
+ * signature for any node, including a lone phrasing-only node whose own
2831
+ * handler may change its type.
2832
+ *
2833
+ * @remarks
2834
+ * Walk order, structural sharing, handler typing and the recursion limit
2835
+ * are exactly as documented on the first signature. The difference is the
2836
+ * result type, {@link MarkdownTransformResult}: for a phrasing-only node
2837
+ * (a `link`, `text`, `emphasis`, ...) it is any `PhrasingContent`, since
2838
+ * that node's handler may return, say, a `linkReference` for a `link`. For
2839
+ * every other node it is `N`, but a caller holding a node of a known
2840
+ * non-phrasing type resolves to the first signature, which returns `N`
2841
+ * without the conditional type.
2842
+ *
2843
+ * @param node - Any node.
2844
+ * @param handlers - At most one function per node `type` tag.
2845
+ * @returns The transformed node: `node` itself when nothing changed.
2846
+ */
2847
+ static map<N extends MarkdownNode>(node: N, handlers: MarkdownTransformHandlers): MarkdownTransformResult<N>;
2848
+ /**
2849
+ * Shift every heading's depth by `by`, clamped into `[min, max]`.
2850
+ *
2851
+ * @remarks
2852
+ * The new depth is `clamp(depth + by, min ?? 1, max ?? 6)`. A heading
2853
+ * whose new depth exceeds 2 loses `headingStyle: "setext"` (the key is
2854
+ * omitted), since setext spells depths 1 and 2 only; it then serializes
2855
+ * as ATX. A heading whose depth does not change, and needs no setext
2856
+ * drop, keeps its identity; structural sharing follows
2857
+ * {@link MarkdownTransform.(map:1)}, on which this is built. Accepts any node,
2858
+ * including a lone {@link Heading}.
2859
+ *
2860
+ * Built on `map`, it recurses the same way: a hand-built tree nested far
2861
+ * deeper than anything `Markdown.parse` or `Mdast.fromMdast` produces can
2862
+ * overflow the call stack.
2863
+ *
2864
+ * @param node - The node whose headings to shift.
2865
+ * @param by - The signed depth offset; must be an integer.
2866
+ * @param options - Optional {@link ShiftHeadingsOptions} clamp bounds.
2867
+ * @returns The node with headings shifted.
2868
+ * @throws `RangeError` when `by` is not an integer, or `min` / `max` is not
2869
+ * a heading depth or `min > max` — a wiring error, not a data error.
2870
+ */
2871
+ static shiftHeadings<N extends MarkdownNode>(node: N, by: number, options?: ShiftHeadingsOptions): N;
2872
+ /**
2873
+ * Locate the root-level sections of one heading depth by child index —
2874
+ * no source text needed, so it works on a tree a
2875
+ * `MarkdownTransform.map` has reshaped.
2876
+ *
2877
+ * @remarks
2878
+ * Returns the root-level headings of exactly `depth` whose index lies in
2879
+ * `[start, end)`, in document order. Each section ends at the next
2880
+ * root-level heading of equal or shallower depth, or at `end` — the same
2881
+ * boundary rule as `MarkdownDocument.sections`, from one shared
2882
+ * implementation. The window is what lets a caller nest: find the depth-2
2883
+ * blocks, then the depth-3 sections inside one block's
2884
+ * `[headingIndex + 1, end)`.
2885
+ *
2886
+ * **A shallower heading ends a section too.** Querying depth 2 on
2887
+ * `## Features`, a list, a stray `# Top` and another list yields one span
2888
+ * whose `end` is the `# Top` heading's index, so neither `# Top` nor the
2889
+ * list after it belongs to the section. That is the outline reading. A
2890
+ * caller that wants each section to run to the next heading of the SAME
2891
+ * depth (slicing a changeset or changelog by entry, where a stray H1 is
2892
+ * content) computes each body's end from the next span's `headingIndex`
2893
+ * instead, or the window's end for the last span, as in the example.
2894
+ *
2895
+ * @example
2896
+ * Bodies that run to the next same-depth heading:
2897
+ *
2898
+ * ```ts
2899
+ * import { Markdown, MarkdownTransform } from "@effected/markdown";
2900
+ * import { Result } from "effect";
2901
+ *
2902
+ * const root = Result.getOrThrow(Markdown.parseResult("## Features\n\n- a\n\n# Top\n\n- b\n"));
2903
+ * const spans = MarkdownTransform.sections(root, { depth: 2 });
2904
+ * // spans[0].end === 2: the outline section stops at `# Top`
2905
+ * const bodies = spans.map((span, index) =>
2906
+ * root.children.slice(span.headingIndex + 1, spans[index + 1]?.headingIndex ?? root.children.length),
2907
+ * );
2908
+ * // bodies[0] holds the first list, `# Top` and the second list
2909
+ * ```
2910
+ *
2911
+ * @param root - The tree whose root-level headings delimit sections.
2912
+ * @param options - The {@link SectionSpanOptions} depth and window.
2913
+ * @returns The matching {@link SectionSpan}s, in document order.
2914
+ * @throws `RangeError` when `depth` is not an integer from 1 to 6, when
2915
+ * `start` or `end` is not an integer in `[0, root.children.length]`, or
2916
+ * when `start > end`.
2917
+ */
2918
+ static sections(root: Root, options: SectionSpanOptions): ReadonlyArray<SectionSpan>;
2919
+ }
2920
+ //#endregion
2664
2921
  //#region src/MarkdownVisitor.d.ts
2665
2922
  /**
2666
2923
  * The discriminated union of markdown visitor events, in document pre-order.
@@ -2872,6 +3129,17 @@ interface MdastNode {
2872
3129
  readonly type: string;
2873
3130
  readonly [key: string]: unknown;
2874
3131
  }
3132
+ /**
3133
+ * Options for {@link Mdast.toText}, matching `mdast-util-to-string`'s.
3134
+ *
3135
+ * @public
3136
+ */
3137
+ interface MdastToTextOptions {
3138
+ /** Whether an `image` or `imageReference` contributes its `alt` text. Defaults to `true`. */
3139
+ readonly includeImageAlt?: boolean;
3140
+ /** Whether an `html` node contributes its raw value. Defaults to `true`. */
3141
+ readonly includeHtml?: boolean;
3142
+ }
2875
3143
  declare const MdastDecodeError_base: Schema.Class<MdastDecodeError, Schema.TaggedStruct<"MdastDecodeError", {
2876
3144
  /** The structured schema issue. Never a string. */
2877
3145
  readonly issue: Schema.Defect;
@@ -2993,6 +3261,39 @@ export declare class Mdast {
2993
3261
  * fails with {@link MdastDecodeError}.
2994
3262
  */
2995
3263
  static readonly fromMdast: (input: unknown) => Effect.Effect<Root, MdastDecodeError, never>;
3264
+ /**
3265
+ * The plain-text content of a node, defined as `mdast-util-to-string`
3266
+ * applied to the node's {@link Mdast.toMdast} projection.
3267
+ *
3268
+ * @remarks
3269
+ * A node carrying a string `value` yields it (an `html` node only when
3270
+ * `includeHtml`); an `image` or `imageReference` yields its non-empty
3271
+ * `alt` when `includeImageAlt`; a node with children yields the
3272
+ * concatenation of its children's text; anything else — a `break`, a
3273
+ * `thematicBreak`, a `definition` — yields `""`. Both options default to
3274
+ * `true`. Pure and total.
3275
+ *
3276
+ * **Values are read as the projection spells them.** The one value the
3277
+ * projection changes is a `code` node's: this package keeps the final line
3278
+ * ending of a code block's content, plain mdast does not, so `toText`
3279
+ * strips it exactly as {@link Mdast.toMdast} does — `"let x"`, not
3280
+ * `"let x\n"`, for a fenced block holding one line.
3281
+ *
3282
+ * This is the remark-ecosystem derivation, and it deliberately differs
3283
+ * from the display derivation behind `DocumentHeading.text` and
3284
+ * `DocumentSection.text`, which render a hard break as a space and drop
3285
+ * raw HTML. Reach for `toText` when matching what remark tooling computes
3286
+ * (slugs, search indexes); reach for the document accessors for display.
3287
+ *
3288
+ * The walk recurses once per nesting level. Every tree `Markdown.parse` or
3289
+ * `Mdast.fromMdast` produces stays within the package's nesting cap, but a
3290
+ * hand-built tree nested far deeper than that can overflow the call stack.
3291
+ *
3292
+ * @param node - The node to flatten.
3293
+ * @param options - Optional {@link MdastToTextOptions}.
3294
+ * @returns The node's text content.
3295
+ */
3296
+ static toText(node: MarkdownNode, options?: MdastToTextOptions): string;
2996
3297
  }
2997
3298
  //#endregion
2998
3299
  //#region src/TomlFrontmatter.d.ts
@@ -3046,5 +3347,5 @@ export declare const TomlFrontmatter: FrontmatterCodec;
3046
3347
  */
3047
3348
  export declare const YamlFrontmatter: FrontmatterCodec;
3048
3349
  //#endregion
3049
- export type { DocumentHeading, DocumentLink, FrontmatterCodec, FrontmatterResolveError, FrontmatterSchemaError, FrontmatterSchemaResolver, FrontmatterWriteError, LinkBearingNode, MarkdownNodeOfType, MarkdownNodeType, MarkdownPath, MarkdownRangeLike, MarkdownSegment, MdastNode, SectionHeadingMatch, SectionQueryOptions };
3350
+ export type { DocumentHeading, DocumentLink, FrontmatterCodec, FrontmatterResolveError, FrontmatterSchemaError, FrontmatterSchemaResolver, FrontmatterWriteError, LinkBearingNode, MarkdownNodeOfType, MarkdownNodeType, MarkdownPath, MarkdownPhrasingOnlyType, MarkdownRangeLike, MarkdownSegment, MarkdownTransformHandlers, MarkdownTransformResult, MdastNode, MdastToTextOptions, SectionHeadingMatch, SectionQueryOptions, SectionSpan, SectionSpanOptions, ShiftHeadingsOptions };
3050
3351
  //# sourceMappingURL=index.d.ts.map
package/index.js CHANGED
@@ -8,9 +8,10 @@ import { MarkdownDiagnostic, MarkdownParseErrorCode } from "./MarkdownDiagnostic
8
8
  import { Markdown, MarkdownDialect, MarkdownParseError, MarkdownParseOptions, MarkdownStringifyError } from "./Markdown.js";
9
9
  import { DocumentSection, MarkdownDocument } from "./MarkdownDocument.js";
10
10
  import { CodeBlockStyle, MarkdownFormat, MarkdownFormattingOptions, MarkdownModificationError, MarkdownModificationErrorCode } from "./MarkdownFormat.js";
11
+ import { MarkdownTransform } from "./MarkdownTransform.js";
11
12
  import { MarkdownVisitor, MarkdownVisitorEvent } from "./MarkdownVisitor.js";
12
13
  import { Mdast, MdastDecodeError } from "./Mdast.js";
13
14
  import { TomlFrontmatter } from "./TomlFrontmatter.js";
14
15
  import { YamlFrontmatter } from "./YamlFrontmatter.js";
15
16
 
16
- export { Blockquote, Break, BreakStyle, BulletChar, Code, CodeBlockStyle, Definition, Delete, DocumentSection, Emphasis, EmphasisChar, FenceChar, FlowContent, FootnoteDefinition, FootnoteReference, Frontmatter, FrontmatterContent, FrontmatterDecodeError, FrontmatterEncodeError, FrontmatterFormat, FrontmatterFormatMismatchError, FrontmatterMissingError, FrontmatterMissingReason, FrontmatterNewline, FrontmatterSource, FrontmatterSourceBlock, FrontmatterSourceSplit, FrontmatterValidationError, Heading, HeadingDepth, HeadingStyle, Html, Image, ImageReference, InlineCode, JsonFrontmatter, Link, LinkReference, List, ListContent, ListDelimiter, ListItem, Markdown, MarkdownDiagnostic, MarkdownDialect, MarkdownDocument, MarkdownEdit, MarkdownFormat, MarkdownFormattingOptions, MarkdownFrontmatter, MarkdownModificationError, MarkdownModificationErrorCode, MarkdownNode, MarkdownParseError, MarkdownParseErrorCode, MarkdownParseOptions, MarkdownRange, MarkdownStringifyError, MarkdownVisitor, MarkdownVisitorEvent, Mdast, MdastDecodeError, MdxFlowExpression, MdxJsxAttribute, MdxJsxAttributeContent, MdxJsxAttributeValueExpression, MdxJsxExpressionAttribute, MdxJsxFlowElement, MdxJsxTextElement, MdxTextExpression, MdxjsEsm, Paragraph, PhrasingContent, Point, Position, ReferenceType, Root, RowContent, SchemaDeclaration, SchemaDeclarationByName, SchemaDeclarationByPath, SchemaDeclarationByUrl, SchemaDeclarationInline, SchemaDeclarationInvalidError, SchemaDeclarationMissingError, SchemaNameUnknownError, SchemaResolver, SchemaVersionUnresolvableError, Strong, Table, TableAlign, TableCell, TableContent, TableRow, Text, ThematicBreak, ThematicBreakChar, TomlFrontmatter, YamlFrontmatter };
17
+ export { Blockquote, Break, BreakStyle, BulletChar, Code, CodeBlockStyle, Definition, Delete, DocumentSection, Emphasis, EmphasisChar, FenceChar, FlowContent, FootnoteDefinition, FootnoteReference, Frontmatter, FrontmatterContent, FrontmatterDecodeError, FrontmatterEncodeError, FrontmatterFormat, FrontmatterFormatMismatchError, FrontmatterMissingError, FrontmatterMissingReason, FrontmatterNewline, FrontmatterSource, FrontmatterSourceBlock, FrontmatterSourceSplit, FrontmatterValidationError, Heading, HeadingDepth, HeadingStyle, Html, Image, ImageReference, InlineCode, JsonFrontmatter, Link, LinkReference, List, ListContent, ListDelimiter, ListItem, Markdown, MarkdownDiagnostic, MarkdownDialect, MarkdownDocument, MarkdownEdit, MarkdownFormat, MarkdownFormattingOptions, MarkdownFrontmatter, MarkdownModificationError, MarkdownModificationErrorCode, MarkdownNode, MarkdownParseError, MarkdownParseErrorCode, MarkdownParseOptions, MarkdownRange, MarkdownStringifyError, MarkdownTransform, MarkdownVisitor, MarkdownVisitorEvent, Mdast, MdastDecodeError, MdxFlowExpression, MdxJsxAttribute, MdxJsxAttributeContent, MdxJsxAttributeValueExpression, MdxJsxExpressionAttribute, MdxJsxFlowElement, MdxJsxTextElement, MdxTextExpression, MdxjsEsm, Paragraph, PhrasingContent, Point, Position, ReferenceType, Root, RowContent, SchemaDeclaration, SchemaDeclarationByName, SchemaDeclarationByPath, SchemaDeclarationByUrl, SchemaDeclarationInline, SchemaDeclarationInvalidError, SchemaDeclarationMissingError, SchemaNameUnknownError, SchemaResolver, SchemaVersionUnresolvableError, Strong, Table, TableAlign, TableCell, TableContent, TableRow, Text, ThematicBreak, ThematicBreakChar, TomlFrontmatter, YamlFrontmatter };
@@ -0,0 +1,44 @@
1
+ //#region src/internal/sectionBounds.ts
2
+ /**
3
+ * Every root-level heading of `children`, in document order, paired with the
4
+ * child index its section ends at.
5
+ */
6
+ const rootHeadingBounds = (children) => {
7
+ const headings = [];
8
+ for (let index = 0; index < children.length; index += 1) {
9
+ const block = children[index];
10
+ if (block !== void 0 && block.type === "heading") headings.push({
11
+ index,
12
+ node: block
13
+ });
14
+ }
15
+ const nextByDepth = [
16
+ void 0,
17
+ void 0,
18
+ void 0,
19
+ void 0,
20
+ void 0,
21
+ void 0,
22
+ void 0
23
+ ];
24
+ const bounds = new Array(headings.length);
25
+ for (let index = headings.length - 1; index >= 0; index -= 1) {
26
+ const current = headings[index];
27
+ if (current === void 0) continue;
28
+ let boundary;
29
+ for (let depth = 1; depth <= current.node.depth; depth += 1) {
30
+ const candidate = nextByDepth[depth];
31
+ if (candidate !== void 0 && (boundary === void 0 || candidate < boundary)) boundary = candidate;
32
+ }
33
+ bounds[index] = {
34
+ heading: current.node,
35
+ index: current.index,
36
+ end: boundary === void 0 ? children.length : headings[boundary]?.index ?? children.length
37
+ };
38
+ nextByDepth[current.node.depth] = index;
39
+ }
40
+ return bounds;
41
+ };
42
+
43
+ //#endregion
44
+ export { rootHeadingBounds };
@@ -824,21 +824,19 @@ const serializeBlocks = (children, state, tight = false) => {
824
824
  return out;
825
825
  };
826
826
  /**
827
- * Serialize a document tree to canonical markdown source.
828
- *
829
- * Throws the raw `GuardExceeded` carrier when the tree nests past
830
- * `MAX_NESTING_DEPTH`; the facade materializes the typed error. Output is
831
- * empty for an empty root and ends with exactly one newline otherwise.
827
+ * Serialize a root-level block list — the one path behind both a whole
828
+ * `Root` and a lone flow node (`[node]`), so the two cannot diverge. Builds
829
+ * no wrapper node, so nothing here can fail a schema constructor.
832
830
  */
833
- const stringifyTree = (root) => {
831
+ const stringifyBlocks = (children) => {
834
832
  const state = {
835
833
  depth: 0,
836
834
  jsxDepth: 0,
837
- mdx: treeContainsMdx(root)
835
+ mdx: children.some((child) => treeContainsMdx(child))
838
836
  };
839
- const body = serializeBlocks(root.children, state);
837
+ const body = serializeBlocks(children, state);
840
838
  return body === "" ? "" : `${body}\n`;
841
839
  };
842
840
 
843
841
  //#endregion
844
- export { stringifyTree };
842
+ export { stringifyBlocks };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@effected/markdown",
3
- "version": "0.18.0",
3
+ "version": "0.19.0",
4
4
  "private": false,
5
5
  "description": "CommonMark and GFM markdown parse, edit and transform schemas for Effect",
6
6
  "keywords": [