Skip to content

Obj

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

Obj makes objects and reads and writes them. Its functions take the object as the first argument and work whether or not the object is in a database yet, so a getter that needs the database returns undefined outside one instead of throwing. org.dxos.echo#F-3.3

Obj.make checks the props against the type and gives the object an id. Reserved fields such as Obj.Meta go in the same props. Here the meta records the object’s key in an external system. org.dxos.echo#F-3.1

import {
import Obj
Obj
,
import Ref
Ref
} from '@dxos/echo';
import {
class Person
Person
} from './schema.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
, {
[
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
: [{
source: string
source
: 'crm.example.com',
id: string
id
: 'contact-42' }] },
name: string
name
: 'Ada',
employer?: Ref<Organization & OfKind<EntityKind.Object> & Obj.Unknown> | undefined
employer
:
import Ref
Ref
.
const make: <BaseObj & Organization & OfKind<EntityKind.Object>>(object: BaseObj & Organization & OfKind<EntityKind.Object>) => Ref<BaseObj & Organization & OfKind<EntityKind.Object>>

Create a reference to an entity.

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

make
(
const acme: BaseObj & Organization & OfKind<EntityKind.Object>
acme
),
});
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
(
const ada: Obj.OfShape<Person & OfKind<EntityKind.Object>>
ada
);

Read a field like any property. Writes go inside Obj.update; a write outside it throws. All the writes in one callback reach subscribers as one change.

A live object always shows the latest state. Obj.getSnapshot returns a frozen copy that later updates do not touch, which is what you want to keep a before-and-after or hand data to code that must not write it.

import {
import Obj
Obj
} from '@dxos/echo';
const
const name: string
name
=
const ada: Obj.OfShape<Person & OfKind<EntityKind.Object>>
ada
.
name: string
name
; // read a field directly
import Obj
Obj
.
const update: <Obj.OfShape<Person & OfKind<EntityKind.Object>>>(obj: Obj.OfShape<Person & OfKind<EntityKind.Object>>, callback: ChangeCallback<Obj.OfShape<Person & OfKind<EntityKind.Object>>>) => Obj.OfShape<Person & 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 ada: Obj.OfShape<Person & OfKind<EntityKind.Object>>
ada
, (
ada: {
id: EntityId;
"~@dxos/echo/Kind": EntityKind.Object;
name: string;
email?: string | undefined;
employer?: Ref<Organization & OfKind<EntityKind.Object> & Obj.Unknown> | undefined;
}
ada
) => {
ada: {
id: EntityId;
"~@dxos/echo/Kind": EntityKind.Object;
name: string;
email?: string | undefined;
employer?: Ref<Organization & OfKind<EntityKind.Object> & Obj.Unknown> | undefined;
}
ada
.
email?: string | undefined
email
= 'ada@example.com';
});
const
const before: Obj.Snapshot<Obj.OfShape<Person & OfKind<EntityKind.Object>>>
before
=
import Obj
Obj
.
const getSnapshot: <Obj.OfShape<Person & OfKind<EntityKind.Object>>>(obj: Obj.OfShape<Person & OfKind<EntityKind.Object>>) => Obj.Snapshot<Obj.OfShape<Person & OfKind<EntityKind.Object>>>

Returns an immutable snapshot of an object. The snapshot is branded with SnapshotKindId instead of KindId, making it distinguishable from the reactive object at the type level.

@performance ― O(n) in entity size; deep-copies plain data (refs are shared, not resolved) into a frozen object per call.

getSnapshot
(
const ada: Obj.OfShape<Person & OfKind<EntityKind.Object>>
ada
); // an immutable copy
import Obj
Obj
.
const update: <Obj.OfShape<Person & OfKind<EntityKind.Object>>>(obj: Obj.OfShape<Person & OfKind<EntityKind.Object>>, callback: ChangeCallback<Obj.OfShape<Person & OfKind<EntityKind.Object>>>) => Obj.OfShape<Person & 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 ada: Obj.OfShape<Person & OfKind<EntityKind.Object>>
ada
, (
ada: {
id: EntityId;
"~@dxos/echo/Kind": EntityKind.Object;
name: string;
email?: string | undefined;
employer?: Ref<Organization & OfKind<EntityKind.Object> & Obj.Unknown> | undefined;
}
ada
) => {
ada: {
id: EntityId;
"~@dxos/echo/Kind": EntityKind.Object;
name: string;
email?: string | undefined;
employer?: Ref<Organization & OfKind<EntityKind.Object> & Obj.Unknown> | undefined;
}
ada
.
name: string
name
= 'Ada Lovelace';
});
// before.name is still 'Ada'

