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
@@ -3,39 +3,39 @@ name: effect-rpc-cluster
3
3
  description: Build typed RPC endpoints and cluster-distributed entities, singletons, cron jobs, and durable workflows with Effect's RPC and Cluster modules (Rpc/RpcGroup/RpcServer/RpcClient, Entity/Sharding/Singleton, Node/Bun bundles). Use when building RPC services or distributed/clustered Effect systems.
4
4
  ---
5
5
 
6
- You are an Effect TypeScript expert specializing in `effect/unstable/rpc` and `effect/unstable/cluster`.
6
+ You are an Effect TypeScript expert specializing in `effect/rpc` and `effect/cluster`.
7
7
 
8
- These modules live under `effect/unstable/*`. There are no `@effect/rpc` or `@effect/cluster` packages in v4 — everything ships from the `effect` package. APIs may move between betas.
8
+ These modules live under `effect/*`. There are no `@effect/rpc` or `@effect/cluster` packages in v4 — everything ships from the `effect` package. They remain `@stability unstable` and may break in minor releases. Keep Effect-family packages on the same release.
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 — the shape of these modules changes more often than the website docs.
12
+ The Effect v4 source is at `~/.local/share/opencode/repos/github.com/Effect-TS/effect@main/`. Inspect the `effect@4.0.0` tag for this skill; main may be newer.
13
13
 
14
14
  Key files:
15
15
 
