Skip to content

App Graph

import * as AppGraph from '@dxos/app-graph/AppGraph';
import * as AppGraphBuilder from '@dxos/app-graph/AppGraphBuilder';
import * as AppGraphNode from '@dxos/app-graph/AppGraphNode';
import * as AppNode from '@dxos/app-toolkit/AppNode';

App Graph is a graph of everything a person can open or do in a PLEX app. In Composer, the navigation tree, toolbars, context menus and the command palette all read it, so a plugin that adds to App Graph shows up in all of them.

App Graph comes from the App Toolkit, not the framework: it is how a workspace app such as Composer organizes what plugins add. A PLEX app can run without it.

A node has an id, a type, data and properties. Its data is usually the thing it stands for, such as a space or an ECHO object; its properties carry the label, icon and display hints. The graph starts from a root node.

Nodes connect by relations. child makes the navigation tree: a space’s children are its sections and objects. action connects a node to the things a person can do to it; an action node’s data is a function that returns an Effect. Other relations, such as companion, attach panels to a node.

A node’s identity is its path: the ids from the root down to it, joined like a URL. GraphPath from @dxos/app-toolkit builds the paths of the common nodes, such as a space or an object in it, and navigation operations take paths. In Composer the address bar shows the open node’s path.

Plugins add nodes through builder extensions. An extension has a matcher and a connector: the matcher picks the nodes it applies to, and the connector returns the nodes to connect to them. For the usual section that lists an object type, TypeSection.createTypeSectionExtension builds the extensions for you, with an address for each object. Stash uses it:

src/capabilities/app-graph-builder.ts
import * as
import AppNodeMatcher
AppNodeMatcher
from '@dxos/app-toolkit/AppNodeMatcher';
import * as
import GraphPath
GraphPath
from '@dxos/app-toolkit/GraphPath';
import * as
import TypeSection
TypeSection
from '@dxos/app-toolkit/TypeSection';
4 collapsed lines
import * as
import Operation
Operation
from '@dxos/compute/Operation';
import {
import Obj
Obj
,
import Ref
Ref
} from '@dxos/echo';
import {
const StashNamespace: {
key: "com.example.plugin.stash";
make: <T>() => <const N extends string>(name: N) => Capability.MultiTag<T, `com.example.plugin.stash.capability.${N}`>;
makeSingleton: <T>() => <const N extends string>(name: N) => Capability.Tag<T, `com.example.plugin.stash.capability.${N}`>;
}
StashNamespace
} from '../namespace.ts';
import {
class StashedLink
StashedLink
} from '../types/StashedLink.ts';
2 collapsed lines
import {
const MarkRead: Operation.Definition<{
readonly link: Ref<StashedLink & OfKind<EntityKind.Object> & Obj.Unknown>;
}, void, Service>
MarkRead
} from '../types/StashedLinkOperation.ts';
/** A "Stashed links" section in each space's Content group, once it has one. */
const
const section: Effect.Effect<Readonly<{
id: string;
position?: Position;
relation?: RelationInput | undefined;
meta?: AppGraphBuilder.UrlBinding | undefined;
connector?: Connector<Readonly<{
id: string;
type: string;
cacheable?: string[];
properties: Readonly<Record<string, any>>;
data: any;
}>, NodeArg<any, Record<string, any>>> | undefined;
}>[], never, never>

A "Stashed links" section in each space's Content group, once it has one.

section
=
import TypeSection
TypeSection
.
const createTypeSectionExtension: <typeof StashedLink>(type: typeof StashedLink, options: {
position?: Position;
match?: (node: Node) => Option<Space>;
groupSegment?: string;
createObject?: (space: Space) => Effect.Effect<any, any, any>;
urlKey: string;
sectionUrlKey?: string;
dropInto?: ((object: StashedLink & OfKind<EntityKind.Object>) => Container) | undefined;
sort?: ((a: StashedLink & OfKind<EntityKind.Object>, b: StashedLink & OfKind<EntityKind.Object>) => number) | undefined;
showEmpty?: boolean;
}) => Effect.Effect<...>
createTypeSectionExtension
(
class StashedLink
StashedLink
, {
match?: ((node: Node) => Option<Space>) | undefined

Override the default

AppNodeMatcher.whenSpace

match function. Use when the section should live under a group node rather than directly under a space. The match must still return Option<Space> so the connector can query the space db.

match
:
import AppNodeMatcher
AppNodeMatcher
.
const whenNavTreeGroup: (groupType: string) => (node: Node) => Option<Space>

Match navtree section-group nodes of a specific type and return the

Space

stored in node.properties.space. Group nodes carry the space in properties (not node.data) so child connectors can access it without an extra graph lookup.

@example

GraphBuilder.createExtension({
id: 'myAiSection',
match: AppNodeMatcher.whenNavTreeGroup(GraphPath.GroupTypes.ai),
connector: (space) => Effect.succeed([...]),
});

whenNavTreeGroup
(
import GraphPath
GraphPath
.
const GroupTypes: {
readonly ai: "org.dxos.navtree.group.ai";
readonly content: "org.dxos.navtree.group.content";
readonly communications: "org.dxos.navtree.group.communications";
readonly crm: "org.dxos.navtree.group.crm";
readonly system: "org.dxos.navtree.group.system";
readonly debug: "org.dxos.navtree.group.debug";
}

Navtree section-group node type values. Co-located with

GroupSegments

so plugins can match (via AppNodeMatcher.whenNavTreeGroup) and create group nodes without depending on plugin-navtree.

GroupTypes
.
content: "org.dxos.navtree.group.content"
content
),
groupSegment?: string | undefined

Group segment the section nests under (e.g. GraphPath.GroupSegments.ai), when match places it beneath a navtree group rather than directly under the space. Included in the forward-resolution urlPath so section objects at root/<space>/<groupSegment>/<typename>/<id> resolve deterministically. Omit for a space-direct section (root/<space>/<typename>/<id>).

groupSegment
:
import GraphPath
GraphPath
.
const GroupSegments: {
readonly ai: "ai";
readonly content: "content";
readonly communications: "communications";
readonly crm: "crm";
readonly system: "system";
readonly debug: "debug";
}

Navtree section-group node id values, which double as path segments: a section (collections, database, etc.) is nested under its group node in the graph, so its canonical path includes the group segment. Kept in app-toolkit so any plugin can reference them without depending on plugin-navtree.

GroupSegments
.
content: "content"
content
,
// The link's address in Composer's URL: `/w/<space>/stash/<id>`.
urlKey: string

Registered URL prefix key for this section's connector (e.g. doc, mail). Keys are global, so it is declared here rather than derived — see @dxos/app-graph's path-resolution.ts for how registered keys resolve and serialize URLs.

urlKey
: 'stash',
});

Stash’s App Graph module in the inspector shows the branches that lead to what it added:

  • App Graph expands lazily. The framework runs a node’s extensions only when something asks for that node’s connections, such as the person opening a branch in the tree. A space with ten thousand objects costs nothing until it is opened.
  • A connector reads reactive state through get. When that state changes, such as a query gaining a result, the connector reruns and the node’s connections update.
  • Nodes nothing is showing can be dropped again. A plugin that must keep a node loaded while it is off screen, such as the one a panel is pinned to, contributes it through AppCapabilities.AppGraphRetention.
  • Actions reach every menu from one place. An action node carries a disposition that says where it appears: toolbar for an object’s toolbar, list-item for its context menu in the tree. The command palette lists every action of the open node, so an action needs no command of its own.
src/capabilities/app-graph-builder.ts
import * as
import Effect
Effect
from 'effect/Effect';
2 collapsed lines
import * as
import Capability
Capability
from '@dxos/app-framework/Capability';
import * as
import AppGraphBuilder
AppGraphBuilder
from '@dxos/app-graph/AppGraphBuilder';
4 collapsed lines
import * as
import AppCapabilities
AppCapabilities
from '@dxos/app-toolkit/AppCapabilities';
import * as
import AppNodeMatcher
AppNodeMatcher
from '@dxos/app-toolkit/AppNodeMatcher';
import * as
import GraphPath
GraphPath
from '@dxos/app-toolkit/GraphPath';
import * as
import TypeSection
TypeSection
from '@dxos/app-toolkit/TypeSection';
import * as
import Operation
Operation
from '@dxos/compute/Operation';
import {
import Obj
Obj
,
import Ref
Ref
} from '@dxos/echo';
import {
const StashNamespace: {
key: "com.example.plugin.stash";
make: <T>() => <const N extends string>(name: N) => Capability.MultiTag<T, `com.example.plugin.stash.capability.${N}`>;
makeSingleton: <T>() => <const N extends string>(name: N) => Capability.Tag<T, `com.example.plugin.stash.capability.${N}`>;
}
StashNamespace
} from '../namespace.ts';
import {
class StashedLink
StashedLink
} from '../types/StashedLink.ts';
import {
const MarkRead: Operation.Definition<{
readonly link: Ref<StashedLink & OfKind<EntityKind.Object> & Obj.Unknown>;
}, void, Service>
MarkRead
} from '../types/StashedLinkOperation.ts';
9 collapsed lines
/** A "Stashed links" section in each space's Content group, once it has one. */
const
const section: Effect.Effect<Readonly<{
id: string;
position?: Position;
relation?: RelationInput | undefined;
meta?: AppGraphBuilder.UrlBinding | undefined;
connector?: Connector<Readonly<{
id: string;
type: string;
cacheable?: string[];
properties: Readonly<Record<string, any>>;
data: any;
}>, NodeArg<any, Record<string, any>>> | undefined;
}>[], never, never>

A "Stashed links" section in each space's Content group, once it has one.

