Skip to content

Installation

By the end of this page the tutorial app will have a running client, an identity, and the space it keeps its records in.

The Client is the app’s way into ECHO. It holds the databases of the spaces you belong to, on the device, and connects them to the network so peers can replicate them.

A client acts for an identity. The identity owns the spaces it creates, and other peers recognize it when it joins theirs. A space is a database that a set of identities replicate between them, and every object lives in exactly one space. The app keeps all its records in one space, called Contacts.

  1. Install the client. @dxos/client holds the Client, and it brings @dxos/echo with it.

    Terminal window
    pnpm add @dxos/client

    In a React app, install @dxos/react-client instead and follow the React guide.

  2. Configure the bundler. In the browser, ECHO loads WebAssembly in the page and in workers, so Vite needs vite-plugin-wasm for both. ConfigPlugin loads the client’s config from YAML files at the package root.

    vite.config.ts
    export default
    function defineConfig(config: UserConfig): UserConfig (+5 overloads)

    Type helper to make it easier to use vite.config.ts accepts a direct

    UserConfig

    object, or a function that returns it. The function receives a

    ConfigEnv

    object.

    defineConfig
    ({
    UserConfig.plugins?: PluginOption[] | undefined

    Array of vite plugins to use.

    plugins
    : [
    function ConfigPlugin(options?: ConfigPluginOpts): Plugin
    ConfigPlugin
    ({
    ConfigPluginOpts.root?: string | undefined

    Root directory of the project.

    root
    : 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.dirname: string

    The directory name of the current module. This is the same as the path.dirname() of the import.meta.filename. Caveat: only present on file: modules.

    dirname
    ,
    ConfigPluginOpts.env?: string[] | undefined

    Environment variables to be passed to the app.

    env
    : ['MY_ENV_VAR'] }),
    function wasm(): any
    wasm
    ()],
    UserConfig.worker?: {
    format?: "es" | "iife";
    plugins?: () => PluginOption[];
    rollupOptions?: Omit<RolldownOptions, "plugins" | "input" | "onwarn" | "preserveEntrySignatures">;
    rolldownOptions?: Omit<RolldownOptions, "plugins" | "input" | "onwarn" | "preserveEntrySignatures">;
    } | undefined

    Worker bundle options

    worker
    : {
    format?: "es" | "iife" | undefined

    Output format for worker bundle

    @default ― 'iife'

    format
    : 'es',
    plugins?: (() => PluginOption[]) | undefined

    Vite plugins that apply to worker bundle. The plugins returned by this function should be new instances every time it is called, because they are used for each rolldown worker bundling process.

    plugins
    : () => [
    function wasm(): any
    wasm
    ()],
    },
    });

    @dxos/config/rollup-plugin and @dxos/config/esbuild-plugin export the same ConfigPlugin for Rollup and esbuild. Node needs no bundler setup.

  3. Open the app’s space. The app does this in one function. new Client takes the app’s types, which the next page declares. initialize opens local storage and connects to the network. The client then needs an identity before it can create a space; HALO covers identities and devices. Last, it creates the space and waits until its database is ready.

    /** Starts a client that knows the app's types, gives it an identity, and creates the space the app keeps its records in. */
    export const
    const openContacts: (services?: ClientServicesProvider) => Promise<{
    client: Client;
    space: Space;
    }>

    Starts a client that knows the app's types, gives it an identity, and creates the space the app keeps its records in.

    openContacts
    = async (
    services: ClientServicesProvider | undefined
    services
    ?:
    (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
    ({
    types?: AnyEntity[] | undefined

    ECHO schema.

    types
    ,
    services?: MaybePromise<ClientServicesProvider> | undefined

    Custom services provider.

    services
    });
    await
    const client: Client
    client
    .
    Client.initialize(): Promise<Client>

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

    initialize
    ();
    if (!
    const client: Client
    client
    .
    Client.halo: Halo

    HALO credentials.

    halo
    .
    Halo.identity: MulticastObservable<Identity | null>
    identity
    .
    MulticastObservable<Identity | null>.get(): Identity | null

    Get the current value of the observable.

    get
    ()) {
    await
    const client: Client
    client
    .
    Client.halo: Halo

    HALO credentials.

    halo
    .
    Halo.createIdentity(options?: ProfileDocument, deviceProfile?: DeviceProfileDocument): Promise<Identity>
    createIdentity
    ();
    }
    const
    const space: Space
    space
    = await
    const client: Client
    client
    .
    Client.spaces: Echo

    ECHO Spaces.

    spaces
    .
    Echo.create(props?: Pick<SpaceProperties, "name" | "hue" | "icon" | "invocationTraceFeed">, options?: CreateSpaceOptions): Promise<Space>

    Creates a new space.

    create
    ({
    name?: string | undefined
    name
    : 'Contacts' });
    await
    const space: Space
    space
    .
    Space.waitUntilReady(): Promise<Space>

    Waits until the space is in the ready state, with database initialized.

    waitUntilReady
    ();
    return {
    client: Client
    client
    ,
    space: Space
    space
    };
    };

    space.db is the database the rest of the learning path reads and writes. services is for running the client somewhere other than the default, as the embeds on these pages do: each runs its clients in memory in the page.

new Client() uses built-in defaults. Pass a Config to choose where settings come from. Each source is a function from @dxos/config, and earlier sources override later ones.

Defaults returns the config in dx.yml. In Node it reads config/default.yml in your project instead.

const
const fromDefaults: Client
fromDefaults
= new
new Client(options?: ClientOptions): Client

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

Client
({
config?: Config | undefined

Client configuration object.

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

Creates an immutable instance.

@constructor

Config
(
function Defaults(basePath?: string): ConfigInit

JSON config.

Defaults
()) });

Local returns the config in dx-local.yml, so a development preset can sit beside the defaults. In Node, Local returns nothing.

const
const forLocalDevelopment: Client
forLocalDevelopment
= new
new Client(options?: ClientOptions): Client

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

Client
({
config?: Config | undefined

Client configuration object.

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

Creates an immutable instance.

@constructor

Config
(
function Local(): ConfigInit

Development config.

Local
(),
function Defaults(basePath?: string): ConfigInit

JSON config.

Defaults
()) });

Envs maps environment variables to config paths, as listed in dx-env.yml.

const
const fromEnvironment: Client
fromEnvironment
= new
new Client(options?: ClientOptions): Client

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

Client
({
config?: Config | undefined

Client configuration object.

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

Creates an immutable instance.

@constructor

Config
(
function Envs(basePath?: string): ConfigInit

ENV variable (key/value) map.

Envs
(),
function Local(): ConfigInit

Development config.

Local
(),
function Defaults(basePath?: string): ConfigInit

JSON config.

Defaults
()) });

Each entry in dx-env.yml names a variable, the config path it sets, and its type:

LOG_FILTER:
path: runtime.client.log.filter
type: string
DX_PERSIST:
path: runtime.client.storage.persistence
type: boolean

You can install ECHO, configure a client for the browser or Node, and open the app’s space with an identity.

Defining types: declare the app’s organizations, people and events.