Hyperlink for Effect
Define once. Run anywhere. yield* everywhere.
An Effect Service lives in one runtime. A Hyperlink Service is still a Service — same Tag, same yield* — but its Contract is schema-typed, so the seam can sit between processes, not just modules. You define it once; you decide later whether it runs in-process, on another core, or across the network. The call site does not change.
What you yield* is a typed Handle: call methods, observe live state, steer the service at runtime. Local and remote are the same type. Change the Contract and TypeScript flags every caller — in every process that imports the Tag. One surface.
The rest of this page is that idea under load: two runtimes sharing a queue, the same Handle operating it live, building your own HyperService, then peers across a fleet.
Two runtimes, one program
A worker drains a queue; a scheduler fills it. Two runtimes, one Tag — no hand-rolled client on the scheduler side.
Define two HyperServices once — a priority queue and a scheduled daemon (included tools, used here as the demo):
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; }, Void, Never>.payload: Schema.Struct<{ readonly to: Schema.String }>(property) QueueTagConfig<{ readonly to: 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 }) => 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; }, 'Type'>, readonly []>) => Schema.Struct<{ readonly to: Schema.String; }>;
annotateKey: (annotations: Schema.Annotations.Key<Schema.Struct.ReadonlySide<{ readonly to: Schema.String }, 'Type'>>) => Schema.Struct<{ readonly to: Schema.String }>;
check: (checks_0: Check<Schema.Struct.ReadonlySide<{ readonly to: Schema.String }, 'Type'>>, ...checks: Array<Check<Schema.Struct.ReadonlySide<{ readonly to: Schema.String }, 'Type'>>>) => Schema.Struct<{ readonly to: Schema.String }>;
rebuild: (ast: Objects) => Schema.Struct<{ readonly to: Schema.String }>;
make: (input: Struct.ReadonlyMakeIn<{ readonly to: String }>, options?: MakeOptions) => Struct.ReadonlySide<{ readonly to: String }, 'Type'>;
makeOption: (input: Struct.ReadonlyMakeIn<{ readonly to: String }>, options?: MakeOptions) => Option_.Option<Struct.ReadonlySide<{ readonly to: String }, 'Type'>>;
makeEffect: (input: Struct.ReadonlyMakeIn<{ readonly to: String }>, options?: MakeOptions) => Effect.Effect<Struct.ReadonlySide<{ readonly to: 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
}>
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 }) => 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; }, 'Type'>, readonly []>) => Schema.Struct<{ readonly to: Schema.String; }>;
annotateKey: (annotations: Schema.Annotations.Key<Schema.Struct.ReadonlySide<{ readonly to: Schema.String }, 'Type'>>) => Schema.Struct<{ readonly to: Schema.String }>;
check: (checks_0: Check<Schema.Struct.ReadonlySide<{ readonly to: Schema.String }, 'Type'>>, ...checks: Array<Check<Schema.Struct.ReadonlySide<{ readonly to: Schema.String }, 'Type'>>>) => Schema.Struct<{ readonly to: Schema.String }>;
rebuild: (ast: Objects) => Schema.Struct<{ readonly to: Schema.String }>;
make: (input: Struct.ReadonlyMakeIn<{ readonly to: String }>, options?: MakeOptions) => Struct.ReadonlySide<{ readonly to: String }, 'Type'>;
makeOption: (input: Struct.ReadonlyMakeIn<{ readonly to: String }>, options?: MakeOptions) => Option_.Option<Struct.ReadonlySide<{ readonly to: String }, 'Type'>>;
makeEffect: (input: Struct.ReadonlyMakeIn<{ readonly to: String }>, options?: MakeOptions) => Effect.Effect<Struct.ReadonlySide<{ readonly to: 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 }) {}
class class Digestclass Digest {
key: Identifier;
Service: {
status: Subscribable<{ readonly supervising: boolean; readonly armed: boolean; readonly activeInstances: number; readonly runsStarted: number; readonly runsSucceeded: number; readonly runsFailed: number; readonly nextTriggerRun?: Utc | undefined; …;
start: Effect<void, never, never>;
stop: Effect<void, never, never>;
wake: Effect<void, never, never>;
resetCadence: Effect<void, never, never>;
events: Stream<Schema.Struct.ReadonlySide<{ readonly _tag: Schema.tag<'Started'>; readonly key: Schema.String; readonly scheduleKey: Schema.NullOr<Schema.String>; readonly startedAt: Schema.Number; readonly isStartupRun: Schema.Boolean }, 'Type'> …;
run: Effect<void, never, never>;
};
}
Digest extends import DaemonDaemon.const Tag: <
Self
>() => DaemonTagBuild<Self>
Define a managed daemon as a toolkit HyperService. Self is given explicitly (Effect's ()
two-stage form). The base tag carries observation + lifecycle; add a schedule with
.pipe(
schedule
(…)). Declare value/error wire schemas on the tag:
class Health extends Daemon.Tag<Health>()("app/Health") {}
class Prices extends Daemon.Tag<Prices>()("app/Prices", PriceSchema) {}
class PricesE extends Daemon.Tag<PricesE>()("app/Prices", PriceSchema, FetchErr) {}
class PricesCfg extends Daemon.Tag<PricesCfg>()("app/Prices", {
success: PriceSchema,
error: FetchErr,
}) {}
Pass options.node to bind the daemon to a
Node.Tag
.
Tag<class Digestclass Digest {
key: Identifier;
Service: {
status: Subscribable<{ readonly supervising: boolean; readonly armed: boolean; readonly activeInstances: number; readonly runsStarted: number; readonly runsSucceeded: number; readonly runsFailed: number; readonly nextTriggerRun?: Utc | undefined; …;
start: Effect<void, never, never>;
stop: Effect<void, never, never>;
wake: Effect<void, never, never>;
resetCadence: Effect<void, never, never>;
events: Stream<Schema.Struct.ReadonlySide<{ readonly _tag: Schema.tag<'Started'>; readonly key: Schema.String; readonly scheduleKey: Schema.NullOr<Schema.String>; readonly startedAt: Schema.Number; readonly isStartupRun: Schema.Boolean }, 'Type'> …;
run: Effect<void, never, never>;
};
}
Digest>()("app/Digest") {}Same-machine, nameless: Node.unix mints a Node when you dont pass one — no Node.Tag, no path, no port. The engine is mounted; discovery is built in:
const const worker: Layer<HandlerContextOf<{
add: Method<Schema.Union<readonly [Schema.Struct<F>, Schema.$Array<Schema.Struct<F>>]>, Schema.Void, Schema.Never, false, MethodAnnotations & {
description: string;
}, {
(item: PrettifyPayload<Schema.Struct.View<F, "Type", Schema.Struct.TypeOptionalKeys<F>, Schema.Struct.TypeMutableKeys<F>>>): Effect.Effect<void>;
(items: readonly PrettifyPayload<Schema.Struct.View<F, "Type", Schema.Struct.TypeOptionalKeys<F>, Schema.Struct.TypeMutableKeys<F>>>[]): Effect.Effect<void>;
(itemOrItems: PrettifyPayload<...> | readonly PrettifyPayload<...>[]): Effect.Effect<void>;
}>;
... 16 more ...;
metrics: {
...;
};
}> | Storage | Emails | Local<...> | Node.ListenNode, never, never>
const worker: {
build: (memoMap: MemoMap, scope: Scope) => Effect.Effect<Context<HandlerContextOf<{ add: Method<Schema.Union<readonly [Schema.Struct<F>, Schema.$Array<Schema.Struct<F>>]>, Schema.Void, Schema.Never, false, MethodAnnotations & { description: strin…;
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; <…;
}
worker = import NodeNode.unix<readonly [Layer<HandlerContextOf<{
add: Method<Schema.Union<readonly [Schema.Struct<F>, Schema.$Array<Schema.Struct<F>>]>, Schema.Void, Schema.Never, false, MethodAnnotations & {
description: string;
}, {
(item: PrettifyPayload<Schema.Struct.View<F, "Type", Schema.Struct<Fields extends Schema.Struct.Fields>.TypeOptionalKeys<F>, Schema.Struct.TypeMutableKeys<F>>>): Effect.Effect<void>;
(items: readonly PrettifyPayload<Schema.Struct.View<F, "Type", Schema.Struct<Fields extends Schema.Struct.Fields>.TypeOptionalKeys<F>, Schema.Struct.TypeMutableKeys<...>>>[]): Effect.Effect<void>;
(itemOrItems: PrettifyPayload<...> | readonly PrettifyPayload<...>[]): Effect.Effect<void>;
}>;
... 16 more ...;
metrics: {
...;
};
}> | Storage | Emails | Local<...>, never, never>]>(serves: readonly [...], options?: Node.IpcListenArg): Layer<...> (+6 overloads)
export unix
Unix-domain IPC listen — all ipc mint/bind. Nameless forms Soft-bake
Lookup.layer
(claim + advertise) — override with Layer.provide(Lookup.layerOptions({ path })).
Overload family (keep aligned with
http
/
ws
/
nPipe
):
unix(tag, impl) / unix(tag, impl, path) — unbound Tag → nameless; bound Tag → that Node
unix(tag, impl, node) — named listen without andNode
unix(serve, path?) / unix([serve…], path?) — nameless (brackets optional for one)
unix(node, serve | [serve…], options?) — named node + serves
Path: "/tmp/x.sock" or { unlink, … }; omit for ephemeral. Prefer this for same-machine.
unix([
import WorkPoolWorkPool.function serve<Emails, {
readonly to: Schema.String;
}, never, never, Schema.Void, Schema.Never>(tag: QueueTagFor<Emails, {
readonly to: Schema.String;
}, Schema.Void, Schema.Never>, config: QueueVerbConfig<{
readonly to: Schema.String;
}, never, never, never, Schema.Void>): Layer<HandlerContextOf<{
add: Method<Schema.Union<readonly [Schema.Struct<F>, Schema.$Array<Schema.Struct<F>>]>, Schema.Void, Schema.Never, false, MethodAnnotations & {
...;
}, {
...;
}>;
... 16 more ...;
metrics: {
...;
};
}> | Storage | Emails | Local<...>, never, never> (+1 overload)
Serve this queue and grant its local instance from one materialization — run the worker /
refill / persist engine behind the tag, mount its RPC handlers, register into
Hyperlink.servedHyperServicesLayer
, and grant Self | Local<Self> so co-located code
can yield* Tag. The served cells are the in-process instance (one engine, one peersLayer); the
worker requirement R is preserved for per-HyperService Layer.provide. This is the queue's counterpart
to
Hyperlink.serve
; a served-only gateway uses
serveRemote
.
Node.httpServer([
WorkPool.serve(RosterQueue, { effect, itemSchema }),
Daemon.serve(SeasonMatches, { effect }),
]).pipe(Layer.provide(NodeHttpServer.layer({ port: 3001 })));
serve(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 }, void, never, never>) => WorkPool.WorkPool<{ to: string }, void, never, never>;
context: (self: WorkPool.WorkPool<{ to: string }, void, never, never>) => Context<Emails>;
use: (f: (service: WorkPool.WorkPool<{ to: string }, void, never, never>) => Effect.Effect<A, E, R>) => Effect.Effect<A, E, Emails | R>;
useSync: (f: (service: WorkPool.WorkPool<{ to: 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: (
job: typeof EmailJob.Type
) => Effect.Effect<void>
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: const sendEmail: (
job: typeof EmailJob.Type
) => Effect.Effect<void>
sendEmail }),
])The scheduler dials the same Tag — still yield* Emails:
const const scheduler: Layer.Layer<
Digest | Hyperlink.Local<Digest> | Storage,
Hyperlink.LookupClientError,
never
>
const scheduler: {
build: (memoMap: MemoMap, scope: Scope.Scope) => Effect<Context.Context<Digest | Local<Digest> | Storage>, LookupClientError, 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; <…;
}
scheduler = import DaemonDaemon.function layer<
Self,
S extends Spec,
A = void,
E = never,
R = never
>(
tag: HyperlinkTag<Self, S>,
config: DaemonLayerConfig<A, E, R>
): Layer.Layer<
Self | Local<Self> | Store.Storage,
never,
R
>
The local layer for a daemon: build its driver (auto-started) and provide its service.
Soft-defaults an in-memory
Store.Storage
(R fulfilled). Override with your app store:
Daemon.layer(Tag, config).pipe(Layer.provideMerge(AppStore.layer({ filename })))
layerMemory
is an alias for the same soft-default.
layer(class Digestclass Digest {
key: Identifier;
Service: {
status: Hyperlink.Subscribable<{ readonly supervising: boolean; readonly armed: boolean; readonly activeInstances: number; readonly runsStarted: number; readonly runsSucceeded: number; readonly runsFailed: number; readonly nextTriggerRun?: Utc | u…;
start: Effect.Effect<void, never, never>;
stop: Effect.Effect<void, never, never>;
wake: Effect.Effect<void, never, never>;
resetCadence: Effect.Effect<void, never, never>;
events: Stream<Schema.Struct.ReadonlySide<{ readonly _tag: Schema.tag<'Started'>; readonly key: Schema.String; readonly scheduleKey: Schema.NullOr<Schema.String>; readonly startedAt: Schema.Number; readonly isStartupRun: Schema.Boolean }, 'Type'> …;
run: Effect.Effect<void, never, never>;
};
description: string | undefined;
of: (this: void, self: { readonly [x: string]: Effect.Effect<unknown, never, Hyperlink.Local<Digest>>; readonly status: Hyperlink.Subscribable<{ readonly supervising: boolean; readonly armed: boolean; readonly activeInstances: number; readonly…;
context: (self: { readonly [x: string]: Effect.Effect<unknown, never, Hyperlink.Local<Digest>>; readonly status: Hyperlink.Subscribable<{ readonly supervising: boolean; readonly armed: boolean; readonly activeInstances: number; readonly runsStarted…;
use: (f: (service: { readonly [x: string]: Effect.Effect<unknown, never, Hyperlink.Local<Digest>>; readonly status: Hyperlink.Subscribable<{ readonly supervising: boolean; readonly armed: boolean; readonly activeInstances: number; readonly runs…;
useSync: (f: (service: { readonly [x: string]: Effect.Effect<unknown, never, Hyperlink.Local<Digest>>; readonly status: Hyperlink.Subscribable<{ readonly supervising: boolean; readonly armed: boolean; readonly activeInstances: number; readonly runs…;
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;
}
Digest, {
DaemonLayerConfig<void, never, Emails>.effect: Effect.Effect<void, never, Emails>(property) DaemonLayerConfig<void, never, Emails>.effect: {
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;
}
effect: 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 },
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 }, void, never, never>) => WorkPool.WorkPool<{ to: string }, void, never, never>;
context: (self: WorkPool.WorkPool<{ to: string }, void, never, never>) => Context<Emails>;
use: (f: (service: WorkPool.WorkPool<{ to: string }, void, never, never>) => Effect.Effect<A, E, R>) => Effect.Effect<A, E, Emails | R>;
useSync: (f: (service: WorkPool.WorkPool<{ to: 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 // same Handle type as local
const const email: Schema.Struct.ReadonlySide<
{
readonly to: Schema.String
},
"Type"
>
const email: {
to: string;
}
email = yield* const nextEmail: Effect.Effect<
typeof EmailJob.Type
>
const nextEmail: {
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;
}
nextEmail
yield* const emails: WorkPool.WorkPool<
{ to: 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; }, void, never, never>.add: QueueEnqueue<Payload, never, Requirements>add(const email: Schema.Struct.ReadonlySide<
{
readonly to: Schema.String
},
"Type"
>
const email: {
to: string;
}
email)
}),
DaemonLayerConfig<A, E, R>.polling?: Layer.Layer<PollingTag, never, never>(property) DaemonLayerConfig<A, E, R>.polling?: {
build: (memoMap: MemoMap, scope: Scope.Scope) => Effect<Context.Context<PollingTag>, 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; <…;
}
Optional polling layer for in-instance repeat cadence.
polling: import PollingPolling.const spaced: (
interval: Duration.Input
) => Layer.Layer<PollingTag>
Fixed interval between ticks. resetCadence wakes the current wait immediately.
spaced(import DurationDuration.const hours: (hours: number) => DurationCreates a Duration from hours.
Example (Creating durations from hours)
import { Duration } from "effect"
const duration = Duration.hours(2)
console.log(Duration.toMillis(duration)) // 7200000
hours(1)),
}).Pipeable.pipe<Layer.Layer<Digest | Hyperlink.Local<Digest> | Storage, never, Emails>, Layer.Layer<Digest | Hyperlink.Local<Digest> | Storage, Hyperlink.LookupClientError, never>>(this: Layer.Layer<...>, ab: (_: Layer.Layer<Digest | Hyperlink.Local<Digest> | Storage, never, Emails>) => Layer.Layer<Digest | Hyperlink.Local<Digest> | Storage, Hyperlink.LookupClientError, never>): Layer.Layer<...> (+21 overloads)pipe(import LayerLayer.const provide: {
<RIn, E, ROut>(that: Layer<ROut, E, RIn>): <
RIn2,
E2,
ROut2
>(
self: Layer<ROut2, E2, RIn2>
) => Layer<
ROut2,
E | E2,
RIn | Exclude<RIn2, ROut>
>
<Layers extends [Any, ...Array<Any>]>(
that: Layers
): <A, E, R>(
self: Layer<A, E, R>
) => Layer<
A,
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<
ROut2,
E | E2,
RIn | Exclude<RIn2, ROut>
>
<A, E, R, Layers extends [Any, ...Array<Any>]>(
self: Layer<A, E, R>,
that: Layers
): Layer<
A,
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 only provides the services from this layer.
When to use
Use when you need to hide an implementation dependency layer from callers.
Details
In serviceLayer.pipe(Layer.provide(dependencyLayer)), the dependency layer is
built first and is used to satisfy the requirements of serviceLayer.
Example (Providing layer dependencies)
import { Context, Effect, Layer } from "effect"
class Database extends Context.Service<Database, {
readonly query: (sql: string) => Effect.Effect<string>
}>()("Database") {}
class UserService extends Context.Service<UserService, {
readonly getUser: (id: string) => Effect.Effect<{
id: string
name: string
}>
}>()("UserService") {}
class Logger extends Context.Service<Logger, {
readonly log: (msg: string) => Effect.Effect<void>
}>()("Logger") {}
// Create dependency layers
const databaseLayer = Layer.succeed(Database, {
query: Effect.fn("Database.query")((sql: string) => Effect.succeed(`DB: ${sql}`))
})
const loggerLayer = Layer.succeed(Logger, {
log: Effect.fn("Logger.log")((msg: string) => Effect.sync(() => console.log(`[LOG] ${msg}`)))
})
// UserService depends on Database and Logger
const userServiceLayer = Layer.effect(UserService, Effect.gen(function*() {
const database = yield* Database
const logger = yield* Logger
return {
getUser: Effect.fn("UserService.getUser")(function*(id: string) {
yield* logger.log(`Looking up user ${id}`)
const result = yield* database.query(
`SELECT * FROM users WHERE id = ${id}`
)
return { id, name: result }
})
}
}))
// Provide dependencies to UserService layer
const userServiceWithDependencies = userServiceLayer.pipe(
Layer.provide(Layer.mergeAll(databaseLayer, loggerLayer))
)
// Now UserService layer has no dependencies
const program = Effect.gen(function*() {
const userService = yield* UserService
return yield* userService.getUser("123")
}).pipe(
Effect.provide(userServiceWithDependencies)
)
provide(import HyperlinkHyperlink.unix<Emails, {
add: Hyperlink.Method<Schema.Union<readonly [Schema.Struct<{
readonly to: Schema.String;
}>, Schema.$Array<Schema.Struct<{
readonly to: Schema.String;
}>>]>, Schema.Void, Schema.Never, false, Hyperlink.MethodAnnotations & {
description: string;
}, {
(item: {
to: string;
}): Effect.Effect<void>;
(items: readonly {
to: string;
}[]): Effect.Effect<void>;
(itemOrItems: {
to: string;
} | readonly {
to: string;
}[]): Effect.Effect<void>;
}>;
prioritize: Hyperlink.Method<Schema.Union<readonly [Schema.Struct<{
readonly to: Schema.String;
}>, Schema.$Array<...>]>, ... 4 more ..., {
...;
}>;
... 15 more ...;
metrics: {
...;
};
}>(tag: Hyperlink.HyperlinkTag<...>, options?: Hyperlink.DiscoverClientOptions): Layer.Layer<...> (+1 overload)
export unix
Same-machine ipc client — sibling of
Node.unix
.
unix(node) — dial that Node’s Unix-domain path (connect +
protocolIpc
).
unix(tag) — nameless / Lookup path: soft-bake Lookup, resolve an endpoint for tag,
and dial it (identity, then directory). Pairs with nameless
Node.unix
([serve…]).
// Named node you already addressed:
Hyperlink.unix(Worker)
// Nameless serve advertised via Lookup:
Hyperlink.unix(Emails)
Hyperlink.unix(Jobs, { lookupPath, pick: "first" })
unix(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 }, void, never, never>) => WorkPool.WorkPool<{ to: string }, void, never, never>;
context: (self: WorkPool.WorkPool<{ to: string }, void, never, never>) => Context<Emails>;
use: (f: (service: WorkPool.WorkPool<{ to: string }, void, never, never>) => Effect.Effect<A, E, R>) => Effect.Effect<A, E, Emails | R>;
useSync: (f: (service: WorkPool.WorkPool<{ to: 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)))Digest runs on the scheduler, Emails on the worker — yet emails.add(…) looks like one process. Two HyperServices, two runtimes, one program. (Named Node: Node.unix(Worker, …) pairs with Hyperlink.unix(Worker); nameless: Node.unix([serve…]) pairs with Hyperlink.unix(Tag).)
When you need another machine (or a browser), step up to HTTP. Same worker, same Tag — only the listen changes:
const const worker: Layer<HandlerContextOf<{
add: Method<Schema.Union<readonly [Schema.Struct<F>, Schema.$Array<Schema.Struct<F>>]>, Schema.Void, Schema.Never, false, MethodAnnotations & {
description: string;
}, {
(item: PrettifyPayload<Schema.Struct.View<F, "Type", Schema.Struct.TypeOptionalKeys<F>, Schema.Struct.TypeMutableKeys<F>>>): Effect.Effect<void>;
(items: readonly PrettifyPayload<Schema.Struct.View<F, "Type", Schema.Struct.TypeOptionalKeys<F>, Schema.Struct.TypeMutableKeys<F>>>[]): Effect.Effect<void>;
(itemOrItems: PrettifyPayload<...> | readonly PrettifyPayload<...>[]): Effect.Effect<void>;
}>;
... 16 more ...;
metrics: {
...;
};
}> | Storage | Emails | Local<...> | Node.ListenNode, never, never>
const worker: {
build: (memoMap: MemoMap, scope: Scope) => Effect.Effect<Context<HandlerContextOf<{ add: Method<Schema.Union<readonly [Schema.Struct<F>, Schema.$Array<Schema.Struct<F>>]>, Schema.Void, Schema.Never, false, MethodAnnotations & { description: strin…;
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; <…;
}
worker = import NodeNode.http<HandlerContextOf<{
add: Method<Schema.Union<readonly [Schema.Struct<F>, Schema.$Array<Schema.Struct<F>>]>, Schema.Void, Schema.Never, false, MethodAnnotations & {
description: string;
}, {
(item: PrettifyPayload<Schema.Struct.View<F, "Type", Schema.Struct<Fields extends Schema.Struct.Fields>.TypeOptionalKeys<F>, Schema.Struct.TypeMutableKeys<F>>>): Effect.Effect<void>;
(items: readonly PrettifyPayload<Schema.Struct.View<F, "Type", Schema.Struct<Fields extends Schema.Struct.Fields>.TypeOptionalKeys<F>, Schema.Struct.TypeMutableKeys<...>>>[]): Effect.Effect<void>;
(itemOrItems: PrettifyPayload<...> | readonly PrettifyPayload<...>[]): Effect.Effect<void>;
}>;
... 16 more ...;
metrics: {
...;
};
}> | Storage | Emails | Local<...>, never, never>(serve: Layer<...>, options?: Node.HttpListenArg): Layer<...> (+6 overloads)
export http
Local Http listen — localhost bind. Nameless forms Soft-bake
Lookup.layer
when
Identity is absent (claim + advertise); override with Layer.provide(Lookup.layerOptions(…)).
Overload family (keep aligned with
unix
/
ws
/
nPipe
):
http(tag, impl) / http(tag, impl, address) — unbound Tag → nameless; bound Tag → that Node
http(tag, impl, node) — named listen without andNode
http(serve, address?) / http([serve…], address?) — nameless (brackets optional for one)
http(node, serve | [serve…], address?) — named node + serves
Address: 3000 / ":3000" / "http://…" / { port | url | … }. Prefer this over
httpServer
when the battery localhost bind is enough.
http(
import WorkPoolWorkPool.function serve<Emails, {
readonly to: Schema.String;
}, never, never, Schema.Void, Schema.Never>(tag: QueueTagFor<Emails, {
readonly to: Schema.String;
}, Schema.Void, Schema.Never>, config: QueueVerbConfig<{
readonly to: Schema.String;
}, never, never, never, Schema.Void>): Layer<HandlerContextOf<{
add: Method<Schema.Union<readonly [Schema.Struct<F>, Schema.$Array<Schema.Struct<F>>]>, Schema.Void, Schema.Never, false, MethodAnnotations & {
...;
}, {
...;
}>;
... 16 more ...;
metrics: {
...;
};
}> | Storage | Emails | Local<...>, never, never> (+1 overload)
Serve this queue and grant its local instance from one materialization — run the worker /
refill / persist engine behind the tag, mount its RPC handlers, register into
Hyperlink.servedHyperServicesLayer
, and grant Self | Local<Self> so co-located code
can yield* Tag. The served cells are the in-process instance (one engine, one peersLayer); the
worker requirement R is preserved for per-HyperService Layer.provide. This is the queue's counterpart
to
Hyperlink.serve
; a served-only gateway uses
serveRemote
.
Node.httpServer([
WorkPool.serve(RosterQueue, { effect, itemSchema }),
Daemon.serve(SeasonMatches, { effect }),
]).pipe(Layer.provide(NodeHttpServer.layer({ port: 3001 })));
serve(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 }, void, never, never>) => WorkPool.WorkPool<{ to: string }, void, never, never>;
context: (self: WorkPool.WorkPool<{ to: string }, void, never, never>) => Context<Emails>;
use: (f: (service: WorkPool.WorkPool<{ to: string }, void, never, never>) => Effect.Effect<A, E, R>) => Effect.Effect<A, E, Emails | R>;
useSync: (f: (service: WorkPool.WorkPool<{ to: 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: (
job: typeof EmailJob.Type
) => Effect.Effect<void>
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: const sendEmail: (
job: typeof EmailJob.Type
) => Effect.Effect<void>
sendEmail }),
3001,
)The scheduler dials with Hyperlink.connect(Emails, Hyperlink.protocolHttp(3001)). Move a runtime to another machine and only the address changes. (Node.httpServer is the escape hatch when you need a custom platform bind — prefer Node.http / Node.ws day to day; see Managing Layers.)
The same Handle steers it
Callable across runtimes is half the product. The Handle is also operable across them — pause, depth, live events — from anywhere the Tag is reached:
const const emails: WorkPool.WorkPool<
{ to: 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 }, void, never, never>) => WorkPool.WorkPool<{ to: string }, void, never, never>;
context: (self: WorkPool.WorkPool<{ to: string }, void, never, never>) => Context<Emails>;
use: (f: (service: WorkPool.WorkPool<{ to: string }, void, never, never>) => Effect.Effect<A, E, R>) => Effect.Effect<A, E, Emails | R>;
useSync: (f: (service: WorkPool.WorkPool<{ to: 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 // local OR remote — same type
yield* const emails: WorkPool.WorkPool<
{ to: 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; }, void, never, never>.pause: Effect.Effect<void>(property) WorkPool<{ to: string; }, void, never, never>.pause: {
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;
}
Pause processing; items can still be enqueued and accumulate.
pause // stop draining, at runtime
const const depth: numberdepth = yield* const emails: WorkPool.WorkPool<
{ to: 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; }, void, never, never>.size: Hyperlink.Subscribable<number>(property) WorkPool<{ to: 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 // how many waiting, right now
yield* const emails: WorkPool.WorkPool<
{ to: 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; }, void, never, never>.events: Stream.Stream<QueueEvent<Payload, Error, Success>>(property) WorkPool<{ to: 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.Stream<WorkPool.QueueEvent<{
to: string;
}, never, void>, never, never>, Effect.Effect<void, never, never>>(this: Stream.Stream<WorkPool.QueueEvent<{
to: string;
}, never, void>, never, never>, ab: (_: Stream.Stream<WorkPool.QueueEvent<{
to: string;
}, never, void>, 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 onChange: (
e: unknown
) => Effect.Effect<void>
onChange))
Dashboards ride the same Tag — a CLI, a TUI, and a web dashboard — without touching the Implementation.
Build your own
Emails and Digest are not special cases. Every Hyperlink Service is a Contract plus an Implementation. You use that primitive directly:
class class Counterclass Counter {
key: Identifier;
Service: {
value: Hyperlink.Subscribable<number>;
increment: (payload: { by: number }) => Effect<void, never, never>;
reset: Effect<void, never, never>;
};
}
Counter extends import HyperlinkHyperlink.Tag<Counter>(): SchemaTagBuilder<Counter> (+2 overloads)
export Tag
Schema-driven solo: infer the service from spec; bare
local
is a compile error.
Tag<class Counterclass Counter {
key: Identifier;
Service: {
value: Hyperlink.Subscribable<number>;
increment: (payload: { by: number }) => Effect<void, never, never>;
reset: Effect<void, never, never>;
};
}
Counter>()("app/Counter", {
value: Hyperlink.RefField<
Hyperlink.Method<
undefined,
Schema.Number,
Schema.Never,
true,
Hyperlink.MethodAnnotations,
Hyperlink.Derive
>
>
value: import HyperlinkHyperlink.const ref: <Su extends Schema.Top>(
success: Su
) => RefField<
Method<undefined, Su, typeof Schema.Never, true>
>
Define a ref field — reactive state surfaced as a
Subscribable
(get + changes),
uniform local and remote. The impl owns a SubscriptionRef (writes it) and provides it via
subscribable
; consumers read (yield* svc.x.get) and observe (svc.x.changes) — a read
is an honest Effect, not a synchronous peek. For values fixed at acquire use
value
; for
on-demand calls use
effect
.
ref(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),
increment: Hyperlink.Method<
{ readonly by: Schema.Number },
Schema.Void,
Schema.Never,
false,
Hyperlink.MethodAnnotations,
Hyperlink.Derive
>
(property) increment: {
kind: MethodKind;
payload: P;
success: Su;
error: E;
stream: Str;
annotations: Ann;
annotate: <A extends MethodAnnotations>(annotations: A) => Method<P, Su, E, Str, Ann & A, Client>;
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; <…;
}
increment: import HyperlinkHyperlink.function effectFn<{
readonly by: Schema.Number;
}>(payload: {
readonly by: Schema.Number;
}): Hyperlink.Method<{
readonly by: Schema.Number;
}, Schema.Void, Schema.Never, false, Hyperlink.MethodAnnotations, Hyperlink.Derive> (+7 overloads)
Two-stage
effectFn
— override the client-facing type with a Client that must narrow
the schema-derived shape: effectFn<Client>()(payload). Reshape freely (e.g. add overloads), but a
Client that would accept payloads the wire rejects fails to compile (payload resolves to never).
For an override that can't be a narrowing (a generic library), use
unsafeEffectFn
.
effectFn({ by: Schema.Number(property) by: {
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; <…;
}
by: 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 }),
reset: Hyperlink.Method<
undefined,
Schema.Void,
Schema.Never,
false,
Hyperlink.MethodAnnotations,
Hyperlink.Derive
>
(property) reset: {
kind: MethodKind;
payload: P;
success: Su;
error: E;
stream: Str;
annotations: Ann;
annotate: <A extends MethodAnnotations>(annotations: A) => Method<P, Su, E, Str, Ann & A, Client>;
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; <…;
}
reset: import HyperlinkHyperlink.function effect<Schema.Void>(success: Schema.Void): Hyperlink.Method<undefined, Schema.Void, Schema.Never, false, Hyperlink.MethodAnnotations, Hyperlink.Derive> (+3 overloads)Two-stage
effect
— override the client-facing type with a Client (an Effect type;
a read surfaces as Effect<Success>) that must narrow the schema-derived shape:
effect<Client>()(success). Widening the success fails to compile. For a free override, see
effect(import SchemaSchema.const Void: Voidconst Void: {
Rebuild: Rebuild;
Iso: Iso;
ast: Ast;
Type: T;
Encoded: E;
DecodingServices: RD;
EncodingServices: RE;
annotate: (annotations: Schema.Annotations.Bottom<void, readonly []>) => Schema.Void;
annotateKey: (annotations: Schema.Annotations.Key<void>) => Schema.Void;
check: (checks_0: Check<void>, ...checks: Array<Check<void>>) => Schema.Void;
rebuild: (ast: Void) => Schema.Void;
make: (input: void, options?: MakeOptions) => void;
makeOption: (input: void, options?: MakeOptions) => Option_.Option<void>;
makeEffect: (input: void, options?: MakeOptions) => Effect.Effect<void, 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
Void
.
Schema for a TypeScript void return value.
When to use
Use when you need to model the return value of a function, RPC, or endpoint
whose result is intentionally ignored.
Details
Runtime parsing accepts any present value and discards it, producing
undefined. The public decoded and encoded TypeScript representation remains
void, so typed construction, decoding, and encoding APIs are still modeled
as void.
Void),
}) {}const const counterImpl: Effect.Effect<
{
value: Hyperlink.Subscribable<number>
increment: ({
by,
}: {
by: number
}) => Effect.Effect<void, never, never>
reset: Effect.Effect<void, never, never>
},
never,
never
>
const counterImpl: {
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;
}
counterImpl = 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 ref: SubscriptionRef.SubscriptionRef<number>const ref: {
value: A;
semaphore: Semaphore.Semaphore;
pubsub: PubSub.PubSub<A>;
pipe: { <A>(this: A): A; <A, B = never>(this: A, ab: (_: A) => B): B; <A, B = never, C = never>(this: A, ab: (_: A) => B, bc: (_: B) => C): C; <A, B = never, C = never, D = never>(this: A, ab: (_: A) => B, bc: (_: B) => C, cd: (_: C) => D): D; <…;
}
ref = yield* import SubscriptionRefSubscriptionRef.const make: <A>(
value: A
) => Effect.Effect<SubscriptionRef<A>>
Constructs a new SubscriptionRef from an initial value.
When to use
Use to create a SubscriptionRef when consumers need to read the latest
value and subscribe to every update.
Details
The initial value is published during construction, so changes starts new
subscribers with that value before future updates.
make(0)
return {
value: Hyperlink.Subscribable<number>(property) value: {
get: Effect.Effect<A>;
changes: Stream.Stream<A>;
}
value: import HyperlinkHyperlink.const subscribable: <A>(
source: SubscriptionRef.SubscriptionRef<A>
) => Subscribable<A>
Build a
Subscribable
view over a SubscriptionRef — the impl side of a
ref
field: the
impl owns the ref (writes it), consumers get read + observe.
subscribable(const ref: SubscriptionRef.SubscriptionRef<number>const ref: {
value: A;
semaphore: Semaphore.Semaphore;
pubsub: PubSub.PubSub<A>;
pipe: { <A>(this: A): A; <A, B = never>(this: A, ab: (_: A) => B): B; <A, B = never, C = never>(this: A, ab: (_: A) => B, bc: (_: B) => C): C; <A, B = never, C = never, D = never>(this: A, ab: (_: A) => B, bc: (_: B) => C, cd: (_: C) => D): D; <…;
}
ref),
increment: ({
by,
}: {
by: number
}) => Effect.Effect<void, never, never>
increment: ({ by: numberby }: { by: numberby: number }) => import SubscriptionRefSubscriptionRef.const update: {
<A>(update: (a: A) => A): (
self: SubscriptionRef<A>
) => Effect.Effect<void>
<A>(
self: SubscriptionRef<A>,
update: (a: A) => A
): Effect.Effect<void>
}
Updates the value of the SubscriptionRef with the result of applying a
function, notifying subscribers of the change.
Example (Updating a value)
import { Effect, SubscriptionRef } from "effect"
const program = Effect.gen(function*() {
const ref = yield* SubscriptionRef.make(10)
yield* SubscriptionRef.update(ref, (n) => n * 2)
const value = yield* SubscriptionRef.get(ref)
console.log(value)
})
update(const ref: SubscriptionRef.SubscriptionRef<number>const ref: {
value: A;
semaphore: Semaphore.Semaphore;
pubsub: PubSub.PubSub<A>;
pipe: { <A>(this: A): A; <A, B = never>(this: A, ab: (_: A) => B): B; <A, B = never, C = never>(this: A, ab: (_: A) => B, bc: (_: B) => C): C; <A, B = never, C = never, D = never>(this: A, ab: (_: A) => B, bc: (_: B) => C, cd: (_: C) => D): D; <…;
}
ref, (n: numbern) => n: numbern + by: numberby),
reset: Effect.Effect<void, never, never>(property) reset: {
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;
}
reset: import SubscriptionRefSubscriptionRef.const set: {
<A>(value: A): (
self: SubscriptionRef<A>
) => Effect.Effect<void>
<A>(
self: SubscriptionRef<A>,
value: A
): Effect.Effect<void>
}
Sets the value of the SubscriptionRef, notifying all subscribers of the
change.
Example (Setting a value)
import { Effect, SubscriptionRef } from "effect"
const program = Effect.gen(function*() {
const ref = yield* SubscriptionRef.make(0)
yield* SubscriptionRef.set(ref, 42)
const value = yield* SubscriptionRef.get(ref)
console.log(value)
})
set(const ref: SubscriptionRef.SubscriptionRef<number>const ref: {
value: A;
semaphore: Semaphore.Semaphore;
pubsub: PubSub.PubSub<A>;
pipe: { <A>(this: A): A; <A, B = never>(this: A, ab: (_: A) => B): B; <A, B = never, C = never>(this: A, ab: (_: A) => B, bc: (_: B) => C): C; <A, B = never, C = never, D = never>(this: A, ab: (_: A) => B, bc: (_: B) => C, cd: (_: C) => D): D; <…;
}
ref, 0),
}
})Same Tag, three placements — in-process, served, or connected:
import HyperlinkHyperlink.layer<Counter, {
readonly value: Hyperlink.RefField<Hyperlink.Method<undefined, Schema.Number, Schema.Never, true, Hyperlink.MethodAnnotations, Hyperlink.Derive>>;
readonly increment: Hyperlink.Method<{
readonly by: Schema.Number;
}, Schema.Void, Schema.Never, false, Hyperlink.MethodAnnotations, Hyperlink.Derive>;
readonly reset: Hyperlink.Method<undefined, Schema.Void, Schema.Never, false, Hyperlink.MethodAnnotations, Hyperlink.Derive>;
}, never>(tag: Hyperlink.HyperlinkTag<...>, impl: Effect.Effect<...>): Layer<...> (+3 overloads)
export layer
layer(class Counterclass Counter {
key: Identifier;
Service: {
value: Hyperlink.Subscribable<number>;
increment: (payload: { by: number }) => Effect.Effect<void, never, never>;
reset: Effect.Effect<void, never, never>;
};
description: string | undefined;
of: (this: void, self: { readonly value: Hyperlink.Subscribable<number>; readonly increment: (payload: { by: number }) => Effect.Effect<void, never, never>; readonly reset: Effect.Effect<void, never, never> }) => { readonly value: Hyperlink.Su…;
context: (self: { readonly value: Hyperlink.Subscribable<number>; readonly increment: (payload: { by: number }) => Effect.Effect<void, never, never>; readonly reset: Effect.Effect<void, never, never> }) => Context<Counter>;
use: (f: (service: { readonly value: Hyperlink.Subscribable<number>; readonly increment: (payload: { by: number }) => Effect.Effect<void, never, never>; readonly reset: Effect.Effect<void, never, never> }) => Effect.Effect<A, E, R>) => Effect.E…;
useSync: (f: (service: { readonly value: Hyperlink.Subscribable<number>; readonly increment: (payload: { by: number }) => Effect.Effect<void, never, never>; readonly reset: Effect.Effect<void, never, never> }) => A) => Effect.Effect<A, never, Count…;
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;
}
Counter, const counterImpl: Effect.Effect<
{
value: Hyperlink.Subscribable<number>
increment: ({
by,
}: {
by: number
}) => Effect.Effect<void, never, never>
reset: Effect.Effect<void, never, never>
},
never,
never
>
const counterImpl: {
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;
}
counterImpl) // in-process
import NodeNode.http<Counter | Hyperlink.Local<Counter> | Handler<"value"> | Handler<"increment"> | Handler<"reset">, never, never>(serve: Layer<Counter | Hyperlink.Local<Counter> | Handler<"value"> | Handler<"increment"> | Handler<"reset">, never, never>, options?: Node.HttpListenArg): Layer<Counter | Hyperlink.Local<Counter> | Handler<"value"> | Handler<"increment"> | Handler<...> | Node.ListenNode, never, never> (+6 overloads)
export http
Local Http listen — localhost bind. Nameless forms Soft-bake
Lookup.layer
when
Identity is absent (claim + advertise); override with Layer.provide(Lookup.layerOptions(…)).
Overload family (keep aligned with
unix
/
ws
/
nPipe
):
http(tag, impl) / http(tag, impl, address) — unbound Tag → nameless; bound Tag → that Node
http(tag, impl, node) — named listen without andNode
http(serve, address?) / http([serve…], address?) — nameless (brackets optional for one)
http(node, serve | [serve…], address?) — named node + serves
Address: 3000 / ":3000" / "http://…" / { port | url | … }. Prefer this over
httpServer
when the battery localhost bind is enough.
http(import HyperlinkHyperlink.const serve: <
Self,
S extends Spec,
R = never
>(
tag: HyperlinkTag<Self, S>,
impl:
| ImplWithDefaultOverrides<S>
| Driver<S, R>
| Effect.Effect<
| ImplWithDefaultOverrides<S>
| Driver<S, R>,
never,
R
>
) => Layer.Layer<
Self | Local<Self> | HandlerContextOf<S>,
ValueErrorsOf<S>,
R
>
Expose a
Tag
's implementation as an RPC server layer — the served counterpart of
layer
. Pair it with
Node.httpServer
/
Node.wsServer
(or
listen
) to
put it on a transport, and dial it with
client
/
connect
.
Shared-Spec instances (Tag(wireKey, spec) → Factory<Self>()(instanceKey)) share one
RpcGroup: Layer.mergeAll(serve(A, …), serve(B, …)) mounts handlers once and routes by
the per-call key header.
serve(class Counterclass Counter {
key: Identifier;
Service: {
value: Hyperlink.Subscribable<number>;
increment: (payload: { by: number }) => Effect.Effect<void, never, never>;
reset: Effect.Effect<void, never, never>;
};
description: string | undefined;
of: (this: void, self: { readonly value: Hyperlink.Subscribable<number>; readonly increment: (payload: { by: number }) => Effect.Effect<void, never, never>; readonly reset: Effect.Effect<void, never, never> }) => { readonly value: Hyperlink.Su…;
context: (self: { readonly value: Hyperlink.Subscribable<number>; readonly increment: (payload: { by: number }) => Effect.Effect<void, never, never>; readonly reset: Effect.Effect<void, never, never> }) => Context<Counter>;
use: (f: (service: { readonly value: Hyperlink.Subscribable<number>; readonly increment: (payload: { by: number }) => Effect.Effect<void, never, never>; readonly reset: Effect.Effect<void, never, never> }) => Effect.Effect<A, E, R>) => Effect.E…;
useSync: (f: (service: { readonly value: Hyperlink.Subscribable<number>; readonly increment: (payload: { by: number }) => Effect.Effect<void, never, never>; readonly reset: Effect.Effect<void, never, never> }) => A) => Effect.Effect<A, never, Count…;
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;
}
Counter, const counterImpl: Effect.Effect<
{
value: Hyperlink.Subscribable<number>
increment: ({
by,
}: {
by: number
}) => Effect.Effect<void, never, never>
reset: Effect.Effect<void, never, never>
},
never,
never
>
const counterImpl: {
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;
}
counterImpl), 4000) // served over RPC
import HyperlinkHyperlink.const connect: <
Self,
S extends Spec,
E = never
>(
tag: HyperlinkTag<Self, S>,
protocol: Layer.Layer<RpcClient.Protocol, E>
) => Layer.Layer<Self, E | ValueErrorsOf<S>>
Dial a HyperService tag over a transport you provide — the no-batteries client. connect bakes in
no transport of its own (unlike
http
/
ws
/
unix
/
nPipe
, whose
wire is in the name and bundled): you hand it a
protocolHttp
/
protocolWebsocket
/
protocolIpc
layer, so a browser build pulls in only the one wire it passes.
program.pipe(Effect.provide(Hyperlink.connect(Emails, Hyperlink.protocolHttp(3009)))); // server
program.pipe(Effect.provide(Hyperlink.connect(Emails, Hyperlink.protocolWebsocket("/rpc")))); // browser (ws only)
The port shorthand (3009) resolves against
clientHost
(default "localhost"), so the same
3009 points at your production host once HYPERLINK_CLIENT_HOST is set.
Replaces the retired clientHttp(tag, target) — use connect(tag, protocolHttp(target)).
connect(class Counterclass Counter {
key: Identifier;
Service: {
value: Hyperlink.Subscribable<number>;
increment: (payload: { by: number }) => Effect.Effect<void, never, never>;
reset: Effect.Effect<void, never, never>;
};
description: string | undefined;
of: (this: void, self: { readonly value: Hyperlink.Subscribable<number>; readonly increment: (payload: { by: number }) => Effect.Effect<void, never, never>; readonly reset: Effect.Effect<void, never, never> }) => { readonly value: Hyperlink.Su…;
context: (self: { readonly value: Hyperlink.Subscribable<number>; readonly increment: (payload: { by: number }) => Effect.Effect<void, never, never>; readonly reset: Effect.Effect<void, never, never> }) => Context<Counter>;
use: (f: (service: { readonly value: Hyperlink.Subscribable<number>; readonly increment: (payload: { by: number }) => Effect.Effect<void, never, never>; readonly reset: Effect.Effect<void, never, never> }) => Effect.Effect<A, E, R>) => Effect.E…;
useSync: (f: (service: { readonly value: Hyperlink.Subscribable<number>; readonly increment: (payload: { by: number }) => Effect.Effect<void, never, never>; readonly reset: Effect.Effect<void, never, never> }) => A) => Effect.Effect<A, never, Count…;
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;
}
Counter, import HyperlinkHyperlink.const protocolHttp: (
target?: number | string,
serialization?: Layer.Layer<RpcSerialization.RpcSerialization>
) => Layer.Layer<RpcClient.Protocol>
Build an http client Protocol (Fetch + ndjson serialization) for an endpoint — the value you
hand
layerProtocol
or
connect
. target is a port (3009 → http://${clientHost}:3009/rpc,
Config host default "localhost"), a full url, or a same-origin path (default "/rpc"). The
server/CLI transport; a browser should prefer
protocolWebsocket
(HTTP/1.1's ~6-connection cap
starves streams — protocolHttp dies loudly in a browser).
protocolHttp(4000)) // from another runtimeIt gets operability and dashboard slots for free — because it is the same kind of thing Emails is. Walk through this end to end in Creating a Hyperlink Service.
Working with peers
The same Tag can reach its peers — other instances of itself — and coordinate. Sessions sharded across droplets: each Node holds what it owns; a lookup for someone elses session is forwarded to the owner. ShardMap is that pattern as an included HyperService factory:
class class SessionsSessions extends import ShardMapShardMap.const Tag: <Sessions>() => <Key extends Schema.Top, Value extends Schema.Top, Error extends Schema.Top = typeof Schema.Never>(key: string, schemas: ShardMap.ShardMapSchemas<Key, Value, Error>) => ShardMap.ShardMapTag<Sessions, Key, Value, Error>Declare a ShardMap tag — schemas on the Tag; partition strategy is a runtime option on
layer
/
serve
.
Tag<class SessionsSessions>()("app/Sessions", {
ShardMapSchemas<String, Struct<{ readonly id: String; readonly userId: String; }>, Never>.key: Schema.Stringkey: const SessionId: Schema.StringSessionId,
ShardMapSchemas<String, Struct<{ readonly id: String; readonly userId: String; }>, Never>.value: Schema.Struct<{
readonly id: Schema.String;
readonly userId: Schema.String;
}>
value: const Session: Schema.Struct<{
readonly id: Schema.String;
readonly userId: Schema.String;
}>
Session,
ShardMapSchemas<String, Struct<{ readonly id: String; readonly userId: String; }>, Never>.keyOf: (value: Schema.Struct.ReadonlySide<{
readonly id: Schema.String;
readonly userId: Schema.String;
}, "Type">) => string
Extract the partition key from a value (routed put / putLocal).
keyOf: (s: Schema.Struct.ReadonlySide<{
readonly id: Schema.String;
readonly userId: Schema.String;
}, "Type">
s) => s: Schema.Struct.ReadonlySide<{
readonly id: Schema.String;
readonly userId: Schema.String;
}, "Type">
s.id: stringid,
}).Pipeable.pipe<ShardMap.ShardMapTag<Sessions, Schema.String, Schema.Struct<{
readonly id: Schema.String;
readonly userId: Schema.String;
}>, Schema.Never>, Hyperlink.HyperlinkTag<Sessions, {
get: Hyperlink.Method<Schema.String, Schema.Option<Schema.Struct<{
readonly id: Schema.String;
readonly userId: Schema.String;
}>>, Schema.Never, false, Hyperlink.MethodAnnotations & {
description: string;
}, Hyperlink.Derive>;
... 7 more ...;
size: Hyperlink.Marked<...>;
}, {
...;
}> & {
...;
}>(this: ShardMap.ShardMapTag<...>, ab: (_: ShardMap.ShardMapTag<...>) => Hyperlink.HyperlinkTag<Sessions, {
get: Hyperlink.Method<Schema.String, Schema.Option<Schema.Struct<{
readonly id: Schema.String;
readonly userId: Schema.String;
}>>, Schema.Never, false, Hyperlink.MethodAnnotations & {
description: string;
}, Hyperlink.Derive>;
... 7 more ...;
size: Hyperlink.Marked<...>;
}, {
...;
}> & {
...;
}): Hyperlink.HyperlinkTag<Sessions, {
get: Hyperlink.Method<Schema.String, Schema.Option<Schema.Struct<{
readonly id: Schema.String;
readonly userId: Schema.String;
}>>, Schema.Never, false, Hyperlink.MethodAnnotations & {
description: string;
}, Hyperlink.Derive>;
... 7 more ...;
size: Hyperlink.Marked<...>;
}, {
...;
}> & {
...;
} (+21 overloads)
pipe(
import HyperlinkHyperlink.const nodes: <Hyperlink.HyperlinkTag<Sessions, {
get: Hyperlink.Method<Schema.String, Schema.Option<Schema.Struct<{
readonly id: Schema.String;
readonly userId: Schema.String;
}>>, Schema.Never, false, Hyperlink.MethodAnnotations & {
description: string;
}, Hyperlink.Derive>;
put: Hyperlink.Method<Schema.Struct<{
readonly id: Schema.String;
readonly userId: Schema.String;
}>, Schema.Boolean, Schema.Never, false, Hyperlink.MethodAnnotations & {
...;
}, Hyperlink.Derive>;
... 6 more ...;
size: Hyperlink.Marked<...>;
}, {
...;
}> & {
...;
}>(nodeSet: ReadonlyArray<Node.AnyNode>) => (tag: Hyperlink.HyperlinkTag<Sessions, {
get: Hyperlink.Method<Schema.String, Schema.Option<Schema.Struct<{
readonly id: Schema.String;
readonly userId: Schema.String;
}>>, Schema.Never, false, Hyperlink.MethodAnnotations & {
description: string;
}, Hyperlink.Derive>;
put: Hyperlink.Method<Schema.Struct<{
readonly id: Schema.String;
readonly userId: Schema.String;
}>, Schema.Boolean, Schema.Never, false, Hyperlink.MethodAnnotations & {
...;
}, Hyperlink.Derive>;
... 6 more ...;
size: Hyperlink.Marked<...>;
}, {
...;
}> & {
...;
}) => Hyperlink.HyperlinkTag<Sessions, {
get: Hyperlink.Method<Schema.String, Schema.Option<Schema.Struct<{
readonly id: Schema.String;
readonly userId: Schema.String;
}>>, Schema.Never, false, Hyperlink.MethodAnnotations & {
description: string;
}, Hyperlink.Derive>;
put: Hyperlink.Method<Schema.Struct<{
readonly id: Schema.String;
readonly userId: Schema.String;
}>, Schema.Boolean, Schema.Never, false, Hyperlink.MethodAnnotations & {
...;
}, Hyperlink.Derive>;
... 6 more ...;
size: Hyperlink.Marked<...>;
}, {
...;
}> & {
...;
} (+6 overloads)
nodes([class DropletEastDropletEast, class DropletWestDropletWest, class DropletCentralDropletCentral]),
) {}Serve a droplet — local shard + peer clients from one materialization:
const const east: Layer.Layer<Handler<"get"> | Handler<"put"> | Handler<"delete"> | Handler<"getLocal"> | Handler<"putLocal"> | Handler<"deleteLocal"> | Handler<"sizeLocal"> | Handler<"sizeByNode"> | Handler<"size"> | Sessions | Hyperlink.Local<Sessions> | Node.ListenNode, never, never>east = import NodeNode.http<readonly [Layer.Layer<Handler<"get"> | Handler<"put"> | Handler<"delete"> | Handler<"getLocal"> | Handler<"putLocal"> | Handler<"deleteLocal"> | Handler<"sizeLocal"> | Handler<"sizeByNode"> | Handler<"size"> | Sessions | Hyperlink.Local<Sessions>, never, Hyperlink.PeersId<Sessions> | Hyperlink.SelfNodeId<Sessions>>]>(serves: readonly [Layer.Layer<Handler<"get"> | Handler<"put"> | ... 8 more ... | Hyperlink.Local<...>, never, Hyperlink.PeersId<...> | Hyperlink.SelfNodeId<...>>], options?: Node.HttpListenArg): Layer.Layer<...> (+6 overloads)
export http
Local Http listen — localhost bind. Nameless forms Soft-bake
Lookup.layer
when
Identity is absent (claim + advertise); override with Layer.provide(Lookup.layerOptions(…)).
Overload family (keep aligned with
unix
/
ws
/
nPipe
):
http(tag, impl) / http(tag, impl, address) — unbound Tag → nameless; bound Tag → that Node
http(tag, impl, node) — named listen without andNode
http(serve, address?) / http([serve…], address?) — nameless (brackets optional for one)
http(node, serve | [serve…], address?) — named node + serves
Address: 3000 / ":3000" / "http://…" / { port | url | … }. Prefer this over
httpServer
when the battery localhost bind is enough.
http([import ShardMapShardMap.const serve: <Sessions, Schema.String, Schema.Struct<{
readonly id: Schema.String;
readonly userId: Schema.String;
}>, Schema.Never>(tag: ShardMap.ShardMapTag<Sessions, Schema.String, Schema.Struct<{
readonly id: Schema.String;
readonly userId: Schema.String;
}>, Schema.Never>, options?: ShardMap.ShardMapOptions) => Layer.Layer<Handler<"get"> | Handler<"put"> | Handler<"delete"> | Handler<"getLocal"> | ... 6 more ... | Hyperlink.Local<...>, never, Hyperlink.PeersId<...> | Hyperlink.SelfNodeId<...>>
Serve this ShardMap and grant its local instance from one materialization —
counterpart to
Hyperlink.serve
. Opens SQLite (:memory: by default; pass
{ filename } for a durable file). Requires the mesh capability:
serve(class SessionsSessions)], 3001).Pipeable.pipe<Layer.Layer<Handler<"get"> | Handler<"put"> | Handler<"delete"> | Handler<"getLocal"> | Handler<"putLocal"> | Handler<"deleteLocal"> | Handler<"sizeLocal"> | Handler<"sizeByNode"> | Handler<"size"> | Sessions | Hyperlink.Local<Sessions> | Node.ListenNode, never, Hyperlink.PeersId<Sessions> | Hyperlink.SelfNodeId<Sessions>>, Layer.Layer<Handler<"get"> | ... 10 more ... | Node.ListenNode, never, never>>(this: Layer.Layer<...>, ab: (_: Layer.Layer<...>) => Layer.Layer<...>): Layer.Layer<...> (+21 overloads)pipe(
import LayerLayer.const provide: <never, never, Hyperlink.PeersId<Sessions> | Hyperlink.SelfNodeId<Sessions>>(that: Layer.Layer<Hyperlink.PeersId<Sessions> | Hyperlink.SelfNodeId<Sessions>, never, never>) => <RIn2, E2, ROut2>(self: Layer.Layer<ROut2, E2, RIn2>) => Layer.Layer<ROut2, E2, Exclude<RIn2, Hyperlink.PeersId<Sessions> | Hyperlink.SelfNodeId<Sessions>>> (+3 overloads)Feeds the output services of the dependency layer into the requirements of
this layer, returning a layer that only provides the services from this layer.
When to use
Use when you need to hide an implementation dependency layer from callers.
Details
In serviceLayer.pipe(Layer.provide(dependencyLayer)), the dependency layer is
built first and is used to satisfy the requirements of serviceLayer.
Example (Providing layer dependencies)
import { Context, Effect, Layer } from "effect"
class Database extends Context.Service<Database, {
readonly query: (sql: string) => Effect.Effect<string>
}>()("Database") {}
class UserService extends Context.Service<UserService, {
readonly getUser: (id: string) => Effect.Effect<{
id: string
name: string
}>
}>()("UserService") {}
class Logger extends Context.Service<Logger, {
readonly log: (msg: string) => Effect.Effect<void>
}>()("Logger") {}
// Create dependency layers
const databaseLayer = Layer.succeed(Database, {
query: Effect.fn("Database.query")((sql: string) => Effect.succeed(`DB: ${sql}`))
})
const loggerLayer = Layer.succeed(Logger, {
log: Effect.fn("Logger.log")((msg: string) => Effect.sync(() => console.log(`[LOG] ${msg}`)))
})
// UserService depends on Database and Logger
const userServiceLayer = Layer.effect(UserService, Effect.gen(function*() {
const database = yield* Database
const logger = yield* Logger
return {
getUser: Effect.fn("UserService.getUser")(function*(id: string) {
yield* logger.log(`Looking up user ${id}`)
const result = yield* database.query(
`SELECT * FROM users WHERE id = ${id}`
)
return { id, name: result }
})
}
}))
// Provide dependencies to UserService layer
const userServiceWithDependencies = userServiceLayer.pipe(
Layer.provide(Layer.mergeAll(databaseLayer, loggerLayer))
)
// Now UserService layer has no dependencies
const program = Effect.gen(function*() {
const userService = yield* UserService
return yield* userService.getUser("123")
}).pipe(
Effect.provide(userServiceWithDependencies)
)
provide(import HyperlinkHyperlink.const peersLayer: <Sessions, {
get: Hyperlink.Method<Schema.String, Schema.Option<Schema.Struct<{
readonly id: Schema.String;
readonly userId: Schema.String;
}>>, Schema.Never, false, Hyperlink.MethodAnnotations & {
description: string;
}, Hyperlink.Derive>;
put: Hyperlink.Method<Schema.Struct<{
readonly id: Schema.String;
readonly userId: Schema.String;
}>, Schema.Boolean, Schema.Never, false, Hyperlink.MethodAnnotations & {
...;
}, Hyperlink.Derive>;
... 6 more ...;
size: Hyperlink.Marked<...>;
}, never, never>(tag: Hyperlink.HyperlinkTag<...>, self: Node.AnyNode, options?: {
...;
} | undefined) => Layer.Layer<...>
Provide the
peers
capability on this node: connect every OTHER node in the tag's
distributed
/
nodes
set and expose them as the peer clients. Also provides the
selfNode
capability (this node's key) for byNode-style folds. The opt-in mesh — add
it to a node's serve only where the HyperService's own logic reaches across nodes. self is the node
you are, so you're excluded from your own peer set.
Membership (D3):
- Fixed — non-empty
options.nodes or stamped nodes([…]) / distributed([…]).
- Directory — stamped empty set (bare
.pipe(Hyperlink.distributed) / nodes([])): read
Lookup Directory.nodesServing(tag.key) at layer build. Soft empty map when Directory is absent.
- Undeclared — no
nodesSym and no options.nodes → empty static peers (not directory).
Peer addresses: each
Node
's own url / path is the default. Pass options.url to
override the url per node — an env-specific port, a tunnel, or a value from Effect Config —
falling back to Node.url when the resolver returns undefined. A node with no dialable address
is skipped (never a throw), so a partial mesh degrades cleanly. IpcSocket peers dial via
protocolIpc
when only path is set. The resolver's error and requirements flow to the
layer (typed).
peersLayer(class SessionsSessions, class DropletEastDropletEast)),
)From any Node, a caller just asks — ownership and the hop stay inside the HyperService:
const const program: Effect.Effect<
void,
never,
Sessions
>
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 sessions: {
readonly get: (payload: string) => Effect.Effect<Option<Schema.Struct.ReadonlySide<{
readonly id: Schema.String;
readonly userId: Schema.String;
}, "Type">>, never, never>;
readonly put: (payload: {
id: string;
userId: string;
}) => Effect.Effect<boolean, never, never>;
readonly delete: (payload: string) => Effect.Effect<boolean, never, never>;
readonly getLocal: (payload: string) => Effect.Effect<Option<Schema.Struct.ReadonlySide<{
readonly id: Schema.String;
readonly userId: Schema.String;
}, "Type">>, never, never>;
... 4 more ...;
readonly size: Effect.Effect<...>;
}
sessions = yield* class Sessionsclass Sessions {
key: Identifier;
Service: {
get: (payload: string) => Effect.Effect<Option<Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly userId: Schema.String }, 'Type'>>, never, never>;
put: (payload: { id: string; userId: string }) => Effect.Effect<boolean, never, never>;
delete: (payload: string) => Effect.Effect<boolean, never, never>;
getLocal: (payload: string) => Effect.Effect<Option<Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly userId: Schema.String }, 'Type'>>, never, never>;
putLocal: (payload: { id: string; userId: string }) => Effect.Effect<void, never, never>;
deleteLocal: (payload: string) => Effect.Effect<boolean, never, never>;
sizeLocal: Effect.Effect<number, never, never>;
sizeByNode: Effect.Effect<{ readonly [x: string]: number }, never, never>;
size: Effect.Effect<number, never, never>;
};
description: string | undefined;
of: (this: void, self: { readonly get: (payload: string) => Effect.Effect<Option<Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly userId: Schema.String }, 'Type'>>, never, never>; readonly put: (payload: { id: string; userId: …;
context: (self: { readonly get: (payload: string) => Effect.Effect<Option<Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly userId: Schema.String }, 'Type'>>, never, never>; readonly put: (payload: { id: string; userId: string }) =>…;
use: (f: (service: { readonly get: (payload: string) => Effect.Effect<Option<Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly userId: Schema.String }, 'Type'>>, never, never>; readonly put: (payload: { id: string; userId: strin…;
useSync: (f: (service: { readonly get: (payload: string) => Effect.Effect<Option<Schema.Struct.ReadonlySide<{ readonly id: Schema.String; readonly userId: Schema.String }, 'Type'>>, never, never>; readonly put: (payload: { id: string; userId: strin…;
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;
}
Sessions
const const session: Option<
Schema.Struct.ReadonlySide<
{
readonly id: Schema.String
readonly userId: Schema.String
},
"Type"
>
>
session = yield* const sessions: {
readonly get: (payload: string) => Effect.Effect<Option<Schema.Struct.ReadonlySide<{
readonly id: Schema.String;
readonly userId: Schema.String;
}, "Type">>, never, never>;
readonly put: (payload: {
id: string;
userId: string;
}) => Effect.Effect<boolean, never, never>;
readonly delete: (payload: string) => Effect.Effect<boolean, never, never>;
readonly getLocal: (payload: string) => Effect.Effect<Option<Schema.Struct.ReadonlySide<{
readonly id: Schema.String;
readonly userId: Schema.String;
}, "Type">>, never, never>;
... 4 more ...;
readonly size: Effect.Effect<...>;
}
sessions.get: (payload: string) => Effect.Effect<Option<Schema.Struct<Fields extends Schema.Struct.Fields>.ReadonlySide<{
readonly id: Schema.String;
readonly userId: Schema.String;
}, "Type">>, never, never>
get(const id: stringid) // Option<Session> — from whoever owns it
})An unreachable owner degrades to a miss instead of blocking. Every instance an equal — reached, and reaching others, through the same Tag.
Included Hyperlink Services
Building your own is the focus. The package also ships a few included Hyperlink Services — full Hyperlink Services you can drop in when you need them: