Skip to content

Relation

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

A relation is an object with two typed endpoints, a source and a target. Use one when the link has data of its own, such as a role or a date, or when you need to query it from either end. A plain Ref field is enough when one object just points at another. Relation mirrors Obj for everything that is not about endpoints.

Declare the type with Type.makeRelation, which fixes the endpoint types org.dxos.echo#F-2.2. Then pass the endpoints under the reserved Relation.Source and Relation.Target keys.

import {
import Obj
Obj
,
import Relation
Relation
} from '@dxos/echo';
import {
class Organization
Organization
,
class Person
Person
} from './schema.ts';
import {
class WorksAt
WorksAt
} from './works-at.ts';
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 acme: BaseObj & Organization & OfKind<EntityKind.Object>
acme
=
const db: Database.Database
db
.
Database.add<BaseObj & Organization & OfKind<EntityKind.Object>>(obj: BaseObj & Organization & OfKind<EntityKind.Object>, opts?: Database.AddOptions): BaseObj & Organization & 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 Organization>(type: typeof Organization, props: NoInfer<Obj.MakeProps<typeof Organization>>, options?: Obj.MakeOptions): Obj.OfShape<Organization & 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 Organization
Organization
, {
name: string
name
: 'Acme' }));
const
const job: Relation.Endpoints<Person & OfKind<EntityKind.Object>, Organization & OfKind<EntityKind.Object>> & WorksAt & OfKind<EntityKind.Relation> & OfKind<EntityKind.Relation>
job
=
const db: Database.Database
db
.
Database.add<Relation.Endpoints<Person & OfKind<EntityKind.Object>, Organization & OfKind<EntityKind.Object>> & WorksAt & OfKind<EntityKind.Relation> & OfKind<EntityKind.Relation>>(obj: Relation.Endpoints<Person & OfKind<EntityKind.Object>, Organization & OfKind<EntityKind.Object>> & WorksAt & OfKind<EntityKind.Relation> & OfKind<EntityKind.Relation>, opts?: Database.AddOptions): Relation.Endpoints<...> & ... 2 more ... & OfKind<...>

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 Relation
Relation
.
const make: <typeof WorksAt>(type: typeof WorksAt, props: NoInfer<Relation.MakeProps<typeof WorksAt>>) => Relation.Endpoints<Person & OfKind<EntityKind.Object>, Organization & OfKind<EntityKind.Object>> & WorksAt & OfKind<EntityKind.Relation> & OfKind<EntityKind.Relation>

Creates new relation.

@param ― schema - Relation schema.

@param ― props - Relation properties. Endpoints are passed as [Relation.Source] and [Relation.Target] keys.

@param ― meta - Relation metadata. (deprecated; use [Obj.Meta] instead)

@returns

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

make
(
class WorksAt
WorksAt
, {
[
import Relation
Relation
.
const Source: typeof Relation.Source
Source
]:
const ada: BaseObj & Person & OfKind<EntityKind.Object>
ada
,
[
import Relation
Relation
.
const Target: typeof Relation.Target
Target
]:
const acme: BaseObj & Organization & OfKind<EntityKind.Object>
acme
,
role: string
role
: 'Engineer',
}),
);

getSource and getTarget return the endpoint objects with their types. getSourceURI and getTargetURI return their URIs without resolving them.

import {
import Relation
Relation
} from '@dxos/echo';
import {
class Organization
Organization
,
class Person
Person
} from './schema.ts';
const
const employee: Person & OfKind<EntityKind.Object>
employee
=
import Relation
Relation
.
const getSource: <Relation.Endpoints<Person & OfKind<EntityKind.Object>, Organization & OfKind<EntityKind.Object>> & WorksAt & OfKind<EntityKind.Relation> & OfKind<EntityKind.Relation>>(relation: Relation.Endpoints<Person & OfKind<EntityKind.Object>, Organization & OfKind<EntityKind.Object>> & WorksAt & OfKind<EntityKind.Relation> & OfKind<EntityKind.Relation>) => Person & OfKind<...>

@returns ― Relation source. Accepts both reactive relations and snapshots.

@throws ― If the object is not a relation.

@performance ― O(1); reads the endpoint slot (a working-set lookup for database relations, never a load).

