v0 · Developer Preview Frond is under active development. APIs may change between releases.

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.

FieldMeaning
argsThe input passed when requesting the node.
keyThe value the key(args) function returns.
resultThe type of this.result once ready.
depsThe declared dependencies record.
actionsThe 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:

FieldRequiredMeaning
tagYesFrond.tag(value) — names the kind of node.
keyYes(args) => Frond.Key.structure(...) or Frond.Key.singleton() — derives the instance key. Pure and deterministic.
dependenciesNoFrond.dependencies((args) => ({ ... })). Omitted means no dependencies.
driverYesHow 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.

MemberTypeDescription
this.resultspec resultThe loaded result. Reading it registers observation.
this.depsresolved depsThe ready dependency instances.
this.argsspec argsThe args this node was requested with.
this.actionsaction facadeCallable actions. See Actions.
this.nodeIdNodeIdThe graph node id.
this.tagstringThe 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.