Skip to content

Why ECHO?

ECHO is the data model that applications and agents share. Plugins code against its objects instead of keeping private stores. An agent keeps its memory in the same graph, and can inspect the schema of everything in it. Data from external systems lands there as linked objects, so one query can reach all of it.

A conversation ends and its context goes with it. An agent that works across sessions needs somewhere durable to put what it learned, and that memory is only useful if it connects to the work it is about.

In ECHO a memory is an ordinary object. It has a type, it can reference the issue it is about, and it comes back through the same query an application uses. The org.dxos.skill.memory skill stores an agent’s memories this way.

class
class Memory
Memory
extends
import Type
Type
.
const makeObject: <Memory>(dxn: DXN.DXN, options?: {
id?: EntityId;
}) => Type.MakeObject<Memory>

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 Memory
Memory
>(
import DXN
DXN
.
const make: <"com.example.type.memory">(nsid: "com.example.type.memory", 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.memory', '0.1.0'))(
import Schema
Schema
.
function Struct<{
readonly title: Schema.String;
readonly content: Schema.String;
readonly about: Schema.optional<Ref.RefSchema<Issue & OfKind<EntityKind.Object> & Obj.Unknown>>;
}>(fields: {
readonly title: Schema.String;
readonly content: Schema.String;
readonly about: Schema.optional<Ref.RefSchema<Issue & OfKind<EntityKind.Object> & Obj.Unknown>>;
}): Schema.Struct<{
readonly title: Schema.String;
readonly content: Schema.String;
readonly about: Schema.optional<Ref.RefSchema<Issue & OfKind<EntityKind.Object> & Obj.Unknown>>;
}>

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
({
title: Schema.String
title
:
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
,
content: Schema.String
content
:
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
,
about: Schema.optional<Ref.RefSchema<Issue & OfKind<EntityKind.Object> & Obj.Unknown>>
about
:
import Schema
Schema
.
const optional: optionalLambda
<Ref.RefSchema<Issue & OfKind<EntityKind.Object> & Obj.Unknown>>(self: Ref.RefSchema<Issue & OfKind<EntityKind.Object> & Obj.Unknown>) => Schema.optional<Ref.RefSchema<Issue & 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 Issue>(type: typeof Issue) => Ref.RefSchema<Issue & OfKind<EntityKind.Object> & Obj.Unknown> (+3 overloads)
Ref
(
class Issue
Issue
)),
}),
) {}
const
const issue: BaseObj & Issue & OfKind<EntityKind.Object>
issue
=
const db: Database.Database
db
.
Database.add<BaseObj & Issue & OfKind<EntityKind.Object>>(obj: BaseObj & Issue & OfKind<EntityKind.Object>, opts?: Database.AddOptions): BaseObj & Issue & OfKind<EntityKind.Object>

Adds an object or relation to the database.

Only Object and Relation entities are accepted. To persist a Type definition use

addType

— passing a Type entity is rejected at compile time (and at runtime).

Pass { to: feed } to append to a feed instead (synchronous; confirm with

flush

).

@performance ― Synchronous; O(n) in object size to write it into a new linked document (or the root), persisted in the background.

add
(
import Obj
Obj
.
function make<typeof Issue>(type: typeof Issue, props: NoInfer<Obj.MakeProps<typeof Issue>>, options?: Obj.MakeOptions): Obj.OfShape<Issue & 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 Issue
Issue
, {
title: string
title
: 'Sync drops the last page' }));
const db: Database.Database
db
.
Database.add<BaseObj & Memory & OfKind<EntityKind.Object>>(obj: BaseObj & Memory & OfKind<EntityKind.Object>, opts?: Database.AddOptions): BaseObj & Memory & OfKind<EntityKind.Object>

Adds an object or relation to the database.

Only Object and Relation entities are accepted. To persist a Type definition use

addType

— passing a Type entity is rejected at compile time (and at runtime).

Pass { to: feed } to append to a feed instead (synchronous; confirm with

flush

).

@performance ― Synchronous; O(n) in object size to write it into a new linked document (or the root), persisted in the background.

