Invite people
By the end of this page Alice can invite Bob into Book club, and Bob joins with two codes: one she sends him and one she tells him.
Background
Section titled “Background”An invitation takes two codes, which travel by different routes. The invitation code is long and goes in a message or a link. The authorization code is six digits, which the host sees once the guest connects and passes on another way, such as reading it aloud. Someone who intercepts only the message cannot get in.
When the guest enters the right code, the two sides exchange contact cards: each names its identity and carries the keys the other needs. Then the host writes a delegation, a statement signed by a member allowed to make it, that admits the guest. Every member checks that delegation for itself, so no server has to vouch for the guest.
An invitation is a Flow. Its events say where the handshake is: connecting, connected, readyForAuthentication,
authenticating, then success, cancelled or error. Invitations covers the rest.
-
Invite someone.
Space.sharestarts the invitation on the host, andInvitation.codeis the code to send.shared-secretmakes the guest enter the authorization code. Invite calls this and shows the code.src/app/Invitations.tsx import * asEffect from 'effect/Effect';import Effect2 collapsed linesimportReact, {(alias) namespace Reactimport ReactuseEffect,function useEffect(effect: React.EffectCallback, deps?: React.DependencyList): voidAccepts a function that contains imperative, possibly effectful code.
useRef,function useRef<T>(initialValue: T): React.RefObject<T> (+2 overloads)useRefreturns a mutable ref object whose.currentproperty is initialized to the passed argument (initialValue). The returned object will persist for the full lifetime of the component.Note that
useRef()is useful for more than therefattribute. It’s handy for keeping any mutable value around similar to how you’d use instance fields in classes.useState } from 'react';function useState<S>(initialState: S | (() => S)): [S, React.Dispatch<React.SetStateAction<S>>] (+1 overload)Returns a stateful value, and a function to update it.
import {Invitation,import InvitationSpace } from '@dxos/halo';import Space1 collapsed lineimport {useHaloServices,const useHaloServices: () => Context<HaloServices>Returns the HALO services context. Throws if no
HaloProvider
is above in the tree.
useInvitationFlow } from '@dxos/halo-react';const useInvitationFlow: (flow?: Invitation.Flow) => InvitationFlowStateTracks one
Invitation.Flow
for rendering, re-rendering on each lifecycle event. Needs no
HaloProvider: a flow's streams and effects carry no service requirement.import { typeSpaceId } from '@dxos/keys';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).
8 collapsed linesimport {Copyable,const Copyable: ({ value }: {value: string;}) => React.JSX.ElementPrompt } from './ui.tsx';const Prompt: ({ label, action, initial, multiline, compact, autoFocus, onSubmit, }: {label: string;action: string;initial?: string;multiline?: boolean;compact?: boolean;autoFocus?: boolean;onSubmit: (value: string) => Promise<unknown>;}) => React.JSX.ElementA field and a button, which shows the error if
onSubmitfails./** Invite someone to the current space, or join a space with a code. */export constInviteBar = ({const InviteBar: ({ spaceId }: {spaceId?: SpaceId;}) => React.JSX.ElementInvite someone to the current space, or join a space with a code.
spaceId }: {spaceId: SpaceId | undefinedspaceId?:spaceId?: SpaceId | undefinedSpaceId }) => {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).
constservices =const services: Context<HaloServices>useHaloServices();function useHaloServices(): Context<HaloServices>Returns the HALO services context. Throws if no
HaloProvider
is above in the tree.
const [open,const open: "invite" | "join" | undefinedsetOpen] =const setOpen: React.Dispatch<React.SetStateAction<"invite" | "join" | undefined>>useState<'invite' | 'join'>();useState<"invite" | "join">(): ["invite" | "join" | undefined, React.Dispatch<React.SetStateAction<"invite" | "join" | undefined>>] (+1 overload)Returns a stateful value, and a function to update it.
/** Starts an invitation to the space, and returns the flow and the code to send. */consthandleInvite = (const handleInvite: (spaceId: SpaceId) => Promise<{flow: Invitation.Flow;code: string;}>Starts an invitation to the space, and returns the flow and the code to send.
spaceId:spaceId: SpaceIdSpaceId) =>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).
Effect.import Effectgen(function* () {const gen: <Effect.Effect<Invitation.Flow, SpaceError, Space.Service> | Effect.Effect<string, InvitationError, never>, {flow: Invitation.Flow;code: string;}>(f: () => Generator<Effect.Effect<Invitation.Flow, SpaceError, Space.Service> | Effect.Effect<string, InvitationError, never>, {flow: Invitation.Flow;code: string;}, never>) => Effect.Effect<{flow: Invitation.Flow;code: string;}, 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/awaitbut keep errors, requirements, and interruption in the Effect type. You canyield*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 + 1const 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* fetchTransactionAmountconst discountRate = yield* fetchDiscountRateconst discountedAmount = yield* applyDiscount(transactionAmount,discountRate)const finalAmount = addServiceCharge(discountedAmount)return `Final amount to charge: ${finalAmount}`})await Effect.runPromise(program) // => "Final amount to charge: 96"constflow = yield*const flow: Invitation.FlowSpace.import Spaceshare(const share: (id: SpaceId, options?: Invitation.ShareOptions) => Effect.Effect<Invitation.Flow, SpaceError, Space.Service>Initiate a space invitation (requires
Service
).
spaceId, {spaceId: SpaceIdauthMethod: 'shared-secret',authMethod?: Invitation.AuthMethod | undefined});constcode = yield*const code: stringInvitation.import Invitationcode(const code: (flow: Invitation.Flow) => Effect.Effect<string, InvitationError>Encoded, shareable code for a flow.
flow);const flow: Invitation.Flowreturn {flow,flow: Invitation.Flowcode };code: string}).pipe(Pipeable.pipe<Effect.Effect<{flow: Invitation.Flow;code: string;}, SpaceError | InvitationError, Space.Service>, Effect.Effect<{flow: Invitation.Flow;code: string;}, SpaceError | InvitationError, never>, Promise<{flow: Invitation.Flow;code: string;}>>(this: Effect.Effect<...>, ab: (_: Effect.Effect<{flow: Invitation.Flow;code: string;}, SpaceError | InvitationError, Space.Service>) => Effect.Effect<...>, bc: (_: Effect.Effect<...>) => Promise<...>): Promise<...> (+21 overloads)Effect.import Effectprovide(const provide: <HaloServices>(context: Context<HaloServices>) => <A, E, R>(self: Effect.Effect<A, E, R>) => Effect.Effect<A, E, Exclude<R, HaloServices>> (+5 overloads)Provides dependencies to an effect using layers or a context. Use
options.localto build the layer every time; by default, layers are shared between provide calls.Example (Providing dependencies with a layer)
import { Context, Effect, Layer } from "effect"interface Database {readonly query: (sql: string) => Effect.Effect<string>}const Database = Context.Service<Database>("Database")const DatabaseLayer = Layer.succeed(Database)({query: Effect.fn("Database.query")((sql: string) => Effect.succeed(`Result for: ${sql}`))})const program = Effect.gen(function*() {const db = yield* Databasereturn yield* db.query("SELECT * FROM users")})const provided = Effect.provide(program, DatabaseLayer)await Effect.runPromise(provided) // => "Result for: SELECT * FROM users"services),const services: Context<HaloServices>Effect.import EffectrunPromise);const runPromise: <A, E>(effect: Effect.Effect<A, E>, options?: Effect.RunOptions | undefined) => Promise<A>Executes an effect and returns the result as a
Promise.When to use
Use when you need to execute an effect and work with the result using
Promisesyntax, typically for compatibility with other promise-based code.If the effect succeeds, the promise will resolve with the result. If the effect fails, the promise will reject with an error.
Example (Running a successful effect as a Promise)
import { Effect } from "effect"await Effect.runPromise(Effect.succeed(1)) // => 1Example (Running effects as promises)
//Example: Handling a Failing Effect as a Rejected Promiseimport { Effect } from "effect"const output: Array<unknown> = []await Effect.runPromise(Effect.fail("my error")).catch(() => {void output.push("rejected")})output // => ["rejected"] -
Join. On the guest,
Space.joinredeems the code and returns the guest’s own flow. Join calls this.src/app/Invitations.tsx import * asEffect from 'effect/Effect';import Effect2 collapsed linesimportReact, {(alias) namespace Reactimport ReactuseEffect,function useEffect(effect: React.EffectCallback, deps?: React.DependencyList): voidAccepts a function that contains imperative, possibly effectful code.
useRef,function useRef<T>(initialValue: T): React.RefObject<T> (+2 overloads)useRefreturns a mutable ref object whose.currentproperty is initialized to the passed argument (initialValue). The returned object will persist for the full lifetime of the component.Note that
useRef()is useful for more than therefattribute. It’s handy for keeping any mutable value around similar to how you’d use instance fields in classes.useState } from 'react';function useState<S>(initialState: S | (() => S)): [S, React.Dispatch<React.SetStateAction<S>>] (+1 overload)Returns a stateful value, and a function to update it.
import {Invitation,import InvitationSpace } from '@dxos/halo';import Space20 collapsed linesimport {useHaloServices,const useHaloServices: () => Context<HaloServices>Returns the HALO services context. Throws if no
HaloProvider
is above in the tree.
useInvitationFlow } from '@dxos/halo-react';const useInvitationFlow: (flow?: Invitation.Flow) => InvitationFlowStateTracks one
Invitation.Flow
for rendering, re-rendering on each lifecycle event. Needs no
HaloProvider: a flow's streams and effects carry no service requirement.import { typeSpaceId } from '@dxos/keys';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).
import {Copyable,const Copyable: ({ value }: {value: string;}) => React.JSX.ElementPrompt } from './ui.tsx';const Prompt: ({ label, action, initial, multiline, compact, autoFocus, onSubmit, }: {label: string;action: string;initial?: string;multiline?: boolean;compact?: boolean;autoFocus?: boolean;onSubmit: (value: string) => Promise<unknown>;}) => React.JSX.ElementA field and a button, which shows the error if
onSubmitfails./** Invite someone to the current space, or join a space with a code. */export constInviteBar = ({const InviteBar: ({ spaceId }: {spaceId?: SpaceId;}) => React.JSX.ElementInvite someone to the current space, or join a space with a code.
spaceId }: {spaceId: SpaceId | undefinedspaceId?:spaceId?: SpaceId | undefinedSpaceId }) => {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).
constservices =const services: Context<HaloServices>useHaloServices();function useHaloServices(): Context<HaloServices>Returns the HALO services context. Throws if no
HaloProvider
is above in the tree.
const [open,const open: "invite" | "join" | undefinedsetOpen] =const setOpen: React.Dispatch<React.SetStateAction<"invite" | "join" | undefined>>useState<'invite' | 'join'>();useState<"invite" | "join">(): ["invite" | "join" | undefined, React.Dispatch<React.SetStateAction<"invite" | "join" | undefined>>] (+1 overload)Returns a stateful value, and a function to update it.
/** Starts an invitation to the space, and returns the flow and the code to send. */consthandleInvite = (const handleInvite: (spaceId: SpaceId) => Promise<{flow: Invitation.Flow;code: string;}>Starts an invitation to the space, and returns the flow and the code to send.
spaceId:spaceId: SpaceIdSpaceId) =>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).
Effect.import Effectgen(function* () {const gen: <Effect.Effect<Invitation.Flow, SpaceError, Space.Service> | Effect.Effect<string, InvitationError, never>, {flow: Invitation.Flow;code: string;}>(f: () => Generator<Effect.Effect<Invitation.Flow, SpaceError, Space.Service> | Effect.Effect<string, InvitationError, never>, {flow: Invitation.Flow;code: string;}, never>) => Effect.Effect<{flow: Invitation.Flow;code: string;}, 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/awaitbut keep errors, requirements, and interruption in the Effect type. You canyield*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 + 1const 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* fetchTransactionAmountconst discountRate = yield* fetchDiscountRateconst discountedAmount = yield* applyDiscount(transactionAmount,discountRate)const finalAmount = addServiceCharge(discountedAmount)return `Final amount to charge: ${finalAmount}`})await Effect.runPromise(program) // => "Final amount to charge: 96"constflow = yield*const flow: Invitation.FlowSpace.import Spaceshare(const share: (id: SpaceId, options?: Invitation.ShareOptions) => Effect.Effect<Invitation.Flow, SpaceError, Space.Service>Initiate a space invitation (requires
Service
).
spaceId, {spaceId: SpaceIdauthMethod: 'shared-secret',authMethod?: Invitation.AuthMethod | undefined});constcode = yield*const code: stringInvitation.import Invitationcode(const code: (flow: Invitation.Flow) => Effect.Effect<string, InvitationError>Encoded, shareable code for a flow.
flow);const flow: Invitation.Flowreturn {flow,flow: Invitation.Flowcode };code: string}).pipe(Pipeable.pipe<Effect.Effect<{flow: Invitation.Flow;code: string;}, SpaceError | InvitationError, Space.Service>, Effect.Effect<{flow: Invitation.Flow;code: string;}, SpaceError | InvitationError, never>, Promise<{flow: Invitation.Flow;code: string;}>>(this: Effect.Effect<...>, ab: (_: Effect.Effect<{flow: Invitation.Flow;code: string;}, SpaceError | InvitationError, Space.Service>) => Effect.Effect<...>, bc: (_: Effect.Effect<...>) => Promise<...>): Promise<...> (+21 overloads)Effect.import Effectprovide(const provide: <HaloServices>(context: Context<HaloServices>) => <A, E, R>(self: Effect.Effect<A, E, R>) => Effect.Effect<A, E, Exclude<R, HaloServices>> (+5 overloads)Provides dependencies to an effect using layers or a context. Use
options.localto build the layer every time; by default, layers are shared between provide calls.Example (Providing dependencies with a layer)
import { Context, Effect, Layer } from "effect"interface Database {readonly query: (sql: string) => Effect.Effect<string>}const Database = Context.Service<Database>("Database")const DatabaseLayer = Layer.succeed(Database)({query: Effect.fn("Database.query")((sql: string) => Effect.succeed(`Result for: ${sql}`))})const program = Effect.gen(function*() {const db = yield* Databasereturn yield* db.query("SELECT * FROM users")})const provided = Effect.provide(program, DatabaseLayer)await Effect.runPromise(provided) // => "Result for: SELECT * FROM users"services),const services: Context<HaloServices>Effect.import EffectrunPromise);const runPromise: <A, E>(effect: Effect.Effect<A, E>, options?: Effect.RunOptions | undefined) => Promise<A>Executes an effect and returns the result as a
Promise.When to use
Use when you need to execute an effect and work with the result using
Promisesyntax, typically for compatibility with other promise-based code.If the effect succeeds, the promise will resolve with the result. If the effect fails, the promise will reject with an error.
Example (Running a successful effect as a Promise)
import { Effect } from "effect"await Effect.runPromise(Effect.succeed(1)) // => 1Example (Running effects as promises)
//Example: Handling a Failing Effect as a Rejected Promiseimport { Effect } from "effect"const output: Array<unknown> = []await Effect.runPromise(Effect.fail("my error")).catch(() => {void output.push("rejected")})output // => ["rejected"]/** Redeems the invitation `code`, and returns the flow. */consthandleJoin = (const handleJoin: (code: string) => Promise<Invitation.Flow>Redeems the invitation
code, and returns the flow.code: string) =>code: stringSpace.import Spacejoin(const join: (code: string) => Effect.Effect<Invitation.Flow, SpaceError, Space.Service>Redeem a space-invitation code (requires
Service
).
code).code: stringpipe(Pipeable.pipe<Effect.Effect<Invitation.Flow, SpaceError, Space.Service>, Effect.Effect<Invitation.Flow, SpaceError, never>, Promise<Invitation.Flow>>(this: Effect.Effect<...>, ab: (_: Effect.Effect<Invitation.Flow, SpaceError, Space.Service>) => Effect.Effect<Invitation.Flow, SpaceError, never>, bc: (_: Effect.Effect<Invitation.Flow, SpaceError, never>) => Promise<...>): Promise<...> (+21 overloads)Effect.import Effectprovide(const provide: <HaloServices>(context: Context<HaloServices>) => <A, E, R>(self: Effect.Effect<A, E, R>) => Effect.Effect<A, E, Exclude<R, HaloServices>> (+5 overloads)Provides dependencies to an effect using layers or a context. Use
options.localto build the layer every time; by default, layers are shared between provide calls.Example (Providing dependencies with a layer)
import { Context, Effect, Layer } from "effect"interface Database {readonly query: (sql: string) => Effect.Effect<string>}const Database = Context.Service<Database>("Database")const DatabaseLayer = Layer.succeed(Database)({query: Effect.fn("Database.query")((sql: string) => Effect.succeed(`Result for: ${sql}`))})const program = Effect.gen(function*() {const db = yield* Databasereturn yield* db.query("SELECT * FROM users")})const provided = Effect.provide(program, DatabaseLayer)await Effect.runPromise(provided) // => "Result for: SELECT * FROM users"services),const services: Context<HaloServices>Effect.import EffectrunPromise);const runPromise: <A, E>(effect: Effect.Effect<A, E>, options?: Effect.RunOptions | undefined) => Promise<A>Executes an effect and returns the result as a
Promise.When to use
Use when you need to execute an effect and work with the result using
Promisesyntax, typically for compatibility with other promise-based code.If the effect succeeds, the promise will resolve with the result. If the effect fails, the promise will reject with an error.
Example (Running a successful effect as a Promise)
import { Effect } from "effect"await Effect.runPromise(Effect.succeed(1)) // => 1Example (Running effects as promises)
//Example: Handling a Failing Effect as a Rejected Promiseimport { Effect } from "effect"const output: Array<unknown> = []await Effect.runPromise(Effect.fail("my error")).catch(() => {void output.push("rejected")})output // => ["rejected"] -
Follow the handshake.
useInvitationFlowreturns the flow’s latest event and re-renders on each one, until the flow ends. Both sides follow their flow with it. On the host,readyForAuthenticationcarries the authorization code to show; on the guest, it is the moment to ask for it.src/app/Invitations.tsx import {useHaloServices,const useHaloServices: () => Context<HaloServices>Returns the HALO services context. Throws if no
HaloProvider
is above in the tree.
useInvitationFlow } from '@dxos/halo-react';const useInvitationFlow: (flow?: Invitation.Flow) => InvitationFlowStateTracks one
Invitation.Flow
for rendering, re-rendering on each lifecycle event. Needs no
HaloProvider: a flow's streams and effects carry no service requirement.79 collapsed linesimport { typeSpaceId } from '@dxos/keys';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).
import {Copyable,const Copyable: ({ value }: {value: string;}) => React.JSX.ElementPrompt } from './ui.tsx';const Prompt: ({ label, action, initial, multiline, compact, autoFocus, onSubmit, }: {label: string;action: string;initial?: string;multiline?: boolean;compact?: boolean;autoFocus?: boolean;onSubmit: (value: string) => Promise<unknown>;}) => React.JSX.ElementA field and a button, which shows the error if
onSubmitfails./** Invite someone to the current space, or join a space with a code. */export constInviteBar = ({const InviteBar: ({ spaceId }: {spaceId?: SpaceId;}) => React.JSX.ElementInvite someone to the current space, or join a space with a code.
spaceId }: {spaceId: SpaceId | undefinedspaceId?:spaceId?: SpaceId | undefinedSpaceId }) => {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).
constservices =const services: Context<HaloServices>useHaloServices();function useHaloServices(): Context<HaloServices>Returns the HALO services context. Throws if no
HaloProvider
is above in the tree.
const [open,const open: "invite" | "join" | undefinedsetOpen] =const setOpen: React.Dispatch<React.SetStateAction<"invite" | "join" | undefined>>useState<'invite' | 'join'>();useState<"invite" | "join">(): ["invite" | "join" | undefined, React.Dispatch<React.SetStateAction<"invite" | "join" | undefined>>] (+1 overload)Returns a stateful value, and a function to update it.
/** Starts an invitation to the space, and returns the flow and the code to send. */consthandleInvite = (const handleInvite: (spaceId: SpaceId) => Promise<{flow: Invitation.Flow;code: string;}>Starts an invitation to the space, and returns the flow and the code to send.
spaceId:spaceId: SpaceIdSpaceId) =>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).
Effect.import Effectgen(function* () {const gen: <Effect.Effect<Invitation.Flow, SpaceError, Space.Service> | Effect.Effect<string, InvitationError, never>, {flow: Invitation.Flow;code: string;}>(f: () => Generator<Effect.Effect<Invitation.Flow, SpaceError, Space.Service> | Effect.Effect<string, InvitationError, never>, {flow: Invitation.Flow;code: string;}, never>) => Effect.Effect<{flow: Invitation.Flow;code: string;}, 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/awaitbut keep errors, requirements, and interruption in the Effect type. You canyield*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 + 1const 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* fetchTransactionAmountconst discountRate = yield* fetchDiscountRateconst discountedAmount = yield* applyDiscount(transactionAmount,discountRate)const finalAmount = addServiceCharge(discountedAmount)return `Final amount to charge: ${finalAmount}`})await Effect.runPromise(program) // => "Final amount to charge: 96"constflow = yield*const flow: Invitation.FlowSpace.import Spaceshare(const share: (id: SpaceId, options?: Invitation.ShareOptions) => Effect.Effect<Invitation.Flow, SpaceError, Space.Service>Initiate a space invitation (requires
Service
).
spaceId, {spaceId: SpaceIdauthMethod: 'shared-secret',authMethod?: Invitation.AuthMethod | undefined});constcode = yield*const code: stringInvitation.import Invitationcode(const code: (flow: Invitation.Flow) => Effect.Effect<string, InvitationError>Encoded, shareable code for a flow.
flow);const flow: Invitation.Flowreturn {flow,flow: Invitation.Flowcode };code: string}).pipe(Pipeable.pipe<Effect.Effect<{flow: Invitation.Flow;code: string;}, SpaceError | InvitationError, Space.Service>, Effect.Effect<{flow: Invitation.Flow;code: string;}, SpaceError | InvitationError, never>, Promise<{flow: Invitation.Flow;code: string;}>>(this: Effect.Effect<...>, ab: (_: Effect.Effect<{flow: Invitation.Flow;code: string;}, SpaceError | InvitationError, Space.Service>) => Effect.Effect<...>, bc: (_: Effect.Effect<...>) => Promise<...>): Promise<...> (+21 overloads)Effect.import Effectprovide(const provide: <HaloServices>(context: Context<HaloServices>) => <A, E, R>(self: Effect.Effect<A, E, R>) => Effect.Effect<A, E, Exclude<R, HaloServices>> (+5 overloads)Provides dependencies to an effect using layers or a context. Use
options.localto build the layer every time; by default, layers are shared between provide calls.Example (Providing dependencies with a layer)
import { Context, Effect, Layer } from "effect"interface Database {readonly query: (sql: string) => Effect.Effect<string>}const Database = Context.Service<Database>("Database")const DatabaseLayer = Layer.succeed(Database)({query: Effect.fn("Database.query")((sql: string) => Effect.succeed(`Result for: ${sql}`))})const program = Effect.gen(function*() {const db = yield* Databasereturn yield* db.query("SELECT * FROM users")})const provided = Effect.provide(program, DatabaseLayer)await Effect.runPromise(provided) // => "Result for: SELECT * FROM users"services),const services: Context<HaloServices>Effect.import EffectrunPromise);const runPromise: <A, E>(effect: Effect.Effect<A, E>, options?: Effect.RunOptions | undefined) => Promise<A>Executes an effect and returns the result as a
Promise.When to use
Use when you need to execute an effect and work with the result using
Promisesyntax, typically for compatibility with other promise-based code.If the effect succeeds, the promise will resolve with the result. If the effect fails, the promise will reject with an error.
Example (Running a successful effect as a Promise)
import { Effect } from "effect"await Effect.runPromise(Effect.succeed(1)) // => 1Example (Running effects as promises)
//Example: Handling a Failing Effect as a Rejected Promiseimport { Effect } from "effect"const output: Array<unknown> = []await Effect.runPromise(Effect.fail("my error")).catch(() => {void output.push("rejected")})output // => ["rejected"]/** Redeems the invitation `code`, and returns the flow. */consthandleJoin = (const handleJoin: (code: string) => Promise<Invitation.Flow>Redeems the invitation
code, and returns the flow.code: string) =>code: stringSpace.import Spacejoin(const join: (code: string) => Effect.Effect<Invitation.Flow, SpaceError, Space.Service>Redeem a space-invitation code (requires
Service
).
code).code: stringpipe(Pipeable.pipe<Effect.Effect<Invitation.Flow, SpaceError, Space.Service>, Effect.Effect<Invitation.Flow, SpaceError, never>, Promise<Invitation.Flow>>(this: Effect.Effect<...>, ab: (_: Effect.Effect<Invitation.Flow, SpaceError, Space.Service>) => Effect.Effect<Invitation.Flow, SpaceError, never>, bc: (_: Effect.Effect<Invitation.Flow, SpaceError, never>) => Promise<...>): Promise<...> (+21 overloads)Effect.import Effectprovide(const provide: <HaloServices>(context: Context<HaloServices>) => <A, E, R>(self: Effect.Effect<A, E, R>) => Effect.Effect<A, E, Exclude<R, HaloServices>> (+5 overloads)Provides dependencies to an effect using layers or a context. Use
options.localto build the layer every time; by default, layers are shared between provide calls.Example (Providing dependencies with a layer)
import { Context, Effect, Layer } from "effect"interface Database {readonly query: (sql: string) => Effect.Effect<string>}const Database = Context.Service<Database>("Database")const DatabaseLayer = Layer.succeed(Database)({query: Effect.fn("Database.query")((sql: string) => Effect.succeed(`Result for: ${sql}`))})const program = Effect.gen(function*() {const db = yield* Databasereturn yield* db.query("SELECT * FROM users")})const provided = Effect.provide(program, DatabaseLayer)await Effect.runPromise(provided) // => "Result for: SELECT * FROM users"services),const services: Context<HaloServices>Effect.import EffectrunPromise);const runPromise: <A, E>(effect: Effect.Effect<A, E>, options?: Effect.RunOptions | undefined) => Promise<A>Executes an effect and returns the result as a
Promise.When to use
Use when you need to execute an effect and work with the result using
Promisesyntax, typically for compatibility with other promise-based code.If the effect succeeds, the promise will resolve with the result. If the effect fails, the promise will reject with an error.
Example (Running a successful effect as a Promise)
import { Effect } from "effect"await Effect.runPromise(Effect.succeed(1)) // => 1Example (Running effects as promises)
//Example: Handling a Failing Effect as a Rejected Promiseimport { Effect } from "effect"const output: Array<unknown> = []await Effect.runPromise(Effect.fail("my error")).catch(() => {void output.push("rejected")})output // => ["rejected"]return (<divReact.JSX.IntrinsicElements.div: React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>className='cb-invite'>React.HTMLAttributes<HTMLDivElement>.className?: string | undefined<divReact.JSX.IntrinsicElements.div: React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>className='cb-row'>React.HTMLAttributes<HTMLDivElement>.className?: string | undefined<buttonReact.JSX.IntrinsicElements.button: React.DetailedHTMLProps<React.ButtonHTMLAttributes<HTMLButtonElement>, HTMLButtonElement>className='cb-button'React.HTMLAttributes<T>.className?: string | undefineddisabled={!React.ButtonHTMLAttributes<HTMLButtonElement>.disabled?: boolean | undefinedspaceId}spaceId: SpaceId | undefinedaria-pressed={React.AriaAttributes["aria-pressed"]?: boolean | "false" | "mixed" | "true" | undefinedIndicates the current "pressed" state of toggle buttons.
open === 'invite'}const open: "invite" | "join" | undefinedonClick={() =>React.DOMAttributes<HTMLButtonElement>.onClick?: React.MouseEventHandler<HTMLButtonElement> | undefinedsetOpen(const setOpen: (value: React.SetStateAction<"invite" | "join" | undefined>) => voidopen === 'invite' ?const open: "invite" | "join" | undefinedundefined : 'invite')}var undefined>Invite</button>React.JSX.IntrinsicElements.button: React.DetailedHTMLProps<React.ButtonHTMLAttributes<HTMLButtonElement>, HTMLButtonElement><buttonReact.JSX.IntrinsicElements.button: React.DetailedHTMLProps<React.ButtonHTMLAttributes<HTMLButtonElement>, HTMLButtonElement>className='cb-button'React.HTMLAttributes<T>.className?: string | undefinedaria-pressed={React.AriaAttributes["aria-pressed"]?: boolean | "false" | "mixed" | "true" | undefinedIndicates the current "pressed" state of toggle buttons.
open === 'join'}const open: "invite" | "join" | undefinedonClick={() =>React.DOMAttributes<HTMLButtonElement>.onClick?: React.MouseEventHandler<HTMLButtonElement> | undefinedsetOpen(const setOpen: (value: React.SetStateAction<"invite" | "join" | undefined>) => voidopen === 'join' ?const open: "invite" | "join" | undefinedundefined : 'join')}var undefined>Join</button>React.JSX.IntrinsicElements.button: React.DetailedHTMLProps<React.ButtonHTMLAttributes<HTMLButtonElement>, HTMLButtonElement></div>React.JSX.IntrinsicElements.div: React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>{open === 'invite' &&const open: "invite" | "join" | undefinedspaceId && (spaceId: SpaceId | undefined<Hostconst Host: ({ start, send, }: {start: () => Promise<Flow>;send: string;}) => React.JSX.ElementThe host's side of an invitation: the code to send, then the authorization code to tell.
key={React.Attributes.key?: React.Key | null | undefinedspaceId}spaceId: SpaceIdstart={() =>start: () => Promise<Flow>handleInvite(const handleInvite: (spaceId: SpaceId) => Promise<{flow: Invitation.Flow;code: string;}>Starts an invitation to the space, and returns the flow and the code to send.
spaceId)}spaceId: SpaceIdsend='Send this invitation code to the person you invite:'send: string/>)}{open === 'join' && (const open: "invite" | "join" | undefined<Guestconst Guest: ({ label, join, onDone, }: {label: string;join: (code: string) => Promise<Invitation.Flow>;onDone?: () => void;}) => React.JSX.ElementThe guest's side of an invitation: paste the code, then type the authorization code.
label='Invitation code'label: stringjoin={join: (code: string) => Promise<Invitation.Flow>handleJoin}const handleJoin: (code: string) => Promise<Invitation.Flow>Redeems the invitation
code, and returns the flow.onDone={() =>onDone?: (() => void) | undefinedsetOpen(const setOpen: (value: React.SetStateAction<"invite" | "join" | undefined>) => voidundefined)}var undefined/>)}</div>React.JSX.IntrinsicElements.div: React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>);};typeFlow = {type Flow = {flow: Invitation.Flow;code: string;}flow:flow: Invitation.FlowInvitation.import InvitationFlow;interface FlowAn in-flight invitation, host or guest side. Produced by
Identity.share
/
joinandSpace.share
/
join; its lifecycle is then driven through this handle and theService
.
code: string };code: string/** The host's side of an invitation: the code to send, then the authorization code to tell. */export constHost = ({const Host: ({ start, send, }: {start: () => Promise<Flow>;send: string;}) => React.JSX.ElementThe host's side of an invitation: the code to send, then the authorization code to tell.
start,start: () => Promise<Flow>send,send: string}: {start: () =>start: () => Promise<Flow>Promise<interface Promise<T>Represents the completion of an asynchronous operation
Flow>;type Flow = {flow: Invitation.Flow;code: string;}send: string;send: string}) => {const [flow,const flow: Flow | undefinedsetFlow] =const setFlow: React.Dispatch<React.SetStateAction<Flow | undefined>>useState<useState<Flow>(): [Flow | undefined, React.Dispatch<React.SetStateAction<Flow | undefined>>] (+1 overload)Returns a stateful value, and a function to update it.
Flow>();type Flow = {flow: Invitation.Flow;code: string;}const [error,const error: string | undefinedsetError] =const setError: React.Dispatch<React.SetStateAction<string | undefined>>useState<string>();useState<string>(): [string | undefined, React.Dispatch<React.SetStateAction<string | undefined>>] (+1 overload)Returns a stateful value, and a function to update it.
conststarted =const started: React.RefObject<boolean>useRef(false);useRef<boolean>(initialValue: boolean): React.RefObject<boolean> (+2 overloads)useRefreturns a mutable ref object whose.currentproperty is initialized to the passed argument (initialValue). The returned object will persist for the full lifetime of the component.Note that
useRef()is useful for more than therefattribute. It’s handy for keeping any mutable value around similar to how you’d use instance fields in classes.useEffect(() => {function useEffect(effect: React.EffectCallback, deps?: React.DependencyList): voidAccepts a function that contains imperative, possibly effectful code.
if (!started.const started: React.RefObject<boolean>current) {React.RefObject<boolean>.current: booleanThe current value of the ref.
started.const started: React.RefObject<boolean>current = true;React.RefObject<boolean>.current: booleanThe current value of the ref.
start().start: () => Promise<Flow>then(Promise<Flow>.then<void, void>(onfulfilled?: ((value: Flow) => void | PromiseLike<void>) | null | undefined, onrejected?: ((reason: any) => void | PromiseLike<void>) | null | undefined): Promise<void>Attaches callbacks for the resolution and/or rejection of the Promise.
setFlow, (const setFlow: React.Dispatch<React.SetStateAction<Flow | undefined>>error) =>error: anysetError(const setError: (value: React.SetStateAction<string | undefined>) => voidString(var String: StringConstructor(value?: any) => stringAllows manipulation and formatting of text strings and determination and location of substrings within strings.
error)));error: any}}, []);/** The flow's latest event, re-rendering as the handshake moves on. */const {event } =const event: Invitation.Event | undefineduseInvitationFlow(function useInvitationFlow(flow?: Invitation.Flow): InvitationFlowStateTracks one
Invitation.Flow
for rendering, re-rendering on each lifecycle event. Needs no
HaloProvider: a flow's streams and effects carry no service requirement.flow?.const flow: Flow | undefinedflow);flow: Invitation.Flow | undefined -
Send the authorization code. On the guest,
Invitation.authenticatesends the code the host showed. Send calls this.src/app/Invitations.tsx import * asEffect from 'effect/Effect';import Effect2 collapsed linesimportReact, {(alias) namespace Reactimport ReactuseEffect,function useEffect(effect: React.EffectCallback, deps?: React.DependencyList): voidAccepts a function that contains imperative, possibly effectful code.
useRef,function useRef<T>(initialValue: T): React.RefObject<T> (+2 overloads)useRefreturns a mutable ref object whose.currentproperty is initialized to the passed argument (initialValue). The returned object will persist for the full lifetime of the component.Note that
useRef()is useful for more than therefattribute. It’s handy for keeping any mutable value around similar to how you’d use instance fields in classes.useState } from 'react';function useState<S>(initialState: S | (() => S)): [S, React.Dispatch<React.SetStateAction<S>>] (+1 overload)Returns a stateful value, and a function to update it.
import {Invitation,import InvitationSpace } from '@dxos/halo';import Space126 collapsed linesimport {useHaloServices,const useHaloServices: () => Context<HaloServices>Returns the HALO services context. Throws if no
HaloProvider
is above in the tree.
useInvitationFlow } from '@dxos/halo-react';const useInvitationFlow: (flow?: Invitation.Flow) => InvitationFlowStateTracks one
Invitation.Flow
for rendering, re-rendering on each lifecycle event. Needs no
HaloProvider: a flow's streams and effects carry no service requirement.import { typeSpaceId } from '@dxos/keys';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).
import {Copyable,const Copyable: ({ value }: {value: string;}) => React.JSX.ElementPrompt } from './ui.tsx';const Prompt: ({ label, action, initial, multiline, compact, autoFocus, onSubmit, }: {label: string;action: string;initial?: string;multiline?: boolean;compact?: boolean;autoFocus?: boolean;onSubmit: (value: string) => Promise<unknown>;}) => React.JSX.ElementA field and a button, which shows the error if
onSubmitfails./** Invite someone to the current space, or join a space with a code. */export constInviteBar = ({const InviteBar: ({ spaceId }: {spaceId?: SpaceId;}) => React.JSX.ElementInvite someone to the current space, or join a space with a code.
spaceId }: {spaceId: SpaceId | undefinedspaceId?:spaceId?: SpaceId | undefinedSpaceId }) => {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).
constservices =const services: Context<HaloServices>useHaloServices();function useHaloServices(): Context<HaloServices>Returns the HALO services context. Throws if no
HaloProvider
is above in the tree.
const [open,const open: "invite" | "join" | undefinedsetOpen] =const setOpen: React.Dispatch<React.SetStateAction<"invite" | "join" | undefined>>useState<'invite' | 'join'>();useState<"invite" | "join">(): ["invite" | "join" | undefined, React.Dispatch<React.SetStateAction<"invite" | "join" | undefined>>] (+1 overload)Returns a stateful value, and a function to update it.
/** Starts an invitation to the space, and returns the flow and the code to send. */consthandleInvite = (const handleInvite: (spaceId: SpaceId) => Promise<{flow: Invitation.Flow;code: string;}>Starts an invitation to the space, and returns the flow and the code to send.
spaceId:spaceId: SpaceIdSpaceId) =>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).
Effect.import Effectgen(function* () {const gen: <Effect.Effect<Invitation.Flow, SpaceError, Space.Service> | Effect.Effect<string, InvitationError, never>, {flow: Invitation.Flow;code: string;}>(f: () => Generator<Effect.Effect<Invitation.Flow, SpaceError, Space.Service> | Effect.Effect<string, InvitationError, never>, {flow: Invitation.Flow;code: string;}, never>) => Effect.Effect<{flow: Invitation.Flow;code: string;}, 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/awaitbut keep errors, requirements, and interruption in the Effect type. You canyield*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 + 1const 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* fetchTransactionAmountconst discountRate = yield* fetchDiscountRateconst discountedAmount = yield* applyDiscount(transactionAmount,discountRate)const finalAmount = addServiceCharge(discountedAmount)return `Final amount to charge: ${finalAmount}`})await Effect.runPromise(program) // => "Final amount to charge: 96"constflow = yield*const flow: Invitation.FlowSpace.import Spaceshare(const share: (id: SpaceId, options?: Invitation.ShareOptions) => Effect.Effect<Invitation.Flow, SpaceError, Space.Service>Initiate a space invitation (requires
Service
).
spaceId, {spaceId: SpaceIdauthMethod: 'shared-secret',authMethod?: Invitation.AuthMethod | undefined});constcode = yield*const code: stringInvitation.import Invitationcode(const code: (flow: Invitation.Flow) => Effect.Effect<string, InvitationError>Encoded, shareable code for a flow.
flow);const flow: Invitation.Flowreturn {flow,flow: Invitation.Flowcode };code: string}).pipe(Pipeable.pipe<Effect.Effect<{flow: Invitation.Flow;code: string;}, SpaceError | InvitationError, Space.Service>, Effect.Effect<{flow: Invitation.Flow;code: string;}, SpaceError | InvitationError, never>, Promise<{flow: Invitation.Flow;code: string;}>>(this: Effect.Effect<...>, ab: (_: Effect.Effect<{flow: Invitation.Flow;code: string;}, SpaceError | InvitationError, Space.Service>) => Effect.Effect<...>, bc: (_: Effect.Effect<...>) => Promise<...>): Promise<...> (+21 overloads)Effect.import Effectprovide(const provide: <HaloServices>(context: Context<HaloServices>) => <A, E, R>(self: Effect.Effect<A, E, R>) => Effect.Effect<A, E, Exclude<R, HaloServices>> (+5 overloads)Provides dependencies to an effect using layers or a context. Use
options.localto build the layer every time; by default, layers are shared between provide calls.Example (Providing dependencies with a layer)
import { Context, Effect, Layer } from "effect"interface Database {readonly query: (sql: string) => Effect.Effect<string>}const Database = Context.Service<Database>("Database")const DatabaseLayer = Layer.succeed(Database)({query: Effect.fn("Database.query")((sql: string) => Effect.succeed(`Result for: ${sql}`))})const program = Effect.gen(function*() {const db = yield* Databasereturn yield* db.query("SELECT * FROM users")})const provided = Effect.provide(program, DatabaseLayer)await Effect.runPromise(provided) // => "Result for: SELECT * FROM users"services),const services: Context<HaloServices>Effect.import EffectrunPromise);const runPromise: <A, E>(effect: Effect.Effect<A, E>, options?: Effect.RunOptions | undefined) => Promise<A>Executes an effect and returns the result as a
Promise.When to use
Use when you need to execute an effect and work with the result using
Promisesyntax, typically for compatibility with other promise-based code.If the effect succeeds, the promise will resolve with the result. If the effect fails, the promise will reject with an error.
Example (Running a successful effect as a Promise)
import { Effect } from "effect"await Effect.runPromise(Effect.succeed(1)) // => 1Example (Running effects as promises)
//Example: Handling a Failing Effect as a Rejected Promiseimport { Effect } from "effect"const output: Array<unknown> = []await Effect.runPromise(Effect.fail("my error")).catch(() => {void output.push("rejected")})output // => ["rejected"]/** Redeems the invitation `code`, and returns the flow. */consthandleJoin = (const handleJoin: (code: string) => Promise<Invitation.Flow>Redeems the invitation
code, and returns the flow.code: string) =>code: stringSpace.import Spacejoin(const join: (code: string) => Effect.Effect<Invitation.Flow, SpaceError, Space.Service>Redeem a space-invitation code (requires
Service
).
code).code: stringpipe(Pipeable.pipe<Effect.Effect<Invitation.Flow, SpaceError, Space.Service>, Effect.Effect<Invitation.Flow, SpaceError, never>, Promise<Invitation.Flow>>(this: Effect.Effect<...>, ab: (_: Effect.Effect<Invitation.Flow, SpaceError, Space.Service>) => Effect.Effect<Invitation.Flow, SpaceError, never>, bc: (_: Effect.Effect<Invitation.Flow, SpaceError, never>) => Promise<...>): Promise<...> (+21 overloads)Effect.import Effectprovide(const provide: <HaloServices>(context: Context<HaloServices>) => <A, E, R>(self: Effect.Effect<A, E, R>) => Effect.Effect<A, E, Exclude<R, HaloServices>> (+5 overloads)Provides dependencies to an effect using layers or a context. Use
options.localto build the layer every time; by default, layers are shared between provide calls.Example (Providing dependencies with a layer)
import { Context, Effect, Layer } from "effect"interface Database {readonly query: (sql: string) => Effect.Effect<string>}const Database = Context.Service<Database>("Database")const DatabaseLayer = Layer.succeed(Database)({query: Effect.fn("Database.query")((sql: string) => Effect.succeed(`Result for: ${sql}`))})const program = Effect.gen(function*() {const db = yield* Databasereturn yield* db.query("SELECT * FROM users")})const provided = Effect.provide(program, DatabaseLayer)await Effect.runPromise(provided) // => "Result for: SELECT * FROM users"services),const services: Context<HaloServices>Effect.import EffectrunPromise);const runPromise: <A, E>(effect: Effect.Effect<A, E>, options?: Effect.RunOptions | undefined) => Promise<A>Executes an effect and returns the result as a
Promise.When to use
Use when you need to execute an effect and work with the result using
Promisesyntax, typically for compatibility with other promise-based code.If the effect succeeds, the promise will resolve with the result. If the effect fails, the promise will reject with an error.
Example (Running a successful effect as a Promise)
import { Effect } from "effect"await Effect.runPromise(Effect.succeed(1)) // => 1Example (Running effects as promises)
//Example: Handling a Failing Effect as a Rejected Promiseimport { Effect } from "effect"const output: Array<unknown> = []await Effect.runPromise(Effect.fail("my error")).catch(() => {void output.push("rejected")})output // => ["rejected"]return (<divReact.JSX.IntrinsicElements.div: React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>className='cb-invite'>React.HTMLAttributes<HTMLDivElement>.className?: string | undefined<divReact.JSX.IntrinsicElements.div: React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>className='cb-row'>React.HTMLAttributes<HTMLDivElement>.className?: string | undefined<buttonReact.JSX.IntrinsicElements.button: React.DetailedHTMLProps<React.ButtonHTMLAttributes<HTMLButtonElement>, HTMLButtonElement>className='cb-button'React.HTMLAttributes<T>.className?: string | undefineddisabled={!React.ButtonHTMLAttributes<HTMLButtonElement>.disabled?: boolean | undefinedspaceId}spaceId: SpaceId | undefinedaria-pressed={React.AriaAttributes["aria-pressed"]?: boolean | "false" | "mixed" | "true" | undefinedIndicates the current "pressed" state of toggle buttons.
open === 'invite'}const open: "invite" | "join" | undefinedonClick={() =>React.DOMAttributes<HTMLButtonElement>.onClick?: React.MouseEventHandler<HTMLButtonElement> | undefinedsetOpen(const setOpen: (value: React.SetStateAction<"invite" | "join" | undefined>) => voidopen === 'invite' ?const open: "invite" | "join" | undefinedundefined : 'invite')}var undefined>Invite</button>React.JSX.IntrinsicElements.button: React.DetailedHTMLProps<React.ButtonHTMLAttributes<HTMLButtonElement>, HTMLButtonElement><buttonReact.JSX.IntrinsicElements.button: React.DetailedHTMLProps<React.ButtonHTMLAttributes<HTMLButtonElement>, HTMLButtonElement>className='cb-button'React.HTMLAttributes<T>.className?: string | undefinedaria-pressed={React.AriaAttributes["aria-pressed"]?: boolean | "false" | "mixed" | "true" | undefinedIndicates the current "pressed" state of toggle buttons.
open === 'join'}const open: "invite" | "join" | undefinedonClick={() =>React.DOMAttributes<HTMLButtonElement>.onClick?: React.MouseEventHandler<HTMLButtonElement> | undefinedsetOpen(const setOpen: (value: React.SetStateAction<"invite" | "join" | undefined>) => voidopen === 'join' ?const open: "invite" | "join" | undefinedundefined : 'join')}var undefined>Join</button>React.JSX.IntrinsicElements.button: React.DetailedHTMLProps<React.ButtonHTMLAttributes<HTMLButtonElement>, HTMLButtonElement></div>React.JSX.IntrinsicElements.div: React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>{open === 'invite' &&const open: "invite" | "join" | undefinedspaceId && (spaceId: SpaceId | undefined<Hostconst Host: ({ start, send, }: {start: () => Promise<Flow>;send: string;}) => React.JSX.ElementThe host's side of an invitation: the code to send, then the authorization code to tell.
key={React.Attributes.key?: React.Key | null | undefinedspaceId}spaceId: SpaceIdstart={() =>start: () => Promise<Flow>handleInvite(const handleInvite: (spaceId: SpaceId) => Promise<{flow: Invitation.Flow;code: string;}>Starts an invitation to the space, and returns the flow and the code to send.
spaceId)}spaceId: SpaceIdsend='Send this invitation code to the person you invite:'send: string/>)}{open === 'join' && (const open: "invite" | "join" | undefined<Guestconst Guest: ({ label, join, onDone, }: {label: string;join: (code: string) => Promise<Invitation.Flow>;onDone?: () => void;}) => React.JSX.ElementThe guest's side of an invitation: paste the code, then type the authorization code.
label='Invitation code'label: stringjoin={join: (code: string) => Promise<Invitation.Flow>handleJoin}const handleJoin: (code: string) => Promise<Invitation.Flow>Redeems the invitation
code, and returns the flow.onDone={() =>onDone?: (() => void) | undefinedsetOpen(const setOpen: (value: React.SetStateAction<"invite" | "join" | undefined>) => voidundefined)}var undefined/>)}</div>React.JSX.IntrinsicElements.div: React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>);};typeFlow = {type Flow = {flow: Invitation.Flow;code: string;}flow:flow: Invitation.FlowInvitation.import InvitationFlow;interface FlowAn in-flight invitation, host or guest side. Produced by
Identity.share
/
joinandSpace.share
/
join; its lifecycle is then driven through this handle and theService
.
code: string };code: string/** The host's side of an invitation: the code to send, then the authorization code to tell. */export constHost = ({const Host: ({ start, send, }: {start: () => Promise<Flow>;send: string;}) => React.JSX.ElementThe host's side of an invitation: the code to send, then the authorization code to tell.
start,start: () => Promise<Flow>send,send: string}: {start: () =>start: () => Promise<Flow>Promise<interface Promise<T>Represents the completion of an asynchronous operation
Flow>;type Flow = {flow: Invitation.Flow;code: string;}send: string;send: string}) => {const [flow,const flow: Flow | undefinedsetFlow] =const setFlow: React.Dispatch<React.SetStateAction<Flow | undefined>>useState<useState<Flow>(): [Flow | undefined, React.Dispatch<React.SetStateAction<Flow | undefined>>] (+1 overload)Returns a stateful value, and a function to update it.
Flow>();type Flow = {flow: Invitation.Flow;code: string;}const [error,const error: string | undefinedsetError] =const setError: React.Dispatch<React.SetStateAction<string | undefined>>useState<string>();useState<string>(): [string | undefined, React.Dispatch<React.SetStateAction<string | undefined>>] (+1 overload)Returns a stateful value, and a function to update it.
conststarted =const started: React.RefObject<boolean>useRef(false);useRef<boolean>(initialValue: boolean): React.RefObject<boolean> (+2 overloads)useRefreturns a mutable ref object whose.currentproperty is initialized to the passed argument (initialValue). The returned object will persist for the full lifetime of the component.Note that
useRef()is useful for more than therefattribute. It’s handy for keeping any mutable value around similar to how you’d use instance fields in classes.useEffect(() => {function useEffect(effect: React.EffectCallback, deps?: React.DependencyList): voidAccepts a function that contains imperative, possibly effectful code.
if (!started.const started: React.RefObject<boolean>current) {React.RefObject<boolean>.current: booleanThe current value of the ref.
started.const started: React.RefObject<boolean>current = true;React.RefObject<boolean>.current: booleanThe current value of the ref.
start().start: () => Promise<Flow>then(Promise<Flow>.then<void, void>(onfulfilled?: ((value: Flow) => void | PromiseLike<void>) | null | undefined, onrejected?: ((reason: any) => void | PromiseLike<void>) | null | undefined): Promise<void>Attaches callbacks for the resolution and/or rejection of the Promise.
setFlow, (const setFlow: React.Dispatch<React.SetStateAction<Flow | undefined>>error) =>error: anysetError(const setError: (value: React.SetStateAction<string | undefined>) => voidString(var String: StringConstructor(value?: any) => stringAllows manipulation and formatting of text strings and determination and location of substrings within strings.
error)));error: any}}, []);/** The flow's latest event, re-rendering as the handshake moves on. */const {event } =const event: Invitation.Event | undefineduseInvitationFlow(function useInvitationFlow(flow?: Invitation.Flow): InvitationFlowStateTracks one
Invitation.Flow
for rendering, re-rendering on each lifecycle event. Needs no
HaloProvider: a flow's streams and effects carry no service requirement.flow?.const flow: Flow | undefinedflow);flow: Invitation.Flow | undefinedif (error) {const error: string | undefinedreturn <pReact.JSX.IntrinsicElements.p: React.DetailedHTMLProps<React.HTMLAttributes<HTMLParagraphElement>, HTMLParagraphElement>className='cb-error'>{React.HTMLAttributes<T>.className?: string | undefinederror}</const error: stringp>;React.JSX.IntrinsicElements.p: React.DetailedHTMLProps<React.HTMLAttributes<HTMLParagraphElement>, HTMLParagraphElement>}if (!flow) {const flow: Flow | undefinedreturn <pReact.JSX.IntrinsicElements.p: React.DetailedHTMLProps<React.HTMLAttributes<HTMLParagraphElement>, HTMLParagraphElement>className='cb-muted'>Starting the invitation…</React.HTMLAttributes<T>.className?: string | undefinedp>;React.JSX.IntrinsicElements.p: React.DetailedHTMLProps<React.HTMLAttributes<HTMLParagraphElement>, HTMLParagraphElement>}return (<divReact.JSX.IntrinsicElements.div: React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>className='cb-panel'>React.HTMLAttributes<HTMLDivElement>.className?: string | undefined<p>{React.JSX.IntrinsicElements.p: React.DetailedHTMLProps<React.HTMLAttributes<HTMLParagraphElement>, HTMLParagraphElement>send}</send: stringp>React.JSX.IntrinsicElements.p: React.DetailedHTMLProps<React.HTMLAttributes<HTMLParagraphElement>, HTMLParagraphElement><Copyableconst Copyable: ({ value }: {value: string;}) => React.JSX.Elementvalue={value: stringflow.const flow: Flowcode} />code: string<Statusconst Status: ({ event, host, }: {event?: Invitation.Event;host?: boolean;}) => React.JSX.Element | nullevent={event?: Invitation.Event | undefinedevent}const event: Invitation.Event | undefinedhost />host?: boolean | undefined</div>React.JSX.IntrinsicElements.div: React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>);};/** The guest's side of an invitation: paste the code, then type the authorization code. */export constGuest = ({const Guest: ({ label, join, onDone, }: {label: string;join: (code: string) => Promise<Invitation.Flow>;onDone?: () => void;}) => React.JSX.ElementThe guest's side of an invitation: paste the code, then type the authorization code.
label,label: stringjoin,join: (code: string) => Promise<Invitation.Flow>onDone,onDone: (() => void) | undefined}: {label: string;label: stringjoin: (join: (code: string) => Promise<Invitation.Flow>code: string) =>code: stringPromise<interface Promise<T>Represents the completion of an asynchronous operation
Invitation.import InvitationFlow>;interface FlowAn in-flight invitation, host or guest side. Produced by
Identity.share
/
joinandSpace.share
/
join; its lifecycle is then driven through this handle and theService
.
onDone?: () => void;onDone?: (() => void) | undefined}) => {constservices =const services: Context<HaloServices>useHaloServices();function useHaloServices(): Context<HaloServices>Returns the HALO services context. Throws if no
HaloProvider
is above in the tree.
const [flow,const flow: Invitation.Flow | undefinedsetFlow] =const setFlow: React.Dispatch<React.SetStateAction<Invitation.Flow | undefined>>useState<useState<Invitation.Flow>(): [Invitation.Flow | undefined, React.Dispatch<React.SetStateAction<Invitation.Flow | undefined>>] (+1 overload)Returns a stateful value, and a function to update it.
Invitation.import InvitationFlow>();interface FlowAn in-flight invitation, host or guest side. Produced by
Identity.share
/
joinandSpace.share
/
join; its lifecycle is then driven through this handle and theService
.
const {event } =const event: Invitation.Event | undefineduseInvitationFlow(function useInvitationFlow(flow?: Invitation.Flow): InvitationFlowStateTracks one
Invitation.Flow
for rendering, re-rendering on each lifecycle event. Needs no
HaloProvider: a flow's streams and effects carry no service requirement.flow);const flow: Invitation.Flow | undefineduseEffect(() => {function useEffect(effect: React.EffectCallback, deps?: React.DependencyList): voidAccepts a function that contains imperative, possibly effectful code.
if (event?.const event: Invitation.Event | undefined_tag === 'success' &&_tag: "connecting" | "connected" | "readyForAuthentication" | "authenticating" | "success" | "cancelled" | "error" | undefinedonDone) {onDone: (() => void) | undefinedconsttimeout =const timeout: NodeJS.TimeoutsetTimeout(function setTimeout<[]>(callback: () => void, ms?: number): NodeJS.Timeout (+2 overloads)Schedules execution of a one-time
callbackafterdelaymilliseconds.The
callbackwill likely not be invoked in preciselydelaymilliseconds. Node.js makes no guarantees about the exact timing of when callbacks will fire, nor of their ordering. The callback will be called as close as possible to the time specified.When
delayis larger than2147483647or less than1, thedelaywill be set to1. Non-integer delays are truncated to an integer.If
callbackis not a function, aTypeErrorwill be thrown.This method has a custom variant for promises that is available using
timersPromises.setTimeout().onDone, 1500);onDone: () => voidreturn () =>clearTimeout(function clearTimeout(timeoutId: NodeJS.Timeout | string | number | undefined): void (+1 overload)Cancels a
Timeoutobject created bysetTimeout().timeout);const timeout: NodeJS.Timeout}}, [event]);const event: Invitation.Event | undefinedif (!flow) {const flow: Invitation.Flow | undefinedreturn (<Promptconst Prompt: ({ label, action, initial, multiline, compact, autoFocus, onSubmit, }: {label: string;action: string;initial?: string;multiline?: boolean;compact?: boolean;autoFocus?: boolean;onSubmit: (value: string) => Promise<unknown>;}) => React.JSX.ElementA field and a button, which shows the error if
onSubmitfails.label={label: stringlabel}label: stringaction='Join'action: stringonSubmit={(onSubmit: (value: string) => Promise<unknown>code) =>code: stringjoin(join: (code: string) => Promise<Invitation.Flow>code.code: stringtrim()).String.trim(): stringRemoves the leading and trailing white space and line terminator characters from a string.
then(Promise<Flow>.then<void, unknown>(onfulfilled?: ((value: Invitation.Flow) => void | PromiseLike<void>) | null | undefined, onrejected?: ((reason: any) => unknown) | null | undefined): Promise<unknown>Attaches callbacks for the resolution and/or rejection of the Promise.
setFlow)}const setFlow: React.Dispatch<React.SetStateAction<Invitation.Flow | undefined>>/>);}/** Sends the authorization code the host shows. */consthandleAuthenticate = (const handleAuthenticate: (authCode: string) => Promise<void>Sends the authorization code the host shows.
authCode: string) =>authCode: stringInvitation.import Invitationauthenticate(const authenticate: (flow: Invitation.Flow, code: string) => Effect.Effect<void, InvitationError>Supply the auth code to a flow awaiting authentication. Flow verbs operate on a
Flow
handle directly and require no service; the active-flow set is queried through
Identity
(device invitations) and
Space
(space invitations).
flow,const flow: Invitation.FlowauthCode.authCode: stringtrim()).String.trim(): stringRemoves the leading and trailing white space and line terminator characters from a string.
pipe(Pipeable.pipe<Effect.Effect<void, InvitationError, never>, Effect.Effect<void, InvitationError, never>, Promise<void>>(this: Effect.Effect<void, InvitationError, never>, ab: (_: Effect.Effect<void, InvitationError, never>) => Effect.Effect<void, InvitationError, never>, bc: (_: Effect.Effect<void, InvitationError, never>) => Promise<void>): Promise<void> (+21 overloads)Effect.import Effectprovide(const provide: <HaloServices>(context: Context<HaloServices>) => <A, E, R>(self: Effect.Effect<A, E, R>) => Effect.Effect<A, E, Exclude<R, HaloServices>> (+5 overloads)Provides dependencies to an effect using layers or a context. Use
options.localto build the layer every time; by default, layers are shared between provide calls.Example (Providing dependencies with a layer)
import { Context, Effect, Layer } from "effect"interface Database {readonly query: (sql: string) => Effect.Effect<string>}const Database = Context.Service<Database>("Database")const DatabaseLayer = Layer.succeed(Database)({query: Effect.fn("Database.query")((sql: string) => Effect.succeed(`Result for: ${sql}`))})const program = Effect.gen(function*() {const db = yield* Databasereturn yield* db.query("SELECT * FROM users")})const provided = Effect.provide(program, DatabaseLayer)await Effect.runPromise(provided) // => "Result for: SELECT * FROM users"services),const services: Context<HaloServices>Effect.import EffectrunPromise,const runPromise: <A, E>(effect: Effect.Effect<A, E>, options?: Effect.RunOptions | undefined) => Promise<A>Executes an effect and returns the result as a
Promise.When to use
Use when you need to execute an effect and work with the result using
Promisesyntax, typically for compatibility with other promise-based code.If the effect succeeds, the promise will resolve with the result. If the effect fails, the promise will reject with an error.
Example (Running a successful effect as a Promise)
import { Effect } from "effect"await Effect.runPromise(Effect.succeed(1)) // => 1Example (Running effects as promises)
//Example: Handling a Failing Effect as a Rejected Promiseimport { Effect } from "effect"const output: Array<unknown> = []await Effect.runPromise(Effect.fail("my error")).catch(() => {void output.push("rejected")})output // => ["rejected"]);
Try it
Section titled “Try it”Alice has Book club; Bob has nothing yet.
- On Alice’s device, press Invite and copy the code.
- On Bob’s, press Join, paste it and press Join.
- Alice’s device now shows an authorization code. Type it into Bob’s and press Send.
Bob’s dropdown now has Book club, and both devices list Alice and Bob under People. Rename one of them and watch the other follow.
What you can do now
Section titled “What you can do now”You can invite someone into a space and join one, with an authorization code. Next, Your contacts gathers everyone you share a space with.