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

Kinds

A node spec is built with one of four kind constructors. They take the same input and produce nodes that behave identically at runtime. The kind is a label: it states what the node is for and appears in diagnostics (resource:resources/profile). Pick the one that matches the node’s intent.

Frond.resourceSpec<Spec>({ /* ... */ });
Frond.serviceSpec<Spec>({ /* ... */ });
Frond.facadeSpec<Spec>({ /* ... */ });
Frond.nodeSpec<Spec>({ /* ... */ });

Choosing a kind

KindConstructorUse for
ResourceresourceSpecData that loads and can refresh or expire — a profile, a list, a fetched document.
ServiceserviceSpecA long-lived capability other nodes depend on — a config client, a transport, an auth session.
FacadefacadeSpecA node whose result composes its dependencies — an overview built from other nodes.
NodenodeSpecThe neutral default when none of the above fit.

The distinction is for you and your diagnostics, not the runtime. A resource and a service with the same tag and key would still resolve to the same node — kind is not part of identity, and it does not change readiness, refresh, eviction, or liveness.

Resource

The common case: a node that owns a result loaded from somewhere, refreshable and subject to result validity.

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)),
  }),
});

Service

A capability that other nodes depend on and that tends to live for the session — configuration, a websocket transport, an auth client. Often a singleton.

static readonly spec = Frond.serviceSpec<ConfigSpec>({
  tag: Frond.tag("services/config"),
  key: () => Frond.Key.singleton(),
  driver: Frond.Driver.Async<ConfigSpec>({
    acquire: Frond.Driver.Acquire(() => loadConfig()),
  }),
});

Facade

A node whose result is assembled from its dependencies, presenting one combined view.

static readonly spec = Frond.facadeSpec<AccountSpec>({
  tag: Frond.tag("facades/account"),
  key: () => Frond.Key.singleton(),
  dependencies: Frond.dependencies(() => ({
    profile: Frond.dep(ProfileNode, { userId: "u_42" }),
    billing: Frond.dep(BillingNode, { userId: "u_42" }),
  })),
  driver: Frond.Driver.Async<AccountSpec>({
    acquire: Frond.Driver.Acquire((ctx) =>
      mergeAccount(ctx.deps.profile.result, ctx.deps.billing.result)
    ),
  }),
});

Next: Drivers — the acquire, refresh, action, and live hooks, and the context they run with.