opencode-effect-enforcer 0.2.5 → 0.2.8

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 (52) hide show
  1. package/README.md +42 -140
  2. package/docs/effect-4.0.0-rc.116-changelog.md +2654 -0
  3. package/docs/effect-4.0.0-rc.116.md +102 -0
  4. package/guidance/effect-first-development.md +28 -311
  5. package/guidance/progressive-disclosure-guidance.md +5 -19
  6. package/package.json +2 -2
  7. package/patterns/avoid-direct-tag-checks.md +1 -1
  8. package/patterns/avoid-process-env.md +4 -4
  9. package/patterns/context-tag-extends.md +4 -4
  10. package/patterns/prefer-arr-sort.md +1 -1
  11. package/patterns/prefer-redacted-config.md +10 -10
  12. package/patterns/prefer-schema-class.md +1 -1
  13. package/patterns/require-effect-concurrency.md +1 -1
  14. package/skills/effect-ai-chat/SKILL.md +2 -2
  15. package/skills/effect-ai-language-model/SKILL.md +36 -4
  16. package/skills/effect-ai-prompt/SKILL.md +1 -1
  17. package/skills/effect-ai-provider/SKILL.md +23 -12
  18. package/skills/effect-ai-tool/SKILL.md +13 -0
  19. package/skills/effect-atom-rpc/SKILL.md +7 -1
  20. package/skills/effect-atom-state/SKILL.md +8 -2
  21. package/skills/effect-cache/SKILL.md +10 -1
  22. package/skills/effect-cli/SKILL.md +105 -94
  23. package/skills/effect-command-executor/SKILL.md +7 -1
  24. package/skills/effect-config/SKILL.md +67 -44
  25. package/skills/effect-domain-modeling/SKILL.md +3 -3
  26. package/skills/effect-error-handling/SKILL.md +2 -2
  27. package/skills/effect-fiber/SKILL.md +2 -2
  28. package/skills/effect-filesystem/SKILL.md +34 -4
  29. package/skills/effect-http-api/SKILL.md +17 -2
  30. package/skills/effect-http-client/SKILL.md +11 -2
  31. package/skills/effect-http-server/SKILL.md +28 -11
  32. package/skills/effect-layer-design/SKILL.md +6 -2
  33. package/skills/effect-mcp-server/SKILL.md +21 -4
  34. package/skills/effect-observability/SKILL.md +2 -2
  35. package/skills/effect-optics/SKILL.md +1 -1
  36. package/skills/effect-parallelization/SKILL.md +2 -2
  37. package/skills/effect-pattern-matching/SKILL.md +1 -1
  38. package/skills/effect-platform-abstraction/SKILL.md +3 -3
  39. package/skills/effect-rpc-api/SKILL.md +3 -3
  40. package/skills/effect-rpc-client/SKILL.md +14 -13
  41. package/skills/effect-rpc-cluster/SKILL.md +15 -12
  42. package/skills/effect-rpc-server/SKILL.md +11 -13
  43. package/skills/effect-scheduling/SKILL.md +7 -0
  44. package/skills/effect-schema-composition/SKILL.md +12 -4
  45. package/skills/effect-schema-v4/SKILL.md +75 -20
  46. package/skills/effect-scope/SKILL.md +4 -4
  47. package/skills/effect-socket/SKILL.md +161 -658
  48. package/skills/effect-sql/SKILL.md +50 -12
  49. package/skills/effect-stream/SKILL.md +19 -24
  50. package/skills/effect-testing/SKILL.md +35 -22
  51. package/skills/effect-workflow/SKILL.md +13 -2
  52. package/src/guidance.ts +0 -1
@@ -19,9 +19,9 @@ suggestSkills:
19
19
  processEnv :: String -> IO (Maybe String) -- side effect, untyped, untestable
20
20
 
21
21
  -- Instead
22
- Config.string :: String -> Config String -- typed, composable, testable
22
+ Config.String :: String -> Config String -- typed, composable, testable
23
23
  Config.withDefault :: a -> Config a -> Config a
24
- Config.redacted :: String -> Config Redacted -- for sensitive values
24
+ Config.Redacted :: String -> Config Redacted -- for sensitive values
25
25
  ```
26
26
 
27
27
  ```haskell