16
- - `packages/effect/src/unstable/rpc/Rpc.ts` — `Rpc.make`, custom constructors, `Wrapper`, `ServerClient`, `exitSchema`
17
- - `packages/effect/src/unstable/rpc/RpcGroup.ts` — group construction, handler wiring (`toLayer` / `toHandlers` / `toLayerHandler` / `accessHandler`), prefixing, omit/merge, annotations
18
- - `packages/effect/src/unstable/rpc/RpcServer.ts` — `make`, `layer`, `layerHttp`, every `layerProtocol*` and `toHttpEffect*`
19
- - `packages/effect/src/unstable/rpc/RpcClient.ts` — `make`, `Protocol`, every `layerProtocol*`, `withHeaders`, `CurrentHeaders`, `ConnectionHooks`
20
- - `packages/effect/src/unstable/rpc/RpcMiddleware.ts` — `Service` constructor, `layerClient`
21
- - `packages/effect/src/unstable/rpc/RpcSerialization.ts` — JSON/NDJSON/JSON-RPC/SchemaBinary codecs and layers
22
- - `packages/effect/src/unstable/rpc/RpcTest.ts` — in-process test client
23
- - `packages/effect/src/unstable/rpc/RpcWorker.ts` — `InitialMessage` for worker transports
24
- - `packages/effect/src/unstable/rpc/RpcSchema.ts` — `Stream` schema marker, `ClientAbort` cause annotation
25
- - `packages/effect/src/unstable/rpc/RpcClientError.ts` — client-side error union
26
- - `packages/effect/src/unstable/cluster/Entity.ts` — `Entity.make` / `fromRpcGroup`, handler envelopes, `Replier`, `CurrentAddress`, `keepAlive`, `makeTestClient`
27
- - `packages/effect/src/unstable/cluster/ClusterSchema.ts` — `Persisted`, `Uninterruptible`, `WithTransaction`, `ShardGroup`, `ClientTracingEnabled`, `Dynamic`
28
- - `packages/effect/src/unstable/cluster/ClusterError.ts` — `MailboxFull`, `AlreadyProcessingMessage`, `PersistenceError`, `EntityNotAssignedToRunner`, `MalformedMessage`, `RunnerUnavailable`, `RunnerNotRegistered`
29
- - `packages/effect/src/unstable/cluster/Sharding.ts` — the `Sharding` service surface
30
- - `packages/effect/src/unstable/cluster/ShardingConfig.ts` — config schema + env loader
31
- - `packages/effect/src/unstable/cluster/Singleton.ts` — singleton-per-cluster effects
32
- - `packages/effect/src/unstable/cluster/ClusterCron.ts` — cron-driven singletons
33
- - `packages/effect/src/unstable/cluster/SingleRunner.ts` — single-node sql-backed bundle
34
- - `packages/effect/src/unstable/cluster/TestRunner.ts` — in-memory testing bundle
35
- - `packages/effect/src/unstable/cluster/EntityProxy.ts` + `EntityProxyServer.ts` — entity ↔ RPC/HTTP bridge
36
- - `packages/effect/src/unstable/workflow/WorkflowProxy.ts` + `WorkflowProxyServer.ts` — workflow ↔ RPC/HTTP bridge
37
- - `packages/effect/src/unstable/cluster/ClusterWorkflowEngine.ts` — production workflow engine backed by sharding + storage
38
- - `packages/effect/src/unstable/reactivity/AtomRpc.ts` — reactive RPC client for Atom UIs (see also `effect-atom-rpc` skill)
16
+ - `packages/effect/src/rpc/Rpc.ts` — `Rpc.make`, custom constructors, `Wrapper`, `ServerClient`, `exitSchema`
17
+ - `packages/effect/src/rpc/RpcGroup.ts` — group construction, handler wiring (`toLayer` / `toHandlers` / `toLayerHandler` / `accessHandler`), prefixing, omit/merge, annotations
18
+ - `packages/effect/src/rpc/RpcServer.ts` — `make`, `layer`, `layerHttp`, every `layerProtocol*` and `toHttpEffect*`
19
+ - `packages/effect/src/rpc/RpcClient.ts` — `make`, `Protocol`, every `layerProtocol*`, `withHeaders`, `CurrentHeaders`, `ConnectionHooks`
20
+ - `packages/effect/src/rpc/RpcMiddleware.ts` — `Service` constructor, `layerClient`
21
+ - `packages/effect/src/rpc/RpcSerialization.ts` — JSON/NDJSON/JSON-RPC/SchemaBinary codecs and layers
22
+ - `packages/effect/src/rpc/RpcTest.ts` — in-process test client
23
+ - `packages/effect/src/rpc/RpcWorker.ts` — `InitialMessage` for worker transports
24
+ - `packages/effect/src/rpc/RpcSchema.ts` — `Stream` schema marker, `ClientAbort` cause annotation
25
+ - `packages/effect/src/rpc/RpcClientError.ts` — client-side error union
26
+ - `packages/effect/src/cluster/Entity.ts` — `Entity.make` / `fromRpcGroup`, handler envelopes, `Replier`, `CurrentAddress`, `keepAlive`, `makeTestClient`
27
+ - `packages/effect/src/cluster/ClusterSchema.ts` — `Persisted`, `Uninterruptible`, `WithTransaction`, `ShardGroup`, `ClientTracingEnabled`, `Dynamic`
28
+ - `packages/effect/src/cluster/ClusterError.ts` — `MailboxFull`, `AlreadyProcessingMessage`, `PersistenceError`, `EntityNotAssignedToRunner`, `MalformedMessage`, `RunnerUnavailable`, `RunnerNotRegistered`
29
+ - `packages/effect/src/cluster/Sharding.ts` — the `Sharding` service surface
30
+ - `packages/effect/src/cluster/ShardingConfig.ts` — config schema + env loader
31
+ - `packages/effect/src/cluster/Singleton.ts` — singleton-per-cluster effects
32
+ - `packages/effect/src/cluster/ClusterCron.ts` — cron-driven singletons
33
+ - `packages/effect/src/cluster/SingleRunner.ts` — single-node sql-backed bundle
34
+ - `packages/effect/src/cluster/TestRunner.ts` — in-memory testing bundle
35
+ - `packages/effect/src/cluster/EntityProxy.ts` + `EntityProxyServer.ts` — entity ↔ RPC/HTTP bridge
36
+ - `packages/effect/src/workflow/WorkflowProxy.ts` + `WorkflowProxyServer.ts` — workflow ↔ RPC/HTTP bridge
37
+ - `packages/effect/src/cluster/ClusterWorkflowEngine.ts` — production workflow engine backed by sharding + storage
38
+ - `packages/effect/src/reactivity/AtomRpc.ts` — reactive RPC client for Atom UIs (see also `effect-atom-rpc` skill)
39
39
  - `packages/platform/node/src/NodeClusterHttp.ts` / `NodeClusterSocket.ts` — Node "all-in-one" cluster layers
40
40
  - `packages/platform/bun/src/BunClusterHttp.ts` / `BunClusterSocket.ts` — Bun equivalents
41
41
  - `packages/platform/node/test/RpcServer.test.ts` + `test/fixtures/rpc-{schemas,e2e}.ts` — best end-to-end reference for real RPC wiring
@@ -55,8 +55,8 @@ import {
55
55
  RpcServer,
56
56
  RpcTest,
57
57
  RpcWorker
58
- } from 'effect/unstable/rpc';
59
- import { RpcClientError } from 'effect/unstable/rpc/RpcClientError';
58
+ } from 'effect/rpc';
59
+ import { RpcClientError } from 'effect/rpc/RpcClientError';
60
60
 
61
61
  // Cluster
62
62
  import {
@@ -77,7 +77,7 @@ import {
77
77
  SqlMessageStorage,
78
78
  SqlRunnerStorage,
79
79
  TestRunner
80
- } from 'effect/unstable/cluster';
80
+ } from 'effect/cluster';
81
81
 
82
82
  // Workflow (see effect-workflow skill for the full surface)
