Przejdź do treści
Biblioteka skilli
Programowanie · Budowanie i kod

effect-socket

/effect-socket

Buduje transporty TCP, Unix, TLS, WebSocket i UDP na czytnikach i zapisywaczach Effect Socket, z ramkowaniem i ponownym łączeniem.

Licencja
MIT
Ostatnia zmiana repozytorium
02.11.2026

Ocena i bezpieczeństwo

100/ 100Klasa: dobry
Format25 / 25
Frontmatter z nazwą i opisem, poprawna nazwa, opis do 1024 znaków, nazwa zgodna z folderem.
Odkrywalność25 / 25
Opis mówi, kiedy użyć skilla, ma sensowną długość i jest w 3. osobie.
Treść35 / 35
Co najmniej 150 słów i do 500 linii, nagłówki, listy kroków, przykłady albo kod.
Higiena15 / 15
Bez zaślepek typu TODO, bez ścieżek Windows, z licencją.
Ryzykobrak sygnałów w skanie
Pliki w paczce
1
Skrypty
0
Komentarze HTML
5
Adresy URL w SKILL.md
0
Przestarzałość

Brak sygnałów. Repozytorium zmienione 02.11.2026, bez odwołań do wycofanych modeli i API.

Licencja

MIT, otwarta: można używać i zmieniać, z zachowaniem informacji o autorze.

Co sprawdzić przed instalacją
  • SKILL.md zawiera komentarze HTML (5). Nie widać ich w podglądzie Markdown, a model je czyta.

Co robi i kiedy

Buduje transporty TCP, Unix, TLS, WebSocket i UDP na czytnikach i zapisywaczach Effect Socket, z ramkowaniem i ponownym łączeniem.

Oryginalny opis (EN)

Build pull-based TCP, Unix, TLS, WebSocket, and UDP transports with Effect Socket readers and writers. Use for bidirectional connections, datagrams, framing, reconnects, socket servers, or RPC socket transports.

Jak działa

  1. Source reference
  2. Connection ownership
  3. WebSocket clients
  4. TCP, Unix, and TLS
  5. Errors, close, and reconnect
  6. Streams, channels, and framing
  7. Servers and bound addresses
  8. UDP datagrams (experimental)
  9. Higher-level transports and tests
Sekcje z nagłówków SKILL.md, w oryginale · 341 linii, 2 189 słów
Podgląd SKILL.md
---
name: effect-socket
description: Build pull-based TCP, Unix, TLS, WebSocket, and UDP transports with Effect Socket readers and writers. Use for bidirectional connections, datagrams, framing, reconnects, socket servers, or RPC socket transports.
---

# Effect Socket

## Source reference

Read `packages/effect/src/socket/{Socket,SocketServer,DatagramSocket}.ts` at
`effect@4.1.0` in the Effect reference. Platform adapters live in
`packages/platform/node-shared/src/{NodeSocket,NodeSocketServer}.ts`; Node and
Bun expose these as `NodeSocket` / `BunSocket` and their server counterparts.
These APIs remain `@stability unstable` and may break in minor releases despite
the stable package version. Keep Effect-family packages on the same release.
Load `effect-scope` and `effect-fiber` for lifecycle ownership,
`effect-stream` for framing, and `effect-scheduling` for reconnect policy.

## Connection ownership

`Socket.Socket` is both the service tag and the interface. A client socket is
a connection recipe. Acquiring `socket.reader` establishes a connection and
returns a `Reader` with `pull` and `upgrade`. Its acquisition scope owns the
connection. A pull yields a non-empty batch of `string | Uint8Array` frames.
Every termination, including clean close, fails with `SocketError`.

`socket.writer` is an infallible scoped acquisition of a `Writer` object:

- `writer.write(frameOrCloseEvent)` sends one frame or a close request.
- `writer.writeAll(nonEmptyBatch)` batches outgoing frames with native backpressure.
- Both write operations may fail with `SocketError`.
- Writes while disconnected wait for a reader to establish the connection.
- Releasing the writer scope half-closes transports that support it.

One owner acquires the reader. Process each batch sequentially by default;
bounded concurrency is a deliberate application decision. Place a handshake
after reader acquisition and before the first pull so it runs per connection.
There are no callback-driven `run`, `runString`, or `runRaw` methods on a socket.

