@effected/markdown 0.20.0 → 0.22.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 +3 -3
- package/FrontmatterResolver.js +6 -20
- package/MarkdownDocument.js +22 -13
- package/MarkdownVisitor.js +57 -17
- package/Mdast.js +27 -11
- package/README.md +26 -8
- package/index.d.ts +111 -33
- package/package.json +4 -4
package/Frontmatter.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { MarkdownEdit } from "./MarkdownEdit.js";
|
|
2
2
|
import { FrontmatterFormat } from "./MarkdownNode.js";
|
|
3
|
-
import { Effect, Schema } from "effect";
|
|
3
|
+
import { Effect, Option, Schema } from "effect";
|
|
4
4
|
|
|
5
5
|
// src/Frontmatter.ts
|
|
6
6
|
/**
|
|
@@ -212,7 +212,7 @@ var MarkdownFrontmatter = class MarkdownFrontmatter {
|
|
|
212
212
|
*/
|
|
213
213
|
static schema(schema, codec) {
|
|
214
214
|
return (document) => {
|
|
215
|
-
const node = document.frontmatter;
|
|
215
|
+
const node = Option.getOrUndefined(document.frontmatter);
|
|
216
216
|
return node === void 0 ? Effect.fail(new FrontmatterMissingError({ reason: document.hasFrontmatterBlock ? "captureDisabled" : "absent" })) : codec.decode(node).pipe(Effect.flatMap((data) => Schema.decodeUnknownEffect(schema)(data).pipe(Effect.catchTag("SchemaError", (error) => Effect.fail(new FrontmatterValidationError({ issue: error.issue }))))));
|
|
217
217
|
};
|
|
218
218
|
}
|
|
@@ -258,7 +258,7 @@ var MarkdownFrontmatter = class MarkdownFrontmatter {
|
|
|
258
258
|
*/
|
|
259
259
|
static set(schema, codec) {
|
|
260
260
|
return (document, data) => {
|
|
261
|
-
const node = document.frontmatter;
|
|
261
|
+
const node = Option.getOrUndefined(document.frontmatter);
|
|
262
262
|
if (node !== void 0 && node.format !== codec.format) return Effect.fail(new FrontmatterFormatMismatchError({
|
|
263
263
|
expected: codec.format,
|
|
264
264
|
actual: node.format
|
package/FrontmatterResolver.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Effect, Result, Schema } from "effect";
|
|
1
|
+
import { Effect, Option, Predicate, Result, Schema } from "effect";
|
|
2
2
|
|
|
3
3
|
// src/FrontmatterResolver.ts
|
|
4
4
|
/**
|
|
@@ -140,7 +140,6 @@ const parseVersionSegments = (version) => {
|
|
|
140
140
|
if (!/^\d+(\.\d+){0,2}$/.test(version)) return;
|
|
141
141
|
return version.split(".").map((segment) => Number.parseInt(segment, 10));
|
|
142
142
|
};
|
|
143
|
-
const isMapping = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
144
143
|
/**
|
|
145
144
|
* The `$schema` declaration classifier and the package's one built-in
|
|
146
145
|
* resolver implementation.
|
|
@@ -184,30 +183,17 @@ var SchemaResolver = class SchemaResolver {
|
|
|
184
183
|
version
|
|
185
184
|
}));
|
|
186
185
|
}
|
|
187
|
-
if (
|
|
186
|
+
if (Predicate.isObject(value)) return Result.succeed(new SchemaDeclarationInline({ document: value }));
|
|
188
187
|
return Result.fail(new SchemaDeclarationInvalidError({
|
|
189
188
|
reason: "the declaration is neither a string nor a mapping",
|
|
190
189
|
value
|
|
191
190
|
}));
|
|
192
191
|
}
|
|
193
|
-
/**
|
|
194
|
-
* Extract and classify the `$schema` declaration from decoded frontmatter
|
|
195
|
-
* data.
|
|
196
|
-
*
|
|
197
|
-
* @remarks
|
|
198
|
-
* Non-mapping data and a mapping without a `$schema` key both carry no
|
|
199
|
-
* declaration: the result succeeds with `undefined` by default, or fails
|
|
200
|
-
* with {@link SchemaDeclarationMissingError} under `requireDeclaration`.
|
|
201
|
-
*
|
|
202
|
-
* @param data - The decoded frontmatter data.
|
|
203
|
-
* @param options - `requireDeclaration` makes a missing `$schema` a typed
|
|
204
|
-
* error.
|
|
205
|
-
* @returns The classified declaration, `undefined` when absent and
|
|
206
|
-
* tolerated, or the typed error.
|
|
207
|
-
*/
|
|
208
192
|
static declarationOf(data, options) {
|
|
209
|
-
|
|
210
|
-
|
|
193
|
+
const required = options?.requireDeclaration === true;
|
|
194
|
+
if (!Predicate.isObject(data) || !Object.hasOwn(data, "$schema")) return required ? Result.fail(new SchemaDeclarationMissingError()) : Result.succeed(Option.none());
|
|
195
|
+
const classified = SchemaResolver.classify(data.$schema);
|
|
196
|
+
return required ? classified : Result.map(classified, Option.some);
|
|
211
197
|
}
|
|
212
198
|
/**
|
|
213
199
|
* The package's one built-in resolver: a name-keyed registry with
|
package/MarkdownDocument.js
CHANGED
|
@@ -6,7 +6,7 @@ import { normalizeLabelText } from "./internal/references.js";
|
|
|
6
6
|
import { MarkdownDiagnostic } from "./MarkdownDiagnostic.js";
|
|
7
7
|
import { parsePassResult } from "./Markdown.js";
|
|
8
8
|
import { rootHeadingBounds } from "./internal/sectionBounds.js";
|
|
9
|
-
import { Effect, Result, Schema } from "effect";
|
|
9
|
+
import { Effect, Option, Result, Schema } from "effect";
|
|
10
10
|
|
|
11
11
|
// src/MarkdownDocument.ts
|
|
12
12
|
/**
|
|
@@ -167,19 +167,26 @@ var MarkdownDocument = class MarkdownDocument extends Schema.Class("MarkdownDocu
|
|
|
167
167
|
definitions: Schema.ReadonlyMap(Schema.String, Definition)
|
|
168
168
|
}) {
|
|
169
169
|
/**
|
|
170
|
-
* The document's frontmatter capture, or `
|
|
170
|
+
* The document's frontmatter capture, or `Option.none()` when there is
|
|
171
|
+
* none.
|
|
171
172
|
*
|
|
172
173
|
* @remarks
|
|
173
174
|
* Derived from the tree rather than stored: a {@link Frontmatter} node can
|
|
174
175
|
* only ever sit at the head of `root.children` (the capture fires at most
|
|
175
176
|
* once, at offset 0), so the tree is the single source of truth and the
|
|
176
|
-
* accessor can never disagree with it. `
|
|
177
|
-
* with no frontmatter block and one parsed with the capture toggle
|
|
178
|
-
* {@link MarkdownDocument.hasFrontmatterBlock} tells the two apart.
|
|
177
|
+
* accessor can never disagree with it. `Option.none()` covers both a
|
|
178
|
+
* document with no frontmatter block and one parsed with the capture toggle
|
|
179
|
+
* off — {@link MarkdownDocument.hasFrontmatterBlock} tells the two apart.
|
|
180
|
+
*
|
|
181
|
+
* An `Option` rather than `Frontmatter | undefined` because this is a
|
|
182
|
+
* lookup that can miss, and the kit's lookups return `Option` — the same
|
|
183
|
+
* shape as the sibling format packages' tree lookups (`JsoncNode.find`,
|
|
184
|
+
* `YamlNode.find`). A derived getter is a lookup, not a data field: it
|
|
185
|
+
* recomputes from the tree on every access.
|
|
179
186
|
*/
|
|
180
187
|
get frontmatter() {
|
|
181
188
|
const head = this.root.children[0];
|
|
182
|
-
return head !== void 0 && head.type === "frontmatter" ? head :
|
|
189
|
+
return head !== void 0 && head.type === "frontmatter" ? Option.some(head) : Option.none();
|
|
183
190
|
}
|
|
184
191
|
/**
|
|
185
192
|
* Whether the source opens with a well-formed frontmatter block —
|
|
@@ -187,10 +194,10 @@ var MarkdownDocument = class MarkdownDocument extends Schema.Class("MarkdownDocu
|
|
|
187
194
|
*
|
|
188
195
|
* @remarks
|
|
189
196
|
* The structural signal that disambiguates the `frontmatter` accessor's
|
|
190
|
-
* `
|
|
197
|
+
* `Option.none()`: capture is opt-in (`MarkdownParseOptions.frontmatter`
|
|
191
198
|
* defaults off), so a document whose source visibly starts with `---` can
|
|
192
199
|
* still carry no capture node — spec-correct CommonMark reads the fences as
|
|
193
|
-
* a thematic break plus content.
|
|
200
|
+
* a thematic break plus content. An empty `frontmatter` with
|
|
194
201
|
* `hasFrontmatterBlock === true` means exactly "parsed with capture off";
|
|
195
202
|
* with `false` it means the source genuinely has no block.
|
|
196
203
|
*
|
|
@@ -274,11 +281,12 @@ var MarkdownDocument = class MarkdownDocument extends Schema.Class("MarkdownDocu
|
|
|
274
281
|
* document repeatedly should bind `document.sections` once instead.
|
|
275
282
|
*
|
|
276
283
|
* @param options - Depth restriction.
|
|
277
|
-
* @returns The first matching section, or `
|
|
284
|
+
* @returns The first matching section, or `Option.none()` when no section
|
|
285
|
+
* matches.
|
|
278
286
|
*/
|
|
279
287
|
firstSection(options) {
|
|
280
288
|
const depth = options?.depth;
|
|
281
|
-
return this.sections.find((section) => depth === void 0 || section.depth === depth);
|
|
289
|
+
return Option.fromUndefinedOr(this.sections.find((section) => depth === void 0 || section.depth === depth));
|
|
282
290
|
}
|
|
283
291
|
/**
|
|
284
292
|
* The first section whose heading matches, in document order.
|
|
@@ -295,7 +303,8 @@ var MarkdownDocument = class MarkdownDocument extends Schema.Class("MarkdownDocu
|
|
|
295
303
|
*
|
|
296
304
|
* @param match - A string, a RegExp, or a predicate.
|
|
297
305
|
* @param options - Depth restriction, applied before matching.
|
|
298
|
-
* @returns The first matching section, or `
|
|
306
|
+
* @returns The first matching section, or `Option.none()` when no section
|
|
307
|
+
* matches.
|
|
299
308
|
*/
|
|
300
309
|
sectionByHeading(match, options) {
|
|
301
310
|
const depth = options?.depth;
|
|
@@ -303,7 +312,7 @@ var MarkdownDocument = class MarkdownDocument extends Schema.Class("MarkdownDocu
|
|
|
303
312
|
match.lastIndex = 0;
|
|
304
313
|
return match.test(section.text.trim());
|
|
305
314
|
} : (section) => match(section.text, section);
|
|
306
|
-
return this.sections.find((section) => (depth === void 0 || section.depth === depth) && matches(section));
|
|
315
|
+
return Option.fromUndefinedOr(this.sections.find((section) => (depth === void 0 || section.depth === depth) && matches(section)));
|
|
307
316
|
}
|
|
308
317
|
/**
|
|
309
318
|
* Every link-bearing node in the document, in document order: links,
|
|
@@ -343,7 +352,7 @@ var MarkdownDocument = class MarkdownDocument extends Schema.Class("MarkdownDocu
|
|
|
343
352
|
return entries;
|
|
344
353
|
}
|
|
345
354
|
find(selector) {
|
|
346
|
-
return findInTree(this.root, 0, selectorPredicate(selector));
|
|
355
|
+
return Option.fromUndefinedOr(findInTree(this.root, 0, selectorPredicate(selector)));
|
|
347
356
|
}
|
|
348
357
|
findAll(selector) {
|
|
349
358
|
const predicate = selectorPredicate(selector);
|
package/MarkdownVisitor.js
CHANGED
|
@@ -11,8 +11,9 @@ import { Data, Stream } from "effect";
|
|
|
11
11
|
*/
|
|
12
12
|
const MarkdownVisitorEvent = Data.taggedEnum();
|
|
13
13
|
/**
|
|
14
|
-
* Walks a parsed markdown tree as a lazy
|
|
15
|
-
* document pre-order.
|
|
14
|
+
* Walks a parsed markdown tree as a lazy sequence of `Enter`/`Exit` events in
|
|
15
|
+
* document pre-order — synchronously with {@link MarkdownVisitor.walk}, or as
|
|
16
|
+
* a `Stream` with {@link MarkdownVisitor.visit}. Not instantiable.
|
|
16
17
|
*
|
|
17
18
|
* @example
|
|
18
19
|
* ```ts
|
|
@@ -32,9 +33,52 @@ const MarkdownVisitorEvent = Data.taggedEnum();
|
|
|
32
33
|
*
|
|
33
34
|
* @public
|
|
34
35
|
*/
|
|
35
|
-
var MarkdownVisitor = class {
|
|
36
|
+
var MarkdownVisitor = class MarkdownVisitor {
|
|
36
37
|
constructor() {}
|
|
37
38
|
/**
|
|
39
|
+
* Walk `root` synchronously, as a lazy `Iterable` of `MarkdownVisitorEvent`
|
|
40
|
+
* in document pre-order: `Enter` and `Exit` for every node including the
|
|
41
|
+
* root and every leaf. The primitive {@link MarkdownVisitor.visit} is
|
|
42
|
+
* defined in terms of, so the two yield the same events in the same order.
|
|
43
|
+
*
|
|
44
|
+
* @remarks
|
|
45
|
+
* For callers that cannot suspend — a lint rule whose host calls it
|
|
46
|
+
* synchronously, a getter, a non-Effect adapter. Events are produced on
|
|
47
|
+
* demand: a `for…of` that `break`s stops the walk without visiting the
|
|
48
|
+
* rest of the tree. The iterable is re-iterable — every `[Symbol.iterator]()`
|
|
49
|
+
* call starts a fresh walk from the root, so binding the result once and
|
|
50
|
+
* iterating it twice walks the tree twice, independently.
|
|
51
|
+
*
|
|
52
|
+
* Not named `walkResult`: it returns no `Result`, and the walk has no
|
|
53
|
+
* failure channel to put in one. A hand-constructed tree nested past the
|
|
54
|
+
* depth cap surfaces as a terminal `MarkdownVisitorEvent` `Error` event
|
|
55
|
+
* carrying a `NestingDepthExceeded` {@link MarkdownDiagnostic}, after which
|
|
56
|
+
* nothing is yielded — the same posture as `visit`. A tree produced by
|
|
57
|
+
* `Markdown.parse` or decoded by `Mdast.fromMdast` can never trip it.
|
|
58
|
+
*
|
|
59
|
+
* @example
|
|
60
|
+
* ```ts
|
|
61
|
+
* import { Markdown, MarkdownVisitor } from "@effected/markdown";
|
|
62
|
+
* import { Result } from "effect";
|
|
63
|
+
*
|
|
64
|
+
* const root = Result.getOrThrow(Markdown.parseResult("# Hi\n\ntext\n"));
|
|
65
|
+
* const entered: Array<string> = [];
|
|
66
|
+
* for (const event of MarkdownVisitor.walk(root)) {
|
|
67
|
+
* if (event._tag === "Enter") entered.push(event.node.type);
|
|
68
|
+
* }
|
|
69
|
+
* console.log(entered);
|
|
70
|
+
* // => ["root", "heading", "text", "paragraph", "text"]
|
|
71
|
+
* ```
|
|
72
|
+
*
|
|
73
|
+
* @param root - The tree to walk.
|
|
74
|
+
* @returns A lazy, re-iterable `Iterable` of `MarkdownVisitorEvent`.
|
|
75
|
+
*/
|
|
76
|
+
static walk(root) {
|
|
77
|
+
return { [Symbol.iterator]() {
|
|
78
|
+
return walkNode(root, [], 0);
|
|
79
|
+
} };
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
38
82
|
* Create a lazy `Stream` of `MarkdownVisitorEvent` walking `root` in
|
|
39
83
|
* document pre-order: `Enter` and `Exit` for every node including the
|
|
40
84
|
* root and every leaf. Events are produced on demand, so combining with
|
|
@@ -44,28 +88,24 @@ var MarkdownVisitor = class {
|
|
|
44
88
|
* @remarks
|
|
45
89
|
* Infallible at the type level. A tree produced by `Markdown.parse` can
|
|
46
90
|
* never trip the walk's depth guard — the parser refuses deeper input —
|
|
47
|
-
*
|
|
48
|
-
*
|
|
49
|
-
*
|
|
50
|
-
*
|
|
91
|
+
* and neither can a decoded foreign tree (`Mdast.fromMdast`), because the
|
|
92
|
+
* decode boundary refuses it first; only a hand-constructed tree can trip
|
|
93
|
+
* the guard. The trip surfaces as a terminal `MarkdownVisitorEvent`
|
|
94
|
+
* `Error` event carrying a `NestingDepthExceeded` {@link MarkdownDiagnostic},
|
|
95
|
+
* mirroring the typed failure `Markdown.stringify` produces for the same
|
|
96
|
+
* tree.
|
|
97
|
+
*
|
|
98
|
+
* Defined as `Stream.fromIterable(MarkdownVisitor.walk(root))`: every run
|
|
99
|
+
* of the stream walks afresh, and the two forms never diverge.
|
|
51
100
|
*
|
|
52
101
|
* @param root - The tree to walk.
|
|
53
102
|
* @returns A lazy `Stream` of `MarkdownVisitorEvent`, infallible at the type
|
|
54
103
|
* level.
|
|
55
104
|
*/
|
|
56
105
|
static visit(root) {
|
|
57
|
-
return Stream.fromIterable(
|
|
106
|
+
return Stream.fromIterable(MarkdownVisitor.walk(root));
|
|
58
107
|
}
|
|
59
108
|
};
|
|
60
|
-
/** Lazy per-subscription iterable so each run of the stream walks afresh. */
|
|
61
|
-
function walkIterable(root) {
|
|
62
|
-
return { [Symbol.iterator]() {
|
|
63
|
-
return walk(root)[Symbol.iterator]();
|
|
64
|
-
} };
|
|
65
|
-
}
|
|
66
|
-
function* walk(root) {
|
|
67
|
-
yield* walkNode(root, [], 0);
|
|
68
|
-
}
|
|
69
109
|
function* walkNode(node, path, depth) {
|
|
70
110
|
if (depth > 256) {
|
|
71
111
|
yield MarkdownVisitorEvent.Error({
|
package/Mdast.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Root } from "./MarkdownNode.js";
|
|
2
2
|
import { unescapeString } from "./internal/unescape.js";
|
|
3
|
-
import {
|
|
3
|
+
import { GuardExceeded, isGuardExceeded } from "./internal/carriers.js";
|
|
4
|
+
import { Effect, Predicate, Result, Schema, SchemaIssue } from "effect";
|
|
4
5
|
|
|
5
6
|
// src/Mdast.ts
|
|
6
7
|
/**
|
|
@@ -286,10 +287,9 @@ const admittedFields = {
|
|
|
286
287
|
mdxTextExpression: ["value"],
|
|
287
288
|
mdxjsEsm: ["value"]
|
|
288
289
|
};
|
|
289
|
-
const
|
|
290
|
-
const completePoint = (value) => isRecord(value) && typeof value.line === "number" && typeof value.column === "number" && typeof value.offset === "number";
|
|
290
|
+
const completePoint = (value) => Predicate.isObject(value) && typeof value.line === "number" && typeof value.column === "number" && typeof value.offset === "number";
|
|
291
291
|
const normalizePosition = (value) => {
|
|
292
|
-
if (
|
|
292
|
+
if (Predicate.isObject(value) && completePoint(value.start) && completePoint(value.end)) {
|
|
293
293
|
const start = value.start;
|
|
294
294
|
const end = value.end;
|
|
295
295
|
return {
|
|
@@ -307,8 +307,9 @@ const normalizePosition = (value) => {
|
|
|
307
307
|
}
|
|
308
308
|
return sentinelPosition;
|
|
309
309
|
};
|
|
310
|
-
const normalizeNode = (value) => {
|
|
311
|
-
if (!
|
|
310
|
+
const normalizeNode = (value, depth = 0) => {
|
|
311
|
+
if (!Predicate.isObject(value) || typeof value.type !== "string") return value;
|
|
312
|
+
if (depth > 256) throw new GuardExceeded("NestingDepthExceeded", 256, depth, 0);
|
|
312
313
|
const type = value.type;
|
|
313
314
|
const format = frontmatterTypes.get(type);
|
|
314
315
|
if (format !== void 0) return {
|
|
@@ -321,7 +322,7 @@ const normalizeNode = (value) => {
|
|
|
321
322
|
type,
|
|
322
323
|
name: value.name,
|
|
323
324
|
attributes: Array.isArray(value.attributes) ? value.attributes.map(normalizeMdxAttribute) : value.attributes,
|
|
324
|
-
children: Array.isArray(value.children) ? value.children.map(normalizeNode) : value.children,
|
|
325
|
+
children: Array.isArray(value.children) ? value.children.map((child) => normalizeNode(child, depth + 1)) : value.children,
|
|
325
326
|
position: normalizePosition(value.position)
|
|
326
327
|
};
|
|
327
328
|
const admitted = admittedFields[type];
|
|
@@ -335,12 +336,12 @@ const normalizeNode = (value) => {
|
|
|
335
336
|
const code = normalized.value;
|
|
336
337
|
normalized.value = code.endsWith("\n") ? code : `${code}\n`;
|
|
337
338
|
}
|
|
338
|
-
if (Array.isArray(value.children)) normalized.children = value.children.map(normalizeNode);
|
|
339
|
+
if (Array.isArray(value.children)) normalized.children = value.children.map((child) => normalizeNode(child, depth + 1));
|
|
339
340
|
normalized.position = normalizePosition(value.position);
|
|
340
341
|
return normalized;
|
|
341
342
|
};
|
|
342
343
|
const normalizeMdxAttribute = (value) => {
|
|
343
|
-
if (!
|
|
344
|
+
if (!Predicate.isObject(value) || typeof value.type !== "string") return value;
|
|
344
345
|
if (value.type === "mdxJsxExpressionAttribute") return {
|
|
345
346
|
type: "mdxJsxExpressionAttribute",
|
|
346
347
|
value: value.value,
|
|
@@ -353,7 +354,7 @@ const normalizeMdxAttribute = (value) => {
|
|
|
353
354
|
name: value.name,
|
|
354
355
|
position: normalizePosition(value.position)
|
|
355
356
|
};
|
|
356
|
-
if (raw !== void 0) normalized.value =
|
|
357
|
+
if (raw !== void 0) normalized.value = Predicate.isObject(raw) && raw.type === "mdxJsxAttributeValueExpression" ? {
|
|
357
358
|
type: "mdxJsxAttributeValueExpression",
|
|
358
359
|
value: raw.value,
|
|
359
360
|
position: normalizePosition(raw.position)
|
|
@@ -452,12 +453,27 @@ var Mdast = class Mdast {
|
|
|
452
453
|
* plain tree built for `Markdown.stringify` can carry it straight in. A
|
|
453
454
|
* value outside `"canonical" | "literal"` fails the decode typed.
|
|
454
455
|
*
|
|
456
|
+
* **Nesting is capped at 256 levels below the root**, the same cap the
|
|
457
|
+
* parser and `Markdown.stringify` enforce. A tree with any node deeper
|
|
458
|
+
* than that, leaves included, fails typed with a `SchemaIssue.InvalidValue`
|
|
459
|
+
* issue before the schema decode runs, so hostile depth is never a defect.
|
|
460
|
+
* Because leaves count, a document the parser accepts can still be
|
|
461
|
+
* refused here: `"> ".repeat(255) + "x"` parses, but its `text` leaf sits
|
|
462
|
+
* at depth 257, and `Markdown.stringify` refuses that tree too.
|
|
463
|
+
*
|
|
455
464
|
* @param input - A plain mdast tree, typically a `root`.
|
|
456
465
|
* @returns A `Result` succeeding with the decoded {@link Root}, or
|
|
457
466
|
* failing with {@link MdastDecodeError} carrying the structured issue.
|
|
458
467
|
*/
|
|
459
468
|
static fromMdastResult(input) {
|
|
460
|
-
|
|
469
|
+
let normalized;
|
|
470
|
+
try {
|
|
471
|
+
normalized = normalizeNode(input);
|
|
472
|
+
} catch (error) {
|
|
473
|
+
if (!isGuardExceeded(error)) throw error;
|
|
474
|
+
return Result.fail(new MdastDecodeError({ issue: new SchemaIssue.InvalidValue({ message: error.message }) }));
|
|
475
|
+
}
|
|
476
|
+
return Result.mapError(decodeRoot(normalized), (error) => new MdastDecodeError({ issue: error.issue }));
|
|
461
477
|
}
|
|
462
478
|
/**
|
|
463
479
|
* Decode foreign plain mdast into the package's node classes. Defined in
|
package/README.md
CHANGED
|
@@ -85,7 +85,7 @@ Every parse has a synchronous twin — `MarkdownDocument.parseResult` and `Markd
|
|
|
85
85
|
|
|
86
86
|
```ts
|
|
87
87
|
import { MarkdownDocument, MarkdownFormat } from "@effected/markdown";
|
|
88
|
-
import { Effect } from "effect";
|
|
88
|
+
import { Effect, Option } from "effect";
|
|
89
89
|
|
|
90
90
|
const source = `# Release notes
|
|
91
91
|
|
|
@@ -94,7 +94,7 @@ See the [changelog](./CHANGELOG.md).
|
|
|
94
94
|
|
|
95
95
|
const program = Effect.gen(function* () {
|
|
96
96
|
const doc = yield* MarkdownDocument.parse(source);
|
|
97
|
-
const label = doc.find("text")
|
|
97
|
+
const label = Option.getOrThrow(doc.find("text"));
|
|
98
98
|
return yield* MarkdownFormat.modifyToString(doc, label, "Release notes (2026)");
|
|
99
99
|
});
|
|
100
100
|
|
|
@@ -104,7 +104,7 @@ Effect.runPromise(program).then(console.log);
|
|
|
104
104
|
// See the [changelog](./CHANGELOG.md).
|
|
105
105
|
```
|
|
106
106
|
|
|
107
|
-
`find` walks the tree in document pre-order and returns the document's own node
|
|
107
|
+
`find` walks the tree in document pre-order and returns the document's own node inside an `Option` — `Option.none()` when nothing matches — so the match feeds `modify` by identity. Replacements are literal strings or node fragments, and both render through the canonical stringifier, so a modified document re-parses cleanly by construction — you cannot splice in raw markdown that reopens a fence or breaks a table.
|
|
108
108
|
|
|
109
109
|
`MarkdownFormat.format` handles the other half: conservative marker normalization, never content rewriting. It converts heading style, bullet character, emphasis marker, fence character, thematic-break character and code-block style, and skips any conversion that would not be safe rather than attempting it cleverly:
|
|
110
110
|
|
|
@@ -198,11 +198,11 @@ Effect.runPromise(program).then(console.log);
|
|
|
198
198
|
// [ "./CHANGELOG.md", "https://example.com/docs" ]
|
|
199
199
|
```
|
|
200
200
|
|
|
201
|
-
`headings` lists every heading wherever it sits, including inside blockquotes and list items. `sections` are delimited by root-level headings only, and each section's range spans its subsections, so the edit layer can splice a whole section out in one edit. `firstSection` and `sectionByHeading` find one `DocumentSection` without scanning `sections` yourself — the second matches on an exact trimmed heading string, a `RegExp`, or a predicate, and both accept `{ depth }` to restrict to one heading level:
|
|
201
|
+
`headings` lists every heading wherever it sits, including inside blockquotes and list items. `sections` are delimited by root-level headings only, and each section's range spans its subsections, so the edit layer can splice a whole section out in one edit. `firstSection` and `sectionByHeading` find one `DocumentSection` without scanning `sections` yourself — the second matches on an exact trimmed heading string, a `RegExp`, or a predicate, and both accept `{ depth }` to restrict to one heading level. Both return an `Option`, as every lookup in the kit does (the `frontmatter` accessor and `find` too):
|
|
202
202
|
|
|
203
203
|
```ts
|
|
204
204
|
import { MarkdownDocument } from "@effected/markdown";
|
|
205
|
-
import { Effect } from "effect";
|
|
205
|
+
import { Effect, Option } from "effect";
|
|
206
206
|
|
|
207
207
|
const source = `# Changelog
|
|
208
208
|
|
|
@@ -217,7 +217,10 @@ Initial release.
|
|
|
217
217
|
|
|
218
218
|
const program = Effect.gen(function* () {
|
|
219
219
|
const doc = yield* MarkdownDocument.parse(source);
|
|
220
|
-
return doc.sectionByHeading("1.2.3", { depth: 2 })
|
|
220
|
+
return Option.match(doc.sectionByHeading("1.2.3", { depth: 2 }), {
|
|
221
|
+
onNone: () => "no such release",
|
|
222
|
+
onSome: (section) => section.body.trim(),
|
|
223
|
+
});
|
|
221
224
|
});
|
|
222
225
|
|
|
223
226
|
Effect.runPromise(program).then(console.log);
|
|
@@ -226,7 +229,7 @@ Effect.runPromise(program).then(console.log);
|
|
|
226
229
|
|
|
227
230
|
`DocumentSection.body` is the section without its heading, and it is deliberately untrimmed — exactly the bytes `bodyRange` describes — so callers write `.body.trim()` when they want the tidy form.
|
|
228
231
|
|
|
229
|
-
For anything the accessors do not cover, `find` and `findAll` walk the whole tree in document pre-order. A string selector matches the node's `type` and narrows the result — `doc.findAll("heading")` is `ReadonlyArray<Heading>`, `doc.find("table")` is `Table
|
|
232
|
+
For anything the accessors do not cover, `find` and `findAll` walk the whole tree in document pre-order. A string selector matches the node's `type` and narrows the result — `doc.findAll("heading")` is `ReadonlyArray<Heading>`, `doc.find("table")` is `Option<Table>` — and a type-guard predicate narrows the same way. The nodes come back by identity, so `doc.findAll("heading")[1]` addresses the second heading for `MarkdownFormat.modify` without raw child indexing.
|
|
230
233
|
|
|
231
234
|
`MarkdownVisitor.visit` streams the same walk as `Enter`/`Exit` events carrying the node, its child-index path and its depth:
|
|
232
235
|
|
|
@@ -246,6 +249,21 @@ Effect.runPromise(program).then(console.log);
|
|
|
246
249
|
// "Exit:text", "Exit:emphasis", "Enter:text", "Exit:text", "Exit:paragraph", "Exit:root" ]
|
|
247
250
|
```
|
|
248
251
|
|
|
252
|
+
`MarkdownVisitor.walk` is the same walk as a synchronous, lazy `Iterable` — the primitive `visit` is defined in terms of — for code that cannot suspend, such as a lint rule a host calls synchronously. Each iteration walks afresh from the root, and a `for…of` that `break`s stops the walk:
|
|
253
|
+
|
|
254
|
+
```ts
|
|
255
|
+
import { Markdown, MarkdownVisitor } from "@effected/markdown";
|
|
256
|
+
import { Result } from "effect";
|
|
257
|
+
|
|
258
|
+
const root = Result.getOrThrow(Markdown.parseResult("# Title\n\nA *b* c\n"));
|
|
259
|
+
const headings: Array<number> = [];
|
|
260
|
+
for (const event of MarkdownVisitor.walk(root)) {
|
|
261
|
+
if (event._tag === "Enter" && event.node.type === "heading") headings.push(event.node.depth);
|
|
262
|
+
}
|
|
263
|
+
console.log(headings);
|
|
264
|
+
// [ 1 ]
|
|
265
|
+
```
|
|
266
|
+
|
|
249
267
|
## mdast interop
|
|
250
268
|
|
|
251
269
|
The node classes already use mdast's type names and field shapes; `Mdast.toMdast` strips the fidelity fields this package adds — bullet characters, fence style, ATX-versus-setext spelling — (keeping only a `Text` node's `escapeStyle` when set, see [Literal text](#literal-text)) and emits plain spec-valid mdast JSON that the remark ecosystem consumes directly, including `mdast-util-to-hast` if you want hast:
|
|
@@ -364,7 +382,7 @@ To *normalize* an existing document to different choices, use `MarkdownFormat` w
|
|
|
364
382
|
- `FrontmatterSource` — string-level frontmatter `split`/`join` over the same closed fence grammar, byte-exact boundaries, no parsing of value or body; for bodies the CommonMark engine should not touch.
|
|
365
383
|
- `MarkdownEdit` / `MarkdownRange` (with `applyAll`) — the non-mutating text-edit vocabulary, field-identical to `@effected/jsonc`'s, `@effected/yaml`'s and `@effected/toml`'s.
|
|
366
384
|
- `MarkdownFormat` — `format`/`formatToString` compute conservative marker-normalization edits; `modify`/`modifyToString` replace a node by identity through the canonical stringifier.
|
|
367
|
-
- `MarkdownVisitor` — walk a parsed tree as
|
|
385
|
+
- `MarkdownVisitor` — walk a parsed tree as `Enter`/`Exit` events with child-index paths and depth: synchronously as a lazy `Iterable` (`walk`), or as a `Stream` (`visit`).
|
|
368
386
|
- `Mdast` — `toMdast` projects to plain mdast JSON; `fromMdast`/`fromMdastResult` admit a foreign mdast tree with validation.
|
|
369
387
|
- `MarkdownFrontmatter.schema` plus the `YamlFrontmatter`, `TomlFrontmatter` and `JsonFrontmatter` codecs — typed frontmatter decoding over optional per-format peers, with four separately catchable failure modes and a `FrontmatterMissingReason` on the absent one.
|
|
370
388
|
- `SchemaResolver` — classify a frontmatter `$schema` declaration into `ByUrl`/`ByPath`/`Inline`/`ByName` and resolve names against a registry, with no IO and no dependencies.
|
package/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Data, Effect, Result, Schema, Stream } from "effect";
|
|
1
|
+
import { Data, Effect, Option, Result, Schema, Stream } from "effect";
|
|
2
2
|
declare const Point_base: Schema.Class<Point, Schema.Struct<{
|
|
3
3
|
readonly line: Schema.Number;
|
|
4
4
|
readonly column: Schema.Number;
|
|
@@ -1589,27 +1589,34 @@ declare const MarkdownDocument_base: Schema.Class<MarkdownDocument, Schema.Struc
|
|
|
1589
1589
|
*/
|
|
1590
1590
|
export declare class MarkdownDocument extends MarkdownDocument_base {
|
|
1591
1591
|
/**
|
|
1592
|
-
* The document's frontmatter capture, or `
|
|
1592
|
+
* The document's frontmatter capture, or `Option.none()` when there is
|
|
1593
|
+
* none.
|
|
1593
1594
|
*
|
|
1594
1595
|
* @remarks
|
|
1595
1596
|
* Derived from the tree rather than stored: a {@link Frontmatter} node can
|
|
1596
1597
|
* only ever sit at the head of `root.children` (the capture fires at most
|
|
1597
1598
|
* once, at offset 0), so the tree is the single source of truth and the
|
|
1598
|
-
* accessor can never disagree with it. `
|
|
1599
|
-
* with no frontmatter block and one parsed with the capture toggle
|
|
1600
|
-
* {@link MarkdownDocument.hasFrontmatterBlock} tells the two apart.
|
|
1599
|
+
* accessor can never disagree with it. `Option.none()` covers both a
|
|
1600
|
+
* document with no frontmatter block and one parsed with the capture toggle
|
|
1601
|
+
* off — {@link MarkdownDocument.hasFrontmatterBlock} tells the two apart.
|
|
1602
|
+
*
|
|
1603
|
+
* An `Option` rather than `Frontmatter | undefined` because this is a
|
|
1604
|
+
* lookup that can miss, and the kit's lookups return `Option` — the same
|
|
1605
|
+
* shape as the sibling format packages' tree lookups (`JsoncNode.find`,
|
|
1606
|
+
* `YamlNode.find`). A derived getter is a lookup, not a data field: it
|
|
1607
|
+
* recomputes from the tree on every access.
|
|
1601
1608
|
*/
|
|
1602
|
-
get frontmatter(): Frontmatter
|
|
1609
|
+
get frontmatter(): Option.Option<Frontmatter>;
|
|
1603
1610
|
/**
|
|
1604
1611
|
* Whether the source opens with a well-formed frontmatter block —
|
|
1605
1612
|
* regardless of how the document was parsed.
|
|
1606
1613
|
*
|
|
1607
1614
|
* @remarks
|
|
1608
1615
|
* The structural signal that disambiguates the `frontmatter` accessor's
|
|
1609
|
-
* `
|
|
1616
|
+
* `Option.none()`: capture is opt-in (`MarkdownParseOptions.frontmatter`
|
|
1610
1617
|
* defaults off), so a document whose source visibly starts with `---` can
|
|
1611
1618
|
* still carry no capture node — spec-correct CommonMark reads the fences as
|
|
1612
|
-
* a thematic break plus content.
|
|
1619
|
+
* a thematic break plus content. An empty `frontmatter` with
|
|
1613
1620
|
* `hasFrontmatterBlock === true` means exactly "parsed with capture off";
|
|
1614
1621
|
* with `false` it means the source genuinely has no block.
|
|
1615
1622
|
*
|
|
@@ -1656,9 +1663,10 @@ export declare class MarkdownDocument extends MarkdownDocument_base {
|
|
|
1656
1663
|
* document repeatedly should bind `document.sections` once instead.
|
|
1657
1664
|
*
|
|
1658
1665
|
* @param options - Depth restriction.
|
|
1659
|
-
* @returns The first matching section, or `
|
|
1666
|
+
* @returns The first matching section, or `Option.none()` when no section
|
|
1667
|
+
* matches.
|
|
1660
1668
|
*/
|
|
1661
|
-
firstSection(options?: SectionQueryOptions): DocumentSection
|
|
1669
|
+
firstSection(options?: SectionQueryOptions): Option.Option<DocumentSection>;
|
|
1662
1670
|
/**
|
|
1663
1671
|
* The first section whose heading matches, in document order.
|
|
1664
1672
|
*
|
|
@@ -1674,9 +1682,10 @@ export declare class MarkdownDocument extends MarkdownDocument_base {
|
|
|
1674
1682
|
*
|
|
1675
1683
|
* @param match - A string, a RegExp, or a predicate.
|
|
1676
1684
|
* @param options - Depth restriction, applied before matching.
|
|
1677
|
-
* @returns The first matching section, or `
|
|
1685
|
+
* @returns The first matching section, or `Option.none()` when no section
|
|
1686
|
+
* matches.
|
|
1678
1687
|
*/
|
|
1679
|
-
sectionByHeading(match: SectionHeadingMatch, options?: SectionQueryOptions): DocumentSection
|
|
1688
|
+
sectionByHeading(match: SectionHeadingMatch, options?: SectionQueryOptions): Option.Option<DocumentSection>;
|
|
1680
1689
|
/**
|
|
1681
1690
|
* Every link-bearing node in the document, in document order: links,
|
|
1682
1691
|
* images, definitions, and the reference forms resolved through the
|
|
@@ -1697,7 +1706,7 @@ export declare class MarkdownDocument extends MarkdownDocument_base {
|
|
|
1697
1706
|
*
|
|
1698
1707
|
* @remarks
|
|
1699
1708
|
* A string selector matches on the node's `type` tag and narrows the
|
|
1700
|
-
* result (`find("heading")` is `Heading
|
|
1709
|
+
* result (`find("heading")` is `Option<Heading>`); a type-guard
|
|
1701
1710
|
* predicate narrows the same way, and a plain predicate returns the wide
|
|
1702
1711
|
* `MarkdownNode` union. The returned node is the document's own — matched
|
|
1703
1712
|
* and returnable by identity, so it feeds `MarkdownFormat.modify`
|
|
@@ -1706,11 +1715,12 @@ export declare class MarkdownDocument extends MarkdownDocument_base {
|
|
|
1706
1715
|
* hand-built or foreign decoded tree) is a thrown defect.
|
|
1707
1716
|
*
|
|
1708
1717
|
* @param selector - A node `type` tag or a predicate over nodes.
|
|
1709
|
-
* @returns The first matching node in document order, or `
|
|
1718
|
+
* @returns The first matching node in document order, or `Option.none()`
|
|
1719
|
+
* when none matches.
|
|
1710
1720
|
*/
|
|
1711
|
-
find<T extends MarkdownNodeType>(selector: T): MarkdownNodeOfType<T
|
|
1712
|
-
find<T extends MarkdownNode>(selector: (node: MarkdownNode) => node is T): T
|
|
1713
|
-
find(selector: (node: MarkdownNode) => boolean): MarkdownNode
|
|
1721
|
+
find<T extends MarkdownNodeType>(selector: T): Option.Option<MarkdownNodeOfType<T>>;
|
|
1722
|
+
find<T extends MarkdownNode>(selector: (node: MarkdownNode) => node is T): Option.Option<T>;
|
|
1723
|
+
find(selector: (node: MarkdownNode) => boolean): Option.Option<MarkdownNode>;
|
|
1714
1724
|
/**
|
|
1715
1725
|
* Find every node matching a selector, in document pre-order — the same
|
|
1716
1726
|
* order {@link MarkdownVisitor} enters nodes, starting at the root itself.
|
|
@@ -2241,19 +2251,34 @@ export declare class SchemaResolver {
|
|
|
2241
2251
|
* data.
|
|
2242
2252
|
*
|
|
2243
2253
|
* @remarks
|
|
2244
|
-
*
|
|
2245
|
-
*
|
|
2246
|
-
* with
|
|
2254
|
+
* A lookup that can miss, so by default the success channel is an
|
|
2255
|
+
* `Option`: non-mapping data and a mapping without a `$schema` key both
|
|
2256
|
+
* carry no declaration and succeed with `Option.none()`, and the error
|
|
2257
|
+
* union is {@link SchemaDeclarationInvalidError} alone. Under
|
|
2258
|
+
* `requireDeclaration: true` the miss becomes a typed
|
|
2259
|
+
* {@link SchemaDeclarationMissingError} and the success is the bare
|
|
2260
|
+
* declaration, since it is statically always present. The two overloads
|
|
2261
|
+
* keep each caller's types honest: no `Option` to unwrap when the
|
|
2262
|
+
* declaration is required, no `SchemaDeclarationMissingError` to handle
|
|
2263
|
+
* when it is not. A present key is classified by
|
|
2264
|
+
* {@link SchemaResolver.classify}, and its
|
|
2265
|
+
* {@link SchemaDeclarationInvalidError} propagates unchanged. Hand an
|
|
2266
|
+
* optional declaration to a {@link FrontmatterSchemaResolver} with
|
|
2267
|
+
* `Option.getOrUndefined`, since the seam takes the absent case as
|
|
2268
|
+
* `undefined`; a required one passes straight through.
|
|
2247
2269
|
*
|
|
2248
2270
|
* @param data - The decoded frontmatter data.
|
|
2249
|
-
* @param options - `requireDeclaration` makes a missing `$schema` a
|
|
2250
|
-
* error.
|
|
2251
|
-
* @returns The classified declaration
|
|
2252
|
-
*
|
|
2271
|
+
* @param options - `requireDeclaration: true` makes a missing `$schema` a
|
|
2272
|
+
* typed error and narrows the success to the declaration itself.
|
|
2273
|
+
* @returns The classified declaration under `requireDeclaration`;
|
|
2274
|
+
* otherwise `Option.some` of it, or `Option.none()` when absent.
|
|
2253
2275
|
*/
|
|
2276
|
+
static declarationOf(data: unknown, options: {
|
|
2277
|
+
readonly requireDeclaration: true;
|
|
2278
|
+
}): Result.Result<SchemaDeclaration, SchemaDeclarationInvalidError | SchemaDeclarationMissingError>;
|
|
2254
2279
|
static declarationOf(data: unknown, options?: {
|
|
2255
|
-
readonly requireDeclaration?:
|
|
2256
|
-
}): Result.Result<SchemaDeclaration
|
|
2280
|
+
readonly requireDeclaration?: false;
|
|
2281
|
+
}): Result.Result<Option.Option<SchemaDeclaration>, SchemaDeclarationInvalidError>;
|
|
2257
2282
|
/**
|
|
2258
2283
|
* The package's one built-in resolver: a name-keyed registry with
|
|
2259
2284
|
* exact version-segment resolution.
|
|
@@ -2902,7 +2927,7 @@ export declare class MarkdownTransform {
|
|
|
2902
2927
|
* leaf the two fire back to back. Every variant carries `path` (child
|
|
2903
2928
|
* indexes from the root; the root's own path is empty) and `depth`
|
|
2904
2929
|
* (zero-based; the root is 0). `Error` is terminal: it carries the
|
|
2905
|
-
* depth-guard diagnostic for a
|
|
2930
|
+
* depth-guard diagnostic for a hand-constructed tree nested past
|
|
2906
2931
|
* `MAX_NESTING_DEPTH` and nothing follows it.
|
|
2907
2932
|
*
|
|
2908
2933
|
* @public
|
|
@@ -3046,8 +3071,9 @@ export declare const MarkdownVisitorEvent: {
|
|
|
3046
3071
|
}, "_tag">;
|
|
3047
3072
|
};
|
|
3048
3073
|
/**
|
|
3049
|
-
* Walks a parsed markdown tree as a lazy
|
|
3050
|
-
* document pre-order.
|
|
3074
|
+
* Walks a parsed markdown tree as a lazy sequence of `Enter`/`Exit` events in
|
|
3075
|
+
* document pre-order — synchronously with {@link MarkdownVisitor.walk}, or as
|
|
3076
|
+
* a `Stream` with {@link MarkdownVisitor.visit}. Not instantiable.
|
|
3051
3077
|
*
|
|
3052
3078
|
* @example
|
|
3053
3079
|
* ```ts
|
|
@@ -3069,6 +3095,45 @@ export declare const MarkdownVisitorEvent: {
|
|
|
3069
3095
|
*/
|
|
3070
3096
|
export declare class MarkdownVisitor {
|
|
3071
3097
|
private constructor();
|
|
3098
|
+
/**
|
|
3099
|
+
* Walk `root` synchronously, as a lazy `Iterable` of `MarkdownVisitorEvent`
|
|
3100
|
+
* in document pre-order: `Enter` and `Exit` for every node including the
|
|
3101
|
+
* root and every leaf. The primitive {@link MarkdownVisitor.visit} is
|
|
3102
|
+
* defined in terms of, so the two yield the same events in the same order.
|
|
3103
|
+
*
|
|
3104
|
+
* @remarks
|
|
3105
|
+
* For callers that cannot suspend — a lint rule whose host calls it
|
|
3106
|
+
* synchronously, a getter, a non-Effect adapter. Events are produced on
|
|
3107
|
+
* demand: a `for…of` that `break`s stops the walk without visiting the
|
|
3108
|
+
* rest of the tree. The iterable is re-iterable — every `[Symbol.iterator]()`
|
|
3109
|
+
* call starts a fresh walk from the root, so binding the result once and
|
|
3110
|
+
* iterating it twice walks the tree twice, independently.
|
|
3111
|
+
*
|
|
3112
|
+
* Not named `walkResult`: it returns no `Result`, and the walk has no
|
|
3113
|
+
* failure channel to put in one. A hand-constructed tree nested past the
|
|
3114
|
+
* depth cap surfaces as a terminal `MarkdownVisitorEvent` `Error` event
|
|
3115
|
+
* carrying a `NestingDepthExceeded` {@link MarkdownDiagnostic}, after which
|
|
3116
|
+
* nothing is yielded — the same posture as `visit`. A tree produced by
|
|
3117
|
+
* `Markdown.parse` or decoded by `Mdast.fromMdast` can never trip it.
|
|
3118
|
+
*
|
|
3119
|
+
* @example
|
|
3120
|
+
* ```ts
|
|
3121
|
+
* import { Markdown, MarkdownVisitor } from "@effected/markdown";
|
|
3122
|
+
* import { Result } from "effect";
|
|
3123
|
+
*
|
|
3124
|
+
* const root = Result.getOrThrow(Markdown.parseResult("# Hi\n\ntext\n"));
|
|
3125
|
+
* const entered: Array<string> = [];
|
|
3126
|
+
* for (const event of MarkdownVisitor.walk(root)) {
|
|
3127
|
+
* if (event._tag === "Enter") entered.push(event.node.type);
|
|
3128
|
+
* }
|
|
3129
|
+
* console.log(entered);
|
|
3130
|
+
* // => ["root", "heading", "text", "paragraph", "text"]
|
|
3131
|
+
* ```
|
|
3132
|
+
*
|
|
3133
|
+
* @param root - The tree to walk.
|
|
3134
|
+
* @returns A lazy, re-iterable `Iterable` of `MarkdownVisitorEvent`.
|
|
3135
|
+
*/
|
|
3136
|
+
static walk(root: Root): Iterable<MarkdownVisitorEvent>;
|
|
3072
3137
|
/**
|
|
3073
3138
|
* Create a lazy `Stream` of `MarkdownVisitorEvent` walking `root` in
|
|
3074
3139
|
* document pre-order: `Enter` and `Exit` for every node including the
|
|
@@ -3079,10 +3144,15 @@ export declare class MarkdownVisitor {
|
|
|
3079
3144
|
* @remarks
|
|
3080
3145
|
* Infallible at the type level. A tree produced by `Markdown.parse` can
|
|
3081
3146
|
* never trip the walk's depth guard — the parser refuses deeper input —
|
|
3082
|
-
*
|
|
3083
|
-
*
|
|
3084
|
-
*
|
|
3085
|
-
*
|
|
3147
|
+
* and neither can a decoded foreign tree (`Mdast.fromMdast`), because the
|
|
3148
|
+
* decode boundary refuses it first; only a hand-constructed tree can trip
|
|
3149
|
+
* the guard. The trip surfaces as a terminal `MarkdownVisitorEvent`
|
|
3150
|
+
* `Error` event carrying a `NestingDepthExceeded` {@link MarkdownDiagnostic},
|
|
3151
|
+
* mirroring the typed failure `Markdown.stringify` produces for the same
|
|
3152
|
+
* tree.
|
|
3153
|
+
*
|
|
3154
|
+
* Defined as `Stream.fromIterable(MarkdownVisitor.walk(root))`: every run
|
|
3155
|
+
* of the stream walks afresh, and the two forms never diverge.
|
|
3086
3156
|
*
|
|
3087
3157
|
* @param root - The tree to walk.
|
|
3088
3158
|
* @returns A lazy `Stream` of `MarkdownVisitorEvent`, infallible at the type
|
|
@@ -3221,6 +3291,14 @@ export declare class Mdast {
|
|
|
3221
3291
|
* plain tree built for `Markdown.stringify` can carry it straight in. A
|
|
3222
3292
|
* value outside `"canonical" | "literal"` fails the decode typed.
|
|
3223
3293
|
*
|
|
3294
|
+
* **Nesting is capped at 256 levels below the root**, the same cap the
|
|
3295
|
+
* parser and `Markdown.stringify` enforce. A tree with any node deeper
|
|
3296
|
+
* than that, leaves included, fails typed with a `SchemaIssue.InvalidValue`
|
|
3297
|
+
* issue before the schema decode runs, so hostile depth is never a defect.
|
|
3298
|
+
* Because leaves count, a document the parser accepts can still be
|
|
3299
|
+
* refused here: `"> ".repeat(255) + "x"` parses, but its `text` leaf sits
|
|
3300
|
+
* at depth 257, and `Markdown.stringify` refuses that tree too.
|
|
3301
|
+
*
|
|
3224
3302
|
* @param input - A plain mdast tree, typically a `root`.
|
|
3225
3303
|
* @returns A `Result` succeeding with the decoded {@link Root}, or
|
|
3226
3304
|
* failing with {@link MdastDecodeError} carrying the structured issue.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@effected/markdown",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.22.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "CommonMark and GFM markdown parse, edit and transform schemas for Effect",
|
|
6
6
|
"keywords": [
|
|
@@ -39,9 +39,9 @@
|
|
|
39
39
|
"./package.json": "./package.json"
|
|
40
40
|
},
|
|
41
41
|
"peerDependencies": {
|
|
42
|
-
"@effected/jsonc": "^0.
|
|
43
|
-
"@effected/toml": "^0.
|
|
44
|
-
"@effected/yaml": "^0.
|
|
42
|
+
"@effected/jsonc": "^0.20.0",
|
|
43
|
+
"@effected/toml": "^0.16.0",
|
|
44
|
+
"@effected/yaml": "^0.25.0",
|
|
45
45
|
"effect": "^4.0.0"
|
|
46
46
|
},
|
|
47
47
|
"peerDependenciesMeta": {
|