83
83
  import {
@@ -87,8 +87,8 @@ import {
87
87
  Workflow,
88
88
  WorkflowProxy,
89
89
  WorkflowProxyServer
90
- } from 'effect/unstable/workflow';
91
- import { ClusterWorkflowEngine } from 'effect/unstable/cluster';
90
+ } from 'effect/workflow';
91
+ import { ClusterWorkflowEngine } from 'effect/cluster';
92
92
 
93
93
  // Platform "all-in-one" cluster bundles
94
94
  import { NodeClusterHttp, NodeClusterSocket } from '@effect/platform-node';
@@ -133,7 +133,7 @@ Two big invariants:
133
133
 
134
134
  ```ts
135
135
  import { Schema } from 'effect';
136
- import { Rpc } from 'effect/unstable/rpc';
136
+ import { Rpc } from 'effect/rpc';
137
137
 
138
138
  // Style A — const value. Compact, fine for ad-hoc rpcs.
139
139
  export const Ping = Rpc.make('Ping', { success: Schema.String });
@@ -151,7 +151,9 @@ Both styles are official. The platform-node test fixtures and the cluster test f
151
151
  - `class extends` when the rpc is shared across many modules and the nominal type helps documentation/imports
152
152
  - `const` when you're listing a dozen rpcs in one file and the boilerplate hurts more than the nominal type helps
153
153
 
154
- > Note: this is **not** the same as `Workflow.make`, `Activity.make`, `Entity.make`, or `RpcGroup.make` — those all return plain values you assign with `const`. The class-extends pattern is unique to `Rpc.make` (and to `Schema.Class`-style constructors) because `Rpc` declares `new (_: never): {}` in its interface.
154
+ `RpcGroup.make` and `Workflow.make` also support class-extends declarations;
155
+ `Activity.make` and `Entity.make` use plain values. Prefer the convention already
156
+ used by the shared contract package.
155
157
 
156
158
  ### `Rpc.make` options
157
159
 
@@ -273,7 +275,7 @@ Returns a `Schema.Exit<Success, Error, Defect>` for the rpc that includes any mi
273
275
  Rare but powerful: build a constructor that transforms every rpc's success/error schemas. Lets you encode a convention like "every list endpoint returns a paginated wrapper":
274
276
 
275
277
  ```ts
276
- import { Rpc } from 'effect/unstable/rpc';
278
+ import { Rpc } from 'effect/rpc';
277
279
  import { Schema } from 'effect';
278
280
 