<!-- typecheck -->
```ts
import { Effect } from 'effect';
import { Socket } from 'effect/socket';

const echo = Effect.fn('Socket.echo')(function* (socket: Socket.Socket) {
	const { pull } = yield* socket.reader;
	const writer = yield* socket.writer;
	yield* pull.pipe(Effect.flatMap(writer.writeAll), Effect.forever);
}, Effect.scoped);
```

`Socket.readerBytes(socket)` and `Socket.readerString(socket, encoding?)`
acquire a converted pull directly, rather than returning a Reader object.
The string adapter owns one **streaming decoder per reader acquisition**, so
binary UTF-8 can span frames and pulls. It may emit empty strings while waiting
for the rest of a character. A string frame flushes pending binary text (using a
replacement character for an incomplete sequence) before its own text. A close
with code `1000` flushes any tail once, then the next pull propagates close;
other failures do not flush. `toChannelString` inherits this behavior. For
per-message binary WebSocket decoding, use `readerBytes` and decode frames
separately instead.

## WebSocket clients

<!-- typecheck -->
```ts
import { Duration, Effect } from 'effect';
import { Socket } from 'effect/socket';

const receive = Effect.gen(function* () {
	const socket = yield* Socket.makeWebSocket('wss://example.com/feed', {
		openTimeout: Duration.seconds(5),
		highWaterMark: 64 * 1024,
		protocols: ['v1']
	});
	const pull = yield* Socket.readerString(socket);
	const writer = yield* socket.writer;
	yield* writer.write('subscribe');
	yield* pull.pipe(
		Effect.flatMap((batch) => Effect.forEach(batch, Effect.logInfo)),
		Effect.forever
	);
}).pipe(Effect.scoped, Effect.provide(Socket.layerWebSocketConstructorGlobal));
```

Use `NodeSocket.layerWebSocketConstructor` or
`NodeWS.layerWebSocketConstructor` on Node (import `NodeWS` from
`@effect/platform-node` or `@effect/platform-node-shared`) and
`BunSocket.layerWebSocketConstructor` on Bun. Platform `layerWebSocket(url, options)`
conveniences supply the constructor. Core `Socket.layerWebSocket` requires it.
Constructors accept compatible browser, Bun, and Node implementations without
casts; opening-handshake headers are available where the platform supports them.

Node/Bun WebSocket constructors, layers, and channel helpers accept headers
alongside protocols. The global/browser constructor rejects an options object
containing headers: browsers cannot set arbitrary opening-handshake headers.

An effectful URL is reevaluated on each connection. `fromWebSocket(acquire, options)`
wraps a scoped transport acquisition. Pausable transports pause at `highWaterMark`
(default 64 KiB) and resume when drained. Browser WebSockets cannot pause; their
default buffer is unbounded. Set `highWaterMark` explicitly to fail with
`SocketReadError` when that byte bound is exceeded. Text-frame
buffering counts UTF-8 bytes, not string length.

Node's default constructor now uses global `WebSocket`, including connections
with handshake headers. It cannot pause reads: buffering is unbounded unless a
`highWaterMark` is set, which fails with `SocketReadError` on overflow. Connection
errors are generic `ErrorEvent`s, custom `Host` is ignored, outgoing compression
is disabled, and connections use undici's global dispatcher. Choose
`NodeWS.layerWebSocketConstructor` for the previous `ws` behavior, including
`ws`-specific methods. `NodeSocket.NodeWS` / `BunSocket.NodeWS` were removed;
use `NodeWS.WebSocket` on Node and import `ws` directly on Bun.
Node platform packages now require Node.js 22.19.0 or newer.

Node stream/socket `chunkSize` now bounds chunks per pull (default 4096).
Use the NodeStream `readSize` option for byte read requests; `chunkSize: Infinity`
permits unbounded batching.

## TCP, Unix, and TLS

```ts
import { NodeSocket } from '@effect/platform-node';

const tcp = NodeSocket.makeNet({ host: 'localhost', port: 9000, openTimeout: '3 seconds' });
const unix = NodeSocket.makeNet({ path: '/tmp/app.sock' });
const tls = NodeSocket.makeTls({ host: 'example.com', port: 443, servername: 'example.com' });
```

`makeNet` / `makeTls` return recipes; connection failures occur during reader
acquisition. `layerNet`, `layerTls`, `makeNetChannel`, and `makeTlsChannel` provide
service/channel equivalents. Prefer `makeTls` over hand-wrapping `tls.connect`.
`fromDuplex` remains the adapter for an existing scoped Node duplex source.
Omitted TCP `openTimeout` is unbounded; zero means an immediate timeout.

