# Serialization

Import these from `@grafloria/engine`.

## Functions

### `beginIncrementalCapture`

Convenience: start watching a diagram for incremental commits.

```ts
function beginIncrementalCapture(diagram: DiagramModel): IncrementalCapture
```

### `canonicalStringify`

Deterministic JSON: object keys sorted recursively so the same logical
document always hashes identically, regardless of property insertion order. (Arrays keep their order — element order IS meaning in nodes/links.)

```ts
function canonicalStringify(value: unknown): string
```

### `checksumOf`

Integrity checksum of a document's canonical JSON.

```ts
function checksumOf(document: SerializedDiagram): string
```

### `deserializeSubgraphInto`

Install a subgraph into a live diagram through the unified restore path.

This is a USER MUTATION (unlike document load): entities are installed via
restore* so the normal node:added/link:added events fire for undo and
rendering, and port registries are reconciled at the end.

```ts
function deserializeSubgraphInto(
  diagram: DiagramModel,
  subgraph: SerializedSubgraph,
  options: DeserializeSubgraphOptions = {}
): DeserializedSubgraph
```

### `exportDiagramText`

Export a diagram as Mermaid-compatible text. With `lossless` (default) the
exact document travels in a `%%grafloria:document` comment, so importing this
text reproduces the diagram byte-for-byte (serialize-equality), while the
body stays renderable by any Mermaid consumer.

```ts
function exportDiagramText(
  diagram: DiagramModel,
  options: ExportTextOptions = {}
): string
```

### `getDiagramMigrations`

Registered migrations, in order (primarily for tests/diagnostics).

```ts
function getDiagramMigrations(): readonly DiagramMigration[]
```

### `importDiagramText`

Import diagram text. Sidecar-carrying text loads losslessly through the
unified JSON path; pure Mermaid text parses through the DSL (best effort —
the documented lossy boundary). See ImportTextOptions.prefer for who wins
when the body was hand-edited after export.

```ts
function importDiagramText(
  text: string,
  options: ImportTextOptions = {}
): ImportTextResult
```

### `isDiagramDocumentEnvelope`

```ts
function isDiagramDocumentEnvelope(value: unknown): value is DiagramDocumentEnvelope
```

### `registerDiagramMigration`

Register a document migration. Apps embedding the engine can register their
own steps when they extend the document shape.

```ts
function registerDiagramMigration(migration: DiagramMigration): void
```

### `runDiagramMigrations`

Upgrade a serialized document to DIAGRAM_SCHEMA_VERSION.

- A document with no `schemaVersion` is treated as v1.
- A document NEWER than the runtime throws (loading it would silently drop
  or mangle data written by a newer app — the caller must upgrade instead).
- A missing step in the chain throws (a partial upgrade is worse than none).

```ts
function runDiagramMigrations(
  data: SerializedDiagram,
  migrations: readonly DiagramMigration[] = registry
): SerializedDiagram
```

**Parameters**

- `migrations`: override the global registry (tests / embedders).

### `serializeSubgraph`

```ts
function serializeSubgraph(
  diagram: DiagramModel,
  selection: SubgraphSelection
): SerializedSubgraph
```

### `stripGrafloriaSidecar`

The body without any %%grafloria sidecar lines (what a human reads/edits).

```ts
function stripGrafloriaSidecar(text: string): string
```

### `unwrapDiagramDocument`

Accepts an enveloped document OR a legacy flat SerializedDiagram and
returns the inner document. When the envelope carries a checksum it is
verified (throws DiagramChecksumError on mismatch — an integrity failure
must never load silently).

```ts
function unwrapDiagramDocument(
  input: SerializedDiagram | DiagramDocumentEnvelope
): UnwrapResult
```

### `validateSerializedDiagram`

```ts
function validateSerializedDiagram(data: SerializedDiagram): DiagramValidationReport
```

### `wrapDiagramDocument`

```ts
function wrapDiagramDocument(
  document: SerializedDiagram,
  options: WrapOptions = {}
): DiagramDocumentEnvelope
```

## Classes

### `DiagramChecksumError`

```ts
class DiagramChecksumError extends Error
```

**Properties**

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| `name` | `string` |  |  |
| `message` | `string` |  |  |
| `stack?` | `string` |  |  |

**Methods**

- `constructor( public readonly expected: string, public readonly actual: string )`

### `DiagramSerializer`

```ts
class DiagramSerializer
```

**Methods**

- `serialize(diagram: DiagramModel): SerializedDiagram` — Serialize diagram to plain object
- `serializeEnvelope(diagram: DiagramModel, options?: WrapOptions): DiagramDocumentEnvelope` — Serialize wrapped in the portable document envelope (generator identity,
createdAt, integrity checksum). The envelope is the recommended shape for
NEW persistence; deserialize() accepts both it and the legacy flat form.
- `deserialize( data: SerializedDiagram | DiagramDocumentEnvelope, options?: DiagramLoadOptions ): DiagramModel` — Deserialize diagram from plain object — accepts the enveloped document,
the legacy Serializer flat form, or a raw DiagramModel.serialize payload. Envelope checksums are verified (mismatch throws — corruption must never
load silently).

