Welcome to the new Golem Cloud Docs! 👋
Documentation
Experimental Languages
JavaScript
Defining Components

Defining Golem Components in JavaScript

Creating a project

Using Golem's command line interface provides a set of predefined, Golem-specific examples to choose from as a starting point.

To see the available examples for JavaScript, run:

$ golem-cli list-examples --language js

The set of examples provided by Golem CLI is defined in the open-source repository golem-examples (opens in a new tab).

Then to create a new project based on the default JavaScript example, run:

$ golem-cli new --language js --package-name 'golem:demo' js-example

The command will create a new Golem project in the js-example directory, and print short, language-specific instructions on how to build the project.

Specification-first approach

Golem and the JavaScript toolchain currently requires defining the component's interface using the WebAssembly Interface Type (WIT) format. See the official documentation of this format (opens in a new tab) for reference.

Each new project generated with golem-cli contains a wit directory with at least one .wit file defining a world. This world can contain exports (exported functions and interfaces) and these exports will be the compiled Golem component's public API.

To implement the specification written in WIT, the JavaScript code must implement these interfaces and export them in main.js.

Exporting top-level functions

WIT allows exporting one or more top-level functions in the world section, for example:

package golem:demo;
 
world example {
    export hello-world: func() -> string;
}

To implement this function in JavaScript we simply have to export a function with matching names and types in main.js:

export function helloWorld(): string {
  return "hello";
}
⚠️

Note that in WIT, identifiers are using the kebab-case naming convention, while JavaScript uses the PascalCase and camelCase convention. The generated bindings map between these are handled automatically.

During componentization the exported objects and functions are checked against the wit world, eg. if the helloWorld function were not exported, we would get the following error:

(jco componentize) ComponentError: Expected a JS export definition for 'helloWorld'

Exporting interfaces

WIT supports defining and exporting whole interfaces, coupling together multiple functions and possibly custom data types.

Take the following example:

package golem:demo;
 
interface api {
  add: func(value: u64);
  get: func() -> u64;
}
 
world example {
  export api;
}

This is similar to having the two exported functions directly exported from the world section, but now a matching JavaScript object needs to be exported:

let state = BigInt(0)
 
export const api = {
  add(value) {
    console.log(`Adding ${value} to the counter`)
    state += value
  },
  get() {
    return state
  },
}

During componentization the exported objects and functions are checked against the wit world, eg. if the get function were not part of the api objects, we would get an error similar to this:

Exception while pre-initializing: (new Error(main.js source does not export a golem:component/api get function as expected by the world.
Try defining it on the interface alias api:
    export const api = {
        get () {}
    };,
    "main.bindings.js", 73))

See the Managing state section below to learn the recommended way of managing state in Golem components, which is required to implement these two functions.

Exporting resources

The WIT format supports defining and exporting resources - entities defined by their constructor function and the available methods on them.

Golem supports exporting these resources as part of the worker's API.

The following example modifies the previously seen counter example to define it as a resource, getting the counter's name as a constructor parameter:

package golem:demo;
 
interface api {
  resource counter {
    constructor(name: string);
    add: func(value: u64);
    get: func() -> u64;
  }
}
 
world example {
  export api;
}

Resources can have multiple instances within a worker. Their constructor returns a handle which is then used to call the methods on the resource. Learn more about how resources can be implicitly created and invoked through Golem's APIs in the Invocations page.

To implement the above defined WIT resource in JavaScript a few new steps must be taken:

  • define a class representing the resource - it can contain data!
  • implement the interface generated as the resource's interface for this class named CounterInstance
  • implement the constructor on the component class as defined by the interface CounterStatic

Let's see in code:

class Counter {
  name
  state = BigInt(0)
 
  constructor(name) {
    this.name = name
  }
 
  add(value) {
    this.state += value
  }
 
  get() {
    return this.state
  }
}
 
export const api = {
  Counter: Counter,
}

Data types defined in WIT

The WIT specifications contains some primitive and higher level data types and also allows defining custom data types which can be used as function parameters and return values on the exported functions, interfaces and resources.

The following table shows an example of each WIT data type and its corresponding Javascript type:

WIT TypeJavascript TypeNotes
boolboolean
u8, s8, u16, s16, u32, s32, f32, f64number
u64, s64bigint
charstringRepresents a single Unicode character
stringstring
list<T>T[]For most types
list<u8>Uint8ArraySpecial case for byte arrays
tuple<T1, T2, ...>[T1, T2, ...]
recordinterfaceFields become properties
variantunion of interfacesEach case becomes an interface with a tag and optional val
enumunion of string literals
option<T>T | nullIf T can be represented as null
option<T>Option<T>If T cannot be represented as null
result<T, E>Result<T, E>
result<_, string>Result<void, string>
flagsinterfaceEach flag becomes an optional boolean property
resourceclassMethods become class methods

The Result type above is expected to be a discriminated union:

const okResult = { tag: "ok", val: "hello" }
const errorResult = { tag: "err", val: "error" }

WIT records

The following WIT record type:

package golem:demo;
 
interface api {
    record user {
        id: u64,
        name: string,
    }
}

Will expect the following JavaScript object:

const user = {
  id: BigInt(1),
  name: "name",
}

WIT variants

The following WIT variant type:

package golem:demo;
 
interface api {
    variant color {
        red,
        green,
        blue,
        rgb(string)
    }
}

Will expect a JavaScript tagged union:

const colorRed = {"tag": "red"};
const colorRgb = {"tag": "rgb", val: "#ff00ff"};

WIT enums

The following WIT enum type:

package golem:demo;
 
interface api {
    enum color {
        red,
        green,
        blue
    }
}

Will expect a JavaScript string matching the above enum cases:

const color = "green";

WIT flags

The following WIT flags type:

package golem:demo;
 
interface api {
    flags access {
        read,
        write,
        lst
    }
}

Will expect a JavaScript object:

const access = {
  read: true,
  write: false,
  // some of the flags can be omitted, like `lst` in this example
}

Worker configuration

It is often required to pass configuration values to workers when they are started.

In general Golem supports three different ways of doing this:

  1. Defining a list of string arguments passed to the worker, available as command line arguments
  2. Defining a list of key-value pairs passed to the worker, available as environment variables.
  3. Using resource constructors to pass configuration values to the worker.

Command line arguments and environment variables

Command line arguments and environment variables are accessible through the generated bindings:

import { getArguments, getEnvironment } from "wasi:cli/environment@0.2.0"
 
const args = getArguments() // returns []string
const env = getEnvironment() // returns [string, string][]

Environment variables can be specified when a worker is explicitly created, but there are some environment variables that are always set by Golem:

  • GOLEM_WORKER_NAME - the name of the worker
  • GOLEM_COMPONENT_ID - the ID of the worker's component
  • GOLEM_COMPONENT_VERSION - the version of the component used for this worker

In addition to these, when using Worker to Worker communication, workers created by remote calls inherit the environment variables of the caller.

This feature makes environment variables a good fit for passing configuration such as hostnames, ports, or access tokens to trees of workers.

Resource constructors

As explained earlier, Golem workers can export resources and these resources can have constructor parameters.

Although resources can be used in many ways, one pattern for Golem is only create a single instance of the exported resource in each worker, and use it to pass configuration values to the worker. This is supported by Golem's worker invocation syntax directly, allowing to implicitly create workers and the corresponding resource by a single invocation as described on the Invocations page.

Managing state

Golem workers are stateful. There are two major techniques to store and manipulate state in a Golem worker implemented in JavaScript:

  1. Using a global variable
  2. Using resources and storing state in the resource's class

All techniques were demonstrated above in the code examples.