For STARTTLS acquire the reader, complete the protocol handshake, then call
`reader.upgrade(options)`. TLS keys and passphrases are redacted. Server-role
upgrades require both key and certificate; unsupported transports and invalid
credentials fail with `SocketUpgradeError` inside `SocketError`.

Client-side `reader.upgrade({ servername: 'example.com' })` supports SNI and
certificate-hostname verification; `servername` is ignored for server-role
upgrades. Node duplex readers retain already-consumed bytes across interrupted
pulls and ordinary EOF; closing the acquisition scope discards retained data.

## Errors, close, and reconnect

`SocketError.reason` is one of `SocketOpenError`, `SocketReadError`,
`SocketWriteError`, `SocketCloseError`, or `SocketUpgradeError`. Narrow with
`Effect.catchReason`, schema matching, or the supplied guards. Open failures
distinguish `Unknown` and `Timeout`; close reasons carry `code` and `closeReason`.
Handle a normal close at the owning protocol boundary when completion is intended.
Close-code predicate options and `SocketCloseError.filterClean` are removed.

Reconnect by retrying the **scoped reader acquisition and consumption**, not just
the already-acquired pull. This releases each connection before the next attempt.
Retry only connection failures classified as transient for the protocol; replayed
handshakes and commands must be idempotent. Accepted server connections cannot
reconnect. Use a new socket value for an independent concurrent client connection.

<!-- typecheck -->
```ts
import { Duration, Effect, Schedule } from 'effect';
import { Socket } from 'effect/socket';

const consume = Effect.fn('Socket.consume')(function* (socket: Socket.Socket) {
	const pull = yield* Socket.readerString(socket);
	const writer = yield* socket.writer;
	yield* writer.write('subscribe'); // protocol-specific idempotent handshake
	yield* pull.pipe(
		Effect.flatMap((batch) => Effect.forEach(batch, Effect.logInfo)),
		Effect.forever
	);
}, Effect.scoped);

const reconnect = (socket: Socket.Socket) => consume(socket).pipe(
	Effect.retry({
		schedule: Schedule.exponential(Duration.millis(500)).pipe(Schedule.upTo({ times: 5 })),
		while: (error) => error.reason._tag === 'SocketCloseError' && error.reason.code !== 1000
	})
);
```

`new Socket.CloseEvent(1000, 'done')` requests a close through `writer.write`.
WebSocket close codes are protocol data; TCP does not transmit them. Prefer
writer-scope release for a graceful TCP half-close rather than a close event.

Node/Bun HTTP upgrade adapters choose server close codes from the owning scope
exit: `1000` for success, `1001` for interruption-only exits, and `1011` for other
failures. An explicit code already sent is preserved. HTTP response conversion
and observation middleware retain the handler failure for request cleanup. This
is server-upgrade behavior, not a configurable clean-close predicate on sockets.

## Streams, channels, and framing

`Socket.toStream(socket)` provides read-only byte consumption.
`Socket.toChannel(socket)` connects outgoing upstream frames to incoming byte
batches; `toChannelString` converts incoming frames to strings. These adapters
fail on close. A finite input alone does not imply successful completion of a
bidirectional protocol: bound expected responses or explicitly handle closure.

For TCP message boundaries use `Ndjson.duplexSchema(Socket.toChannel(socket),
{ inputSchema, outputSchema })`, or `SchemaBinary.duplex` with compatible schemas.
WebSocket text frames may use `Ndjson.duplexSchemaString` when NDJSON is the
agreed protocol. Decode unknown frames with schemas before domain processing.
MessagePack adapters are removed; SchemaBinary is a different wire format and
requires coordinated peer and persisted-data migration.

`Socket.make({ reader, writer })` is the custom-adapter boundary. Its reader must
fail suspended pulls when the acquisition scope closes; otherwise downstream
channel shutdown hangs. Prefer built-in adapters for platform transports.

## Servers and bound addresses

`NodeSocketServer.make({ port: 0 })` / `.layer(...)` bind TCP or Unix listeners.
Use `makeTls` / `layerTls` for TLS and `makeWebSocket` / `layerWebSocket` for
WebSockets. Bun exposes the shared API under `BunSocketServer`.

Fork `server.run(handler)` into the server scope; it does not complete normally.
Each accepted socket pauses until its reader takes ownership. Scope each handler's
reader/writer with `Effect.scoped`. Handler errors are reported by the server
rather than becoming accept-loop failures; add application-level supervision
where they must stop the service. HTTP upgrades yield the same Socket interface;
see `effect-http-server`.

