Recover an identity
By the end of this page Alice has a recovery code, and with it gets her name, spaces and contacts back on a new laptop.
Background
Section titled “Background”If every device is lost, the identity is lost with them, unless it has a recovery credential: a key kept outside every device. A recovery code is one such key, written as twelve words. Making it adds the key to the identity’s group as a member, the same way a device is added, so the key can admit a new device later.
Recovery goes through EDGE. The new device asks EDGE for a challenge, signs it with the key derived from the words, and EDGE checks the signature against the identity’s recovery credentials before it admits the device. The new device is a new member of the same identity, with the same DID, and syncs its spaces from EDGE and from anyone online.
HALO shows a recovery code once and keeps only its public half, and it will not revoke the last code you have: make the new one first. A passkey works the same way; see Passkey recovery.
-
Make a recovery code.
Identity.createRecoveryCredentialmakes the key, adds it to the identity, and returns the words. Make a recovery code on the Recovery tab calls this.src/app/Recovery.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 Identity14 collapsed linesimport {useCredentials,const useCredentials: () => readonly Identity.Credential[]Returns the local identity's HALO credentials. Reactive: re-renders when credentials are written. Replaces
@dxos/react-client'suseCredentials.useHaloServices } from '@dxos/halo-react';const useHaloServices: () => Context<HaloServices>Returns the HALO services context. Throws if no
HaloProvider
is above in the tree.
import {Copyable } from './ui.tsx';const Copyable: ({ value }: {value: string;}) => React.JSX.Element/** Your recovery codes: make one, see the ones that work, and revoke one. */export constRecovery = () => {const Recovery: () => React.JSX.ElementYour recovery codes: make one, see the ones that work, and revoke one.
constservices =const services: Context<HaloServices>useHaloServices();function useHaloServices(): Context<HaloServices>Returns the HALO services context. Throws if no
HaloProvider
is above in the tree.
/** The recovery credentials that still work, re-rendering on each change. */constcodes =const codes: {issuanceDate: Date | undefined;kind: "passkey" | "recovery-code" | "oauth" | "unknown";revoked: boolean;lookupKey?: string | undefined;label?: string | undefined;}[]The recovery credentials that still work, re-rendering on each change.
useCredentials().function useCredentials(): readonly Identity.Credential[]Returns the local identity's HALO credentials. Reactive: re-renders when credentials are written. Replaces
@dxos/react-client'suseCredentials.flatMap(({ReadonlyArray<{ readonly type: string; readonly id?: string | undefined; readonly issuanceDate?: Date | undefined; readonly recovery?: { readonly kind: "passkey" | "recovery-code" | "oauth" | "unknown"; readonly revoked: boolean; readonly lookupKey?: string | undefined; readonly label?: string | undefined; } | undefined; }>.flatMap<{issuanceDate: Date | undefined;kind: "passkey" | "recovery-code" | "oauth" | "unknown";revoked: boolean;lookupKey?: string | undefined;label?: string | undefined;}, undefined>(callback: (this: undefined, value: {readonly type: string;readonly id?: string | undefined;readonly issuanceDate?: Date | undefined;readonly recovery?: {readonly kind: "passkey" | "recovery-code" | "oauth" | "unknown";readonly revoked: boolean;readonly lookupKey?: string | undefined;readonly label?: string | undefined;} | undefined;}, index: number, array: {readonly type: string;readonly id?: string | undefined;readonly issuanceDate?: Date | undefined;readonly recovery?: {readonly kind: "passkey" | "recovery-code" | "oauth" | "unknown";readonly revoked: boolean;readonly lookupKey?: string | undefined;readonly label?: string | undefined;} | undefined;}[]) => {issuanceDate: Date | undefined;kind: "passkey" | "recovery-code" | "oauth" | "unknown";revoked: boolean;lookupKey?: string | undefined;label?: string | undefined;} | readonly {issuanceDate: Date | undefined;kind: "passkey" | "recovery-code" | "oauth" | "unknown";revoked: boolean;lookupKey?: string | undefined;label?: string | undefined;}[], thisArg?: undefined): {issuanceDate: Date | undefined;kind: "passkey" | "recovery-code" | "oauth" | "unknown";revoked: boolean;lookupKey?: string | undefined;label?: string | undefined;}[]Calls a defined callback function on each element of an array. Then, flattens the result into a new array. This is identical to a map followed by flat with depth 1.
recovery,recovery: {readonly kind: "passkey" | "recovery-code" | "oauth" | "unknown";readonly revoked: boolean;readonly lookupKey?: string | undefined;readonly label?: string | undefined;} | undefinedissuanceDate }) =>issuanceDate: Date | undefinedrecovery && !recovery: {readonly kind: "passkey" | "recovery-code" | "oauth" | "unknown";readonly revoked: boolean;readonly lookupKey?: string | undefined;readonly label?: string | undefined;} | undefinedrecovery.recovery: {readonly kind: "passkey" | "recovery-code" | "oauth" | "unknown";readonly revoked: boolean;readonly lookupKey?: string | undefined;readonly label?: string | undefined;}revoked ? [{ ...revoked: booleanWhether an
IdentityRecoveryRevokedassertion cancels this credential.recovery,recovery: {readonly kind: "passkey" | "recovery-code" | "oauth" | "unknown";readonly revoked: boolean;readonly lookupKey?: string | undefined;readonly label?: string | undefined;}issuanceDate }] : [],issuanceDate: Date | undefined);const [created,const created: string | undefinedsetCreated] =const setCreated: 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.
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.
/** Makes a recovery code, and shows it. HALO returns it only this once. */consthandleCreate = () =>const handleCreate: () => Promise<void>Makes a recovery code, and shows it. HALO returns it only this once.
Effect.import Effectgen(function* () {const gen: <Effect.Effect<{recoveryCode?: string;}, IdentityError, Identity.Service>, string | undefined>(f: () => Generator<Effect.Effect<{recoveryCode?: string;}, IdentityError, Identity.Service>, string | undefined, never>) => Effect.Effect<string | undefined, IdentityError, Identity.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"const {recoveryCode } = yield*const recoveryCode: string | undefinedIdentity.import IdentitycreateRecoveryCredential();const createRecoveryCredential: (options?: Identity.RecoveryCredentialOptions) => Effect.Effect<{recoveryCode?: string;}, IdentityError, Identity.Service>Write a recovery credential (requires
Service
).
returnrecoveryCode;const recoveryCode: string | undefined}).pipe(Pipeable.pipe<Effect.Effect<string | undefined, IdentityError, Identity.Service>, Effect.Effect<string | undefined, IdentityError, never>, Promise<string | undefined>>(this: Effect.Effect<...>, ab: (_: Effect.Effect<string | undefined, IdentityError, Identity.Service>) => Effect.Effect<string | undefined, IdentityError, never>, bc: (_: Effect.Effect<string | undefined, IdentityError, never>) => Promise<string | undefined>): 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"].then((Promise<string | undefined>.then<void, never>(onfulfilled?: ((value: string | undefined) => void | PromiseLike<void>) | null | undefined, onrejected?: ((reason: any) => PromiseLike<never>) | null | undefined): Promise<void>Attaches callbacks for the resolution and/or rejection of the Promise.
code) => {code: string | undefinedsetError(const setError: (value: React.SetStateAction<string | undefined>) => voidundefined);var undefinedsetCreated(const setCreated: (value: React.SetStateAction<string | undefined>) => voidcode);code: string | undefined}); -
List your codes.
useCredentialsreturns the identity’s credentials and re-renders when one is written. It followsIdentity.credentials. A recovery credential carries its kind, a label, the date it was made, a lookup key and whether it was revoked. The Recovery tab lists the ones still active.src/app/Recovery.tsx import {useCredentials,const useCredentials: () => readonly Identity.Credential[]Returns the local identity's HALO credentials. Reactive: re-renders when credentials are written. Replaces
@dxos/react-client'suseCredentials.useHaloServices } from '@dxos/halo-react';const useHaloServices: () => Context<HaloServices>Returns the HALO services context. Throws if no
HaloProvider
is above in the tree.
6 collapsed linesimport {Copyable } from './ui.tsx';const Copyable: ({ value }: {value: string;}) => React.JSX.Element/** Your recovery codes: make one, see the ones that work, and revoke one. */export constRecovery = () => {const Recovery: () => React.JSX.ElementYour recovery codes: make one, see the ones that work, and revoke one.
constservices =const services: Context<HaloServices>useHaloServices();function useHaloServices(): Context<HaloServices>Returns the HALO services context. Throws if no
HaloProvider
is above in the tree.
/** The recovery credentials that still work, re-rendering on each change. */constcodes =const codes: {issuanceDate: Date | undefined;kind: "passkey" | "recovery-code" | "oauth" | "unknown";revoked: boolean;lookupKey?: string | undefined;label?: string | undefined;}[]The recovery credentials that still work, re-rendering on each change.
useCredentials().function useCredentials(): readonly Identity.Credential[]Returns the local identity's HALO credentials. Reactive: re-renders when credentials are written. Replaces
@dxos/react-client'suseCredentials.flatMap(({ReadonlyArray<{ readonly type: string; readonly id?: string | undefined; readonly issuanceDate?: Date | undefined; readonly recovery?: { readonly kind: "passkey" | "recovery-code" | "oauth" | "unknown"; readonly revoked: boolean; readonly lookupKey?: string | undefined; readonly label?: string | undefined; } | undefined; }>.flatMap<{issuanceDate: Date | undefined;kind: "passkey" | "recovery-code" | "oauth" | "unknown";revoked: boolean;lookupKey?: string | undefined;label?: string | undefined;}, undefined>(callback: (this: undefined, value: {readonly type: string;readonly id?: string | undefined;readonly issuanceDate?: Date | undefined;readonly recovery?: {readonly kind: "passkey" | "recovery-code" | "oauth" | "unknown";readonly revoked: boolean;readonly lookupKey?: string | undefined;readonly label?: string | undefined;} | undefined;}, index: number, array: {readonly type: string;readonly id?: string | undefined;readonly issuanceDate?: Date | undefined;readonly recovery?: {readonly kind: "passkey" | "recovery-code" | "oauth" | "unknown";readonly revoked: boolean;readonly lookupKey?: string | undefined;readonly label?: string | undefined;} | undefined;}[]) => {issuanceDate: Date | undefined;kind: "passkey" | "recovery-code" | "oauth" | "unknown";revoked: boolean;lookupKey?: string | undefined;label?: string | undefined;} | readonly {issuanceDate: Date | undefined;kind: "passkey" | "recovery-code" | "oauth" | "unknown";revoked: boolean;lookupKey?: string | undefined;label?: string | undefined;}[], thisArg?: undefined): {issuanceDate: Date | undefined;kind: "passkey" | "recovery-code" | "oauth" | "unknown";revoked: boolean;lookupKey?: string | undefined;label?: string | undefined;}[]Calls a defined callback function on each element of an array. Then, flattens the result into a new array. This is identical to a map followed by flat with depth 1.
recovery,recovery: {readonly kind: "passkey" | "recovery-code" | "oauth" | "unknown";readonly revoked: boolean;readonly lookupKey?: string | undefined;readonly label?: string | undefined;} | undefinedissuanceDate }) =>issuanceDate: Date | undefinedrecovery && !recovery: {readonly kind: "passkey" | "recovery-code" | "oauth" | "unknown";readonly revoked: boolean;readonly lookupKey?: string | undefined;readonly label?: string | undefined;} | undefinedrecovery.recovery: {readonly kind: "passkey" | "recovery-code" | "oauth" | "unknown";readonly revoked: boolean;readonly lookupKey?: string | undefined;readonly label?: string | undefined;}revoked ? [{ ...revoked: booleanWhether an
IdentityRecoveryRevokedassertion cancels this credential.recovery,recovery: {readonly kind: "passkey" | "recovery-code" | "oauth" | "unknown";readonly revoked: boolean;readonly lookupKey?: string | undefined;readonly label?: string | undefined;}issuanceDate }] : [],issuanceDate: Date | undefined); -
Revoke a code.
Identity.revokeRecoveryCredentialtakes the lookup key. Revoke a code you think someone has seen.src/app/Recovery.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 Identity26 collapsed linesimport {useCredentials,const useCredentials: () => readonly Identity.Credential[]Returns the local identity's HALO credentials. Reactive: re-renders when credentials are written. Replaces
@dxos/react-client'suseCredentials.useHaloServices } from '@dxos/halo-react';const useHaloServices: () => Context<HaloServices>Returns the HALO services context. Throws if no
HaloProvider
is above in the tree.
import {Copyable } from './ui.tsx';const Copyable: ({ value }: {value: string;}) => React.JSX.Element/** Your recovery codes: make one, see the ones that work, and revoke one. */export constRecovery = () => {const Recovery: () => React.JSX.ElementYour recovery codes: make one, see the ones that work, and revoke one.
constservices =const services: Context<HaloServices>useHaloServices();function useHaloServices(): Context<HaloServices>Returns the HALO services context. Throws if no
HaloProvider
is above in the tree.
/** The recovery credentials that still work, re-rendering on each change. */constcodes =const codes: {issuanceDate: Date | undefined;kind: "passkey" | "recovery-code" | "oauth" | "unknown";revoked: boolean;lookupKey?: string | undefined;label?: string | undefined;}[]The recovery credentials that still work, re-rendering on each change.
useCredentials().function useCredentials(): readonly Identity.Credential[]Returns the local identity's HALO credentials. Reactive: re-renders when credentials are written. Replaces
@dxos/react-client'suseCredentials.flatMap(({ReadonlyArray<{ readonly type: string; readonly id?: string | undefined; readonly issuanceDate?: Date | undefined; readonly recovery?: { readonly kind: "passkey" | "recovery-code" | "oauth" | "unknown"; readonly revoked: boolean; readonly lookupKey?: string | undefined; readonly label?: string | undefined; } | undefined; }>.flatMap<{issuanceDate: Date | undefined;kind: "passkey" | "recovery-code" | "oauth" | "unknown";revoked: boolean;lookupKey?: string | undefined;label?: string | undefined;}, undefined>(callback: (this: undefined, value: {readonly type: string;readonly id?: string | undefined;readonly issuanceDate?: Date | undefined;readonly recovery?: {readonly kind: "passkey" | "recovery-code" | "oauth" | "unknown";readonly revoked: boolean;readonly lookupKey?: string | undefined;readonly label?: string | undefined;} | undefined;}, index: number, array: {readonly type: string;readonly id?: string | undefined;readonly issuanceDate?: Date | undefined;readonly recovery?: {readonly kind: "passkey" | "recovery-code" | "oauth" | "unknown";readonly revoked: boolean;readonly lookupKey?: string | undefined;readonly label?: string | undefined;} | undefined;}[]) => {issuanceDate: Date | undefined;kind: "passkey" | "recovery-code" | "oauth" | "unknown";revoked: boolean;lookupKey?: string | undefined;label?: string | undefined;} | readonly {issuanceDate: Date | undefined;kind: "passkey" | "recovery-code" | "oauth" | "unknown";revoked: boolean;lookupKey?: string | undefined;label?: string | undefined;}[], thisArg?: undefined): {issuanceDate: Date | undefined;kind: "passkey" | "recovery-code" | "oauth" | "unknown";revoked: boolean;lookupKey?: string | undefined;label?: string | undefined;}[]Calls a defined callback function on each element of an array. Then, flattens the result into a new array. This is identical to a map followed by flat with depth 1.
recovery,recovery: {readonly kind: "passkey" | "recovery-code" | "oauth" | "unknown";readonly revoked: boolean;readonly lookupKey?: string | undefined;readonly label?: string | undefined;} | undefinedissuanceDate }) =>issuanceDate: Date | undefinedrecovery && !recovery: {readonly kind: "passkey" | "recovery-code" | "oauth" | "unknown";readonly revoked: boolean;readonly lookupKey?: string | undefined;readonly label?: string | undefined;} | undefinedrecovery.recovery: {readonly kind: "passkey" | "recovery-code" | "oauth" | "unknown";readonly revoked: boolean;readonly lookupKey?: string | undefined;readonly label?: string | undefined;}revoked ? [{ ...revoked: booleanWhether an
IdentityRecoveryRevokedassertion cancels this credential.recovery,recovery: {readonly kind: "passkey" | "recovery-code" | "oauth" | "unknown";readonly revoked: boolean;readonly lookupKey?: string | undefined;readonly label?: string | undefined;}issuanceDate }] : [],issuanceDate: Date | undefined);const [created,const created: string | undefinedsetCreated] =const setCreated: 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.
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.
/** Makes a recovery code, and shows it. HALO returns it only this once. */consthandleCreate = () =>const handleCreate: () => Promise<void>Makes a recovery code, and shows it. HALO returns it only this once.
Effect.import Effectgen(function* () {const gen: <Effect.Effect<{recoveryCode?: string;}, IdentityError, Identity.Service>, string | undefined>(f: () => Generator<Effect.Effect<{recoveryCode?: string;}, IdentityError, Identity.Service>, string | undefined, never>) => Effect.Effect<string | undefined, IdentityError, Identity.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"const {recoveryCode } = yield*const recoveryCode: string | undefinedIdentity.import IdentitycreateRecoveryCredential();const createRecoveryCredential: (options?: Identity.RecoveryCredentialOptions) => Effect.Effect<{recoveryCode?: string;}, IdentityError, Identity.Service>Write a recovery credential (requires
Service
).
returnrecoveryCode;const recoveryCode: string | undefined}).pipe(Pipeable.pipe<Effect.Effect<string | undefined, IdentityError, Identity.Service>, Effect.Effect<string | undefined, IdentityError, never>, Promise<string | undefined>>(this: Effect.Effect<...>, ab: (_: Effect.Effect<string | undefined, IdentityError, Identity.Service>) => Effect.Effect<string | undefined, IdentityError, never>, bc: (_: Effect.Effect<string | undefined, IdentityError, never>) => Promise<string | undefined>): 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"].then((Promise<string | undefined>.then<void, never>(onfulfilled?: ((value: string | undefined) => void | PromiseLike<void>) | null | undefined, onrejected?: ((reason: any) => PromiseLike<never>) | null | undefined): Promise<void>Attaches callbacks for the resolution and/or rejection of the Promise.
code) => {code: string | undefinedsetError(const setError: (value: React.SetStateAction<string | undefined>) => voidundefined);var undefinedsetCreated(const setCreated: (value: React.SetStateAction<string | undefined>) => voidcode);code: string | undefined});/** Revokes the recovery credential with `lookupKey`. */consthandleRevoke = (const handleRevoke: (lookupKey: string) => Promise<void>Revokes the recovery credential with
lookupKey.lookupKey: string) =>lookupKey: stringIdentity.import IdentityrevokeRecoveryCredential(const revokeRecoveryCredential: (lookupKey: string) => Effect.Effect<void, IdentityError, Identity.Service>Revoke a recovery credential by lookup key (requires
Service
).
lookupKey)lookupKey: string.pipe(Pipeable.pipe<Effect.Effect<void, IdentityError, Identity.Service>, Effect.Effect<void, IdentityError, never>, Promise<void>>(this: Effect.Effect<void, IdentityError, Identity.Service>, ab: (_: Effect.Effect<void, IdentityError, Identity.Service>) => Effect.Effect<void, IdentityError, never>, bc: (_: Effect.Effect<void, IdentityError, never>) => Promise<void>): 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"].then(Promise<void>.then<void, void>(onfulfilled?: ((value: void) => 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.
() =>setError(const setError: (value: React.SetStateAction<string | undefined>) => voidundefined),var undefined(error) =>error: anysetError(const setError: (value: React.SetStateAction<string | undefined>) => voidcodes.const codes: {issuanceDate: Date | undefined;kind: "passkey" | "recovery-code" | "oauth" | "unknown";revoked: boolean;lookupKey?: string | undefined;label?: string | undefined;}[]The recovery credentials that still work, re-rendering on each change.
length === 1Array<T>.length: numberGets or sets the length of the array. This is a number one higher than the highest index in the array.
? 'HALO will not revoke your last recovery code. Make another one first.':String(var String: StringConstructor(value?: any) => stringAllows manipulation and formatting of text strings and determination and location of substrings within strings.
error),error: any),); -
Recover on a new device. On a device with no identity,
Identity.recovertakes the words and gets the identity back. Recover with a code 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 Identity28 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"]);/** Redeems the invitation `code` from your other device, and returns the flow. */consthandleJoin = (const handleJoin: (code: string) => Promise<Flow>Redeems the invitation
codefrom your other device, and returns the flow.code: string) =>code: stringIdentity.import Identityjoin(const join: (code: string) => Effect.Effect<Flow, IdentityError, Identity.Service>Redeem a device-invitation code (requires
Service
).
code).code: stringpipe(Pipeable.pipe<Effect.Effect<Flow, IdentityError, Identity.Service>, Effect.Effect<Flow, IdentityError, never>, Promise<Flow>>(this: Effect.Effect<Flow, IdentityError, Identity.Service>, ab: (_: Effect.Effect<Flow, IdentityError, Identity.Service>) => Effect.Effect<Flow, IdentityError, never>, bc: (_: Effect.Effect<Flow, IdentityError, never>) => Promise<Flow>): 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"]/** Gets the identity back with `recoveryCode`. */consthandleRecover = (const handleRecover: (recoveryCode: string) => Promise<{readonly did: IdentityDid;readonly displayName?: string | undefined;readonly identityKey?: string | undefined;readonly data?: {readonly [x: string]: any;} | undefined;}>Gets the identity back with
recoveryCode.recoveryCode: string) =>recoveryCode: stringIdentity.import Identityrecover({const recover: (args: Identity.RecoverArgs) => Effect.Effect<Identity.Info, IdentityError, Identity.Service>Re-admit this device via a recovery credential (requires
Service
).
recoveryCode:recoveryCode: stringrecoveryCode.recoveryCode: stringtrim() }).String.trim(): stringRemoves the leading and trailing white space and line terminator characters from a string.
pipe(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"]);
Try it
Section titled “Try it”Open Recovery on Alice’s laptop and make a code. Make a second one, then revoke the first. Try revoking the last one too, and HALO refuses.
What you can do now
Section titled “What you can do now”You can make and revoke recovery codes, and recover an identity on a new device. That is the whole contact book. From
here, Core concepts explains the model behind it, and the
API pages cover the rest of @dxos/halo.