@knpkv/confluence-to-markdown 0.5.0 → 0.6.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.
Files changed (108) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/README.md +45 -10
  3. package/dist/ConfluenceAuth.d.ts.map +1 -1
  4. package/dist/ConfluenceAuth.js +12 -22
  5. package/dist/ConfluenceAuth.js.map +1 -1
  6. package/dist/ConfluenceClient.d.ts +13 -3
  7. package/dist/ConfluenceClient.d.ts.map +1 -1
  8. package/dist/ConfluenceClient.js +34 -70
  9. package/dist/ConfluenceClient.js.map +1 -1
  10. package/dist/ConfluenceError.d.ts +12 -12
  11. package/dist/GitError.d.ts +5 -5
  12. package/dist/GitService.d.ts.map +1 -1
  13. package/dist/GitService.js +0 -3
  14. package/dist/GitService.js.map +1 -1
  15. package/dist/SchemaConverterError.d.ts +3 -3
  16. package/dist/ast/BlockNode.d.ts +48 -33
  17. package/dist/ast/BlockNode.d.ts.map +1 -1
  18. package/dist/ast/BlockNode.js +11 -2
  19. package/dist/ast/BlockNode.js.map +1 -1
  20. package/dist/ast/Document.d.ts +30 -2
  21. package/dist/ast/Document.d.ts.map +1 -1
  22. package/dist/parsers/ConfluenceParser.d.ts.map +1 -1
  23. package/dist/parsers/ConfluenceParser.js +7 -12
  24. package/dist/parsers/ConfluenceParser.js.map +1 -1
  25. package/dist/parsers/MarkdownParser.js +8 -117
  26. package/dist/parsers/MarkdownParser.js.map +1 -1
  27. package/dist/parsers/preprocessing/ConfluencePreprocessing.d.ts +23 -0
  28. package/dist/parsers/preprocessing/ConfluencePreprocessing.d.ts.map +1 -0
  29. package/dist/parsers/preprocessing/ConfluencePreprocessing.js +323 -0
  30. package/dist/parsers/preprocessing/ConfluencePreprocessing.js.map +1 -0
  31. package/dist/parsers/preprocessing/index.d.ts +7 -0
  32. package/dist/parsers/preprocessing/index.d.ts.map +1 -0
  33. package/dist/parsers/preprocessing/index.js +7 -0
  34. package/dist/parsers/preprocessing/index.js.map +1 -0
  35. package/dist/schemas/preprocessing/ConfluencePreprocessor.d.ts +29 -0
  36. package/dist/schemas/preprocessing/ConfluencePreprocessor.d.ts.map +1 -1
  37. package/dist/schemas/preprocessing/ConfluencePreprocessor.js +5 -15
  38. package/dist/schemas/preprocessing/ConfluencePreprocessor.js.map +1 -1
  39. package/dist/serializers/ConfluenceSerializer.js +0 -9
  40. package/dist/serializers/ConfluenceSerializer.js.map +1 -1
  41. package/dist/serializers/MarkdownSerializer.js +9 -49
  42. package/dist/serializers/MarkdownSerializer.js.map +1 -1
  43. package/package.json +35 -26
  44. package/src/AdfPlaceholders.ts +266 -0
  45. package/src/AdfSchemaValidator.ts +67 -0
  46. package/src/AdfWalker.ts +511 -0
  47. package/src/AtlaskitTransformers.ts +72 -0
  48. package/src/ConfluenceClient.ts +4 -4
  49. package/src/ConfluenceError.ts +65 -3
  50. package/src/MarkdownConverter.ts +106 -139
  51. package/src/Schemas.ts +4 -4
  52. package/src/SyncEngine.ts +130 -83
  53. package/src/atlaskit-adf-schema.d.ts +3 -0
  54. package/src/commands/clone.ts +8 -1
  55. package/src/commands/layers.ts +11 -4
  56. package/src/index.ts +3 -18
  57. package/test/AdfPlaceholders.test.ts +295 -0
  58. package/test/AdfSchemaValidator.test.ts +34 -0
  59. package/test/AdfWalker.test.ts +530 -0
  60. package/test/AtlaskitTransformers.test.ts +25 -0
  61. package/test/MarkdownConverter.test.ts +120 -105
  62. package/test/RoundTrip.test.ts +266 -0
  63. package/LICENSE +0 -21
  64. package/src/SchemaConverterError.ts +0 -108
  65. package/src/ast/BlockNode.ts +0 -469
  66. package/src/ast/Document.ts +0 -90
  67. package/src/ast/InlineNode.ts +0 -323
  68. package/src/ast/MacroNode.ts +0 -245
  69. package/src/ast/index.ts +0 -83
  70. package/src/parsers/ConfluenceParser.ts +0 -956
  71. package/src/parsers/MarkdownParser.ts +0 -1338
  72. package/src/parsers/index.ts +0 -8
  73. package/src/schemas/ConfluenceSchema.ts +0 -56
  74. package/src/schemas/ConversionSchema.ts +0 -318
  75. package/src/schemas/MarkdownSchema.ts +0 -56
  76. package/src/schemas/hast/HastFromHtml.ts +0 -153
  77. package/src/schemas/hast/HastSchema.ts +0 -274
  78. package/src/schemas/hast/index.ts +0 -35
  79. package/src/schemas/index.ts +0 -20
  80. package/src/schemas/mdast/MdastFromMarkdown.ts +0 -118
  81. package/src/schemas/mdast/MdastSchema.ts +0 -566
  82. package/src/schemas/mdast/index.ts +0 -59
  83. package/src/schemas/mdast/mdastToString.ts +0 -102
  84. package/src/schemas/nodes/block/BlockSchema.ts +0 -773
  85. package/src/schemas/nodes/block/index.ts +0 -13
  86. package/src/schemas/nodes/index.ts +0 -20
  87. package/src/schemas/nodes/inline/InlineSchema.ts +0 -523
  88. package/src/schemas/nodes/inline/index.ts +0 -14
  89. package/src/schemas/nodes/macro/MacroSchema.ts +0 -226
  90. package/src/schemas/nodes/macro/index.ts +0 -6
  91. package/src/schemas/preprocessing/ConfluencePreprocessor.ts +0 -455
  92. package/src/schemas/preprocessing/index.ts +0 -8
  93. package/src/serializers/ConfluenceSerializer.ts +0 -737
  94. package/src/serializers/MarkdownSerializer.ts +0 -543
  95. package/src/serializers/index.ts +0 -8
  96. package/test/ast/BlockNode.test.ts +0 -265
  97. package/test/ast/Document.test.ts +0 -126
  98. package/test/ast/InlineNode.test.ts +0 -161
  99. package/test/fixtures/integration-test.html.fixture +0 -103
  100. package/test/fixtures/integration-test.md.expected +0 -257
  101. package/test/parsers/ConfluenceParser.test.ts +0 -452
  102. package/test/schemas/ConfluencePreprocessor.test.ts +0 -180
  103. package/test/schemas/ConversionSchema.test.ts +0 -159
  104. package/test/schemas/HastSchema.test.ts +0 -138
  105. package/test/schemas/MdastSchema.test.ts +0 -145
  106. package/test/schemas/nodes/block/BlockSchema.test.ts +0 -173
  107. package/test/schemas/nodes/inline/InlineSchema.test.ts +0 -198
  108. package/test/schemas/nodes/macro/MacroSchema.test.ts +0 -142
