@knpkv/atlassian-common 0.2.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 (121) hide show
  1. package/CHANGELOG.md +11 -0
  2. package/LICENSE +21 -0
  3. package/README.md +107 -0
  4. package/dist/Brand.d.ts +118 -0
  5. package/dist/Brand.d.ts.map +1 -0
  6. package/dist/Brand.js +107 -0
  7. package/dist/Brand.js.map +1 -0
  8. package/dist/Hash.d.ts +53 -0
  9. package/dist/Hash.d.ts.map +1 -0
  10. package/dist/Hash.js +63 -0
  11. package/dist/Hash.js.map +1 -0
  12. package/dist/SerializeError.d.ts +48 -0
  13. package/dist/SerializeError.d.ts.map +1 -0
  14. package/dist/SerializeError.js +42 -0
  15. package/dist/SerializeError.js.map +1 -0
  16. package/dist/ast/BlockNode.d.ts +429 -0
  17. package/dist/ast/BlockNode.d.ts.map +1 -0
  18. package/dist/ast/BlockNode.js +279 -0
  19. package/dist/ast/BlockNode.js.map +1 -0
  20. package/dist/ast/Document.d.ts +255 -0
  21. package/dist/ast/Document.d.ts.map +1 -0
  22. package/dist/ast/Document.js +80 -0
  23. package/dist/ast/Document.js.map +1 -0
  24. package/dist/ast/InlineNode.d.ts +483 -0
  25. package/dist/ast/InlineNode.d.ts.map +1 -0
  26. package/dist/ast/InlineNode.js +268 -0
  27. package/dist/ast/InlineNode.js.map +1 -0
  28. package/dist/ast/MacroNode.d.ts +214 -0
  29. package/dist/ast/MacroNode.d.ts.map +1 -0
  30. package/dist/ast/MacroNode.js +110 -0
  31. package/dist/ast/MacroNode.js.map +1 -0
  32. package/dist/ast/index.d.ts +10 -0
  33. package/dist/ast/index.d.ts.map +1 -0
  34. package/dist/ast/index.js +14 -0
  35. package/dist/ast/index.js.map +1 -0
  36. package/dist/auth/OAuthEndpoints.d.ts +95 -0
  37. package/dist/auth/OAuthEndpoints.d.ts.map +1 -0
  38. package/dist/auth/OAuthEndpoints.js +112 -0
  39. package/dist/auth/OAuthEndpoints.js.map +1 -0
  40. package/dist/auth/OAuthErrors.d.ts +76 -0
  41. package/dist/auth/OAuthErrors.d.ts.map +1 -0
  42. package/dist/auth/OAuthErrors.js +85 -0
  43. package/dist/auth/OAuthErrors.js.map +1 -0
  44. package/dist/auth/OAuthOperations.d.ts +87 -0
  45. package/dist/auth/OAuthOperations.d.ts.map +1 -0
  46. package/dist/auth/OAuthOperations.js +169 -0
  47. package/dist/auth/OAuthOperations.js.map +1 -0
  48. package/dist/auth/OAuthResponseSchemas.d.ts +65 -0
  49. package/dist/auth/OAuthResponseSchemas.d.ts.map +1 -0
  50. package/dist/auth/OAuthResponseSchemas.js +47 -0
  51. package/dist/auth/OAuthResponseSchemas.js.map +1 -0
  52. package/dist/auth/index.d.ts +11 -0
  53. package/dist/auth/index.d.ts.map +1 -0
  54. package/dist/auth/index.js +16 -0
  55. package/dist/auth/index.js.map +1 -0
  56. package/dist/auth/uuid.d.ts +14 -0
  57. package/dist/auth/uuid.d.ts.map +1 -0
  58. package/dist/auth/uuid.js +14 -0
  59. package/dist/auth/uuid.js.map +1 -0
  60. package/dist/config/ConfigPaths.d.ts +95 -0
  61. package/dist/config/ConfigPaths.d.ts.map +1 -0
  62. package/dist/config/ConfigPaths.js +102 -0
  63. package/dist/config/ConfigPaths.js.map +1 -0
  64. package/dist/config/OAuthSchemas.d.ts +144 -0
  65. package/dist/config/OAuthSchemas.d.ts.map +1 -0
  66. package/dist/config/OAuthSchemas.js +107 -0
  67. package/dist/config/OAuthSchemas.js.map +1 -0
  68. package/dist/config/TokenStorage.d.ts +96 -0
  69. package/dist/config/TokenStorage.d.ts.map +1 -0
  70. package/dist/config/TokenStorage.js +128 -0
  71. package/dist/config/TokenStorage.js.map +1 -0
  72. package/dist/config/index.d.ts +9 -0
  73. package/dist/config/index.d.ts.map +1 -0
  74. package/dist/config/index.js +12 -0
  75. package/dist/config/index.js.map +1 -0
  76. package/dist/index.d.ts +13 -0
  77. package/dist/index.d.ts.map +1 -0
  78. package/dist/index.js +20 -0
  79. package/dist/index.js.map +1 -0
  80. package/dist/parsers/index.d.ts +7 -0
  81. package/dist/parsers/index.d.ts.map +1 -0
  82. package/dist/parsers/index.js +8 -0
  83. package/dist/parsers/index.js.map +1 -0
  84. package/dist/serializers/MarkdownSerializer.d.ts +63 -0
  85. package/dist/serializers/MarkdownSerializer.d.ts.map +1 -0
  86. package/dist/serializers/MarkdownSerializer.js +367 -0
  87. package/dist/serializers/MarkdownSerializer.js.map +1 -0
  88. package/dist/serializers/index.d.ts +7 -0
  89. package/dist/serializers/index.d.ts.map +1 -0
  90. package/dist/serializers/index.js +7 -0
  91. package/dist/serializers/index.js.map +1 -0
  92. package/package.json +86 -0
  93. package/src/Brand.ts +142 -0
  94. package/src/Hash.ts +68 -0
  95. package/src/SerializeError.ts +50 -0
  96. package/src/ast/BlockNode.ts +386 -0
  97. package/src/ast/Document.ts +101 -0
  98. package/src/ast/InlineNode.ts +328 -0
  99. package/src/ast/MacroNode.ts +172 -0
  100. package/src/ast/index.ts +87 -0
  101. package/src/auth/OAuthEndpoints.ts +142 -0
  102. package/src/auth/OAuthErrors.ts +101 -0
  103. package/src/auth/OAuthOperations.ts +276 -0
  104. package/src/auth/OAuthResponseSchemas.ts +70 -0
  105. package/src/auth/index.ts +47 -0
  106. package/src/auth/uuid.ts +14 -0
  107. package/src/config/ConfigPaths.ts +179 -0
  108. package/src/config/OAuthSchemas.ts +146 -0
  109. package/src/config/TokenStorage.ts +228 -0
  110. package/src/config/index.ts +43 -0
  111. package/src/index.ts +34 -0
  112. package/src/parsers/index.ts +8 -0
  113. package/src/serializers/MarkdownSerializer.ts +498 -0
  114. package/src/serializers/index.ts +7 -0
  115. package/test/Brand.test.ts +90 -0
  116. package/test/MarkdownSerializer.test.ts +82 -0
  117. package/test/OAuthEndpoints.test.ts +109 -0
  118. package/test/OAuthOperations.test.ts +315 -0
  119. package/tsconfig.json +11 -0
  120. package/tsconfig.tsbuildinfo +1 -0
  121. package/vitest.config.ts +12 -0
