@effected/markdown 0.6.2 → 0.7.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/index.d.ts CHANGED
@@ -353,11 +353,14 @@ declare class FootnoteReference extends FootnoteReference_base {}
353
353
  */
354
354
  declare const PhrasingContent: Schema.Codec<PhrasingContent>;
355
355
  /**
356
- * The union of all phrasing-content node types.
356
+ * The union of all phrasing-content node types. Widened for MDX with
357
+ * {@link MdxJsxTextElement} and {@link MdxTextExpression}, per
358
+ * mdast-util-mdx's `PhrasingContentMap` registrations — the parser never
359
+ * produces either; they serve constructed trees.
357
360
  *
358
361
  * @public
359
362
  */
360
- type PhrasingContent = Break | Delete | Emphasis | FootnoteReference | Html | Image | ImageReference | InlineCode | Link | LinkReference | Strong | Text;
363
+ type PhrasingContent = Break | Delete | Emphasis | FootnoteReference | Html | Image | ImageReference | InlineCode | Link | LinkReference | MdxJsxTextElement | MdxTextExpression | Strong | Text;
361
364
  declare const ThematicBreak_base: Schema.Class<ThematicBreak, Schema.Struct<{
362
365
  readonly type: Schema.tag<"thematicBreak">;
363
366
  readonly position: Schema.withConstructorDefault<typeof Position>;
@@ -618,11 +621,14 @@ declare const FlowContent: Schema.Codec<FlowContent>;
618
621
  /**
619
622
  * The union of all flow-content node types. Includes mdast's `Content`
620
623
  * category (`Definition | Paragraph`) inline, as the spec's `FlowContent`
621
- * definition does, and the GFM extras `FootnoteDefinition` and `Table`.
624
+ * definition does, the GFM extras `FootnoteDefinition` and `Table`, and the
625
+ * MDX extras {@link MdxJsxFlowElement} and {@link MdxFlowExpression} per
626
+ * mdast-util-mdx's `BlockContentMap` registrations — the parser never
627
+ * produces the MDX members; they serve constructed trees.
622
628
  *
623
629
  * @public
624
630
  */
625
- type FlowContent = Blockquote | Code | Definition | FootnoteDefinition | Heading | Html | List | Paragraph | Table | ThematicBreak;
631
+ type FlowContent = Blockquote | Code | Definition | FootnoteDefinition | Heading | Html | List | MdxFlowExpression | MdxJsxFlowElement | Paragraph | Table | ThematicBreak;
626
632
  /**
627
633
  * The union of every node that may appear where mdast expects **list**
628
634
  * content. A one-member union, kept because mdast names the category and
@@ -638,6 +644,155 @@ declare const ListContent: Schema.Codec<ListContent>;
638
644
  * @public
639
645
  */
640
646
  type ListContent = ListItem;
647
+ declare const MdxJsxAttributeValueExpression_base: Schema.Class<MdxJsxAttributeValueExpression, Schema.Struct<{
648
+ readonly type: Schema.tag<"mdxJsxAttributeValueExpression">;
649
+ readonly value: Schema.String;
650
+ readonly position: Schema.withConstructorDefault<typeof Position>;
651
+ }>, {}>;
652
+ /**
653
+ * MdxJsxAttributeValueExpression — a JSX attribute value written as an
654
+ * expression (`<a b={c} />`); `value` holds the expression source text
655
+ * between the braces, never evaluated or parsed.
656
+ *
657
+ * The primary construction path is a JSON-encoded prop:
658
+ * `MdxJsxAttributeValueExpression.make({ value: JSON.stringify(props) })`.
659
+ *
660
+ * @public
661
+ */
662
+ declare class MdxJsxAttributeValueExpression extends MdxJsxAttributeValueExpression_base {}
663
+ declare const MdxJsxAttribute_base: Schema.Class<MdxJsxAttribute, Schema.Struct<{
664
+ readonly type: Schema.tag<"mdxJsxAttribute">;
665
+ readonly name: Schema.String;
666
+ readonly value: Schema.optionalKey<Schema.NullOr<Schema.Union<readonly [typeof MdxJsxAttributeValueExpression, Schema.String]>>>;
667
+ readonly position: Schema.withConstructorDefault<typeof Position>;
668
+ }>, {}>;
669
+ /**
670
+ * MdxJsxAttribute — a named JSX attribute (`<a b="c" />`). `value` is a
671
+ * string literal, a {@link MdxJsxAttributeValueExpression}, or — for a
672
+ * boolean attribute (`<a b />`) — absent or `null`, both of which the
673
+ * mdast-util-mdx-jsx contract spells (its parser writes `null`; absence is
674
+ * the constructed-tree spelling). The serializer treats the two identically.
675
+ *
676
+ * `name` must be non-empty — an attribute without a name has no MDX spelling,
677
+ * so the schema refuses it at construction and decode (the oracle's
678
+ * serialize-time crash, moved to the admission boundary).
679
+ *
680
+ * @public
681
+ */
682
+ declare class MdxJsxAttribute extends MdxJsxAttribute_base {}
683
+ declare const MdxJsxExpressionAttribute_base: Schema.Class<MdxJsxExpressionAttribute, Schema.Struct<{
684
+ readonly type: Schema.tag<"mdxJsxExpressionAttribute">;
685
+ readonly value: Schema.String;
686
+ readonly position: Schema.withConstructorDefault<typeof Position>;
687
+ }>, {}>;
688
+ /**
689
+ * MdxJsxExpressionAttribute — a JSX attribute written whole as an expression
690
+ * (`<a {...b} />`); `value` holds the expression source text between the
691
+ * braces.
692
+ *
693
+ * @public
694
+ */
695
+ declare class MdxJsxExpressionAttribute extends MdxJsxExpressionAttribute_base {}
696
+ /**
697
+ * The union of every node that may appear in a JSX element's `attributes`
698
+ * array. A real `Schema.Union` for the construction pass-through documented
699
+ * on `RowContent`.
700
+ *
701
+ * Attribute carriers are node-shaped values — they carry `type` and
702
+ * `position` per the mdast-util-mdx-jsx contract — but they are **not tree
703
+ * content**: they never appear in a `children` array, so they are excluded
704
+ * from `MarkdownNode` and invisible to the visitor and to
705
+ * `MarkdownDocument.find`.
706
+ *
707
+ * @public
708
+ */
709
+ declare const MdxJsxAttributeContent: Schema.Codec<MdxJsxAttributeContent>;
710
+ /**
711
+ * The union of all JSX attribute node types.
712
+ *
713
+ * @public
714
+ */
715
+ type MdxJsxAttributeContent = MdxJsxAttribute | MdxJsxExpressionAttribute;
716
+ declare const MdxJsxFlowElement_base: Schema.Class<MdxJsxFlowElement, Schema.Struct<{
717
+ readonly type: Schema.tag<"mdxJsxFlowElement">;
718
+ readonly name: Schema.NullOr<Schema.String>;
719
+ readonly attributes: Schema.$Array<Schema.Codec<MdxJsxAttributeContent, MdxJsxAttributeContent, never, never>>;
720
+ readonly children: Schema.$Array<Schema.suspend<Schema.Codec<FlowContent, FlowContent, never, never>>>;
721
+ readonly position: Schema.withConstructorDefault<typeof Position>;
722
+ }>, {}>;
723
+ /**
724
+ * MdxJsxFlowElement — a JSX element in flow (block) position (`<Component />`
725
+ * on its own lines). `name` is `null` for a fragment (`<></>`); children are
726
+ * flow content, per the oracle's `BlockContent | DefinitionContent` model.
727
+ *
728
+ * A fragment cannot carry attributes — that shape has no MDX spelling — so
729
+ * the schema refuses it at construction and decode. A **named** element's
730
+ * name must be non-empty on the same terms: `""` has no MDX spelling either
731
+ * (the oracle's parser only ever produces a real name or `null`, and its
732
+ * serializer treats a falsy name as the fragment), so `null` is the one
733
+ * fragment spelling and the empty string fails typed.
734
+ *
735
+ * @public
736
+ */
737
+ declare class MdxJsxFlowElement extends MdxJsxFlowElement_base {}
738
+ declare const MdxJsxTextElement_base: Schema.Class<MdxJsxTextElement, Schema.Struct<{
739
+ readonly type: Schema.tag<"mdxJsxTextElement">;
740
+ readonly name: Schema.NullOr<Schema.String>;
741
+ readonly attributes: Schema.$Array<Schema.Codec<MdxJsxAttributeContent, MdxJsxAttributeContent, never, never>>;
742
+ readonly children: Schema.$Array<Schema.suspend<Schema.Codec<PhrasingContent, PhrasingContent, never, never>>>;
743
+ readonly position: Schema.withConstructorDefault<typeof Position>;
744
+ }>, {}>;
745
+ /**
746
+ * MdxJsxTextElement — a JSX element in text (phrasing) position
747
+ * (`a <b>c</b> d`). `name` is `null` for a fragment; children are phrasing
748
+ * content. Refuses attributes on a fragment and an empty-string name, on the
749
+ * same terms as {@link MdxJsxFlowElement}.
750
+ *
751
+ * @public
752
+ */
753
+ declare class MdxJsxTextElement extends MdxJsxTextElement_base {}
754
+ declare const MdxFlowExpression_base: Schema.Class<MdxFlowExpression, Schema.Struct<{
755
+ readonly type: Schema.tag<"mdxFlowExpression">;
756
+ readonly value: Schema.String;
757
+ readonly position: Schema.withConstructorDefault<typeof Position>;
758
+ }>, {}>;
759
+ /**
760
+ * MdxFlowExpression — an expression in flow (block) position (`{a + b}` on
761
+ * its own lines); `value` holds the expression source text between the
762
+ * braces.
763
+ *
764
+ * @public
765
+ */
766
+ declare class MdxFlowExpression extends MdxFlowExpression_base {}
767
+ declare const MdxTextExpression_base: Schema.Class<MdxTextExpression, Schema.Struct<{
768
+ readonly type: Schema.tag<"mdxTextExpression">;
769
+ readonly value: Schema.String;
770
+ readonly position: Schema.withConstructorDefault<typeof Position>;
771
+ }>, {}>;
772
+ /**
773
+ * MdxTextExpression — an expression in text (phrasing) position
774
+ * (`a {b} c`); `value` holds the expression source text between the braces.
775
+ *
776
+ * @public
777
+ */
778
+ declare class MdxTextExpression extends MdxTextExpression_base {}
779
+ declare const MdxjsEsm_base: Schema.Class<MdxjsEsm, Schema.Struct<{
780
+ readonly type: Schema.tag<"mdxjsEsm">;
781
+ readonly value: Schema.String;
782
+ readonly position: Schema.withConstructorDefault<typeof Position>;
783
+ }>, {}>;
784
+ /**
785
+ * MdxjsEsm — an MDX ESM block (`import`/`export` statements); `value` holds
786
+ * the statement source verbatim.
787
+ *
788
+ * Only ever a child of {@link Root}, per the mdast-util-mdxjs-esm content
789
+ * registration — ESM cannot nest inside a JSX element or any other
790
+ * container. As with the frontmatter head node, the constraint is structural
791
+ * (the `Root` children union admits it, no other union does), not validated.
792
+ *
793
+ * @public
794
+ */
795
+ declare class MdxjsEsm extends MdxjsEsm_base {}
641
796
  /**
642
797
  * The frontmatter formats the capture recognizes, keyed by their opening
643
798
  * fence: `---` is yaml, `+++` is toml and `---json` is json.
@@ -694,7 +849,7 @@ declare const FrontmatterContent: Schema.Codec<FrontmatterContent>;
694
849
  type FrontmatterContent = Frontmatter;
695
850
  declare const Root_base: Schema.Class<Root, Schema.Struct<{
696
851
  readonly type: Schema.tag<"root">;
697
- readonly children: Schema.$Array<Schema.suspend<Schema.Codec<Frontmatter | FlowContent, Frontmatter | FlowContent, never, never>>>;
852
+ readonly children: Schema.$Array<Schema.suspend<Schema.Codec<Frontmatter | MdxjsEsm | FlowContent, Frontmatter | MdxjsEsm | FlowContent, never, never>>>;
698
853
  readonly position: Schema.withConstructorDefault<typeof Position>;
699
854
  }>, {}>;
700
855
  /**
@@ -703,7 +858,8 @@ declare const Root_base: Schema.Class<Root, Schema.Struct<{
703
858
  * mdast leaves a root's content model open; a parsed markdown document is
704
859
  * flow content, optionally headed by one {@link Frontmatter} node — mdast's
705
860
  * `FlowContentFrontmatter` merge, which admits frontmatter at the root and
706
- * nowhere else.
861
+ * nowhere else. {@link MdxjsEsm} is likewise admitted at the root and nowhere
862
+ * else, per mdast-util-mdxjs-esm's `RootContentMap` registration.
707
863
  *
708
864
  * @public
709
865
  */
@@ -714,7 +870,7 @@ declare class Root extends Root_base {}
714
870
  *
715
871
  * @public
716
872
  */
717
- type MarkdownNode = Root | FrontmatterContent | FlowContent | ListContent | PhrasingContent | RowContent | TableContent;
873
+ type MarkdownNode = Root | FrontmatterContent | FlowContent | ListContent | MdxjsEsm | PhrasingContent | RowContent | TableContent;
718
874
  /**
719
875
  * A schema matching any node in the tree.
720
876
  *
@@ -929,6 +1085,67 @@ declare class Markdown {
929
1085
  * fails with {@link MarkdownParseError}.
930
1086
  */
931
1087
  static readonly parse: (text: string, options?: MarkdownParseOptions | undefined) => Effect.Effect<Root, MarkdownParseError, never>;
1088
+ /**
1089
+ * Parse a text fragment as a single paragraph's inline content,
1090
+ * synchronously, as a `Result` — the phrasing-level twin of
1091
+ * {@link Markdown.parseResult}, for callers holding already-markdown
1092
+ * prose (a link-carrying sentence, a backtick span) who want its
1093
+ * phrasing nodes without a full document parse and a paragraph splice.
1094
+ *
1095
+ * @remarks
1096
+ * The whole input is prepared exactly the way the block pass prepares one
1097
+ * paragraph's content — leading and trailing whitespace trimmed, line
1098
+ * terminators normalized to `\n` — and handed to the inline pass, so the
1099
+ * result matches the `children` of the paragraph a full parse of the same
1100
+ * fragment would produce. Node positions are correct relative to the
1101
+ * input string.
1102
+ *
1103
+ * Two consequences of the single-paragraph contract:
1104
+ *
1105
+ * - **Blank lines do not break blocks.** A `\n\n` in the input stays
1106
+ * inline content (literal newlines in a text node); nothing at this
1107
+ * level opens a heading, list or code block — `# foo` is the text
1108
+ * `# foo`.
1109
+ * - **No reference context exists.** The fragment carries no definitions,
1110
+ * so `[foo]`, `![foo]` and `[^foo]` remain literal text — the same
1111
+ * result a full parse of the isolated fragment produces.
1112
+ *
1113
+ * `options.dialect` selects the inline dialect (default `"gfm"`);
1114
+ * `options.frontmatter` has no effect at phrasing level. Failure is rare
1115
+ * by design, on {@link Markdown.parseResult}'s terms: only a
1116
+ * hardening-guard trip (delimiter or bracket nesting past the cap) fails.
1117
+ *
1118
+ * @example
1119
+ * ```ts
1120
+ * import { Markdown } from "@effected/markdown";
1121
+ * import { Result } from "effect";
1122
+ *
1123
+ * const ok = Markdown.parsePhrasingResult("see [the docs](./docs.md)");
1124
+ * if (Result.isSuccess(ok)) {
1125
+ * console.log(ok.success.map((node) => node.type)); // => ["text", "link"]
1126
+ * }
1127
+ * ```
1128
+ *
1129
+ * @param text - The prose fragment to parse.
1130
+ * @param options - Optional {@link MarkdownParseOptions}; the dialect
1131
+ * defaults to `"gfm"`.
1132
+ * @returns A `Result` succeeding with the fragment's phrasing content, or
1133
+ * failing with {@link MarkdownParseError}.
1134
+ */
1135
+ static parsePhrasingResult(text: string, options?: MarkdownParseOptions): Result.Result<ReadonlyArray<PhrasingContent>, MarkdownParseError>;
1136
+ /**
1137
+ * Parse a text fragment as a single paragraph's inline content. Defined
1138
+ * in terms of {@link Markdown.parsePhrasingResult} — synchronous callers
1139
+ * can use that variant directly; the single-paragraph contract (blank
1140
+ * lines stay inline, references never form) is documented there.
1141
+ *
1142
+ * @param text - The prose fragment to parse.
1143
+ * @param options - Optional {@link MarkdownParseOptions}; the dialect
1144
+ * defaults to `"gfm"`.
1145
+ * @returns An `Effect` that succeeds with the fragment's phrasing
1146
+ * content, or fails with {@link MarkdownParseError}.
1147
+ */
1148
+ static readonly parsePhrasing: (text: string, options?: MarkdownParseOptions | undefined) => Effect.Effect<readonly PhrasingContent[], MarkdownParseError, never>;
932
1149
  /**
933
1150
  * Serialize a {@link Root} tree to canonical markdown, synchronously, as a
934
1151
  * `Result`. The pure primitive twin of {@link Markdown.stringify}, on the
@@ -946,6 +1163,66 @@ declare class Markdown {
946
1163
  * contract — but is not a byte-level round-trip of any original source;
947
1164
  * surgical editing goes through the offset-splice edit layer instead.
948
1165
  *
1166
+ * **The canonical form is a stability commitment, so a test may assert
1167
+ * against these bytes.** There are no options to configure and therefore
1168
+ * nothing to drift, the choices below are pinned by byte-level tests, and
1169
+ * the engine is cross-checked against commonmark.js over the full
1170
+ * CommonMark 0.31.2 corpus. Changing any of them is a **breaking** change
1171
+ * to this package, never a patch. A consumer asserting on rendered
1172
+ * markdown should serialize through here rather than through a third-party
1173
+ * stringifier whose defaults are free to move.
1174
+ *
1175
+ * The canonical choices a byte-level assertion depends on, for a node
1176
+ * carrying no fidelity field:
1177
+ *
1178
+ * | Construct | Canonical form |
1179
+ * | --- | --- |
1180
+ * | Heading | ATX (`## x`), at every depth |
1181
+ * | Thematic break | `***` |
1182
+ * | Bullet list marker | `-` |
1183
+ * | Ordered list delimiter | `.`, flipping to `)` to separate an immediately adjacent sibling list |
1184
+ * | Emphasis / strong | `*` / `**` |
1185
+ * | Code block | fenced when the node carries a `lang` or a `fenceChar`, with the fence grown past any interior backtick run; otherwise indented — **except where indenting would not re-parse as a code block**, which forces a fence (see below) |
1186
+ * | Block separation | exactly one blank line |
1187
+ * | Document | a single trailing newline |
1188
+ *
1189
+ * A node carrying a fidelity field overrides the corresponding row —
1190
+ * `headingStyle: "setext"`, `markerChar`, `fenceChar`, `delimiter` — which
1191
+ * is how a parsed document re-serializes in its author's spelling. The
1192
+ * rows describe a synthesized node, which is the case a test asserting on
1193
+ * generated markdown actually has.
1194
+ *
1195
+ * **Representability wins over the table.** The canonical form never emits
1196
+ * text that would re-parse as something else, so a row yields where the two
1197
+ * conflict. The case that reaches a consumer is the indented code block: an
1198
+ * indented block directly after a list is absorbed as list content, so a
1199
+ * `Code` node with neither `lang` nor `fenceChar` emits **fenced** in that
1200
+ * position and indented everywhere else. A byte-level assertion over
1201
+ * synthesized code blocks therefore depends on the preceding sibling.
1202
+ *
1203
+ * **The posture that makes this a non-issue**, and the one a consumer
1204
+ * building trees for byte-level assertion should adopt: set `fenceChar` on
1205
+ * every `Code` node that carries neither a `lang` nor one already, in a
1206
+ * post-decode walk that reaches nested nodes. The choice then leaves the
1207
+ * emitter entirely and no output depends on a node's neighbours.
1208
+ *
1209
+ * **MDX nodes (constructed trees only — the parser reads no MDX syntax)**
1210
+ * serialize to valid MDX with the mdast-util-mdx defaults, and these
1211
+ * choices are part of the same stability commitment: attribute values
1212
+ * quote with `"` (the quote escaped as `&#x22;`), an empty element
1213
+ * self-closes spaced (`<a />`), a fragment is `<></>`, flow children take
1214
+ * block layout indented two spaces per JSX ancestor, attributes move onto
1215
+ * their own lines only when one carries a line ending, expressions emit
1216
+ * `{expr}` with two-space continuation indent, and ESM values emit
1217
+ * verbatim. A tree containing any MDX node additionally escapes `{` in
1218
+ * text — MDX makes it significant — while a tree with none serializes
1219
+ * byte-identically to the table above.
1220
+ *
1221
+ * To *normalize* a document to different choices — a `-` list rewritten to
1222
+ * `*`, setext headings rewritten to ATX — use `MarkdownFormat` with
1223
+ * `MarkdownFormattingOptions`. That is the configurable surface; this one
1224
+ * deliberately is not.
1225
+ *
949
1226
  * Failure is rare by design, symmetric with parse: only a hardening-guard
950
1227
  * trip on a tree nesting past the depth cap fails, and only synthesized
951
1228
  * or decoded trees can nest that far.
@@ -960,6 +1237,18 @@ declare class Markdown {
960
1237
  * {@link Markdown.stringifyResult} — synchronous callers can use that
961
1238
  * variant directly.
962
1239
  *
1240
+ * @remarks
1241
+ * The canonical form is a **stability commitment** and safe to assert
1242
+ * against byte-for-byte; the choices it makes, and the reason changing one
1243
+ * is a breaking change, are documented on
1244
+ * {@link Markdown.stringifyResult}.
1245
+ *
1246
+ * **Fidelity fields must be set on the DECODED tree.** `Mdast.fromMdast`
1247
+ * admits spec mdast and strips everything outside it, so a `fenceChar`,
1248
+ * `headingStyle`, `markerChar` or `delimiter` placed on a plain mdast tree
1249
+ * before admission is silently dropped and the canonical default applies.
1250
+ * Set them on the nodes that come back.
1251
+ *
963
1252
  * @param root - The document tree to serialize.
964
1253
  * @returns An `Effect` that succeeds with markdown source, or fails with
965
1254
  * {@link MarkdownStringifyError}.
@@ -1903,6 +2192,144 @@ declare class SchemaResolver {
1903
2192
  static fromRegistry(registrations: Readonly<Record<string, Schema.Top>>): FrontmatterSchemaResolver;
1904
2193
  }
1905
2194
  //#endregion
2195
+ //#region src/FrontmatterSource.d.ts
2196
+ /**
2197
+ * The three line-terminator spellings a fence line may end with. Recorded by
2198
+ * {@link FrontmatterSource.split} as fidelity — what the source's fence
2199
+ * lines actually used — and consumed by {@link FrontmatterSource.join}.
2200
+ *
2201
+ * @public
2202
+ */
2203
+ declare const FrontmatterNewline: Schema.Literals<readonly ["\n", "\r\n", "\r"]>;
2204
+ /**
2205
+ * The union of all fence line-terminator literals.
2206
+ *
2207
+ * @public
2208
+ */
2209
+ type FrontmatterNewline = typeof FrontmatterNewline.Type;
2210
+ declare const FrontmatterSourceBlock_base: Schema.Class<FrontmatterSourceBlock, Schema.Struct<{
2211
+ readonly format: Schema.Literals<readonly ["yaml", "toml", "json"]>;
2212
+ readonly value: Schema.String;
2213
+ readonly newline: Schema.optionalKey<Schema.Literals<readonly ["\n", "\r\n", "\r"]>>;
2214
+ }>, {}>;
2215
+ /**
2216
+ * A frontmatter block at string level: the format its fence declared, the
2217
+ * exact bytes between the fence lines, and the fence lines' terminator.
2218
+ *
2219
+ * @remarks
2220
+ * `value` is the exact source slice strictly between the opening fence line
2221
+ * (its terminator included) and the closing fence line: every value line
2222
+ * WITH its own terminator, verbatim — so a block whose value is one blank
2223
+ * line (`"\n"`) stays distinct from a block with no value lines (`""`), and
2224
+ * interior CRLF terminators survive untouched. This deliberately differs
2225
+ * from the parsed `Frontmatter` NODE's `value`, which drops the final
2226
+ * terminator; the node feeds codecs, this feeds byte-exact reconstruction.
2227
+ * {@link FrontmatterSource.join} accepts a value without a trailing
2228
+ * terminator too, appending `newline` so a hand-built block cannot fuse its
2229
+ * last line into the closing fence.
2230
+ *
2231
+ * `newline` is a fidelity field: `split` always records the terminator
2232
+ * following the opening fence; when absent (a hand-built block), `join`
2233
+ * uses `"\n"`.
2234
+ *
2235
+ * @public
2236
+ */
2237
+ declare class FrontmatterSourceBlock extends FrontmatterSourceBlock_base {}
2238
+ declare const FrontmatterSourceSplit_base: Schema.Class<FrontmatterSourceSplit, Schema.Struct<{
2239
+ readonly frontmatter: Schema.optionalKey<typeof FrontmatterSourceBlock>;
2240
+ readonly body: Schema.String;
2241
+ readonly bodyOffset: Schema.withConstructorDefault<Schema.Number>;
2242
+ }>, {}>;
2243
+ /**
2244
+ * The result of {@link FrontmatterSource.split}: the frontmatter block when
2245
+ * one exists, the exact body remainder, and the body's byte offset.
2246
+ *
2247
+ * @remarks
2248
+ * `body` is exactly `source.slice(bodyOffset)` — the string and the offset
2249
+ * can never disagree. With no block, `body` is the whole source and
2250
+ * `bodyOffset` is `0`. With a block, `bodyOffset` sits immediately after
2251
+ * the closing fence line and its line terminator (the terminator belongs to
2252
+ * the fence line, as every line's terminator belongs to its line), which is
2253
+ * gray-matter's body boundary; when the closing fence ends the document
2254
+ * there is no terminator and the body is empty.
2255
+ *
2256
+ * `bodyOffset` is informational output of `split`; `join` never reads it,
2257
+ * and it constructor-defaults to `0` so a hand-built value for `join` can
2258
+ * omit it.
2259
+ *
2260
+ * @public
2261
+ */
2262
+ declare class FrontmatterSourceSplit extends FrontmatterSourceSplit_base {}
2263
+ /**
2264
+ * The string-level frontmatter facade: split raw source at the fence
2265
+ * boundaries and join it back, without parsing anything.
2266
+ *
2267
+ * @public
2268
+ */
2269
+ declare class FrontmatterSource {
2270
+ private constructor();
2271
+ /**
2272
+ * Split raw source into its frontmatter block and body, at string level.
2273
+ * Total: never fails, never parses — absence of a block (including an
2274
+ * opening fence with no closing fence, which per the grammar is not
2275
+ * frontmatter) is a representable result, not an error.
2276
+ *
2277
+ * @remarks
2278
+ * The grammar is the parser's own offset-0 pre-scan, over raw bytes:
2279
+ * the fence set is closed (`---` yaml, `+++` toml, `---json` json), a
2280
+ * fence line is exactly the fence with no surrounding whitespace, and a
2281
+ * leading BOM means offset 0 is not a fence — strip one before calling
2282
+ * if the source may carry it. Unlike the parse path there is no capture
2283
+ * toggle here, so absence is one fact: the source has no closed block.
2284
+ * (`MarkdownDocument`'s two-reason absence exists because parsing can be
2285
+ * asked not to look; this surface always looks.)
2286
+ *
2287
+ * Byte exactness: `source.slice(0, result.bodyOffset) + result.body`
2288
+ * reassembles `source` exactly, and {@link FrontmatterSource.join}
2289
+ * reproduces the original bytes for an unmodified round-trip — see
2290
+ * `join` for the two normalizations at the contract's edge.
2291
+ *
2292
+ * @example
2293
+ * ```ts
2294
+ * import { FrontmatterSource } from "@effected/markdown";
2295
+ *
2296
+ * const split = FrontmatterSource.split("---\ntitle: hi\n---\n\n# Body\n");
2297
+ * console.log(split.frontmatter?.format); // => "yaml"
2298
+ * console.log(split.frontmatter?.value); // => "title: hi\n"
2299
+ * console.log(split.body); // => "\n# Body\n"
2300
+ * ```
2301
+ *
2302
+ * @param source - The raw document source, body format irrelevant.
2303
+ * @returns A {@link FrontmatterSourceSplit}; `frontmatter` is absent when
2304
+ * the source opens with no closed fence block.
2305
+ */
2306
+ static split(source: string): FrontmatterSourceSplit;
2307
+ /**
2308
+ * Serialize a split back to one source string. Total and pure.
2309
+ *
2310
+ * @remarks
2311
+ * With no block the body is returned verbatim. With a block the output
2312
+ * is the opening fence, `newline` (default `"\n"`), the value — a
2313
+ * non-empty value missing a final line terminator gets `newline`
2314
+ * appended, so the closing fence always starts its own line — the
2315
+ * closing fence, `newline`, then the body verbatim.
2316
+ *
2317
+ * **Round-trip contract:** `join(split(source)) === source`, byte for
2318
+ * byte, for every source whose closing fence line ends with a line
2319
+ * terminator matching the opening fence's. The two edges where join
2320
+ * normalizes instead: a closing fence at end-of-document gains a final
2321
+ * `newline` (the block's one unterminated spelling), and fence lines
2322
+ * with MISMATCHED terminators (say `---\r\n … ---\n`) re-emit both with
2323
+ * the opening one. Value and body bytes survive verbatim in every case.
2324
+ *
2325
+ * @param split - The parts to serialize, typically from
2326
+ * {@link FrontmatterSource.split} or built with
2327
+ * `FrontmatterSourceSplit.make`.
2328
+ * @returns The combined source string.
2329
+ */
2330
+ static join(split: FrontmatterSourceSplit): string;
2331
+ }
2332
+ //#endregion
1906
2333
  //#region src/JsonFrontmatter.d.ts
1907
2334
  /**
1908
2335
  * The json frontmatter codec, over `@effected/jsonc`.
@@ -2344,6 +2771,14 @@ declare class Mdast {
2344
2771
  * decode into the {@link Frontmatter} capture. Unknown node types fail
2345
2772
  * typed.
2346
2773
  *
2774
+ * **This package's fidelity fields are among the fields dropped**, because
2775
+ * they are not spec mdast. A `fenceChar`, `headingStyle`, `markerChar` or
2776
+ * `delimiter` set on the tree BEFORE admission is silently discarded, and
2777
+ * the node then serializes with the canonical default — set them on the
2778
+ * decoded nodes this returns instead. The drop is correct (the boundary
2779
+ * admits spec mdast and nothing else) but it is silent, which is why it is
2780
+ * called out here.
2781
+ *
2347
2782
  * @param input - A plain mdast tree, typically a `root`.
2348
2783
  * @returns A `Result` succeeding with the decoded {@link Root}, or
2349
2784
  * failing with {@link MdastDecodeError} carrying the structured issue.
@@ -2412,5 +2847,5 @@ declare const TomlFrontmatter: FrontmatterCodec;
2412
2847
  */
2413
2848
  declare const YamlFrontmatter: FrontmatterCodec;
2414
2849
  //#endregion
2415
- export { Blockquote, Break, BreakStyle, BulletChar, Code, CodeBlockStyle, Definition, Delete, type DocumentHeading, type DocumentLink, DocumentSection, Emphasis, EmphasisChar, FenceChar, FlowContent, FootnoteDefinition, FootnoteReference, Frontmatter, type FrontmatterCodec, FrontmatterContent, FrontmatterDecodeError, FrontmatterEncodeError, FrontmatterFormat, FrontmatterFormatMismatchError, FrontmatterMissingError, FrontmatterMissingReason, type FrontmatterResolveError, type FrontmatterSchemaError, type FrontmatterSchemaResolver, FrontmatterValidationError, type FrontmatterWriteError, Heading, HeadingDepth, HeadingStyle, Html, Image, ImageReference, InlineCode, JsonFrontmatter, Link, type LinkBearingNode, LinkReference, List, ListContent, ListDelimiter, ListItem, Markdown, MarkdownDiagnostic, MarkdownDialect, MarkdownDocument, MarkdownEdit, MarkdownFormat, MarkdownFormattingOptions, MarkdownFrontmatter, MarkdownModificationError, MarkdownModificationErrorCode, MarkdownNode, type MarkdownNodeOfType, type MarkdownNodeType, MarkdownParseError, MarkdownParseErrorCode, MarkdownParseOptions, type MarkdownPath, MarkdownRange, type MarkdownRangeLike, type MarkdownSegment, MarkdownStringifyError, MarkdownVisitor, MarkdownVisitorEvent, Mdast, MdastDecodeError, type MdastNode, Paragraph, PhrasingContent, Point, Position, ReferenceType, Root, RowContent, SchemaDeclaration, SchemaDeclarationByName, SchemaDeclarationByPath, SchemaDeclarationByUrl, SchemaDeclarationInline, SchemaDeclarationInvalidError, SchemaDeclarationMissingError, SchemaNameUnknownError, SchemaResolver, SchemaVersionUnresolvableError, type SectionHeadingMatch, type SectionQueryOptions, Strong, Table, TableAlign, TableCell, TableContent, TableRow, Text, ThematicBreak, ThematicBreakChar, TomlFrontmatter, YamlFrontmatter };
2850
+ export { Blockquote, Break, BreakStyle, BulletChar, Code, CodeBlockStyle, Definition, Delete, type DocumentHeading, type DocumentLink, DocumentSection, Emphasis, EmphasisChar, FenceChar, FlowContent, FootnoteDefinition, FootnoteReference, Frontmatter, type FrontmatterCodec, FrontmatterContent, FrontmatterDecodeError, FrontmatterEncodeError, FrontmatterFormat, FrontmatterFormatMismatchError, FrontmatterMissingError, FrontmatterMissingReason, FrontmatterNewline, type FrontmatterResolveError, type FrontmatterSchemaError, type FrontmatterSchemaResolver, FrontmatterSource, FrontmatterSourceBlock, FrontmatterSourceSplit, FrontmatterValidationError, type FrontmatterWriteError, Heading, HeadingDepth, HeadingStyle, Html, Image, ImageReference, InlineCode, JsonFrontmatter, Link, type LinkBearingNode, LinkReference, List, ListContent, ListDelimiter, ListItem, Markdown, MarkdownDiagnostic, MarkdownDialect, MarkdownDocument, MarkdownEdit, MarkdownFormat, MarkdownFormattingOptions, MarkdownFrontmatter, MarkdownModificationError, MarkdownModificationErrorCode, MarkdownNode, type MarkdownNodeOfType, type MarkdownNodeType, MarkdownParseError, MarkdownParseErrorCode, MarkdownParseOptions, type MarkdownPath, MarkdownRange, type MarkdownRangeLike, type MarkdownSegment, MarkdownStringifyError, MarkdownVisitor, MarkdownVisitorEvent, Mdast, MdastDecodeError, type MdastNode, MdxFlowExpression, MdxJsxAttribute, MdxJsxAttributeContent, MdxJsxAttributeValueExpression, MdxJsxExpressionAttribute, MdxJsxFlowElement, MdxJsxTextElement, MdxTextExpression, MdxjsEsm, Paragraph, PhrasingContent, Point, Position, ReferenceType, Root, RowContent, SchemaDeclaration, SchemaDeclarationByName, SchemaDeclarationByPath, SchemaDeclarationByUrl, SchemaDeclarationInline, SchemaDeclarationInvalidError, SchemaDeclarationMissingError, SchemaNameUnknownError, SchemaResolver, SchemaVersionUnresolvableError, type SectionHeadingMatch, type SectionQueryOptions, Strong, Table, TableAlign, TableCell, TableContent, TableRow, Text, ThematicBreak, ThematicBreakChar, TomlFrontmatter, YamlFrontmatter };
2416
2851
  //# sourceMappingURL=index.d.ts.map
package/index.js CHANGED
@@ -1,7 +1,8 @@
1
1
  import { MarkdownEdit, MarkdownRange } from "./MarkdownEdit.js";
2
- import { Blockquote, Break, BreakStyle, BulletChar, Code, Definition, Delete, Emphasis, EmphasisChar, FenceChar, FlowContent, FootnoteDefinition, FootnoteReference, Frontmatter, FrontmatterContent, FrontmatterFormat, Heading, HeadingDepth, HeadingStyle, Html, Image, ImageReference, InlineCode, Link, LinkReference, List, ListContent, ListDelimiter, ListItem, MarkdownNode, Paragraph, PhrasingContent, Point, Position, ReferenceType, Root, RowContent, Strong, Table, TableAlign, TableCell, TableContent, TableRow, Text, ThematicBreak, ThematicBreakChar } from "./MarkdownNode.js";
2
+ import { Blockquote, Break, BreakStyle, BulletChar, Code, Definition, Delete, Emphasis, EmphasisChar, FenceChar, FlowContent, FootnoteDefinition, FootnoteReference, Frontmatter, FrontmatterContent, FrontmatterFormat, Heading, HeadingDepth, HeadingStyle, Html, Image, ImageReference, InlineCode, Link, LinkReference, List, ListContent, ListDelimiter, ListItem, MarkdownNode, MdxFlowExpression, MdxJsxAttribute, MdxJsxAttributeContent, MdxJsxAttributeValueExpression, MdxJsxExpressionAttribute, MdxJsxFlowElement, MdxJsxTextElement, MdxTextExpression, MdxjsEsm, Paragraph, PhrasingContent, Point, Position, ReferenceType, Root, RowContent, Strong, Table, TableAlign, TableCell, TableContent, TableRow, Text, ThematicBreak, ThematicBreakChar } from "./MarkdownNode.js";
3
3
  import { FrontmatterDecodeError, FrontmatterEncodeError, FrontmatterFormatMismatchError, FrontmatterMissingError, FrontmatterMissingReason, FrontmatterValidationError, MarkdownFrontmatter } from "./Frontmatter.js";
4
4
  import { SchemaDeclaration, SchemaDeclarationByName, SchemaDeclarationByPath, SchemaDeclarationByUrl, SchemaDeclarationInline, SchemaDeclarationInvalidError, SchemaDeclarationMissingError, SchemaNameUnknownError, SchemaResolver, SchemaVersionUnresolvableError } from "./FrontmatterResolver.js";
5
+ import { FrontmatterNewline, FrontmatterSource, FrontmatterSourceBlock, FrontmatterSourceSplit } from "./FrontmatterSource.js";
5
6
  import { JsonFrontmatter } from "./JsonFrontmatter.js";
6
7
  import { MarkdownDiagnostic, MarkdownParseErrorCode } from "./MarkdownDiagnostic.js";
7
8
  import { Markdown, MarkdownDialect, MarkdownParseError, MarkdownParseOptions, MarkdownStringifyError } from "./Markdown.js";
@@ -12,4 +13,4 @@ import { Mdast, MdastDecodeError } from "./Mdast.js";
12
13
  import { TomlFrontmatter } from "./TomlFrontmatter.js";
13
14
  import { YamlFrontmatter } from "./YamlFrontmatter.js";
14
15
 
15
- export { Blockquote, Break, BreakStyle, BulletChar, Code, CodeBlockStyle, Definition, Delete, DocumentSection, Emphasis, EmphasisChar, FenceChar, FlowContent, FootnoteDefinition, FootnoteReference, Frontmatter, FrontmatterContent, FrontmatterDecodeError, FrontmatterEncodeError, FrontmatterFormat, FrontmatterFormatMismatchError, FrontmatterMissingError, FrontmatterMissingReason, FrontmatterValidationError, Heading, HeadingDepth, HeadingStyle, Html, Image, ImageReference, InlineCode, JsonFrontmatter, Link, LinkReference, List, ListContent, ListDelimiter, ListItem, Markdown, MarkdownDiagnostic, MarkdownDialect, MarkdownDocument, MarkdownEdit, MarkdownFormat, MarkdownFormattingOptions, MarkdownFrontmatter, MarkdownModificationError, MarkdownModificationErrorCode, MarkdownNode, MarkdownParseError, MarkdownParseErrorCode, MarkdownParseOptions, MarkdownRange, MarkdownStringifyError, MarkdownVisitor, MarkdownVisitorEvent, Mdast, MdastDecodeError, Paragraph, PhrasingContent, Point, Position, ReferenceType, Root, RowContent, SchemaDeclaration, SchemaDeclarationByName, SchemaDeclarationByPath, SchemaDeclarationByUrl, SchemaDeclarationInline, SchemaDeclarationInvalidError, SchemaDeclarationMissingError, SchemaNameUnknownError, SchemaResolver, SchemaVersionUnresolvableError, Strong, Table, TableAlign, TableCell, TableContent, TableRow, Text, ThematicBreak, ThematicBreakChar, TomlFrontmatter, YamlFrontmatter };
16
+ export { Blockquote, Break, BreakStyle, BulletChar, Code, CodeBlockStyle, Definition, Delete, DocumentSection, Emphasis, EmphasisChar, FenceChar, FlowContent, FootnoteDefinition, FootnoteReference, Frontmatter, FrontmatterContent, FrontmatterDecodeError, FrontmatterEncodeError, FrontmatterFormat, FrontmatterFormatMismatchError, FrontmatterMissingError, FrontmatterMissingReason, FrontmatterNewline, FrontmatterSource, FrontmatterSourceBlock, FrontmatterSourceSplit, FrontmatterValidationError, Heading, HeadingDepth, HeadingStyle, Html, Image, ImageReference, InlineCode, JsonFrontmatter, Link, LinkReference, List, ListContent, ListDelimiter, ListItem, Markdown, MarkdownDiagnostic, MarkdownDialect, MarkdownDocument, MarkdownEdit, MarkdownFormat, MarkdownFormattingOptions, MarkdownFrontmatter, MarkdownModificationError, MarkdownModificationErrorCode, MarkdownNode, MarkdownParseError, MarkdownParseErrorCode, MarkdownParseOptions, MarkdownRange, MarkdownStringifyError, MarkdownVisitor, MarkdownVisitorEvent, Mdast, MdastDecodeError, MdxFlowExpression, MdxJsxAttribute, MdxJsxAttributeContent, MdxJsxAttributeValueExpression, MdxJsxExpressionAttribute, MdxJsxFlowElement, MdxJsxTextElement, MdxTextExpression, MdxjsEsm, Paragraph, PhrasingContent, Point, Position, ReferenceType, Root, RowContent, SchemaDeclaration, SchemaDeclarationByName, SchemaDeclarationByPath, SchemaDeclarationByUrl, SchemaDeclarationInline, SchemaDeclarationInvalidError, SchemaDeclarationMissingError, SchemaNameUnknownError, SchemaResolver, SchemaVersionUnresolvableError, Strong, Table, TableAlign, TableCell, TableContent, TableRow, Text, ThematicBreak, ThematicBreakChar, TomlFrontmatter, YamlFrontmatter };
@@ -1,9 +1,9 @@
1
1
  import { Frontmatter, Point, Position, Root } from "../MarkdownNode.js";
2
+ import { scanFrontmatter } from "./blocks/frontmatter.js";
2
3
  import { makeBlockNode } from "./blockTypes.js";
3
4
  import { columnsToNextTabStop, preprocessLines } from "./preprocess.js";
4
5
  import { isHtmlBlockEnd } from "./blocks/htmlBlock.js";
5
6
  import { blockDialect } from "./blockRegistry.js";
6
- import { scanFrontmatter } from "./blocks/frontmatter.js";
7
7
  import { GuardExceeded } from "./carriers.js";
8
8
  import { LineIndex } from "./lineIndex.js";
9
9
  import { prepareInline } from "./rawInline.js";
@@ -51,6 +51,52 @@ const scanFrontmatter = (lines, text) => {
51
51
  }
52
52
  return null;
53
53
  };