add
(
import Obj
Obj
.
function make<typeof Memory>(type: typeof Memory, props: NoInfer<Obj.MakeProps<typeof Memory>>, options?: Obj.MakeOptions): Obj.OfShape<Memory & 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 Memory
Memory
, {
title: string
title
: 'Root cause',
content: string
content
: 'The cursor skips short pages.',
about?: Ref<Issue & OfKind<EntityKind.Object> & Obj.Unknown> | undefined
about
:
import Ref
Ref
.
const make: <BaseObj & Issue & OfKind<EntityKind.Object>>(object: BaseObj & Issue & OfKind<EntityKind.Object>) => Ref<BaseObj & Issue & OfKind<EntityKind.Object>>

Create a reference to an entity.

@performance ― O(1); allocates a ref that inlines the target, with no lookup.

make
(
const issue: BaseObj & Issue & OfKind<EntityKind.Object>
issue
) }));
const
const recalled: (Memory & OfKind<EntityKind.Object>)[]
recalled
= await
const db: Database.Database
db
.
Database.query: Database.QueryFn
<Query.Query<Memory & OfKind<EntityKind.Object>>>(query: Query.Query<Memory & OfKind<EntityKind.Object>>) => QueryResult<Memory & OfKind<EntityKind.Object>> (+1 overload)
query
(
import Query
Query
.
const select: <Filter.Filter<Memory & OfKind<EntityKind.Object>>>(filter: Filter.Filter<Memory & OfKind<EntityKind.Object>>) => Query.Query<Memory & OfKind<EntityKind.Object>>

Select objects based on a filter.

@param ― filter - Filter to select the objects.

@returns ― Query for the selected objects.

@performance ― O(1); wraps the current AST in a new immutable node without executing anything.

select
(
import Filter
Filter
.
const type: <typeof Memory>(type: typeof Memory, props?: Filter.Props<Memory & OfKind<EntityKind.Object>> | undefined) => Filter.Filter<Memory & OfKind<EntityKind.Object>> (+4 overloads)
type
(
class Memory
Memory
))).
QueryResult<Memory & OfKind<EntityKind.Object>>.run(opts?: RunOptions): Promise<(Memory & OfKind<EntityKind.Object>)[]>

Returns all known results.

@performance ― Async; executes the full query (index plus working set) on every call, not cached; O(n) in the data the executor examines, not in the result count.

run
();

Because memory is not a separate store, it replicates with the rest of the space, and any code that can query the space can read it.

An agent has no compiled types. It works from what it can read at run time, so the schema has to be data, not only a TypeScript declaration.

Every ECHO type reports its name, version and metadata at run time, and converts to JSON Schema, the format tool parameters already use. org.dxos.echo#F-2.3 The schema goes the other way too: a type built from JSON Schema and added to a database replicates like any object, so an agent can define a new type while it runs.

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 Memory
Memory
);
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 Memory
Memory
);
const
const parameters: {
type?: "string" | "number" | "boolean" | "object" | "array" | "integer" | "null" | ("string" | "number" | "boolean" | "object" | "array" | "integer" | "null")[] | undefined;
required?: string[] | undefined;
title?: 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;
description?: string | undefined;
... 43 more ...;
echo?: {
...;
} | undefined;
}
parameters
=
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
(
class Memory
Memory
);
const
const Contact: Type.Type<TypeSchema>
Contact
=
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.contact',
version?: string | undefined
version
: '0.1.0',
jsonSchema: JsonSchemaType
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 Schema
Schema
.
function Struct<{
readonly name: Schema.String;
}>(fields: {
readonly name: Schema.String;
}): Schema.Struct<{
readonly name: Schema.String;
}>

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
})),
});
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 Contact: Type.Type<TypeSchema>
Contact
);

The database skill in plugin-space gives agents this through its QueryTypes and AddType operations. An agent can look up what a space holds before it writes, instead of guessing at field names.

Mail, issues and calendars live in systems that do not know about each other. Syncing them into ECHO makes each record an object, with a foreign key that records where it came from. A Ref can then link a GitHub issue to a Linear task, or to the note that discusses both.

