// JSON Schemas for Markdown AST nodes (mdast-shaped), editor-agnostic. // These schemas are editor-agnostic and can be fed to Ajv. import type { FromSchema } from "json-schema-to-ts" export type JsonSchema = Record // Base TS type applied to all node types to provide typed children/data/position // while still deriving attributes from JSON Schemas. export type BaseNode = { type: string children?: MarkdownNode[] position?: Record data?: Record } type WithBase = Omit & BaseNode const nodeDataSchema = { type: "object", properties: { id: { type: "string" }, }, additionalProperties: true, } as const const dataIdUniqueConstraint = [["/data/id"]] as const export const RootSchema = { "x-lix-key": "markdown_wc_root", "x-lix-version": "1.0", "x-lix-unique": dataIdUniqueConstraint, description: "Markdown AST root node (mdast-shaped). Holds all top-level children.", type: "object", properties: { type: { const: "root" }, children: { type: "array" }, position: { type: "object" }, data: nodeDataSchema, }, required: ["type", "children"], additionalProperties: false, } as const // Document-level snapshot used for block-based persistence (not part of the AST) export const DocumentSchema = { "x-lix-key": "markdown_wc_document", "x-lix-version": "1.0", description: "Top-level block order for a Markdown document. Stores an array of block ids (node.data.id) to reconstruct the document order without reparsing.", type: "object", properties: { order: { type: "array", items: { type: "string" } }, }, required: ["order"], additionalProperties: false, } as const export type ParagraphNode = WithBase> export const ParagraphSchema = { "x-lix-key": "markdown_wc_paragraph", "x-lix-version": "1.0", "x-lix-unique": dataIdUniqueConstraint, description: "Markdown paragraph block.", type: "object", properties: { type: { const: "paragraph" }, children: { type: "array" }, position: { type: "object" }, data: nodeDataSchema, }, required: ["type"], additionalProperties: false, } as const export type HeadingNode = WithBase> export const HeadingSchema = { "x-lix-key": "markdown_wc_heading", "x-lix-version": "1.0", "x-lix-unique": dataIdUniqueConstraint, description: "Markdown heading block (depth 1–6).", type: "object", properties: { type: { const: "heading" }, children: { type: "array" }, position: { type: "object" }, depth: { type: "integer", minimum: 1, maximum: 6 }, data: nodeDataSchema, }, required: ["type"], additionalProperties: false, } as const export type ListNode = WithBase> export const ListSchema = { "x-lix-key": "markdown_wc_list", "x-lix-version": "1.0", "x-lix-unique": dataIdUniqueConstraint, description: "Markdown list block (ordered or unordered).", type: "object", properties: { type: { const: "list" }, children: { type: "array" }, position: { type: "object" }, ordered: { type: "boolean" }, start: { type: ["integer", "null"] }, spread: { type: ["boolean", "null"] }, data: nodeDataSchema, }, required: ["type"], additionalProperties: false, } as const export type ListItemNode = WithBase> export const ListItemSchema = { "x-lix-key": "markdown_wc_list_item", "x-lix-version": "1.0", "x-lix-unique": dataIdUniqueConstraint, description: "Markdown list item.", type: "object", properties: { type: { const: "listItem" }, children: { type: "array" }, position: { type: "object" }, checked: { type: ["boolean", "null"] }, spread: { type: ["boolean", "null"] }, data: nodeDataSchema, }, required: ["type"], additionalProperties: false, } as const export type BlockquoteNode = WithBase> export const BlockquoteSchema = { "x-lix-key": "markdown_wc_blockquote", "x-lix-version": "1.0", "x-lix-unique": dataIdUniqueConstraint, description: "Markdown blockquote block.", type: "object", properties: { type: { const: "blockquote" }, children: { type: "array" }, position: { type: "object" }, data: nodeDataSchema, }, required: ["type"], additionalProperties: false, } as const export type CodeNode = WithBase> export const CodeSchema = { "x-lix-key": "markdown_wc_code", "x-lix-version": "1.0", "x-lix-unique": dataIdUniqueConstraint, description: "Markdown fenced code block.", type: "object", properties: { type: { const: "code" }, children: { type: "array" }, position: { type: "object" }, value: { type: "string" }, lang: { type: ["string", "null"] }, meta: { type: ["string", "null"] }, data: nodeDataSchema, }, required: ["type"], additionalProperties: false, } as const export type InlineCodeNode = WithBase> export const InlineCodeSchema = { "x-lix-key": "markdown_wc_inline_code", "x-lix-version": "1.0", "x-lix-unique": dataIdUniqueConstraint, description: "Inline code span.", type: "object", properties: { type: { const: "inlineCode" }, children: { type: "array" }, position: { type: "object" }, value: { type: "string" }, data: nodeDataSchema, }, required: ["type"], additionalProperties: false, } as const export type ThematicBreakNode = WithBase> export const ThematicBreakSchema = { "x-lix-key": "markdown_wc_thematic_break", "x-lix-version": "1.0", "x-lix-unique": dataIdUniqueConstraint, description: "Horizontal rule (thematic break).", type: "object", properties: { type: { const: "thematicBreak" }, children: { type: "array" }, position: { type: "object" }, data: nodeDataSchema, }, required: ["type"], additionalProperties: false, } as const export type BreakNode = WithBase> export const BreakSchema = { "x-lix-key": "markdown_wc_break", "x-lix-version": "1.0", "x-lix-unique": dataIdUniqueConstraint, description: "Hard line break.", type: "object", properties: { type: { const: "break" }, children: { type: "array" }, position: { type: "object" }, data: nodeDataSchema, }, required: ["type"], additionalProperties: false, } as const export type HtmlNode = WithBase> export const HtmlSchema = { "x-lix-key": "markdown_wc_html", "x-lix-version": "1.0", "x-lix-unique": dataIdUniqueConstraint, description: "Raw HTML block.", type: "object", properties: { type: { const: "html" }, children: { type: "array" }, position: { type: "object" }, value: { type: "string" }, data: nodeDataSchema, }, required: ["type"], additionalProperties: false, } as const export type ImageNode = WithBase> export const ImageSchema = { "x-lix-key": "markdown_wc_image", "x-lix-version": "1.0", "x-lix-unique": dataIdUniqueConstraint, description: "Image node.", type: "object", properties: { type: { const: "image" }, children: { type: "array" }, position: { type: "object" }, url: { type: "string" }, title: { type: ["string", "null"] }, alt: { type: ["string", "null"] }, data: nodeDataSchema, }, required: ["type"], additionalProperties: false, } as const export type LinkNode = WithBase> export const LinkSchema = { "x-lix-key": "markdown_wc_link", "x-lix-version": "1.0", "x-lix-unique": dataIdUniqueConstraint, description: "Link node containing inline children.", type: "object", properties: { type: { const: "link" }, children: { type: "array" }, position: { type: "object" }, url: { type: "string" }, title: { type: ["string", "null"] }, data: nodeDataSchema, }, required: ["type"], additionalProperties: false, } as const export type EmphasisNode = WithBase> export const EmphasisSchema = { "x-lix-key": "markdown_wc_emphasis", "x-lix-version": "1.0", "x-lix-unique": dataIdUniqueConstraint, description: "Emphasis (italic) inline node.", type: "object", properties: { type: { const: "emphasis" }, children: { type: "array" }, position: { type: "object" }, data: nodeDataSchema, }, required: ["type"], additionalProperties: false, } as const export type StrongNode = WithBase> export const StrongSchema = { "x-lix-key": "markdown_wc_strong", "x-lix-version": "1.0", "x-lix-unique": dataIdUniqueConstraint, description: "Strong (bold) inline node.", type: "object", properties: { type: { const: "strong" }, children: { type: "array" }, position: { type: "object" }, data: nodeDataSchema, }, required: ["type"], additionalProperties: false, } as const export type DeleteNode = WithBase> export const DeleteSchema = { "x-lix-key": "markdown_wc_delete", "x-lix-version": "1.0", "x-lix-unique": dataIdUniqueConstraint, description: "Delete/Strikethrough inline node.", type: "object", properties: { type: { const: "delete" }, children: { type: "array" }, position: { type: "object" }, data: nodeDataSchema, }, required: ["type"], additionalProperties: false, } as const export type TableNode = WithBase> export const TableSchema = { "x-lix-key": "markdown_wc_table", "x-lix-version": "1.0", "x-lix-unique": dataIdUniqueConstraint, description: "Markdown table block (GFM).", type: "object", properties: { type: { const: "table" }, children: { type: "array" }, position: { type: "object" }, align: { type: "array", items: { type: ["string", "null"], enum: ["left", "right", "center", null] }, }, data: nodeDataSchema, }, required: ["type"], additionalProperties: false, } as const export type TableRowNode = WithBase> export const TableRowSchema = { "x-lix-key": "markdown_wc_table_row", "x-lix-version": "1.0", "x-lix-unique": dataIdUniqueConstraint, description: "Table row (GFM).", type: "object", properties: { type: { const: "tableRow" }, children: { type: "array" }, position: { type: "object" }, data: nodeDataSchema, }, required: ["type"], additionalProperties: false, } as const export type TableCellNode = WithBase> export const TableCellSchema = { "x-lix-key": "markdown_wc_table_cell", "x-lix-version": "1.0", "x-lix-unique": dataIdUniqueConstraint, description: "Table cell (GFM).", type: "object", properties: { type: { const: "tableCell" }, children: { type: "array" }, position: { type: "object" }, data: nodeDataSchema, }, required: ["type"], additionalProperties: false, } as const export type TextNode = WithBase> export const TextSchema = { "x-lix-key": "markdown_wc_text", "x-lix-version": "1.0", "x-lix-unique": dataIdUniqueConstraint, description: "Plain text leaf node.", type: "object", properties: { type: { const: "text" }, children: { type: "array" }, position: { type: "object" }, value: { type: "string" }, data: nodeDataSchema, }, required: ["type"], additionalProperties: false, } as const export type YamlNode = WithBase> export const YamlSchema = { "x-lix-key": "markdown_wc_yaml", "x-lix-version": "1.0", "x-lix-unique": dataIdUniqueConstraint, description: "YAML frontmatter block.", type: "object", properties: { type: { const: "yaml" }, children: { type: "array" }, position: { type: "object" }, value: { type: "string" }, data: nodeDataSchema, }, required: ["type"], additionalProperties: false, } as const /** * Runtime map from mdast `node.type` → JSON schema. * * !Mapping of Markdown AST node `type` to its schema. * !Excludes persistence-only schemas such as `DocumentSchema`. * * Why this exists: * * - Generic code only knows `node.type` at runtime (e.g., during AST walks, diffing, persistence). * A data-driven map avoids hardcoding switch/case over every node type or importing each schema by name. * - Stable lookup by type string keeps callsites decoupled from export names and enables easy extension. * * @example * // Generic validation/dispatch by node.type * import { schemasByType } from "@opral/markdown-wc"; * * function schemaFor(node: { type: string }) { * return schemasByType[node.type]; * } * * @example * // Persisting changes with per-type schemas * import { schemasByType } from "@opral/markdown-wc"; * * function toDetectedChange(node: any, id: string) { * const schema = schemasByType[node.type]; * return { entity_id: id, schema, snapshot_content: node }; * } * * @example * // Querying by schema key (known type) * import * as AstSchemas from "@opral/markdown-wc"; * * db.select("changes") * .where("schema_key", "=", AstSchemas.schemasByType.paragraph["x-lix-key"]); */ export const schemasByType: Record = { root: RootSchema, paragraph: ParagraphSchema, heading: HeadingSchema, list: ListSchema, listItem: ListItemSchema, blockquote: BlockquoteSchema, code: CodeSchema, inlineCode: InlineCodeSchema, thematicBreak: ThematicBreakSchema, break: BreakSchema, html: HtmlSchema, image: ImageSchema, link: LinkSchema, emphasis: EmphasisSchema, strong: StrongSchema, delete: DeleteSchema, table: TableSchema, tableRow: TableRowSchema, tableCell: TableCellSchema, text: TextSchema, yaml: YamlSchema, } /** * Collection of Markdown schemas including AST nodes and persistence metadata like `DocumentSchema`. */ export const allSchemas: JsonSchema[] = [...Object.values(schemasByType), DocumentSchema] // Type exports derived from JSON Schemas (no mdast import) export type AstRoot = FromSchema export type MarkdownNode = | ParagraphNode | HeadingNode | ListNode | ListItemNode | BlockquoteNode | CodeNode | InlineCodeNode | ThematicBreakNode | BreakNode | HtmlNode | ImageNode | LinkNode | EmphasisNode | StrongNode | DeleteNode | TableNode | TableRowNode | TableCellNode | TextNode | YamlNode // Root AST type with typed children export type Ast = Omit & { children: MarkdownNode[] }