getSource
(
const job: Relation.Endpoints<Person & OfKind<EntityKind.Object>, Organization & OfKind<EntityKind.Object>> & WorksAt & OfKind<EntityKind.Relation> & OfKind<EntityKind.Relation>
job
); // Person
const
const employer: Organization & OfKind<EntityKind.Object>
employer
=
import Relation
Relation
.
const getTarget: <Relation.Endpoints<Person & OfKind<EntityKind.Object>, Organization & OfKind<EntityKind.Object>> & WorksAt & OfKind<EntityKind.Relation> & OfKind<EntityKind.Relation>>(relation: Relation.Endpoints<Person & OfKind<EntityKind.Object>, Organization & OfKind<EntityKind.Object>> & WorksAt & OfKind<EntityKind.Relation> & OfKind<EntityKind.Relation>) => Organization & OfKind<...>

@returns ― Relation target. Accepts both reactive relations and snapshots.

@throws ― If the object is not a relation.

@performance ― O(1); reads the endpoint slot (a working-set lookup for database relations, never a load).

getTarget
(
const job: Relation.Endpoints<Person & OfKind<EntityKind.Object>, Organization & OfKind<EntityKind.Object>> & WorksAt & OfKind<EntityKind.Relation> & OfKind<EntityKind.Relation>
job
); // Organization
const
const employerUri: EID
employerUri
=
import Relation
Relation
.
const getTargetURI: (value: Relation.Unknown | Relation.Snapshot) => EID

@returns ― Relation target URI. Accepts both reactive relations and snapshots.

@throws ― If the object is not a relation.

@performance ― O(1) read of the stored endpoint URI.

getTargetURI
(
const job: Relation.Endpoints<Person & OfKind<EntityKind.Object>, Organization & OfKind<EntityKind.Object>> & WorksAt & OfKind<EntityKind.Relation> & OfKind<EntityKind.Relation>
job
);

targetOf selects the relations that point at the current selection, and sourceOf the ones that start from it. Then source() or target() steps to the objects at the other end. Query covers traversal.

import {
import Filter
Filter
,
import Query
Query
} from '@dxos/echo';
import {
class WorksAt
WorksAt
} from './works-at.ts';
const
const jobsAtAcme: (Relation.Endpoints<Person & OfKind<EntityKind.Object>, Organization & OfKind<EntityKind.Object>> & WorksAt & OfKind<EntityKind.Relation>)[]
jobsAtAcme
= await
const db: Database.Database
db
.
Database.query: Database.QueryFn
<Query.Query<Relation.Endpoints<Person & OfKind<EntityKind.Object>, Organization & OfKind<EntityKind.Object>> & WorksAt & OfKind<EntityKind.Relation>>>(query: Query.Query<Relation.Endpoints<Person & OfKind<EntityKind.Object>, Organization & OfKind<EntityKind.Object>> & WorksAt & OfKind<EntityKind.Relation>>) => QueryResult<Relation.Endpoints<Person & OfKind<...>, Organization & OfKind<...>> & WorksAt & OfKind<EntityKind.Relation>> (+1 overload)
query
(
import Query
Query
.
const select: <Filter.Any>(filter: Filter.Any) => Query.Query<any>

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 id: (...ids: EntityId[]) => Filter.Any

Filter by EntityId.

@performance ― O(k) in the number of ids (validated); builds the AST without matching anything.

id
(
const acme: BaseObj & Organization & OfKind<EntityKind.Object>
acme
.
id: string
id
)).
Query<any>.targetOf<typeof WorksAt>(relation?: typeof WorksAt | URI | undefined, predicates?: Filter.Props<Relation.Endpoints<Person & OfKind<EntityKind.Object>, Organization & OfKind<EntityKind.Object>> & WorksAt & OfKind<EntityKind.Relation>> | undefined): Query.Query<Relation.Endpoints<Person & OfKind<EntityKind.Object>, Organization & OfKind<EntityKind.Object>> & WorksAt & OfKind<EntityKind.Relation>>

Find relations where this object is the target.

@returns ― Query for the relation objects.

@param ― relation - Type entity of the relation.

@param ― predicates - Predicates to filter the relation objects.

