Skip to content

Operation

import * as Operation from '@dxos/compute/Operation';
import * as OperationHandlerSet from '@dxos/compute/OperationHandlerSet';

An operation is a named action with a typed input and output. Operations explains why the UI and agents share them.

Define an operation with input and output schema

Section titled “Define an operation with input and output schema”

Operation.make takes a key, metadata and two schemas. Annotations such as Operation.mutation describe the operation’s effect, which tools show before running it.

import * as
import Schema
Schema
from 'effect/Schema';
import * as
import Operation
Operation
from '@dxos/compute/Operation';
import {
import DXN
DXN
} from '@dxos/echo';
export const
const Slugify: Operation.Definition<{
readonly title: string;
}, {
readonly slug: string;
}, never>
Slugify
=
import Operation
Operation
.
const make: <{
readonly meta: {
readonly key: DXN.DXN;
readonly name: "Slugify";
readonly description: "Turn a title into a URL slug.";
};
readonly input: Schema.Struct<{
readonly title: Schema.String;
}>;
readonly output: Schema.Struct<{
readonly slug: Schema.String;
}>;
}>(props: {
readonly meta: {
readonly key: DXN.DXN;
readonly name: "Slugify";
readonly description: "Turn a title into a URL slug.";
};
readonly input: Schema.Struct<{
readonly title: Schema.String;
}>;
readonly output: Schema.Struct<{
readonly slug: Schema.String;
}>;
}) => Operation.Definition<...>

Creates a new Operation definition. Applies default executionMode of 'async' if not specified. The returned type preserves the literal types of props (including services).

make
({
meta: {
readonly key: DXN.DXN;
readonly name: "Slugify";
readonly description: "Turn a title into a URL slug.";
}
meta
: {
key: DXN.DXN
key
:
import DXN
DXN
.
const make: <"com.example.operation.text.slugify">(nsid: "com.example.operation.text.slugify", version?: string) => DXN.DXN

Constructs a DXN from an NSID (and optional version). Throws if the result is not a valid DXN. Use tryMake for non-throwing string parsing.

Static NSID strings are validated at compile time via

Name

: the final segment must be camelCase (no hyphens). Template-literal strings with a runtime prefix are accepted here but still validated at runtime.

@example make('org.dxos.type.calendar') → 'dxn:org.dxos.type.calendar'

@example make('org.dxos.type.calendar', '1.0.0') → 'dxn:org.dxos.type.calendar:1.0.0'

make
('com.example.operation.text.slugify'),
name: "Slugify"
name
: 'Slugify',
description: "Turn a title into a URL slug."
description
: 'Turn a title into a URL slug.',
},
input: Schema.Struct<{
readonly title: Schema.String;
}>
input
:
import Schema
Schema
.
function Struct<{
readonly title: Schema.String;
}>(fields: {
readonly title: Schema.String;
}): Schema.Struct<{
readonly title: Schema.String;
}>

Defines a struct schema from a map of field schemas.

Details

Each field value is a schema. Use

optionalKey

or

optional

to mark fields as optional, and

mutableKey

to mark them as mutable.

The resulting schema's Type is a readonly object type with the fields' decoded types. The Encoded form mirrors the field schemas' encoded types. Declared fields may be inherited and are copied to own properties in the output. The __proto__ field is accepted only when it is an own property. Parsing does not guarantee that output keys retain their input order.

Example (Defining a basic struct)

import { Schema } from "effect"
const Person = Schema.Struct({
name: Schema.String,
age: Schema.Number,
email: Schema.optionalKey(Schema.String)
})
// { readonly name: string; readonly age: number; readonly email?: string }
type Person = typeof Person.Type
Schema.decodeUnknownSync(Person)({ name: "Alice", age: 30 }) // => { name: "Alice", age: 30 }

@stability ― stable

@category ― constructors

@since ― 3.10.0

Struct
({
title: Schema.String
title
:
import Schema
Schema
.
const String: Schema.String

Type-level representation of

String

.

Schema for string values. Validates that the input is typeof "string".

@stability ― stable

@category ― models

@since ― 4.0.0

@stability ― stable

@category ― schemas

@since ― 4.0.0

String
}),
output: Schema.Struct<{
readonly slug: Schema.String;
}>
output
:
import Schema
Schema
.
function Struct<{
readonly slug: Schema.String;
}>(fields: {
readonly slug: Schema.String;
}): Schema.Struct<{
readonly slug: Schema.String;
}>

Defines a struct schema from a map of field schemas.

Details

Each field value is a schema. Use

optionalKey

or

optional

to mark fields as optional, and

mutableKey

to mark them as mutable.

The resulting schema's Type is a readonly object type with the fields' decoded types. The Encoded form mirrors the field schemas' encoded types. Declared fields may be inherited and are copied to own properties in the output. The __proto__ field is accepted only when it is an own property. Parsing does not guarantee that output keys retain their input order.

Example (Defining a basic struct)

import { Schema } from "effect"
const Person = Schema.Struct({
name: Schema.String,
age: Schema.Number,
email: Schema.optionalKey(Schema.String)
})
// { readonly name: string; readonly age: number; readonly email?: string }
type Person = typeof Person.Type
Schema.decodeUnknownSync(Person)({ name: "Alice", age: 30 }) // => { name: "Alice", age: 30 }

@stability ― stable

@category ― constructors

@since ― 3.10.0

Struct
({
slug: Schema.String
slug
:
import Schema
Schema
.
const String: Schema.String

Type-level representation of

String

.

Schema for string values. Validates that the input is typeof "string".

@stability ― stable

@category ― models

@since ― 4.0.0

@stability ― stable

@category ― schemas

@since ― 4.0.0

String
}),
}).
Pipeable.pipe<Operation.Definition<{
readonly title: string;
}, {
readonly slug: string;
}, never>, Operation.Definition<{
readonly title: string;
}, {
readonly slug: string;
}, never>>(this: Operation.Definition<{
readonly title: string;
}, {
readonly slug: string;
}, never>, ab: (_: Operation.Definition<{
readonly title: string;
}, {
readonly slug: string;
}, never>) => Operation.Definition<{
readonly title: string;
}, {
readonly slug: string;
}, never>): Operation.Definition<{
readonly title: string;
}, {
readonly slug: string;
}, never> (+21 overloads)
pipe
(
import Operation
Operation
.
const mutation: (value: Operation.Mutation) => <Def extends Operation.Definition<I, O, S = any>.Any>(op: Def) => Def

Pipeable combinator classifying the operation's effect on state — see

MutationAnnotation

. Apply at the definition site: Operation.make({ ... }).pipe(Operation.mutation('none')).

mutation
('none'));

