Skip to content

Annotation

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

An annotation is a typed value attached to a schema, a field, or an object. Annotations on a type are saved with the type’s schema, so they replicate with it and a tool that has never seen the code can still read them.

Each annotation has a set that pipes onto a schema, and a get that reads it back as an Option.

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'),
)(
import Schema
Schema
.
function Struct<{
readonly name: Schema.String;
readonly summary: Schema.optional<Schema.String>;
}>(fields: {
readonly name: Schema.String;
readonly summary: Schema.optional<Schema.String>;
}): Schema.Struct<{
readonly name: Schema.String;
readonly summary: Schema.optional<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
,
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
),
}).
Pipeable.pipe<Schema.Struct<{
readonly name: Schema.String;
readonly summary: Schema.optional<Schema.String>;
}>, Schema.Struct<{
readonly name: Schema.String;
readonly summary: Schema.optional<Schema.String>;
}>, Schema.Struct<{
readonly name: Schema.String;
readonly summary: Schema.optional<Schema.String>;
}>, Schema.Struct<{
readonly name: Schema.String;
readonly summary: Schema.optional<Schema.String>;
}>>(this: Schema.Struct<...>, ab: (_: Schema.Struct<{
readonly name: Schema.String;
readonly summary: Schema.optional<Schema.String>;
}>) => Schema.Struct<...>, bc: (_: Schema.Struct<...>) => Schema.Struct<...>, cd: (_: Schema.Struct<...>) => Schema.Struct<...>): Schema.Struct<...> (+21 overloads)
pipe
(
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']),
import Annotation
Annotation
.
const IconAnnotation: Annotation.Annotation<Annotation.IconAnnotation>
export IconAnnotation

Icon to render in the UI.

IconAnnotation
.
Annotation<IconAnnotation>.set: (value: Annotation.IconAnnotation) => <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.

@performance ― O(value size) encode, once at schema-definition time.

set
({
icon: string

Sprite icon name (e.g., 'ph--user--regular', 'ph--cube--regular', 'px--anthropic--regular').

ph--* is Phosphor; px--* and dx--* are brand glyphs, which are sprite-only and carry no weight variants. All three are admitted because a type whose subject IS a brand — an Anthropic session, a GitHub repo — has no honest Phosphor equivalent.

icon
: 'ph--kanban--regular',
hue?: string | undefined

Color name.

List of colors:

  • 'red'
  • 'orange'
  • 'amber'
  • 'yellow'
  • 'lime'
  • 'green'
  • 'emerald'
  • 'teal'
  • 'cyan'
  • 'violet'
  • 'purple'
  • 'fuchsia'
  • 'pink'
  • 'rose'

hue
: 'indigo',
}),
import Annotation
Annotation
.
const DescriptionAnnotation: AnnotationHelper<string>
export DescriptionAnnotation
DescriptionAnnotation
.
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
('summary'),
),
) {}
Annotation Value What reads it
LabelAnnotation The fields that label an object, in order Obj.getLabel, lists and pickers
DescriptionAnnotation The field that describes an object Obj.getDescription
IconAnnotation An icon and a hue Composer’s navigation tree and cards
FormInputAnnotation FormLayoutAnnotation Whether a field shows in a form, and the form’s layout Forms built from the schema
ContentVersionAnnotation The fields a version field describes Content migrations
UserType That the type is for people, not internal Composer’s pickers, navigation tree and collections
import * as
import Option
Option
from 'effect/Option';
import {
import Annotation
Annotation
,
import Obj
Obj
,
import Type
Type
} from '@dxos/echo';
import {
class Event
Event
} from './schema.ts';
const
const icon: Option.Option<Annotation.IconAnnotation>
icon
=
import Annotation
Annotation
.
const IconAnnotation: Annotation.Annotation<Annotation.IconAnnotation>
export IconAnnotation

Icon to render in the UI.

IconAnnotation
.
Annotation<IconAnnotation>.get: (schema: Schema.Top) => Option.Option<Annotation.IconAnnotation>

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.

@performance ― O(1) annotation lookup, but decodes the value against schema on every call (not cached).

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
));
import Option
Option
.
const getOrUndefined: <Annotation.IconAnnotation>(self: Option.Option<Annotation.IconAnnotation>) => Annotation.IconAnnotation | undefined

Extracts the value from a Some, or returns undefined for None.