Node HTTP/1.1 clients and servers also support **raw upgrades** in 4.1.0:
`HttpClientRequest.upgrade` requests one and `HttpClientResponse.upgrade` yields
a Socket whose reader scope owns the connection. `HttpApiSchema.Upgrade`
clients return `{ headers, socket }`.
Fetch/XHR reject raw upgrades before sending; Bun/Deno servers support only
WebSocket upgrades. Do not confuse raw upgrades with WebSocket framing or
assume they work through an arbitrary HttpClient layer.

Server addresses are `NetAddress.SocketAddress` from `effect/net`.
Narrow to `InetAddress` before reading `port`; format its IP with `formatIp`,
or use `formatHost` when passing a host and port separately (preserves IPv6 scopes).
Unix path addresses expose `path`. Use `inetAddressFromHostString` to parse numeric
hosts and `scopeIdsFromInterfaces` with supplied interface data for named zones.
URL helpers bracket IPv6 and reject scoped IPv6; do not assemble URLs by casting
an address to the removed `TcpAddress` type.

Treat IP values as opaque: IPv4 now stores one unsigned 32-bit number and IPv6
four words internally, not public `.bytes`. Use `ipv4ToOctets`, `ipv6ToOctets`,
`ipv6ToSegments`, and formatting/construction helpers. Do not persist internal
fields or hash values as a wire representation.

`NetAddress.Family<A>` selects the IPv4/IPv6 address family, `Inet<A>` the matching
port-carrying address, and `MulticastInterface<A>` an IPv4 interface address or
IPv6 numeric interface index (`ipv4Unspecified`/`0` choose the OS default).
Classification predicates such as `isMulticast`, `isUnicast`, `isLoopback`, and
`isLinkLocal` preserve the input family while refining it to the corresponding
branded type. Use the named schemas in `effect/Schema` for boundary validation
rather than asserting these refinements.

<!-- typecheck -->
```ts
import { Effect } from 'effect';
import * as Schema from 'effect/Schema';
import { NetAddress } from 'effect/net';

const parseMulticast = Schema.decodeUnknownEffect(Schema.IpMulticastAddressFromString);
const displayMulticast = Effect.fn('Socket.displayMulticast')(function* (input: unknown) {
	const address = yield* parseMulticast(input);
	return NetAddress.formatIp(address);
});
```

For native adapters, `formatNativeHost(address, scopeIds, platform?)` and
`formatMulticastInterface(selector, scopeIds, platform?)` use an explicitly
supplied interface-name-to-index map. Non-Windows output prefers a matching
interface name; Windows uses numeric zones. `toCanonical` accepts both IP and
internet addresses: mapped IPv6 becomes IPv4 while retaining its port; other
values retain identity and IPv6 scope metadata.

## UDP datagrams (experimental)

`DatagramSocket` from `effect/socket` is separate from stream `Socket`. Its
experimental Node/Bun/Deno adapters expose `make` and `layer`; Node also exposes
`fromSocket` for an existing scoped native socket acquisition.
Acquire a reader even for a **send-only client**: it opens/binds the native
socket, and writes otherwise wait. Only one reader acquisition is open at a
time; another waits interruptibly. The reader's scope owns the handle.

<!-- typecheck -->
```ts
import { NodeDatagramSocket } from '@effect/platform-node-shared';
import { Effect } from 'effect';

const echoOneBatch = Effect.gen(function* () {
	const socket = yield* NodeDatagramSocket.make({ bind: { port: 0 } });
	const reader = yield* socket.reader;
	const writer = yield* socket.writer;
	const packets = yield* reader.pull;
	yield* Effect.forEach(packets, (received) =>
		writer.write({ payload: received.payload, address: received }),
		{ concurrency: 1 }
	);
}).pipe(Effect.scoped);
```

- `reader.pull` yields non-empty packet batches. Each packet's `Uint8Array`
  payload is safe to retain; sender/local addresses are parsed lazily.
- A platform-received datagram can be the outgoing `address`, as above, without
  parsing its sender. Hand-built/custom datagrams must use `received.address`.
- Receive capacity defaults to 1024 **packets**, not bytes (~64 MiB worst case).
  `receiveBuffer: { capacity, strategy: 'dropping' | 'sliding' }` drops incoming
  packets by default, or drops the oldest with `sliding`. `reader.dropped()`
  counts only this buffer overflow, not network/kernel loss or scope-close loss.