279
281
  interface PaginatedRpc extends Rpc.Custom {
@@ -434,8 +436,8 @@ Returns an `Effect<Context.Context<Rpc.ToHandler<R>>>` — the unprovided form o
434
436
  Returns an Effect that resolves to a single handler function with `services` already provided. The handler is callable as `(payload, options)` directly. This is the easiest way to unit-test one rpc handler in isolation:
435
437
 
436
438
  ```ts
437
- import { Headers } from 'effect/unstable/http';
438
- import { RequestId } from 'effect/unstable/rpc/RpcMessage';
439
+ import { Headers } from 'effect/http';
440
+ import { RequestId } from 'effect/rpc/RpcMessage';
439
441
 
440
442
  const result =
441
443
  yield*
@@ -462,7 +464,7 @@ Requires a `Protocol` in context (one of the `RpcServer.layerProtocol*`), the ha
462
464
 
463
465
  ```ts
464
466
  import { Layer } from 'effect';
465
- import { HttpRouter } from 'effect/unstable/http';
467
+ import { HttpRouter } from 'effect/http';
466
468
 
467
469
  const ServerLayer = RpcServer.layer(UsersGroup, {
468
470
  concurrency: 'unbounded', // default; set a number to backpressure handlers
@@ -473,11 +475,17 @@ const ServerLayer = RpcServer.layer(UsersGroup, {
473
475
  }).pipe(
474
476
  Layer.provide(UsersLive), // handlers
475
477
  Layer.provide(RpcServer.layerProtocolHttp({ path: '/rpc' })),
476
- Layer.provide(RpcSerialization.layerNdjson),
477
- Layer.provide(HttpRouter.layer)
478
+ Layer.provide(RpcSerialization.layerNdjson)
478
479
  );
479
480
  ```
480
481
 
482
+ Pass `ServerLayer` to `HttpRouter.serve`, `toWebHandler`, or `toHttpEffect` with
483
+ its router requirement intact. `serve` and `toHttpEffect` build a fresh router in
484
+ a forked memo map; `toWebHandler` builds separately by default but passes an
485
+ explicit `memoMap` through unchanged. Pre-providing `HttpRouter.layer` would register on a different router.
486
+ Layers first built inside the entrypoint are private. Provide stateful services
487
+ shared by sibling servers outside those entrypoints.
488
+
481
489
  Server options:
482
490
 
483
491
  - **`concurrency: number | 'unbounded'`** (default `'unbounded'`) — one semaphore around handler execution for the whole server instance, shared by all clients. `Rpc.fork(...)` opts a single handler out of this limit.
@@ -498,8 +506,7 @@ const ServerLayer = RpcServer.layerHttp({
498
506
  streamBufferSize: 16 // framed HTTP response queue; default 16
499
507
  }).pipe(
500
508
  Layer.provide(UsersLive),
501
- Layer.provide(RpcSerialization.layerNdjson),
502
- Layer.provide(HttpRouter.layer)
509
+ Layer.provide(RpcSerialization.layerNdjson)
503
510
  );
504
511
  ```
505
512
 
@@ -618,7 +625,7 @@ messages additionally have their documented storage/delivery outcomes.
618
625
  For one-off headers, use the per-call `headers` option. For region-scoped headers, use `RpcClient.withHeaders` (which updates the `RpcClient.CurrentHeaders` Reference):
619
626
 
620
627
  ```ts
621
- import { RpcClient } from 'effect/unstable/rpc';
628
+ import { RpcClient } from 'effect/rpc';
622
629
 
623
630
  yield* program.pipe(
624
631
  RpcClient.withHeaders({ authorization: `Bearer ${token}`, userid: '123' })
@@ -673,6 +680,11 @@ Pattern-match on `error.reason._tag` to handle transport faults (network down, m
673
680
  | `RpcClient.layerProtocolSocket({ retryTransientErrors?, onTransientError? })` | `RpcSerialization`, `Socket.Socket` | full duplex. Auto-pings every 5s; reconnects on transient socket errors; reports retried open failures through `onTransientError` |
674
681
  | `RpcClient.layerProtocolWorker(options)` | `Worker.WorkerPlatform`, `Worker.Spawner` | pool of worker-backed clients. Options: either `{ size, concurrency?, targetUtilization? }` or `{ minSize, maxSize, timeToLive, concurrency?, targetUtilization? }` |
675
682
 
683
+ A missed pong is a `SocketReadError`, fails all in-flight calls, and triggers
684
+ reconnection even with `retryTransientErrors: true`. It does not invoke
685
+ `onTransientError`; that hook applies to retried connection-open failures.
686
+ Reconnection does not replay failed calls. Retry only idempotent calls explicitly.
687
+
676
688
  For each there's a corresponding `make*` Effect (`makeProtocolHttp`, `makeProtocolSocket`, `makeProtocolWorker`) when you need finer control over context.
677
689
 
678
690
  ### `RpcClient.ConnectionHooks`
@@ -694,7 +706,7 @@ not a Cause; use `onExit` to distinguish client cancel from server shutdown:
694
706
 
695
707
  <!-- typecheck -->
696
708
  ```ts
697
- import { RpcSchema } from 'effect/unstable/rpc';
709
+ import { RpcSchema } from 'effect/rpc';
698
710
  import { Cause, Effect, Exit, Stream } from 'effect';
699
711
  import * as Arr from 'effect/Array';
700
712
 
@@ -713,7 +725,7 @@ const subscribeHandler = stream.pipe(Stream.runDrain,
713
725
  `RpcMiddleware.Service<Self, Config>()(name, options)` defines a middleware service. The config positionally encodes what the middleware *provides*, *requires*, and what *client-only* error type it can throw. The options carry the wire-error schema and the `requiredForClient` enforcement flag.
714
726
 
715
727
  ```ts
716
- import { RpcMiddleware } from 'effect/unstable/rpc';
728
+ import { RpcMiddleware } from 'effect/rpc';
717
729
  import { Context, Schema } from 'effect';
718
730
 
719
731
  class CurrentUser extends Context.Service<CurrentUser, User>()('CurrentUser') {}
@@ -766,7 +778,7 @@ Middleware can chain `requires` and `provides` — `DbMiddleware extends RpcMidd
766
778
  For middleware that needs to *also* run client-side (most commonly: attach an auth header), provide a `layerClient`:
767
779
 
768
780
  ```ts
769
- import { Headers } from 'effect/unstable/http';
781
+ import { Headers } from 'effect/http';
770
782
 
771
783
  export const AuthClient = RpcMiddleware.layerClient(
772
784
  AuthMiddleware,
@@ -780,7 +792,7 @@ export const AuthClient = RpcMiddleware.layerClient(
780
792
 
781
793
  Important details:
782
794
 
783
- - `request.headers` is `Headers.Headers` (already parsed). Use the helpers from `effect/unstable/http/Headers` (`Headers.set`, `Headers.merge`, `Headers.fromInput`).
795
+ - `request.headers` is `Headers.Headers` (already parsed). Use the helpers from `effect/http/Headers` (`Headers.set`, `Headers.merge`, `Headers.fromInput`).
784
796
  - You **must** call `next(request)` (with the modified or original request) — the middleware's job is to wrap the send, not replace it.
785
797
  - The Layer signature is `Layer<ForClient<AuthMiddleware>>` — it's a distinct service from the server-side middleware, and providing both is the norm for client packages.
786
798
 
@@ -810,7 +822,7 @@ same defect fallback that storage records.
810
822
 
811
823
  Picking the wrong one is a real bug:
812
824
 
813
- - `layerJson` over a websocket → no framing → the first chunk past the first message is misinterpreted
825
+ - `layerJson` over raw TCP → no framing → message boundaries cannot be recovered; WebSocket frames whole messages and supports plain JSON
814
826
  - SchemaBinary against a JSON-only HTTP client → incompatible responses
815
827
  - `layerNdjson` against `layerProtocolHttp` → streams incrementally through a bounded response queue (default 16), without RPC acks
816
828
 
@@ -820,7 +832,7 @@ In-process server+client wired together, no network. The simplest possible RPC t
820
832
 
821
833
  ```ts
822
834
  import { Effect, Layer } from 'effect';
823
- import { RpcTest } from 'effect/unstable/rpc';
835
+ import { RpcTest } from 'effect/rpc';
824
836
  import { it } from '@effect/vitest';
825
837
 
826
838
  const TestClient = Layer.effect(UsersClient)(
@@ -843,7 +855,7 @@ For worker-backed clients, you can pass typed initial config at spawn time witho
843
855
 
844
856
  ```ts
845
857
  // On the worker (server side):
846
- import { RpcWorker } from 'effect/unstable/rpc';
858
+ import { RpcWorker } from 'effect/rpc';
847
859
 
848
860
  class WorkerConfig extends Schema.Class<WorkerConfig>('WorkerConfig')({
849
861
  apiUrl: Schema.String,
@@ -875,8 +887,8 @@ Cluster turns rpcs into **addressable, distributed actors** (`Entity`). Messages
875
887
 
876
888
  ```ts
877
889
  import { Schema } from 'effect';
878
- import { ClusterSchema, Entity } from 'effect/unstable/cluster';
879
- import { Rpc } from 'effect/unstable/rpc';
890
+ import { ClusterSchema, Entity } from 'effect/cluster';
891
+ import { Rpc } from 'effect/rpc';
880
892
 
881
893
  export class Increment extends Rpc.make('Increment', {
882
894
  payload: { amount: Schema.Number },
@@ -958,7 +970,7 @@ export const CounterLive = Counter.toLayer(
958
970
  Entity handlers can pull two services from context:
959
971
 
960
972
  ```ts
961
- import { Entity } from 'effect/unstable/cluster';
973
+ import { Entity } from 'effect/cluster';
962
974
 
963
975
  Counter.toLayer(Effect.gen(function*() {
964
976
  return Counter.of({
@@ -1108,7 +1120,7 @@ In-process entity testing without a real cluster. Returns `(entityId) => Effect<
1108
1120
 
1109
1121
  ```ts
1110
1122
  import { Effect } from 'effect';
1111
- import { Entity, ShardingConfig } from 'effect/unstable/cluster';
1123
+ import { Entity, ShardingConfig } from 'effect/cluster';
1112
1124
  import { it } from '@effect/vitest';
1113
1125
 
1114
1126
  const TestShardingConfig = ShardingConfig.layer({
@@ -1132,7 +1144,7 @@ Required context: `Scope | ShardingConfig | Rpc.MiddlewareClient<Rpcs> | (handle
1132
1144
  A `Singleton` is an effect that runs *exactly once across the cluster*. The shard manager elects a single runner to host it; if that runner dies, another one takes over. Distinct from `ClusterCron` — the latter is built on top of singletons + entities.
1133
1145
 
1134
1146
  ```ts
1135
- import { Singleton } from 'effect/unstable/cluster';
1147
+ import { Singleton } from 'effect/cluster';
1136
1148
 
1137
1149
  const LeaderElection = Singleton.make(
1138
1150
  'leader-elector',
@@ -1154,7 +1166,7 @@ Cluster-singleton cron executions. The cron schedule is durable: missed runs (wi
1154
1166
 
1155
1167
  ```ts
1156
1168
  import { Cron, Effect } from 'effect';
1157
- import { ClusterCron } from 'effect/unstable/cluster';
1169
+ import { ClusterCron } from 'effect/cluster';
1158
1170
 
1159
1171
  const DailyReport = ClusterCron.make({
1160
1172
  name: 'DailyReport',
@@ -1178,7 +1190,7 @@ The schedule survives runner restarts because the next invocation is durably sto
1178
1190
  Inside any entity handler (or any effect running in the cluster), you can pull `Sharding.Sharding` for cluster-aware operations:
1179
1191
 
1180
1192
  ```ts
1181
- import { Sharding } from 'effect/unstable/cluster';
1193
+ import { Sharding } from 'effect/cluster';
1182
1194
 
1183
1195
  const sharding = yield* Sharding.Sharding;
1184
1196
 
@@ -1197,7 +1209,7 @@ Three levels of convenience.
1197
1209
  ### Level 1: testing — `TestRunner.layer`
1198
1210
 
1199
1211
  ```ts
1200
- import { TestRunner } from 'effect/unstable/cluster';
1212
+ import { TestRunner } from 'effect/cluster';
1201
1213
 
1202
1214
  const TestLayer = Layer.mergeAll(CounterLive, OrderLive).pipe(
1203
1215
  Layer.provideMerge(TestRunner.layer)
@@ -1217,7 +1229,7 @@ expect(driver.journal[0].address.entityId).toBe('test-1');
1217
1229
  ### Level 2: single-node, sql-backed — `SingleRunner.layer`
1218
1230
 
1219
1231
  ```ts
1220
- import { SingleRunner } from 'effect/unstable/cluster';
1232
+ import { SingleRunner } from 'effect/cluster';
1221
1233
 
1222
1234
  const ClusterLayer = SingleRunner.layer({
1223
1235
  shardingConfig: { entityMaxIdleTime: '10 minutes' },
@@ -1296,7 +1308,7 @@ When the bundles aren't quite right, assemble from the primitives:
1296
1308
  `availableShardGroups` is **cluster-wide** and must be identical on every runner that shares the same storage backend — shard and advisory-lock numbering is derived from it. `assignedShardGroups` is per-runner and is filtered against `availableShardGroups`, so a runner only ever owns groups that appear in *both*. If your code routes entities or workflows to a non-`default` `ClusterSchema.ShardGroup`, that group must be in `availableShardGroups` everywhere and in `assignedShardGroups` on the runners meant to host it:
1297
1309
 
1298
1310
  ```ts
1299
- import { ShardingConfig } from 'effect/unstable/cluster';
1311
+ import { ShardingConfig } from 'effect/cluster';
1300
1312
 
1301
1313
  const Config = ShardingConfig.layer({
1302
1314
  availableShardGroups: ['default', 'workflow'], // cluster-wide; same on all runners
@@ -1321,6 +1333,32 @@ The storage poller reads at most `unprocessedMessageBatchSize` messages per batc
1321
1333
 
1322
1334
  The memory driver now uses the same ten-minute claim window as SQL. `resetAddress`/`resetAddresses` or `resetShards` makes claimed messages immediately eligible again, which prevents bounded reads from repeatedly selecting in-flight rows while still allowing explicit recovery.
1323
1335
 
1336
+ Both decoded and encoded storage services also require `resetRequests(ids)`:
1337
+ release those request claims without changing replies or processed state. This
1338
+ lets a reset workflow request be redelivered promptly when its prior completion
1339
+ is still deduplicated locally. Memory storage models claim expiry but not SQL
1340
+ reply filtering or transaction isolation; use SQL-backed tests for those rules.
1341
+
1342
+ `clearReplies(requestId, { expectedReplyId? })` must compare the **latest** reply
1343
+ ID atomically at the storage boundary before clearing when an expected ID is
1344
+ provided. A mismatch is a successful no-op; without the option clearing is
1345
+ unconditional. `Sharding.reset(requestId, options?)` forwards that condition and
1346
+ returns `false` only when clearing fails, not on a comparison mismatch. Custom
1347
+ stores that ignore the condition allow stale workflow resumes to erase newer
1348
+ completed replies. Cluster workflows retry failed resets before acknowledging
1349
+ deferred completion.
1350
+
1351
+ With message storage disabled, completed request IDs are not retained for
1352
+ deduplication. Redelivery runs the request again; design volatile handlers for
1353
+ that possibility rather than expecting `AlreadyProcessingMessage` forever.
1354
+
1355
+ Local sends waiting on a missing entity type share the runner's
1356
+ `entityRegistrationTimeout` deadline with storage reads. They defect with
1357
+ `Entity type ... not registered` when it expires. The window is not renewed per
1358
+ send; dynamically registered types first contacted after that deadline fail
1359
+ immediately until registered. If registration never starts, the fallback window
1360
+ is two timeout intervals from the first missing type observed.
1361
+
1324
1362
  For custom SQL composition, `SqlMessageStorage.makeEncoded({ prefix? })` returns the low-level `MessageStorage.Encoded` driver directly. `SqlMessageStorage.make`, `layer`, and `layerWith` remain the decoded service constructors.
1325
1363
 
1326
1364
  ## Bridges — exposing entities and workflows as RPC/HTTP
@@ -1330,9 +1368,9 @@ Both `Entity` and `Workflow` ship "proxy" helpers that auto-derive `RpcGroup`s a
1330
1368
  ### `EntityProxy` — entity → RPC / HTTP
1331
1369
 
1332
1370
  ```ts
1333
- import { Entity, EntityProxy, EntityProxyServer } from 'effect/unstable/cluster';
1334
- import { RpcServer } from 'effect/unstable/rpc';
1335
- import { HttpApi, HttpApiBuilder } from 'effect/unstable/httpapi';
1371
+ import { Entity, EntityProxy, EntityProxyServer } from 'effect/cluster';
1372
+ import { RpcServer } from 'effect/rpc';
1373
+ import { HttpApi, HttpApiBuilder } from 'effect/http-api';
1336
1374
 
1337
1375
  const Counter = Entity.make('Counter', [Increment, GetCount])
1338
1376
  .annotateRpcs(ClusterSchema.Persisted, true);
@@ -1378,8 +1416,8 @@ remotes rather than adding an independent retry loop.
1378
1416
  ### `WorkflowProxy` — workflow → RPC / HTTP
1379
1417
 
1380
1418
  ```ts
1381
- import { Workflow, WorkflowProxy, WorkflowProxyServer } from 'effect/unstable/workflow';
1382
- import { RpcServer } from 'effect/unstable/rpc';
1419
+ import { Workflow, WorkflowProxy, WorkflowProxyServer } from 'effect/workflow';
1420
+ import { RpcServer } from 'effect/rpc';
1383
1421
 
1384
1422
  const myWorkflows = [EmailWorkflow, OrderWorkflow] as const;
1385
1423
 
@@ -1410,8 +1448,8 @@ discards even that ID. Entity discard endpoints keep their separate contract.
1410
1448
  The in-memory `WorkflowEngine.layerMemory` is for testing only. For production, use `ClusterWorkflowEngine.layer`, which wires the workflow engine into the cluster's `Sharding` + `MessageStorage`:
1411
1449
 
1412
1450
  ```ts
1413
- import { ClusterWorkflowEngine } from 'effect/unstable/cluster';
1414
- import { Workflow } from 'effect/unstable/workflow';
1451
+ import { ClusterWorkflowEngine } from 'effect/cluster';
1452
+ import { Workflow } from 'effect/workflow';
1415
1453
 
1416
1454
  const WorkflowsLayer = Layer.mergeAll(
1417
1455
  EmailWorkflowLayer,
@@ -1426,9 +1464,9 @@ const WorkflowsLayer = Layer.mergeAll(
1426
1464
  A workflow can be annotated with `ClusterSchema.ShardGroup`, exactly like an entity:
1427
1465
 
1428
1466
  ```ts
1429
- import { ClusterSchema } from 'effect/unstable/cluster';
1467
+ import { ClusterSchema } from 'effect/cluster';
1430
1468
 
1431
- const OrderWorkflow = Workflow.make({ /* ... */ })
1469
+ const OrderWorkflow = Workflow.make('OrderWorkflow', { /* ... */ })
1432
1470
  .annotate(ClusterSchema.ShardGroup, () => 'workflow');
1433
1471
  ```
1434
1472
 
@@ -1436,6 +1474,12 @@ const OrderWorkflow = Workflow.make({ /* ... */ })
1436
1474
 
1437
1475
  Workflow execution entities and the durable-clock entity use a fixed `10 seconds` idle timeout. Completed and suspended workflows therefore release runner residency slots quickly; their durable state is reconstructed from storage when the next resume, deferred completion, or clock message arrives. Do not use `Entity.keepAlive` to pin these internal workflow entities.
1438
1476
 
1477
+ Execution IDs now hash `"${name.length}:${name}:${idempotencyKey(payload)}"`.
1478
+ Recomputing IDs created under rc.116 yields new identities; retain stored IDs
1479
+ when operating on existing runs. Reusing a workflow tag with a different
1480
+ definition warns and retains the existing definition. See `effect-workflow` for
1481
+ the execution-identity and deferred-token boundaries.
1482
+
1439
1483
  See the `effect-workflow` skill for the full `Workflow` / `Activity` / `DurableClock` / `DurableDeferred` / `DurableQueue` API surface.
1440
1484
 
1441
1485
  ## Reactive frontend — `AtomRpc`
@@ -1443,7 +1487,7 @@ See the `effect-workflow` skill for the full `Workflow` / `Activity` / `DurableC
1443
1487
  `AtomRpc.Service()(...)` produces an Atom-aware RPC client with `query` (cached, reactive) and `mutation` (invalidating) helpers, designed for React + Atom apps. See the `effect-atom-rpc` skill for the full surface; brief teaser:
1444
1488
 
1445
1489
  ```ts
1446
- import { AtomRpc } from 'effect/unstable/reactivity';
1490
+ import { AtomRpc } from 'effect/reactivity';
1447
1491
 
1448
1492
  class UsersClient extends AtomRpc.Service<UsersClient>()('UsersClient', {
1449
1493
  group: UsersGroup,
@@ -1473,7 +1517,7 @@ A small users service with auth middleware, websocket transport, and a test clie
1473
1517
  ```ts
1474
1518
  // --- definitions/users.ts (shared between server and client) ---
1475
1519
  import { Context, Schema } from 'effect';
1476
- import { Rpc, RpcGroup, RpcMiddleware } from 'effect/unstable/rpc';
1520
+ import { Rpc, RpcGroup, RpcMiddleware } from 'effect/rpc';
1477
1521
 
1478
1522
  export class User extends Schema.Class<User>('User')({
1479
1523
  id: Schema.String,
@@ -1516,8 +1560,8 @@ export const UsersGroup = RpcGroup.make(GetUser, StreamUsers).middleware(AuthMid
1516
1560
  ```ts
1517
1561
  // --- server/handlers.ts ---
1518
1562
  import { Effect, Layer, Stream } from 'effect';
1519
- import { Headers } from 'effect/unstable/http';
1520
- import { Rpc, RpcMiddleware } from 'effect/unstable/rpc';
1563
+ import { Headers } from 'effect/http';
1564
+ import { Rpc, RpcMiddleware } from 'effect/rpc';
1521
1565
  import { CurrentUser, UnauthorizedError, UsersGroup, User, UserNotFound } from '../definitions/users.ts';
1522
1566
 
1523
1567
  export const UsersHandlersLive = UsersGroup.toLayer(
@@ -1549,8 +1593,8 @@ export const AuthLive = Layer.succeed(AuthMiddleware)(
1549
1593
  // --- server/main.ts ---
1550
1594
  import { Layer } from 'effect';
1551
1595
  import { NodeHttpServer, NodeRuntime } from '@effect/platform-node';
1552
- import { HttpRouter } from 'effect/unstable/http';
1553
- import { RpcSerialization, RpcServer } from 'effect/unstable/rpc';
1596
+ import { HttpRouter } from 'effect/http';
1597
+ import { RpcSerialization, RpcServer } from 'effect/rpc';
1554
1598
  import { createServer } from 'node:http';
1555
1599
 
1556
1600
  const ServerLayer = RpcServer.layerHttp({
@@ -1573,9 +1617,9 @@ Layer.launch(HttpLayer).pipe(NodeRuntime.runMain);
1573
1617
  ```ts
1574
1618
  // --- client/users-client.ts ---
1575
1619
  import { Context, Effect, Layer } from 'effect';
1576
- import { FetchHttpClient } from 'effect/unstable/http';
1577
- import { RpcClient, RpcMiddleware, RpcSerialization } from 'effect/unstable/rpc';
1578
- import { RpcClientError } from 'effect/unstable/rpc/RpcClientError';
1620
+ import { FetchHttpClient } from 'effect/http';
1621
+ import { RpcClient, RpcMiddleware, RpcSerialization } from 'effect/rpc';
1622
+ import { RpcClientError } from 'effect/rpc/RpcClientError';
1579
1623
 
1580
1624
  const AuthClient = RpcMiddleware.layerClient(AuthMiddleware, ({ next, request }) =>
1581
1625
  next({
@@ -1634,7 +1678,7 @@ const usersByName = Effect.gen(function*() {
1634
1678
  12. **Reading `Date.now()` inside an entity or workflow handler.** Use `Clock` (and inside workflows, `DateTime.now` works because the engine wraps activities). For durable timers, use `DurableClock.sleep`.
1635
1679
  13. **`yield* fiber` / `yield* deferred` / `yield* ref`.** Removed in v4. Use `Fiber.join`, `Deferred.await`, `Ref.get` explicitly.
1636
1680
  14. **Treating `maxResidentEntities` like mailbox capacity.** It is a runner-wide resident-entity cap. Persisted messages wait in storage at the cap; volatile sends to new addresses fail with `MailboxFull`.
1637
- 15. **Implementing the old encoded storage driver.** `MessageStorage.Encoded` now requires bounded/address-filtered `unprocessedMessages` and batched `resetAddresses`.
1681
+ 15. **Implementing an incomplete encoded storage driver.** `MessageStorage.Encoded` requires bounded/address-filtered `unprocessedMessages`, batched `resetAddresses`, claim-only `resetRequests`, and atomic conditional `clearReplies` when `expectedReplyId` is supplied.
1638
1682
 
1639
1683
  ## Rules
1640
1684