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.
Files changed (72) hide show
  1. package/README.md +3 -3
  2. package/docs/effect-4.0.0-changelog.md +3213 -0
  3. package/docs/effect-4.0.0.md +110 -0
  4. package/guidance/effect-first-development.md +8 -6
  5. package/guidance/progressive-disclosure-guidance.md +15 -7
  6. package/package.json +2 -2
  7. package/patterns/avoid-any.md +2 -2
  8. package/patterns/avoid-direct-json.md +6 -6
  9. package/patterns/avoid-native-fetch.md +8 -6
  10. package/patterns/avoid-node-imports.md +2 -2
  11. package/patterns/avoid-non-null-assertion.md +2 -2
  12. package/patterns/avoid-object-type.md +2 -2
  13. package/patterns/avoid-platform-coupling.md +1 -1
  14. package/patterns/avoid-process-env.md +3 -4
  15. package/patterns/avoid-ts-ignore.md +1 -1
  16. package/patterns/context-tag-extends.md +11 -8
  17. package/patterns/effect-promise-vs-trypromise.md +6 -7
  18. package/patterns/prefer-arr-sort.md +1 -1
  19. package/patterns/prefer-effect-fn.md +21 -65
  20. package/patterns/prefer-schema-class.md +3 -3
  21. package/patterns/throw-in-effect-gen.md +1 -1
  22. package/patterns/use-clock-service.md +4 -0
  23. package/patterns/use-command-executor-service.md +2 -2
  24. package/patterns/use-http-client-service.md +8 -6
  25. package/patterns/use-random-service.md +6 -7
  26. package/skills/effect-ai-chat/SKILL.md +13 -7
  27. package/skills/effect-ai-language-model/SKILL.md +50 -21
  28. package/skills/effect-ai-prompt/SKILL.md +25 -14
  29. package/skills/effect-ai-provider/SKILL.md +50 -22
  30. package/skills/effect-ai-streaming/SKILL.md +27 -12
  31. package/skills/effect-ai-tool/SKILL.md +37 -28
  32. package/skills/effect-atom-rpc/SKILL.md +57 -36
  33. package/skills/effect-atom-state/SKILL.md +57 -19
  34. package/skills/effect-batching/SKILL.md +5 -3
  35. package/skills/effect-cache/SKILL.md +19 -7
  36. package/skills/effect-cli/SKILL.md +17 -8
  37. package/skills/effect-command-executor/SKILL.md +115 -64
  38. package/skills/effect-concurrency-testing/SKILL.md +26 -6
  39. package/skills/effect-config/SKILL.md +53 -2
  40. package/skills/effect-context-witness/SKILL.md +6 -6
  41. package/skills/effect-domain-modeling/SKILL.md +8 -1
  42. package/skills/effect-error-handling/SKILL.md +15 -2
  43. package/skills/effect-fiber/SKILL.md +20 -25
  44. package/skills/effect-filesystem/SKILL.md +69 -57
  45. package/skills/effect-http-api/SKILL.md +72 -22
  46. package/skills/effect-http-client/SKILL.md +25 -21
  47. package/skills/effect-http-server/SKILL.md +51 -21
  48. package/skills/effect-incremental-migration/SKILL.md +17 -8
  49. package/skills/effect-layer-design/SKILL.md +8 -0
  50. package/skills/effect-managed-runtime/SKILL.md +6 -0
  51. package/skills/effect-mcp-server/SKILL.md +64 -24
  52. package/skills/effect-observability/SKILL.md +61 -15
  53. package/skills/effect-parallelization/SKILL.md +24 -7
  54. package/skills/effect-path/SKILL.md +8 -2
  55. package/skills/effect-platform-abstraction/SKILL.md +88 -66
  56. package/skills/effect-platform-layers/SKILL.md +68 -67
  57. package/skills/effect-pubsub-event-bus/SKILL.md +56 -60
  58. package/skills/effect-react-composition/SKILL.md +19 -6
  59. package/skills/effect-rpc-api/SKILL.md +24 -24
  60. package/skills/effect-rpc-client/SKILL.md +33 -28
  61. package/skills/effect-rpc-cluster/SKILL.md +122 -78
  62. package/skills/effect-rpc-server/SKILL.md +56 -20
  63. package/skills/effect-scheduling/SKILL.md +29 -1
  64. package/skills/effect-schema-composition/SKILL.md +31 -13
  65. package/skills/effect-schema-v4/SKILL.md +94 -10
  66. package/skills/effect-scope/SKILL.md +13 -5
  67. package/skills/effect-service-implementation/SKILL.md +1 -1
  68. package/skills/effect-socket/SKILL.md +52 -8
  69. package/skills/effect-sql/SKILL.md +67 -33
  70. package/skills/effect-stream/SKILL.md +50 -5
  71. package/skills/effect-testing/SKILL.md +91 -2
  72. 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/unstable/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.
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/unstable/rpc`.
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 it directly when in doubt — these modules are under `unstable` and move between betas.
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/unstable/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/unstable/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/unstable/rpc/RpcSchema.ts` — the `Stream` schema marker, `isStreamSchema`, `ClientAbort` cause annotation
26
- - `packages/effect/src/unstable/rpc/RpcMiddleware.ts` — `Service` constructor, `layerClient`, `ForClient`, `ApplyServices` and the middleware function shapes
27
- - `packages/effect/src/unstable/rpc/RpcMessage.ts` — wire envelopes: `Request`, `Ack`, `Interrupt`, `Eof`, `Ping`, `ResponseChunk`, `ResponseExit`, `ExitEncoded`, `RequestId`
28
- - `packages/effect/src/unstable/rpc/RpcClientError.ts` — the transport error type referenced when typing shared client aliases
29
- - `packages/effect/src/unstable/rpc/index.ts` — public exports of the rpc namespace
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/unstable/rpc';
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/unstable/rpc/RpcSchema'` or `import { RpcClientError } from 'effect/unstable/rpc/RpcClientError'`.
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/unstable/rpc';
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/unstable/rpc';
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/unstable/http';
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/unstable/rpc';
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/unstable/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.
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/unstable/rpc';
497
- import type { RpcClientError } from 'effect/unstable/rpc/RpcClientError';
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/unstable/rpc';
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/unstable/cluster';
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/unstable/rpc';
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/unstable/rpc` (deep subpaths like `effect/unstable/rpc/RpcMessage` also work).
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/unstable/rpc`.
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 it directly when in doubt — these modules are under `unstable` and change between betas.
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/unstable/rpc/RpcClient.ts` — `make`, `makeNoSerialization`, the `Protocol` service, `layerProtocolHttp`/`layerProtocolSocket`/`layerProtocolWorker` (+ `makeProtocol*`), `CurrentHeaders`, `withHeaders`, `ConnectionHooks`
17
- - `packages/effect/src/unstable/rpc/RpcClientError.ts` — `RpcClientError` and `RpcClientDefect`
18
- - `packages/effect/src/unstable/rpc/RpcSerialization.ts` — JSON, NDJSON, JSON-RPC, SchemaBinary codecs, their layers, the `Parser` interface and `includesFraming`
19
- - `packages/effect/src/unstable/rpc/RpcTest.ts` — `makeClient` in-process test client
20
- - `packages/effect/src/unstable/rpc/RpcWorker.ts` — `InitialMessage`, `layerInitialMessage`, `initialMessage`
21
- - `packages/effect/src/unstable/rpc/RpcMessage.ts` — wire vocabulary: `Request`, `Ack`, `Interrupt`, `Chunk`, `Exit`, `Defect`, `Ping`/`Pong`, `ClientProtocolError`, `RequestId`
22
- - `packages/effect/src/unstable/socket/Socket.ts` — `Socket` service, `layerWebSocket`, `WebSocketConstructor`
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/unstable/rpc';
62
- import { RpcClientError } from 'effect/unstable/rpc/RpcClientError';
63
- import { FetchHttpClient, Headers, HttpClient, HttpClientRequest } from 'effect/unstable/http';
64
- import { Socket } from 'effect/unstable/socket';
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/unstable/rpc` barrel exports `RpcClientError` as a *namespace*; import the class from the module path `effect/unstable/rpc/RpcClientError` (this is what the upstream tests do).
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/unstable/rpc/RpcMessage`. `bigint` is not accepted.
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
- | `RpcSerialization.layerJsonRpc({ contentType? })` | `application/json` | no | JSON-RPC 2.0 interop over HTTP/WebSocket; maps rpc tag ↔ `method`, supports batch arrays |
321
- | `RpcSerialization.layerNdJsonRpc({ contentType? })` | `application/json-rpc` | yes (newline) | JSON-RPC 2.0 over sockets |
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, includes ping timeouts |
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 `SocketOpenError` (kind `'Timeout'`) and triggers reconnect.
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 (failure to connect, and ping timeouts, which are classified as open errors) are not broadcast: pending requests stay pending across reconnect attempts instead of failing. Read/write/close errors on an established connection still fail in-flight requests.
445
- - **`onTransientError`** — called for every retried `SocketOpenError` (including ping timeouts) while `retryTransientErrors` is enabled. Its infallible, service-free effect is for logging/metrics; defects in the hook are logged and ignored.
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/unstable/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.
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/unstable/http';
582
- import { RpcClient, RpcGroup, RpcSerialization } from 'effect/unstable/rpc';
583
- import { RpcClientError } from 'effect/unstable/rpc/RpcClientError';
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/unstable/rpc`. And `import { RpcClientError } from 'effect/unstable/rpc'` gives you a *namespace*; import the class from `effect/unstable/rpc/RpcClientError`.
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.