@performance ― O(1) plus O(p) for predicates; builds the AST without executing anything.

targetOf
(
class WorksAt
WorksAt
))
.
QueryResult<Endpoints<Person & OfKind<EntityKind.Object>, Organization & OfKind<EntityKind.Object>> & WorksAt & OfKind<...>>.run(opts?: RunOptions): Promise<(Relation.Endpoints<Person & OfKind<EntityKind.Object>, Organization & OfKind<EntityKind.Object>> & WorksAt & OfKind<EntityKind.Relation>)[]>

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
();
const
const staff: (Person & OfKind<EntityKind.Object>)[]
staff
= await
const db: Database.Database
db
.
Database.query: Database.QueryFn
<Query.Query<Person & OfKind<EntityKind.Object>>>(query: Query.Query<Person & OfKind<EntityKind.Object>>) => QueryResult<Person & OfKind<EntityKind.Object>> (+1 overload)
query
(
import Query
Query
.
const select: <Filter.Any>(filter: Filter.Any) => Query.Query<any>

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 id: (...ids: EntityId[]) => Filter.Any

Filter by EntityId.

@performance ― O(k) in the number of ids (validated); builds the AST without matching anything.

id
(
const acme: BaseObj & Organization & OfKind<EntityKind.Object>
acme
.
id: string
id
)).
Query<any>.targetOf<typeof WorksAt>(relation?: typeof WorksAt | URI | undefined, predicates?: Filter.Props<Relation.Endpoints<Person & OfKind<EntityKind.Object>, Organization & OfKind<EntityKind.Object>> & WorksAt & OfKind<EntityKind.Relation>> | undefined): Query.Query<Relation.Endpoints<Person & OfKind<EntityKind.Object>, Organization & OfKind<EntityKind.Object>> & WorksAt & OfKind<EntityKind.Relation>>

Find relations where this object is the target.

@returns ― Query for the relation objects.

@param ― relation - Type entity of the relation.

@param ― predicates - Predicates to filter the relation objects.

@performance ― O(1) plus O(p) for predicates; builds the AST without executing anything.

targetOf
(
class WorksAt
WorksAt
).
Query<Endpoints<Person & OfKind<EntityKind.Object>, Organization & OfKind<EntityKind.Object>> & WorksAt & OfKind<...>>.source(): Query.Query<Person & OfKind<EntityKind.Object>>

For a query for relations, get the source objects.

@returns ― Query for the source objects.

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

