Soft-default Storage for toolkit engines.
- No ambient Storage → merge layerDefaultMemory (R fulfilled).
- Ambient Storage already present (app
Store.ServiceviaLayer.provide/provideMergeinto this layer) → build the engine against that store (override, including SQLite).
Prefer composing override into the toolkit layer so Soft unwrap sees it:
Process.layer(…).pipe(Layer.provideMerge(AppStore.layer…)) or
httpServer([…]).pipe(Layer.provide(AppStore.layer…)).
export const const withDefaultStorage: <A, E, R>(
engine: Layer.Layer<A, E, R | Storage>
) => Layer.Layer<A | Storage, E, R>
Soft-default
Storage
for toolkit engines.
- No ambient
Storage
→ merge
layerDefaultMemory
(R fulfilled).
- Ambient
Storage
already present (app
Store.Service via Layer.provide /
provideMerge into this layer) → build the engine against that store (override, including SQLite).
Prefer composing override into the toolkit layer so Soft unwrap sees it:
Process.layer(…).pipe(Layer.provideMerge(AppStore.layer…)) or
httpServer([…]).pipe(Layer.provide(AppStore.layer…)).
withDefaultStorage = <function (type parameter) A in <A, E, R>(engine: Layer.Layer<A, E, R | Storage>): Layer.Layer<A | Storage, E, R>A, function (type parameter) E in <A, E, R>(engine: Layer.Layer<A, E, R | Storage>): Layer.Layer<A | Storage, E, R>E, function (type parameter) R in <A, E, R>(engine: Layer.Layer<A, E, R | Storage>): Layer.Layer<A | Storage, E, R>R>(
engine: Layer.Layer<A, E, R | Storage>(parameter) engine: {
build: (memoMap: Layer.MemoMap, scope: Scope.Scope) => Effect.Effect<Context.Context<A>, E, R | Storage>;
pipe: { <A>(this: A): A; <A, B = never>(this: A, ab: (_: A) => B): B; <A, B = never, C = never>(this: A, ab: (_: A) => B, bc: (_: B) => C): C; <A, B = never, C = never, D = never>(this: A, ab: (_: A) => B, bc: (_: B) => C, cd: (_: C) => D): D; <…;
}
engine: import LayerLayer.interface Layer<in ROut, out E = never, out RIn = never>A Layer describes how to build one or more services for dependency injection.
When to use
Use to model construction of application services for dependency injection,
especially when services have dependencies, can fail during construction, or
need scoped setup and release.
Details
A Layer<ROut, E, RIn> represents ROut as the services this layer
provides, E as the possible errors during layer construction, and RIn as
the services this layer requires as dependencies.
Layer<function (type parameter) A in <A, E, R>(engine: Layer.Layer<A, E, R | Storage>): Layer.Layer<A | Storage, E, R>A, function (type parameter) E in <A, E, R>(engine: Layer.Layer<A, E, R | Storage>): Layer.Layer<A | Storage, E, R>E, function (type parameter) R in <A, E, R>(engine: Layer.Layer<A, E, R | Storage>): Layer.Layer<A | Storage, E, R>R | class Storageclass Storage {
Service: Service;
key: Identifier;
}
Scope bridge every store handle resolves through — provided by an app
Service
layer or
the soft-default
layerDefaultMemory
(via
withDefaultStorage
). Engines resolve
handles via
withDefault
/
withStorage
(preferred) or bridge.at.
Storage>,
): import LayerLayer.interface Layer<in ROut, out E = never, out RIn = never>A Layer describes how to build one or more services for dependency injection.
When to use
Use to model construction of application services for dependency injection,
especially when services have dependencies, can fail during construction, or
need scoped setup and release.
Details
A Layer<ROut, E, RIn> represents ROut as the services this layer
provides, E as the possible errors during layer construction, and RIn as
the services this layer requires as dependencies.
Layer<function (type parameter) A in <A, E, R>(engine: Layer.Layer<A, E, R | Storage>): Layer.Layer<A | Storage, E, R>A | class Storageclass Storage {
Service: Service;
key: Identifier;
}
Scope bridge every store handle resolves through — provided by an app
Service
layer or
the soft-default
layerDefaultMemory
(via
withDefaultStorage
). Engines resolve
handles via
withDefault
/
withStorage
(preferred) or bridge.at.
Storage, function (type parameter) E in <A, E, R>(engine: Layer.Layer<A, E, R | Storage>): Layer.Layer<A | Storage, E, R>E, function (type parameter) R in <A, E, R>(engine: Layer.Layer<A, E, R | Storage>): Layer.Layer<A | Storage, E, R>R> =>
import LayerLayer.const unwrap: <A, E, Exclude<R, Storage>, never, never>(self: Effect.Effect<Layer.Layer<A, E, Exclude<R, Storage>>, never, never>) => Layer.Layer<A, E, Exclude<R, Storage>>Unwraps a Layer from an Effect, flattening the nested structure.
When to use
Use when you have an Effect that produces a Layer and you want to
use that layer directly.
Details
The resulting Layer will have the combined error and dependency types from
both the outer Effect and the inner Layer.
Example (Unwrapping an effectful layer)
import { Context, Effect, Layer } from "effect"
class Database extends Context.Service<Database, {
readonly query: (sql: string) => Effect.Effect<string>
}>()("Database") {}
const layerEffect = Effect.succeed(
Layer.succeed(Database, { query: Effect.fn("Database.query")((sql: string) => Effect.succeed("result")) })
)
const unwrappedLayer = Layer.unwrap(layerEffect)
unwrap(
import EffectEffect.const gen: <Effect.Effect<Option.Option<StorageApi>, never, never>, Layer.Layer<A, E, Exclude<R, Storage>>>(f: () => Generator<Effect.Effect<Option.Option<StorageApi>, never, never>, Layer.Layer<A, E, Exclude<R, Storage>>, never>) => Effect.Effect<Layer.Layer<A, E, Exclude<R, Storage>>, never, never> (+1 overload)Provides a way to write effectful code using generator functions, simplifying
control flow and error handling.
When to use
Use when you want to write effectful code that looks and behaves like
synchronous code, while still handling asynchronous tasks, errors, and complex
control flow such as loops and conditions.
Generator functions work similarly to async/await but keep errors,
requirements, and interruption in the Effect type. You can yield* values
from effects and return the final result at the end.
Example (Sequencing effects with generators)
import { Data, Effect } from "effect"
class DiscountRateError extends Data.TaggedError("DiscountRateError")<{}> {}
const addServiceCharge = (amount: number) => amount + 1
const applyDiscount = (
total: number,
discountRate: number
): Effect.Effect<number, DiscountRateError> =>
discountRate === 0
? Effect.fail(new DiscountRateError())
: Effect.succeed(total - (total * discountRate) / 100)
const fetchTransactionAmount = Effect.promise(() => Promise.resolve(100))
const fetchDiscountRate = Effect.promise(() => Promise.resolve(5))
export const program = Effect.gen(function*() {
const transactionAmount = yield* fetchTransactionAmount
const discountRate = yield* fetchDiscountRate
const discountedAmount = yield* applyDiscount(
transactionAmount,
discountRate
)
const finalAmount = addServiceCharge(discountedAmount)
return `Final amount to charge: ${finalAmount}`
})
gen(function* () {
const const existing: Option.Option<StorageApi>existing = yield* import EffectEffect.const serviceOption: <
Storage,
StorageApi
>(
key: Context.Key<Storage, StorageApi>
) => Effect.Effect<
Option.Option<StorageApi>,
never,
never
>
Optionally accesses a service from the environment.
When to use
Use to read an optional dependency from the current context without making
that dependency part of the effect's required environment.
Details
This function attempts to access a service from the environment. If the
service is available, it returns Some(service). If the service is not
available, it returns None. Unlike service, this function does not
require the service to be present in the environment.
Example (Accessing an optional service)
import { Context, Effect, Option } from "effect"
// Define a service key
const Logger = Context.Service<{
log: (msg: string) => void
}>("Logger")
// Use serviceOption to optionally access the logger
const program = Effect.gen(function*() {
const maybeLogger = yield* Effect.serviceOption(Logger)
if (Option.isSome(maybeLogger)) {
maybeLogger.value.log("Service is available")
} else {
console.log("Service not available")
}
})
serviceOption(class Storageclass Storage {
key: Identifier;
of: (this: void, self: StorageApi) => StorageApi;
context: (self: StorageApi) => Context.Context<Storage>;
use: (f: (service: StorageApi) => Effect.Effect<A, E, R>) => Effect.Effect<A, E, Storage | R>;
useSync: (f: (service: StorageApi) => A) => Effect.Effect<A, never, Storage>;
Identifier: Identifier;
Service: Shape;
stack: string | undefined;
pipe: { <A>(this: A): A; <A, B = never>(this: A, ab: (_: A) => B): B; <A, B = never, C = never>(this: A, ab: (_: A) => B, bc: (_: B) => C): C; <A, B = never, C = never, D = never>(this: A, ab: (_: A) => B, bc: (_: B) => C, cd: (_: C) => D): D; <…;
toString: () => string;
toJSON: () => unknown;
}
Scope bridge every store handle resolves through — provided by an app
Service
layer or
the soft-default
layerDefaultMemory
(via
withDefaultStorage
). Engines resolve
handles via
withDefault
/
withStorage
(preferred) or bridge.at.
Storage);
if (import OptionOption.const isSome: <StorageApi>(
self: Option.Option<StorageApi>
) => self is Option.Some<StorageApi>
Checks whether an Option contains a value (Some).
When to use
Use when you need to branch on a present Option before accessing .value.
Details
- Acts as a type guard, narrowing to
Some<A>
Example (Checking for Some)
import { Option } from "effect"
console.log(Option.isSome(Option.some(1)))
// Output: true
console.log(Option.isSome(Option.none()))
// Output: false
isSome(const existing: Option.Option<StorageApi>existing)) {
return import LayerLayer.const provide: <R | Storage, E, A, never, never, Storage>(self: Layer.Layer<A, E, R | Storage>, that: Layer.Layer<Storage, never, never>) => Layer.Layer<A, E, Exclude<R, Storage>> (+3 overloads)Feeds the output services of the dependency layer into the requirements of
this layer, returning a layer that only provides the services from this layer.
When to use
Use when you need to hide an implementation dependency layer from callers.
Details
In serviceLayer.pipe(Layer.provide(dependencyLayer)), the dependency layer is
built first and is used to satisfy the requirements of serviceLayer.
Example (Providing layer dependencies)
import { Context, Effect, Layer } from "effect"
class Database extends Context.Service<Database, {
readonly query: (sql: string) => Effect.Effect<string>
}>()("Database") {}
class UserService extends Context.Service<UserService, {
readonly getUser: (id: string) => Effect.Effect<{
id: string
name: string
}>
}>()("UserService") {}
class Logger extends Context.Service<Logger, {
readonly log: (msg: string) => Effect.Effect<void>
}>()("Logger") {}
// Create dependency layers
const databaseLayer = Layer.succeed(Database, {
query: Effect.fn("Database.query")((sql: string) => Effect.succeed(`DB: ${sql}`))
})
const loggerLayer = Layer.succeed(Logger, {
log: Effect.fn("Logger.log")((msg: string) => Effect.sync(() => console.log(`[LOG] ${msg}`)))
})
// UserService depends on Database and Logger
const userServiceLayer = Layer.effect(UserService, Effect.gen(function*() {
const database = yield* Database
const logger = yield* Logger
return {
getUser: Effect.fn("UserService.getUser")(function*(id: string) {
yield* logger.log(`Looking up user ${id}`)
const result = yield* database.query(
`SELECT * FROM users WHERE id = ${id}`
)
return { id, name: result }
})
}
}))
// Provide dependencies to UserService layer
const userServiceWithDependencies = userServiceLayer.pipe(
Layer.provide(Layer.mergeAll(databaseLayer, loggerLayer))
)
// Now UserService layer has no dependencies
const program = Effect.gen(function*() {
const userService = yield* UserService
return yield* userService.getUser("123")
}).pipe(
Effect.provide(userServiceWithDependencies)
)
provide(engine: Layer.Layer<A, E, R | Storage>(parameter) engine: {
build: (memoMap: Layer.MemoMap, scope: Scope.Scope) => Effect.Effect<Context.Context<A>, E, R | Storage>;
pipe: { <A>(this: A): A; <A, B = never>(this: A, ab: (_: A) => B): B; <A, B = never, C = never>(this: A, ab: (_: A) => B, bc: (_: B) => C): C; <A, B = never, C = never, D = never>(this: A, ab: (_: A) => B, bc: (_: B) => C, cd: (_: C) => D): D; <…;
}
engine, import LayerLayer.const succeed: <Storage, StorageApi>(service: Context.Key<Storage, StorageApi>, resource: StorageApi) => Layer.Layer<Storage, never, never> (+1 overload)Constructs a layer that provides a single service from an already available
value.
When to use
Use when you need a Layer that provides a service from an already
constructed implementation without effectful acquisition.
Example (Creating a layer from a service implementation)
import { Context, Effect, Layer } from "effect"
class Database extends Context.Service<Database, {
readonly query: (sql: string) => Effect.Effect<string>
}>()("Database") {}
const DatabaseLive = Layer.succeed(Database, {
query: Effect.fn("Database.query")((sql: string) => Effect.succeed(`Query result: ${sql}`))
})
succeed(class Storageclass Storage {
key: Identifier;
of: (this: void, self: StorageApi) => StorageApi;
context: (self: StorageApi) => Context.Context<Storage>;
use: (f: (service: StorageApi) => Effect.Effect<A, E, R>) => Effect.Effect<A, E, Storage | R>;
useSync: (f: (service: StorageApi) => A) => Effect.Effect<A, never, Storage>;
Identifier: Identifier;
Service: Shape;
stack: string | undefined;
pipe: { <A>(this: A): A; <A, B = never>(this: A, ab: (_: A) => B): B; <A, B = never, C = never>(this: A, ab: (_: A) => B, bc: (_: B) => C): C; <A, B = never, C = never, D = never>(this: A, ab: (_: A) => B, bc: (_: B) => C, cd: (_: C) => D): D; <…;
toString: () => string;
toJSON: () => unknown;
}
Scope bridge every store handle resolves through — provided by an app
Service
layer or
the soft-default
layerDefaultMemory
(via
withDefaultStorage
). Engines resolve
handles via
withDefault
/
withStorage
(preferred) or bridge.at.
Storage, const existing: Option.Some<StorageApi>const existing: {
_tag: "Some";
_op: "Some";
value: A;
valueOrUndefined: A;
pipe: { <A>(this: A): A; <A, B = never>(this: A, ab: (_: A) => B): B; <A, B = never, C = never>(this: A, ab: (_: A) => B, bc: (_: B) => C): C; <A, B = never, C = never, D = never>(this: A, ab: (_: A) => B, bc: (_: B) => C, cd: (_: C) => D): D; <…;
toString: () => string;
toJSON: () => unknown;
}
existing.Some<StorageApi>.value: StorageApivalue));
}
return import LayerLayer.const provideMerge: <R | Storage, E, A, never, never, Storage>(self: Layer.Layer<A, E, R | Storage>, that: Layer.Layer<Storage, never, never>) => Layer.Layer<A | Storage, E, Exclude<R, Storage>> (+3 overloads)Feeds the output services of the dependency layer into the requirements of
this layer, returning a layer that provides both sets of services.
When to use
Use when you need to compose Layers while keeping both the constructed
service and the dependency used to build it available.
Details
Prefer
provide
when the dependency should stay private.
Example (Providing dependencies while retaining services)
import { Context, Effect, Layer } from "effect"
class Database extends Context.Service<Database, {
readonly query: (sql: string) => Effect.Effect<string>
}>()("Database") {}
class Logger extends Context.Service<Logger, {
readonly log: (msg: string) => Effect.Effect<void>
}>()("Logger") {}
class UserService extends Context.Service<UserService, {
readonly getUser: (id: string) => Effect.Effect<{
id: string
name: string
}>
}>()("UserService") {}
// Create dependency layers
const databaseLayer = Layer.succeed(Database, {
query: Effect.fn("Database.query")((sql: string) => Effect.succeed(`DB: ${sql}`))
})
const loggerLayer = Layer.succeed(Logger, {
log: Effect.fn("Logger.log")((msg: string) => Effect.sync(() => console.log(`[LOG] ${msg}`)))
})
// UserService depends on Database and Logger
const userServiceLayer = Layer.effect(UserService, Effect.gen(function*() {
const database = yield* Database
const logger = yield* Logger
return {
getUser: Effect.fn("UserService.getUser")(function*(id: string) {
yield* logger.log(`Looking up user ${id}`)
const result = yield* database.query(
`SELECT * FROM users WHERE id = ${id}`
)
return { id, name: result }
})
}
}))
// Provide dependencies and merge all services together
const allServicesLayer = userServiceLayer.pipe(
Layer.provideMerge(Layer.mergeAll(databaseLayer, loggerLayer))
)
// Now the resulting layer provides UserService, Database, AND Logger
const program = Effect.gen(function*() {
const userService = yield* UserService
const logger = yield* Logger // Still available!
const database = yield* Database // Still available!
const user = yield* userService.getUser("123")
yield* logger.log(`Found user: ${user.name}`)
return user
}).pipe(
Effect.provide(allServicesLayer)
)
provideMerge(engine: Layer.Layer<A, E, R | Storage>(parameter) engine: {
build: (memoMap: Layer.MemoMap, scope: Scope.Scope) => Effect.Effect<Context.Context<A>, E, R | Storage>;
pipe: { <A>(this: A): A; <A, B = never>(this: A, ab: (_: A) => B): B; <A, B = never, C = never>(this: A, ab: (_: A) => B, bc: (_: B) => C): C; <A, B = never, C = never, D = never>(this: A, ab: (_: A) => B, bc: (_: B) => C, cd: (_: C) => D): D; <…;
}
engine, const layerDefaultMemory: Layer.Layer<Storage>const layerDefaultMemory: {
build: (memoMap: Layer.MemoMap, scope: Scope.Scope) => Effect.Effect<Context.Context<Storage>, never, never>;
pipe: { <A>(this: A): A; <A, B = never>(this: A, ab: (_: A) => B): B; <A, B = never, C = never>(this: A, ab: (_: A) => B, bc: (_: B) => C): C; <A, B = never, C = never, D = never>(this: A, ab: (_: A) => B, bc: (_: B) => C, cd: (_: C) => D): D; <…;
}
Baked-in default store layer: provides
Storage
from a process-local in-memory
EventJournal. Materializes any scope on demand so
withDefault
never fails when this
layer is in context. Toolkit layers soft-merge this via
withDefaultStorage
; apps
override by providing a
Service
(Layer.provide / provideMerge) so Soft unwrap sees
ambient
Storage
before the default.
layerDefaultMemory);
}),
) as import LayerLayer.interface Layer<in ROut, out E = never, out RIn = never>A Layer describes how to build one or more services for dependency injection.
When to use
Use to model construction of application services for dependency injection,
especially when services have dependencies, can fail during construction, or
need scoped setup and release.
Details
A Layer<ROut, E, RIn> represents ROut as the services this layer
provides, E as the possible errors during layer construction, and RIn as
the services this layer requires as dependencies.
Layer<function (type parameter) A in <A, E, R>(engine: Layer.Layer<A, E, R | Storage>): Layer.Layer<A | Storage, E, R>A | class Storageclass Storage {
Service: Service;
key: Identifier;
}
Scope bridge every store handle resolves through — provided by an app
Service
layer or
the soft-default
layerDefaultMemory
(via
withDefaultStorage
). Engines resolve
handles via
withDefault
/
withStorage
(preferred) or bridge.at.
Storage, function (type parameter) E in <A, E, R>(engine: Layer.Layer<A, E, R | Storage>): Layer.Layer<A | Storage, E, R>E, function (type parameter) R in <A, E, R>(engine: Layer.Layer<A, E, R | Storage>): Layer.Layer<A | Storage, E, R>R>;