This module contains functionality specific to each program.
import { state, nodes, root } from "membrane";
// ↑
// |
// |
// The Membrane Module
The state
object is used to keep long-lived data around.
This object is needed because every time a program is updated with new code, a new JavaScript module is created
to replace the old one, but module-level variables from the old one are not automatically transferred over. The state
object is thus used to share state between the two.
A good rule of thumb is to keep persistent data in the state
object.
nodes
is the object that holds the dependencies of a program. It holds handles to graph nodes. Using these nodes is
how the program communicates with others, makes http request, etc.
Along with the dependencies that you specify, the nodes
object comes with two dependencies that are injected by
default:
nodes.process
: used to read metadata about the program. For now just endpointUrl
is available.nodes.clock
: used to read the time, set timers and cronjobs.These two, instead of pointing to other programs on your graph, point to special system programs that can provide the corresponding functionality.
The fetch
function in Membrane uses nodes.http
behind the scenes to
actually make requests, and sleep
uses nodes.clock
root
is a handle that references the program's own root node. It can be used to reference any node in the program's
graph, for example to emit event, or to specify event handlers.
Fields are the readable nodes in the graph.
Reads the value of a scalar field. For non-scalar fields (aka objects), use $query
instead.
This method has been deprecated since you can now await
a scalar field to get its value:
const value = await nodes.path.to.string;
However, it's still available, so the above could be written like so:
const value = await nodes.path.to.string.$get();
Reads subvalues from a values by running a GraphQL query on the node. For example:
// Query two fields on a pull-request
const { title, body } = await nodes.pullRequest.$query("{ title body }");
// Outermost curly braces can be omitted.
const { title, body } = await nodes.pullRequest.$query("title body");
// Nested fields can be queried too!
const { title, comments } = await nodes.pullRequest.$query(
"title comments { page { body } }"
);
Actions are the callable nodes in the graph. You can think of actions as functions that can be referenced by other programs.
Actions in the UI are rendered in magenta.
Invokes the action returning a promise to its result.
This method has been deprecated since you can now await
an action to invoke it:
const result = await nodes.path.to.action({ ... });
However, it's still available. The above could be written with $invoke
like so:
const result = await nodes.path.to.action({ ... }).$invoke();
Invokes the action after the specified number of seconds.
// Invoke after a minute
nodes.path.to.action.$invokeIn(60);
Invokes the action at the specified date.
// Invoke at the New Year
nodes.path.to.action.$invokeAt(new Date("2023-01-01"));
Invokes the action repeatedly based on the provided cron expression. Unlike traditional cron, however, our cron expressions include seconds. For example:
// Invoke every 15 min
nodes.path.to.action.$cron("0 */15 * * * *");
// A more elaborate example:
// sec min hour day of month month day of week year
let expression = "0 30 9,12,15 1,15 May-Aug Mon,Wed,Fri 2018/2";
nodes.path.to.action.$cron(expression);
Events are nodes you can subscribe to in order to receive notifications when they happen.
The program that exposes the event can then $emit
it to invoke the handling action registered by all subscribers.
Events in the UI are rendered in purple.
Subscribes to an event. An event is always handled by an action, so you need to declare an action to handle them. We understand that it would be more convenient to allow callbacks, but callbacks are not graph-referenceable (e.g. what happens when you change the code of the callback?) but actions are, so we use them.
const id = await nodes.pullRequest.closed.$subscribe(root.handler);
// Note that the event is received in the second parameter of the action
export function handler(args, { event }) {
console.log("The pull request has been closed", event);
}
// Later, to unsubscribe
unsubscribe(id);
Another example demonstrating how to pass arguments to the handler action. Handler args can be used to hold some context about the subscription.
const context = "hello";
nodes.some.event.$subscribe(root.handler({ context }));
// Note that the event is received in the second parameter of the action
export function handler(args, { event }) {
console.log("Event fired", args.context, event);
}
Emits the event to all subscribers. For example:
root.path.to.event.$emit();
The type of value passed to $emit
must match the type declared in the schema. So a String
-typed event could be emitted like this:
root.path.to.event.$emit("Something happened");
Returns a promise that sleeps for the provided number of seconds.
console.log("Before");
await sleep(60);
console.log("After 1 minute");
Unsubscribes to a previously created subscription. For example, to subscribe for only one event:
state.subscription = await nodes.some.event.$subscribe(root.handler());
export handler() {
console.log('Event fired. Unsubscribing');
unsubscribe(state.subscription);
}