Fluid Signals API


This page documents the API of fluid.cell, the primitive of the reactive library underlying Fluid Infusion 6. You can read background information on how fluid.cell offers extra capabilities above traditional reactive libraries, and also a more extended essay explaining its goals in the context of a malleable substrate.

The raw markdown underlying these docs is available on GitHub, suitable for direct ingestion by your friendly local neighbourhood intelligence.

Construction

fluid.cell(initialValue, props)

  • {Any} [initialValue] - The initial value to store in the cell
  • {Object} [props] - Optional additional properties to contextualise the cell
  • Returns: {Cell} - The freshly constructed cell

A fresh reactive cell is constructed, with an optional initial value and optional additional properties.

If the initial value is undefined, this will be upgraded to an unavailable value marking the signal’s value as uninitialised.

The only currently supported property in props is

  • {String} name - Supplies a name or address for this cell

Reading and writing

Cell.get()

The value of a reactive cell can be read by calling Cell.get. This forces the reactive tree of computations behind the cell to bring its value up to date. A call to Cell.get within the reactive context of a computed function will set up an automatic subscription to updates of the cell, rerunning the function whenever the value changes.

Cell.set(newValue, [source])

  • {any} newValue The new value to which the cell is to be updated
  • {String} [source] An optional source tag name to be applied to this update as is propagates.

The reactive cell’s value can be updated by calling Cell.set. This will trigger a cascade of updates through the reactive tree. Any computed values that are depended on by effects will be updated immediately. Computed values that are not depended on by effects will only updated if an effect is created which later reads them, or Cell.get is called for a cell which depends on them.

Optionally, at the time of calling

Cell._value

A “blind read” of the current value of the cell, regardless of how stale it is, can be made by looking at the _value property. This isn’t recommended except in specialised cases, usually of framework-building.

Relations and effects

Cell.computed(fn, staticSources, props)

  • {Function | null} fn - The function which will reactively evaluate this cell’s value, or null if an existing relation is to be torn down
  • {Cell[]} [staticSources] - Any statically known cell dependencies whose reactively evaluated arguments will be supplied to fn when it is called
  • {ComputedProps} [props] - Optional additional properties to contextualise the computation
  • Returns: {Cell} - This cell

computed is available as a function in the prototype of cells created via fluid.cell.

Cell.computed constructs or tears down a computed relationship between a target cell (this one) and one or more source cells accepted by the supplied function fn. It’s preferred that the source cells are specified in the staticSources arguments — when they update, their values will be dereferenced and supplied as plain values as arguments to the function. However, any other cells that the function manages to reference in its surrounding scopes as it executes will also be tracked and schedule a reinvocation of the function, following the standard tracking semantics of commodity signals implementations.

The first staticSource cell, if there is one, will have a special status of establishing the key of the relationship which will be unique in the context of the target cell. If there is no such cell, a null value will serve as the key. If an existing relation is present on the target cell with the same key, it will be replaced by the supplied one.

If null is supplied in place of the fn argument, any existing relation with the same key will be torn down.

The additional ComputedProps argument can contain the following fields:

{Object} ComputedProps:

  • {Boolean} isAsync - Indicates if the computation is asynchronous.
  • {Boolean} isFree - Indicates if this is a “free” computation that will deliver unavailable values.

Here is a basic example setting up two cells connected by a simple computation:

For a more advanced example setting up and tearing down bidirectional relationships, you can see the visual test sample for Bidirectional temperature conversion in the extended essay.

Cell.asyncComputed(fn, [staticSources], [props])

  • {Function | null} fn - The function which will reactively evaluate this cell’s value, or null if an existing relation is to be torn down
  • {Cell[]} [staticSources] - Any statically known cell dependencies whose reactively evaluated arguments will be supplied to fn when it is called
  • {ComputedProps} [props] - Optional additional properties to contextualise the computation
  • Returns: {Cell} - This cell

Sets up an asynchronous computed relationship between one or more cells. This is equivalent to a call to Cell.computed() with the props in the 3rd argument set with isAsync: true.

In this case, the function argument fn rather than returning a plain value should be an async or async* function which returns either:

  • A Promise representing a deferred value or error
  • An AsyncIterator produced by the yield function indicating that this function is a async generator of a bounded or unbounded sequence of deferred values.

Here’s an example receiving a result through an asynchronous computation. Rather than manually waiting for event turns as here, asynchronous results are more conveniently received through fluid.cell.signalToPromise, see below.

