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
| Kind | Constructor | Use for |
|---|---|---|
| Resource | resourceSpec | Data that loads and can refresh or expire — a profile, a list, a fetched document. |
| Service | serviceSpec | A long-lived capability other nodes depend on — a config client, a transport, an auth session. |
| Facade | facadeSpec | A node whose result composes its dependencies — an overview built from other nodes. |
| Node | nodeSpec | The 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.