@@ -30,10 +30,10 @@ bad :: Effect String
30
30
  bad = Effect.sync \_ -> process.env.API_KEY -- raw side effect
31
31
 
32
32
  good :: Effect String ConfigError
33
- good = Config.string "API_KEY" -- typed, validated
33
+ good = Config.Redacted "API_KEY" -- typed, redacted
34
34
 
35
35
  better :: Effect String ConfigError
36
- better = Config.string("PORT")
36
+ better = Config.Int("PORT")
37
37
  & Config.withDefault "3000"
38
38
  & Config.map Number.parse -- with transformation
39
39
  ```
@@ -26,13 +26,13 @@ suggestSkills:
26
26
 
27
27
  # Use `Context.Service` for All Service Definitions
28
28
 
29
- `Context.Service` is the single canonical service-definition API in Effect v4 (beta.46+). Three legacy spellings exist and must all be replaced:
29
+ Use `Context.Service` for service definitions. Replace these legacy spellings:
30
30
 
31
31
  | Legacy API | Era | Replacement |
32
32
  | --------------------------- | --------- | -------------------------- |
33
33
  | `Context.Tag` / `GenericTag` | pre-v4 | `Context.Service` |
34
34
  | `Effect.Service` | early v4 | `Context.Service` |
35
- | `ServiceMap.Service` / `.*` | beta.43 | `Context.Service` / `Context.*` |
35
+ | `ServiceMap.Service` / `.*` | prerelease | `Context.Service` / `Context.*` |
36
36
 
37
37
  ```haskell
38
38
  -- Anti-pattern: *Tag suffix + Context.Tag — removed in v4
@@ -43,10 +43,10 @@ data ParallelClientService = ...
43
43
  -- Anti-pattern: Effect.Service — also removed in v4
44
44
  class ParallelClient extends Effect.Service<ParallelClient>()(...)
45
45
 
46
- -- Anti-pattern: ServiceMap.* — removed before v4 beta.46 stabilized
46
+ -- Anti-pattern: ServiceMap.*
47
47
  class MyService extends ServiceMap.Service<MyService>()("@app/MyService", { ... })
48
48
 
49
- -- Fix: Context.Service (beta.46 API)
49
+ -- Fix: Context.Service
50
50
  class ParallelClient extends Context.Service<ParallelClient>()(
51
51
  "@parallel/ParallelClient"
52
52
  )
@@ -49,4 +49,4 @@ descending = Arr.sort (Order.reverse byName)
49
49
 
50
50
  Native `.sort()` mutates the array in place and uses an untyped comparator. `Arr.sort` from `effect/Array` returns a new sorted array using a composable, typed `Order`.
51
51
 
52
- References: EF-38 in effect-first-development.md
52
+ References: EF-5 in effect-first-development.md
@@ -3,13 +3,13 @@ action: context
3
3
  tool: (edit|write)
4
4
  event: after
5
5
  name: prefer-redacted-config
6
- description: Use Config.redacted or Schema.Redacted for secret-like configuration values
6
+ description: Use Config.Redacted or Schema.Redacted for secret-like configuration values
7
7
  glob: '**/*.{ts,tsx}'
8
8
  detector: ast
9
9
  rule:
10
10
  any:
11
- - pattern: Config.string($KEY)
12
- - pattern: Config.nonEmptyString($KEY)
11
+ - pattern: Config.String($KEY)
12
+ - pattern: Config.NonEmptyString($KEY)
13
13
  - all:
14
14
  - kind: pair
15
15
  - has:
@@ -33,18 +33,18 @@ suggestSkills:
33
33
 
34
34
  ```haskell
35
35
  -- Transformation
36
- Config.string secretKey :: String -- easy to log accidentally
37
- Config.redacted secretKey :: Redacted String -- hidden from logs/toString
36
+ Config.String secretKey :: String -- easy to log accidentally
37
+ Config.Redacted secretKey :: Redacted String -- hidden from logs/toString
38
38
  ```
39
39
 
