Server-sent events
Turn an EventSource into an Effect Stream that reconnects with backoff.
Ilha paints any Effect Stream. You wrap EventSource in Stream.callback, and Stream.retry reconnects after the connection drops.
import * as import CauseCause from "effect/Cause";
import * as import EffectEffect from "effect/Effect";
import * as import QueueQueue from "effect/Queue";
import * as import ScheduleSchedule from "effect/Schedule";
import * as import StreamStream from "effect/Stream";
/** Messages from `url`; reconnects with backoff capped at 30 seconds. */
export const const eventSource: (url: string, event?: string) => Stream.Stream<string, Event, never>Messages from `url`; reconnects with backoff capped at 30 seconds.eventSource = (url: stringurl: string, event: stringevent = "message") =>
import StreamStream.const callback: <string, Event, never>(f: (queue: Queue.Queue<string, Event | Cause.Done<void>>) => Effect.Effect<unknown, Event, Scope>, options?: {
readonly bufferSize?: number | undefined;
readonly strategy?: "sliding" | "dropping" | "suspend" | undefined;
}) => Stream.Stream<string, Event, never>
Creates a stream from a callback that can emit values into a queue.
**When to use**
Use when you need callback-based code to emit stream values by offering to a
`Queue`, or signal stream completion through the `Queue` module APIs.
By default it uses an "unbounded" buffer size.
You can customize the buffer size and strategy by passing an object as the
second argument with the `bufferSize` and `strategy` fields.
**Example** (Creating a stream from a callback that can emit values into a queue)
```ts import.meta.vitest
import { Effect, Queue, Stream } from "effect"
const stream = Stream.callback<number>((queue) =>
Effect.sync(() => {
// Emit values to the stream
Queue.offerUnsafe(queue, 1)
Queue.offerUnsafe(queue, 2)
Queue.offerUnsafe(queue, 3)
// Signal completion
Queue.endUnsafe(queue)
})
)
await Effect.runPromise(Stream.runCollect(stream)) // => [1, 2, 3]
```callback<string, Event>((queue: Queue.Queue<string, Event | Cause.Done<void>>queue) =>
import EffectEffect.const acquireRelease: <EventSource, never, never, never>(acquire: Effect.Effect<EventSource, never, never>, release: (a: EventSource, exit: Exit<unknown, unknown>) => Effect.Effect<unknown, never, never>, options?: {
readonly interruptible?: boolean;
}) => Effect.Effect<EventSource, never, Scope>
Constructs a scoped resource from an acquisition effect and a release
finalizer.
**When to use**
Use to acquire a scoped resource with an explicit release finalizer.
**Details**
If acquisition succeeds, the release finalizer is added to the current scope
and is guaranteed to run when that scope closes. The finalizer receives the
`Exit` value used to close the scope.
By default, acquisition is protected by an uninterruptible region. Pass
`{ interruptible: true }` to allow the acquisition effect to be interrupted.
**Example** (Acquiring and releasing a resource)
```ts import.meta.vitest
import { Effect, Exit } from "effect"
const output: Array<unknown> = []
// Simulate a resource that needs cleanup
interface FileHandle {
readonly path: string
readonly content: string
}
// Acquire a file handle
const acquire = Effect.gen(function*() {
yield* Effect.sync(() => { output.push("Opening file") })
return { path: "/tmp/file.txt", content: "file content" }
})
// Release the file handle
const release = (handle: FileHandle, exit: Exit.Exit<unknown, unknown>) =>
Effect.sync(() => { output.push(
`Closing file ${handle.path} with exit: ${
Exit.isSuccess(exit) ? "success" : "failure"
}`
) })
// Create a scoped resource
const resource = Effect.acquireRelease(acquire, release)
// Use the resource within a scope
const program = Effect.scoped(
Effect.gen(function*() {
const handle = yield* resource
yield* Effect.sync(() => { output.push(`Using file: ${handle.path}`) })
return handle.content
})
)
void output.push(Effect.runSync(program))
output // => ["Opening file", "Using file: /tmp/file.txt", "Closing file /tmp/file.txt with exit: success", "file content"]
```acquireRelease(
import EffectEffect.const sync: <EventSource>(thunk: LazyArg<EventSource>) => Effect.Effect<EventSource, never, never>Creates an `Effect` that represents a synchronous side-effectful computation.
**When to use**
Use when you need to wrap a synchronous side-effectful operation that is not
expected to throw.
**Details**
The provided function is evaluated lazily when the effect runs.
**Gotchas**
The function must not throw. If it throws, the thrown value is treated as a
defect, not as a typed failure. Use `try` when throwing is expected.
**Example** (Capturing synchronous logging in an Effect)
```ts import.meta.vitest
import { Effect } from "effect"
const output: Array<unknown> = []
const log = (message: string) =>
Effect.sync(() => {
void output.push(message) // side effect
})
// ┌─── Effect<void, never, never>
// ▼
const program = log("Hello, World!")
Effect.runSync(program)
output // => ["Hello, World!"]
```sync(() => {
const const source: EventSourcesource = new var EventSource: new (url: string | URL, eventSourceInitDict?: EventSourceInit) => EventSourceThe **`EventSource`** interface is web content's interface to server-sent events.
[MDN Reference](https://developer.mozilla.org/docs/Web/API/EventSource)EventSource(url: stringurl);
const source: EventSourcesource.EventSource.addEventListener(type: string, listener: (this: EventSource, event: MessageEvent) => any, options?: boolean | AddEventListenerOptions): void (+2 overloads)The **`addEventListener()`** method of the EventTarget interface sets up a function that will be called whenever the specified event is delivered to the target.
[MDN Reference](https://developer.mozilla.org/docs/Web/API/EventTarget/addEventListener)addEventListener(event: stringevent, (e: MessageEvent<any>e) => {
if (e: MessageEvent<any>e instanceof var MessageEvent: {
new <T>(type: string, eventInitDict?: MessageEventInit<T>): MessageEvent<T>;
prototype: MessageEvent;
}
The **`MessageEvent`** interface represents a message received by a target object.
[MDN Reference](https://developer.mozilla.org/docs/Web/API/MessageEvent)MessageEvent && typeof e: MessageEvent<any>e.MessageEvent<any>.data: anyThe **`data`** read-only property of the MessageEvent interface represents the data sent by the message emitter.
[MDN Reference](https://developer.mozilla.org/docs/Web/API/MessageEvent/data)data === "string") {
import QueueQueue.const offerUnsafe: <string, Event | Cause.Done<void>>(self: Queue.Enqueue<string, Event | Cause.Done<void>>, message: string) => booleanAdds a message to the queue synchronously. Returns `false` if the queue is done.
**When to use**
Use when you are already in synchronous queue internals or a performance
boundary where wrapping the mutation in `Effect` is intentionally avoided.
**Gotchas**
This is an unsafe operation that directly modifies the queue without Effect wrapping.
Use this only when you're certain about the synchronous nature of the operation.
**Example** (Offering a value synchronously)
```ts import.meta.vitest
import { Effect, Queue } from "effect"
// Create a queue effect and extract the queue for unsafe operations
const program = Effect.gen(function*() {
const queue = yield* Queue.bounded<number>(3)
// Add messages synchronously using unsafe API
const success1 = Queue.offerUnsafe(queue, 1)
const success2 = Queue.offerUnsafe(queue, 2)
// Check current size
const size = Queue.sizeUnsafe(queue)
return { offered: [success1, success2], size }
})
await Effect.runPromise(program) // => { offered: [true, true], size: 2 }
```offerUnsafe(queue: Queue.Queue<string, Event | Cause.Done<void>>queue, e: MessageEvent<any>e.MessageEvent<any>.data: stringThe **`data`** read-only property of the MessageEvent interface represents the data sent by the message emitter.
[MDN Reference](https://developer.mozilla.org/docs/Web/API/MessageEvent/data)data);
}
});
const source: EventSourcesource.EventSource.addEventListener<"error">(type: "error", listener: (this: EventSource, ev: Event) => any, options?: boolean | AddEventListenerOptions): void (+2 overloads)The **`addEventListener()`** method of the EventTarget interface sets up a function that will be called whenever the specified event is delivered to the target.
[MDN Reference](https://developer.mozilla.org/docs/Web/API/EventTarget/addEventListener)addEventListener("error", (e: Evente) => {
if (const source: EventSourcesource.EventSource.readyState: numberThe **`readyState`** read-only property of the EventSource interface returns a number representing the state of the connection.
[MDN Reference](https://developer.mozilla.org/docs/Web/API/EventSource/readyState)readyState === var EventSource: {
new (url: string | URL, eventSourceInitDict?: EventSourceInit): EventSource;
prototype: EventSource;
readonly CONNECTING: 0;
readonly OPEN: 1;
readonly CLOSED: 2;
}
The **`EventSource`** interface is web content's interface to server-sent events.
[MDN Reference](https://developer.mozilla.org/docs/Web/API/EventSource)EventSource.type CLOSED: 2CLOSED) {
import QueueQueue.const failCauseUnsafe: <string, Event>(self: Queue.Enqueue<string, Event>, cause: Cause.Cause<Event>) => booleanFails the queue with a cause synchronously. If the queue is already done, `false` is
returned.
**When to use**
Use when queue completion must be driven from synchronous internals while
preserving the full failure `Cause`.
**Gotchas**
This is an unsafe operation that directly modifies the queue without Effect wrapping.
**Example** (Failing queues with a cause synchronously)
```ts import.meta.vitest
import { Cause, Effect, Exit, Queue } from "effect"
const program = Effect.gen(function*() {
const queue = yield* Queue.bounded<number, string>(10)
// Create a cause and fail the queue synchronously
const cause = Cause.fail("Processing error")
const failed = Queue.failCauseUnsafe(queue, cause)
// The queue is now done with the specified failure cause
const exit = Queue.takeUnsafe(queue)
return [failed, exit]
})
await Effect.runPromise(program) // => [true, Exit.failCause(Cause.fail("Processing error"))]
```failCauseUnsafe(queue: Queue.Queue<string, Event | Cause.Done<void>>queue, import CauseCause.const fail: <Event>(error: Event) => Cause.Cause<Event>Creates a `Cause` containing a single `Fail` reason with the
given typed error.
**When to use**
Use to construct a cause from an expected typed error.
**Example** (Creating a fail cause)
```ts import.meta.vitest
import { Cause } from "effect"
Cause.fail("Something went wrong") // => Cause.fromReasons([Cause.makeFailReason("Something went wrong")])
```fail(e: Evente));
}
});
return const source: EventSourcesource;
}),
(source: EventSourcesource) => import EffectEffect.const sync: <void>(thunk: LazyArg<void>) => Effect.Effect<void, never, never>Creates an `Effect` that represents a synchronous side-effectful computation.
**When to use**
Use when you need to wrap a synchronous side-effectful operation that is not
expected to throw.
**Details**
The provided function is evaluated lazily when the effect runs.
**Gotchas**
The function must not throw. If it throws, the thrown value is treated as a
defect, not as a typed failure. Use `try` when throwing is expected.
**Example** (Capturing synchronous logging in an Effect)
```ts import.meta.vitest
import { Effect } from "effect"
const output: Array<unknown> = []
const log = (message: string) =>
Effect.sync(() => {
void output.push(message) // side effect
})
// ┌─── Effect<void, never, never>
// ▼
const program = log("Hello, World!")
Effect.runSync(program)
output // => ["Hello, World!"]
```sync(() => source: EventSourcesource.EventSource.close(): voidThe **`close()`** method of the EventSource interface closes the connection, if one is made, and sets the EventSource.readyState attribute to 2 (closed).
[MDN Reference](https://developer.mozilla.org/docs/Web/API/EventSource/close)close())
)
).Pipeable.pipe<Stream.Stream<string, Event, never>, Stream.Stream<string, Event, never>>(this: Stream.Stream<string, Event, never>, ab: (_: Stream.Stream<string, Event, never>) => Stream.Stream<string, Event, never>): Stream.Stream<string, Event, never> (+21 overloads)pipe(
import StreamStream.const retry: <Event, Duration, never, never>(policy: Schedule.Schedule<Duration, Event, never, never> | (($: <SO, SE, SR>(_: Schedule.Schedule<SO, Event, SE, SR>) => Schedule.Schedule<SO, Event, SE, SR>) => Schedule.Schedule<Duration, Event, never, never>)) => <A, R>(self: Stream.Stream<A, Event, R>) => Stream.Stream<A, Event, R> (+1 overload)Retries the stream according to the given schedule when it fails.
**Details**
This retries the entire stream, so will re-execute all of the stream's
acquire operations.
The schedule is reset as soon as the first element passes through the
stream again.
**Example** (Retrying stream failures)
```ts import.meta.vitest
import { Effect, Schedule, Stream } from "effect"
const program = Effect.gen(function*() {
const values = yield* Stream.make(1).pipe(
Stream.concat(Stream.fail("boom")),
Stream.retry(Schedule.recurs(1)),
Stream.take(2),
Stream.runCollect
)
values // => [ 1, 1 ]
})
await Effect.runPromise(program)
```retry(
import ScheduleSchedule.const min: <readonly [Schedule.Schedule<Duration, unknown, never, never>, Schedule.Schedule<number, unknown, never, never>]>(schedules: readonly [Schedule.Schedule<Duration, unknown, never, never>, Schedule.Schedule<number, unknown, never, never>]) => Schedule.Schedule<Duration, unknown, never, never>Combines schedules by recurring while at least one schedule wants to recur,
using the minimum delay between recurrences and outputting that minimum delay.
**When to use**
Use when a combined policy should continue while any schedule still recurs,
and should wait for the fastest schedule between recurrences.
**Example** (Combining retry schedules by their minimum delay)
```ts import.meta.vitest
import { Schedule } from "effect"
const schedule = Schedule.min([Schedule.fixed("5 seconds"), Schedule.spaced("10 seconds")])
Schedule.isSchedule(schedule) // => true
```min([
import ScheduleSchedule.const exponential: (base: Input, factor?: number) => Schedule.Schedule<Duration>Schedule that always recurs, but will wait a certain amount between
repetitions, given by `base * factor.pow(n)`, where `n` is the number of
repetitions so far. Returns the current duration between recurrences.
**Example** (Retrying with exponential backoff)
```ts import.meta.vitest
import { Duration, Effect, Schedule } from "effect"
const program = Effect.gen(function*() {
const step = yield* Schedule.toStep(Schedule.exponential("100 millis"))
return yield* step(0, undefined)
})
await Effect.runPromise(program) // => [Duration.millis(100), Duration.millis(100)]
```exponential("1 second"),
import ScheduleSchedule.const spaced: (duration: Input) => Schedule.Schedule<number>Returns a schedule that recurs continuously, each repetition spaced the
specified duration from the last run.
**When to use**
Use when each delay should start after the previous action completes.
**Example** (Repeating with fixed spacing)
```ts import.meta.vitest
import { Duration, Effect, Schedule } from "effect"
const program = Effect.gen(function*() {
const step = yield* Schedule.toStep(Schedule.spaced("2 seconds"))
return yield* step(0, undefined)
})
await Effect.runPromise(program) // => [0, Duration.seconds(2)]
```spaced("30 seconds"),
])
)
);
Closing the stream closes the connection. Ilha closes it when the component unmounts.
Paint each message
Yield the mapped stream from a generator component:
import * as import StreamStream from "effect/Stream";
import { const eventSource: (url: string) => Stream.Stream<string, Event>eventSource } from "./sse";
export function* function Ticks(): Generator<Stream.Stream<View, Event, never>, void, unknown>Ticks() {
yield function eventSource(url: string): Stream.Stream<string, Event>eventSource("/ticks").Pipeable.pipe<Stream.Stream<string, Event, never>, Stream.Stream<View, Event, never>>(this: Stream.Stream<string, Event, never>, ab: (_: Stream.Stream<string, Event, never>) => Stream.Stream<View, Event, never>): Stream.Stream<View, Event, never> (+21 overloads)pipe(import StreamStream.const map: <string, View>(f: (a: string, i: number) => View) => <E, R>(self: Stream.Stream<string, E, R>) => Stream.Stream<View, E, R> (+1 overload)Transforms the elements of this stream using the supplied function.
**Example** (Mapping stream values)
```ts import.meta.vitest
import { Effect, Option, Stream } from "effect"
const stream = Stream.fromArray([1, 2, 3]).pipe(Stream.map((n, i) => n + i))
await Effect.runPromise(Stream.runCollect(stream)) // => [1, 3, 5]
```map((msg: stringmsg) => <JSX.IntrinsicElements.p: HTMLAttributes<HTMLParagraphElement>p>{msg: stringmsg}</JSX.IntrinsicElements.p: HTMLAttributes<HTMLParagraphElement>p>));
}
Read the latest value
Pass the stream to atom() to keep the latest message in a sync component:
import { const atom: AtomFnatom } from "ilha";
import { const eventSource: (url: string) => Stream<string, Event>eventSource } from "./sse";
export const const Price: () => ViewPrice = () => {
const const price: AtomHandle<AsyncResult<string, Event>>price = atom<string, Event>(init: Effect<string, Event, AtomRegistry> | Stream<string, Event, AtomRegistry>, options?: AtomOptions<AsyncResult<string, Event>> | undefined): AtomHandle<AsyncResult<string, Event>> (+1 overload)An effect or stream runs once; the handle holds its `AsyncResult`.atom(function eventSource(url: string): Stream<string, Event>eventSource("/price"));
return <JSX.IntrinsicElements.p: HTMLAttributes<HTMLParagraphElement>p>{const price: AtomHandle<AsyncResult<string, Event>>price}</JSX.IntrinsicElements.p: HTMLAttributes<HTMLParagraphElement>p>;
};
Decode messages
Parse data with Schema and drop invalid messages:
import * as import FilterFilter from "effect/Filter";
import * as import SchemaSchema from "effect/Schema";
import * as import StreamStream from "effect/Stream";
import { const eventSource: (url: string) => Stream.Stream<string, Event>eventSource } from "./sse";
const const Tick: Schema.fromJsonString<Schema.Struct<{
readonly n: Schema.Number;
}>>
Tick = import SchemaSchema.function fromJsonString<Schema.Struct<{
readonly n: Schema.Number;
}>>(schema: Schema.Struct<{
readonly n: Schema.Number;
}>, options?: {
readonly reviver?: Parameters<typeof JSON.parse>[1] | undefined;
readonly replacer?: JsonReplacer | undefined;
readonly space?: Parameters<typeof JSON.stringify>[2] | undefined;
}): Schema.fromJsonString<Schema.Struct<{
readonly n: Schema.Number;
}>>
Returns a schema that decodes a JSON string and then decodes the parsed value
using the given schema.
**Details**
This is useful when working with JSON-encoded strings where the actual
structure of the value is known and described by an existing schema.
During decoding, the resulting schema first parses the input string as JSON,
using `reviver` when provided, and then runs the provided schema on the
parsed result. During encoding, it first encodes with the provided schema and
then passes the result to `JSON.stringify` with the optional `replacer` and
`space`.
**Example** (Formatting encoded JSON)
```ts import.meta.vitest
import { Schema } from "effect"
const schema = Schema.Struct({ a: Schema.Number })
const schemaFromJsonString = Schema.fromJsonString(schema, { space: 2 })
Schema.encodeSync(schemaFromJsonString)({ a: 1 }) // => "{\n \"a\": 1\n}"
```fromJsonString(import SchemaSchema.function Struct<{
readonly n: Schema.Number;
}>(fields: {
readonly n: Schema.Number;
}): Schema.Struct<{
readonly n: Schema.Number;
}>
Defines a struct schema from a map of field schemas.
**Details**
Each field value is a schema. Use
{@link
optionalKey
}
or
{@link
optional
}
to
mark fields as optional, and
{@link
mutableKey
}
to mark them as mutable.
The resulting schema's `Type` is a readonly object type with the fields'
decoded types. The `Encoded` form mirrors the field schemas' encoded types.
Declared fields may be inherited and are copied to own properties in the
output. The `__proto__` field is accepted only when it is an own property.
Parsing does not guarantee that output keys retain their input order.
**Example** (Defining a basic struct)
```ts import.meta.vitest
import { Schema } from "effect"
const Person = Schema.Struct({
name: Schema.String,
age: Schema.Number,
email: Schema.optionalKey(Schema.String)
})
// { readonly name: string; readonly age: number; readonly email?: string }
type Person = typeof Person.Type
Schema.decodeUnknownSync(Person)({ name: "Alice", age: 30 }) // => { name: "Alice", age: 30 }
```Struct({ n: Schema.Numbern: import SchemaSchema.const Number: Schema.NumberType-level representation of
{@link
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 }));
export const const ticks: Stream.Stream<{
readonly n: number;
}, Event, never>
ticks = function eventSource(url: string): Stream.Stream<string, Event>eventSource("/ticks").Pipeable.pipe<Stream.Stream<string, Event, never>, Stream.Stream<{
readonly n: number;
}, Event, never>>(this: Stream.Stream<string, Event, never>, ab: (_: Stream.Stream<string, Event, never>) => Stream.Stream<{
readonly n: number;
}, Event, never>): Stream.Stream<{
readonly n: number;
}, Event, never> (+21 overloads)
pipe(
import StreamStream.const filterMap: <string, {
readonly n: number;
}, unknown>(filter: Filter.Filter<string, {
readonly n: number;
}, unknown>) => <E, R>(self: Stream.Stream<string, E, R>) => Stream.Stream<{
readonly n: number;
}, E, R> (+1 overload)
Filters and maps stream elements in one pass using a `Filter`.
**When to use**
Use to keep only stream elements accepted by a `Filter` and emit each filter
success value.
**Details**
`Result.succeed` values are emitted and `Result.fail` values are skipped.filterMap(import FilterFilter.const fromPredicateOption: <unknown, {
readonly n: number;
}>(predicate: (a: unknown) => Option<{
readonly n: number;
}>) => Filter.Filter<unknown, {
readonly n: number;
}, unknown>
Creates a `Filter` from a function that returns an `Option`; `Some(value)`
passes with `value`, and `None` fails with the original input.fromPredicateOption(import SchemaSchema.const decodeUnknownOption: <Schema.fromJsonString<Schema.Struct<{
readonly n: Schema.Number;
}>>>(schema: Schema.fromJsonString<Schema.Struct<{
readonly n: Schema.Number;
}>>, options?: ParseOptions) => (input: unknown, options?: ParseOptions) => Option<{
readonly n: number;
}>
Decodes an `unknown` input against a schema, returning an `Option` that is
`Some` with the decoded value on success or `None` for schema mismatches.
**When to use**
Use when you do not know the input type statically and only need to know
whether decoding succeeded.
**Details**
Prefer this over
{@link
decodeUnknownExit
}
or
{@link
decodeUnknownEffect
}
when you don't need error details. For input already typed as the schema's
`Encoded` type use
{@link
decodeOption
}
.
Options may be provided either when creating the decoder or when applying it;
application options override creation options.
**Gotchas**
Only causes made entirely of schema issues are converted to `None`. Causes
that contain defects, interruptions, or other non-schema reasons throw
instead.decodeUnknownOption(const Tick: Schema.fromJsonString<Schema.Struct<{
readonly n: Schema.Number;
}>>
Tick)))
);
Server rendering
EventSource does not exist on the server. Return Stream.empty there, or render the component only in the browser:
import * as import StreamStream from "effect/Stream";
import { const eventSource: (url: string) => Stream.Stream<string, Event>eventSource } from "./sse";
export const const feed: (url: string) => Stream.Stream<string, Event, never>feed = (url: stringurl: string) =>
module globalThisglobalThis.var EventSource: {
new (url: string | URL, eventSourceInitDict?: EventSourceInit): EventSource;
prototype: EventSource;
readonly CONNECTING: 0;
readonly OPEN: 1;
readonly CLOSED: 2;
}
The **`EventSource`** interface is web content's interface to server-sent events.
[MDN Reference](https://developer.mozilla.org/docs/Web/API/EventSource)EventSource === var undefinedundefined ? import StreamStream.const empty: Stream.Stream<never, never, never>Creates an empty stream.
**Example** (Creating an empty stream)
```ts import.meta.vitest
import { Effect, Stream } from "effect"
await Effect.runPromise(Stream.runCollect(Stream.empty)) // => []
```empty : function eventSource(url: string): Stream.Stream<string, Event>eventSource(url: stringurl);