typetype

package
v0.4.3 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 23, 2026 License: MIT Imports: 7 Imported by: 0

Documentation

Overview

Package typetype is the built-in `type` meta-type — the shape of type objects themselves. Ships the description of the meta-type and the handler that validates writes to a type object's `properties` dataset (the property-definition records).

Directory is internal/types/type/; the package is declared `typetype` because `type` is a Go keyword and can't be a package name.

`type` ships as a derived object in every space (well-known id).

Index

Constants

View Source
const (
	DefKindPart    = "part"    // part record: one per part of the type
	DefKindDataset = "dataset" // head record: one per dataset of a part
	DefKindField   = "field"   // field record: one per field of a records dataset
)

Discriminator values of the pinned `def` field.

View Source
const (
	DefFieldDef         = "def"         // discriminator, pinned
	DefFieldModule      = "module"      // module slug (head), pinned
	DefFieldShared      = "shared"      // bool (head), pinned — the module's canonical collection
	DefFieldPart        = "part"        // owning part record id (head), pinned
	DefFieldDynamic     = "dynamic"     // bool (head), pinned
	DefFieldIdRule      = "idRule"      // "auto"/"user" (head), pinned ("id" is a reserved head)
	DefFieldIdPattern   = "idPattern"   // RE2 (head), pinned
	DefFieldIdMaxLen    = "idMaxLen"    // number (head), pinned
	DefFieldDeleteBy    = "deleteBy"    // "anyone"/"author" (head), pinned
	DefFieldSkipHistory = "skipHistory" // bool (head), pinned
	DefFieldSearch      = "search"      // {title,text,scope} (head); leaves mutable, text string-or-array
	DefFieldDisplayName = "displayName" // human label (head), mutable
	DefFieldDataset     = "dataset"     // owning head record id (field), pinned
	DefFieldStamp       = "stamp"       // "creator"/"createTime"/"modifyTime" (field), pinned
	DefFieldRequired    = "required"    // bool (field), pinned
	DefFieldMutableBy   = "mutableBy"   // "never"/"author"/"any" (field), pinned
)

Dataset-def record field names. Part records use the Part* set plus FieldKey / FieldName; head records the Def* set plus FieldKey; field records reuse FieldKey/FieldKind/FieldName/FieldDescription/FieldItems/ FieldProperties from the property-record vocabulary plus the Field* behavioral set below.

View Source
const (
	PartFieldIcon   = "icon"   // string
	PartFieldPos    = "pos"    // string, clients sort parts by it
	PartFieldHidden = "hidden" // bool, not shown by default
	PartFieldUI     = "ui"     // object {type, config}, written whole
	PartFieldUses   = "uses"   // array of dataset keys of this type
)

Part record fields — the display slice a client renders. All mutable; `key` (FieldKey) is the only pinned one.

View Source
const (
	SearchKeyTitle = "title"
	SearchKeyText  = "text"
	SearchKeyScope = "scope"
)

Sub-keys of the head `search` object — mutable leaves (broad `search` replaces are pinned, the leaves mutate freely). `title` and `scope` are scalar strings; `text` is a bare field key or a non-empty array of field keys.

View Source
const (
	ShortIdFieldChangeId = "changeId" // the changeId the shortId was derived from
	ShortIdFieldPropId   = "propId"   // the property record this shortId stamps (added or removed)
	ShortIdFieldKind     = "kind"     // on-wire kind label, present on add rows
	ShortIdFieldRemoved  = "removed"  // bool, true on removal rows
	ShortIdFieldDefId    = "defId"    // the dataset-def record this shortId stamps
	ShortIdFieldSrc      = "src"      // discriminator: absent = properties, "datasets" = dataset defs
)

ShortId row field names.

View Source
const (
	MetaTypeMarker = "__type__"
	TypeId         = "type"
)

MetaTypeMarker is the reserved value every type object carries in `any.type` — what distinguishes a type object from a regular one (see spaceobjects.LiveTypeRowsFilter). A definition object has no type of its own: the slot holds the marker.