40
40
  ```typescript
41
41
  // Bad
42
- const apiKey = Config.string('API_KEY');
43
- const token = Config.nonEmptyString('GITHUB_TOKEN');
42
+ const apiKey = Config.String('API_KEY');
43
+ const token = Config.NonEmptyString('GITHUB_TOKEN');
44
44
 
45
45
  // Good
46
- const apiKey = Config.redacted('API_KEY');
47
- const token = Config.redacted('GITHUB_TOKEN');
46
+ const apiKey = Config.Redacted('API_KEY');
47
+ const token = Config.Redacted('GITHUB_TOKEN');
48
48
  ```
49
49
 
50
50
  For structured config schemas, wrap secret-like string fields in `Schema.Redacted`:
@@ -67,4 +67,4 @@ const AppConfig = Config.schema(
67
67
  );
68
68
  ```
69
69
 
70
- Secrets should remain redacted from the moment they enter the program. Use `Config.redacted` for primitive config values and `Schema.Redacted(Schema.String)` for schema-based config fields.
70
+ Secrets should remain redacted from the moment they enter the program. Use `Config.Redacted` for primitive config values and `Schema.Redacted(Schema.String)` for schema-based config fields.
@@ -51,4 +51,4 @@ extend = class Admin extends User.extend<Admin>("Admin")({
51
51
 
52
52
  `Schema.Struct` produces an anonymous schema without a constructor or `instanceof` support. `Schema.Class` provides a named type, constructor, extensibility, and optional annotation support when docs or introspection benefit from it. Prefer `Schema.Class` for decoded domain/API shapes, union members, and values that need identity. `Schema.Struct` remains appropriate for local structural composition, configuration internals, and schemas where class identity adds no value, so review this informational finding in context.
53
53
 
54
- References: EF-3, EF-33 in effect-first-development.md
54
+ References: EF-3 in effect-first-development.md
@@ -79,7 +79,7 @@ Effect.forEach xs f opts -- concurrency intent explicit
79
79
  Effect.forEach(items, processItem);
80
80
  Effect.all(tasks);
81
81
  Effect.validate(inputs, validateInput, { discard: true });
82
- Effect.all(tasks, { concurrency: 'inherit' }); // removed in beta.102
82
+ Effect.all(tasks, { concurrency: 'inherit' }); // unsupported ambient policy
83
83
 
84
84
  // Good
85
85
  Effect.forEach(items, processItem, { concurrency: 1 });
@@ -231,7 +231,7 @@ const program = Effect.gen(function* () {
231
231
  timeToLive: '1 hour' // optional TTL
232
232
  });
233
233
 
234
- // chat is a `Chat.Persisted` — same API as Chat.Service but auto-saves
234
+ // chat is a `Chat.Persisted` — same API as Chat.Chat but auto-saves
235
235
  const response = yield* chat
236
236
  .generateText({
237
237
  prompt: 'Hello!'
@@ -245,7 +245,7 @@ const program = Effect.gen(function* () {
245
245
  });
246
246
  ```
247
247
 
248
- The `Persisted` interface extends `Chat.Service` with:
248
+ The `Persisted` interface extends `Chat.Chat` with:
249
249
 
250
250
  - `id: string` — the chat identifier in the store
251
251
  - `save: Effect<void, AiError | PersistenceError>` — manual save trigger
@@ -94,7 +94,13 @@ const manualTools = LanguageModel.generateText({
94
94
  });