The foreign key is what makes a repeat sync safe. Look the record up by its key, then update the object you found or add a new one, and the record does not end up in the graph twice.

const
const key: {
source: string;
id: string;
}
key
= {
source: string
source
: 'github.com',
id: string
id
: '1234' };
const [
const existing: Issue & OfKind<EntityKind.Object>
existing
] = await
const db: Database.Database
db
.
Database.query: Database.QueryFn
<Query.Query<Issue & OfKind<EntityKind.Object>>>(query: Query.Query<Issue & OfKind<EntityKind.Object>>) => QueryResult<Issue & OfKind<EntityKind.Object>> (+1 overload)
query
(
import Query
Query
.
const select: <Filter.Filter<Issue & OfKind<EntityKind.Object>>>(filter: Filter.Filter<Issue & OfKind<EntityKind.Object>>) => Query.Query<Issue & OfKind<EntityKind.Object>>

Select objects based on a filter.

@param ― filter - Filter to select the objects.

@returns ― Query for the selected objects.

@performance ― O(1); wraps the current AST in a new immutable node without executing anything.

select
(
import Filter
Filter
.
const foreignKeys: <typeof Issue>(schema: typeof Issue, keys: ForeignKey[]) => Filter.Filter<Issue & OfKind<EntityKind.Object>>

Filter by foreign keys.

@performance ― O(1); builds a filter AST node without matching anything.

foreignKeys
(
class Issue
Issue
, [
const key: {
source: string;
id: string;
}
key
]))).
QueryResult<Issue & OfKind<EntityKind.Object>>.run(opts?: RunOptions): Promise<(Issue & OfKind<EntityKind.Object>)[]>

Returns all known results.

@performance ― Async; executes the full query (index plus working set) on every call, not cached; O(n) in the data the executor examines, not in the result count.

run
();
if (
const existing: Issue & OfKind<EntityKind.Object>
existing
) {
import Obj
Obj
.
const update: <Issue & OfKind<EntityKind.Object>>(obj: Issue & OfKind<EntityKind.Object>, callback: ChangeCallback<Issue & OfKind<EntityKind.Object>>) => Issue & OfKind<EntityKind.Object>

Perform mutations on an echo object within a controlled context.

All mutations within the callback are batched and trigger a single notification when the callback completes. Direct mutations outside of Obj.update will throw an error for echo objects.

This function also works with nested objects within echo objects (e.g., Template structs) that are reactive at runtime.

@param ― obj - The echo object to mutate. Use Relation.update for relations.

@param ― callback - The callback that performs mutations on the object.

@example

const person = Obj.make(Person, { name: 'John', age: 25 });
// Mutate within Obj.update
Obj.update(person, (obj) => {
obj.name = 'Jane';
obj.age = 30;
});
// ONE notification fires here
// Direct mutation throws
person.name = 'Bob'; // Error: Cannot modify outside Obj.update()

Note: Only accepts objects. Use Relation.update for relations.

@performance ― Synchronous; costs the mutations made in the callback plus one batched notification and an O(owning fields) parent pass.

update
(
const existing: Issue & OfKind<EntityKind.Object>
existing
, (
existing: {
title: string;
id: EntityId;
"~@dxos/echo/Kind": EntityKind.Object;
}
existing
) => {
existing: {
title: string;
id: EntityId;
"~@dxos/echo/Kind": EntityKind.Object;
}
existing
.
title: string
title
= 'Sync drops the last page';
});
} else {
const db: Database.Database
db
.
Database.add<BaseObj & Issue & OfKind<EntityKind.Object>>(obj: BaseObj & Issue & OfKind<EntityKind.Object>, opts?: Database.AddOptions): BaseObj & Issue & OfKind<EntityKind.Object>

Adds an object or relation to the database.

Only Object and Relation entities are accepted. To persist a Type definition use

addType

— passing a Type entity is rejected at compile time (and at runtime).

Pass { to: feed } to append to a feed instead (synchronous; confirm with

flush

).

@performance ― Synchronous; O(n) in object size to write it into a new linked document (or the root), persisted in the background.

add
(
import Obj
Obj
.
function make<typeof Issue>(type: typeof Issue, props: NoInfer<Obj.MakeProps<typeof Issue>>, options?: Obj.MakeOptions): Obj.OfShape<Issue & 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 Issue
Issue
, { [
import Obj
Obj
.
const Meta: symbol

Property that accesses metadata for an entity.

Alias for Entity.Meta.

Mutable meta type returned by Obj.getMeta inside an Obj.update callback.

Meta
]: {
keys: {
source: string;
id: string;
}[]
keys
: [
const key: {
source: string;
id: string;
}
key
] },
title: string
title
: 'Sync drops the last page' }));
}

