Skip to content

Types and schema

A type describes the fields an object or relation has, and ECHO uses it to check every write and to tell other peers what the data means.

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.

import {
import Obj
Obj
,
import Type
Type
} from '@dxos/echo';
import {
class Person
Person
} from './schema.ts';
import Type
Type
.
const getTypename: (input: Type.AnyEntity) => string

@returns ―

The typename. Example: com.example.type.person.

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
(
class Person
Person
); // 'com.example.type.person'
import Type
Type
.
const getVersion: (input: Type.AnyEntity) => string

Gets the version.

@example 0.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.

getVersion
(
class Person
Person
); // '0.1.0'
import Type
Type
.
const getURI: (input: Type.AnyEntity, options?: Obj.GetURIOptions) => URI

Returns the URI identifying a type entity. Always defined.

  • Static Type.Obj / Type.Relation → typename DXN (e.g. dxn:com.example.type.person:0.1.0).
  • 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.

getURI
(
class Person
Person
); // 'dxn:com.example.type.person:0.1.0'
const
const ada: Obj.OfShape<Person & OfKind<EntityKind.Object>>
ada
=
import Obj
Obj
.
function make<typeof Person>(type: typeof Person, props: NoInfer<Obj.MakeProps<typeof Person>>, options?: Obj.MakeOptions): Obj.OfShape<Person & OfKind<EntityKind.Object>>

Creates a new echo object of the given schema or Type.Type.

@param ― typeOrSchema - A static object schema (Type.makeObject(...)) or a Type.Type entity (e.g. one returned by db.addType(schemaEntity)).

@param ― props - Object properties.

@param ―

options - See MakeOptions.

Meta can be passed as a symbol in props.

Example:

const obj = Obj.make(Person, { [Obj.Meta]: { keys: [...] }, name: 'John' });

Note: Only accepts object schemas / object-kind types, not relation schemas. Use Relation.make for relations.

@performance ― O(n) in props size; validates props against the schema and allocates a reactive proxy, with no database I/O.

make
(
class Person
Person
, {
name: string
name
: 'Ada' });
import Obj
Obj
.
const getTypename: (entity: Obj.Unknown | Obj.Snapshot) => string | undefined

@returns ― The typename of the object's type. Accepts both reactive objects and snapshots.

@example com.example.type.person

@performance ― O(1); reads the schema type annotation (database objects resolve the schema by registry URI lookup).

getTypename
(
const ada: Obj.OfShape<Person & OfKind<EntityKind.Object>>
ada
); // 'com.example.type.person'

There are two kinds of type:

  • 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.
import * as
import Schema
Schema
from 'effect/Schema';
import {
import DXN
DXN
,
import Type
Type
} from '@dxos/echo';
import {
class Organization
Organization
,
class Person
Person
} from './schema.ts';
export class
class WorksAt
WorksAt
extends
import Type
Type
.
const makeRelation: <WorksAt>(dxn: DXN.DXN) => <SourceInstance, TargetInstance>(opts: {
source: Type.Obj<SourceInstance, any> | UnknownTypeSchema<SourceInstance, EntityKind.Object>;
target: Type.Obj<TargetInstance, any> | UnknownTypeSchema<TargetInstance, EntityKind.Object>;
id?: EntityId;
}) => Type.MakeRelation<WorksAt, SourceInstance, TargetInstance>

Factory function to create an ECHO relation schema. Adds relation metadata annotations to an Effect schema.

@example

class WorksFor extends Type.makeRelation<WorksFor>(DXN.make('com.example.type.worksFor', '0.1.0'))(
{ source: Person, target: Company },
)(Schema.Struct({ role: Schema.String })) {}
// Like `makeObject`, the last call also takes struct fields and an optional annotate function.
class Employs extends Type.makeRelation<Employs>(DXN.make('com.example.type.employs', '0.1.0'))(
{ source: Company, target: Person },
)({ role: Schema.String }, Annotation.LabelAnnotation.set(['role'])) {}

@performance ― O(schema size) once at definition time; builds the type entity eagerly, so call it at module scope, not per use.