package/src/Hash.ts ADDED
@@ -0,0 +1,68 @@
1
+ /**
2
+ * SHA256 content hashing via Node.js crypto, returning branded {@link ContentHash} values.
3
+ *
4
+ * **Mental model**
5
+ *
6
+ * - **Effect-wrapped crypto**: {@link hashContent} and {@link hashBuffer} return
7
+ * `Effect<ContentHash>` so hashing composes into pipelines without try/catch.
8
+ * - **Sync escape hatch**: {@link hashContentSync} for contexts where Effect isn't needed.
9
+ *
10
+ * **Common tasks**
11
+ *
12
+ * - Hash a string: {@link hashContent}
13
+ * - Hash a buffer: {@link hashBuffer}
14
+ * - Compare hashes: {@link hashEquals}
15
+ *
16
+ * @module
17
+ */
18
+ import * as Effect from "effect/Effect"
19
+ import * as crypto from "node:crypto"
20
+ import { ContentHash } from "./Brand.js"
21
+
22
+ /**
23
+ * Compute SHA256 hash of a string.
24
+ *
25
+ * @example
26
+ * ```typescript
27
+ * import { hashContent } from "@knpkv/atlassian-common"
28
+ * import * as Effect from "effect/Effect"
29
+ *
30
+ * const hash = Effect.runSync(hashContent("hello world"))
31
+ * // => "b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9"
32
+ * ```
33
+ *
34
+ * @category Hash
35
+ */
36
+ export const hashContent = (content: string): Effect.Effect<ContentHash> =>
37
+ Effect.sync(() => {
38
+ const hash = crypto.createHash("sha256").update(content, "utf8").digest("hex")
39
+ return ContentHash(hash)
40
+ })
41
+
42
+ /**
43
+ * Compute SHA256 hash of a buffer.
44
+ *
45
+ * @category Hash
46
+ */
47
+ export const hashBuffer = (buffer: Buffer | Uint8Array): Effect.Effect<ContentHash> =>
48
+ Effect.sync(() => {
49
+ const hash = crypto.createHash("sha256").update(buffer).digest("hex")
50
+ return ContentHash(hash)
51
+ })
52
+
53
+ /**
54
+ * Check if two hashes are equal.
55
+ *
56
+ * @category Hash
57
+ */
58
+ export const hashEquals = (a: ContentHash, b: ContentHash): boolean => a === b
59
+
60
+ /**
61
+ * Synchronous version of hashContent for simple use cases.
62
+ *
63
+ * @category Hash
64
+ */
65
+ export const hashContentSync = (content: string): ContentHash => {
66
+ const hash = crypto.createHash("sha256").update(content, "utf8").digest("hex")
67
+ return ContentHash(hash)
68
+ }
@@ -0,0 +1,50 @@
1
+ /**
2
+ * Tagged error types for AST serialization and parsing failures.
3
+ *
4
+ * **Mental model**
5
+ *
6
+ * - **Discriminated by `_tag`**: {@link SerializeError} and {@link ParseError} extend
7
+ * `Data.TaggedError`, enabling `Effect.catchTag` for selective recovery.
8
+ * - **Direction-aware**: Both carry a `target`/`source` field (`"confluence" | "markdown" | "adf"`)
9
+ * so handlers know which serialization pipeline failed.
10
+ *
11
+ * @module
12
+ */
13
+ import * as Data from "effect/Data"
14
+
15
+ /**
16
+ * Error thrown when serializing AST fails.
17
+ *
18
+ * @example
19
+ * ```typescript
20
+ * import { Effect } from "effect"
21
+ * import { SerializeError } from "@knpkv/atlassian-common"
22
+ *
23
+ * Effect.gen(function* () {
24
+ * // ... serialization operation
25
+ * }).pipe(
26
+ * Effect.catchTag("SerializeError", (error) =>
27
+ * Effect.sync(() => console.error(`Serialize error: ${error.message}`))
28
+ * )
29
+ * )
30
+ * ```
31
+ *
32
+ * @category Errors
33
+ */
34
+ export class SerializeError extends Data.TaggedError("SerializeError")<{
35
+ readonly target: "confluence" | "markdown" | "adf"
36
+ readonly nodeType: string
37
+ readonly message: string
38
+ }> {}
39
+
40
+ /**
41
+ * Error thrown when parsing content fails.
42
+ *
43
+ * @category Errors
44
+ */
45
+ export class ParseError extends Data.TaggedError("ParseError")<{
46
+ readonly source: "confluence" | "markdown" | "adf"
47
+ readonly message: string
48
+ readonly position?: { readonly line: number; readonly column: number }
49
+ readonly rawContent?: string
50
+ }> {}
@@ -0,0 +1,386 @@
1
+ /**
2
+ * Block-level AST nodes for Atlassian content (headings, paragraphs, tables, lists, etc.).
3
+ *
4
+ * **Mental model**
5
+ *
6
+ * - **Schema-driven ADT**: Each node is an Effect `Schema.Struct` with a `_tag` discriminant,
7
+ * composed into the {@link BlockNode} union via `Schema.Union`.
8
+ * - **Recursive via suspend**: `List` → `ListItem` → `BlockNode` cycles use `Schema.suspend`
9
+ * to break infinite type recursion.
10
+ * - **Inline children**: Most blocks contain `InlineNode` children for text-level content.
11
+ *
12
+ * @module
13
+ */
14
+ import * as Schema from "effect/Schema"
15
+ import { InlineNode, type InlineNode as InlineNodeType } from "./InlineNode.js"
16
+
17
+ /**
18
+ * Schema version for migration support.
19
+ *
20
+ * @category Version
21
+ */
22
+ export const SchemaVersion = Schema.Number.pipe(
23
+ Schema.int(),
24
+ Schema.positive(),
25
+ Schema.optionalWith({ default: () => 1 })
26
+ )
27
+
28
+ /**
29
+ * Optional raw source for exact roundtrip preservation.
30
+ *
31
+ * @category BlockNode
32
+ */
33
+ export const RawSource = Schema.optional(Schema.String)
34
+
35
+ /**
36
+ * Heading element (h1-h6).
37
+ *
38
+ * @example
39
+ * ```typescript
40
+ * import { Heading, Text } from "@knpkv/atlassian-common/ast"
41
+ *
42
+ * const h1 = new Heading({
43
+ * level: 1,
44
+ * children: [new Text({ value: "Introduction" })]
45
+ * })
46
+ * ```
47
+ *
48
+ * @category BlockNode
49
+ */
50
+ export class Heading extends Schema.TaggedClass<Heading>()("Heading", {
51
+ version: SchemaVersion,
52
+ level: Schema.Literal(1, 2, 3, 4, 5, 6),
53
+ children: Schema.Array(InlineNode),
54
+ rawSource: RawSource
55
+ }) {}
56
+
57
+ /**
58
+ * Text alignment options.
59
+ *
60
+ * @category BlockNode
61
+ */
62
+ export const TextAlignment = Schema.Literal("left", "center", "right")
63
+
64
+ /**
65
+ * Type for TextAlignment.
66
+ *
67
+ * @category Types
68
+ */
69
+ export type TextAlignment = Schema.Schema.Type<typeof TextAlignment>
70
+
71
+ /**
72
+ * Paragraph element with optional alignment and indentation.
73
+ *
74
+ * @example
75
+ * ```typescript
76
+ * import { Paragraph, Text } from "@knpkv/atlassian-common/ast"
77
+ *
78
+ * const para = new Paragraph({
79
+ * children: [new Text({ value: "Hello world" })]
80
+ * })
81
+ * ```
82
+ *
83
+ * @category BlockNode
84
+ */
85
+ export class Paragraph extends Schema.TaggedClass<Paragraph>()("Paragraph", {
86
+ version: SchemaVersion,
87
+ alignment: Schema.optional(TextAlignment),
88
+ indent: Schema.optional(Schema.Number),
89
+ children: Schema.Array(InlineNode),
90
+ rawSource: RawSource
91
+ }) {}
92
+
93
+ /**
94
+ * Code block with optional language.
95
+ *
96
+ * @example
97
+ * ```typescript
98
+ * import { CodeBlock } from "@knpkv/atlassian-common/ast"
99
+ *
100
+ * const code = new CodeBlock({
101
+ * language: "typescript",
102
+ * code: "const x = 1"
103
+ * })
104
+ * ```
105
+ *
106
+ * @category BlockNode
107
+ */
108
+ export class CodeBlock extends Schema.TaggedClass<CodeBlock>()("CodeBlock", {
109
+ version: SchemaVersion,
110
+ language: Schema.optional(Schema.String),
111
+ code: Schema.String,
112
+ rawSource: RawSource
113
+ }) {}
114
+
115
+ /**
116
+ * Thematic break / horizontal rule.
117
+ *
118
+ * @example
119
+ * ```typescript
120
+ * import { ThematicBreak } from "@knpkv/atlassian-common/ast"
121
+ *
122
+ * const hr = new ThematicBreak({})
123
+ * ```
124
+ *
125
+ * @category BlockNode
126
+ */
127
+ export class ThematicBreak extends Schema.TaggedClass<ThematicBreak>()("ThematicBreak", {
128
+ rawSource: RawSource
129
+ }) {}
130
+
131
+ /**
132
+ * Attachment reference for images.
133
+ *
134
+ * @category BlockNode
135
+ */
136
+ export const ImageAttachment = Schema.Struct({
137
+ filename: Schema.String,
138
+ version: Schema.optional(Schema.Number)
139
+ })
140
+
141
+ /**
142
+ * Type for ImageAttachment.
143
+ *
144
+ * @category Types
145
+ */
146
+ export type ImageAttachment = Schema.Schema.Type<typeof ImageAttachment>
147
+
148
+ /**
149
+ * Image element with support for both URL and attachments.
150
+ *
151
+ * @example
152
+ * ```typescript
153
+ * import { Image } from "@knpkv/atlassian-common/ast"
154
+ *
155
+ * const img = new Image({
156
+ * src: "https://example.com/image.png",
157
+ * alt: "Example image"
158
+ * })
159
+ * ```
160
+ *
161
+ * @category BlockNode
162
+ */
163
+ export class Image extends Schema.TaggedClass<Image>()("Image", {
164
+ version: SchemaVersion,
165
+ src: Schema.optional(Schema.String),
166
+ attachment: Schema.optional(ImageAttachment),
167
+ alt: Schema.optional(Schema.String),
168
+ title: Schema.optional(Schema.String),
169
+ align: Schema.optional(Schema.String),
170
+ width: Schema.optional(Schema.Number),
171
+ rawSource: RawSource
172
+ }) {}
173
+
174
+ /**
175
+ * Table cell element.
176
+ *
177
+ * @example
178
+ * ```typescript
179
+ * import { TableCell, Text } from "@knpkv/atlassian-common/ast"
180
+ *
181
+ * const cell = new TableCell({
182
+ * isHeader: true,
183
+ * children: [new Text({ value: "Header" })]
184
+ * })
185
+ * ```
186
+ *
187
+ * @category BlockNode
188
+ */
189
+ export class TableCell extends Schema.TaggedClass<TableCell>()("TableCell", {
190
+ isHeader: Schema.optionalWith(Schema.Boolean, { default: () => false }),
191
+ children: Schema.Array(InlineNode),
192
+ rawSource: RawSource
193
+ }) {}
194
+
195
+ /**
196
+ * Table row element.
197
+ *
198
+ * @category BlockNode
199
+ */
200
+ export class TableRow extends Schema.TaggedClass<TableRow>()("TableRow", {
201
+ cells: Schema.Array(TableCell),
202
+ rawSource: RawSource
203
+ }) {}
204
+
205
+ /**
206
+ * Table element with optional header row.
207
+ *
208
+ * @category BlockNode
209
+ */
210
+ export class Table extends Schema.TaggedClass<Table>()("Table", {
211
+ version: SchemaVersion,
212
+ header: Schema.optional(TableRow),
213
+ rows: Schema.Array(TableRow),
214
+ rawSource: RawSource
215
+ }) {}
216
+
217
+ /**
218
+ * Unsupported block element - preserves raw content for round-tripping.
219
+ *
220
+ * @category BlockNode
221
+ */
222
+ export class UnsupportedBlock extends Schema.TaggedClass<UnsupportedBlock>()("UnsupportedBlock", {
223
+ rawHtml: Schema.optional(Schema.String),
224
+ rawMarkdown: Schema.optional(Schema.String),
225
+ rawAdf: Schema.optional(Schema.String),
226
+ source: Schema.Literal("confluence", "markdown", "adf")
227
+ }) {}
228
+
229
+ // Non-recursive block nodes
230
+ const SimpleBlockNode = Schema.Union(
231
+ Heading,
232
+ Paragraph,
233
+ CodeBlock,
234
+ ThematicBreak,
235
+ Image,
236
+ Table,
237
+ UnsupportedBlock
238
+ )
239
+
240
+ /**
241
+ * Block quote element with nested block content.
242
+ *
243
+ * @category BlockNode
244
+ */
245
+ export const BlockQuote = Schema.Struct({
246
+ _tag: Schema.Literal("BlockQuote"),
247
+ version: SchemaVersion,
248
+ children: Schema.Array(SimpleBlockNode),
249
+ rawSource: RawSource
250
+ })
251
+
252
+ /**
253
+ * Type for BlockQuote.
254
+ *
255
+ * @category Types
256
+ */
257
+ export type BlockQuote = Schema.Schema.Type<typeof BlockQuote>
258
+
259
+ /**
260
+ * List item with nested block content.
261
+ *
262
+ * @category BlockNode
263
+ */
264
+ export const ListItem = Schema.Struct({
265
+ _tag: Schema.Literal("ListItem"),
266
+ checked: Schema.optional(Schema.Boolean),
267
+ children: Schema.Array(SimpleBlockNode),
268
+ rawSource: RawSource
269
+ })
270
+
271
+ /**
272
+ * Type for ListItem.
273
+ *
274
+ * @category Types
275
+ */
276
+ export type ListItem = Schema.Schema.Type<typeof ListItem>
277
+
278
+ /**
279
+ * List element (ordered or unordered).
280
+ *
281
+ * @category BlockNode
282
+ */
283
+ export const List = Schema.Struct({
284
+ _tag: Schema.Literal("List"),
285
+ version: SchemaVersion,
286
+ ordered: Schema.Boolean,
287
+ start: Schema.optional(Schema.Number),
288
+ children: Schema.Array(ListItem),
289
+ rawSource: RawSource
290
+ })
291
+
292
+ /**
293
+ * Type for List.
294
+ *
295
+ * @category Types
296
+ */
297
+ export type List = Schema.Schema.Type<typeof List>
298
+
299
+ /**
300
+ * Task item with status.
301
+ *
302
+ * @category BlockNode
303
+ */
304
+ export const TaskItem = Schema.Struct({
305
+ _tag: Schema.Literal("TaskItem"),
306
+ id: Schema.String,
307
+ uuid: Schema.String,
308
+ status: Schema.Literal("incomplete", "complete"),
309
+ body: Schema.Array(InlineNode),
310
+ rawSource: RawSource
311
+ })
312
+
313
+ /**
314
+ * Type for TaskItem.
315
+ *
316
+ * @category Types
317
+ */
318
+ export type TaskItem = Schema.Schema.Type<typeof TaskItem>
319
+
320
+ /**
321
+ * Task list.
322
+ *
323
+ * @category BlockNode
324
+ */
325
+ export const TaskList = Schema.Struct({
326
+ _tag: Schema.Literal("TaskList"),
327
+ version: SchemaVersion,
328
+ children: Schema.Array(TaskItem),
329
+ rawSource: RawSource
330
+ })
331
+
332
+ /**
333
+ * Type for TaskList.
334
+ *
335
+ * @category Types
336
+ */
337
+ export type TaskList = Schema.Schema.Type<typeof TaskList>
338
+
339
+ /**
340
+ * Union of all block node types.
341
+ *
342
+ * @category BlockNode
343
+ */
344
+ export const BlockNode = Schema.Union(
345
+ Heading,
346
+ Paragraph,
347
+ CodeBlock,
348
+ ThematicBreak,
349
+ BlockQuote,
350
+ Image,
351
+ Table,
352
+ List,
353
+ TaskList,
354
+ UnsupportedBlock
355
+ )
356
+
357
+ /**
358
+ * Type for block nodes.
359
+ *
360
+ * @category Types
361
+ */
362
+ export type BlockNode =
363
+ | Heading
364
+ | Paragraph
365
+ | CodeBlock
366
+ | ThematicBreak
367
+ | BlockQuote
368
+ | Image
369
+ | Table
370
+ | List
371
+ | TaskList
372
+ | UnsupportedBlock
373
+
374
+ /**
375
+ * Type helper for inline node children in blocks.
376
+ *
377
+ * @category Types
378
+ */
379
+ export type { InlineNodeType as InlineNode }
380
+
381
+ /**
382
+ * Simple block nodes (non-recursive).
383
+ *
384
+ * @category BlockNode
385
+ */
386
+ export { SimpleBlockNode }
@@ -0,0 +1,101 @@
1
+ /**
2
+ * Root AST node wrapping a sequence of {@link DocumentNode} children.
3
+ *
4
+ * **Mental model**
5
+ *
6
+ * - **Document = version + children**: The top-level container for parsed Atlassian content.
7
+ * `children` is a union of `BlockNode | MacroNode`.
8
+ * - **Raw source preservation**: Optional `rawSource` field enables lossless roundtripping.
9
+ *
10
+ * **Common tasks**
11
+ *
12
+ * - Construct a document: {@link makeDocument}
13
+ * - Type-guard check: {@link isDocument}
14
+ *
15
+ * @module
16
+ */
17
+ import * as Schema from "effect/Schema"
18
+ import { BlockNode, type BlockNode as BlockNodeType, RawSource, SchemaVersion } from "./BlockNode.js"
19
+ import { MacroNode, type MacroNode as MacroNodeType } from "./MacroNode.js"
20
+
21
+ /**
22
+ * Document node - represents a Block or Macro node.
23
+ *
24
+ * @category Document
25
+ */
26
+ export const DocumentNode = Schema.Union(BlockNode, MacroNode)
27
+
28
+ /**
29
+ * Type for document nodes.
30
+ *
31
+ * @category Types
32
+ */
33
+ export type DocumentNode = BlockNodeType | MacroNodeType
34
+
35
+ /**
36
+ * Document schema - the root AST node.
37
+ *
38
+ * @example
39
+ * ```typescript
40
+ * import { Document, Heading, Paragraph, Text } from "@knpkv/atlassian-common/ast"
41
+ * import * as Schema from "effect/Schema"
42
+ *
43
+ * const doc = {
44
+ * version: 1,
45
+ * children: [
46
+ * new Heading({ level: 1, children: [new Text({ value: "Title" })] }),
47
+ * new Paragraph({ children: [new Text({ value: "Content" })] })
48
+ * ]
49
+ * }
50
+ *
51
+ * const validated = Schema.decodeUnknownSync(Document)(doc)
52
+ * ```
53
+ *
54
+ * @category Document
55
+ */
56
+ export const Document = Schema.Struct({
57
+ version: SchemaVersion,
58
+ children: Schema.Array(DocumentNode),
59
+ rawSource: RawSource
60
+ })
61
+
62
+ /**
63
+ * Type for Document.
64
+ *
65
+ * @category Types
66
+ */
67
+ export type Document = Schema.Schema.Type<typeof Document>
68
+
69
+ /**
70
+ * Create a new Document with default version.
71
+ *
72
+ * @example
73
+ * ```typescript
74
+ * import { makeDocument, Heading, Text } from "@knpkv/atlassian-common/ast"
75
+ *
76
+ * const doc = makeDocument([
77
+ * new Heading({ level: 1, children: [new Text({ value: "Hello" })] })
78
+ * ])
79
+ * ```
80
+ *
81
+ * @category Constructors
82
+ */
83
+ export const makeDocument = (
84
+ children: ReadonlyArray<DocumentNode>,
85
+ rawSource?: string
86
+ ): Document => ({
87
+ version: 1,
88
+ children,
89
+ ...(rawSource !== undefined ? { rawSource } : {})
90
+ })
91
+
92
+ /**
93
+ * Check if a node is a Document.
94
+ *
95
+ * @category Guards
96
+ */
97
+ export const isDocument = (value: unknown): value is Document =>
98
+ typeof value === "object" &&
99
+ value !== null &&
100
+ "children" in value &&
101
+ Array.isArray((value as Document).children)