Hyperlink for Effect
Build cross-runtime Services on Effect.
An Effect Service lives inside one runtime. A Hyperlink Service doesnt: define it once, run it on one runtime, and call it from another over RPC, with the same typed Handle.
A real app runs as more than one runtime — a worker draining a queue here, a scheduler filling it there. Wiring those together normally means one side owns a Hyperlink and the others reach it through a hand-rolled HTTP client. Cross-runtime Services drop that: every Hyperlink is reached with the same typed Handle, wherever it runs.
Here are two Hyperlinks — a queue and a scheduled process — on two runtimes, working together.
// two resources, defined once
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 QueueHyperlinkQueueHyperlink.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", 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: Schema.Struct.ReadonlyMakeIn<{ readonly to: Schema.String }>, options?: Schema.MakeOptions) => Schema.Struct.ReadonlySide<{ readonly to: Schema.String }, 'Type'>;
makeOption: (input: Schema.Struct.ReadonlyMakeIn<{ readonly to: Schema.String }>, options?: Schema.MakeOptions) => Option<Schema.Struct.ReadonlySide<{ readonly to: Schema.String }, 'Type'>>;
makeEffect: (input: Schema.Struct.ReadonlyMakeIn<{ readonly to: Schema.String }>, options?: Schema.MakeOptions) => Effect<Schema.Struct.ReadonlySide<{ readonly to: Schema.String }, 'Type'>, Schema.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) {} // a queue of 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 ProcessProcess.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") {} // a scheduled processNode.httpServer(serve) is platform-agnostic — it just needs an HTTP server provided, and that provide is where you pick your runtime. Define it once as a small helper; swapping NodeHttpServer for Bun, Deno, or an edge runtime is the only line that changes:
// your app, once — the single place that names a platform (data-last, so it pipes)
const const nodeServer: (
port: number
) => <A, E, R>(
resource: Layer.Layer<A, E, R>
) => any
nodeServer = (port: numberport: number) => <function (type parameter) A in <A, E, R>(resource: Layer.Layer<A, E, R>): anyA, function (type parameter) E in <A, E, R>(resource: Layer.Layer<A, E, R>): anyE, function (type parameter) R in <A, E, R>(resource: Layer.Layer<A, E, R>): anyR>(resource: Layer.Layer<A, E, R>(parameter) resource: {
build: (memoMap: Layer.MemoMap, scope: Scope) => Effect<Context<A>, E, 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; <…;
}
resource: import LayerLayer.interface Layer<in ROut, out E = never, out RIn = never>A Layer describes how to build one or more services for dependency injection.
When to use
Use to model construction of application services for dependency injection,
especially when services have dependencies, can fail during construction, or
need scoped setup and release.
Details
A Layer<ROut, E, RIn> represents ROut as the services this layer
provides, E as the possible errors during layer construction, and RIn as
the services this layer requires as dependencies.
Layer<function (type parameter) A in <A, E, R>(resource: Layer.Layer<A, E, R>): anyA, function (type parameter) E in <A, E, R>(resource: Layer.Layer<A, E, R>): anyE, function (type parameter) R in <A, E, R>(resource: Layer.Layer<A, E, R>): anyR>) =>
import NodeNode.httpServer(resource: Layer.Layer<A, E, R>(parameter) resource: {
build: (memoMap: Layer.MemoMap, scope: Scope) => Effect<Context<A>, E, 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; <…;
}
resource).pipe(
import LayerLayer.const provide: <never, ServeError, HttpServer | NodeServices | HttpPlatform | Generator>(that: Layer.Layer<HttpServer | NodeServices | HttpPlatform | Generator, ServeError, never>) => <RIn2, E2, ROut2>(self: Layer.Layer<ROut2, E2, RIn2>) => Layer.Layer<ROut2, ServeError | E2, Exclude<RIn2, HttpServer | NodeServices | HttpPlatform | Generator>> (+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 NodeHttpServerNodeHttpServer.const layer: (
evaluate: LazyArg<Server>,
options: ListenOptions & {
readonly disablePreemptiveShutdown?:
| boolean
| undefined
readonly gracefulShutdownTimeout?:
| Input
| undefined
}
) => Layer.Layer<
| HttpServer
| NodeServices
| HttpPlatform
| Generator,
ServeError
>
Provides a Node HttpServer together with the Node HTTP platform, ETag, and
core platform services required to serve requests.
layer(() => createServer<typeof IncomingMessage, typeof ServerResponse>(requestListener?: RequestListener<typeof IncomingMessage, typeof ServerResponse> | undefined): Server<typeof IncomingMessage, typeof ServerResponse> (+1 overload)Returns a new instance of
Server
.
The requestListener is a function which is automatically
added to the 'request' event.
import http from 'node:http';
// Create a local server to receive data from
const server = http.createServer((req, res) => {
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({
data: 'Hello World!',
}));
});
server.listen(8000);
import http from 'node:http';
// Create a local server to receive data from
const server = http.createServer();
// Listen to the request event
server.on('request', (request, res) => {
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({
data: 'Hello World!',
}));
});
server.listen(8000);
createServer(), { ListenOptions.port?: number | undefinedport })),
)Now the worker runtime is one pipe — QueueHyperlink.serve gives Emails its worker (the effect that drains each job), piped onto port 3001:
const const worker: anyconst worker: {
build: (memoMap: Layer.MemoMap, scope: Scope) => Effect.Effect<Context<Hyperlink.HandlerContextOf<{ add: Hyperlink.Method<Schema.Union<readonly [Schema.Struct<F>, Schema.$Array<Schema.Struct<F>>]>, Schema.Void, Schema.Never, false, Hyperlink.Meth…;
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 QueueHyperlinkQueueHyperlink
.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>>;
};
groupId: string;
description: string | undefined;
of: (this: void, self: QueueHyperlink.QueueHyperlink<{ to: string }, void, never, never>) => QueueHyperlink.QueueHyperlink<{ to: string }, void, never, never>;
context: (self: QueueHyperlink.QueueHyperlink<{ to: string }, void, never, never>) => Context<Emails>;
use: (f: (service: QueueHyperlink.QueueHyperlink<{ to: string }, void, never, never>) => Effect.Effect<A, E, R>) => Effect.Effect<A, E, Emails | R>;
useSync: (f: (service: QueueHyperlink.QueueHyperlink<{ 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>
effect: const sendEmail: (
job: typeof EmailJob.Type
) => Effect.Effect<void>
sendEmail })
.pipe(const nodeServer: (
port: number
) => <A, E, R>(
resource: Layer.Layer<A, E, R>
) => any
nodeServer(3001))
// worker: Layer — provide it to a runtime to run the queue on :3001The scheduler runtime runs Digest every hour, and each run enqueues into Emails — a queue that lives on the other runtime, reached by port:
const const scheduler: anyconst scheduler: {
build: (memoMap: Layer.MemoMap, scope: Scope) => Effect.Effect<Context<Digest | Hyperlink.Local<Digest> | Storage>, never, never>;
pipe: { <A>(this: A): A; <A, B = never>(this: A, ab: (_: A) => B): B; <A, B = never, C = never>(this: A, ab: (_: A) => B, bc: (_: B) => C): C; <A, B = never, C = never, D = never>(this: A, ab: (_: A) => B, bc: (_: B) => C, cd: (_: C) => D): D; <…;
}
scheduler = import ProcessProcess.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>;
};
groupId: string;
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, {
effect: Effect.Effect<void, unknown, unknown>(property) 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: <any, void>(f: () => Generator<any, void, never>) => Effect.Effect<void, unknown, unknown> (+1 overload)Provides a way to write effectful code using generator functions, simplifying
control flow and error handling.
When to use
Use when you want to write effectful code that looks and behaves like
synchronous code, while still handling asynchronous tasks, errors, and complex
control flow such as loops and conditions.
Generator functions work similarly to async/await but keep errors,
requirements, and interruption in the Effect type. You can yield* values
from effects and return the final result at the end.
Example (Sequencing effects with generators)
import { Data, Effect } from "effect"
class DiscountRateError extends Data.TaggedError("DiscountRateError")<{}> {}
const addServiceCharge = (amount: number) => amount + 1
const applyDiscount = (
total: number,
discountRate: number
): Effect.Effect<number, DiscountRateError> =>
discountRate === 0
? Effect.fail(new DiscountRateError())
: Effect.succeed(total - (total * discountRate) / 100)
const fetchTransactionAmount = Effect.promise(() => Promise.resolve(100))
const fetchDiscountRate = Effect.promise(() => Promise.resolve(5))
export const program = Effect.gen(function*() {
const transactionAmount = yield* fetchTransactionAmount
const discountRate = yield* fetchDiscountRate
const discountedAmount = yield* applyDiscount(
transactionAmount,
discountRate
)
const finalAmount = addServiceCharge(discountedAmount)
return `Final amount to charge: ${finalAmount}`
})
gen(function* () {
const const emails: anyconst 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>>;
};
groupId: string;
description: string | undefined;
of: (this: void, self: QueueHyperlink.QueueHyperlink<{ to: string }, void, never, never>) => QueueHyperlink.QueueHyperlink<{ to: string }, void, never, never>;
context: (self: QueueHyperlink.QueueHyperlink<{ to: string }, void, never, never>) => Context<Emails>;
use: (f: (service: QueueHyperlink.QueueHyperlink<{ to: string }, void, never, never>) => Effect.Effect<A, E, R>) => Effect.Effect<A, E, Emails | R>;
useSync: (f: (service: QueueHyperlink.QueueHyperlink<{ 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 // emails: the Emails handle (here, an RPC client)
const const email: Schema.Struct.ReadonlySide<
{
readonly to: Schema.String
},
"Type"
>
const email: {
to: string;
}
email = yield* const nextEmail: Effect.Effect<
Schema.Struct.ReadonlySide<
{
readonly to: Schema.String
},
"Type"
>,
never,
never
>
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 // email: EmailJob
yield* const emails: anyconst 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.add(const email: Schema.Struct.ReadonlySide<
{
readonly to: Schema.String
},
"Type"
>
const email: {
to: string;
}
email) // add(email: EmailJob): Effect<void>
}),
polling: anypolling: import PollingPolling.spaced(import DurationDuration.const hours: (
hours: number
) => Duration.Duration
Creates 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)),
}).pipe(import LayerLayer.const provide: <any>(that: any) => <A, E, R>(self: Layer.Layer<A, E, R>) => Layer.Layer<A, any, any> (+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.connect(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>>;
};
groupId: string;
description: string | undefined;
of: (this: void, self: QueueHyperlink.QueueHyperlink<{ to: string }, void, never, never>) => QueueHyperlink.QueueHyperlink<{ to: string }, void, never, never>;
context: (self: QueueHyperlink.QueueHyperlink<{ to: string }, void, never, never>) => Context<Emails>;
use: (f: (service: QueueHyperlink.QueueHyperlink<{ to: string }, void, never, never>) => Effect.Effect<A, E, R>) => Effect.Effect<A, E, Emails | R>;
useSync: (f: (service: QueueHyperlink.QueueHyperlink<{ 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, import HyperlinkHyperlink.protocolHttp(3001))))
// scheduler: Layer — the scheduler runtimeDigest runs on the scheduler, Emails on the worker — yet inside the process, yield* Emails and emails.add(…) read exactly as if the two shared one process. Two Hyperlinks, two runtimes, one program. Move a runtime to another machine and only its port becomes a url — nothing else changes.
Operate them live
A cross-runtime Service isnt just callable across runtimes — its operable across them. The same Handle that enqueues also controls and observes, so you steer and inspect the workers queue from anywhere its reached:
const const emails: anyconst 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>>;
};
groupId: string;
description: string | undefined;
of: (this: void, self: QueueHyperlink.QueueHyperlink<{ to: string }, void, never, never>) => QueueHyperlink.QueueHyperlink<{ to: string }, void, never, never>;
context: (self: QueueHyperlink.QueueHyperlink<{ to: string }, void, never, never>) => Context<Emails>;
use: (f: (service: QueueHyperlink.QueueHyperlink<{ to: string }, void, never, never>) => Effect.Effect<A, E, R>) => Effect.Effect<A, E, Emails | R>;
useSync: (f: (service: QueueHyperlink.QueueHyperlink<{ 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 // emails: the Emails handle — local OR an RPC client, same type
yield* const emails: anyconst 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.pause // pause: Effect<void> — stop draining, at runtime
const const depth: anydepth = yield* const emails: anyconst 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.size.get // depth: number — how many are waiting, right now
yield* const emails: anyconst 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.events.pipe(import StreamStream.const runForEach: <unknown, void, never, never>(f: (a: unknown) => Effect.Effect<void, never, never>) => <E, R>(self: Stream.Stream<unknown, E, R>) => Effect.Effect<void, E, R> (+1 overload)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)) // events: Stream<QueueEvent> — every change, live
And it comes with dashboards over the same Tag — a pm CLI, a TUI, and a web dashboard — each reading the Hyperlink without ever touching its Implementation.
Working with peers
The same Tag also lets a Hyperlink reach its peers — its own other instances — and coordinate with them. Take sessions sharded across droplets: each Node holds the entries it owns, and a lookup for someone elses session is forwarded to the Node that owns it. ShardMap is that pattern as a Hyperlink factory — schemas on the Tag, routed ops, leaf shards, fleet sizes.
class class SessionsSessions extends import ShardMapShardMap.Tag<class SessionsSessions>()("app/Sessions", {
key: Schema.Stringkey: const SessionId: Schema.StringSessionId,
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,
keyOf: (s: any) => anykeyOf: (s: anys) => s: anys.id,
}).pipe(
import HyperlinkHyperlink.distributed([class DropletEastDropletEast, class DropletWestDropletWest, class DropletCentralDropletCentral]),
) {}Serve a droplet with the mesh discharge — local shard + peer clients from one materialization:
const const east: anyeast = import ShardMapShardMap.serve(class SessionsSessions).pipe(
import LayerLayer.const provide: <any>(that: any) => <A, E, R>(self: Layer.Layer<A, E, R>) => Layer.Layer<A, any, any> (+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.peersLayer(class SessionsSessions, class DropletEastDropletEast)),
const nodeServer: (port: number) => <A, E, R>(resource: Layer.Layer<A, E, R>) => anynodeServer(3001),
)From any Node, a caller just asks — ownership and the cross-Node hop stay inside the Hyperlink:
const const program: Effect.Effect<
void,
unknown,
unknown
>
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: <any, void>(f: () => Generator<any, void, never>) => Effect.Effect<void, unknown, unknown> (+1 overload)Provides a way to write effectful code using generator functions, simplifying
control flow and error handling.
When to use
Use when you want to write effectful code that looks and behaves like
synchronous code, while still handling asynchronous tasks, errors, and complex
control flow such as loops and conditions.
Generator functions work similarly to async/await but keep errors,
requirements, and interruption in the Effect type. You can yield* values
from effects and return the final result at the end.
Example (Sequencing effects with generators)
import { Data, Effect } from "effect"
class DiscountRateError extends Data.TaggedError("DiscountRateError")<{}> {}
const addServiceCharge = (amount: number) => amount + 1
const applyDiscount = (
total: number,
discountRate: number
): Effect.Effect<number, DiscountRateError> =>
discountRate === 0
? Effect.fail(new DiscountRateError())
: Effect.succeed(total - (total * discountRate) / 100)
const fetchTransactionAmount = Effect.promise(() => Promise.resolve(100))
const fetchDiscountRate = Effect.promise(() => Promise.resolve(5))
export const program = Effect.gen(function*() {
const transactionAmount = yield* fetchTransactionAmount
const discountRate = yield* fetchDiscountRate
const discountedAmount = yield* applyDiscount(
transactionAmount,
discountRate
)
const finalAmount = addServiceCharge(discountedAmount)
return `Final amount to charge: ${finalAmount}`
})
gen(function* () {
const const sessions: anyconst sessions: {
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>;
}
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>;
};
groupId: string;
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: anysession = yield* const sessions: anyconst sessions: {
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>;
}
sessions.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.
Build your own
Everything so far — Emails, Digest, Sessions — is built on one primitive you use directly. A Hyperlink is a Contract plus an Implementation, and its first-class, not an escape hatch.
Describe the Contract — methods and their schemas:
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<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: anyvalue: import HyperlinkHyperlink.ref(import SchemaSchema.const Number: Schema.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?: Schema.MakeOptions) => number;
makeOption: (input: number, options?: Schema.MakeOptions) => Option<number>;
makeEffect: (input: number, options?: Schema.MakeOptions) => Effect<number, Schema.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), // an observable value — get + live changes
increment: any(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.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?: Schema.MakeOptions) => number;
makeOption: (input: number, options?: Schema.MakeOptions) => Option<number>;
makeEffect: (input: number, options?: Schema.MakeOptions) => Effect<number, Schema.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: Schema.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?: Schema.MakeOptions) => number;
makeOption: (input: number, options?: Schema.MakeOptions) => Option<number>;
makeEffect: (input: number, options?: Schema.MakeOptions) => Effect<number, Schema.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: any(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.effect(import SchemaSchema.const Void: Schema.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?: Schema.MakeOptions) => void;
makeOption: (input: void, options?: Schema.MakeOptions) => Option<void>;
makeEffect: (input: void, options?: Schema.MakeOptions) => Effect<void, Schema.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),
}) {}Give it an Implementation:
const const counterImpl: Effect.Effect<
{
value: any
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: <Effect.Effect<SubscriptionRef.SubscriptionRef<number>, never, never>, {
value: any;
increment: ({ by }: {
by: number;
}) => Effect.Effect<void, never, never>;
reset: Effect.Effect<void, never, never>;
}>(f: () => Generator<Effect.Effect<SubscriptionRef.SubscriptionRef<number>, never, never>, {
value: any;
increment: ({ by }: {
by: number;
}) => Effect.Effect<void, never, never>;
reset: Effect.Effect<void, never, never>;
}, never>) => Effect.Effect<{
value: any;
increment: ({ by }: {
by: number;
}) => Effect.Effect<void, never, never>;
reset: Effect.Effect<void, never, never>;
}, never, never> (+1 overload)
Provides a way to write effectful code using generator functions, simplifying
control flow and error handling.
When to use
Use when you want to write effectful code that looks and behaves like
synchronous code, while still handling asynchronous tasks, errors, and complex
control flow such as loops and conditions.
Generator functions work similarly to async/await but keep errors,
requirements, and interruption in the Effect type. You can yield* values
from effects and return the final result at the end.
Example (Sequencing effects with generators)
import { Data, Effect } from "effect"
class DiscountRateError extends Data.TaggedError("DiscountRateError")<{}> {}
const addServiceCharge = (amount: number) => amount + 1
const applyDiscount = (
total: number,
discountRate: number
): Effect.Effect<number, DiscountRateError> =>
discountRate === 0
? Effect.fail(new DiscountRateError())
: Effect.succeed(total - (total * discountRate) / 100)
const fetchTransactionAmount = Effect.promise(() => Promise.resolve(100))
const fetchDiscountRate = Effect.promise(() => Promise.resolve(5))
export const program = Effect.gen(function*() {
const transactionAmount = yield* fetchTransactionAmount
const discountRate = yield* fetchDiscountRate
const discountedAmount = yield* applyDiscount(
transactionAmount,
discountRate
)
const finalAmount = addServiceCharge(discountedAmount)
return `Final amount to charge: ${finalAmount}`
})
gen(function* () {
const const 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: <number>(
value: number
) => Effect.Effect<
SubscriptionRef.SubscriptionRef<number>,
never,
never
>
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: any(property) value: {
get: Effect.Effect<A>;
changes: Stream.Stream<A>;
}
value: import HyperlinkHyperlink.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), // surface the ref as the observable field
increment: ({
by,
}: {
by: number
}) => Effect.Effect<void, never, never>
increment: ({ by: numberby }: { by: numberby: number }) => import SubscriptionRefSubscriptionRef.const update: <number>(self: SubscriptionRef.SubscriptionRef<number>, update: (a: number) => number) => Effect.Effect<void> (+1 overload)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: <number>(self: SubscriptionRef.SubscriptionRef<number>, value: number) => Effect.Effect<void> (+1 overload)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),
}
})Thats it — its now a cross-runtime Service like any built-in. The same Tag, provided the same three ways:
import HyperlinkHyperlink.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>;
};
groupId: string;
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: any
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 HyperlinkHyperlink.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>;
};
groupId: string;
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: any
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).pipe(const nodeServer: (
port: number
) => <A, E, R>(
resource: Layer.Layer<A, E, R>
) => Layer.Layer<A, E, R>
nodeServer(4000)) // served over RPC
import HyperlinkHyperlink.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>;
};
groupId: string;
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.protocolHttp(4000)) // reached from another runtimeAnd it gets the rest for free — the live value, runtime control, and a slot in the pm CLI, TUI, and web dashboards — because its the same kind of thing Emails is.
The included types
You dont start from scratch, either — the types you reach for most ship ready-made, each a cross-runtime Service you use like an Effect primitive:
Long-running processes (
Process) — continuous or recurring work: a polling cadence, arm/disarm schedule windows, execution history, and more.Queue (
QueueHyperlink) — a priority work queue: enqueue items, workers drain them with dedup, retry, and concurrency control; durable when you provide a store.Shard map (
ShardMap) — partitioned key/value across a fleet: routedget/put/delete, leaf shards, and fleet size folds via peers.