@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.
- package/CHANGELOG.md +11 -0
- package/LICENSE +21 -0
- package/README.md +107 -0
- package/dist/Brand.d.ts +118 -0
- package/dist/Brand.d.ts.map +1 -0
- package/dist/Brand.js +107 -0
- package/dist/Brand.js.map +1 -0
- package/dist/Hash.d.ts +53 -0
- package/dist/Hash.d.ts.map +1 -0
- package/dist/Hash.js +63 -0
- package/dist/Hash.js.map +1 -0
- package/dist/SerializeError.d.ts +48 -0
- package/dist/SerializeError.d.ts.map +1 -0
- package/dist/SerializeError.js +42 -0
- package/dist/SerializeError.js.map +1 -0
- package/dist/ast/BlockNode.d.ts +429 -0
- package/dist/ast/BlockNode.d.ts.map +1 -0
- package/dist/ast/BlockNode.js +279 -0
- package/dist/ast/BlockNode.js.map +1 -0
- package/dist/ast/Document.d.ts +255 -0
- package/dist/ast/Document.d.ts.map +1 -0
- package/dist/ast/Document.js +80 -0
- package/dist/ast/Document.js.map +1 -0
- package/dist/ast/InlineNode.d.ts +483 -0
- package/dist/ast/InlineNode.d.ts.map +1 -0
- package/dist/ast/InlineNode.js +268 -0
- package/dist/ast/InlineNode.js.map +1 -0
- package/dist/ast/MacroNode.d.ts +214 -0
- package/dist/ast/MacroNode.d.ts.map +1 -0
- package/dist/ast/MacroNode.js +110 -0
- package/dist/ast/MacroNode.js.map +1 -0
- package/dist/ast/index.d.ts +10 -0
- package/dist/ast/index.d.ts.map +1 -0
- package/dist/ast/index.js +14 -0
- package/dist/ast/index.js.map +1 -0
- package/dist/auth/OAuthEndpoints.d.ts +95 -0
- package/dist/auth/OAuthEndpoints.d.ts.map +1 -0
- package/dist/auth/OAuthEndpoints.js +112 -0
- package/dist/auth/OAuthEndpoints.js.map +1 -0
- package/dist/auth/OAuthErrors.d.ts +76 -0
- package/dist/auth/OAuthErrors.d.ts.map +1 -0
- package/dist/auth/OAuthErrors.js +85 -0
- package/dist/auth/OAuthErrors.js.map +1 -0
- package/dist/auth/OAuthOperations.d.ts +87 -0
- package/dist/auth/OAuthOperations.d.ts.map +1 -0
- package/dist/auth/OAuthOperations.js +169 -0
- package/dist/auth/OAuthOperations.js.map +1 -0
- package/dist/auth/OAuthResponseSchemas.d.ts +65 -0
- package/dist/auth/OAuthResponseSchemas.d.ts.map +1 -0
- package/dist/auth/OAuthResponseSchemas.js +47 -0
- package/dist/auth/OAuthResponseSchemas.js.map +1 -0
- package/dist/auth/index.d.ts +11 -0
- package/dist/auth/index.d.ts.map +1 -0
- package/dist/auth/index.js +16 -0
- package/dist/auth/index.js.map +1 -0
- package/dist/auth/uuid.d.ts +14 -0
- package/dist/auth/uuid.d.ts.map +1 -0
- package/dist/auth/uuid.js +14 -0
- package/dist/auth/uuid.js.map +1 -0
- package/dist/config/ConfigPaths.d.ts +95 -0
- package/dist/config/ConfigPaths.d.ts.map +1 -0
- package/dist/config/ConfigPaths.js +102 -0
- package/dist/config/ConfigPaths.js.map +1 -0
- package/dist/config/OAuthSchemas.d.ts +144 -0
- package/dist/config/OAuthSchemas.d.ts.map +1 -0
- package/dist/config/OAuthSchemas.js +107 -0
- package/dist/config/OAuthSchemas.js.map +1 -0
- package/dist/config/TokenStorage.d.ts +96 -0
- package/dist/config/TokenStorage.d.ts.map +1 -0
- package/dist/config/TokenStorage.js +128 -0
- package/dist/config/TokenStorage.js.map +1 -0
- package/dist/config/index.d.ts +9 -0
- package/dist/config/index.d.ts.map +1 -0
- package/dist/config/index.js +12 -0
- package/dist/config/index.js.map +1 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +20 -0
- package/dist/index.js.map +1 -0
- package/dist/parsers/index.d.ts +7 -0
- package/dist/parsers/index.d.ts.map +1 -0
- package/dist/parsers/index.js +8 -0
- package/dist/parsers/index.js.map +1 -0
- package/dist/serializers/MarkdownSerializer.d.ts +63 -0
- package/dist/serializers/MarkdownSerializer.d.ts.map +1 -0
- package/dist/serializers/MarkdownSerializer.js +367 -0
- package/dist/serializers/MarkdownSerializer.js.map +1 -0
- package/dist/serializers/index.d.ts +7 -0
- package/dist/serializers/index.d.ts.map +1 -0
- package/dist/serializers/index.js +7 -0
- package/dist/serializers/index.js.map +1 -0
- package/package.json +86 -0
- package/src/Brand.ts +142 -0
- package/src/Hash.ts +68 -0
- package/src/SerializeError.ts +50 -0
- package/src/ast/BlockNode.ts +386 -0
- package/src/ast/Document.ts +101 -0
- package/src/ast/InlineNode.ts +328 -0
- package/src/ast/MacroNode.ts +172 -0
- package/src/ast/index.ts +87 -0
- package/src/auth/OAuthEndpoints.ts +142 -0
- package/src/auth/OAuthErrors.ts +101 -0
- package/src/auth/OAuthOperations.ts +276 -0
- package/src/auth/OAuthResponseSchemas.ts +70 -0
- package/src/auth/index.ts +47 -0
- package/src/auth/uuid.ts +14 -0
- package/src/config/ConfigPaths.ts +179 -0
- package/src/config/OAuthSchemas.ts +146 -0
- package/src/config/TokenStorage.ts +228 -0
- package/src/config/index.ts +43 -0
- package/src/index.ts +34 -0
- package/src/parsers/index.ts +8 -0
- package/src/serializers/MarkdownSerializer.ts +498 -0
- package/src/serializers/index.ts +7 -0
- package/test/Brand.test.ts +90 -0
- package/test/MarkdownSerializer.test.ts +82 -0
- package/test/OAuthEndpoints.test.ts +109 -0
- package/test/OAuthOperations.test.ts +315 -0
- package/tsconfig.json +11 -0
- package/tsconfig.tsbuildinfo +1 -0
- 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)
|