section
=
import TypeSection
TypeSection
.
const createTypeSectionExtension: <typeof StashedLink>(type: typeof StashedLink, options: {
position?: Position;
match?: (node: Node) => Option<Space>;
groupSegment?: string;
createObject?: (space: Space) => Effect.Effect<any, any, any>;
urlKey: string;
sectionUrlKey?: string;
dropInto?: ((object: StashedLink & OfKind<EntityKind.Object>) => Container) | undefined;
sort?: ((a: StashedLink & OfKind<EntityKind.Object>, b: StashedLink & OfKind<EntityKind.Object>) => number) | undefined;
showEmpty?: boolean;
}) => Effect.Effect<...>
createTypeSectionExtension
(
class StashedLink
StashedLink
, {
match?: ((node: Node) => Option<Space>) | undefined

Override the default

AppNodeMatcher.whenSpace

match function. Use when the section should live under a group node rather than directly under a space. The match must still return Option<Space> so the connector can query the space db.

match
:
import AppNodeMatcher
AppNodeMatcher
.
const whenNavTreeGroup: (groupType: string) => (node: Node) => Option<Space>

Match navtree section-group nodes of a specific type and return the

Space

stored in node.properties.space. Group nodes carry the space in properties (not node.data) so child connectors can access it without an extra graph lookup.

@example

GraphBuilder.createExtension({
id: 'myAiSection',
match: AppNodeMatcher.whenNavTreeGroup(GraphPath.GroupTypes.ai),
connector: (space) => Effect.succeed([...]),
});

whenNavTreeGroup
(
import GraphPath
GraphPath
.
const GroupTypes: {
readonly ai: "org.dxos.navtree.group.ai";
readonly content: "org.dxos.navtree.group.content";
readonly communications: "org.dxos.navtree.group.communications";
readonly crm: "org.dxos.navtree.group.crm";
readonly system: "org.dxos.navtree.group.system";
readonly debug: "org.dxos.navtree.group.debug";
}

Navtree section-group node type values. Co-located with

GroupSegments

so plugins can match (via AppNodeMatcher.whenNavTreeGroup) and create group nodes without depending on plugin-navtree.

GroupTypes
.
content: "org.dxos.navtree.group.content"
content
),
groupSegment?: string | undefined

Group segment the section nests under (e.g. GraphPath.GroupSegments.ai), when match places it beneath a navtree group rather than directly under the space. Included in the forward-resolution urlPath so section objects at root/<space>/<groupSegment>/<typename>/<id> resolve deterministically. Omit for a space-direct section (root/<space>/<typename>/<id>).

groupSegment
:
import GraphPath
GraphPath
.
const GroupSegments: {
readonly ai: "ai";
readonly content: "content";
readonly communications: "communications";
readonly crm: "crm";
readonly system: "system";
readonly debug: "debug";
}

Navtree section-group node id values, which double as path segments: a section (collections, database, etc.) is nested under its group node in the graph, so its canonical path includes the group segment. Kept in app-toolkit so any plugin can reference them without depending on plugin-navtree.

GroupSegments
.
content: "content"
content
,
// The link's address in Composer's URL: `/w/<space>/stash/<id>`.
urlKey: string

Registered URL prefix key for this section's connector (e.g. doc, mail). Keys are global, so it is declared here rather than derived — see @dxos/app-graph's path-resolution.ts for how registered keys resolve and serialize URLs.

urlKey
: 'stash',
});
/** An action on every link, on its toolbar and its menu in the tree. */
const
const stashedLinkActions: Effect.Effect<Readonly<{
id: string;
position?: Position;
relation?: RelationInput | undefined;
meta?: AppGraphBuilder.UrlBinding | undefined;
connector?: Connector<Readonly<{
id: string;
type: string;
cacheable?: string[];
properties: Readonly<Record<string, any>>;
data: any;
}>, NodeArg<any, Record<string, any>>> | undefined;
}>[], never, never>

An action on every link, on its toolbar and its menu in the tree.

stashedLinkActions
=
import AppGraphBuilder
AppGraphBuilder
.
const createTypeExtension: <typeof StashedLink, never, "stashedLinkActions">(options: AppGraphBuilder.CreateTypeExtensionOptions<typeof StashedLink, never, "stashedLinkActions">) => Effect.Effect<Readonly<{
id: string;
position?: Position;
relation?: RelationInput | undefined;
meta?: AppGraphBuilder.UrlBinding | undefined;
connector?: Connector<Readonly<{
id: string;
type: string;
cacheable?: string[];
properties: Readonly<Record<string, any>>;
data: any;
}>, NodeArg<any, Record<string, any>>> | undefined;
}>[], never, never>

Create an extension that matches nodes by schema type. The entity type is inferred from the schema type and works for both object and relation schemas. Returns an Effect to allow callbacks to access services via dependency injection.

createTypeExtension
({
id: "stashedLinkActions"
id
: 'stashedLinkActions',
type: typeof StashedLink
type
:
class StashedLink
StashedLink
,
actions?: ((object: StashedLink & OfKind<EntityKind.Object>, get: AtomContext) => Effect.Effect<Omit<NodeArg<ActionData<any>, Record<string, any>>, "type">[], never, never>) | undefined
actions
: (
link: StashedLink & OfKind<EntityKind.Object>
link
) =>
import Effect
Effect
.
const succeed: <{
id: string;
data: () => Effect.Effect<void, Operation.NoHandlerError, Operation.Service>;
properties: {
label: (string | {
ns: "com.example.plugin.stash";
})[];
icon: string;
disposition: string[];
};
}[]>(value: {
id: string;
data: () => Effect.Effect<void, Operation.NoHandlerError, Operation.Service>;
properties: {
label: (string | {
ns: "com.example.plugin.stash";
})[];
icon: string;
disposition: string[];
};
}[]) => Effect.Effect<{
id: string;
data: () => Effect.Effect<void, Operation.NoHandlerError, Operation.Service>;
properties: {
label: (string | {
ns: "com.example.plugin.stash";
})[];
icon: string;
disposition: string[];
};
}[], never, never>

Creates an Effect that always succeeds with a given value.

When to use

Use when an effect should complete successfully with a specific value without any errors or external dependencies.

Example (Creating a successful effect)

import { Effect } from "effect"
// Creating an effect that represents a successful scenario
//
// ┌─── Effect<number, never, never>
// ▼
const success = Effect.succeed(42)
Effect.runSync(success) // => 42

@see ― fail to create an effect that represents a failure.

@stability ― stable

@category ― constructors

@since ― 2.0.0

succeed
([
{
id: string
id
: 'markRead',
data: () => Effect.Effect<void, Operation.NoHandlerError, Operation.Service>
data
: () =>
import Operation
Operation
.
const invoke: <{
readonly link: Ref<StashedLink & OfKind<EntityKind.Object> & Obj.Unknown>;
}, void>(op: Operation.Definition<{
readonly link: Ref<StashedLink & OfKind<EntityKind.Object> & Obj.Unknown>;
}, void, any>, input: {
readonly link: Ref<StashedLink & OfKind<EntityKind.Object> & Obj.Unknown>;
}, options?: Operation.InvokeOptions | undefined) => Effect.Effect<void, Operation.NoHandlerError, Operation.Service>

Invoke an operation as an Effect. Yields the Operation.Service internally.

@example

yield* Operation.invoke(MyOperation, { data: 'test' });

invoke
(
const MarkRead: Operation.Definition<{
readonly link: Ref<StashedLink & OfKind<EntityKind.Object> & Obj.Unknown>;
}, void, Service>
MarkRead
,
{
link: Ref<StashedLink & OfKind<EntityKind.Object> & Obj.Unknown>
link
:
import Ref
Ref
.
const make: <StashedLink & OfKind<EntityKind.Object>>(object: StashedLink & OfKind<EntityKind.Object>) => Ref<StashedLink & OfKind<EntityKind.Object>>

Create a reference to an entity.

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

make
(
link: StashedLink & OfKind<EntityKind.Object>
link
) },
{
InvokeOptions.spaceId?: SpaceId | undefined

Space ID to provide database context for the handler.

spaceId
:
import Obj
Obj
.
const getDatabase: (entity: Unknown | Snapshot) => Database | undefined

Get the database the object belongs to. Accepts both reactive objects and snapshots.

@idiom ― org.dxos.echo.objGetDatabase applies: Reaching an object's database — to query, add, or remove — when the surrounding Space is not otherwise needed instead-of: getSpace(obj)?.db (resolving the whole Space just to read its .db) uses: getDatabase

@performance ― O(1) slot read.

getDatabase
(
link: StashedLink & OfKind<EntityKind.Object>
link
)?.
Database.spaceId: SpaceId | undefined
spaceId
},
),
properties: {
label: (string | {
ns: "com.example.plugin.stash";
})[];
icon: string;
disposition: string[];
}
properties
: {
label: (string | {
ns: "com.example.plugin.stash";
})[]
label
: ['mark-read.label', {
ns: "com.example.plugin.stash"
ns
:
const StashNamespace: {
key: "com.example.plugin.stash";
make: <T>() => <const N extends string>(name: N) => Capability.MultiTag<T, `com.example.plugin.stash.capability.${N}`>;
makeSingleton: <T>() => <const N extends string>(name: N) => Capability.Tag<T, `com.example.plugin.stash.capability.${N}`>;
}
StashNamespace
.
key: "com.example.plugin.stash"
key
}],
icon: string
icon
: 'ph--check-circle--regular',
disposition: string[]
disposition
: ['toolbar', 'list-item'],
},
},
]),
});

AppGraphBuilder.createExtension takes an id, a matcher and a connector. The matcher returns Option.some(value) for the nodes it applies to; AppNodeMatcher has ready-made ones such as whenSpace. The connector returns the nodes to connect. Read reactive state with get so the connector reruns when it changes.

import * as
import Effect
Effect
from 'effect/Effect';
import * as
import AppGraphBuilder
AppGraphBuilder
from '@dxos/app-graph/AppGraphBuilder';
import * as
import AppNode
AppNode
from '@dxos/app-toolkit/AppNode';
import * as
import AppNodeMatcher
AppNodeMatcher
from '@dxos/app-toolkit/AppNodeMatcher';
import {
import Filter
Filter
} from '@dxos/echo';
// One node per note, as a child of each space node.
const
const notes: Effect.Effect<Readonly<{
id: string;
position?: Position;
relation?: AppGraphNode.RelationInput | undefined;
meta?: AppGraphBuilder.UrlBinding | undefined;
connector?: Connector<Readonly<{
id: string;
type: string;
cacheable?: string[];
properties: Readonly<Record<string, any>>;
data: any;
}>, AppGraphNode.NodeArg<any, Record<string, any>>> | undefined;
}>[], never, never>
notes
=
import AppGraphBuilder
AppGraphBuilder
.
const createExtension: <Space, never, "notes">(options: AppGraphBuilder.CreateExtensionOptions<Space, never, "notes">) => Effect.Effect<Readonly<{
id: string;
position?: Position;
relation?: AppGraphNode.RelationInput | undefined;
meta?: AppGraphBuilder.UrlBinding | undefined;
connector?: Connector<Readonly<{
id: string;
type: string;
cacheable?: string[];
properties: Readonly<Record<string, any>>;
data: any;
}>, AppGraphNode.NodeArg<any, Record<string, any>>> | undefined;
}>[], never, never>

