nupp.serde.document

A document is a value without a schema, which any codec reads and writes. This module holds the Document tree, conversion between bound values and documents, typed views, and saved nupp.util.Store snapshots; see Serialization bindings for the guide.

local document = require("nupp.serde.document")
local json = require("nupp.serde.json")
local value = json.decodeDocument(document.adapter(), '{"id":18446744073709551615,"tags":["a","b"]}')
assert(value.kind == "structure")
assert(assert(value:get("id")):numberToken() == "18446744073709551615")
assert(assert(value:get("tags")):at(2):string() == "b")
assert(json.encodeDocument(document.adapter(), value) == '{"id":18446744073709551615,"tags":["a","b"]}')

A document keeps what the codec read without rounding it: integers beyond 2^53 stay exact, decimals keep their digits, floats keep their width, and absent, null, and present members stay distinct. It also keeps the nupp.serde.Context it was read under, so a protocol can tell where it came from.

A document-valued member, one a schema declares without describing, is a document boundary: boundary wraps its contents so a protocol can treat the member separately from the value inside it.

from and into cross between a bound value and its document, which is how a value moves between codecs or is inspected without knowing its type:

local serde = require("nupp.serde")
local document = require("nupp.serde.document")
local record Point
    x: integer
    y: integer
end
local points = serde.binding(Point)
local tree = document.from(points, new Point(x = 1, y = 2))
assert(assert(tree:get("y")):numberToken() == "2")
assert(document.into(points, tree).x == 1)

Module contents

Types

TypeKindDescription
DecoderinterfaceConstructs values of one binding from documents, without looking the binding up each time.
DocumentrecordAn immutable value tree.
EntryrecordA named structure member, as object takes and Document:field returns.
MapEntryrecordA map entry, whose key is itself a document.
MigrationinterfaceUpgrades a saved entry from an older version to the current one.
StoreEntryrecordOne saved store entry: its name, version, and document.
StoreRegistryinterfaceSaves and restores the entries of a nupp.util.Store by name and version.
StoreSnapshotrecordThe saved entries of a store.
ViewrecordA bound value presented as a document, without building the tree.

Functions

FunctionKindDescription
adapterfunctionReturns the adapter that reads and writes a Document as its contents.
atomfunctionA model-defined atom, snapshotted so the document owns it.
bigIntegerfunctionAn integer of unbounded size, from its decimal text.
booleanfunctionA boolean.
boundaryfunctionA document boundary around contents, for a document-valued member.
bytesfunctionBinary data.
cachedBindingsfunctionReturns how many bindings the decoder cache holds.
clearfunctionEmpties the decoder cache.
decimalfunctionAn exact decimal.
decoderfunctionReturns a binding's Decoder, cached by binding identity.
floatfunctionA float with its width.
fromfunctionConverts a bound value to its document.
integerfunctionA fixed-width integer.
intofunctionConstructs a bound value from a document.
listfunctionA list.
mapfunctionA map whose keys are documents.
nullfunctionA null.
numberTokenfunctionA number kept as the lexeme it was written as, such as a JSON number too large for any integer width.
objectfunctionA structure, from its members in order.
readerfunctionReads a document through the nupp.serde.DocumentReader contract, as a codec's own reader would.
snapshotfunctionCopies a document into one that owns all of its values.
storeBindingfunctionReturns the binding a codec writes and reads a StoreSnapshot through.
storeRegistryfunctionStarts an empty StoreRegistry.
stringfunctionA string.
timestampfunctionA timestamp.
tuplefunctionA tuple: a list whose length is part of its shape.
unionfunctionA union: the one member that is set.
viewfunctionPresents a Copyable value as a View.
viewAdapterfunctionReturns the document adapter for views of one binding.
withContextfunctionThe same document with a context attached.

Types#

Decoderinterface#

interface Decoder<T> ...

Constructs values of one binding from documents, without looking the binding up each time.

Type parameters

NameDescription
T

Members

NameKindDescription
readmethodConstructs a value from a document.

readmethod#

read: function(self, value: documents.Document): T

Constructs a value from a document.

The binding's construction runs exactly as a codec decoder's would, so required fields and defaults are checked the same way.

