Skip to content

Space

import { Space } from '@dxos/halo';

Space manages spaces as units of membership: which spaces exist, who belongs to each and what they may do. It does not read or write the data inside a space; that is ECHO’s Database, reached by the same space id. Every function needs Space.Service, which layerClient provides along with Identity.Service.

Space.spaces is a stream of every space the identity belongs to. It emits the current list straight away, so take one value to read it once. Space.get looks one up by id.

import * as
import Effect
Effect
from 'effect/Effect';
import * as
import Option
Option
from 'effect/Option';
import * as
import Stream
Stream
from 'effect/Stream';
import {
import Space
Space
} from '@dxos/halo';
import { type
type SpaceId = string & {
__SpaceId: true;
}
const SpaceId: Codec<SpaceId, string, never, never> & {
byteLength: number;
encode: (value: Uint8Array) => SpaceId;
decode: (value: SpaceId) => Uint8Array;
isValid: (value: unknown) => value is SpaceId;
make: (value: string) => SpaceId;
random: () => SpaceId;
}

A unique identifier for a space. Space keys are generated by creating a keypair, and then taking the first 20 bytes of the SHA-256 hash of the public key and encoding them to multibase RFC4648 base-32 format (prefixed with B, see Multibase Table).

@example BA25QRC2FEWCSAMRP4RZL65LWJ7352CKE

SpaceId
} from '@dxos/keys';
const
const listSpaces: Effect.Effect<void, never, Space.Service>
listSpaces
=
import Effect
Effect
.
const gen: <Effect.Effect<(readonly {
readonly id: SpaceId;
readonly state: "inactive" | "closed" | "ready";
readonly name?: string | undefined;
}[])[], never, Space.Service>, void>(f: () => Generator<Effect.Effect<(readonly {
readonly id: SpaceId;
readonly state: "inactive" | "closed" | "ready";
readonly name?: string | undefined;
}[])[], never, Space.Service>, void, never>) => Effect.Effect<void, never, Space.Service> (+1 overload)

Provides a way to write effectful code using generator functions, simplifying control flow and error handling.

When to use

Use when you want to write effectful code that looks and behaves like synchronous code, while still handling asynchronous tasks, errors, and complex control flow such as loops and conditions.

Generator functions work similarly to async/await but keep errors, requirements, and interruption in the Effect type. You can yield* values from effects and return the final result at the end.

Example (Sequencing effects with generators)

import { Data, Effect } from "effect"
class DiscountRateError extends Data.TaggedError("DiscountRateError")<{}> {}
const addServiceCharge = (amount: number) => amount + 1
const applyDiscount = (
total: number,
discountRate: number
): Effect.Effect<number, DiscountRateError> =>
discountRate === 0
? Effect.fail(new DiscountRateError())
: Effect.succeed(total - (total * discountRate) / 100)
const fetchTransactionAmount = Effect.promise(() => Promise.resolve(100))
const fetchDiscountRate = Effect.promise(() => Promise.resolve(5))
export const program = Effect.gen(function*() {
const transactionAmount = yield* fetchTransactionAmount
const discountRate = yield* fetchDiscountRate
const discountedAmount = yield* applyDiscount(
transactionAmount,
discountRate
)
const finalAmount = addServiceCharge(discountedAmount)
return `Final amount to charge: ${finalAmount}`
})
await Effect.runPromise(program) // => "Final amount to charge: 96"

@stability ― stable

@category ― constructors

@since ― 2.0.0