### `DiagramValidationError`

Thrown by strict-mode loads when the document has integrity errors.

```ts
class DiagramValidationError extends Error
```

**Properties**

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| `name` | `string` |  |  |
| `message` | `string` |  |  |
| `stack?` | `string` |  |  |

**Methods**

- `constructor(public readonly report: DiagramValidationReport)`

### `IncrementalCapture`

Watches a live diagram and produces a DiagramIncremental per commit(). Continues capturing after commit (autosave-loop friendly) until stop().

```ts
class IncrementalCapture
```

**Methods**

- `constructor(private readonly diagram: DiagramModel)`
- `hasChanges(): boolean` — Anything captured since the window began?
- `commit(): DiagramIncremental | null` — Drain the window into a patch (null when nothing changed) and keep
capturing. Added/modified entities are serialized LIVE at commit time,
so intra-window churn costs nothing.
- `stop(): void` — Unsubscribe everything. The capture cannot be reused afterwards.

## Constants

### `DIAGRAM_ENVELOPE_FORMAT`

```ts
const DIAGRAM_ENVELOPE_FORMAT: "grafloria-diagram"
```

### `DIAGRAM_ENVELOPE_VERSION`

```ts
const DIAGRAM_ENVELOPE_VERSION: 1
```

### `DIAGRAM_SCHEMA_VERSION`

The schema version this engine writes.

History:
 - 1: implicit — documents written before schemaVersion existed
      (fromJSON bypassed the wired restore path; groups could carry a
      runtime 'diagram' metadata key).
 - 2: documents written by the unified load/save path. Structurally
      identical to v1 except: `schemaVersion` is stamped, `groups` is
      always present, and runtime-only metadata is never serialized.

```ts
const DIAGRAM_SCHEMA_VERSION: 3
```

### `GRAFLORIA_DOC_PREFIX`

```ts
const GRAFLORIA_DOC_PREFIX: "%%grafloria:document "
```

### `GRAFLORIA_HASH_PREFIX`

```ts
const GRAFLORIA_HASH_PREFIX: "%%grafloria:body-hash "
```

### `INCREMENTAL_FORMAT`

```ts
const INCREMENTAL_FORMAT: "grafloria-incremental"
```

### `SUBGRAPH_FORMAT`

```ts
const SUBGRAPH_FORMAT: "grafloria-subgraph"
```

## Interfaces

### `DeserializedSubgraph`

```ts
interface DeserializedSubgraph
```

**Properties**

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| `nodes` | `NodeModel[]` |  |  |
| `links` | `LinkModel[]` |  |  |
| `groups` | `GroupModel[]` |  |  |
| `idMap` | `Map<string, string>` |  | oldId -> newId for nodes and groups (identity map when remapIds=false). |
| `portIdMap` | `Map<string, string>` |  | oldPortId -> newPortId (identity when remapIds=false). |

### `DeserializeSubgraphOptions`

```ts
interface DeserializeSubgraphOptions
```

**Properties**

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| `remapIds?` | `boolean` |  | Mint fresh ids/uuids for every entity (default true). Turn off only when importing into a diagram KNOWN not to contain the ids (e.g. template instantiation into an empty document where stable ids are wanted). |
| `offset?` | `Point` |  | Translate all node/group positions by this delta (paste-at-cursor). |

### `DiagramDocumentEnvelope`

```ts
interface DiagramDocumentEnvelope
```

**Properties**

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| `format` | `typeof DIAGRAM_ENVELOPE_FORMAT` |  |  |
| `envelopeVersion` | `number` |  |  |
| `generator` | `string` |  | Writer identity, e.g. '@grafloria/engine'. |
| `generatorVersion` | `string` |  | Writer version — the document schemaVersion the writer targets. |
| `createdAt` | `string` |  | ISO-8601 creation timestamp. |
| `checksum?` | `string` |  | FNV-1a hash of the canonical JSON of `document` (see canonicalStringify). |
| `document` | `SerializedDiagram` |  |  |

### `DiagramIncremental`

```ts
interface DiagramIncremental
```

**Properties**

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| `format` | `typeof INCREMENTAL_FORMAT` |  |  |
| `schemaVersion` | `number` |  |  |
| `baseVersion` | `number` |  | diagram.version at the moment the window began (ordering hint). |
| `targetVersion` | `number` |  | diagram.version at commit — apply converges the replica's counter to it. |
| `added` | `{ nodes: SerializedNode[]; links: SerializedLink[]; groups: SerializedGroup[]; strokes?: SerializedStroke[]; }` |  |  |
| `removed` | `{ nodes: string[]; links: string[]; groups: string[]; strokes?: string[] }` |  |  |
| `modified` | `{ nodes: SerializedNode[]; links: SerializedLink[]; groups: SerializedGroup[]; strokes?: SerializedStroke[]; }` |  |  |
| `diagram?` | `{ name?: string; viewport?: { x: number; y: number; width: number; height: number; zoom: number }; metadata?: Record<string, unknown>; }` |  |  |