plugin-github and plugin-linear sync this way. Once the data is in the graph, the code that reads it does not need to know which system it came from.

A plugin declares its types with Type.makeObject and contributes them to the schema registry. Its UI, its operations and the agents that use it all read and write the same objects. There is no plugin-specific API in between, so a second plugin can select the first plugin’s objects by type.

The rest of this page is the argument for the model itself. Keeping state on a server means client code spends its time on copies: caches, optimistic updates, retry queues and conflict handling. ECHO moves the state onto the device. Objects are CRDT documents in a local database, so a write is finished when the call returns, and peers merge their changes when they connect.

An ECHO type is a class that declares one schema. The class is the runtime schema and the TypeScript type of its instances.

class
class Person
Person
extends
import Type
Type
.
const makeObject: <Person>(dxn: DXN.DXN, options?: {
id?: EntityId;
}) => Type.MakeObject<Person>

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 Person
Person
>(
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.1.0'))(
import Schema
Schema
.
function Struct<{
readonly name: Schema.String;
}>(fields: {
readonly name: Schema.String;
}): Schema.Struct<{
readonly name: Schema.String;
}>

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
}),
) {}
class
class Task
Task
extends
import Type
Type
.
const makeObject: <Task>(dxn: DXN.DXN, options?: {
id?: EntityId;
}) => Type.MakeObject<Task>

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 Task
Task
>(
import DXN
DXN
.
const make: <"com.example.type.task">(nsid: "com.example.type.task", 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.task', '0.1.0'))(
import Schema
Schema
.
function Struct<{
readonly title: Schema.String;
readonly done: Schema.Boolean;
readonly assignee: Schema.optional<Ref.RefSchema<Person & OfKind<EntityKind.Object> & Obj.Unknown>>;
}>(fields: {
readonly title: Schema.String;
readonly done: Schema.Boolean;
readonly assignee: Schema.optional<Ref.RefSchema<Person & OfKind<EntityKind.Object> & Obj.Unknown>>;
}): Schema.Struct<{
readonly title: Schema.String;
readonly done: Schema.Boolean;
readonly assignee: Schema.optional<Ref.RefSchema<Person & OfKind<EntityKind.Object> & Obj.Unknown>>;
}>

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
({
title: Schema.String
title
:
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
,
done: Schema.Boolean
done
:
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
,
assignee: Schema.optional<Ref.RefSchema<Person & OfKind<EntityKind.Object> & Obj.Unknown>>
assignee
:
import Schema
Schema
.
const optional: optionalLambda
<Ref.RefSchema<Person & OfKind<EntityKind.Object> & Obj.Unknown>>(self: Ref.RefSchema<Person & OfKind<EntityKind.Object> & Obj.Unknown>) => Schema.optional<Ref.RefSchema<Person & 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 Person>(type: typeof Person) => Ref.RefSchema<Person & OfKind<EntityKind.Object> & Obj.Unknown> (+3 overloads)
Ref
(
class Person
Person
)),
}),
) {}

Reading the signature left to right:

class Task extends
Type.makeObjectan object type
<Task>instance type
(dxn)global type name and version
(schema)Effect Schema of the fields
{}

Instances are live objects. You add them to a database, change them with Obj.update, and select them with a typed query.

const
const ada: BaseObj & Person & OfKind<EntityKind.Object>
ada
=
const db: Database.Database
db
.
Database.add<BaseObj & Person & OfKind<EntityKind.Object>>(obj: BaseObj & Person & OfKind<EntityKind.Object>, opts?: Database.AddOptions): BaseObj & Person & OfKind<EntityKind.Object>

