Skip to content
This repository has been archived by the owner on Sep 3, 2024. It is now read-only.

noise64/golem-ts-multi

Repository files navigation

Important

This example now lives at golem-examples as a template.

Golem TypeScript Example with Multiple Components and Worker to Worker RPC Communication

Building

The project uses a custom Typescript build file: build.ts, ran through tsx. The build.ts file handles generating stubs, building, deploying and testing; and also handles "up to date" checks based on modification times. Build commands can be run using npx tsx build.ts <command> (or without npx, if tsx is available globally), but all commands have npm run <command> wrappers, in the examples we will use the latter.

To see the available commands use:

npm run help

> help
> npx tsx build.ts

Available commands:
  fmt:                  format using prettier
  lint:                 lint project using eslint
  fix:                  format, lint and fix project using prettier and eslint
  build:                build all components
  updateRpcStubs:       update stubs based on componentDependencies
  generateNewComponent: generates new component from template, expects <component-name>
  deploy:               deploy (create or update) all components
  deployComponent:      deploy (create or update) the specified component, expects <component-name>
  test:                 run tests
  clean:                clean outputs and generated code

For building the project for the first time (or after clean) use the following commands:

npm install
npm run updateRpcStubs
npm run build

After this, using the build command is enough, unless there are changes in the RPC dependencies, in that case updateRpcStubs is needed again.

Note that multiple commands can be used in one invocation (if they do not have parameters), e.g.:

npm run updateRpcStubs build

The final components that are usable by golem are placed in the out/components folder.

Deploying and testing the example

In the example 3 simple counter components are defined, which can be familiar from the smaller examples. To showcase the remote calls, the counters add functions are connected, apart from increasing their own counter:

  • component one delegates the add call to component two and three too,
  • and component two delegates to component three.

In both cases the current worker name will be used as target worker name too.

Apart from worker name, remote calls also require the target components' deployed ID. For this the example uses environment variables, and uses the lib/cfg subpackage (which is shared between the components) to extract it.

The examples assume a configured default golem-cli profile, and will use that.

To test, first we have to build the project as seen in the above:

npm run updateRpcStubs build

Then we can deploy our components with golem-cli, for which a wrapper command is provided:

npm run deploy

Note that npm run deployComponent <component-name> can be used to deploy (or update) only one component.

Once the components are deployed, a simple example integration test suite can be used to test the components.

Note that after deploy it might still take some time for the components to be prepared inside golem, which for TypeScript can take about 30-50 seconds, so invoking workers or running the tests immediately after deploy might have to wait for this preparation.

Once the components are deployed, a simple example integration test suite can be used to test the components. The tests are in the /test/integration.test.ts test file, and can be run with:

npm run test

The first test simply tests if our components metadata is available through golem-cli component get.

The second test will:

  • get the component URNs with golem-cli component get
  • generates a random worker name, so our tests are starting from a clean state
  • adds 1 - 1 worker for component one and component two with the required environment variables containing the other workers' component ids
  • then makes various component invocations with golem-cli worker invoke-and-await and tests if the counters - after increments - are holding the right value according to the delegated add function calls.

Adding Components

Use the generateNewComponent command to add new components to the project:

npm run generateNewComponent component-four

The above will create a new component in the src/components/component-four directory based on the template at /component-template/component.

After adding a new component the build command will also include it.

Using Worker to Worker RPC calls

Under the hood

Under the hood the build.ts commands below use generic golem-cli stubgen subcommands:

  • golem-cli stubgen build for creating remote call stub WIT definitions and WASM components for the stubs
  • golem-cli stubgen add-stub-dependency for adding the stub WIT definitions to a component's WIT dependencies
  • golem-cli stubgen compose for composing components with the stub components

Commands and required manual steps

The dependencies between components are defined in the build.ts build script:

// Defines worker to worker RPC dependencies
const componentDependencies: Dependencies = {
  "component-one": ["component-two", "component-three"],
  "component-two": ["component-three"],
};

After changing dependencies the updateRpcStubs command can be used to create the necessary stubs:

npm run updateRpcStubs

The command will create stubs for the dependency projects in the /out/stub directory and will also place the required stub WIT interfaces on the dependant component's wit/deps directory.

To actually use the dependencies in a project it also has to be manually imported in the component's world.

E.g. with the above definitions the following import has to be manually added to /components/component-one/wit/component-one.wit:

import pack-ns:component-two-stub;
import pack-ns:component-three-stub;

So the component definition should like similar to this:

package pack-ns:component-one;

// See https://component-model.bytecodealliance.org/design/wit.html for more details about the WIT syntax

interface component-one-api {
  add: func(value: u64);
  get: func() -> u64;
}

world component-one {
  // Golem dependencies
  import golem:api/host@0.2.0;
  import golem:rpc/types@0.1.0;

  // WASI dependencies
  import wasi:blobstore/blobstore;
  // .
  // .
  // .
  // other dependencies
  import wasi:sockets/instance-network@0.2.0;

  // Project Component dependencies
  import pack-ns:component-two-stub;
  import pack-ns:component-three-stub;

  export component-one-api;
}

After this build command can be used to update bindings, which now should include the required functions for calling other components.

Here's an example that delegates the Add call to another component and waits for the result:

import {ComponentTwoApi} from "./generated/component-two";
import {ComponentThreeApi} from "golem:component-three-stub/stub-component-three";

let state = BigInt(0);

export const componentTwoApi: ComponentTwoApi = {
  add(value: bigint) {
    console.log(`Adding ${value} to the counter`);

    console.log("Calling component three");
    const componentThree = new ComponentThreeApi({value: "urn"});
    componentThree.blockingAdd(value);

    state += value;
  },
  get() {
    return state;
  }
};

Once a remote call is in place, the build command will also compose the stub components into the caller component.

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published