@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
@@ -124,16 +124,23 @@ export class RateLimitError extends Data.TaggedError("RateLimitError")<{
124
124
  }
125
125
 
126
126
  /**
127
- * Error thrown when HTML/Markdown conversion fails.
127
+ * Direction of an ADF/Markdown conversion.
128
+ *
129
+ * @category Errors
130
+ */
131
+ export type ConversionDirection = "adfToMarkdown" | "markdownToAdf"
132
+
133
+ /**
134
+ * Error thrown when ADF/Markdown conversion fails.
128
135
  *
129
136
  * @category Errors
130
137
  */
131
138
  export class ConversionError extends Data.TaggedError("ConversionError")<{
132
- readonly direction: "htmlToMarkdown" | "markdownToHtml"
139
+ readonly direction: ConversionDirection
133
140
  readonly cause: unknown
134
141
  readonly message: string
135
142
  }> {
136
- constructor(params: { direction: "htmlToMarkdown" | "markdownToHtml"; cause: unknown }) {
143
+ constructor(params: { direction: ConversionDirection; cause: unknown }) {
137
144
  super({
138
145
  direction: params.direction,
139
146
  cause: params.cause,
@@ -142,6 +149,57 @@ export class ConversionError extends Data.TaggedError("ConversionError")<{
142
149
  }
143
150
  }
144
151
 
152
+ /**
153
+ * Issue produced by ADF JSON Schema validation.
154
+ *
155
+ * @category Errors
156
+ */
157
+ export interface AdfSchemaIssue {
158
+ readonly instancePath?: string
159
+ readonly schemaPath?: string
160
+ readonly keyword?: string
161
+ readonly message?: string
162
+ readonly params?: Record<string, unknown>
163
+ }
164
+
165
+ /**
166
+ * Error thrown when an ADF document fails JSON Schema validation.
167
+ *
168
+ * @category Errors
169
+ */
170
+ export class AdfSchemaError extends Data.TaggedError("AdfSchemaError")<{
171
+ readonly direction: "incoming" | "outgoing"
172
+ readonly issues: ReadonlyArray<AdfSchemaIssue>
173
+ readonly message: string
174
+ }> {
175
+ constructor(params: { direction: "incoming" | "outgoing"; issues: ReadonlyArray<AdfSchemaIssue> }) {
176
+ super({
177
+ direction: params.direction,
178
+ issues: params.issues,
179
+ message: `ADF schema validation failed (${params.direction}): ${params.issues.length} issue(s)`
180
+ })
181
+ }
182
+ }
183
+
184
+ /**
185
+ * Error thrown when the wrapped @atlaskit transformer libraries throw.
186
+ *
187
+ * @category Errors
188
+ */
189
+ export class AtlaskitTransformersError extends Data.TaggedError("AtlaskitTransformersError")<{
190
+ readonly cause: unknown
191
+ readonly message: string
192
+ }> {
193
+ constructor(params: { cause: unknown }) {
194
+ super({
195
+ cause: params.cause,
196
+ message: `Atlaskit transformer failed: ${
197
+ params.cause instanceof Error ? params.cause.message : String(params.cause)
198
+ }`
199
+ })
200
+ }
201
+ }
202
+
145
203
  /**
146
204
  * Error thrown when sync conflict is detected.
147
205
  *
@@ -255,6 +313,8 @@ export type ConfluenceError =
255
313
  | ApiError
256
314
  | RateLimitError
257
315
  | ConversionError
316
+ | AdfSchemaError
317
+ | AtlaskitTransformersError
258
318
  | ConflictError
259
319
  | FileSystemError
260
320
  | OAuthError
@@ -281,6 +341,8 @@ export const isConfluenceError = (error: unknown): error is ConfluenceError =>
281
341
  "ApiError",
282
342
  "RateLimitError",
283
343
  "ConversionError",
344
+ "AdfSchemaError",
345
+ "AtlaskitTransformersError",
284
346
  "ConflictError",
285
347
  "FileSystemError",
286
348
  "OAuthError",
@@ -1,26 +1,30 @@
1
1
  /**
2
- * HTML to Markdown conversion service using AST-based approach.
2
+ * ADF Markdown conversion facade.
3
+ *
4
+ * Push (markdown → ADF) routes through the official `@atlaskit` markdown
5
+ * + JSON transformers and is strictly validated by `AdfSchemaValidator` —
6
+ * we author that side, so schema failures are bugs. Pull (ADF → markdown)
7
+ * routes through the in-package `AdfWalker`; incoming validation is advisory
8
+ * (logged, not thrown) because Confluence Cloud routinely emits documents the
9
+ * canonical schema lags behind.
3
10
  *
4
11
  * @module
5
12
  */
13
+ import type { DocNode } from "@atlaskit/adf-schema"
6
14
  import * as Context from "effect/Context"
7
15
  import * as Effect from "effect/Effect"
8
16
  import * as Layer from "effect/Layer"
9
17
  import * as Schema from "effect/Schema"
10
- import type { Document } from "./ast/Document.js"
18
+ import { revertPlaceholders } from "./AdfPlaceholders.js"
19
+ import { AdfSchemaValidator } from "./AdfSchemaValidator.js"
20
+ import { walk, type WalkerWarning } from "./AdfWalker.js"
21
+ import { AtlaskitTransformers } from "./AtlaskitTransformers.js"
22
+ import type { AdfSchemaError, AtlaskitTransformersError } from "./ConfluenceError.js"
11
23
  import { ConversionError } from "./ConfluenceError.js"
12
- import { parseConfluenceHtml } from "./parsers/ConfluenceParser.js"
13
- import { parseMarkdown } from "./parsers/MarkdownParser.js"
14
- import { ParseError, type SerializeError } from "./SchemaConverterError.js"
15
- import { ConfluenceToMarkdown, DocumentFromHast, DocumentFromMdast } from "./schemas/ConversionSchema.js"
16
- import { HastFromHtml } from "./schemas/hast/index.js"
17
- import { MdastFromMarkdown } from "./schemas/mdast/index.js"
18
- import { PreprocessedHtmlFromConfluence } from "./schemas/preprocessing/index.js"
19
- import { serializeToConfluence } from "./serializers/ConfluenceSerializer.js"
20
- import { type SerializeOptions, serializeToMarkdown } from "./serializers/MarkdownSerializer.js"
21
24
 
22
25
  /**
23
- * Markdown conversion service for HTML <-> GFM conversion.
26
+ * Markdown conversion service. Public surface is two delegating methods —
27
+ * `adfToMarkdown` (pull) and `markdownToAdf` (push).
24
28
  *
25
29
  * @example
26
30
  * ```typescript
@@ -29,13 +33,8 @@ import { type SerializeOptions, serializeToMarkdown } from "./serializers/Markdo
29
33
  *
30
34
  * const program = Effect.gen(function* () {
31
35
  * const converter = yield* MarkdownConverter
32
- * const md = yield* converter.htmlToMarkdown("<h1>Hello</h1><p>World</p>")
33
- * console.log(md) // # Hello\n\nWorld
36
+ * const md = yield* converter.adfToMarkdown(adfJson)
34
37
  * })
35
- *
36
- * Effect.runPromise(
37
- * program.pipe(Effect.provide(MarkdownConverter.layer))
38
- * )
39
38
  * ```
40
39
  *
41
40
  * @category Conversion
@@ -46,146 +45,114 @@ export class MarkdownConverter extends Context.Tag(
46
45
  MarkdownConverter,
47
46
  {
48
47
  /**
49
- * Convert Confluence storage format (HTML) to GitHub Flavored Markdown.
50
- */
51
- readonly htmlToMarkdown: (
52
- html: string,
53
- options?: SerializeOptions
54
- ) => Effect.Effect<string, ConversionError>
55
-
56
- /**
57
- * Convert GitHub Flavored Markdown to HTML (Confluence storage format).
58
- */
59
- readonly markdownToHtml: (markdown: string) => Effect.Effect<string, ConversionError>
60
-
61
- /**
62
- * Parse Confluence HTML to Document AST.
63
- */
64
- readonly htmlToAst: (html: string) => Effect.Effect<Document, ParseError>
65
-
66
- /**
67
- * Parse Markdown to Document AST.
68
- */
69
- readonly markdownToAst: (markdown: string) => Effect.Effect<Document, ParseError>
70
-
71
- /**
72
- * Serialize Document AST to Confluence HTML.
48
+ * Convert an ADF JSON document (as wire-format string) to GitHub Flavored
49
+ * Markdown. Checks against the canonical @atlaskit/adf-schema before
50
+ * walking; violations are logged as warnings (remote drift), and only a
51
+ * document too malformed to walk at all fails with ConversionError.
73
52
  */
74
- readonly astToHtml: (doc: Document) => Effect.Effect<string, SerializeError>
53
+ readonly adfToMarkdown: (adfJson: string) => Effect.Effect<string, ConversionError>
75
54
 
76
55
  /**
77
- * Serialize Document AST to Markdown.
56
+ * Convert GitHub Flavored Markdown to a JSON-stringified ADF document.
57
+ * Routes through the official @atlaskit transformers; validates the
58
+ * produced ADF against the canonical schema before stringification.
78
59
  */
79
- readonly astToMarkdown: (doc: Document) => Effect.Effect<string, SerializeError>
60
+ readonly markdownToAdf: (markdown: string) => Effect.Effect<string, ConversionError>
80
61
  }
81
62
  >() {}
82
63
 
64
+ const warningSummary = (w: WalkerWarning): string => {
65
+ switch (w._tag) {
66
+ case "UnsupportedNode":
67
+ return `${w._tag} ${w.nodeType}`
68
+ case "LossyMark":
69
+ return `${w._tag} ${w.mark}`
70
+ case "MediaWithoutUrl":
71
+ return `${w._tag} ${w.mediaId}`
72
+ case "UnsupportedExtension":
73
+ return `${w._tag} ${w.nodeType} ${w.extensionKey || "?"}`
74
+ }
75
+ }
76
+
77
+ const toConversionError = (
78
+ direction: "adfToMarkdown" | "markdownToAdf"
79
+ ) =>
80
+ (cause: AdfSchemaError | AtlaskitTransformersError | { readonly cause: unknown }): ConversionError =>
81
+ new ConversionError({ direction, cause })
82
+
83
+ // The least structure the walker needs: a doc node with a content array.
84
+ // Anything failing this isn't "schema drift", it's not an ADF document —
85
+ // advisory validation must not let it through (walking `null` is a defect;
86
+ // walking `{}` silently produces an empty page).
87
+ const isWalkableDoc = Schema.is(Schema.Struct({
88
+ type: Schema.Literal("doc"),
89
+ content: Schema.Array(Schema.Unknown)
90
+ }))
91
+
83
92
  /**
84
93
  * Layer that provides the MarkdownConverter service.
85
94
  *
86
95
  * @category Layers
87
96
  */
88
- export const layer: Layer.Layer<MarkdownConverter> = Layer.succeed(
97
+ export const layer: Layer.Layer<
89
98
  MarkdownConverter,
90
- MarkdownConverter.of({
91
- htmlToMarkdown: (html, options) =>
92
- Effect.gen(function*() {
93
- // Use AST-based approach to preserve colors, underlines, etc.
94
- const doc = yield* parseConfluenceHtml(html).pipe(
95
- Effect.mapError((e) => new ConversionError({ direction: "htmlToMarkdown", cause: e.message }))
96
- )
97
- return yield* serializeToMarkdown(doc, options).pipe(
98
- Effect.mapError((e) => new ConversionError({ direction: "htmlToMarkdown", cause: e.message }))
99
- )
100
- }),
99
+ never,
100
+ AtlaskitTransformers | AdfSchemaValidator
101
+ > = Layer.effect(
102
+ MarkdownConverter,
103
+ Effect.gen(function*() {
104
+ const transformers = yield* AtlaskitTransformers
105
+ const validator = yield* AdfSchemaValidator
101
106
 
102
- markdownToHtml: (markdown) =>
107
+ const adfToMarkdown = (adfJson: string): Effect.Effect<string, ConversionError> =>
103
108
  Effect.gen(function*() {
104
- // Use AST-based approach for consistency
105
- const doc = yield* parseMarkdown(markdown).pipe(
106
- Effect.mapError((e) => new ConversionError({ direction: "markdownToHtml", cause: e.message }))
109
+ const raw = yield* Effect.try({
110
+ try: () => JSON.parse(adfJson),
111
+ catch: (cause) => new ConversionError({ direction: "adfToMarkdown", cause })
112
+ })
113
+ // Incoming validation is advisory: the canonical @atlaskit schema is
114
+ // routinely stricter than what Confluence Cloud actually emits (old
115
+ // revisions, experimental nodes), so a failure here usually means
116
+ // "schema drift", not "broken document". Log + continue. Outgoing
117
+ // validation stays strict because we control that side.
118
+ const doc = yield* validator.check(raw, "incoming").pipe(
119
+ Effect.catchTag("AdfSchemaError", (err) =>
120
+ isWalkableDoc(raw)
121
+ ? Effect.gen(function*() {
122
+ yield* Effect.logWarning(
123
+ `adf schema (incoming): ${err.issues.length} issue(s); first: ${
124
+ err.issues.slice(0, 3).map((i) =>
125
+ `${i.instancePath ?? "?"} ${i.keyword ?? "?"} ${i.message ?? ""}`.trim()
126
+ ).join(" | ")
127
+ }`
128
+ )
129
+ return raw as DocNode
130
+ })
131
+ : Effect.fail(toConversionError("adfToMarkdown")(err)))
107
132
  )
108
- return yield* serializeToConfluence(doc).pipe(
109
- Effect.mapError((e) => new ConversionError({ direction: "markdownToHtml", cause: e.message }))
110
- )
111
- }),
112
-
113
- htmlToAst: (html) => parseConfluenceHtml(html),
114
-
115
- markdownToAst: (markdown) => parseMarkdown(markdown),
116
-
117
- astToHtml: (doc) => serializeToConfluence(doc),
118
-
119
- astToMarkdown: (doc) => serializeToMarkdown(doc)
120
- })
121
- )
122
-
123
- /**
124
- * Schema-based layer for MarkdownConverter using Effect Schema transforms.
125
- *
126
- * This is an alternative implementation that uses the new Schema-based
127
- * conversion pipeline. It provides the same API as the default layer.
128
- *
129
- * Note: For full fidelity, continue to use the default layer. This schema-based
130
- * layer is useful for simpler use cases or when you want to leverage Schema
131
- * composition.
132
- *
133
- * @example
134
- * ```typescript
135
- * import { MarkdownConverter, schemaBasedLayer } from "@knpkv/confluence-to-markdown/MarkdownConverter"
136
- * import { Effect } from "effect"
137
- *
138
- * const program = Effect.gen(function* () {
139
- * const converter = yield* MarkdownConverter
140
- * const md = yield* converter.htmlToMarkdown("<h1>Hello</h1>")
141
- * })
142
- *
143
- * Effect.runPromise(
144
- * program.pipe(Effect.provide(schemaBasedLayer))
145
- * )
146
- * ```
147
- *
148
- * @category Layers
149
- */
150
- export const schemaBasedLayer: Layer.Layer<MarkdownConverter> = Layer.succeed(
151
- MarkdownConverter,
152
- MarkdownConverter.of({
153
- // Note: Schema-based layer doesn't support includeRawSource option yet
154
- htmlToMarkdown: (html, options) =>
155
- Effect.gen(function*() {
156
- if (options?.includeRawSource !== undefined) {
157
- yield* Effect.logWarning("schemaBasedLayer: includeRawSource option is not supported, use default layer")
133
+ const { markdown, warnings } = walk(doc)
134
+ for (const w of warnings) {
135
+ yield* Effect.logWarning(`adf walker: ${warningSummary(w)}`, w)
158
136
  }
159
- return yield* Schema.decode(ConfluenceToMarkdown)(html)
160
- }).pipe(
161
- Effect.mapError((e) => new ConversionError({ direction: "htmlToMarkdown", cause: e.message }))
162
- ),
137
+ return markdown
138
+ })
163
139
 
164
- markdownToHtml: (markdown) =>
165
- Schema.encode(ConfluenceToMarkdown)(markdown).pipe(
166
- Effect.mapError((e) => new ConversionError({ direction: "markdownToHtml", cause: e.message }))
167
- ),
168
-
169
- htmlToAst: (html) =>
170
- Effect.gen(function*() {
171
- const preprocessed = yield* Schema.decode(PreprocessedHtmlFromConfluence)(html)
172
- const hast = yield* Schema.decode(HastFromHtml)(preprocessed)
173
- return yield* Schema.decode(DocumentFromHast)(hast)
174
- }).pipe(
175
- Effect.mapError((e) => new ParseError({ source: "confluence", message: e.message }))
176
- ),
177
-
178
- markdownToAst: (markdown) =>
140
+ const markdownToAdf = (markdown: string): Effect.Effect<string, ConversionError> =>
179
141
  Effect.gen(function*() {
180
- const mdast = yield* Schema.decode(MdastFromMarkdown)(markdown)
181
- return yield* Schema.decode(DocumentFromMdast)(mdast)
182
- }).pipe(
183
- Effect.mapError((e) => new ParseError({ source: "markdown", message: e.message }))
184
- ),
185
-
186
- // For serialization, continue using the existing serializers for full fidelity
187
- astToHtml: (doc) => serializeToConfluence(doc),
142
+ const adf = yield* transformers.use(({ json, md }) => json.encode(md.parse(markdown))).pipe(
143
+ Effect.mapError(toConversionError("markdownToAdf"))
144
+ )
145
+ // The walker emits HTML/comment placeholders for Confluence-only nodes
146
+ // (status, extension). @atlaskit's markdown transformer doesn't know
147
+ // these, so they come through as plain text. Rewrite them back into
148
+ // the structured nodes Confluence expects before validation.
149
+ const reverted = revertPlaceholders(adf)
150
+ const validated = yield* validator.check(reverted, "outgoing").pipe(
151
+ Effect.mapError(toConversionError("markdownToAdf"))
152
+ )
153
+ return JSON.stringify(validated)
154
+ })
188
155
 
189
- astToMarkdown: (doc) => serializeToMarkdown(doc)
156
+ return MarkdownConverter.of({ adfToMarkdown, markdownToAdf })
190
157
  })
191
158
  )
package/src/Schemas.ts CHANGED
@@ -163,7 +163,7 @@ export const PageResponseSchema = Schema.Struct({
163
163
  }),
164
164
  body: Schema.optional(
165
165
  Schema.Struct({
166
- storage: Schema.optional(
166
+ atlas_doc_format: Schema.optional(
167
167
  Schema.Struct({
168
168
  value: Schema.String,
169
169
  representation: Schema.optional(Schema.String)
@@ -204,7 +204,7 @@ export const PageListItemSchema = Schema.Struct({
204
204
  })),
205
205
  body: Schema.optional(
206
206
  Schema.Struct({
207
- storage: Schema.optional(
207
+ atlas_doc_format: Schema.optional(
208
208
  Schema.Struct({
209
209
  value: Schema.String,
210
210
  representation: Schema.optional(Schema.String)
@@ -377,7 +377,7 @@ export const PageVersionSchema = Schema.Struct({
377
377
  title: Schema.optional(Schema.String),
378
378
  body: Schema.optional(
379
379
  Schema.Struct({
380
- storage: Schema.optional(
380
+ atlas_doc_format: Schema.optional(
381
381
  Schema.Struct({
382
382
  value: Schema.String,
383
383
  representation: Schema.optional(Schema.String)
@@ -413,7 +413,7 @@ export const PageVersionContentSchema = Schema.Struct({
413
413
  /** Page content */
414
414
  body: Schema.optional(
415
415
  Schema.Struct({
416
- storage: Schema.optional(
416
+ atlas_doc_format: Schema.optional(
417
417
  Schema.Struct({
418
418
  value: Schema.String,
419
419
  representation: Schema.optional(Schema.String)