The callback runs synchronously after each committed change. Query subscriptions over the object re-evaluate at the same time. org.dxos.echo#F-3.2 In React, use the hooks in the React guide instead.

import {
import Obj
Obj
} from '@dxos/echo';
const
const unsubscribe: () => void
unsubscribe
=
import Obj
Obj
.
const subscribe: (obj: Obj.Unknown, callback: () => void) => (() => void)

Subscribe to object updates. The callback is called synchronously when the object is modified. Only accepts reactive objects (not snapshots).

@returns ― Unsubscribe function.

@performance ― O(1) listener registration; the callback runs synchronously after every committed change.

subscribe
(
const ada: Obj.OfShape<Person & OfKind<EntityKind.Object>>
ada
, () => {
var console: Console

The console module provides a simple debugging console that is similar to the JavaScript console mechanism provided by web browsers.

The module exports two specific components:

  • A Console class with methods such as console.log(), console.error() and console.warn() that can be used to write to any Node.js stream.
  • A global console instance configured to write to process.stdout and process.stderr. The global console can be used without importing the node:console module.

Warning: The global console object's methods are neither consistently synchronous like the browser APIs they resemble, nor are they consistently asynchronous like all other Node.js streams. See the note on process I/O for more information.

Example using the global console:

console.log('hello world');
// Prints: hello world, to stdout
console.log('hello %s', 'world');
// Prints: hello world, to stdout
console.error(new Error('Whoops, something bad happened'));
// Prints error message and stack trace to stderr:
// Error: Whoops, something bad happened
// at [eval]:5:15
// at Script.runInThisContext (node:vm:132:18)
// at Object.runInThisContext (node:vm:309:38)
// at node:internal/process/execution:77:19
// at [eval]-wrapper:6:22
// at evalScript (node:internal/process/execution:76:60)
// at node:internal/main/eval_string:23:3
const name = 'Will Robinson';
console.warn(`Danger ${name}! Danger!`);
// Prints: Danger Will Robinson! Danger!, to stderr

Example using the Console class:

const out = getStreamSomehow();
const err = getStreamSomehow();
const myConsole = new console.Console(out, err);
myConsole.log('hello world');
// Prints: hello world, to out
myConsole.log('hello %s', 'world');
// Prints: hello world, to out
myConsole.error(new Error('Whoops, something bad happened'));
// Prints: [Error: Whoops, something bad happened], to err
const name = 'Will Robinson';
myConsole.warn(`Danger ${name}! Danger!`);
// Prints: Danger Will Robinson! Danger!, to err

@see ― source

console
.
Console.log(message?: any, ...optionalParams: any[]): void (+1 overload)

Prints to stdout with newline. Multiple arguments can be passed, with the first used as the primary message and all additional used as substitution values similar to printf(3) (the arguments are all passed to util.format()).

const count = 5;
console.log('count: %d', count);
// Prints: count: 5, to stdout
console.log('count:', count);
// Prints: count: 5, to stdout

See util.format() for more information.

@since ― v0.1.100

log
('changed',
const ada: Obj.OfShape<Person & OfKind<EntityKind.Object>>
ada
.
name: string
name
);
});
const unsubscribe: () => void
unsubscribe
();

Meta holds data about the object rather than in it, such as foreign keys and tags. Labels and descriptions are what lists and pickers show.