Arguments
NameTypeDescription
?self
valuedocuments.Document

the document

Returns
TypeDescription
T

the value

Raises
TypeCondition
nupp.serde.Error

when the document does not fit the binding

string

when the binding does not consume the document

Documentrecord#

record Document ...

An immutable value tree.

kind says which accessor applies: string() for a string, at(i) and size() for a list or tuple, get(name) and field(i) for a structure, entry(i) for a map, document() for a boundary. get returns nil on any other kind; a scalar accessor raises on a document of another kind.

Members

NameKindDescription
kindfieldThe kind of value the document holds, which decides the accessor that applies.
textfield
flagfield
timefield
integerValuefield
decimalValuefield
floatValuefield
atomValuefield
entriesfield
elementsfield
fieldsfield
sourcefield
sizemethodReturns how many values the document holds directly.
atmethodReturns the element at a position in a list or tuple.
getmethodFinds the member of a structure or union by name.
stringmethodReturns the string a string document holds.
numberTokenmethodReturns the lexeme a numberToken document holds.
booleanmethodReturns the value a boolean document holds.
integermethodReturns the fixed-width integer an integer document holds.
decimalmethodReturns the exact decimal a decimal document holds.
floatmethodReturns the float a float document holds.
bytesmethodReturns the binary data a bytes document holds.
bigIntegermethodReturns the integer a bigInteger document holds.
timestampmethodReturns the timestamp a timestamp document holds.
atommethodReturns a snapshot of the atom an atom document holds.
entrymethodReturns the entry at a position in a map document.
fieldmethodReturns the member at a position in a structure or union document.
documentmethodReturns the contents of a document boundary.
contextmethodReturns the context the document carries.

kindfield#

kind: core.Kind
@readonly

The kind of value the document holds, which decides the accessor that applies.

textfield#

@private@readonly

flagfield#

flag: boolean?
@private@readonly

timefield#

time: core.Timestamp?
@private@readonly

integerValuefield#

integerValue: core.Integer?
@private@readonly

decimalValuefield#

decimalValue: core.Decimal?
@private@readonly

floatValuefield#

floatValue: core.Float?
@private@readonly

atomValuefield#

atomValue: core.Atom?
@private@readonly

entriesfield#

entries: {MapEntry}?
@private@readonly

elementsfield#

@private@readonly

fieldsfield#

@private@readonly

sourcefield#

source: core.Context?
@private@readonly

sizemethod#

size: function size(self): integer

Returns how many values the document holds directly.

A structure or union counts its members, a map its entries, and a list or tuple its elements. A boundary counts its contents as one, and any other kind returns 0.

Arguments
NameTypeDescription
selfany
Returns
TypeDescription
integer

the count

atmethod#

at: function at(self, index: integer): Document

Returns the element at a position in a list or tuple.

Arguments
NameTypeDescription
selfany
indexinteger

the 1-based position

Returns
TypeDescription
Document

the element

Raises
TypeCondition
string

when no element is at the index

getmethod#

get: function get(self, name: string): Document?

Finds the member of a structure or union by name.

Any other kind has no members, so it returns nil.

Arguments
NameTypeDescription
selfany
namestring

the member name

Returns
TypeDescription
Document?

the member's value, or nil when no member has the name

stringmethod#

string: function string(self): string

Returns the string a string document holds.

Arguments
NameTypeDescription
selfany
Returns
TypeDescription
string
Raises
TypeCondition
string

when the document is not a string

numberTokenmethod#

numberToken: function numberToken(self): string

Returns the lexeme a numberToken document holds.

Arguments
NameTypeDescription
selfany
Returns
TypeDescription
string

the number as it was written

Raises
TypeCondition
string

when the document is not a number token

booleanmethod#

boolean: function boolean(self): boolean

Returns the value a boolean document holds.

Arguments
NameTypeDescription
selfany
Returns
TypeDescription
boolean
Raises
TypeCondition
string

when the document is not a boolean

integermethod#

integer: function integer(self): core.Integer

Returns the fixed-width integer an integer document holds.

Arguments
NameTypeDescription
selfany
Returns
TypeDescription
core.Integer