Adds an object or relation to the database.

Only Object and Relation entities are accepted. To persist a Type definition use

addType

— passing a Type entity is rejected at compile time (and at runtime).

Pass { to: feed } to append to a feed instead (synchronous; confirm with

flush

).

@performance ― Synchronous; O(n) in object size to write it into a new linked document (or the root), persisted in the background.

add
(
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' }));
const
const task: BaseObj & Task & OfKind<EntityKind.Object>
task
=
const db: Database.Database
db
.
Database.add<BaseObj & Task & OfKind<EntityKind.Object>>(obj: BaseObj & Task & OfKind<EntityKind.Object>, opts?: Database.AddOptions): BaseObj & Task & OfKind<EntityKind.Object>

Adds an object or relation to the database.

Only Object and Relation entities are accepted. To persist a Type definition use

addType

— passing a Type entity is rejected at compile time (and at runtime).

Pass { to: feed } to append to a feed instead (synchronous; confirm with

flush

).

@performance ― Synchronous; O(n) in object size to write it into a new linked document (or the root), persisted in the background.

add
(
import Obj
Obj
.
function make<typeof Task>(type: typeof Task, props: NoInfer<Obj.MakeProps<typeof Task>>, options?: Obj.MakeOptions): Obj.OfShape<Task & 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 Task
Task
, {
title: string
title
: 'Review',
done: boolean
done
: false,
assignee?: Ref<Person & OfKind<EntityKind.Object> & Obj.Unknown> | undefined
assignee
:
import Ref
Ref
.
const make: <BaseObj & Person & OfKind<EntityKind.Object>>(object: BaseObj & Person & OfKind<EntityKind.Object>) => Ref<BaseObj & Person & OfKind<EntityKind.Object>>

Create a reference to an entity.

@performance ― O(1); allocates a ref that inlines the target, with no lookup.

make
(
const ada: BaseObj & Person & OfKind<EntityKind.Object>
ada
) }));
import Obj
Obj
.
const update: <BaseObj & Task & OfKind<EntityKind.Object>>(obj: BaseObj & Task & OfKind<EntityKind.Object>, callback: ChangeCallback<BaseObj & Task & OfKind<EntityKind.Object>>) => BaseObj & Task & OfKind<EntityKind.Object>

Perform mutations on an echo object within a controlled context.

All mutations within the callback are batched and trigger a single notification when the callback completes. Direct mutations outside of Obj.update will throw an error for echo objects.

This function also works with nested objects within echo objects (e.g., Template structs) that are reactive at runtime.

@param ― obj - The echo object to mutate. Use Relation.update for relations.

@param ― callback - The callback that performs mutations on the object.

@example

const person = Obj.make(Person, { name: 'John', age: 25 });
// Mutate within Obj.update
Obj.update(person, (obj) => {
obj.name = 'Jane';
obj.age = 30;
});
// ONE notification fires here
// Direct mutation throws
person.name = 'Bob'; // Error: Cannot modify outside Obj.update()

Note: Only accepts objects. Use Relation.update for relations.

@performance ― Synchronous; costs the mutations made in the callback plus one batched notification and an O(owning fields) parent pass.