makeRelation
<
class WorksAt
WorksAt
>(
import DXN
DXN
.
const make: <"com.example.type.worksAt">(nsid: "com.example.type.worksAt", version?: string) => DXN.DXN

Constructs a DXN from an NSID (and optional version). Throws if the result is not a valid DXN. Use tryMake for non-throwing string parsing.

Static NSID strings are validated at compile time via

Name

: the final segment must be camelCase (no hyphens). Template-literal strings with a runtime prefix are accepted here but still validated at runtime.

@example make('org.dxos.type.calendar') → 'dxn:org.dxos.type.calendar'

@example make('org.dxos.type.calendar', '1.0.0') → 'dxn:org.dxos.type.calendar:1.0.0'

make
('com.example.type.worksAt', '0.1.0'),
)({
source: Type.Obj<Person & OfKind<EntityKind.Object>, any> | UnknownTypeSchema<Person & OfKind<EntityKind.Object>, EntityKind.Object>
source
:
class Person
Person
,
target: Type.Obj<Organization & OfKind<EntityKind.Object>, any> | UnknownTypeSchema<Organization & OfKind<EntityKind.Object>, EntityKind.Object>
target
:
class Organization
Organization
,
})({
role: Schema.String
role
:
import Schema
Schema
.
const String: Schema.String

Type-level representation of

String

.

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
}) {}

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.

import {
import Annotation
Annotation
,
import Obj
Obj
,
import Type
Type
} from '@dxos/echo';
import {
class Event
Event
} from './schema.ts';
// The tutorial's Event names `title` as its label.
const
const labelFields: Option<string[]>
labelFields
=
import Annotation
Annotation
.
const LabelAnnotation: AnnotationHelper<string[]>
export LabelAnnotation
LabelAnnotation
.
AnnotationHelper<string[]>.get: (schema: Schema.Top) => Option<string[]>

Get the annotation value from an Effect schema.

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.

get
(
import Type
Type
.
function getSchema<typeof Event>(type: typeof Event): Schema.Codec<Event & OfKind<EntityKind.Object>, unknown, never, never> (+2 overloads)

Returns the Effect Schema for a 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.

getSchema
(
class Event
Event
)); // Some(['title'])
const
const launch: Obj.OfShape<Event & OfKind<EntityKind.Object>>
launch
=
import Obj
Obj
.
function make<typeof Event>(type: typeof Event, props: NoInfer<Obj.MakeProps<typeof Event>>, options?: Obj.MakeOptions): Obj.OfShape<Event & OfKind<EntityKind.Object>>

Creates a new echo object of the given schema or Type.Type.

@param ― typeOrSchema - A static object schema (Type.makeObject(...)) or a Type.Type entity (e.g. one returned by db.addType(schemaEntity)).

@param ― props - Object properties.

@param ―

options - See MakeOptions.

Meta can be passed as a symbol in props.

Example:

const obj = Obj.make(Person, { [Obj.Meta]: { keys: [...] }, name: 'John' });

Note: Only accepts object schemas / object-kind types, not relation schemas. Use Relation.make for relations.

@performance ― O(n) in props size; validates props against the schema and allocates a reactive proxy, with no database I/O.

make
(
class Event
Event
, {
title: string
title
: 'Launch' });
import Obj
Obj
.
const getLabel: (entity: Obj.Unknown | Obj.Snapshot, options?: GetLabelOptions) => string | undefined

Get the label of the object. Accepts both reactive objects and snapshots.

@param ― options.fallback 'typename' returns the object's typename when no label is set (e.g. org.dxos.type.table).

@performance ― O(label accessors); reads the fields named by the schema LabelAnnotation.