54
+ /** The terminator starting at `offset`, or null when none does. */
55
+ const terminatorAt = (source, offset) => {
56
+ const code = source.charCodeAt(offset);
57
+ if (code === 10) return "\n";
58
+ if (code === 13) return source.charCodeAt(offset + 1) === 10 ? "\r\n" : "\r";
59
+ return null;
60
+ };
61
+ /**
62
+ * Scan raw source for a frontmatter block — the same closed fence grammar as
63
+ * {@link scanFrontmatter}, over unpreprocessed bytes, returning exact
64
+ * offsets. The two scans agree on every input about WHETHER a block exists
65
+ * (the preprocessor's substitutions are length-preserving and can only break
66
+ * a fence match both scans require); this one exists for the string-level
67
+ * split/join surface, whose contract is byte exactness.
68
+ *
69
+ * Returns `null` when the document has no frontmatter — no fence at offset
70
+ * 0, or an opening fence with no closing fence, which per the grammar is not
71
+ * frontmatter at all.
72
+ */
73
+ const scanRawFrontmatter = (source) => {
74
+ let index = 0;
75
+ while (index < source.length && terminatorAt(source, index) === null) index += 1;
76
+ const openNewline = index < source.length ? terminatorAt(source, index) : null;
77
+ const rule = FENCES.get(source.slice(0, index));
78
+ if (rule === void 0 || openNewline === null) return null;
79
+ const valueStart = index + openNewline.length;
80
+ let lineStart = valueStart;
81
+ index = valueStart;
82
+ while (index <= source.length) {
83
+ const terminator = index === source.length ? null : terminatorAt(source, index);
84
+ if (terminator === null && index < source.length) {
85
+ index += 1;
86
+ continue;
87
+ }
88
+ if (source.slice(lineStart, index) === rule.close) return {
89
+ format: rule.format,
90
+ value: source.slice(valueStart, lineStart),
91
+ newline: openNewline,
92
+ bodyOffset: terminator === null ? index : index + terminator.length
93
+ };
94
+ if (terminator === null) break;
95
+ index += terminator.length;
96
+ lineStart = index;
97
+ }
98
+ return null;
99
+ };
54
100
 
55
101
  //#endregion
56
- export { scanFrontmatter };
102
+ export { scanFrontmatter, scanRawFrontmatter };