update
(
const task: BaseObj & Task & OfKind<EntityKind.Object>
task
, (
task: {
id: EntityId;
"~@dxos/echo/Kind": EntityKind.Object;
title: string;
done: boolean;
assignee?: Ref<Person & OfKind<EntityKind.Object> & Obj.Unknown> | undefined;
}
task
) => {
task: {
id: EntityId;
"~@dxos/echo/Kind": EntityKind.Object;
title: string;
done: boolean;
assignee?: Ref<Person & OfKind<EntityKind.Object> & Obj.Unknown> | undefined;
}
task
.
done: boolean
done
= true;
});
const
const finished: (Task & OfKind<EntityKind.Object>)[]
finished
= await
const db: Database.Database
db
.
Database.query: Database.QueryFn
<Query.Query<Task & OfKind<EntityKind.Object>>>(query: Query.Query<Task & OfKind<EntityKind.Object>>) => QueryResult<Task & OfKind<EntityKind.Object>> (+1 overload)
query
(
import Query
Query
.
const select: <Filter.Filter<Task & OfKind<EntityKind.Object>>>(filter: Filter.Filter<Task & OfKind<EntityKind.Object>>) => Query.Query<Task & OfKind<EntityKind.Object>>

Select objects based on a filter.

@param ― filter - Filter to select the objects.

@returns ― Query for the selected objects.

@performance ― O(1); wraps the current AST in a new immutable node without executing anything.

select
(
import Filter
Filter
.
const type: <typeof Task>(type: typeof Task, props?: Filter.Props<Task & OfKind<EntityKind.Object>> | undefined) => Filter.Filter<Task & OfKind<EntityKind.Object>> (+4 overloads)
type
(
class Task
Task
, {
done?: boolean | Filter.Filter<boolean> | undefined
done
: true }))).
QueryResult<Task & OfKind<EntityKind.Object>>.run(opts?: RunOptions): Promise<(Task & OfKind<EntityKind.Object>)[]>

Returns all known results.

@performance ― Async; executes the full query (index plus working set) on every call, not cached; O(n) in the data the executor examines, not in the result count.

run
();

Nothing here waits on a network. db.add and Obj.update apply locally and return, and finished already contains the task. org.dxos.echo.query#F-2

Here is one task, marking an item as done, written against a REST API and against ECHO. The REST version is not careless. It is the minimum that handles a slow network, an offline user and a concurrent edit, and it still has two open questions in its comments.

