11. Layer 8 — Persistence
Contents of DISL 0.1
The persistence layer declares exactly how the diagrams users draw are stored as DID definitions: which format, which files, how identifiers are generated, how elements and keys are ordered, which view data is kept, how precise numbers are, how old DID definitions are migrated, and how several people can edit at once. The goal is that any two conforming runtimes produce identical files for the same diagram, that files diff and merge well in version control, and that stored diagrams remain readable for decades. The structure these settings configure, and the rules a writer follows, are specified by DID (DID-specification.md).
{
"persistence": {
"format": "json",
"files": { "mode": "split", "model": "{name}.sm.json", "view": "{name}.sm.view.json" },
"ids": { "strategy": "uuid-v7", "prefix": { "State": "st_", "Transition": "tr_" } },
"references": "id",
"ordering": { "elements": "type-then-id", "keys": "canonical" },
"omitDefaults": true,
"precision": { "canvas": 2 },
"timestamps": { "format": "rfc3339", "timezone": "utc" },
"view": { "store": ["bounds", "waypoints", "labelOffsets", "collapsed", "zIndex", "rotation", "viewport"],
"styleOverrides": ["fill", "stroke.color", "font.weight"] },
"metadata": ["languageVersion", "createdAt", "modifiedAt", "generator"],
"migrations": [],
"collaboration": { "mode": "crdt", "engine": "yjs" }
}
}
| Property | Type | Description |
|---|---|---|
format |
"json" (default), "yaml", "jsonl", "cbor", "plugin:<name>" |
Serialization. All formats encode the same logical DID definition (11.4). jsonl writes one element per line, which merges particularly well. cbor is for large binary-efficient storage. |
encoding |
"utf-8" |
Only UTF-8 is allowed. |
indent |
int or "tab" |
Default 2. 0 writes minified JSON. |
newline |
"lf", "crlf" |
Default "lf". |
finalNewline |
bool | Default true. |
compression |
"none", "gzip", "zip-bundle" |
zip-bundle stores model, views and binary assets (images) in one archive with a manifest. |
mediaType |
string | Media type of DID definitions. |
YAML output MUST quote strings that would otherwise be read as other types (the "Norway problem": no, yes, on, off, ~, numeric-looking strings, dates) and MUST NOT use anchors, aliases or tags.
| Property | Type | Description |
|---|---|---|
mode |
"single" (default), "split", "per-element", "bundle" |
single: one file with model and views. split: model and view data in separate files, so layout edits never touch the model file. per-element: one file per top-level element in a directory plus an index (for very large models edited by teams). bundle: see compression: "zip-bundle". |
model, view, index |
pattern | File name patterns; {name} is the diagram's name, {id} and {type} the element's id and type for per-element. |
assets |
{mode, dir} |
Binary assets: "inline" (base64), "external" (files in dir, referenced by relative URI), "bundle". |
lock |
bool | Write a lock file while open (for file-based single-user editing). |
What a stored diagram looks like, independently of format and file split, is specified by DID, the Diagram Definition Language (DID-specification.md, section 3): the element, relation and view records, and the node and edge view data. Every DID definition names the DISL specification it was made with in its language object. The settings in this section and in 11.5–11.7 are declared by a specification and configure that structure; DID specifies what a conforming writer produces from them.
| Property | Type | Description |
|---|---|---|
strategy |
"uuid-v4", "uuid-v7" (default), "ulid", "nanoid", "sequential", "natural", "cel" |
How new ids are generated (DID, section 4). uuid-v7 and ulid are time-ordered, which keeps id-sorted files in creation order and helps merges. sequential uses per-type counters (State_1) and is only suitable for single-user editing. natural derives ids from key attributes (unique by constraint). cel evaluates expression. |
prefix |
string or map TypeRef → string | Type prefixes ("st_"), making ids self-describing in diffs. |
expression |
Expression | For cel: context create, must return a string; uniqueness is enforced by appending -2, -3, … |
stable |
bool | Ids never change once assigned, even if natural keys change (the first derived id is kept). Default true. |
pattern |
regex | Allowed id syntax; default ^[A-Za-z0-9_.:#-]{1,128}$. |
view.store lists which kinds of view data are persisted: bounds, waypoints, anchors, labelOffsets, collapsed, zIndex, rotation, params, ports, viewport, guides, pinned, settings. Anything not listed is recomputed on load (by layout or defaults). A pure layout-driven language stores nothing but the model. DID, section 5, specifies the stored form.
view.styleOverrides is "none" (default), "all", or a list of style property paths users may override per element ("fill", "stroke.color", "stroke.dash", "font.size"). Overrides are stored in NodeView.style / EdgeView.style and have the highest precedence (6.1).
| Property | Type | Description |
|---|---|---|
ordering.elements |
"creation", "id", "type-then-id", "tree", "cel" |
Order of elements and relations. tree writes parents before children (depth-first, siblings by order). Default "id". |
ordering.orderBy |
Expression | For cel: sort key per element. |
ordering.keys |
"canonical", "alphabetical" |
Key order within objects. canonical: the order of this specification's tables (id, type, parent, slot, order, attributes, ports; then x-); attribute keys in metamodel declaration order. |
omitDefaults |
bool | Do not write attribute values equal to their default, view sizes equal to the notation default, or empty collections. Default true. |
precision |
{canvas, numbers} |
Decimal places for canvas coordinates (default 2) and for number attributes without precision facet (default: shortest round-trip representation). |
timestamps |
{format, timezone} |
format: "rfc3339" (default) or "epoch-ms"; timezone: "utc" (normalise to Z) or "preserve" (keep the offset entered). |
canonical |
bool | Follow RFC 8785 (JSON Canonicalization Scheme) number and string serialization. Default true. |
A conforming writer applies these settings as DID, section 6, specifies, including its determinism requirement: the same logical DID definition and specification MUST produce byte-identical output. Suppressions of constraint problems are stored as DID, section 3, describes.
metadata lists the metadata a DID definition carries in meta: languageVersion (always written in language.version), createdAt, modifiedAt, generator, authors (from env.user if the user consents), title (mirrors a diagram attribute), checksum.
definition controls how DID definitions refer to their specification: { "embed": "none" | "reference" | "inline", "uri": "https://…/statemachine-1.2.0.dis" }. reference writes language.definition with the URI of the specification and its integrity hash; inline embeds the whole specification (self-contained archives). The property names definition are kept unchanged from the earlier format.
Migrations upgrade DID definitions written with older language versions. They run on load, before validation, in version order.
{
"migrations": [
{
"from": ">=1.0.0 <1.1.0", "to": "1.1.0",
"doc": "Renamed 'label' to 'name'; transitions got explicit priorities.",
"steps": [
{ "renameAttribute": { "type": "State", "from": "label", "to": "name" } },
{ "setAttribute": { "type": "Transition", "attribute": "priority", "value": "0", "when": "!has(element.attributes.priority)" } }
]
},
{
"from": ">=1.1.0 <2.0.0", "to": "2.0.0",
"steps": [
{ "renameType": { "from": "EndState", "to": "FinalState" } },
{ "transform": { "type": "State", "cel": "element.attributes.with({'entryAction': element.attributes.?entry.orValue('')})" } },
{ "deleteAttribute": { "type": "State", "attribute": "entry" } },
{ "convertView": { "system": "canvas", "x": "value * 2.0", "y": "value * 2.0" } }
]
}
]
}
| Step | Fields | Effect |
|---|---|---|
renameType |
from, to |
Changes type of elements, relations and references. |
renameAttribute |
type, from, to |
Renames stored attribute keys (including in subtypes). |
deleteType |
type, mode ("delete", "keep-as-unknown") |
Removes elements of a type (and attached relations). |
deleteAttribute |
type, attribute |
|
setAttribute |
type, attribute, value (Expression), when |
Sets a value, for example a default for a new required attribute. |
convertAttribute |
type, attribute, cel |
Converts a value; value is the old value. |
transform |
type, cel |
Returns a replacement attributes map; element is the raw element record as a map. |
retype |
type, to, when |
Changes the type conditionally. |
convertView |
system, x, y, w, h |
Converts stored view coordinates (unit or origin changes). |
renamePort, renameSlot |
||
plugin |
name, args |
Custom migration. |
Migration expressions run in the migration context (12.3), which sees raw records as maps (element, value, document) rather than typed elements, because the old DID definition does not conform to the new metamodel. A migration that fails aborts loading; the original file is never modified until the user saves. Runtimes SHOULD tell users that a diagram was migrated and to which version, and MAY keep a backup.
DID definitions with a newer minor or patch language version than the loaded specification are opened with a warning; newer major versions are opened read-only or refused.
| Property | Type | Description |
|---|---|---|
mode |
"none" (default), "lock", "ot", "crdt" |
Single user; pessimistic element locks; operational transformation; conflict-free replicated data types. |
engine |
"yjs", "automerge", "plugin:<name>" |
CRDT or OT engine. |
granularity |
"element", "attribute" |
Conflict unit. attribute merges concurrent edits of different attributes of one element. |
text |
"replace", "merge" |
Concurrent edits of text/string attributes: last writer wins or character-level merge. |
ordering |
"fractional-index" |
Order keys for ordered children and lists that merge without renumbering. |
presence |
bool | Share cursors and selections. |
conflicts |
"last-writer-wins", "report" |
Whether semantic conflicts (both sides valid, the merge invalid) are reported as constraint problems attributed to the merge. |
The CRDT state is a transport concern; the persisted DID definition remains the canonical form (DID, section 6). Constraints are re-evaluated after merges.
persistence.export and persistence.import declare conversions: [{ "format": "svg" | "png" | "pdf" | "did-fragment" | "plugin:<name>", "label", "doc", "options" }]. did-fragment writes a DID fragment (DID, section 7). Image exports render the notation exactly as on screen (without interaction states) at a chosen scale; pdf honours canvas.page. Constraints with timing: "export" run before export.
Illustrated by
- Entity–relationship diagram: 6 items in this layer
- State machine: 20 items in this layer
- Project timeline: 12 items in this layer
Referenced from
- 2. Foundations
- 3. Specification structure, layers and viewpoints
- 4. Layer 1 — Metamodel
- 5. Layer 2 — Coordinate systems, placement and snapping
- 6. Layer 3 — Notation (visual definition)
- 8. Layer 5 — Constraints
- 12. The CEL environment
- 14. Processing model
- 16. Security, privacy and robustness
- 18. Deprecated aliases
- Appendix D — Design rationale and open questions