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.
Background
Section titled “Background”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.
-
Install the packages.
@dxos/clientholds the client,@dxos/halothe API these pages use,@dxos/halo-adapter-clientthe services between them, and@dxos/halo-reactthe 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.4Terminal window npm install --save 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.4The browser setup is the same as ECHO’s; see Installation for the Vite config. The StackBlitz project has it already.
-
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 {Config } from '@dxos/config';class ConfigGlobal configuration object. NOTE: Config objects are immutable.
9 collapsed linesimport {Identity,import IdentitySpace } from '@dxos/halo';import Spaceimport {makeIdentityService,const makeIdentityService: (client: Client) => Context.Service.Shape<typeof Identity.Service>Builds the
HaloIdentity.Service
implementation over a client's
haloproxy.makeSpaceService,const makeSpaceService: (client: Client) => Context.Service.Shape<typeof Space.Service>Builds the
HaloSpace.Service
implementation over a client's
spacesproxy.} from '@dxos/halo-adapter-client';import {HaloProvider, typeconst HaloProvider: ({ services, children }: HaloProviderProps) => React.JSX.ElementProvides the HALO services to the hook library. Wrap the application (or the subtree that uses the hooks) in this provider. Replaces
ClientProviderfor HALO concerns.HaloServices } from '@dxos/halo-react';type HaloServices = Identity.Service | Space.ServiceThe HALO services, as the tag union carried in an Effect service
Context.Context
.
import {ContactBook } from './ContactBook.tsx';const ContactBook: ({ stage }: ContactBookProps) => React.JSX.ElementThe contact book on one device, with what
stagehas built. It runs against the HALO services of theHaloProviderabove it./*** 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.*/constconfig = newconst config: ConfigKeeps 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 Config(config?: ConfigInit, ...objects: ConfigInit[]): ConfigCreates an immutable instance.
runtime: {runtime: {client: {storage: {persistent: true;};edgeFeatures: {signaling: true;subductionReplicator: true;feedReplicator: true;};};services: {edge: {url: string;};iceProviders: {urls: string;}[];};}client: {client: {storage: {persistent: true;};edgeFeatures: {signaling: true;subductionReplicator: true;feedReplicator: true;};}storage: {storage: {persistent: true;}persistent: true },persistent: trueedgeFeatures: {edgeFeatures: {signaling: true;subductionReplicator: true;feedReplicator: true;}signaling: true,signaling: truesubductionReplicator: true,subductionReplicator: truefeedReplicator: true,feedReplicator: true},},services: {services: {edge: {url: string;};iceProviders: {urls: string;}[];}edge: {edge: {url: string;}url: 'https://dxos.network/' },url: stringiceProviders: [{iceProviders: {urls: string;}[]urls: 'https://dxos.network/ice' }],urls: string},},}); -
Start the client.
openHalostarts a client and returns it with the HALO services it backs. The app calls it once, at startup, and puts the services in aHaloProvider. Below it, a component gets them withuseHaloServices()and runs each piece of HALO code withEffect.provide(services)andEffect.runPromise.src/app/App.tsx import * asContext from 'effect/Context';import Context4 collapsed linesimportReact, {(alias) namespace Reactimport ReactuseEffect,function useEffect(effect: React.EffectCallback, deps?: React.DependencyList): voidAccepts a function that contains imperative, possibly effectful code.
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.
// `@dxos/keys` reads `Buffer` as it loads, so the globals go in before the client.import '@dxos/node-std/globals';import {Client, typeclass ClientThe Client class encapsulates the core client-side API of DXOS.
ClientServicesProvider } from '@dxos/client';(alias) interface ClientServicesProviderimport ClientServicesProviderProvide access to client services definitions and service handler.
1 collapsed lineimport {Config } from '@dxos/config';class ConfigGlobal configuration object. NOTE: Config objects are immutable.
import {Identity,import IdentitySpace } from '@dxos/halo';import Spaceimport {makeIdentityService,const makeIdentityService: (client: Client) => Context.Service.Shape<typeof Identity.Service>Builds the
HaloIdentity.Service
implementation over a client's
haloproxy.makeSpaceService,const makeSpaceService: (client: Client) => Context.Service.Shape<typeof Space.Service>Builds the
HaloSpace.Service
implementation over a client's
spacesproxy.} from '@dxos/halo-adapter-client';25 collapsed linesimport {HaloProvider, typeconst HaloProvider: ({ services, children }: HaloProviderProps) => React.JSX.ElementProvides the HALO services to the hook library. Wrap the application (or the subtree that uses the hooks) in this provider. Replaces
ClientProviderfor HALO concerns.HaloServices } from '@dxos/halo-react';type HaloServices = Identity.Service | Space.ServiceThe HALO services, as the tag union carried in an Effect service
Context.Context
.
import {ContactBook } from './ContactBook.tsx';const ContactBook: ({ stage }: ContactBookProps) => React.JSX.ElementThe contact book on one device, with what
stagehas built. It runs against the HALO services of theHaloProviderabove it./*** 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.*/constconfig = newconst config: ConfigKeeps 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 Config(config?: ConfigInit, ...objects: ConfigInit[]): ConfigCreates an immutable instance.
runtime: {runtime: {client: {storage: {persistent: true;};edgeFeatures: {signaling: true;subductionReplicator: true;feedReplicator: true;};};services: {edge: {url: string;};iceProviders: {urls: string;}[];};}client: {client: {storage: {persistent: true;};edgeFeatures: {signaling: true;subductionReplicator: true;feedReplicator: true;};}storage: {storage: {persistent: true;}persistent: true },persistent: trueedgeFeatures: {edgeFeatures: {signaling: true;subductionReplicator: true;feedReplicator: true;}signaling: true,signaling: truesubductionReplicator: true,subductionReplicator: truefeedReplicator: true,feedReplicator: true},},services: {services: {edge: {url: string;};iceProviders: {urls: string;}[];}edge: {edge: {url: string;}url: 'https://dxos.network/' },url: stringiceProviders: [{iceProviders: {urls: string;}[]urls: 'https://dxos.network/ice' }],urls: string},},});/** Starts a client, and returns it with the HALO services it backs. */export constopenHalo = async (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.
clientServices?:clientServices: ClientServicesProvider | undefinedClientServicesProvider) => {(alias) interface ClientServicesProviderimport ClientServicesProviderProvide access to client services definitions and service handler.
constclient = newconst client: ClientClient(new Client(options?: ClientOptions): ClientThe Client class encapsulates the core client-side API of DXOS.
clientServicesclientServices: ClientServicesProvider | undefined? {services:services?: MaybePromise<ClientServicesProvider> | undefinedCustom services provider.
clientServices }clientServices: ClientServicesProvider: {config,config?: Config | undefinedClient configuration object.
// Keeps the device's keys and data in the browser's private file system.createOpfsWorker: () =>createOpfsWorker?: (() => Worker) | undefinedWhen running in the host mode, a factory to create the worker for OPFS sqlite database.
newWorker(newvar Worker: new (scriptURL: string | URL, options?: WorkerOptions) => WorkerThe
Workerinterface of the Web Workers API represents a background task that can be created via script, which can send messages back to its creator.URL('@dxos/client/opfs-worker', import.var URL: new (url: string | URL, base?: string | URL) => URLThe
URLinterface 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.URLclass is a global reference forimport { URL } from 'url'https://nodejs.org/api/url.html#the-whatwg-url-apimeta.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.url), {ImportMeta.url: stringThe absolute
file:URL of the module.type: 'module',WorkerOptions.type?: WorkerType | undefined}),},);awaitclient.const client: Clientinitialize();Client.initialize(): Promise<Client>Initializes internal resources in an idempotent way. Required before using the Client instance.
constservices =const services: Context.Context<Identity.Service | Space.Service>Context.import Contextempty().const empty: () => Context.Context<never>Returns an empty
Context.Example (Creating an empty context)
import { Context } from "effect"Context.empty().mapUnsafe.size // => 0pipe(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)Context.import Contextadd(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]Identity.import IdentityService,class ServicemakeIdentityService(function makeIdentityService(client: Client): Context.Service.Shape<typeof Identity.Service>Builds the
HaloIdentity.Service
implementation over a client's
haloproxy.client)),const client: ClientContext.import Contextadd(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]Space.import SpaceService,class ServicemakeSpaceService(function makeSpaceService(client: Client): Context.Service.Shape<typeof Space.Service>Builds the
HaloSpace.Service
implementation over a client's
spacesproxy.client)),const client: Client);return {client,client: Clientservices };services: Context.Context<Identity.Service | Space.Service>}; -
Create your identity.
Identity.createmakes 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 * asEffect from 'effect/Effect';import Effect2 collapsed linesimportReact, {(alias) namespace Reactimport ReactuseState } 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 {Identity } from '@dxos/halo';import Identity17 collapsed linesimport {useHaloServices } from '@dxos/halo-react';const useHaloServices: () => Context<HaloServices>Returns the HALO services context. Throws if no
HaloProvider
is above in the tree.
import { typeStage,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.
reached } from '../stage.ts';const reached: (stage: Stage, at: Stage) => booleanWhether
stageincludes whatatadds.import {Guest } from './Invitations.tsx';const Guest: ({ label, join, onDone, }: {label: string;join: (code: string) => Promise<Flow>;onDone?: () => void;}) => React.JSX.ElementThe guest's side of an invitation: paste the code, then type the authorization code.
import {Prompt } 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./** The device before it has an identity: start one, join one from another device, or recover one. */export constWelcome = ({const Welcome: ({ stage }: {stage: Stage;}) => React.JSX.ElementThe device before it has an identity: start one, join one from another device, or recover one.
stage }: {stage: Stagestage:stage: StageStage }) => {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.
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 [mode,const mode: "new" | "device" | "recover"setMode] =const setMode: React.Dispatch<React.SetStateAction<"new" | "device" | "recover">>useState<'new' | 'device' | 'recover'>('new');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.
// Focusing the first field on load would scroll the page to the embed.const [picked,const picked: booleansetPicked] =const setPicked: React.Dispatch<React.SetStateAction<boolean>>useState(false);useState<boolean>(initialState: boolean | (() => boolean)): [boolean, React.Dispatch<React.SetStateAction<boolean>>] (+1 overload)Returns a stateful value, and a function to update it.
constpick = (const pick: (next: "new" | "device" | "recover") => voidnext: typeofnext: "new" | "device" | "recover"mode) => {const mode: "new" | "device" | "recover"setPicked(true);const setPicked: (value: React.SetStateAction<boolean>) => voidsetMode(const setMode: (value: React.SetStateAction<"new" | "device" | "recover">) => voidnext);next: "new" | "device" | "recover"};/** Creates the identity, with this device as its first member. */consthandleStart = (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.
name: string) =>name: stringIdentity.import Identitycreate({const create: (options?: {displayName?: string;data?: Record<string, unknown>;deviceLabel?: string;}) => Effect.Effect<Identity.Info, IdentityError, Identity.Service>Create the local identity (requires
Service
).
displayName:displayName?: string | undefinedname }).name: stringpipe(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)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 your identity.
useIdentityreturns the identity, orundefinedbefore there is one, and re-renders the component on every change. It followsIdentity.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 {useIdentity,const useIdentity: () => Identity.Info | undefinedReturns the local identity, or
undefinedif none exists. Reactive: re-renders when the identity is created or its profile changes. Replaces@dxos/react-client'suseIdentity.useSpaces } from '@dxos/halo-react';const useSpaces: ({ all }?: UseSpacesOptions) => readonly Info[]Returns the spaces known to the local identity. By default only
readyspaces are returned; pass{ all: true }for every space. Reactive. Replaces@dxos/react-client'suseSpaces.21 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 { typeStage,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.
reached } from '../stage.ts';const reached: (stage: Stage, at: Stage) => booleanWhether
stageincludes whatatadds.import {Contacts,const Contacts: () => React.JSX.ElementEveryone you share a space with, once each, with the spaces you share.
Members } from './Contacts.tsx';const Members: ({ spaceId }: {spaceId: SpaceId;}) => React.JSX.ElementThe members of one space, before the app lists contacts.
import {Devices } from './Devices.tsx';const Devices: () => React.JSX.ElementYour identity's devices, and a way to add another.
import {InviteBar } from './Invitations.tsx';const InviteBar: ({ spaceId }: {spaceId?: SpaceId;}) => React.JSX.ElementInvite someone to the current space, or join a space with a code.
import {ProfileChip } from './Profile.tsx';const ProfileChip: ({ name }: {name?: string;}) => React.JSX.ElementYour name, which you click to change.
import {Recovery } from './Recovery.tsx';const Recovery: () => React.JSX.ElementYour recovery codes: make one, see the ones that work, and revoke one.
import {SpacePicker } from './Spaces.tsx';const SpacePicker: ({ spaces, current, onSelect, }: {spaces: readonly Info[];current?: SpaceId;onSelect: (id: SpaceId) => void;}) => React.JSX.ElementPicks the current space, and makes new ones.
import {Boundary } from './ui.tsx';class BoundaryShows the error in place of
childrenwhen they fail to render.import {Welcome } from './Welcome.tsx';const Welcome: ({ stage }: {stage: Stage;}) => React.JSX.ElementThe device before it has an identity: start one, join one from another device, or recover one.
export typeContactBookProps = {type ContactBookProps = {stage: Stage;}stage:stage: StageStage;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.
};/*** The contact book on one device, with what `stage` has built. It runs against* the HALO services of the `HaloProvider` above it.*/export constContactBook = ({const ContactBook: ({ stage }: ContactBookProps) => React.JSX.ElementThe contact book on one device, with what
stagehas built. It runs against the HALO services of theHaloProviderabove it.stage }:stage: StageContactBookProps) => {type ContactBookProps = {stage: Stage;}/** The identity, re-rendering on each change; undefined before it exists. */constidentity =const identity: {readonly did: IdentityDid;readonly identityKey?: string | undefined;readonly displayName?: string | undefined;readonly data?: {readonly [x: string]: any;} | undefined;} | undefinedThe identity, re-rendering on each change; undefined before it exists.
useIdentity();function useIdentity(): Identity.Info | undefinedReturns the local identity, or
undefinedif none exists. Reactive: re-renders when the identity is created or its profile changes. Replaces@dxos/react-client'suseIdentity. -
Change your profile.
Identity.updateProfilewrites a new profile.useIdentityre-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 * asEffect from 'effect/Effect';import Effect2 collapsed linesimportReact, {(alias) namespace Reactimport ReactuseState } 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 {Identity } from '@dxos/halo';import Identity9 collapsed linesimport {useHaloServices } from '@dxos/halo-react';const useHaloServices: () => Context<HaloServices>Returns the HALO services context. Throws if no
HaloProvider
is above in the tree.
import {Prompt,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.initial } from './ui.tsx';const initial: (name?: string) => string/** Your name, which you click to change. */export constProfileChip = ({const ProfileChip: ({ name }: {name?: string;}) => React.JSX.ElementYour name, which you click to change.
name }: {name: string | undefinedname?: string }) => {name?: string | undefinedconstservices =const services: Context<HaloServices>useHaloServices();function useHaloServices(): Context<HaloServices>Returns the HALO services context. Throws if no
HaloProvider
is above in the tree.
const [editing,const editing: booleansetEditing] =const setEditing: React.Dispatch<React.SetStateAction<boolean>>useState(false);useState<boolean>(initialState: boolean | (() => boolean)): [boolean, React.Dispatch<React.SetStateAction<boolean>>] (+1 overload)Returns a stateful value, and a function to update it.
/** Changes the name everyone who shares a space with you sees. */consthandleRename = (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.
name: string) =>name: stringIdentity.import IdentityupdateProfile({const updateProfile: (profile: {displayName?: string;data?: Record<string, unknown>;}) => Effect.Effect<Identity.Info, IdentityError, Identity.Service>Update the identity profile (requires
Service
).
displayName:displayName?: string | undefinedname }).name: stringpipe(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)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”This device has no identity yet. Type a name and press Start, then click your name to change it.
What you can do now
Section titled “What you can do now”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.