On this pageOverview
Resources
Commands are self-contained by default. Each execution starts fresh with no shared state. But some services need a single long-lived instance shared across Commands: clients that do real work at construction time, like an RPC client assembling its serialization and transport stack, or an analytics client that opens one session for the whole app. That’s what resources is for.
Think of it like a restaurant kitchen
Resources are kitchen equipment: the oven, the stand mixer, the deep fryer. They’re turned on when the kitchen opens and run all night. Every dish (Command) can use them. You don’t buy a new oven per order. An RpcClient and an analytics client are the same: expensive singletons that live for the entire app lifecycle. Need multiple pieces of equipment? Combine them with Layer.mergeAll.
Define a service using Context.Service, then pass its default layer to makeApplication via the resources config field. The runtime builds the Layer once, the first time it is needed: at startup in an app that declares Subscriptions (their pipelines run for the application’s lifetime), otherwise when the first Command runs. The built services are shared for the application’s lifetime and released at teardown. Commands access a service by yielding its tag.
import { Context, Effect, Layer, Schema as S } from 'effect'
import { Command, Runtime } from 'foldkit'
class ApiClientService extends Context.Service<ApiClientService, ApiClient>()(
'ApiClientService',
) {
static readonly Default = Layer.effect(this, makeApiClient)
}
const LoadUser = Command.define(
'LoadUser',
{ userId: S.String },
LoadedUser,
)(({ userId }) =>
Effect.gen(function* () {
const apiClient = yield* ApiClientService
const user = yield* apiClient.getUser(userId)
return LoadedUser({ user })
}),
)
const application = Runtime.makeApplication({
Model,
init,
update,
view,
container: document.getElementById('root'),
resources: ApiClientService.Default,
})Commands declare their resource requirements in the type signature via the third type parameter of Command. This makes dependencies explicit and type-checked. If a Command requires a service that isn’t provided via resources, you’ll get a compile error.
A Command can also discharge a requirement itself, with Effect.provide inside its Effect. Both placements work, so the question is which one a given service should use. The deciding lens: is the service an app-wide singleton every Command should reuse, or a per-Command decision where different Commands want different implementations? Four criteria sharpen the call.
- Construction cost times invocation frequency. A per-Command
Effect.providebuilds the layer on every invocation. ForHttp.layerfromfoldkit/http, a thin wrapper aroundfetch, that costs nothing. For an RPC client that assembles a serialization and transport stack, it means a Command that fires on every keystroke rebuilds the whole stack each time. Services whose construction does real work belong inresources, where construction happens once. - Instance identity. A per-Command provide constructs a fresh instance for each invocation, so Commands never share state through it. When every Command must talk to the same object, like one
RpcClientthat multiplexes their calls over a single connection, the service belongs inresources. - Failure isolation versus fail-fast. A per-Command provide scopes construction failure to the Commands that use the service: if the layer can’t be built, those Commands fail and the rest of the app keeps working.
resourcesis the opposite contract. The runtime provides the Layer to every Command, so a Layer that fails to build crashes the app with the crash view. For a service the whole app depends on, that one loud failure is the better behavior; for a genuinely optional service, per-Command provision keeps the failure contained. - Same tag, different implementations.
resourcesbinds each service tag to one implementation for the whole app. Only per-Command provides can give two Commands different implementations of the same tag, like one Command usingKeyValueStoreover localStorage while another uses it over sessionStorage.
Run the criteria over the common cases and the split falls out. HttpClient defaults to per-Command: Effect.provide(Http.layer) is one line, the Command stays self-contained, and no type annotations change anywhere else. Http.layer from foldkit/http is Effect’s Fetch-backed client with trace header propagation disabled by default. Effect tunes that default for servers, where handing trace context to your own downstream services is desirable; in the browser the same traceparent headers make otherwise CORS-simple requests trigger preflights against plain APIs and dev proxies. KeyValueStore stays per-Command: which storage backs the tag is a per-Command decision, and resources could only pick one. An RPC client goes in resources: construction is expensive, every Command should reuse one client, and when its configuration is broken, one visible failure the first time the client is needed tells you more than every server call failing on its own.
import { Effect, Schema as S } from 'effect'
import { HttpClient } from 'effect/unstable/http'
import { Command, Http } from 'foldkit'
const FetchWeather = Command.define(
'FetchWeather',
{ city: S.String },
SucceededFetchWeather,
FailedFetchWeather,
)(({ city }) =>
Effect.gen(function* () {
const client = yield* HttpClient.HttpClient
const response = yield* client.get(`https://api.weather.com/${city}`)
const data = yield* S.decodeUnknownEffect(WeatherResponse)(
yield* response.json,
)
return SucceededFetchWeather({ data })
}).pipe(
Effect.catch(() =>
Effect.succeed(FailedFetchWeather({ error: 'Request failed' })),
),
Effect.provide(Http.layer),
),
)An HTTP client can still graduate. When an app grows many HTTP Commands, or shares a derived HttpApiClient across modules, provide Http.layer once via resources instead. The moment you find yourself writing a withClient helper to cut the repetition is the signal. Providing at the edge also helps tests: the Command’s Effect keeps HttpClient in its requirements, so an Effect-level test can provide a mock with Layer.succeed(HttpClient.HttpClient, mockClient) directly. A per-Command provide needs a separately exported raw Effect to test the same way.
The resources field takes a single Layer, but Effect layers compose. Use Layer.mergeAll to combine multiple service layers into one.
import { Layer } from 'effect'
import { Runtime } from 'foldkit'
const application = Runtime.makeApplication({
Model,
init,
update,
view,
container: document.getElementById('root'),
resources: Layer.mergeAll(
ApiClientService.Default,
AnalyticsService.Default,
ComputeWorkerService.Default,
),
})Resources live for the entire application. But what if a resource should only exist while the model is in a certain state, like a camera stream during a video call, or a WebSocket while on a chat page? That’s what Managed Resources are for.