gen
(function* () {
const [
const spaces: readonly {
readonly id: SpaceId;
readonly state: "inactive" | "closed" | "ready";
readonly name?: string | undefined;
}[]
spaces
] = yield*
import Space
Space
.
const spaces: Stream.Stream<readonly {
readonly id: SpaceId;
readonly state: "inactive" | "closed" | "ready";
readonly name?: string | undefined;
}[], never, Space.Service>

All spaces as a current-value stream (requires

Service

).

spaces
.
Pipeable.pipe<Stream.Stream<readonly {
readonly id: SpaceId;
readonly state: "inactive" | "closed" | "ready";
readonly name?: string | undefined;
}[], never, Space.Service>, Stream.Stream<readonly {
readonly id: SpaceId;
readonly state: "inactive" | "closed" | "ready";
readonly name?: string | undefined;
}[], never, Space.Service>, Effect.Effect<(readonly {
readonly id: SpaceId;
readonly state: "inactive" | "closed" | "ready";
readonly name?: string | undefined;
}[])[], never, Space.Service>>(this: Stream.Stream<...>, ab: (_: Stream.Stream<readonly {
readonly id: SpaceId;
readonly state: "inactive" | "closed" | "ready";
readonly name?: string | undefined;
}[], never, Space.Service>) => Stream.Stream<...>, bc: (_: Stream.Stream<...>) => Effect.Effect<...>): Effect.Effect<...> (+21 overloads)
pipe
(
import Stream
Stream
.
const take: (n: number) => <A, E, R>(self: Stream.Stream<A, E, R>) => Stream.Stream<A, E, R> (+1 overload)

Takes the first n elements from this stream.

Details

Finite fractional values of n are rounded down. NaN and non-positive values return Stream.empty without evaluating the source stream.

Example (Taking values from the left)

import { Effect, Stream } from "effect"
const program = Effect.gen(function*() {
const values = yield* Stream.make(1, 2, 3, 4, 5).pipe(
Stream.take(3),
Stream.runCollect
)
values // => [ 1, 2, 3 ]
})
await Effect.runPromise(program)

@stability ― stable

@category ― filtering

@since ― 2.0.0

take
(1),
import Stream
Stream
.
const runCollect: <A, E, R>(self: Stream.Stream<A, E, R>) => Effect.Effect<Array<A>, E, R>

Runs the stream and collects all elements into an array.

Example (Collecting stream values)

import { Effect, Stream } from "effect"
const stream = Stream.make(1, 2, 3, 4, 5)
const program = Effect.gen(function*() {
const collected = yield* Stream.runCollect(stream)
collected // => [ 1, 2, 3, 4, 5 ]
})
await Effect.runPromise(program)

@stability ― stable

@category ― destructors

@since ― 2.0.0

runCollect
);
for (const
const space: {
readonly id: SpaceId;
readonly state: "inactive" | "closed" | "ready";
readonly name?: string | undefined;
}
space
of
const spaces: readonly {
readonly id: SpaceId;
readonly state: "inactive" | "closed" | "ready";
readonly name?: string | undefined;
}[]
spaces
) {
var console: Console

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

The module exports two specific components:

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

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

Example using the global console:

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

Example using the Console class:

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

@see ― source

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

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

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

See util.format() for more information.

@since ― v0.1.100

log
(
const space: {
readonly id: SpaceId;
readonly state: "inactive" | "closed" | "ready";
readonly name?: string | undefined;
}
space
.
id: SpaceId
id
,
const space: {
readonly id: SpaceId;
readonly state: "inactive" | "closed" | "ready";
readonly name?: string | undefined;
}
space
.
name?: string | undefined
name
,
const space: {
readonly id: SpaceId;
readonly state: "inactive" | "closed" | "ready";
readonly name?: string | undefined;
}
space
.
state: "inactive" | "closed" | "ready"
state
);
}
});
const
const findSpace: (id: SpaceId) => Effect.Effect<{
readonly id: SpaceId;
readonly state: "inactive" | "closed" | "ready";
readonly name?: string | undefined;
} | undefined, never, Space.Service>
findSpace
= (
id: SpaceId
id
:
type SpaceId = string & {
__SpaceId: true;
}

A unique identifier for a space. Space keys are generated by creating a keypair, and then taking the first 20 bytes of the SHA-256 hash of the public key and encoding them to multibase RFC4648 base-32 format (prefixed with B, see Multibase Table).

@example BA25QRC2FEWCSAMRP4RZL65LWJ7352CKE

SpaceId
) =>
import Space
Space
.
const get: (id: SpaceId) => Effect.Effect<Option.Option<Space.Info>, never, Space.Service>