Operation.withHandler attaches an Effect to a definition.

import * as
import Effect
Effect
from 'effect/Effect';
import * as
import Operation
Operation
from '@dxos/compute/Operation';
export const
const SlugifyHandler: Operation.WithHandler<Operation.Definition<{
readonly title: string;
}, {
readonly slug: string;
}, never>>
SlugifyHandler
=
const Slugify: Operation.Definition<{
readonly title: string;
}, {
readonly slug: string;
}, never>
Slugify
.
Pipeable.pipe<Operation.Definition<{
readonly title: string;
}, {
readonly slug: string;
}, never>, Operation.WithHandler<Operation.Definition<{
readonly title: string;
}, {
readonly slug: string;
}, never>>>(this: Operation.Definition<{
readonly title: string;
}, {
readonly slug: string;
}, never>, ab: (_: Operation.Definition<{
readonly title: string;
}, {
readonly slug: string;
}, never>) => Operation.WithHandler<Operation.Definition<{
readonly title: string;
}, {
readonly slug: string;
}, never>>): Operation.WithHandler<...> (+21 overloads)
pipe
(
import Operation
Operation
.
const withHandler: <Operation.Definition<{
readonly title: string;
}, {
readonly slug: string;
}, never>, never>(handler: Operation.Handler<{
readonly title: string;
}, {
readonly slug: string;
}, never, Operation.Service>) => (op: Operation.Definition<{
readonly title: string;
}, {
readonly slug: string;
}, never>) => Operation.WithHandler<Operation.Definition<{
readonly title: string;
}, {
readonly slug: string;
}, never>> (+1 overload)
withHandler
(({
title: string
title
}) =>
import Effect
Effect
.
const succeed: <{
slug: string;
}>(value: {
slug: string;
}) => Effect.Effect<{
slug: 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
({
slug: string
slug
:
title: string
title
.
String.toLowerCase(): string

Converts all the alphabetic characters in a string to lowercase.

toLowerCase
()
.
String.trim(): string

Removes the leading and trailing white space and line terminator characters from a string.

trim
()
.
String.replace(searchValue: {
[Symbol.replace](string: string, replaceValue: string): string;
}, replaceValue: string): string (+3 overloads)

Passes a string and {@linkcode replaceValue } to the [Symbol.replace] method on {@linkcode searchValue } . This method is expected to implement its own replacement algorithm.

@param ― searchValue An object that supports searching for and replacing matches within a string.

@param ― replaceValue The replacement text.

replace
(/[^a-z0-9]+/g, '-'),
}),
),
);

OperationHandlerSet.make groups handlers that are already loaded. OperationHandlerSet.lazy takes lazy handlers, whose modules load the first time each operation runs. Contribute the set through Capabilities.OperationHandler.

import * as
import Operation
Operation
from '@dxos/compute/Operation';
import * as
import OperationHandlerSet
OperationHandlerSet
from '@dxos/compute/OperationHandlerSet';
export const
const TextHandlers: OperationHandlerSet.OperationHandlerSet
TextHandlers
=
import OperationHandlerSet
OperationHandlerSet
.
const make: (...handlers: Operation.WithHandler<Operation.Definition.Any>[]) => OperationHandlerSet.OperationHandlerSet

Creates a new operation handler set from a list of handlers.

@example

const set = OperationHandlerSet.make(
Operation.withHandler(Operation.make({ input: Schema.Void, output: Schema.Void }), (input) => Effect.succeed({})),
Operation.withHandler(Operation.make({ input: Schema.Void, output: Schema.Void }), (input) => Effect.succeed({})),
);

make
(
const SlugifyHandler: Operation.WithHandler<Operation.Definition<{
readonly title: string;
}, {
readonly slug: string;
}, never>>
SlugifyHandler
);
// Lazy: the handler's module loads the first time the operation runs.
export const
const LazyTextHandlers: OperationHandlerSet.OperationHandlerSet
LazyTextHandlers
=
import OperationHandlerSet
OperationHandlerSet
.
const lazy: (entries: readonly Operation.LazyHandler[]) => OperationHandlerSet.OperationHandlerSet

Creates a handler set from typed

Operation.LazyHandler

pairings: definitions are enumerable without loading any handler body, resolving an operation imports only that operation's module — per-operation loading instead of per-plugin — and each pairing is checked, so a definition cannot be wired to another operation's handler. Loaded handlers are cached per key.

@example

const set = OperationHandlerSet.lazy([
MarkdownOperation.Create.pipe(Operation.lazyHandler(() => import('./create'))),
MarkdownOperation.Open.pipe(Operation.lazyHandler(() => import('./open'))),
]);

lazy
([
const Slugify: Operation.Definition<{
readonly title: string;
}, {
readonly slug: string;
}, never>
Slugify
.
Pipeable.pipe<Operation.Definition<{
readonly title: string;
}, {
readonly slug: string;
}, never>, Operation.LazyHandler<Operation.Definition<{
readonly title: string;
}, {
readonly slug: string;
}, never>>>(this: Operation.Definition<{
readonly title: string;
}, {
readonly slug: string;
}, never>, ab: (_: Operation.Definition<{
readonly title: string;
}, {
readonly slug: string;
}, never>) => Operation.LazyHandler<Operation.Definition<{
readonly title: string;
}, {
readonly slug: string;
}, never>>): Operation.LazyHandler<...> (+21 overloads)
pipe
(
import Operation
Operation
.
const lazyHandler: <Operation.Definition<{
readonly title: string;
}, {
readonly slug: string;
}, never>>(load: () => Promise<{
default: Operation.WithHandler<Operation.Definition<{
readonly title: string;
}, {
readonly slug: string;
}, never>>;
}>) => (op: Operation.Definition<{
readonly title: string;
}, {
readonly slug: string;
}, never>) => Operation.LazyHandler<Operation.Definition<{
readonly title: string;
}, {
readonly slug: string;
}, never>> (+1 overload)
lazyHandler
(async () => ({
default: Operation.WithHandler<Operation.Definition<{
readonly title: string;
}, {
readonly slug: string;
}, never>>
default
:
const SlugifyHandler: Operation.WithHandler<Operation.Definition<{
readonly title: string;
}, {
readonly slug: string;
}, never>>
SlugifyHandler
})),
),
]);

Invoke an operation and handle its typed errors

Section titled “Invoke an operation and handle its typed errors”

Operation.invoke returns an Effect that needs the operation service, which the framework provides to modules, handlers and graph actions. It fails with NoHandlerError when no plugin handles the operation.

import * as
import Effect
Effect
from 'effect/Effect';
import * as
import Operation
Operation
from '@dxos/compute/Operation';
export const
const slugOf: (title: string) => Effect.Effect<string, never, Operation.Service>
slugOf
= (
title: string
title
: string) =>
import Operation
Operation
.
const invoke: <{
readonly title: string;
}, {
readonly slug: string;
}>(op: Operation.Definition<{
readonly title: string;
}, {
readonly slug: string;
}, any>, input: {
readonly title: string;
}, options?: Operation.InvokeOptions | undefined) => Effect.Effect<{
readonly slug: string;
}, Operation.NoHandlerError, Operation.Service>

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

@example

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

invoke
(
const Slugify: Operation.Definition<{
readonly title: string;
}, {
readonly slug: string;
}, never>
Slugify
, {
title: string
title
}).
Pipeable.pipe<Effect.Effect<{
readonly slug: string;
}, Operation.NoHandlerError, Operation.Service>, Effect.Effect<string, Operation.NoHandlerError, Operation.Service>, Effect.Effect<string, never, Operation.Service>>(this: Effect.Effect<...>, ab: (_: Effect.Effect<{
readonly slug: string;
}, Operation.NoHandlerError, Operation.Service>) => Effect.Effect<string, Operation.NoHandlerError, Operation.Service>, bc: (_: Effect.Effect<string, Operation.NoHandlerError, Operation.Service>) => Effect.Effect<...>): Effect.Effect<...> (+21 overloads)
pipe
(
import Effect
Effect
.
const map: <{
readonly slug: string;
}, string>(f: (a: {
readonly slug: string;
}) => string) => <E, R>(self: Effect.Effect<{
readonly slug: string;
}, E, R>) => Effect.Effect<string, E, R> (+1 overload)

Transforms the value inside an effect by applying a function to it.

When to use

Use to transform an effect's success value with a function that returns a plain value, producing a new effect without changing the original effect's typed error or context requirements.

Details

map takes a function and applies it to the value contained within an effect, creating a new effect with the transformed value.

It's important to note that effects are immutable, meaning that the original effect is not modified. Instead, a new effect is returned with the updated value.

Example (Choosing map syntax variants)

import { Effect, pipe } from "effect"
const output: Array<unknown> = []
const myEffect = Effect.succeed(1)
const transformation = (n: number) => n + 1
const mappedWithPipe = pipe(myEffect, Effect.map(transformation))
const mappedWithDataFirst = Effect.map(myEffect, transformation)
const mappedWithMethod = myEffect.pipe(Effect.map(transformation))
void output.push(Effect.runSync(Effect.all([
mappedWithPipe,
mappedWithDataFirst,
mappedWithMethod
])))
output // => [[2, 2, 2]]

Example (Adding a service charge)

import { Effect, pipe } from "effect"
const addServiceCharge = (amount: number) => amount + 1
const fetchTransactionAmount = Effect.promise(() => Promise.resolve(100))
const finalAmount = pipe(
fetchTransactionAmount,
Effect.map(addServiceCharge)
)
await Effect.runPromise(finalAmount) // => 101

@see ― mapError for a version that operates on the error channel.

@see ― mapBoth for a version that operates on both channels.

@see ― flatMap or andThen for a version that can return a new effect.

@stability ― stable

@category ― mapping

@since ― 2.0.0

map
(({
slug: string
slug
}) =>
slug: string
slug
),
import Effect
Effect
.
const catchTag: <"NoHandlerError", Operation.NoHandlerError, string, never, never, unassigned, never, never>(k: "NoHandlerError", f: (e: Operation.NoHandlerError) => Effect.Effect<string, never, never>, orElse?: ((e: never) => Effect.Effect<unassigned, never, never>) | undefined) => <A, R>(self: Effect.Effect<A, Operation.NoHandlerError, R>) => Effect.Effect<string | A, never, R> (+1 overload)

Catches and handles specific errors by their _tag field, which is used as a discriminator.

When to use

Use when you need to recover from one specific tagged error in an effect error channel.

Details

The error type must have a readonly _tag field. catchTag matches that field and only handles errors with the requested tag.

Example (Handling a tagged error)

import { Effect } from "effect"
class NetworkError {
readonly _tag = "NetworkError"
constructor(readonly message: string) {}
}
class ValidationError {
readonly _tag = "ValidationError"
constructor(readonly message: string) {}
}
const task: Effect.Effect<string, NetworkError | ValidationError> =
Effect.fail(new NetworkError("offline"))
const program = Effect.catchTag(
task,
"NetworkError",
(error) => Effect.succeed(`Recovered from network error: ${error.message}`)
)
Effect.runSync(program) // => "Recovered from network error: offline"

@see ― catchTags for handling multiple tagged errors in one call

@see ― catchIf for recovering from errors that match a predicate

@stability ― stable

@category ― error handling

@since ― 2.0.0

catchTag
('NoHandlerError', () =>
import Effect
Effect
.
const succeed: <string>(value: string) => Effect.Effect<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
(
title: string
title
)),
);

From React, use useOperation or useOperationInvoker. See React.

LayoutOperation from @dxos/app-toolkit changes what is on screen. Open and Close take node paths; Set replaces the open items; ScrollIntoView, UpdateSidebar, UpdateDialog and AddToast do what their names say.

import * as
import LayoutOperation
LayoutOperation
from '@dxos/app-toolkit/LayoutOperation';
import * as
import Operation
Operation
from '@dxos/compute/Operation';
export const
const openObject: (path: string) => Effect.Effect<readonly string[], Operation.NoHandlerError, Operation.Service>
openObject
= (
path: string
path
: string) =>
import Operation
Operation
.
const invoke: <{
readonly subject: readonly string[];
readonly variant?: string | undefined;
readonly name?: string | undefined;
readonly workspace?: string | undefined;
readonly scrollIntoView?: boolean | undefined;
readonly focus?: "content" | boolean | undefined;
readonly navigation?: "immediate" | "validate" | undefined;
readonly pivotId?: string | undefined;
readonly disposition?: "add" | "auto" | "detail" | "solo" | undefined;
readonly modifiers?: {
readonly shift?: boolean | undefined;
} | undefined;
}, readonly string[]>(op: Operation.Definition<{
readonly subject: readonly string[];
readonly variant?: string | undefined;
readonly name?: string | undefined;
readonly workspace?: string | undefined;
readonly scrollIntoView?: boolean | undefined;
readonly focus?: "content" | boolean | undefined;
readonly navigation?: "immediate" | "validate" | undefined;
readonly pivotId?: string | undefined;
readonly disposition?: "add" | "auto" | "detail" | "solo" | undefined;
readonly modifiers?: {
readonly shift?: boolean | undefined;
} | undefined;
}, readonly string[], any>, input: {
readonly subject: readonly string[];
readonly variant?: string | undefined;
readonly name?: string | undefined;
readonly workspace?: string | undefined;
readonly scrollIntoView?: boolean | undefined;
readonly focus?: "content" | boolean | undefined;
readonly navigation?: "immediate" | "validate" | undefined;
readonly pivotId?: string | undefined;
readonly disposition?: "add" | "auto" | "detail" | "solo" | undefined;
readonly modifiers?: {
readonly shift?: boolean | undefined;
} | undefined;
}, options?: Operation.InvokeOptions | undefined) => Effect.Effect<...>

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

@example

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

invoke
(
import LayoutOperation
LayoutOperation
.
const Open: Operation.Definition<{
readonly subject: readonly string[];
readonly variant?: string | undefined;
readonly name?: string | undefined;
readonly workspace?: string | undefined;
readonly scrollIntoView?: boolean | undefined;
readonly focus?: "content" | boolean | undefined;
readonly navigation?: "immediate" | "validate" | undefined;
readonly pivotId?: string | undefined;
readonly disposition?: "add" | "auto" | "detail" | "solo" | undefined;
readonly modifiers?: {
readonly shift?: boolean | undefined;
} | undefined;
}, readonly string[], Service>
Open
, {
subject: readonly string[]
subject
: [
path: string
path
] });
Page What it covers
Add an operation An operation end to end, including the assistant.
App Graph Attaching an operation to nodes as an action.