This document specifies Observed Overwrite Struct (OO-Struct), a state-based CRDT for fixed-key object structs. Each field maintains one visible value together with authoritative knowledge of overwritten UUIDv7 identifiers. The specification defines the data model, merge rules, snapshot and delta formats, acknowledgement frontiers, overwrite-set compaction, and one conforming JavaScript binding.
This is an unofficial specification maintained alongside a reference implementation.
The key words MUST, MUST NOT, SHOULD, SHOULD NOT, and MAY in this document are to be interpreted as described in BCP 14 [[RFC2119]] [[RFC8174]] when, and only when, they appear in all capitals.
Unless otherwise stated, neutral data-structure and algorithm terms such as struct, map, list, set, contains, and remove are used with the meanings defined by Infra [[INFRA]].
A conforming implementation MUST preserve the replica convergence and overwrite semantics defined by the algorithms in this specification.
OO-Struct is designed for static object shapes where keys are known up front and each key keeps one visible value of a stable runtime kind.
OO-Struct is informed by map CRDTs with observed-remove semantics [[RIAKDTMAP]] [[DELTACRDTS]] and by LWW-register-style one-visible- winner conflict resolution [[CRDTOVERVIEW]], but it is not an OR-Map or a plain last-writer-wins register. Its field set is fixed by the default struct, and its distinguishing replicated state is the per-field overwrite set that records which UUIDv7 identifiers have already been superseded.
Taken together, the fixed field set, one-visible-value policy, per-field overwrite tracking, and UUIDv7 identifiers yield the following operational properties in the reference binding:
An OO-Struct replica is one independently mutable instance of the data type.
A default struct is the application-supplied struct whose keys and default values define the replica's field space.
A field key is one own property name from the default struct.
A field value is the visible payload presently selected for a given field key.
A field state entry is the per-key replicated state record containing a current identifier, a predecessor identifier, a current field value, and a set of overwritten identifiers.
A UUIDv7 identifier is an identifier that is a valid UUID version 7 as defined by [[RFC9562]].
An overwritten identifier is a UUIDv7 that appears in a field state entry's set of overwritten identifiers.
A snapshot is the full current replica state containing one field state entry for every field key.
A delta is a partial snapshot containing only the keys a replica wishes to upstream.
A well-formed snapshot entry is a serialized field value that parses as a valid field state entry for the corresponding default struct field.
A snapshot input MAY contain malformed or unknown members. The reference binding processes known keys individually and ignores entries that do not parse.
Each OO-Struct replica maintains two logical objects:
Each current field state entry records one current UUIDv7 identifier, one predecessor UUIDv7 identifier, one current field value, and one set of overwritten identifiers.
A UUIDv7 that appears in a field's overwrite set is an overwritten identifier. If an identifier is overwritten, a conforming implementation MUST NOT later make it current again for that same field key.
The predecessor identifier names one overwritten predecessor and MUST itself be present in the field's overwrite set. The reference binding bootstraps each field with a synthetic predecessor UUIDv7 so that every field state entry always names an already overwritten predecessor.
A conforming OO-Struct implementation MUST assign one valid UUIDv7 identifier to each current field state entry.
The reference binding generates fresh UUIDv7 identifiers for local overwrites and compares canonical textual UUIDv7 representations lexicographically when it needs an order for merge resolution.
Ordering does not override overwrite knowledge. If an identifier is in a field's overwrite set, it is an overwritten identifier regardless of any later lexical comparison.
A create operation constructs a new replica from a default struct and an optional snapshot.
If a snapshot record is supplied, the replica attempts to adopt each known field whose incoming value is a well-formed snapshot entry. Unknown or malformed entries are ignored. Any field not adopted from the snapshot is initialized from the default struct with fresh UUIDv7 state.
A read operation returns the current visible field value for a known field key.
The reference binding returns a detached clone rather than the live stored value reference.
An update operation MUST mint a fresh UUIDv7 identifier for the target field key, make the previously current UUIDv7 the new predecessor identifier, add that predecessor to the overwrite set, and set the field value to the supplied payload.
In the reference binding, the payload MUST be
structuredClone-compatible and MUST have the same
runtime prototype category as the corresponding default value. Every
successful local update creates a new per-field delta and a
new per-field change event.
In the reference binding, delete() resets fields to
their default values. It does not remove the field key from
the struct shape.
If a key is supplied, only that field is reset. If no key is supplied, every field in the default struct is reset. Unknown explicit keys are ignored.
Merge operates over a delta or a full snapshot. A conforming implementation MUST process the incoming state one field key at a time.
When merging one incoming field state entry:
An acknowledge operation emits one acknowledgement frontier per known field key.
In the reference binding, the acknowledgement frontier for a field is the lexicographically greatest overwritten UUIDv7 currently known for that field.
A garbage-collection operation accepts a list of acknowledgement frontiers and compacts overwrite history that is already dead.
In the reference binding, garbage collection determines the smallest
acknowledged UUIDv7 per known key and removes overwritten
identifiers that are less than or equal to that frontier, except for
the current predecessor identifier named by __after.
A snapshot MUST contain enough information to recreate the current field state entry for every known field key.
A delta MUST use the same per-field state-entry format as a snapshot but MAY include only a subset of keys.
These operations expose resolved live state without mutating replica history.
In the reference binding, keys() returns the known
field keys, values() returns detached clones of the
current visible values, and entries() returns key-value
pairs whose values are likewise detached clones.
The reference binding exposes compaction explicitly through
acknowledge() and garbageCollect().
Acknowledgement and garbage collection operate only on identifiers that
are already known to be overwritten. They do not remove the current
winning UUIDv7 for a field, and they do not remove the current
predecessor identifier named by after.
Consequently, compaction can shrink overwrite history without changing the resolved visible live state of the replica.
OO-Struct is best suited to static object shapes whose fields hold stable runtime kinds. It is not, by itself, a field-level CRDT for recursively mutating nested object graphs.
The reference binding performs a shallow runtime-kind validation against the corresponding default value. It does not define deep schema validation for nested payload structure.
This section defines one conforming JavaScript binding for the model above.
[Exposed=*]
interface OOStruct {
constructor(object defaults, optional object snapshot);
static OOStruct create(object defaults, optional object snapshot);
any read(DOMString key);
undefined update(DOMString key, any value);
undefined delete(optional DOMString key);
undefined merge(object replica);
undefined acknowledge();
undefined garbageCollect(sequence<object> frontiers);
undefined snapshot();
sequence<DOMString> keys();
sequence<any> values();
sequence<any> entries();
undefined addEventListener(DOMString type, any listener, optional any options);
undefined removeEventListener(DOMString type, any listener, optional any options);
};
dictionary OOStructSnapshotEntry {
required DOMString uuidv7;
required DOMString after;
required any value;
required FrozenArray<DOMString> overwrites;
};
Snapshot and delta payloads are plain JavaScript objects keyed by known fields. Change payloads are plain JavaScript objects keyed by changed fields, and acknowledgement payloads are plain JavaScript objects keyed by acknowledged fields.
The actual field-entry payload member names are
__uuidv7, __after, __value, and
__overwrites. The Web IDL above uses IDL-safe member
names because dictionary members cannot use the double-underscore
form.
The Web IDL above is a coarse binding summary. The precise key- and value-level behavior is defined by the prose and algorithms below.
The JavaScript binding throws OOStructError with a string
code attribute for local API misuse.
DEFAULTS_NOT_CLONEABLEstructuredClone.
VALUE_NOT_CLONEABLEstructuredClone.
VALUE_TYPE_MISMATCH
The JavaScript binding maintains an internal
EventTarget object as defined by DOM [[DOM]].
This binding is not specified as inheriting from
EventTarget. Instead, its listener methods forward to
that internal event target, and its local mutations and merges
dispatch synthetic events through it.
The JavaScript binding defines four synthetic event types. Each is a
CustomEvent object whose detail attribute is
initialized as described below [[DOM]].
CustomEvent whose detail is a per-field
delta. The binding dispatches this event for local overwrites
and for merge-time repair or rebuttal state that should be
upstreamed.
CustomEvent whose detail maps changed
keys to their new visible values.
CustomEvent whose detail maps known keys
to their current acknowledgement frontier values produced when
acknowledge()
is invoked.
CustomEvent whose detail is the full
current snapshot produced when
snapshot()
is invoked.
update() and delete() MUST dispatch delta event before change event. A merge MUST dispatch delta event only when it produces egress delta material and MUST dispatch change event only when visible field values change. acknowledge() MUST dispatch ack event, and snapshot() MUST dispatch snapshot event.
__value
member, then return failure.
__uuidv7 member is not a
valid UUIDv7 identifier, then return failure.
__after member is not a
valid UUIDv7 identifier, then return failure.
__overwrites member is not a
list, then return failure.
__value member cannot be
copied with structuredClone, then return failure.
__uuidv7; add the rest to
overwrites.
__after,
then return failure.
Return a new snapshot entry whose scalar members equal the field
entry, whose __value member is a detached structured
clone of the current field value, and whose
__overwrites is a list created from the overwrite set
in iteration order.
structuredClone.
OOStructError with code
DEFAULTS_NOT_CLONEABLE.
__uuidv7, a fresh
synthetic root in __after, and an overwrite set
containing only that synthetic root.
Return a new OOStruct constructed from the arguments.
Return a detached structured clone of the current visible value for key.
structuredClone.
OOStructError with code
VALUE_NOT_CLONEABLE.
OOStructError with code
VALUE_TYPE_MISMATCH.
__after and __overwrites, and set the
field's current value to the copied payload.
undefined and is unknown in the
default struct, then return.
Run serialize a state entry on every current field entry,
assemble the results into a full snapshot, and dispatch a
snapshot event. The method returns undefined.
Return the known field keys from live state.
Return detached structured clones of the current visible field values.
Return key-value pairs where each value is a detached structured clone of the current visible field value.
Forward listener registration and listener removal to the
binding's internal EventTarget object using the
provided type, listener, and options values.
This specification builds on UUIDv7 [[RFC9562]], map CRDTs with observed-remove semantics [[RIAKDTMAP]] [[DELTACRDTS]], and LWW-register literature [[CRDTOVERVIEW]], while defining a new fixed-key overwrite-tracked struct CRDT.