Resolve a space by id (requires

Service

).

get
(
id: SpaceId
id
).
Pipeable.pipe<Effect.Effect<Option.Option<{
readonly id: SpaceId;
readonly state: "inactive" | "closed" | "ready";
readonly name?: string | undefined;
}>, never, Space.Service>, Effect.Effect<{
readonly id: SpaceId;
readonly state: "inactive" | "closed" | "ready";
readonly name?: string | undefined;
} | undefined, never, Space.Service>>(this: Effect.Effect<...>, ab: (_: Effect.Effect<Option.Option<{
readonly id: SpaceId;
readonly state: "inactive" | "closed" | "ready";
readonly name?: string | undefined;
}>, never, Space.Service>) => Effect.Effect<...>): Effect.Effect<...> (+21 overloads)
pipe
(
import Effect
Effect
.
const map: <Option.Option<{
readonly id: SpaceId;
readonly state: "inactive" | "closed" | "ready";
readonly name?: string | undefined;
}>, {
readonly id: SpaceId;
readonly state: "inactive" | "closed" | "ready";
readonly name?: string | undefined;
} | undefined>(f: (a: Option.Option<{
readonly id: SpaceId;
readonly state: "inactive" | "closed" | "ready";
readonly name?: string | undefined;
}>) => {
readonly id: SpaceId;
readonly state: "inactive" | "closed" | "ready";
readonly name?: string | undefined;
} | undefined) => <E, R>(self: Effect.Effect<...>) => Effect.Effect<...> (+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
(
import Option
Option
.
const getOrUndefined: <A>(self: Option.Option<A>) => A | undefined

Extracts the value from a Some, or returns undefined for None.

When to use

Use when you need to pass absent Option values to APIs that expect undefined.

Details

  • Some → the inner value
  • None → undefined

Example (Unwrapping to undefined)

import { Option } from "effect"
Option.getOrUndefined(Option.some(1)) // => 1
Option.getOrUndefined(Option.none()) // => undefined

@see ― getOrNull to return null instead

@see ― getOrElse for a custom fallback

@stability ― stable

@category ― getters

@since ― 2.0.0

getOrUndefined
));

create returns the new space’s id, name and state. Wait for waitReady before you open its database. membershipPolicy: 'locked' creates a space no one else can join.

import * as
import Effect
Effect
from 'effect/Effect';
import {
import Space
Space
} from '@dxos/halo';
const
const createSpace: Effect.Effect<{
readonly id: SpaceId;
readonly state: "inactive" | "closed" | "ready";
readonly name?: string | undefined;
}, SpaceError, Space.Service>
createSpace
=
import Effect
Effect
.
const gen: <Effect.Effect<void, SpaceError, Space.Service>, {
readonly id: SpaceId;
readonly state: "inactive" | "closed" | "ready";
readonly name?: string | undefined;
}>(f: () => Generator<Effect.Effect<void, SpaceError, Space.Service>, {
readonly id: SpaceId;
readonly state: "inactive" | "closed" | "ready";
readonly name?: string | undefined;
}, never>) => Effect.Effect<{
readonly id: SpaceId;
readonly state: "inactive" | "closed" | "ready";
readonly name?: string | undefined;
}, SpaceError, Space.Service> (+1 overload)

Provides a way to write effectful code using generator functions, simplifying control flow and error handling.

When to use

Use when you want to write effectful code that looks and behaves like synchronous code, while still handling asynchronous tasks, errors, and complex control flow such as loops and conditions.

Generator functions work similarly to async/await but keep errors, requirements, and interruption in the Effect type. You can yield* values from effects and return the final result at the end.

Example (Sequencing effects with generators)

import { Data, Effect } from "effect"
class DiscountRateError extends Data.TaggedError("DiscountRateError")<{}> {}
const addServiceCharge = (amount: number) => amount + 1
const applyDiscount = (
total: number,
discountRate: number
): Effect.Effect<number, DiscountRateError> =>
discountRate === 0
? Effect.fail(new DiscountRateError())
: Effect.succeed(total - (total * discountRate) / 100)
const fetchTransactionAmount = Effect.promise(() => Promise.resolve(100))
const fetchDiscountRate = Effect.promise(() => Promise.resolve(5))
export const program = Effect.gen(function*() {
const transactionAmount = yield* fetchTransactionAmount
const discountRate = yield* fetchDiscountRate
const discountedAmount = yield* applyDiscount(
transactionAmount,
discountRate
)
const finalAmount = addServiceCharge(discountedAmount)
return `Final amount to charge: ${finalAmount}`
})
await Effect.runPromise(program) // => "Final amount to charge: 96"

@stability ― stable

@category ― constructors

@since ― 2.0.0

gen
(function* () {
const
const space: {
readonly id: SpaceId;
readonly state: "inactive" | "closed" | "ready";
readonly name?: string | undefined;
}
space
= yield*
import Space
Space
.
const create: (options?: Space.CreateOptions) => Effect.Effect<Space.Info, SpaceError, Space.Service>

Create a new space (requires

Service

).

create
({
name?: string | undefined
name
: 'Team',
membershipPolicy?: "invite" | "locked" | undefined

Who may join the space (defaults to invite).

membershipPolicy
: 'invite',
});
yield*
import Space
Space
.
const waitReady: (id: SpaceId) => Effect.Effect<void, SpaceError, Space.Service>

Resolve once the space is ready (requires

Service

).

waitReady
(
const space: {
readonly id: SpaceId;
readonly state: "inactive" | "closed" | "ready";
readonly name?: string | undefined;
}
space
.
id: SpaceId
id
);
return
const space: {
readonly id: SpaceId;
readonly state: "inactive" | "closed" | "ready";
readonly name?: string | undefined;
}
space
;
});

members is a stream of the member list. Each member has a DID, a display name, an access level (role) and whether it is online. updateMemberRole changes a member’s access and removeMember revokes it; both take the member’s DID and need admin, or causal seniority to revoke.

import * as
import Effect
Effect
from 'effect/Effect';
import * as
import Stream
Stream
from 'effect/Stream';
import {
import Space
Space
} from '@dxos/halo';
const
const listMembers: Effect.Effect<void, never, Space.Service>
listMembers
=
import Effect
Effect
.
const gen: <Effect.Effect<(readonly {
readonly role: "pull" | "read" | "edit" | "admin";
readonly online: boolean;
readonly did?: IdentityDid | undefined;
readonly identityKey?: string | undefined;
readonly displayName?: string | undefined;
readonly data?: {
readonly [x: string]: any;
} | undefined;
}[])[], never, Space.Service>, void>(f: () => Generator<Effect.Effect<(readonly {
readonly role: "pull" | "read" | "edit" | "admin";
readonly online: boolean;
readonly did?: IdentityDid | undefined;
readonly identityKey?: string | undefined;
readonly displayName?: string | undefined;
readonly data?: {
readonly [x: string]: any;
} | undefined;
}[])[], never, Space.Service>, void, never>) => Effect.Effect<...> (+1 overload)

Provides a way to write effectful code using generator functions, simplifying control flow and error handling.

When to use

Use when you want to write effectful code that looks and behaves like synchronous code, while still handling asynchronous tasks, errors, and complex control flow such as loops and conditions.

Generator functions work similarly to async/await but keep errors, requirements, and interruption in the Effect type. You can yield* values from effects and return the final result at the end.

Example (Sequencing effects with generators)

import { Data, Effect } from "effect"
class DiscountRateError extends Data.TaggedError("DiscountRateError")<{}> {}
const addServiceCharge = (amount: number) => amount + 1
const applyDiscount = (
total: number,
discountRate: number
): Effect.Effect<number, DiscountRateError> =>
discountRate === 0
? Effect.fail(new DiscountRateError())
: Effect.succeed(total - (total * discountRate) / 100)
const fetchTransactionAmount = Effect.promise(() => Promise.resolve(100))
const fetchDiscountRate = Effect.promise(() => Promise.resolve(5))
export const program = Effect.gen(function*() {
const transactionAmount = yield* fetchTransactionAmount
const discountRate = yield* fetchDiscountRate
const discountedAmount = yield* applyDiscount(
transactionAmount,
discountRate
)
const finalAmount = addServiceCharge(discountedAmount)
return `Final amount to charge: ${finalAmount}`
})
await Effect.runPromise(program) // => "Final amount to charge: 96"

@stability ― stable

@category ― constructors

@since ― 2.0.0

gen
(function* () {
const [
const members: readonly {
readonly role: "pull" | "read" | "edit" | "admin";
readonly online: boolean;
readonly did?: IdentityDid | undefined;
readonly identityKey?: string | undefined;
readonly displayName?: string | undefined;
readonly data?: {
readonly [x: string]: any;
} | undefined;
}[]
members
] = yield*
import Space
Space
.
const members: (id: SpaceId) => Stream.Stream<readonly Space.Member[], never, Space.Service>

A space's membership as a current-value stream (requires

Service

).

members
(
const spaceId: SpaceId
spaceId
).
Pipeable.pipe<Stream.Stream<readonly {
readonly role: "pull" | "read" | "edit" | "admin";
readonly online: boolean;
readonly did?: IdentityDid | undefined;
readonly identityKey?: string | undefined;
readonly displayName?: string | undefined;
readonly data?: {
readonly [x: string]: any;
} | undefined;
}[], never, Space.Service>, Stream.Stream<readonly {
readonly role: "pull" | "read" | "edit" | "admin";
readonly online: boolean;
readonly did?: IdentityDid | undefined;
readonly identityKey?: string | undefined;
readonly displayName?: string | undefined;
readonly data?: {
readonly [x: string]: any;
} | undefined;
}[], never, Space.Service>, Effect.Effect<...>>(this: Stream.Stream<...>, ab: (_: Stream.Stream<...>) => Stream.Stream<...>, bc: (_: Stream.Stream<...>) => Effect.Effect<...>): Effect.Effect<...> (+21 overloads)
pipe
(
import Stream
Stream
.
const take: (n: number) => <A, E, R>(self: Stream.Stream<A, E, R>) => Stream.Stream<A, E, R> (+1 overload)

Takes the first n elements from this stream.

Details

Finite fractional values of n are rounded down. NaN and non-positive values return Stream.empty without evaluating the source stream.

Example (Taking values from the left)

import { Effect, Stream } from "effect"
const program = Effect.gen(function*() {
const values = yield* Stream.make(1, 2, 3, 4, 5).pipe(
Stream.take(3),
Stream.runCollect
)
values // => [ 1, 2, 3 ]
})
await Effect.runPromise(program)

@stability ― stable

@category ― filtering

@since ― 2.0.0

take
(1),
import Stream
Stream
.
const runCollect: <A, E, R>(self: Stream.Stream<A, E, R>) => Effect.Effect<Array<A>, E, R>

Runs the stream and collects all elements into an array.

Example (Collecting stream values)

import { Effect, Stream } from "effect"
const stream = Stream.make(1, 2, 3, 4, 5)
const program = Effect.gen(function*() {
const collected = yield* Stream.runCollect(stream)
collected // => [ 1, 2, 3, 4, 5 ]
})
await Effect.runPromise(program)

@stability ― stable

@category ― destructors

@since ― 2.0.0

runCollect
,
);
for (const
const member: {
readonly role: "pull" | "read" | "edit" | "admin";
readonly online: boolean;
readonly did?: IdentityDid | undefined;
readonly identityKey?: string | undefined;
readonly displayName?: string | undefined;
readonly data?: {
readonly [x: string]: any;
} | undefined;
}
member
of
const members: readonly {
readonly role: "pull" | "read" | "edit" | "admin";
readonly online: boolean;
readonly did?: IdentityDid | undefined;
readonly identityKey?: string | undefined;
readonly displayName?: string | undefined;
readonly data?: {
readonly [x: string]: any;
} | undefined;
}[]
members
) {
var console: Console

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

The module exports two specific components:

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

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

Example using the global console:

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

Example using the Console class:

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

@see ― source

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

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

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

See util.format() for more information.

@since ― v0.1.100

log
(
const member: {
readonly role: "pull" | "read" | "edit" | "admin";
readonly online: boolean;
readonly did?: IdentityDid | undefined;
readonly identityKey?: string | undefined;
readonly displayName?: string | undefined;
readonly data?: {
readonly [x: string]: any;
} | undefined;
}
member
.
displayName?: string | undefined
displayName
,
const member: {
readonly role: "pull" | "read" | "edit" | "admin";
readonly online: boolean;
readonly did?: IdentityDid | undefined;
readonly identityKey?: string | undefined;
readonly displayName?: string | undefined;
readonly data?: {
readonly [x: string]: any;
} | undefined;
}
member
.
role: "pull" | "read" | "edit" | "admin"
role
,
const member: {
readonly role: "pull" | "read" | "edit" | "admin";
readonly online: boolean;
readonly did?: IdentityDid | undefined;
readonly identityKey?: string | undefined;
readonly displayName?: string | undefined;
readonly data?: {
readonly [x: string]: any;
} | undefined;
}
member
.
online: boolean

Whether the member is currently online.

online
);
}
});
const
const demote: Effect.Effect<void, SpaceError, Space.Service>
demote
=
import Space
Space
.
const updateMemberRole: (id: SpaceId, subject: IdentityDid, role: Space.Access) => Effect.Effect<void, SpaceError, Space.Service>