import {
import Obj
Obj
,
import Tag
Tag
} from '@dxos/echo';
const [
const crm: {
readonly source: string;
readonly id: string;
}
crm
] =
import Obj
Obj
.
const getKeys: (entity: Obj.Unknown | Obj.Snapshot, source: string) => ForeignKey[] (+1 overload)
getKeys
(
const ada: Obj.OfShape<Person & OfKind<EntityKind.Object>>
ada
, 'crm.example.com');
const
const speaker: BaseObj & Tag.Tag & OfKind<EntityKind.Object>
speaker
=
const db: Database.Database
db
.
Database.add<BaseObj & Tag.Tag & OfKind<EntityKind.Object>>(obj: BaseObj & Tag.Tag & OfKind<EntityKind.Object>, opts?: Database.AddOptions): BaseObj & Tag.Tag & 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 Tag
Tag
.
const make: (props: Obj.MakeProps<typeof Tag.Tag>) => Obj.OfShape<Tag.Tag & import("@dxos/echo/Entity").OfKind<EntityKind.Object>>

Creates a new tag object.

@performance ― O(1); allocates an in-memory tag object.

make
({
label: string
label
: 'speaker' }));
import Obj
Obj
.
const update: <Obj.OfShape<Person & OfKind<EntityKind.Object>>>(obj: Obj.OfShape<Person & OfKind<EntityKind.Object>>, callback: ChangeCallback<Obj.OfShape<Person & OfKind<EntityKind.Object>>>) => Obj.OfShape<Person & 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 ada: Obj.OfShape<Person & OfKind<EntityKind.Object>>
ada
, (
ada: {
id: EntityId;
"~@dxos/echo/Kind": EntityKind.Object;
name: string;
email?: string | undefined;
employer?: Ref<Organization & OfKind<EntityKind.Object> & Obj.Unknown> | undefined;
}
ada
) => {
import Obj
Obj
.
const addTag: (entity: Obj.Mutable<Obj.Unknown>, tag: Tag.Tag) => void

Add a tag to the object. Must be called within an Obj.update callback.

NOTE: TypeScript's structural typing allows readonly objects to be passed to Mutable<T> parameters, so there is no compile-time error. Enforcement is runtime-only.

@performance ― O(t) in the tag count; dedupes by tag id before appending.

addTag
(
ada: {
id: EntityId;
"~@dxos/echo/Kind": EntityKind.Object;
name: string;
email?: string | undefined;
employer?: Ref<Organization & OfKind<EntityKind.Object> & Obj.Unknown> | undefined;
}
ada
,
const speaker: BaseObj & Tag.Tag & OfKind<EntityKind.Object>
speaker
);
});

A child object can name a parent with Obj.setParent. The parent must hold a reference to the child, and deleting the parent deletes the child.

Each change to an object is a Change with the device that made it, the time, and the heads of the document after it. Obj.getChanges lists them, for the whole object or one field, where each carries the value before and after. Obj.getVersion reads the object at a change’s heads. Changes and branches explains the model.

import {
import Obj
Obj
} from '@dxos/echo';
const
const changes: ValueChange<string>[]
changes
=
import Obj
Obj
.
function getChanges<Obj.OfShape<Person & OfKind<EntityKind.Object>>, "name">(obj: Obj.OfShape<Person & OfKind<EntityKind.Object>>, opts: Obj.GetChangesOptions<"name"> & {
property: "name";
}): ValueChange<string>[] (+1 overload)

The object's edit history, oldest first: one

Change.Change

per document change that touched the object, carrying its snapshot before and after. Given property, only the changes that touched that property, carrying its value. Pass an entry's heads to

getVersion

to read the whole object as that change left it.

Changes belong to the object's Automerge document, so time, actor and ops describe the whole change, which may also have written other objects in the same document. Edits to the content of a referenced object (e.g. a Ref<Text>) are that object's history, not this one's.

@example

for (const { time, before, after } of Obj.getChanges(task, { property: 'status' })) {
console.log(new Date(time), before, '→', after);
}

@performance ― O(document history): diffs every change of the Automerge document and snapshots each match; not cached.

getChanges
(
const ada: Obj.OfShape<Person & OfKind<EntityKind.Object>>
ada
, {
property: "name"

Narrow the history to the changes that touched this property; before/after then hold its value.

property
: 'name' });
const [
const last: ValueChange<string>
last
] =
const changes: ValueChange<string>[]
changes
.
Array<ValueChange<string>>.slice(start?: number, end?: number): ValueChange<string>[]

Returns a copy of a section of an array. For both start and end, a negative index can be used to indicate an offset from the end of the array. For example, -2 refers to the second to last element of the array.