95
95
  ```
96
96
 
97
- When `disableToolCallResolution: true`, tool-call `params` are preserved in the schema's **encoded** representation instead of being decoded and then returned. The response type reflects this as `GenerateTextResponse<Tools, true>` and `Response.ToolCallParts<Tools, true>`; streaming uses `Response.StreamPart<Tools, true>`. This matters for transformations such as `Schema.NumberFromString`: manual calls contain the wire value `{ count: '3' }`, not `{ count: 3 }`.
97
+ With `disableToolCallResolution: true`, tool-call `params` retain the schema's
98
+ **encoded** representation. Response generics use the mode `"encoded"`, for
99
+ example `GenerateTextResponse<Tools, "encoded">` and
100
+ `Response.StreamPart<Tools, "encoded">`. Automatic resolution uses `"opaque"`
101
+ parameters (`unknown`), because invalid calls can also appear in responses.
102
+ Only successfully decoded handler inputs have the decoded parameter type.
103
+ For `Schema.NumberFromString`, manual calls contain `{ count: '3' }`.
98
104
 
99
105
  Pass those encoded params directly to `toolkit.handle(name, params, toolCallId)` when resolving manually. `Toolkit.handle` now accepts `Tool.ParametersEncoded<T>` and performs the decode before the handler receives `Tool.Parameters<T>`.
100
106
 
@@ -104,7 +110,7 @@ Pass those encoded params directly to `toolkit.handle(name, params, toolCallId)`
104
110
  const response = yield* LanguageModel.generateText({ prompt: '...' });
105
111
 
106
112
  response.text; // string - concatenated text content
107
- response.toolCalls; // decoded params normally; encoded params when resolution is disabled
113
+ response.toolCalls; // opaque params normally; encoded params when resolution is disabled
108
114
  response.toolResults; // Array<ToolResultParts> - tool outputs
109
115
  response.finishReason; // "stop" | "length" | "content-filter" | "tool-calls" | "error" | "pause" | "unknown" | "other"
110
116
  response.usage; // Usage object with nested structure, e.g. response.usage.outputTokens.total
@@ -297,6 +303,32 @@ const robust = LanguageModel.generateText({
297
303
  );
298
304
  ```
299
305
 
306
+ ## Batched classification, rating, and probability
307
+
308
+ Use `DecisionModel` for structured judgments over one schema-encoded input.
309
+ Define named decisions once and send them in one call; input encoding services
310
+ remain explicit and provider/validation failures use `AiError`. Classification
311
+ requires at least two labels, rating requires at least two distinct ordered
312
+ levels, and a definition requires at least one decision.
313
+
314
+ <!-- typecheck -->
315
+ ```ts
316
+ import * as Schema from 'effect/Schema';
317
+ import { Decision, DecisionModel } from 'effect/unstable/ai';
318
+
319
+ class Ticket extends Schema.Class<Ticket>('Ticket')({ body: Schema.String }) {}
320
+ const triage = Decision.make({
321
+ input: Ticket,
322
+ decisions: {
323
+ team: Decision.classify({
324
+ instructions: 'Choose the team responsible for this request',
325
+ criteria: { billing: 'Payments and invoices', support: 'Product support' }
326
+ })
327
+ }
328
+ });
329
+ const result = DecisionModel.decide(triage, { input: new Ticket({ body: 'Invoice question' }) });
330
+ ```
331
+
300
332
  ## Type Extraction Utilities
301
333
 