Change a member's access level (requires

Service

).

updateMemberRole
(
const spaceId: SpaceId
spaceId
,
const graceDid: IdentityDid
graceDid
, 'read');
const
const remove: Effect.Effect<void, SpaceError, Space.Service>
remove
=
import Space
Space
.
const removeMember: (id: SpaceId, subject: IdentityDid) => Effect.Effect<void, SpaceError, Space.Service>

Remove a member (requires

Service

).

removeMember
(
const spaceId: SpaceId
spaceId
,
const graceDid: IdentityDid
graceDid
);

share starts a space invitation and join accepts one. Both return an Invitation.Flow.

import * as
import Effect
Effect
from 'effect/Effect';
import {
import Invitation
Invitation
,
import Space
Space
} from '@dxos/halo';
const
const invite: Effect.Effect<Invitation.Flow, SpaceError | InvitationError, Space.Service>
invite
=
import Effect
Effect
.
const gen: <Effect.Effect<Invitation.Flow, SpaceError, Space.Service> | Effect.Effect<string, InvitationError, never>, Invitation.Flow>(f: () => Generator<Effect.Effect<Invitation.Flow, SpaceError, Space.Service> | Effect.Effect<string, InvitationError, never>, Invitation.Flow, never>) => Effect.Effect<Invitation.Flow, SpaceError | InvitationError, Space.Service> (+1 overload)

