@effected/markdown 0.13.0 → 0.15.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/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,7 +45,7 @@ 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`.
@@ -89,7 +89,7 @@ var MarkdownStringifyError = class extends Schema.TaggedError()("MarkdownStringi
89
89
  const dialectOf = (options) => options?.dialect ?? "gfm";
90
90
  /**
91
91
  * Whether an options object opts into frontmatter capture. The default —
92
- * `false`, a P3 ruling — is spelled exactly once, here: capture changes how
92
+ * `false` — is spelled exactly once, here: capture changes how
93
93
  * a `---` at offset 0 parses, so the spec-conformant reading is the default
94
94
  * and frontmatter is the consumer's explicit choice.
95
95
  */
@@ -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,7 +1049,7 @@ 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`.
@@ -1337,8 +1340,7 @@ export declare class Markdown {
1337
1340
  //#region src/MarkdownEdit.d.ts
1338
1341
  /**
1339
1342
  * 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).
1343
+ * `string` for named addressing (reserved, e.g. for definition identifiers).
1342
1344
  *
1343
1345
  * @public
1344
1346
  */
@@ -1385,6 +1387,10 @@ export declare class MarkdownEdit extends MarkdownEdit_base {
1385
1387
  * reverse-offset order so earlier offsets stay valid; the input `edits`
1386
1388
  * array is not mutated. Overlapping edits are a programmer error and throw
1387
1389
  * as a defect — `MarkdownFormat` never produces them.
1390
+ *
1391
+ * @param text - The source text to edit.
1392
+ * @param edits - The edits to apply, in any order.
1393
+ * @returns The edited text.
1388
1394
  */
1389
1395
  static applyAll(text: string, edits: ReadonlyArray<MarkdownEdit>): string;
1390
1396
  }
@@ -1540,27 +1546,36 @@ declare const MarkdownDocument_base: Schema.Class<MarkdownDocument, Schema.Struc
1540
1546
  *
1541
1547
  * @remarks
1542
1548
  * 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.
1549
+ * tree stay meaningful and so the edit and format layer can splice against
1550
+ * the exact bytes that were parsed.
1545
1551
  *
1546
1552
  * `definitions` is an index over the {@link Definition} nodes that remain in
1547
1553
  * the tree, keyed by case-folded label with the first definition winning; it
1548
1554
  * is not a place they were moved to. References are emitted unresolved, so
1549
1555
  * resolution happens against this map.
1550
1556
  *
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.
1557
+ * `diagnostics` is empty for every input the parser currently accepts: the
1558
+ * plumbing from the engine through to this field exists, but no construct
1559
+ * emits a non-fatal diagnostic yet. Read an empty array as "nothing to
1560
+ * report", and do not code against it staying empty — fatal conditions fail
1561
+ * through `MarkdownParseError` instead.
1559
1562
  *
1560
1563
  * The navigation accessors (`headings`, `sections`, `links`) are derived
1561
1564
  * getters over the tree — no stored state, no parse-time cost, and they can
1562
1565
  * never disagree with the tree they read.
1563
1566
  *
1567
+ * @example
1568
+ * ```ts
1569
+ * import { MarkdownDocument } from "@effected/markdown";
1570
+ * import { Effect } from "effect";
1571
+ *
1572
+ * const program = Effect.gen(function* () {
1573
+ * const doc = yield* MarkdownDocument.parse("# Title\n\nSee [docs](./docs.md).\n\n## Usage\n");
1574
+ * doc.headings.map((heading) => heading.text); // ["Title", "Usage"]
1575
+ * doc.links.map((link) => link.url); // ["./docs.md"]
1576
+ * });
1577
+ * ```
1578
+ *
1564
1579
  * @public
1565
1580
  */
1566
1581
  export declare class MarkdownDocument extends MarkdownDocument_base {
@@ -1891,9 +1906,8 @@ declare const FrontmatterValidationError_base: Schema.Class<FrontmatterValidatio
1891
1906
  * @remarks
1892
1907
  * `issue` carries the **structured** schema failure — at runtime a
1893
1908
  * `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.
1909
+ * never a stringified rendering. It is typed `unknown` because core exposes
1910
+ * no `Schema` for `Issue`; narrow it with the `SchemaIssue` module.
1897
1911
  *
1898
1912
  * @public
1899
1913
  */
@@ -1918,13 +1932,27 @@ type FrontmatterSchemaError = FrontmatterMissingError | FrontmatterFormatMismatc
1918
1932
  */
1919
1933
  type FrontmatterWriteError = FrontmatterFormatMismatchError | FrontmatterEncodeError | FrontmatterValidationError;
1920
1934
  /**
1921
- * The frontmatter schema composition facade — typed gray-matter parity.
1935
+ * The frontmatter schema composition facade: decodes a parsed document's frontmatter into typed data, and writes typed data back as a frontmatter block.
1922
1936
  *
1923
1937
  * @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.
1938
+ * Named `MarkdownFrontmatter` rather than `Frontmatter` because `Frontmatter`
1939
+ * names the capture node class (the node classes are named after their mdast
1940
+ * types).
1941
+ *
1942
+ * @example
1943
+ * ```ts
1944
+ * import { MarkdownDocument, MarkdownFrontmatter, MarkdownParseOptions, YamlFrontmatter } from "@effected/markdown";
1945
+ * import { Effect, Schema } from "effect";
1946
+ *
1947
+ * const Meta = Schema.Struct({ title: Schema.String });
1948
+ *
1949
+ * const program = Effect.gen(function* () {
1950
+ * // Frontmatter capture is opt-in at parse time.
1951
+ * const source = "---\ntitle: Hello\n---\n\n# Body\n";
1952
+ * const doc = yield* MarkdownDocument.parse(source, MarkdownParseOptions.make({ frontmatter: true }));
1953
+ * return yield* MarkdownFrontmatter.schema(Meta, YamlFrontmatter)(doc); // { title: "Hello" }
1954
+ * });
1955
+ * ```
1928
1956
  *
1929
1957
  * @public
1930
1958
  */
@@ -1987,7 +2015,7 @@ export declare class MarkdownFrontmatter {
1987
2015
  * parity, not surgical editing — so anything the format's data model does
1988
2016
  * not carry is not preserved: comments inside a yaml frontmatter block do
1989
2017
  * **not** survive `set`. A per-key surgical mode over the format packages'
1990
- * edit layers is a documented future refinement, not current scope.
2018
+ * edit layers is not offered.
1991
2019
  *
1992
2020
  * Schema-producing in spirit: bind the returned writer to a `const` when
1993
2021
  * writing many documents.
@@ -2210,9 +2238,7 @@ export declare class SchemaResolver {
2210
2238
  * @remarks
2211
2239
  * Non-mapping data and a mapping without a `$schema` key both carry no
2212
2240
  * 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.
2241
+ * with {@link SchemaDeclarationMissingError} under `requireDeclaration`.
2216
2242
  *
2217
2243
  * @param data - The decoded frontmatter data.
2218
2244
  * @param options - `requireDeclaration` makes a missing `$schema` a typed
@@ -2224,7 +2250,7 @@ export declare class SchemaResolver {
2224
2250
  readonly requireDeclaration?: boolean;
2225
2251
  }): Result.Result<SchemaDeclaration | undefined, SchemaDeclarationInvalidError | SchemaDeclarationMissingError>;
2226
2252
  /**
2227
- * The package's one built-in resolver: a name-keyed registry with day-one
2253
+ * The package's one built-in resolver: a name-keyed registry with
2228
2254
  * exact version-segment resolution.
2229
2255
  *
2230
2256
  * @remarks
@@ -2461,7 +2487,7 @@ declare const MarkdownFormattingOptions_base: Schema.Class<MarkdownFormattingOpt
2461
2487
  * source with (same defaults as `Markdown.parse`). Every marker option is
2462
2488
  * optional and independent; an absent option normalizes nothing.
2463
2489
  *
2464
- * Day-one scope is marker normalization only — heading style, bullet
2490
+ * Scope is marker normalization only — heading style, bullet
2465
2491
  * character, emphasis/strong marker, fence character, thematic-break
2466
2492
  * character, code-block style. Content is never rewritten, rewrapped or
2467
2493
  * reflowed.
@@ -2507,7 +2533,7 @@ declare const MarkdownModificationError_base: Schema.Class<MarkdownModificationE
2507
2533
  /**
2508
2534
  * Raised when `MarkdownFormat.modify` cannot perform the requested
2509
2535
  * replacement: the target node is not in the document (`NodeNotInDocument`),
2510
- * the target kind or splice context is outside the day-one scope
2536
+ * the target kind or splice context is outside the supported scope
2511
2537
  * (`UnsupportedTarget`), the fragment's content category does not fit the
2512
2538
  * target's slot (`FragmentCategoryMismatch`), or the fragment trips the
2513
2539
  * stringifier's hardening guard (`FragmentUnrenderable`). Carries the typed
@@ -2520,7 +2546,26 @@ export declare class MarkdownModificationError extends MarkdownModificationError
2520
2546
  get message(): string;
2521
2547
  }
2522
2548
  /**
2523
- * Formatting and modification statics. Not instantiable.
2549
+ * Normalizes markdown markers and replaces a single node, as byte-minimal
2550
+ * edits that leave the rest of the source untouched. Not instantiable.
2551
+ *
2552
+ * @example
2553
+ * ```ts
2554
+ * import { MarkdownDocument, MarkdownFormat, MarkdownFormattingOptions } from "@effected/markdown";
2555
+ * import { Effect } from "effect";
2556
+ *
2557
+ * const options = MarkdownFormattingOptions.make({ bulletChar: "-", headingStyle: "atx" });
2558
+ * const normalized = MarkdownFormat.formatToString("* a\n* b\n\nSetext\n======\n", undefined, options);
2559
+ * // => "- a\n- b\n\n# Setext\n"
2560
+ *
2561
+ * const program = Effect.gen(function* () {
2562
+ * const doc = yield* MarkdownDocument.parse("# Title\n\nHello world\n");
2563
+ * const paragraph = doc.root.children[1];
2564
+ * if (paragraph === undefined) return doc.source;
2565
+ * return yield* MarkdownFormat.modifyToString(doc, paragraph, "Goodbye");
2566
+ * // => "# Title\n\nGoodbye\n"
2567
+ * });
2568
+ * ```
2524
2569
  *
2525
2570
  * @remarks
2526
2571
  * `format`/`formatToString` are pure and total: input that trips a parse
@@ -2551,11 +2596,26 @@ export declare class MarkdownFormat {
2551
2596
  * code block keeps whichever spelling it has — `fenceChar` alone
2552
2597
  * normalizes existing fences and deliberately leaves indented blocks
2553
2598
  * indented.
2599
+ *
2600
+ * @param text - The markdown source to format.
2601
+ * @param range - Optional sub-range; only edits whose node intersects it are
2602
+ * returned.
2603
+ * @param options - Optional {@link MarkdownFormattingOptions}; an absent
2604
+ * marker option normalizes nothing.
2605
+ * @returns The edits that normalize the requested markers; apply them with
2606
+ * `MarkdownEdit.applyAll`. Empty when the input trips a parse hardening
2607
+ * guard.
2554
2608
  */
2555
2609
  static format(text: string, range?: MarkdownRangeLike, options?: MarkdownFormattingOptions): ReadonlyArray<MarkdownEdit>;
2556
2610
  /**
2557
2611
  * Format `text` and apply the resulting edits in one step
2558
2612
  * (`MarkdownEdit.applyAll ∘ format`). Pure and total.
2613
+ *
2614
+ * @param text - The markdown source to format.
2615
+ * @param range - Optional sub-range; only edits whose node intersects it are
2616
+ * applied.
2617
+ * @param options - Optional {@link MarkdownFormattingOptions}.
2618
+ * @returns The formatted text.
2559
2619
  */
2560
2620
  static formatToString(text: string, range?: MarkdownRangeLike, options?: MarkdownFormattingOptions): string;
2561
2621
  /**
@@ -2565,16 +2625,32 @@ export declare class MarkdownFormat {
2565
2625
  * node) or a node fragment whose content category must fit the target's
2566
2626
  * slot (flow for flow targets, phrasing for phrasing targets and table
2567
2627
  * 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
2628
+ * the modified document re-parses cleanly by construction. List
2629
+ * items, table rows, frontmatter and the root refuse with
2570
2630
  * `UnsupportedTarget`, as does a multi-line replacement whose target sits
2571
2631
  * inside a container (a blockquote, list, table or heading) whose
2572
2632
  * continuation lines the splice cannot prefix.
2633
+ *
2634
+ * @param document - The parsed {@link MarkdownDocument} the target belongs to.
2635
+ * @param target - The node to replace; it must be a node of
2636
+ * `document.root`, matched by identity.
2637
+ * @param replacement - A plain string (literal text) or a node fragment of
2638
+ * the target's content category.
2639
+ * @returns An `Effect` that succeeds with the edit to apply (via
2640
+ * `MarkdownEdit.applyAll`), or fails with {@link MarkdownModificationError}.
2573
2641
  */
2574
2642
  static readonly modify: (document: MarkdownDocument, target: MarkdownNode, replacement: string | MarkdownNode) => Effect.Effect<readonly MarkdownEdit[], MarkdownModificationError, never>;
2575
2643
  /**
2576
2644
  * Modify `document` and apply the resulting edit in one step
2577
2645
  * (`MarkdownEdit.applyAll ∘ modify`).
2646
+ *
2647
+ * @param document - The parsed {@link MarkdownDocument} the target belongs to.
2648
+ * @param target - The node to replace; it must be a node of
2649
+ * `document.root`, matched by identity.
2650
+ * @param replacement - A plain string (literal text) or a node fragment of
2651
+ * the target's content category.
2652
+ * @returns An `Effect` that succeeds with the modified source, or fails with
2653
+ * {@link MarkdownModificationError}.
2578
2654
  */
2579
2655
  static readonly modifyToString: (document: MarkdownDocument, target: MarkdownNode, replacement: string | MarkdownNode) => Effect.Effect<string, MarkdownModificationError, never>;
2580
2656
  }
@@ -2730,7 +2806,24 @@ export declare const MarkdownVisitorEvent: {
2730
2806
  }, "_tag">;
2731
2807
  };
2732
2808
  /**
2733
- * SAX-style markdown tree visitor statics. Not instantiable.
2809
+ * Walks a parsed markdown tree as a lazy `Stream` of `Enter`/`Exit` events in
2810
+ * document pre-order. Not instantiable.
2811
+ *
2812
+ * @example
2813
+ * ```ts
2814
+ * import { Markdown, MarkdownVisitor, MarkdownVisitorEvent } from "@effected/markdown";
2815
+ * import { Effect, Stream } from "effect";
2816
+ *
2817
+ * const program = Effect.gen(function* () {
2818
+ * const root = yield* Markdown.parse("# Hi\n\ntext\n");
2819
+ * return yield* MarkdownVisitor.visit(root).pipe(
2820
+ * Stream.filter(MarkdownVisitorEvent.$is("Enter")),
2821
+ * Stream.map((event) => event.node.type),
2822
+ * Stream.runCollect,
2823
+ * );
2824
+ * // => ["root", "heading", "text", "paragraph", "text"]
2825
+ * });
2826
+ * ```
2734
2827
  *
2735
2828
  * @public
2736
2829
  */
@@ -2750,6 +2843,10 @@ export declare class MarkdownVisitor {
2750
2843
  * as a terminal `MarkdownVisitorEvent` `Error` event carrying a
2751
2844
  * `NestingDepthExceeded` {@link MarkdownDiagnostic}, mirroring the typed
2752
2845
  * failure `Markdown.stringify` produces for the same tree.
2846
+ *
2847
+ * @param root - The tree to walk.
2848
+ * @returns A lazy `Stream` of `MarkdownVisitorEvent`, infallible at the type
2849
+ * level.
2753
2850
  */
2754
2851
  static visit(root: Root): Stream.Stream<MarkdownVisitorEvent>;
2755
2852
  }
@@ -2780,9 +2877,8 @@ declare const MdastDecodeError_base: Schema.Class<MdastDecodeError, Schema.Tagge
2780
2877
  * @remarks
2781
2878
  * `issue` carries the **structured** schema failure — at runtime a
2782
2879
  * `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.
2880
+ * never a stringified rendering. It is typed `unknown` because core exposes
2881
+ * no `Schema` for `Issue`; narrow it with the `SchemaIssue` module.
2786
2882
  *
2787
2883
  * @public
2788
2884
  */
@@ -2790,7 +2886,41 @@ export declare class MdastDecodeError extends MdastDecodeError_base {
2790
2886
  get message(): string;
2791
2887
  }
2792
2888
  /**
2793
- * The mdast projection facade — the remark-ecosystem interop boundary.
2889
+ * Projects parsed markdown trees to plain mdast JSON and admits foreign mdast
2890
+ * back into this package's node classes, for interop with the remark
2891
+ * ecosystem.
2892
+ *
2893
+ * @remarks
2894
+ * `Mdast.toMdast` projects a parsed {@link Root} to plain spec-valid mdast
2895
+ * objects — fidelity extras stripped, optional fields spelled the way
2896
+ * `mdast-util-from-markdown@2.0.3` spells them (explicit `null`/`false` where
2897
+ * the reference utility emits them), so the output deep-equals what the
2898
+ * remark ecosystem produces and consumes: `list.ordered`/`start`/`spread` and
2899
+ * `listItem.spread`/`checked` are always explicit (`null` for unknown
2900
+ * `start`/`checked`), `code.lang`/`meta` and every `title` are explicit
2901
+ * `null` when absent, and `image.alt`/`imageReference.alt` are always
2902
+ * strings. Under unist's null-equals-absent convention for optional fields
2903
+ * this stays spec-valid mdast. GFM shapes follow the same convention from
2904
+ * `mdast-util-gfm`; the frontmatter capture projects to mdast's `yaml`/`toml`
2905
+ * literal nodes, and a `json` capture projects to a `json`-typed literal node
2906
+ * per the `mdast-util-frontmatter` custom-preset convention (presets name the
2907
+ * node type after the language).
2908
+ *
2909
+ * `Mdast.fromMdast` admits foreign plain mdast back into the package's Schema
2910
+ * node classes, synthesizing zero-width sentinel positions where unist leaves
2911
+ * them optional.
2912
+ *
2913
+ * @example
2914
+ * ```ts
2915
+ * import { Markdown, Mdast } from "@effected/markdown";
2916
+ * import { Effect } from "effect";
2917
+ *
2918
+ * const program = Effect.gen(function* () {
2919
+ * const root = yield* Markdown.parse("# Hello\n");
2920
+ * const plain = Mdast.toMdast(root); // plain mdast JSON
2921
+ * return yield* Mdast.fromMdast(plain); // back to node classes
2922
+ * });
2923
+ * ```
2794
2924
  *
2795
2925
  * @public
2796
2926
  */
@@ -2872,8 +3002,8 @@ export declare class Mdast {
2872
3002
  *
2873
3003
  * Encodes with `Toml.stringify`; a value toml cannot represent fails as a
2874
3004
  * {@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 —
3005
+ * structurally. An empty object encodes to the **empty body** — the mirror
3006
+ * of an empty capture decoding to `{}` — rendering as adjacent `+++` fences,
2877
3007
  * so `set`-then-decode recovers `{}` exactly.
2878
3008
  *
2879
3009
  * `@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.13.0",
3
+ "version": "0.15.0",
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.13.0",
43
- "@effected/toml": "^0.9.0",
44
- "@effected/yaml": "^0.17.0",
45
- "effect": "4.0.0-rc.117"
42
+ "@effected/jsonc": "^0.15.0",
43
+ "@effected/toml": "^0.11.0",
44
+ "@effected/yaml": "^0.19.0",
45
+ "effect": "^4.0.0"
46
46
  },
47
47
  "peerDependenciesMeta": {
48
48
  "@effected/jsonc": {