opencode-effect-enforcer 0.2.5 → 0.2.6
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 +42 -140
- package/docs/effect-4.0.0-rc.116-changelog.md +2654 -0
- package/docs/effect-4.0.0-rc.116.md +102 -0
- package/guidance/effect-first-development.md +23 -14
- package/guidance/progressive-disclosure-guidance.md +3 -3
- package/package.json +2 -2
- package/patterns/avoid-direct-tag-checks.md +1 -1
- package/patterns/avoid-process-env.md +4 -4
- package/patterns/context-tag-extends.md +4 -4
- package/patterns/prefer-redacted-config.md +10 -10
- package/patterns/require-effect-concurrency.md +1 -1
- package/skills/effect-ai-chat/SKILL.md +2 -2
- package/skills/effect-ai-language-model/SKILL.md +36 -4
- package/skills/effect-ai-prompt/SKILL.md +1 -1
- package/skills/effect-ai-provider/SKILL.md +23 -12
- package/skills/effect-ai-tool/SKILL.md +13 -0
- package/skills/effect-atom-rpc/SKILL.md +7 -1
- package/skills/effect-atom-state/SKILL.md +8 -2
- package/skills/effect-cache/SKILL.md +10 -1
- package/skills/effect-cli/SKILL.md +105 -94
- package/skills/effect-command-executor/SKILL.md +7 -1
- package/skills/effect-config/SKILL.md +67 -44
- package/skills/effect-domain-modeling/SKILL.md +3 -3
- package/skills/effect-error-handling/SKILL.md +2 -2
- package/skills/effect-fiber/SKILL.md +2 -2
- package/skills/effect-filesystem/SKILL.md +34 -4
- package/skills/effect-http-api/SKILL.md +17 -2
- package/skills/effect-http-client/SKILL.md +11 -2
- package/skills/effect-http-server/SKILL.md +28 -11
- package/skills/effect-layer-design/SKILL.md +6 -2
- package/skills/effect-mcp-server/SKILL.md +21 -4
- package/skills/effect-observability/SKILL.md +2 -2
- package/skills/effect-optics/SKILL.md +1 -1
- package/skills/effect-parallelization/SKILL.md +2 -2
- package/skills/effect-pattern-matching/SKILL.md +1 -1
- package/skills/effect-platform-abstraction/SKILL.md +3 -3
- package/skills/effect-rpc-api/SKILL.md +3 -3
- package/skills/effect-rpc-client/SKILL.md +14 -13
- package/skills/effect-rpc-cluster/SKILL.md +15 -12
- package/skills/effect-rpc-server/SKILL.md +11 -13
- package/skills/effect-scheduling/SKILL.md +7 -0
- package/skills/effect-schema-composition/SKILL.md +12 -4
- package/skills/effect-schema-v4/SKILL.md +75 -20
- package/skills/effect-scope/SKILL.md +4 -4
- package/skills/effect-socket/SKILL.md +161 -658
- package/skills/effect-sql/SKILL.md +50 -12
- package/skills/effect-stream/SKILL.md +19 -24
- package/skills/effect-testing/SKILL.md +35 -22
- package/skills/effect-workflow/SKILL.md +13 -2
|
@@ -81,7 +81,7 @@ yield* Effect.forEach(ids, fetchUser, { concurrency: 8 });
|
|
|
81
81
|
yield* Effect.forEach(ids, fetchUser, { concurrency: 'unbounded' });
|
|
82
82
|
```
|
|
83
83
|
|
|
84
|
-
|
|
84
|
+
Reusable APIs that expose fan-out policy should accept a `Types.Concurrency` value and pass it explicitly to each combinator instead of relying on ambient configuration.
|
|
85
85
|
|
|
86
86
|
### Failure semantics under concurrency
|
|
87
87
|
|
|
@@ -652,7 +652,7 @@ const checkConfig = (entries: ReadonlyArray<Entry>) =>
|
|
|
652
652
|
## Common Mistakes
|
|
653
653
|
|
|
654
654
|
1. **Assuming `Effect.all` / `Effect.forEach` are parallel by default** — they are sequential. Pass `{ concurrency: n | 'unbounded' }` explicitly; without it you also silently lose request batching (see effect-batching).
|
|
655
|
-
2. **
|
|
655
|
+
2. **Relying on ambient concurrency** — pass a number or `"unbounded"` explicitly at each combinator.
|
|
656
656
|
3. **Wrong option key on zips** — `Effect.zip`/`zipWith` take `{ concurrent: true }` (boolean), not `{ concurrency: ... }`.
|
|
657
657
|
4. **v3 `mode: 'either'` / `mode: 'validate'` on `Effect.all`** — gone. v4 has `mode: 'result'` (slots become `Result<A, E>`); for accumulate-all-failures use `Effect.validate`, which fails with `NonEmptyArray<E>`.
|
|
658
658
|
5. **`Effect.makeSemaphore` / `Effect.makeLatch` no longer exist** — they moved to their own modules: `Semaphore.make(n)`, `Latch.make(open?)`, both importable from `'effect'`.
|
|
@@ -28,7 +28,7 @@ Reference this for:
|
|
|
28
28
|
- Declarative, not imperative
|
|
29
29
|
- Pipeline-friendly composition
|
|
30
30
|
|
|
31
|
-
## Schema-First Matching
|
|
31
|
+
## Schema-First Matching
|
|
32
32
|
|
|
33
33
|
For domain/wire models, prefer class variants combined with
|
|
34
34
|
`Schema.Union([...]).pipe(Schema.toTaggedUnion('kind'))`. Use `.match` for
|
|
@@ -206,7 +206,7 @@ const fileOperations = Effect.gen(function* () {
|
|
|
206
206
|
});
|
|
207
207
|
```
|
|
208
208
|
|
|
209
|
-
`fs.watch(directory)` reports direct-child changes by default; pass `{ recursive: true }`
|
|
209
|
+
`fs.watch(directory)` reports direct-child changes by default; pass `{ recursive: true }` for nested subdirectories. `file.seek(offset, 'start' | 'current')` accepts and returns `bigint`; negative resulting positions fail without moving the cursor. File byte counts use `ByteSize.ByteSize`. Use scoped File methods for handle operations.
|
|
210
210
|
|
|
211
211
|
**Streaming Files:**
|
|
212
212
|
|
|
@@ -782,10 +782,10 @@ declare const someOperation: Effect.Effect<string>;
|
|
|
782
782
|
|
|
783
783
|
// ✅ CORRECT - Type-safe CLI with full Effect integration
|
|
784
784
|
// Define arguments
|
|
785
|
-
const inputArg = Argument.
|
|
785
|
+
const inputArg = Argument.File('input');
|
|
786
786
|
|
|
787
787
|
// Define flags
|
|
788
|
-
const verboseFlag = Flag.
|
|
788
|
+
const verboseFlag = Flag.Boolean('verbose').pipe(Flag.withAlias('v'), Flag.withDefault(false));
|
|
789
789
|
|
|
790
790
|
// Define command
|
|
791
791
|
const command = CliCommand.make(
|
|
@@ -9,11 +9,11 @@ This skill covers the **contract layer**: the definitions that client and server
|
|
|
9
9
|
|
|
10
10
|
## Effect Source Reference
|
|
11
11
|
|
|
12
|
-
|
|
12
|
+
Schema encoding is selected by the transport's `codecFor`, rather
|
|
13
13
|
than being fixed to canonical JSON for every protocol. Preserve the schemas and
|
|
14
14
|
their decoding/encoding requirements in shared contracts; custom protocol
|
|
15
15
|
implementations must supply `codecFor` (see `effect-rpc-client` /
|
|
16
|
-
`effect-rpc-server`).
|
|
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
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.
|
|
@@ -627,5 +627,5 @@ it('GetUser exits round-trip', () => {
|
|
|
627
627
|
11. **Mutating in place.** `annotate`, `prefix`, `middleware`, `setSuccess`, etc. all return new `Rpc`/`RpcGroup` values; discarding the return value is a no-op.
|
|
628
628
|
12. **Renaming or re-prefixing rpcs after deployment.** `_tag` is the wire identity; old clients will send tags the server no longer knows. Treat tag changes like breaking schema changes.
|
|
629
629
|
13. **Passing a union to `Schema.Union` variadically.** v4 takes an array: `Schema.Union([OrderError, RateLimited])`, not `Schema.Union(OrderError, RateLimited)`.
|
|
630
|
-
14. **Declaring errors as plain `Schema.Struct`s.** Use `Schema.TaggedError` (or `Schema.Error` with a `Schema.tag` field) so errors are yieldable, `catchTag`-able, and carry a stable `_tag` on the wire.
|
|
630
|
+
14. **Declaring errors as plain `Schema.Struct`s.** Use `Schema.TaggedError` (or `Schema.Error` with a `Schema.tag` field) so errors are yieldable, `catchTag`-able, and carry a stable `_tag` on the wire.
|
|
631
631
|
15. **Expecting stack traces in remote defects.** The default `Schema.Defect()` strips stacks; opt in per rpc with `defect: Schema.Defect({ includeStack: true })`.
|
|
@@ -15,7 +15,7 @@ Key files:
|
|
|
15
15
|
|
|
16
16
|
- `packages/effect/src/unstable/rpc/RpcClient.ts` — `make`, `makeNoSerialization`, the `Protocol` service, `layerProtocolHttp`/`layerProtocolSocket`/`layerProtocolWorker` (+ `makeProtocol*`), `CurrentHeaders`, `withHeaders`, `ConnectionHooks`
|
|
17
17
|
- `packages/effect/src/unstable/rpc/RpcClientError.ts` — `RpcClientError` and `RpcClientDefect`
|
|
18
|
-
- `packages/effect/src/unstable/rpc/RpcSerialization.ts` —
|
|
18
|
+
- `packages/effect/src/unstable/rpc/RpcSerialization.ts` — JSON, NDJSON, JSON-RPC, SchemaBinary codecs, their layers, the `Parser` interface and `includesFraming`
|
|
19
19
|
- `packages/effect/src/unstable/rpc/RpcTest.ts` — `makeClient` in-process test client
|
|
20
20
|
- `packages/effect/src/unstable/rpc/RpcWorker.ts` — `InitialMessage`, `layerInitialMessage`, `initialMessage`
|
|
21
21
|
- `packages/effect/src/unstable/rpc/RpcMessage.ts` — wire vocabulary: `Request`, `Ack`, `Interrupt`, `Chunk`, `Exit`, `Defect`, `Ping`/`Pong`, `ClientProtocolError`, `RequestId`
|
|
@@ -30,7 +30,7 @@ A client is assembled from three replaceable layers plus your shared `RpcGroup`
|
|
|
30
30
|
```
|
|
31
31
|
RpcClient.make(group) typed methods, spans, request ids
|
|
32
32
|
requires: Protocol ← RpcClient.layerProtocolHttp / Socket / Worker
|
|
33
|
-
requires: RpcSerialization ←
|
|
33
|
+
requires: RpcSerialization ← JSON / NDJSON / SchemaBinary / JSON-RPC layer
|
|
34
34
|
requires: transport service ← HttpClient | Socket.Socket | WorkerPlatform + Spawner
|
|
35
35
|
```
|
|
36
36
|
|
|
@@ -263,14 +263,14 @@ const BrowserProtocolLive = RpcClient.layerProtocolSocket().pipe(
|
|
|
263
263
|
);
|
|
264
264
|
```
|
|
265
265
|
|
|
266
|
-
`Socket.layerWebSocket(url, {
|
|
266
|
+
`Socket.layerWebSocket(url, { openTimeout?, protocols?, highWaterMark? })` requires a `WebSocketConstructor`; `layerWebSocketConstructorGlobal` uses `globalThis.WebSocket`. `NodeSocket.layerWebSocket` bundles the constructor. Close is a typed failure; reconnect policies reacquire the scoped reader.
|
|
267
267
|
|
|
268
268
|
### Raw TCP
|
|
269
269
|
|
|
270
270
|
```ts
|
|
271
271
|
const TcpProtocolLive = RpcClient.layerProtocolSocket().pipe(
|
|
272
272
|
Layer.provide(NodeSocket.layerNet({ port: 9000 })),
|
|
273
|
-
Layer.provide(RpcSerialization.
|
|
273
|
+
Layer.provide(RpcSerialization.layerSchemaBinary())
|
|
274
274
|
);
|
|
275
275
|
```
|
|
276
276
|
|
|
@@ -293,7 +293,7 @@ const TcpProtocolLive = RpcClient.layerProtocolSocket().pipe(
|
|
|
293
293
|
|
|
294
294
|
Build your own with `RpcClient.Protocol.make((writeResponse, clientIds) => Effect<Omit<Service, 'run'>>)` — it buffers server responses per client until that client's `run` loop is installed. You rarely need this; prefer `RpcTest` for in-memory wiring (section 11).
|
|
295
295
|
|
|
296
|
-
|
|
296
|
+
The returned service must include `codecFor`, normally forwarded
|
|
297
297
|
from the selected `RpcSerialization` service. It chooses the schema codec for
|
|
298
298
|
RPC payloads/results while the protocol handles envelopes. JSON-compatible
|
|
299
299
|
custom transports can use `codecFor: Schema.toCodecJson`. Preserve the schema's
|
|
@@ -310,26 +310,27 @@ full `RpcSerialization.CodecFor` contract.
|
|
|
310
310
|
|---|---|---|---|
|
|
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
|
-
| `RpcSerialization.layerMsgPack` | `application/msgpack` | yes (msgpack frames) | binary framing with JSON-compatible schema codecs; msgpackr `useRecords: true` |
|
|
314
313
|
| `RpcSerialization.layerSchemaBinary(options?)` | `application/vnd.effect.rpc+schema-binary` | yes | schema-derived binary framing and payload codecs; pair on both peers |
|
|
315
314
|
|
|
316
315
|
`layerSchemaBinary({ maxFrameSize?, fingerprintPayloads? })` defaults to a
|
|
317
316
|
16 MiB frame limit and no payload fingerprint. Envelopes are fingerprinted and
|
|
318
317
|
dictionary-enabled; payload fingerprints opt into strict layout agreement.
|
|
319
|
-
|
|
318
|
+
Workers use
|
|
320
319
|
`Schema.toCodecJson` with structured clone without a serialization layer.
|
|
321
320
|
| `RpcSerialization.layerJsonRpc({ contentType? })` | `application/json` | no | JSON-RPC 2.0 interop over HTTP/WebSocket; maps rpc tag ↔ `method`, supports batch arrays |
|
|
322
321
|
| `RpcSerialization.layerNdJsonRpc({ contentType? })` | `application/json-rpc` | yes (newline) | JSON-RPC 2.0 over sockets |
|
|
323
322
|
|
|
324
|
-
|
|
323
|
+
Choose serialization according to transport framing:
|
|
325
324
|
|
|
326
|
-
- **Raw TCP needs a framed codec** (
|
|
325
|
+
- **Raw TCP needs a framed codec** (NDJSON, SchemaBinary, or newline JSON-RPC) — plain JSON cannot find message boundaries in a byte stream.
|
|
327
326
|
- **WebSocket works with any codec** including plain `json`, because the ws transport frames messages itself.
|
|
328
327
|
- **HTTP + unframed (`json`/`jsonRpc`)**: the client buffers the whole response and expects a JSON array of response messages — streaming rpcs only flush when the response ends.
|
|
329
|
-
- **HTTP + framed
|
|
330
|
-
- **Client and server must use the same codec.**
|
|
328
|
+
- **HTTP + framed serialization**: the client incrementally parses the response body — streaming rpc chunks arrive as the server emits them, over a single POST.
|
|
329
|
+
- **Client and server must use the same codec.** SchemaBinary and JSON are distinct wire contracts.
|
|
331
330
|
|
|
332
|
-
|
|
331
|
+
Use `layerSchemaBinary()` for schema-derived binary transport, and coordinate
|
|
332
|
+
both peers when changing formats. Do not treat a serialization change as a
|
|
333
|
+
transparent migration of persisted data.
|
|
333
334
|
|
|
334
335
|
---
|
|
335
336
|
|
|
@@ -666,7 +667,7 @@ const WorkerClientLive = UsersClient.layer.pipe(
|
|
|
666
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`.
|
|
667
668
|
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`.
|
|
668
669
|
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.
|
|
669
|
-
4. **Wrong codec/transport pairing.** Raw TCP
|
|
670
|
+
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.
|
|
670
671
|
5. **Mismatched client/server serialization.** Both sides share one `RpcSerialization` choice; there is no negotiation. Garbled `RpcClientDefect: Error decoding ...` errors usually mean the codecs differ.
|
|
671
672
|
6. **Reading mixed-case header names server-side.** `Headers.fromInput` lowercases keys: send `{ userId: '123' }`, read `headers.userid`. Same for `withHeaders` and middleware `Headers.set`.
|
|
672
673
|
7. **Expecting `discard: true` to be error-free.** It only removes the rpc's *declared* error; `RpcClientError` and middleware errors remain, and over HTTP the POST round-trip is still awaited.
|
|
@@ -18,7 +18,7 @@ Key files:
|
|
|
18
18
|
- `packages/effect/src/unstable/rpc/RpcServer.ts` — `make`, `layer`, `layerHttp`, every `layerProtocol*` and `toHttpEffect*`
|
|
19
19
|
- `packages/effect/src/unstable/rpc/RpcClient.ts` — `make`, `Protocol`, every `layerProtocol*`, `withHeaders`, `CurrentHeaders`, `ConnectionHooks`
|
|
20
20
|
- `packages/effect/src/unstable/rpc/RpcMiddleware.ts` — `Service` constructor, `layerClient`
|
|
21
|
-
- `packages/effect/src/unstable/rpc/RpcSerialization.ts` —
|
|
21
|
+
- `packages/effect/src/unstable/rpc/RpcSerialization.ts` — JSON/NDJSON/JSON-RPC/SchemaBinary codecs and layers
|
|
22
22
|
- `packages/effect/src/unstable/rpc/RpcTest.ts` — in-process test client
|
|
23
23
|
- `packages/effect/src/unstable/rpc/RpcWorker.ts` — `InitialMessage` for worker transports
|
|
24
24
|
- `packages/effect/src/unstable/rpc/RpcSchema.ts` — `Stream` schema marker, `ClientAbort` cause annotation
|
|
@@ -99,7 +99,7 @@ import { BunClusterHttp, BunClusterSocket } from '@effect/platform-bun';
|
|
|
99
99
|
## Architecture at a Glance
|
|
100
100
|
|
|
101
101
|
```
|
|
102
|
-
wire format (json | ndjson |
|
|
102
|
+
wire format (json | ndjson | schema-binary | jsonRpc | ndJsonRpc)
|
|
103
103
|
│
|
|
104
104
|
┌──────────────┐ Protocol │ Protocol ┌──────────────┐
|
|
105
105
|
│ RpcClient │ ───────────────► │ ◄───────────────── │ RpcServer │
|
|
@@ -794,21 +794,24 @@ The choice of serialization is load-bearing because of *framing*. Some transport
|
|
|
794
794
|
| `RpcSerialization.layerNdjson` | `application/ndjson` | yes (newline) | `layerProtocolWebsocket`, sockets, http+stream | Newline-delimited JSON; one of several streaming formats |
|
|
795
795
|
| `RpcSerialization.layerJsonRpc()` | `application/json` (configurable) | no | JSON-RPC 2.0 interop | Maps `_tag` to `method`; preserves batched arrays |
|
|
796
796
|
| `RpcSerialization.layerNdJsonRpc()` | `application/json-rpc` (configurable) | yes (newline) | JSON-RPC 2.0 over sockets | |
|
|
797
|
-
| `RpcSerialization.layerMsgPack` | `application/msgpack` | yes (msgpack frames) | binary transports | JSON-compatible schema codecs; uses `useRecords: true` |
|
|
798
797
|
| `RpcSerialization.layerSchemaBinary(options?)` | `application/vnd.effect.rpc+schema-binary` | yes | binary transports, framed HTTP | schema-aware binary payloads and envelopes |
|
|
799
798
|
|
|
800
|
-
|
|
799
|
+
Serializations and both protocol services require `codecFor`.
|
|
801
800
|
Custom protocols forward it from the serialization; custom JSON-compatible
|
|
802
801
|
protocols can use `Schema.toCodecJson`. Cluster network codecs now follow the
|
|
803
802
|
transport while persistent message storage remains JSON. See `effect-rpc-server`
|
|
804
803
|
for the complete contract, binary options, and compatibility constraints.
|
|
805
804
|
|
|
806
|
-
|
|
805
|
+
SchemaBinary is the default cluster network serialization. Select NDJSON explicitly
|
|
806
|
+
for JSON transport. Event-log persistence and remote messages also use SchemaBinary;
|
|
807
|
+
coordinate persisted-data and peer format changes. Cluster message storage uses
|
|
808
|
+
its own JSON codecs: when a reply cannot be stored, waiting callers receive the
|
|
809
|
+
same defect fallback that storage records.
|
|
807
810
|
|
|
808
811
|
Picking the wrong one is a real bug:
|
|
809
812
|
|
|
810
813
|
- `layerJson` over a websocket → no framing → the first chunk past the first message is misinterpreted
|
|
811
|
-
-
|
|
814
|
+
- SchemaBinary against a JSON-only HTTP client → incompatible responses
|
|
812
815
|
- `layerNdjson` against `layerProtocolHttp` → streams incrementally through a bounded response queue (default 16), without RPC acks
|
|
813
816
|
|
|
814
817
|
## Testing — `RpcTest.makeClient`
|
|
@@ -1232,7 +1235,7 @@ The Node and Bun platform packages ship opinionated all-in-one layers that wire
|
|
|
1232
1235
|
import { NodeClusterSocket } from '@effect/platform-node';
|
|
1233
1236
|
|
|
1234
1237
|
const ClusterLayer = NodeClusterSocket.layer({
|
|
1235
|
-
serialization: '
|
|
1238
|
+
serialization: 'binary', // or 'ndjson'; default 'binary'
|
|
1236
1239
|
clientOnly: false, // true → don't bind a server port
|
|
1237
1240
|
storage: 'sql', // 'sql' | 'memory' | 'byo'; default 'sql'
|
|
1238
1241
|
runnerHealth: 'ping', // 'ping' | 'k8s'; default 'ping'
|
|
@@ -1361,9 +1364,9 @@ The generated **RPC** payload wraps the original payload as `{ entityId: string,
|
|
|
1361
1364
|
|
|
1362
1365
|
### Encrypted Event-Log Compatibility
|
|
1363
1366
|
|
|
1364
|
-
|
|
1367
|
+
`EventLogEncryption.encrypt` returns `{ iv, encryptedEntry }` for every input entry, and `EventLogMessage.WriteEntries.encryptedEntries` carries `{ entryId, iv, encryptedEntry }` values. Each entry uses a fresh AES-GCM IV. Encrypted replication peers must agree on the wire format.
|
|
1365
1368
|
|
|
1366
|
-
|
|
1369
|
+
`EventJournal.withRemoteUncommited` (spelling intentional) receives a
|
|
1367
1370
|
non-empty readonly entry array in its callback and returns `Effect<Option<A>, ...>`.
|
|
1368
1371
|
No pending entries means `None` and the callback is not invoked; `Some(result)`
|
|
1369
1372
|
means it ran. Update adapters/tests that assumed every flush calls the writer.
|
|
@@ -1397,7 +1400,7 @@ To namespace the generated rpcs, pass `prefix` as the **second** argument: `Work
|
|
|
1397
1400
|
|
|
1398
1401
|
These proxies are how you give a frontend or an external system a typed RPC/HTTP surface that drives durable workflows, without leaking workflow-engine internals.
|
|
1399
1402
|
|
|
1400
|
-
|
|
1403
|
+
Workflow discard endpoints return a `Schema.String` execution ID
|
|
1401
1404
|
instead of `void`, for both RPC and HTTP. Call the generated `<Name>Discard` RPC
|
|
1402
1405
|
normally to receive it; passing the RPC client's `{ discard: true }` option
|
|
1403
1406
|
discards even that ID. Entity discard endpoints keep their separate contract.
|
|
@@ -1622,7 +1625,7 @@ const usersByName = Effect.gen(function*() {
|
|
|
1622
1625
|
3. **Treating `Rpc.fork` and `Rpc.uninterruptible` like options.** They are wrappers — apply with `.pipe(Rpc.fork)` on the handler's return value.
|
|
1623
1626
|
4. **Forgetting `primaryKey` on entity rpcs that need dedup.** Without it, retried sends are *not* deduplicated; this is critical for clustered handlers that should be idempotent.
|
|
1624
1627
|
5. **Using `JSON.parse`/`JSON.stringify` on rpc payloads.** Schemas already round-trip; if you need a JSON string boundary, use `Schema.fromJsonString(...)`.
|
|
1625
|
-
6. **Picking
|
|
1628
|
+
6. **Picking unframed JSON for raw TCP or incremental HTTP streaming.** Use `layerNdjson` or `layerSchemaBinary()`.
|
|
1626
1629
|
7. **Forgetting `Layer.provide(AuthClient)` on a `requiredForClient: true` middleware.** Compile error, but a confusing one if you don't know to look.
|
|
1627
1630
|
8. **Using `WorkflowEngine.layerMemory` in production.** It is testing-only; use `ClusterWorkflowEngine.layer` plus a real cluster bundle.
|
|
1628
1631
|
9. **Forgetting `ShardingConfig` when using `Entity.makeTestClient`.** `TestRunner.layer` provides one; `makeTestClient` does not.
|
|
@@ -1649,5 +1652,5 @@ const usersByName = Effect.gen(function*() {
|
|
|
1649
1652
|
- Use `RpcTest.makeClient` for handler tests and `Entity.makeTestClient` for entity tests; reach for `TestRunner.layer` for full-cluster integration tests.
|
|
1650
1653
|
- For production cluster, use `NodeClusterSocket.layer` / `NodeClusterHttp.layer` (or the Bun equivalents) unless you specifically need to assemble layers manually.
|
|
1651
1654
|
- Size `maxResidentEntities` and `unprocessedMessageBatchSize` deliberately for the runner's memory and storage throughput.
|
|
1652
|
-
- Match
|
|
1655
|
+
- Match serialization to framing: unframed JSON for whole messages; NDJSON or SchemaBinary for byte streams and incremental responses.
|
|
1653
1656
|
- Pattern-match on `client.GetUser(...).pipe(Effect.catchTag('UserNotFound', ...), Effect.catchFilter(...))` for typed recovery; reserve broad `Effect.catch` for an explicit boundary recovery policy.
|
|
@@ -17,7 +17,7 @@ Key files:
|
|
|
17
17
|
- `packages/effect/src/unstable/rpc/RpcGroup.ts` — `toLayer`, `toLayerHandler`, `toHandlers`, `accessHandler`, `of`, handler type derivation
|
|
18
18
|
- `packages/effect/src/unstable/rpc/Rpc.ts` — `ServerClient`, `Handler`, `ToHandlerFn`, `ResultFrom`, `fork`, `uninterruptible`, `ServicesServer`
|
|
19
19
|
- `packages/effect/src/unstable/rpc/RpcMiddleware.ts` — `Service` constructor, server middleware function shape, `layerClient`
|
|
20
|
-
- `packages/effect/src/unstable/rpc/RpcSerialization.ts` —
|
|
20
|
+
- `packages/effect/src/unstable/rpc/RpcSerialization.ts` — JSON, NDJSON, JSON-RPC, and SchemaBinary parsers and layers
|
|
21
21
|
- `packages/effect/src/unstable/rpc/RpcMessage.ts` — the wire vocabulary (`Request`, `Ack`, `Interrupt`, `Eof`, `Chunk`, `Exit`, `Defect`, `ClientEnd`)
|
|
22
22
|
- `packages/effect/src/unstable/rpc/RpcWorker.ts` — `InitialMessage` for worker transports
|
|
23
23
|
- `packages/effect/src/unstable/rpc/RpcTest.ts` — in-process test client
|
|
@@ -293,7 +293,7 @@ const TcpServer = RpcServer.layer(UserRpcs).pipe(
|
|
|
293
293
|
Layer.provide(UsersLive),
|
|
294
294
|
Layer.provide(RpcServer.layerProtocolSocketServer),
|
|
295
295
|
Layer.provide(NodeSocketServer.layer({ port: 9000 })),
|
|
296
|
-
Layer.provide(RpcSerialization.
|
|
296
|
+
Layer.provide(RpcSerialization.layerSchemaBinary()) // must be a framed format (§4)
|
|
297
297
|
);
|
|
298
298
|
```
|
|
299
299
|
|
|
@@ -349,10 +349,9 @@ Provide exactly one `RpcSerialization` layer. The load-bearing property is `incl
|
|
|
349
349
|
| `RpcSerialization.layerNdjson` | `application/ndjson` | yes | newline-delimited JSON |
|
|
350
350
|
| `RpcSerialization.layerJsonRpc({ contentType? })` | `application/json` | no | JSON-RPC 2.0 interop |
|
|
351
351
|
| `RpcSerialization.layerNdJsonRpc({ contentType? })` | `application/json-rpc` | yes | JSON-RPC 2.0, newline-framed |
|
|
352
|
-
| `RpcSerialization.layerMsgPack` | `application/msgpack` | yes | msgpackr `useRecords: true`; schema payloads still use JSON codecs |
|
|
353
352
|
| `RpcSerialization.layerSchemaBinary(options?)` | `application/vnd.effect.rpc+schema-binary` | yes | schema-derived binary payloads and envelopes |
|
|
354
353
|
|
|
355
|
-
### Schema-aware serialization
|
|
354
|
+
### Schema-aware serialization
|
|
356
355
|
|
|
357
356
|
`RpcSerialization.RpcSerialization`, `RpcClient.Protocol`, and
|
|
358
357
|
`RpcServer.Protocol` now require `codecFor: RpcSerialization.CodecFor`:
|
|
@@ -364,9 +363,9 @@ type CodecFor = <S extends Schema.Top>(schema: S) =>
|
|
|
364
363
|
|
|
365
364
|
Forward the selected serialization's `codecFor` when implementing a protocol.
|
|
366
365
|
This selects codecs for payloads, successes, errors, defects, and stream elements;
|
|
367
|
-
envelope framing remains the serialization's responsibility.
|
|
368
|
-
NDJSON, JSON-RPC
|
|
369
|
-
|
|
366
|
+
envelope framing remains the serialization's responsibility. JSON,
|
|
367
|
+
NDJSON, and JSON-RPC use JSON-compatible schema codecs.
|
|
368
|
+
Workers supply `Schema.toCodecJson` themselves over structured clone and
|
|
370
369
|
still need no serialization layer. Cluster network traffic follows the protocol
|
|
371
370
|
codec; cluster persistence continues to use JSON.
|
|
372
371
|
|
|
@@ -374,17 +373,16 @@ codec; cluster persistence continues to use JSON.
|
|
|
374
373
|
both peers. The default maximum frame size is 16 MiB. Envelopes use fingerprints
|
|
375
374
|
and a connection-local string dictionary. Payload fingerprints default to `false`
|
|
376
375
|
to allow compatible schema evolution; enable them for strict layout agreement.
|
|
377
|
-
This is an Effect RPC format
|
|
376
|
+
This is an Effect RPC format; use JSON-RPC layers for JSON-RPC interoperability.
|
|
378
377
|
See `effect-schema-composition` for binary layout/ownership constraints.
|
|
379
378
|
|
|
380
379
|
Rules, verified against the protocol implementations and the e2e matrix:
|
|
381
380
|
|
|
382
|
-
- **Raw TCP sockets need a framed format** (
|
|
383
|
-
- **WebSocket frames messages itself**, so
|
|
384
|
-
- **HTTP POST works with both**, with different response behavior:
|
|
381
|
+
- **Raw TCP sockets need a framed format** (NDJSON, newline JSON-RPC, or SchemaBinary). Plain JSON cannot split the byte stream.
|
|
382
|
+
- **WebSocket frames messages itself**, so it also supports unframed serialization.
|
|
383
|
+
- **HTTP POST works with both**, with different response behavior: an **unframed** format buffers responses until all requests finish; a **framed** format emits chunks incrementally. Use `layerNdjson` or `layerSchemaBinary()` for streaming RPCs.
|
|
385
384
|
- The response `content-type` is the serialization's `contentType`.
|
|
386
385
|
- `RpcSerialization.layerJsonRpc()` / `layerNdJsonRpc()` speak JSON-RPC 2.0: rpc tags map to `method`, batched arrays are preserved, and internal signals travel as `@effect/rpc/Ack`-style methods. Use for interop with non-Effect JSON-RPC clients.
|
|
387
|
-
- `RpcSerialization.makeMsgPack(options)` customizes msgpackr (`useRecords`, `useFloat32`, ...); wrap with `Layer.succeed(RpcSerialization.RpcSerialization)(RpcSerialization.makeMsgPack({ ... }))`.
|
|
388
386
|
|
|
389
387
|
Client and server must use the **same** serialization.
|
|
390
388
|
|
|
@@ -778,7 +776,7 @@ const ServerLayer = RpcServer.layer(StoreRpcs, { concurrency: 1 }).pipe(
|
|
|
778
776
|
1. **Importing from `@effect/rpc`.** v3 habit; the package does not exist in v4. Everything is `effect/unstable/rpc` (and platform layers come from `@effect/platform-node` / `-bun` / `-browser`).
|
|
779
777
|
2. **Forgetting `protocol: 'http'` on `layerHttp`.** The default is `'websocket'` — your `POST /rpc` curl returns 404 and only a `GET` upgrade route exists. Also note `layerHttp` takes `{ group, path, ... }` as one options bag, while `layer(group, options)` takes the group positionally.
|
|
780
778
|
3. **Providing handlers/middleware but no `Protocol` or `RpcSerialization`.** `RpcServer.layer` requires all of: handler layer(s), middleware implementation layers, a `layerProtocol*`, and (for non-worker protocols) a `RpcSerialization.layer*`. Missing ones surface as unresolved layer requirements.
|
|
781
|
-
4. **`layerJson` on a raw TCP socket server.**
|
|
779
|
+
4. **`layerJson` on a raw TCP socket server.** Sockets need `layerNdjson`, `layerNdJsonRpc()`, or `layerSchemaBinary()` for framing. WebSocket supplies its own framing and supports `layerJson`.
|
|
782
780
|
5. **Streaming rpcs over `layerProtocolHttp` + `layerJson` and wondering why chunks arrive all at once.** Unframed HTTP buffers the whole response until every request in the call finishes. Framed HTTP streams incrementally with a bounded response queue (`streamBufferSize`, default 16). HTTP has no RPC acknowledgment protocol, but the bounded queue still backpressures producers; full-duplex transports additionally support RPC acks.
|
|
783
781
|
6. **Leaving `disableFatalDefects: false` in production.** One handler `die` then nukes every in-flight request on that connection with a connection-level defect. Set `true` to confine defects to the failing request.
|
|
784
782
|
7. **Assuming `concurrency` is per-client.** It is one semaphore per server instance shared by all clients. Use `Rpc.fork` to exempt cheap read handlers instead of raising the global limit.
|
|
@@ -11,6 +11,13 @@ Verify APIs against `~/.local/share/opencode/repos/github.com/Effect-TS/effect@m
|
|
|
11
11
|
|
|
12
12
|
## Semantics
|
|
13
13
|
|
|
14
|
+
`Effect.timeoutOrElse` finishes interrupting the source before evaluating the
|
|
15
|
+
fallback. The fallback runs in the caller fiber with its interruptibility and
|
|
16
|
+
supervision. `Effect.repeatOrElse` receives the previous step's `Schedule.Metadata`
|
|
17
|
+
in its fallback. Custom `RateLimiterStore.tokenBucket` implementations return
|
|
18
|
+
`[remaining, elapsedMillis]` atomically; fabricating zero elapsed time produces
|
|
19
|
+
incorrect delay, retryAfter, and resetAfter values.
|
|
20
|
+
|
|
14
21
|
- `Effect.retry` reruns typed failures. It does not retry defects or interruption.
|
|
15
22
|
- `Effect.repeat` reruns successes. A typed failure stops repetition unless the pass handles it first.
|
|
16
23
|
- The source effect runs once before the schedule is stepped.
|
|
@@ -465,7 +465,7 @@ function split(separator: string) {
|
|
|
465
465
|
|
|
466
466
|
## Custom Transformations
|
|
467
467
|
|
|
468
|
-
### Schema-derived binary boundaries
|
|
468
|
+
### Schema-derived binary boundaries
|
|
469
469
|
|
|
470
470
|
Use `SchemaBinary.toCodec(schema)` from `effect/unstable/encoding` for a compact
|
|
471
471
|
`Uint8Array` representation. It derives the wire layout from the schema's
|
|
@@ -535,9 +535,9 @@ const BooleanFromString = Schema.Literals(['on', 'off']).pipe(
|
|
|
535
535
|
);
|
|
536
536
|
```
|
|
537
537
|
|
|
538
|
-
### SchemaTransformation.
|
|
538
|
+
### SchemaTransformation.transformEffect — Transformations That Can Fail
|
|
539
539
|
|
|
540
|
-
Use `SchemaTransformation.
|
|
540
|
+
Use `SchemaTransformation.transformEffect` when transformation might fail:
|
|
541
541
|
|
|
542
542
|
```typescript
|
|
543
543
|
import {
|
|
@@ -551,7 +551,7 @@ import {
|
|
|
551
551
|
|
|
552
552
|
const NumberFromString = Schema.String.pipe(
|
|
553
553
|
Schema.decodeTo(Schema.Number, {
|
|
554
|
-
decode: SchemaGetter.
|
|
554
|
+
decode: SchemaGetter.transformEffect((s) =>
|
|
555
555
|
Option.match(Number.parse(s), {
|
|
556
556
|
onNone: () =>
|
|
557
557
|
Effect.fail(
|
|
@@ -633,6 +633,14 @@ export const beGreaterThan =
|
|
|
633
633
|
|
|
634
634
|
### Constructor vs Boundary Decoder
|
|
635
635
|
|
|
636
|
+
`make`, `makeOption`, and `makeEffect` return an existing class
|
|
637
|
+
instance unchanged. Use `new` when a distinct instance is required. Pass parsing
|
|
638
|
+
options at the adapter call, not in annotations; options apply operation-wide.
|
|
639
|
+
Products support parsing concurrency but union alternatives stay sequential.
|
|
640
|
+
Retain extra object fields with Record/StructWithRest; excess-property modes are
|
|
641
|
+
`ignore` or `error`. See `effect-schema-v4` for inherited-field, template-literal,
|
|
642
|
+
JSON Schema, Getter composition, and optional JIT/AOT contracts.
|
|
643
|
+
|
|
636
644
|
Keep decoded shapes schema-first with `Schema.Class`. Choose construction and decoding APIs by input trust and failure semantics:
|
|
637
645
|
|
|
638
646
|
| API | Use Case | Failure |
|
|
@@ -20,7 +20,7 @@ Reference this for:
|
|
|
20
20
|
|
|
21
21
|
## 1. Key Renames (find-and-replace safe)
|
|
22
22
|
|
|
23
|
-
###
|
|
23
|
+
### Type model
|
|
24
24
|
|
|
25
25
|
`Schema.Schema<T>` describes only the decoded type. Preserve wire types and
|
|
26
26
|
services with `Schema.Codec<T, E, RD, RE>`: decoded Type, Encoded representation,
|
|
@@ -35,13 +35,13 @@ schema helpers so concrete schema operations are retained.
|
|
|
35
35
|
| `typeSchema(schema)` | `toType(schema)` | |
|
|
36
36
|
| `asSchema(schema)` | `revealCodec(schema)` | |
|
|
37
37
|
| `equivalence()` | `toEquivalence()` | |
|
|
38
|
-
| `arbitrary()` | `
|
|
38
|
+
| `arbitrary()` | `Arbitrary.schema(schema)` | Native `effect/unstable/arbitrary`; fast-check bridge removed |
|
|
39
39
|
| `pretty()` | `toFormatter()` | |
|
|
40
|
-
| `parseJson()` | `fromJsonString(Schema.Unknown)` |
|
|
40
|
+
| `parseJson()` | `fromJsonString(Schema.Unknown)` | Public unknown-JSON codec |
|
|
41
41
|
| `parseJson(schema)` | `fromJsonString(schema)` | With-schema version |
|
|
42
|
-
| `TaggedErrorClass` | `TaggedError` |
|
|
43
|
-
| `ErrorClass` | `Error` |
|
|
44
|
-
| `Error` (instance schema) | `ErrorInstance` |
|
|
42
|
+
| `TaggedErrorClass` | `TaggedError` | Tagged error constructor |
|
|
43
|
+
| `ErrorClass` | `Error` | Error constructor |
|
|
44
|
+
| `Error` (instance schema) | `ErrorInstance` | Error instance schema |
|
|
45
45
|
| `BigIntFromSelf` | `BigInt` | |
|
|
46
46
|
| `SymbolFromSelf` | `Symbol` | |
|
|
47
47
|
| `URLFromSelf` | `URL` | |
|
|
@@ -66,7 +66,7 @@ schema helpers so concrete schema operations are retained.
|
|
|
66
66
|
| `standardSchemaV1` | `toStandardSchemaV1` | |
|
|
67
67
|
| `nonEmptyString` | `isNonEmpty()` | Now used with `.check()` |
|
|
68
68
|
| `disableValidation` | `disableChecks` | In `MakeOptions` for Class constructors |
|
|
69
|
-
| standalone `SchemaError` module | `Schema.SchemaError` |
|
|
69
|
+
| standalone `SchemaError` module | `Schema.SchemaError` | Use `Schema.isSchemaError` to narrow |
|
|
70
70
|
|
|
71
71
|
### Parser/Codec Function Renames
|
|
72
72
|
|
|
@@ -161,7 +161,7 @@ const isNegative = Schema.isLessThan(0);
|
|
|
161
161
|
const isNonPositive = Schema.isLessThanOrEqualTo(0);
|
|
162
162
|
```
|
|
163
163
|
|
|
164
|
-
For the
|
|
164
|
+
For the non-negative safe-integer domain, use `Schema.Natural`.
|
|
165
165
|
|
|
166
166
|
### Custom Filters
|
|
167
167
|
|
|
@@ -252,7 +252,7 @@ import {
|
|
|
252
252
|
|
|
253
253
|
const NumberFromString = Schema.String.pipe(
|
|
254
254
|
Schema.decodeTo(Schema.Number, {
|
|
255
|
-
decode: SchemaGetter.
|
|
255
|
+
decode: SchemaGetter.transformEffect((s) =>
|
|
256
256
|
Option.match(Number.parse(s), {
|
|
257
257
|
onNone: () =>
|
|
258
258
|
Effect.fail(
|
|
@@ -386,8 +386,8 @@ const fallback = SchemaGetter.withDefault(Effect.succeed('viewer'));
|
|
|
386
386
|
- `Schema.resolveInto` was renamed to `Schema.resolveAnnotations`.
|
|
387
387
|
- `Schema.resolveAnnotationsKey(schema)` returns key-level annotations.
|
|
388
388
|
- `Schema.annotateEncoded({...})` annotates the encoded side of a transformed schema; use `Schema.annotate({...})` for the decoded Type side.
|
|
389
|
-
- Schemas are directly extendable as classes
|
|
390
|
-
- `
|
|
389
|
+
- Schemas are directly extendable as classes.
|
|
390
|
+
- Derive native generators with `Arbitrary.schema(schema)` from `effect/unstable/arbitrary`. See `effect-testing` for sampling, bounded generation, shrinking, and replay.
|
|
391
391
|
- New built-in schemas:
|
|
392
392
|
- `Schema.DateFromString`
|
|
393
393
|
- `Schema.BigIntFromString`
|
|
@@ -404,7 +404,7 @@ const fallback = SchemaGetter.withDefault(Effect.succeed('viewer'));
|
|
|
404
404
|
|
|
405
405
|
```ts
|
|
406
406
|
import { Effect, Schema } from 'effect';
|
|
407
|
-
import
|
|
407
|
+
import { Arbitrary } from 'effect/unstable/arbitrary';
|
|
408
408
|
|
|
409
409
|
class UserName extends Schema.NonEmptyString {
|
|
410
410
|
static readonly decodeUnknownSync = Schema.decodeUnknownSync(this);
|
|
@@ -428,8 +428,7 @@ const duration = Schema.DurationFromString;
|
|
|
428
428
|
|
|
429
429
|
const parsed = Schema.String.makeEffect('alice');
|
|
430
430
|
|
|
431
|
-
const
|
|
432
|
-
const nameArbitrary = makeNameArbitrary(FastCheck);
|
|
431
|
+
const nameArbitrary = Arbitrary.schema(Schema.NonEmptyString);
|
|
433
432
|
```
|
|
434
433
|
|
|
435
434
|
### Graph Schemas
|
|
@@ -493,12 +492,12 @@ The input is `{ ast, occurrences, identifier }`; return a name to extract that c
|
|
|
493
492
|
|
|
494
493
|
The standalone `SchemaError` root module was removed. Parser adapters such as `Schema.decodeUnknownEffect` fail with `Schema.SchemaError`, which contains the structured `issue`; narrow unknown failures with `Schema.isSchemaError`. By contrast, schema/class `makeEffect` and constructor defaults fail directly with `SchemaIssue.Issue`.
|
|
495
494
|
|
|
496
|
-
|
|
495
|
+
`SchemaError` skips stack-frame capture for lower construction cost.
|
|
497
496
|
Use its `issue` and formatter/message for validation diagnostics; a captured
|
|
498
497
|
parser-error stack is not a diagnostic contract. Synchronous parser optimizations
|
|
499
498
|
do not change the choice between constructors, sync decoders, and Effect decoders.
|
|
500
499
|
|
|
501
|
-
### Partial tagged-union matching
|
|
500
|
+
### Partial tagged-union matching
|
|
502
501
|
|
|
503
502
|
`Schema.TaggedUnion(...)` and `Schema.Union([...]).pipe(Schema.toTaggedUnion(tag))`
|
|
504
503
|
now expose `matchOrElse(value, cases, fallback)` and its curried
|
|
@@ -508,7 +507,7 @@ one behavior. The `toTaggedUnion` helper narrows the fallback to omitted variant
|
|
|
508
507
|
the direct `TaggedUnion` overload types its fallback as the full union.
|
|
509
508
|
Neither matcher decodes unknown input. See `effect-pattern-matching` for a checked example.
|
|
510
509
|
|
|
511
|
-
### JSON Schema import, conversion, and Standard Schema
|
|
510
|
+
### JSON Schema import, conversion, and Standard Schema
|
|
512
511
|
|
|
513
512
|
- `SchemaRepresentation.fromJsonSchemaDocument` rejects unsupported references,
|
|
514
513
|
validation keywords, object/array `const` or `enum` values, and intersections
|
|
@@ -527,6 +526,50 @@ Neither matcher decodes unknown input. See `effect-pattern-matching` for a check
|
|
|
527
526
|
- Binary encoding is available from `effect/unstable/encoding` as `SchemaBinary`.
|
|
528
527
|
See `effect-schema-composition` for codecs and `effect-stream` for framing.
|
|
529
528
|
|
|
529
|
+
## Parsing and compilation contracts
|
|
530
|
+
|
|
531
|
+
- Pass parse options to decoder, encoder, or constructor adapters. `parseOptions`
|
|
532
|
+
annotations no longer affect parsing, and operation options apply throughout
|
|
533
|
+
the complete parse. `propertyOrder` is removed; order presentation explicitly.
|
|
534
|
+
- `onExcessProperty` supports `"ignore"` and `"error"`. Model retained extra fields
|
|
535
|
+
with `Schema.Record` or `Schema.StructWithRest`, rather than unvalidated preserve.
|
|
536
|
+
- Parsing concurrency applies to product children (arrays, tuples, fields, record
|
|
537
|
+
entries), defaults to sequential, and applies independently at each nesting.
|
|
538
|
+
Union candidates remain sequential. Concurrent transformed record-key collisions
|
|
539
|
+
retain the value that finishes last.
|
|
540
|
+
- Declared fields may be inherited and are copied to own output properties;
|
|
541
|
+
dynamic record keys remain own-only and `__proto__` remains own-only. Enforce
|
|
542
|
+
ownership at the boundary when the protocol requires own declared fields.
|
|
543
|
+
- Class `make`, `makeOption`, and `makeEffect` preserve existing instances. Use
|
|
544
|
+
`new MyClass(fields)` for a distinct instance. Class equivalence now compares
|
|
545
|
+
declared fields, excluding unrelated runtime properties.
|
|
546
|
+
- `TemplateLiteral` rejects encoded/transformed parts, even in nested templates
|
|
547
|
+
or unions. Use `TemplateLiteralParser` for transformed tuple parts and provide
|
|
548
|
+
their decoding/encoding services. Its encoded projection validates the template.
|
|
549
|
+
- JSON Schema generation uses `onExcessProperty`, replacing its former
|
|
550
|
+
`additionalProperties` option; generated objects are open by default. Schema-valued
|
|
551
|
+
extra properties belong in Record/StructWithRest. `Enum` rejects non-finite
|
|
552
|
+
members; `isMultipleOf` rejects zero/non-finite divisors and normalizes negatives.
|
|
553
|
+
- Built-in revivers live in `SchemaRepresentation`; constructors are
|
|
554
|
+
`makeReviverDeclaration`, `makeReviverFilter`, and `makeReviverFilterGroup`.
|
|
555
|
+
`toEncoderXml` fails directly with `SchemaIssue.Issue`.
|
|
556
|
+
- For tooling, use `SchemaAST.AST` and named instance interfaces rather than
|
|
557
|
+
`SchemaAST.Base` or constructor prototype types. Union mode is
|
|
558
|
+
`ast.options?.mode` (default `anyOf`); migrate persisted representation documents.
|
|
559
|
+
`SchemaAST.Context.constructorDefault` holds an Effect directly, not a Link.
|
|
560
|
+
- JSON Schema imports support `{ not: {} }` and closed single-pattern records
|
|
561
|
+
with `patterns: "apply"`. Open patterned objects and references beneath a nested
|
|
562
|
+
`$id` are rejected; flatten references or explicitly choose to ignore constraints.
|
|
563
|
+
|
|
564
|
+
Experimental JIT/AOT compilation uses the existing `SchemaParser` APIs. Opt in
|
|
565
|
+
globally with `effect/unstable/schema/SchemaJITCompiler/enable` or selectively with
|
|
566
|
+
`SchemaJITCompiler.enable(ast)`. AOT's `SchemaAOTCompiler/Build` discovers direct
|
|
567
|
+
schema exports from explicit loaders and writes a self-installing module via
|
|
568
|
+
FileSystem/Path. Choose prepared operations explicitly: omitted operations use
|
|
569
|
+
the interpreter, and detailed effectful parsing remains the fallback. Keep the
|
|
570
|
+
interpreter as the default unless measured performance warrants compilation;
|
|
571
|
+
use AOT where dynamic function construction is unavailable.
|
|
572
|
+
|
|
530
573
|
## 8. New Modules
|
|
531
574
|
|
|
532
575
|
### SchemaTransformation
|
|
@@ -563,10 +606,15 @@ const DurationFromString = Schema.String.pipe(
|
|
|
563
606
|
|
|
564
607
|
### SchemaGetter
|
|
565
608
|
|
|
566
|
-
Single-direction transform primitives.
|
|
609
|
+
Single-direction transform primitives. `Getter<T, E, R>` is a tagged
|
|
610
|
+
union of synchronous, optional, and effectful transformations. Interpret one with
|
|
611
|
+
`SchemaGetter.run(getter, option)` to obtain `Effect<Option<T>, Issue, R>`.
|
|
612
|
+
Getter values expose only `pipe`; use standalone dual `map`, `compose`, and `run`.
|
|
613
|
+
Key exports:
|
|
567
614
|
|
|
568
615
|
- `transform(fn)` — pure map over present values
|
|
569
|
-
- `
|
|
616
|
+
- `transformEffect(fn)` — fallible map of a present value returning `Effect`
|
|
617
|
+
- `transformOptionalEffect(fn)` — effectful map that handles missing values too
|
|
570
618
|
- `transformOptional(fn)` — full `Option<E> → Option<T>` control (for optional field transforms)
|
|
571
619
|
- `passthrough()` — identity getter
|
|
572
620
|
- `withDefault(effect)` — provide a default `Effect` for missing values
|
|
@@ -574,6 +622,13 @@ Single-direction transform primitives. A `Getter<T, E, R>` is `Option<E> → Eff
|
|
|
574
622
|
- `checkEffect(fn)` — effectful validation
|
|
575
623
|
- `String()`, `Number()`, `Boolean()`, `BigInt()`, `Date()` — coercion getters
|
|
576
624
|
|
|
625
|
+
Use `transformOptionalEffect` instead of constructing a Getter directly.
|
|
626
|
+
Use `transformEffect` / `transformOptionalEffect` instead of `onSome` / `onNone`.
|
|
627
|
+
`SchemaGetter.forbiddenEncoding` is the encode getter for a decode-only codec.
|
|
628
|
+
For transformation pairs, use `SchemaTransformation.makeTransformation` and
|
|
629
|
+
the standalone dual `composeTransformation(first, second)`; transformations and
|
|
630
|
+
middleware support `.pipe`.
|
|
631
|
+
|
|
577
632
|
```ts
|
|
578
633
|
import { Schema, SchemaGetter } from 'effect';
|
|
579
634
|
|
|
@@ -680,7 +735,7 @@ class Cat extends Schema.TaggedClass<Cat>()('Cat', {
|
|
|
680
735
|
|
|
681
736
|
## 10. TaggedError
|
|
682
737
|
|
|
683
|
-
|
|
738
|
+
Define yieldable tagged errors with `Schema.TaggedError`:
|
|
684
739
|
|
|
685
740
|
```ts
|
|
686
741
|
import { Effect, Schema } from 'effect';
|