Provides a way to write effectful code using generator functions, simplifying control flow and error handling.

When to use

Use when you want to write effectful code that looks and behaves like synchronous code, while still handling asynchronous tasks, errors, and complex control flow such as loops and conditions.

Generator functions work similarly to async/await but keep errors, requirements, and interruption in the Effect type. You can yield* values from effects and return the final result at the end.

Example (Sequencing effects with generators)

import { Data, Effect } from "effect"
class DiscountRateError extends Data.TaggedError("DiscountRateError")<{}> {}
const addServiceCharge = (amount: number) => amount + 1
const applyDiscount = (
total: number,
discountRate: number
): Effect.Effect<number, DiscountRateError> =>
discountRate === 0
? Effect.fail(new DiscountRateError())
: Effect.succeed(total - (total * discountRate) / 100)
const fetchTransactionAmount = Effect.promise(() => Promise.resolve(100))
const fetchDiscountRate = Effect.promise(() => Promise.resolve(5))
export const program = Effect.gen(function*() {
const transactionAmount = yield* fetchTransactionAmount
const discountRate = yield* fetchDiscountRate
const discountedAmount = yield* applyDiscount(
transactionAmount,
discountRate
)
const finalAmount = addServiceCharge(discountedAmount)
return `Final amount to charge: ${finalAmount}`
})
await Effect.runPromise(program) // => "Final amount to charge: 96"