source
())
.
QueryResult<Person & OfKind<EntityKind.Object>>.run(opts?: RunOptions): Promise<(Person & 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
();
import {
import Relation
Relation
} from '@dxos/echo';
import Relation
Relation
.
const update: <Relation.Endpoints<Person & OfKind<EntityKind.Object>, Organization & OfKind<EntityKind.Object>> & WorksAt & OfKind<EntityKind.Relation> & OfKind<EntityKind.Relation>>(relation: Relation.Endpoints<Person & OfKind<EntityKind.Object>, Organization & OfKind<EntityKind.Object>> & WorksAt & OfKind<EntityKind.Relation> & OfKind<EntityKind.Relation>, callback: ChangeCallback<...>) => void

Perform mutations on an echo relation within a controlled context.

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

@param ― relation - The echo relation to mutate. Use Obj.update for objects.

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

@example

const worksFor = Relation.make(EmployedBy, {
[Relation.Source]: person,
[Relation.Target]: company,
role: 'Engineer',
});
// Mutate within Relation.update
Relation.update(worksFor, (obj) => {
obj.role = 'Senior Engineer';
});

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

@performance ― Synchronous; costs the mutations made in the callback plus one batched notification.

update
(
const job: Relation.Endpoints<Person & OfKind<EntityKind.Object>, Organization & OfKind<EntityKind.Object>> & WorksAt & OfKind<EntityKind.Relation> & OfKind<EntityKind.Relation>
job
, (
job: {
[Source]: {
name: string;
email?: string | undefined;
employer?: Ref<Organization & OfKind<EntityKind.Object> & Obj.Unknown> | undefined;
id: EntityId;
"~@dxos/echo/Kind": EntityKind.Object;
};
[Target]: {
name: string;
website?: string | undefined;
id: EntityId;
"~@dxos/echo/Kind": EntityKind.Object;
};
role: string;
id: EntityId;
"~@dxos/echo/Kind": EntityKind.Relation;
}
job
) => {
job: {
[Source]: {
name: string;
email?: string | undefined;
employer?: Ref<Organization & OfKind<EntityKind.Object> & Obj.Unknown> | undefined;
id: EntityId;
"~@dxos/echo/Kind": EntityKind.Object;
};
[Target]: {
name: string;
website?: string | undefined;
id: EntityId;
"~@dxos/echo/Kind": EntityKind.Object;
};
role: string;
id: EntityId;
"~@dxos/echo/Kind": EntityKind.Relation;
}
job
.
role: string
role
= 'Staff engineer';
});
const
const unsubscribe: () => void
unsubscribe
=
import Relation
Relation
.
const subscribe: (rel: Relation.Unknown, callback: () => void) => (() => void)

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

@returns ― Unsubscribe function.

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

subscribe
(
const job: Relation.Endpoints<Person & OfKind<EntityKind.Object>, Organization & OfKind<EntityKind.Object>> & WorksAt & OfKind<EntityKind.Relation> & OfKind<EntityKind.Relation>
job
, () =>
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
(
const job: Relation.Endpoints<Person & OfKind<EntityKind.Object>, Organization & OfKind<EntityKind.Object>> & WorksAt & OfKind<EntityKind.Relation> & OfKind<EntityKind.Relation>
job
.
role: string
role
));
const unsubscribe: () => void
unsubscribe
();
const
const sorted: (Relation.Endpoints<Person & OfKind<EntityKind.Object>, Organization & OfKind<EntityKind.Object>> & WorksAt & OfKind<EntityKind.Relation>)[]
sorted
=
const jobsAtAcme: (Relation.Endpoints<Person & OfKind<EntityKind.Object>, Organization & OfKind<EntityKind.Object>> & WorksAt & OfKind<EntityKind.Relation>)[]
jobsAtAcme
.
Array<Endpoints<Person & OfKind<EntityKind.Object>, Organization & OfKind<EntityKind.Object>> & WorksAt & OfKind<...>>.sort(compareFn?: ((a: Relation.Endpoints<Person & OfKind<EntityKind.Object>, Organization & OfKind<EntityKind.Object>> & WorksAt & OfKind<EntityKind.Relation>, b: Relation.Endpoints<Person & OfKind<EntityKind.Object>, Organization & OfKind<EntityKind.Object>> & WorksAt & OfKind<EntityKind.Relation>) => number) | undefined): (Relation.Endpoints<Person & OfKind<EntityKind.Object>, Organization & OfKind<...>> & WorksAt & OfKind<EntityKind.Relation>)[]

Sorts an array in place. This method mutates the array and returns a reference to the same array.

@param ―

compareFn Function used to determine the order of the elements. It is expected to return a negative value if the first argument is less than the second argument, zero if they're equal, and a positive value otherwise. If omitted, the elements are sorted in ascending, UTF-16 code unit order.

[11,2,22,1].sort((a, b) => a - b)

sort
(
import Relation
Relation
.
const sort: (...comparators: Relation.Comparator[]) => Relation.Comparator

Compose comparators, applying each in order until one returns non-zero.

@performance ― O(c) per comparison in the number of composed comparators.

sort
(
import Relation
Relation
.
const sortByLabel: Relation.Comparator

Comparator that orders relations by label.

@performance ― O(label accessors) per comparison; labels are recomputed, not cached, so a sort costs O(n log n) label reads.

sortByLabel
,
import Relation
Relation
.
const sortByTypename: Relation.Comparator

Comparator that orders relations by typename.

@performance ― O(1) per comparison.

sortByTypename
),
);
Member Summary
make Create a relation between two objects
Source Target Reserved keys for the endpoints
getSource getTarget The endpoint objects
getSourceURI getTargetURI The endpoint URIs, unresolved
update subscribe getSnapshot Write, watch and copy, as in Obj
sort sortByLabel sortByTypename Comparators for Array.sort
instanceOf isRelation Predicates

The full list is in the API reference.