### `DiagramMigration`

```ts
interface DiagramMigration
```

**Properties**

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| `from` | `number` |  | Source schema version this migration upgrades FROM. |
| `to` | `number` |  | Target schema version (must be from + 1 — migrations run stepwise). |
| `description` | `string` |  | Human-readable summary shown in errors/logs. |

**Members**

- `migrate(data: SerializedDiagram): SerializedDiagram` — Pure upgrade: receives the document, returns the upgraded document.

### `DiagramValidationFinding`

```ts
interface DiagramValidationFinding
```

**Properties**

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| `severity` | `'error' \| 'warning'` |  |  |
| `code` | `string` |  | Stable machine-readable code (e.g. 'duplicate-id', 'dangling-link-endpoint'). |
| `message` | `string` |  |  |
| `entityId?` | `string` |  | Id of the entity the finding is about, when applicable. |

### `DiagramValidationReport`

```ts
interface DiagramValidationReport
```

**Properties**

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| `ok` | `boolean` |  |  |
| `errors` | `DiagramValidationFinding[]` |  |  |
| `warnings` | `DiagramValidationFinding[]` |  |  |

### `ExportTextOptions`

```ts
interface ExportTextOptions
```

**Properties**

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| `lossless?` | `boolean` |  | Append the lossless sidecar (default true). Without it the text is pure Mermaid and imports are best-effort DSL parses (the lossy boundary). |
| `positions?` | `boolean` |  | Write every node's and zone's exact position and size as `%%grafloria:at id x,y WxH` (default false). The sidecar already carries them; this is for a READABLE body that redraws the same picture on its own — the way an AI-drawn diagram is written for Grafloria. |

### `ImportTextOptions`

Also has every member of `DiagramLoadOptions`, listed on its own entry.

```ts
interface ImportTextOptions extends DiagramLoadOptions
```

**Properties**

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| `prefer?` | `'auto' \| 'sidecar' \| 'text'` |  | Which source wins when both exist: - 'auto' (default): the sidecar wins UNLESS the body hash shows the body was hand-edited after export — then the edited body wins. - 'sidecar': always load the sidecar document (ignore body edits). - 'text': always parse the body text (ignore the sidecar). |

### `ImportTextResult`

```ts
interface ImportTextResult
```

**Properties**

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| `diagram` | `DiagramModel` |  |  |
| `source` | `'sidecar' \| 'text'` |  | Which source actually produced the model. |
| `bodyEdited` | `boolean` |  | True when a sidecar existed but the body had been hand-edited. |
| `sidecarMerged?` | `boolean` |  | True when a hand-edited body was applied ON TOP of the sidecar document — the edit took, and everything the grammar cannot express (positions, styles, ports, groups, viewport) survived from the sidecar. |
| `sidecarInvalid?` | `boolean` |  | True when a sidecar line existed but its JSON would not parse. |
| `unsupported?` | `string` |  | Set to the diagram-type name when the body is a Mermaid type we recognise but do not yet parse (sequenceDiagram, gantt, pie, …). The diagram is empty rather than a garbage flowchart. |

### `SerializedDiagramData`

Also has every member of `SerializedDiagram`, `SerializedEntity`, listed on their own entries.

```ts
interface SerializedDiagram extends Omit<DiagramSerializedData, 'version'>
```

**Properties**

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| `version` | `string` |  |  |
| `diagramVersion?` | `number` |  |  |
| `mode?` | `DiagramMode` |  |  |

### `SerializedSubgraph`

```ts
interface SerializedSubgraph
```

**Properties**

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| `format` | `typeof SUBGRAPH_FORMAT` |  |  |
| `schemaVersion` | `number` |  |  |
| `sourceDiagramId?` | `string` |  |  |
| `nodes` | `SerializedNode[]` |  |  |
| `links` | `SerializedLink[]` |  |  |
| `groups` | `SerializedGroup[]` |  |  |
| `boundaryLinks` | `Array<{ linkId: string; insideEnd: 'source' \| 'target' }>` |  | Links that crossed the selection boundary and were therefore excluded. |

### `SubgraphSelection`

```ts
interface SubgraphSelection
```

**Properties**

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| `nodeIds` | `Iterable<string>` |  |  |
| `groupIds?` | `Iterable<string>` |  |  |

### `UnwrapResult`

```ts
interface UnwrapResult
```

**Properties**

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| `document` | `SerializedDiagram` |  |  |
| `envelope?` | `DiagramDocumentEnvelope` |  | Present when the input was enveloped. |

### `WrapOptions`

```ts
interface WrapOptions
```

**Properties**

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| `generator?` | `string` |  |  |
| `generatorVersion?` | `string` |  |  |
| `checksum?` | `boolean` |  | Include an integrity checksum (default true). |
| `createdAt?` | `string` |  | Timestamp override (tests / deterministic exports). |