@param ― start The beginning index of the specified portion of the array. If start is undefined, then the slice begins at index 0.

@param ― end The end index of the specified portion of the array. This is exclusive of the element at the index 'end'. If end is undefined, then the slice extends to the end of the array.

slice
(-1);
const last: ValueChange<string>
last
.
actor: string

Writer that made the change; for a document change, an Automerge actor, which is a device, not a member.

actor
; // the device that made the change
const last: ValueChange<string>
last
.
ValueChange<string>.before: string | undefined

The value before the change; undefined where the object (or property) did not exist yet.

before
; // 'Ada'
const last: ValueChange<string>
last
.
ValueChange<string>.after: string | undefined

The value after the change; undefined where the change removed the property.

after
; // 'Ada Lovelace'
// The object as it was after a change, without touching the live object.
const
const then: Obj.Snapshot<Obj.OfShape<Person & OfKind<EntityKind.Object>>>
then
=
import Obj
Obj
.
const getVersion: <Obj.OfShape<Person & OfKind<EntityKind.Object>>>(obj: Obj.OfShape<Person & OfKind<EntityKind.Object>>, heads: readonly string[]) => Obj.Snapshot<Obj.OfShape<Person & OfKind<EntityKind.Object>>>

Get an immutable snapshot of the object at the given historical heads — a detached instance, not a pin on the live object. Only the surface that asks for it sees the historical value; the live object and every other surface are unaffected. The functional alternative to a read-time-travel pin.

@performance ― O(object size) plus the Automerge cost of viewing the document at heads; nothing is cached.

getVersion
(
const ada: Obj.OfShape<Person & OfKind<EntityKind.Object>>
ada
,
const changes: ValueChange<string>[]
changes
[0].
ValueChange<string>.heads: readonly string[]

The document's frontier once the change applied; Obj.getVersion(obj, heads) reads the object as it left it.

heads
);

Obj.clone makes a structural copy. Obj.toJSON and Obj.fromJSON round-trip an object’s id, type, meta and references.

org.dxos.echo#F-3.4
import {
import Obj
Obj
} from '@dxos/echo';
const
const copy: Obj.OfShape<Person & OfKind<EntityKind.Object>>
copy
=
import Obj
Obj
.
const clone: <Obj.OfShape<Person & OfKind<EntityKind.Object>>>(obj: Obj.OfShape<Person & OfKind<EntityKind.Object>>, opts?: Obj.CloneOptions) => Obj.OfShape<Person & OfKind<EntityKind.Object>>

Clones an object or relation. This does not clone referenced objects, only the properties in the object.

@returns ― A new object with the same schema and properties.

@performance ― O(n) in object size; with deep, also clones each qualifying ref target that resolves synchronously (O(reachable subgraph)).

clone
(
const ada: Obj.OfShape<Person & OfKind<EntityKind.Object>>
ada
);
const
const json: ObjectJSON
json
=
import Obj
Obj
.
const toJSON: (entity: Obj.Unknown | Obj.Snapshot) => Obj.JSON

Converts object to its JSON representation. Accepts both reactive objects and snapshots.

JSON.stringify(obj) gives the same result for an in-memory object, which shares this serializer. A database-backed object carries its own toJSON, which reads the document rather than the target and still differs in two ways: it omits @uri, and it leaves Uint8Array values unencoded. Prefer this function where the two must agree.

@performance ― O(n) in entity size; serializes the whole entity on every call.

toJSON
(
const ada: Obj.OfShape<Person & OfKind<EntityKind.Object>>
ada
);
Member Summary
make Create an object of a type
update Write to an object; one notification per call
getSnapshot A frozen copy of the current state
subscribe Run a callback after each change
getMeta getKeys deleteKeys Read and change metadata and foreign keys
addTag removeTag Tag an object
getLabel setLabel The label lists and pickers show
getParent setParent Parent and child ownership
getChanges getVersion getBranch History, past versions and the current branch
instanceOf isObject isDeleted Predicates
clone toJSON fromJSON Copy and serialize

Functions shared by objects and relations, such as getURI and getTypename, are also in the Entity module. The full list is in the API reference.