<Self, S extends Spec>(
tag: HyperlinkTag<Self, S>,
options?: LookupClientOptions
): Layer.Layer<
Self,
LookupClientError,
LookupIdentity | LookupDirectory | LookupAdvice
>Lookup-resolved nodeless client (D7/D4) — you do not pass a Node; Lookup
chooses the dial target. Contrast client(Tag, node), where you name the Node.
Resolution order: Lookup.Identityresolve(tag.key), else
Lookup.DirectorynodesServing(tag.key).
Fail-closed by default: missing or more than one directory row →
LookupClientError. When N>1, a live Lookup.Advice prefer that
matches a directory row wins before D4 { pick }. Opt into soft pick with
{ pick: "first" } or a sync (rows) => DirectoryEntry. Identity resolve
ignores advice / pick (unique by key).
Hot-rebind: after the initial resolve, watches Lookup.Directorychanges
and rebuilds the dial when the chosen endpoint moves (dialChanged) or membership
for this service key changes — same plane as directory peersLayer. A brief
gap keeps the previous client (calls may fail until the new row appears).
Bake name sketch was unsafeLookupClient (“trust Lookup or die”); bare
lookupClient(Tag) keeps that fail-closed contract when advice is absent/stale.
// Sole endpoint (identity winner or one directory row):
Hyperlink.lookupClient(Mail).pipe(Layer.provide(Lookup.layer))
// Coordinator published advice — bare client honors prefer:
yield* Lookup.advise({ serviceKey: Mail.key, prefer: "fleet/Mail#w2" })
Hyperlink.lookupClient(Mail)
// N>1 replicas — opt-in pick when no advice (still fail on 0):
Hyperlink.lookupClient(Mail, { pick: "first" })
// You already know an addressed Node — client auto-connects:
Hyperlink.client(Mail, East)export const const lookupClient: <
Self,
S extends Spec
>(
tag: HyperlinkTag<Self, S>,
options?: LookupClientOptions
) => Layer.Layer<
Self,
LookupClientError,
LookupIdentity | LookupDirectory | LookupAdvice
>
Lookup-resolved nodeless client (D7/D4) — you do not pass a
Node
; Lookup
chooses the dial target. Contrast
client
(Tag, node), where you name the Node.
Resolution order:
Lookup.Identity
resolve(tag.key), else
Lookup.Directory
nodesServing(tag.key).
Fail-closed by default: missing or more than one directory row →
LookupClientError
. When N>1, a live
Lookup.Advice
prefer that
matches a directory row wins before D4 { pick }. Opt into soft pick with
{ pick: "first" } or a sync (rows) => DirectoryEntry. Identity resolve
ignores advice / pick (unique by key).
Hot-rebind: after the initial resolve, watches
Lookup.Directory
changes
and rebuilds the dial when the chosen endpoint moves (dialChanged) or membership
for this service key changes — same plane as directory
peersLayer
. A brief
gap keeps the previous client (calls may fail until the new row appears).
Bake name sketch was unsafeLookupClient (“trust Lookup or die”); bare
lookupClient(Tag) keeps that fail-closed contract when advice is absent/stale.
// Sole endpoint (identity winner or one directory row):
Hyperlink.lookupClient(Mail).pipe(Layer.provide(Lookup.layer))
// Coordinator published advice — bare client honors prefer:
yield* Lookup.advise({ serviceKey: Mail.key, prefer: "fleet/Mail#w2" })
Hyperlink.lookupClient(Mail)
// N>1 replicas — opt-in pick when no advice (still fail on 0):
Hyperlink.lookupClient(Mail, { pick: "first" })
// You already know an addressed Node — client auto-connects:
Hyperlink.client(Mail, East)
lookupClient = <function (type parameter) Self in <Self, S extends Spec>(tag: HyperlinkTag<Self, S>, options?: LookupClientOptions): Layer.Layer<Self, LookupClientError, LookupIdentity | LookupDirectory | LookupAdvice>Self, function (type parameter) S in <Self, S extends Spec>(tag: HyperlinkTag<Self, S>, options?: LookupClientOptions): Layer.Layer<Self, LookupClientError, LookupIdentity | LookupDirectory | LookupAdvice>S extends Spec>(
tag: HyperlinkTag<Self, S>(parameter) tag: {
description: string | undefined;
key: Identifier;
of: (this: void, self: Simplify<{ readonly [K in keyof S]: S[K] extends FromLocalMethod<infer M> ? InjectLocal<M, Self> : S[K] extends LocalMethod<infer T> ? LocalEffect<T, never, Self> : S[K] extends DefaultMethod<infer F> ? F : S[K] extends …;
context: (self: Simplify<{ readonly [K in keyof S]: S[K] extends FromLocalMethod<infer M> ? InjectLocal<M, Self> : S[K] extends LocalMethod<infer T> ? LocalEffect<T, never, Self> : S[K] extends DefaultMethod<infer F> ? F : S[K] extends { readonly _…;
use: (f: (service: Simplify<{ readonly [K in keyof S]: S[K] extends FromLocalMethod<infer M> ? InjectLocal<M, Self> : S[K] extends LocalMethod<infer T> ? LocalEffect<T, never, Self> : S[K] extends DefaultMethod<infer F> ? F : S[K] extends { rea…;
useSync: (f: (service: Simplify<{ readonly [K in keyof S]: S[K] extends FromLocalMethod<infer M> ? InjectLocal<M, Self> : S[K] extends LocalMethod<infer T> ? LocalEffect<T, never, Self> : S[K] extends DefaultMethod<infer F> ? F : S[K] extends { rea…;
Identifier: Identifier;
Service: Shape;
stack: string | undefined;
pipe: { <A>(this: A): A; <A, B = never>(this: A, ab: (_: A) => B): B; <A, B = never, C = never>(this: A, ab: (_: A) => B, bc: (_: B) => C): C; <A, B = never, C = never, D = never>(this: A, ab: (_: A) => B, bc: (_: B) => C, cd: (_: C) => D): D; <…;
toString: () => string;
toJSON: () => unknown;
}
tag: interface HyperlinkTag<Self, S extends Spec, Svc = Simplify<{ readonly [K in keyof S]: S[K] extends FromLocalMethod<infer M> ? InjectLocal<M, Self> : S[K] extends LocalMethod<infer T> ? LocalEffect<...> : S[K] extends DefaultMethod<...> ? F : S[K] extends { ...; } ? SuccessOf<...> : S[K] extends { ...; } ? Subscribable<...> : S[K] extends { ...; } ? ClientMethod<...> : S[K] extends Spec ? Simplify<...> : never; }>>The type of a HyperService tag carrying spec S — what
Hyperlink.Tag
produces
(and what you extend). Lets a consumer write
<S extends Spec>(tag: HyperlinkTag<Self, S>) and read the spec through named types
(
specOf
/
groupOf
) instead of a Parameters<typeof specOf> workaround.
HyperlinkTag<function (type parameter) Self in <Self, S extends Spec>(tag: HyperlinkTag<Self, S>, options?: LookupClientOptions): Layer.Layer<Self, LookupClientError, LookupIdentity | LookupDirectory | LookupAdvice>Self, function (type parameter) S in <Self, S extends Spec>(tag: HyperlinkTag<Self, S>, options?: LookupClientOptions): Layer.Layer<Self, LookupClientError, LookupIdentity | LookupDirectory | LookupAdvice>S>,
options: LookupClientOptionsoptions?: type LookupClientOptions = {
readonly pick?: LookupClientPick
}
Options for
lookupClient
— opt-in soft pick when the directory is ambiguous
and placement advice is absent or stale.
LookupClientOptions,
): 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) Self in <Self, S extends Spec>(tag: HyperlinkTag<Self, S>, options?: LookupClientOptions): Layer.Layer<Self, LookupClientError, LookupIdentity | LookupDirectory | LookupAdvice>Self,
class LookupClientErrorclass LookupClientError {
name: string;
message: string;
stack: string;
cause: unknown;
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;
_tag: Tag;
tag: string;
reason: 'missing' | 'ambiguous';
count: number;
}
lookupClient
could not resolve exactly one dial target for the Tag
(missing = none; ambiguous = more than one directory row and no
LookupClientOptions.pick
).
LookupClientError,
class LookupIdentityclass LookupIdentity {
key: Identifier;
Service: {
claim: (payload: { kind: 'Http' | 'WebSocket' | 'IpcSocket'; key: string; nodeKey: string; url?: string | undefined; path?: string | undefined }) => Effect.Effect<Endpoint, DuplicateIdentity, never>;
resolve: (payload: { key: string }) => Effect.Effect<Option.Option<Endpoint>, never, never>;
};
}
Lookup identity service — claim HyperService keys (first wins; dead winners replaceable).
LookupIdentity | class LookupDirectoryclass LookupDirectory {
key: Identifier;
Service: {
advertise: (payload: { kind: 'Http' | 'WebSocket' | 'IpcSocket'; nodeKey: string; serves: ReadonlyArray<string>; url?: string | undefined; path?: string | undefined; onConflict?: 'livenessReplace' | 'askIncumbent' | 'reject' | 'inherit' | undefined }…;
unregister: (payload: { nodeKey: string; kind?: 'Http' | 'WebSocket' | 'IpcSocket' | undefined; url?: string | undefined; path?: string | undefined }) => Effect.Effect<boolean, never, never>;
nodesServing: (payload: { serviceKey: string }) => Effect.Effect<ReadonlyArray<LookupDirectoryEntry>, never, never>;
changes: Stream.Stream<DirectoryUpserted | DirectoryRemoved, never, never>;
};
}
Lookup node directory — advertise / unregister / list by served HyperService key /
directorySpec.changes
membership push.
LookupDirectory | class LookupAdviceclass LookupAdvice {
key: Identifier;
Service: {
advise: (payload: { serviceKey: string; prefer: string }) => Effect.Effect<string, never, never>;
clear: (payload: { serviceKey: string }) => Effect.Effect<boolean, never, never>;
preferred: (payload: { serviceKey: string }) => Effect.Effect<Option.Option<string>, never, never>;
};
}
Lookup placement board — coordinator advice for nodeless /
Hyperlink.lookupClient
dial.
v1: last-write-wins, in-memory, no advisor ACL. Algorithms stay app-owned (who calls
advise
); Lookup only stores and surfaces the preference.
LookupAdvice
> =>
import LayerLayer.const effect: {
<I, S>(service: Context.Key<I, S>): <E, R>(
effect: Effect<S, E, R>
) => Layer<I, E, Exclude<R, Scope.Scope>>
<I, S, E, R>(
service: Context.Key<I, S>,
effect: Effect<Types.NoInfer<S>, E, R>
): Layer<I, E, Exclude<R, Scope.Scope>>
}
Constructs a layer from an effect that produces a single service.
When to use
Use when you need to construct a Layer-provided service with an Effect,
dependencies, or scoped resource acquisition.
Details
This allows you to create a Layer from an Effect that produces a service.
The Effect is executed in the scope of the layer, allowing for proper
resource management.
Example (Creating a layer from an effect)
import { Context, Effect, Layer } from "effect"
class Database extends Context.Service<Database, {
readonly query: (sql: string) => Effect.Effect<string>
}>()("Database") {}
const layer = Layer.effect(Database,
Effect.sync(() => ({
query: (sql: string) => Effect.succeed(`Query: ${sql}`)
}))
)
effect(
tag: HyperlinkTag<Self, S>(parameter) tag: {
description: string | undefined;
key: Identifier;
of: (this: void, self: Simplify<{ readonly [K in keyof S]: S[K] extends FromLocalMethod<infer M> ? InjectLocal<M, Self> : S[K] extends LocalMethod<infer T> ? LocalEffect<T, never, Self> : S[K] extends DefaultMethod<infer F> ? F : S[K] extends …;
context: (self: Simplify<{ readonly [K in keyof S]: S[K] extends FromLocalMethod<infer M> ? InjectLocal<M, Self> : S[K] extends LocalMethod<infer T> ? LocalEffect<T, never, Self> : S[K] extends DefaultMethod<infer F> ? F : S[K] extends { readonly _…;
use: (f: (service: Simplify<{ readonly [K in keyof S]: S[K] extends FromLocalMethod<infer M> ? InjectLocal<M, Self> : S[K] extends LocalMethod<infer T> ? LocalEffect<T, never, Self> : S[K] extends DefaultMethod<infer F> ? F : S[K] extends { rea…;
useSync: (f: (service: Simplify<{ readonly [K in keyof S]: S[K] extends FromLocalMethod<infer M> ? InjectLocal<M, Self> : S[K] extends LocalMethod<infer T> ? LocalEffect<T, never, Self> : S[K] extends DefaultMethod<infer F> ? F : S[K] extends { rea…;
Identifier: Identifier;
Service: Shape;
stack: string | undefined;
pipe: { <A>(this: A): A; <A, B = never>(this: A, ab: (_: A) => B): B; <A, B = never, C = never>(this: A, ab: (_: A) => B, bc: (_: B) => C): C; <A, B = never, C = never, D = never>(this: A, ab: (_: A) => B, bc: (_: B) => C, cd: (_: C) => D): D; <…;
toString: () => string;
toJSON: () => unknown;
}
tag,
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 Lookup: typeof import("/Users/nikolasstow/Coding/Hyperlink/worktrees/epsilon/src/Lookup")const Lookup: {
resolveOnConflict: (...prefs: ReadonlyArray<OnConflict | undefined>) => OnConflictResolved;
Endpoint: typeof Endpoint;
ClaimRequest: typeof ClaimRequest;
ResolveRequest: typeof ResolveRequest;
DuplicateIdentity: typeof DuplicateIdentity;
DirectoryEntry: typeof LookupDirectoryEntry;
DirectoryUpserted: typeof DirectoryUpserted;
DirectoryRemoved: typeof DirectoryRemoved;
DirectoryChange: Schema.Union<readonly [typeof DirectoryUpserted, typeof DirectoryRemoved]>;
AdvertiseRequest: typeof AdvertiseRequest;
UnregisterRequest: typeof UnregisterRequest;
NodesServingRequest: typeof NodesServingRequest;
IncumbentAlive: typeof IncumbentAlive;
AdviseRequest: typeof AdviseRequest;
ClearAdviceRequest: typeof ClearAdviceRequest;
PreferredRequest: typeof PreferredRequest;
LookupUnaddressed: typeof LookupUnaddressed;
kind: 'hyperlink-ts/Lookup';
Identity: typeof LookupIdentity;
Directory: typeof LookupDirectory;
Advice: typeof LookupAdvice;
nodesServing: (service: string | { readonly key: string }) => Effect.Effect<ReadonlyArray<DirectoryEntry>, never, Directory>;
advise: (input: { readonly serviceKey: string; readonly prefer: string }) => Effect.Effect<string, never, Advice>;
prefer: (service: string | { readonly key: string }, nodeKey: string) => Effect.Effect<string, never, Advice>;
preferEntry: (service: string | { readonly key: string }, entry: { readonly nodeKey: string }) => Effect.Effect<string, never, Advice>;
clearAdvice: (service: string | { readonly key: string }) => Effect.Effect<boolean, never, Advice>;
preferred: (service: string | { readonly key: string }) => Effect.Effect<Option.Option<string>, never, Advice>;
changes: Stream.Stream<DirectoryChange, never, Directory>;
directoryTable: () => Effect.Effect<{ readonly get: Effect.Effect<ReadonlyMap<string, DirectoryEntry>>; readonly getNode: (nodeKey: string) => Effect.Effect<Option.Option<DirectoryEntry>> }, never, Directory | Scope.Scope>;
defaultIpcPath: '/tmp/hyperlink-ts-lookup.sock';
layerIpc: (path: string, options?: { readonly unlink?: boolean; readonly onConflict?: OnConflictResolved }) => Layer.Layer<Identity | Directory | Advice | Hyperlink.Local<Identity> | Handler<'claim'> | Handler<'resolve'> | Hyperlink.Local<Directory>…;
layerNode: (node: AnyNode & { readonly key: string }, options?: { readonly unlink?: boolean }) => Layer.Layer<never, LookupUnaddressed>;
directoryAdvertiseLayer: (node: AnyNode & { readonly key: string }, serves: ReadonlyArray<string>, options?: { readonly onConflict?: OnConflict }) => Layer.Layer<never, IncumbentAlive>;
client: (node: AnyNode & { readonly path?: string }) => Layer.Layer<Services, LookupUnaddressed>;
clientOptions: (options?: { readonly path?: string }) => Layer.Layer<Services, LookupUnaddressed>;
layerOptions: (options?: { readonly path?: string; readonly unlink?: boolean }) => Layer.Layer<Services>;
layer: Layer.Layer<Services>;
}
Lookup = yield* import EffectEffect.const promise: <A>(
evaluate: (
signal: AbortSignal
) => PromiseLike<A>
) => Effect<A>
Creates an Effect that represents an asynchronous computation guaranteed to
succeed.
When to use
Use to convert a Promise into an Effect when the async operation is
guaranteed to succeed and will not reject.
Details
An optional AbortSignal can be provided to allow for interruption of the
wrapped Promise API.
Gotchas
The Promise must not reject. If it rejects, the rejection is treated as a
defect, not as a typed failure. Use tryPromise when rejection is expected.
Interruption aborts the provided AbortSignal, but the underlying
asynchronous operation only stops if it observes that signal.
Example (Wrapping a non-rejecting Promise)
import { Effect } from "effect"
const delay = (message: string) =>
Effect.promise<string>(
() =>
new Promise((resolve) => {
setTimeout(() => {
resolve(message)
}, 2000)
})
)
// ┌─── Effect<string, never, never>
// ▼
const program = delay("Async operation completed successfully!")
promise(() => import("./Lookup"));
const const identity: {
readonly claim: (payload: {
kind: "Http" | "WebSocket" | "IpcSocket"
key: string
nodeKey: string
url?: string | undefined
path?: string | undefined
}) => Effect.Effect<
Endpoint,
DuplicateIdentity,
never
>
readonly resolve: (payload: {
key: string
}) => Effect.Effect<
Option.Option<Endpoint>,
never,
never
>
}
identity = yield* const Lookup: typeof import("/Users/nikolasstow/Coding/Hyperlink/worktrees/epsilon/src/Lookup")const Lookup: {
resolveOnConflict: (...prefs: ReadonlyArray<OnConflict | undefined>) => OnConflictResolved;
Endpoint: typeof Endpoint;
ClaimRequest: typeof ClaimRequest;
ResolveRequest: typeof ResolveRequest;
DuplicateIdentity: typeof DuplicateIdentity;
DirectoryEntry: typeof LookupDirectoryEntry;
DirectoryUpserted: typeof DirectoryUpserted;
DirectoryRemoved: typeof DirectoryRemoved;
DirectoryChange: Schema.Union<readonly [typeof DirectoryUpserted, typeof DirectoryRemoved]>;
AdvertiseRequest: typeof AdvertiseRequest;
UnregisterRequest: typeof UnregisterRequest;
NodesServingRequest: typeof NodesServingRequest;
IncumbentAlive: typeof IncumbentAlive;
AdviseRequest: typeof AdviseRequest;
ClearAdviceRequest: typeof ClearAdviceRequest;
PreferredRequest: typeof PreferredRequest;
LookupUnaddressed: typeof LookupUnaddressed;
kind: 'hyperlink-ts/Lookup';
Identity: typeof LookupIdentity;
Directory: typeof LookupDirectory;
Advice: typeof LookupAdvice;
nodesServing: (service: string | { readonly key: string }) => Effect.Effect<ReadonlyArray<DirectoryEntry>, never, Directory>;
advise: (input: { readonly serviceKey: string; readonly prefer: string }) => Effect.Effect<string, never, Advice>;
prefer: (service: string | { readonly key: string }, nodeKey: string) => Effect.Effect<string, never, Advice>;
preferEntry: (service: string | { readonly key: string }, entry: { readonly nodeKey: string }) => Effect.Effect<string, never, Advice>;
clearAdvice: (service: string | { readonly key: string }) => Effect.Effect<boolean, never, Advice>;
preferred: (service: string | { readonly key: string }) => Effect.Effect<Option.Option<string>, never, Advice>;
changes: Stream.Stream<DirectoryChange, never, Directory>;
directoryTable: () => Effect.Effect<{ readonly get: Effect.Effect<ReadonlyMap<string, DirectoryEntry>>; readonly getNode: (nodeKey: string) => Effect.Effect<Option.Option<DirectoryEntry>> }, never, Directory | Scope.Scope>;
defaultIpcPath: '/tmp/hyperlink-ts-lookup.sock';
layerIpc: (path: string, options?: { readonly unlink?: boolean; readonly onConflict?: OnConflictResolved }) => Layer.Layer<Identity | Directory | Advice | Hyperlink.Local<Identity> | Handler<'claim'> | Handler<'resolve'> | Hyperlink.Local<Directory>…;
layerNode: (node: AnyNode & { readonly key: string }, options?: { readonly unlink?: boolean }) => Layer.Layer<never, LookupUnaddressed>;
directoryAdvertiseLayer: (node: AnyNode & { readonly key: string }, serves: ReadonlyArray<string>, options?: { readonly onConflict?: OnConflict }) => Layer.Layer<never, IncumbentAlive>;
client: (node: AnyNode & { readonly path?: string }) => Layer.Layer<Services, LookupUnaddressed>;
clientOptions: (options?: { readonly path?: string }) => Layer.Layer<Services, LookupUnaddressed>;
layerOptions: (options?: { readonly path?: string; readonly unlink?: boolean }) => Layer.Layer<Services>;
layer: Layer.Layer<Services>;
}
Lookup.class Identityclass Identity {
key: Identifier;
Service: {
claim: (payload: { kind: 'Http' | 'WebSocket' | 'IpcSocket'; key: string; nodeKey: string; url?: string | undefined; path?: string | undefined }) => Effect.Effect<Endpoint, DuplicateIdentity, never>;
resolve: (payload: { key: string }) => Effect.Effect<Option.Option<Endpoint>, never, never>;
};
description: string | undefined;
of: (this: void, self: { readonly claim: (payload: { kind: 'Http' | 'WebSocket' | 'IpcSocket'; key: string; nodeKey: string; url?: string | undefined; path?: string | undefined }) => Effect.Effect<Endpoint, DuplicateIdentity, never>; readonly …;
context: (self: { readonly claim: (payload: { kind: 'Http' | 'WebSocket' | 'IpcSocket'; key: string; nodeKey: string; url?: string | undefined; path?: string | undefined }) => Effect.Effect<Endpoint, DuplicateIdentity, never>; readonly resolve: (pa…;
use: (f: (service: { readonly claim: (payload: { kind: 'Http' | 'WebSocket' | 'IpcSocket'; key: string; nodeKey: string; url?: string | undefined; path?: string | undefined }) => Effect.Effect<Endpoint, DuplicateIdentity, never>; readonly resol…;
useSync: (f: (service: { readonly claim: (payload: { kind: 'Http' | 'WebSocket' | 'IpcSocket'; key: string; nodeKey: string; url?: string | undefined; path?: string | undefined }) => Effect.Effect<Endpoint, DuplicateIdentity, never>; readonly resol…;
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;
}
Lookup identity service — claim HyperService keys (first wins; dead winners replaceable).
Identity;
const const directory: {
readonly advertise: (payload: {
kind: "Http" | "WebSocket" | "IpcSocket";
nodeKey: string;
serves: readonly string[];
url?: string | undefined;
path?: string | undefined;
onConflict?: "livenessReplace" | "askIncumbent" | "reject" | "inherit" | undefined;
}) => Effect.Effect<LookupDirectoryEntry, IncumbentAlive, never>;
readonly unregister: (payload: {
nodeKey: string;
kind?: "Http" | "WebSocket" | "IpcSocket" | undefined;
url?: string | undefined;
path?: string | undefined;
}) => Effect.Effect<boolean, never, never>;
readonly nodesServing: (payload: {
...;
}) => Effect.Effect<...>;
readonly changes: Stream.Stream<...>;
}
directory = yield* const Lookup: typeof import("/Users/nikolasstow/Coding/Hyperlink/worktrees/epsilon/src/Lookup")const Lookup: {
resolveOnConflict: (...prefs: ReadonlyArray<OnConflict | undefined>) => OnConflictResolved;
Endpoint: typeof Endpoint;
ClaimRequest: typeof ClaimRequest;
ResolveRequest: typeof ResolveRequest;
DuplicateIdentity: typeof DuplicateIdentity;
DirectoryEntry: typeof LookupDirectoryEntry;
DirectoryUpserted: typeof DirectoryUpserted;
DirectoryRemoved: typeof DirectoryRemoved;
DirectoryChange: Schema.Union<readonly [typeof DirectoryUpserted, typeof DirectoryRemoved]>;
AdvertiseRequest: typeof AdvertiseRequest;
UnregisterRequest: typeof UnregisterRequest;
NodesServingRequest: typeof NodesServingRequest;
IncumbentAlive: typeof IncumbentAlive;
AdviseRequest: typeof AdviseRequest;
ClearAdviceRequest: typeof ClearAdviceRequest;
PreferredRequest: typeof PreferredRequest;
LookupUnaddressed: typeof LookupUnaddressed;
kind: 'hyperlink-ts/Lookup';
Identity: typeof LookupIdentity;
Directory: typeof LookupDirectory;
Advice: typeof LookupAdvice;
nodesServing: (service: string | { readonly key: string }) => Effect.Effect<ReadonlyArray<DirectoryEntry>, never, Directory>;
advise: (input: { readonly serviceKey: string; readonly prefer: string }) => Effect.Effect<string, never, Advice>;
prefer: (service: string | { readonly key: string }, nodeKey: string) => Effect.Effect<string, never, Advice>;
preferEntry: (service: string | { readonly key: string }, entry: { readonly nodeKey: string }) => Effect.Effect<string, never, Advice>;
clearAdvice: (service: string | { readonly key: string }) => Effect.Effect<boolean, never, Advice>;
preferred: (service: string | { readonly key: string }) => Effect.Effect<Option.Option<string>, never, Advice>;
changes: Stream.Stream<DirectoryChange, never, Directory>;
directoryTable: () => Effect.Effect<{ readonly get: Effect.Effect<ReadonlyMap<string, DirectoryEntry>>; readonly getNode: (nodeKey: string) => Effect.Effect<Option.Option<DirectoryEntry>> }, never, Directory | Scope.Scope>;
defaultIpcPath: '/tmp/hyperlink-ts-lookup.sock';
layerIpc: (path: string, options?: { readonly unlink?: boolean; readonly onConflict?: OnConflictResolved }) => Layer.Layer<Identity | Directory | Advice | Hyperlink.Local<Identity> | Handler<'claim'> | Handler<'resolve'> | Hyperlink.Local<Directory>…;
layerNode: (node: AnyNode & { readonly key: string }, options?: { readonly unlink?: boolean }) => Layer.Layer<never, LookupUnaddressed>;
directoryAdvertiseLayer: (node: AnyNode & { readonly key: string }, serves: ReadonlyArray<string>, options?: { readonly onConflict?: OnConflict }) => Layer.Layer<never, IncumbentAlive>;
client: (node: AnyNode & { readonly path?: string }) => Layer.Layer<Services, LookupUnaddressed>;
clientOptions: (options?: { readonly path?: string }) => Layer.Layer<Services, LookupUnaddressed>;
layerOptions: (options?: { readonly path?: string; readonly unlink?: boolean }) => Layer.Layer<Services>;
layer: Layer.Layer<Services>;
}
Lookup.class Directoryclass Directory {
key: Identifier;
Service: {
advertise: (payload: { kind: 'Http' | 'WebSocket' | 'IpcSocket'; nodeKey: string; serves: ReadonlyArray<string>; url?: string | undefined; path?: string | undefined; onConflict?: 'livenessReplace' | 'askIncumbent' | 'reject' | 'inherit' | undefined }…;
unregister: (payload: { nodeKey: string; kind?: 'Http' | 'WebSocket' | 'IpcSocket' | undefined; url?: string | undefined; path?: string | undefined }) => Effect.Effect<boolean, never, never>;
nodesServing: (payload: { serviceKey: string }) => Effect.Effect<ReadonlyArray<LookupDirectoryEntry>, never, never>;
changes: Stream.Stream<DirectoryUpserted | DirectoryRemoved, never, never>;
};
description: string | undefined;
of: (this: void, self: { readonly advertise: (payload: { kind: 'Http' | 'WebSocket' | 'IpcSocket'; nodeKey: string; serves: ReadonlyArray<string>; url?: string | undefined; path?: string | undefined; onConflict?: 'livenessReplace' | 'askIncumb…;
context: (self: { readonly advertise: (payload: { kind: 'Http' | 'WebSocket' | 'IpcSocket'; nodeKey: string; serves: ReadonlyArray<string>; url?: string | undefined; path?: string | undefined; onConflict?: 'livenessReplace' | 'askIncumbent' | 'reje…;
use: (f: (service: { readonly advertise: (payload: { kind: 'Http' | 'WebSocket' | 'IpcSocket'; nodeKey: string; serves: ReadonlyArray<string>; url?: string | undefined; path?: string | undefined; onConflict?: 'livenessReplace' | 'askIncumbent' …;
useSync: (f: (service: { readonly advertise: (payload: { kind: 'Http' | 'WebSocket' | 'IpcSocket'; nodeKey: string; serves: ReadonlyArray<string>; url?: string | undefined; path?: string | undefined; onConflict?: 'livenessReplace' | 'askIncumbent' …;
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;
}
Lookup node directory — advertise / unregister / list by served HyperService key /
directorySpec.changes
membership push.
Directory;
const const serviceKey: stringserviceKey = tag: HyperlinkTag<Self, S>(parameter) tag: {
description: string | undefined;
key: Identifier;
of: (this: void, self: Simplify<{ readonly [K in keyof S]: S[K] extends FromLocalMethod<infer M> ? InjectLocal<M, Self> : S[K] extends LocalMethod<infer T> ? LocalEffect<T, never, Self> : S[K] extends DefaultMethod<infer F> ? F : S[K] extends …;
context: (self: Simplify<{ readonly [K in keyof S]: S[K] extends FromLocalMethod<infer M> ? InjectLocal<M, Self> : S[K] extends LocalMethod<infer T> ? LocalEffect<T, never, Self> : S[K] extends DefaultMethod<infer F> ? F : S[K] extends { readonly _…;
use: (f: (service: Simplify<{ readonly [K in keyof S]: S[K] extends FromLocalMethod<infer M> ? InjectLocal<M, Self> : S[K] extends LocalMethod<infer T> ? LocalEffect<T, never, Self> : S[K] extends DefaultMethod<infer F> ? F : S[K] extends { rea…;
useSync: (f: (service: Simplify<{ readonly [K in keyof S]: S[K] extends FromLocalMethod<infer M> ? InjectLocal<M, Self> : S[K] extends LocalMethod<infer T> ? LocalEffect<T, never, Self> : S[K] extends DefaultMethod<infer F> ? F : S[K] extends { rea…;
Identifier: Identifier;
Service: Shape;
stack: string | undefined;
pipe: { <A>(this: A): A; <A, B = never>(this: A, ab: (_: A) => B): B; <A, B = never, C = never>(this: A, ab: (_: A) => B, bc: (_: B) => C): C; <A, B = never, C = never, D = never>(this: A, ab: (_: A) => B, bc: (_: B) => C, cd: (_: C) => D): D; <…;
toString: () => string;
toJSON: () => unknown;
}
tag[const wireKeySym: typeof wireKeySymWhere the wire key (RpcGroup prefix) is stowed on a Tag. Solo
Tag
s use the
same string as
Context.Key.key
; shared-Spec instances use the factory wire key.
Read with
wireKeyOf
.
wireKeySym];
const const resolve: Effect.Effect<
Endpoint | LookupDirectoryEntry,
LookupClientError,
LookupDirectory | LookupAdvice
>
const resolve: {
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;
}
resolve = 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 resolved: Option.Option<Endpoint>resolved = yield* const identity: {
readonly claim: (payload: {
kind: "Http" | "WebSocket" | "IpcSocket"
key: string
nodeKey: string
url?: string | undefined
path?: string | undefined
}) => Effect.Effect<
Endpoint,
DuplicateIdentity,
never
>
readonly resolve: (payload: {
key: string
}) => Effect.Effect<
Option.Option<Endpoint>,
never,
never
>
}
identity.resolve: (payload: {
key: string
}) => Effect.Effect<
Option.Option<Endpoint>,
never,
never
>
resolve(
new const Lookup: typeof import("/Users/nikolasstow/Coding/Hyperlink/worktrees/epsilon/src/Lookup")const Lookup: {
resolveOnConflict: (...prefs: ReadonlyArray<OnConflict | undefined>) => OnConflictResolved;
Endpoint: typeof Endpoint;
ClaimRequest: typeof ClaimRequest;
ResolveRequest: typeof ResolveRequest;
DuplicateIdentity: typeof DuplicateIdentity;
DirectoryEntry: typeof LookupDirectoryEntry;
DirectoryUpserted: typeof DirectoryUpserted;
DirectoryRemoved: typeof DirectoryRemoved;
DirectoryChange: Schema.Union<readonly [typeof DirectoryUpserted, typeof DirectoryRemoved]>;
AdvertiseRequest: typeof AdvertiseRequest;
UnregisterRequest: typeof UnregisterRequest;
NodesServingRequest: typeof NodesServingRequest;
IncumbentAlive: typeof IncumbentAlive;
AdviseRequest: typeof AdviseRequest;
ClearAdviceRequest: typeof ClearAdviceRequest;
PreferredRequest: typeof PreferredRequest;
LookupUnaddressed: typeof LookupUnaddressed;
kind: 'hyperlink-ts/Lookup';
Identity: typeof LookupIdentity;
Directory: typeof LookupDirectory;
Advice: typeof LookupAdvice;
nodesServing: (service: string | { readonly key: string }) => Effect.Effect<ReadonlyArray<DirectoryEntry>, never, Directory>;
advise: (input: { readonly serviceKey: string; readonly prefer: string }) => Effect.Effect<string, never, Advice>;
prefer: (service: string | { readonly key: string }, nodeKey: string) => Effect.Effect<string, never, Advice>;
preferEntry: (service: string | { readonly key: string }, entry: { readonly nodeKey: string }) => Effect.Effect<string, never, Advice>;
clearAdvice: (service: string | { readonly key: string }) => Effect.Effect<boolean, never, Advice>;
preferred: (service: string | { readonly key: string }) => Effect.Effect<Option.Option<string>, never, Advice>;
changes: Stream.Stream<DirectoryChange, never, Directory>;
directoryTable: () => Effect.Effect<{ readonly get: Effect.Effect<ReadonlyMap<string, DirectoryEntry>>; readonly getNode: (nodeKey: string) => Effect.Effect<Option.Option<DirectoryEntry>> }, never, Directory | Scope.Scope>;
defaultIpcPath: '/tmp/hyperlink-ts-lookup.sock';
layerIpc: (path: string, options?: { readonly unlink?: boolean; readonly onConflict?: OnConflictResolved }) => Layer.Layer<Identity | Directory | Advice | Hyperlink.Local<Identity> | Handler<'claim'> | Handler<'resolve'> | Hyperlink.Local<Directory>…;
layerNode: (node: AnyNode & { readonly key: string }, options?: { readonly unlink?: boolean }) => Layer.Layer<never, LookupUnaddressed>;
directoryAdvertiseLayer: (node: AnyNode & { readonly key: string }, serves: ReadonlyArray<string>, options?: { readonly onConflict?: OnConflict }) => Layer.Layer<never, IncumbentAlive>;
client: (node: AnyNode & { readonly path?: string }) => Layer.Layer<Services, LookupUnaddressed>;
clientOptions: (options?: { readonly path?: string }) => Layer.Layer<Services, LookupUnaddressed>;
layerOptions: (options?: { readonly path?: string; readonly unlink?: boolean }) => Layer.Layer<Services>;
layer: Layer.Layer<Services>;
}
Lookup.constructor ResolveRequest(props: Schema.Struct<Fields extends Schema.Struct.Fields>.ReadonlyMakeIn<{
readonly key: Schema.String;
}>, options?: Schema.MakeOptions | undefined): ResolveRequest
constructor ResolveRequest(props: Schema.Struct<Fields extends Schema.Struct.Fields>.ReadonlyMakeIn<{
readonly key: Schema.String;
}>, options?: Schema.MakeOptions | undefined): {
Type: Self;
Encoded: S["Encoded"];
DecodingServices: S["DecodingServices"];
EncodingServices: S["EncodingServices"];
Iso: S["Iso"];
identifier: string;
fields: S["fields"];
mapFields: (f: (fields: { readonly key: Schema.String }) => To, options?: { readonly unsafePreserveChecks?: boolean | undefined } | undefined) => Schema.Struct<{ [K in keyof Readonly<To>]: Readonly<To>[K]; }>;
extend: (identifier: string) => { (fields: NewFields, annotations?: Schema.Annotations.Declaration<Extended, readonly [Schema.Struct<{ [K in keyof { [K in keyof ('key' & keyof NewFields extends never ? { readonly key: Schema.String; } & NewFields …;
Rebuild: Rebuild;
ast: Ast;
annotate: (annotations: Schema.Annotations.Bottom<ResolveRequest, readonly [Schema.Struct<{ readonly key: Schema.String }>]>) => Schema.decodeTo<Schema.declareConstructor<ResolveRequest, Schema.Struct.ReadonlySide<{ readonly key: Schema.String }, 'E…;
annotateKey: (annotations: Schema.Annotations.Key<ResolveRequest>) => Schema.decodeTo<Schema.declareConstructor<ResolveRequest, Schema.Struct.ReadonlySide<{ readonly key: Schema.String }, 'Encoded'>, readonly [Schema.Struct<{ readonly key: Schema.Strin…;
check: (checks_0: Check<ResolveRequest>, ...checks: Array<Check<ResolveRequest>>) => Schema.decodeTo<Schema.declareConstructor<ResolveRequest, Schema.Struct.ReadonlySide<{ readonly key: Schema.String }, 'Encoded'>, readonly [Schema.Struct<{ reado…;
rebuild: (ast: Declaration) => Schema.decodeTo<Schema.declareConstructor<ResolveRequest, Schema.Struct.ReadonlySide<{ readonly key: Schema.String }, 'Encoded'>, readonly [Schema.Struct<{ readonly key: Schema.String }>], Schema.Struct.ReadonlySide<{…;
make: (input: Struct.ReadonlyMakeIn<{ readonly key: String }>, options?: MakeOptions) => ResolveRequest;
makeOption: (input: Struct.ReadonlyMakeIn<{ readonly key: String }>, options?: MakeOptions) => Option_.Option<ResolveRequest>;
makeEffect: (input: Struct.ReadonlyMakeIn<{ readonly key: String }>, options?: MakeOptions) => Effect.Effect<ResolveRequest, 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; <…;
}
Resolve payload — look up a winning claim by HyperService key (nodeless clients).
ResolveRequest({ key: stringkey: tag: HyperlinkTag<Self, S>(parameter) tag: {
description: string | undefined;
key: Identifier;
of: (this: void, self: Simplify<{ readonly [K in keyof S]: S[K] extends FromLocalMethod<infer M> ? InjectLocal<M, Self> : S[K] extends LocalMethod<infer T> ? LocalEffect<T, never, Self> : S[K] extends DefaultMethod<infer F> ? F : S[K] extends …;
context: (self: Simplify<{ readonly [K in keyof S]: S[K] extends FromLocalMethod<infer M> ? InjectLocal<M, Self> : S[K] extends LocalMethod<infer T> ? LocalEffect<T, never, Self> : S[K] extends DefaultMethod<infer F> ? F : S[K] extends { readonly _…;
use: (f: (service: Simplify<{ readonly [K in keyof S]: S[K] extends FromLocalMethod<infer M> ? InjectLocal<M, Self> : S[K] extends LocalMethod<infer T> ? LocalEffect<T, never, Self> : S[K] extends DefaultMethod<infer F> ? F : S[K] extends { rea…;
useSync: (f: (service: Simplify<{ readonly [K in keyof S]: S[K] extends FromLocalMethod<infer M> ? InjectLocal<M, Self> : S[K] extends LocalMethod<infer T> ? LocalEffect<T, never, Self> : S[K] extends DefaultMethod<infer F> ? F : S[K] extends { rea…;
Identifier: Identifier;
Service: Shape;
stack: string | undefined;
pipe: { <A>(this: A): A; <A, B = never>(this: A, ab: (_: A) => B): B; <A, B = never, C = never>(this: A, ab: (_: A) => B, bc: (_: B) => C): C; <A, B = never, C = never, D = never>(this: A, ab: (_: A) => B, bc: (_: B) => C, cd: (_: C) => D): D; <…;
toString: () => string;
toJSON: () => unknown;
}
tag.ServiceClass<Self, string, Simplify<{ readonly [K in keyof S]: S[K] extends FromLocalMethod<infer M> ? InjectLocal<M, Self> : S[K] extends LocalMethod<...> ? LocalEffect<...> : S[K] extends DefaultMethod<...> ? F : S[K] extends { ...; } ? SuccessOf<...> : S[K] extends { ...; } ? Subscribable<...> : S[K] extends { ...; } ? ClientMethod<...> : S[K] extends Spec ? Simplify<...> : never; }>>.key: stringkey }),
);
if (import OptionOption.const isSome: <A>(
self: Option<A>
) => self is Some<A>
Checks whether an Option contains a value (Some).
When to use
Use when you need to branch on a present Option before accessing .value.
Details
- Acts as a type guard, narrowing to
Some<A>
Example (Checking for Some)
import { Option } from "effect"
console.log(Option.isSome(Option.some(1)))
// Output: true
console.log(Option.isSome(Option.none()))
// Output: false
isSome(const resolved: Option.Option<Endpoint>resolved)) {
return const resolved: Option.Some<Endpoint>const resolved: {
_tag: "Some";
_op: "Some";
value: A;
valueOrUndefined: A;
pipe: { <A>(this: A): A; <A, B = never>(this: A, ab: (_: A) => B): B; <A, B = never, C = never>(this: A, ab: (_: A) => B, bc: (_: B) => C): C; <A, B = never, C = never, D = never>(this: A, ab: (_: A) => B, bc: (_: B) => C, cd: (_: C) => D): D; <…;
toString: () => string;
toJSON: () => unknown;
}
resolved.Some<Endpoint>.value: Endpoint(property) Some<Endpoint>.value: {
kind: 'Http' | 'WebSocket' | 'IpcSocket';
nodeKey: string;
url: string | undefined;
path: string | undefined;
}
value satisfies type LookupDialEndpoint = {
readonly nodeKey: string
readonly kind: ProtocolKind
readonly url?: string
readonly path?: string
}
Dial target resolved by
lookupClient
.
LookupDialEndpoint;
}
const const entries: ReadonlyArray<LookupDirectoryEntry>entries = yield* const Lookup: typeof import("/Users/nikolasstow/Coding/Hyperlink/worktrees/epsilon/src/Lookup")const Lookup: {
resolveOnConflict: (...prefs: ReadonlyArray<OnConflict | undefined>) => OnConflictResolved;
Endpoint: typeof Endpoint;
ClaimRequest: typeof ClaimRequest;
ResolveRequest: typeof ResolveRequest;
DuplicateIdentity: typeof DuplicateIdentity;
DirectoryEntry: typeof LookupDirectoryEntry;
DirectoryUpserted: typeof DirectoryUpserted;
DirectoryRemoved: typeof DirectoryRemoved;
DirectoryChange: Schema.Union<readonly [typeof DirectoryUpserted, typeof DirectoryRemoved]>;
AdvertiseRequest: typeof AdvertiseRequest;
UnregisterRequest: typeof UnregisterRequest;
NodesServingRequest: typeof NodesServingRequest;
IncumbentAlive: typeof IncumbentAlive;
AdviseRequest: typeof AdviseRequest;
ClearAdviceRequest: typeof ClearAdviceRequest;
PreferredRequest: typeof PreferredRequest;
LookupUnaddressed: typeof LookupUnaddressed;
kind: 'hyperlink-ts/Lookup';
Identity: typeof LookupIdentity;
Directory: typeof LookupDirectory;
Advice: typeof LookupAdvice;
nodesServing: (service: string | { readonly key: string }) => Effect.Effect<ReadonlyArray<DirectoryEntry>, never, Directory>;
advise: (input: { readonly serviceKey: string; readonly prefer: string }) => Effect.Effect<string, never, Advice>;
prefer: (service: string | { readonly key: string }, nodeKey: string) => Effect.Effect<string, never, Advice>;
preferEntry: (service: string | { readonly key: string }, entry: { readonly nodeKey: string }) => Effect.Effect<string, never, Advice>;
clearAdvice: (service: string | { readonly key: string }) => Effect.Effect<boolean, never, Advice>;
preferred: (service: string | { readonly key: string }) => Effect.Effect<Option.Option<string>, never, Advice>;
changes: Stream.Stream<DirectoryChange, never, Directory>;
directoryTable: () => Effect.Effect<{ readonly get: Effect.Effect<ReadonlyMap<string, DirectoryEntry>>; readonly getNode: (nodeKey: string) => Effect.Effect<Option.Option<DirectoryEntry>> }, never, Directory | Scope.Scope>;
defaultIpcPath: '/tmp/hyperlink-ts-lookup.sock';
layerIpc: (path: string, options?: { readonly unlink?: boolean; readonly onConflict?: OnConflictResolved }) => Layer.Layer<Identity | Directory | Advice | Hyperlink.Local<Identity> | Handler<'claim'> | Handler<'resolve'> | Hyperlink.Local<Directory>…;
layerNode: (node: AnyNode & { readonly key: string }, options?: { readonly unlink?: boolean }) => Layer.Layer<never, LookupUnaddressed>;
directoryAdvertiseLayer: (node: AnyNode & { readonly key: string }, serves: ReadonlyArray<string>, options?: { readonly onConflict?: OnConflict }) => Layer.Layer<never, IncumbentAlive>;
client: (node: AnyNode & { readonly path?: string }) => Layer.Layer<Services, LookupUnaddressed>;
clientOptions: (options?: { readonly path?: string }) => Layer.Layer<Services, LookupUnaddressed>;
layerOptions: (options?: { readonly path?: string; readonly unlink?: boolean }) => Layer.Layer<Services>;
layer: Layer.Layer<Services>;
}
Lookup.const nodesServing: (
service: string | { readonly key: string }
) => Effect.Effect<
ReadonlyArray<DirectoryEntry>,
never,
Directory
>
List directory rows that advertise a HyperService (Tag or wire key).
Sugar over
Directory
.nodesServing — wire stays
NodesServingRequest
.
const rows = yield* Lookup.nodesServing(Jobs)
// or Lookup.nodesServing("fleet/Jobs")
nodesServing(tag: HyperlinkTag<Self, S>(parameter) tag: {
description: string | undefined;
key: Identifier;
of: (this: void, self: Simplify<{ readonly [K in keyof S]: S[K] extends FromLocalMethod<infer M> ? InjectLocal<M, Self> : S[K] extends LocalMethod<infer T> ? LocalEffect<T, never, Self> : S[K] extends DefaultMethod<infer F> ? F : S[K] extends …;
context: (self: Simplify<{ readonly [K in keyof S]: S[K] extends FromLocalMethod<infer M> ? InjectLocal<M, Self> : S[K] extends LocalMethod<infer T> ? LocalEffect<T, never, Self> : S[K] extends DefaultMethod<infer F> ? F : S[K] extends { readonly _…;
use: (f: (service: Simplify<{ readonly [K in keyof S]: S[K] extends FromLocalMethod<infer M> ? InjectLocal<M, Self> : S[K] extends LocalMethod<infer T> ? LocalEffect<T, never, Self> : S[K] extends DefaultMethod<infer F> ? F : S[K] extends { rea…;
useSync: (f: (service: Simplify<{ readonly [K in keyof S]: S[K] extends FromLocalMethod<infer M> ? InjectLocal<M, Self> : S[K] extends LocalMethod<infer T> ? LocalEffect<T, never, Self> : S[K] extends DefaultMethod<infer F> ? F : S[K] extends { rea…;
Identifier: Identifier;
Service: Shape;
stack: string | undefined;
pipe: { <A>(this: A): A; <A, B = never>(this: A, ab: (_: A) => B): B; <A, B = never, C = never>(this: A, ab: (_: A) => B, bc: (_: B) => C): C; <A, B = never, C = never, D = never>(this: A, ab: (_: A) => B, bc: (_: B) => C, cd: (_: C) => D): D; <…;
toString: () => string;
toJSON: () => unknown;
}
tag);
if (const entries: ReadonlyArray<LookupDirectoryEntry>entries.ReadonlyArray<T>.length: numberGets the length of the array. This is a number one higher than the highest element defined in an array.
length === 0) {
return yield* new constructor LookupClientError<{
readonly tag: string;
readonly reason: "missing" | "ambiguous";
readonly count: number;
}>(args: {
readonly tag: string;
readonly reason: "missing" | "ambiguous";
readonly count: number;
}): LookupClientError
lookupClient
could not resolve exactly one dial target for the Tag
(missing = none; ambiguous = more than one directory row and no
LookupClientOptions.pick
).
LookupClientError({
tag: stringtag: tag: HyperlinkTag<Self, S>(parameter) tag: {
description: string | undefined;
key: Identifier;
of: (this: void, self: Simplify<{ readonly [K in keyof S]: S[K] extends FromLocalMethod<infer M> ? InjectLocal<M, Self> : S[K] extends LocalMethod<infer T> ? LocalEffect<T, never, Self> : S[K] extends DefaultMethod<infer F> ? F : S[K] extends …;
context: (self: Simplify<{ readonly [K in keyof S]: S[K] extends FromLocalMethod<infer M> ? InjectLocal<M, Self> : S[K] extends LocalMethod<infer T> ? LocalEffect<T, never, Self> : S[K] extends DefaultMethod<infer F> ? F : S[K] extends { readonly _…;
use: (f: (service: Simplify<{ readonly [K in keyof S]: S[K] extends FromLocalMethod<infer M> ? InjectLocal<M, Self> : S[K] extends LocalMethod<infer T> ? LocalEffect<T, never, Self> : S[K] extends DefaultMethod<infer F> ? F : S[K] extends { rea…;
useSync: (f: (service: Simplify<{ readonly [K in keyof S]: S[K] extends FromLocalMethod<infer M> ? InjectLocal<M, Self> : S[K] extends LocalMethod<infer T> ? LocalEffect<T, never, Self> : S[K] extends DefaultMethod<infer F> ? F : S[K] extends { rea…;
Identifier: Identifier;
Service: Shape;
stack: string | undefined;
pipe: { <A>(this: A): A; <A, B = never>(this: A, ab: (_: A) => B): B; <A, B = never, C = never>(this: A, ab: (_: A) => B, bc: (_: B) => C): C; <A, B = never, C = never, D = never>(this: A, ab: (_: A) => B, bc: (_: B) => C, cd: (_: C) => D): D; <…;
toString: () => string;
toJSON: () => unknown;
}
tag.ServiceClass<Self, string, Simplify<{ readonly [K in keyof S]: S[K] extends FromLocalMethod<infer M> ? InjectLocal<M, Self> : S[K] extends LocalMethod<...> ? LocalEffect<...> : S[K] extends DefaultMethod<...> ? F : S[K] extends { ...; } ? SuccessOf<...> : S[K] extends { ...; } ? Subscribable<...> : S[K] extends { ...; } ? ClientMethod<...> : S[K] extends Spec ? Simplify<...> : never; }>>.key: stringkey,
reason: "missing" | "ambiguous"reason: "missing",
count: numbercount: 0,
});
}
if (const entries: ReadonlyArray<LookupDirectoryEntry>entries.ReadonlyArray<T>.length: numberGets the length of the array. This is a number one higher than the highest element defined in an array.
length === 1) {
return const entries: ReadonlyArray<LookupDirectoryEntry>entries[0]!;
}
const const prefer: Option.Option<string>prefer = yield* const Lookup: typeof import("/Users/nikolasstow/Coding/Hyperlink/worktrees/epsilon/src/Lookup")const Lookup: {
resolveOnConflict: (...prefs: ReadonlyArray<OnConflict | undefined>) => OnConflictResolved;
Endpoint: typeof Endpoint;
ClaimRequest: typeof ClaimRequest;
ResolveRequest: typeof ResolveRequest;
DuplicateIdentity: typeof DuplicateIdentity;
DirectoryEntry: typeof LookupDirectoryEntry;
DirectoryUpserted: typeof DirectoryUpserted;
DirectoryRemoved: typeof DirectoryRemoved;
DirectoryChange: Schema.Union<readonly [typeof DirectoryUpserted, typeof DirectoryRemoved]>;
AdvertiseRequest: typeof AdvertiseRequest;
UnregisterRequest: typeof UnregisterRequest;
NodesServingRequest: typeof NodesServingRequest;
IncumbentAlive: typeof IncumbentAlive;
AdviseRequest: typeof AdviseRequest;
ClearAdviceRequest: typeof ClearAdviceRequest;
PreferredRequest: typeof PreferredRequest;
LookupUnaddressed: typeof LookupUnaddressed;
kind: 'hyperlink-ts/Lookup';
Identity: typeof LookupIdentity;
Directory: typeof LookupDirectory;
Advice: typeof LookupAdvice;
nodesServing: (service: string | { readonly key: string }) => Effect.Effect<ReadonlyArray<DirectoryEntry>, never, Directory>;
advise: (input: { readonly serviceKey: string; readonly prefer: string }) => Effect.Effect<string, never, Advice>;
prefer: (service: string | { readonly key: string }, nodeKey: string) => Effect.Effect<string, never, Advice>;
preferEntry: (service: string | { readonly key: string }, entry: { readonly nodeKey: string }) => Effect.Effect<string, never, Advice>;
clearAdvice: (service: string | { readonly key: string }) => Effect.Effect<boolean, never, Advice>;
preferred: (service: string | { readonly key: string }) => Effect.Effect<Option.Option<string>, never, Advice>;
changes: Stream.Stream<DirectoryChange, never, Directory>;
directoryTable: () => Effect.Effect<{ readonly get: Effect.Effect<ReadonlyMap<string, DirectoryEntry>>; readonly getNode: (nodeKey: string) => Effect.Effect<Option.Option<DirectoryEntry>> }, never, Directory | Scope.Scope>;
defaultIpcPath: '/tmp/hyperlink-ts-lookup.sock';
layerIpc: (path: string, options?: { readonly unlink?: boolean; readonly onConflict?: OnConflictResolved }) => Layer.Layer<Identity | Directory | Advice | Hyperlink.Local<Identity> | Handler<'claim'> | Handler<'resolve'> | Hyperlink.Local<Directory>…;
layerNode: (node: AnyNode & { readonly key: string }, options?: { readonly unlink?: boolean }) => Layer.Layer<never, LookupUnaddressed>;
directoryAdvertiseLayer: (node: AnyNode & { readonly key: string }, serves: ReadonlyArray<string>, options?: { readonly onConflict?: OnConflict }) => Layer.Layer<never, IncumbentAlive>;
client: (node: AnyNode & { readonly path?: string }) => Layer.Layer<Services, LookupUnaddressed>;
clientOptions: (options?: { readonly path?: string }) => Layer.Layer<Services, LookupUnaddressed>;
layerOptions: (options?: { readonly path?: string; readonly unlink?: boolean }) => Layer.Layer<Services>;
layer: Layer.Layer<Services>;
}
Lookup.const preferred: (
service: string | { readonly key: string }
) => Effect.Effect<
Option.Option<string>,
never,
Advice
>
Read preferred directory nodeKey for a HyperService (Tag or wire key).
preferred(tag: HyperlinkTag<Self, S>(parameter) tag: {
description: string | undefined;
key: Identifier;
of: (this: void, self: Simplify<{ readonly [K in keyof S]: S[K] extends FromLocalMethod<infer M> ? InjectLocal<M, Self> : S[K] extends LocalMethod<infer T> ? LocalEffect<T, never, Self> : S[K] extends DefaultMethod<infer F> ? F : S[K] extends …;
context: (self: Simplify<{ readonly [K in keyof S]: S[K] extends FromLocalMethod<infer M> ? InjectLocal<M, Self> : S[K] extends LocalMethod<infer T> ? LocalEffect<T, never, Self> : S[K] extends DefaultMethod<infer F> ? F : S[K] extends { readonly _…;
use: (f: (service: Simplify<{ readonly [K in keyof S]: S[K] extends FromLocalMethod<infer M> ? InjectLocal<M, Self> : S[K] extends LocalMethod<infer T> ? LocalEffect<T, never, Self> : S[K] extends DefaultMethod<infer F> ? F : S[K] extends { rea…;
useSync: (f: (service: Simplify<{ readonly [K in keyof S]: S[K] extends FromLocalMethod<infer M> ? InjectLocal<M, Self> : S[K] extends LocalMethod<infer T> ? LocalEffect<T, never, Self> : S[K] extends DefaultMethod<infer F> ? F : S[K] extends { rea…;
Identifier: Identifier;
Service: Shape;
stack: string | undefined;
pipe: { <A>(this: A): A; <A, B = never>(this: A, ab: (_: A) => B): B; <A, B = never, C = never>(this: A, ab: (_: A) => B, bc: (_: B) => C): C; <A, B = never, C = never, D = never>(this: A, ab: (_: A) => B, bc: (_: B) => C, cd: (_: C) => D): D; <…;
toString: () => string;
toJSON: () => unknown;
}
tag.ServiceClass<Self, string, Simplify<{ readonly [K in keyof S]: S[K] extends FromLocalMethod<infer M> ? InjectLocal<M, Self> : S[K] extends LocalMethod<...> ? LocalEffect<...> : S[K] extends DefaultMethod<...> ? F : S[K] extends { ...; } ? SuccessOf<...> : S[K] extends { ...; } ? Subscribable<...> : S[K] extends { ...; } ? ClientMethod<...> : S[K] extends Spec ? Simplify<...> : never; }>>.key: stringkey);
if (import OptionOption.const isSome: <A>(
self: Option<A>
) => self is Some<A>
Checks whether an Option contains a value (Some).
When to use
Use when you need to branch on a present Option before accessing .value.
Details
- Acts as a type guard, narrowing to
Some<A>
Example (Checking for Some)
import { Option } from "effect"
console.log(Option.isSome(Option.some(1)))
// Output: true
console.log(Option.isSome(Option.none()))
// Output: false
isSome(const prefer: Option.Option<string>prefer)) {
const const advised:
| LookupDirectoryEntry
| undefined
advised = const entries: ReadonlyArray<LookupDirectoryEntry>entries.ReadonlyArray<DirectoryEntry>.find(predicate: (value: LookupDirectoryEntry, index: number, obj: readonly LookupDirectoryEntry[]) => unknown, thisArg?: any): LookupDirectoryEntry | undefined (+1 overload)Returns the value of the first element in the array where predicate is true, and undefined
otherwise.
find((row: LookupDirectoryEntry(parameter) row: {
kind: 'Http' | 'WebSocket' | 'IpcSocket';
nodeKey: string;
serves: ReadonlyArray<string>;
url: string | undefined;
path: string | undefined;
}
row) => row: LookupDirectoryEntry(parameter) row: {
kind: 'Http' | 'WebSocket' | 'IpcSocket';
nodeKey: string;
serves: ReadonlyArray<string>;
url: string | undefined;
path: string | undefined;
}
row.nodeKey: stringnodeKey === const prefer: Option.Some<string>const prefer: {
_tag: "Some";
_op: "Some";
value: A;
valueOrUndefined: A;
pipe: { <A>(this: A): A; <A, B = never>(this: A, ab: (_: A) => B): B; <A, B = never, C = never>(this: A, ab: (_: A) => B, bc: (_: B) => C): C; <A, B = never, C = never, D = never>(this: A, ab: (_: A) => B, bc: (_: B) => C, cd: (_: C) => D): D; <…;
toString: () => string;
toJSON: () => unknown;
}
prefer.Some<string>.value: stringvalue);
if (const advised:
| LookupDirectoryEntry
| undefined
advised !== var undefinedundefined) {
return const advised: LookupDirectoryEntryconst advised: {
kind: 'Http' | 'WebSocket' | 'IpcSocket';
nodeKey: string;
serves: ReadonlyArray<string>;
url: string | undefined;
path: string | undefined;
}
advised;
}
}
const const pick: LookupClientPick | undefinedpick = options: LookupClientOptionsoptions?.pick?: LookupClientPickpick;
if (const pick: LookupClientPick | undefinedpick === var undefinedundefined) {
return yield* new constructor LookupClientError<{
readonly tag: string;
readonly reason: "missing" | "ambiguous";
readonly count: number;
}>(args: {
readonly tag: string;
readonly reason: "missing" | "ambiguous";
readonly count: number;
}): LookupClientError
lookupClient
could not resolve exactly one dial target for the Tag
(missing = none; ambiguous = more than one directory row and no
LookupClientOptions.pick
).
LookupClientError({
tag: stringtag: tag: HyperlinkTag<Self, S>(parameter) tag: {
description: string | undefined;
key: Identifier;
of: (this: void, self: Simplify<{ readonly [K in keyof S]: S[K] extends FromLocalMethod<infer M> ? InjectLocal<M, Self> : S[K] extends LocalMethod<infer T> ? LocalEffect<T, never, Self> : S[K] extends DefaultMethod<infer F> ? F : S[K] extends …;
context: (self: Simplify<{ readonly [K in keyof S]: S[K] extends FromLocalMethod<infer M> ? InjectLocal<M, Self> : S[K] extends LocalMethod<infer T> ? LocalEffect<T, never, Self> : S[K] extends DefaultMethod<infer F> ? F : S[K] extends { readonly _…;
use: (f: (service: Simplify<{ readonly [K in keyof S]: S[K] extends FromLocalMethod<infer M> ? InjectLocal<M, Self> : S[K] extends LocalMethod<infer T> ? LocalEffect<T, never, Self> : S[K] extends DefaultMethod<infer F> ? F : S[K] extends { rea…;
useSync: (f: (service: Simplify<{ readonly [K in keyof S]: S[K] extends FromLocalMethod<infer M> ? InjectLocal<M, Self> : S[K] extends LocalMethod<infer T> ? LocalEffect<T, never, Self> : S[K] extends DefaultMethod<infer F> ? F : S[K] extends { rea…;
Identifier: Identifier;
Service: Shape;
stack: string | undefined;
pipe: { <A>(this: A): A; <A, B = never>(this: A, ab: (_: A) => B): B; <A, B = never, C = never>(this: A, ab: (_: A) => B, bc: (_: B) => C): C; <A, B = never, C = never, D = never>(this: A, ab: (_: A) => B, bc: (_: B) => C, cd: (_: C) => D): D; <…;
toString: () => string;
toJSON: () => unknown;
}
tag.ServiceClass<Self, string, Simplify<{ readonly [K in keyof S]: S[K] extends FromLocalMethod<infer M> ? InjectLocal<M, Self> : S[K] extends LocalMethod<...> ? LocalEffect<...> : S[K] extends DefaultMethod<...> ? F : S[K] extends { ...; } ? SuccessOf<...> : S[K] extends { ...; } ? Subscribable<...> : S[K] extends { ...; } ? ClientMethod<...> : S[K] extends Spec ? Simplify<...> : never; }>>.key: stringkey,
reason: "missing" | "ambiguous"reason: "ambiguous",
count: numbercount: const entries: ReadonlyArray<LookupDirectoryEntry>entries.ReadonlyArray<T>.length: numberGets the length of the array. This is a number one higher than the highest element defined in an array.
length,
});
}
return const pick: LookupClientPickpick === "first" ? const entries: ReadonlyArray<LookupDirectoryEntry>entries[0]! : const pick: (
rows: ReadonlyArray<LookupDirectoryEntry>
) => LookupDirectoryEntry
pick(const entries: ReadonlyArray<LookupDirectoryEntry>entries);
});
let let clientScope:
| Scope.Closeable
| undefined
clientScope: import ScopeScope.Closeable | undefined;
const const holder: {
current: ServiceOf<S, Self>
}
holder: { current: ServiceOf<S, Self>current: type ServiceOf<S extends Spec, Self = unknown> = { [K in keyof { readonly [K in keyof S]: S[K] extends FromLocalMethod<infer M> ? InjectLocal<M, Self> : S[K] extends LocalMethod<infer T> ? LocalEffect<T, never, Self> : S[K] extends DefaultMethod<infer F> ? F : S[K] extends {
readonly _tag: "value";
} ? SuccessOf<AsMethod<S[K]>> : S[K] extends {
readonly _tag: "ref";
} ? Subscribable<SuccessOf<AsMethod<S[K]>>> : S[K] extends {
...;
} ? ClientMethod<...> : S[K] extends Spec ? Simplify<...> : never; }]: { readonly [K in keyof S]: S[K] extends FromLocalMethod<infer M> ? InjectLocal<M, Self> : S[K] extends LocalMethod<infer T> ? LocalEffect<T, never, Self> : S[K] extends DefaultMethod<infer F> ? F : S[K] extends {
readonly _tag: "value";
} ? SuccessOf<AsMethod<S[K]>> : S[K] extends {
readonly _tag: "ref";
} ? Subscribable<SuccessOf<AsMethod<S[K]>>> : S[K] extends {
...;
} ? ClientMethod<...> : S[K] extends Spec ? Simplify<...> : never; }[K]; } extends infer B ? B : never
The full service interface inferred from a
Spec
. Wire
Method
s map to
Effect/function members; off-wire
LocalMethod
s surface as
Effect<T, never, Local<Self>> — yield* to obtain the value, requiring the local layer
(
Local
) (so they're uncallable through
Hyperlink.client
).
ServiceOf<function (type parameter) S in <Self, S extends Spec>(tag: HyperlinkTag<Self, S>, options?: LookupClientOptions): Layer.Layer<Self, LookupClientError, LookupIdentity | LookupDirectory | LookupAdvice>S, function (type parameter) Self in <Self, S extends Spec>(tag: HyperlinkTag<Self, S>, options?: LookupClientOptions): Layer.Layer<Self, LookupClientError, LookupIdentity | LookupDirectory | LookupAdvice>Self> } = {
current: Simplify<{ readonly [K in keyof S]: S[K] extends FromLocalMethod<infer M> ? InjectLocal<M, Self> : S[K] extends LocalMethod<infer T> ? LocalEffect<T, never, Self> : S[K] extends DefaultMethod<infer F> ? F : S[K] extends {
readonly _tag: "value";
} ? SuccessOf<AsMethod<S[K]>> : S[K] extends {
readonly _tag: "ref";
} ? Subscribable<SuccessOf<AsMethod<S[K]>>> : S[K] extends {
...;
} ? ClientMethod<...> : S[K] extends Spec ? Simplify<...> : never; }>
current: null as unknown as type ServiceOf<S extends Spec, Self = unknown> = { [K in keyof { readonly [K in keyof S]: S[K] extends FromLocalMethod<infer M> ? InjectLocal<M, Self> : S[K] extends LocalMethod<infer T> ? LocalEffect<T, never, Self> : S[K] extends DefaultMethod<infer F> ? F : S[K] extends {
readonly _tag: "value";
} ? SuccessOf<AsMethod<S[K]>> : S[K] extends {
readonly _tag: "ref";
} ? Subscribable<SuccessOf<AsMethod<S[K]>>> : S[K] extends {
...;
} ? ClientMethod<...> : S[K] extends Spec ? Simplify<...> : never; }]: { readonly [K in keyof S]: S[K] extends FromLocalMethod<infer M> ? InjectLocal<M, Self> : S[K] extends LocalMethod<infer T> ? LocalEffect<T, never, Self> : S[K] extends DefaultMethod<infer F> ? F : S[K] extends {
readonly _tag: "value";
} ? SuccessOf<AsMethod<S[K]>> : S[K] extends {
readonly _tag: "ref";
} ? Subscribable<SuccessOf<AsMethod<S[K]>>> : S[K] extends {
...;
} ? ClientMethod<...> : S[K] extends Spec ? Simplify<...> : never; }[K]; } extends infer B ? B : never
The full service interface inferred from a
Spec
. Wire
Method
s map to
Effect/function members; off-wire
LocalMethod
s surface as
Effect<T, never, Local<Self>> — yield* to obtain the value, requiring the local layer
(
Local
) (so they're uncallable through
Hyperlink.client
).
ServiceOf<function (type parameter) S in <Self, S extends Spec>(tag: HyperlinkTag<Self, S>, options?: LookupClientOptions): Layer.Layer<Self, LookupClientError, LookupIdentity | LookupDirectory | LookupAdvice>S, function (type parameter) Self in <Self, S extends Spec>(tag: HyperlinkTag<Self, S>, options?: LookupClientOptions): Layer.Layer<Self, LookupClientError, LookupIdentity | LookupDirectory | LookupAdvice>Self>,
};
const const install: (
endpoint: LookupDialEndpoint
) => Effect.Effect<void>
install = (endpoint: LookupDialEndpoint(parameter) endpoint: {
nodeKey: string;
kind: ProtocolKind;
url: string;
path: string;
}
endpoint: type LookupDialEndpoint = {
readonly nodeKey: string
readonly kind: ProtocolKind
readonly url?: string
readonly path?: string
}
Dial target resolved by
lookupClient
.
LookupDialEndpoint): import EffectEffect.interface Effect<out A, out E = never, out R = never>The Effect interface defines a value that lazily describes a workflow or
job. The workflow requires some context R, and may fail with an error of
type E, or succeed with a value of type A.
When to use
Use when you need to represent a lazy, composable workflow that can require
services, fail with a typed error, or succeed with a typed value.
Details
Effect values model resourceful interaction with the outside world,
including synchronous, asynchronous, concurrent, and parallel interaction.
They use a fiber-based concurrency model, with built-in support for
scheduling, fine-grained interruption, structured concurrency, and high
scalability.
To run an Effect value, you need a Runtime, which is a type that is
capable of executing Effect values.
Effect<void> =>
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* () {
if (let clientScope:
| Scope.Closeable
| undefined
clientScope !== var undefinedundefined) {
yield* import ScopeScope.const close: <A, E>(
self: Scope,
exit: Exit<A, E>
) => Effect<void>
Closes a scope and runs its registered finalizers.
When to use
Use to close a scope manually with a specific exit value.
Details
Finalizers run in the scope's configured order and receive the supplied
Exit.
Example (Running scope finalizers)
import { Console, Effect, Exit, Scope } from "effect"
const resourceManagement = Effect.gen(function*() {
const scope = yield* Scope.make("sequential")
// Add multiple finalizers
yield* Scope.addFinalizer(scope, Console.log("Close database connection"))
yield* Scope.addFinalizer(scope, Console.log("Close file handle"))
yield* Scope.addFinalizer(scope, Console.log("Release memory"))
// Do some work...
yield* Console.log("Performing operations...")
// Close scope - finalizers run in reverse order of registration
yield* Scope.close(scope, Exit.succeed("Success!"))
// Output: "Release memory", "Close file handle", "Close database connection"
})
close(let clientScope:
| Scope.Closeable
| undefined
let clientScope: {
strategy: "sequential" | "parallel";
state: State.Open | State.Closed | State.Empty;
}
clientScope, import ExitExit.const void: Exit.Exit<void, never>Provides a pre-allocated successful Exit with a void value.
When to use
Use when you need a shared successful Exit with no meaningful value.
Details
Equivalent to Exit.succeed(undefined) but shared as a single instance,
avoiding allocation for a common case.
Example (Referencing the void Exit)
import { Exit } from "effect"
const exit = Exit.void
console.log(Exit.isSuccess(exit)) // true
void);
}
const const scope: Scope.Closeableconst scope: {
strategy: "sequential" | "parallel";
state: State.Open | State.Closed | State.Empty;
}
scope = yield* import ScopeScope.const make: (
finalizerStrategy?: "sequential" | "parallel"
) => Effect<Closeable>
Creates a new Scope with the specified finalizer strategy.
Example (Creating a scope)
import { Console, Effect, Exit, Scope } from "effect"
const program = Effect.gen(function*() {
// Create a scope with sequential cleanup
const scope = yield* Scope.make("sequential")
// Add finalizers
yield* Scope.addFinalizer(scope, Console.log("Cleanup 1"))
yield* Scope.addFinalizer(scope, Console.log("Cleanup 2"))
// Close the scope (finalizers run in reverse order)
yield* Scope.close(scope, Exit.void)
// Output: "Cleanup 2", then "Cleanup 1"
})
make();
let clientScope:
| Scope.Closeable
| undefined
clientScope = const scope: Scope.Closeableconst scope: {
strategy: "sequential" | "parallel";
state: State.Open | State.Closed | State.Empty;
}
scope;
const const ctx: Context.Context<Self>const ctx: {
mapUnsafe: ReadonlyMap<string, any>;
mutable: boolean;
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;
}
ctx = yield* import LayerLayer.const build: <RIn, E, ROut>(
self: Layer<ROut, E, RIn>
) => Effect<
Context.Context<ROut>,
E,
RIn | Scope.Scope
>
Builds a layer into a scoped value.
Example (Building a layer into a context)
import { Context, Effect, Layer } from "effect"
class Database extends Context.Service<Database, {
readonly query: (sql: string) => Effect.Effect<string>
}>()("Database") {}
// Build a layer to get its services
const program = Effect.gen(function*() {
const dbLayer = Layer.succeed(Database, {
query: Effect.fn("Database.query")((sql: string) => Effect.succeed("result"))
})
// Build the layer into Context - automatically manages scope and memoization
const context = yield* Layer.build(dbLayer)
// Extract the specific service from the built layer
const database = Context.get(context, Database)
return yield* database.query("SELECT * FROM users")
})
build(
const clientLayerForEndpoint: <
Self,
S extends Spec
>(
tag: HyperlinkTag<Self, S>,
endpoint: {
readonly nodeKey: string
readonly kind: ProtocolKind
readonly url?: string
readonly path?: string
}
) => Layer.Layer<Self>
Client layer dialing a Lookup winner's
Endpoint
— used when identity claim loses.
clientLayerForEndpoint(tag: HyperlinkTag<Self, S>(parameter) tag: {
description: string | undefined;
key: Identifier;
of: (this: void, self: Simplify<{ readonly [K in keyof S]: S[K] extends FromLocalMethod<infer M> ? InjectLocal<M, Self> : S[K] extends LocalMethod<infer T> ? LocalEffect<T, never, Self> : S[K] extends DefaultMethod<infer F> ? F : S[K] extends …;
context: (self: Simplify<{ readonly [K in keyof S]: S[K] extends FromLocalMethod<infer M> ? InjectLocal<M, Self> : S[K] extends LocalMethod<infer T> ? LocalEffect<T, never, Self> : S[K] extends DefaultMethod<infer F> ? F : S[K] extends { readonly _…;
use: (f: (service: Simplify<{ readonly [K in keyof S]: S[K] extends FromLocalMethod<infer M> ? InjectLocal<M, Self> : S[K] extends LocalMethod<infer T> ? LocalEffect<T, never, Self> : S[K] extends DefaultMethod<infer F> ? F : S[K] extends { rea…;
useSync: (f: (service: Simplify<{ readonly [K in keyof S]: S[K] extends FromLocalMethod<infer M> ? InjectLocal<M, Self> : S[K] extends LocalMethod<infer T> ? LocalEffect<T, never, Self> : S[K] extends DefaultMethod<infer F> ? F : S[K] extends { rea…;
Identifier: Identifier;
Service: Shape;
stack: string | undefined;
pipe: { <A>(this: A): A; <A, B = never>(this: A, ab: (_: A) => B): B; <A, B = never, C = never>(this: A, ab: (_: A) => B, bc: (_: B) => C): C; <A, B = never, C = never, D = never>(this: A, ab: (_: A) => B, bc: (_: B) => C, cd: (_: C) => D): D; <…;
toString: () => string;
toJSON: () => unknown;
}
tag, endpoint: LookupDialEndpoint(parameter) endpoint: {
nodeKey: string;
kind: ProtocolKind;
url: string;
path: string;
}
endpoint),
).Pipeable.pipe<Effect.Effect<Context.Context<Self>, never, Scope.Scope>, Effect.Effect<Context.Context<Self>, never, never>>(this: Effect.Effect<Context.Context<Self>, never, Scope.Scope>, ab: (_: Effect.Effect<Context.Context<Self>, never, Scope.Scope>) => Effect.Effect<Context.Context<Self>, never, never>): Effect.Effect<Context.Context<Self>, never, never> (+21 overloads)pipe(import ScopeScope.const provide: {
(value: Scope): <A, E, R>(
self: Effect<A, E, R>
) => Effect<A, E, Exclude<R, Scope>>
<A, E, R>(
self: Effect<A, E, R>,
value: Scope
): Effect<A, E, Exclude<R, Scope>>
}
Provides a concrete Scope to an effect.
When to use
Use to run an effect that requires Scope with a scope managed by the
caller.
Details
Providing the scope removes the Scope requirement from the effect context.
Example (Providing a scope)
import { Console, Effect, Scope } from "effect"
// An effect that requires a Scope
const program = Effect.gen(function*() {
const scope = yield* Scope.Scope
yield* Scope.addFinalizer(scope, Console.log("Cleanup"))
yield* Console.log("Working...")
})
// Provide a scope to the program
const withScope = Effect.gen(function*() {
const scope = yield* Scope.make()
yield* Scope.provide(scope)(program)
})
provide(const scope: Scope.Closeableconst scope: {
strategy: "sequential" | "parallel";
state: State.Open | State.Closed | State.Empty;
}
scope));
const holder: {
current: ServiceOf<S, Self>
}
holder.current: ServiceOf<S, Self>current = import ContextContext.const get: {
<Services, I extends Services, S>(
service: Key<I, S>
): (self: Context<Services>) => S
<Services, I extends Services, S>(
self: Context<Services>,
service: Key<I, S>
): S
}
Gets a service from the context that corresponds to the given key.
When to use
Use when you need type-checked access to a service already included in the
context type.
Example (Getting a service from a context)
import { Context, pipe } from "effect"
import * as assert from "node:assert"
const Port = Context.Service<{ PORT: number }>("Port")
const Timeout = Context.Service<{ TIMEOUT: number }>("Timeout")
const context = pipe(
Context.make(Port, { PORT: 8080 }),
Context.add(Timeout, { TIMEOUT: 5000 })
)
assert.deepStrictEqual(Context.get(context, Timeout), { TIMEOUT: 5000 })
get(const ctx: Context.Context<Self>const ctx: {
mapUnsafe: ReadonlyMap<string, any>;
mutable: boolean;
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;
}
ctx, tag: HyperlinkTag<Self, S>(parameter) tag: {
description: string | undefined;
key: Identifier;
of: (this: void, self: Simplify<{ readonly [K in keyof S]: S[K] extends FromLocalMethod<infer M> ? InjectLocal<M, Self> : S[K] extends LocalMethod<infer T> ? LocalEffect<T, never, Self> : S[K] extends DefaultMethod<infer F> ? F : S[K] extends …;
context: (self: Simplify<{ readonly [K in keyof S]: S[K] extends FromLocalMethod<infer M> ? InjectLocal<M, Self> : S[K] extends LocalMethod<infer T> ? LocalEffect<T, never, Self> : S[K] extends DefaultMethod<infer F> ? F : S[K] extends { readonly _…;
use: (f: (service: Simplify<{ readonly [K in keyof S]: S[K] extends FromLocalMethod<infer M> ? InjectLocal<M, Self> : S[K] extends LocalMethod<infer T> ? LocalEffect<T, never, Self> : S[K] extends DefaultMethod<infer F> ? F : S[K] extends { rea…;
useSync: (f: (service: Simplify<{ readonly [K in keyof S]: S[K] extends FromLocalMethod<infer M> ? InjectLocal<M, Self> : S[K] extends LocalMethod<infer T> ? LocalEffect<T, never, Self> : S[K] extends DefaultMethod<infer F> ? F : S[K] extends { rea…;
Identifier: Identifier;
Service: Shape;
stack: string | undefined;
pipe: { <A>(this: A): A; <A, B = never>(this: A, ab: (_: A) => B): B; <A, B = never, C = never>(this: A, ab: (_: A) => B, bc: (_: B) => C): C; <A, B = never, C = never, D = never>(this: A, ab: (_: A) => B, bc: (_: B) => C, cd: (_: C) => D): D; <…;
toString: () => string;
toJSON: () => unknown;
}
tag);
});
const const endpoint:
| Endpoint
| LookupDirectoryEntry
endpoint = yield* const resolve: Effect.Effect<
Endpoint | LookupDirectoryEntry,
LookupClientError,
LookupDirectory | LookupAdvice
>
const resolve: {
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;
}
resolve;
yield* const install: (
endpoint: LookupDialEndpoint
) => Effect.Effect<void>
install(const endpoint:
| Endpoint
| LookupDirectoryEntry
endpoint);
let let currentKey: stringcurrentKey = const lookupDialKey: (
endpoint: LookupDialEndpoint
) => string
lookupDialKey(const endpoint:
| Endpoint
| LookupDirectoryEntry
endpoint);
let let currentNodeKey: stringcurrentNodeKey = const endpoint:
| Endpoint
| LookupDirectoryEntry
endpoint.nodeKey: stringnodeKey;
yield* const directory: {
readonly advertise: (payload: {
kind: "Http" | "WebSocket" | "IpcSocket";
nodeKey: string;
serves: readonly string[];
url?: string | undefined;
path?: string | undefined;
onConflict?: "livenessReplace" | "askIncumbent" | "reject" | "inherit" | undefined;
}) => Effect.Effect<LookupDirectoryEntry, IncumbentAlive, never>;
readonly unregister: (payload: {
nodeKey: string;
kind?: "Http" | "WebSocket" | "IpcSocket" | undefined;
url?: string | undefined;
path?: string | undefined;
}) => Effect.Effect<boolean, never, never>;
readonly nodesServing: (payload: {
...;
}) => Effect.Effect<...>;
readonly changes: Stream.Stream<...>;
}
directory.changes: Stream.Stream<
DirectoryUpserted | DirectoryRemoved,
never,
never
>
(property) changes: {
channel: Channel.Channel<Arr.NonEmptyReadonlyArray<A>, E, void, unknown, unknown, unknown, R>;
pipe: { <A>(this: A): A; <A, B = never>(this: A, ab: (_: A) => B): B; <A, B = never, C = never>(this: A, ab: (_: A) => B, bc: (_: B) => C): C; <A, B = never, C = never, D = never>(this: A, ab: (_: A) => B, bc: (_: B) => C, cd: (_: C) => D): D; <…;
}
changes.Pipeable.pipe<Stream.Stream<DirectoryUpserted | DirectoryRemoved, never, never>, Effect.Effect<void, never, LookupDirectory | LookupAdvice>, Effect.Effect<Fiber.Fiber<void, never>, never, LookupDirectory | LookupAdvice | Scope.Scope>>(this: Stream.Stream<DirectoryUpserted | DirectoryRemoved, never, never>, ab: (_: Stream.Stream<DirectoryUpserted | DirectoryRemoved, never, never>) => Effect.Effect<void, never, LookupDirectory | LookupAdvice>, bc: (_: Effect.Effect<...>) => Effect.Effect<...>): Effect.Effect<...> (+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((event: DirectoryUpserted | DirectoryRemovedevent) =>
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* () {
if (event: DirectoryUpserted | DirectoryRemovedevent._tag: "DirectoryUpserted" | "DirectoryRemoved"_tag === "DirectoryRemoved") {
if (event: DirectoryRemoved(parameter) event: {
_tag: 'DirectoryRemoved';
nodeKey: string;
previous: LookupDirectoryEntry;
}
event.nodeKey: stringnodeKey !== let currentNodeKey: stringcurrentNodeKey) return;
} else {
const const servesUs: booleanservesUs =
event: DirectoryUpserted(parameter) event: {
_tag: 'DirectoryUpserted';
entry: LookupDirectoryEntry;
dialChanged: boolean;
previous: LookupDirectoryEntry | undefined;
}
event.entry: LookupDirectoryEntry(property) entry: {
kind: 'Http' | 'WebSocket' | 'IpcSocket';
nodeKey: string;
serves: ReadonlyArray<string>;
url: string | undefined;
path: string | undefined;
}
entry.serves: readonly string[]serves.ReadonlyArray<string>.includes(searchElement: string, fromIndex?: number): booleanDetermines whether an array includes a certain element, returning true or false as appropriate.
includes(const serviceKey: stringserviceKey) ||
event: DirectoryUpserted(parameter) event: {
_tag: 'DirectoryUpserted';
entry: LookupDirectoryEntry;
dialChanged: boolean;
previous: LookupDirectoryEntry | undefined;
}
event.entry: LookupDirectoryEntry(property) entry: {
kind: 'Http' | 'WebSocket' | 'IpcSocket';
nodeKey: string;
serves: ReadonlyArray<string>;
url: string | undefined;
path: string | undefined;
}
entry.serves: readonly string[]serves.ReadonlyArray<string>.includes(searchElement: string, fromIndex?: number): booleanDetermines whether an array includes a certain element, returning true or false as appropriate.
includes(tag: HyperlinkTag<Self, S>(parameter) tag: {
description: string | undefined;
key: Identifier;
of: (this: void, self: Simplify<{ readonly [K in keyof S]: S[K] extends FromLocalMethod<infer M> ? InjectLocal<M, Self> : S[K] extends LocalMethod<infer T> ? LocalEffect<T, never, Self> : S[K] extends DefaultMethod<infer F> ? F : S[K] extends …;
context: (self: Simplify<{ readonly [K in keyof S]: S[K] extends FromLocalMethod<infer M> ? InjectLocal<M, Self> : S[K] extends LocalMethod<infer T> ? LocalEffect<T, never, Self> : S[K] extends DefaultMethod<infer F> ? F : S[K] extends { readonly _…;
use: (f: (service: Simplify<{ readonly [K in keyof S]: S[K] extends FromLocalMethod<infer M> ? InjectLocal<M, Self> : S[K] extends LocalMethod<infer T> ? LocalEffect<T, never, Self> : S[K] extends DefaultMethod<infer F> ? F : S[K] extends { rea…;
useSync: (f: (service: Simplify<{ readonly [K in keyof S]: S[K] extends FromLocalMethod<infer M> ? InjectLocal<M, Self> : S[K] extends LocalMethod<infer T> ? LocalEffect<T, never, Self> : S[K] extends DefaultMethod<infer F> ? F : S[K] extends { rea…;
Identifier: Identifier;
Service: Shape;
stack: string | undefined;
pipe: { <A>(this: A): A; <A, B = never>(this: A, ab: (_: A) => B): B; <A, B = never, C = never>(this: A, ab: (_: A) => B, bc: (_: B) => C): C; <A, B = never, C = never, D = never>(this: A, ab: (_: A) => B, bc: (_: B) => C, cd: (_: C) => D): D; <…;
toString: () => string;
toJSON: () => unknown;
}
tag.ServiceClass<Self, string, Simplify<{ readonly [K in keyof S]: S[K] extends FromLocalMethod<infer M> ? InjectLocal<M, Self> : S[K] extends LocalMethod<...> ? LocalEffect<...> : S[K] extends DefaultMethod<...> ? F : S[K] extends { ...; } ? SuccessOf<...> : S[K] extends { ...; } ? Subscribable<...> : S[K] extends { ...; } ? ClientMethod<...> : S[K] extends Spec ? Simplify<...> : never; }>>.key: stringkey);
const const isCurrent: booleanisCurrent = event: DirectoryUpserted(parameter) event: {
_tag: 'DirectoryUpserted';
entry: LookupDirectoryEntry;
dialChanged: boolean;
previous: LookupDirectoryEntry | undefined;
}
event.entry: LookupDirectoryEntry(property) entry: {
kind: 'Http' | 'WebSocket' | 'IpcSocket';
nodeKey: string;
serves: ReadonlyArray<string>;
url: string | undefined;
path: string | undefined;
}
entry.nodeKey: stringnodeKey === let currentNodeKey: stringcurrentNodeKey;
if (!const servesUs: booleanservesUs && !const isCurrent: booleanisCurrent) return;
// Same-dial refresh for our current node — nothing to rebuild.
if (
const isCurrent: booleanisCurrent &&
const servesUs: booleanservesUs &&
event: DirectoryUpserted(parameter) event: {
_tag: 'DirectoryUpserted';
entry: LookupDirectoryEntry;
dialChanged: boolean;
previous: LookupDirectoryEntry | undefined;
}
event.previous?: LookupDirectoryEntry | undefinedprevious !== var undefinedundefined &&
!event: DirectoryUpserted(parameter) event: {
_tag: 'DirectoryUpserted';
entry: LookupDirectoryEntry;
dialChanged: boolean;
previous: LookupDirectoryEntry | undefined;
}
event.dialChanged: booleandialChanged
) {
return;
}
}
const const next: Option.Option<
Endpoint | LookupDirectoryEntry
>
next = yield* import EffectEffect.const option: <A, E, R>(
self: Effect<A, E, R>
) => Effect<Option<A>, never, R>
Converts success to Option.some and failure to Option.none.
When to use
Use when you only care whether an effect succeeds and want recoverable
failures represented as Option.none.
Details
Success values become Option.some, recoverable failures become
Option.none, and defects still fail the effect.
Gotchas
option only captures typed, recoverable failures as Option.none.
Defects and interruptions are not captured inside the Option and still
fail the effect.
option also discards typed failure values. Use result if the failure
value matters.
Example (Capturing success or failure as Option)
import { Console, Effect, Option } from "effect"
const program = Effect.gen(function*() {
const someValue = yield* Effect.option(Effect.succeed(1))
const noneValue = yield* Effect.option(Effect.fail("missing"))
yield* Console.log(Option.isSome(someValue))
yield* Console.log(Option.isNone(noneValue))
})
Effect.runPromise(program)
// true
// true
option(const resolve: Effect.Effect<
Endpoint | LookupDirectoryEntry,
LookupClientError,
LookupDirectory | LookupAdvice
>
const resolve: {
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;
}
resolve);
if (import OptionOption.const isNone: <A>(
self: Option<A>
) => self is None<A>
Checks whether an Option is None (absent).
When to use
Use when you need to branch on an absent Option before accessing .value.
Details
- Acts as a type guard, narrowing to
None<A>
Example (Checking for None)
import { Option } from "effect"
console.log(Option.isNone(Option.some(1)))
// Output: false
console.log(Option.isNone(Option.none()))
// Output: true
isNone(const next: Option.Option<
Endpoint | LookupDirectoryEntry
>
next)) return;
const const nextKey: stringnextKey = const lookupDialKey: (
endpoint: LookupDialEndpoint
) => string
lookupDialKey(const next: Option.Some<
Endpoint | LookupDirectoryEntry
>
const next: {
_tag: "Some";
_op: "Some";
value: A;
valueOrUndefined: A;
pipe: { <A>(this: A): A; <A, B = never>(this: A, ab: (_: A) => B): B; <A, B = never, C = never>(this: A, ab: (_: A) => B, bc: (_: B) => C): C; <A, B = never, C = never, D = never>(this: A, ab: (_: A) => B, bc: (_: B) => C, cd: (_: C) => D): D; <…;
toString: () => string;
toJSON: () => unknown;
}
next.Some<Endpoint | DirectoryEntry>.value: Endpoint | LookupDirectoryEntryvalue);
if (const nextKey: stringnextKey === let currentKey: stringcurrentKey) return;
const const installed: Exit.Exit<void, never>installed = yield* import EffectEffect.const exit: <A, E, R>(
self: Effect<A, E, R>
) => Effect<Exit.Exit<A, E>, never, R>
Transforms an effect to encapsulate both failure and success using the Exit
data type.
When to use
Use when you need to inspect the full outcome, including typed failures, defects,
and interruptions.
Details
exit wraps an effect's success or failure inside an Exit type, allowing
you to handle both cases explicitly.
The resulting effect cannot fail because the failure is encapsulated within
the Exit.Failure type. The error type is set to never, indicating that
the effect is structured to never fail directly.
Example (Capturing completion as Exit)
import { Effect } from "effect"
const success = Effect.succeed(42)
const failure = Effect.fail("Something went wrong")
const program1 = Effect.exit(success)
const program2 = Effect.exit(failure)
Effect.runPromise(program1).then(console.log)
// { _id: 'Exit', _tag: 'Success', value: 42 }
Effect.runPromise(program2).then(console.log)
// { _id: 'Exit', _tag: 'Failure', cause: { _id: 'Cause', _tag: 'Fail', failure: 'Something went wrong' } }
exit(const install: (
endpoint: LookupDialEndpoint
) => Effect.Effect<void>
install(const next: Option.Some<
Endpoint | LookupDirectoryEntry
>
const next: {
_tag: "Some";
_op: "Some";
value: A;
valueOrUndefined: A;
pipe: { <A>(this: A): A; <A, B = never>(this: A, ab: (_: A) => B): B; <A, B = never, C = never>(this: A, ab: (_: A) => B, bc: (_: B) => C): C; <A, B = never, C = never, D = never>(this: A, ab: (_: A) => B, bc: (_: B) => C, cd: (_: C) => D): D; <…;
toString: () => string;
toJSON: () => unknown;
}
next.Some<Endpoint | DirectoryEntry>.value: Endpoint | LookupDirectoryEntryvalue));
if (import ExitExit.const isSuccess: <A, E>(
self: Exit<A, E>
) => self is Success<A, E>
Checks whether an Exit is a Success.
When to use
Use as a type guard to narrow Exit<A, E> to Success<A, E> and access the
value property.
Example (Narrowing to success)
import { Exit } from "effect"
const exit = Exit.succeed(42)
if (Exit.isSuccess(exit)) {
console.log(exit.value) // 42
}
isSuccess(const installed: Exit.Exit<void, never>installed)) {
let currentKey: stringcurrentKey = const nextKey: stringnextKey;
let currentNodeKey: stringcurrentNodeKey = const next: Option.Some<
Endpoint | LookupDirectoryEntry
>
const next: {
_tag: "Some";
_op: "Some";
value: A;
valueOrUndefined: A;
pipe: { <A>(this: A): A; <A, B = never>(this: A, ab: (_: A) => B): B; <A, B = never, C = never>(this: A, ab: (_: A) => B, bc: (_: B) => C): C; <A, B = never, C = never, D = never>(this: A, ab: (_: A) => B, bc: (_: B) => C, cd: (_: C) => D): D; <…;
toString: () => string;
toJSON: () => unknown;
}
next.Some<Endpoint | DirectoryEntry>.value: Endpoint | LookupDirectoryEntryvalue.nodeKey: stringnodeKey;
return;
}
yield* import EffectEffect.const logWarning: (
...message: ReadonlyArray<any>
) => Effect<void>
Logs one or more messages at the WARNING level.
Example (Logging warnings)
import { Effect } from "effect"
const program = Effect.gen(function*() {
yield* Effect.logWarning("API rate limit approaching")
yield* Effect.logWarning("Retries remaining:", 2, "Operation:", "fetchData")
// Useful for non-critical issues
const deprecated = true
if (deprecated) {
yield* Effect.logWarning("Using deprecated API endpoint")
}
})
Effect.runPromise(program)
// Output:
// timestamp=2023-... level=WARN message="API rate limit approaching"
// timestamp=2023-... level=WARN message="Retries remaining: 2 Operation: fetchData"
// timestamp=2023-... level=WARN message="Using deprecated API endpoint"
logWarning(
"lookupClient rebind failed; keeping prior dial",
).Pipeable.pipe<Effect.Effect<void, never, never>, Effect.Effect<void, never, never>>(this: Effect.Effect<void, never, never>, ab: (_: Effect.Effect<void, never, never>) => Effect.Effect<void, never, never>): Effect.Effect<void, never, never> (+21 overloads)pipe(
import EffectEffect.const annotateLogs: (values: Record<string, unknown>) => <A, E, R>(effect: Effect.Effect<A, E, R>) => Effect.Effect<A, E, R> (+3 overloads)annotateLogs({
"lookupClient.tag": tag: HyperlinkTag<Self, S>(parameter) tag: {
description: string | undefined;
key: Identifier;
of: (this: void, self: Simplify<{ readonly [K in keyof S]: S[K] extends FromLocalMethod<infer M> ? InjectLocal<M, Self> : S[K] extends LocalMethod<infer T> ? LocalEffect<T, never, Self> : S[K] extends DefaultMethod<infer F> ? F : S[K] extends …;
context: (self: Simplify<{ readonly [K in keyof S]: S[K] extends FromLocalMethod<infer M> ? InjectLocal<M, Self> : S[K] extends LocalMethod<infer T> ? LocalEffect<T, never, Self> : S[K] extends DefaultMethod<infer F> ? F : S[K] extends { readonly _…;
use: (f: (service: Simplify<{ readonly [K in keyof S]: S[K] extends FromLocalMethod<infer M> ? InjectLocal<M, Self> : S[K] extends LocalMethod<infer T> ? LocalEffect<T, never, Self> : S[K] extends DefaultMethod<infer F> ? F : S[K] extends { rea…;
useSync: (f: (service: Simplify<{ readonly [K in keyof S]: S[K] extends FromLocalMethod<infer M> ? InjectLocal<M, Self> : S[K] extends LocalMethod<infer T> ? LocalEffect<T, never, Self> : S[K] extends DefaultMethod<infer F> ? F : S[K] extends { rea…;
Identifier: Identifier;
Service: Shape;
stack: string | undefined;
pipe: { <A>(this: A): A; <A, B = never>(this: A, ab: (_: A) => B): B; <A, B = never, C = never>(this: A, ab: (_: A) => B, bc: (_: B) => C): C; <A, B = never, C = never, D = never>(this: A, ab: (_: A) => B, bc: (_: B) => C, cd: (_: C) => D): D; <…;
toString: () => string;
toJSON: () => unknown;
}
tag.ServiceClass<Self, string, Simplify<{ readonly [K in keyof S]: S[K] extends FromLocalMethod<infer M> ? InjectLocal<M, Self> : S[K] extends LocalMethod<...> ? LocalEffect<...> : S[K] extends DefaultMethod<...> ? F : S[K] extends { ...; } ? SuccessOf<...> : S[K] extends { ...; } ? Subscribable<...> : S[K] extends { ...; } ? ClientMethod<...> : S[K] extends Spec ? Simplify<...> : never; }>>.key: stringkey,
"lookupClient.node": const next: Option.Some<
Endpoint | LookupDirectoryEntry
>
const next: {
_tag: "Some";
_op: "Some";
value: A;
valueOrUndefined: A;
pipe: { <A>(this: A): A; <A, B = never>(this: A, ab: (_: A) => B): B; <A, B = never, C = never>(this: A, ab: (_: A) => B, bc: (_: B) => C): C; <A, B = never, C = never, D = never>(this: A, ab: (_: A) => B, bc: (_: B) => C, cd: (_: C) => D): D; <…;
toString: () => string;
toJSON: () => unknown;
}
next.Some<Endpoint | DirectoryEntry>.value: Endpoint | LookupDirectoryEntryvalue.nodeKey: stringnodeKey,
}),
);
}),
),
import EffectEffect.const forkScoped: <
Arg extends
| Effect<any, any, any>
| {
readonly startImmediately?:
| boolean
| undefined
readonly uninterruptible?:
| boolean
| "inherit"
| undefined
}
| undefined = {
readonly startImmediately?:
| boolean
| undefined
readonly uninterruptible?:
| boolean
| "inherit"
| undefined
}
>(
effectOrOptions?: Arg,
options?:
| {
readonly startImmediately?:
| boolean
| undefined
readonly uninterruptible?:
| boolean
| "inherit"
| undefined
}
| undefined
) => [Arg] extends [
Effect<infer _A, infer _E, infer _R>
]
? Effect<Fiber<_A, _E>, never, _R | Scope>
: <A, E, R>(
self: Effect<A, E, R>
) => Effect<Fiber<A, E>, never, R | Scope>
Forks the fiber in a Scope, interrupting it when the scope is closed.
Example (Forking into the current scope)
import { Effect } from "effect"
const backgroundTask = Effect.gen(function*() {
yield* Effect.sleep("5 seconds")
yield* Effect.log("Background task completed")
return "result"
})
const program = Effect.scoped(
Effect.gen(function*() {
const fiber = yield* backgroundTask.pipe(Effect.forkScoped)
// or fork a fiber that starts immediately:
yield* backgroundTask.pipe(Effect.forkScoped({ startImmediately: true }))
yield* Effect.log("Task forked in scope")
yield* Effect.sleep("1 second")
// Fiber will be interrupted when scope closes
return "scope completed"
})
)
forkScoped,
);
yield* import EffectEffect.const addFinalizer: <R>(
finalizer: (
exit: Exit.Exit<unknown, unknown>
) => Effect<void, never, R>
) => Effect<void, never, R | Scope>
Adds a finalizer to the current scope.
When to use
Use to register low-level cleanup in the current scope.
Details
The finalizer runs when the surrounding scope is closed and receives the
Exit value used to close the scope.
Example (Registering scope finalizers)
import { Console, Effect, Exit } from "effect"
const program = Effect.scoped(
Effect.gen(function*() {
// Add a finalizer that runs when the scope closes
yield* Effect.addFinalizer((exit) =>
Console.log(
Exit.isSuccess(exit)
? "Cleanup: Operation completed successfully"
: "Cleanup: Operation failed, cleaning up resources"
)
)
yield* Console.log("Performing main operation...")
// This could succeed or fail
return "operation result"
})
)
Effect.runPromise(program).then(console.log)
// Output:
// Performing main operation...
// Cleanup: Operation completed successfully
// operation result
addFinalizer(() =>
let clientScope:
| Scope.Closeable
| undefined
clientScope === var undefinedundefined
? import EffectEffect.const void: Effect.Effect<void, never, never>(alias) const void: {
pipe: { <A>(this: A): A; <A, B = never>(this: A, ab: (_: A) => B): B; <A, B = never, C = never>(this: A, ab: (_: A) => B, bc: (_: B) => C): C; <A, B = never, C = never, D = never>(this: A, ab: (_: A) => B, bc: (_: B) => C, cd: (_: C) => D): D; <…;
toString: () => string;
toJSON: () => unknown;
}
Returns an effect that succeeds with void.
void
: import ScopeScope.const close: <A, E>(
self: Scope,
exit: Exit<A, E>
) => Effect<void>
Closes a scope and runs its registered finalizers.
When to use
Use to close a scope manually with a specific exit value.
Details
Finalizers run in the scope's configured order and receive the supplied
Exit.
Example (Running scope finalizers)
import { Console, Effect, Exit, Scope } from "effect"
const resourceManagement = Effect.gen(function*() {
const scope = yield* Scope.make("sequential")
// Add multiple finalizers
yield* Scope.addFinalizer(scope, Console.log("Close database connection"))
yield* Scope.addFinalizer(scope, Console.log("Close file handle"))
yield* Scope.addFinalizer(scope, Console.log("Release memory"))
// Do some work...
yield* Console.log("Performing operations...")
// Close scope - finalizers run in reverse order of registration
yield* Scope.close(scope, Exit.succeed("Success!"))
// Output: "Release memory", "Close file handle", "Close database connection"
})
close(let clientScope:
| Scope.Closeable
| undefined
let clientScope: {
strategy: "sequential" | "parallel";
state: State.Open | State.Closed | State.Empty;
}
clientScope, import ExitExit.const void: Exit.Exit<void, never>Provides a pre-allocated successful Exit with a void value.
When to use
Use when you need a shared successful Exit with no meaningful value.
Details
Equivalent to Exit.succeed(undefined) but shared as a single instance,
avoiding allocation for a common case.
Example (Referencing the void Exit)
import { Exit } from "effect"
const exit = Exit.void
console.log(Exit.isSuccess(exit)) // true
void),
);
return const makeLiveLookupService: <
Self,
S extends Spec
>(
tag: HyperlinkTag<Self, S>,
holder: { current: ServiceOf<S, Self> }
) => ServiceOf<S, Self>
Stable service facade that always reads
holder
.current — so Directory
dial swaps can replace the underlying client without changing the Context value.
makeLiveLookupService(tag: HyperlinkTag<Self, S>(parameter) tag: {
description: string | undefined;
key: Identifier;
of: (this: void, self: Simplify<{ readonly [K in keyof S]: S[K] extends FromLocalMethod<infer M> ? InjectLocal<M, Self> : S[K] extends LocalMethod<infer T> ? LocalEffect<T, never, Self> : S[K] extends DefaultMethod<infer F> ? F : S[K] extends …;
context: (self: Simplify<{ readonly [K in keyof S]: S[K] extends FromLocalMethod<infer M> ? InjectLocal<M, Self> : S[K] extends LocalMethod<infer T> ? LocalEffect<T, never, Self> : S[K] extends DefaultMethod<infer F> ? F : S[K] extends { readonly _…;
use: (f: (service: Simplify<{ readonly [K in keyof S]: S[K] extends FromLocalMethod<infer M> ? InjectLocal<M, Self> : S[K] extends LocalMethod<infer T> ? LocalEffect<T, never, Self> : S[K] extends DefaultMethod<infer F> ? F : S[K] extends { rea…;
useSync: (f: (service: Simplify<{ readonly [K in keyof S]: S[K] extends FromLocalMethod<infer M> ? InjectLocal<M, Self> : S[K] extends LocalMethod<infer T> ? LocalEffect<T, never, Self> : S[K] extends DefaultMethod<infer F> ? F : S[K] extends { rea…;
Identifier: Identifier;
Service: Shape;
stack: string | undefined;
pipe: { <A>(this: A): A; <A, B = never>(this: A, ab: (_: A) => B): B; <A, B = never, C = never>(this: A, ab: (_: A) => B, bc: (_: B) => C): C; <A, B = never, C = never, D = never>(this: A, ab: (_: A) => B, bc: (_: B) => C, cd: (_: C) => D): D; <…;
toString: () => string;
toJSON: () => unknown;
}
tag, const holder: {
current: ServiceOf<S, Self>
}
holder);
}),
) as import LayerLayer.interface Layer<in ROut, out E = never, out RIn = never>A Layer describes how to build one or more services for dependency injection.
When to use
Use to model construction of application services for dependency injection,
especially when services have dependencies, can fail during construction, or
need scoped setup and release.
Details
A Layer<ROut, E, RIn> represents ROut as the services this layer
provides, E as the possible errors during layer construction, and RIn as
the services this layer requires as dependencies.
Layer<
function (type parameter) Self in <Self, S extends Spec>(tag: HyperlinkTag<Self, S>, options?: LookupClientOptions): Layer.Layer<Self, LookupClientError, LookupIdentity | LookupDirectory | LookupAdvice>Self,
class LookupClientErrorclass LookupClientError {
name: string;
message: string;
stack: string;
cause: unknown;
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;
_tag: Tag;
tag: string;
reason: 'missing' | 'ambiguous';
count: number;
}
lookupClient
could not resolve exactly one dial target for the Tag
(missing = none; ambiguous = more than one directory row and no
LookupClientOptions.pick
).
LookupClientError,
class LookupIdentityclass LookupIdentity {
key: Identifier;
Service: {
claim: (payload: { kind: 'Http' | 'WebSocket' | 'IpcSocket'; key: string; nodeKey: string; url?: string | undefined; path?: string | undefined }) => Effect.Effect<Endpoint, DuplicateIdentity, never>;
resolve: (payload: { key: string }) => Effect.Effect<Option.Option<Endpoint>, never, never>;
};
}
Lookup identity service — claim HyperService keys (first wins; dead winners replaceable).
LookupIdentity | class LookupDirectoryclass LookupDirectory {
key: Identifier;
Service: {
advertise: (payload: { kind: 'Http' | 'WebSocket' | 'IpcSocket'; nodeKey: string; serves: ReadonlyArray<string>; url?: string | undefined; path?: string | undefined; onConflict?: 'livenessReplace' | 'askIncumbent' | 'reject' | 'inherit' | undefined }…;
unregister: (payload: { nodeKey: string; kind?: 'Http' | 'WebSocket' | 'IpcSocket' | undefined; url?: string | undefined; path?: string | undefined }) => Effect.Effect<boolean, never, never>;
nodesServing: (payload: { serviceKey: string }) => Effect.Effect<ReadonlyArray<LookupDirectoryEntry>, never, never>;
changes: Stream.Stream<DirectoryUpserted | DirectoryRemoved, never, never>;
};
}
Lookup node directory — advertise / unregister / list by served HyperService key /
directorySpec.changes
membership push.
LookupDirectory | class LookupAdviceclass LookupAdvice {
key: Identifier;
Service: {
advise: (payload: { serviceKey: string; prefer: string }) => Effect.Effect<string, never, never>;
clear: (payload: { serviceKey: string }) => Effect.Effect<boolean, never, never>;
preferred: (payload: { serviceKey: string }) => Effect.Effect<Option.Option<string>, never, never>;
};
}
Lookup placement board — coordinator advice for nodeless /
Hyperlink.lookupClient
dial.
v1: last-write-wins, in-memory, no advisor ACL. Algorithms stay app-owned (who calls
advise
); Lookup only stores and surfaces the preference.
LookupAdvice
>;