REST, cache and queue
const
const finish: (id: string) => Promise<void>
finish
= async (
id: string
id
: string) => {
const
const previous: Task | undefined
previous
=
const cache: Map<string, Task>
cache
.
Map<string, Task>.get(key: string): Task | undefined

Returns a specified element from the Map object. If the value that is associated to the provided key is an object, then you will get a reference to that object and any change made to that object will effectively modify it inside the Map.

@returns ― Returns the element associated with the specified key. If no element is associated with the specified key, undefined is returned.

get
(
id: string
id
);
if (!
const previous: Task | undefined
previous
) {
return;
}
const
const next: {
done: boolean;
id: string;
version: number;
}
next
= { ...
const previous: Task
previous
,
done: boolean
done
: true };
const cache: Map<string, Task>
cache
.
Map<string, Task>.set(key: string, value: Task): Map<string, Task>

Adds a new element with a specified key and value to the Map. If an element with the same key already exists, the element will be updated.

set
(
id: string
id
,
const next: {
done: boolean;
id: string;
version: number;
}
next
);
const render: () => void
render
();
const
const response: Response | undefined
response
= await
function fetch(input: RequestInfo | URL, init?: RequestInit): Promise<Response>
fetch
(`/api/tasks/${
id: string
id
}`, {
RequestInit.method?: string | undefined

A string to set request's method.

method
: 'PATCH',
RequestInit.headers?: HeadersInit | undefined

A Headers object, an object literal, or an array of two-item arrays to set request's headers.

headers
: { 'If-Match':
var String: StringConstructor
(value?: any) => string

Allows manipulation and formatting of text strings and determination and location of substrings within strings.

String
(
const previous: Task
previous
.
version: number
version
) },
RequestInit.body?: BodyInit | null | undefined

A BodyInit object or null to set request's body.

body
:
var JSON: JSON

An intrinsic object that provides functions to convert JavaScript values to and from the JavaScript Object Notation (JSON) format.

JSON
.
JSON.stringify(value: any, replacer?: (this: any, key: string, value: any) => any, space?: string | number): string (+1 overload)

Converts a JavaScript value to a JavaScript Object Notation (JSON) string.

@param ― value A JavaScript value, usually an object or array, to be converted.

@param ― replacer A function that transforms the results.

@param ― space Adds indentation, white space, and line break characters to the return-value JSON text to make it easier to read.

@throws ― {TypeError} If a circular reference or a BigInt value is found.

stringify
({
done: boolean
done
: true }),
}).
Promise<Response>.catch<undefined>(onrejected?: ((reason: any) => PromiseLike<undefined> | undefined) | null | undefined): Promise<Response | undefined>

Attaches a callback for only the rejection of the Promise.

@param ― onrejected The callback to execute when the Promise is rejected.

@returns ― A Promise for the completion of the callback.

catch
(() =>
var undefined
undefined
);
if (!
const response: Response | undefined
response
) {
const queue: {
push: (task: Task) => void;
}
queue
.
push: (task: Task) => void
push
(
const next: {
done: boolean;
id: string;
version: number;
}
next
); // Offline: replay later, somehow.
} else if (
const response: Response
response
.
Response.status: number

The status read-only property of the Response interface contains the HTTP status codes of the response.

MDN Reference

status
=== 412) {
const cache: Map<string, Task>
cache
.
Map<string, Task>.set(key: string, value: Task): Map<string, Task>

Adds a new element with a specified key and value to the Map. If an element with the same key already exists, the element will be updated.

set
(
id: string
id
,
const previous: Task
previous
); // Conflict: drop the user's edit?
const render: () => void
render
();
}
};
ECHO
const
const finish: (id: string) => void
finish
= (
id: string
id
: string) => {
const
const task: Task | undefined
task
=
const db: Database.Database
db
.
Database.getObjectById<Task>(id: string, opts?: Database.GetObjectByIdOptions): Task | undefined

Return object by local ID.

@deprecated ― Use db.query(Filter.id(id)).runSync()[0] for a working-set lookup, or resolve via a Ref.

@performance ― O(1) working-set lookup by id; never loads from disk.

getObjectById
<
class Task
Task
>(
id: string
id
);
if (
const task: Task | undefined
task
) {
import Obj
Obj
.
const update: <Task>(obj: Task, callback: ChangeCallback<Task>) => Task

Perform mutations on an echo object within a controlled context.

All mutations within the callback are batched and trigger a single notification when the callback completes. Direct mutations outside of Obj.update will throw an error for echo objects.

This function also works with nested objects within echo objects (e.g., Template structs) that are reactive at runtime.

@param ― obj - The echo object to mutate. Use Relation.update for relations.

@param ― callback - The callback that performs mutations on the object.

@example

const person = Obj.make(Person, { name: 'John', age: 25 });
// Mutate within Obj.update
Obj.update(person, (obj) => {
obj.name = 'Jane';
obj.age = 30;
});
// ONE notification fires here
// Direct mutation throws
person.name = 'Bob'; // Error: Cannot modify outside Obj.update()

Note: Only accepts objects. Use Relation.update for relations.

@performance ― Synchronous; costs the mutations made in the callback plus one batched notification and an O(owning fields) parent pass.

update
(
const task: Task
task
, (
task: {
done: boolean;
id: EntityId;
"~@dxos/echo/Kind": EntityKind.Object;
}
task
) => {
task: {
done: boolean;
id: EntityId;
"~@dxos/echo/Kind": EntityKind.Object;
}
task
.
done: boolean
done
= true;
});
}
};

The ECHO version has no error branches for these cases because the failures do not reach it. There is no request to fail, the local write cannot conflict with itself, and a concurrent edit from a peer merges as a CRDT change.

Each query reads one local engine, which answers from the objects this tab holds. A subscriber sees your own write in the same call that made it, so a list does not need an optimistic copy of itself. org.dxos.echo.query#F-2

A query can see more than this device holds

Section titled “A query can see more than this device holds”

Queries also read the host index and EDGE, which hold objects this tab has not loaded. ECHO treats those answers as evidence. It re-evaluates each remote entry against the local document and drops the entry when local state contradicts it, so a slow server cannot bring back an object you deleted. org.dxos.echo.query#F-5

Every type carries a version in its DXN. An object migration declares how objects of an old version become the new one, so data written by last year’s build still opens. org.dxos.echo#Migration

A Ref field stores a DXN and resolves lazily, across objects and across spaces. Relations are objects too, with typed source and target endpoints, so you can query from either end. org.dxos.echo#F-4

Once persistence and merging are the database’s job, the code that remains is your application. Start with the learning path, or look up a module in the reference.