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.
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 }
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".
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 }
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".
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'));
Implement handlers and group them
Section titled “Implement handlers and group them”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
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.
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.
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.
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.
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
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"
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
succeed(title: string
title)), );
From React, use useOperation or useOperationInvoker. See React.
The built-in layout operations
Section titled “The built-in layout operations”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.
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] });
Related
Section titled “Related”| 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. |