the integer's decimal digits, width, and signedness

Raises
TypeCondition
string

when the document is not an integer

decimalmethod#

decimal: function decimal(self): core.Decimal

Returns the exact decimal a decimal document holds.

Arguments
NameTypeDescription
selfany
Returns
TypeDescription
core.Decimal

the decimal's coefficient and exponent

Raises
TypeCondition
string

when the document is not a decimal

floatmethod#

float: function float(self): core.Float

Returns the float a float document holds.

Arguments
NameTypeDescription
selfany
Returns
TypeDescription
core.Float

the float's value and width

Raises
TypeCondition
string

when the document is not a float

bytesmethod#

bytes: function bytes(self): string

Returns the binary data a bytes document holds.

Arguments
NameTypeDescription
selfany
Returns
TypeDescription
string

the data as a string

Raises
TypeCondition
string

when the document is not bytes

bigIntegermethod#

bigInteger: function bigInteger(self): string

Returns the integer a bigInteger document holds.

Arguments
NameTypeDescription
selfany
Returns
TypeDescription
string

the integer's decimal text

Raises
TypeCondition
string

when the document is not a big integer

timestampmethod#

timestamp: function timestamp(self): core.Timestamp

Returns the timestamp a timestamp document holds.

Arguments
NameTypeDescription
selfany
Returns
TypeDescription
core.Timestamp
Raises
TypeCondition
string

when the document is not a timestamp

atommethod#

atom: function atom(self): core.Atom

Returns a snapshot of the atom an atom document holds.

Each call returns a new snapshot, so the document keeps its own.

Arguments
NameTypeDescription
selfany
Returns
TypeDescription
core.Atom
Raises
TypeCondition
string

when the document is not an atom

entrymethod#

entry: function entry(self, index: integer): MapEntry

Returns the entry at a position in a map document.

Arguments
NameTypeDescription
selfany
indexinteger

the 1-based position

Returns
TypeDescription
MapEntry

the entry

Raises
TypeCondition
string

when the document is not a map

string

when no entry is at the index

fieldmethod#

field: function field(self, index: integer): Entry

Returns the member at a position in a structure or union document.

Arguments
NameTypeDescription
selfany
indexinteger

the 1-based position

Returns
TypeDescription
Entry

the member

Raises
TypeCondition
string

when the document is neither a structure nor a union

string

when no member is at the index

documentmethod#

document: function document(self): Document

Returns the contents of a document boundary.

Arguments
NameTypeDescription
selfany
Returns
TypeDescription
Document
Raises
TypeCondition
string

when the document is not a boundary

contextmethod#

context: function context(self): core.Context?

Returns the context the document carries.

A decoded document carries the context it was read under, and nupp.serde.document.withContext attaches one to any document.

Arguments
NameTypeDescription
selfany
Returns
TypeDescription
core.Context?

the context, or nil when the document has none

Entryrecord#

record Entry ...

A named structure member, as object takes and Document:field returns.

Members

NameKindDescription
namefieldThe member's name.
valuefieldThe member's value.

namefield#

@readonly

The member's name.

valuefield#

@readonly

The member's value.

MapEntryrecord#

record MapEntry ...

A map entry, whose key is itself a document.

Members

NameKindDescription
keyfieldThe entry's key.
valuefieldThe entry's value.

keyfield#

@readonly

The entry's key.

valuefield#

@readonly

The entry's value.

Migrationinterface#

interface Migration ...

Upgrades a saved entry from an older version to the current one.

Members

NameKindDescription
applymethodUpgrades a saved entry's document to the registered version.

applymethod#

apply: function(self, previousVersion: integer, value: documents.Document): documents.Document

Upgrades a saved entry's document to the registered version.

Loading calls it once per older entry, and its result is constructed through the registered binding, so it must return the current version's document directly.

Arguments
NameTypeDescription
?self
previousVersioninteger

the version the entry was saved at

valuedocuments.Document

the saved document

Returns
TypeDescription
documents.Document

the document at the registered version

StoreEntryrecord#

record StoreEntry ...

One saved store entry: its name, version, and document.

Members