@stability ― stable

@category ― constructors

@since ― 2.0.0

gen
(function* () {
const
const flow: Invitation.Flow
flow
= yield*
import Space
Space
.
const share: (id: SpaceId, options?: Invitation.ShareOptions) => Effect.Effect<Invitation.Flow, SpaceError, Space.Service>

Initiate a space invitation (requires

Service

).

share
(
const spaceId: SpaceId
spaceId
, {
authMethod?: Invitation.AuthMethod | undefined
authMethod
: 'shared-secret' });
var console: Console

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

The module exports two specific components:

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

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

Example using the global console:

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

Example using the Console class:

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

@see ― source

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

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

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

See util.format() for more information.

@since ― v0.1.100

log
('Send this to Grace:', yield*
import Invitation
Invitation
.
const code: (flow: Invitation.Flow) => Effect.Effect<string, InvitationError>

Encoded, shareable code for a flow.

code
(
const flow: Invitation.Flow
flow
));
return
const flow: Invitation.Flow
flow
;
});
declare const
const code: string
code
: string;
const
const accept: Effect.Effect<Invitation.Flow, SpaceError, Space.Service>
accept
=
import Space
Space
.
const join: (code: string) => Effect.Effect<Invitation.Flow, SpaceError, Space.Service>

Redeem a space-invitation code (requires

Service

).