Create a graph builder extension with simplified API. Returns an Effect to allow callbacks to access services via dependency injection.

createExtension
({
id: "notes"
id
: 'notes',
match: (node: AppGraphNode.Node, get: AtomContext) => Option<Space>
match
:
import AppNodeMatcher
AppNodeMatcher
.
const whenSpace: (node: AppGraphNode.Node) => Option<Space>

Match space nodes and return the

Space

payload — saves callers from unwrapping node.data themselves and adds an isSpace runtime guard.

@example

GraphBuilder.createExtension({
id: 'mySpaceSection',
match: AppNodeMatcher.whenSpace,
connector: (space) => Effect.succeed([...]),
});

whenSpace
,
connector?: ((matched: Space, get: AtomContext) => Effect.Effect<AppGraphNode.NodeArg<any, any>[], never, never>) | undefined
connector
: (
space: Space
space
,
get: AtomContext
get
) => {
const
const objects: (Note & OfKind<EntityKind.Object>)[]
objects
=
get: AtomContext
<(Note & OfKind<EntityKind.Object>)[]>(atom: Atom<(Note & OfKind<EntityKind.Object>)[]>) => (Note & OfKind<EntityKind.Object>)[]
get
(
space: Space
space
.
Space.db: EchoDatabase

Echo database.

db
.
Database.query: QueryFn
<Filter.Filter<Note & OfKind<EntityKind.Object>>>(filter: Filter.Filter<Note & OfKind<EntityKind.Object>>) => QueryResult<Note & OfKind<EntityKind.Object>> (+1 overload)
query
(
import Filter
Filter
.
const type: <typeof Note>(type: typeof Note, props?: Filter.Props<Note & OfKind<EntityKind.Object>> | undefined) => Filter.Filter<Note & OfKind<EntityKind.Object>> (+4 overloads)
type
(
class Note
Note
)).
QueryResult<Note & OfKind<EntityKind.Object>>.atom: Atom<(Note & OfKind<EntityKind.Object>)[]>

Self-updating atom. Updates automatically when query results change.

Memoized per QueryResult instance — repeated accesses on the same instance return the same Atom. Safe only when the QueryResult is itself held stable across re-renders (e.g. behind a useMemo). It must NOT be used in graph-builder connectors/actions or other atom computes, where db.query(...) is called fresh on each re-evaluation: every run constructs a new QueryResult and so a new atom + subscription, leaking the previous ones. Use the memoized

atom

family there instead.

@performance ― O(1) memoized per instance; each emission is a runSync.

atom
);
return
import Effect
Effect
.
const succeed: <{
id: string;
type: string;
cacheable: string[];
data: Obj.Unknown;
properties: {
role?: "branch" | undefined;
acceptPersistenceClass?: Set<string> | undefined;
acceptPersistenceKey?: Set<string> | undefined;
moveScope?: string | undefined;
isLink?: ((child: AppGraphNode.Node<Obj.Unknown>, from?: AppGraphNode.Node) => boolean) | undefined;
onMoveIn?: ((child: AppGraphNode.Node<Obj.Unknown>, index?: number) => void) | undefined;
onLink?: ((child: AppGraphNode.Node<Obj.Unknown>, index?: number) => void) | undefined;
dropInto?: Container | undefined;
label: string | [string, Record<string, any>];
icon: string;
iconHue: string | undefined;
disposition: string | string[] | undefined;
testId: string;
persistenceClass: string;
persistenceKey: import("@dxos/keys").SpaceId;
selectable: boolean;
draggable: boolean | undefined;
droppable: boolean | undefined;
blockInstruction: ((source: TreeData, instruction: AppNode.Instruction) => boolean) | undefined;
canDrop: typeof AppNode.CAN_DROP_OBJECT | undefined;
} | {
managesAutofocus?: boolean;
role?: "branch" | undefined;
acceptPersistenceClass?: Set<string> | undefined;
acceptPersistenceKey?: Set<string> | undefined;
moveScope?: string | undefined;
isLink?: ((child: AppGraphNode.Node<Obj.Unknown>, from?: AppGraphNode.Node) => boolean) | undefined;
onMoveIn?: ((child: AppGraphNode.Node<Obj.Unknown>, index?: number) => void) | undefined;
onLink?: ((child: AppGraphNode.Node<Obj.Unknown>, index?: number) => void) | undefined;
dropInto?: Container | undefined;
label: string | [string, Record<string, any>];
icon: string;
iconHue: string | undefined;
disposition: string | string[] | undefined;
testId: string;
persistenceClass: string;
persistenceKey: import("@dxos/keys").SpaceId;
selectable: boolean;
draggable: boolean | undefined;
droppable: boolean | undefined;
blockInstruction: ((source: TreeData, instruction: AppNode.Instruction) => boolean) | undefined;
canDrop: typeof AppNode.CAN_DROP_OBJECT | undefined;
} | {
role: "branch";
acceptPersistenceClass: Set<string>;
acceptPersistenceKey: Set<string>;
moveScope: string | undefined;
canDrop: (source: TreeData) => boolean;
isLink: (child: AppGraphNode.Node<Obj.Unknown>, from?: AppGraphNode.Node) => boolean;
onMoveIn: (child: AppGraphNode.Node<Obj.Unknown>, index?: number) => void;
onLink: (child: AppGraphNode.Node<Obj.Unknown>, index?: number) => void;
dropInto: Container;
onRearrange: (nextOrder: unknown[]) => void;
onMoveOut: (child: AppGraphNode.Node<Obj.Unknown>, destination: AppGraphNode.Node) => void;
listOf: Container;
label: string | [string, Record<string, any>];
icon: string;
iconHue: string | undefined;
disposition: string | string[] | undefined;
testId: string;
persistenceClass: string;
persistenceKey: import("@dxos/keys").SpaceId;
selectable: boolean;
draggable: boolean | undefined;
droppable: boolean | undefined;
blockInstruction: ((source: TreeData, instruction: AppNode.Instruction) => boolean) | undefined;
};
}[]>(value: {
id: string;
type: string;
cacheable: string[];
data: Obj.Unknown;
properties: {
role?: "branch" | undefined;
acceptPersistenceClass?: Set<string> | undefined;
acceptPersistenceKey?: Set<string> | undefined;
moveScope?: string | undefined;
isLink?: ((child: AppGraphNode.Node<Obj.Unknown>, from?: AppGraphNode.Node) => boolean) | undefined;
onMoveIn?: ((child: AppGraphNode.Node<Obj.Unknown>, index?: number) => void) | undefined;
onLink?: ((child: AppGraphNode.Node<Obj.Unknown>, index?: number) => void) | undefined;
dropInto?: Container | undefined;
label: string | [string, Record<string, any>];
icon: string;
iconHue: string | undefined;
disposition: string | string[] | undefined;
testId: string;
persistenceClass: string;
persistenceKey: import("@dxos/keys").SpaceId;
selectable: boolean;
draggable: boolean | undefined;
droppable: boolean | undefined;
blockInstruction: ((source: TreeData, instruction: AppNode.Instruction) => boolean) | undefined;
canDrop: typeof AppNode.CAN_DROP_OBJECT | undefined;
} | {
managesAutofocus?: boolean;
role?: "branch" | undefined;
acceptPersistenceClass?: Set<string> | undefined;
acceptPersistenceKey?: Set<string> | undefined;
moveScope?: string | undefined;
isLink?: ((child: AppGraphNode.Node<Obj.Unknown>, from ...

Creates an Effect that always succeeds with a given value.

When to use

Use when an effect should complete successfully with a specific value without any errors or external dependencies.

Example (Creating a successful effect)

import { Effect } from "effect"
// Creating an effect that represents a successful scenario
//
// ┌─── Effect<number, never, never>
// ▼
const success = Effect.succeed(42)
Effect.runSync(success) // => 42

@see ― fail to create an effect that represents a failure.

@stability ― stable

@category ― constructors

@since ― 2.0.0

succeed
(
const objects: (Note & OfKind<EntityKind.Object>)[]
objects
.
Array<Note & OfKind<EntityKind.Object>>.map<{
id: string;
type: string;
cacheable: string[];
data: Obj.Unknown;
properties: {
role?: "branch" | undefined;
acceptPersistenceClass?: Set<string> | undefined;
acceptPersistenceKey?: Set<string> | undefined;
moveScope?: string | undefined;
isLink?: ((child: AppGraphNode.Node<Obj.Unknown>, from?: AppGraphNode.Node) => boolean) | undefined;
onMoveIn?: ((child: AppGraphNode.Node<Obj.Unknown>, index?: number) => void) | undefined;
onLink?: ((child: AppGraphNode.Node<Obj.Unknown>, index?: number) => void) | undefined;
dropInto?: Container | undefined;
label: string | [string, Record<string, any>];
icon: string;
iconHue: string | undefined;
disposition: string | string[] | undefined;
testId: string;
persistenceClass: string;
persistenceKey: import("@dxos/keys").SpaceId;
selectable: boolean;
draggable: boolean | undefined;
droppable: boolean | undefined;
blockInstruction: ((source: TreeData, instruction: AppNode.Instruction) => boolean) | undefined;
canDrop: typeof AppNode.CAN_DROP_OBJECT | undefined;
} | {
managesAutofocus?: boolean;
role?: "branch" | undefined;
acceptPersistenceClass?: Set<string> | undefined;
acceptPersistenceKey?: Set<string> | undefined;
moveScope?: string | undefined;
isLink?: ((child: AppGraphNode.Node<Obj.Unknown>, from?: AppGraphNode.Node) => boolean) | undefined;
onMoveIn?: ((child: AppGraphNode.Node<Obj.Unknown>, index?: number) => void) | undefined;
onLink?: ((child: AppGraphNode.Node<Obj.Unknown>, index?: number) => void) | undefined;
dropInto?: Container | undefined;
label: string | [string, Record<string, any>];
icon: string;
iconHue: string | undefined;
disposition: string | string[] | undefined;
testId: string;
persistenceClass: string;
persistenceKey: import("@dxos/keys").SpaceId;
selectable: boolean;
draggable: boolean | undefined;
droppable: boolean | undefined;
blockInstruction: ((source: TreeData, instruction: AppNode.Instruction) => boolean) | undefined;
canDrop: typeof AppNode.CAN_DROP_OBJECT | undefined;
} | {
role: "branch";
acceptPersistenceClass: Set<string>;
acceptPersistenceKey: Set<string>;
moveScope: string | undefined;
canDrop: (source: TreeData) => boolean;
isLink: (child: AppGraphNode.Node<Obj.Unknown>, from?: AppGraphNode.Node) => boolean;
onMoveIn: (child: AppGraphNode.Node<Obj.Unknown>, index?: number) => void;
onLink: (child: AppGraphNode.Node<Obj.Unknown>, index?: number) => void;
dropInto: Container;
onRearrange: (nextOrder: unknown[]) => void;
onMoveOut: (child: AppGraphNode.Node<Obj.Unknown>, destination: AppGraphNode.Node) => void;
listOf: Container;
label: string | [string, Record<string, any>];
icon: string;
iconHue: string | undefined;
disposition: string | string[] | undefined;
testId: string;
persistenceClass: string;
persistenceKey: import("@dxos/keys").SpaceId;
selectable: boolean;
draggable: boolean | undefined;
droppable: boolean | undefined;
blockInstruction: ((source: TreeData, instruction: AppNode.Instruction) => boolean) | undefined;
};
} | null>(callbackfn: (value: Note & OfKind<...>, index: number, array: (Note & OfKind<...>)[]) => {
id: string;
type: string;
cacheable: string[];
data: Obj.Unknown;
properties: {
role?: "branch" | undefined;
acceptPersistenceClass?: Set<string> | undefined;
acceptPersistenceKey?: Set<string> | undefined;
moveScope?: string | undefined;
isLink?: ((child: AppGraphNode.Node<Obj.Unknown>, from?: AppGraphNode.Node) => boolean) | undefined;
onMoveIn?: ((child: AppGraphNode.Node<Obj.Unknown>, index?: number) => void) | undefined;
onLink?: ((child: AppGraphNode.Node<Obj.Unknown>, index?: number) => void) | undefined;
dropInto?: Container | undefined;
label: string | [string, Record<string, any>];
icon: string;
iconHue: string | undefined;
disposition: string | string[] | undefined;
testId: string;
persistenceClass: string;
persistenceKey: import("@dxos/keys").SpaceId;
selectable: boolean;
draggable: boolean | undefined;
droppable: boolean | undefined;
blockInstruction: ((source: TreeData, instruction: AppNode.Instruction) => boolean) | undefined;
canDrop: typeof AppNode.CAN_DROP_OBJECT | undefined;
} | {
managesAutofocus?: boolean;
role?: "branch" | undefined;
acceptPersistenceClass?: Set<string> | undefined;
acceptPersistenceKey?: Set<string> | undefined;
moveScope ...

Calls a defined callback function on each element of an array, and returns an array that contains the results.

@param ― callbackfn A function that accepts up to three arguments. The map method calls the callbackfn function one time for each element in the array.

@param ― thisArg An object to which the this keyword can refer in the callbackfn function. If thisArg is omitted, undefined is used as the this value.

map
((
object: Note & OfKind<EntityKind.Object>
object
) =>
import AppNode
AppNode
.
const makeObject: ({ get, db, object, disposition, draggable, droppable, navigable, dropInto, canDrop: canDropOverride, blockInstruction, }: {
get: AtomContext;
db: Database;
object: Obj.Unknown;
disposition?: string | string[];
draggable?: boolean;
droppable?: boolean;
navigable?: boolean;
dropInto?: Container;
canDrop?: (source: TreeData) => boolean;
blockInstruction?: (source: TreeData, instruction: AppNode.Instruction) => boolean;
}) => {
id: string;
type: string;
cacheable: string[];
data: Obj.Unknown;
properties: {
role?: "branch" | undefined;
acceptPersistenceClass?: Set<string> | undefined;
acceptPersistenceKey?: Set<string> | undefined;
moveScope?: string | undefined;
isLink?: ((child: AppGraphNode.Node<Obj.Unknown>, from?: AppGraphNode.Node) => boolean) | undefined;
onMoveIn?: ((child: AppGraphNode.Node<Obj.Unknown>, index?: number) => void) | undefined;
onLink?: ((child: AppGraphNode.Node<Obj.Unknown>, index?: number) => void) | undefined;
dropInto?: Container | undefined;
label: string | [string, Record<string, any>];
icon: string;
iconHue: string | undefined;
disposition: string | string[] | undefined;
testId: string;
persistenceClass: string;
persistenceKey: import("@dxos/keys").SpaceId;
selectable: boolean;
draggable: boolean | undefined;
droppable: boolean | undefined;
blockInstruction: ((source: TreeData, instruction: AppNode.Instruction) => boolean) | undefined;
canDrop: typeof AppNode.CAN_DROP_OBJECT | undefined;
} | {
managesAutofocus?: boolean;
role?: "branch" | undefined;
acceptPersistenceClass?: Set<string> | undefined;
acceptPersistenceKey?: Set<string> | undefined;
moveScope?: string | undefined;
isLink?: ((child: AppGraphNode.Node<Obj.Unknown>, from?: AppGraphNode.Node) => boolean) | undefined;
onMoveIn?: ((child: AppGraphNode.Node<Obj.Unknown>, index?: number) => void) | undefined;
onLink?: ((child: AppGraphNode.Node<Obj.Unknown>, index?: number) => void) | undefined;
dropInto?: Container | undefined;
label: string | [string, Record<string, any>];
icon: string;
iconHue: string | undefined;
disposition: string | string[] | undefined;
testId: string;
persistenceClass: string;
persistenceKey: import("@dxos/keys").SpaceId;
selectable: boolean;
draggable: boolean | undefined;
droppable: boolean | undefined;
blockInstruction: ((source: TreeData, instruction: AppNode.Instruction) => boolean) | undefined;
canDrop: typeof AppNode.CAN_DROP_OBJECT | undefined;
} | {
role: "branch";
acceptPersistenceClass: Set<string>;
acceptPersistenceKey: Set<string>;
moveScope: string | undefined;
canDrop: (source: TreeData) => boolean;
isLink: (child: AppGraphNode.Node<Obj.Unknown>, from?: AppGraphNode.Node) => boolean;
onMoveIn: (child: AppGraphNode.Node<Obj.Unknown>, index?: number) => void;
onLink: (child: AppGraphNode.Node<Obj.Unknown>, index?: number) => void;
dropInto: Container;
onRearrange: (nextOrder: unknown[]) => void;
onMoveOut: (child: AppGraphNode.Node<Obj.Unknown>, destination: AppGraphNode.Node) => void;
listOf: Container;
label: string | [string, Record<string, any>];
icon: string;
iconHue: string | undefined;
disposition: string | string[] | undefined;
testId: string;
persistenceClass: string;
persistenceKey: import("@dxos/keys").SpaceId;
selectable: boolean;
draggable: boolean | undefined;
droppable: boolean | undefined;
blockInstruction: ((source: TreeData, instruction: AppNode.Instruction) => boolean) | undefined;
};
} | null

Builds an app-graph node for an ECHO object. Uses the local object ID as the graph node ID.

makeObject
({
get: AtomContext

Atom context from the enclosing connector — registers reactive subscriptions so property changes re-run the connector.

get
,
db: Database
db
:
space: Space
space
.
Space.db: EchoDatabase

Echo database.

db
,
object: Obj.Unknown
object
}))
.
Array<{ id: string; type: string; cacheable: string[]; data: Obj.Unknown; properties: { role?: "branch" | undefined; acceptPersistenceClass?: Set<string> | undefined; acceptPersistenceKey?: Set<string> | undefined; moveScope?: string | undefined; isLink?: ((child: AppGraphNode.Node<Obj.Unknown>, from?: AppGraphNode.Node) => boolean) | undefined; onMoveIn?: ((child: AppGraphNode.Node<Obj.Unknown>, index?: number) => void) | undefined; onLink?: ((child: AppGraphNode.Node<Obj.Unknown>, index?: number) => void) | undefined; dropInto?: Container | undefined; label: string | [string, Record<string, any>]; icon: string; iconHue: string | undefined; disposition: string | string[] | undefined; testId: string; persistenceClass: string; persistenceKey: import("@dxos/keys").SpaceId; selectable: boolean; draggable: boolean | undefined; droppable: boolean | undefined; blockInstruction: ((source: TreeData, instruction: Instruction) => boolean) | undefined; canDrop: typeof CAN_DROP_OBJECT | undefined; } | { managesAutofocus?: boolean; role?: "branch" | undefined; acceptPersistenceClass?: Set<string> | undefined; acceptPersistenceKey?: Set<string> | undefined; moveScope?: string | undefined; isLink?: ((child: AppGraphNode.Node<Obj.Unknown>, from?: AppGraphNode.Node) => boolean) | undefined; onMoveIn?: ((child: AppGraphNode.Node<Obj.Unknown>, index?: number) => void) | undefined; onLink?: ((child: AppGraphNode.Node<Obj.Unknown>, index?: number) => void) | undefined; dropInto?: Container | undefined; label: string | [string, Record<string, any>]; icon: string; iconHue: string | undefined; disposition ...<{
id: string;
type: string;
cacheable: string[];
data: Obj.Unknown;
properties: {
role?: "branch" | undefined;
acceptPersistenceClass?: Set<string> | undefined;
acceptPersistenceKey?: Set<string> | undefined;
moveScope?: string | undefined;
isLink?: ((child: AppGraphNode.Node<Obj.Unknown>, from?: AppGraphNode.Node) => boolean) | undefined;
onMoveIn?: ((child: AppGraphNode.Node<Obj.Unknown>, index?: number) => void) | undefined;
onLink?: ((child: AppGraphNode.Node<Obj.Unknown>, index?: number) => void) | undefined;
dropInto?: Container | undefined;
label: string | [string, Record<string, any>];
icon: string;
iconHue: string | undefined;
disposition: string | string[] | undefined;
testId: string;
persistenceClass: string;
persistenceKey: import("@dxos/keys").SpaceId;
selectable: boolean;
draggable: boolean | undefined;
droppable: boolean | undefined;
blockInstruction: ((source: TreeData, instruction: AppNode.Instruction) => boolean) | undefined;
canDrop: typeof AppNode.CAN_DROP_OBJECT | undefined;
} | {
managesAutofocus?: boolean;
role?: "branch" | undefined;
acceptPersistenceClass?: Set<string> | undefined;
acceptPersistenceKey?: Set<string> | undefined;
moveScope?: string | undefined;
isLink?: ((child: AppGraphNode.Node<Obj.Unknown>, from?: AppGraphNode.Node) => boolean) | undefined;
onMoveIn?: ((child: AppGraphNode.Node<Obj.Unknown>, index?: number) => void) | undefined;
onLink?: ((child: AppGraphNode.Node<Obj.Unknown>, index?: number) => void) | undefined;
dropInto?: Container | undefined;
label: string | [string, Record<string, any>];
icon: string;
iconHue: string | undefined;
disposition: string | string[] | undefined;
testId: string;
persistenceClass: string;
persistenceKey: import("@dxos/keys").SpaceId;
selectable: boolean;
draggable: boolean | undefined;
droppable: boolean | undefined;
blockInstruction: ((source: TreeData, instruction: AppNode.Instruction) => boolean) | undefined;
canDrop: typeof AppNode.CAN_DROP_OBJECT | undefined;
} | {
role: "branch";
acceptPersistenceClass: Set<string>;
acceptPersistenceKey: Set<string>;
moveScope: string | undefined;
canDrop: (source: TreeData) => boolean;
isLink: (child: AppGraphNode.Node<Obj.Unknown>, from?: AppGraphNode.Node) => boolean;
onMoveIn: (child: AppGraphNode.Node<Obj.Unknown>, index?: number) => void;
onLink: (child: AppGraphNode.Node<Obj.Unknown>, index?: number) => void;
dropInto: Container;
onRearrange: (nextOrder: unknown[]) => void;
onMoveOut: (child: AppGraphNode.Node<Obj.Unknown>, destination: AppGraphNode.Node) => void;
listOf: Container;
label: string | [string, Record<string, any>];
icon: string;
iconHue: string | undefined;
disposition: string | string[] | undefined;
testId: string;
persistenceClass: string;
persistenceKey: import("@dxos/keys").SpaceId;
selectable: boolean;
draggable: boolean | undefined;
droppable: boolean | undefined;
blockInstruction: ((source: TreeData, instruction: AppNode.Instruction) => boolean) | undefined;
};
}>(predicate: (value: {
id: string;
type: string;
cacheable: string[];
data: Obj.Unknown;
properties: {
role?: "branch" | undefined;
acceptPersistenceClass?: Set<string> | undefined;
acceptPersistenceKey?: Set<string> | undefined;
moveScope?: string | undefined;
isLink?: ((child: AppGraphNode.Node<Obj.Unknown>, from?: AppGraphNode.Node) => boolean) | undefined;
onMoveIn?: ((child: AppGraphNode.Node<Obj.Unknown>, index?: number) => void) | undefined;
onLink?: ((child: AppGraphNode.Node<Obj.Unknown>, index?: number) => void) | undefined;
dropInto?: Container | undefined;
label: string | [string, Record<string, any>];
icon: string;
iconHue: string | undefined;
disposition: string | string[] | undefined;
testId: string;
persistenceClass: string;
persistenceKey: import("@dxos/keys").SpaceId;
selectable: boolean;
draggable: boolean | undefined;
droppable: boolean | undefined;
blockInstruction: ((source: TreeData, instruction: AppNode.Instruction) => boolean) | undefined;
canDrop: typeof AppNode.CAN_DROP_OBJECT | undefined;
} | {
managesAutofocus?: boolean;
role?: "branch" | undefined;
acceptPersistenceClass?: Set<string> | undefined;
acceptPersistenceKey?: Set<string> | undefined;
moveScope?: string | undefined;
isLink?: ((child: AppGraphNode.Node<Obj.Unknown ... (+1 overload)

Returns the elements of an array that meet the condition specified in a callback function.

@param ― predicate A function that accepts up to three arguments. The filter method calls the predicate function one time for each element in the array.

@param ― thisArg An object to which the this keyword can refer in the predicate function. If thisArg is omitted, undefined is used as the this value.

filter
((
node: {
id: string;
type: string;
cacheable: string[];
data: Obj.Unknown;
properties: {
role?: "branch" | undefined;
acceptPersistenceClass?: Set<string> | undefined;
acceptPersistenceKey?: Set<string> | undefined;
moveScope?: string | undefined;
isLink?: ((child: AppGraphNode.Node<Obj.Unknown>, from?: AppGraphNode.Node) => boolean) | undefined;
onMoveIn?: ((child: AppGraphNode.Node<Obj.Unknown>, index?: number) => void) | undefined;
onLink?: ((child: AppGraphNode.Node<Obj.Unknown>, index?: number) => void) | undefined;
dropInto?: Container | undefined;
label: string | [string, Record<string, any>];
icon: string;
iconHue: string | undefined;
disposition: string | string[] | undefined;
testId: string;
persistenceClass: string;
persistenceKey: import("@dxos/keys").SpaceId;
selectable: boolean;
draggable: boolean | undefined;
droppable: boolean | undefined;
blockInstruction: ((source: TreeData, instruction: AppNode.Instruction) => boolean) | undefined;
canDrop: typeof AppNode.CAN_DROP_OBJECT | undefined;
} | {
managesAutofocus?: boolean;
role?: "branch" | undefined;
acceptPersistenceClass?: Set<string> | undefined;
acceptPersistenceKey?: Set<string> | undefined;
moveScope?: string | undefined;
isLink?: ((child: AppGraphNode.Node<Obj.Unknown>, from?: AppGraphNode.Node) => boolean) | undefined;
onMoveIn?: ((child: AppGraphNode.Node<Obj.Unknown>, index?: number) => void) | undefined;
onLink?: ((child: AppGraphNode.Node<Obj.Unknown>, index?: number) => void) | undefined;
dropInto?: Container | undefined;
label: string | [string, Record<string, any>];
icon: string;
iconHue: string | undefined;
disposition: string | string[] | undefined;
testId: string;
persistenceClass: string;
persistenceKey: import("@dxos/keys").SpaceId;
selectable: boolean;
draggable: boolean | undefined;
droppable: boolean | undefined;
blockInstruction: ((source: TreeData, instruction: AppNode.Instruction) => boolean) | undefined;
canDrop: typeof AppNode.CAN_DROP_OBJECT | undefined;
} | {
role: "branch";
acceptPersistenceClass: Set<string>;
acceptPersistenceKey: Set<string>;
moveScope: string | undefined;
canDrop: (source: TreeData) => boolean;
isLink: (child: AppGraphNode.Node<Obj.Unknown>, from?: AppGraphNode.Node) => boolean;
onMoveIn: (child: AppGraphNode.Node<Obj.Unknown>, index?: number) => void;
onLink: (child: AppGraphNode.Node<Obj.Unknown>, index?: number) => void;
dropInto: Container;
onRearrange: (nextOrder: unknown[]) => void;
onMoveOut: (child: AppGraphNode.Node<Obj.Unknown>, destination: AppGraphNode.Node) => void;
listOf: Container;
label: string | [string, Record<string, any>];
icon: string;
iconHue: string | undefined;
disposition: string | string[] | undefined;
testId: string;
persistenceClass: string;
persistenceKey: import("@dxos/keys").SpaceId;
selectable: boolean;
draggable: boolean | undefined;
droppable: boolean | undefined;
blockInstruction: ((source: TreeData, instruction: AppNode.Instruction) => boolean) | undefined;
};
} | null
node
):
node: {
id: string;
type: string;
cacheable: string[];
data: Obj.Unknown;
properties: {
role?: "branch" | undefined;
acceptPersistenceClass?: Set<string> | undefined;
acceptPersistenceKey?: Set<string> | undefined;
moveScope?: string | undefined;
isLink?: ((child: AppGraphNode.Node<Obj.Unknown>, from?: AppGraphNode.Node) => boolean) | undefined;
onMoveIn?: ((child: AppGraphNode.Node<Obj.Unknown>, index?: number) => void) | undefined;
onLink?: ((child: AppGraphNode.Node<Obj.Unknown>, index?: number) => void) | undefined;
dropInto?: Container | undefined;
label: string | [string, Record<string, any>];
icon: string;
iconHue: string | undefined;
disposition: string | string[] | undefined;
testId: string;
persistenceClass: string;
persistenceKey: import("@dxos/keys").SpaceId;
selectable: boolean;
draggable: boolean | undefined;
droppable: boolean | undefined;
blockInstruction: ((source: TreeData, instruction: AppNode.Instruction) => boolean) | undefined;
canDrop: typeof AppNode.CAN_DROP_OBJECT | undefined;
} | {
managesAutofocus?: boolean;
role?: "branch" | undefined;
acceptPersistenceClass?: Set<string> | undefined;
acceptPersistenceKey?: Set<string> | undefined;
moveScope?: string | undefined;
isLink?: ((child: AppGraphNode.Node<Obj.Unknown>, from?: AppGraphNode.Node) => boolean) | undefined;
onMoveIn?: ((child: AppGraphNode.Node<Obj.Unknown>, index?: number) => void) | undefined;
onLink?: ((child: AppGraphNode.Node<Obj.Unknown>, index?: number) => void) | undefined;
dropInto?: Container | undefined;
label: string | [string, Record<string, any>];
icon: string;
iconHue: string | undefined;
disposition: string | string[] | undefined;
testId: string;
persistenceClass: string;
persistenceKey: import("@dxos/keys").SpaceId;
selectable: boolean;
draggable: boolean | undefined;
droppable: boolean | undefined;
blockInstruction: ((source: TreeData, instruction: AppNode.Instruction) => boolean) | undefined;
canDrop: typeof AppNode.CAN_DROP_OBJECT | undefined;
} | {
role: "branch";
acceptPersistenceClass: Set<string>;
acceptPersistenceKey: Set<string>;
moveScope: string | undefined;
canDrop: (source: TreeData) => boolean;
isLink: (child: AppGraphNode.Node<Obj.Unknown>, from?: AppGraphNode.Node) => boolean;
onMoveIn: (child: AppGraphNode.Node<Obj.Unknown>, index?: number) => void;
onLink: (child: AppGraphNode.Node<Obj.Unknown>, index?: number) => void;
dropInto: Container;
onRearrange: (nextOrder: unknown[]) => void;
onMoveOut: (child: AppGraphNode.Node<Obj.Unknown>, destination: AppGraphNode.Node) => void;
listOf: Container;
label: string | [string, Record<string, any>];
icon: string;
iconHue: string | undefined;
disposition: string | string[] | undefined;
testId: string;
persistenceClass: string;
persistenceKey: import("@dxos/keys").SpaceId;
selectable: boolean;
draggable: boolean | undefined;
droppable: boolean | undefined;
blockInstruction: ((source: TreeData, instruction: AppNode.Instruction) => boolean) | undefined;
};
} | null
node
is
type NonNullable<T> = T & {}

Exclude null and undefined from T

NonNullable
<typeof
node: {
id: string;
type: string;
cacheable: string[];
data: Obj.Unknown;
properties: {
role?: "branch" | undefined;
acceptPersistenceClass?: Set<string> | undefined;
acceptPersistenceKey?: Set<string> | undefined;
moveScope?: string | undefined;
isLink?: ((child: AppGraphNode.Node<Obj.Unknown>, from?: AppGraphNode.Node) => boolean) | undefined;
onMoveIn?: ((child: AppGraphNode.Node<Obj.Unknown>, index?: number) => void) | undefined;
onLink?: ((child: AppGraphNode.Node<Obj.Unknown>, index?: number) => void) | undefined;
dropInto?: Container | undefined;
label: string | [string, Record<string, any>];
icon: string;
iconHue: string | undefined;
disposition: string | string[] | undefined;
testId: string;
persistenceClass: string;
persistenceKey: import("@dxos/keys").SpaceId;
selectable: boolean;
draggable: boolean | undefined;
droppable: boolean | undefined;
blockInstruction: ((source: TreeData, instruction: AppNode.Instruction) => boolean) | undefined;
canDrop: typeof AppNode.CAN_DROP_OBJECT | undefined;
} | {
managesAutofocus?: boolean;
role?: "branch" | undefined;
acceptPersistenceClass?: Set<string> | undefined;
acceptPersistenceKey?: Set<string> | undefined;
moveScope?: string | undefined;
isLink?: ((child: AppGraphNode.Node<Obj.Unknown>, from?: AppGraphNode.Node) => boolean) | undefined;
onMoveIn?: ((child: AppGraphNode.Node<Obj.Unknown>, index?: number) => void) | undefined;
onLink?: ((child: AppGraphNode.Node<Obj.Unknown>, index?: number) => void) | undefined;
dropInto?: Container | undefined;
label: string | [string, Record<string, any>];
icon: string;
iconHue: string | undefined;
disposition: string | string[] | undefined;
testId: string;
persistenceClass: string;
persistenceKey: import("@dxos/keys").SpaceId;
selectable: boolean;
draggable: boolean | undefined;
droppable: boolean | undefined;
blockInstruction: ((source: TreeData, instruction: AppNode.Instruction) => boolean) | undefined;
canDrop: typeof AppNode.CAN_DROP_OBJECT | undefined;
} | {
role: "branch";
acceptPersistenceClass: Set<string>;
acceptPersistenceKey: Set<string>;
moveScope: string | undefined;
canDrop: (source: TreeData) => boolean;
isLink: (child: AppGraphNode.Node<Obj.Unknown>, from?: AppGraphNode.Node) => boolean;
onMoveIn: (child: AppGraphNode.Node<Obj.Unknown>, index?: number) => void;
onLink: (child: AppGraphNode.Node<Obj.Unknown>, index?: number) => void;
dropInto: Container;
onRearrange: (nextOrder: unknown[]) => void;
onMoveOut: (child: AppGraphNode.Node<Obj.Unknown>, destination: AppGraphNode.Node) => void;
listOf: Container;
label: string | [string, Record<string, any>];
icon: string;
iconHue: string | undefined;
disposition: string | string[] | undefined;
testId: string;
persistenceClass: string;
persistenceKey: import("@dxos/keys").SpaceId;
selectable: boolean;
draggable: boolean | undefined;
droppable: boolean | undefined;
blockInstruction: ((source: TreeData, instruction: AppNode.Instruction) => boolean) | undefined;
};
} | null
node
> =>
node: {
id: string;
type: string;
cacheable: string[];
data: Obj.Unknown;
properties: {
role?: "branch" | undefined;
acceptPersistenceClass?: Set<string> | undefined;
acceptPersistenceKey?: Set<string> | undefined;
moveScope?: string | undefined;
isLink?: ((child: AppGraphNode.Node<Obj.Unknown>, from?: AppGraphNode.Node) => boolean) | undefined;
onMoveIn?: ((child: AppGraphNode.Node<Obj.Unknown>, index?: number) => void) | undefined;
onLink?: ((child: AppGraphNode.Node<Obj.Unknown>, index?: number) => void) | undefined;
dropInto?: Container | undefined;
label: string | [string, Record<string, any>];
icon: string;
iconHue: string | undefined;
disposition: string | string[] | undefined;
testId: string;
persistenceClass: string;
persistenceKey: import("@dxos/keys").SpaceId;
selectable: boolean;
draggable: boolean | undefined;
droppable: boolean | undefined;
blockInstruction: ((source: TreeData, instruction: AppNode.Instruction) => boolean) | undefined;
canDrop: typeof AppNode.CAN_DROP_OBJECT | undefined;
} | {
managesAutofocus?: boolean;
role?: "branch" | undefined;
acceptPersistenceClass?: Set<string> | undefined;
acceptPersistenceKey?: Set<string> | undefined;
moveScope?: string | undefined;
isLink?: ((child: AppGraphNode.Node<Obj.Unknown>, from?: AppGraphNode.Node) => boolean) | undefined;
onMoveIn?: ((child: AppGraphNode.Node<Obj.Unknown>, index?: number) => void) | undefined;
onLink?: ((child: AppGraphNode.Node<Obj.Unknown>, index?: number) => void) | undefined;
dropInto?: Container | undefined;
label: string | [string, Record<string, any>];
icon: string;
iconHue: string | undefined;
disposition: string | string[] | undefined;
testId: string;
persistenceClass: string;
persistenceKey: import("@dxos/keys").SpaceId;
selectable: boolean;
draggable: boolean | undefined;
droppable: boolean | undefined;
blockInstruction: ((source: TreeData, instruction: AppNode.Instruction) => boolean) | undefined;
canDrop: typeof AppNode.CAN_DROP_OBJECT | undefined;
} | {
role: "branch";
acceptPersistenceClass: Set<string>;
acceptPersistenceKey: Set<string>;
moveScope: string | undefined;
canDrop: (source: TreeData) => boolean;
isLink: (child: AppGraphNode.Node<Obj.Unknown>, from?: AppGraphNode.Node) => boolean;
onMoveIn: (child: AppGraphNode.Node<Obj.Unknown>, index?: number) => void;
onLink: (child: AppGraphNode.Node<Obj.Unknown>, index?: number) => void;
dropInto: Container;
onRearrange: (nextOrder: unknown[]) => void;
onMoveOut: (child: AppGraphNode.Node<Obj.Unknown>, destination: AppGraphNode.Node) => void;
listOf: Container;
label: string | [string, Record<string, any>];
icon: string;
iconHue: string | undefined;
disposition: string | string[] | undefined;
testId: string;
persistenceClass: string;
persistenceKey: import("@dxos/keys").SpaceId;
selectable: boolean;
draggable: boolean | undefined;
droppable: boolean | undefined;
blockInstruction: ((source: TreeData, instruction: AppNode.Instruction) => boolean) | undefined;
};
} | null
node
!== null),
);
},
});

AppGraphBuilder.createTypeExtension matches nodes whose data is an object of one type. Its actions callback returns action nodes. AppGraphNode.makeAction makes one; disposition decides where it shows. AppNode.makeToolbarAction is a shortcut for the object toolbar.

import * as
import Effect
Effect
from 'effect/Effect';
import * as
import AppGraphBuilder
AppGraphBuilder
from '@dxos/app-graph/AppGraphBuilder';
import * as
import AppGraphNode
AppGraphNode
from '@dxos/app-graph/AppGraphNode';
import {
import Obj
Obj
} from '@dxos/echo';
// An action on every node whose data is a Note.
const
const pin: Effect.Effect<Readonly<{
id: string;
position?: Position;
relation?: AppGraphNode.RelationInput | undefined;
meta?: AppGraphBuilder.UrlBinding | undefined;
connector?: Connector<Readonly<{
id: string;
type: string;
cacheable?: string[];
properties: Readonly<Record<string, any>>;
data: any;
}>, AppGraphNode.NodeArg<any, Record<string, any>>> | undefined;
}>[], never, never>
pin
=
import AppGraphBuilder
AppGraphBuilder
.
const createTypeExtension: <typeof Note, never, "pin">(options: AppGraphBuilder.CreateTypeExtensionOptions<typeof Note, never, "pin">) => Effect.Effect<Readonly<{
id: string;
position?: Position;
relation?: AppGraphNode.RelationInput | undefined;
meta?: AppGraphBuilder.UrlBinding | undefined;
connector?: Connector<Readonly<{
id: string;
type: string;
cacheable?: string[];
properties: Readonly<Record<string, any>>;
data: any;
}>, AppGraphNode.NodeArg<any, Record<string, any>>> | undefined;
}>[], never, never>

Create an extension that matches nodes by schema type. The entity type is inferred from the schema type and works for both object and relation schemas. Returns an Effect to allow callbacks to access services via dependency injection.

createTypeExtension
({
id: "pin"
id
: 'pin',
type: typeof Note
type
:
class Note
Note
,
actions?: ((object: Note & OfKind<EntityKind.Object>, get: AtomContext) => Effect.Effect<Omit<AppGraphNode.NodeArg<AppGraphNode.ActionData<any>, Record<string, any>>, "type">[], never, never>) | undefined
actions
: (
note: Note & OfKind<EntityKind.Object>
note
) =>
import Effect
Effect
.
const succeed: <AppGraphNode.NodeArg<AppGraphNode.ActionData<never>, Record<string, any>>[]>(value: AppGraphNode.NodeArg<AppGraphNode.ActionData<never>, Record<string, any>>[]) => Effect.Effect<AppGraphNode.NodeArg<AppGraphNode.ActionData<never>, Record<string, any>>[], never, never>

Creates an Effect that always succeeds with a given value.

When to use

Use when an effect should complete successfully with a specific value without any errors or external dependencies.

Example (Creating a successful effect)

import { Effect } from "effect"
// Creating an effect that represents a successful scenario
//
// ┌─── Effect<number, never, never>
// ▼
const success = Effect.succeed(42)
Effect.runSync(success) // => 42

@see ― fail to create an effect that represents a failure.

@stability ― stable

@category ― constructors

@since ― 2.0.0

succeed
([
import AppGraphNode
AppGraphNode
.
const makeAction: <never>(arg: Omit<AppGraphNode.NodeArg<AppGraphNode.ActionData<never>, Record<string, any>>, "type" | "nodes" | "edges">) => AppGraphNode.NodeArg<AppGraphNode.ActionData<never>, Record<string, any>>

Create an action node. Automatically sets type: ActionType.

makeAction
({
id: string

Globally unique ID.

id
: 'pin',
data?: AppGraphNode.ActionData<never> | undefined

Data the node represents.

data
: () =>
import Effect
Effect
.
const sync: <Note & OfKind<EntityKind.Object>>(thunk: LazyArg<Note & OfKind<EntityKind.Object>>) => Effect.Effect<Note & OfKind<EntityKind.Object>, never, never>

Creates an Effect that represents a synchronous side-effectful computation.

When to use

Use when you need to wrap a synchronous side-effectful operation that is not expected to throw.

Details

The provided function is evaluated lazily when the effect runs.

Gotchas

The function must not throw. If it throws, the thrown value is treated as a defect, not as a typed failure. Use try when throwing is expected.

Example (Capturing synchronous logging in an Effect)

import { Effect } from "effect"
const output: Array<unknown> = []
const log = (message: string) =>
Effect.sync(() => {
void output.push(message) // side effect
})
// ┌─── Effect<void, never, never>
// ▼
const program = log("Hello, World!")
Effect.runSync(program)
output // => ["Hello, World!"]

@see ― try_try for a version that can handle failures.

@stability ― stable

@category ― constructors

@since ― 2.0.0

sync
(() =>
import Obj
Obj
.
const update: <Note & OfKind<EntityKind.Object>>(obj: Note & OfKind<EntityKind.Object>, callback: ChangeCallback<Note & OfKind<EntityKind.Object>>) => Note & 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
(
note: Note & OfKind<EntityKind.Object>
note
, (
note: {
title?: string | undefined;
pinned?: boolean | undefined;
id: EntityId;
"~@dxos/echo/Kind": EntityKind.Object;
}
note
) => (
note: {
title?: string | undefined;
pinned?: boolean | undefined;
id: EntityId;
"~@dxos/echo/Kind": EntityKind.Object;
}
note
.
pinned?: boolean | undefined
pinned
= !
note: {
title?: string | undefined;
pinned?: boolean | undefined;
id: EntityId;
"~@dxos/echo/Kind": EntityKind.Object;
}
note
.
pinned?: boolean | undefined
pinned
)),
),
properties?: Readonly<Record<string, any>> | undefined

Properties of the node relevant to displaying the node.

properties
: {
label: string
label
: 'Pin',
icon: string
icon
: 'ph--push-pin--regular',
disposition: string
disposition
: 'list-item',
},
}),
]),
});

Both builders return Effects. Run them in the module body and contribute the result through AppCapabilities.AppGraphBuilder.

import * as
import Effect
Effect
from 'effect/Effect';
import * as
import Capability
Capability
from '@dxos/app-framework/Capability';
import * as
import AppCapabilities
AppCapabilities
from '@dxos/app-toolkit/AppCapabilities';
export default
import Capability
Capability
.
const makeModule: <void, Capability.Contribution<Capability.CapabilityIdentifier<"org.dxos.app-framework.capability.appGraphBuilder", "multi">>, never, never>(fn: (props: void) => Effect.Effect<Capability.Contribution<Capability.CapabilityIdentifier<"org.dxos.app-framework.capability.appGraphBuilder", "multi">>, never, Scope>) => (props: void) => Effect.Effect<Capability.Contribution<Capability.CapabilityIdentifier<"org.dxos.app-framework.capability.appGraphBuilder", "multi">>, never, Scope>

Helper to define a capability module with explicit typing. Wraps the default export function to provide better type inference and make the pattern explicit.

This helper provides explicit typing for the module activation function, making it clear that the function should:

  • Access declared requires via yield* (or the ambient Capability.Service/Plugin.Service)
  • Return an array of typed

Contribution

s (see

contribute

/

contributeAll

)

@example

// Module without options - single capability
export default Capability.makeModule(
Effect.fnUntraced(function* () {
const client = yield* ClientCapabilities.Client;
return Capability.contribute(Capabilities.SettingsStore, store);
})
);
// Module with multiple capabilities
export default Capability.makeModule(
Effect.fnUntraced(function* () {
return [
Capability.contribute(Capabilities.SettingsStore, store),
Capability.contribute(Capabilities.Translations, translations),
];
})
);
// Module with required options (context accessed via layer)
export default Capability.makeModule(
Effect.fnUntraced(function* (props: { observability: boolean }) {
const invoker = yield* Capabilities.OperationInvoker;
return Capability.contribute(Capabilities.OperationHandler, ...);
})
);
// Module with scoped resources (closed automatically on deactivation)
export default Capability.makeModule(
Effect.fnUntraced(function* () {
const scope = yield* Scope.Scope;
yield* Scope.addFinalizer(scope, Effect.sync(() => cleanup()));
return Capability.contribute(Capabilities.MyCapability, implementation);
})
);

makeModule
(
import Effect
Effect
.
const fnUntraced: <Effect.Effect<[Readonly<{
id: string;
position?: Position;
relation?: AppGraphNode.RelationInput | undefined;
meta?: AppGraphBuilder.UrlBinding | undefined;
connector?: Connector<Readonly<{
id: string;
type: string;
cacheable?: string[];
properties: Readonly<Record<string, any>>;
data: any;
}>, AppGraphNode.NodeArg<any, Record<string, any>>> | undefined;
}>[], Readonly<{
id: string;
position?: Position;
relation?: AppGraphNode.RelationInput | undefined;
meta?: AppGraphBuilder.UrlBinding | undefined;
connector?: Connector<Readonly<{
id: string;
type: string;
cacheable?: string[];
properties: Readonly<Record<string, any>>;
data: any;
}>, AppGraphNode.NodeArg<any, Record<string, any>>> | undefined;
}>[]], never, never>, Capability.Contribution<...>, []>(body: (this: unassigned) => Generator<...>) => () => Effect.Effect<...> (+41 overloads)
fnUntraced
(function* () {
const
const extensions: [Readonly<{
id: string;
position?: Position;
relation?: AppGraphNode.RelationInput | undefined;
meta?: AppGraphBuilder.UrlBinding | undefined;
connector?: Connector<Readonly<{
id: string;
type: string;
cacheable?: string[];
properties: Readonly<Record<string, any>>;
data: any;
}>, AppGraphNode.NodeArg<any, Record<string, any>>> | undefined;
}>[], Readonly<{
id: string;
position?: Position;
relation?: AppGraphNode.RelationInput | undefined;
meta?: AppGraphBuilder.UrlBinding | undefined;
connector?: Connector<Readonly<{
id: string;
type: string;
cacheable?: string[];
properties: Readonly<Record<string, any>>;
data: any;
}>, AppGraphNode.NodeArg<any, Record<string, any>>> | undefined;
}>[]]
extensions
= yield*
import Effect
Effect
.
const all: <readonly [Effect.Effect<Readonly<{
id: string;
position?: Position;
relation?: AppGraphNode.RelationInput | undefined;
meta?: AppGraphBuilder.UrlBinding | undefined;
connector?: Connector<Readonly<{
id: string;
type: string;
cacheable?: string[];
properties: Readonly<Record<string, any>>;
data: any;
}>, AppGraphNode.NodeArg<any, Record<string, any>>> | undefined;
}>[], never, never>, Effect.Effect<Readonly<{
id: string;
position?: Position;
relation?: AppGraphNode.RelationInput | undefined;
meta?: AppGraphBuilder.UrlBinding | undefined;
connector?: Connector<Readonly<{
id: string;
type: string;
cacheable?: string[];
properties: Readonly<Record<string, any>>;
data: any;
}>, AppGraphNode.NodeArg<any, Record<string, any>>> | undefined;
}>[], never, never>], {
...;
}>(arg: readonly [...], options?: {
...;
} | undefined) => Effect.Effect<...>

Combines an iterable or record of effects into one effect whose success shape follows the input.

When to use

Use to run a known collection of effects and collect results in the same tuple, iterable, or record shape.

Details

Tuple and iterable inputs collect results in order. Record inputs collect results under the same keys. By default, the combined effect fails on the first failure; with concurrent execution, effects that have already started may be interrupted, while effects not yet started are skipped.

Options:

Use concurrency to control sequential or concurrent execution. Use mode: "result" to run every effect and collect each success or failure as a Result in the same output shape. Use discard: true to ignore successful values and return void.

Example (Collecting tuple results in order)

import { Effect } from "effect"
const tupleOfEffects = [
Effect.succeed(42),
Effect.succeed("Hello")
] as const
// ┌─── Effect<[number, string], never, never>
// ▼
const resultsAsTuple = Effect.all(tupleOfEffects)
await Effect.runPromise(resultsAsTuple) // => [42, "Hello"]

Example (Collecting iterable results in order)

import { Effect } from "effect"
const iterableOfEffects: Iterable<Effect.Effect<number>> = [1, 2, 3].map(
Effect.succeed
)
// ┌─── Effect<number[], never, never>
// ▼
const resultsAsArray = Effect.all(iterableOfEffects)
await Effect.runPromise(resultsAsArray) // => [1, 2, 3]

Example (Collecting struct results by key)

import { Effect } from "effect"
const structOfEffects = {
a: Effect.succeed(42),
b: Effect.succeed("Hello")
}
// ┌─── Effect<{ a: number; b: string; }, never, never>
// ▼
const resultsAsStruct = Effect.all(structOfEffects)
await Effect.runPromise(resultsAsStruct) // => { a: 42, b: "Hello" }

Example (Collecting record results by key)

import { Effect } from "effect"
const recordOfEffects: Record<string, Effect.Effect<number>> = {
key1: Effect.succeed(1),
key2: Effect.succeed(2)
}
// ┌─── Effect<{ [x: string]: number; }, never, never>
// ▼
const resultsAsRecord = Effect.all(recordOfEffects)
await Effect.runPromise(resultsAsRecord) // => { key1: 1, key2: 2 }

Example (Stopping on the first failure)

import { Effect, Exit } from "effect"
const output: Array<unknown> = []
const record = (value: unknown) => Effect.sync(() => { output.push(value) })
const program = Effect.all([
Effect.succeed("Task1").pipe(Effect.tap(record)),
Effect.fail("Task2: Oh no!").pipe(Effect.tap(record)),
// Won't execute due to earlier failure
Effect.succeed("Task3").pipe(Effect.tap(record))
])
const outcome = await Effect.runPromiseExit(program)
const observation = [output, outcome] // => [["Task1"], Exit.fail("Task2: Oh no!")]

@see ― forEach for iterating over elements and applying an effect.

@stability ― stable

@category ― collecting

@since ― 2.0.0

all
([
const notes: Effect.Effect<Readonly<{
id: string;
position?: Position;
relation?: AppGraphNode.RelationInput | undefined;
meta?: AppGraphBuilder.UrlBinding | undefined;
connector?: Connector<Readonly<{
id: string;
type: string;
cacheable?: string[];
properties: Readonly<Record<string, any>>;
data: any;
}>, AppGraphNode.NodeArg<any, Record<string, any>>> | undefined;
}>[], never, never>
notes
,
const pin: Effect.Effect<Readonly<{
id: string;
position?: Position;
relation?: AppGraphNode.RelationInput | undefined;
meta?: AppGraphBuilder.UrlBinding | undefined;
connector?: Connector<Readonly<{
id: string;
type: string;
cacheable?: string[];
properties: Readonly<Record<string, any>>;
data: any;
}>, AppGraphNode.NodeArg<any, Record<string, any>>> | undefined;
}>[], never, never>
pin
]);
return
import Capability
Capability
.
const contribute: <Capability.MultiTag<AppGraphBuilder.BuilderExtensions, "org.dxos.app-framework.capability.appGraphBuilder">>(capability: Capability.MultiTag<AppGraphBuilder.BuilderExtensions, "org.dxos.app-framework.capability.appGraphBuilder">, implementation: AppGraphBuilder.BuilderExtensions) => Capability.Contribution<Capability.CapabilityIdentifier<"org.dxos.app-framework.capability.appGraphBuilder", "multi">> (+1 overload)
contribute
(
import AppCapabilities
AppCapabilities
.
const AppGraphBuilder: Capability.MultiTag<AppGraphBuilder.BuilderExtensions, "org.dxos.app-framework.capability.appGraphBuilder">

@category ― Capability

AppGraphBuilder
,
const extensions: [Readonly<{
id: string;
position?: Position;
relation?: AppGraphNode.RelationInput | undefined;
meta?: AppGraphBuilder.UrlBinding | undefined;
connector?: Connector<Readonly<{
id: string;
type: string;
cacheable?: string[];
properties: Readonly<Record<string, any>>;
data: any;
}>, AppGraphNode.NodeArg<any, Record<string, any>>> | undefined;
}>[], Readonly<{
id: string;
position?: Position;
relation?: AppGraphNode.RelationInput | undefined;
meta?: AppGraphBuilder.UrlBinding | undefined;
connector?: Connector<Readonly<{
id: string;
type: string;
cacheable?: string[];
properties: Readonly<Record<string, any>>;
data: any;
}>, AppGraphNode.NodeArg<any, Record<string, any>>> | undefined;
}>[]]
extensions
.
Array<Readonly<{ id: string; position?: Position; relation?: RelationInput | undefined; meta?: UrlBinding | undefined; connector?: Connector<...> | undefined; }>[]>.flat<[Readonly<{
id: string;
position?: Position;
relation?: AppGraphNode.RelationInput | undefined;
meta?: AppGraphBuilder.UrlBinding | undefined;
connector?: Connector<Readonly<{
id: string;
type: string;
cacheable?: string[];
properties: Readonly<Record<string, any>>;
data: any;
}>, AppGraphNode.NodeArg<any, Record<string, any>>> | undefined;
}>[], Readonly<{
id: string;
position?: Position;
relation?: AppGraphNode.RelationInput | undefined;
meta?: AppGraphBuilder.UrlBinding | undefined;
connector?: Connector<Readonly<{
id: string;
type: string;
cacheable?: string[];
properties: Readonly<Record<string, any>>;
data: any;
}>, AppGraphNode.NodeArg<any, Record<string, any>>> | undefined;
}>[]], 1>(this: [...], depth?: 1 | undefined): Readonly<...>[]

Returns a new array with all sub-array elements concatenated into it recursively up to the specified depth.

@param ― depth The maximum recursion depth

flat
(),
);
}),
);

AppNode builds the common kinds of node: makeObject for an ECHO object, makeSection for a heading in the tree, makeCompanion and makeDeckCompanion for panels. GraphPath builds and parses node paths, which navigation operations take.

import * as
import GraphPath
GraphPath
from '@dxos/app-toolkit/GraphPath';
export const
const pathOfNote: (note: Note, spaceId: string) => string
pathOfNote
= (
note: Note
note
:
class Note
Note
,
spaceId: string
spaceId
: string) =>
import GraphPath
GraphPath
.
const getSpacePath: (spaceId: string, ...segments: string[]) => string

Canonical qualified path to a space node. Optional additional segments are appended (e.g. a section name for a well-known child node).

getSpacePath
(
spaceId: string
spaceId
,
note: Note
note
.
OfKind<EntityKind.Object>.id: string
id
);

Expand and read the graph from a component

Section titled “Expand and read the graph from a component”

useAppGraph returns the graph. Expand a node before reading its connections, because the graph builds them lazily. graph.connections(id, relation) is the same data as an atom, for a component that should update as the graph changes.

import {
function useEffect(effect: React.EffectCallback, deps?: React.DependencyList): void

Accepts a function that contains imperative, possibly effectful code.

@param ― effect Imperative function that can return a cleanup function

@param ― deps If present, effect will only activate if the values in the list change.

@version ― 16.8.0

@see ― https://react.dev/reference/react/useEffect

useEffect
,
function useState<S>(initialState: S | (() => S)): [S, React.Dispatch<React.SetStateAction<S>>] (+1 overload)

Returns a stateful value, and a function to update it.

@version ― 16.8.0

@see ― https://react.dev/reference/react/useState

useState
} from 'react';
import * as
import AppGraph
AppGraph
from '@dxos/app-graph/AppGraph';
import * as
import AppGraphNode
AppGraphNode
from '@dxos/app-graph/AppGraphNode';
import * as
import Hooks
Hooks
from '@dxos/app-toolkit/Hooks';
import * as
import EffectEx
EffectEx
from '@dxos/effect/EffectEx';
export const
const ChildCount: ({ id }: {
id: string;
}) => React.JSX.Element
ChildCount
= ({
id: string
id
}: {
id: string
id
: string }) => {
const {
const graph: AppGraph.ExpandableGraph
graph
} =
import Hooks
Hooks
.
const useAppGraph: () => AppCapabilities.AppGraph

Hook to get the current app graph.

useAppGraph
();
const [
const count: number
count
,
const setCount: React.Dispatch<React.SetStateAction<number>>
setCount
] =
useState<number>(initialState: number | (() => number)): [number, React.Dispatch<React.SetStateAction<number>>] (+1 overload)

Returns a stateful value, and a function to update it.

@version ― 16.8.0

@see ― https://react.dev/reference/react/useState

useState
(0);
function useEffect(effect: React.EffectCallback, deps?: React.DependencyList): void

Accepts a function that contains imperative, possibly effectful code.

@param ― effect Imperative function that can return a cleanup function

@param ― deps If present, effect will only activate if the values in the list change.

@version ― 16.8.0

@see ― https://react.dev/reference/react/useEffect

useEffect
(() => {
// Children are built lazily: expand the node, then read its connections.
void
import EffectEx
EffectEx
.
runPromise<void, never>(effect: Effect.Effect<void, never, never>, options?: {
signal?: AbortSignal;
}): Promise<void>
export runPromise

Runs the embedded effect asynchronously and throws any failures and defects as errors. Inserts effect spans as stack frames. The error will have stack frames of where the effect was run (if stack trace limit allows). Removes effect runtime internal stack frames.

To be used in place of Effect.runPromise.

@throws ― AggregateError if there are multiple errors.

runPromise
(
import AppGraph
AppGraph
.
const expand: <AppGraph.ExpandableGraph>(graph: AppGraph.ExpandableGraph, id: string, relation: AppGraphNode.RelationInput) => Effect.Effect<void>

Expand a node in the graph, off the paint-critical path.

Yields to the main thread before running

expandSync

, so a caller reacting to input does not block the frame. Interrupting the effect cancels a still-pending expansion, which makes rescheduling (e.g. superseding a hover with the next one) a matter of interrupting the previous fiber.

expand
(
const graph: AppGraph.ExpandableGraph
graph
,
id: string
id
,
import AppGraphNode
AppGraphNode
.
const child: Readonly<{
kind: string;
direction: AppGraphNode.RelationDirection;
}>

The relation a node's children hang off; the graph's structure.

child
),
).
Promise<void>.then<void, never>(onfulfilled?: ((value: void) => void | PromiseLike<void>) | null | undefined, onrejected?: ((reason: any) => PromiseLike<never>) | null | undefined): Promise<void>

Attaches callbacks for the resolution and/or rejection of the Promise.

@param ― onfulfilled The callback to execute when the Promise is resolved.

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

@returns ― A Promise for the completion of which ever callback is executed.

then
(() =>
const setCount: (value: React.SetStateAction<number>) => void
setCount
(
import AppGraph
AppGraph
.
const getConnections: (graph: AppGraph.BaseGraph, id: string, relation: AppGraphNode.RelationInput) => AppGraphNode.Node[]

Get all nodes connected to the node with the given id by the given relation from the graph's registry.

getConnections
(
const graph: AppGraph.ExpandableGraph
graph
,
id: string
id
,
import AppGraphNode
AppGraphNode
.
const child: Readonly<{
kind: string;
direction: AppGraphNode.RelationDirection;
}>

The relation a node's children hang off; the graph's structure.

child
).
Array<Readonly<{ id: string; type: string; cacheable?: string[]; properties: Readonly<Record<string, any>>; data: any; }>>.length: number

Gets or sets the length of the array. This is a number one higher than the highest index in the array.

length
),
);
}, [
const graph: AppGraph.ExpandableGraph
graph
,
id: string
id
]);
return <
JSX.IntrinsicElements.span: React.DetailedHTMLProps<React.HTMLAttributes<HTMLSpanElement>, HTMLSpanElement>
span
>{
const count: number
count
}</
JSX.IntrinsicElements.span: React.DetailedHTMLProps<React.HTMLAttributes<HTMLSpanElement>, HTMLSpanElement>
span
>;
};
  • An extension’s id and a node’s id must be camelCase. A hyphenated id is dropped without an error.
  • A connector must be cheap and must not write. It runs again on every change to the state it reads.
  • Paths are stable only while the ids along them are. Moving an object to another space changes its path.
Page What it covers
Extend the App Graph Sections, items and actions end to end.
Operation The operations that actions invoke.