When to use

Use when you need to pass absent Option values to APIs that expect undefined.

Details

  • Some → the inner value
  • None → undefined

Example (Unwrapping to undefined)

import { Option } from "effect"
Option.getOrUndefined(Option.some(1)) // => 1
Option.getOrUndefined(Option.none()) // => undefined

@see ― getOrNull to return null instead

@see ― getOrElse for a custom fallback

@stability ― stable

@category ― getters

@since ― 2.0.0

getOrUndefined
(
const icon: Option.Option<Annotation.IconAnnotation>
icon
); // { icon: 'ph--calendar-dots--regular', hue: 'green' }
const
const roadmap: Obj.OfShape<Project & OfKind<EntityKind.Object>>
roadmap
=
import Obj
Obj
.
function make<typeof Project>(type: typeof Project, props: NoInfer<Obj.MakeProps<typeof Project>>, options?: Obj.MakeOptions): Obj.OfShape<Project & 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 Project
Project
, {
name: string
name
: 'Roadmap',
summary?: string | undefined
summary
: 'What ships next',
});
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 roadmap: Obj.OfShape<Project & OfKind<EntityKind.Object>>
roadmap
); // 'Roadmap'
import Obj
Obj
.
const getDescription: (entity: Obj.Unknown | Obj.Snapshot) => string | undefined

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

@performance ― O(1); reads the field named by the schema DescriptionAnnotation.

getDescription
(
const roadmap: Obj.OfShape<Project & OfKind<EntityKind.Object>>
roadmap
); // 'What ships next'

Annotation.make defines an annotation with an id and a schema for its value. Set it on a schema like the built-in ones, or on one object with Annotation.set, which stores the value in the object’s metadata.

import * as
import Schema
Schema
from 'effect/Schema';
import {
import Annotation
Annotation
,
import Obj
Obj
} from '@dxos/echo';
const
const ColorAnnotation: Annotation.Annotation<string>
ColorAnnotation
=
import Annotation
Annotation
.
const make: <string>(props: MakeProps<string>) => Annotation.Annotation<string>

Create a new schema annotation. Annotation can be assigned both to fields and to the schema itself. Annotation is serialized with the schema.

@example

const ColorAnnotation = Annotation.make({
id: 'org.dxos.annotation.color',
schema: Schema.String,
});
const schema = Schema.String.annotate(ColorAnnotation.set('red'));

@performance ― O(1) at definition time; validates the id format.

make
({
MakeProps<T>.id: string
id
: 'com.example.annotation.color',
MakeProps<string>.schema: Schema.Codec<string, any, never, never>
schema
:
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
,
});
// On an instance: stored in the object's metadata, and replicated with it.
import Obj
Obj
.
const update: <Obj.OfShape<Project & OfKind<EntityKind.Object>>>(obj: Obj.OfShape<Project & OfKind<EntityKind.Object>>, callback: ChangeCallback<Obj.OfShape<Project & OfKind<EntityKind.Object>>>) => Obj.OfShape<Project & 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 roadmap: Obj.OfShape<Project & OfKind<EntityKind.Object>>
roadmap
, (
roadmap: {
id: EntityId;
"~@dxos/echo/Kind": EntityKind.Object;
name: string;
summary?: string | undefined;
}
roadmap
) => {
import Annotation
Annotation
.
const set: <string>(target: Mutable<Unknown>, annotation: Annotation.Annotation<string>, value: string) => void (+1 overload)
set
(
roadmap: {
id: EntityId;
"~@dxos/echo/Kind": EntityKind.Object;
name: string;
summary?: string | undefined;
}
roadmap
,
const ColorAnnotation: Annotation.Annotation<string>
ColorAnnotation
, 'teal');
});
const
const color: Option.Option<string>
color
=
import Annotation
Annotation
.
const get: <string>(target: Unknown | Snapshot, annotation: Annotation.Annotation<string>) => Option.Option<string> (+1 overload)
get
(
const roadmap: Obj.OfShape<Project & OfKind<EntityKind.Object>>
roadmap
,
const ColorAnnotation: Annotation.Annotation<string>
ColorAnnotation
); // Some('teal')
Member Summary
make Define an annotation
get set update Read and write an annotation on an object
<annotation>.get <annotation>.set Read and write an annotation on a schema

The full list is in the API reference.