opencode-effect-enforcer 0.2.8 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -3
- package/docs/effect-4.0.0-changelog.md +3213 -0
- package/docs/effect-4.0.0.md +110 -0
- package/guidance/effect-first-development.md +8 -6
- package/guidance/progressive-disclosure-guidance.md +15 -7
- package/package.json +2 -2
- package/patterns/avoid-any.md +2 -2
- package/patterns/avoid-direct-json.md +6 -6
- package/patterns/avoid-native-fetch.md +8 -6
- package/patterns/avoid-node-imports.md +2 -2
- package/patterns/avoid-non-null-assertion.md +2 -2
- package/patterns/avoid-object-type.md +2 -2
- package/patterns/avoid-platform-coupling.md +1 -1
- package/patterns/avoid-process-env.md +3 -4
- package/patterns/avoid-ts-ignore.md +1 -1
- package/patterns/context-tag-extends.md +11 -8
- package/patterns/effect-promise-vs-trypromise.md +6 -7
- package/patterns/prefer-arr-sort.md +1 -1
- package/patterns/prefer-effect-fn.md +21 -65
- package/patterns/prefer-schema-class.md +3 -3
- package/patterns/throw-in-effect-gen.md +1 -1
- package/patterns/use-clock-service.md +4 -0
- package/patterns/use-command-executor-service.md +2 -2
- package/patterns/use-http-client-service.md +8 -6
- package/patterns/use-random-service.md +6 -7
- package/skills/effect-ai-chat/SKILL.md +13 -7
- package/skills/effect-ai-language-model/SKILL.md +50 -21
- package/skills/effect-ai-prompt/SKILL.md +25 -14
- package/skills/effect-ai-provider/SKILL.md +50 -22
- package/skills/effect-ai-streaming/SKILL.md +27 -12
- package/skills/effect-ai-tool/SKILL.md +37 -28
- package/skills/effect-atom-rpc/SKILL.md +57 -36
- package/skills/effect-atom-state/SKILL.md +57 -19
- package/skills/effect-batching/SKILL.md +5 -3
- package/skills/effect-cache/SKILL.md +19 -7
- package/skills/effect-cli/SKILL.md +17 -8
- package/skills/effect-command-executor/SKILL.md +115 -64
- package/skills/effect-concurrency-testing/SKILL.md +26 -6
- package/skills/effect-config/SKILL.md +53 -2
- package/skills/effect-context-witness/SKILL.md +6 -6
- package/skills/effect-domain-modeling/SKILL.md +8 -1
- package/skills/effect-error-handling/SKILL.md +15 -2
- package/skills/effect-fiber/SKILL.md +20 -25
- package/skills/effect-filesystem/SKILL.md +69 -57
- package/skills/effect-http-api/SKILL.md +72 -22
- package/skills/effect-http-client/SKILL.md +25 -21
- package/skills/effect-http-server/SKILL.md +51 -21
- package/skills/effect-incremental-migration/SKILL.md +17 -8
- package/skills/effect-layer-design/SKILL.md +8 -0
- package/skills/effect-managed-runtime/SKILL.md +6 -0
- package/skills/effect-mcp-server/SKILL.md +64 -24
- package/skills/effect-observability/SKILL.md +61 -15
- package/skills/effect-parallelization/SKILL.md +24 -7
- package/skills/effect-path/SKILL.md +8 -2
- package/skills/effect-platform-abstraction/SKILL.md +88 -66
- package/skills/effect-platform-layers/SKILL.md +68 -67
- package/skills/effect-pubsub-event-bus/SKILL.md +56 -60
- package/skills/effect-react-composition/SKILL.md +19 -6
- package/skills/effect-rpc-api/SKILL.md +24 -24
- package/skills/effect-rpc-client/SKILL.md +33 -28
- package/skills/effect-rpc-cluster/SKILL.md +122 -78
- package/skills/effect-rpc-server/SKILL.md +56 -20
- package/skills/effect-scheduling/SKILL.md +29 -1
- package/skills/effect-schema-composition/SKILL.md +31 -13
- package/skills/effect-schema-v4/SKILL.md +94 -10
- package/skills/effect-scope/SKILL.md +13 -5
- package/skills/effect-service-implementation/SKILL.md +1 -1
- package/skills/effect-socket/SKILL.md +52 -8
- package/skills/effect-sql/SKILL.md +67 -33
- package/skills/effect-stream/SKILL.md +50 -5
- package/skills/effect-testing/SKILL.md +91 -2
- package/skills/effect-workflow/SKILL.md +76 -39
|
@@ -7,20 +7,20 @@ 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
|
-
`@effect/atom-react`
|
|
11
|
-
|
|
12
|
-
the Effect release; core atoms still live in `effect/unstable/reactivity`, and
|
|
10
|
+
`@effect/atom-react@4.0.0` requires React `>=19.0.0 <20.0.0`.
|
|
11
|
+
Keep the adapter at the same version as `effect`; core atoms live in `effect/reactivity`, and
|
|
13
12
|
React bindings live in `@effect/atom-react` (`packages/atom/react` upstream).
|
|
13
|
+
Core reactivity APIs carry `@stability unstable` and may break in minor releases.
|
|
14
14
|
|
|
15
15
|
## Effect Source Reference
|
|
16
16
|
|
|
17
17
|
The Effect v4 source is available at `~/.local/share/opencode/repos/github.com/Effect-TS/effect@main/`.
|
|
18
|
-
|
|
18
|
+
Inspect `git show effect@4.0.0:<path>` there for this baseline; main may be ahead.
|
|
19
19
|
|
|
20
20
|
Reference this for:
|
|
21
21
|
|
|
22
|
-
- Atom reactivity: `packages/effect/src/
|
|
23
|
-
- AsyncResult source: `packages/effect/src/
|
|
22
|
+
- Atom reactivity: `packages/effect/src/reactivity/`
|
|
23
|
+
- AsyncResult source: `packages/effect/src/reactivity/AsyncResult.ts`
|
|
24
24
|
- Effect source: `packages/effect/src/`
|
|
25
25
|
|
|
26
26
|
## Core Concepts
|
|
@@ -36,7 +36,7 @@ explicit for state that must survive SSR dehydration.
|
|
|
36
36
|
Atoms work **by reference** - they are stable containers for reactive state:
|
|
37
37
|
|
|
38
38
|
```typescript
|
|
39
|
-
import * as Atom from 'effect/
|
|
39
|
+
import * as Atom from 'effect/reactivity/Atom';
|
|
40
40
|
|
|
41
41
|
// Atoms are created once and referenced throughout the app
|
|
42
42
|
export const counterAtom = Atom.make(0);
|
|
@@ -47,10 +47,13 @@ export const counterAtom = Atom.make(0);
|
|
|
47
47
|
|
|
48
48
|
### Automatic Cleanup
|
|
49
49
|
|
|
50
|
-
|
|
50
|
+
Registry nodes become eligible for disposal when unused by subscribers and
|
|
51
|
+
dependent atoms. Disposal follows the atom's idle TTL or registry default; it is
|
|
52
|
+
not necessarily synchronous with the last React unmount. `keepAlive` retains the
|
|
53
|
+
node until its registry is reset/disposed:
|
|
51
54
|
|
|
52
55
|
```typescript
|
|
53
|
-
//
|
|
56
|
+
// Disposed after becoming unused, according to the registry's idle policy
|
|
54
57
|
export const temporaryState = Atom.make(initialValue);
|
|
55
58
|
|
|
56
59
|
// Persists across component lifecycles
|
|
@@ -61,10 +64,15 @@ export const persistentState = Atom.make(initialValue).pipe(Atom.keepAlive);
|
|
|
61
64
|
|
|
62
65
|
Atom values are computed on-demand when subscribers access them.
|
|
63
66
|
|
|
67
|
+
Tracked dependencies remain retained while a node is stale and are reconciled
|
|
68
|
+
on its next build. Register cleanup with `get.addFinalizer` or scoped effects so
|
|
69
|
+
superseded builds release resources. Observed failed builds can recover when a
|
|
70
|
+
dependency changes; avoid manual reset workarounds for stale-node propagation.
|
|
71
|
+
|
|
64
72
|
## Pattern: Basic Atoms
|
|
65
73
|
|
|
66
74
|
```typescript
|
|
67
|
-
import * as Atom from 'effect/
|
|
75
|
+
import * as Atom from 'effect/reactivity/Atom';
|
|
68
76
|
|
|
69
77
|
// Simple atom
|
|
70
78
|
export const count = Atom.make(0);
|
|
@@ -132,19 +140,29 @@ export const userAtoms = Atom.family((userId: string) =>
|
|
|
132
140
|
Atom.make<User | null>(null).pipe(Atom.keepAlive)
|
|
133
141
|
);
|
|
134
142
|
|
|
135
|
-
//
|
|
143
|
+
// Reuses the cached live atom for a given ID
|
|
136
144
|
const userAtom = userAtoms(userId);
|
|
137
145
|
```
|
|
138
146
|
|
|
147
|
+
Family caches use weak references where supported. Identity is reused while the
|
|
148
|
+
cached atom is live; it is not a permanent store of every key ever requested.
|
|
149
|
+
An old atom's finalizer does not evict a newer cached atom for the same key.
|
|
150
|
+
|
|
139
151
|
## Pattern: Atom.fn for Async Actions
|
|
140
152
|
|
|
141
153
|
Use `Atom.fn` with `Effect.fnUntraced` for async operations:
|
|
142
154
|
|
|
143
155
|
- Reading gives `AsyncResult<Success, Error>` with automatic `.waiting` flag
|
|
144
156
|
- Triggering via `useAtomSet` runs the effect
|
|
157
|
+
- The callback takes one application argument plus an `Atom.FnContext`; bundle
|
|
158
|
+
multiple inputs into an object. The second argument is not another payload.
|
|
159
|
+
- By default a new write replaces the current run. Use `{ concurrent: true }`
|
|
160
|
+
deliberately for overlapping Effect executions. Synchronous successes and
|
|
161
|
+
failures are retained in concurrent mode; it still exposes one `AsyncResult`,
|
|
162
|
+
not a per-invocation result log.
|
|
145
163
|
|
|
146
164
|
```typescript
|
|
147
|
-
import * as Atom from "effect/
|
|
165
|
+
import * as Atom from "effect/reactivity/Atom"
|
|
148
166
|
import { useAtomValue, useAtomSet } from "@effect/atom-react"
|
|
149
167
|
import { Effect, Exit } from "effect"
|
|
150
168
|
|
|
@@ -278,7 +296,7 @@ Atom.runtime.addGlobalLayer(
|
|
|
278
296
|
Atoms can return `AsyncResult` types for explicit error handling:
|
|
279
297
|
|
|
280
298
|
```tsx
|
|
281
|
-
import * as AsyncResult from 'effect/
|
|
299
|
+
import * as AsyncResult from 'effect/reactivity/AsyncResult';
|
|
282
300
|
|
|
283
301
|
export const userData = Atom.make<AsyncResult.AsyncResult<User, Error>>(
|
|
284
302
|
AsyncResult.initial()
|
|
@@ -354,7 +372,7 @@ Use `Atom.kvs` for persisted state:
|
|
|
354
372
|
|
|
355
373
|
```typescript
|
|
356
374
|
import { BrowserKeyValueStore as BrowserKvs } from '@effect/platform-browser';
|
|
357
|
-
import * as Atom from 'effect/
|
|
375
|
+
import * as Atom from 'effect/reactivity/Atom';
|
|
358
376
|
import * as Schema from 'effect/Schema';
|
|
359
377
|
|
|
360
378
|
export const userSettings = Atom.kvs({
|
|
@@ -452,7 +470,7 @@ export const wsConnection = Atom.make(
|
|
|
452
470
|
2. **Never Manual Void Wrappers**: Don't wrap Effects in void functions—you lose `waiting` control
|
|
453
471
|
3. **Reference Stability**: Use `Atom.family` for dynamically generated atom sets
|
|
454
472
|
4. **Lazy Evaluation**: Values computed on-demand when accessed
|
|
455
|
-
5. **Automatic Cleanup**:
|
|
473
|
+
5. **Automatic Cleanup**: Unused atoms follow idle retention; `keepAlive` lasts until registry reset/disposal
|
|
456
474
|
6. **Derive, Don't Coordinate**: Use computed atoms to derive state
|
|
457
475
|
7. **Result Types**: Handle errors explicitly with AsyncResult.match
|
|
458
476
|
8. **Services in Runtime**: Wrap layers once, use in multiple atoms
|
|
@@ -467,7 +485,7 @@ export const wsConnection = Atom.make(
|
|
|
467
485
|
Use `Atom.fn` with `Effect.fnUntraced` which automatically provides `AsyncResult` with `.waiting` flag:
|
|
468
486
|
|
|
469
487
|
```typescript
|
|
470
|
-
import * as Atom from "effect/
|
|
488
|
+
import * as Atom from "effect/reactivity/Atom"
|
|
471
489
|
import { useAtomValue, useAtomSet } from "@effect/atom-react"
|
|
472
490
|
import { Effect } from "effect"
|
|
473
491
|
|
|
@@ -496,7 +514,7 @@ function UserProfile() {
|
|
|
496
514
|
|
|
497
515
|
```typescript
|
|
498
516
|
export const updateItem = runtime.fn(
|
|
499
|
-
Effect.fnUntraced(function* (id: string
|
|
517
|
+
Effect.fnUntraced(function* ({ id, updates }: { id: string; updates: Partial<Item> }) {
|
|
500
518
|
const current = yield* Atom.get(itemsAtom);
|
|
501
519
|
|
|
502
520
|
// Optimistic update
|
|
@@ -569,12 +587,32 @@ Atom.batch(() => {
|
|
|
569
587
|
registry.set(ageAtom, 30);
|
|
570
588
|
registry.set(statusAtom, 'active');
|
|
571
589
|
});
|
|
572
|
-
//
|
|
590
|
+
// Dependents observe the final batched state
|
|
573
591
|
```
|
|
574
592
|
|
|
575
593
|
Outside Effect/Atom contexts, use an `AtomRegistry` (`registry.set(...)`) inside the batch. Inside an atom or write context, use that context (`ctx.set(...)`) in the same pattern. `Atom.batch` only batches notifications; it does not introduce a free `set` function.
|
|
576
594
|
|
|
577
|
-
|
|
595
|
+
Batch synchronous writes to avoid intermediate notifications. Writes made by
|
|
596
|
+
commit listeners are processed in subsequent commit work rather than dropped,
|
|
597
|
+
so do not promise exactly one callback when listeners themselves write.
|
|
598
|
+
Batching is not rollback: writes made before a thrown exception are still
|
|
599
|
+
committed and notified, then the first failure is rethrown. Async work after an
|
|
600
|
+
`await` is outside the batch.
|
|
601
|
+
|
|
602
|
+
## Stale-while-revalidate reads
|
|
603
|
+
|
|
604
|
+
Wrap an `AsyncResult` query atom with `Atom.swr({ staleTime: '30 seconds' })`.
|
|
605
|
+
Reads return the current result and defer stale-source refresh until after the
|
|
606
|
+
read. The scheduled refresh is skipped if the source becomes fresh or the
|
|
607
|
+
wrapper is disposed; a one-shot unmounted read does not keep background work
|
|
608
|
+
alive. Mount/subscribe for a continuing query lifetime.
|
|
609
|
+
|
|
610
|
+
`revalidateOnMount` controls initial stale refreshes. `revalidateOnFocus: true`
|
|
611
|
+
respects `staleTime`, while `'always'` forces a refresh. Manual refresh always
|
|
612
|
+
forwards to the source. `staleTime` is a freshness window, distinct from idle TTL.
|
|
613
|
+
Create the wrapper once (module scope, `Atom.family`, or `useMemo`) rather than
|
|
614
|
+
on every render. `swr` returns `WithoutSerializable<R>`; retain the serializable
|
|
615
|
+
source atom for hydration or explicitly serialize the wrapper under its own key.
|
|
578
616
|
|
|
579
617
|
## AsyncResult.builder
|
|
580
618
|
|
|
@@ -14,7 +14,7 @@ Reference this for:
|
|
|
14
14
|
|
|
15
15
|
- `Request` and `Request.Class` definitions (`packages/effect/src/Request.ts`)
|
|
16
16
|
- `RequestResolver` constructors and combinators (`packages/effect/src/RequestResolver.ts`)
|
|
17
|
-
- `SqlResolver` for SQL-specific batching (`packages/effect/src/
|
|
17
|
+
- `SqlResolver` for SQL-specific batching (`packages/effect/src/sql/SqlResolver.ts`)
|
|
18
18
|
- Batching tutorial (`ai-docs/src/05_batching/10_request-resolver.ts`)
|
|
19
19
|
|
|
20
20
|
## The N+1 Problem
|
|
@@ -440,10 +440,12 @@ class Users extends Context.Service<
|
|
|
440
440
|
|
|
441
441
|
## SQL Integration with SqlResolver
|
|
442
442
|
|
|
443
|
-
`SqlResolver` (from `effect/
|
|
443
|
+
`SqlResolver` (from `effect/sql`) provides schema-validated, batched SQL resolvers.
|
|
444
|
+
It remains marked `@stability unstable` even though its import path has no
|
|
445
|
+
`unstable` segment; check its release-specific contract when upgrading. Import:
|
|
444
446
|
|
|
445
447
|
```typescript
|
|
446
|
-
import { SqlResolver } from 'effect/
|
|
448
|
+
import { SqlResolver } from 'effect/sql';
|
|
447
449
|
```
|
|
448
450
|
|
|
449
451
|
### SqlResolver.ordered
|
|
@@ -15,6 +15,9 @@ the yielded service and `get` / `contextEffect` / `contextEffectOption`: an entr
|
|
|
15
15
|
can fail when reacquired after successful preloading. Invalidating an active
|
|
16
16
|
RcMap/LayerMap entry releases it after its last borrower closes; a replacement
|
|
17
17
|
entry remains independently owned.
|
|
18
|
+
Preload keys with zero `idleTimeToLive` are skipped, including the default TTL.
|
|
19
|
+
Set a non-zero TTL when construction must eagerly acquire and validate them;
|
|
20
|
+
otherwise acquisition failures surface on first use.
|
|
18
21
|
|
|
19
22
|
The Effect v4 source is available at `~/.local/share/opencode/repos/github.com/Effect-TS/effect@main/`.
|
|
20
23
|
Browse and read files there directly to look up APIs, types, and implementations.
|
|
@@ -47,7 +50,7 @@ interface Cache<Key, A, E = never, R = never> {
|
|
|
47
50
|
How to think about it:
|
|
48
51
|
|
|
49
52
|
- **Entries store the lookup `Exit`** — successes *and failures* are cached. A failed lookup keeps failing from cache until the entry expires, is invalidated, or is refreshed.
|
|
50
|
-
- **Concurrent `get`s of the same missing key share one lookup.**
|
|
53
|
+
- **Concurrent `get`s of the same missing key share one lookup fiber.** Each caller waits independently; interrupting one waiter does not cancel work still needed by another. The last departing waiter interrupts a pending lookup.
|
|
51
54
|
- **Insertion-ordered map = LRU.** Reads move the entry to the back; when capacity is exceeded the oldest entries are evicted.
|
|
52
55
|
- **TTL is computed per entry** from the lookup `Exit` and the key, against the fiber's `Clock` — `TestClock` works. Expiry is lazy: entries are removed when next touched.
|
|
53
56
|
- **`ScopedCache`** is the same model where each entry additionally owns a `Scope`: resources acquired during the lookup live exactly as long as the entry is cached.
|
|
@@ -191,7 +194,7 @@ If an older in-flight lookup is interrupted after `set` installs a newer value,
|
|
|
191
194
|
yield* Cache.invalidate(cache, 'k'); // remove one key; no-op if absent
|
|
192
195
|
yield* Cache.invalidateAll(cache); // clear everything
|
|
193
196
|
|
|
194
|
-
// Conditional: removes
|
|
197
|
+
// Conditional: awaits a pending entry, then removes a successful value matching the predicate.
|
|
195
198
|
// Returns false for missing, expired, failed, or non-matching entries.
|
|
196
199
|
const removed: boolean = yield* Cache.invalidateWhen(cache, 'k', (v) => v.stale);
|
|
197
200
|
```
|
|
@@ -205,7 +208,7 @@ const fresh = yield* Cache.refresh(cache, 'k');
|
|
|
205
208
|
- Always invokes the lookup, even for an unexpired entry, and resets the TTL from the new exit.
|
|
206
209
|
- For an **existing** key, the old entry keeps serving `get` callers until the new lookup completes — built-in stale-while-revalidate.
|
|
207
210
|
- For a **missing** key, a pending entry is inserted immediately; concurrent `get`s wait on it.
|
|
208
|
-
-
|
|
211
|
+
- `Cache.refresh` calls are **not deduplicated** — each runs the lookup independently. `ScopedCache.refresh` of a missing key delegates to `get` and shares that pending lookup; refreshes of existing keys run independently.
|
|
209
212
|
|
|
210
213
|
---
|
|
211
214
|
|
|
@@ -234,6 +237,11 @@ yield* Cache.get(cache, 'd'); // evicts 'b'
|
|
|
234
237
|
- `Duration.infinity` (the default) means no expiry. `Duration.zero` means the entry expires immediately — effectively "do not cache this result".
|
|
235
238
|
- `refresh` and re-`get`-after-expiry both restart the TTL clock; plain `get` hits do not extend it (no sliding expiration).
|
|
236
239
|
|
|
240
|
+
`Cache.get` does not retain a zero-TTL result after completion. A synchronous
|
|
241
|
+
zero-TTL lookup does not occupy capacity and evict a live entry; a pending lookup
|
|
242
|
+
still occupies capacity while it is shared. `ScopedCache` has the distinct lazy
|
|
243
|
+
resource-release behavior described in section 8.
|
|
244
|
+
|
|
237
245
|
Per-key and per-result TTL via `makeWith`:
|
|
238
246
|
|
|
239
247
|
```ts
|
|
@@ -274,7 +282,7 @@ const robust = yield* Cache.makeWith(fetchUser, {
|
|
|
274
282
|
});
|
|
275
283
|
```
|
|
276
284
|
|
|
277
|
-
**Interruption
|
|
285
|
+
**Interruption is not cached.** Missing-key lookups run in daemon fibers, shared by their waiters. One caller's interruption leaves the lookup alive while another caller is waiting. When the last waiter leaves before completion, the lookup is interrupted and its entry removed; a later `get` starts again. `ScopedCache` also closes that lookup's entry scope. Children forked with `forkChild` inside the lookup end with the lookup fiber, not with the requesting caller; use the entry scope for resource-lifetime background work.
|
|
278
286
|
|
|
279
287
|
`getSuccess`, `values`, and `entries` skip failed entries; `getOption` and `get` propagate the cached error; `invalidateWhen` returns `false` for failed entries (the predicate only sees successes).
|
|
280
288
|
|
|
@@ -341,6 +349,10 @@ The entry's scope is closed — releasing everything the lookup acquired — whe
|
|
|
341
349
|
4. **replaced** (`set` over an existing key; `refresh` closes the old entry's scope after the new lookup completes),
|
|
342
350
|
5. **orphaned by cache close** (the owning scope closes).
|
|
343
351
|
|
|
352
|
+
A pending shared lookup also closes its entry scope when its last waiter leaves.
|
|
353
|
+
Interrupting an existing-key `refresh` closes the replacement scope and leaves
|
|
354
|
+
the previous entry intact.
|
|
355
|
+
|
|
344
356
|
```ts
|
|
345
357
|
const tracker: Array<string> = [];
|
|
346
358
|
const cache = yield* ScopedCache.make({
|
|
@@ -434,7 +446,7 @@ const cacheB = yield* Cache.make({
|
|
|
434
446
|
const row = yield* Cache.get(cacheB, 1).pipe(Effect.provideService(Db, dbImpl));
|
|
435
447
|
```
|
|
436
448
|
|
|
437
|
-
At runtime the lookup
|
|
449
|
+
At runtime the lookup sees the construction-time context merged with the initiating caller's context (the caller's services win on conflicts). The shared lookup fiber inherits that caller's tracing context. Remember the lookup runs **once per miss** — later waiters do not replace the context for a pending entry.
|
|
438
450
|
|
|
439
451
|
The same option exists on `ScopedCache.make`/`makeWith`.
|
|
440
452
|
|
|
@@ -597,9 +609,9 @@ it.effect('expires entries after the TTL', () =>
|
|
|
597
609
|
2. **v4 renames type parameters, it does not reorder them** — v3's `Cache<Key, Value, Error>` becomes `Cache<Key, A, E, R>`: same value-before-error order with a services parameter appended. Only the pre-v3 `@effect/io` era used `Cache<Key, Error, Value>`.
|
|
598
610
|
3. **`Cache.makeWith(lookup, options)` vs `ScopedCache.makeWith({ lookup, ...options })`** — `Cache.makeWith` takes the lookup as a separate first argument; `ScopedCache.makeWith` (and both `make`s) take one options object containing `lookup`.
|
|
599
611
|
4. **Failures are cached with the default infinite TTL** — one transient lookup error fails that key forever. Use `makeWith` with an exit-aware TTL (`Exit.isSuccess(exit) ? ttl : Duration.zero`).
|
|
600
|
-
5. **
|
|
612
|
+
5. **Confusing caller cancellation with lookup cancellation** — a shared missing-key lookup survives while any waiter remains. The last departing waiter interrupts pending work; interrupted entries are removed, and `ScopedCache` closes their scopes. Failure TTL policy is still needed for ordinary failures.
|
|
601
613
|
6. **`timeToLive` in `make` is a `Duration.Input` value, not a function** — the `(exit, key) => Duration.Input` form only exists on `makeWith`; passing a function to `make` won't compile.
|
|
602
|
-
7.
|
|
614
|
+
7. **Assuming all refreshes deduplicate** — `Cache.refresh` and existing-key `ScopedCache.refresh` run independently. Only missing-key `ScopedCache.refresh` delegates to the shared `get` path. Serialize expensive refreshes if needed.
|
|
603
615
|
8. **Don't mutate key objects after first use** — plain objects compare structurally in v4 (equal-content literals do hit), but `Equal`/`Hash` cache their results per object, so a mutated key misbehaves silently. Prefer primitives or immutable `Data.Class`/`Schema.Class` keys.
|
|
604
616
|
9. **Don't `Effect.acquireRelease` inside a plain `Cache` lookup** — `Scope` leaks into `R` and finalizers attach to whatever outer scope is around, not to the entry: nothing is released on eviction/invalidation. Use `ScopedCache`, which provides a per-entry scope.
|
|
605
617
|
10. **ScopedCache values die with their entry** — after `invalidate`/eviction/expiry-purge the value's finalizers have run. Don't hold the value beyond the entry's lifetime; use `effect/Pool` for checkout semantics.
|
|
@@ -20,8 +20,11 @@ scalars containing colon-whitespace or a trailing colon: quote those values.
|
|
|
20
20
|
|
|
21
21
|
## Import Pattern
|
|
22
22
|
|
|
23
|
+
The CLI APIs are marked `@stability unstable` even at the stable package release.
|
|
24
|
+
Inspect `effect@4.0.0:packages/effect/src/cli/` for this baseline's contracts.
|
|
25
|
+
|
|
23
26
|
```typescript
|
|
24
|
-
import { Argument, Command, Flag, Prompt } from 'effect/
|
|
27
|
+
import { Argument, Command, Flag, Prompt } from 'effect/cli';
|
|
25
28
|
```
|
|
26
29
|
|
|
27
30
|
Platform services and runtime for the entry point:
|
|
@@ -37,7 +40,7 @@ Positional arguments are parsed in order. Use `Flag.Boolean` for toggles or `Arg
|
|
|
37
40
|
### Constructors
|
|
38
41
|
|
|
39
42
|
```typescript
|
|
40
|
-
import { Argument } from 'effect/
|
|
43
|
+
import { Argument } from 'effect/cli';
|
|
41
44
|
|
|
42
45
|
Argument.String('name'); // string
|
|
43
46
|
Argument.Int('count'); // number (integer)
|
|
@@ -64,7 +67,7 @@ Argument.FileSchema('config', MySchema); // reads and validates file via Schema
|
|
|
64
67
|
### Combinators
|
|
65
68
|
|
|
66
69
|
```typescript
|
|
67
|
-
import { Argument } from 'effect/
|
|
70
|
+
import { Argument } from 'effect/cli';
|
|
68
71
|
|
|
69
72
|
// Description for help text
|
|
70
73
|
Argument.String('file').pipe(Argument.withDescription('Input file'));
|
|
@@ -121,10 +124,16 @@ Argument.Int('count').pipe(
|
|
|
121
124
|
|
|
122
125
|
Flags are named options with `--name` or `-alias` syntax.
|
|
123
126
|
|
|
127
|
+
Negative numeric tokens such as `-3`, `-3.70`, `-.5`, and `-1e-3` are values,
|
|
128
|
+
not short-option bundles: `--lon -3.70` works with `Flag.Finite('lon')`.
|
|
129
|
+
Use `--` to pass trailing operands that otherwise look like options; a lone
|
|
130
|
+
`-` is also retained as a value. Numeric lexing does not replace the chosen
|
|
131
|
+
primitive's validation.
|
|
132
|
+
|
|
124
133
|
### Constructors
|
|
125
134
|
|
|
126
135
|
```typescript
|
|
127
|
-
import { Flag } from 'effect/
|
|
136
|
+
import { Flag } from 'effect/cli';
|
|
128
137
|
|
|
129
138
|
Flag.Boolean('verbose'); // required: --verbose / --no-verbose; omission fails
|
|
130
139
|
Flag.String('config'); // --config value
|
|
@@ -152,7 +161,7 @@ Flag.KeyValuePair('env'); // --env FOO=bar → Record<string, string>
|
|
|
152
161
|
### Combinators
|
|
153
162
|
|
|
154
163
|
```typescript
|
|
155
|
-
import { Flag } from 'effect/
|
|
164
|
+
import { Flag } from 'effect/cli';
|
|
156
165
|
|
|
157
166
|
// Alias
|
|
158
167
|
Flag.Boolean('verbose').pipe(
|
|
@@ -217,7 +226,7 @@ Bare boolean flags are required. `--verbose` produces `true`, `--no-verbose` pro
|
|
|
217
226
|
<!-- typecheck -->
|
|
218
227
|
```typescript
|
|
219
228
|
import { Effect } from 'effect';
|
|
220
|
-
import { Prompt } from 'effect/
|
|
229
|
+
import { Prompt } from 'effect/cli';
|
|
221
230
|
|
|
222
231
|
Prompt.Int({ message: 'Count', default: 42 });
|
|
223
232
|
Prompt.File({ message: 'Pick file', default: '/workspace/config.json' });
|
|
@@ -253,7 +262,7 @@ choice values rather than pre-escaping your domain values.
|
|
|
253
262
|
|
|
254
263
|
```typescript
|
|
255
264
|
import { Console, Effect } from 'effect';
|
|
256
|
-
import { Argument, Command, Flag } from 'effect/
|
|
265
|
+
import { Argument, Command, Flag } from 'effect/cli';
|
|
257
266
|
|
|
258
267
|
// Simple command (no config, no handler)
|
|
259
268
|
const version = Command.make('version');
|
|
@@ -509,7 +518,7 @@ Command.provideSync(MyService, (config) => makeMyService(config.env));
|
|
|
509
518
|
```typescript
|
|
510
519
|
import { NodeRuntime, NodeServices } from '@effect/platform-node';
|
|
511
520
|
import { Effect } from 'effect';
|
|
512
|
-
import { Command, Flag } from 'effect/
|
|
521
|
+
import { Command, Flag } from 'effect/cli';
|
|
513
522
|
|
|
514
523
|
const myCommand = Command.make(
|
|
515
524
|
'myapp',
|