WorkPool
WorkPool is an included Hyperlink Service — a priority work queue you can drop in when you need one. It takes a stream of items and drains them through a worker effect — one at a time, or many in parallel, with priority, de-duplication, retries, and back-pressure. You declare it once, and everywhere you yield* Emails you get a single handle that does everything — enqueue work, watch it drain, and steer it — through the same value.
That handle is the whole surface. There is no separate producer and admin API: the code that enqueues an email can also pause the queue, read how many are pending, and subscribe to every completion. And because its a HyperService, the handle reads identically whether the queue runs in this process or across the network — only the layer that provides it changes.
The librarys focus is building your own HyperServices; WorkPool is a ready-made one when a priority queue is what you need. This page starts at the smallest WorkPool that works, then each piece it was built from.
Your first WorkPool
A WorkPool has two halves: a Tag (what the queue is — its item type and name) and a layer (how it runs — the worker). Here is the whole thing:
import { import WorkPoolWorkPool } from "hyperlink-ts"
import { import EffectEffect, import SchemaSchema } from "effect"
// The item: a plain schema. This is the queue's payload type.
const const EmailJob: Schema.Struct<{
readonly to: Schema.String
readonly subject: Schema.String
}>
const EmailJob: {
Type: Struct.Type<Fields>;
Encoded: Struct.Encoded<Fields>;
DecodingServices: Struct.DecodingServices<Fields>;
EncodingServices: Struct.EncodingServices<Fields>;
Iso: Struct.Iso<Fields>;
fields: Fields;
mapFields: (f: (fields: { readonly to: Schema.String; readonly subject: Schema.String }) => To, options?: { readonly unsafePreserveChecks?: boolean | undefined } | undefined) => Schema.Struct<{ [K in keyof Readonly<To>]: Readonly<To>[K]; }>;
Rebuild: Rebuild;
ast: Ast;
annotate: (annotations: Schema.Annotations.Bottom<Schema.Struct.ReadonlySide<{ readonly to: Schema.String; readonly subject: Schema.String; }, 'Type'>, readonly []>) => Schema.Struct<{ readonly to: Schema.String; readonly subject: Schema.String; }>;
annotateKey: (annotations: Schema.Annotations.Key<Schema.Struct.ReadonlySide<{ readonly to: Schema.String; readonly subject: Schema.String }, 'Type'>>) => Schema.Struct<{ readonly to: Schema.String; readonly subject: Schema.String }>;
check: (checks_0: Check<Schema.Struct.ReadonlySide<{ readonly to: Schema.String; readonly subject: Schema.String }, 'Type'>>, ...checks: Array<Check<Schema.Struct.ReadonlySide<{ readonly to: Schema.String; readonly subject: Schema.String }, 'Type…;
rebuild: (ast: Objects) => Schema.Struct<{ readonly to: Schema.String; readonly subject: Schema.String }>;
make: (input: Struct.ReadonlyMakeIn<{ readonly to: String; readonly subject: String }>, options?: MakeOptions) => Struct.ReadonlySide<{ readonly to: String; readonly subject: String }, 'Type'>;
makeOption: (input: Struct.ReadonlyMakeIn<{ readonly to: String; readonly subject: String }>, options?: MakeOptions) => Option_.Option<Struct.ReadonlySide<{ readonly to: String; readonly subject: String }, 'Type'>>;
makeEffect: (input: Struct.ReadonlyMakeIn<{ readonly to: String; readonly subject: String }>, options?: MakeOptions) => Effect.Effect<Struct.ReadonlySide<{ readonly to: String; readonly subject: String }, 'Type'>, SchemaError, 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; <…;
}
EmailJob = import SchemaSchema.function Struct<
Fields extends Struct.Fields
>(fields: Fields): Struct<Fields>
Defines a struct schema from a map of field schemas.
Details
Each field value is a schema. Use
optionalKey
or
optional
to
mark fields as optional, and
mutableKey
to mark them as mutable.
The resulting schema's Type is a readonly object type with the fields'
decoded types. The Encoded form mirrors the field schemas' encoded types.
Example (Defining a basic struct)
import { Schema } from "effect"
const Person = Schema.Struct({
name: Schema.String,
age: Schema.Number,
email: Schema.optionalKey(Schema.String)
})
// { readonly name: string; readonly age: number; readonly email?: string }
type Person = typeof Person.Type
const alice = Schema.decodeUnknownSync(Person)({ name: "Alice", age: 30 })
console.log(alice)
// { name: 'Alice', age: 30 }
Struct({
to: Schema.String(property) to: {
Rebuild: Rebuild;
Iso: Iso;
ast: Ast;
Type: T;
Encoded: E;
DecodingServices: RD;
EncodingServices: RE;
annotate: (annotations: Schema.Annotations.Bottom<string, readonly []>) => Schema.String;
annotateKey: (annotations: Schema.Annotations.Key<string>) => Schema.String;
check: (checks_0: Check<string>, ...checks: Array<Check<string>>) => Schema.String;
rebuild: (ast: String) => Schema.String;
make: (input: string, options?: MakeOptions) => string;
makeOption: (input: string, options?: MakeOptions) => Option_.Option<string>;
makeEffect: (input: string, options?: MakeOptions) => Effect.Effect<string, SchemaError, 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; <…;
}
to: import SchemaSchema.const String: Stringconst String: {
Rebuild: Rebuild;
Iso: Iso;
ast: Ast;
Type: T;
Encoded: E;
DecodingServices: RD;
EncodingServices: RE;
annotate: (annotations: Schema.Annotations.Bottom<string, readonly []>) => Schema.String;
annotateKey: (annotations: Schema.Annotations.Key<string>) => Schema.String;
check: (checks_0: Check<string>, ...checks: Array<Check<string>>) => Schema.String;
rebuild: (ast: String) => Schema.String;
make: (input: string, options?: MakeOptions) => string;
makeOption: (input: string, options?: MakeOptions) => Option_.Option<string>;
makeEffect: (input: string, options?: MakeOptions) => Effect.Effect<string, SchemaError, 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; <…;
}
Type-level representation of
String
.
Schema for string values. Validates that the input is typeof "string".
String,
subject: Schema.String(property) subject: {
Rebuild: Rebuild;
Iso: Iso;
ast: Ast;
Type: T;
Encoded: E;
DecodingServices: RD;
EncodingServices: RE;
annotate: (annotations: Schema.Annotations.Bottom<string, readonly []>) => Schema.String;
annotateKey: (annotations: Schema.Annotations.Key<string>) => Schema.String;
check: (checks_0: Check<string>, ...checks: Array<Check<string>>) => Schema.String;
rebuild: (ast: String) => Schema.String;
make: (input: string, options?: MakeOptions) => string;
makeOption: (input: string, options?: MakeOptions) => Option_.Option<string>;
makeEffect: (input: string, options?: MakeOptions) => Effect.Effect<string, SchemaError, 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; <…;
}
subject: import SchemaSchema.const String: Stringconst String: {
Rebuild: Rebuild;
Iso: Iso;
ast: Ast;
Type: T;
Encoded: E;
DecodingServices: RD;
EncodingServices: RE;
annotate: (annotations: Schema.Annotations.Bottom<string, readonly []>) => Schema.String;
annotateKey: (annotations: Schema.Annotations.Key<string>) => Schema.String;
check: (checks_0: Check<string>, ...checks: Array<Check<string>>) => Schema.String;
rebuild: (ast: String) => Schema.String;
make: (input: string, options?: MakeOptions) => string;
makeOption: (input: string, options?: MakeOptions) => Option_.Option<string>;
makeEffect: (input: string, options?: MakeOptions) => Effect.Effect<string, SchemaError, 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; <…;
}
Type-level representation of
String
.
Schema for string values. Validates that the input is typeof "string".
String,
})
// The tag: the contract. `Self` is the class itself (Effect's two-stage form).
class class Emailsclass Emails {
key: Identifier;
Service: {
status: Hyperlink.Subscribable<QueueStatus>;
size: Hyperlink.Subscribable<number>;
isEmpty: Hyperlink.Subscribable<boolean>;
start: Effect.Effect<void, never, Requirements>;
pause: Effect.Effect<void>;
resume: Effect.Effect<void>;
shutdown: Effect.Effect<void>;
clear: Effect.Effect<number, never, Requirements>;
metrics: { readonly stream: Stream.Stream<QueueMetrics>; readonly query: (input: { readonly limit?: number; readonly since?: DateTime.Utc; readonly until?: DateTime.Utc }) => Effect.Effect<ReadonlyArray<QueueMetrics>, never, Requirements> };
add: QueueEnqueue<Payload, never, Requirements>;
prioritize: QueueEnqueue<Payload, never, Requirements>;
defer: QueueEnqueue<Payload, never, Requirements>;
enqueue: (entries: ReadonlyArray<QueueEntry<Payload>>) => Effect.Effect<void, never, Requirements>;
release: (input: { readonly options?: QueueReleaseOptions }) => Effect.Effect<ReadonlyArray<QueueEntry<Payload>>, never, Requirements>;
releaseEncoded: (input: { readonly options?: QueueReleaseOptions }) => Effect.Effect<ReadonlyArray<Hyperlink.Decoded<typeof queueEncodedEntry>>, QueueReleaseEncodingError, Requirements>;
deadLetter: (input: { readonly selector: QueueEntrySelector<Payload> | QueueEntry<Payload>; readonly options: QueueRouteOptions }) => Effect.Effect<ReadonlyArray<QueueEntry<Payload>>, never, Requirements>;
drop: (input: { readonly selector: QueueEntrySelector<Payload> | QueueEntry<Payload>; readonly options: QueueRouteOptions }) => Effect.Effect<ReadonlyArray<QueueEntry<Payload>>, never, Requirements>;
events: Stream.Stream<QueueEvent<Payload, Error, Success>>;
};
}
Emails extends import WorkPoolWorkPool.Tag<Emails>(): {
<F, HSelf, D>(key: string, payload: Schema.Struct<F>, options: {
readonly description?: string;
readonly node: NodeKey<HSelf>;
readonly defaults: DefaultsInput<D>;
}): TagWithDefaults<QueueNodeBoundTagCarriers<Emails, F, HSelf, Schema.Void, Schema.Never>, D>;
<F, HSelf>(key: string, payload: Schema.Struct<F>, options: {
readonly description?: string;
readonly node: NodeKey<HSelf>;
}): QueueNodeBoundTagCarriers<...>;
<F, Success, Error>(key: string, payload: Schema.Struct<...>, success: Success, error?: Error | undefined): QueueTagCarriers<...>;
<F, D>(key: string, payload: Schema.Struct<...>, options: {
...;
}): TagWithDefaults<...>;
<F>(key: string, payload: Schema.Struct<...>, options?: {
...;
} | undefined): QueueTagCarriers<...>;
<F, HSelf, D>(key: string, config: WorkPool.QueueTagConfig<...> & {
...;
}): TagWithDefaults<...>;
<F, HSelf>(key: string, config: WorkPool.QueueTagConfig<...> & {
...;
}): QueueNodeBoundTagCarriers<...>;
<F, D, Success, Error>(key: string, config: WorkPool.QueueTagConfig<...> & {
...;
}): TagWithDefaults<...>;
<F, Success, Error>(key: string, config: WorkPool.QueueTagConfig<...>): QueueTagCarriers<...>;
}
export Tag
Tag<class Emailsclass Emails {
key: Identifier;
Service: {
status: Hyperlink.Subscribable<QueueStatus>;
size: Hyperlink.Subscribable<number>;
isEmpty: Hyperlink.Subscribable<boolean>;
start: Effect.Effect<void, never, Requirements>;
pause: Effect.Effect<void>;
resume: Effect.Effect<void>;
shutdown: Effect.Effect<void>;
clear: Effect.Effect<number, never, Requirements>;
metrics: { readonly stream: Stream.Stream<QueueMetrics>; readonly query: (input: { readonly limit?: number; readonly since?: DateTime.Utc; readonly until?: DateTime.Utc }) => Effect.Effect<ReadonlyArray<QueueMetrics>, never, Requirements> };
add: QueueEnqueue<Payload, never, Requirements>;
prioritize: QueueEnqueue<Payload, never, Requirements>;
defer: QueueEnqueue<Payload, never, Requirements>;
enqueue: (entries: ReadonlyArray<QueueEntry<Payload>>) => Effect.Effect<void, never, Requirements>;
release: (input: { readonly options?: QueueReleaseOptions }) => Effect.Effect<ReadonlyArray<QueueEntry<Payload>>, never, Requirements>;
releaseEncoded: (input: { readonly options?: QueueReleaseOptions }) => Effect.Effect<ReadonlyArray<Hyperlink.Decoded<typeof queueEncodedEntry>>, QueueReleaseEncodingError, Requirements>;
deadLetter: (input: { readonly selector: QueueEntrySelector<Payload> | QueueEntry<Payload>; readonly options: QueueRouteOptions }) => Effect.Effect<ReadonlyArray<QueueEntry<Payload>>, never, Requirements>;
drop: (input: { readonly selector: QueueEntrySelector<Payload> | QueueEntry<Payload>; readonly options: QueueRouteOptions }) => Effect.Effect<ReadonlyArray<QueueEntry<Payload>>, never, Requirements>;
events: Stream.Stream<QueueEvent<Payload, Error, Success>>;
};
}
Emails>()("app/Emails", {
QueueTagConfig<{ readonly to: String; readonly subject: String; }, Void, Never>.payload: Schema.Struct<{ readonly to: Schema.String; readonly subject: Schema.String }>(property) QueueTagConfig<{ readonly to: String; readonly subject: String; }, Void, Never>.payload: {
Type: Struct.Type<Fields>;
Encoded: Struct.Encoded<Fields>;
DecodingServices: Struct.DecodingServices<Fields>;
EncodingServices: Struct.EncodingServices<Fields>;
Iso: Struct.Iso<Fields>;
fields: Fields;
mapFields: (f: (fields: { readonly to: Schema.String; readonly subject: Schema.String }) => To, options?: { readonly unsafePreserveChecks?: boolean | undefined } | undefined) => Schema.Struct<{ [K in keyof Readonly<To>]: Readonly<To>[K]; }>;
Rebuild: Rebuild;
ast: Ast;
annotate: (annotations: Schema.Annotations.Bottom<Schema.Struct.ReadonlySide<{ readonly to: Schema.String; readonly subject: Schema.String; }, 'Type'>, readonly []>) => Schema.Struct<{ readonly to: Schema.String; readonly subject: Schema.String; }>;
annotateKey: (annotations: Schema.Annotations.Key<Schema.Struct.ReadonlySide<{ readonly to: Schema.String; readonly subject: Schema.String }, 'Type'>>) => Schema.Struct<{ readonly to: Schema.String; readonly subject: Schema.String }>;
check: (checks_0: Check<Schema.Struct.ReadonlySide<{ readonly to: Schema.String; readonly subject: Schema.String }, 'Type'>>, ...checks: Array<Check<Schema.Struct.ReadonlySide<{ readonly to: Schema.String; readonly subject: Schema.String }, 'Type…;
rebuild: (ast: Objects) => Schema.Struct<{ readonly to: Schema.String; readonly subject: Schema.String }>;
make: (input: Struct.ReadonlyMakeIn<{ readonly to: String; readonly subject: String }>, options?: MakeOptions) => Struct.ReadonlySide<{ readonly to: String; readonly subject: String }, 'Type'>;
makeOption: (input: Struct.ReadonlyMakeIn<{ readonly to: String; readonly subject: String }>, options?: MakeOptions) => Option_.Option<Struct.ReadonlySide<{ readonly to: String; readonly subject: String }, 'Type'>>;
makeEffect: (input: Struct.ReadonlyMakeIn<{ readonly to: String; readonly subject: String }>, options?: MakeOptions) => Effect.Effect<Struct.ReadonlySide<{ readonly to: String; readonly subject: String }, 'Type'>, SchemaError, 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; <…;
}
payload: const EmailJob: Schema.Struct<{
readonly to: Schema.String
readonly subject: Schema.String
}>
const EmailJob: {
Type: Struct.Type<Fields>;
Encoded: Struct.Encoded<Fields>;
DecodingServices: Struct.DecodingServices<Fields>;
EncodingServices: Struct.EncodingServices<Fields>;
Iso: Struct.Iso<Fields>;
fields: Fields;
mapFields: (f: (fields: { readonly to: Schema.String; readonly subject: Schema.String }) => To, options?: { readonly unsafePreserveChecks?: boolean | undefined } | undefined) => Schema.Struct<{ [K in keyof Readonly<To>]: Readonly<To>[K]; }>;
Rebuild: Rebuild;
ast: Ast;
annotate: (annotations: Schema.Annotations.Bottom<Schema.Struct.ReadonlySide<{ readonly to: Schema.String; readonly subject: Schema.String; }, 'Type'>, readonly []>) => Schema.Struct<{ readonly to: Schema.String; readonly subject: Schema.String; }>;
annotateKey: (annotations: Schema.Annotations.Key<Schema.Struct.ReadonlySide<{ readonly to: Schema.String; readonly subject: Schema.String }, 'Type'>>) => Schema.Struct<{ readonly to: Schema.String; readonly subject: Schema.String }>;
check: (checks_0: Check<Schema.Struct.ReadonlySide<{ readonly to: Schema.String; readonly subject: Schema.String }, 'Type'>>, ...checks: Array<Check<Schema.Struct.ReadonlySide<{ readonly to: Schema.String; readonly subject: Schema.String }, 'Type…;
rebuild: (ast: Objects) => Schema.Struct<{ readonly to: Schema.String; readonly subject: Schema.String }>;
make: (input: Struct.ReadonlyMakeIn<{ readonly to: String; readonly subject: String }>, options?: MakeOptions) => Struct.ReadonlySide<{ readonly to: String; readonly subject: String }, 'Type'>;
makeOption: (input: Struct.ReadonlyMakeIn<{ readonly to: String; readonly subject: String }>, options?: MakeOptions) => Option_.Option<Struct.ReadonlySide<{ readonly to: String; readonly subject: String }, 'Type'>>;
makeEffect: (input: Struct.ReadonlyMakeIn<{ readonly to: String; readonly subject: String }>, options?: MakeOptions) => Effect.Effect<Struct.ReadonlySide<{ readonly to: String; readonly subject: String }, 'Type'>, SchemaError, 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; <…;
}
EmailJob,
}) {}
// The layer: the worker. `effect` runs once per item.
const const EmailsLive: Layer<
Storage | Emails | Local<Emails>,
never,
never
>
const EmailsLive: {
build: (memoMap: MemoMap, scope: Scope.Scope) => Effect<Context.Context<Storage | Emails | Local<Emails>>, 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; <…;
}
EmailsLive = import WorkPoolWorkPool.function layer<Emails, {
readonly to: Schema.String;
readonly subject: Schema.String;
}, never, never, Schema.Void, Schema.Never>(tag: QueueTagFor<Emails, {
readonly to: Schema.String;
readonly subject: Schema.String;
}, Schema.Void, Schema.Never>, config: QueueVerbConfig<{
readonly to: Schema.String;
readonly subject: Schema.String;
}, never, never, never, Schema.Void>): Layer<Emails | Local<Emails> | Storage, never, never> (+1 overload)
Run this WorkPool locally — soft-defaults
Store.Storage
(R fulfilled; override by
providing an app store into this layer). Accepts a plain
Tag
or a
priority
tag and
dispatches to the matching engine.
layerMemory
is an alias for the same soft-default.
layer(class Emailsclass Emails {
key: Identifier;
Service: {
status: Hyperlink.Subscribable<QueueStatus>;
size: Hyperlink.Subscribable<number>;
isEmpty: Hyperlink.Subscribable<boolean>;
start: Effect.Effect<void, never, Requirements>;
pause: Effect.Effect<void>;
resume: Effect.Effect<void>;
shutdown: Effect.Effect<void>;
clear: Effect.Effect<number, never, Requirements>;
metrics: { readonly stream: Stream.Stream<QueueMetrics>; readonly query: (input: { readonly limit?: number; readonly since?: DateTime.Utc; readonly until?: DateTime.Utc }) => Effect.Effect<ReadonlyArray<QueueMetrics>, never, Requirements> };
add: QueueEnqueue<Payload, never, Requirements>;
prioritize: QueueEnqueue<Payload, never, Requirements>;
defer: QueueEnqueue<Payload, never, Requirements>;
enqueue: (entries: ReadonlyArray<QueueEntry<Payload>>) => Effect.Effect<void, never, Requirements>;
release: (input: { readonly options?: QueueReleaseOptions }) => Effect.Effect<ReadonlyArray<QueueEntry<Payload>>, never, Requirements>;
releaseEncoded: (input: { readonly options?: QueueReleaseOptions }) => Effect.Effect<ReadonlyArray<Hyperlink.Decoded<typeof queueEncodedEntry>>, QueueReleaseEncodingError, Requirements>;
deadLetter: (input: { readonly selector: QueueEntrySelector<Payload> | QueueEntry<Payload>; readonly options: QueueRouteOptions }) => Effect.Effect<ReadonlyArray<QueueEntry<Payload>>, never, Requirements>;
drop: (input: { readonly selector: QueueEntrySelector<Payload> | QueueEntry<Payload>; readonly options: QueueRouteOptions }) => Effect.Effect<ReadonlyArray<QueueEntry<Payload>>, never, Requirements>;
events: Stream.Stream<QueueEvent<Payload, Error, Success>>;
};
description: string | undefined;
of: (this: void, self: WorkPool.WorkPool<{ to: string; subject: string }, void, never, never>) => WorkPool.WorkPool<{ to: string; subject: string }, void, never, never>;
context: (self: WorkPool.WorkPool<{ to: string; subject: string }, void, never, never>) => Context<Emails>;
use: (f: (service: WorkPool.WorkPool<{ to: string; subject: string }, void, never, never>) => Effect.Effect<A, E, R>) => Effect.Effect<A, E, Emails | R>;
useSync: (f: (service: WorkPool.WorkPool<{ to: string; subject: string }, void, never, never>) => A) => Effect.Effect<A, never, Emails>;
Identifier: Identifier;
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;
}
Emails, {
effect: (
item: Schema.Struct.ReadonlySide<
{
readonly to: Schema.String
readonly subject: Schema.String
},
"Type"
>,
ctx: WorkPool.EffectContext<
Schema.Struct.ReadonlySide<
{
readonly to: Schema.String
readonly subject: Schema.String
},
"Type"
>,
QueueEnqueueErrors,
never
>
) => Effect.Effect<void, never, never>
Daemon each item. The success channel A is driven by the tag's success wire schema
(default void): with a success schema the worker must return Effect<A, E, R> and that
value rides Completed.success / store.completed; without one it stays Effect<void, E, R>.
effect: (job: Schema.Struct.ReadonlySide<
{
readonly to: Schema.String
readonly subject: Schema.String
},
"Type"
>
(parameter) job: {
to: string;
subject: string;
}
job) => import EffectEffect.const log: (
...message: ReadonlyArray<any>
) => Effect<void>
Logs one or more messages using the default log level.
Example (Logging at the default level)
import { Effect } from "effect"
const program = Effect.gen(function*() {
yield* Effect.log("Starting computation")
const result = 2 + 2
yield* Effect.log("Result:", result)
yield* Effect.log("Multiple", "values", "can", "be", "logged")
return result
})
Effect.runPromise(program).then(console.log)
// Output:
// timestamp=2023-... level=INFO message="Starting computation"
// timestamp=2023-... level=INFO message="Result: 4"
// timestamp=2023-... level=INFO message="Multiple values can be logged"
// 4
log(`sending "${job: Schema.Struct.ReadonlySide<
{
readonly to: Schema.String
readonly subject: Schema.String
},
"Type"
>
(parameter) job: {
to: string;
subject: string;
}
job.subject: stringsubject}" to ${job: Schema.Struct.ReadonlySide<
{
readonly to: Schema.String
readonly subject: Schema.String
},
"Type"
>
(parameter) job: {
to: string;
subject: string;
}
job.to: stringto}`),
concurrency?: number | undefinedMax items processing concurrently (worker count).
concurrency: 4,
})Thats a complete, running WorkPool. To use it, yield* Emails for the handle and add an item — anywhere the layer is provided:
const const program: Effect.Effect<
void,
never,
Emails
>
const program: {
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;
}
program = import EffectEffect.const gen: {
<Eff extends Effect<any, any, any>, AEff>(
f: () => Generator<Eff, AEff, never>
): Effect<
AEff,
[Eff] extends [never]
? never
: [Eff] extends [
Effect<infer _A, infer E, infer _R>
]
? E
: never,
[Eff] extends [never]
? never
: [Eff] extends [
Effect<infer _A, infer _E, infer R>
]
? R
: never
>
<Self, Eff extends Effect<any, any, any>, AEff>(
options: { readonly self: Self },
f: (this: Self) => Generator<Eff, AEff, never>
): Effect<
AEff,
[Eff] extends [never]
? never
: [Eff] extends [
Effect<infer _A, infer E, infer _R>
]
? E
: never,
[Eff] extends [never]
? never
: [Eff] extends [
Effect<infer _A, infer _E, infer R>
]
? R
: never
>
}
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 emails: WorkPool.WorkPool<
{ to: string; subject: string },
void,
never,
never
>
const emails: {
status: Hyperlink.Subscribable<QueueStatus>;
size: Hyperlink.Subscribable<number>;
isEmpty: Hyperlink.Subscribable<boolean>;
start: Effect.Effect<void, never, Requirements>;
pause: Effect.Effect<void>;
resume: Effect.Effect<void>;
shutdown: Effect.Effect<void>;
clear: Effect.Effect<number, never, Requirements>;
metrics: { readonly stream: Stream.Stream<QueueMetrics>; readonly query: (input: { readonly limit?: number; readonly since?: DateTime.Utc; readonly until?: DateTime.Utc }) => Effect.Effect<ReadonlyArray<QueueMetrics>, never, Requirements> };
add: QueueEnqueue<Payload, never, Requirements>;
prioritize: QueueEnqueue<Payload, never, Requirements>;
defer: QueueEnqueue<Payload, never, Requirements>;
enqueue: (entries: ReadonlyArray<QueueEntry<Payload>>) => Effect.Effect<void, never, Requirements>;
release: (input: { readonly options?: QueueReleaseOptions }) => Effect.Effect<ReadonlyArray<QueueEntry<Payload>>, never, Requirements>;
releaseEncoded: (input: { readonly options?: QueueReleaseOptions }) => Effect.Effect<ReadonlyArray<Hyperlink.Decoded<typeof queueEncodedEntry>>, QueueReleaseEncodingError, Requirements>;
deadLetter: (input: { readonly selector: QueueEntrySelector<Payload> | QueueEntry<Payload>; readonly options: QueueRouteOptions }) => Effect.Effect<ReadonlyArray<QueueEntry<Payload>>, never, Requirements>;
drop: (input: { readonly selector: QueueEntrySelector<Payload> | QueueEntry<Payload>; readonly options: QueueRouteOptions }) => Effect.Effect<ReadonlyArray<QueueEntry<Payload>>, never, Requirements>;
events: Stream.Stream<QueueEvent<Payload, Error, Success>>;
}
emails = yield* class Emailsclass Emails {
key: Identifier;
Service: {
status: Hyperlink.Subscribable<QueueStatus>;
size: Hyperlink.Subscribable<number>;
isEmpty: Hyperlink.Subscribable<boolean>;
start: Effect.Effect<void, never, Requirements>;
pause: Effect.Effect<void>;
resume: Effect.Effect<void>;
shutdown: Effect.Effect<void>;
clear: Effect.Effect<number, never, Requirements>;
metrics: { readonly stream: Stream.Stream<QueueMetrics>; readonly query: (input: { readonly limit?: number; readonly since?: DateTime.Utc; readonly until?: DateTime.Utc }) => Effect.Effect<ReadonlyArray<QueueMetrics>, never, Requirements> };
add: QueueEnqueue<Payload, never, Requirements>;
prioritize: QueueEnqueue<Payload, never, Requirements>;
defer: QueueEnqueue<Payload, never, Requirements>;
enqueue: (entries: ReadonlyArray<QueueEntry<Payload>>) => Effect.Effect<void, never, Requirements>;
release: (input: { readonly options?: QueueReleaseOptions }) => Effect.Effect<ReadonlyArray<QueueEntry<Payload>>, never, Requirements>;
releaseEncoded: (input: { readonly options?: QueueReleaseOptions }) => Effect.Effect<ReadonlyArray<Hyperlink.Decoded<typeof queueEncodedEntry>>, QueueReleaseEncodingError, Requirements>;
deadLetter: (input: { readonly selector: QueueEntrySelector<Payload> | QueueEntry<Payload>; readonly options: QueueRouteOptions }) => Effect.Effect<ReadonlyArray<QueueEntry<Payload>>, never, Requirements>;
drop: (input: { readonly selector: QueueEntrySelector<Payload> | QueueEntry<Payload>; readonly options: QueueRouteOptions }) => Effect.Effect<ReadonlyArray<QueueEntry<Payload>>, never, Requirements>;
events: Stream.Stream<QueueEvent<Payload, Error, Success>>;
};
description: string | undefined;
of: (this: void, self: WorkPool.WorkPool<{ to: string; subject: string }, void, never, never>) => WorkPool.WorkPool<{ to: string; subject: string }, void, never, never>;
context: (self: WorkPool.WorkPool<{ to: string; subject: string }, void, never, never>) => Context<Emails>;
use: (f: (service: WorkPool.WorkPool<{ to: string; subject: string }, void, never, never>) => Effect.Effect<A, E, R>) => Effect.Effect<A, E, Emails | R>;
useSync: (f: (service: WorkPool.WorkPool<{ to: string; subject: string }, void, never, never>) => A) => Effect.Effect<A, never, Emails>;
Identifier: Identifier;
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;
}
Emails
yield* const emails: WorkPool.WorkPool<
{ to: string; subject: string },
void,
never,
never
>
const emails: {
status: Hyperlink.Subscribable<QueueStatus>;
size: Hyperlink.Subscribable<number>;
isEmpty: Hyperlink.Subscribable<boolean>;
start: Effect.Effect<void, never, Requirements>;
pause: Effect.Effect<void>;
resume: Effect.Effect<void>;
shutdown: Effect.Effect<void>;
clear: Effect.Effect<number, never, Requirements>;
metrics: { readonly stream: Stream.Stream<QueueMetrics>; readonly query: (input: { readonly limit?: number; readonly since?: DateTime.Utc; readonly until?: DateTime.Utc }) => Effect.Effect<ReadonlyArray<QueueMetrics>, never, Requirements> };
add: QueueEnqueue<Payload, never, Requirements>;
prioritize: QueueEnqueue<Payload, never, Requirements>;
defer: QueueEnqueue<Payload, never, Requirements>;
enqueue: (entries: ReadonlyArray<QueueEntry<Payload>>) => Effect.Effect<void, never, Requirements>;
release: (input: { readonly options?: QueueReleaseOptions }) => Effect.Effect<ReadonlyArray<QueueEntry<Payload>>, never, Requirements>;
releaseEncoded: (input: { readonly options?: QueueReleaseOptions }) => Effect.Effect<ReadonlyArray<Hyperlink.Decoded<typeof queueEncodedEntry>>, QueueReleaseEncodingError, Requirements>;
deadLetter: (input: { readonly selector: QueueEntrySelector<Payload> | QueueEntry<Payload>; readonly options: QueueRouteOptions }) => Effect.Effect<ReadonlyArray<QueueEntry<Payload>>, never, Requirements>;
drop: (input: { readonly selector: QueueEntrySelector<Payload> | QueueEntry<Payload>; readonly options: QueueRouteOptions }) => Effect.Effect<ReadonlyArray<QueueEntry<Payload>>, never, Requirements>;
events: Stream.Stream<QueueEvent<Payload, Error, Success>>;
}
emails.WorkPool<{ to: string; subject: string; }, void, never, never>.add: QueueEnqueue<Payload, never, Requirements>add({ to: stringto: "[email protected]", subject: stringsubject: "Welcome" })
})Provide EmailsLive to program and the item drains through the worker. Nothing else is wired: the worker pool, the retry machinery, and the observability store all come with the layer.
The handle
Hover emails and youll see its type — the named handle:
const const emails: WorkPool.WorkPool<
{ to: string; subject: string },
void,
never,
never
>
const emails: {
status: Hyperlink.Subscribable<QueueStatus>;
size: Hyperlink.Subscribable<number>;
isEmpty: Hyperlink.Subscribable<boolean>;
start: Effect.Effect<void, never, Requirements>;
pause: Effect.Effect<void>;
resume: Effect.Effect<void>;
shutdown: Effect.Effect<void>;
clear: Effect.Effect<number, never, Requirements>;
metrics: { readonly stream: Stream.Stream<QueueMetrics>; readonly query: (input: { readonly limit?: number; readonly since?: DateTime.Utc; readonly until?: DateTime.Utc }) => Effect.Effect<ReadonlyArray<QueueMetrics>, never, Requirements> };
add: QueueEnqueue<Payload, never, Requirements>;
prioritize: QueueEnqueue<Payload, never, Requirements>;
defer: QueueEnqueue<Payload, never, Requirements>;
enqueue: (entries: ReadonlyArray<QueueEntry<Payload>>) => Effect.Effect<void, never, Requirements>;
release: (input: { readonly options?: QueueReleaseOptions }) => Effect.Effect<ReadonlyArray<QueueEntry<Payload>>, never, Requirements>;
releaseEncoded: (input: { readonly options?: QueueReleaseOptions }) => Effect.Effect<ReadonlyArray<Hyperlink.Decoded<typeof queueEncodedEntry>>, QueueReleaseEncodingError, Requirements>;
deadLetter: (input: { readonly selector: QueueEntrySelector<Payload> | QueueEntry<Payload>; readonly options: QueueRouteOptions }) => Effect.Effect<ReadonlyArray<QueueEntry<Payload>>, never, Requirements>;
drop: (input: { readonly selector: QueueEntrySelector<Payload> | QueueEntry<Payload>; readonly options: QueueRouteOptions }) => Effect.Effect<ReadonlyArray<QueueEntry<Payload>>, never, Requirements>;
events: Stream.Stream<QueueEvent<Payload, Error, Success>>;
}
emails = yield* class Emailsclass Emails {
key: Identifier;
Service: {
status: Hyperlink.Subscribable<QueueStatus>;
size: Hyperlink.Subscribable<number>;
isEmpty: Hyperlink.Subscribable<boolean>;
start: Effect.Effect<void, never, Requirements>;
pause: Effect.Effect<void>;
resume: Effect.Effect<void>;
shutdown: Effect.Effect<void>;
clear: Effect.Effect<number, never, Requirements>;
metrics: { readonly stream: Stream.Stream<QueueMetrics>; readonly query: (input: { readonly limit?: number; readonly since?: DateTime.Utc; readonly until?: DateTime.Utc }) => Effect.Effect<ReadonlyArray<QueueMetrics>, never, Requirements> };
add: QueueEnqueue<Payload, never, Requirements>;
prioritize: QueueEnqueue<Payload, never, Requirements>;
defer: QueueEnqueue<Payload, never, Requirements>;
enqueue: (entries: ReadonlyArray<QueueEntry<Payload>>) => Effect.Effect<void, never, Requirements>;
release: (input: { readonly options?: QueueReleaseOptions }) => Effect.Effect<ReadonlyArray<QueueEntry<Payload>>, never, Requirements>;
releaseEncoded: (input: { readonly options?: QueueReleaseOptions }) => Effect.Effect<ReadonlyArray<Hyperlink.Decoded<typeof queueEncodedEntry>>, QueueReleaseEncodingError, Requirements>;
deadLetter: (input: { readonly selector: QueueEntrySelector<Payload> | QueueEntry<Payload>; readonly options: QueueRouteOptions }) => Effect.Effect<ReadonlyArray<QueueEntry<Payload>>, never, Requirements>;
drop: (input: { readonly selector: QueueEntrySelector<Payload> | QueueEntry<Payload>; readonly options: QueueRouteOptions }) => Effect.Effect<ReadonlyArray<QueueEntry<Payload>>, never, Requirements>;
events: Stream.Stream<QueueEvent<Payload, Error, Success>>;
};
description: string | undefined;
of: (this: void, self: WorkPool.WorkPool<{ to: string; subject: string }, void, never, never>) => WorkPool.WorkPool<{ to: string; subject: string }, void, never, never>;
context: (self: WorkPool.WorkPool<{ to: string; subject: string }, void, never, never>) => Context<Emails>;
use: (f: (service: WorkPool.WorkPool<{ to: string; subject: string }, void, never, never>) => Effect.Effect<A, E, R>) => Effect.Effect<A, E, Emails | R>;
useSync: (f: (service: WorkPool.WorkPool<{ to: string; subject: string }, void, never, never>) => A) => Effect.Effect<A, never, Emails>;
Identifier: Identifier;
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;
}
Emails
})WorkPool<{ to: string; subject: string }, void, never, never> reads as WorkPool<Payload, Success, Error, Requirements>:
Payload — the decoded item type. What
addaccepts.Success — the workers return value (here
void; see Success values).Error — the workers typed failure channel (here
never— this queues worker cant fail in a typed way; see When work fails).Requirements — what a local
yield*needs (never); for a remote client its the transport.
The handle groups its members by what theyre for:
Enqueue —
add,prioritize,defer,enqueue.Observe —
size,isEmpty,status,events,metrics.Control —
start,pause,resume,shutdown,clear.Route —
release,deadLetter,drop.
The rest of this guide walks those groups.
The item, and its schema
The payload schema is the single source of truth for the item type. It is a real Effect Schema, not just a type: it decodes the item on the way in and — when the queue is served over RPC — validates it on the wire, so a bad item is rejected before it ever reaches a worker. The decoded type flows everywhere: add(item), the workers argument, and every event that carries the item.
Use whatever Schema shape fits — structs, unions, branded strings, nested data. The only rule is that the payload is a single schema (a Schema.Struct is the common case), so the wire contract is unambiguous.
The worker
The worker lives on the layer. effect is the only required field; the rest tune how it drains:
const const EmailsLive: Layer<
Emails | Local<Emails> | Storage,
never,
never
>
const EmailsLive: {
build: (memoMap: MemoMap, scope: Scope.Scope) => Effect<Context.Context<Emails | Local<Emails> | 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; <…;
}
EmailsLive = import WorkPoolWorkPool.function layer<Emails, {
readonly to: Schema.String;
readonly subject: Schema.String;
}, never, never, Schema.Void, Schema.Never>(tag: QueueTagFor<Emails, {
readonly to: Schema.String;
readonly subject: Schema.String;
}, Schema.Void, Schema.Never>, config: QueueVerbConfig<{
readonly to: Schema.String;
readonly subject: Schema.String;
}, never, never, never, Schema.Void>): Layer<Emails | Local<Emails> | Storage, never, never> (+1 overload)
Run this WorkPool locally — soft-defaults
Store.Storage
(R fulfilled; override by
providing an app store into this layer). Accepts a plain
Tag
or a
priority
tag and
dispatches to the matching engine.
layerMemory
is an alias for the same soft-default.
layer(class Emailsclass Emails {
key: Identifier;
Service: {
status: Hyperlink.Subscribable<QueueStatus>;
size: Hyperlink.Subscribable<number>;
isEmpty: Hyperlink.Subscribable<boolean>;
start: Effect.Effect<void, never, Requirements>;
pause: Effect.Effect<void>;
resume: Effect.Effect<void>;
shutdown: Effect.Effect<void>;
clear: Effect.Effect<number, never, Requirements>;
metrics: { readonly stream: Stream.Stream<QueueMetrics>; readonly query: (input: { readonly limit?: number; readonly since?: DateTime.Utc; readonly until?: DateTime.Utc }) => Effect.Effect<ReadonlyArray<QueueMetrics>, never, Requirements> };
add: QueueEnqueue<Payload, never, Requirements>;
prioritize: QueueEnqueue<Payload, never, Requirements>;
defer: QueueEnqueue<Payload, never, Requirements>;
enqueue: (entries: ReadonlyArray<QueueEntry<Payload>>) => Effect.Effect<void, never, Requirements>;
release: (input: { readonly options?: QueueReleaseOptions }) => Effect.Effect<ReadonlyArray<QueueEntry<Payload>>, never, Requirements>;
releaseEncoded: (input: { readonly options?: QueueReleaseOptions }) => Effect.Effect<ReadonlyArray<Hyperlink.Decoded<typeof queueEncodedEntry>>, QueueReleaseEncodingError, Requirements>;
deadLetter: (input: { readonly selector: QueueEntrySelector<Payload> | QueueEntry<Payload>; readonly options: QueueRouteOptions }) => Effect.Effect<ReadonlyArray<QueueEntry<Payload>>, never, Requirements>;
drop: (input: { readonly selector: QueueEntrySelector<Payload> | QueueEntry<Payload>; readonly options: QueueRouteOptions }) => Effect.Effect<ReadonlyArray<QueueEntry<Payload>>, never, Requirements>;
events: Stream.Stream<QueueEvent<Payload, Error, Success>>;
};
description: string | undefined;
of: (this: void, self: WorkPool.WorkPool<{ to: string; subject: string }, void, never, never>) => WorkPool.WorkPool<{ to: string; subject: string }, void, never, never>;
context: (self: WorkPool.WorkPool<{ to: string; subject: string }, void, never, never>) => Context<Emails>;
use: (f: (service: WorkPool.WorkPool<{ to: string; subject: string }, void, never, never>) => Effect.Effect<A, E, R>) => Effect.Effect<A, E, Emails | R>;
useSync: (f: (service: WorkPool.WorkPool<{ to: string; subject: string }, void, never, never>) => A) => Effect.Effect<A, never, Emails>;
Identifier: Identifier;
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;
}
Emails, {
// runs once per item; the second arg is per-attempt context
effect: (
item: Schema.Struct.ReadonlySide<
{
readonly to: Schema.String
readonly subject: Schema.String
},
"Type"
>,
ctx: WorkPool.EffectContext<
Schema.Struct.ReadonlySide<
{
readonly to: Schema.String
readonly subject: Schema.String
},
"Type"
>,
QueueEnqueueErrors,
never
>
) => Effect.Effect<void, never, never>
Daemon each item. The success channel A is driven by the tag's success wire schema
(default void): with a success schema the worker must return Effect<A, E, R> and that
value rides Completed.success / store.completed; without one it stays Effect<void, E, R>.
effect: (job: Schema.Struct.ReadonlySide<
{
readonly to: Schema.String
readonly subject: Schema.String
},
"Type"
>
(parameter) job: {
to: string;
subject: string;
}
job, ctx: WorkPool.EffectContext<
Schema.Struct.ReadonlySide<
{
readonly to: Schema.String
readonly subject: Schema.String
},
"Type"
>,
QueueEnqueueErrors,
never
>
(parameter) ctx: {
add: QueueEnqueue<T, EEnqueue, R>;
prioritize: QueueEnqueue<T, EEnqueue, R>;
defer: QueueEnqueue<T, EEnqueue, R>;
attempts: number;
enqueuedAt: number;
priority: Priority;
}
ctx) =>
import EffectEffect.const log: (
...message: ReadonlyArray<any>
) => Effect<void>
Logs one or more messages using the default log level.
Example (Logging at the default level)
import { Effect } from "effect"
const program = Effect.gen(function*() {
yield* Effect.log("Starting computation")
const result = 2 + 2
yield* Effect.log("Result:", result)
yield* Effect.log("Multiple", "values", "can", "be", "logged")
return result
})
Effect.runPromise(program).then(console.log)
// Output:
// timestamp=2023-... level=INFO message="Starting computation"
// timestamp=2023-... level=INFO message="Result: 4"
// timestamp=2023-... level=INFO message="Multiple values can be logged"
// 4
log(`send ${job: Schema.Struct.ReadonlySide<
{
readonly to: Schema.String
readonly subject: Schema.String
},
"Type"
>
(parameter) job: {
to: string;
subject: string;
}
job.to: stringto} (attempt ${ctx: WorkPool.EffectContext<
Schema.Struct.ReadonlySide<
{
readonly to: Schema.String
readonly subject: Schema.String
},
"Type"
>,
QueueEnqueueErrors,
never
>
(parameter) ctx: {
add: QueueEnqueue<T, EEnqueue, R>;
prioritize: QueueEnqueue<T, EEnqueue, R>;
defer: QueueEnqueue<T, EEnqueue, R>;
attempts: number;
enqueuedAt: number;
priority: Priority;
}
ctx.EffectContext<Struct<Fields extends Struct.Fields>.ReadonlySide<{ readonly to: String; readonly subject: String; }, "Type">, QueueEnqueueErrors, never>.attempts: numberHow many times this item has been processed (1 = first attempt).
attempts}, ${ctx: WorkPool.EffectContext<
Schema.Struct.ReadonlySide<
{
readonly to: Schema.String
readonly subject: Schema.String
},
"Type"
>,
QueueEnqueueErrors,
never
>
(parameter) ctx: {
add: QueueEnqueue<T, EEnqueue, R>;
prioritize: QueueEnqueue<T, EEnqueue, R>;
defer: QueueEnqueue<T, EEnqueue, R>;
attempts: number;
enqueuedAt: number;
priority: Priority;
}
ctx.EffectContext<Struct<Fields extends Struct.Fields>.ReadonlySide<{ readonly to: String; readonly subject: String; }, "Type">, QueueEnqueueErrors, never>.priority: PriorityThe priority level this item was enqueued at.
priority})`),
concurrency?: number | undefinedMax items processing concurrently (worker count).
concurrency: 4, // worker pool size — up to 4 items in flight
attempts?: number | undefinedMax attempts for an item — the initial try plus automatic re-enqueues. On failure the
worker re-enqueues the item at its own priority, preserving its attempt count, until
attempts is reached; then it's exhausted (emits a RetryExhausted event). Observe the
RetryScheduled / RetryExhausted lifecycle on the
QueueHandleApi.events
stream.
In-place retry of the worker effect is a separate concern — put Effect.retry(...) on your
effect.
attempts: 3, // 1 try + 2 retries, then dead-lettered
key?: | ((
item: Schema.Struct.ReadonlySide<
{
readonly to: Schema.String
readonly subject: Schema.String
},
"Type"
>
) => string)
| undefined
Extract a deduplication key from each item. When set, items with a key
already in-flight (enqueued or processing) are silently dropped.
The key is released after the worker effect completes.
key: (job: Schema.Struct.ReadonlySide<
{
readonly to: Schema.String
readonly subject: Schema.String
},
"Type"
>
(parameter) job: {
to: string;
subject: string;
}
job) => job: Schema.Struct.ReadonlySide<
{
readonly to: Schema.String
readonly subject: Schema.String
},
"Type"
>
(parameter) job: {
to: string;
subject: string;
}
job.to: stringto, // de-dup: same key is skipped while one is in flight
})concurrency— how many items drain at once. Default is 1 (strictly sequential); raise it for I/O-bound work.attempts— total tries per item. On the last failure the item is dead-lettered rather than retried.key— a de-duplication key. While an item with a given key is in flight, another with the same key is skipped — handy for refresh user 7 style work.
The worker effect may require services (R); the layer captures that context at build time and provides it to every run, so the resulting service needs nothing beyond what the layer itself requires.
When work fails
A queues worker either succeeds, or it fails in a way the queue declares. The tags error schema is that declaration — and its enforced: if you dont declare an error, the workers typed error channel is never, so a worker that Effect.fails wont even compile. Its failures must become defects (orDie), which the queue still catches — they just arent a typed part of the contract.
Declare an error schema and the worker may fail with it; that typed failure then rides the Failed events cause:
const const EmailJob: Schema.Struct<{
readonly to: Schema.String
readonly subject: Schema.String
}>
const EmailJob: {
Type: Struct.Type<Fields>;
Encoded: Struct.Encoded<Fields>;
DecodingServices: Struct.DecodingServices<Fields>;
EncodingServices: Struct.EncodingServices<Fields>;
Iso: Struct.Iso<Fields>;
fields: Fields;
mapFields: (f: (fields: { readonly to: Schema.String; readonly subject: Schema.String }) => To, options?: { readonly unsafePreserveChecks?: boolean | undefined } | undefined) => Schema.Struct<{ [K in keyof Readonly<To>]: Readonly<To>[K]; }>;
Rebuild: Rebuild;
ast: Ast;
annotate: (annotations: Schema.Annotations.Bottom<Schema.Struct.ReadonlySide<{ readonly to: Schema.String; readonly subject: Schema.String; }, 'Type'>, readonly []>) => Schema.Struct<{ readonly to: Schema.String; readonly subject: Schema.String; }>;
annotateKey: (annotations: Schema.Annotations.Key<Schema.Struct.ReadonlySide<{ readonly to: Schema.String; readonly subject: Schema.String }, 'Type'>>) => Schema.Struct<{ readonly to: Schema.String; readonly subject: Schema.String }>;
check: (checks_0: Check<Schema.Struct.ReadonlySide<{ readonly to: Schema.String; readonly subject: Schema.String }, 'Type'>>, ...checks: Array<Check<Schema.Struct.ReadonlySide<{ readonly to: Schema.String; readonly subject: Schema.String }, 'Type…;
rebuild: (ast: Objects) => Schema.Struct<{ readonly to: Schema.String; readonly subject: Schema.String }>;
make: (input: Struct.ReadonlyMakeIn<{ readonly to: String; readonly subject: String }>, options?: MakeOptions) => Struct.ReadonlySide<{ readonly to: String; readonly subject: String }, 'Type'>;
makeOption: (input: Struct.ReadonlyMakeIn<{ readonly to: String; readonly subject: String }>, options?: MakeOptions) => Option_.Option<Struct.ReadonlySide<{ readonly to: String; readonly subject: String }, 'Type'>>;
makeEffect: (input: Struct.ReadonlyMakeIn<{ readonly to: String; readonly subject: String }>, options?: MakeOptions) => Effect.Effect<Struct.ReadonlySide<{ readonly to: String; readonly subject: String }, 'Type'>, SchemaError, 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; <…;
}
EmailJob = import SchemaSchema.function Struct<
Fields extends Struct.Fields
>(fields: Fields): Struct<Fields>
Defines a struct schema from a map of field schemas.
Details
Each field value is a schema. Use
optionalKey
or
optional
to
mark fields as optional, and
mutableKey
to mark them as mutable.
The resulting schema's Type is a readonly object type with the fields'
decoded types. The Encoded form mirrors the field schemas' encoded types.
Example (Defining a basic struct)
import { Schema } from "effect"
const Person = Schema.Struct({
name: Schema.String,
age: Schema.Number,
email: Schema.optionalKey(Schema.String)
})
// { readonly name: string; readonly age: number; readonly email?: string }
type Person = typeof Person.Type
const alice = Schema.decodeUnknownSync(Person)({ name: "Alice", age: 30 })
console.log(alice)
// { name: 'Alice', age: 30 }
Struct({ to: Schema.String(property) to: {
Rebuild: Rebuild;
Iso: Iso;
ast: Ast;
Type: T;
Encoded: E;
DecodingServices: RD;
EncodingServices: RE;
annotate: (annotations: Schema.Annotations.Bottom<string, readonly []>) => Schema.String;
annotateKey: (annotations: Schema.Annotations.Key<string>) => Schema.String;
check: (checks_0: Check<string>, ...checks: Array<Check<string>>) => Schema.String;
rebuild: (ast: String) => Schema.String;
make: (input: string, options?: MakeOptions) => string;
makeOption: (input: string, options?: MakeOptions) => Option_.Option<string>;
makeEffect: (input: string, options?: MakeOptions) => Effect.Effect<string, SchemaError, 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; <…;
}
to: import SchemaSchema.const String: Stringconst String: {
Rebuild: Rebuild;
Iso: Iso;
ast: Ast;
Type: T;
Encoded: E;
DecodingServices: RD;
EncodingServices: RE;
annotate: (annotations: Schema.Annotations.Bottom<string, readonly []>) => Schema.String;
annotateKey: (annotations: Schema.Annotations.Key<string>) => Schema.String;
check: (checks_0: Check<string>, ...checks: Array<Check<string>>) => Schema.String;
rebuild: (ast: String) => Schema.String;
make: (input: string, options?: MakeOptions) => string;
makeOption: (input: string, options?: MakeOptions) => Option_.Option<string>;
makeEffect: (input: string, options?: MakeOptions) => Effect.Effect<string, SchemaError, 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; <…;
}
Type-level representation of
String
.
Schema for string values. Validates that the input is typeof "string".
String, subject: Schema.String(property) subject: {
Rebuild: Rebuild;
Iso: Iso;
ast: Ast;
Type: T;
Encoded: E;
DecodingServices: RD;
EncodingServices: RE;
annotate: (annotations: Schema.Annotations.Bottom<string, readonly []>) => Schema.String;
annotateKey: (annotations: Schema.Annotations.Key<string>) => Schema.String;
check: (checks_0: Check<string>, ...checks: Array<Check<string>>) => Schema.String;
rebuild: (ast: String) => Schema.String;
make: (input: string, options?: MakeOptions) => string;
makeOption: (input: string, options?: MakeOptions) => Option_.Option<string>;
makeEffect: (input: string, options?: MakeOptions) => Effect.Effect<string, SchemaError, 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; <…;
}
subject: import SchemaSchema.const String: Stringconst String: {
Rebuild: Rebuild;
Iso: Iso;
ast: Ast;
Type: T;
Encoded: E;
DecodingServices: RD;
EncodingServices: RE;
annotate: (annotations: Schema.Annotations.Bottom<string, readonly []>) => Schema.String;
annotateKey: (annotations: Schema.Annotations.Key<string>) => Schema.String;
check: (checks_0: Check<string>, ...checks: Array<Check<string>>) => Schema.String;
rebuild: (ast: String) => Schema.String;
make: (input: string, options?: MakeOptions) => string;
makeOption: (input: string, options?: MakeOptions) => Option_.Option<string>;
makeEffect: (input: string, options?: MakeOptions) => Effect.Effect<string, SchemaError, 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; <…;
}
Type-level representation of
String
.
Schema for string values. Validates that the input is typeof "string".
String })
// declare the failure type on the tag…
class class Emailsclass Emails {
key: Identifier;
Service: {
status: Hyperlink.Subscribable<QueueStatus>;
size: Hyperlink.Subscribable<number>;
isEmpty: Hyperlink.Subscribable<boolean>;
start: Effect.Effect<void, never, Requirements>;
pause: Effect.Effect<void>;
resume: Effect.Effect<void>;
shutdown: Effect.Effect<void>;
clear: Effect.Effect<number, never, Requirements>;
metrics: { readonly stream: Stream.Stream<QueueMetrics>; readonly query: (input: { readonly limit?: number; readonly since?: DateTime.Utc; readonly until?: DateTime.Utc }) => Effect.Effect<ReadonlyArray<QueueMetrics>, never, Requirements> };
add: QueueEnqueue<Payload, never, Requirements>;
prioritize: QueueEnqueue<Payload, never, Requirements>;
defer: QueueEnqueue<Payload, never, Requirements>;
enqueue: (entries: ReadonlyArray<QueueEntry<Payload>>) => Effect.Effect<void, never, Requirements>;
release: (input: { readonly options?: QueueReleaseOptions }) => Effect.Effect<ReadonlyArray<QueueEntry<Payload>>, never, Requirements>;
releaseEncoded: (input: { readonly options?: QueueReleaseOptions }) => Effect.Effect<ReadonlyArray<Hyperlink.Decoded<typeof queueEncodedEntry>>, QueueReleaseEncodingError, Requirements>;
deadLetter: (input: { readonly selector: QueueEntrySelector<Payload> | QueueEntry<Payload>; readonly options: QueueRouteOptions }) => Effect.Effect<ReadonlyArray<QueueEntry<Payload>>, never, Requirements>;
drop: (input: { readonly selector: QueueEntrySelector<Payload> | QueueEntry<Payload>; readonly options: QueueRouteOptions }) => Effect.Effect<ReadonlyArray<QueueEntry<Payload>>, never, Requirements>;
events: Stream.Stream<QueueEvent<Payload, Error, Success>>;
};
}
Emails extends import WorkPoolWorkPool.Tag<Emails>(): {
<F, HSelf, D>(key: string, payload: Schema.Struct<F>, options: {
readonly description?: string;
readonly node: NodeKey<HSelf>;
readonly defaults: DefaultsInput<D>;
}): TagWithDefaults<QueueNodeBoundTagCarriers<Emails, F, HSelf, Schema.Void, Schema.Never>, D>;
<F, HSelf>(key: string, payload: Schema.Struct<F>, options: {
readonly description?: string;
readonly node: NodeKey<HSelf>;
}): QueueNodeBoundTagCarriers<...>;
<F, Success, Error>(key: string, payload: Schema.Struct<...>, success: Success, error?: Error | undefined): QueueTagCarriers<...>;
<F, D>(key: string, payload: Schema.Struct<...>, options: {
...;
}): TagWithDefaults<...>;
<F>(key: string, payload: Schema.Struct<...>, options?: {
...;
} | undefined): QueueTagCarriers<...>;
<F, HSelf, D>(key: string, config: WorkPool.QueueTagConfig<...> & {
...;
}): TagWithDefaults<...>;
<F, HSelf>(key: string, config: WorkPool.QueueTagConfig<...> & {
...;
}): QueueNodeBoundTagCarriers<...>;
<F, D, Success, Error>(key: string, config: WorkPool.QueueTagConfig<...> & {
...;
}): TagWithDefaults<...>;
<F, Success, Error>(key: string, config: WorkPool.QueueTagConfig<...>): QueueTagCarriers<...>;
}
export Tag
Tag<class Emailsclass Emails {
key: Identifier;
Service: {
status: Hyperlink.Subscribable<QueueStatus>;
size: Hyperlink.Subscribable<number>;
isEmpty: Hyperlink.Subscribable<boolean>;
start: Effect.Effect<void, never, Requirements>;
pause: Effect.Effect<void>;
resume: Effect.Effect<void>;
shutdown: Effect.Effect<void>;
clear: Effect.Effect<number, never, Requirements>;
metrics: { readonly stream: Stream.Stream<QueueMetrics>; readonly query: (input: { readonly limit?: number; readonly since?: DateTime.Utc; readonly until?: DateTime.Utc }) => Effect.Effect<ReadonlyArray<QueueMetrics>, never, Requirements> };
add: QueueEnqueue<Payload, never, Requirements>;
prioritize: QueueEnqueue<Payload, never, Requirements>;
defer: QueueEnqueue<Payload, never, Requirements>;
enqueue: (entries: ReadonlyArray<QueueEntry<Payload>>) => Effect.Effect<void, never, Requirements>;
release: (input: { readonly options?: QueueReleaseOptions }) => Effect.Effect<ReadonlyArray<QueueEntry<Payload>>, never, Requirements>;
releaseEncoded: (input: { readonly options?: QueueReleaseOptions }) => Effect.Effect<ReadonlyArray<Hyperlink.Decoded<typeof queueEncodedEntry>>, QueueReleaseEncodingError, Requirements>;
deadLetter: (input: { readonly selector: QueueEntrySelector<Payload> | QueueEntry<Payload>; readonly options: QueueRouteOptions }) => Effect.Effect<ReadonlyArray<QueueEntry<Payload>>, never, Requirements>;
drop: (input: { readonly selector: QueueEntrySelector<Payload> | QueueEntry<Payload>; readonly options: QueueRouteOptions }) => Effect.Effect<ReadonlyArray<QueueEntry<Payload>>, never, Requirements>;
events: Stream.Stream<QueueEvent<Payload, Error, Success>>;
};
}
Emails>()("app/Emails", {
QueueTagConfig<{ readonly to: String; readonly subject: String; }, Void, String>.payload: Schema.Struct<{ readonly to: Schema.String; readonly subject: Schema.String }>(property) QueueTagConfig<{ readonly to: String; readonly subject: String; }, Void, String>.payload: {
Type: Struct.Type<Fields>;
Encoded: Struct.Encoded<Fields>;
DecodingServices: Struct.DecodingServices<Fields>;
EncodingServices: Struct.EncodingServices<Fields>;
Iso: Struct.Iso<Fields>;
fields: Fields;
mapFields: (f: (fields: { readonly to: Schema.String; readonly subject: Schema.String }) => To, options?: { readonly unsafePreserveChecks?: boolean | undefined } | undefined) => Schema.Struct<{ [K in keyof Readonly<To>]: Readonly<To>[K]; }>;
Rebuild: Rebuild;
ast: Ast;
annotate: (annotations: Schema.Annotations.Bottom<Schema.Struct.ReadonlySide<{ readonly to: Schema.String; readonly subject: Schema.String; }, 'Type'>, readonly []>) => Schema.Struct<{ readonly to: Schema.String; readonly subject: Schema.String; }>;
annotateKey: (annotations: Schema.Annotations.Key<Schema.Struct.ReadonlySide<{ readonly to: Schema.String; readonly subject: Schema.String }, 'Type'>>) => Schema.Struct<{ readonly to: Schema.String; readonly subject: Schema.String }>;
check: (checks_0: Check<Schema.Struct.ReadonlySide<{ readonly to: Schema.String; readonly subject: Schema.String }, 'Type'>>, ...checks: Array<Check<Schema.Struct.ReadonlySide<{ readonly to: Schema.String; readonly subject: Schema.String }, 'Type…;
rebuild: (ast: Objects) => Schema.Struct<{ readonly to: Schema.String; readonly subject: Schema.String }>;
make: (input: Struct.ReadonlyMakeIn<{ readonly to: String; readonly subject: String }>, options?: MakeOptions) => Struct.ReadonlySide<{ readonly to: String; readonly subject: String }, 'Type'>;
makeOption: (input: Struct.ReadonlyMakeIn<{ readonly to: String; readonly subject: String }>, options?: MakeOptions) => Option_.Option<Struct.ReadonlySide<{ readonly to: String; readonly subject: String }, 'Type'>>;
makeEffect: (input: Struct.ReadonlyMakeIn<{ readonly to: String; readonly subject: String }>, options?: MakeOptions) => Effect.Effect<Struct.ReadonlySide<{ readonly to: String; readonly subject: String }, 'Type'>, SchemaError, 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; <…;
}
payload: const EmailJob: Schema.Struct<{
readonly to: Schema.String
readonly subject: Schema.String
}>
const EmailJob: {
Type: Struct.Type<Fields>;
Encoded: Struct.Encoded<Fields>;
DecodingServices: Struct.DecodingServices<Fields>;
EncodingServices: Struct.EncodingServices<Fields>;
Iso: Struct.Iso<Fields>;
fields: Fields;
mapFields: (f: (fields: { readonly to: Schema.String; readonly subject: Schema.String }) => To, options?: { readonly unsafePreserveChecks?: boolean | undefined } | undefined) => Schema.Struct<{ [K in keyof Readonly<To>]: Readonly<To>[K]; }>;
Rebuild: Rebuild;
ast: Ast;
annotate: (annotations: Schema.Annotations.Bottom<Schema.Struct.ReadonlySide<{ readonly to: Schema.String; readonly subject: Schema.String; }, 'Type'>, readonly []>) => Schema.Struct<{ readonly to: Schema.String; readonly subject: Schema.String; }>;
annotateKey: (annotations: Schema.Annotations.Key<Schema.Struct.ReadonlySide<{ readonly to: Schema.String; readonly subject: Schema.String }, 'Type'>>) => Schema.Struct<{ readonly to: Schema.String; readonly subject: Schema.String }>;
check: (checks_0: Check<Schema.Struct.ReadonlySide<{ readonly to: Schema.String; readonly subject: Schema.String }, 'Type'>>, ...checks: Array<Check<Schema.Struct.ReadonlySide<{ readonly to: Schema.String; readonly subject: Schema.String }, 'Type…;
rebuild: (ast: Objects) => Schema.Struct<{ readonly to: Schema.String; readonly subject: Schema.String }>;
make: (input: Struct.ReadonlyMakeIn<{ readonly to: String; readonly subject: String }>, options?: MakeOptions) => Struct.ReadonlySide<{ readonly to: String; readonly subject: String }, 'Type'>;
makeOption: (input: Struct.ReadonlyMakeIn<{ readonly to: String; readonly subject: String }>, options?: MakeOptions) => Option_.Option<Struct.ReadonlySide<{ readonly to: String; readonly subject: String }, 'Type'>>;
makeEffect: (input: Struct.ReadonlyMakeIn<{ readonly to: String; readonly subject: String }>, options?: MakeOptions) => Effect.Effect<Struct.ReadonlySide<{ readonly to: String; readonly subject: String }, 'Type'>, SchemaError, 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; <…;
}
EmailJob,
QueueTagConfig<{ readonly to: String; readonly subject: String; }, Void, String>.error?: Schema.String(property) QueueTagConfig<{ readonly to: String; readonly subject: String; }, Void, String>.error?: {
Rebuild: Rebuild;
Iso: Iso;
ast: Ast;
Type: T;
Encoded: E;
DecodingServices: RD;
EncodingServices: RE;
annotate: (annotations: Schema.Annotations.Bottom<string, readonly []>) => Schema.String;
annotateKey: (annotations: Schema.Annotations.Key<string>) => Schema.String;
check: (checks_0: Check<string>, ...checks: Array<Check<string>>) => Schema.String;
rebuild: (ast: String) => Schema.String;
make: (input: string, options?: MakeOptions) => string;
makeOption: (input: string, options?: MakeOptions) => Option_.Option<string>;
makeEffect: (input: string, options?: MakeOptions) => Effect.Effect<string, SchemaError, 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; <…;
}
error: import SchemaSchema.const String: Stringconst String: {
Rebuild: Rebuild;
Iso: Iso;
ast: Ast;
Type: T;
Encoded: E;
DecodingServices: RD;
EncodingServices: RE;
annotate: (annotations: Schema.Annotations.Bottom<string, readonly []>) => Schema.String;
annotateKey: (annotations: Schema.Annotations.Key<string>) => Schema.String;
check: (checks_0: Check<string>, ...checks: Array<Check<string>>) => Schema.String;
rebuild: (ast: String) => Schema.String;
make: (input: string, options?: MakeOptions) => string;
makeOption: (input: string, options?: MakeOptions) => Option_.Option<string>;
makeEffect: (input: string, options?: MakeOptions) => Effect.Effect<string, SchemaError, 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; <…;
}
Type-level representation of
String
.
Schema for string values. Validates that the input is typeof "string".
String, // the worker may fail with a string
}) {}
// …and now the worker is allowed to fail with it
const const EmailsLive: Layer<
Emails | Storage | Local<Emails>,
never,
never
>
const EmailsLive: {
build: (memoMap: MemoMap, scope: Scope.Scope) => Effect<Context.Context<Emails | Storage | Local<Emails>>, 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; <…;
}
EmailsLive = import WorkPoolWorkPool.function layer<Emails, {
readonly to: Schema.String;
readonly subject: Schema.String;
}, never, never, Schema.Void, Schema.String>(tag: QueueTagFor<Emails, {
readonly to: Schema.String;
readonly subject: Schema.String;
}, Schema.Void, Schema.String>, config: QueueVerbConfig<{
readonly to: Schema.String;
readonly subject: Schema.String;
}, string, never, never, Schema.Void>): Layer<Emails | Local<Emails> | Storage, never, never> (+1 overload)
Run this WorkPool locally — soft-defaults
Store.Storage
(R fulfilled; override by
providing an app store into this layer). Accepts a plain
Tag
or a
priority
tag and
dispatches to the matching engine.
layerMemory
is an alias for the same soft-default.
layer(class Emailsclass Emails {
key: Identifier;
Service: {
status: Hyperlink.Subscribable<QueueStatus>;
size: Hyperlink.Subscribable<number>;
isEmpty: Hyperlink.Subscribable<boolean>;
start: Effect.Effect<void, never, Requirements>;
pause: Effect.Effect<void>;
resume: Effect.Effect<void>;
shutdown: Effect.Effect<void>;
clear: Effect.Effect<number, never, Requirements>;
metrics: { readonly stream: Stream.Stream<QueueMetrics>; readonly query: (input: { readonly limit?: number; readonly since?: DateTime.Utc; readonly until?: DateTime.Utc }) => Effect.Effect<ReadonlyArray<QueueMetrics>, never, Requirements> };
add: QueueEnqueue<Payload, never, Requirements>;
prioritize: QueueEnqueue<Payload, never, Requirements>;
defer: QueueEnqueue<Payload, never, Requirements>;
enqueue: (entries: ReadonlyArray<QueueEntry<Payload>>) => Effect.Effect<void, never, Requirements>;
release: (input: { readonly options?: QueueReleaseOptions }) => Effect.Effect<ReadonlyArray<QueueEntry<Payload>>, never, Requirements>;
releaseEncoded: (input: { readonly options?: QueueReleaseOptions }) => Effect.Effect<ReadonlyArray<Hyperlink.Decoded<typeof queueEncodedEntry>>, QueueReleaseEncodingError, Requirements>;
deadLetter: (input: { readonly selector: QueueEntrySelector<Payload> | QueueEntry<Payload>; readonly options: QueueRouteOptions }) => Effect.Effect<ReadonlyArray<QueueEntry<Payload>>, never, Requirements>;
drop: (input: { readonly selector: QueueEntrySelector<Payload> | QueueEntry<Payload>; readonly options: QueueRouteOptions }) => Effect.Effect<ReadonlyArray<QueueEntry<Payload>>, never, Requirements>;
events: Stream.Stream<QueueEvent<Payload, Error, Success>>;
};
description: string | undefined;
of: (this: void, self: WorkPool.WorkPool<{ to: string; subject: string }, void, string, never>) => WorkPool.WorkPool<{ to: string; subject: string }, void, string, never>;
context: (self: WorkPool.WorkPool<{ to: string; subject: string }, void, string, never>) => Context<Emails>;
use: (f: (service: WorkPool.WorkPool<{ to: string; subject: string }, void, string, never>) => Effect.Effect<A, E, R>) => Effect.Effect<A, E, Emails | R>;
useSync: (f: (service: WorkPool.WorkPool<{ to: string; subject: string }, void, string, never>) => A) => Effect.Effect<A, never, Emails>;
Identifier: Identifier;
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;
}
Emails, {
effect: (
item: Schema.Struct.ReadonlySide<
{
readonly to: Schema.String
readonly subject: Schema.String
},
"Type"
>,
ctx: WorkPool.EffectContext<
Schema.Struct.ReadonlySide<
{
readonly to: Schema.String
readonly subject: Schema.String
},
"Type"
>,
QueueEnqueueErrors,
never
>
) => Effect.Effect<void, string, never>
Daemon each item. The success channel A is driven by the tag's success wire schema
(default void): with a success schema the worker must return Effect<A, E, R> and that
value rides Completed.success / store.completed; without one it stays Effect<void, E, R>.
effect: (job: Schema.Struct.ReadonlySide<
{
readonly to: Schema.String
readonly subject: Schema.String
},
"Type"
>
(parameter) job: {
to: string;
subject: string;
}
job) =>
job: Schema.Struct.ReadonlySide<
{
readonly to: Schema.String
readonly subject: Schema.String
},
"Type"
>
(parameter) job: {
to: string;
subject: string;
}
job.to: stringto.String.includes(searchString: string, position?: number): booleanReturns true if searchString appears as a substring of the result of converting this
object to a String, at one or more positions that are
greater than or equal to position; otherwise, returns false.
includes("@")
? import EffectEffect.const void: Effect.Effect<void, never, never>(alias) const void: {
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;
}
Returns an effect that succeeds with void.
void
: import EffectEffect.const fail: <E>(
error: E
) => Effect<never, E>
Creates an Effect that represents a recoverable error.
When to use
Use to explicitly signal a recoverable error in an Effect.
Details
The error keeps propagating unless it is handled. You can handle tagged
errors with functions like
catchTag
or
catchTags
.
Example (Creating a failed effect)
import { Data, Effect } from "effect"
class OperationFailedError extends Data.TaggedError("OperationFailedError")<{}> {}
// ┌─── Effect<never, OperationFailedError, never>
// ▼
const failure = Effect.fail(
new OperationFailedError()
)
fail(`invalid address: ${job: Schema.Struct.ReadonlySide<
{
readonly to: Schema.String
readonly subject: Schema.String
},
"Type"
>
(parameter) job: {
to: string;
subject: string;
}
job.to: stringto}`),
attempts?: number | undefinedMax attempts for an item — the initial try plus automatic re-enqueues. On failure the
worker re-enqueues the item at its own priority, preserving its attempt count, until
attempts is reached; then it's exhausted (emits a RetryExhausted event). Observe the
RetryScheduled / RetryExhausted lifecycle on the
QueueHandleApi.events
stream.
In-place retry of the worker effect is a separate concern — put Effect.retry(...) on your
effect.
attempts: 1,
})The handle now types as WorkPool<…, void, string, never> — the string error is visible to anyone watching events. The rule is deliberate: the tag is the error contract, and workers conform to it. A queues declared failures are part of its public shape, not an implementation detail.
Enqueueing
Four verbs put work in. Three are priority lanes:
yield* const emails: WorkPool.WorkPool<
{ to: string; subject: string },
void,
never,
never
>
const emails: {
status: Hyperlink.Subscribable<QueueStatus>;
size: Hyperlink.Subscribable<number>;
isEmpty: Hyperlink.Subscribable<boolean>;
start: Effect.Effect<void, never, Requirements>;
pause: Effect.Effect<void>;
resume: Effect.Effect<void>;
shutdown: Effect.Effect<void>;
clear: Effect.Effect<number, never, Requirements>;
metrics: { readonly stream: Stream.Stream<QueueMetrics>; readonly query: (input: { readonly limit?: number; readonly since?: DateTime.Utc; readonly until?: DateTime.Utc }) => Effect.Effect<ReadonlyArray<QueueMetrics>, never, Requirements> };
add: QueueEnqueue<Payload, never, Requirements>;
prioritize: QueueEnqueue<Payload, never, Requirements>;
defer: QueueEnqueue<Payload, never, Requirements>;
enqueue: (entries: ReadonlyArray<QueueEntry<Payload>>) => Effect.Effect<void, never, Requirements>;
release: (input: { readonly options?: QueueReleaseOptions }) => Effect.Effect<ReadonlyArray<QueueEntry<Payload>>, never, Requirements>;
releaseEncoded: (input: { readonly options?: QueueReleaseOptions }) => Effect.Effect<ReadonlyArray<Hyperlink.Decoded<typeof queueEncodedEntry>>, QueueReleaseEncodingError, Requirements>;
deadLetter: (input: { readonly selector: QueueEntrySelector<Payload> | QueueEntry<Payload>; readonly options: QueueRouteOptions }) => Effect.Effect<ReadonlyArray<QueueEntry<Payload>>, never, Requirements>;
drop: (input: { readonly selector: QueueEntrySelector<Payload> | QueueEntry<Payload>; readonly options: QueueRouteOptions }) => Effect.Effect<ReadonlyArray<QueueEntry<Payload>>, never, Requirements>;
events: Stream.Stream<QueueEvent<Payload, Error, Success>>;
}
emails.WorkPool<{ to: string; subject: string; }, void, never, never>.add: QueueEnqueue<Payload, never, Requirements>add({ to: stringto: "[email protected]", subject: stringsubject: "Welcome" }) // normal
yield* const emails: WorkPool.WorkPool<
{ to: string; subject: string },
void,
never,
never
>
const emails: {
status: Hyperlink.Subscribable<QueueStatus>;
size: Hyperlink.Subscribable<number>;
isEmpty: Hyperlink.Subscribable<boolean>;
start: Effect.Effect<void, never, Requirements>;
pause: Effect.Effect<void>;
resume: Effect.Effect<void>;
shutdown: Effect.Effect<void>;
clear: Effect.Effect<number, never, Requirements>;
metrics: { readonly stream: Stream.Stream<QueueMetrics>; readonly query: (input: { readonly limit?: number; readonly since?: DateTime.Utc; readonly until?: DateTime.Utc }) => Effect.Effect<ReadonlyArray<QueueMetrics>, never, Requirements> };
add: QueueEnqueue<Payload, never, Requirements>;
prioritize: QueueEnqueue<Payload, never, Requirements>;
defer: QueueEnqueue<Payload, never, Requirements>;
enqueue: (entries: ReadonlyArray<QueueEntry<Payload>>) => Effect.Effect<void, never, Requirements>;
release: (input: { readonly options?: QueueReleaseOptions }) => Effect.Effect<ReadonlyArray<QueueEntry<Payload>>, never, Requirements>;
releaseEncoded: (input: { readonly options?: QueueReleaseOptions }) => Effect.Effect<ReadonlyArray<Hyperlink.Decoded<typeof queueEncodedEntry>>, QueueReleaseEncodingError, Requirements>;
deadLetter: (input: { readonly selector: QueueEntrySelector<Payload> | QueueEntry<Payload>; readonly options: QueueRouteOptions }) => Effect.Effect<ReadonlyArray<QueueEntry<Payload>>, never, Requirements>;
drop: (input: { readonly selector: QueueEntrySelector<Payload> | QueueEntry<Payload>; readonly options: QueueRouteOptions }) => Effect.Effect<ReadonlyArray<QueueEntry<Payload>>, never, Requirements>;
events: Stream.Stream<QueueEvent<Payload, Error, Success>>;
}
emails.WorkPool<{ to: string; subject: string; }, void, never, never>.prioritize: QueueEnqueue<Payload, never, Requirements>prioritize({ to: stringto: "[email protected]", subject: stringsubject: "Reset code" }) // jumps the line
yield* const emails: WorkPool.WorkPool<
{ to: string; subject: string },
void,
never,
never
>
const emails: {
status: Hyperlink.Subscribable<QueueStatus>;
size: Hyperlink.Subscribable<number>;
isEmpty: Hyperlink.Subscribable<boolean>;
start: Effect.Effect<void, never, Requirements>;
pause: Effect.Effect<void>;
resume: Effect.Effect<void>;
shutdown: Effect.Effect<void>;
clear: Effect.Effect<number, never, Requirements>;
metrics: { readonly stream: Stream.Stream<QueueMetrics>; readonly query: (input: { readonly limit?: number; readonly since?: DateTime.Utc; readonly until?: DateTime.Utc }) => Effect.Effect<ReadonlyArray<QueueMetrics>, never, Requirements> };
add: QueueEnqueue<Payload, never, Requirements>;
prioritize: QueueEnqueue<Payload, never, Requirements>;
defer: QueueEnqueue<Payload, never, Requirements>;
enqueue: (entries: ReadonlyArray<QueueEntry<Payload>>) => Effect.Effect<void, never, Requirements>;
release: (input: { readonly options?: QueueReleaseOptions }) => Effect.Effect<ReadonlyArray<QueueEntry<Payload>>, never, Requirements>;
releaseEncoded: (input: { readonly options?: QueueReleaseOptions }) => Effect.Effect<ReadonlyArray<Hyperlink.Decoded<typeof queueEncodedEntry>>, QueueReleaseEncodingError, Requirements>;
deadLetter: (input: { readonly selector: QueueEntrySelector<Payload> | QueueEntry<Payload>; readonly options: QueueRouteOptions }) => Effect.Effect<ReadonlyArray<QueueEntry<Payload>>, never, Requirements>;
drop: (input: { readonly selector: QueueEntrySelector<Payload> | QueueEntry<Payload>; readonly options: QueueRouteOptions }) => Effect.Effect<ReadonlyArray<QueueEntry<Payload>>, never, Requirements>;
events: Stream.Stream<QueueEvent<Payload, Error, Success>>;
}
emails.WorkPool<{ to: string; subject: string; }, void, never, never>.defer: QueueEnqueue<Payload, never, Requirements>defer({ to: stringto: "[email protected]", subject: stringsubject: "Newsletter" }) // sinks to the back
})add, prioritize, and defer each also accept an array — one call enqueues a batch, which matters over RPC (one round trip, not N). The fourth verb, enqueue, re-injects existing entries (from a release, below) with their attempt counts preserved.
Observing
Three kinds of read, for three questions.
How much is waiting, right now? — size, isEmpty, and status are reactive Subscribables. .get reads the current value once; .changes is a live stream you can render:
const const pending: numberpending = yield* const emails: WorkPool.WorkPool<
{ to: string; subject: string },
void,
never,
never
>
const emails: {
status: Hyperlink.Subscribable<QueueStatus>;
size: Hyperlink.Subscribable<number>;
isEmpty: Hyperlink.Subscribable<boolean>;
start: Effect.Effect<void, never, Requirements>;
pause: Effect.Effect<void>;
resume: Effect.Effect<void>;
shutdown: Effect.Effect<void>;
clear: Effect.Effect<number, never, Requirements>;
metrics: { readonly stream: Stream.Stream<QueueMetrics>; readonly query: (input: { readonly limit?: number; readonly since?: DateTime.Utc; readonly until?: DateTime.Utc }) => Effect.Effect<ReadonlyArray<QueueMetrics>, never, Requirements> };
add: QueueEnqueue<Payload, never, Requirements>;
prioritize: QueueEnqueue<Payload, never, Requirements>;
defer: QueueEnqueue<Payload, never, Requirements>;
enqueue: (entries: ReadonlyArray<QueueEntry<Payload>>) => Effect.Effect<void, never, Requirements>;
release: (input: { readonly options?: QueueReleaseOptions }) => Effect.Effect<ReadonlyArray<QueueEntry<Payload>>, never, Requirements>;
releaseEncoded: (input: { readonly options?: QueueReleaseOptions }) => Effect.Effect<ReadonlyArray<Hyperlink.Decoded<typeof queueEncodedEntry>>, QueueReleaseEncodingError, Requirements>;
deadLetter: (input: { readonly selector: QueueEntrySelector<Payload> | QueueEntry<Payload>; readonly options: QueueRouteOptions }) => Effect.Effect<ReadonlyArray<QueueEntry<Payload>>, never, Requirements>;
drop: (input: { readonly selector: QueueEntrySelector<Payload> | QueueEntry<Payload>; readonly options: QueueRouteOptions }) => Effect.Effect<ReadonlyArray<QueueEntry<Payload>>, never, Requirements>;
events: Stream.Stream<QueueEvent<Payload, Error, Success>>;
}
emails.WorkPool<{ to: string; subject: string; }, void, never, never>.size: Hyperlink.Subscribable<number>(property) WorkPool<{ to: string; subject: string; }, void, never, never>.size: {
get: Effect.Effect<A>;
changes: Stream.Stream<A>;
}
Total pending items across all priority lanes.
size.Subscribable<number>.get: Effect.Effect<A>(property) Subscribable<number>.get: {
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;
}
get // a number, right now
const const empty: booleanempty = yield* const emails: WorkPool.WorkPool<
{ to: string; subject: string },
void,
never,
never
>
const emails: {
status: Hyperlink.Subscribable<QueueStatus>;
size: Hyperlink.Subscribable<number>;
isEmpty: Hyperlink.Subscribable<boolean>;
start: Effect.Effect<void, never, Requirements>;
pause: Effect.Effect<void>;
resume: Effect.Effect<void>;
shutdown: Effect.Effect<void>;
clear: Effect.Effect<number, never, Requirements>;
metrics: { readonly stream: Stream.Stream<QueueMetrics>; readonly query: (input: { readonly limit?: number; readonly since?: DateTime.Utc; readonly until?: DateTime.Utc }) => Effect.Effect<ReadonlyArray<QueueMetrics>, never, Requirements> };
add: QueueEnqueue<Payload, never, Requirements>;
prioritize: QueueEnqueue<Payload, never, Requirements>;
defer: QueueEnqueue<Payload, never, Requirements>;
enqueue: (entries: ReadonlyArray<QueueEntry<Payload>>) => Effect.Effect<void, never, Requirements>;
release: (input: { readonly options?: QueueReleaseOptions }) => Effect.Effect<ReadonlyArray<QueueEntry<Payload>>, never, Requirements>;
releaseEncoded: (input: { readonly options?: QueueReleaseOptions }) => Effect.Effect<ReadonlyArray<Hyperlink.Decoded<typeof queueEncodedEntry>>, QueueReleaseEncodingError, Requirements>;
deadLetter: (input: { readonly selector: QueueEntrySelector<Payload> | QueueEntry<Payload>; readonly options: QueueRouteOptions }) => Effect.Effect<ReadonlyArray<QueueEntry<Payload>>, never, Requirements>;
drop: (input: { readonly selector: QueueEntrySelector<Payload> | QueueEntry<Payload>; readonly options: QueueRouteOptions }) => Effect.Effect<ReadonlyArray<QueueEntry<Payload>>, never, Requirements>;
events: Stream.Stream<QueueEvent<Payload, Error, Success>>;
}
emails.WorkPool<{ to: string; subject: string; }, void, never, never>.isEmpty: Hyperlink.Subscribable<boolean>(property) WorkPool<{ to: string; subject: string; }, void, never, never>.isEmpty: {
get: Effect.Effect<A>;
changes: Stream.Stream<A>;
}
Whether all priority queues are empty.
isEmpty.Subscribable<boolean>.get: Effect.Effect<A>(property) Subscribable<boolean>.get: {
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;
}
get // boolean
yield* const emails: WorkPool.WorkPool<
{ to: string; subject: string },
void,
never,
never
>
const emails: {
status: Hyperlink.Subscribable<QueueStatus>;
size: Hyperlink.Subscribable<number>;
isEmpty: Hyperlink.Subscribable<boolean>;
start: Effect.Effect<void, never, Requirements>;
pause: Effect.Effect<void>;
resume: Effect.Effect<void>;
shutdown: Effect.Effect<void>;
clear: Effect.Effect<number, never, Requirements>;
metrics: { readonly stream: Stream.Stream<QueueMetrics>; readonly query: (input: { readonly limit?: number; readonly since?: DateTime.Utc; readonly until?: DateTime.Utc }) => Effect.Effect<ReadonlyArray<QueueMetrics>, never, Requirements> };
add: QueueEnqueue<Payload, never, Requirements>;
prioritize: QueueEnqueue<Payload, never, Requirements>;
defer: QueueEnqueue<Payload, never, Requirements>;
enqueue: (entries: ReadonlyArray<QueueEntry<Payload>>) => Effect.Effect<void, never, Requirements>;
release: (input: { readonly options?: QueueReleaseOptions }) => Effect.Effect<ReadonlyArray<QueueEntry<Payload>>, never, Requirements>;
releaseEncoded: (input: { readonly options?: QueueReleaseOptions }) => Effect.Effect<ReadonlyArray<Hyperlink.Decoded<typeof queueEncodedEntry>>, QueueReleaseEncodingError, Requirements>;
deadLetter: (input: { readonly selector: QueueEntrySelector<Payload> | QueueEntry<Payload>; readonly options: QueueRouteOptions }) => Effect.Effect<ReadonlyArray<QueueEntry<Payload>>, never, Requirements>;
drop: (input: { readonly selector: QueueEntrySelector<Payload> | QueueEntry<Payload>; readonly options: QueueRouteOptions }) => Effect.Effect<ReadonlyArray<QueueEntry<Payload>>, never, Requirements>;
events: Stream.Stream<QueueEvent<Payload, Error, Success>>;
}
emails.WorkPool<{ to: string; subject: string; }, void, never, never>.size: Hyperlink.Subscribable<number>(property) WorkPool<{ to: string; subject: string; }, void, never, never>.size: {
get: Effect.Effect<A>;
changes: Stream.Stream<A>;
}
Total pending items across all priority lanes.
size.Subscribable<number>.changes: Stream.Stream<A>(property) Subscribable<number>.changes: {
channel: Channel.Channel<Arr.NonEmptyReadonlyArray<A>, E, void, unknown, unknown, unknown, R>;
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; <…;
}
changes.Pipeable.pipe<Stream.Stream<number, never, never>, Effect.Effect<void, never, never>>(this: Stream.Stream<number, never, never>, ab: (_: Stream.Stream<number, never, never>) => Effect.Effect<void, never, never>): Effect.Effect<void, never, never> (+21 overloads)pipe(import StreamStream.const runForEach: {
<A, X, E2, R2>(
f: (a: A) => Effect.Effect<X, E2, R2>
): <E, R>(
self: Stream<A, E, R>
) => Effect.Effect<void, E2 | E, R2 | R>
<A, E, R, X, E2, R2>(
self: Stream<A, E, R>,
f: (a: A) => Effect.Effect<X, E2, R2>
): Effect.Effect<void, E | E2, R | R2>
}
Runs the provided effectful callback for each element of the stream.
Example (Running an effect for each value)
import { Console, Effect, Stream } from "effect"
const stream = Stream.make(1, 2, 3)
const program = Effect.gen(function*() {
yield* Stream.runForEach(stream, (n) => Console.log(`Processing: ${n}`))
})
Effect.runPromise(program)
// Processing: 1
// Processing: 2
// Processing: 3
runForEach(const onDepth: (
n: number
) => Effect.Effect<void>
onDepth)) // live, every change
})What just happened? — events is a stream of discrete facts: Enqueued, Started, Completed, Failed, RetryScheduled, RetryExhausted, and more. Subscribe once, off-fiber, and dispatch by tag:
yield* import EffectEffect.const forkScoped: <
Arg extends
| Effect<any, any, any>
| {
readonly startImmediately?:
| boolean
| undefined
readonly uninterruptible?:
| boolean
| "inherit"
| undefined
}
| undefined = {
readonly startImmediately?:
| boolean
| undefined
readonly uninterruptible?:
| boolean
| "inherit"
| undefined
}
>(
effectOrOptions?: Arg,
options?:
| {
readonly startImmediately?:
| boolean
| undefined
readonly uninterruptible?:
| boolean
| "inherit"
| undefined
}
| undefined
) => [Arg] extends [
Effect<infer _A, infer _E, infer _R>
]
? Effect<Fiber<_A, _E>, never, _R | Scope>
: <A, E, R>(
self: Effect<A, E, R>
) => Effect<Fiber<A, E>, never, R | Scope>
Forks the fiber in a Scope, interrupting it when the scope is closed.
Example (Forking into the current scope)
import { Effect } from "effect"
const backgroundTask = Effect.gen(function*() {
yield* Effect.sleep("5 seconds")
yield* Effect.log("Background task completed")
return "result"
})
const program = Effect.scoped(
Effect.gen(function*() {
const fiber = yield* backgroundTask.pipe(Effect.forkScoped)
// or fork a fiber that starts immediately:
yield* backgroundTask.pipe(Effect.forkScoped({ startImmediately: true }))
yield* Effect.log("Task forked in scope")
yield* Effect.sleep("1 second")
// Fiber will be interrupted when scope closes
return "scope completed"
})
)
forkScoped(
const emails: WorkPool.WorkPool<
{ to: string; subject: string },
void,
never,
never
>
const emails: {
status: Hyperlink.Subscribable<QueueStatus>;
size: Hyperlink.Subscribable<number>;
isEmpty: Hyperlink.Subscribable<boolean>;
start: Effect.Effect<void, never, Requirements>;
pause: Effect.Effect<void>;
resume: Effect.Effect<void>;
shutdown: Effect.Effect<void>;
clear: Effect.Effect<number, never, Requirements>;
metrics: { readonly stream: Stream.Stream<QueueMetrics>; readonly query: (input: { readonly limit?: number; readonly since?: DateTime.Utc; readonly until?: DateTime.Utc }) => Effect.Effect<ReadonlyArray<QueueMetrics>, never, Requirements> };
add: QueueEnqueue<Payload, never, Requirements>;
prioritize: QueueEnqueue<Payload, never, Requirements>;
defer: QueueEnqueue<Payload, never, Requirements>;
enqueue: (entries: ReadonlyArray<QueueEntry<Payload>>) => Effect.Effect<void, never, Requirements>;
release: (input: { readonly options?: QueueReleaseOptions }) => Effect.Effect<ReadonlyArray<QueueEntry<Payload>>, never, Requirements>;
releaseEncoded: (input: { readonly options?: QueueReleaseOptions }) => Effect.Effect<ReadonlyArray<Hyperlink.Decoded<typeof queueEncodedEntry>>, QueueReleaseEncodingError, Requirements>;
deadLetter: (input: { readonly selector: QueueEntrySelector<Payload> | QueueEntry<Payload>; readonly options: QueueRouteOptions }) => Effect.Effect<ReadonlyArray<QueueEntry<Payload>>, never, Requirements>;
drop: (input: { readonly selector: QueueEntrySelector<Payload> | QueueEntry<Payload>; readonly options: QueueRouteOptions }) => Effect.Effect<ReadonlyArray<QueueEntry<Payload>>, never, Requirements>;
events: Stream.Stream<QueueEvent<Payload, Error, Success>>;
}
emails.WorkPool<{ to: string; subject: string; }, void, never, never>.events: Stream.Stream<QueueEvent<Payload, Error, Success>>(property) WorkPool<{ to: string; subject: string; }, void, never, never>.events: {
channel: Channel.Channel<Arr.NonEmptyReadonlyArray<A>, E, void, unknown, unknown, unknown, R>;
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; <…;
}
Discrete entry / worker / queue lifecycle events.
events.Pipeable.pipe<Stream<WorkPool.QueueEvent<{
to: string;
subject: string;
}, never, void>, never, never>, Effect.Effect<void, never, never>>(this: Stream<WorkPool.QueueEvent<{
to: string;
subject: string;
}, never, void>, never, never>, ab: (_: Stream<WorkPool.QueueEvent<{
to: string;
subject: string;
}, never, void>, never, never>) => Effect.Effect<void, never, never>): Effect.Effect<void, never, never> (+21 overloads)
pipe(
import HyperlinkHyperlink.const runForEachTag: {
<
A extends TaggedEvent,
Cases extends TagHandlers<A, unknown, unknown>
>(
handlers: Cases
): (
self: Stream.Stream<A>
) => Effect.Effect<
void,
HandlersError<Cases>,
HandlersContext<Cases>
>
<
A extends TaggedEvent,
K extends A["_tag"],
E,
R
>(
tag: K,
f: (
event: Extract<A, { readonly _tag: K }>
) => Effect.Effect<void, E, R>
): (
self: Stream.Stream<A>
) => Effect.Effect<void, E, R>
<
A extends TaggedEvent,
K extends A["_tag"],
E,
R
>(
self: Stream.Stream<A>,
tag: K,
f: (
event: Extract<A, { readonly _tag: K }>
) => Effect.Effect<void, E, R>
): Effect.Effect<void, E, R>
<
A extends TaggedEvent,
Cases extends TagHandlers<A, unknown, unknown>
>(
self: Stream.Stream<A>,
handlers: Cases
): Effect.Effect<
void,
HandlersError<Cases>,
HandlersContext<Cases>
>
}
runForEachTag({
type Completed: (e: {
readonly _tag: "Completed"
readonly entry: WorkPool.QueueEntry<{
to: string
subject: string
}>
readonly success: void
readonly elapsed: Duration
}) => Effect.Effect<void, never, never>
Completed: (e: {
readonly _tag: "Completed"
readonly entry: QueueEntry<{
to: string
subject: string
}>
readonly success: void
readonly elapsed: Duration.Duration
}
e) => import EffectEffect.const log: (
...message: ReadonlyArray<any>
) => Effect<void>
Logs one or more messages using the default log level.
Example (Logging at the default level)
import { Effect } from "effect"
const program = Effect.gen(function*() {
yield* Effect.log("Starting computation")
const result = 2 + 2
yield* Effect.log("Result:", result)
yield* Effect.log("Multiple", "values", "can", "be", "logged")
return result
})
Effect.runPromise(program).then(console.log)
// Output:
// timestamp=2023-... level=INFO message="Starting computation"
// timestamp=2023-... level=INFO message="Result: 4"
// timestamp=2023-... level=INFO message="Multiple values can be logged"
// 4
log(`sent → ${e: {
readonly _tag: "Completed"
readonly entry: QueueEntry<{
to: string
subject: string
}>
readonly success: void
readonly elapsed: Duration.Duration
}
e.entry: QueueEntry<T>(property) entry: {
item: T;
entryId: string;
key: string;
priority: Priority;
lane: number;
attempts: number;
timestamps: QueueEntryTimestamps;
batchId: string;
releaseId: string;
sourceHyperlinkId: string;
attributes: { readonly [key: string]: unknown };
}
entry.QueueEntry<{ to: string; subject: string; }>.item: {
to: string;
subject: string;
}
item.to: stringto}`),
type RetryExhausted: (e: {
readonly _tag: "RetryExhausted"
readonly entry: WorkPool.QueueEntry<{
to: string
subject: string
}>
readonly cause: Cause.Cause<never>
}) => Effect.Effect<void, never, never>
RetryExhausted: (e: {
readonly _tag: "RetryExhausted"
readonly entry: WorkPool.QueueEntry<{
to: string
subject: string
}>
readonly cause: Cause.Cause<never>
}
e) =>
import EffectEffect.const logError: (
...message: ReadonlyArray<any>
) => Effect<void>
Logs one or more messages at the ERROR level.
Example (Logging errors)
import { Effect } from "effect"
const program = Effect.gen(function*() {
yield* Effect.logError("Database connection failed")
yield* Effect.logError(
"Error code:",
500,
"Message:",
"Internal server error"
)
// Can be used with error objects
const error = new Error("Something went wrong")
yield* Effect.logError("Caught error:", error.message)
})
Effect.runPromise(program)
// Output:
// timestamp=2023-... level=ERROR message="Database connection failed"
// timestamp=2023-... level=ERROR message="Error code: 500 Message: Internal server error"
// timestamp=2023-... level=ERROR message="Caught error: Something went wrong"
logError(`dead-letter ${e: {
readonly _tag: "RetryExhausted"
readonly entry: WorkPool.QueueEntry<{
to: string
subject: string
}>
readonly cause: Cause.Cause<never>
}
e.entry: QueueEntry<T>(property) entry: {
item: T;
entryId: string;
key: string;
priority: Priority;
lane: number;
attempts: number;
timestamps: QueueEntryTimestamps;
batchId: string;
releaseId: string;
sourceHyperlinkId: string;
attributes: { readonly [key: string]: unknown };
}
entry.QueueEntry<{ to: string; subject: string; }>.item: {
to: string;
subject: string;
}
item.to: stringto}: ${import CauseCause.const pretty: <E>(
cause: Cause<E>
) => string
Formats a Cause as a human-readable string for logging or debugging.
When to use
Use to render a whole cause as one human-readable string for logs or
diagnostics.
Details
Delegates to
prettyErrors
to convert each reason to an Error,
then joins their stack traces with newlines. Nested Error.cause chains
are rendered inline with indentation:
ErrorName: message
at ...
at ... {
[cause]: NestedError: message
at ...
}
Span annotations are appended to the relevant stack frames when available.
Gotchas
Rendering an empty cause produces an empty string because there are no
errors to render.
Example (Rendering a cause)
import { Cause } from "effect"
const rendered = Cause.pretty(Cause.fail("something went wrong"))
console.log(rendered.includes("something went wrong")) // true
pretty(e: {
readonly _tag: "RetryExhausted"
readonly entry: WorkPool.QueueEntry<{
to: string
subject: string
}>
readonly cause: Cause.Cause<never>
}
e.cause: Cause.Cause<E>(property) cause: {
reasons: ReadonlyArray<Reason<E>>;
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;
}
cause)}`),
}),
),
)
})How is it trending? — metrics.stream emits a windowed aggregate (throughput, average wait, per-window counts) once per window; metrics.query reads historical windows back from the store.
Effect queues cant enumerate their pending items — theres no list. You read counts (size, per-priority sizes on status) and facts (events), and you target what you already know by key (drop, deadLetter). Thats a feature: it keeps the queue O(1) to observe at any depth.
Controlling
The same handle steers the queue. pause stops draining (items still enqueue and accumulate); resume starts again; shutdown drains gracefully and stops; clear empties the pending items and returns how many it cleared; start forks the worker pool (idempotent — layers do this for you).
Three verbs route work out of the queue: release exports pending entries and removes them (hand them to another runtime, then enqueue them there); deadLetter removes entries matching a selector and records them as dead-lettered; drop removes them without a trace. You target these by what you know — an entry id or a matching item — never by listing.
Success values
If the worker returns a value, declare a success schema and that value flows onto the Completed event and the stores analytics:
const const Job: Schema.Struct<{
readonly id: Schema.String
}>
const Job: {
Type: Struct.Type<Fields>;
Encoded: Struct.Encoded<Fields>;
DecodingServices: Struct.DecodingServices<Fields>;
EncodingServices: Struct.EncodingServices<Fields>;
Iso: Struct.Iso<Fields>;
fields: Fields;
mapFields: (f: (fields: { readonly id: Schema.String }) => To, options?: { readonly unsafePreserveChecks?: boolean | undefined } | undefined) => Schema.Struct<{ [K in keyof Readonly<To>]: Readonly<To>[K]; }>;
Rebuild: Rebuild;
ast: Ast;
annotate: (annotations: Schema.Annotations.Bottom<Schema.Struct.ReadonlySide<{ readonly id: Schema.String; }, 'Type'>, readonly []>) => Schema.Struct<{ readonly id: Schema.String; }>;
annotateKey: (annotations: Schema.Annotations.Key<Schema.Struct.ReadonlySide<{ readonly id: Schema.String }, 'Type'>>) => Schema.Struct<{ readonly id: Schema.String }>;
check: (checks_0: Check<Schema.Struct.ReadonlySide<{ readonly id: Schema.String }, 'Type'>>, ...checks: Array<Check<Schema.Struct.ReadonlySide<{ readonly id: Schema.String }, 'Type'>>>) => Schema.Struct<{ readonly id: Schema.String }>;
rebuild: (ast: Objects) => Schema.Struct<{ readonly id: Schema.String }>;
make: (input: Struct.ReadonlyMakeIn<{ readonly id: String }>, options?: MakeOptions) => Struct.ReadonlySide<{ readonly id: String }, 'Type'>;
makeOption: (input: Struct.ReadonlyMakeIn<{ readonly id: String }>, options?: MakeOptions) => Option_.Option<Struct.ReadonlySide<{ readonly id: String }, 'Type'>>;
makeEffect: (input: Struct.ReadonlyMakeIn<{ readonly id: String }>, options?: MakeOptions) => Effect.Effect<Struct.ReadonlySide<{ readonly id: String }, 'Type'>, SchemaError, 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; <…;
}
Job = import SchemaSchema.function Struct<
Fields extends Struct.Fields
>(fields: Fields): Struct<Fields>
Defines a struct schema from a map of field schemas.
Details
Each field value is a schema. Use
optionalKey
or
optional
to
mark fields as optional, and
mutableKey
to mark them as mutable.
The resulting schema's Type is a readonly object type with the fields'
decoded types. The Encoded form mirrors the field schemas' encoded types.
Example (Defining a basic struct)
import { Schema } from "effect"
const Person = Schema.Struct({
name: Schema.String,
age: Schema.Number,
email: Schema.optionalKey(Schema.String)
})
// { readonly name: string; readonly age: number; readonly email?: string }
type Person = typeof Person.Type
const alice = Schema.decodeUnknownSync(Person)({ name: "Alice", age: 30 })
console.log(alice)
// { name: 'Alice', age: 30 }
Struct({ id: Schema.String(property) id: {
Rebuild: Rebuild;
Iso: Iso;
ast: Ast;
Type: T;
Encoded: E;
DecodingServices: RD;
EncodingServices: RE;
annotate: (annotations: Schema.Annotations.Bottom<string, readonly []>) => Schema.String;
annotateKey: (annotations: Schema.Annotations.Key<string>) => Schema.String;
check: (checks_0: Check<string>, ...checks: Array<Check<string>>) => Schema.String;
rebuild: (ast: String) => Schema.String;
make: (input: string, options?: MakeOptions) => string;
makeOption: (input: string, options?: MakeOptions) => Option_.Option<string>;
makeEffect: (input: string, options?: MakeOptions) => Effect.Effect<string, SchemaError, 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; <…;
}
id: import SchemaSchema.const String: Stringconst String: {
Rebuild: Rebuild;
Iso: Iso;
ast: Ast;
Type: T;
Encoded: E;
DecodingServices: RD;
EncodingServices: RE;
annotate: (annotations: Schema.Annotations.Bottom<string, readonly []>) => Schema.String;
annotateKey: (annotations: Schema.Annotations.Key<string>) => Schema.String;
check: (checks_0: Check<string>, ...checks: Array<Check<string>>) => Schema.String;
rebuild: (ast: String) => Schema.String;
make: (input: string, options?: MakeOptions) => string;
makeOption: (input: string, options?: MakeOptions) => Option_.Option<string>;
makeEffect: (input: string, options?: MakeOptions) => Effect.Effect<string, SchemaError, 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; <…;
}
Type-level representation of
String
.
Schema for string values. Validates that the input is typeof "string".
String })
class class Doublerclass Doubler {
key: Identifier;
Service: {
status: Hyperlink.Subscribable<QueueStatus>;
size: Hyperlink.Subscribable<number>;
isEmpty: Hyperlink.Subscribable<boolean>;
start: Effect.Effect<void, never, Requirements>;
pause: Effect.Effect<void>;
resume: Effect.Effect<void>;
shutdown: Effect.Effect<void>;
clear: Effect.Effect<number, never, Requirements>;
metrics: { readonly stream: Stream.Stream<QueueMetrics>; readonly query: (input: { readonly limit?: number; readonly since?: DateTime.Utc; readonly until?: DateTime.Utc }) => Effect.Effect<ReadonlyArray<QueueMetrics>, never, Requirements> };
add: QueueEnqueue<Payload, never, Requirements>;
prioritize: QueueEnqueue<Payload, never, Requirements>;
defer: QueueEnqueue<Payload, never, Requirements>;
enqueue: (entries: ReadonlyArray<QueueEntry<Payload>>) => Effect.Effect<void, never, Requirements>;
release: (input: { readonly options?: QueueReleaseOptions }) => Effect.Effect<ReadonlyArray<QueueEntry<Payload>>, never, Requirements>;
releaseEncoded: (input: { readonly options?: QueueReleaseOptions }) => Effect.Effect<ReadonlyArray<Hyperlink.Decoded<typeof queueEncodedEntry>>, QueueReleaseEncodingError, Requirements>;
deadLetter: (input: { readonly selector: QueueEntrySelector<Payload> | QueueEntry<Payload>; readonly options: QueueRouteOptions }) => Effect.Effect<ReadonlyArray<QueueEntry<Payload>>, never, Requirements>;
drop: (input: { readonly selector: QueueEntrySelector<Payload> | QueueEntry<Payload>; readonly options: QueueRouteOptions }) => Effect.Effect<ReadonlyArray<QueueEntry<Payload>>, never, Requirements>;
events: Stream.Stream<QueueEvent<Payload, Error, Success>>;
};
}
Doubler extends import WorkPoolWorkPool.Tag<Doubler>(): {
<F, HSelf, D>(key: string, payload: Schema.Struct<F>, options: {
readonly description?: string;
readonly node: NodeKey<HSelf>;
readonly defaults: DefaultsInput<D>;
}): TagWithDefaults<QueueNodeBoundTagCarriers<Doubler, F, HSelf, Schema.Void, Schema.Never>, D>;
<F, HSelf>(key: string, payload: Schema.Struct<F>, options: {
readonly description?: string;
readonly node: NodeKey<...>;
}): QueueNodeBoundTagCarriers<...>;
<F, Success, Error>(key: string, payload: Schema.Struct<...>, success: Success, error?: Error | undefined): QueueTagCarriers<...>;
<F, D>(key: string, payload: Schema.Struct<...>, options: {
...;
}): TagWithDefaults<...>;
<F>(key: string, payload: Schema.Struct<...>, options?: {
...;
} | undefined): QueueTagCarriers<...>;
<F, HSelf, D>(key: string, config: WorkPool.QueueTagConfig<...> & {
...;
}): TagWithDefaults<...>;
<F, HSelf>(key: string, config: WorkPool.QueueTagConfig<...> & {
...;
}): QueueNodeBoundTagCarriers<...>;
<F, D, Success, Error>(key: string, config: WorkPool.QueueTagConfig<...> & {
...;
}): TagWithDefaults<...>;
<F, Success, Error>(key: string, config: WorkPool.QueueTagConfig<...>): QueueTagCarriers<...>;
}
export Tag
Tag<class Doublerclass Doubler {
key: Identifier;
Service: {
status: Hyperlink.Subscribable<QueueStatus>;
size: Hyperlink.Subscribable<number>;
isEmpty: Hyperlink.Subscribable<boolean>;
start: Effect.Effect<void, never, Requirements>;
pause: Effect.Effect<void>;
resume: Effect.Effect<void>;
shutdown: Effect.Effect<void>;
clear: Effect.Effect<number, never, Requirements>;
metrics: { readonly stream: Stream.Stream<QueueMetrics>; readonly query: (input: { readonly limit?: number; readonly since?: DateTime.Utc; readonly until?: DateTime.Utc }) => Effect.Effect<ReadonlyArray<QueueMetrics>, never, Requirements> };
add: QueueEnqueue<Payload, never, Requirements>;
prioritize: QueueEnqueue<Payload, never, Requirements>;
defer: QueueEnqueue<Payload, never, Requirements>;
enqueue: (entries: ReadonlyArray<QueueEntry<Payload>>) => Effect.Effect<void, never, Requirements>;
release: (input: { readonly options?: QueueReleaseOptions }) => Effect.Effect<ReadonlyArray<QueueEntry<Payload>>, never, Requirements>;
releaseEncoded: (input: { readonly options?: QueueReleaseOptions }) => Effect.Effect<ReadonlyArray<Hyperlink.Decoded<typeof queueEncodedEntry>>, QueueReleaseEncodingError, Requirements>;
deadLetter: (input: { readonly selector: QueueEntrySelector<Payload> | QueueEntry<Payload>; readonly options: QueueRouteOptions }) => Effect.Effect<ReadonlyArray<QueueEntry<Payload>>, never, Requirements>;
drop: (input: { readonly selector: QueueEntrySelector<Payload> | QueueEntry<Payload>; readonly options: QueueRouteOptions }) => Effect.Effect<ReadonlyArray<QueueEntry<Payload>>, never, Requirements>;
events: Stream.Stream<QueueEvent<Payload, Error, Success>>;
};
}
Doubler>()("app/Doubler", {
QueueTagConfig<{ readonly id: String; }, Number, Never>.payload: Schema.Struct<{ readonly id: Schema.String }>(property) QueueTagConfig<{ readonly id: String; }, Number, Never>.payload: {
Type: Struct.Type<Fields>;
Encoded: Struct.Encoded<Fields>;
DecodingServices: Struct.DecodingServices<Fields>;
EncodingServices: Struct.EncodingServices<Fields>;
Iso: Struct.Iso<Fields>;
fields: Fields;
mapFields: (f: (fields: { readonly id: Schema.String }) => To, options?: { readonly unsafePreserveChecks?: boolean | undefined } | undefined) => Schema.Struct<{ [K in keyof Readonly<To>]: Readonly<To>[K]; }>;
Rebuild: Rebuild;
ast: Ast;
annotate: (annotations: Schema.Annotations.Bottom<Schema.Struct.ReadonlySide<{ readonly id: Schema.String; }, 'Type'>, readonly []>) => Schema.Struct<{ readonly id: Schema.String; }>;
annotateKey: (annotations: Schema.Annotations.Key<Schema.Struct.ReadonlySide<{ readonly id: Schema.String }, 'Type'>>) => Schema.Struct<{ readonly id: Schema.String }>;
check: (checks_0: Check<Schema.Struct.ReadonlySide<{ readonly id: Schema.String }, 'Type'>>, ...checks: Array<Check<Schema.Struct.ReadonlySide<{ readonly id: Schema.String }, 'Type'>>>) => Schema.Struct<{ readonly id: Schema.String }>;
rebuild: (ast: Objects) => Schema.Struct<{ readonly id: Schema.String }>;
make: (input: Struct.ReadonlyMakeIn<{ readonly id: String }>, options?: MakeOptions) => Struct.ReadonlySide<{ readonly id: String }, 'Type'>;
makeOption: (input: Struct.ReadonlyMakeIn<{ readonly id: String }>, options?: MakeOptions) => Option_.Option<Struct.ReadonlySide<{ readonly id: String }, 'Type'>>;
makeEffect: (input: Struct.ReadonlyMakeIn<{ readonly id: String }>, options?: MakeOptions) => Effect.Effect<Struct.ReadonlySide<{ readonly id: String }, 'Type'>, SchemaError, 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; <…;
}
payload: const Job: Schema.Struct<{
readonly id: Schema.String
}>
const Job: {
Type: Struct.Type<Fields>;
Encoded: Struct.Encoded<Fields>;
DecodingServices: Struct.DecodingServices<Fields>;
EncodingServices: Struct.EncodingServices<Fields>;
Iso: Struct.Iso<Fields>;
fields: Fields;
mapFields: (f: (fields: { readonly id: Schema.String }) => To, options?: { readonly unsafePreserveChecks?: boolean | undefined } | undefined) => Schema.Struct<{ [K in keyof Readonly<To>]: Readonly<To>[K]; }>;
Rebuild: Rebuild;
ast: Ast;
annotate: (annotations: Schema.Annotations.Bottom<Schema.Struct.ReadonlySide<{ readonly id: Schema.String; }, 'Type'>, readonly []>) => Schema.Struct<{ readonly id: Schema.String; }>;
annotateKey: (annotations: Schema.Annotations.Key<Schema.Struct.ReadonlySide<{ readonly id: Schema.String }, 'Type'>>) => Schema.Struct<{ readonly id: Schema.String }>;
check: (checks_0: Check<Schema.Struct.ReadonlySide<{ readonly id: Schema.String }, 'Type'>>, ...checks: Array<Check<Schema.Struct.ReadonlySide<{ readonly id: Schema.String }, 'Type'>>>) => Schema.Struct<{ readonly id: Schema.String }>;
rebuild: (ast: Objects) => Schema.Struct<{ readonly id: Schema.String }>;
make: (input: Struct.ReadonlyMakeIn<{ readonly id: String }>, options?: MakeOptions) => Struct.ReadonlySide<{ readonly id: String }, 'Type'>;
makeOption: (input: Struct.ReadonlyMakeIn<{ readonly id: String }>, options?: MakeOptions) => Option_.Option<Struct.ReadonlySide<{ readonly id: String }, 'Type'>>;
makeEffect: (input: Struct.ReadonlyMakeIn<{ readonly id: String }>, options?: MakeOptions) => Effect.Effect<Struct.ReadonlySide<{ readonly id: String }, 'Type'>, SchemaError, 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; <…;
}
Job,
QueueTagConfig<{ readonly id: String; }, Number, Never>.success?: Schema.Number(property) QueueTagConfig<{ readonly id: String; }, Number, Never>.success?: {
Rebuild: Rebuild;
Iso: Iso;
ast: Ast;
Type: T;
Encoded: E;
DecodingServices: RD;
EncodingServices: RE;
annotate: (annotations: Schema.Annotations.Bottom<number, readonly []>) => Schema.Number;
annotateKey: (annotations: Schema.Annotations.Key<number>) => Schema.Number;
check: (checks_0: Check<number>, ...checks: Array<Check<number>>) => Schema.Number;
rebuild: (ast: Number) => Schema.Number;
make: (input: number, options?: MakeOptions) => number;
makeOption: (input: number, options?: MakeOptions) => Option_.Option<number>;
makeEffect: (input: number, options?: MakeOptions) => Effect.Effect<number, SchemaError, 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; <…;
}
success: import SchemaSchema.const Number: Numberconst Number: {
Rebuild: Rebuild;
Iso: Iso;
ast: Ast;
Type: T;
Encoded: E;
DecodingServices: RD;
EncodingServices: RE;
annotate: (annotations: Schema.Annotations.Bottom<number, readonly []>) => Schema.Number;
annotateKey: (annotations: Schema.Annotations.Key<number>) => Schema.Number;
check: (checks_0: Check<number>, ...checks: Array<Check<number>>) => Schema.Number;
rebuild: (ast: Number) => Schema.Number;
make: (input: number, options?: MakeOptions) => number;
makeOption: (input: number, options?: MakeOptions) => Option_.Option<number>;
makeEffect: (input: number, options?: MakeOptions) => Effect.Effect<number, SchemaError, 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; <…;
}
Type-level representation of
Number
.
Schema for number values, including NaN, Infinity, and -Infinity.
Details
Default JSON serializer:
- Finite numbers are serialized as numbers.
- Non-finite values are serialized as strings (
"NaN", "Infinity", "-Infinity").
Number, // the worker returns a number
}) {}
const const DoublerLive: Layer<
Doubler | Storage | Local<Doubler>,
never,
never
>
const DoublerLive: {
build: (memoMap: MemoMap, scope: Scope.Scope) => Effect<Context.Context<Doubler | Storage | Local<Doubler>>, 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; <…;
}
DoublerLive = import WorkPoolWorkPool.function layer<Doubler, {
readonly id: Schema.String;
}, never, never, Schema.Number, Schema.Never>(tag: QueueTagFor<Doubler, {
readonly id: Schema.String;
}, Schema.Number, Schema.Never>, config: QueueVerbConfig<{
readonly id: Schema.String;
}, never, never, never, Schema.Number>): Layer<Doubler | Local<Doubler> | Storage, never, never> (+1 overload)
Run this WorkPool locally — soft-defaults
Store.Storage
(R fulfilled; override by
providing an app store into this layer). Accepts a plain
Tag
or a
priority
tag and
dispatches to the matching engine.
layerMemory
is an alias for the same soft-default.
layer(class Doublerclass Doubler {
key: Identifier;
Service: {
status: Hyperlink.Subscribable<QueueStatus>;
size: Hyperlink.Subscribable<number>;
isEmpty: Hyperlink.Subscribable<boolean>;
start: Effect.Effect<void, never, Requirements>;
pause: Effect.Effect<void>;
resume: Effect.Effect<void>;
shutdown: Effect.Effect<void>;
clear: Effect.Effect<number, never, Requirements>;
metrics: { readonly stream: Stream.Stream<QueueMetrics>; readonly query: (input: { readonly limit?: number; readonly since?: DateTime.Utc; readonly until?: DateTime.Utc }) => Effect.Effect<ReadonlyArray<QueueMetrics>, never, Requirements> };
add: QueueEnqueue<Payload, never, Requirements>;
prioritize: QueueEnqueue<Payload, never, Requirements>;
defer: QueueEnqueue<Payload, never, Requirements>;
enqueue: (entries: ReadonlyArray<QueueEntry<Payload>>) => Effect.Effect<void, never, Requirements>;
release: (input: { readonly options?: QueueReleaseOptions }) => Effect.Effect<ReadonlyArray<QueueEntry<Payload>>, never, Requirements>;
releaseEncoded: (input: { readonly options?: QueueReleaseOptions }) => Effect.Effect<ReadonlyArray<Hyperlink.Decoded<typeof queueEncodedEntry>>, QueueReleaseEncodingError, Requirements>;
deadLetter: (input: { readonly selector: QueueEntrySelector<Payload> | QueueEntry<Payload>; readonly options: QueueRouteOptions }) => Effect.Effect<ReadonlyArray<QueueEntry<Payload>>, never, Requirements>;
drop: (input: { readonly selector: QueueEntrySelector<Payload> | QueueEntry<Payload>; readonly options: QueueRouteOptions }) => Effect.Effect<ReadonlyArray<QueueEntry<Payload>>, never, Requirements>;
events: Stream.Stream<QueueEvent<Payload, Error, Success>>;
};
description: string | undefined;
of: (this: void, self: WorkPool.WorkPool<{ id: string }, number, never, never>) => WorkPool.WorkPool<{ id: string }, number, never, never>;
context: (self: WorkPool.WorkPool<{ id: string }, number, never, never>) => Context<Doubler>;
use: (f: (service: WorkPool.WorkPool<{ id: string }, number, never, never>) => Effect.Effect<A, E, R>) => Effect.Effect<A, E, Doubler | R>;
useSync: (f: (service: WorkPool.WorkPool<{ id: string }, number, never, never>) => A) => Effect.Effect<A, never, Doubler>;
Identifier: Identifier;
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;
}
Doubler, {
effect: (
item: Schema.Struct.ReadonlySide<
{
readonly id: Schema.String
},
"Type"
>,
ctx: WorkPool.EffectContext<
Schema.Struct.ReadonlySide<
{
readonly id: Schema.String
},
"Type"
>,
QueueEnqueueErrors,
never
>
) => Effect.Effect<number, never, never>
Daemon each item. The success channel A is driven by the tag's success wire schema
(default void): with a success schema the worker must return Effect<A, E, R> and that
value rides Completed.success / store.completed; without one it stays Effect<void, E, R>.
effect: (job: Schema.Struct.ReadonlySide<
{
readonly id: Schema.String
},
"Type"
>
(parameter) job: {
id: string;
}
job) => import EffectEffect.const succeed: <A>(value: A) => Effect<A>Creates an Effect that always succeeds with a given value.
When to use
Use when an effect should complete successfully with a specific value without any errors
or external dependencies.
Example (Creating a successful effect)
import { Effect } from "effect"
// Creating an effect that represents a successful scenario
//
// ┌─── Effect<number, never, never>
// ▼
const success = Effect.succeed(42)
succeed(job: Schema.Struct.ReadonlySide<
{
readonly id: Schema.String
},
"Type"
>
(parameter) job: {
id: string;
}
job.id: stringid.String.length: numberReturns the length of a String object.
length * 2),
})The handle types as WorkPool<{ id: string }, number, never, never>, and Completed.success carries the number.
The .Service shorthand
Tag + layer keeps the contract and the worker separate — which is what makes a queue location-transparent (the same tag, a different layer, and it runs remotely). When you dont need that split, WorkPool.Service fuses both into one class — a self-contained Service:
class class Emailsclass Emails {
Service: Service;
key: Identifier;
}
Emails extends import WorkPoolWorkPool.Service<Emails, Schema.Struct<Fields extends Schema.Struct.Fields>.ReadonlySide<{
readonly to: Schema.String;
readonly subject: Schema.String;
}, "Type">, never>(): WorkPoolServiceFactory
export Service
Engine class factory: creates a Context tag with a baked-in .layer. Surfaced publicly as
WorkPool.Service
.
The returned value is both a Context.Service (yieldable tag) and has a .layer property for
providing the queue to your program. When itemSchema is set, the declaration also exposes
WorkPoolDefinition.item
for typed WorkPool contracts.
Service<class Emailsclass Emails {
Service: Service;
key: Identifier;
}
Emails, typeof const EmailJob: Schema.Struct<{
readonly to: Schema.String
readonly subject: Schema.String
}>
const EmailJob: {
Type: Struct.Type<Fields>;
Encoded: Struct.Encoded<Fields>;
DecodingServices: Struct.DecodingServices<Fields>;
EncodingServices: Struct.EncodingServices<Fields>;
Iso: Struct.Iso<Fields>;
fields: Fields;
mapFields: (f: (fields: { readonly to: Schema.String; readonly subject: Schema.String }) => To, options?: { readonly unsafePreserveChecks?: boolean | undefined } | undefined) => Schema.Struct<{ [K in keyof Readonly<To>]: Readonly<To>[K]; }>;
Rebuild: Rebuild;
ast: Ast;
annotate: (annotations: Schema.Annotations.Bottom<Schema.Struct.ReadonlySide<{ readonly to: Schema.String; readonly subject: Schema.String; }, 'Type'>, readonly []>) => Schema.Struct<{ readonly to: Schema.String; readonly subject: Schema.String; }>;
annotateKey: (annotations: Schema.Annotations.Key<Schema.Struct.ReadonlySide<{ readonly to: Schema.String; readonly subject: Schema.String }, 'Type'>>) => Schema.Struct<{ readonly to: Schema.String; readonly subject: Schema.String }>;
check: (checks_0: Check<Schema.Struct.ReadonlySide<{ readonly to: Schema.String; readonly subject: Schema.String }, 'Type'>>, ...checks: Array<Check<Schema.Struct.ReadonlySide<{ readonly to: Schema.String; readonly subject: Schema.String }, 'Type…;
rebuild: (ast: Objects) => Schema.Struct<{ readonly to: Schema.String; readonly subject: Schema.String }>;
make: (input: Struct.ReadonlyMakeIn<{ readonly to: String; readonly subject: String }>, options?: MakeOptions) => Struct.ReadonlySide<{ readonly to: String; readonly subject: String }, 'Type'>;
makeOption: (input: Struct.ReadonlyMakeIn<{ readonly to: String; readonly subject: String }>, options?: MakeOptions) => Option_.Option<Struct.ReadonlySide<{ readonly to: String; readonly subject: String }, 'Type'>>;
makeEffect: (input: Struct.ReadonlyMakeIn<{ readonly to: String; readonly subject: String }>, options?: MakeOptions) => Effect.Effect<Struct.ReadonlySide<{ readonly to: String; readonly subject: String }, 'Type'>, SchemaError, 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; <…;
}
EmailJob.Struct<{ readonly to: String; readonly subject: String; }>["Type"]: Schema.Struct.ReadonlySide<{
readonly to: Schema.String;
readonly subject: Schema.String;
}, "Type">
(property) Struct<{ readonly to: String; readonly subject: String; }>["Type"]: {
to: string;
subject: string;
}
Type, never>()(
"app/Emails",
{
WorkPoolConfigBase<T>.concurrency?: number | undefinedMax items processing concurrently (worker count).
concurrency: 4,
effect: (
item: Schema.Struct.ReadonlySide<
{
readonly to: Schema.String
readonly subject: Schema.String
},
"Type"
>,
ctx: WorkPool.EffectContext<
Schema.Struct.ReadonlySide<
{
readonly to: Schema.String
readonly subject: Schema.String
},
"Type"
>,
never,
never
>
) => Effect.Effect<void, never, never>
Daemon each item. Receives a guarded
EffectContext
for spawning
derived work. The exit of this effect determines success/failure for the item;
the success channel is always void.
effect: (job: Schema.Struct.ReadonlySide<
{
readonly to: Schema.String
readonly subject: Schema.String
},
"Type"
>
(parameter) job: {
to: string;
subject: string;
}
job) => import EffectEffect.const log: (
...message: ReadonlyArray<any>
) => Effect<void>
Logs one or more messages using the default log level.
Example (Logging at the default level)
import { Effect } from "effect"
const program = Effect.gen(function*() {
yield* Effect.log("Starting computation")
const result = 2 + 2
yield* Effect.log("Result:", result)
yield* Effect.log("Multiple", "values", "can", "be", "logged")
return result
})
Effect.runPromise(program).then(console.log)
// Output:
// timestamp=2023-... level=INFO message="Starting computation"
// timestamp=2023-... level=INFO message="Result: 4"
// timestamp=2023-... level=INFO message="Multiple values can be logged"
// 4
log(`send ${job: Schema.Struct.ReadonlySide<
{
readonly to: Schema.String
readonly subject: Schema.String
},
"Type"
>
(parameter) job: {
to: string;
subject: string;
}
job.to: stringto}`),
},
) {}yield* Emails yields the exact same handle type. Reach for Service for a self-contained local queue; reach for Tag + layer when the queue might move.
Everything so far is the basic queue. The rest of this guide is the operating surface — the controls you reach for once a queue is real.
Handling failure
attempts is the blunt instrument: try N times, then dead-letter. Real failure handling is per-error, and thats what onFailure is for. It runs when an attempt fails, receives the entry and the Cause, and decides what happens next — retry, dead-letter, or drop:
const const EmailsLive: Layer<
Emails | Local<Emails> | Storage,
never,
never
>
const EmailsLive: {
build: (memoMap: MemoMap, scope: Scope.Scope) => Effect<Context.Context<Emails | Local<Emails> | 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; <…;
}
EmailsLive = import WorkPoolWorkPool.function layer<Emails, {
readonly to: Schema.String;
readonly subject: Schema.String;
}, never, never, Schema.Void, Schema.String>(tag: QueueTagFor<Emails, {
readonly to: Schema.String;
readonly subject: Schema.String;
}, Schema.Void, Schema.String>, config: QueueVerbConfig<{
readonly to: Schema.String;
readonly subject: Schema.String;
}, string, never, never, Schema.Void>): Layer<Emails | Local<Emails> | Storage, never, never> (+1 overload)
Run this WorkPool locally — soft-defaults
Store.Storage
(R fulfilled; override by
providing an app store into this layer). Accepts a plain
Tag
or a
priority
tag and
dispatches to the matching engine.
layerMemory
is an alias for the same soft-default.
layer(class Emailsclass Emails {
key: Identifier;
Service: {
status: Hyperlink.Subscribable<QueueStatus>;
size: Hyperlink.Subscribable<number>;
isEmpty: Hyperlink.Subscribable<boolean>;
start: Effect.Effect<void, never, Requirements>;
pause: Effect.Effect<void>;
resume: Effect.Effect<void>;
shutdown: Effect.Effect<void>;
clear: Effect.Effect<number, never, Requirements>;
metrics: { readonly stream: Stream.Stream<QueueMetrics>; readonly query: (input: { readonly limit?: number; readonly since?: DateTime.Utc; readonly until?: DateTime.Utc }) => Effect.Effect<ReadonlyArray<QueueMetrics>, never, Requirements> };
add: QueueEnqueue<Payload, never, Requirements>;
prioritize: QueueEnqueue<Payload, never, Requirements>;
defer: QueueEnqueue<Payload, never, Requirements>;
enqueue: (entries: ReadonlyArray<QueueEntry<Payload>>) => Effect.Effect<void, never, Requirements>;
release: (input: { readonly options?: QueueReleaseOptions }) => Effect.Effect<ReadonlyArray<QueueEntry<Payload>>, never, Requirements>;
releaseEncoded: (input: { readonly options?: QueueReleaseOptions }) => Effect.Effect<ReadonlyArray<Hyperlink.Decoded<typeof queueEncodedEntry>>, QueueReleaseEncodingError, Requirements>;
deadLetter: (input: { readonly selector: QueueEntrySelector<Payload> | QueueEntry<Payload>; readonly options: QueueRouteOptions }) => Effect.Effect<ReadonlyArray<QueueEntry<Payload>>, never, Requirements>;
drop: (input: { readonly selector: QueueEntrySelector<Payload> | QueueEntry<Payload>; readonly options: QueueRouteOptions }) => Effect.Effect<ReadonlyArray<QueueEntry<Payload>>, never, Requirements>;
events: Stream.Stream<QueueEvent<Payload, Error, Success>>;
};
description: string | undefined;
of: (this: void, self: WorkPool.WorkPool<{ to: string; subject: string }, void, string, never>) => WorkPool.WorkPool<{ to: string; subject: string }, void, string, never>;
context: (self: WorkPool.WorkPool<{ to: string; subject: string }, void, string, never>) => Context<Emails>;
use: (f: (service: WorkPool.WorkPool<{ to: string; subject: string }, void, string, never>) => Effect.Effect<A, E, R>) => Effect.Effect<A, E, Emails | R>;
useSync: (f: (service: WorkPool.WorkPool<{ to: string; subject: string }, void, string, never>) => A) => Effect.Effect<A, never, Emails>;
Identifier: Identifier;
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;
}
Emails, {
effect: (
item: Schema.Struct.ReadonlySide<
{
readonly to: Schema.String
readonly subject: Schema.String
},
"Type"
>,
ctx: WorkPool.EffectContext<
Schema.Struct.ReadonlySide<
{
readonly to: Schema.String
readonly subject: Schema.String
},
"Type"
>,
QueueEnqueueErrors,
never
>
) => Effect.Effect<void, string, never>
Daemon each item. The success channel A is driven by the tag's success wire schema
(default void): with a success schema the worker must return Effect<A, E, R> and that
value rides Completed.success / store.completed; without one it stays Effect<void, E, R>.
effect: (job: Schema.Struct.ReadonlySide<
{
readonly to: Schema.String
readonly subject: Schema.String
},
"Type"
>
(parameter) job: {
to: string;
subject: string;
}
job) => const send: (job: {
to: string
}) => Effect.Effect<void, string>
send(job: Schema.Struct.ReadonlySide<
{
readonly to: Schema.String
readonly subject: Schema.String
},
"Type"
>
(parameter) job: {
to: string;
subject: string;
}
job),
attempts?: number | undefinedMax attempts for an item — the initial try plus automatic re-enqueues. On failure the
worker re-enqueues the item at its own priority, preserving its attempt count, until
attempts is reached; then it's exhausted (emits a RetryExhausted event). Observe the
RetryScheduled / RetryExhausted lifecycle on the
QueueHandleApi.events
stream.
In-place retry of the worker effect is a separate concern — put Effect.retry(...) on your
effect.
attempts: 5,
onFailure?: | WorkPool.QueueOnFailure<
Schema.Struct.ReadonlySide<
{
readonly to: Schema.String
readonly subject: Schema.String
},
"Type"
>,
string,
never
>
| undefined
Optional inline per-error disposition. See
QueueOnFailure
.
onFailure: (entry: WorkPool.QueueEntry<
Schema.Struct.ReadonlySide<
{
readonly to: Schema.String
readonly subject: Schema.String
},
"Type"
>
>
(parameter) entry: {
item: T;
entryId: string;
key: string;
priority: Priority;
lane: number;
attempts: number;
timestamps: QueueEntryTimestamps;
batchId: string;
releaseId: string;
sourceHyperlinkId: string;
attributes: { readonly [key: string]: unknown };
}
entry, cause: Cause.Cause<string>(parameter) cause: {
reasons: ReadonlyArray<Reason<E>>;
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;
}
cause) =>
const isTransient: (
cause: Cause.Cause<string>
) => boolean
isTransient(cause: Cause.Cause<string>(parameter) cause: {
reasons: ReadonlyArray<Reason<E>>;
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;
}
cause)
? import EffectEffect.const succeed: <A>(value: A) => Effect<A>Creates an Effect that always succeeds with a given value.
When to use
Use when an effect should complete successfully with a specific value without any errors
or external dependencies.
Example (Creating a successful effect)
import { Effect } from "effect"
// Creating an effect that represents a successful scenario
//
// ┌─── Effect<number, never, never>
// ▼
const success = Effect.succeed(42)
succeed("retry" as type const = "retry"const) // a blip — spend an attempt
: import EffectEffect.const succeed: <A>(value: A) => Effect<A>Creates an Effect that always succeeds with a given value.
When to use
Use when an effect should complete successfully with a specific value without any errors
or external dependencies.
Example (Creating a successful effect)
import { Effect } from "effect"
// Creating an effect that represents a successful scenario
//
// ┌─── Effect<number, never, never>
// ▼
const success = Effect.succeed(42)
succeed("deadLetter" as type const = "deadLetter"const), // a bad address — set it aside
})Three dispositions: "retry" re-enqueues (until attempts runs out), "deadLetter" sets the entry aside as failed (a DeadLettered event), and "drop" discards it silently. Without onFailure, the default is retry until attempts, then dead-letter. For retrying the effect itself (backoff, jitter), put Effect.retry on your worker effect — thats a different layer of the onion: onFailure decides the entrys fate after the effect has given up.
Rate limiting the drain
A queue that hammers a downstream API needs a ceiling. rateLimit caps how many items start per window; excess wait, and a RateLimitExceeded event fires when the ceiling bites:
const const EmailsLive: Layer<
Emails | Local<Emails> | Storage,
never,
never
>
const EmailsLive: {
build: (memoMap: MemoMap, scope: Scope.Scope) => Effect<Context.Context<Emails | Local<Emails> | 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; <…;
}
EmailsLive = import WorkPoolWorkPool.function layer<Emails, {
readonly to: Schema.String;
readonly subject: Schema.String;
}, never, never, Schema.Void, Schema.Never>(tag: QueueTagFor<Emails, {
readonly to: Schema.String;
readonly subject: Schema.String;
}, Schema.Void, Schema.Never>, config: QueueVerbConfig<{
readonly to: Schema.String;
readonly subject: Schema.String;
}, never, never, never, Schema.Void>): Layer<Emails | Local<Emails> | Storage, never, never> (+1 overload)
Run this WorkPool locally — soft-defaults
Store.Storage
(R fulfilled; override by
providing an app store into this layer). Accepts a plain
Tag
or a
priority
tag and
dispatches to the matching engine.
layerMemory
is an alias for the same soft-default.
layer(class Emailsclass Emails {
key: Identifier;
Service: {
status: Hyperlink.Subscribable<QueueStatus>;
size: Hyperlink.Subscribable<number>;
isEmpty: Hyperlink.Subscribable<boolean>;
start: Effect.Effect<void, never, Requirements>;
pause: Effect.Effect<void>;
resume: Effect.Effect<void>;
shutdown: Effect.Effect<void>;
clear: Effect.Effect<number, never, Requirements>;
metrics: { readonly stream: Stream.Stream<QueueMetrics>; readonly query: (input: { readonly limit?: number; readonly since?: DateTime.Utc; readonly until?: DateTime.Utc }) => Effect.Effect<ReadonlyArray<QueueMetrics>, never, Requirements> };
add: QueueEnqueue<Payload, never, Requirements>;
prioritize: QueueEnqueue<Payload, never, Requirements>;
defer: QueueEnqueue<Payload, never, Requirements>;
enqueue: (entries: ReadonlyArray<QueueEntry<Payload>>) => Effect.Effect<void, never, Requirements>;
release: (input: { readonly options?: QueueReleaseOptions }) => Effect.Effect<ReadonlyArray<QueueEntry<Payload>>, never, Requirements>;
releaseEncoded: (input: { readonly options?: QueueReleaseOptions }) => Effect.Effect<ReadonlyArray<Hyperlink.Decoded<typeof queueEncodedEntry>>, QueueReleaseEncodingError, Requirements>;
deadLetter: (input: { readonly selector: QueueEntrySelector<Payload> | QueueEntry<Payload>; readonly options: QueueRouteOptions }) => Effect.Effect<ReadonlyArray<QueueEntry<Payload>>, never, Requirements>;
drop: (input: { readonly selector: QueueEntrySelector<Payload> | QueueEntry<Payload>; readonly options: QueueRouteOptions }) => Effect.Effect<ReadonlyArray<QueueEntry<Payload>>, never, Requirements>;
events: Stream.Stream<QueueEvent<Payload, Error, Success>>;
};
description: string | undefined;
of: (this: void, self: WorkPool.WorkPool<{ to: string; subject: string }, void, never, never>) => WorkPool.WorkPool<{ to: string; subject: string }, void, never, never>;
context: (self: WorkPool.WorkPool<{ to: string; subject: string }, void, never, never>) => Context<Emails>;
use: (f: (service: WorkPool.WorkPool<{ to: string; subject: string }, void, never, never>) => Effect.Effect<A, E, R>) => Effect.Effect<A, E, Emails | R>;
useSync: (f: (service: WorkPool.WorkPool<{ to: string; subject: string }, void, never, never>) => A) => Effect.Effect<A, never, Emails>;
Identifier: Identifier;
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;
}
Emails, {
effect: (
item: Schema.Struct.ReadonlySide<
{
readonly to: Schema.String
readonly subject: Schema.String
},
"Type"
>,
ctx: WorkPool.EffectContext<
Schema.Struct.ReadonlySide<
{
readonly to: Schema.String
readonly subject: Schema.String
},
"Type"
>,
QueueEnqueueErrors,
never
>
) => Effect.Effect<void, never, never>
Daemon each item. The success channel A is driven by the tag's success wire schema
(default void): with a success schema the worker must return Effect<A, E, R> and that
value rides Completed.success / store.completed; without one it stays Effect<void, E, R>.
effect: (job: Schema.Struct.ReadonlySide<
{
readonly to: Schema.String
readonly subject: Schema.String
},
"Type"
>
(parameter) job: {
to: string;
subject: string;
}
job) => import EffectEffect.const log: (
...message: ReadonlyArray<any>
) => Effect<void>
Logs one or more messages using the default log level.
Example (Logging at the default level)
import { Effect } from "effect"
const program = Effect.gen(function*() {
yield* Effect.log("Starting computation")
const result = 2 + 2
yield* Effect.log("Result:", result)
yield* Effect.log("Multiple", "values", "can", "be", "logged")
return result
})
Effect.runPromise(program).then(console.log)
// Output:
// timestamp=2023-... level=INFO message="Starting computation"
// timestamp=2023-... level=INFO message="Result: 4"
// timestamp=2023-... level=INFO message="Multiple values can be logged"
// 4
log(`send ${job: Schema.Struct.ReadonlySide<
{
readonly to: Schema.String
readonly subject: Schema.String
},
"Type"
>
(parameter) job: {
to: string;
subject: string;
}
job.to: stringto}`),
concurrency?: number | undefinedMax items processing concurrently (worker count).
concurrency: 8,
rateLimit?: | WorkPool.WorkPoolRateLimitOptions
| undefined
(property) rateLimit?: {
limit: number;
window: Duration.Duration;
}
Optional Effect RateLimiter on item workers (before concurrency semaphore).
Omitted = no rate limit (only concurrency).
rateLimit: { limit: numberlimit: 100, window: Duration.Duration(property) window: {
value: DurationValue;
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;
}
window: import DurationDuration.const seconds: (
seconds: number
) => Duration
Creates a Duration from seconds.
Example (Creating durations from seconds)
import { Duration } from "effect"
const duration = Duration.seconds(30)
console.log(Duration.toMillis(duration)) // 30000
seconds(1) }, // ≤ 100 starts/sec
})concurrency and rateLimit are orthogonal: concurrency bounds in-flight work, rate limit bounds start rate. Use both — a pool of 8 workers that collectively start no faster than 100/sec.
rateLimit is Effect’s RateLimiter.consume options (same shape as Gate). The backing store is presence-driven: provide RateLimiter.layerStoreRedis + NodeRedis.layer at the app root for a fleet-wide budget; omit it and the queue Soft-builds in-memory. Do not rely on Soft memory across multiple Nodes — that yields N× the limit. Live Redis proof: test/rate-limit-redis.test.ts (shared store across two queues + Gate child-process peer). Local Redis: docker compose -f docker-compose.redis.yml up -d.
Bootstrapping: start paused
Sometimes you want to load a queue before it drains — seed a backlog, wire up a subscriber, then let it rip. Start it paused and resume when ready:
yield* const emails: WorkPool.WorkPool<
{ to: string; subject: string },
void,
never,
never
>
const emails: {
status: Hyperlink.Subscribable<QueueStatus>;
size: Hyperlink.Subscribable<number>;
isEmpty: Hyperlink.Subscribable<boolean>;
start: Effect.Effect<void, never, Requirements>;
pause: Effect.Effect<void>;
resume: Effect.Effect<void>;
shutdown: Effect.Effect<void>;
clear: Effect.Effect<number, never, Requirements>;
metrics: { readonly stream: Stream.Stream<QueueMetrics>; readonly query: (input: { readonly limit?: number; readonly since?: DateTime.Utc; readonly until?: DateTime.Utc }) => Effect.Effect<ReadonlyArray<QueueMetrics>, never, Requirements> };
add: QueueEnqueue<Payload, never, Requirements>;
prioritize: QueueEnqueue<Payload, never, Requirements>;
defer: QueueEnqueue<Payload, never, Requirements>;
enqueue: (entries: ReadonlyArray<QueueEntry<Payload>>) => Effect.Effect<void, never, Requirements>;
release: (input: { readonly options?: QueueReleaseOptions }) => Effect.Effect<ReadonlyArray<QueueEntry<Payload>>, never, Requirements>;
releaseEncoded: (input: { readonly options?: QueueReleaseOptions }) => Effect.Effect<ReadonlyArray<Hyperlink.Decoded<typeof queueEncodedEntry>>, QueueReleaseEncodingError, Requirements>;
deadLetter: (input: { readonly selector: QueueEntrySelector<Payload> | QueueEntry<Payload>; readonly options: QueueRouteOptions }) => Effect.Effect<ReadonlyArray<QueueEntry<Payload>>, never, Requirements>;
drop: (input: { readonly selector: QueueEntrySelector<Payload> | QueueEntry<Payload>; readonly options: QueueRouteOptions }) => Effect.Effect<ReadonlyArray<QueueEntry<Payload>>, never, Requirements>;
events: Stream.Stream<QueueEvent<Payload, Error, Success>>;
}
emails.WorkPool<{ to: string; subject: string; }, void, never, never>.add: QueueEnqueue<Payload, never, Requirements>add({ to: stringto: "[email protected]", subject: stringsubject: "queued while paused" })
yield* const emails: WorkPool.WorkPool<
{ to: string; subject: string },
void,
never,
never
>
const emails: {
status: Hyperlink.Subscribable<QueueStatus>;
size: Hyperlink.Subscribable<number>;
isEmpty: Hyperlink.Subscribable<boolean>;
start: Effect.Effect<void, never, Requirements>;
pause: Effect.Effect<void>;
resume: Effect.Effect<void>;
shutdown: Effect.Effect<void>;
clear: Effect.Effect<number, never, Requirements>;
metrics: { readonly stream: Stream.Stream<QueueMetrics>; readonly query: (input: { readonly limit?: number; readonly since?: DateTime.Utc; readonly until?: DateTime.Utc }) => Effect.Effect<ReadonlyArray<QueueMetrics>, never, Requirements> };
add: QueueEnqueue<Payload, never, Requirements>;
prioritize: QueueEnqueue<Payload, never, Requirements>;
defer: QueueEnqueue<Payload, never, Requirements>;
enqueue: (entries: ReadonlyArray<QueueEntry<Payload>>) => Effect.Effect<void, never, Requirements>;
release: (input: { readonly options?: QueueReleaseOptions }) => Effect.Effect<ReadonlyArray<QueueEntry<Payload>>, never, Requirements>;
releaseEncoded: (input: { readonly options?: QueueReleaseOptions }) => Effect.Effect<ReadonlyArray<Hyperlink.Decoded<typeof queueEncodedEntry>>, QueueReleaseEncodingError, Requirements>;
deadLetter: (input: { readonly selector: QueueEntrySelector<Payload> | QueueEntry<Payload>; readonly options: QueueRouteOptions }) => Effect.Effect<ReadonlyArray<QueueEntry<Payload>>, never, Requirements>;
drop: (input: { readonly selector: QueueEntrySelector<Payload> | QueueEntry<Payload>; readonly options: QueueRouteOptions }) => Effect.Effect<ReadonlyArray<QueueEntry<Payload>>, never, Requirements>;
events: Stream.Stream<QueueEvent<Payload, Error, Success>>;
}
emails.WorkPool<{ to: string; subject: string; }, void, never, never>.resume: Effect.Effect<void>(property) WorkPool<{ to: string; subject: string; }, void, never, never>.resume: {
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;
}
Resume processing after a pause.
resume // now it drains
})Pulling work in
The queues so far are push — something calls add. A queue can also pull, with refill: a loader that the engine calls to fetch work. onStart seeds it once on boot; onDrained re-polls the source every time the queue empties — turning a queue into a durable poller over an external source (a table, a topic, an inbox):
const const EmailsLive: Layer<
Emails | Local<Emails> | Storage,
never,
never
>
const EmailsLive: {
build: (memoMap: MemoMap, scope: Scope.Scope) => Effect<Context.Context<Emails | Local<Emails> | 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; <…;
}
EmailsLive = import WorkPoolWorkPool.function layer<Emails, {
readonly to: Schema.String;
readonly subject: Schema.String;
}, never, never, Schema.Void, Schema.Never>(tag: QueueTagFor<Emails, {
readonly to: Schema.String;
readonly subject: Schema.String;
}, Schema.Void, Schema.Never>, config: QueueVerbConfig<{
readonly to: Schema.String;
readonly subject: Schema.String;
}, never, never, never, Schema.Void>): Layer<Emails | Local<Emails> | Storage, never, never> (+1 overload)
Run this WorkPool locally — soft-defaults
Store.Storage
(R fulfilled; override by
providing an app store into this layer). Accepts a plain
Tag
or a
priority
tag and
dispatches to the matching engine.
layerMemory
is an alias for the same soft-default.
layer(class Emailsclass Emails {
key: Identifier;
Service: {
status: Hyperlink.Subscribable<QueueStatus>;
size: Hyperlink.Subscribable<number>;
isEmpty: Hyperlink.Subscribable<boolean>;
start: Effect.Effect<void, never, Requirements>;
pause: Effect.Effect<void>;
resume: Effect.Effect<void>;
shutdown: Effect.Effect<void>;
clear: Effect.Effect<number, never, Requirements>;
metrics: { readonly stream: Stream.Stream<QueueMetrics>; readonly query: (input: { readonly limit?: number; readonly since?: DateTime.Utc; readonly until?: DateTime.Utc }) => Effect.Effect<ReadonlyArray<QueueMetrics>, never, Requirements> };
add: QueueEnqueue<Payload, never, Requirements>;
prioritize: QueueEnqueue<Payload, never, Requirements>;
defer: QueueEnqueue<Payload, never, Requirements>;
enqueue: (entries: ReadonlyArray<QueueEntry<Payload>>) => Effect.Effect<void, never, Requirements>;
release: (input: { readonly options?: QueueReleaseOptions }) => Effect.Effect<ReadonlyArray<QueueEntry<Payload>>, never, Requirements>;
releaseEncoded: (input: { readonly options?: QueueReleaseOptions }) => Effect.Effect<ReadonlyArray<Hyperlink.Decoded<typeof queueEncodedEntry>>, QueueReleaseEncodingError, Requirements>;
deadLetter: (input: { readonly selector: QueueEntrySelector<Payload> | QueueEntry<Payload>; readonly options: QueueRouteOptions }) => Effect.Effect<ReadonlyArray<QueueEntry<Payload>>, never, Requirements>;
drop: (input: { readonly selector: QueueEntrySelector<Payload> | QueueEntry<Payload>; readonly options: QueueRouteOptions }) => Effect.Effect<ReadonlyArray<QueueEntry<Payload>>, never, Requirements>;
events: Stream.Stream<QueueEvent<Payload, Error, Success>>;
};
description: string | undefined;
of: (this: void, self: WorkPool.WorkPool<{ to: string; subject: string }, void, never, never>) => WorkPool.WorkPool<{ to: string; subject: string }, void, never, never>;
context: (self: WorkPool.WorkPool<{ to: string; subject: string }, void, never, never>) => Context<Emails>;
use: (f: (service: WorkPool.WorkPool<{ to: string; subject: string }, void, never, never>) => Effect.Effect<A, E, R>) => Effect.Effect<A, E, Emails | R>;
useSync: (f: (service: WorkPool.WorkPool<{ to: string; subject: string }, void, never, never>) => A) => Effect.Effect<A, never, Emails>;
Identifier: Identifier;
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;
}
Emails, {
effect: (
item: Schema.Struct.ReadonlySide<
{
readonly to: Schema.String
readonly subject: Schema.String
},
"Type"
>,
ctx: WorkPool.EffectContext<
Schema.Struct.ReadonlySide<
{
readonly to: Schema.String
readonly subject: Schema.String
},
"Type"
>,
QueueEnqueueErrors,
never
>
) => Effect.Effect<void, never, never>
Daemon each item. The success channel A is driven by the tag's success wire schema
(default void): with a success schema the worker must return Effect<A, E, R> and that
value rides Completed.success / store.completed; without one it stays Effect<void, E, R>.
effect: (job: Schema.Struct.ReadonlySide<
{
readonly to: Schema.String
readonly subject: Schema.String
},
"Type"
>
(parameter) job: {
to: string;
subject: string;
}
job) => import EffectEffect.const log: (
...message: ReadonlyArray<any>
) => Effect<void>
Logs one or more messages using the default log level.
Example (Logging at the default level)
import { Effect } from "effect"
const program = Effect.gen(function*() {
yield* Effect.log("Starting computation")
const result = 2 + 2
yield* Effect.log("Result:", result)
yield* Effect.log("Multiple", "values", "can", "be", "logged")
return result
})
Effect.runPromise(program).then(console.log)
// Output:
// timestamp=2023-... level=INFO message="Starting computation"
// timestamp=2023-... level=INFO message="Result: 4"
// timestamp=2023-... level=INFO message="Multiple values can be logged"
// 4
log(job: Schema.Struct.ReadonlySide<
{
readonly to: Schema.String
readonly subject: Schema.String
},
"Type"
>
(parameter) job: {
to: string;
subject: string;
}
job.to: stringto),
refill?: | {
readonly onStart?: boolean
readonly onDrained?: boolean
readonly load: (
queue: EngineQueueHandle<
Schema.Struct.ReadonlySide<
{
readonly to: Schema.String
readonly subject: Schema.String
},
"Type"
>,
never,
QueueEnqueueErrors,
never,
void
>
) => Effect.Effect<void, never, never>
}
| undefined
Optional self-refill. Its loader carries its own requirement RR — independent of the
worker R — so a refill that pulls from a source (a repository/DB service) the worker doesn't
use is expressible; the layer's requirement is the union R | RR. (Sharing one R would
intersect to never, since the requirement channel is contravariant.)
RR is kept to load's return only (the handle is requirement-free here) so TS infers it
cleanly — if RR also appeared on the handle parameter its variance would conflict and default
to never.
refill: {
onStart?: boolean | undefinedRun load once when the worker pool starts (bootstrap).
onStart: true, // seed on boot
onDrained?: boolean | undefinedRun load each time the queue drains to empty (re-poll the source).
onDrained: true, // re-poll when empty
load: (
queue: EngineQueueHandle<
Schema.Struct.ReadonlySide<
{
readonly to: Schema.String
readonly subject: Schema.String
},
"Type"
>,
never,
QueueEnqueueErrors,
never,
void
>
) => Effect.Effect<void, never, never>
Load + enqueue work from a source. Handle its own errors (best-effort).
load: (queue: EngineQueueHandle<
Schema.Struct.ReadonlySide<
{
readonly to: Schema.String
readonly subject: Schema.String
},
"Type"
>,
never,
QueueEnqueueErrors,
never,
void
>
queue) => import EffectEffect.const flatMap: {
<A, B, E1, R1>(
f: (a: A) => Effect<B, E1, R1>
): <E, R>(
self: Effect<A, E, R>
) => Effect<B, E1 | E, R1 | R>
<A, E, R, B, E1, R1>(
self: Effect<A, E, R>,
f: (a: A) => Effect<B, E1, R1>
): Effect<B, E | E1, R | R1>
}
Chains effects to produce new Effect instances, useful for combining
operations that depend on previous results.
When to use
Use when you need to chain multiple effects, ensuring that each
step produces a new Effect while flattening any nested effects that may
occur.
Details
flatMap lets you sequence effects so that the result of one effect can be
used in the next step. It is similar to flatMap used with arrays but works
specifically with Effect instances, allowing you to avoid deeply nested
effect structures.
Since effects are immutable, flatMap always returns a new effect instead of
changing the original one.
Example (Choosing flatMap syntax variants)
import { Effect, pipe } from "effect"
const myEffect = Effect.succeed(1)
const transformation = (n: number) => Effect.succeed(n + 1)
const flatMappedWithPipe = pipe(myEffect, Effect.flatMap(transformation))
const flatMappedWithDataFirst = Effect.flatMap(myEffect, transformation)
const flatMappedWithMethod = myEffect.pipe(Effect.flatMap(transformation))
Example (Sequencing dependent effects)
import { Data, Effect, pipe } from "effect"
class DiscountRateError extends Data.TaggedError("DiscountRateError")<{}> {}
// Function to apply a discount safely to a transaction amount
const applyDiscount = (
total: number,
discountRate: number
): Effect.Effect<number, DiscountRateError> =>
discountRate === 0
? Effect.fail(new DiscountRateError())
: Effect.succeed(total - (total * discountRate) / 100)
// Simulated asynchronous task to fetch a transaction amount from database
const fetchTransactionAmount = Effect.promise(() => Promise.resolve(100))
// Chaining the fetch and discount application using `flatMap`
const finalAmount = pipe(
fetchTransactionAmount,
Effect.flatMap((amount) => applyDiscount(amount, 5))
)
Effect.runPromise(finalAmount).then(console.log)
// Output: 95
flatMap(const nextBatch: Effect.Effect<
ReadonlyArray<{ to: string; subject: string }>
>
const nextBatch: {
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;
}
nextBatch, queue: EngineQueueHandle<
Schema.Struct.ReadonlySide<
{
readonly to: Schema.String
readonly subject: Schema.String
},
"Type"
>,
never,
QueueEnqueueErrors,
never,
void
>
queue.QueueHandleApi<Struct<Fields extends Struct.Fields>.ReadonlySide<{ readonly to: String; readonly subject: String; }, "Type">, never, QueueEnqueueErrors, never, void>.add: QueueEnqueue<T, EEnqueue, R>Enqueue items at normal priority.
add).Pipeable.pipe<Effect.Effect<void, QueueEnqueueErrors, never>, Effect.Effect<void, never, never>>(this: Effect.Effect<void, QueueEnqueueErrors, never>, ab: (_: Effect.Effect<void, QueueEnqueueErrors, never>) => Effect.Effect<void, never, never>): Effect.Effect<void, never, never> (+21 overloads)pipe(import EffectEffect.const orDie: <A, E, R>(
self: Effect<A, E, R>
) => Effect<A, never, R>
Converts typed failures from the error channel into defects, removing the
error type from the returned effect.
When to use
Use when you need to turn an Effect typed failure that represents an
unrecoverable bug or invalid state into a defect.
Example (Converting typed failures into defects)
import { Data, Effect } from "effect"
class DivideByZeroError extends Data.TaggedError("DivideByZeroError")<{}> {}
const divide = (a: number, b: number) =>
b === 0
? Effect.fail(new DivideByZeroError())
: Effect.succeed(a / b)
// ┌─── Effect<number, never, never>
// ▼
const program = Effect.orDie(divide(1, 0))
Effect.runPromise(program).catch(console.error)
// Output:
// (FiberFailure) DivideByZeroError
// ...stack trace...
orDie),
},
})The loader gets the queue handle, so it enqueues with the same verbs you do.
Operating a live queue
Three streams tell you what a running queue is doing, from three angles.
events is the fact log. Every discrete thing the queue does is a tagged event, and they fall into four families:
lifecycle —
Enqueued,Started,Completedfailure —
Failed,RetryScheduled,RetryExhaustedrouting —
Released,DeadLettered,Dropped,Clearedqueue-level —
Start,RateLimitExceeded,ShutdownRequested,ShutdownComplete,Drained
You never handle all of them — pick the tags you care about with Hyperlink.runForEachTag and ignore the rest.
status is the current-state snapshot (a Subscribable): per-priority pending sizes, how many are inFlight, the running completed count, whether its paused, and its phase — running, then draining after a shutdown request, then off. Its the one value a dashboard renders.
metrics is the aggregate view: metrics.stream emits one windowed summary per window (throughput, average wait and execution time, per-window counts); metrics.query reads past windows back from the store for charts and trends.
status answers what is true now, events answers what happened, metrics answers how is it trending. Reach for the one that matches the question — they dont overlap.
Persistence and analytics
Three separate planes — do not collapse them into one “SQLite store”:
Soft observability (
Store.Service) — every WorkPool soft-defaults an in-memory journal. Lifecycle events and analytics live there for the process lifetime. Override with an appStore.Servicethat registersWorkPool.store(tag)(SQLite or memory + Logs). Reach analytics withWorkPool.store(tag)/yield* AppStore.at(tag).Durability (
DurableWorkPoolStore) — pending + in-flight work that must survive a restart. Presence-driven: provideSQLiteDurableWorkPoolStore.layer({ filename })fromhyperlink-ts/storage/sqlite(needs apayload/itemSchemaon the tag). No Soft default — omit the layer and the queue is not durable.History backfill (
HistoryStore) — optional keyed append-log for windowedmetrics.query/*History(memory orSQLiteHistoryStore).
Fleet rate limits use Effect RateLimiterStore (Soft memory, or Redis — see above). Full wiring SSOT: Stores.
Running it across the network
This is the payoff of the tag/layer split. The tag is the contract; the layer decides where the work runs — and nothing else in your code changes.
Provide WorkPool.layer and the queue is local. Provide WorkPool.serve instead and the worker runs behind an RPC server, its handlers mounted for callers. A different process then provides Hyperlink.client(Tag) (or Hyperlink.connect(Tag, Hyperlink.protocolHttp(port)) over HTTP), and the same yield* Tag code drives the remote queue — add, size, events, pause, all of it — as if it were in-process. The handles Requirements param is the only tell: never locally, the transport for a client.
For moving pending work between runtimes, release exports entries decoded and releaseEncoded exports them in wire form (no item schema needed on the receiver); the other side enqueues them, attempt budgets intact.
Reconfiguring at runtime
A queues concurrency, rateLimit, or paused state isnt frozen at definition. WorkPool.configure(Tag, patch) is a layer that overlays a config patch on top of the base — Layer.provideMerge it, and the queue drains under the merged config:
const const Tuned: Layer.Layer<
Emails,
never,
never
>
const Tuned: {
build: (memoMap: MemoMap, scope: Scope.Scope) => Effect<Context.Context<Emails>, 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; <…;
}
Tuned = const EmailsLive: Layer.Layer<Emails>const EmailsLive: {
build: (memoMap: MemoMap, scope: Scope.Scope) => Effect<Context.Context<Emails>, 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; <…;
}
EmailsLive.Pipeable.pipe<Layer.Layer<Emails, never, never>, Layer.Layer<Emails, never, never>>(this: Layer.Layer<Emails, never, never>, ab: (_: Layer.Layer<Emails, never, never>) => Layer.Layer<Emails, never, never>): Layer.Layer<Emails, never, never> (+21 overloads)pipe(
import LayerLayer.const provideMerge: {
<RIn, E, ROut>(that: Layer<ROut, E, RIn>): <
RIn2,
E2,
ROut2
>(
self: Layer<ROut2, E2, RIn2>
) => Layer<
ROut | ROut2,
E | E2,
RIn | Exclude<RIn2, ROut>
>
<Layers extends [Any, ...Array<Any>]>(
that: Layers
): <A, E, R>(
self: Layer<A, E, R>
) => Layer<
A | Success<Layers[number]>,
E | Error<Layers[number]>,
| Services<Layers[number]>
| Exclude<R, Success<Layers[number]>>
>
<RIn2, E2, ROut2, RIn, E, ROut>(
self: Layer<ROut2, E2, RIn2>,
that: Layer<ROut, E, RIn>
): Layer<
ROut | ROut2,
E | E2,
RIn | Exclude<RIn2, ROut>
>
<A, E, R, Layers extends [Any, ...Array<Any>]>(
self: Layer<A, E, R>,
that: Layers
): Layer<
A | Success<Layers[number]>,
E | Error<Layers[number]>,
| Services<Layers[number]>
| Exclude<R, Success<Layers[number]>>
>
}
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(import WorkPoolWorkPool.function configure<Emails, {
readonly to: Schema.String;
readonly subject: Schema.String;
}, never, never, Schema.Void, Schema.Never>(tag: QueueTagFor<Emails, {
readonly to: Schema.String;
readonly subject: Schema.String;
}, Schema.Void, Schema.Never>, patch: ConfigPatch<QueueVerbConfig<{
readonly to: Schema.String;
readonly subject: Schema.String;
}, never, never, never, Schema.Void>>): Layer.Layer<never> (+1 overload)
A config-patch layer for the WorkPool tag — the toolkit successor to the old
WorkPool.Service(...).configure(...). Merge it with the queue's
layer
(e.g. per
environment) and its patch (concurrency / rateLimit / attempts / …) folds onto the layer's base
config at build. Keyed by tag.key; later patches win. Config lives in the layer, not the tag,
so configure takes the tag and returns a layer rather than being a tag method.
const Prod = Layer.mergeAll(
WorkPool.layer(MyQueue, { effect }),
WorkPool.configure(MyQueue, { concurrency: 3, rateLimit: { window: "1 second", limit: 5 } }),
);
configure(class Emailsclass Emails {
key: Identifier;
Service: {
status: Hyperlink.Subscribable<QueueStatus>;
size: Hyperlink.Subscribable<number>;
isEmpty: Hyperlink.Subscribable<boolean>;
start: Effect.Effect<void, never, Requirements>;
pause: Effect.Effect<void>;
resume: Effect.Effect<void>;
shutdown: Effect.Effect<void>;
clear: Effect.Effect<number, never, Requirements>;
metrics: { readonly stream: Stream.Stream<QueueMetrics>; readonly query: (input: { readonly limit?: number; readonly since?: DateTime.Utc; readonly until?: DateTime.Utc }) => Effect.Effect<ReadonlyArray<QueueMetrics>, never, Requirements> };
add: QueueEnqueue<Payload, never, Requirements>;
prioritize: QueueEnqueue<Payload, never, Requirements>;
defer: QueueEnqueue<Payload, never, Requirements>;
enqueue: (entries: ReadonlyArray<QueueEntry<Payload>>) => Effect.Effect<void, never, Requirements>;
release: (input: { readonly options?: QueueReleaseOptions }) => Effect.Effect<ReadonlyArray<QueueEntry<Payload>>, never, Requirements>;
releaseEncoded: (input: { readonly options?: QueueReleaseOptions }) => Effect.Effect<ReadonlyArray<Hyperlink.Decoded<typeof queueEncodedEntry>>, QueueReleaseEncodingError, Requirements>;
deadLetter: (input: { readonly selector: QueueEntrySelector<Payload> | QueueEntry<Payload>; readonly options: QueueRouteOptions }) => Effect.Effect<ReadonlyArray<QueueEntry<Payload>>, never, Requirements>;
drop: (input: { readonly selector: QueueEntrySelector<Payload> | QueueEntry<Payload>; readonly options: QueueRouteOptions }) => Effect.Effect<ReadonlyArray<QueueEntry<Payload>>, never, Requirements>;
events: Stream.Stream<QueueEvent<Payload, Error, Success>>;
};
description: string | undefined;
of: (this: void, self: WorkPool.WorkPool<{ to: string; subject: string }, void, never, never>) => WorkPool.WorkPool<{ to: string; subject: string }, void, never, never>;
context: (self: WorkPool.WorkPool<{ to: string; subject: string }, void, never, never>) => Context<Emails>;
use: (f: (service: WorkPool.WorkPool<{ to: string; subject: string }, void, never, never>) => Effect<A, E, R>) => Effect<A, E, Emails | R>;
useSync: (f: (service: WorkPool.WorkPool<{ to: string; subject: string }, void, never, never>) => A) => Effect<A, never, Emails>;
Identifier: Identifier;
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;
}
Emails, { concurrency?: number | undefinedMax items processing concurrently (worker count).
concurrency: 16 })),
)Because its just a layer, the patch can come from anywhere a layer can — an env flag, or a live DynamicConfig swap that re-tunes the queue while it runs.
Custom priority lanes
high / normal / defer covers most needs, but some domains have their own ordering — tiers, SLAs, numbered levels. Reach for WorkPool.priority — the same HyperService with arbitrary lanes: you define the levels, and add targets one by name. The handle reads the same; only the priority axis is yours to shape.
A live control panel
Because the handle is the whole surface, a UI is just another consumer of it. These docs render one inline: a ``` queue block mounts a live control panel for a declared queue — buttons that call add / prioritize / pause / resume / clear, and stats read straight off the status stream. Enqueue an item and watch pending climb, then drain to completed:
The same handle that runs the queue drives the panel — no separate admin API, no extra wiring.
The raw engine
Under the HyperService wrapper is a plain queue engine. WorkPool.make(config) returns a handle directly — the workers, retries, and events, without the Tag, Layer, or RPC machinery. layer and Service are built on it; reach for make only when you want to embed a queue inside something else and manage its scope yourself. For everything else, the Tag is the WorkPool.