getLabel
(
const launch: Obj.OfShape<Event & OfKind<EntityKind.Object>>
launch
); // 'Launch'
  • 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.

    import {
    class Client

    The Client class encapsulates the core client-side API of DXOS.

    Client
    } from '@dxos/client';
    import {
    const types: (typeof Organization | typeof Person | typeof Event)[]
    types
    } from './schema.ts';
    import {
    class WorksAt
    WorksAt
    } from './works-at.ts';
    const
    const client: Client
    client
    = new
    new Client(options?: ClientOptions): Client

    The Client class encapsulates the core client-side API of DXOS.

    Client
    ({
    types?: Type.AnyEntity[] | undefined

    ECHO schema.

    types
    });
    await
    const client: Client
    client
    .
    Client.initialize(): Promise<Client>

    Initializes internal resources in an idempotent way. Required before using the Client instance.

    initialize
    ();
    await
    const client: Client
    client
    .
    Client.addTypes(types: Type.AnyEntity[]): Promise<void>

    Add schema types to the client.

    addTypes
    ([
    class WorksAt
    WorksAt
    ]);
  • A type converts to and from JSON Schema, so a type can come from a form, an agent or another system as well as from code.

    import {
    import JsonSchema
    JsonSchema
    ,
    import Type
    Type
    } from '@dxos/echo';
    import {
    class Organization
    Organization
    } from './schema.ts';
    const
    const jsonSchema: {
    type?: "string" | "number" | "boolean" | "object" | "array" | "integer" | "null" | ("string" | "number" | "boolean" | "object" | "array" | "integer" | "null")[] | undefined;
    required?: string[] | undefined;
    $id?: string | undefined;
    $schema?: string | undefined;
    $ref?: string | undefined;
    $comment?: string | undefined;
    entityKind?: EntityKind | undefined;
    typename?: string | undefined;
    version?: string | undefined;
    relationTarget?: ... | undefined;
    relationSource?: ... | undefined;
    title?: string | undefined;
    description?: string | undefined;
    ... 43 more ...;
    echo?: {
    ...;
    } | undefined;
    }
    jsonSchema
    =
    import JsonSchema
    JsonSchema
    .
    const toJsonSchema: (schema: Schema.Top | Type.AnyEntity, options?: JsonSchemaOptions) => DeepMutable<JsonSchemaType>

    Convert effect schema to JSON Schema. NOTE: This handles custom annotations.

    @param ― schema

    toJsonSchema
    (
    import Type
    Type
    .
    function getSchema<typeof Organization>(type: typeof Organization): Schema.Codec<Organization & OfKind<EntityKind.Object>, unknown, never, never> (+2 overloads)

    Returns the Effect Schema for a 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.

    getSchema
    (
    class Organization
    Organization
    ));
    const
    const Note: Type.Type<TypeSchema>
    Note
    =
    import Type
    Type
    .
    const makeObjectFromJsonSchema: (props: MakeTypeProps) => Type.Type<TypeSchema>

    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.

    makeObjectFromJsonSchema
    ({
    typename?: string | undefined
    typename
    : 'com.example.type.note',
    version?: string | undefined
    version
    : '0.1.0',
    jsonSchema: JsonSchemaType
    jsonSchema
    : {
    type?: "string" | "number" | "boolean" | "object" | "array" | "integer" | "null" | readonly ("string" | "number" | "boolean" | "object" | "array" | "integer" | "null")[] | undefined

    Base type of the schema.

    type
    : 'object',
    properties?: {
    readonly [x: string]: JsonSchemaType;
    } | undefined
    properties
    : {
    title: {
    type: "string";
    }
    title
    : {
    type?: "string" | "number" | "boolean" | "object" | "array" | "integer" | "null" | readonly ("string" | "number" | "boolean" | "object" | "array" | "integer" | "null")[] | undefined

    Base type of the schema.

    type
    : 'string' } } },
    });
  • 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.

    import * as
    import Schema
    Schema
    from 'effect/Schema';
    import {
    import Type
    Type
    } from '@dxos/echo';
    // Stored in the space, so it replicates to the other members.
    const
    const note: Type.Type<TypeSchema>
    note
    = await
    const db: Database.Database
    db
    .
    Database.addType<Type.Type<TypeSchema>>(type: Type.Type<TypeSchema>, opts?: Database.WriteOptions): Promise<Type.Type<TypeSchema>>

    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.

    addType
    (
    const Note: Type.Type<TypeSchema>
    Note
    );
    import Type
    Type
    .
    const addFields: (type: Type.AnyEntity, fields: Schema.Struct.Fields) => void

    Add fields to a persisted type's schema.

    @throws ― if the type is not persisted.

    @performance ― O(schema size); rebuilds and re-encodes the whole JSON schema, then writes it in one change.

    addFields
    (
    const note: Type.Type<TypeSchema>
    note
    , {
    pinned: Schema.optional<Schema.Boolean>
    pinned
    :
    import Schema
    Schema
    .
    const optional: optionalLambda
    <Schema.Boolean>(self: Schema.Boolean) => Schema.optional<Schema.Boolean>

    Type-level representation returned by

    optional

    .

    Marks a struct field as optional, allowing the key to be absent or undefined.

    Details

    The resulting property may be absent or explicitly set to undefined. Equivalent to optionalKey(UndefinedOr(S)).

    Use

    optionalKey

    instead if you want exact optional semantics (absent only, not undefined).

    Example (Defining an optional field accepting undefined)

    import { Schema } from "effect"
    const schema = Schema.Struct({
    name: Schema.String,
    age: Schema.optional(Schema.Number)
    })
    // { readonly name: string; readonly age?: number | undefined }
    type Person = typeof schema.Type

    @stability ― stable

    @category ― models

    @since ― 3.10.0

    @stability ― stable

    @category ― combinators

    @since ― 3.10.0

    optional
    (
    import Schema
    Schema
    .
    const Boolean: Schema.Boolean

    Type-level representation of

    Boolean

    .

    Schema for boolean values. Validates that the input is typeof "boolean".

    When to use

    Use to validate values that are already JavaScript booleans.

    @stability ― stable

    @category ― models

    @since ― 4.0.0

    @see ― BooleanFromBit for a schema that decodes bit literals 0 or 1 into a boolean

    @stability ― stable

    @category ― schemas

    @since ― 4.0.0

    Boolean
    ) });

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.
import * as
import Schema
Schema
from 'effect/Schema';
import {
import DXN
DXN
,
import Format
Format
,
import Migration
Migration
,
import Ref
Ref
,
import Type
Type
} from '@dxos/echo';
import {
class Organization
Organization
,
class Person
Person
} from './schema.ts';
// Version 0.2.0 of Person holds any number of email addresses.
class
class PersonV2
PersonV2
extends
import Type
Type
.
const makeObject: <PersonV2>(dxn: DXN.DXN, options?: {
id?: EntityId;
}) => Type.MakeObject<PersonV2>

Factory function to create an ECHO object type.

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.

@example

export class Person extends Type.makeObject<Person>(DXN.make('com.example.type.myType', '0.1.0'))(Schema.Struct({
name: Schema.String,
})) {}
export class Tag extends Type.makeObject<Tag>(DXN.make('com.example.type.tag', '0.1.0'))(
{ label: Schema.String },
Annotation.LabelAnnotation.set(['label']),
) {}

@performance ― O(schema size) once at definition time; builds the type entity eagerly, so call it at module scope, not per use.

makeObject
<
class PersonV2
PersonV2
>(
import DXN
DXN
.
const make: <"com.example.type.person">(nsid: "com.example.type.person", version?: string) => DXN.DXN

Constructs a DXN from an NSID (and optional version). Throws if the result is not a valid DXN. Use tryMake for non-throwing string parsing.

Static NSID strings are validated at compile time via

Name

: the final segment must be camelCase (no hyphens). Template-literal strings with a runtime prefix are accepted here but still validated at runtime.

@example make('org.dxos.type.calendar') → 'dxn:org.dxos.type.calendar'

@example make('org.dxos.type.calendar', '1.0.0') → 'dxn:org.dxos.type.calendar:1.0.0'

make
('com.example.type.person', '0.2.0'),
)(
import Schema
Schema
.
function Struct<{
readonly name: Schema.String;
readonly emails: Schema.$Array<Schema.String>;
readonly employer: Schema.optional<Ref.RefSchema<Organization & OfKind<EntityKind.Object> & Obj.Unknown>>;
}>(fields: {
readonly name: Schema.String;
readonly emails: Schema.$Array<Schema.String>;
readonly employer: Schema.optional<Ref.RefSchema<Organization & OfKind<EntityKind.Object> & Obj.Unknown>>;
}): Schema.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.

Example (Defining a basic struct)

import { Schema } from "effect"
const Person = Schema.Struct({
name: Schema.String,
age: Schema.Number,
email: Schema.optionalKey(Schema.String)
})
// { readonly name: string; readonly age: number; readonly email?: string }
type Person = typeof Person.Type
Schema.decodeUnknownSync(Person)({ name: "Alice", age: 30 }) // => { name: "Alice", age: 30 }

@stability ― stable

@category ― constructors

@since ― 3.10.0

Struct
({
name: Schema.String
name
:
import Schema
Schema
.
const String: Schema.String

Type-level representation of

String

.

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
,
emails: Schema.$Array<Schema.String>
emails
:
import Schema
Schema
.
Array<Schema.String>(self: Schema.String): Schema.$Array<Schema.String>
export Array

Defines a ReadonlyArray schema for a given element schema.

Example (Defining an array of strings)

import { Schema } from "effect"
const schema = Schema.Array(Schema.String)
Schema.decodeUnknownSync(schema)(["a", "b", "c"]) // => ["a", "b", "c"]

@stability ― stable

@category ― constructors

@since ― 4.0.0

Array
(
import Format
Format
.
const Email: Schema.String

Email address (RFC 5321). The local part also allows an apostrophe, which real mail carries (o'day@corp.com). https://datatracker.ietf.org/doc/html/rfc5321#section-4.1.2

Email
),
employer: Schema.optional<Ref.RefSchema<Organization & OfKind<EntityKind.Object> & Obj.Unknown>>
employer
:
import Schema
Schema
.
const optional: optionalLambda
<Ref.RefSchema<Organization & OfKind<EntityKind.Object> & Obj.Unknown>>(self: Ref.RefSchema<Organization & OfKind<EntityKind.Object> & Obj.Unknown>) => Schema.optional<Ref.RefSchema<Organization & OfKind<EntityKind.Object> & Obj.Unknown>>

Type-level representation returned by

optional

.

Marks a struct field as optional, allowing the key to be absent or undefined.

Details

The resulting property may be absent or explicitly set to undefined. Equivalent to optionalKey(UndefinedOr(S)).

Use

optionalKey

instead if you want exact optional semantics (absent only, not undefined).

Example (Defining an optional field accepting undefined)

import { Schema } from "effect"
const schema = Schema.Struct({
name: Schema.String,
age: Schema.optional(Schema.Number)
})
// { readonly name: string; readonly age?: number | undefined }
type Person = typeof schema.Type

@stability ― stable

@category ― models

@since ― 3.10.0

@stability ― stable

@category ― combinators

@since ― 3.10.0

optional
(
import Ref
Ref
.
const Ref: <typeof Organization>(type: typeof Organization) => Ref.RefSchema<Organization & OfKind<EntityKind.Object> & Obj.Unknown> (+3 overloads)
Ref
(
class Organization
Organization
)),
}),
) {}
const
const personEmails: Migration.ObjectMigration
personEmails
=
import Migration
Migration
.
const define: <typeof Person, typeof PersonV2>(options: DefineObjectMigrationOptions<typeof Person, typeof PersonV2>) => Migration.ObjectMigration

Define a migration between two object schemas.

@example

const migration = Migration.define({
from: ContactV1,
to: ContactV2,
transform: async (from) => ({ name: `${from.firstName} ${from.lastName}` }),
onMigration: async () => {},
});

@performance ― O(1) at definition time; reads both schema URIs.

define
({
from: typeof Person
from
:
class Person
Person
,
to: typeof PersonV2
to
:
class PersonV2
PersonV2
,
transform: (from: Person & OfKind<EntityKind.Object>, context: Migration.ObjectMigrationContext) => Promise<Migration.TransformResult<typeof PersonV2>>

Pure function that converts the old object data to the new object data.

The returned object may include an optional [Obj.Meta] entry to update the object's meta (e.g. registry key / version) atomically with the data swap.

transform
: async ({
name: string
name
,
email: string | undefined
email
,
employer: Ref<Organization & OfKind<EntityKind.Object> & Obj.Unknown> | undefined
employer
}) => ({
name: string
name
,
emails: readonly string[]
emails
:
email: string | undefined
email
? [
email: string
email
] : [],
employer?: Ref<Organization & OfKind<EntityKind.Object> & Obj.Unknown> | undefined
employer
,
}),
onMigration?: ((params: OnMigrateProps<typeof Person, typeof PersonV2>) => Promise<void>) | undefined

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 () => {},
});
// Points every reference to the old typename at the new one.
const
const renameEvent: Migration.RenameMigration
renameEvent
=
import Migration
Migration
.
const defineRename: <"com.example.type.event", "com.example.type.meeting">(options: {
from: "com.example.type.event";
to: "com.example.type.meeting";
}) => Migration.RenameMigration

Define a migration that renames a named entity.

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
({
from: "com.example.type.event"
from
: 'com.example.type.event',
to: "com.example.type.meeting"
to
: 'com.example.type.meeting',
});

A plugin contributes its migrations to Composer, which runs them on every space once the plugin is active. The API reference covers each kind.

  • Objects of two versions of one type do not merge. Peers must run the same migration before they edit the same objects.
  • The docs do not yet say how to run migrations outside a Composer plugin.
  • Adding a field to a stored type is safe; changing or removing one leaves the old values in objects already written.