fluid.cell.effect(fn, [staticSources], [props])

  • {Function} fn - The function to execute reactively when any of the staticSources change
  • {Cell[]} staticSources - The array of source cells whose values are dependencies for the effect
  • {Object} [props] - Optional properties to configure the effect
  • {Function} [props.onDispose] - Optional cleanup function to run when the effect is disposed
  • {Boolean} [props.isFree] - If true, the effect will run even if some sources are unavailable, delivering unavailable
  • {String} [props.excludeSource] - Optional source name (as supplied as last argument to cell.set) that will have its notification skipped values in their place.
  • Returns: {Effect}

fluid.cell.effect accepts a function and arguments to run when one or more reactive values change. The signature is similar to Cell.compute only the evaluation is not lazy — an effect runs immediately upon registration and again when any values change. Another difference is that effects do not correspond to a value — values returned from fn are ignored. In order to deactivate the effect it needs to be explicitly disposed by calling the Effect.dispose() method on the returned Effect.

Another difference is in handling of unavailable arguments — if any cells referenced in staticSources or in dynamically tracked reactive values resolve to an unavailable value, and the effect is not marked as isFree, notification of the function is skipped.

An effect represents a resource in the world outside the reactive graph and as such may require special actions when it is torn down. The optional props argument accepts a callback onDispose which is called when the effect is disposed via Effect.dispose().

This simple example follows the pattern of previous examples, but actively pulls updates to B using an effect rather than manually pulling them via get():

This example shows fluid.cell’s source tracking facility. If a source of changes is tagged by using the source argument to fluid.set, changes due to this source can skip notifying an effect with a matching excludeSource argument. This can be helpful when interfacing with external systems which should not be notified of changes that they themselves have caused:

Comprehension

fluid.cell.findCause([cell])

  • {Cell} cell If supplied, the cell whose update cause should be reported. If absent, any current reaction will be used instead.
  • Returns: {Cell[]}

Reports the cause of any reaction which has updated a given cell, or else the one that is currently in progress, in the form of an array of nodes reaching back from the supplied cell to the one whose modification triggered the reaction. The triggering cell appears first in the array.

Utilities

fluid.cell.signalToPromise(valSignal)

  • {Cell<any>} valSignal - The signal to monitor.
  • Returns: Promise<any> - A Promise that resolves with the signal’s first available value.

Gear from the world of signals into a Promise that can be awaited.

The asyncComputed example from above is more conveniently written:

Cell.refresh(staticSources)

  • {Cell[]} [staticSources] - An optional array of static source cells to identify the computation edge to be refreshed
  • this: {Cell} The cell for which an incoming edge is to be refreshed

A method available on any Cell: Refreshes the value of the cell by re-evaluating its computation for the specified static sources. Finds the incoming edge corresponding to the given static sources and triggers an update for this cell along that edge. Particularly useful when the value is computed via an asynchronous activity in the world — if the edge is async, the cell’s value will be set to stale and the asynchronous activity will be restarted.

Unavailable values

Some background on unavailable values is available on their own page. An unavailable value held at a cell represents a value which is either uninitialised, pending due to outstanding I/O, or an error.

Unavailable values can be received by effects by marking them with the isFree property — otherwise, they only receive fully settled, concrete values.

An unavailable value is created using the fluid.unavailable API, which in the simplest usage simply accepts a string holding a message explaining why the value is unavailable. These values are normally created by frameworks rather than users.

fluid.unavailable(cause = {}, variety = “error”)

  • {String|Object|Array} [cause={}] - A list of dependencies or reasons for unavailability.
  • {String} [variety="error"] - The variety of unavailable value:
    • "error" indicates a syntax issue that needs design intervention.
    • "config" indicates configuration designed to short-circuit evaluation which is not required.
    • "I/O" indicates pending I/O
  • Returns: {Unavailable} An unavailable value marker.

A special variety of unavailable value represents a value which is temporarily stale as a result of I/O pending to update it. This is created by fluid.pending which is just a wrapper for fluid.unavailable:

fluid.pending(staleValue, site)

  • {Any} staleValue - The most recently seen value before it became unavailable due to pending I/O.
  • {String} site - The site or resource (e.g. URL) responsible for the pending I/O.
  • Returns: {Unavailable} An object representing the unavailable state due to pending I/O.

fluid.isUnavailable(totest)

  • {any} totest - The value to be tested for being an unavailable value
  • Returns {Boolean} true if the supplied value was an unavailable, false if it is a plain value.

For more complex examples of propagating and receiving unavailable states, please consult the test cases in GitHub, and in this context, “Error propagation across async graph”, “Error propagation across async surfacing as promise rejection”.