join
(
const code: string
code
);

exportSpace writes the space to an archive, and importSpace creates a new space from one. setEdgeReplication turns replication through EDGE on or off; with it off, the space syncs only between peers that are online together.

import * as
import Effect
Effect
from 'effect/Effect';
import {
import Space
Space
} from '@dxos/halo';
const
const backUp: Effect.Effect<SpaceId, SpaceError, Space.Service>
backUp
=
import Effect
Effect
.
const gen: <Effect.Effect<void, SpaceError, Space.Service>, SpaceId>(f: () => Generator<Effect.Effect<void, SpaceError, Space.Service>, SpaceId, never>) => Effect.Effect<SpaceId, SpaceError, Space.Service> (+1 overload)

Provides a way to write effectful code using generator functions, simplifying control flow and error handling.

When to use

Use when you want to write effectful code that looks and behaves like synchronous code, while still handling asynchronous tasks, errors, and complex control flow such as loops and conditions.

Generator functions work similarly to async/await but keep errors, requirements, and interruption in the Effect type. You can yield* values from effects and return the final result at the end.

Example (Sequencing effects with generators)

import { Data, Effect } from "effect"
class DiscountRateError extends Data.TaggedError("DiscountRateError")<{}> {}
const addServiceCharge = (amount: number) => amount + 1
const applyDiscount = (
total: number,
discountRate: number
): Effect.Effect<number, DiscountRateError> =>
discountRate === 0
? Effect.fail(new DiscountRateError())
: Effect.succeed(total - (total * discountRate) / 100)
const fetchTransactionAmount = Effect.promise(() => Promise.resolve(100))
const fetchDiscountRate = Effect.promise(() => Promise.resolve(5))
export const program = Effect.gen(function*() {
const transactionAmount = yield* fetchTransactionAmount
const discountRate = yield* fetchDiscountRate
const discountedAmount = yield* applyDiscount(
transactionAmount,
discountRate
)
const finalAmount = addServiceCharge(discountedAmount)
return `Final amount to charge: ${finalAmount}`
})
await Effect.runPromise(program) // => "Final amount to charge: 96"

@stability ― stable

@category ― constructors

@since ― 2.0.0