- A terminal read error is sticky: drain queued packets, then pulls fail until
  scope close. Scope close discards queued packets and fails pending pulls.
- `writer.writeAll` fails on the first send error without resending; partial
  delivery is unspecified. A successful send is not delivery acknowledgement.
  There is no built-in write timeout; bound writes explicitly when necessary.
- Node's `peer` is a default destination, not sender filtering; `connect`
  applies native sender filtering and cannot be combined with `peer`. An
  explicit InetAddress destination is rejected on connected sockets.
- Errors wrap Open/Read/Write/Closed/Unsupported reasons in
  `DatagramSocketError`. Bun may omit the failed batch packet's address.
  `reader.joinMulticast` is scoped; ingress selection is separate from sending
  `multicast.interface`. Source-specific multicast is unsupported on Deno.

Do not retry sends blindly: UDP does not provide exactly-once delivery. A new
reader acquisition opens a new socket; scope retry around acquisition and use
protocol-level identifiers/deduplication if a request might be sent again.

## Higher-level transports and tests

- RPC: `RpcClient.layerProtocolSocket` and `RpcServer.layerProtocolSocketServer`;
  provide matching `RpcSerialization` layers and the socket/server layer.
  Missed pongs are read failures that fail in-flight calls even with transient
  open-error retries enabled; reconnect does not replay those calls.
  `pingInterval` and `pingTimeout` default to 5 seconds; **any decoded frame**
  counts as liveness, not only a pong. Optional `onPingTimeout` runs only when
  dropping an open connection, before disconnect notification/in-flight failures;
  callback defects are logged and ignored. Delayed ping ticks do not by
  themselves disconnect a connection that has received recent traffic.
- Cluster: `NodeClusterSocket` / `BunClusterSocket`; SchemaBinary is the default,
  NDJSON is explicitly selectable.
- In-memory tests: `Socket.fromTransformStream` wraps a readable/writable pair;
  end-of-input is still a close failure. Bound the read or handle the close reason.
- Integration tests: use a loopback listener on port 0, scope both sides, assert
  message order, close/error propagation, and interruption cleanup.

Completion: every connection has one reader owner, writes have an active reader,
frames are decoded, retries reacquire resources, and shutdown releases both sides.

Wersje

RepozytoriumWynikLicencja
Ta strona · mpsuesser/opencode-effect-enforcer100MIT
mpsuesser/opencode-effect-enforcer100MIT

Migawka bazy z 13.11.2026 · wersja paczki 73bb222919e5 (skrót SHA-256 całej paczki)

Pobrania w ostatnich 30 dniach: 0 · ranking

Zgłoś problem z tym skillem: szkodliwa treść, błędny opis albo prośba autora o usunięcie.

Podobne skille

  • 100websocket-engineer
    Projektuje komunikację dwukierunkową w czasie rzeczywistym przez WebSockety i Socket.IO, także na dużą skalę.
  • 100socket-io
    Opisuje komunikację dwukierunkową w czasie rzeczywistym w Socket.IO: pokoje, przestrzenie nazw, potwierdzenia, middleware, skalowanie z adapterem Redis i typy TypeScript.
  • 100dotnetty
    Wspiera pracę z DotNetty, asynchronicznym frameworkiem sieciowym: serwery i klienci TCP i UDP, własne protokoły binarne, potoki kanałów w stylu Netty, handlery kodeków i połączenia TLS.
  • 100socketio
    Pomaga budować w Socket.IO komunikację w czasie rzeczywistym dla Node.js: czat, powiadomienia na żywo, panele, wskaźniki obecności, współdzielenie edycji i pub/sub, z uwierzytelnianiem połączeń i skalowaniem.
  • 100react-websocket
    Opisuje integrację WebSocket w aplikacjach React: własne hooki, providery kontekstu, Socket.IO i synchronizacja stanu w czasie rzeczywistym z TanStack Query.
  • 100websocket-engineer
    Projektuje systemy czasu rzeczywistego na WebSockets i Socket.IO: dwukierunkowe wiadomości, skalowanie przez Redis, obecność i pokoje.

Uwagi i zgłoszenia

Wiadomość trafi do Bartosza Sokolińskiego. Adres e-mail jest potrzebny tylko do odpowiedzi. Szczegóły w polityce prywatności.