NameKindDescription
namefieldThe name the entry's key is registered under.
versionfieldThe version the entry was saved at.
valuefieldThe entry's value, as a document.

namefield#

@readonly

The name the entry's key is registered under.

versionfield#

version: integer
@readonly

The version the entry was saved at.

valuefield#

value: documents.Document
@readonly

The entry's value, as a document.

StoreRegistryinterface#

interface StoreRegistry ...

Saves and restores the entries of a nupp.util.Store by name and version.

Each registered key names its entry, its binding, its current version, and the migration that upgrades an older one. Saving produces a StoreSnapshot any codec can carry through storeBinding.

local serde = require("nupp.serde")
local document = require("nupp.serde.document")
local json = require("nupp.serde.json")
local record Settings
    volume: integer
end
local settings: nupp.util.Key<Settings> = nupp.util.newKey()
local registry = document.storeRegistry()
registry:register("settings", settings, serde.binding(Settings), 1, nil)

local store = nupp.util.newStore()
store:set(settings, new Settings(volume = 7))
local bytes = json.encode(document.storeBinding(), registry:save(store))

local restored = registry:load(json.decode(document.storeBinding(), bytes), "reject")
assert(assert(restored:get(settings)).volume == 7)

Members

NameKindDescription
registermethodRegisters a store key for saving under a persistent name.
savemethodSaves the registered keys that hold a value in a store.
loadmethodRestores a snapshot's entries into a new store.

registermethod#

register: function<T is nupp.Copyable>(
    self,
    name: string,
    key: util.Key<T>,
    binding: core.Binding<T>,
    version: integer,
    migration: Migration?
): nil

Registers a store key for saving under a persistent name.

Arguments
NameTypeDescription
?self
namestring

the name the key's entry is saved under

keyutil.Key<T>

the store key

bindingcore.Binding<T>

the binding the key's value is saved and restored through

versioninteger

the current version, a positive integer

migrationMigration?

the migration that upgrades an older entry, or nil when only the current version loads

Returns
TypeDescription
nil
Raises
TypeCondition
string

when the name is empty or already registered

string

when the key is already registered

string

when the version is not a positive integer

savemethod#

save: function(self, borrows store: util.Store): Snapshot

Saves the registered keys that hold a value in a store.

Entries are in registration order, each at its key's current version. A registered key the store holds no value under is left out.

Arguments
NameTypeDescription
?self
borrows storeutil.Store

the store to read, which is borrowed

Returns
TypeDescription
Snapshot

the snapshot

loadmethod#

load: function(self, snapshot: Snapshot, unknown: "reject" | "skip"): util.Store

Restores a snapshot's entries into a new store.

An entry at its key's registered version is constructed through the binding as it is. An older entry passes through the key's migration first. A newer entry, or an older one with no migration, raises.

Arguments
NameTypeDescription
?self
snapshotSnapshot

the saved entries

unknown"reject" | "skip"

"reject" to raise on an entry whose name is not registered, or "skip" to leave it out

Returns
TypeDescription
util.Store

the store holding every restored entry

Raises
TypeCondition
string

when the snapshot's format is not 1

string

when two entries share a name

string

when an entry's name is not registered and unknown is "reject"

string

when an entry's version is not a positive integer, is newer than the registered version, or is older with no migration

nupp.serde.Error

when an entry's document does not fit its binding

string

when an entry's document constructs a nil value

StoreSnapshotrecord#

record StoreSnapshot ...

The saved entries of a store.

Members

NameKindDescription
formatfieldThe snapshot layout version, which loading requires to be 1.
entriesfieldThe saved entries.

formatfield#

format: integer
@readonly

The snapshot layout version, which loading requires to be 1.

entriesfield#

entries: {Entry}
@readonly

The saved entries.

Viewrecord#

record View<T is nupp.Copyable> ...

A bound value presented as a document, without building the tree.

A codec reads the current value through the binding each time, so a view follows changes to its owner. snapshot() reconstructs a detached copy, and document() materializes the tree when one is wanted.

local serde = require("nupp.serde")
local document = require("nupp.serde.document")
local json = require("nupp.serde.json")
local record Counter
    count: integer
