Skip to content

Document format

Ezynota documents are plain, portable, versioned JSON. The DOM is never the source of truth — you can store a document in any database, diff it, sync it, or regenerate it years later.

EzynotaDocument

json
{
  "schemaVersion": "1.0.0",
  "generator": { "name": "ezynota", "version": "0.1.1" },
  "createdAt": 1727000000000,
  "updatedAt": 1727000123456,
  "meta": { "title": "Meeting notes" },
  "blocks": []
}
FieldTypeDescription
schemaVersionstringDocument schema version ("1.0.0") — independent of the package version.
blocksEzynotaBlock[]Ordered top-level blocks.
createdAt / updatedAtnumber?Epoch milliseconds.
metaJsonObject?Free-form metadata; the note title lives at meta.title.
generator{ name, version }?Stamp of the producing editor.

EzynotaBlock

json
{
  "id": "b7f9c2e1",
  "type": "paragraph",
  "data": { "content": [{ "type": "text", "text": "Hello" }] },
  "tunes": { "alignment": "center" },
  "meta": { "createdAt": 1727000000000 },
  "children": []
}
FieldTypeDescription
idstringUnique id (UUID or ez_* fallback, or your idGenerator).
typestringBlock type — a built-in or any custom tool key.
dataJsonValueTool-specific payload. Always JSON-serializable.
tunesRecord<string, JsonValue>?Per-block tune values, e.g. { alignment: "center" }.
meta{ createdAt?, updatedAt? }?Block timestamps.
childrenEzynotaBlock[]?Nested blocks (used by toggle sections; reserved for future nesting).

Inline content

Text content is a structured array — never raw HTML:

ts
type InlineContent = TextNode | LinkNode;

interface TextNode {
  type: "text";
  text: string;
  marks?: InlineMark[];
}

interface LinkNode {
  type: "link";
  href: string;        // https:, mailto:, or note:<id> internal links
  content: TextNode[];
}

interface InlineMark {
  type: string;        // see mark types below
  attrs?: JsonObject;
}

Helper: textNode("hi", [{ type: "bold" }]).

Mark types

MarkRendered as
bold<strong>
italic<em>
underline<u>
strike<s>
code<code>
mark<mark>
colorspan with attrs.color
backgroundspan with attrs.color
link<a> (via LinkNode)
custom<span data-ez-mark="…">

Marks are stored in a deterministic order and normalized by normalizeInline, so equal content always produces byte-identical JSON.

Block data shapes

TypeData
paragraph{ content: InlineContent[] }
heading{ level: 1|2|3, content: InlineContent[] }
quote{ content: InlineContent[] }
code{ code: string } (plain text, no inline marks)
delimiter{}
list{ style: "unordered"|"ordered"|"task", items: [{ content, checked? }] }
table{ header: boolean, rows: [[{ content: InlineContent[] }]] }
image{ src, alt, caption?, width? }src may be asset:<id> or a data URL
callout{ variant: "info"|"warning"|"success"|"danger", content: InlineContent[] }
toggle{ open: boolean, heading: InlineContent[] } + children
unknownraw original data (read-only fallback)

Validation, salvage & freezing

  • normalizeDocument — strict validation; throws on bad input.
  • salvageDocument — never throws; repairs what it can.
  • freezeDocument — deep-freeze snapshots (used by getSnapshot()).
  • Duplicate ids are detected and renamed during normalization (<id>_2, —).

Versioning

SCHEMA_VERSION ("1.0.0") evolves independently of the npm version. Documents written by older schemas are migrated automatically on render() and on workspace load.

Released under the MIT License. Zero runtime dependencies.