gen
(function* () {
const
const space: {
readonly id: SpaceId;
readonly state: "inactive" | "closed" | "ready";
readonly name?: string | undefined;
}
space
= yield*
const createSpace: Effect.Effect<{
readonly id: SpaceId;
readonly state: "inactive" | "closed" | "ready";
readonly name?: string | undefined;
}, SpaceError, Space.Service>
createSpace
;
yield*
import Space
Space
.
const setEdgeReplication: (id: SpaceId, setting: Space.EdgeReplication) => Effect.Effect<void, SpaceError, Space.Service>

Enable or disable EDGE replication for a space (requires

Service

).

setEdgeReplication
(
const space: {
readonly id: SpaceId;
readonly state: "inactive" | "closed" | "ready";
readonly name?: string | undefined;
}
space
.
id: SpaceId
id
, 'enabled');
const
const archive: Space.Archive
archive
= yield*
import Space
Space
.
const exportSpace: (id: SpaceId) => Effect.Effect<Space.Archive, SpaceError, Space.Service>

Export a space to an archive (requires

Service

).

exportSpace
(
const space: {
readonly id: SpaceId;
readonly state: "inactive" | "closed" | "ready";
readonly name?: string | undefined;
}
space
.
id: SpaceId
id
);
const
const copy: {
readonly id: SpaceId;
readonly state: "inactive" | "closed" | "ready";
readonly name?: string | undefined;
}
copy
= yield*
import Space
Space
.
const importSpace: (archive: Space.Archive, options?: {
tags?: readonly string[];
}) => Effect.Effect<Space.Info, SpaceError, Space.Service>

Import a space from an archive (requires

Service

).

importSpace
(
const archive: Space.Archive
archive
);
return
const copy: {
readonly id: SpaceId;
readonly state: "inactive" | "closed" | "ready";
readonly name?: string | undefined;
}
copy
.
id: SpaceId
id
;
});
Member Summary
Service The Effect service
spaces get The identity’s spaces
create waitReady Make a space and wait until it opens
members The member list as a stream
updateMemberRole removeMember Change a member’s access
share join invitations Space invitations
exportSpace importSpace Archives
setEdgeReplication Sync through EDGE, or only peer to peer

The full list is in the API reference.

Every function that can fail fails with SpaceError, whose message says what went wrong.

import * as
import Effect
Effect
from 'effect/Effect';
const
const safeRemove: Effect.Effect<void, never, Space.Service>
safeRemove
=
const remove: Effect.Effect<void, SpaceError, Space.Service>
remove
.
Pipeable.pipe<Effect.Effect<void, SpaceError, Space.Service>, Effect.Effect<void, never, Space.Service>>(this: Effect.Effect<void, SpaceError, Space.Service>, ab: (_: Effect.Effect<void, SpaceError, Space.Service>) => Effect.Effect<void, never, Space.Service>): Effect.Effect<void, never, Space.Service> (+21 overloads)
pipe
(
import Effect
Effect
.
const catchTag: <"SpaceError", SpaceError, void, never, never, unassigned, never, never>(k: "SpaceError", f: (e: SpaceError) => Effect.Effect<void, never, never>, orElse?: ((e: never) => Effect.Effect<unassigned, never, never>) | undefined) => <A, R>(self: Effect.Effect<A, SpaceError, R>) => Effect.Effect<void | 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
('SpaceError', (
error: SpaceError
error
) =>
import Effect
Effect
.
const sync: <void>(thunk: LazyArg<void>) => Effect.Effect<void, 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
(() =>
var console: Console

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

The module exports two specific components:

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

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

Example using the global console:

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

Example using the Console class:

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

@see ― source

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

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

const code = 5;
console.error('error #%d', code);
// Prints: error #5, to stderr
console.error('error', code);
// Prints: error 5, to stderr

If formatting elements (e.g. %d) are not found in the first string then util.inspect() is called on each argument and the resulting string values are concatenated. See util.format() for more information.

@since ― v0.1.100

error
('Could not remove:',
error: SpaceError
error
.
message: string
message
)),
),
);

A membership change Keyhive refuses names the reason with one of the errors @dxos/halo exports:

Error Raised when
NotAuthorizedError The acting member lacks the access, such as revoking without seniority
UnknownPrincipalError The member named is not known to the space
InvalidSignatureError A signed operation fails verification
MissingDependencyError An operation refers to earlier operations that have not arrived