II Basics

Calls

A component calls another component’s operations through context.remote. Calls use logical addresses, so the caller does not need to know where the other component runs.

An address is the component’s name followed by the endpoint: orders.reduce. A component may belong to a namespace, which groups related components and becomes the first part of the address: an invoices component in the billing namespace is called as context.remote.billing.invoices.issue.

§Input/Query Segregation

A component implements an operation; the caller determines which entity data it applies to and supplies its arguments. These are separate parts of a call:

  • Query selects the entity data.
  • Input carries the arguments of the operation.

Suppose the orders component implements an operation that reduces an order’s total:

export async function transition (input: Input, entry: State) {
  if (input.amount > entry.total)
    return new Error('AMOUNT_EXCEEDS_TOTAL')

  entry.total -= input.amount
}

A call to this operation looks like this:

await context.remote.orders.reduce({
  query: { id: orderId },
  input: { amount: 500 }
})

An endpoint is a named operation exposed by a component. Here, reduce is an endpoint of orders, addressed as orders.reduce.

The runtime uses the query to retrieve the order and passes its state alongside the input to the implementation. The order identifier does not have to become part of the operation’s input contract merely to locate its state.

§Input

Each operation declares the shape of its arguments with an input schema:

operations:
  reduce:
    type: transition
    input:
      type: object
      properties:
        amount:
          type: integer
          minimum: 1
      required: [amount]

The runtime validates the input before executing the operation. A missing amount, a fractional value, or a value below one is rejected without running the implementation.

The schema describes valid arguments. Rules that depend on current entity state remain in the operation: the runtime can check that amount is a positive integer, while reduce checks that it does not exceed the selected order’s total.

§Query

A query selects data within the operation’s declared scope: one entity object, a set, or a stream. It can select by identity (id or ids) or filter entity properties through criteria. It can also order the selection with sort and, for observations, choose the state properties supplied to the operation with projection.

Criteria use RSQL: == tests equality, >= compares values, ; joins conditions with AND, and , joins them with OR. Parentheses group conditions.

For example, a call to an observation over a set of orders can select a page of pending orders:

const orders = await context.remote.orders.enumerate({
  query: {
    criteria: 'status==pending;total>=1000',
    sort: ['total:desc', 'id:asc'],
    omit: 20,
    limit: 10
  }
})

The query selects pending orders with a total of at least 1000, orders them by descending total and then by identity, skips the first 20, and supplies at most 10 to the observation. The enumerate operation returns the selected entries.

omit and limit apply to the entity selection, not to the operation’s eventual reply. Their availability depends on the operation’s scope, not on its type:

  • An operation over a set (scope: entries) — an observation, a transition or an effect — is called with the ids of its set, or with a limit on what its criteria select. A call with neither is refused: a set is loaded whole, so every set is bounded.
  • omit skips entities of an ordered selection, so it is refused without sort: pages of an unordered set repeat and miss entities.
  • A stream accepts limit to bound a window but does not accept omit; continuation uses a token. When limit or a token is supplied, sorting is restricted to id and CREATED.
  • An operation over one entity accepts neither, and neither does an assignment: its changeset is written to whatever its query selects.

limit is a positive integer and omit a non-negative one. The runtime validates the query against the operation’s contract before executing it.

§Local calls

A local call is the same kind of operation call addressed to the caller’s own component. context.local supplies that address implicitly:

await context.local.reduce({
  query: { id: orderId },
  input: { amount: 500 }
})

Within orders, this calls its own reduce endpoint. Input validation, query selection, and the operation’s execution phases still apply; it is not a direct invocation of the function.

§Calls nobody waits for

Every call above waits for its reply. A call can also be handed over as a task: it is accepted, kept, and run later, and its caller carries on without an answer. Tasks describes what that changes.

Search
Search the documentation