end
local counters = serde.binding(Counter)
local owner = new Counter(count = 1)
local live = document.view(counters, owner)
local frozen = live:snapshot()
owner.count = 2
local views = document.viewAdapter(counters)
assert(json.encodeDocument(views, live) == '{"count":2}')
assert(json.encodeDocument(views, frozen) == '{"count":1}')

Type parameters

NameDescription
T

Members

NameKindDescription
bindingfieldThe binding the view reads its value through.
valuefield
sourcefield
contextmethodReturns the context the view carries.
documentmethodBuilds the document tree of the value's current state.
snapshotmethodReturns a view of an independent copy of the value.

bindingfield#

binding: core.Binding<T>
@readonly

The binding the view reads its value through.

valuefield#

@private@readonly

sourcefield#

source: core.Context?
@private@readonly

contextmethod#

context: function context(self): core.Context?

Returns the context the view carries.

A view made by nupp.serde.document.view carries the context it was given, and a decoded view the context it was read under.

Arguments
NameTypeDescription
selfany
Returns
TypeDescription
core.Context?

the context, or nil when the view has none

documentmethod#

document: function document(self): documents.Document

Builds the document tree of the value's current state.

Arguments
NameTypeDescription
selfany
Returns
TypeDescription
documents.Document

the document, carrying the view's context

snapshotmethod#

snapshot: function snapshot(self): View<T>

Returns a view of an independent copy of the value.

The copy is reconstructed from the value's document through the binding, so later changes to the original do not reach it.

Arguments
NameTypeDescription
selfany
Returns
TypeDescription
View<T>

the detached view, with the same binding and context

Raises
TypeCondition
nupp.serde.Error

when the binding cannot construct a value from the document it wrote

Functions#

adapterfunction#

function adapter(): core.DocumentAdapter<Document>

Returns the adapter that reads and writes a Document as its contents.

Pass it to a codec's encodeDocument and decodeDocument, or to nupp.serde.adapters.document for a document-valued member.

Returns

TypeDescription
core.DocumentAdapter<Document>

atomfunction#

function atom(borrows value: core.Atom): Document

A model-defined atom, snapshotted so the document owns it.

Arguments

NameTypeDescription
borrows valuecore.Atom

Returns

TypeDescription
Document

bigIntegerfunction#

function bigInteger(value: string): Document

An integer of unbounded size, from its decimal text.

Arguments

NameTypeDescription
valuestring

Returns

TypeDescription
Document

booleanfunction#

function boolean(value: boolean): Document

A boolean.

Arguments

NameTypeDescription
valueboolean

Returns

TypeDescription
Document

boundaryfunction#

function boundary(value: Document): Document

A document boundary around contents, for a document-valued member.

Arguments

NameTypeDescription
valueDocument

Returns

TypeDescription
Document

bytesfunction#

function bytes(value: string): Document

Binary data.

Arguments

NameTypeDescription
valuestring

Returns

TypeDescription
Document

cachedBindingsfunction#

function cachedBindings(): integer

Returns how many bindings the decoder cache holds.

Returns

TypeDescription
integer

clearfunction#

function clear(): nil

Empties the decoder cache. Decoders already handed out keep working.

Returns

TypeDescription
nil

decimalfunction#

function decimal(value: core.Decimal): Document

An exact decimal.

Arguments

NameTypeDescription
valuecore.Decimal

Returns

TypeDescription
Document

decoderfunction#

function decoder<T>(binding: core.Binding<T>): Decoder<T>

Returns a binding's Decoder, cached by binding identity.

The cache keeps at most 256 bindings and evicts the least recently used.

Type parameters

NameDescription
T

Arguments

NameTypeDescription
bindingcore.Binding<T>

Returns

TypeDescription
Decoder<T>

floatfunction#

function float(value: core.Float): Document

A float with its width.

Arguments

NameTypeDescription
valuecore.Float

Returns

TypeDescription
Document

fromfunction#

function from<T>(binding: core.Binding<T>, borrows value: T, context: core.Context?): documents.Document

Converts a bound value to its document.

Type parameters

NameDescription
T

Arguments

