Skip to content

Your profile

By the end of this page the contact book will create your identity with a profile the first time it runs, show your name from it in the corner, and let you change it.

To write the code as you read, open the starter on StackBlitz. It is the finished app with the code from these pages taken out. It runs from the start, and when the app reaches code you have not written yet, a line at the top names the step and its file. Each sample on these pages is titled with the file it goes in, and numbered as that file is.

An identity stands for you. In Keyhive, which HALO is built on, it is a group named by a DID, such as did:halo:BA25QRC2…. Its members are your devices, and the first member is the device that creates it. No server issues the identity; the client makes it on the device, with no network needed.

The identity also has a profile: a display name, and any other data the app wants, such as an avatar. Everyone you share a space with sees it.

The code is Effect. @dxos/halo describes each operation as an Effect or a Stream that needs Identity.Service or Space.Service. @dxos/halo-adapter-client makes both from a client, and @dxos/halo-react hands them to the components through React context, with hooks that follow the streams.

  1. Install the packages. @dxos/client holds the client, @dxos/halo the API these pages use, @dxos/halo-adapter-client the services between them, and @dxos/halo-react the provider for React. Until the next npm release, install the build of main these pages describe, from pkg.pr.new.

    Terminal window
    pnpm add https://pkg.pr.new/dxos/dxos/@dxos/client@20a14b9 https://pkg.pr.new/dxos/dxos/@dxos/halo@20a14b9 https://pkg.pr.new/dxos/dxos/@dxos/halo-adapter-client@20a14b9 https://pkg.pr.new/dxos/dxos/@dxos/halo-react@20a14b9 effect@4.0.4

    The browser setup is the same as ECHO’s; see Installation for the Vite config. The StackBlitz project has it already.

  2. Configure the client. The client keeps the device’s keys and data on the device, and reaches other devices through EDGE, DXOS’s network, which relays invitations and replicates spaces.

    src/app/App.tsx
    import {
    class Config

    Global configuration object. NOTE: Config objects are immutable.

    Config
    } from '@dxos/config';
    9 collapsed lines
    import {
    import Identity
    Identity
    ,
    import Space
    Space
    } from '@dxos/halo';
    import {
    const makeIdentityService: (client: Client) => Context.Service.Shape<typeof Identity.Service>

    Builds the

    HaloIdentity.Service

    implementation over a client's halo proxy.

    makeIdentityService
    ,
    const makeSpaceService: (client: Client) => Context.Service.Shape<typeof Space.Service>

    Builds the

    HaloSpace.Service

    implementation over a client's spaces proxy.

    makeSpaceService
    ,
    } from '@dxos/halo-adapter-client';
    import {
    const HaloProvider: ({ services, children }: HaloProviderProps) => React.JSX.Element

    Provides the HALO services to the hook library. Wrap the application (or the subtree that uses the hooks) in this provider. Replaces ClientProvider for HALO concerns.

    HaloProvider
    , type
    type HaloServices = Identity.Service | Space.Service

    The HALO services, as the tag union carried in an Effect service

    Context.Context

    .

    HaloServices
    } from '@dxos/halo-react';
    import {
    const ContactBook: ({ stage }: ContactBookProps) => React.JSX.Element

    The contact book on one device, with what stage has built. It runs against the HALO services of the HaloProvider above it.

    ContactBook
    } from './ContactBook.tsx';
    /**
    * Keeps the app's keys and data on the device and reaches other devices
    * through DXOS's EDGE network, which relays invitations and replicates spaces.
    */
    const
    const config: Config

    Keeps the app's keys and data on the device and reaches other devices through DXOS's EDGE network, which relays invitations and replicates spaces.

    config
    = new
    new Config(config?: ConfigInit, ...objects: ConfigInit[]): Config

    Creates an immutable instance.

    @constructor

    Config
    ({
    runtime: {
    client: {
    storage: {
    persistent: true;
    };
    edgeFeatures: {
    signaling: true;
    subductionReplicator: true;
    feedReplicator: true;
    };
    };
    services: {
    edge: {
    url: string;
    };
    iceProviders: {
    urls: string;
    }[];
    };
    }
    runtime
    : {
    client: {
    storage: {
    persistent: true;
    };
    edgeFeatures: {
    signaling: true;
    subductionReplicator: true;
    feedReplicator: true;
    };
    }
    client
    : {
    storage: {
    persistent: true;
    }
    storage
    : {
    persistent: true
    persistent
    : true },
    edgeFeatures: {
    signaling: true;
    subductionReplicator: true;
    feedReplicator: true;
    }
    edgeFeatures
    : {
    signaling: true
    signaling
    : true,
    subductionReplicator: true
    subductionReplicator
    : true,
    feedReplicator: true
    feedReplicator
    : true,
    },
    },
    services: {
    edge: {
    url: string;
    };
    iceProviders: {
    urls: string;
    }[];
    }
    services
    : {
    edge: {
    url: string;
    }
    edge
    : {
    url: string
    url
    : 'https://dxos.network/' },
    iceProviders: {
    urls: string;
    }[]
    iceProviders
    : [{
    urls: string
    urls
    : 'https://dxos.network/ice' }],
    },
    },
    });
  3. Start the client. openHalo starts a client and returns it with the HALO services it backs. The app calls it once, at startup, and puts the services in a HaloProvider. Below it, a component gets them with useHaloServices() and runs each piece of HALO code with Effect.provide(services) and Effect.runPromise.

    src/app/App.tsx
    import * as
    import Context
    Context
    from 'effect/Context';
    4 collapsed lines
    import
    (alias) namespace React
    import React
    React
    , {
    function useEffect(effect: React.EffectCallback, deps?: React.DependencyList): void

    Accepts a function that contains imperative, possibly effectful code.

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

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

    @version ― 16.8.0

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

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

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

    @version ― 16.8.0

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

    useState
    } from 'react';
    // `@dxos/keys` reads `Buffer` as it loads, so the globals go in before the client.
    import '@dxos/node-std/globals';
    import {
    class Client

    The Client class encapsulates the core client-side API of DXOS.

    Client
    , type
    (alias) interface ClientServicesProvider
    import ClientServicesProvider

    Provide access to client services definitions and service handler.

    ClientServicesProvider
    } from '@dxos/client';
    1 collapsed line
    import {
    class Config

    Global configuration object. NOTE: Config objects are immutable.

    Config
    } from '@dxos/config';
    import {
    import Identity
    Identity
    ,
    import Space
    Space
    } from '@dxos/halo';
    import {
    const makeIdentityService: (client: Client) => Context.Service.Shape<typeof Identity.Service>

    Builds the

    HaloIdentity.Service

    implementation over a client's halo proxy.

    makeIdentityService
    ,
    const makeSpaceService: (client: Client) => Context.Service.Shape<typeof Space.Service>

    Builds the

    HaloSpace.Service

    implementation over a client's spaces proxy.

    makeSpaceService
    ,
    } from '@dxos/halo-adapter-client';
    25 collapsed lines
    import {
    const HaloProvider: ({ services, children }: HaloProviderProps) => React.JSX.Element

    Provides the HALO services to the hook library. Wrap the application (or the subtree that uses the hooks) in this provider. Replaces ClientProvider for HALO concerns.

    HaloProvider
    , type
    type HaloServices = Identity.Service | Space.Service

    The HALO services, as the tag union carried in an Effect service

    Context.Context

    .

    HaloServices
    } from '@dxos/halo-react';
    import {
    const ContactBook: ({ stage }: ContactBookProps) => React.JSX.Element

    The contact book on one device, with what stage has built. It runs against the HALO services of the HaloProvider above it.

    ContactBook
    } from './ContactBook.tsx';
    /**
    * Keeps the app's keys and data on the device and reaches other devices
    * through DXOS's EDGE network, which relays invitations and replicates spaces.
    */
    const
    const config: Config

    Keeps the app's keys and data on the device and reaches other devices through DXOS's EDGE network, which relays invitations and replicates spaces.

    config
    = new
    new Config(config?: ConfigInit, ...objects: ConfigInit[]): Config

    Creates an immutable instance.

    @constructor

    Config
    ({
    runtime: {
    client: {
    storage: {
    persistent: true;
    };
    edgeFeatures: {
    signaling: true;
    subductionReplicator: true;
    feedReplicator: true;
    };
    };
    services: {
    edge: {
    url: string;
    };
    iceProviders: {
    urls: string;
    }[];
    };
    }
    runtime
    : {
    client: {
    storage: {
    persistent: true;
    };
    edgeFeatures: {
    signaling: true;
    subductionReplicator: true;
    feedReplicator: true;
    };
    }
    client
    : {
    storage: {
    persistent: true;
    }
    storage
    : {
    persistent: true
    persistent
    : true },
    edgeFeatures: {
    signaling: true;
    subductionReplicator: true;
    feedReplicator: true;
    }
    edgeFeatures
    : {
    signaling: true
    signaling
    : true,
    subductionReplicator: true
    subductionReplicator
    : true,
    feedReplicator: true
    feedReplicator
    : true,
    },
    },
    services: {
    edge: {
    url: string;
    };
    iceProviders: {
    urls: string;
    }[];
    }
    services
    : {
    edge: {
    url: string;
    }
    edge
    : {
    url: string
    url
    : 'https://dxos.network/' },
    iceProviders: {
    urls: string;
    }[]
    iceProviders
    : [{
    urls: string
    urls
    : 'https://dxos.network/ice' }],
    },
    },
    });
    /** Starts a client, and returns it with the HALO services it backs. */
    export const
    const openHalo: (clientServices?: ClientServicesProvider) => Promise<{
    client: Client;
    services: Context.Context<Identity.Service | Space.Service>;
    }>

    Starts a client, and returns it with the HALO services it backs.

    openHalo
    = async (
    clientServices: ClientServicesProvider | undefined
    clientServices
    ?:
    (alias) interface ClientServicesProvider
    import ClientServicesProvider

    Provide access to client services definitions and service handler.

    ClientServicesProvider
    ) => {
    const
    const client: Client
    client
    = new
    new Client(options?: ClientOptions): Client

    The Client class encapsulates the core client-side API of DXOS.

    Client
    (
    clientServices: ClientServicesProvider | undefined
    clientServices
    ? {
    services?: MaybePromise<ClientServicesProvider> | undefined

    Custom services provider.

    services
    :
    clientServices: ClientServicesProvider
    clientServices
    }
    : {
    config?: Config | undefined

    Client configuration object.

    config
    ,
    // Keeps the device's keys and data in the browser's private file system.
    createOpfsWorker?: (() => Worker) | undefined

    When running in the host mode, a factory to create the worker for OPFS sqlite database.

    createOpfsWorker
    : () =>
    new
    var Worker: new (scriptURL: string | URL, options?: WorkerOptions) => Worker

    The Worker interface of the Web Workers API represents a background task that can be created via script, which can send messages back to its creator.

    MDN Reference

    Worker
    (new
    var URL: new (url: string | URL, base?: string | URL) => URL

    The URL interface is used to parse, construct, normalize, and encode URLs. It works by providing properties which allow you to easily read and modify the components of a URL.

    MDN Reference

    URL class is a global reference for import { URL } from 'url' https://nodejs.org/api/url.html#the-whatwg-url-api

    @since ― v10.0.0

    URL
    ('@dxos/client/opfs-worker', import.

    The type of import.meta.

    If you need to declare that a given property exists on import.meta, this type may be augmented via interface merging.

    meta
    .
    ImportMeta.url: string

    The absolute file: URL of the module.

    url
    ), {
    WorkerOptions.type?: WorkerType | undefined
    type
    : 'module',
    }),
    },
    );
    await
    const client: Client
    client
    .
    Client.initialize(): Promise<Client>

    Initializes internal resources in an idempotent way. Required before using the Client instance.

    initialize
    ();
    const
    const services: Context.Context<Identity.Service | Space.Service>
    services
    =
    import Context
    Context
    .
    const empty: () => Context.Context<never>

    Returns an empty Context.

    Example (Creating an empty context)

    import { Context } from "effect"
    Context.empty().mapUnsafe.size // => 0

    @stability ― stable

    @category ― constructors

    @since ― 2.0.0

    empty
    ().
    Pipeable.pipe<Context.Context<never>, Context.Context<Identity.Service>, Context.Context<Identity.Service | Space.Service>>(this: Context.Context<never>, ab: (_: Context.Context<never>) => Context.Context<Identity.Service>, bc: (_: Context.Context<Identity.Service>) => Context.Context<Identity.Service | Space.Service>): Context.Context<Identity.Service | Space.Service> (+21 overloads)
    pipe
    (
    import Context
    Context
    .
    const add: <Identity.Service, Identity.ServiceApi>(key: Context.Key<Identity.Service, Identity.ServiceApi>, service: Identity.ServiceApi) => <Services>(self: Context.Context<Services>) => Context.Context<Identity.Service | Services> (+1 overload)

    Adds a service to a given Context.

    When to use

    Use when you need to store a known service value in a Context.

    Details

    If the context already contains the same service key, the new service replaces the previous one.

    Example (Adding a service to a context)

    import { Context, pipe } from "effect"
    const Port = Context.Service<{ PORT: number }>("Port")
    const Timeout = Context.Service<{ TIMEOUT: number }>("Timeout")
    const someContext = Context.make(Port, { PORT: 8080 })
    const context = pipe(
    someContext,
    Context.add(Timeout, { TIMEOUT: 5000 })
    )
    const values = [Context.get(context, Port).PORT, Context.get(context, Timeout).TIMEOUT]
    values // => [8080, 5000]

    @see ― addOrOmit for adding or removing a service from an Option

    @stability ― stable

    @category ― combining

    @since ― 2.0.0

    add
    (
    import Identity
    Identity
    .
    class Service
    Service
    ,
    function makeIdentityService(client: Client): Context.Service.Shape<typeof Identity.Service>

    Builds the

    HaloIdentity.Service

    implementation over a client's halo proxy.

    makeIdentityService
    (
    const client: Client
    client
    )),
    import Context
    Context
    .
    const add: <Space.Service, Space.ServiceApi>(key: Context.Key<Space.Service, Space.ServiceApi>, service: Space.ServiceApi) => <Services>(self: Context.Context<Services>) => Context.Context<Space.Service | Services> (+1 overload)

    Adds a service to a given Context.

    When to use

    Use when you need to store a known service value in a Context.

    Details

    If the context already contains the same service key, the new service replaces the previous one.

    Example (Adding a service to a context)

    import { Context, pipe } from "effect"
    const Port = Context.Service<{ PORT: number }>("Port")
    const Timeout = Context.Service<{ TIMEOUT: number }>("Timeout")
    const someContext = Context.make(Port, { PORT: 8080 })
    const context = pipe(
    someContext,
    Context.add(Timeout, { TIMEOUT: 5000 })
    )
    const values = [Context.get(context, Port).PORT, Context.get(context, Timeout).TIMEOUT]
    values // => [8080, 5000]

    @see ― addOrOmit for adding or removing a service from an Option

    @stability ― stable

    @category ― combining

    @since ― 2.0.0

    add
    (
    import Space
    Space
    .
    class Service
    Service
    ,
    function makeSpaceService(client: Client): Context.Service.Shape<typeof Space.Service>

    Builds the

    HaloSpace.Service

    implementation over a client's spaces proxy.

    makeSpaceService
    (
    const client: Client
    client
    )),
    );
    return {
    client: Client
    client
    ,
    services: Context.Context<Identity.Service | Space.Service>
    services
    };
    };
  4. Create your identity. Identity.create makes the identity, with this device as its first member, and gives its profile your name. Start on the welcome screen calls this.

    src/app/Welcome.tsx
    import * as
    import Effect
    Effect
    from 'effect/Effect';
    2 collapsed lines
    import
    (alias) namespace React
    import React
    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.

    @version ― 16.8.0

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

    useState
    } from 'react';
    import {
    import Identity
    Identity
    } from '@dxos/halo';
    17 collapsed lines
    import {
    const useHaloServices: () => Context<HaloServices>

    Returns the HALO services context. Throws if no

    HaloProvider

    is above in the tree.

    useHaloServices
    } from '@dxos/halo-react';
    import { type
    type Stage = "name" | "spaces" | "invite" | "contacts" | "devices" | "recovery"

    How far a page of the learning path has built the app, in the order the path builds it.

    Stage
    ,
    const reached: (stage: Stage, at: Stage) => boolean

    Whether stage includes what at adds.

    reached
    } from '../stage.ts';
    import {
    const Guest: ({ label, join, onDone, }: {
    label: string;
    join: (code: string) => Promise<Flow>;
    onDone?: () => void;
    }) => React.JSX.Element

    The guest's side of an invitation: paste the code, then type the authorization code.

    Guest
    } from './Invitations.tsx';
    import {
    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.Element

    A field and a button, which shows the error if onSubmit fails.

    Prompt
    } from './ui.tsx';
    /** The device before it has an identity: start one, join one from another device, or recover one. */
    export const
    const Welcome: ({ stage }: {
    stage: Stage;
    }) => React.JSX.Element

    The device before it has an identity: start one, join one from another device, or recover one.

    Welcome
    = ({
    stage: Stage
    stage
    }: {
    stage: Stage
    stage
    :
    type Stage = "name" | "spaces" | "invite" | "contacts" | "devices" | "recovery"

    How far a page of the learning path has built the app, in the order the path builds it.

    Stage
    }) => {
    const
    const services: Context<HaloServices>
    services
    =
    function useHaloServices(): Context<HaloServices>

    Returns the HALO services context. Throws if no

    HaloProvider

    is above in the tree.

    useHaloServices
    ();
    const [
    const mode: "new" | "device" | "recover"
    mode
    ,
    const setMode: React.Dispatch<React.SetStateAction<"new" | "device" | "recover">>
    setMode
    ] =
    useState<"new" | "device" | "recover">(initialState: "new" | "device" | "recover" | (() => "new" | "device" | "recover")): ["new" | "device" | "recover", React.Dispatch<React.SetStateAction<"new" | "device" | "recover">>] (+1 overload)

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

    @version ― 16.8.0

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

    useState
    <'new' | 'device' | 'recover'>('new');
    // Focusing the first field on load would scroll the page to the embed.
    const [
    const picked: boolean
    picked
    ,
    const setPicked: React.Dispatch<React.SetStateAction<boolean>>
    setPicked
    ] =
    useState<boolean>(initialState: boolean | (() => boolean)): [boolean, React.Dispatch<React.SetStateAction<boolean>>] (+1 overload)

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

    @version ― 16.8.0

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

    useState
    (false);
    const
    const pick: (next: "new" | "device" | "recover") => void
    pick
    = (
    next: "new" | "device" | "recover"
    next
    : typeof
    const mode: "new" | "device" | "recover"
    mode
    ) => {
    const setPicked: (value: React.SetStateAction<boolean>) => void
    setPicked
    (true);
    const setMode: (value: React.SetStateAction<"new" | "device" | "recover">) => void
    setMode
    (
    next: "new" | "device" | "recover"
    next
    );
    };
    /** Creates the identity, with this device as its first member. */
    const
    const handleStart: (name: string) => Promise<{
    readonly did: IdentityDid;
    readonly displayName?: string | undefined;
    readonly identityKey?: string | undefined;
    readonly data?: {
    readonly [x: string]: any;
    } | undefined;
    }>

    Creates the identity, with this device as its first member.

    handleStart
    = (
    name: string
    name
    : string) =>
    import Identity
    Identity
    .
    const create: (options?: {
    displayName?: string;
    data?: Record<string, unknown>;
    deviceLabel?: string;
    }) => Effect.Effect<Identity.Info, IdentityError, Identity.Service>

    Create the local identity (requires

    Service

    ).

    create
    ({
    displayName?: string | undefined
    displayName
    :
    name: string
    name
    }).
    Pipeable.pipe<Effect.Effect<{
    readonly did: IdentityDid;
    readonly displayName?: string | undefined;
    readonly identityKey?: string | undefined;
    readonly data?: {
    readonly [x: string]: any;
    } | undefined;
    }, IdentityError, Identity.Service>, Effect.Effect<{
    readonly did: IdentityDid;
    readonly displayName?: string | undefined;
    readonly identityKey?: string | undefined;
    readonly data?: {
    readonly [x: string]: any;
    } | undefined;
    }, IdentityError, never>, Promise<{
    readonly did: IdentityDid;
    readonly displayName?: string | undefined;
    readonly identityKey?: string | undefined;
    readonly data?: {
    readonly [x: string]: any;
    } | undefined;
    }>>(this: Effect.Effect<...>, ab: (_: Effect.Effect<...>) => Effect.Effect<...>, bc: (_: Effect.Effect<...>) => Promise<...>): Promise<...> (+21 overloads)
    pipe
    (
    import Effect
    Effect
    .
    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.local to 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* Database
    return yield* db.query("SELECT * FROM users")
    })
    const provided = Effect.provide(program, DatabaseLayer)
    await Effect.runPromise(provided) // => "Result for: SELECT * FROM users"

    @stability ― stable

    @category ― providing services

    @since ― 2.0.0

    provide
    (
    const services: Context<HaloServices>
    services
    ),
    import Effect
    Effect
    .
    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 Promise syntax, 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)) // => 1

    Example (Running effects as promises)

    //Example: Handling a Failing Effect as a Rejected Promise
    import { Effect } from "effect"
    const output: Array<unknown> = []
    await Effect.runPromise(Effect.fail("my error")).catch(() => {
    void output.push("rejected")
    })
    output // => ["rejected"]

    @see ― runPromiseExit for a version that returns an Exit type instead of rejecting.

    @stability ― stable

    @category ― running

    @since ― 2.0.0

    runPromise
    ,
    );
  5. Follow your identity. useIdentity returns the identity, or undefined before there is one, and re-renders the component on every change. It follows Identity.identity, a stream of the identity. The contact book reads it to show either the welcome screen or your name.

    src/app/ContactBook.tsx
    import {
    const useIdentity: () => Identity.Info | undefined

    Returns the local identity, or undefined if none exists. Reactive: re-renders when the identity is created or its profile changes. Replaces @dxos/react-client's useIdentity.

    useIdentity
    ,
    const useSpaces: ({ all }?: UseSpacesOptions) => readonly Info[]

    Returns the spaces known to the local identity. By default only ready spaces are returned; pass { all: true } for every space. Reactive. Replaces @dxos/react-client's useSpaces.

    useSpaces
    } from '@dxos/halo-react';
    21 collapsed lines
    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';
    import { type
    type Stage = "name" | "spaces" | "invite" | "contacts" | "devices" | "recovery"

    How far a page of the learning path has built the app, in the order the path builds it.

    Stage
    ,
    const reached: (stage: Stage, at: Stage) => boolean

    Whether stage includes what at adds.

    reached
    } from '../stage.ts';
    import {
    const Contacts: () => React.JSX.Element

    Everyone you share a space with, once each, with the spaces you share.

    Contacts
    ,
    const Members: ({ spaceId }: {
    spaceId: SpaceId;
    }) => React.JSX.Element

    The members of one space, before the app lists contacts.

    Members
    } from './Contacts.tsx';
    import {
    const Devices: () => React.JSX.Element

    Your identity's devices, and a way to add another.

    Devices
    } from './Devices.tsx';
    import {
    const InviteBar: ({ spaceId }: {
    spaceId?: SpaceId;
    }) => React.JSX.Element

    Invite someone to the current space, or join a space with a code.

    InviteBar
    } from './Invitations.tsx';
    import {
    const ProfileChip: ({ name }: {
    name?: string;
    }) => React.JSX.Element

    Your name, which you click to change.

    ProfileChip
    } from './Profile.tsx';
    import {
    const Recovery: () => React.JSX.Element

    Your recovery codes: make one, see the ones that work, and revoke one.

    Recovery
    } from './Recovery.tsx';
    import {
    const SpacePicker: ({ spaces, current, onSelect, }: {
    spaces: readonly Info[];
    current?: SpaceId;
    onSelect: (id: SpaceId) => void;
    }) => React.JSX.Element

    Picks the current space, and makes new ones.

    SpacePicker
    } from './Spaces.tsx';
    import {
    class Boundary

    Shows the error in place of children when they fail to render.

    Boundary
    } from './ui.tsx';
    import {
    const Welcome: ({ stage }: {
    stage: Stage;
    }) => React.JSX.Element

    The device before it has an identity: start one, join one from another device, or recover one.

    Welcome
    } from './Welcome.tsx';
    export type
    type ContactBookProps = {
    stage: Stage;
    }
    ContactBookProps
    = {
    stage: Stage
    stage
    :
    type Stage = "name" | "spaces" | "invite" | "contacts" | "devices" | "recovery"

    How far a page of the learning path has built the app, in the order the path builds it.

    Stage
    ;
    };
    /**
    * The contact book on one device, with what `stage` has built. It runs against
    * the HALO services of the `HaloProvider` above it.
    */
    export const
    const ContactBook: ({ stage }: ContactBookProps) => React.JSX.Element

    The contact book on one device, with what stage has built. It runs against the HALO services of the HaloProvider above it.

    ContactBook
    = ({
    stage: Stage
    stage
    }:
    type ContactBookProps = {
    stage: Stage;
    }
    ContactBookProps
    ) => {
    /** The identity, re-rendering on each change; undefined before it exists. */
    const
    const identity: {
    readonly did: IdentityDid;
    readonly identityKey?: string | undefined;
    readonly displayName?: string | undefined;
    readonly data?: {
    readonly [x: string]: any;
    } | undefined;
    } | undefined

    The identity, re-rendering on each change; undefined before it exists.

    identity
    =
    function useIdentity(): Identity.Info | undefined

    Returns the local identity, or undefined if none exists. Reactive: re-renders when the identity is created or its profile changes. Replaces @dxos/react-client's useIdentity.

    useIdentity
    ();
  6. Change your profile. Identity.updateProfile writes a new profile. useIdentity re-renders with it, and so does every space member list that includes you. Clicking your name, then Save, calls this.

    src/app/Profile.tsx
    import * as
    import Effect
    Effect
    from 'effect/Effect';
    2 collapsed lines
    import
    (alias) namespace React
    import React
    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.

    @version ― 16.8.0

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

    useState
    } from 'react';
    import {
    import Identity
    Identity
    } from '@dxos/halo';
    9 collapsed lines
    import {
    const useHaloServices: () => Context<HaloServices>

    Returns the HALO services context. Throws if no

    HaloProvider

    is above in the tree.

    useHaloServices
    } from '@dxos/halo-react';
    import {
    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.Element

    A field and a button, which shows the error if onSubmit fails.

    Prompt
    ,
    const initial: (name?: string) => string
    initial
    } from './ui.tsx';
    /** Your name, which you click to change. */
    export const
    const ProfileChip: ({ name }: {
    name?: string;
    }) => React.JSX.Element

    Your name, which you click to change.

    ProfileChip
    = ({
    name: string | undefined
    name
    }: {
    name?: string | undefined
    name
    ?: string }) => {
    const
    const services: Context<HaloServices>
    services
    =
    function useHaloServices(): Context<HaloServices>

    Returns the HALO services context. Throws if no

    HaloProvider

    is above in the tree.

    useHaloServices
    ();
    const [
    const editing: boolean
    editing
    ,
    const setEditing: React.Dispatch<React.SetStateAction<boolean>>
    setEditing
    ] =
    useState<boolean>(initialState: boolean | (() => boolean)): [boolean, React.Dispatch<React.SetStateAction<boolean>>] (+1 overload)

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

    @version ― 16.8.0

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

    useState
    (false);
    /** Changes the name everyone who shares a space with you sees. */
    const
    const handleRename: (name: string) => Promise<{
    readonly did: IdentityDid;
    readonly identityKey?: string | undefined;
    readonly displayName?: string | undefined;
    readonly data?: {
    readonly [x: string]: any;
    } | undefined;
    }>

    Changes the name everyone who shares a space with you sees.

    handleRename
    = (
    name: string
    name
    : string) =>
    import Identity
    Identity
    .
    const updateProfile: (profile: {
    displayName?: string;
    data?: Record<string, unknown>;
    }) => Effect.Effect<Identity.Info, IdentityError, Identity.Service>

    Update the identity profile (requires

    Service

    ).

    updateProfile
    ({
    displayName?: string | undefined
    displayName
    :
    name: string
    name
    }).
    Pipeable.pipe<Effect.Effect<{
    readonly did: IdentityDid;
    readonly identityKey?: string | undefined;
    readonly displayName?: string | undefined;
    readonly data?: {
    readonly [x: string]: any;
    } | undefined;
    }, IdentityError, Identity.Service>, Effect.Effect<{
    readonly did: IdentityDid;
    readonly identityKey?: string | undefined;
    readonly displayName?: string | undefined;
    readonly data?: {
    readonly [x: string]: any;
    } | undefined;
    }, IdentityError, never>, Promise<{
    readonly did: IdentityDid;
    readonly identityKey?: string | undefined;
    readonly displayName?: string | undefined;
    readonly data?: {
    readonly [x: string]: any;
    } | undefined;
    }>>(this: Effect.Effect<...>, ab: (_: Effect.Effect<...>) => Effect.Effect<...>, bc: (_: Effect.Effect<...>) => Promise<...>): Promise<...> (+21 overloads)
    pipe
    (
    import Effect
    Effect
    .
    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.local to 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* Database
    return yield* db.query("SELECT * FROM users")
    })
    const provided = Effect.provide(program, DatabaseLayer)
    await Effect.runPromise(provided) // => "Result for: SELECT * FROM users"

    @stability ― stable

    @category ― providing services

    @since ― 2.0.0

    provide
    (
    const services: Context<HaloServices>
    services
    ),
    import Effect
    Effect
    .
    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 Promise syntax, 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)) // => 1

    Example (Running effects as promises)

    //Example: Handling a Failing Effect as a Rejected Promise
    import { Effect } from "effect"
    const output: Array<unknown> = []
    await Effect.runPromise(Effect.fail("my error")).catch(() => {
    void output.push("rejected")
    })
    output // => ["rejected"]

    @see ― runPromiseExit for a version that returns an Exit type instead of rejecting.

    @stability ― stable

    @category ― running

    @since ― 2.0.0

    runPromise
    ,
    );

This device has no identity yet. Type a name and press Start, then click your name to change it.

You can start a client, create an identity with a profile, follow it from a component, and change its profile. Next, Your spaces gives it somewhere to invite people.