Creating a Hyperlink Service
Build one Hyperlink Service end to end: declare a Tag, write its Contract with an Implementation, place it with a Layer, and call it through a Handle.
Each fence adds one piece. The Tag runs in-process when you finish. The same call site still works when you later serve or client it.
Contract method shapes live in Core Concepts. Serve, client, and fleet layers live in Managing Layers. Prebuilt HyperServices (WorkPool, Daemon, and the rest) are optional tools — secondary to building your own.
Declare the Tag
Put the Contract on the Tag: methods and their schemas. Nothing runs yet. This is the typed name everything else hangs from:
import * as import HyperlinkHyperlink from "hyperlink-ts/Hyperlink"
import { import SchemaSchema } from "effect"
class class Counterclass Counter {
key: Identifier;
Service: {
value: Hyperlink.Subscribable<number>;
increment: (payload: { by: number }) => Effect<void, never, never>;
reset: Effect<void, never, never>;
};
}
Counter extends import HyperlinkHyperlink.Tag<Counter>(): SchemaTagBuilder<Counter> (+2 overloads)
export Tag
Schema-driven solo: infer the service from spec; bare
local
is a compile error.
Tag<class Counterclass Counter {
key: Identifier;
Service: {
value: Hyperlink.Subscribable<number>;
increment: (payload: { by: number }) => Effect<void, never, never>;
reset: Effect<void, never, never>;
};
}
Counter>()("app/Counter", {
value: Hyperlink.RefField<
Hyperlink.Method<
undefined,
Schema.Number,
Schema.Never,
true,
Hyperlink.MethodAnnotations,
Hyperlink.Derive
>
>
value: import HyperlinkHyperlink.const ref: <Su extends Schema.Top>(
success: Su
) => RefField<
Method<undefined, Su, typeof Schema.Never, true>
>
Define a ref field — reactive state surfaced as a
Subscribable
(get + changes),
uniform local and remote. The impl owns a SubscriptionRef (writes it) and provides it via
subscribable
; consumers read (yield* svc.x.get) and observe (svc.x.changes) — a read
is an honest Effect, not a synchronous peek. For values fixed at acquire use
value
; for
on-demand calls use
effect
.
ref(import SchemaSchema.const Number: Numberconst Number: {
Rebuild: Rebuild;
Iso: Iso;
ast: Ast;
Type: T;
Encoded: E;
DecodingServices: RD;
EncodingServices: RE;
annotate: (annotations: Schema.Annotations.Bottom<number, readonly []>) => Schema.Number;
annotateKey: (annotations: Schema.Annotations.Key<number>) => Schema.Number;
check: (checks_0: Check<number>, ...checks: Array<Check<number>>) => Schema.Number;
rebuild: (ast: Number) => Schema.Number;
make: (input: number, options?: MakeOptions) => number;
makeOption: (input: number, options?: MakeOptions) => Option_.Option<number>;
makeEffect: (input: number, options?: MakeOptions) => Effect.Effect<number, SchemaError, never>;
pipe: { <A>(this: A): A; <A, B = never>(this: A, ab: (_: A) => B): B; <A, B = never, C = never>(this: A, ab: (_: A) => B, bc: (_: B) => C): C; <A, B = never, C = never, D = never>(this: A, ab: (_: A) => B, bc: (_: B) => C, cd: (_: C) => D): D; <…;
}
Type-level representation of
Number
.
Schema for number values, including NaN, Infinity, and -Infinity.
Details
Default JSON serializer:
- Finite numbers are serialized as numbers.
- Non-finite values are serialized as strings (
"NaN", "Infinity", "-Infinity").
Number),
increment: Hyperlink.Method<
{ readonly by: Schema.Number },
Schema.Void,
Schema.Never,
false,
Hyperlink.MethodAnnotations,
Hyperlink.Derive
>
(property) increment: {
kind: MethodKind;
payload: P;
success: Su;
error: E;
stream: Str;
annotations: Ann;
annotate: <A extends MethodAnnotations>(annotations: A) => Method<P, Su, E, Str, Ann & A, Client>;
pipe: { <A>(this: A): A; <A, B = never>(this: A, ab: (_: A) => B): B; <A, B = never, C = never>(this: A, ab: (_: A) => B, bc: (_: B) => C): C; <A, B = never, C = never, D = never>(this: A, ab: (_: A) => B, bc: (_: B) => C, cd: (_: C) => D): D; <…;
}
increment: import HyperlinkHyperlink.function effectFn<{
readonly by: Schema.Number;
}>(payload: {
readonly by: Schema.Number;
}): Hyperlink.Method<{
readonly by: Schema.Number;
}, Schema.Void, Schema.Never, false, Hyperlink.MethodAnnotations, Hyperlink.Derive> (+7 overloads)
Two-stage
effectFn
— override the client-facing type with a Client that must narrow
the schema-derived shape: effectFn<Client>()(payload). Reshape freely (e.g. add overloads), but a
Client that would accept payloads the wire rejects fails to compile (payload resolves to never).
For an override that can't be a narrowing (a generic library), use
unsafeEffectFn
.
effectFn({ by: Schema.Number(property) by: {
Rebuild: Rebuild;
Iso: Iso;
ast: Ast;
Type: T;
Encoded: E;
DecodingServices: RD;
EncodingServices: RE;
annotate: (annotations: Schema.Annotations.Bottom<number, readonly []>) => Schema.Number;
annotateKey: (annotations: Schema.Annotations.Key<number>) => Schema.Number;
check: (checks_0: Check<number>, ...checks: Array<Check<number>>) => Schema.Number;
rebuild: (ast: Number) => Schema.Number;
make: (input: number, options?: MakeOptions) => number;
makeOption: (input: number, options?: MakeOptions) => Option_.Option<number>;
makeEffect: (input: number, options?: MakeOptions) => Effect.Effect<number, SchemaError, never>;
pipe: { <A>(this: A): A; <A, B = never>(this: A, ab: (_: A) => B): B; <A, B = never, C = never>(this: A, ab: (_: A) => B, bc: (_: B) => C): C; <A, B = never, C = never, D = never>(this: A, ab: (_: A) => B, bc: (_: B) => C, cd: (_: C) => D): D; <…;
}
by: import SchemaSchema.const Number: Numberconst Number: {
Rebuild: Rebuild;
Iso: Iso;
ast: Ast;
Type: T;
Encoded: E;
DecodingServices: RD;
EncodingServices: RE;
annotate: (annotations: Schema.Annotations.Bottom<number, readonly []>) => Schema.Number;
annotateKey: (annotations: Schema.Annotations.Key<number>) => Schema.Number;
check: (checks_0: Check<number>, ...checks: Array<Check<number>>) => Schema.Number;
rebuild: (ast: Number) => Schema.Number;
make: (input: number, options?: MakeOptions) => number;
makeOption: (input: number, options?: MakeOptions) => Option_.Option<number>;
makeEffect: (input: number, options?: MakeOptions) => Effect.Effect<number, SchemaError, never>;
pipe: { <A>(this: A): A; <A, B = never>(this: A, ab: (_: A) => B): B; <A, B = never, C = never>(this: A, ab: (_: A) => B, bc: (_: B) => C): C; <A, B = never, C = never, D = never>(this: A, ab: (_: A) => B, bc: (_: B) => C, cd: (_: C) => D): D; <…;
}
Type-level representation of
Number
.
Schema for number values, including NaN, Infinity, and -Infinity.
Details
Default JSON serializer:
- Finite numbers are serialized as numbers.
- Non-finite values are serialized as strings (
"NaN", "Infinity", "-Infinity").
Number }),
reset: Hyperlink.Method<
undefined,
Schema.Void,
Schema.Never,
false,
Hyperlink.MethodAnnotations,
Hyperlink.Derive
>
(property) reset: {
kind: MethodKind;
payload: P;
success: Su;
error: E;
stream: Str;
annotations: Ann;
annotate: <A extends MethodAnnotations>(annotations: A) => Method<P, Su, E, Str, Ann & A, Client>;
pipe: { <A>(this: A): A; <A, B = never>(this: A, ab: (_: A) => B): B; <A, B = never, C = never>(this: A, ab: (_: A) => B, bc: (_: B) => C): C; <A, B = never, C = never, D = never>(this: A, ab: (_: A) => B, bc: (_: B) => C, cd: (_: C) => D): D; <…;
}
reset: import HyperlinkHyperlink.function effect<Schema.Void>(success: Schema.Void): Hyperlink.Method<undefined, Schema.Void, Schema.Never, false, Hyperlink.MethodAnnotations, Hyperlink.Derive> (+3 overloads)Two-stage
effect
— override the client-facing type with a Client (an Effect type;
a read surfaces as Effect<Success>) that must narrow the schema-derived shape:
effect<Client>()(success). Widening the success fails to compile. For a free override, see
effect(import SchemaSchema.const Void: Voidconst Void: {
Rebuild: Rebuild;
Iso: Iso;
ast: Ast;
Type: T;
Encoded: E;
DecodingServices: RD;
EncodingServices: RE;
annotate: (annotations: Schema.Annotations.Bottom<void, readonly []>) => Schema.Void;
annotateKey: (annotations: Schema.Annotations.Key<void>) => Schema.Void;
check: (checks_0: Check<void>, ...checks: Array<Check<void>>) => Schema.Void;
rebuild: (ast: Void) => Schema.Void;
make: (input: void, options?: MakeOptions) => void;
makeOption: (input: void, options?: MakeOptions) => Option_.Option<void>;
makeEffect: (input: void, options?: MakeOptions) => Effect.Effect<void, SchemaError, never>;
pipe: { <A>(this: A): A; <A, B = never>(this: A, ab: (_: A) => B): B; <A, B = never, C = never>(this: A, ab: (_: A) => B, bc: (_: B) => C): C; <A, B = never, C = never, D = never>(this: A, ab: (_: A) => B, bc: (_: B) => C, cd: (_: C) => D): D; <…;
}
Type-level representation of
Void
.
Schema for a TypeScript void return value.
When to use
Use when you need to model the return value of a function, RPC, or endpoint
whose result is intentionally ignored.
Details
Runtime parsing accepts any present value and discards it, producing
undefined. The public decoded and encoded TypeScript representation remains
void, so typed construction, decoding, and encoding APIs are still modeled
as void.
Void),
}) {}Fulfil it
Return those methods from an Implementation. A SubscriptionRef backs the observable value:
const const counterImpl: Effect.Effect<
{
value: Hyperlink.Subscribable<number>
increment: ({
by,
}: {
readonly by: number
}) => Effect.Effect<void, never, never>
reset: Effect.Effect<void, never, never>
},
never,
never
>
const counterImpl: {
pipe: { <A>(this: A): A; <A, B = never>(this: A, ab: (_: A) => B): B; <A, B = never, C = never>(this: A, ab: (_: A) => B, bc: (_: B) => C): C; <A, B = never, C = never, D = never>(this: A, ab: (_: A) => B, bc: (_: B) => C, cd: (_: C) => D): D; <…;
toString: () => string;
toJSON: () => unknown;
}
counterImpl = import EffectEffect.const gen: {
<Eff extends Effect<any, any, any>, AEff>(
f: () => Generator<Eff, AEff, never>
): Effect<
AEff,
[Eff] extends [never]
? never
: [Eff] extends [
Effect<infer _A, infer E, infer _R>
]
? E
: never,
[Eff] extends [never]
? never
: [Eff] extends [
Effect<infer _A, infer _E, infer R>
]
? R
: never
>
<Self, Eff extends Effect<any, any, any>, AEff>(
options: { readonly self: Self },
f: (this: Self) => Generator<Eff, AEff, never>
): Effect<
AEff,
[Eff] extends [never]
? never
: [Eff] extends [
Effect<infer _A, infer E, infer _R>
]
? E
: never,
[Eff] extends [never]
? never
: [Eff] extends [
Effect<infer _A, infer _E, infer R>
]
? R
: never
>
}
Provides a way to write effectful code using generator functions, simplifying
control flow and error handling.
When to use
Use when you want to write effectful code that looks and behaves like
synchronous code, while still handling asynchronous tasks, errors, and complex
control flow such as loops and conditions.
Generator functions work similarly to async/await but keep errors,
requirements, and interruption in the Effect type. You can yield* values
from effects and return the final result at the end.
Example (Sequencing effects with generators)
import { Data, Effect } from "effect"
class DiscountRateError extends Data.TaggedError("DiscountRateError")<{}> {}
const addServiceCharge = (amount: number) => amount + 1
const applyDiscount = (
total: number,
discountRate: number
): Effect.Effect<number, DiscountRateError> =>
discountRate === 0
? Effect.fail(new DiscountRateError())
: Effect.succeed(total - (total * discountRate) / 100)
const fetchTransactionAmount = Effect.promise(() => Promise.resolve(100))
const fetchDiscountRate = Effect.promise(() => Promise.resolve(5))
export const program = Effect.gen(function*() {
const transactionAmount = yield* fetchTransactionAmount
const discountRate = yield* fetchDiscountRate
const discountedAmount = yield* applyDiscount(
transactionAmount,
discountRate
)
const finalAmount = addServiceCharge(discountedAmount)
return `Final amount to charge: ${finalAmount}`
})
gen(function* () {
const const ref: SubscriptionRef.SubscriptionRef<number>const ref: {
value: A;
semaphore: Semaphore.Semaphore;
pubsub: PubSub.PubSub<A>;
pipe: { <A>(this: A): A; <A, B = never>(this: A, ab: (_: A) => B): B; <A, B = never, C = never>(this: A, ab: (_: A) => B, bc: (_: B) => C): C; <A, B = never, C = never, D = never>(this: A, ab: (_: A) => B, bc: (_: B) => C, cd: (_: C) => D): D; <…;
}
ref = yield* import SubscriptionRefSubscriptionRef.const make: <A>(
value: A
) => Effect.Effect<SubscriptionRef<A>>
Constructs a new SubscriptionRef from an initial value.
When to use
Use to create a SubscriptionRef when consumers need to read the latest
value and subscribe to every update.
Details
The initial value is published during construction, so changes starts new
subscribers with that value before future updates.
make(0)
return {
value: Hyperlink.Subscribable<number>(property) value: {
get: Effect.Effect<A>;
changes: Stream.Stream<A>;
}
value: import HyperlinkHyperlink.const subscribable: <A>(
source: SubscriptionRef.SubscriptionRef<A>
) => Subscribable<A>
Build a
Subscribable
view over a SubscriptionRef — the impl side of a
ref
field: the
impl owns the ref (writes it), consumers get read + observe.
subscribable(const ref: SubscriptionRef.SubscriptionRef<number>const ref: {
value: A;
semaphore: Semaphore.Semaphore;
pubsub: PubSub.PubSub<A>;
pipe: { <A>(this: A): A; <A, B = never>(this: A, ab: (_: A) => B): B; <A, B = never, C = never>(this: A, ab: (_: A) => B, bc: (_: B) => C): C; <A, B = never, C = never, D = never>(this: A, ab: (_: A) => B, bc: (_: B) => C, cd: (_: C) => D): D; <…;
}
ref),
increment: ({
by,
}: {
readonly by: number
}) => Effect.Effect<void, never, never>
increment: ({ by: numberby }: { readonly by: numberby: number }) =>
import SubscriptionRefSubscriptionRef.const update: {
<A>(update: (a: A) => A): (
self: SubscriptionRef<A>
) => Effect.Effect<void>
<A>(
self: SubscriptionRef<A>,
update: (a: A) => A
): Effect.Effect<void>
}
Updates the value of the SubscriptionRef with the result of applying a
function, notifying subscribers of the change.
Example (Updating a value)
import { Effect, SubscriptionRef } from "effect"
const program = Effect.gen(function*() {
const ref = yield* SubscriptionRef.make(10)
yield* SubscriptionRef.update(ref, (n) => n * 2)
const value = yield* SubscriptionRef.get(ref)
console.log(value)
})
update(const ref: SubscriptionRef.SubscriptionRef<number>const ref: {
value: A;
semaphore: Semaphore.Semaphore;
pubsub: PubSub.PubSub<A>;
pipe: { <A>(this: A): A; <A, B = never>(this: A, ab: (_: A) => B): B; <A, B = never, C = never>(this: A, ab: (_: A) => B, bc: (_: B) => C): C; <A, B = never, C = never, D = never>(this: A, ab: (_: A) => B, bc: (_: B) => C, cd: (_: C) => D): D; <…;
}
ref, (n: numbern) => n: numbern + by: numberby),
reset: Effect.Effect<void, never, never>(property) reset: {
pipe: { <A>(this: A): A; <A, B = never>(this: A, ab: (_: A) => B): B; <A, B = never, C = never>(this: A, ab: (_: A) => B, bc: (_: B) => C): C; <A, B = never, C = never, D = never>(this: A, ab: (_: A) => B, bc: (_: B) => C, cd: (_: C) => D): D; <…;
toString: () => string;
toJSON: () => unknown;
}
reset: import SubscriptionRefSubscriptionRef.const set: {
<A>(value: A): (
self: SubscriptionRef<A>
) => Effect.Effect<void>
<A>(
self: SubscriptionRef<A>,
value: A
): Effect.Effect<void>
}
Sets the value of the SubscriptionRef, notifying all subscribers of the
change.
Example (Setting a value)
import { Effect, SubscriptionRef } from "effect"
const program = Effect.gen(function*() {
const ref = yield* SubscriptionRef.make(0)
yield* SubscriptionRef.set(ref, 42)
const value = yield* SubscriptionRef.get(ref)
console.log(value)
})
set(const ref: SubscriptionRef.SubscriptionRef<number>const ref: {
value: A;
semaphore: Semaphore.Semaphore;
pubsub: PubSub.PubSub<A>;
pipe: { <A>(this: A): A; <A, B = never>(this: A, ab: (_: A) => B): B; <A, B = never, C = never>(this: A, ab: (_: A) => B, bc: (_: B) => C): C; <A, B = never, C = never, D = never>(this: A, ab: (_: A) => B, bc: (_: B) => C, cd: (_: C) => D): D; <…;
}
ref, 0),
}
})Place it in-process
Wire Tag and Implementation with Hyperlink.layer:
const const inProcess: Layer<
Counter | Hyperlink.Local<Counter>,
never,
never
>
const inProcess: {
build: (memoMap: MemoMap, scope: Scope.Scope) => Effect<Context.Context<Counter | Local<Counter>>, never, never>;
pipe: { <A>(this: A): A; <A, B = never>(this: A, ab: (_: A) => B): B; <A, B = never, C = never>(this: A, ab: (_: A) => B, bc: (_: B) => C): C; <A, B = never, C = never, D = never>(this: A, ab: (_: A) => B, bc: (_: B) => C, cd: (_: C) => D): D; <…;
}
inProcess = import HyperlinkHyperlink.layer<Counter, {
readonly value: Hyperlink.RefField<Hyperlink.Method<undefined, Schema.Number, Schema.Never, true, Hyperlink.MethodAnnotations, Hyperlink.Derive>>;
readonly increment: Hyperlink.Method<{
readonly by: Schema.Number;
}, Schema.Void, Schema.Never, false, Hyperlink.MethodAnnotations, Hyperlink.Derive>;
readonly reset: Hyperlink.Method<undefined, Schema.Void, Schema.Never, false, Hyperlink.MethodAnnotations, Hyperlink.Derive>;
}, never>(tag: Hyperlink.HyperlinkTag<...>, impl: Effect.Effect<...>): Layer<...> (+3 overloads)
export layer
layer(class Counterclass Counter {
key: Identifier;
Service: {
value: Hyperlink.Subscribable<number>;
increment: (payload: { by: number }) => Effect.Effect<void, never, never>;
reset: Effect.Effect<void, never, never>;
};
description: string | undefined;
of: (this: void, self: { readonly value: Hyperlink.Subscribable<number>; readonly increment: (payload: { by: number }) => Effect.Effect<void, never, never>; readonly reset: Effect.Effect<void, never, never> }) => { readonly value: Hyperlink.Su…;
context: (self: { readonly value: Hyperlink.Subscribable<number>; readonly increment: (payload: { by: number }) => Effect.Effect<void, never, never>; readonly reset: Effect.Effect<void, never, never> }) => Context<Counter>;
use: (f: (service: { readonly value: Hyperlink.Subscribable<number>; readonly increment: (payload: { by: number }) => Effect.Effect<void, never, never>; readonly reset: Effect.Effect<void, never, never> }) => Effect.Effect<A, E, R>) => Effect.E…;
useSync: (f: (service: { readonly value: Hyperlink.Subscribable<number>; readonly increment: (payload: { by: number }) => Effect.Effect<void, never, never>; readonly reset: Effect.Effect<void, never, never> }) => A) => Effect.Effect<A, never, Count…;
Identifier: Identifier;
stack: string | undefined;
pipe: { <A>(this: A): A; <A, B = never>(this: A, ab: (_: A) => B): B; <A, B = never, C = never>(this: A, ab: (_: A) => B, bc: (_: B) => C): C; <A, B = never, C = never, D = never>(this: A, ab: (_: A) => B, bc: (_: B) => C, cd: (_: C) => D): D; <…;
toString: () => string;
toJSON: () => unknown;
}
Counter, const counterImpl: Effect.Effect<
{
value: Hyperlink.Subscribable<number>
increment: ({
by,
}: {
readonly by: number
}) => Effect.Effect<void, never, never>
reset: Effect.Effect<void, never, never>
},
never,
never
>
const counterImpl: {
pipe: { <A>(this: A): A; <A, B = never>(this: A, ab: (_: A) => B): B; <A, B = never, C = never>(this: A, ab: (_: A) => B, bc: (_: B) => C): C; <A, B = never, C = never, D = never>(this: A, ab: (_: A) => B, bc: (_: B) => C, cd: (_: C) => D): D; <…;
toString: () => string;
toJSON: () => unknown;
}
counterImpl)Call the Handle
yield* Counter returns the Handle. Increment, read value, reset. Same shapes later sit behind RPC:
const const program: Effect.Effect<
number,
never,
never
>
const program: {
pipe: { <A>(this: A): A; <A, B = never>(this: A, ab: (_: A) => B): B; <A, B = never, C = never>(this: A, ab: (_: A) => B, bc: (_: B) => C): C; <A, B = never, C = never, D = never>(this: A, ab: (_: A) => B, bc: (_: B) => C, cd: (_: C) => D): D; <…;
toString: () => string;
toJSON: () => unknown;
}
program = import EffectEffect.const gen: {
<Eff extends Effect<any, any, any>, AEff>(
f: () => Generator<Eff, AEff, never>
): Effect<
AEff,
[Eff] extends [never]
? never
: [Eff] extends [
Effect<infer _A, infer E, infer _R>
]
? E
: never,
[Eff] extends [never]
? never
: [Eff] extends [
Effect<infer _A, infer _E, infer R>
]
? R
: never
>
<Self, Eff extends Effect<any, any, any>, AEff>(
options: { readonly self: Self },
f: (this: Self) => Generator<Eff, AEff, never>
): Effect<
AEff,
[Eff] extends [never]
? never
: [Eff] extends [
Effect<infer _A, infer E, infer _R>
]
? E
: never,
[Eff] extends [never]
? never
: [Eff] extends [
Effect<infer _A, infer _E, infer R>
]
? R
: never
>
}
Provides a way to write effectful code using generator functions, simplifying
control flow and error handling.
When to use
Use when you want to write effectful code that looks and behaves like
synchronous code, while still handling asynchronous tasks, errors, and complex
control flow such as loops and conditions.
Generator functions work similarly to async/await but keep errors,
requirements, and interruption in the Effect type. You can yield* values
from effects and return the final result at the end.
Example (Sequencing effects with generators)
import { Data, Effect } from "effect"
class DiscountRateError extends Data.TaggedError("DiscountRateError")<{}> {}
const addServiceCharge = (amount: number) => amount + 1
const applyDiscount = (
total: number,
discountRate: number
): Effect.Effect<number, DiscountRateError> =>
discountRate === 0
? Effect.fail(new DiscountRateError())
: Effect.succeed(total - (total * discountRate) / 100)
const fetchTransactionAmount = Effect.promise(() => Promise.resolve(100))
const fetchDiscountRate = Effect.promise(() => Promise.resolve(5))
export const program = Effect.gen(function*() {
const transactionAmount = yield* fetchTransactionAmount
const discountRate = yield* fetchDiscountRate
const discountedAmount = yield* applyDiscount(
transactionAmount,
discountRate
)
const finalAmount = addServiceCharge(discountedAmount)
return `Final amount to charge: ${finalAmount}`
})
gen(function* () {
const const counter: {
readonly value: Hyperlink.Subscribable<number>
readonly increment: (payload: {
by: number
}) => Effect.Effect<void, never, never>
readonly reset: Effect.Effect<
void,
never,
never
>
}
counter = yield* class Counterclass Counter {
key: Identifier;
Service: {
value: Hyperlink.Subscribable<number>;
increment: (payload: { by: number }) => Effect.Effect<void, never, never>;
reset: Effect.Effect<void, never, never>;
};
description: string | undefined;
of: (this: void, self: { readonly value: Hyperlink.Subscribable<number>; readonly increment: (payload: { by: number }) => Effect.Effect<void, never, never>; readonly reset: Effect.Effect<void, never, never> }) => { readonly value: Hyperlink.Su…;
context: (self: { readonly value: Hyperlink.Subscribable<number>; readonly increment: (payload: { by: number }) => Effect.Effect<void, never, never>; readonly reset: Effect.Effect<void, never, never> }) => Context<Counter>;
use: (f: (service: { readonly value: Hyperlink.Subscribable<number>; readonly increment: (payload: { by: number }) => Effect.Effect<void, never, never>; readonly reset: Effect.Effect<void, never, never> }) => Effect.Effect<A, E, R>) => Effect.E…;
useSync: (f: (service: { readonly value: Hyperlink.Subscribable<number>; readonly increment: (payload: { by: number }) => Effect.Effect<void, never, never>; readonly reset: Effect.Effect<void, never, never> }) => A) => Effect.Effect<A, never, Count…;
Identifier: Identifier;
stack: string | undefined;
pipe: { <A>(this: A): A; <A, B = never>(this: A, ab: (_: A) => B): B; <A, B = never, C = never>(this: A, ab: (_: A) => B, bc: (_: B) => C): C; <A, B = never, C = never, D = never>(this: A, ab: (_: A) => B, bc: (_: B) => C, cd: (_: C) => D): D; <…;
toString: () => string;
toJSON: () => unknown;
}
Counter // counter: the Counter handle
yield* const counter: {
readonly value: Hyperlink.Subscribable<number>
readonly increment: (payload: {
by: number
}) => Effect.Effect<void, never, never>
readonly reset: Effect.Effect<
void,
never,
never
>
}
counter.increment: (payload: {
by: number
}) => Effect.Effect<void, never, never>
increment({ by: numberby: 1 })
const const n: numbern = yield* const counter: {
readonly value: Hyperlink.Subscribable<number>
readonly increment: (payload: {
by: number
}) => Effect.Effect<void, never, never>
readonly reset: Effect.Effect<
void,
never,
never
>
}
counter.value: Hyperlink.Subscribable<number>(property) value: {
get: Effect.Effect<A>;
changes: Stream.Stream<A>;
}
value.Subscribable<number>.get: Effect.Effect<A>(property) Subscribable<number>.get: {
pipe: { <A>(this: A): A; <A, B = never>(this: A, ab: (_: A) => B): B; <A, B = never, C = never>(this: A, ab: (_: A) => B, bc: (_: B) => C): C; <A, B = never, C = never, D = never>(this: A, ab: (_: A) => B, bc: (_: B) => C, cd: (_: C) => D): D; <…;
toString: () => string;
toJSON: () => unknown;
}
get // n: number
yield* const counter: {
readonly value: Hyperlink.Subscribable<number>
readonly increment: (payload: {
by: number
}) => Effect.Effect<void, never, never>
readonly reset: Effect.Effect<
void,
never,
never
>
}
counter.reset: Effect.Effect<void, never, never>(property) reset: {
pipe: { <A>(this: A): A; <A, B = never>(this: A, ab: (_: A) => B): B; <A, B = never, C = never>(this: A, ab: (_: A) => B, bc: (_: B) => C): C; <A, B = never, C = never, D = never>(this: A, ab: (_: A) => B, bc: (_: B) => C, cd: (_: C) => D): D; <…;
toString: () => string;
toJSON: () => unknown;
}
reset
return const n: numbern
}).Pipeable.pipe<Effect.Effect<number, never, Counter>, Effect.Effect<number, never, never>>(this: Effect.Effect<number, never, Counter>, ab: (_: Effect.Effect<number, never, Counter>) => Effect.Effect<number, never, never>): Effect.Effect<number, never, never> (+21 overloads)pipe(import EffectEffect.const provide: {
<
Layers extends [
Layer.Any,
...Array<Layer.Any>
]
>(
layers: Layers,
options?:
| { readonly local?: boolean | undefined }
| undefined
): <A, E, R>(
self: Effect<A, E, R>
) => Effect<
A,
E | Layer.Error<Layers[number]>,
| Layer.Services<Layers[number]>
| Exclude<R, Layer.Success<Layers[number]>>
>
<ROut, E2, RIn>(
layer: Layer.Layer<ROut, E2, RIn>,
options?:
| { readonly local?: boolean | undefined }
| undefined
): <A, E, R>(
self: Effect<A, E, R>
) => Effect<A, E | E2, RIn | Exclude<R, ROut>>
<R2>(context: Context.Context<R2>): <A, E, R>(
self: Effect<A, E, R>
) => Effect<A, E, Exclude<R, R2>>
<
A,
E,
R,
Layers extends [
Layer.Any,
...Array<Layer.Any>
]
>(
self: Effect<A, E, R>,
layers: Layers,
options?:
| { readonly local?: boolean | undefined }
| undefined
): Effect<
A,
E | Layer.Error<Layers[number]>,
| Layer.Services<Layers[number]>
| Exclude<R, Layer.Success<Layers[number]>>
>
<A, E, R, ROut, E2, RIn>(
self: Effect<A, E, R>,
layer: Layer.Layer<ROut, E2, RIn>,
options?:
| { readonly local?: boolean | undefined }
| undefined
): Effect<A, E | E2, RIn | Exclude<R, ROut>>
<A, E, R, R2>(
self: Effect<A, E, R>,
context: Context.Context<R2>
): Effect<A, E, Exclude<R, R2>>
}
Provides dependencies to an effect using layers or a context. Use options.local
to build the layer every time; by default, layers are shared between provide
calls.
Example (Providing dependencies with a layer)
import { Context, Effect, Layer } from "effect"
interface Database {
readonly query: (sql: string) => Effect.Effect<string>
}
const Database = Context.Service<Database>("Database")
const DatabaseLive = Layer.succeed(Database)({
query: Effect.fn("Database.query")((sql: string) => Effect.succeed(`Result for: ${sql}`))
})
const program = Effect.gen(function*() {
const db = yield* Database
return yield* db.query("SELECT * FROM users")
})
const provided = Effect.provide(program, DatabaseLive)
Effect.runPromise(provided).then(console.log)
// Output: "Result for: SELECT * FROM users"
provide(const inProcess: Layer<
Counter | Hyperlink.Local<Counter>,
never,
never
>
const inProcess: {
build: (memoMap: MemoMap, scope: Scope.Scope) => Effect<Context.Context<Counter | Local<Counter>>, never, never>;
pipe: { <A>(this: A): A; <A, B = never>(this: A, ab: (_: A) => B): B; <A, B = never, C = never>(this: A, ab: (_: A) => B, bc: (_: B) => C): C; <A, B = never, C = never, D = never>(this: A, ab: (_: A) => B, bc: (_: B) => C, cd: (_: C) => D): D; <…;
}
inProcess))Try It Live
This exact Counter (the same Tag, the same Hyperlink.layer) is running in this page right now. The buttons call increment / reset on the Handle; the count reads straight off value.changes. There is no extra API between the UI and the service: the Handle is the surface.
docs/CounterTag-baked defaults (optional)
Same value on local and remote, no impl slot, no RPC. One field in the Contract with Hyperlink.default; several extras with Hyperlink.defaults on the Tag:
import * as import HyperlinkHyperlink from "hyperlink-ts/Hyperlink"
import { import SchemaSchema } from "effect"
class class Counterclass Counter {
key: Identifier;
Service: {
value: Hyperlink.Subscribable<number>;
label: (n: number) => string;
};
}
Counter extends import HyperlinkHyperlink.Tag<Counter>(): SchemaTagBuilder<Counter> (+2 overloads)
export Tag
Schema-driven solo: infer the service from spec; bare
local
is a compile error.
Tag<class Counterclass Counter {
key: Identifier;
Service: {
value: Hyperlink.Subscribable<number>;
label: (n: number) => string;
};
}
Counter>()("app/Counter", {
value: Hyperlink.RefField<
Hyperlink.Method<
undefined,
Schema.Number,
Schema.Never,
true,
Hyperlink.MethodAnnotations,
Hyperlink.Derive
>
>
value: import HyperlinkHyperlink.const ref: <Su extends Schema.Top>(
success: Su
) => RefField<
Method<undefined, Su, typeof Schema.Never, true>
>
Define a ref field — reactive state surfaced as a
Subscribable
(get + changes),
uniform local and remote. The impl owns a SubscriptionRef (writes it) and provides it via
subscribable
; consumers read (yield* svc.x.get) and observe (svc.x.changes) — a read
is an honest Effect, not a synchronous peek. For values fixed at acquire use
value
; for
on-demand calls use
effect
.
ref(import SchemaSchema.const Number: Numberconst Number: {
Rebuild: Rebuild;
Iso: Iso;
ast: Ast;
Type: T;
Encoded: E;
DecodingServices: RD;
EncodingServices: RE;
annotate: (annotations: Schema.Annotations.Bottom<number, readonly []>) => Schema.Number;
annotateKey: (annotations: Schema.Annotations.Key<number>) => Schema.Number;
check: (checks_0: Check<number>, ...checks: Array<Check<number>>) => Schema.Number;
rebuild: (ast: Number) => Schema.Number;
make: (input: number, options?: MakeOptions) => number;
makeOption: (input: number, options?: MakeOptions) => Option_.Option<number>;
makeEffect: (input: number, options?: MakeOptions) => Effect.Effect<number, SchemaError, never>;
pipe: { <A>(this: A): A; <A, B = never>(this: A, ab: (_: A) => B): B; <A, B = never, C = never>(this: A, ab: (_: A) => B, bc: (_: B) => C): C; <A, B = never, C = never, D = never>(this: A, ab: (_: A) => B, bc: (_: B) => C, cd: (_: C) => D): D; <…;
}
Type-level representation of
Number
.
Schema for number values, including NaN, Infinity, and -Infinity.
Details
Default JSON serializer:
- Finite numbers are serialized as numbers.
- Non-finite values are serialized as strings (
"NaN", "Infinity", "-Infinity").
Number),
label: Hyperlink.DefaultMethod<
(n: number) => string
>
(property) label: {
value: V;
}
label: import HyperlinkHyperlink.default<V>(value: [V] extends [(...args: never) => Promise<unknown>] ? never : V): DefaultMethod<V>Declare a single Tag-baked default in the contract — identical local and remote, no wire,
no impl slot. Accepts a literal or a sync function (Promise-returning fns are a type error —
async belongs in
effect
/
effectFn
or
promise
).
For multiple defaults, pipe
defaults
onto the Tag instead (bag keys
widen Service at construction — yield* Tag sees them).
Layer/serve may override a Spec default at the provide site only (see
ImplWithDefaultOverrides
); the remote client always installs the Tag-baked value.
class Counter extends Hyperlink.Tag<Counter>()("counter", {
current: Hyperlink.effect(Schema.Number),
label: Hyperlink.default((n: number) => `count=${n}`),
unit: Hyperlink.default("count"),
}) {}
default((n: numbern: number) => `count=${n: numbern}`),
}, {
defaults: Hyperlink.DefaultsInput<{
unit: "count"
}>
(property) defaults: {
unit: 'count';
}
defaults: { unit: "count"unit: "count" as type const = "count"const },
}) {}label is on Service (Spec leaf). Factory { defaults } (or .pipe(Hyperlink.defaults(…))) widens Service the same way — yield* Counter sees both. Layer overrides are provide-site only (local Handle) — clients always see the Tag-baked value. Post-hoc local patches: Layer.updateService.
What changes next
Serve or client the same Tag without rewriting the program body. Only the Layer at the edge changes. That tour is Managing Layers.
Sharp edge — browsers. A dashboard that opens many live streams hits the browser HTTP connection cap if you pair Node.http(…, 3000) with connect(tag, protocolHttp(3000)). Serve with Node.ws(…, 3000) and connect with Hyperlink.ws (or protocolWebsocket(3000)). Same Tag, different wire.