NameTypeDescription
bindingcore.Binding<T>
borrows valueT
contextcore.Context?

Returns

TypeDescription
documents.Document

integerfunction#

function integer(value: core.Integer): Document

A fixed-width integer.

Arguments

NameTypeDescription
valuecore.Integer

Returns

TypeDescription
Document

intofunction#

function into<T>(binding: core.Binding<T>, value: documents.Document): T

Constructs a bound value from a document.

The binding's construction runs exactly as a decoder's would, so required fields and defaults are checked the same way.

Type parameters

NameDescription
T

Arguments

NameTypeDescription
bindingcore.Binding<T>
valuedocuments.Document

Returns

TypeDescription
T

listfunction#

function list(values: {Document}): Document

A list.

Arguments

NameTypeDescription
values{Document}

Returns

TypeDescription
Document

mapfunction#

function map(values: {MapEntry}): Document

A map whose keys are documents.

Arguments

NameTypeDescription
values{MapEntry}

Returns

TypeDescription
Document

nullfunction#

function null(): Document

A null.

Returns

TypeDescription
Document

numberTokenfunction#

function numberToken(value: string): Document

A number kept as the lexeme it was written as, such as a JSON number too large for any integer width.

Arguments

NameTypeDescription
valuestring

Returns

TypeDescription
Document

objectfunction#

function object(values: {Entry}): Document

A structure, from its members in order.

local document = require("nupp.serde.document")
local json = require("nupp.serde.json")
local value = document.object({
    new document.Entry(name = "ok", value = document.boolean(true)),
    new document.Entry(name = "note", value = document.null())
})
assert(json.encodeDocument(document.adapter(), value) == '{"ok":true,"note":null}')

Arguments

NameTypeDescription
values{Entry}

Returns

TypeDescription
Document

readerfunction#

function reader(value: documents.Document): Reader

Reads a document through the nupp.serde.DocumentReader contract, as a codec's own reader would.

Arguments

NameTypeDescription
valuedocuments.Document

Returns

TypeDescription
Reader

snapshotfunction#

function snapshot(value: documents.Document): documents.Document

Copies a document into one that owns all of its values.

Arguments

NameTypeDescription
valuedocuments.Document

Returns

TypeDescription
documents.Document

storeBindingfunction#

function storeBinding(): core.Binding<Snapshot>

Returns the binding a codec writes and reads a StoreSnapshot through.

Returns

TypeDescription
core.Binding<Snapshot>

storeRegistryfunction#

function storeRegistry(): Registry

Starts an empty StoreRegistry.

Returns

TypeDescription
Registry

stringfunction#

function string(value: string): Document

A string.

Arguments

NameTypeDescription
valuestring

Returns

TypeDescription
Document

timestampfunction#

function timestamp(value: core.Timestamp): Document

A timestamp.

Arguments

NameTypeDescription
valuecore.Timestamp

Returns

TypeDescription
Document

tuplefunction#

function tuple(values: {Document}): Document

A tuple: a list whose length is part of its shape.

Arguments

NameTypeDescription
values{Document}

Returns

TypeDescription
Document

unionfunction#

function union(name: string, value: Document): Document

A union: the one member that is set.

Arguments

NameTypeDescription
namestring
valueDocument

Returns

TypeDescription
Document

viewfunction#

function view<T is nupp.Copyable>(binding: core.Binding<T>, value: T, context: core.Context?): View<T>

Presents a Copyable value as a View.

Type parameters

NameDescription
T

Arguments

NameTypeDescription
bindingcore.Binding<T>
valueT
contextcore.Context?

Returns

TypeDescription
View<T>

viewAdapterfunction#

function viewAdapter<T is nupp.Copyable>(binding: core.Binding<T>): core.DocumentAdapter<View<T>>

Returns the document adapter for views of one binding.

Type parameters

NameDescription
T

Arguments

NameTypeDescription
bindingcore.Binding<T>

Returns

TypeDescription
core.DocumentAdapter<View<T>>

withContextfunction#

function withContext(value: Document, context: core.Context): Document

The same document with a context attached.

Arguments

NameTypeDescription
valueDocument
contextcore.Context

Returns

TypeDescription
Document