opencode-effect-enforcer 0.2.8 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -3
- package/docs/effect-4.0.0-changelog.md +3213 -0
- package/docs/effect-4.0.0.md +110 -0
- package/guidance/effect-first-development.md +8 -6
- package/guidance/progressive-disclosure-guidance.md +15 -7
- package/package.json +2 -2
- package/patterns/avoid-any.md +2 -2
- package/patterns/avoid-direct-json.md +6 -6
- package/patterns/avoid-native-fetch.md +8 -6
- package/patterns/avoid-node-imports.md +2 -2
- package/patterns/avoid-non-null-assertion.md +2 -2
- package/patterns/avoid-object-type.md +2 -2
- package/patterns/avoid-platform-coupling.md +1 -1
- package/patterns/avoid-process-env.md +3 -4
- package/patterns/avoid-ts-ignore.md +1 -1
- package/patterns/context-tag-extends.md +11 -8
- package/patterns/effect-promise-vs-trypromise.md +6 -7
- package/patterns/prefer-arr-sort.md +1 -1
- package/patterns/prefer-effect-fn.md +21 -65
- package/patterns/prefer-schema-class.md +3 -3
- package/patterns/throw-in-effect-gen.md +1 -1
- package/patterns/use-clock-service.md +4 -0
- package/patterns/use-command-executor-service.md +2 -2
- package/patterns/use-http-client-service.md +8 -6
- package/patterns/use-random-service.md +6 -7
- package/skills/effect-ai-chat/SKILL.md +13 -7
- package/skills/effect-ai-language-model/SKILL.md +50 -21
- package/skills/effect-ai-prompt/SKILL.md +25 -14
- package/skills/effect-ai-provider/SKILL.md +50 -22
- package/skills/effect-ai-streaming/SKILL.md +27 -12
- package/skills/effect-ai-tool/SKILL.md +37 -28
- package/skills/effect-atom-rpc/SKILL.md +57 -36
- package/skills/effect-atom-state/SKILL.md +57 -19
- package/skills/effect-batching/SKILL.md +5 -3
- package/skills/effect-cache/SKILL.md +19 -7
- package/skills/effect-cli/SKILL.md +17 -8
- package/skills/effect-command-executor/SKILL.md +115 -64
- package/skills/effect-concurrency-testing/SKILL.md +26 -6
- package/skills/effect-config/SKILL.md +53 -2
- package/skills/effect-context-witness/SKILL.md +6 -6
- package/skills/effect-domain-modeling/SKILL.md +8 -1
- package/skills/effect-error-handling/SKILL.md +15 -2
- package/skills/effect-fiber/SKILL.md +20 -25
- package/skills/effect-filesystem/SKILL.md +69 -57
- package/skills/effect-http-api/SKILL.md +72 -22
- package/skills/effect-http-client/SKILL.md +25 -21
- package/skills/effect-http-server/SKILL.md +51 -21
- package/skills/effect-incremental-migration/SKILL.md +17 -8
- package/skills/effect-layer-design/SKILL.md +8 -0
- package/skills/effect-managed-runtime/SKILL.md +6 -0
- package/skills/effect-mcp-server/SKILL.md +64 -24
- package/skills/effect-observability/SKILL.md +61 -15
- package/skills/effect-parallelization/SKILL.md +24 -7
- package/skills/effect-path/SKILL.md +8 -2
- package/skills/effect-platform-abstraction/SKILL.md +88 -66
- package/skills/effect-platform-layers/SKILL.md +68 -67
- package/skills/effect-pubsub-event-bus/SKILL.md +56 -60
- package/skills/effect-react-composition/SKILL.md +19 -6
- package/skills/effect-rpc-api/SKILL.md +24 -24
- package/skills/effect-rpc-client/SKILL.md +33 -28
- package/skills/effect-rpc-cluster/SKILL.md +122 -78
- package/skills/effect-rpc-server/SKILL.md +56 -20
- package/skills/effect-scheduling/SKILL.md +29 -1
- package/skills/effect-schema-composition/SKILL.md +31 -13
- package/skills/effect-schema-v4/SKILL.md +94 -10
- package/skills/effect-scope/SKILL.md +13 -5
- package/skills/effect-service-implementation/SKILL.md +1 -1
- package/skills/effect-socket/SKILL.md +52 -8
- package/skills/effect-sql/SKILL.md +67 -33
- package/skills/effect-stream/SKILL.md +50 -5
- package/skills/effect-testing/SKILL.md +91 -2
- package/skills/effect-workflow/SKILL.md +76 -39
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: effect-rpc-api
|
|
3
|
-
description: Define type-safe RPC contracts with effect/
|
|
3
|
+
description: Define type-safe RPC contracts with effect/rpc — Rpc.make payload/success/error/defect schemas, RpcSchema.Stream streaming responses, RpcGroup composition (add/merge/omit/prefix/annotate), and RpcMiddleware.Service definitions shared by client and server. Use when declaring or evolving RPC procedures, building a shared contract package, adding streaming endpoints, or defining auth/observability middleware types.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
You are an Effect TypeScript expert specializing in defining shared RPC contracts with `Rpc`, `RpcGroup`, `RpcSchema`, and `RpcMiddleware` from `effect/
|
|
6
|
+
You are an Effect TypeScript expert specializing in defining shared RPC contracts with `Rpc`, `RpcGroup`, `RpcSchema`, and `RpcMiddleware` from `effect/rpc`.
|
|
7
7
|
|
|
8
8
|
This skill covers the **contract layer**: the definitions that client and server packages both import. Wiring handlers into a server is the `effect-rpc-server` skill; constructing clients and protocols is the `effect-rpc-client` skill; distributed entities are the `effect-rpc-cluster` skill.
|
|
9
9
|
|
|
@@ -16,17 +16,17 @@ implementations must supply `codecFor` (see `effect-rpc-client` /
|
|
|
16
16
|
`effect-rpc-server`). JSON, NDJSON, and JSON-RPC formats use
|
|
17
17
|
their wire representation; the new SchemaBinary transport has its own codecs.
|
|
18
18
|
|
|
19
|
-
The Effect v4 source is at `~/.local/share/opencode/repos/github.com/Effect-TS/effect@main/`. Read
|
|
19
|
+
The Effect v4 source is at `~/.local/share/opencode/repos/github.com/Effect-TS/effect@main/`. Read the `effect@4.0.0` tag for this skill; main may be newer. These APIs remain `@stability unstable` and may break in minor releases despite the shorter imports. Keep Effect-family packages on the same release.
|
|
20
20
|
|
|
21
21
|
Key files:
|
|
22
22
|
|
|
23
|
-
- `packages/effect/src/
|
|
24
|
-
- `packages/effect/src/
|
|
25
|
-
- `packages/effect/src/
|
|
26
|
-
- `packages/effect/src/
|
|
27
|
-
- `packages/effect/src/
|
|
28
|
-
- `packages/effect/src/
|
|
29
|
-
- `packages/effect/src/
|
|
23
|
+
- `packages/effect/src/rpc/Rpc.ts` — `Rpc.make`, `Rpc.custom`, per-rpc combinators, `exitSchema`, `Wrapper` (`fork`/`uninterruptible`/`wrap`), `ServerClient`, every type helper (`Payload`, `Success`, `Error`, `Exit`, `ToHandlerFn`, `ResultFrom`, ...)
|
|
24
|
+
- `packages/effect/src/rpc/RpcGroup.ts` — group construction and composition (`add`/`merge`/`omit`/`prefix`/`middleware`), group vs per-rpc annotations, handler-conversion surface (`toLayer`/`toHandlers`/`toLayerHandler`/`accessHandler`/`of`)
|
|
25
|
+
- `packages/effect/src/rpc/RpcSchema.ts` — the `Stream` schema marker, `isStreamSchema`, `ClientAbort` cause annotation
|
|
26
|
+
- `packages/effect/src/rpc/RpcMiddleware.ts` — `Service` constructor, `layerClient`, `ForClient`, `ApplyServices` and the middleware function shapes
|
|
27
|
+
- `packages/effect/src/rpc/RpcMessage.ts` — wire envelopes: `Request`, `Ack`, `Interrupt`, `Eof`, `Ping`, `ResponseChunk`, `ResponseExit`, `ExitEncoded`, `RequestId`
|
|
28
|
+
- `packages/effect/src/rpc/RpcClientError.ts` — the transport error type referenced when typing shared client aliases
|
|
29
|
+
- `packages/effect/src/rpc/index.ts` — public exports of the rpc namespace
|
|
30
30
|
- `packages/platform/node/test/fixtures/rpc-schemas.ts` — the best real-world contract fixture: rpcs, streaming, middleware, deferred responses
|
|
31
31
|
- `packages/effect/test/rpc/Rpc.test.ts` — `exitSchema`, custom defect schemas, `getStreamSchemas` semantics
|
|
32
32
|
|
|
@@ -54,10 +54,10 @@ Imports used throughout (all from the `effect` package; there is no `@effect/rpc
|
|
|
54
54
|
|
|
55
55
|
```ts
|
|
56
56
|
import { Context, Schema } from 'effect';
|
|
57
|
-
import { Rpc, RpcGroup, RpcMiddleware, RpcSchema } from 'effect/
|
|
57
|
+
import { Rpc, RpcGroup, RpcMiddleware, RpcSchema } from 'effect/rpc';
|
|
58
58
|
```
|
|
59
59
|
|
|
60
|
-
Deep subpath imports also work (the package exports a `./*` wildcard), e.g. `import * as RpcSchema from 'effect/
|
|
60
|
+
Deep subpath imports also work (the package exports a `./*` wildcard), e.g. `import * as RpcSchema from 'effect/rpc/RpcSchema'` or `import { RpcClientError } from 'effect/rpc/RpcClientError'`.
|
|
61
61
|
|
|
62
62
|
---
|
|
63
63
|
|
|
@@ -78,7 +78,7 @@ Two declaration styles, both official:
|
|
|
78
78
|
|
|
79
79
|
```ts
|
|
80
80
|
import { Schema } from 'effect';
|
|
81
|
-
import { Rpc } from 'effect/
|
|
81
|
+
import { Rpc } from 'effect/rpc';
|
|
82
82
|
|
|
83
83
|
export class User extends Schema.Class<User>('User')({
|
|
84
84
|
id: Schema.String,
|
|
@@ -308,7 +308,7 @@ A middleware is declared as a `Context.Service` class with two parameter slots:
|
|
|
308
308
|
|
|
309
309
|
```ts
|
|
310
310
|
import { Context, Schema } from 'effect';
|
|
311
|
-
import { RpcMiddleware } from 'effect/
|
|
311
|
+
import { RpcMiddleware } from 'effect/rpc';
|
|
312
312
|
|
|
313
313
|
export class CurrentUser extends Context.Service<CurrentUser, User>()('CurrentUser') {}
|
|
314
314
|
|
|
@@ -347,7 +347,7 @@ The service value is a function `(effect, options) => Effect` — options: `{ cl
|
|
|
347
347
|
Defined alongside the contract so client packages can provide it. The function receives `{ rpc, request, next }` and **must** call `next` (with the original or a modified request):
|
|
348
348
|
|
|
349
349
|
```ts
|
|
350
|
-
import { Headers } from 'effect/
|
|
350
|
+
import { Headers } from 'effect/http';
|
|
351
351
|
|
|
352
352
|
export const AuthClient = RpcMiddleware.layerClient(AuthMiddleware, ({ next, request }) =>
|
|
353
353
|
next({
|
|
@@ -368,7 +368,7 @@ export const AuthClient = RpcMiddleware.layerClient(AuthMiddleware, ({ next, req
|
|
|
368
368
|
|
|
369
369
|
```ts
|
|
370
370
|
import { Schema } from 'effect';
|
|
371
|
-
import { Rpc } from 'effect/
|
|
371
|
+
import { Rpc } from 'effect/rpc';
|
|
372
372
|
|
|
373
373
|
// Type-level definition: how success/error transform
|
|
374
374
|
export interface RpcWithPagination extends Rpc.Custom {
|
|
@@ -451,8 +451,8 @@ You rarely touch `RpcMessage` directly, but the envelope explains several contra
|
|
|
451
451
|
Key facts:
|
|
452
452
|
|
|
453
453
|
- A `RequestEncoded` carries `{ _tag: 'Request', id: string | number, tag: string, payload: unknown, headers: Array<[string, string]>, isNotification?, traceId?, spanId?, sampled? }`. The rpc's `_tag` **is the wire identity** — renaming or re-prefixing an rpc is a breaking protocol change for deployed clients.
|
|
454
|
-
- `RequestId` is a branded `string | number`; construct it with `RequestId(1)` or `RequestId('1')` (from `effect/
|
|
455
|
-
- Terminal results travel as `ExitEncoded` — `Success` with a value, or `Failure` with a cause array of `Fail` (your error union, encoded), `Die` (via `defectSchema`), and `Interrupt` entries. This is exactly what `Rpc.exitSchema` encodes/decodes.
|
|
454
|
+
- `RequestId` is a branded `string | number`; construct it with `RequestId(1)` or `RequestId('1')` (from `effect/rpc/RpcMessage`). You need it when invoking handlers manually in tests via `accessHandler`. `bigint` is not accepted.
|
|
455
|
+
- Terminal results travel as `ExitEncoded` — `Success` with a value, or `Failure` with a cause array of `Fail` (your error union, encoded), `Die` (via `defectSchema`), and `Interrupt` entries. This is exactly what `Rpc.exitSchema` encodes/decodes. Encoded interrupt `fiberId` may be `null` as well as absent/`undefined`; JSON encoding can produce `null`. Custom envelope consumers must accept both absence forms.
|
|
456
456
|
- Stream elements travel as batched `Chunk` messages, acknowledged by `Ack` on ack-capable protocols (sockets, workers); the HTTP protocol declares `supportsAck: false`. Incremental delivery requires a serialization with framing (e.g. ndjson) — with non-framed json over HTTP the chunks are buffered and returned in one final batch (see serialization notes in the `effect-rpc-cluster` skill).
|
|
457
457
|
- Servers represent server-originated calls and notifications with `RequestEncoded` in `FromServerEncoded`. Set `isNotification: true` for a notification; JSON-RPC serialization then omits `id`. Buffered, unframed JSON-RPC HTTP cannot deliver notifications and drops them, while framed HTTP, sockets, stdio, and workers support them.
|
|
458
458
|
|
|
@@ -493,8 +493,8 @@ RpcGroup.HandlerFrom<R, Tag>; // one handler fn type
|
|
|
493
493
|
The canonical use — typing a client service in the shared package without constructing anything:
|
|
494
494
|
|
|
495
495
|
```ts
|
|
496
|
-
import type { RpcClient } from 'effect/
|
|
497
|
-
import type { RpcClientError } from 'effect/
|
|
496
|
+
import type { RpcClient } from 'effect/rpc';
|
|
497
|
+
import type { RpcClientError } from 'effect/rpc/RpcClientError';
|
|
498
498
|
|
|
499
499
|
export type UsersClient = RpcClient.RpcClient<
|
|
500
500
|
RpcGroup.Rpcs<typeof UsersGroup>,
|
|
@@ -513,7 +513,7 @@ Everything client and server need, with zero runtime wiring:
|
|
|
513
513
|
```ts
|
|
514
514
|
// contracts/users.ts — imported by both server and client packages
|
|
515
515
|
import { Context, Schema } from 'effect';
|
|
516
|
-
import { Rpc, RpcGroup, RpcMiddleware } from 'effect/
|
|
516
|
+
import { Rpc, RpcGroup, RpcMiddleware } from 'effect/rpc';
|
|
517
517
|
|
|
518
518
|
// --- domain schemas ---
|
|
519
519
|
export class User extends Schema.Class<User>('User')({
|
|
@@ -591,7 +591,7 @@ export const PublicGroup = AdminGroup.omit('DeleteUser', 'PurgeAll').middleware(
|
|
|
591
591
|
Contracts double as entity protocols — annotate, then hand to `Entity.fromRpcGroup`:
|
|
592
592
|
|
|
593
593
|
```ts
|
|
594
|
-
import { ClusterSchema } from 'effect/
|
|
594
|
+
import { ClusterSchema } from 'effect/cluster';
|
|
595
595
|
|
|
596
596
|
export const DurableUsers = UsersGroup.annotateRpcs(ClusterSchema.Persisted, true);
|
|
597
597
|
// Entity.fromRpcGroup('Users', DurableUsers) — see the effect-rpc-cluster skill
|
|
@@ -602,7 +602,7 @@ export const DurableUsers = UsersGroup.annotateRpcs(ClusterSchema.Persisted, tru
|
|
|
602
602
|
```ts
|
|
603
603
|
import { assert, it } from '@effect/vitest';
|
|
604
604
|
import { Exit, Schema } from 'effect';
|
|
605
|
-
import { Rpc } from 'effect/
|
|
605
|
+
import { Rpc } from 'effect/rpc';
|
|
606
606
|
|
|
607
607
|
it('GetUser exits round-trip', () => {
|
|
608
608
|
const schema = Rpc.exitSchema(GetUser);
|
|
@@ -614,7 +614,7 @@ it('GetUser exits round-trip', () => {
|
|
|
614
614
|
|
|
615
615
|
## Common Mistakes
|
|
616
616
|
|
|
617
|
-
1. **Importing from `@effect/rpc`.** The package does not exist in v4 — everything is `effect/
|
|
617
|
+
1. **Importing from `@effect/rpc`.** The package does not exist in v4 — everything is `effect/rpc` (deep subpaths like `effect/rpc/RpcMessage` also work).
|
|
618
618
|
2. **Reaching for `Rpc.fromTaggedRequest` / `Schema.TaggedRequest`.** Neither exists in v4. The tag is `Rpc.make`'s first argument; the payload schema carries no `_tag` field — the wire envelope transports the tag separately.
|
|
619
619
|
3. **Expecting `error` to stay the Effect error with `stream: true`.** It becomes the *stream* error schema and the rpc's `errorSchema` is set to `Schema.Never`. Likewise `success` becomes the *element* schema.
|
|
620
620
|
4. **Passing `primaryKey` with a schema payload.** It is typed `never` unless `payload` is inline struct fields — wrap the fields inline or drop `primaryKey`.
|
|
@@ -3,23 +3,23 @@ name: effect-rpc-client
|
|
|
3
3
|
description: Consume typed RPC services with Effect's RpcClient — protocol layers (HTTP, WebSocket, TCP, worker, in-memory), RpcSerialization codecs, per-call and ambient headers, streaming calls, interruption, reconnection, and RpcClientError handling. Use when calling an RpcGroup from a client, wiring a client transport + serialization stack, debugging RPC transport failures or reconnects, or testing RPC consumers with RpcTest.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
You are an Effect TypeScript expert specializing in consuming RPC services with `RpcClient` from `effect/
|
|
6
|
+
You are an Effect TypeScript expert specializing in consuming RPC services with `RpcClient` from `effect/rpc`.
|
|
7
7
|
|
|
8
8
|
This skill covers the client side only: building clients, transports, serialization, headers, streaming consumption, interruption, errors, and testing. Defining `Rpc`/`RpcGroup` contracts is the `effect-rpc-api` skill; serving them is the `effect-rpc-server` skill; cluster entity clients are the `effect-rpc-cluster` skill.
|
|
9
9
|
|
|
10
10
|
## Effect Source Reference
|
|
11
11
|
|
|
12
|
-
The Effect v4 source is at `~/.local/share/opencode/repos/github.com/Effect-TS/effect@main/`. Read
|
|
12
|
+
The Effect v4 source is at `~/.local/share/opencode/repos/github.com/Effect-TS/effect@main/`. Read the `effect@4.0.0` tag for this skill; main may be newer. These APIs remain `@stability unstable` and may break in minor releases. Keep Effect-family packages on the same release.
|
|
13
13
|
|
|
14
14
|
Key files:
|
|
15
15
|
|
|
16
|
-
- `packages/effect/src/
|
|
17
|
-
- `packages/effect/src/
|
|
18
|
-
- `packages/effect/src/
|
|
19
|
-
- `packages/effect/src/
|
|
20
|
-
- `packages/effect/src/
|
|
21
|
-
- `packages/effect/src/
|
|
22
|
-
- `packages/effect/src/
|
|
16
|
+
- `packages/effect/src/rpc/RpcClient.ts` — `make`, `makeNoSerialization`, the `Protocol` service, `layerProtocolHttp`/`layerProtocolSocket`/`layerProtocolWorker` (+ `makeProtocol*`), `CurrentHeaders`, `withHeaders`, `ConnectionHooks`
|
|
17
|
+
- `packages/effect/src/rpc/RpcClientError.ts` — `RpcClientError` and `RpcClientDefect`
|
|
18
|
+
- `packages/effect/src/rpc/RpcSerialization.ts` — JSON, NDJSON, JSON-RPC, SchemaBinary codecs, their layers, the `Parser` interface and `includesFraming`
|
|
19
|
+
- `packages/effect/src/rpc/RpcTest.ts` — `makeClient` in-process test client
|
|
20
|
+
- `packages/effect/src/rpc/RpcWorker.ts` — `InitialMessage`, `layerInitialMessage`, `initialMessage`
|
|
21
|
+
- `packages/effect/src/rpc/RpcMessage.ts` — wire vocabulary: `Request`, `Ack`, `Interrupt`, `Chunk`, `Exit`, `Defect`, `Ping`/`Pong`, `ClientProtocolError`, `RequestId`
|
|
22
|
+
- `packages/effect/src/socket/Socket.ts` — `Socket` service, `layerWebSocket`, `WebSocketConstructor`
|
|
23
23
|
- `packages/platform/node/test/RpcServer.test.ts` + `test/fixtures/rpc-e2e.ts` + `test/fixtures/rpc-schemas.ts` — the best end-to-end reference: every transport × serialization combination, headers, streams, interrupts, defects
|
|
24
24
|
- `packages/platform/browser/test/RpcWorker.test.ts` + `test/fixtures/rpc-worker.ts` — worker transport end to end
|
|
25
25
|
|
|
@@ -58,16 +58,16 @@ import {
|
|
|
58
58
|
RpcSerialization,
|
|
59
59
|
RpcTest,
|
|
60
60
|
RpcWorker
|
|
61
|
-
} from 'effect/
|
|
62
|
-
import { RpcClientError } from 'effect/
|
|
63
|
-
import { FetchHttpClient, Headers, HttpClient, HttpClientRequest } from 'effect/
|
|
64
|
-
import { Socket } from 'effect/
|
|
61
|
+
} from 'effect/rpc';
|
|
62
|
+
import { RpcClientError } from 'effect/rpc/RpcClientError';
|
|
63
|
+
import { FetchHttpClient, Headers, HttpClient, HttpClientRequest } from 'effect/http';
|
|
64
|
+
import { Socket } from 'effect/socket';
|
|
65
65
|
// Platform transports:
|
|
66
66
|
import { NodeSocket, NodeWorker } from '@effect/platform-node';
|
|
67
67
|
import { BrowserWorker } from '@effect/platform-browser';
|
|
68
68
|
```
|
|
69
69
|
|
|
70
|
-
Note the `RpcClientError` import: the `effect/
|
|
70
|
+
Note the `RpcClientError` import: the `effect/rpc` barrel exports `RpcClientError` as a *namespace*; import the class from the module path `effect/rpc/RpcClientError` (this is what the upstream tests do).
|
|
71
71
|
|
|
72
72
|
---
|
|
73
73
|
|
|
@@ -113,7 +113,7 @@ export class UsersClient extends Context.Service<
|
|
|
113
113
|
|
|
114
114
|
### `flatten: true` — single-function client
|
|
115
115
|
|
|
116
|
-
The client becomes one function `(tag, payload, options?)` instead of a property-per-tag object. Useful for generic proxying (this is what `AtomRpc` uses internally):
|
|
116
|
+
The client becomes one function `(tag, payload, options?)` instead of a property-per-tag object. Useful for generic proxying (this is what `AtomRpc` uses internally). Union-valued tags infer payloads/results from the selected RPCs rather than `never`; retain tag/payload correlation in application dispatch:
|
|
117
117
|
|
|
118
118
|
```ts
|
|
119
119
|
const client = yield* RpcClient.make(UserRpcs, { flatten: true });
|
|
@@ -122,7 +122,7 @@ const user = yield* client('GetUser', { id: 'u1' });
|
|
|
122
122
|
|
|
123
123
|
### `generateRequestId`
|
|
124
124
|
|
|
125
|
-
Request ids correlate requests with responses on a shared connection. The default is a process-wide incrementing `number`. Override only when ids need application-specific generation. The function must return `RequestId`, a branded `string | number`; construct it with `RequestId(1)` or `RequestId('1')` from `effect/
|
|
125
|
+
Request ids correlate requests with responses on a shared connection. The default is a process-wide incrementing `number`. Override only when ids need application-specific generation. The function must return `RequestId`, a branded `string | number`; construct it with `RequestId(1)` or `RequestId('1')` from `effect/rpc/RpcMessage`. `bigint` is not accepted.
|
|
126
126
|
|
|
127
127
|
### Tracing
|
|
128
128
|
|
|
@@ -311,14 +311,19 @@ full `RpcSerialization.CodecFor` contract.
|
|
|
311
311
|
| `RpcSerialization.layerJson` | `application/json` | no | HTTP (response decoded once, as an array); WebSocket (each ws message is one frame already) |
|
|
312
312
|
| `RpcSerialization.layerNdjson` | `application/ndjson` | yes (newline) | anything — the safe default; enables streaming over HTTP |
|
|
313
313
|
| `RpcSerialization.layerSchemaBinary(options?)` | `application/vnd.effect.rpc+schema-binary` | yes | schema-derived binary framing and payload codecs; pair on both peers |
|
|
314
|
+
| `RpcSerialization.layerJsonRpc({ contentType? })` | `application/json` | no | JSON-RPC 2.0 interop over HTTP/WebSocket; maps rpc tag ↔ `method`, supports batch arrays |
|
|
315
|
+
| `RpcSerialization.layerNdJsonRpc({ contentType? })` | `application/json-rpc` | yes (newline) | JSON-RPC 2.0 over sockets |
|
|
314
316
|
|
|
315
317
|
`layerSchemaBinary({ maxFrameSize?, fingerprintPayloads? })` defaults to a
|
|
316
318
|
16 MiB frame limit and no payload fingerprint. Envelopes are fingerprinted and
|
|
317
319
|
dictionary-enabled; payload fingerprints opt into strict layout agreement.
|
|
318
320
|
Workers use
|
|
319
321
|
`Schema.toCodecJson` with structured clone without a serialization layer.
|
|
320
|
-
|
|
321
|
-
|
|
322
|
+
|
|
323
|
+
NDJSON parsers skip malformed lines and continue with subsequent frames.
|
|
324
|
+
JSON-RPC decoding skips non-object values (including batch entries), and guards
|
|
325
|
+
non-string notification methods before interpreting internal protocol methods.
|
|
326
|
+
This framing recovery does not replace payload/schema validation.
|
|
322
327
|
|
|
323
328
|
Choose serialization according to transport framing:
|
|
324
329
|
|
|
@@ -403,8 +408,8 @@ On the server, a client-initiated interrupt carries the `RpcSchema.ClientAbort`
|
|
|
403
408
|
| `reason._tag` | Transport | Meaning |
|
|
404
409
|
|---|---|---|
|
|
405
410
|
| `'HttpError'` | HTTP | `HttpClientErrorSchema`: has `kind: 'EncodeError' \| 'DecodeError' \| 'TransportError' \| 'InvalidUrlError' \| 'StatusCodeError' \| 'EmptyBodyError'` and optional `cause` |
|
|
406
|
-
| `'SocketOpenError'` | socket | failed to (re)connect,
|
|
407
|
-
| `'SocketReadError'` / `'SocketWriteError'` | socket | I/O failure on a live connection |
|
|
411
|
+
| `'SocketOpenError'` | socket | failed to (re)connect, including opening-handshake timeouts |
|
|
412
|
+
| `'SocketReadError'` / `'SocketWriteError'` | socket | I/O failure on a live connection; a missed pong is a read error |
|
|
408
413
|
| `'SocketCloseError'` | socket | connection closed (has `code`) |
|
|
409
414
|
| `'WorkerSpawnError'` / `'WorkerSendError'` / `'WorkerReceiveError'` / `'WorkerUnknownError'` | worker | worker lifecycle failures |
|
|
410
415
|
| `'RpcClientDefect'` | any | protocol bug: undecodable message, empty HTTP response, non-array unframed response; has `message` + `cause` |
|
|
@@ -438,11 +443,11 @@ Semantics to remember:
|
|
|
438
443
|
|
|
439
444
|
The socket protocol owns a long-lived connection loop:
|
|
440
445
|
|
|
441
|
-
- **Keepalive** — the client sends `Ping` every 5 seconds; a missing `Pong` by the next tick fails the connection with a `
|
|
446
|
+
- **Keepalive** — the client sends `Ping` every 5 seconds; a missing `Pong` by the next tick fails the connection with a `SocketReadError` and triggers reconnect. Every in-flight call fails even with `retryTransientErrors: true`; calls are not replayed automatically.
|
|
442
447
|
- **Reconnect policy** — the loop retries with `Schedule.min([Schedule.exponential("500 millis", 1.5), Schedule.spaced("5 seconds")])`, capped at 5s between attempts, forever, by default. Customize the schedule via `makeProtocolSocket({ retryPolicy })` + `Layer.effect(RpcClient.Protocol)(...)`; both constructors accept `retryTransientErrors` and `onTransientError`.
|
|
443
448
|
- **Failure broadcast** — on a connection error, all in-flight requests fail with `RpcClientError`, and subsequent `send`s fail fast with the same error until the socket reopens.
|
|
444
|
-
- **`retryTransientErrors: true`** — `SocketOpenError` failures
|
|
445
|
-
- **`onTransientError`** — called for every retried `SocketOpenError`
|
|
449
|
+
- **`retryTransientErrors: true`** — connection-establishment `SocketOpenError` failures are not broadcast: pending requests stay pending across those reconnect attempts instead of failing. Established-connection read/write/close errors, including missed pongs, still fail in-flight requests.
|
|
450
|
+
- **`onTransientError`** — called for every retried `SocketOpenError` while `retryTransientErrors` is enabled. Missed pongs do **not** invoke it. Its infallible, service-free effect is for logging/metrics; defects in the hook are logged and ignored.
|
|
446
451
|
|
|
447
452
|
```ts
|
|
448
453
|
const ProtocolLive = RpcClient.layerProtocolSocket({ retryTransientErrors: true }).pipe(
|
|
@@ -504,7 +509,7 @@ const BrowserClient = UsersClient.layer.pipe(
|
|
|
504
509
|
|
|
505
510
|
No `RpcSerialization` is needed — structured clone carries the messages. The worker side runs `RpcServer.layerProtocolWorkerRunner` (see the effect-rpc-server skill).
|
|
506
511
|
|
|
507
|
-
Transferables: to move binary data instead of copying it, wrap fields in the shared contract with `Transferable` from `effect/
|
|
512
|
+
Transferables: to move binary data instead of copying it, wrap fields in the shared contract with `Transferable` from `effect/workers` — `Transferable.Uint8Array` transfers the backing `ArrayBuffer`, `Transferable.ImageData`/`Transferable.MessagePort` are prebuilt, and `Transferable.schema(s, (encoded) => [encoded.buffer])` wraps any schema with a custom transfer-list extractor. Transfer only happens on protocols with `supportsTransferables: true` — the worker protocol only.
|
|
508
513
|
|
|
509
514
|
### Typed startup config — `RpcWorker.InitialMessage`
|
|
510
515
|
|
|
@@ -578,9 +583,9 @@ The primitive under `RpcTest`: builds a client from a raw decoded-message channe
|
|
|
578
583
|
```ts
|
|
579
584
|
// client.ts — UserRpcs comes from the shared contract module (effect-rpc-api skill)
|
|
580
585
|
import { Context, Effect, Layer } from 'effect';
|
|
581
|
-
import { FetchHttpClient } from 'effect/
|
|
582
|
-
import { RpcClient, RpcGroup, RpcSerialization } from 'effect/
|
|
583
|
-
import { RpcClientError } from 'effect/
|
|
586
|
+
import { FetchHttpClient } from 'effect/http';
|
|
587
|
+
import { RpcClient, RpcGroup, RpcSerialization } from 'effect/rpc';
|
|
588
|
+
import { RpcClientError } from 'effect/rpc/RpcClientError';
|
|
584
589
|
import { AuthClient, UserRpcs } from './contract.ts';
|
|
585
590
|
|
|
586
591
|
export class UsersClient extends Context.Service<
|
|
@@ -664,7 +669,7 @@ const WorkerClientLive = UsersClient.layer.pipe(
|
|
|
664
669
|
|
|
665
670
|
## Common Mistakes
|
|
666
671
|
|
|
667
|
-
1. **Importing from `@effect/rpc`.** Gone in v4 — everything is `effect/
|
|
672
|
+
1. **Importing from `@effect/rpc`.** Gone in v4 — everything is `effect/rpc`. And `import { RpcClientError } from 'effect/rpc'` gives you a *namespace*; import the class from `effect/rpc/RpcClientError`.
|
|
668
673
|
2. **Missing `Scope` for `RpcClient.make`.** The constructor is scoped. Build clients inside `Layer.effect(Tag)(RpcClient.make(group))` or under `Effect.scoped` — providing protocol layers alone will not discharge `Scope`.
|
|
669
674
|
3. **Forgetting the client middleware layer.** A group with a `requiredForClient: true` middleware needs `Layer.provide(RpcMiddleware.layerClient(M, ...))` on the client (and in `RpcTest.makeClient`'s context). The compile error points at an unsatisfied `ForClient<M>` requirement — provide the layer, don't cast.
|
|
670
675
|
4. **Wrong codec/transport pairing.** Raw TCP requires `layerNdjson`, `layerSchemaBinary()`, or `layerNdJsonRpc()`. HTTP with unframed JSON buffers the response; use a framed codec for streaming. WebSocket supplies its own framing.
|