302
334
  ```typescript
@@ -308,8 +340,8 @@ type MyError = LanguageModel.ExtractError<typeof options>;
308
340
  // Extract service requirements from options
309
341
  type MyRequirements = LanguageModel.ExtractServices<typeof options>;
310
342
 
311
- // true only when options has literal disableToolCallResolution: true
312
- type EncodedParams = LanguageModel.ExtractEncodedToolParameters<typeof options>;
343
+ // "encoded" for literal disableToolCallResolution: true; otherwise "opaque"
344
+ type ParametersMode = LanguageModel.ExtractToolParametersMode<typeof options>;
313
345
 
314
346
  // Inferred based on:
315
347
  // - toolkit: Toolkit.WithHandler<Tools> → Tool.HandlerError<Tools> ∈ E
@@ -516,7 +516,7 @@ const program = Effect.gen(function* () {
516
516
 
517
517
  ## Provider-Specific Options
518
518
 
519
- ### OpenAI Responses explicit cache breakpoints (rc.112)
519
+ ### OpenAI Responses explicit cache breakpoints
520
520
 
521
521
  With `@effect/ai-openai` loaded, system-message and text-part options accept
522
522
  `openai.promptCacheBreakpoint`. This requires GPT-5.6 or later; earlier models
@@ -100,7 +100,7 @@ import { FetchHttpClient } from 'effect/unstable/http';
100
100
 
101
101
  // Client layer (reusable across models)
102
102
  const AnthropicClientLayer = AnthropicClient.layerConfig({
103
- apiKey: Config.redacted('ANTHROPIC_API_KEY')
103
+ apiKey: Config.Redacted('ANTHROPIC_API_KEY')
104
104
  }).pipe(Layer.provide(FetchHttpClient.layer));
105
105
 
106
106
  // Option A: model() — returns Model.Model (preferred)
@@ -130,7 +130,7 @@ import { Config, Layer } from 'effect';
130
130
  import { FetchHttpClient } from 'effect/unstable/http';
131
131
 
132
132
  const OpenAiClientLayer = OpenAiClient.layerConfig({
133
- apiKey: Config.redacted('OPENAI_API_KEY')
133
+ apiKey: Config.Redacted('OPENAI_API_KEY')
134
134
  }).pipe(Layer.provide(FetchHttpClient.layer));
135
135
 
136
136
  // model() constructor (preferred)
@@ -186,7 +186,7 @@ import { Config, Layer } from 'effect';
186
186
  import { FetchHttpClient, HttpClient, HttpClientRequest } from 'effect/unstable/http';
187
187
 
188
188
  const CompatibleClientLayer = OpenAiClient.layerConfig({
189
- apiKey: Config.redacted('OPENAI_COMPAT_API_KEY'),
189
+ apiKey: Config.Redacted('OPENAI_COMPAT_API_KEY'),
190
190
  apiUrl: Config.succeed('https://my-provider.example.com/v1')
191
191
  }).pipe(Layer.provide(FetchHttpClient.layer));
192
192
 
@@ -211,7 +211,7 @@ import { Config, Layer } from 'effect';
211
211
  import { FetchHttpClient } from 'effect/unstable/http';
212
212
 
213
213
  const OpenRouterClientLayer = OpenRouterClient.layerConfig({
214
- apiKey: Config.redacted('OPENROUTER_API_KEY')
214
+ apiKey: Config.Redacted('OPENROUTER_API_KEY')
215
215
  }).pipe(Layer.provide(FetchHttpClient.layer));
216
216
 
217
217
  // model() constructor — use provider-prefixed model IDs
@@ -420,7 +420,7 @@ export class AiWriter extends Context.Service<
420
420
 
421
421
  ## Custom Error Wrapping
422
422
 
423
- In rc.112, `AiError.AuthenticationError` accepts an optional `description` and
423
+ `AiError.AuthenticationError` accepts an optional `description` and
424
424
  appends it after the kind-based remediation message. Anthropic, OpenAI,
425
425
  OpenAI-compatible, and OpenRouter adapters propagate provider error text from
426
426
  401/403 responses. Preserve this reason rather than replacing it with a generic
@@ -496,11 +496,11 @@ import { FetchHttpClient } from 'effect/unstable/http';
496
496
  // ---------------------------------------------------------------------------
497
497
 
498
498
  const AnthropicClientLayer = AnthropicClient.layerConfig({
499
- apiKey: Config.redacted('ANTHROPIC_API_KEY')
499
+ apiKey: Config.Redacted('ANTHROPIC_API_KEY')
500
500
  }).pipe(Layer.provide(FetchHttpClient.layer));
501
501
 
502
502
  const OpenAiClientLayer = OpenAiClient.layerConfig({
503
- apiKey: Config.redacted('OPENAI_API_KEY')
503
+ apiKey: Config.Redacted('OPENAI_API_KEY')
504
504
  }).pipe(Layer.provide(FetchHttpClient.layer));
505
505
 
506
506
  // ---------------------------------------------------------------------------
@@ -619,15 +619,15 @@ Effect.runPromise(program.pipe(Effect.provide(AiWriter.layer)));
619
619
  // WRONG: Hardcoded API keys
620
620
  AnthropicClient.layerConfig({ apiKey: 'sk-...' });
621
621
 
622
- // RIGHT: Config.redacted for secrets
623
- AnthropicClient.layerConfig({ apiKey: Config.redacted('ANTHROPIC_API_KEY') });
622
+ // RIGHT: Config.Redacted for secrets
623
+ AnthropicClient.layerConfig({ apiKey: Config.Redacted('ANTHROPIC_API_KEY') });
624
624
 
625
625
  // WRONG: Missing FetchHttpClient layer
626
- AnthropicClient.layerConfig({ apiKey: Config.redacted('KEY') });
626
+ AnthropicClient.layerConfig({ apiKey: Config.Redacted('KEY') });
627
627
  // Will fail at runtime — providers require an HttpClient
628
628
 
629
629
  // RIGHT: Always provide an HTTP client layer
630
- AnthropicClient.layerConfig({ apiKey: Config.redacted('KEY') }).pipe(
630
+ AnthropicClient.layerConfig({ apiKey: Config.Redacted('KEY') }).pipe(
631
631
  Layer.provide(FetchHttpClient.layer)
632
632
  );
633
633
 
@@ -653,7 +653,7 @@ import { BedrockClient } from '@effect/ai-amazon-bedrock'; // Does NOT exist
653
653
 
654
654
  ## Quality Checklist
655
655
 
656
- - [ ] Use `Config.redacted` for API keys (never hardcode)
656
+ - [ ] Use `Config.Redacted` for API keys (never hardcode)
657
657
  - [ ] Provide `FetchHttpClient.layer` to all client layers
658
658
  - [ ] Use `.model()` constructor for `ExecutionPlan` and `Effect.provide`
659
659
  - [ ] Use `ExecutionPlan` for multi-provider fallback with retry
@@ -674,6 +674,17 @@ import { BedrockClient } from '@effect/ai-amazon-bedrock'; // Does NOT exist
674
674
 
675
675
  ## References
676
676
 
677
+ Provider-neutral structured decisions use `Decision` / `DecisionModel` from
678
+ `effect/unstable/ai`. `OpenRouterDecisionModel` targets OpenRouter's alpha Decisions
679
+ API; `@effect/ai-typesafe` supplies a provider for TypeSafe System One. Custom
680
+ OpenRouter client implementations include `createDecisions`.
681
+
682
+ Use each branded service's same-name type (`LanguageModel.LanguageModel`,
683
+ `EmbeddingModel.EmbeddingModel`, `Chat.Chat`) and its exported TypeId for custom
684
+ implementations. Prefer provided constructors; do not hard-code marker strings.
685
+ OpenAI web-search sources are a discriminated union: narrow `type` before reading
686
+ `url` or `name`. Provider-executed tool failures remain failure results.
687
+
677
688
  - `packages/ai/anthropic/src/AnthropicLanguageModel.ts`
678
689
  - `packages/ai/openai/src/OpenAiLanguageModel.ts`
679
690
  - `packages/ai/openrouter/src/OpenRouterLanguageModel.ts`
@@ -208,6 +208,19 @@ type Error = Tool.Failure<typeof FindUser>;
208
208
 
209
209
  **Key Pattern: failureMode**
210
210
 
211
+ Parameter validation follows the tool's `failureMode`. Failure results include
212
+ `Tool.ExecutionFailure` for framework failures (AI errors, denial, interruption)
213
+ as well as declared user errors. Select result codecs using `isFailure` and
214
+ `Tool.failureResultSchema(tool)`; preserve `encodedResult` when storing or
215
+ replaying response parts. A failure with `Schema.NumberFromString` is stored as
216
+ a string, even when the success schema stores a number. Validation errors do not
217
+ expose a `toolParams` field.
218
+
219
+ `Toolkit.handle` accepts parse options for parameter decoding. Returned failures
220
+ carry `failureOrigin`, also available through the `Toolkit.FailureOrigin` cause
221
+ annotation and `Tool.FailureOrigin` type. Keep validation, declared handler, and
222
+ internal failures distinct when presenting or reporting them.
223
+
211
224
  - `"error"` (default): Failures go to Effect error channel
212
225
  - `"return"`: Failures returned as tool result (captured, not thrown)
213
226
 
@@ -20,7 +20,7 @@ Use this skill when building React (or Atom-based) frontends that consume an exi
20
20
  For the underlying RPC definitions, see the `effect-rpc-cluster` skill.
21
21
  For Atom fundamentals (`Atom.make`, `family`, `keepAlive`, `AsyncResult`, hydration), see the `effect-atom-state` skill.
22
22
 
23
- The underlying RPC protocols require `codecFor` in rc.112. Built-in protocol
23
+ The underlying RPC protocols require `codecFor`. Built-in protocol
24
24
  layers supply it; forward it when implementing a custom transport. You can pair
25
25
  `RpcSerialization.layerSchemaBinary` on client/server without changing query or
26
26
  mutation call sites; see `effect-rpc-client` for frame limits and compatibility.
@@ -29,6 +29,12 @@ type is `string`. This is distinct from the client's `{ discard: true }` option.
29
29
 
30
30
  ## Effect Source Reference
31
31
 
32
+ Query and mutation errors include client middleware errors. AtomHttpApi stream
33
+ successes retain transport, decoding, and SSE failures in the stream error channel;
34
+ handle them when consuming the stream. An explicit zero `timeToLive` disables
35
+ default idle retention, so unmount/remount may dispose and refetch. Omission uses
36
+ the registry default. HttpApi calls accept per-call `sseOptions`.
37
+
32
38
  - `packages/effect/src/unstable/reactivity/AtomRpc.ts` — the whole API (~270 lines)
33
39
  - `packages/effect/test/reactivity/AtomRpc.test.ts` — minimal usage + serialization test
34
40
  - `packages/effect/src/unstable/reactivity/AtomHttpApi.ts` — the cousin pattern for `HttpApi` (same idea, same options)
@@ -7,7 +7,7 @@ description: Implement reactive state management with Effect Atom for React appl
7
7
 
8
8
  Effect Atom is a reactive state management library for Effect that seamlessly integrates with React.
9
9
 
10
- At rc.112, `@effect/atom-react` supports React `>=19.0.0 <20.0.0` (the peer range
10
+ `@effect/atom-react` supports React `>=19.0.0 <20.0.0` (the peer range
11
11
  was relaxed). This does not add React 18 support. Keep the adapter aligned with
12
12
  the Effect release; core atoms still live in `effect/unstable/reactivity`, and
13
13
  React bindings live in `@effect/atom-react` (`packages/atom/react` upstream).
@@ -27,6 +27,12 @@ Reference this for:
27
27
 
28
28
  ### Atoms as References
29
29
 
30
+ `Reactivity.Reactivity` is the branded service interface. Custom implementations
31
+ include its exported `[TypeId]: TypeId`; prefer the supplied constructor/layer.
32
+ Registry dehydration skips unencodable values while preserving other atoms.
33
+ `Atom.withFallback` forwards writes to the primary atom. Keep encoding contracts
34
+ explicit for state that must survive SSR dehydration.
35
+
30
36
  Atoms work **by reference** - they are stable containers for reactive state:
31
37
 
32
38
  ```typescript