TypeId is the meta-type's id: the namespace its type-only property values live under (`record.type.xkey`) and the id surfaced through Space.Types(). The two differ because a `_`-prefixed top-level field is protocol-owned (crdt.validatePath), so the marker cannot double as a storage namespace.

Both are reserved — user-derived type ids are content-addressable and never produce either string.

View Source
const (
	Name        = "Type"
	Description = "A type — defines the properties, parts and layout of the objects that have it"
)

Display metadata for the `type` meta-type object.

View Source
const (
	FieldHiddenProp = "hidden"
	FieldMetaProp   = "meta"
)

`type.hidden` keeps a type out of default listings and pickers (self-typed bundle roots carry it); `type.meta` is the open bag of consumer flags — one scalar per key, written per key so writers touching different keys merge (`meta.index`, a client's own tags).

View Source
const (
	FieldKey         = "key"         // user-facing stable identifier (e.g. "actors")
	FieldKind        = "kind"        // "string"/"number"/"boolean"/"null"/"array"/"object"/"datetime"
	FieldScope       = "scope"       // "synced"/"account"/"local" — write/sync class, pinned
	FieldName        = "name"        // human label, mutable
	FieldDescription = "description" // mutable
	FieldXKey        = "x-key"       // caller-side handle, mutable
	FieldItems       = "items"       // recursive sub-shape for arrays
	FieldProperties  = "properties"  // recursive sub-shape for objects
	FieldRequired    = "required"    // []string, mutable per docs/data-structure.md
	FieldMeta        = "meta"        // opaque consumer flag map (string→string), mutable
	FieldXFormat     = "x-format"    // opaque descriptor object, mutable — see below
)

Property-record field names. The shape is hardcoded in Go (no JSON Schema applies on this dataset — see docs/types-properties-proposal.md § "Schema format — decision").

View Source
const DatasetDefs = "datasets"

DatasetDefs is the dataset on a type object holding its dataset definition records. On disk the collection is `<typeId>_datasets`.

View Source
const DatasetDefsHandlerVersion = "typeDatasetHandler-v1"

DatasetDefsHandlerVersion is the DataVersion stamped on changes to the DatasetDefs dataset itself (hardcoded, like HandlerVersion — definition writes are never gated on their own schema state).

Known mixed-fleet limitation: this string is deliberately NOT parseable by the spaceobjects DataVersion gate (legacy handler-version strings pass through unconstrained — see gateFor), so bumping it CANNOT park new wire forms on old replicas. A peer that predates the array text form sheds an array-form `search.text` $set (its handler admits only scalar strings) and never replays it, diverging the head record until re-written. Accepted pre-release; a real fix needs a version pair the gate parses (and old drainers can satisfy), which is a protocol change.

View Source
const DatasetDefsLocalVersion = 4

DatasetDefsLocalVersion is this handler's LOCAL logic version (HandlerReg.Version): bumped when a validation change means an already-materialized set of dataset definitions would come out different, so the SDK replays the type object's tree. Like the property handler's, it is also the recovery path for definitions an older build dropped because it could not validate them.

v2: `datetime` is a kind, so a stamped time field validates. v3: field records carry an opaque `x-format` (an object, created whole, mutable); head records carry none. v4: parts. A head is keyed (`key`, pinned) and bound to a part and a module (`part`, `module`, `shared`, pinned); the `collection` field is gone — the collection is computed at compile.

View Source
const DatasetPropertyDefs = "properties"

DatasetPropertyDefs is the dataset on a type object that holds its property-*definition* records (id, name, kind, ...). On disk the collection is named "properties"; the Go identifier was chosen to disambiguate from property *values*, which live in a separate dataset called "objects" (properties.Dataset, handled by properties.SystemPropertiesHandler). Every object registers both handlers — only type objects actually write to this one.

View Source
const FieldLayoutProp = "layout"

Rendering metadata a type object carries in its own namespace: `type.layout` is the type's layout descriptor ({type, config}, written whole, opaque to the SDK).

View Source
const FieldXKeyProp = "xkey"

FieldXKeyProp is the property id of the meta-type's `xkey` — the caller-side programmatic handle for the type itself, stored at `record.type.xkey`. Distinct from FieldXKey (`x-key`), which is the same idea one level down, on a property DEFINITION record.

View Source
const HandlerVersion = "typePropertyHandler-v1"

HandlerVersion is the DataVersion string stamped onto every change this handler emits against the `properties` dataset of a type object. Per docs/types-properties-proposal.md § "Change-level DataVersion", this dataset uses a hardcoded handler-chosen identifier (not a shortId) — bump the suffix if the validation rules ever change in a way that must reject stale writers.

View Source
const PropertyHandlerLocalVersion = 3

PropertyHandlerLocalVersion is this handler's LOCAL logic version (HandlerReg.Version) — bumped when a validation change means an already-materialized set of definitions would come out different, so the SDK replays the type object's tree (docs/versioning.md).

It is also how a peer recovers definitions its previous build dropped: the wire DataVersion deliberately stays put (bumping it would park every property change on peers that don't know the new version — see the Properties note above), so an older build rejects a definition it cannot validate, and the replay after the upgrade applies it.

v2: `datetime` is a kind, and the date formats accept it. v3: the typed `format` object is gone (no format→kind coupling, no pinned `format.type`); `x-format` must be an object, created whole.

View Source
const ShortIdSrcDatasets = "datasets"

ShortIdSrcDatasets marks rows projected by DatasetDefsHandler. Property rows carry no `src` — the absent value is the legacy discriminant, so old readers keep working. The gate never inspects fields (KnownShortId is a FindId), so mixing both streams in one collection is free; LatestShortId then gates data changes against the type's WHOLE schema state (properties + dataset defs), which is strictly safer.

View Source
const ShortIdsDataset = "shortIds"

ShortIdsDataset is the name of the sibling collection registered on every type object's Controller alongside DatasetPropertyDefs. One row per important change to the type's properties dataset.

docs/types-properties-proposal.md § "Known-shortIds set":

{ shortId, versionId, changeId }

Append-only in v1; no GC. The shortId record id is base58(xxh3-64(changeId)) — the same derivation as crdt.DeriveRecordId, since a content-addressable changeId is the only thing we hash.

View Source
const WellKnownDeriveSeed = "builtin:type"

WellKnownDeriveSeed mints the same `type` type object id on every peer.

Variables

View Source
var ErrBadDatasetDef = errors.New("typetype: invalid dataset definition record")

ErrBadDatasetDef indicates a structurally invalid dataset-definition record. Wraps crdt.ErrValidation.

View Source
var ErrBadScope = errors.New("typetype: property `scope` must be one of synced/account/local")

ErrBadScope indicates a property record declared an unknown scope label, or the reserved "derived" scope (SDK built-ins only). Wraps crdt.ErrValidation so callers can match either.

View Source
var ErrBadXFormat = errors.New("typetype: `x-format` must be an object created whole")

ErrBadXFormat indicates a structurally malformed `x-format` in a creation change: not an object, or written through dotted `x-format.*` keys instead of as one whole object. Shared by property and dataset-field records. Wraps crdt.ErrValidation.

View Source
var ErrMissingKind = errors.New("typetype: property record requires `kind`")

ErrMissingKind indicates a property record was created without a `kind` field. Wraps crdt.ErrValidation so callers can match either.

View Source
var Properties = []BuiltInProperty{
	{Id: FieldXKeyProp, Name: "XKey", Kind: schema.KindString, Scope: schema.ScopeSynced,
		Description: "Programmatic handle of the type; consumers keep it unique per space."},
	{Id: FieldLayoutProp, Name: "Layout", Kind: schema.KindObject, Scope: schema.ScopeSynced,
		Description: "Layout descriptor of the primary type: {type, config}."},
	{Id: FieldHiddenProp, Name: "Hidden", Kind: schema.KindBoolean, Scope: schema.ScopeSynced,
		Description: "Keeps the type out of default listings and pickers.",
		XFormat:     map[string]any{"type": "checkbox"}},
	{Id: FieldMetaProp, Name: "Meta", Kind: schema.KindObject, Scope: schema.ScopeSynced,
		Description: "Consumer flags, one scalar per key."},
}

Properties lists the meta-type's hardcoded property definitions — the values that are meaningful only on a type object. They live in the `type` namespace rather than `any` so the membership check in properties.SystemPropertiesHandler fences them off: a row that doesn't carry the marker cannot hold them at all.

The `objects` DataVersion is deliberately NOT bumped for the move off `any.xkey`: a peer that doesn't know a version parks every change for that dataset, so bumping would halt all property sync with older peers to protect one field. Mixed versions instead drop the unknown-namespace op per-op — the type's other metadata still applies, and its xkey resolves on upgraded peers only.

Functions

func CheckSearchLeafValue

func CheckSearchLeafValue(path []string, payload *anyenc.Value) error

CheckSearchLeafValue validates a search.* leaf's $set payload: `title`/`scope` must be scalar strings, `text` a bare string or a non-empty array of unique non-empty string keys (both wire forms). Shared with the client-side PatchDataset preflight; returns a plain error — callers wrap with their own sentinel.

The array branch deliberately re-checks entry types instead of decoding through schema.SearchTextFromAnyenc: the parser is tolerance-biased (a non-string entry becomes "" for the compile fold to flag), while a write gate owes the author the precise "entries must be strings". A bare "" stays admissible here — older handlers accepted it, so rejecting it at apply time would diverge on old-authored changes; the PatchDataset preflight is where the empty spellings get refused.

func IsDatasetDefPinnedPath

func IsDatasetDefPinnedPath(path []string) bool

IsDatasetDefPinnedPath is the client-side preflight for a HEAD record (PatchDataset): the handler's rule, minus `x-format` — a head carries no descriptor (nothing reads one back), so a write there would be a permanent no-op in the DAG.

func IsDatasetFieldPinnedPath

func IsDatasetFieldPinnedPath(path []string) bool

IsDatasetFieldPinnedPath reports whether a PatchDatasetField path touches pinned field-record state — everything but name, description and x-format.*.

func IsPartPinnedPath

func IsPartPinnedPath(path []string) bool

IsPartPinnedPath reports whether a PatchPart path touches pinned part-record state — everything but the display slice. `ui` is written whole (like `x-format`), so a path below it is pinned too.

func IsPinnedPath

func IsPinnedPath(path []string) bool

IsPinnedPath is the exported form of isPinnedPath, so the space layer (PatchProperty) can reject writes to pinned state client-side and fail the whole patch fast, rather than relying on the handler's per-op drop (which would partially apply a mixed patch).

func ValidateKey

func ValidateKey(what, key string) error

ValidateKey checks a part or dataset key: the slug rules. Shared by the create-time handler gate and the AddPart / AddDataset preflights.

Types

type BuiltInProperty

type BuiltInProperty struct {
	Id    string
	Name  string
	Kind  schema.Kind
	Scope schema.Scope
	// Description and XFormat are the descriptive slice: a display
	// description and the opaque descriptor bag (docs/data-structure.md § The
	// `x-format` descriptor). Surfaced by Types().Properties and
	// dataset discovery like a user definition's; never interpreted
	// or enforced by the SDK.
	Description string
	XFormat     map[string]any
}

BuiltInProperty is one hardcoded property definition. Same shape as anytype.BuiltInProperty so the types registry surfaces built-ins uniformly with user-defined types.

type DatasetDefsHandler

type DatasetDefsHandler struct{}

DatasetDefsHandler validates ops on a type object's `datasets` dataset and projects shortId rows on every important change (def added / removed), so the existing DataVersion gate parks data changes written against schema state this replica hasn't applied yet.

All validation is record-local and stateless (see PropertyHandler for the convergence rationale): cross-record consistency — orphan field records, author rules without a creator stamp — is resolved deterministically at catalog compile, never here.

func (DatasetDefsHandler) BeforeCreate

func (DatasetDefsHandler) BeforeCreate(ctx *crdt.ChangeCtx, rec *crdt.RecordChange, sink *crdt.Sink) error

BeforeCreate validates a definition record's creation shape and projects the "added" shortId row.

func (DatasetDefsHandler) BeforeDelete

func (DatasetDefsHandler) BeforeDelete(ctx *crdt.ChangeCtx, rec *crdt.RecordChange, sink *crdt.Sink) error

BeforeDelete classifies the removal as an important change (removed definitions must gate stale writers) and projects the removal row. Record data under a removed dataset is not cleaned up in v1 — the RemoveProperty stance.

func (DatasetDefsHandler) BeforeModify

func (DatasetDefsHandler) BeforeModify(_ *crdt.ChangeCtx, _ *crdt.RecordChange, op *crdt.Op, _ *crdt.Sink) error

BeforeModify rejects edits to pinned dataset-def state; the mutable display fields pass through and the search leaves must keep their wire shapes (scalar strings; `text` also accepts a key array).

func (DatasetDefsHandler) Init

type PropertyHandler

type PropertyHandler struct{}

PropertyHandler validates ops on a type object's `properties` dataset and projects shortId rows into the sibling shortIds dataset on every "important" change (add / remove). Per docs/types-properties-proposal.md § "Schema evolution rules" and § "ShortId — derivation".

func (PropertyHandler) BeforeCreate

func (PropertyHandler) BeforeCreate(ctx *crdt.ChangeCtx, rec *crdt.RecordChange, sink *crdt.Sink) error

BeforeCreate validates a creation: the record must declare a known `kind`, and — when present — a creatable `scope` (synced / account / local; absent means synced; "derived" is reserved for SDK built-ins). Mints a shortId from the change's ChangeId and projects a row into ShortIdsDataset. The apply loop stamps that row's `_ver` with this change's VersionId — same versionId as the property record itself, so the gate is consistent.

func (PropertyHandler) BeforeDelete

func (PropertyHandler) BeforeDelete(ctx *crdt.ChangeCtx, rec *crdt.RecordChange, sink *crdt.Sink) error

BeforeDelete classifies the removal as an "important" change and projects a shortId row marking it. Existing record data on per-object stores isn't cleaned up — subsequent writes touching the removed property drop op-by-op via the unknown-property rule.

func (PropertyHandler) BeforeModify

func (PropertyHandler) BeforeModify(ctx *crdt.ChangeCtx, rec *crdt.RecordChange, op *crdt.Op, sink *crdt.Sink) error

BeforeModify rejects edits to pinned state on an existing record: the schema-bearing fields (`key`, `kind`, `scope`, `items`, `properties`). Everything else — `name`, `description`, `x-key`, `required`, `meta.*` and every path under `x-format` — passes through with no value checks, bar one: a $set of the whole `x-format` must be an object (checkXFormatWholeSet), so the create-time shape holds for the record's life. Not an "important" change — no shortId minting — with one exception: a creation-shaped upsert landing on an existing record.

That is a concurrent duplicate create — two devices declaring a bundle property under its deterministic id while apart. The change is a create on its author's replica (BeforeCreate projected its shortId row there) and a modify everywhere else. The author stamps its later data writes with the newest shortId it knows, which may be this one, so every replica must know it too or those writes park forever: the row is projected here as well. Idempotent — the same row id on every replica — and the record itself merges as any modify does (the pinned keys are shed, the rest is identical).

func (PropertyHandler) Init

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL