Skip to content

Type

import { Type } from '@dxos/echo';

A type names a kind of entity and fixes its fields. You declare one at module scope as a class, and that class is both the runtime schema and the TypeScript type of its instances. Every type carries a DXN with a typename and a version, so two peers agree on what an object is without sharing code.

makeObject takes the DXN first, then the fields. The optional second argument pipes annotations onto the schema, here the field that labels an object in lists.

import * as
import Schema
Schema
from 'effect/Schema';
import {
import Annotation
Annotation
,
import DXN
DXN
,
import Type
Type
} from '@dxos/echo';
class
class Project
Project
extends
import Type
Type
.
const makeObject: <Project>(dxn: DXN.DXN, options?: {
id?: EntityId;
}) => Type.MakeObject<Project>

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 Project
Project
>(
import DXN
DXN
.
const make: <"com.example.type.project">(nsid: "com.example.type.project", 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.project', '0.1.0'),
)(
{
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
,
summary: Schema.optional<Schema.String>
summary
:
import Schema
Schema
.
const optional: optionalLambda
<Schema.String>(self: Schema.String) => Schema.optional<Schema.String>

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 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
) },
import Annotation
Annotation
.
const LabelAnnotation: AnnotationHelper<string[]>
export LabelAnnotation
LabelAnnotation
.
AnnotationHelper<string[]>.set: (value: string[]) => <S extends Schema.Top>(schema: S) => S

Set the annotation on an Effect schema.

Only accepts Schema.Top — annotations must be applied to the source schema BEFORE wrapping it with Type.makeObject / Type.makeRelation. In a pipe, place every Annotation.X.set(...) before the Type.make... step.

set
(['name']),
) {}

The last segment of the typename must be camelCase. A typename such as com.example.type.works-at fails to compile.

A relation type also names the types of its two endpoints. Relation covers making instances.

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

The JsonSchema module converts a type’s schema to JSON Schema and back. Type.makeObjectFromJsonSchema builds a type from JSON Schema, for a type that comes from a form, an agent or another system.

import * as
import Schema
Schema
from 'effect/Schema';
import {
import JsonSchema
JsonSchema
,
import Type
Type
} from '@dxos/echo';
import {
class Organization
Organization
} from './schema.ts';
// Out: a JSON Schema document for a form, an agent or another system.
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
));
// In: a type built from JSON Schema at runtime.
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' } } },
});

A type stored in a space with db.addType can gain and lose fields after it exists; the change replicates like any write. The functions throw for a type that is not stored. Objects already made from the type keep their data. Use a new version and a migration instead when old objects need converting.

import * as
import Schema
Schema
from 'effect/Schema';
import {
import Type
Type
} from '@dxos/echo';
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
) });
import Type
Type
.
const removeFields: (type: Type.AnyEntity, fieldNames: string[]) => void

Remove fields from 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.

removeFields
(
const note: Type.Type<TypeSchema>
note
, ['pinned']);

These read the identity and schema of a type, which is what an agent or a generic UI works from.

import {
import Type
Type
} from '@dxos/echo';
import {
class Person
Person
} from './schema.ts';
const
const typename: string
typename
=
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'
const
const version: string
version
=
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'
const
const uri: URI
uri
=
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 schema: Schema.Codec<Person & OfKind<EntityKind.Object>, unknown, never, never>
schema
=
import Type
Type
.
function getSchema<typeof Person>(type: typeof Person): Schema.Codec<Person & 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 Person
Person
);

Type.isObject and Type.isRelation test a type. To test an instance, use the predicates in Obj and Relation.

import {
import Obj
Obj
,
import Type
Type
} from '@dxos/echo';
import {
class Person
Person
} from './schema.ts';
import {
class WorksAt
WorksAt
} from './works-at.ts';
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 Type
Type
.
const isObject: (entity: Type.AnyEntity) => entity is Type.AnyObj

Type guard: narrows a Type.AnyEntity to an object-kind entity. Checks ENTITIES, not instances — use Obj.isObject for instances. Raw Schema.Schema values (including the branded Obj.Unknown companion) are intentionally not accepted; inspect their TypeAnnotation directly.

@performance ― O(1) brand read.

isObject
(
class Person
Person
); // true
import Type
Type
.
const isRelation: (entity: Type.AnyEntity) => entity is Type.AnyRelation

Type guard: narrows a Type.AnyEntity to a relation-kind entity. Checks ENTITIES, not instances — use Relation.isRelation for instances.

@performance ― O(1) brand read.

isRelation
(
class WorksAt
WorksAt
); // true
import Obj
Obj
.
const isObject: (obj: unknown) => obj is Obj.Unknown

Determine if object is an ECHO object.

@performance ― O(1) brand check; no allocation.

isObject
(
const ada: Obj.OfShape<Person & OfKind<EntityKind.Object>>
ada
); // true
import Obj
Obj
.
const instanceOf: <typeof Person>(schema: typeof Person, value: unknown) => value is Person & OfKind<EntityKind.Object> (+3 overloads)
instanceOf
(
class Person
Person
,
const ada: Obj.OfShape<Person & OfKind<EntityKind.Object>>
ada
); // true
Member Summary
makeObject Declare an object type
makeRelation Declare a relation type with typed endpoints
makeObjectFromJsonSchema Build an object type from JSON Schema
addFields updateFields removeFields Change the fields of a type at runtime
getTypename getVersion getURI getMeta Read the identity and metadata of a type
getSchema The Effect Schema of a type
JsonSchema.toJsonSchema JsonSchema.toEffectSchema Convert to and from JSON Schema
isObject isRelation isType Test what kind of type a value is

The full list is in the API reference.