A type is an Effect Schema with a typename and a version.
Together they make the type’s DXN, dxn:<typename>:<version>, and every object stores the DXN of its type.
Defining types builds the Person, Organization and Event types that the snippets on
this page use.
Persisted Type.Type entities carry typename in EntityMeta.key (the
canonical registry-provenance field); unnamed drafts fall back to the
entity's object id so the helper always returns a string. Any dxn: or
echo:/ prefix is stripped — typename is a bare identifier, not a URI.
@performance ― O(1) meta read with jsonSchema and id fallbacks.
getTypename(
classPerson
Person); // 'com.example.type.person'
5
import Type
Type.
constgetVersion: (input: Type.AnyEntity) =>string
Gets the version.
@example0.1.0
@example
0.1.0- (in-database, versioned by automerge heads)
The registry-provenance semver lives in EntityMeta.version; unversioned
drafts default to DRAFT_VERSION ('0.0.0'). In-database entities are
additionally versioned by their automerge heads, which are exposed as the
semver pre-release tag (<semver>-<heads>). In-memory declarations have no
heads and surface the bare semver. Read the registry semver alone via
Type.getMeta(input).version.
@performance ― O(h log h) in Automerge heads for database types (sorted into the suffix per call); O(1) for in-memory
types.
Persisted Type.Type instance (has id) → local EID (echo:///<objectId>).
In-memory Type.Type draft (has id, no typename) → local EID.
When options.prefer === 'named' the result is forced to a DXN — for
persisted type entities the typename + version from meta are used to
construct the DXN; static types already return a DXN by default.
Only accepts Type.AnyEntity entities. Raw Schema.Schema values and the
branded Obj.Unknown / Relation.Unknown schemas are intentionally not
supported — use internal.getSchemaURI or the schema's typename annotation
directly when working at the schema level.
@performance ― O(1); reads the id or schema identifier annotation, allocating a new URI string.
An object type, made with Type.makeObject, describes a record such as a person.
A relation type, made with Type.makeRelation, describes an entity that joins a source and a target, and can carry
fields of its own. Objects and relations shows when to use one.
Schema for string values. Validates that the input is typeof"string".
@stability ― stable
@category ― models
@since ― 4.0.0
@stability ― stable
@category ― schemas
@since ― 4.0.0
String }) {}
11
Annotations add what the schema alone does not say: which field labels an object, which icon it has, how a form
orders its fields. ECHO reads some of them itself; Obj.getLabel returns the field the label annotation names. Tools
such as Composer read the rest. Annotation lists them.
1
import {
import Annotation
Annotation,
import Obj
Obj,
import Type
Type } from'@dxos/echo';
2
import {
classEvent
Event } from'./schema.ts';
3
4
// The tutorial's Event names `title` as its label.
Only accepts Schema.Top — to read an annotation off a Type.Type
entity, unwrap it first with Type.getSchema(entity). This keeps the
annotation pipeline single-shaped and forces annotations to live on the
source schema, not on the post-construction Type entity.
For static Type.Obj / Type.Relation entities the source Effect Schema is
read from a hidden slot — these overloads preserve the instance type.
For Type.Type entities (the meta-schema kind) the schema is rebuilt from
type.jsonSchema; the instance type isn't statically knowable so the wide
AnyEntity overload widens to Schema.Codec<any, any>.
Always call this when you need to interact with the Effect Schema API
(e.g. before passing to Effect.Schema functions). For ECHO-side APIs
(Obj.make, Filter.type, Ref) pass the type entity directly.
Only accepts Type.AnyEntity — raw Schema.Schema values can be used
directly without unwrapping.
@performance ― O(1) cached read; a persisted type rebuilds its Effect Schema from jsonSchema (O(schema size)) on
first access.
A database accepts only objects of types it knows. Register types with the client, when you make it or later; types
registered there apply to every space.
1
import {
classClient
The Client class encapsulates the core client-side API of DXOS.
Client } from'@dxos/client';
2
import {
const types: (typeof Organization | typeof Person | typeof Event)[]
types } from'./schema.ts';
3
import {
classWorksAt
WorksAt } from'./works-at.ts';
4
5
const
const client: Client
client = new
newClient(options?: ClientOptions): Client
The Client class encapsulates the core client-side API of DXOS.
Client({
types?: Type.AnyEntity[] | undefined
ECHO schema.
types });
6
await
const client: Client
client.
Client.initialize(): Promise<Client>
Initializes internal resources in an idempotent way.
Required before using the Client instance.
For static Type.Obj / Type.Relation entities the source Effect Schema is
read from a hidden slot — these overloads preserve the instance type.
For Type.Type entities (the meta-schema kind) the schema is rebuilt from
type.jsonSchema; the instance type isn't statically knowable so the wide
AnyEntity overload widens to Schema.Codec<any, any>.
Always call this when you need to interact with the Effect Schema API
(e.g. before passing to Effect.Schema functions). For ECHO-side APIs
(Obj.make, Filter.type, Ref) pass the type entity directly.
Only accepts Type.AnyEntity — raw Schema.Schema values can be used
directly without unwrapping.
@performance ― O(1) cached read; a persisted type rebuilds its Effect Schema from jsonSchema (O(schema size)) on
first access.
Construct a new object-kind type entity from raw metadata — for cases where
an Effect Schema isn't available (e.g. JSON-Schema arriving over the network
or from a UI editor). Parallel to
makeObject
but takes pre-built
jsonSchema instead of piping through an Effect schema.
The returned entity is in-memory; persist it with db.addType(entity).
@performance ― O(schema size); validates the JSON schema into a new in-memory type entity, with no database I/O.
db.addType stores a type in the space, as an object of its own, so it replicates to the other members like data.
Fields of a stored type can be added, changed and removed at runtime.
1
import*as
import Schema
Schemafrom'effect/Schema';
2
import {
import Type
Type } from'@dxos/echo';
3
4
// Stored in the space, so it replicates to the other members.
Persists a Type definition (clones/forks the entity) so it replicates to other peers.
Runs a conflict query first: if a type with the same typename + version already exists in
this space, the existing persisted entity is returned and no duplicate is created. This is
the only supported way to add Type entities —
add
rejects them.
@performance ― Async; O(t) in the space persisted types, which it queries in full for a duplicate before writing.
A change to a type that old data does not fit, such as a field that becomes an array, is a new version of the type. A
migration moves objects from one version to the next:
Migration.define reads an object of the old version and returns the fields of the new one.
Migration.defineRename gives a type a new typename and points every reference at it.
Migration.defineContent upgrades a content format that an object carries in a field, such as a drawing’s scene,
without changing the type’s version.
1
import*as
import Schema
Schemafrom'effect/Schema';
2
import {
import DXN
DXN,
import Format
Format,
import Migration
Migration,
import Ref
Ref,
import Type
Type } from'@dxos/echo';
3
import {
classOrganization
Organization,
classPerson
Person } from'./schema.ts';
4
5
// Version 0.2.0 of Person holds any number of email addresses.
Returns a Type.Type entity — a live, in-memory TypeSchema instance,
NOT a Schema.Schema. Use Type.InstanceType<typeof Foo> for the instance
type and Type.getSchema(Foo) to obtain the underlying Effect Schema.
The entity's id defaults to EntityId.deterministic(typename, version) so
constructing a type never reaches crypto.getRandomValues() — required for
Cloudflare workerd, which forbids RNG calls in global (module-evaluation)
scope. Pass { id } to override (e.g. with EntityId.random() from a
request handler).
Giving the type a nominal class name keeps its inferred type portable for
declaration emit: structural expansion (which can pull in unnameable union
members from referenced schemas) is replaced by a reference to the class.
The second call takes either a schema, or struct fields (wrapped in Schema.Struct) with an optional function
that pipes annotations onto the struct.
Defines a struct schema from a map of field schemas.
Details
Each field value is a schema. Use
optionalKey
or
optional
to
mark fields as optional, and
mutableKey
to mark them as mutable.
The resulting schema's Type is a readonly object type with the fields'
decoded types. The Encoded form mirrors the field schemas' encoded types.
Declared fields may be inherited and are copied to own properties in the
output. The __proto__ field is accepted only when it is an own property.
Parsing does not guarantee that output keys retain their input order.
Callback that is called after the object is migrated. Called for every object that is migrated.
NOTE: Database mutations performed in this callback are not guaranteed to be idempotent.
If multiple peers run the migration separately, the effects may be applied multiple times.
onMigration: async () => {},
25
});
26
27
// Points every reference to the old typename at the new one.
Applying it rewrites every reference pointing at from to point at to, preserving the
reference's version suffix. Idempotent: a reference that already reads correctly is not written.
@example
const migration = Migration.defineRename({
from: 'org.example.operation.foo',
to: 'org.example.operation.bar',
});
@performance ― O(1) at definition time.
defineRename({
29
from: "com.example.type.event"
from: 'com.example.type.event',
30
to: "com.example.type.meeting"
to: 'com.example.type.meeting',
31
});
32
A plugin contributes its migrations to Composer, which runs them on every space once the plugin is active. The
API reference covers each kind.