@effected/markdown 0.14.0 → 0.15.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/Frontmatter.js CHANGED
@@ -115,9 +115,8 @@ reason: FrontmatterMissingReason }) {
115
115
  * @remarks
116
116
  * `issue` carries the **structured** schema failure — at runtime a
117
117
  * `SchemaIssue.Issue` tree, reachable through `_tag` and nested `issues` —
118
- * never a stringified rendering (the `ConfigValidationError` precedent from
119
- * `@effected/config-file`). It is typed `unknown` because v4 exposes no
120
- * `Schema` for `Issue`; narrow it with the `SchemaIssue` module.
118
+ * never a stringified rendering. It is typed `unknown` because core exposes
119
+ * no `Schema` for `Issue`; narrow it with the `SchemaIssue` module.
121
120
  *
122
121
  * @public
123
122
  */
@@ -159,13 +158,27 @@ const renderBlock = (format, body) => {
159
158
  return `${open}\n${body === "" || body.endsWith("\n") ? body : `${body}\n`}${close}`;
160
159
  };
161
160
  /**
162
- * The frontmatter schema composition facade — typed gray-matter parity.
161
+ * The frontmatter schema composition facade: decodes a parsed document's frontmatter into typed data, and writes typed data back as a frontmatter block.
163
162
  *
164
163
  * @remarks
165
- * The design doc's indicative spelling was `Frontmatter.schema`, but
166
- * `Frontmatter` names the capture node class (the node classes co-locate in
167
- * `MarkdownNode.ts` and are named after their mdast types), so the facade
168
- * follows the package's Markdown-prefix convention instead.
164
+ * Named `MarkdownFrontmatter` rather than `Frontmatter` because `Frontmatter`
165
+ * names the capture node class (the node classes are named after their mdast
166
+ * types).
167
+ *
168
+ * @example
169
+ * ```ts
170
+ * import { MarkdownDocument, MarkdownFrontmatter, MarkdownParseOptions, YamlFrontmatter } from "@effected/markdown";
171
+ * import { Effect, Schema } from "effect";
172
+ *
173
+ * const Meta = Schema.Struct({ title: Schema.String });
174
+ *
175
+ * const program = Effect.gen(function* () {
176
+ * // Frontmatter capture is opt-in at parse time.
177
+ * const source = "---\ntitle: Hello\n---\n\n# Body\n";
178
+ * const doc = yield* MarkdownDocument.parse(source, MarkdownParseOptions.make({ frontmatter: true }));
179
+ * return yield* MarkdownFrontmatter.schema(Meta, YamlFrontmatter)(doc); // { title: "Hello" }
180
+ * });
181
+ * ```
169
182
  *
170
183
  * @public
171
184
  */
@@ -233,7 +246,7 @@ var MarkdownFrontmatter = class MarkdownFrontmatter {
233
246
  * parity, not surgical editing — so anything the format's data model does
234
247
  * not carry is not preserved: comments inside a yaml frontmatter block do
235
248
  * **not** survive `set`. A per-key surgical mode over the format packages'
236
- * edit layers is a documented future refinement, not current scope.
249
+ * edit layers is not offered.
237
250
  *
238
251
  * Schema-producing in spirit: bind the returned writer to a `const` when
239
252
  * writing many documents.
@@ -197,9 +197,7 @@ var SchemaResolver = class SchemaResolver {
197
197
  * @remarks
198
198
  * Non-mapping data and a mapping without a `$schema` key both carry no
199
199
  * declaration: the result succeeds with `undefined` by default, or fails
200
- * with {@link SchemaDeclarationMissingError} under `requireDeclaration` —
201
- * the design's strictness knob, applied at extraction where the
202
- * present-or-absent branch actually lives.
200
+ * with {@link SchemaDeclarationMissingError} under `requireDeclaration`.
203
201
  *
204
202
  * @param data - The decoded frontmatter data.
205
203
  * @param options - `requireDeclaration` makes a missing `$schema` a typed
@@ -212,7 +210,7 @@ var SchemaResolver = class SchemaResolver {
212
210
  return SchemaResolver.classify(data.$schema);
213
211
  }
214
212
  /**
215
- * The package's one built-in resolver: a name-keyed registry with day-one
213
+ * The package's one built-in resolver: a name-keyed registry with
216
214
  * exact version-segment resolution.
217
215
  *
218
216
  * @remarks
@@ -4,21 +4,6 @@ import { Effect, Schema } from "effect";
4
4
 
5
5
  //#region src/FrontmatterSource.ts
6
6
  /**
7
- * String-level frontmatter split and join — the raw-source surface, usable
8
- * without parsing the body at all.
9
- *
10
- * @remarks
11
- * The `MarkdownFrontmatter` seam requires a parsed `MarkdownDocument`; this
12
- * module serves the consumer whose body the CommonMark engine cannot or
13
- * should not parse (an MDX page, a template) and whose contract is
14
- * byte-exact boundaries (a snapshot hash over the body). It runs the SAME
15
- * closed fence grammar as the parser's offset-0 pre-scan — `---` yaml,
16
- * `+++` toml, `---json` json, fence lines exactly the fence, an unclosed
17
- * fence is not frontmatter — over raw bytes, and never parses the
18
- * frontmatter value or the body: string level only, decoding is the codec
19
- * modules' business.
20
- */
21
- /**
22
7
  * The three line-terminator spellings a fence line may end with. Recorded by
23
8
  * {@link FrontmatterSource.split} as fidelity — what the source's fence
24
9
  * lines actually used — and consumed by {@link FrontmatterSource.join}.
package/Markdown.js CHANGED
@@ -45,17 +45,23 @@ var MarkdownParseOptions = class extends Schema.Class("MarkdownParseOptions")({
45
45
  *
46
46
  * @remarks
47
47
  * CommonMark has no syntax errors — every string is a valid document — so
48
- * this error carries only hardening-guard trips (P1: `NestingDepthExceeded`).
48
+ * this error carries only hardening-guard trips (`NestingDepthExceeded`).
49
49
  * Recoverable oddities are diagnostics on {@link MarkdownDocument}, not
50
50
  * failures. A malformed-looking document parses; a nesting bomb fails here
51
51
  * rather than crashing with a `RangeError`.
52
52
  *
53
+ * The `message` renders the diagnostic's position 1-based
54
+ * (`line + 1:character + 1`) for human readers — a CLI printing this string
55
+ * shows the line/column a person counts in their editor. The structured
56
+ * {@link MarkdownDiagnostic} `line`/`character` fields stay 0-based (LSP
57
+ * convention); the offset applies to the rendered message only.
58
+ *
53
59
  * @public
54
60
  */
55
61
  var MarkdownParseError = class extends Schema.TaggedError()("MarkdownParseError", { diagnostic: MarkdownDiagnostic }) {
56
62
  get message() {
57
63
  const { code, line, character, message } = this.diagnostic;
58
- return `Markdown parse failed: ${code} at ${line}:${character} ${message}`;
64
+ return `Markdown parse failed: ${code} at ${line + 1}:${character + 1} ${message}`;
59
65
  }
60
66
  };
61
67
  /**
@@ -89,7 +95,7 @@ var MarkdownStringifyError = class extends Schema.TaggedError()("MarkdownStringi
89
95
  const dialectOf = (options) => options?.dialect ?? "gfm";
90
96
  /**
91
97
  * Whether an options object opts into frontmatter capture. The default —
92
- * `false`, a P3 ruling — is spelled exactly once, here: capture changes how
98
+ * `false` — is spelled exactly once, here: capture changes how
93
99
  * a `---` at offset 0 parses, so the spec-conformant reading is the default
94
100
  * and frontmatter is the consumer's explicit choice.
95
101
  */
@@ -3,9 +3,9 @@ import { Schema } from "effect";
3
3
 
4
4
  //#region src/MarkdownDiagnostic.ts
5
5
  /**
6
- * Error codes `Markdown.parse`/`MarkdownDocument.parse` can fail with. P1
7
- * registers exactly the hardening-guard trip; later phases widen the union
8
- * as new fatal (as opposed to diagnostic-only) conditions are identified.
6
+ * Error codes `Markdown.parse`/`MarkdownDocument.parse` can fail with.
7
+ * Currently exactly one, `"NestingDepthExceeded"`, the hardening-guard trip;
8
+ * the union may widen as new fatal conditions are identified.
9
9
  *
10
10
  * @public
11
11
  */
@@ -22,9 +22,8 @@ const MarkdownParseErrorCode = Schema.Literals(MARKDOWN_PARSE_ERROR_CODES);
22
22
  * shape and `@effected/yaml`'s `YamlDiagnostic`); `message` is this
23
23
  * package's additive extra. `line`/`character` here are zero-based to match
24
24
  * that contract — a different numbering from the one-based `line`/`column`
25
- * unist `Point`s carried on `MarkdownNode` positions (`internal/lineIndex.ts`),
26
- * which is a deliberate, unrelated convention for the AST rather than a
27
- * mismatch to reconcile.
25
+ * unist `Point`s carried on `MarkdownNode` positions, which is a deliberate,
26
+ * unrelated convention for the AST rather than a mismatch to reconcile.
28
27
  *
29
28
  * @public
30
29
  */
@@ -38,8 +37,12 @@ var MarkdownDiagnostic = class MarkdownDiagnostic extends Schema.Class("Markdown
38
37
  }) {
39
38
  /**
40
39
  * Materialize an engine record, deriving zero-based `line`/`character`
41
- * from `offset` against the source `text`. Advanced — the parse entry
40
+ * from `offset` against the source text. Advanced — the parse entry
42
41
  * points call this for you.
42
+ *
43
+ * @param source - The source text the record's `offset` indexes into.
44
+ * @param raw - The engine record: `code`, `message`, `offset` and `length`.
45
+ * @returns The diagnostic with `line` and `character` filled in.
43
46
  */
44
47
  static fromRaw(source, raw) {
45
48
  const { line, character } = lineChar(source, raw.offset);
@@ -127,27 +127,36 @@ const phrasingText = (nodes) => {
127
127
  *
128
128
  * @remarks
129
129
  * The document is the lossless unit — `source` is retained so offsets on the
130
- * tree stay meaningful and so P4's edit/format layer can splice against the
131
- * exact bytes that were parsed.
130
+ * tree stay meaningful and so the edit and format layer can splice against
131
+ * the exact bytes that were parsed.
132
132
  *
133
133
  * `definitions` is an index over the {@link Definition} nodes that remain in
134
134
  * the tree, keyed by case-folded label with the first definition winning; it
135
135
  * is not a place they were moved to. References are emitted unresolved, so
136
136
  * resolution happens against this map.
137
137
  *
138
- * `diagnostics` is empty for every input the P1 parser accepts, and that is
139
- * the current state of the world rather than a missing feature: the plumbing
140
- * from the engine through to this field is real and exercised, but no P1
141
- * construct emits a non-fatal diagnostic yet. The producers arrive with the
142
- * conditions that warrant them — unresolved link references, and
143
- * present-but-unparseable frontmatter in P3. Read an empty array as "nothing
144
- * to report", not as "not implemented", and do not code against it staying
145
- * empty.
138
+ * `diagnostics` is empty for every input the parser currently accepts: the
139
+ * plumbing from the engine through to this field exists, but no construct
140
+ * emits a non-fatal diagnostic yet. Read an empty array as "nothing to
141
+ * report", and do not code against it staying empty — fatal conditions fail
142
+ * through `MarkdownParseError` instead.
146
143
  *
147
144
  * The navigation accessors (`headings`, `sections`, `links`) are derived
148
145
  * getters over the tree — no stored state, no parse-time cost, and they can
149
146
  * never disagree with the tree they read.
150
147
  *
148
+ * @example
149
+ * ```ts
150
+ * import { MarkdownDocument } from "@effected/markdown";
151
+ * import { Effect } from "effect";
152
+ *
153
+ * const program = Effect.gen(function* () {
154
+ * const doc = yield* MarkdownDocument.parse("# Title\n\nSee [docs](./docs.md).\n\n## Usage\n");
155
+ * doc.headings.map((heading) => heading.text); // ["Title", "Usage"]
156
+ * doc.links.map((link) => link.url); // ["./docs.md"]
157
+ * });
158
+ * ```
159
+ *
151
160
  * @public
152
161
  */
153
162
  var MarkdownDocument = class MarkdownDocument extends Schema.Class("MarkdownDocument")({
package/MarkdownEdit.js CHANGED
@@ -34,6 +34,10 @@ var MarkdownEdit = class extends Schema.Class("MarkdownEdit")({
34
34
  * reverse-offset order so earlier offsets stay valid; the input `edits`
35
35
  * array is not mutated. Overlapping edits are a programmer error and throw
36
36
  * as a defect — `MarkdownFormat` never produces them.
37
+ *
38
+ * @param text - The source text to edit.
39
+ * @param edits - The edits to apply, in any order.
40
+ * @returns The edited text.
37
41
  */
38
42
  static applyAll(text, edits) {
39
43
  const sorted = [...edits].sort((a, b) => b.offset - a.offset);
package/MarkdownFormat.js CHANGED
@@ -23,7 +23,7 @@ const CodeBlockStyle = Schema.Literals(["fenced", "indented"]);
23
23
  * source with (same defaults as `Markdown.parse`). Every marker option is
24
24
  * optional and independent; an absent option normalizes nothing.
25
25
  *
26
- * Day-one scope is marker normalization only — heading style, bullet
26
+ * Scope is marker normalization only — heading style, bullet
27
27
  * character, emphasis/strong marker, fence character, thematic-break
28
28
  * character, code-block style. Content is never rewritten, rewrapped or
29
29
  * reflowed.
@@ -71,7 +71,7 @@ const MarkdownModificationErrorCode = Schema.Literals([
71
71
  /**
72
72
  * Raised when `MarkdownFormat.modify` cannot perform the requested
73
73
  * replacement: the target node is not in the document (`NodeNotInDocument`),
74
- * the target kind or splice context is outside the day-one scope
74
+ * the target kind or splice context is outside the supported scope
75
75
  * (`UnsupportedTarget`), the fragment's content category does not fit the
76
76
  * target's slot (`FragmentCategoryMismatch`), or the fragment trips the
77
77
  * stringifier's hardening guard (`FragmentUnrenderable`). Carries the typed
@@ -337,7 +337,26 @@ const findAncestry = (root, target) => {
337
337
  return search(root, []);
338
338
  };
339
339
  /**
340
- * Formatting and modification statics. Not instantiable.
340
+ * Normalizes markdown markers and replaces a single node, as byte-minimal
341
+ * edits that leave the rest of the source untouched. Not instantiable.
342
+ *
343
+ * @example
344
+ * ```ts
345
+ * import { MarkdownDocument, MarkdownFormat, MarkdownFormattingOptions } from "@effected/markdown";
346
+ * import { Effect } from "effect";
347
+ *
348
+ * const options = MarkdownFormattingOptions.make({ bulletChar: "-", headingStyle: "atx" });
349
+ * const normalized = MarkdownFormat.formatToString("* a\n* b\n\nSetext\n======\n", undefined, options);
350
+ * // => "- a\n- b\n\n# Setext\n"
351
+ *
352
+ * const program = Effect.gen(function* () {
353
+ * const doc = yield* MarkdownDocument.parse("# Title\n\nHello world\n");
354
+ * const paragraph = doc.root.children[1];
355
+ * if (paragraph === undefined) return doc.source;
356
+ * return yield* MarkdownFormat.modifyToString(doc, paragraph, "Goodbye");
357
+ * // => "# Title\n\nGoodbye\n"
358
+ * });
359
+ * ```
341
360
  *
342
361
  * @remarks
343
362
  * `format`/`formatToString` are pure and total: input that trips a parse
@@ -368,6 +387,15 @@ var MarkdownFormat = class MarkdownFormat {
368
387
  * code block keeps whichever spelling it has — `fenceChar` alone
369
388
  * normalizes existing fences and deliberately leaves indented blocks
370
389
  * indented.
390
+ *
391
+ * @param text - The markdown source to format.
392
+ * @param range - Optional sub-range; only edits whose node intersects it are
393
+ * returned.
394
+ * @param options - Optional {@link MarkdownFormattingOptions}; an absent
395
+ * marker option normalizes nothing.
396
+ * @returns The edits that normalize the requested markers; apply them with
397
+ * `MarkdownEdit.applyAll`. Empty when the input trips a parse hardening
398
+ * guard.
371
399
  */
372
400
  static format(text, range, options) {
373
401
  const parsed = Markdown.parseResult(text, MarkdownParseOptions.make({
@@ -394,6 +422,12 @@ var MarkdownFormat = class MarkdownFormat {
394
422
  /**
395
423
  * Format `text` and apply the resulting edits in one step
396
424
  * (`MarkdownEdit.applyAll ∘ format`). Pure and total.
425
+ *
426
+ * @param text - The markdown source to format.
427
+ * @param range - Optional sub-range; only edits whose node intersects it are
428
+ * applied.
429
+ * @param options - Optional {@link MarkdownFormattingOptions}.
430
+ * @returns The formatted text.
397
431
  */
398
432
  static formatToString(text, range, options) {
399
433
  return MarkdownEdit.applyAll(text, MarkdownFormat.format(text, range, options));
@@ -405,11 +439,19 @@ var MarkdownFormat = class MarkdownFormat {
405
439
  * node) or a node fragment whose content category must fit the target's
406
440
  * slot (flow for flow targets, phrasing for phrasing targets and table
407
441
  * cells). Every replacement renders through the canonical stringifier, so
408
- * the modified document re-parses cleanly by construction. Day-one scope:
409
- * list items, table rows, frontmatter and the root refuse with
442
+ * the modified document re-parses cleanly by construction. List
443
+ * items, table rows, frontmatter and the root refuse with
410
444
  * `UnsupportedTarget`, as does a multi-line replacement whose target sits
411
445
  * inside a container (a blockquote, list, table or heading) whose
412
446
  * continuation lines the splice cannot prefix.
447
+ *
448
+ * @param document - The parsed {@link MarkdownDocument} the target belongs to.
449
+ * @param target - The node to replace; it must be a node of
450
+ * `document.root`, matched by identity.
451
+ * @param replacement - A plain string (literal text) or a node fragment of
452
+ * the target's content category.
453
+ * @returns An `Effect` that succeeds with the edit to apply (via
454
+ * `MarkdownEdit.applyAll`), or fails with {@link MarkdownModificationError}.
413
455
  */
414
456
  static modify = Effect.fn("MarkdownFormat.modify")(function* (document, target, replacement) {
415
457
  const { start, end } = spanOf(target);
@@ -454,6 +496,14 @@ var MarkdownFormat = class MarkdownFormat {
454
496
  /**
455
497
  * Modify `document` and apply the resulting edit in one step
456
498
  * (`MarkdownEdit.applyAll ∘ modify`).
499
+ *
500
+ * @param document - The parsed {@link MarkdownDocument} the target belongs to.
501
+ * @param target - The node to replace; it must be a node of
502
+ * `document.root`, matched by identity.
503
+ * @param replacement - A plain string (literal text) or a node fragment of
504
+ * the target's content category.
505
+ * @returns An `Effect` that succeeds with the modified source, or fails with
506
+ * {@link MarkdownModificationError}.
457
507
  */
458
508
  static modifyToString = Effect.fn("MarkdownFormat.modifyToString")(function* (document, target, replacement) {
459
509
  const edits = yield* MarkdownFormat.modify(document, target, replacement);
package/MarkdownNode.js CHANGED
@@ -792,11 +792,11 @@ const FrontmatterFormat = Schema.Literals([
792
792
  *
793
793
  * mdast has no single frontmatter node: it names `yaml` in the readme and
794
794
  * `toml` through the frontmatter extension, and json has no mdast name at
795
- * all. This package captures all three through ONE node — the design doc's
796
- * "text plus a format marker" — and the `Mdast` projection (P5) maps
797
- * `format` onto the mdast type names where they exist. Decoding the value is
798
- * the codec modules' job (`YamlFrontmatter`/`TomlFrontmatter`/
799
- * `JsonFrontmatter`, P3 Task 2); the engine never looks inside it.
795
+ * all. This package captures all three through ONE node — text plus a format
796
+ * marker — and the `Mdast` projection maps `format` onto the mdast type
797
+ * names where they exist. Decoding the value is the codec modules' job
798
+ * (`YamlFrontmatter`/`TomlFrontmatter`/`JsonFrontmatter`); the engine never
799
+ * looks inside it.
800
800
  *
801
801
  * Only ever the first child of {@link Root}, and only when parsing opted in
802
802
  * via `MarkdownParseOptions.frontmatter` — mdast's "limited to one node, only
@@ -11,7 +11,24 @@ import { Data, Stream } from "effect";
11
11
  */
12
12
  const MarkdownVisitorEvent = Data.taggedEnum();
13
13
  /**
14
- * SAX-style markdown tree visitor statics. Not instantiable.
14
+ * Walks a parsed markdown tree as a lazy `Stream` of `Enter`/`Exit` events in
15
+ * document pre-order. Not instantiable.
16
+ *
17
+ * @example
18
+ * ```ts
19
+ * import { Markdown, MarkdownVisitor, MarkdownVisitorEvent } from "@effected/markdown";
20
+ * import { Effect, Stream } from "effect";
21
+ *
22
+ * const program = Effect.gen(function* () {
23
+ * const root = yield* Markdown.parse("# Hi\n\ntext\n");
24
+ * return yield* MarkdownVisitor.visit(root).pipe(
25
+ * Stream.filter(MarkdownVisitorEvent.$is("Enter")),
26
+ * Stream.map((event) => event.node.type),
27
+ * Stream.runCollect,
28
+ * );
29
+ * // => ["root", "heading", "text", "paragraph", "text"]
30
+ * });
31
+ * ```
15
32
  *
16
33
  * @public
17
34
  */
@@ -31,6 +48,10 @@ var MarkdownVisitor = class {
31
48
  * as a terminal `MarkdownVisitorEvent` `Error` event carrying a
32
49
  * `NestingDepthExceeded` {@link MarkdownDiagnostic}, mirroring the typed
33
50
  * failure `Markdown.stringify` produces for the same tree.
51
+ *
52
+ * @param root - The tree to walk.
53
+ * @returns A lazy `Stream` of `MarkdownVisitorEvent`, infallible at the type
54
+ * level.
34
55
  */
35
56
  static visit(root) {
36
57
  return Stream.fromIterable(walkIterable(root));
package/Mdast.js CHANGED
@@ -4,41 +4,14 @@ import { Effect, Result, Schema } from "effect";
4
4
 
5
5
  //#region src/Mdast.ts
6
6
  /**
7
- * The remark-ecosystem interop boundary: projection between this package's
8
- * node classes and plain mdast JSON.
9
- *
10
- * @remarks
11
- * `Mdast.toMdast` projects a parsed {@link Root} to plain spec-valid mdast
12
- * objects — fidelity extras stripped, optional fields spelled the way
13
- * `mdast-util-from-markdown` spells them (explicit `null`/`false` where the
14
- * reference utility emits them), so the output deep-equals what the remark
15
- * ecosystem produces and consumes. `Mdast.fromMdast` admits foreign plain
16
- * mdast back into the package's Schema node classes, synthesizing zero-width
17
- * sentinel positions where unist leaves them optional.
18
- *
19
- * The emission target is `mdast-util-from-markdown@2.0.3` (the vendored
20
- * interop corpus pin): `list.ordered`/`start`/`spread` and
21
- * `listItem.spread`/`checked` always explicit (`null` for unknown `start`/
22
- * `checked`), `code.lang`/`meta` and every `title` explicit `null` when
23
- * absent, `image.alt`/`imageReference.alt` always a string. Under unist's
24
- * null-equals-absent convention for optional fields this stays spec-valid
25
- * mdast; it is also byte-identical to the reference utility's output, which
26
- * is what interop means operationally. GFM shapes follow the same convention
27
- * from `mdast-util-gfm`; the frontmatter capture projects to mdast's
28
- * `yaml`/`toml` literal nodes, and a `json` capture projects to a
29
- * `json`-typed literal node per the `mdast-util-frontmatter` custom-preset
30
- * convention (presets name the node type after the language).
31
- */
32
- /**
33
7
  * Indicates that foreign mdast input failed to decode into the package's
34
8
  * node classes.
35
9
  *
36
10
  * @remarks
37
11
  * `issue` carries the **structured** schema failure — at runtime a
38
12
  * `SchemaIssue.Issue` tree, reachable through `_tag` and nested `issues` —
39
- * never a stringified rendering (the `FrontmatterValidationError`
40
- * precedent). It is typed `unknown` because v4 exposes no `Schema` for
41
- * `Issue`; narrow it with the `SchemaIssue` module.
13
+ * never a stringified rendering. It is typed `unknown` because core exposes
14
+ * no `Schema` for `Issue`; narrow it with the `SchemaIssue` module.
42
15
  *
43
16
  * @public
44
17
  */
@@ -391,7 +364,41 @@ const normalizeMdxAttribute = (value) => {
391
364
  };
392
365
  const decodeRoot = Schema.decodeUnknownResult(Root);
393
366
  /**
394
- * The mdast projection facade — the remark-ecosystem interop boundary.
367
+ * Projects parsed markdown trees to plain mdast JSON and admits foreign mdast
368
+ * back into this package's node classes, for interop with the remark
369
+ * ecosystem.
370
+ *
371
+ * @remarks
372
+ * `Mdast.toMdast` projects a parsed {@link Root} to plain spec-valid mdast
373
+ * objects — fidelity extras stripped, optional fields spelled the way
374
+ * `mdast-util-from-markdown@2.0.3` spells them (explicit `null`/`false` where
375
+ * the reference utility emits them), so the output deep-equals what the
376
+ * remark ecosystem produces and consumes: `list.ordered`/`start`/`spread` and
377
+ * `listItem.spread`/`checked` are always explicit (`null` for unknown
378
+ * `start`/`checked`), `code.lang`/`meta` and every `title` are explicit
379
+ * `null` when absent, and `image.alt`/`imageReference.alt` are always
380
+ * strings. Under unist's null-equals-absent convention for optional fields
381
+ * this stays spec-valid mdast. GFM shapes follow the same convention from
382
+ * `mdast-util-gfm`; the frontmatter capture projects to mdast's `yaml`/`toml`
383
+ * literal nodes, and a `json` capture projects to a `json`-typed literal node
384
+ * per the `mdast-util-frontmatter` custom-preset convention (presets name the
385
+ * node type after the language).
386
+ *
387
+ * `Mdast.fromMdast` admits foreign plain mdast back into the package's Schema
388
+ * node classes, synthesizing zero-width sentinel positions where unist leaves
389
+ * them optional.
390
+ *
391
+ * @example
392
+ * ```ts
393
+ * import { Markdown, Mdast } from "@effected/markdown";
394
+ * import { Effect } from "effect";
395
+ *
396
+ * const program = Effect.gen(function* () {
397
+ * const root = yield* Markdown.parse("# Hello\n");
398
+ * const plain = Mdast.toMdast(root); // plain mdast JSON
399
+ * return yield* Mdast.fromMdast(plain); // back to node classes
400
+ * });
401
+ * ```
395
402
  *
396
403
  * @public
397
404
  */
package/README.md CHANGED
@@ -7,10 +7,10 @@
7
7
 
8
8
  Zero-dependency CommonMark 0.31.2 and GFM parsing, editing and transformation expressed as Effect schemas and pure functions. Parse markdown into mdast-shaped nodes carrying byte offsets, navigate headings, sections and links, compute surgical offset-splice edits, normalize markers, decode frontmatter into a validated domain schema, project to and from plain mdast for the remark ecosystem, and walk a document as a `Stream`.
9
9
 
10
- > **Pre-release.** This package is part of the `@effected/*` kit, in pre-`1.0.0`
11
- > development against a single pinned Effect v4 prerelease. Packages graduate to
12
- > `1.0.0` once Effect `4.0.0` ships. To hold your own `effect` versions at
13
- > exactly the ones the kit is built and tested against, install
10
+ > **Pre-`1.0.0`.** This package is part of the `@effected/*` kit, built on stable
11
+ > Effect v4 (`effect` `^4.0.0`) and still in `0.x` development. Stable Effect
12
+ > makes a kit `1.0.0` possible, not automatic. To keep your `effect` and
13
+ > `@effect/*` versions on the line the kit is built and tested against, install
14
14
  > [`@effected/pnpm-plugin-effect`](https://www.npmjs.com/package/@effected/pnpm-plugin-effect).
15
15
  >
16
16
  > **Stability: unstable.** This package's API surface is not yet considered
@@ -15,8 +15,8 @@ import { Toml } from "@effected/toml";
15
15
  *
16
16
  * Encodes with `Toml.stringify`; a value toml cannot represent fails as a
17
17
  * {@link FrontmatterEncodeError} carrying the `TomlStringifyError`
18
- * structurally. An empty object encodes to the **empty body** — the exact
19
- * mirror of the decode ruling above, rendering as adjacent `+++` fences —
18
+ * structurally. An empty object encodes to the **empty body** — the mirror
19
+ * of an empty capture decoding to `{}` — rendering as adjacent `+++` fences,
20
20
  * so `set`-then-decode recovers `{}` exactly.
21
21
  *
22
22
  * `@effected/toml` is an optional peer — importing this module is what
package/index.d.ts CHANGED
@@ -868,11 +868,11 @@ declare const Frontmatter_base: Schema.Class<Frontmatter, Schema.Struct<{
868
868
  *
869
869
  * mdast has no single frontmatter node: it names `yaml` in the readme and
870
870
  * `toml` through the frontmatter extension, and json has no mdast name at
871
- * all. This package captures all three through ONE node — the design doc's
872
- * "text plus a format marker" — and the `Mdast` projection (P5) maps
873
- * `format` onto the mdast type names where they exist. Decoding the value is
874
- * the codec modules' job (`YamlFrontmatter`/`TomlFrontmatter`/
875
- * `JsonFrontmatter`, P3 Task 2); the engine never looks inside it.
871
+ * all. This package captures all three through ONE node — text plus a format
872
+ * marker — and the `Mdast` projection maps `format` onto the mdast type
873
+ * names where they exist. Decoding the value is the codec modules' job
874
+ * (`YamlFrontmatter`/`TomlFrontmatter`/`JsonFrontmatter`); the engine never
875
+ * looks inside it.
876
876
  *
877
877
  * Only ever the first child of {@link Root}, and only when parsing opted in
878
878
  * via `MarkdownParseOptions.frontmatter` — mdast's "limited to one node, only
@@ -944,9 +944,9 @@ type MarkdownNodeOfType<T extends MarkdownNodeType> = Extract<MarkdownNode, {
944
944
  //#endregion
945
945
  //#region src/MarkdownDiagnostic.d.ts
946
946
  /**
947
- * Error codes `Markdown.parse`/`MarkdownDocument.parse` can fail with. P1
948
- * registers exactly the hardening-guard trip; later phases widen the union
949
- * as new fatal (as opposed to diagnostic-only) conditions are identified.
947
+ * Error codes `Markdown.parse`/`MarkdownDocument.parse` can fail with.
948
+ * Currently exactly one, `"NestingDepthExceeded"`, the hardening-guard trip;
949
+ * the union may widen as new fatal conditions are identified.
950
950
  *
951
951
  * @public
952
952
  */
@@ -977,17 +977,20 @@ declare const MarkdownDiagnostic_base: Schema.Class<MarkdownDiagnostic, Schema.S
977
977
  * shape and `@effected/yaml`'s `YamlDiagnostic`); `message` is this
978
978
  * package's additive extra. `line`/`character` here are zero-based to match
979
979
  * that contract — a different numbering from the one-based `line`/`column`
980
- * unist `Point`s carried on `MarkdownNode` positions (`internal/lineIndex.ts`),
981
- * which is a deliberate, unrelated convention for the AST rather than a
982
- * mismatch to reconcile.
980
+ * unist `Point`s carried on `MarkdownNode` positions, which is a deliberate,
981
+ * unrelated convention for the AST rather than a mismatch to reconcile.
983
982
  *
984
983
  * @public
985
984
  */
986
985
  export declare class MarkdownDiagnostic extends MarkdownDiagnostic_base {
987
986
  /**
988
987
  * Materialize an engine record, deriving zero-based `line`/`character`
989
- * from `offset` against the source `text`. Advanced — the parse entry
988
+ * from `offset` against the source text. Advanced — the parse entry
990
989
  * points call this for you.
990
+ *
991
+ * @param source - The source text the record's `offset` indexes into.
992
+ * @param raw - The engine record: `code`, `message`, `offset` and `length`.
993
+ * @returns The diagnostic with `line` and `character` filled in.
991
994
  */
992
995
  static fromRaw(source: string, raw: {
993
996
  readonly code: MarkdownParseErrorCode;
@@ -1046,11 +1049,17 @@ declare const MarkdownParseError_base: Schema.Class<MarkdownParseError, Schema.T
1046
1049
  *
1047
1050
  * @remarks
1048
1051
  * CommonMark has no syntax errors — every string is a valid document — so
1049
- * this error carries only hardening-guard trips (P1: `NestingDepthExceeded`).
1052
+ * this error carries only hardening-guard trips (`NestingDepthExceeded`).
1050
1053
  * Recoverable oddities are diagnostics on {@link MarkdownDocument}, not
1051
1054
  * failures. A malformed-looking document parses; a nesting bomb fails here
1052
1055
  * rather than crashing with a `RangeError`.
1053
1056
  *
1057
+ * The `message` renders the diagnostic's position 1-based
1058
+ * (`line + 1:character + 1`) for human readers — a CLI printing this string
1059
+ * shows the line/column a person counts in their editor. The structured
1060
+ * {@link MarkdownDiagnostic} `line`/`character` fields stay 0-based (LSP
1061
+ * convention); the offset applies to the rendered message only.
1062
+ *
1054
1063
  * @public
1055
1064
  */
1056
1065
  export declare class MarkdownParseError extends MarkdownParseError_base {
@@ -1337,8 +1346,7 @@ export declare class Markdown {
1337
1346
  //#region src/MarkdownEdit.d.ts
1338
1347
  /**
1339
1348
  * A single path segment: a `number` for child indices in the node tree, or a
1340
- * `string` for named addressing (reserved for the navigation surface — e.g.
1341
- * definition identifiers — which arrives with the visitor phase).
1349
+ * `string` for named addressing (reserved, e.g. for definition identifiers).
1342
1350
  *
1343
1351
  * @public
1344
1352
  */
@@ -1385,6 +1393,10 @@ export declare class MarkdownEdit extends MarkdownEdit_base {
1385
1393
  * reverse-offset order so earlier offsets stay valid; the input `edits`
1386
1394
  * array is not mutated. Overlapping edits are a programmer error and throw
1387
1395
  * as a defect — `MarkdownFormat` never produces them.
1396
+ *
1397
+ * @param text - The source text to edit.
1398
+ * @param edits - The edits to apply, in any order.
1399
+ * @returns The edited text.
1388
1400
  */
1389
1401
  static applyAll(text: string, edits: ReadonlyArray<MarkdownEdit>): string;
1390
1402
  }
@@ -1540,27 +1552,36 @@ declare const MarkdownDocument_base: Schema.Class<MarkdownDocument, Schema.Struc
1540
1552
  *
1541
1553
  * @remarks
1542
1554
  * The document is the lossless unit — `source` is retained so offsets on the
1543
- * tree stay meaningful and so P4's edit/format layer can splice against the
1544
- * exact bytes that were parsed.
1555
+ * tree stay meaningful and so the edit and format layer can splice against
1556
+ * the exact bytes that were parsed.
1545
1557
  *
1546
1558
  * `definitions` is an index over the {@link Definition} nodes that remain in
1547
1559
  * the tree, keyed by case-folded label with the first definition winning; it
1548
1560
  * is not a place they were moved to. References are emitted unresolved, so
1549
1561
  * resolution happens against this map.
1550
1562
  *
1551
- * `diagnostics` is empty for every input the P1 parser accepts, and that is
1552
- * the current state of the world rather than a missing feature: the plumbing
1553
- * from the engine through to this field is real and exercised, but no P1
1554
- * construct emits a non-fatal diagnostic yet. The producers arrive with the
1555
- * conditions that warrant them — unresolved link references, and
1556
- * present-but-unparseable frontmatter in P3. Read an empty array as "nothing
1557
- * to report", not as "not implemented", and do not code against it staying
1558
- * empty.
1563
+ * `diagnostics` is empty for every input the parser currently accepts: the
1564
+ * plumbing from the engine through to this field exists, but no construct
1565
+ * emits a non-fatal diagnostic yet. Read an empty array as "nothing to
1566
+ * report", and do not code against it staying empty — fatal conditions fail
1567
+ * through `MarkdownParseError` instead.
1559
1568
  *
1560
1569
  * The navigation accessors (`headings`, `sections`, `links`) are derived
1561
1570
  * getters over the tree — no stored state, no parse-time cost, and they can
1562
1571
  * never disagree with the tree they read.
1563
1572
  *
1573
+ * @example
1574
+ * ```ts
1575
+ * import { MarkdownDocument } from "@effected/markdown";
1576
+ * import { Effect } from "effect";
1577
+ *
1578
+ * const program = Effect.gen(function* () {
1579
+ * const doc = yield* MarkdownDocument.parse("# Title\n\nSee [docs](./docs.md).\n\n## Usage\n");
1580
+ * doc.headings.map((heading) => heading.text); // ["Title", "Usage"]
1581
+ * doc.links.map((link) => link.url); // ["./docs.md"]
1582
+ * });
1583
+ * ```
1584
+ *
1564
1585
  * @public
1565
1586
  */
1566
1587
  export declare class MarkdownDocument extends MarkdownDocument_base {
@@ -1891,9 +1912,8 @@ declare const FrontmatterValidationError_base: Schema.Class<FrontmatterValidatio
1891
1912
  * @remarks
1892
1913
  * `issue` carries the **structured** schema failure — at runtime a
1893
1914
  * `SchemaIssue.Issue` tree, reachable through `_tag` and nested `issues` —
1894
- * never a stringified rendering (the `ConfigValidationError` precedent from
1895
- * `@effected/config-file`). It is typed `unknown` because v4 exposes no
1896
- * `Schema` for `Issue`; narrow it with the `SchemaIssue` module.
1915
+ * never a stringified rendering. It is typed `unknown` because core exposes
1916
+ * no `Schema` for `Issue`; narrow it with the `SchemaIssue` module.
1897
1917
  *
1898
1918
  * @public
1899
1919
  */
@@ -1918,13 +1938,27 @@ type FrontmatterSchemaError = FrontmatterMissingError | FrontmatterFormatMismatc
1918
1938
  */
1919
1939
  type FrontmatterWriteError = FrontmatterFormatMismatchError | FrontmatterEncodeError | FrontmatterValidationError;
1920
1940
  /**
1921
- * The frontmatter schema composition facade — typed gray-matter parity.
1941
+ * The frontmatter schema composition facade: decodes a parsed document's frontmatter into typed data, and writes typed data back as a frontmatter block.
1922
1942
  *
1923
1943
  * @remarks
1924
- * The design doc's indicative spelling was `Frontmatter.schema`, but
1925
- * `Frontmatter` names the capture node class (the node classes co-locate in
1926
- * `MarkdownNode.ts` and are named after their mdast types), so the facade
1927
- * follows the package's Markdown-prefix convention instead.
1944
+ * Named `MarkdownFrontmatter` rather than `Frontmatter` because `Frontmatter`
1945
+ * names the capture node class (the node classes are named after their mdast
1946
+ * types).
1947
+ *
1948
+ * @example
1949
+ * ```ts
1950
+ * import { MarkdownDocument, MarkdownFrontmatter, MarkdownParseOptions, YamlFrontmatter } from "@effected/markdown";
1951
+ * import { Effect, Schema } from "effect";
1952
+ *
1953
+ * const Meta = Schema.Struct({ title: Schema.String });
1954
+ *
1955
+ * const program = Effect.gen(function* () {
1956
+ * // Frontmatter capture is opt-in at parse time.
1957
+ * const source = "---\ntitle: Hello\n---\n\n# Body\n";
1958
+ * const doc = yield* MarkdownDocument.parse(source, MarkdownParseOptions.make({ frontmatter: true }));
1959
+ * return yield* MarkdownFrontmatter.schema(Meta, YamlFrontmatter)(doc); // { title: "Hello" }
1960
+ * });
1961
+ * ```
1928
1962
  *
1929
1963
  * @public
1930
1964
  */
@@ -1987,7 +2021,7 @@ export declare class MarkdownFrontmatter {
1987
2021
  * parity, not surgical editing — so anything the format's data model does
1988
2022
  * not carry is not preserved: comments inside a yaml frontmatter block do
1989
2023
  * **not** survive `set`. A per-key surgical mode over the format packages'
1990
- * edit layers is a documented future refinement, not current scope.
2024
+ * edit layers is not offered.
1991
2025
  *
1992
2026
  * Schema-producing in spirit: bind the returned writer to a `const` when
1993
2027
  * writing many documents.
@@ -2210,9 +2244,7 @@ export declare class SchemaResolver {
2210
2244
  * @remarks
2211
2245
  * Non-mapping data and a mapping without a `$schema` key both carry no
2212
2246
  * declaration: the result succeeds with `undefined` by default, or fails
2213
- * with {@link SchemaDeclarationMissingError} under `requireDeclaration` —
2214
- * the design's strictness knob, applied at extraction where the
2215
- * present-or-absent branch actually lives.
2247
+ * with {@link SchemaDeclarationMissingError} under `requireDeclaration`.
2216
2248
  *
2217
2249
  * @param data - The decoded frontmatter data.
2218
2250
  * @param options - `requireDeclaration` makes a missing `$schema` a typed
@@ -2224,7 +2256,7 @@ export declare class SchemaResolver {
2224
2256
  readonly requireDeclaration?: boolean;
2225
2257
  }): Result.Result<SchemaDeclaration | undefined, SchemaDeclarationInvalidError | SchemaDeclarationMissingError>;
2226
2258
  /**
2227
- * The package's one built-in resolver: a name-keyed registry with day-one
2259
+ * The package's one built-in resolver: a name-keyed registry with
2228
2260
  * exact version-segment resolution.
2229
2261
  *
2230
2262
  * @remarks
@@ -2461,7 +2493,7 @@ declare const MarkdownFormattingOptions_base: Schema.Class<MarkdownFormattingOpt
2461
2493
  * source with (same defaults as `Markdown.parse`). Every marker option is
2462
2494
  * optional and independent; an absent option normalizes nothing.
2463
2495
  *
2464
- * Day-one scope is marker normalization only — heading style, bullet
2496
+ * Scope is marker normalization only — heading style, bullet
2465
2497
  * character, emphasis/strong marker, fence character, thematic-break
2466
2498
  * character, code-block style. Content is never rewritten, rewrapped or
2467
2499
  * reflowed.
@@ -2507,7 +2539,7 @@ declare const MarkdownModificationError_base: Schema.Class<MarkdownModificationE
2507
2539
  /**
2508
2540
  * Raised when `MarkdownFormat.modify` cannot perform the requested
2509
2541
  * replacement: the target node is not in the document (`NodeNotInDocument`),
2510
- * the target kind or splice context is outside the day-one scope
2542
+ * the target kind or splice context is outside the supported scope
2511
2543
  * (`UnsupportedTarget`), the fragment's content category does not fit the
2512
2544
  * target's slot (`FragmentCategoryMismatch`), or the fragment trips the
2513
2545
  * stringifier's hardening guard (`FragmentUnrenderable`). Carries the typed
@@ -2520,7 +2552,26 @@ export declare class MarkdownModificationError extends MarkdownModificationError
2520
2552
  get message(): string;
2521
2553
  }
2522
2554
  /**
2523
- * Formatting and modification statics. Not instantiable.
2555
+ * Normalizes markdown markers and replaces a single node, as byte-minimal
2556
+ * edits that leave the rest of the source untouched. Not instantiable.
2557
+ *
2558
+ * @example
2559
+ * ```ts
2560
+ * import { MarkdownDocument, MarkdownFormat, MarkdownFormattingOptions } from "@effected/markdown";
2561
+ * import { Effect } from "effect";
2562
+ *
2563
+ * const options = MarkdownFormattingOptions.make({ bulletChar: "-", headingStyle: "atx" });
2564
+ * const normalized = MarkdownFormat.formatToString("* a\n* b\n\nSetext\n======\n", undefined, options);
2565
+ * // => "- a\n- b\n\n# Setext\n"
2566
+ *
2567
+ * const program = Effect.gen(function* () {
2568
+ * const doc = yield* MarkdownDocument.parse("# Title\n\nHello world\n");
2569
+ * const paragraph = doc.root.children[1];
2570
+ * if (paragraph === undefined) return doc.source;
2571
+ * return yield* MarkdownFormat.modifyToString(doc, paragraph, "Goodbye");
2572
+ * // => "# Title\n\nGoodbye\n"
2573
+ * });
2574
+ * ```
2524
2575
  *
2525
2576
  * @remarks
2526
2577
  * `format`/`formatToString` are pure and total: input that trips a parse
@@ -2551,11 +2602,26 @@ export declare class MarkdownFormat {
2551
2602
  * code block keeps whichever spelling it has — `fenceChar` alone
2552
2603
  * normalizes existing fences and deliberately leaves indented blocks
2553
2604
  * indented.
2605
+ *
2606
+ * @param text - The markdown source to format.
2607
+ * @param range - Optional sub-range; only edits whose node intersects it are
2608
+ * returned.
2609
+ * @param options - Optional {@link MarkdownFormattingOptions}; an absent
2610
+ * marker option normalizes nothing.
2611
+ * @returns The edits that normalize the requested markers; apply them with
2612
+ * `MarkdownEdit.applyAll`. Empty when the input trips a parse hardening
2613
+ * guard.
2554
2614
  */
2555
2615
  static format(text: string, range?: MarkdownRangeLike, options?: MarkdownFormattingOptions): ReadonlyArray<MarkdownEdit>;
2556
2616
  /**
2557
2617
  * Format `text` and apply the resulting edits in one step
2558
2618
  * (`MarkdownEdit.applyAll ∘ format`). Pure and total.
2619
+ *
2620
+ * @param text - The markdown source to format.
2621
+ * @param range - Optional sub-range; only edits whose node intersects it are
2622
+ * applied.
2623
+ * @param options - Optional {@link MarkdownFormattingOptions}.
2624
+ * @returns The formatted text.
2559
2625
  */
2560
2626
  static formatToString(text: string, range?: MarkdownRangeLike, options?: MarkdownFormattingOptions): string;
2561
2627
  /**
@@ -2565,16 +2631,32 @@ export declare class MarkdownFormat {
2565
2631
  * node) or a node fragment whose content category must fit the target's
2566
2632
  * slot (flow for flow targets, phrasing for phrasing targets and table
2567
2633
  * cells). Every replacement renders through the canonical stringifier, so
2568
- * the modified document re-parses cleanly by construction. Day-one scope:
2569
- * list items, table rows, frontmatter and the root refuse with
2634
+ * the modified document re-parses cleanly by construction. List
2635
+ * items, table rows, frontmatter and the root refuse with
2570
2636
  * `UnsupportedTarget`, as does a multi-line replacement whose target sits
2571
2637
  * inside a container (a blockquote, list, table or heading) whose
2572
2638
  * continuation lines the splice cannot prefix.
2639
+ *
2640
+ * @param document - The parsed {@link MarkdownDocument} the target belongs to.
2641
+ * @param target - The node to replace; it must be a node of
2642
+ * `document.root`, matched by identity.
2643
+ * @param replacement - A plain string (literal text) or a node fragment of
2644
+ * the target's content category.
2645
+ * @returns An `Effect` that succeeds with the edit to apply (via
2646
+ * `MarkdownEdit.applyAll`), or fails with {@link MarkdownModificationError}.
2573
2647
  */
2574
2648
  static readonly modify: (document: MarkdownDocument, target: MarkdownNode, replacement: string | MarkdownNode) => Effect.Effect<readonly MarkdownEdit[], MarkdownModificationError, never>;
2575
2649
  /**
2576
2650
  * Modify `document` and apply the resulting edit in one step
2577
2651
  * (`MarkdownEdit.applyAll ∘ modify`).
2652
+ *
2653
+ * @param document - The parsed {@link MarkdownDocument} the target belongs to.
2654
+ * @param target - The node to replace; it must be a node of
2655
+ * `document.root`, matched by identity.
2656
+ * @param replacement - A plain string (literal text) or a node fragment of
2657
+ * the target's content category.
2658
+ * @returns An `Effect` that succeeds with the modified source, or fails with
2659
+ * {@link MarkdownModificationError}.
2578
2660
  */
2579
2661
  static readonly modifyToString: (document: MarkdownDocument, target: MarkdownNode, replacement: string | MarkdownNode) => Effect.Effect<string, MarkdownModificationError, never>;
2580
2662
  }
@@ -2730,7 +2812,24 @@ export declare const MarkdownVisitorEvent: {
2730
2812
  }, "_tag">;
2731
2813
  };
2732
2814
  /**
2733
- * SAX-style markdown tree visitor statics. Not instantiable.
2815
+ * Walks a parsed markdown tree as a lazy `Stream` of `Enter`/`Exit` events in
2816
+ * document pre-order. Not instantiable.
2817
+ *
2818
+ * @example
2819
+ * ```ts
2820
+ * import { Markdown, MarkdownVisitor, MarkdownVisitorEvent } from "@effected/markdown";
2821
+ * import { Effect, Stream } from "effect";
2822
+ *
2823
+ * const program = Effect.gen(function* () {
2824
+ * const root = yield* Markdown.parse("# Hi\n\ntext\n");
2825
+ * return yield* MarkdownVisitor.visit(root).pipe(
2826
+ * Stream.filter(MarkdownVisitorEvent.$is("Enter")),
2827
+ * Stream.map((event) => event.node.type),
2828
+ * Stream.runCollect,
2829
+ * );
2830
+ * // => ["root", "heading", "text", "paragraph", "text"]
2831
+ * });
2832
+ * ```
2734
2833
  *
2735
2834
  * @public
2736
2835
  */
@@ -2750,6 +2849,10 @@ export declare class MarkdownVisitor {
2750
2849
  * as a terminal `MarkdownVisitorEvent` `Error` event carrying a
2751
2850
  * `NestingDepthExceeded` {@link MarkdownDiagnostic}, mirroring the typed
2752
2851
  * failure `Markdown.stringify` produces for the same tree.
2852
+ *
2853
+ * @param root - The tree to walk.
2854
+ * @returns A lazy `Stream` of `MarkdownVisitorEvent`, infallible at the type
2855
+ * level.
2753
2856
  */
2754
2857
  static visit(root: Root): Stream.Stream<MarkdownVisitorEvent>;
2755
2858
  }
@@ -2780,9 +2883,8 @@ declare const MdastDecodeError_base: Schema.Class<MdastDecodeError, Schema.Tagge
2780
2883
  * @remarks
2781
2884
  * `issue` carries the **structured** schema failure — at runtime a
2782
2885
  * `SchemaIssue.Issue` tree, reachable through `_tag` and nested `issues` —
2783
- * never a stringified rendering (the `FrontmatterValidationError`
2784
- * precedent). It is typed `unknown` because v4 exposes no `Schema` for
2785
- * `Issue`; narrow it with the `SchemaIssue` module.
2886
+ * never a stringified rendering. It is typed `unknown` because core exposes
2887
+ * no `Schema` for `Issue`; narrow it with the `SchemaIssue` module.
2786
2888
  *
2787
2889
  * @public
2788
2890
  */
@@ -2790,7 +2892,41 @@ export declare class MdastDecodeError extends MdastDecodeError_base {
2790
2892
  get message(): string;
2791
2893
  }
2792
2894
  /**
2793
- * The mdast projection facade — the remark-ecosystem interop boundary.
2895
+ * Projects parsed markdown trees to plain mdast JSON and admits foreign mdast
2896
+ * back into this package's node classes, for interop with the remark
2897
+ * ecosystem.
2898
+ *
2899
+ * @remarks
2900
+ * `Mdast.toMdast` projects a parsed {@link Root} to plain spec-valid mdast
2901
+ * objects — fidelity extras stripped, optional fields spelled the way
2902
+ * `mdast-util-from-markdown@2.0.3` spells them (explicit `null`/`false` where
2903
+ * the reference utility emits them), so the output deep-equals what the
2904
+ * remark ecosystem produces and consumes: `list.ordered`/`start`/`spread` and
2905
+ * `listItem.spread`/`checked` are always explicit (`null` for unknown
2906
+ * `start`/`checked`), `code.lang`/`meta` and every `title` are explicit
2907
+ * `null` when absent, and `image.alt`/`imageReference.alt` are always
2908
+ * strings. Under unist's null-equals-absent convention for optional fields
2909
+ * this stays spec-valid mdast. GFM shapes follow the same convention from
2910
+ * `mdast-util-gfm`; the frontmatter capture projects to mdast's `yaml`/`toml`
2911
+ * literal nodes, and a `json` capture projects to a `json`-typed literal node
2912
+ * per the `mdast-util-frontmatter` custom-preset convention (presets name the
2913
+ * node type after the language).
2914
+ *
2915
+ * `Mdast.fromMdast` admits foreign plain mdast back into the package's Schema
2916
+ * node classes, synthesizing zero-width sentinel positions where unist leaves
2917
+ * them optional.
2918
+ *
2919
+ * @example
2920
+ * ```ts
2921
+ * import { Markdown, Mdast } from "@effected/markdown";
2922
+ * import { Effect } from "effect";
2923
+ *
2924
+ * const program = Effect.gen(function* () {
2925
+ * const root = yield* Markdown.parse("# Hello\n");
2926
+ * const plain = Mdast.toMdast(root); // plain mdast JSON
2927
+ * return yield* Mdast.fromMdast(plain); // back to node classes
2928
+ * });
2929
+ * ```
2794
2930
  *
2795
2931
  * @public
2796
2932
  */
@@ -2872,8 +3008,8 @@ export declare class Mdast {
2872
3008
  *
2873
3009
  * Encodes with `Toml.stringify`; a value toml cannot represent fails as a
2874
3010
  * {@link FrontmatterEncodeError} carrying the `TomlStringifyError`
2875
- * structurally. An empty object encodes to the **empty body** — the exact
2876
- * mirror of the decode ruling above, rendering as adjacent `+++` fences —
3011
+ * structurally. An empty object encodes to the **empty body** — the mirror
3012
+ * of an empty capture decoding to `{}` — rendering as adjacent `+++` fences,
2877
3013
  * so `set`-then-decode recovers `{}` exactly.
2878
3014
  *
2879
3015
  * `@effected/toml` is an optional peer — importing this module is what
@@ -1,7 +1,7 @@
1
1
  //#region src/internal/carriers.ts
2
2
  /**
3
- * P1's error-code vocabulary. Widens as later phases add parse-error kinds;
4
- * P1 registers exactly one, the hardening-guard trip.
3
+ * The engine's error-code vocabulary: currently exactly one, the
4
+ * hardening-guard trip. Widens as new parse-error kinds are added.
5
5
  */
6
6
  const MARKDOWN_PARSE_ERROR_CODES = ["NestingDepthExceeded"];
7
7
  /** The engine's carrier for a recoverable-turned-fatal parse condition. */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@effected/markdown",
3
- "version": "0.14.0",
3
+ "version": "0.15.1",
4
4
  "private": false,
5
5
  "description": "CommonMark and GFM markdown parse, edit and transform schemas for Effect",
6
6
  "keywords": [
@@ -39,10 +39,10 @@
39
39
  "./package.json": "./package.json"
40
40
  },
41
41
  "peerDependencies": {
42
- "@effected/jsonc": "^0.14.0",
43
- "@effected/toml": "^0.10.0",
44
- "@effected/yaml": "^0.18.0",
45
- "effect": "4.0.0-rc.118"
42
+ "@effected/jsonc": "^0.15.1",
43
+ "@effected/toml": "^0.11.1",
44
+ "@effected/yaml": "^0.19.1",
45
+ "effect": "^4.0.0"
46
46
  },
47
47
  "peerDependenciesMeta": {
48
48
  "@effected/jsonc": {