@@ -301,7 +307,7 @@ export const notifications = Atom.make(
301
307
  Stream.fromEventListener(window, 'notification').pipe(
302
308
  Stream.map(parseNotification),
303
309
  Stream.filter(isValid),
304
- Stream.scan([], (acc, n) => [...acc, n].slice(-10))
310
+ Stream.scan(() => [], (acc, n) => [...acc, n].slice(-10))
305
311
  )
306
312
  );
307
313
  ```
@@ -7,6 +7,15 @@ You are an Effect TypeScript expert specializing in in-memory caching with `Cach
7
7
 
8
8
  ## Effect Source Reference
9
9
 
10
+ `Effect.cachedWithTTL` accepts either a fixed Duration input or an Exit-dependent
11
+ TTL function, so success and failure retention can differ. Allocate the cache
12
+ once in its owning layer. A zero TTL is explicit, not omission.
13
+ For `LayerMap.Service({ preload: true, ... })`, preserve acquisition errors on
14
+ the yielded service and `get` / `contextEffect` / `contextEffectOption`: an entry
15
+ can fail when reacquired after successful preloading. Invalidating an active
16
+ RcMap/LayerMap entry releases it after its last borrower closes; a replacement
17
+ entry remains independently owned.
18
+
10
19
  The Effect v4 source is available at `~/.local/share/opencode/repos/github.com/Effect-TS/effect@main/`.
11
20
  Browse and read files there directly to look up APIs, types, and implementations.
12
21
 
@@ -363,7 +372,7 @@ The full method surface (`get`, `getOption`, `getSuccess`, `set`, `has`, `invali
363
372
 
364
373
  ## 9. Services in Lookups (requireServicesAt)
365
374
 
366
- ### Retain an existing keyed resource (rc.112)
375
+ ### Retain an existing keyed resource
367
376
 
368
377
  `RcMap.getOption(map, key)` atomically retains a cached entry for the caller's
369
378
  `Scope` before awaiting it. It returns `Option.none()` if the entry is missing or