Spec and class
A node is a class that extends Frond.NodeBase and carries a static spec. The spec is configuration — tag, key, dependencies, driver. The class is the instance consumers read: this.result, this.deps, this.args, plus any getters and methods you add.
import * as Frond from "@frondruntime/core";
type ProfileResult = { displayName: string; email: string };
type ProfileSpec = Frond.NodeSpec<{
args: { userId: string };
key: Frond.Key.Structure<{ userId: string }>;
result: ProfileResult;
}>;
export class ProfileNode extends Frond.NodeBase<ProfileSpec> {
static readonly spec = Frond.resourceSpec<ProfileSpec>({
tag: Frond.tag("resources/profile"),
key: (args) => Frond.Key.structure({ userId: args.userId }),
driver: Frond.Driver.Async<ProfileSpec>({
acquire: Frond.Driver.Acquire((ctx) => fetchProfile(ctx.args.userId)),
}),
});
get displayName(): string {
return this.result.displayName;
}
}
The spec type
Frond.NodeSpec<TShape> is a type-only descriptor of the node’s shape. Declare the fields your node uses.
| Field | Meaning |
|---|---|
args | The input passed when requesting the node. |
key | The value the key(args) function returns. |
result | The type of this.result once ready. |
deps | The declared dependencies record. |
actions | The action contracts the node exposes. |
A node with no input uses Frond.Args.None for args and Frond.Key.Singleton for key.
The spec value
The static spec is built with a kind constructor — resourceSpec, serviceSpec, or facadeSpec. See Kinds. All take the same input:
| Field | Required | Meaning |
|---|---|---|
tag | Yes | Frond.tag(value) — names the kind of node. |
key | Yes | (args) => Frond.Key.structure(...) or Frond.Key.singleton() — derives the instance key. Pure and deterministic. |
dependencies | No | Frond.dependencies((args) => ({ ... })). Omitted means no dependencies. |
driver | Yes | How the node loads and updates. See Drivers. |
tag and key together produce the node’s id. See Identity and keys.
The class
Frond.NodeBase<TSpec> gives every node a typed read surface. You extend it and add domain logic.
| Member | Type | Description |
|---|---|---|
this.result | spec result | The loaded result. Reading it registers observation. |
this.deps | resolved deps | The ready dependency instances. |
this.args | spec args | The args this node was requested with. |
this.actions | action facade | Callable actions. See Actions. |
this.nodeId | NodeId | The graph node id. |
this.tag | string | The node’s tag. |
Add getters and methods that compute from this.result, this.deps, and this.args. They are MobX-derived, so a component reading them inside an observer re-renders when the underlying result changes.
export class ProfileNode extends Frond.NodeBase<ProfileSpec> {
static readonly spec = Frond.resourceSpec<ProfileSpec>({
/* ... */
});
get initials(): string {
return this.result.displayName
.split(" ")
.map((part) => part[0])
.join("");
}
}
The runtime constructs the instance only once the node is ready, so inside the class this.result and this.deps are always present. To run cleanup when the runtime closes the node, register it with the protected this.onRuntimeClose(disposer).
Next: Kinds — resource, service, and facade, and when to pick which.