@@ -1,469 +0,0 @@
1
- /**
2
- * Block-level AST node types for structural content.
3
- *
4
- * @module
5
- */
6
- import * as Schema from "effect/Schema"
7
- import { InlineNode, type InlineNode as InlineNodeType } from "./InlineNode.js"
8
-
9
- /**
10
- * Schema version for migration support.
11
- *
12
- * @category Version
13
- */
14
- export const SchemaVersion = Schema.Number.pipe(
15
- Schema.int(),
16
- Schema.positive(),
17
- Schema.optionalWith({ default: () => 1 })
18
- )
19
-
20
- /**
21
- * Optional raw Confluence HTML for exact roundtrip preservation.
22
- * When present, the Confluence serializer will output this instead of reconstructing.
23
- *
24
- * @category BlockNode
25
- */
26
- export const RawConfluence = Schema.optional(Schema.String)
27
-
28
- /**
29
- * Heading element (h1-h6).
30
- *
31
- * @example
32
- * ```typescript
33
- * import { Heading, Text } from "@knpkv/confluence-to-markdown/ast"
34
- *
35
- * const h1 = new Heading({
36
- * level: 1,
37
- * children: [new Text({ value: "Introduction" })]
38
- * })
39
- * ```
40
- *
41
- * @category BlockNode
42
- */
43
- export class Heading extends Schema.TaggedClass<Heading>()("Heading", {
44
- version: SchemaVersion,
45
- level: Schema.Literal(1, 2, 3, 4, 5, 6),
46
- children: Schema.Array(InlineNode),
47
- rawConfluence: RawConfluence
48
- }) {}
49
-
50
- /**
51
- * Text alignment options.
52
- *
53
- * @category BlockNode
54
- */
55
- export const TextAlignment = Schema.Literal("left", "center", "right")
56
-
57
- /**
58
- * Type for TextAlignment.
59
- *
60
- * @category Types
61
- */
62
- export type TextAlignment = Schema.Schema.Type<typeof TextAlignment>
63
-
64
- /**
65
- * Paragraph element with optional alignment and indentation.
66
- *
67
- * @example
68
- * ```typescript
69
- * import { Paragraph, Text } from "@knpkv/confluence-to-markdown/ast"
70
- *
71
- * const para = new Paragraph({
72
- * children: [new Text({ value: "Hello world" })]
73
- * })
74
- *
75
- * // With alignment
76
- * const centered = new Paragraph({
77
- * alignment: "center",
78
- * children: [new Text({ value: "Centered text" })]
79
- * })
80
- *
81
- * // With indentation (in pixels)
82
- * const indented = new Paragraph({
83
- * indent: 30,
84
- * children: [new Text({ value: "Indented text" })]
85
- * })
86
- * ```
87
- *
88
- * @category BlockNode
89
- */
90
- export class Paragraph extends Schema.TaggedClass<Paragraph>()("Paragraph", {
91
- version: SchemaVersion,
92
- alignment: Schema.optional(TextAlignment),
93
- indent: Schema.optional(Schema.Number),
94
- children: Schema.Array(InlineNode),
95
- rawConfluence: RawConfluence
96
- }) {}
97
-
98
- /**
99
- * Code block with optional language.
100
- *
101
- * @example
102
- * ```typescript
103
- * import { CodeBlock } from "@knpkv/confluence-to-markdown/ast"
104
- *
105
- * const code = new CodeBlock({
106
- * language: "typescript",
107
- * code: "const x = 1"
108
- * })
109
- * ```
110
- *
111
- * @category BlockNode
112
- */
113
- export class CodeBlock extends Schema.TaggedClass<CodeBlock>()("CodeBlock", {
114
- version: SchemaVersion,
115
- language: Schema.optional(Schema.String),
116
- code: Schema.String,
117
- rawConfluence: RawConfluence
118
- }) {}
119
-
120
- /**
121
- * Thematic break / horizontal rule.
122
- *
123
- * @example
124
- * ```typescript
125
- * import { ThematicBreak } from "@knpkv/confluence-to-markdown/ast"
126
- *
127
- * const hr = new ThematicBreak({})
128
- * ```
129
- *
130
- * @category BlockNode
131
- */
132
- export class ThematicBreak extends Schema.TaggedClass<ThematicBreak>()("ThematicBreak", {
133
- rawConfluence: RawConfluence
134
- }) {}
135
-
136
- /**
137
- * Attachment reference for images stored in Confluence.
138
- *
139
- * @category BlockNode
140
- */
141
- export const ImageAttachment = Schema.Struct({
142
- filename: Schema.String,
143
- version: Schema.optional(Schema.Number)
144
- })
145
-
146
- /**
147
- * Type for ImageAttachment.
148
- *
149
- * @category Types
150
- */
151
- export type ImageAttachment = Schema.Schema.Type<typeof ImageAttachment>
152
-
153
- /**
154
- * Image element with support for both URL and Confluence attachments.
155
- *
156
- * @example
157
- * ```typescript
158
- * import { Image } from "@knpkv/confluence-to-markdown/ast"
159
- *
160
- * // URL-based image
161
- * const img = new Image({
162
- * src: "https://example.com/image.png",
163
- * alt: "Example image"
164
- * })
165
- *
166
- * // Confluence attachment
167
- * const attachment = new Image({
168
- * attachment: { filename: "logo.svg" },
169
- * align: "center",
170
- * width: 250
171
- * })
172
- * ```
173
- *
174
- * @category BlockNode
175
- */
176
- export class Image extends Schema.TaggedClass<Image>()("Image", {
177
- version: SchemaVersion,
178
- src: Schema.optional(Schema.String),
179
- attachment: Schema.optional(ImageAttachment),
180
- alt: Schema.optional(Schema.String),
181
- title: Schema.optional(Schema.String),
182
- align: Schema.optional(Schema.String),
183
- width: Schema.optional(Schema.Number),
184
- rawConfluence: RawConfluence
185
- }) {}
186
-
187
- /**
188
- * Table cell element.
189
- *
190
- * @example
191
- * ```typescript
192
- * import { TableCell, Text } from "@knpkv/confluence-to-markdown/ast"
193
- *
194
- * const cell = new TableCell({
195
- * isHeader: true,
196
- * children: [new Text({ value: "Header" })]
197
- * })
198
- * ```
199
- *
200
- * @category BlockNode
201
- */
202
- export class TableCell extends Schema.TaggedClass<TableCell>()("TableCell", {
203
- isHeader: Schema.optionalWith(Schema.Boolean, { default: () => false }),
204
- children: Schema.Array(InlineNode),
205
- rawConfluence: RawConfluence
206
- }) {}
207
-
208
- /**
209
- * Table row element.
210
- *
211
- * @example
212
- * ```typescript
213
- * import { TableRow, TableCell, Text } from "@knpkv/confluence-to-markdown/ast"
214
- *
215
- * const row = new TableRow({
216
- * cells: [
217
- * new TableCell({ children: [new Text({ value: "A" })] }),
218
- * new TableCell({ children: [new Text({ value: "B" })] })
219
- * ]
220
- * })
221
- * ```
222
- *
223
- * @category BlockNode
224
- */
225
- export class TableRow extends Schema.TaggedClass<TableRow>()("TableRow", {
226
- cells: Schema.Array(TableCell),
227
- rawConfluence: RawConfluence
228
- }) {}
229
-
230
- /**
231
- * Table element with optional header row.
232
- *
233
- * @example
234
- * ```typescript
235
- * import { Table, TableRow, TableCell, Text } from "@knpkv/confluence-to-markdown/ast"
236
- *
237
- * const table = new Table({
238
- * header: new TableRow({
239
- * cells: [new TableCell({ isHeader: true, children: [new Text({ value: "Col" })] })]
240
- * }),
241
- * rows: []
242
- * })
243
- * ```
244
- *
245
- * @category BlockNode
246
- */
247
- export class Table extends Schema.TaggedClass<Table>()("Table", {
248
- version: SchemaVersion,
249
- header: Schema.optional(TableRow),
250
- rows: Schema.Array(TableRow),
251
- rawConfluence: RawConfluence
252
- }) {}
253
-
254
- /**
255
- * Unsupported block element - preserves raw content for round-tripping.
256
- *
257
- * @example
258
- * ```typescript
259
- * import { UnsupportedBlock } from "@knpkv/confluence-to-markdown/ast"
260
- *
261
- * const unknown = new UnsupportedBlock({
262
- * rawHtml: "<custom-block>content</custom-block>",
263
- * source: "confluence"
264
- * })
265
- * ```
266
- *
267
- * @category BlockNode
268
- */
269
- export class UnsupportedBlock extends Schema.TaggedClass<UnsupportedBlock>()("UnsupportedBlock", {
270
- rawHtml: Schema.optional(Schema.String),
271
- rawMarkdown: Schema.optional(Schema.String),
272
- source: Schema.Literal("confluence", "markdown")
273
- }) {}
274
-
275
- // Non-recursive block nodes
276
- const SimpleBlockNode = Schema.Union(
277
- Heading,
278
- Paragraph,
279
- CodeBlock,
280
- ThematicBreak,
281
- Image,
282
- Table,
283
- UnsupportedBlock
284
- )
285
-
286
- /**
287
- * Block quote element with nested block content.
288
- *
289
- * @category BlockNode
290
- */
291
- export const BlockQuote = Schema.Struct({
292
- _tag: Schema.Literal("BlockQuote"),
293
- version: SchemaVersion,
294
- children: Schema.Array(SimpleBlockNode),
295
- rawConfluence: RawConfluence
296
- })
297
-
298
- /**
299
- * Type for BlockQuote.
300
- *
301
- * @category Types
302
- */
303
- export type BlockQuote = Schema.Schema.Type<typeof BlockQuote>
304
-
305
- /**
306
- * List item with nested block content.
307
- *
308
- * Children may include any simple block as well as nested {@link List} elements.
309
- *
310
- * @category BlockNode
311
- */
312
- export interface ListItem {
313
- readonly _tag: "ListItem"
314
- readonly checked?: boolean | undefined
315
- readonly children: ReadonlyArray<
316
- Heading | Paragraph | CodeBlock | ThematicBreak | Image | Table | UnsupportedBlock | List
317
- >
318
- readonly rawConfluence?: string | undefined
319
- }
320
-
321
- /**
322
- * List element (ordered or unordered).
323
- *
324
- * @category BlockNode
325
- */
326
- export interface List {
327
- readonly _tag: "List"
328
- readonly version: number
329
- readonly ordered: boolean
330
- readonly start?: number | undefined
331
- readonly children: ReadonlyArray<ListItem>
332
- readonly rawConfluence?: string | undefined
333
- }
334
-
335
- // Encoded shapes (input to decode): `version` is optional via SchemaVersion's
336
- // optionalWith({ default }), and the recursive cycle is List → ListItem → List.
337
- export interface ListEncoded {
338
- readonly _tag: "List"
339
- readonly version?: number | undefined
340
- readonly ordered: boolean
341
- readonly start?: number | undefined
342
- readonly children: ReadonlyArray<ListItemEncoded>
343
- readonly rawConfluence?: string | undefined
344
- }
345
-
346
- export interface ListItemEncoded {
347
- readonly _tag: "ListItem"
348
- readonly checked?: boolean | undefined
349
- readonly children: ReadonlyArray<
350
- | Schema.Schema.Encoded<typeof Heading>
351
- | Schema.Schema.Encoded<typeof Paragraph>
352
- | Schema.Schema.Encoded<typeof CodeBlock>
353
- | Schema.Schema.Encoded<typeof ThematicBreak>
354
- | Schema.Schema.Encoded<typeof Image>
355
- | Schema.Schema.Encoded<typeof Table>
356
- | Schema.Schema.Encoded<typeof UnsupportedBlock>
357
- | ListEncoded
358
- >
359
- readonly rawConfluence?: string | undefined
360
- }
361
-
362
- const ListItemChild = Schema.Union(
363
- Heading,
364
- Paragraph,
365
- CodeBlock,
366
- ThematicBreak,
367
- Image,
368
- Table,
369
- UnsupportedBlock,
370
- Schema.suspend((): Schema.Schema<List, ListEncoded> => List)
371
- )
372
-
373
- export const ListItem: Schema.Schema<ListItem, ListItemEncoded> = Schema.Struct({
374
- _tag: Schema.Literal("ListItem"),
375
- checked: Schema.optional(Schema.Boolean),
376
- children: Schema.Array(ListItemChild),
377
- rawConfluence: RawConfluence
378
- })
379
-
380
- export const List: Schema.Schema<List, ListEncoded> = Schema.Struct({
381
- _tag: Schema.Literal("List"),
382
- version: SchemaVersion,
383
- ordered: Schema.Boolean,
384
- start: Schema.optional(Schema.Number),
385
- children: Schema.Array(ListItem),
386
- rawConfluence: RawConfluence
387
- })
388
-
389
- /**
390
- * Task item with status for Confluence task lists.
391
- *
392
- * @category BlockNode
393
- */
394
- export const TaskItem = Schema.Struct({
395
- _tag: Schema.Literal("TaskItem"),
396
- id: Schema.String,
397
- uuid: Schema.String,
398
- status: Schema.Literal("incomplete", "complete"),
399
- body: Schema.Array(InlineNode),
400
- rawConfluence: RawConfluence
401
- })
402
-
403
- /**
404
- * Type for TaskItem.
405
- *
406
- * @category Types
407
- */
408
- export type TaskItem = Schema.Schema.Type<typeof TaskItem>
409
-
410
- /**
411
- * Task list (Confluence action items).
412
- *
413
- * @category BlockNode
414
- */
415
- export const TaskList = Schema.Struct({
416
- _tag: Schema.Literal("TaskList"),
417
- version: SchemaVersion,
418
- children: Schema.Array(TaskItem),
419
- rawConfluence: RawConfluence
420
- })
421
-
422
- /**
423
- * Type for TaskList.
424
- *
425
- * @category Types
426
- */
427
- export type TaskList = Schema.Schema.Type<typeof TaskList>
428
-
429
- /**
430
- * Union of all block node types.
431
- *
432
- * @category BlockNode
433
- */
434
- export const BlockNode = Schema.Union(
435
- Heading,
436
- Paragraph,
437
- CodeBlock,
438
- ThematicBreak,
439
- BlockQuote,
440
- Image,
441
- Table,
442
- List,
443
- TaskList,
444
- UnsupportedBlock
445
- )
446
-
447
- /**
448
- * Type for block nodes.
449
- *
450
- * @category Types
451
- */
452
- export type BlockNode =
453
- | Heading
454
- | Paragraph
455
- | CodeBlock
456
- | ThematicBreak
457
- | BlockQuote
458
- | Image
459
- | Table
460
- | List
461
- | TaskList
462
- | UnsupportedBlock
463
-
464
- /**
465
- * Type helper for inline node children in blocks.
466
- *
467
- * @category Types
468
- */
469
- export type { InlineNodeType as InlineNode }
@@ -1,90 +0,0 @@
1
- /**
2
- * Document AST node - the root of the AST tree.
3
- *
4
- * @module
5
- */
6
- import * as Schema from "effect/Schema"
7
- import { BlockNode, type BlockNode as BlockNodeType, RawConfluence, SchemaVersion } from "./BlockNode.js"
8
- import { MacroNode, type MacroNode as MacroNodeType } from "./MacroNode.js"
9
-
10
- /**
11
- * Document node - represents a Block or Macro node.
12
- *
13
- * @category Document
14
- */
15
- export const DocumentNode = Schema.Union(BlockNode, MacroNode)
16
-
17
- /**
18
- * Type for document nodes.
19
- *
20
- * @category Types
21
- */
22
- export type DocumentNode = BlockNodeType | MacroNodeType
23
-
24
- /**
25
- * Document schema - the root AST node.
26
- *
27
- * @example
28
- * ```typescript
29
- * import { Document, Heading, Paragraph, Text } from "@knpkv/confluence-to-markdown/ast"
30
- * import * as Schema from "effect/Schema"
31
- *
32
- * const doc = {
33
- * version: 1,
34
- * children: [
35
- * new Heading({ level: 1, children: [new Text({ value: "Title" })] }),
36
- * new Paragraph({ children: [new Text({ value: "Content" })] })
37
- * ]
38
- * }
39
- *
40
- * const validated = Schema.decodeUnknownSync(Document)(doc)
41
- * ```
42
- *
43
- * @category Document
44
- */
45
- export const Document = Schema.Struct({
46
- version: SchemaVersion,
47
- children: Schema.Array(DocumentNode),
48
- rawConfluence: RawConfluence
49
- })
50
-
51
- /**
52
- * Type for Document.
53
- *
54
- * @category Types
55
- */
56
- export type Document = Schema.Schema.Type<typeof Document>
57
-
58
- /**
59
- * Create a new Document with default version.
60
- *
61
- * @example
62
- * ```typescript
63
- * import { makeDocument, Heading, Text } from "@knpkv/confluence-to-markdown/ast"
64
- *
65
- * const doc = makeDocument([
66
- * new Heading({ level: 1, children: [new Text({ value: "Hello" })] })
67
- * ])
68
- * ```
69
- *
70
- * @category Constructors
71
- */
72
- export const makeDocument = (
73
- children: ReadonlyArray<DocumentNode>,
74
- rawConfluence?: string
75
- ): Document => ({
76
- version: 1,
77
- children,
78
- ...(rawConfluence !== undefined ? { rawConfluence } : {})
79
- })
80
-
81
- /**
82
- * Check if a node is a Document.
83
- *
84
- * @category Guards
85
- */
86
- export const isDocument = (value: unknown): value is Document =>
87
- typeof value === "object" &&
88
- value !== null &&
89
- "children" in value &&
90
- Array.isArray((value as Document).children)