The document a history row can restore — gzipped HEEx, in a table of its own.
Split from MishkaCmsCore.Runtime.PageBuilderEvent on a measurement rather than a preference.
Postgres only moves a value out of the row past ~2 kB, and a typical snapshot is around 1.3 kB, so
keeping it on the event row left it INLINE: 1,638 bytes of heap per row against 122, thirteen times
the heap the timeline's own query walks on every page it draws.
Why HEEx and not the tree
The editor's tree is 5.5–12.7× the size of the HEEx it came from, and a compressed tree is still
2.3–4.5× a gzipped HEEx while costing 3–5× the CPU to produce. Serializer.to_heex/1 already runs
on every save, so gzipping its output adds a few percent to work already being done, where encoding
the tree adds ten to fifteen.
What a round trip loses
Node ids. They are minted fresh by every Tree.parse/1, so a restored document is structurally and
stylistically identical and its ids are new. Anything that must survive a restore anchors by
Tree.address/2 instead — the same answer MishkaCmsCore.Runtime.PageComment reached when it hit
this first.
Refusal
A document past max_bytes/0 gets no snapshot at all: the event row is still written and simply
cannot be reverted into, which the timeline says out loud. Storing a multi-megabyte payload on a
path that runs while somebody is typing is the wrong trade, and replay was rejected for making
every restore depend on every earlier row staying correct forever.
Summary
Functions
Compress a document for storage, or refuse it for being too large.
The document back, or :error when the payload will not decompress.
Validates that the keys in the provided input are valid for at least one action on the resource.
Same as input/1, except restricts the keys to values accepted by the action provided.
Largest HEEx a snapshot will be taken of, in bytes, for a change made by author_type.
Types
Functions
@spec compress(String.t(), pos_integer()) :: {:ok, binary(), non_neg_integer()} | :too_large
Compress a document for storage, or refuse it for being too large.
Returns {:ok, payload, byte_size} or :too_large. The size recorded is the ORIGINAL's, because
that is the number a threshold is about and the compressed length says nothing useful to a reader.
The document back, or :error when the payload will not decompress.
Validates that the keys in the provided input are valid for at least one action on the resource.
Raises a KeyError error at compile time if not. This exists because generally a struct should only ever
be created by Ash as a result of a successful action. You should not be creating records manually in code,
e.g %MyResource{value: 1, value: 2}. Generally that is fine, but often with embedded resources it is nice
to be able to validate the keys that are being provided, e.g
Resource
|> Ash.Changeset.for_create(:create, %{embedded: EmbeddedResource.input(foo: 1, bar: 2)})
|> Ash.create()
Same as input/1, except restricts the keys to values accepted by the action provided.
@spec max_bytes(atom() | nil) :: pos_integer()
Largest HEEx a snapshot will be taken of, in bytes, for a change made by author_type.
The assistant's rows are allowed a bigger document than anybody else's; see the note above
@ai_max_bytes.