opencode-effect-enforcer 0.2.2 → 0.2.4
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 +38 -10
- package/docs/effect-4.0.0-rc.112.md +316 -0
- package/guidance/effect-first-development.md +30 -17
- package/guidance/progressive-disclosure-guidance.md +13 -0
- package/package.json +3 -2
- package/patterns/avoid-direct-tag-checks.md +8 -2
- package/patterns/avoid-react-hooks.md +18 -37
- package/patterns/effect-run-in-body.md +1 -1
- package/patterns/require-effect-concurrency.md +11 -0
- package/patterns/use-console-service.md +6 -1
- package/skills/effect-ai-language-model/SKILL.md +10 -16
- package/skills/effect-ai-prompt/SKILL.md +36 -2
- package/skills/effect-ai-provider/SKILL.md +13 -0
- package/skills/effect-ai-streaming/SKILL.md +81 -108
- package/skills/effect-ai-tool/SKILL.md +50 -87
- package/skills/effect-atom-rpc/SKILL.md +9 -2
- package/skills/effect-atom-state/SKILL.md +5 -0
- package/skills/effect-cache/SKILL.md +32 -0
- package/skills/effect-cli/SKILL.md +22 -3
- package/skills/effect-concurrency-testing/SKILL.md +7 -9
- package/skills/effect-domain-modeling/SKILL.md +208 -1169
- package/skills/effect-domain-predicates/SKILL.md +5 -6
- package/skills/effect-error-handling/SKILL.md +5 -4
- package/skills/effect-http-api/SKILL.md +12 -1
- package/skills/effect-http-client/SKILL.md +1 -1
- package/skills/effect-http-server/SKILL.md +14 -3
- package/skills/effect-layer-design/SKILL.md +22 -56
- package/skills/effect-mcp-server/SKILL.md +1 -1
- package/skills/effect-pattern-matching/SKILL.md +44 -11
- package/skills/effect-platform-abstraction/SKILL.md +1 -1
- package/skills/effect-platform-layers/SKILL.md +1 -1
- package/skills/effect-rpc-api/SKILL.md +8 -1
- package/skills/effect-rpc-client/SKILL.md +20 -6
- package/skills/effect-rpc-cluster/SKILL.md +44 -14
- package/skills/effect-rpc-server/SKILL.md +32 -5
- package/skills/effect-scheduling/SKILL.md +1 -1
- package/skills/effect-schema-composition/SKILL.md +69 -15
- package/skills/effect-schema-v4/SKILL.md +43 -1
- package/skills/effect-scope/SKILL.md +30 -0
- package/skills/effect-service-implementation/SKILL.md +10 -4
- package/skills/effect-socket/SKILL.md +5 -5
- package/skills/effect-sql/SKILL.md +22 -0
- package/skills/effect-stream/SKILL.md +32 -1
- package/skills/effect-testing/SKILL.md +39 -31
- package/skills/effect-workflow/SKILL.md +6 -0
- package/patterns/vm-in-wrong-file.md +0 -51
- package/skills/effect-react-vm/SKILL.md +0 -675
|
@@ -91,7 +91,7 @@ const GetCurrentTime = Tool.make('GetCurrentTime', {
|
|
|
91
91
|
success: Schema.Number
|
|
92
92
|
});
|
|
93
93
|
|
|
94
|
-
|
|
94
|
+
type Result = Tool.Success<typeof GetCurrentTime>;
|
|
95
95
|
```
|
|
96
96
|
|
|
97
97
|
**Key Pattern: Tool.make**
|
|
@@ -234,13 +234,13 @@ const QueryDatabase = Tool.make('QueryDatabase', {
|
|
|
234
234
|
dependencies: [Database]
|
|
235
235
|
});
|
|
236
236
|
|
|
237
|
-
type Requirements = Tool.
|
|
237
|
+
type Requirements = Tool.HandlerServices<typeof QueryDatabase>;
|
|
238
238
|
```
|
|
239
239
|
|
|
240
240
|
**Key Pattern: dependencies**
|
|
241
241
|
|
|
242
242
|
- Array of service tags
|
|
243
|
-
-
|
|
243
|
+
- `Tool.HandlerServices<T>` includes declared dependencies, parameter-decoding services, and result-encoding services
|
|
244
244
|
- Must be provided when creating handlers
|
|
245
245
|
|
|
246
246
|
## Creating Toolkits
|
|
@@ -392,12 +392,12 @@ const toolkitLayer = toolkit.toLayer({
|
|
|
392
392
|
});
|
|
393
393
|
|
|
394
394
|
const program = Effect.gen(function* () {
|
|
395
|
-
const handlers = yield*
|
|
395
|
+
const handlers = yield* toolkit;
|
|
396
396
|
const resultStream = yield* handlers.handle('GetWeather', { location: 'NYC' });
|
|
397
397
|
return resultStream;
|
|
398
|
-
}).pipe(Effect.provide(WeatherServiceLive));
|
|
398
|
+
}).pipe(Effect.provide(toolkitLayer), Effect.provide(WeatherServiceLive));
|
|
399
399
|
|
|
400
|
-
declare const WeatherServiceLive: Layer<WeatherService>;
|
|
400
|
+
declare const WeatherServiceLive: Layer.Layer<WeatherService>;
|
|
401
401
|
```
|
|
402
402
|
|
|
403
403
|
**Key Pattern: Handler Context**
|
|
@@ -666,7 +666,7 @@ Approved calls execute on the next model call; denied calls are converted to fai
|
|
|
666
666
|
import { Effect, Stream } from 'effect';
|
|
667
667
|
|
|
668
668
|
const program = Effect.gen(function* () {
|
|
669
|
-
const toolkit = yield*
|
|
669
|
+
const toolkit = yield* MyToolkit;
|
|
670
670
|
|
|
671
671
|
const resultStream = yield* toolkit.handle('GetWeather', {
|
|
672
672
|
location: 'San Francisco'
|
|
@@ -823,7 +823,7 @@ import { Effect, Match } from 'effect';
|
|
|
823
823
|
|
|
824
824
|
const executeTool = (toolName: string, params: unknown) =>
|
|
825
825
|
Effect.gen(function* () {
|
|
826
|
-
const toolkit = yield*
|
|
826
|
+
const toolkit = yield* MyToolkit;
|
|
827
827
|
|
|
828
828
|
const handler = Match.value(toolName).pipe(
|
|
829
829
|
Match.when('GetWeather', () =>
|
|
@@ -843,10 +843,12 @@ const executeTool = (toolName: string, params: unknown) =>
|
|
|
843
843
|
|
|
844
844
|
## Complete Example
|
|
845
845
|
|
|
846
|
+
<!-- typecheck -->
|
|
846
847
|
```typescript
|
|
847
848
|
import * as Tool from 'effect/unstable/ai/Tool';
|
|
848
849
|
import * as Toolkit from 'effect/unstable/ai/Toolkit';
|
|
849
|
-
import
|
|
850
|
+
import * as Schema from 'effect/Schema';
|
|
851
|
+
import { Clock, Context, Effect, Layer, Stream } from 'effect';
|
|
850
852
|
|
|
851
853
|
class UserNotFound extends Schema.TaggedError<UserNotFound>()(
|
|
852
854
|
'UserNotFound',
|
|
@@ -855,40 +857,27 @@ class UserNotFound extends Schema.TaggedError<UserNotFound>()(
|
|
|
855
857
|
}
|
|
856
858
|
) {}
|
|
857
859
|
|
|
858
|
-
class
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
860
|
+
class User extends Schema.Class<User>('User')({
|
|
861
|
+
id: Schema.String,
|
|
862
|
+
name: Schema.String,
|
|
863
|
+
email: Schema.String
|
|
864
|
+
}) {}
|
|
865
|
+
|
|
866
|
+
class UserQuery extends Schema.Class<UserQuery>('UserQuery')({
|
|
867
|
+
userId: Schema.String
|
|
868
|
+
}) {}
|
|
869
|
+
|
|
870
|
+
class Users extends Context.Service<Users, {
|
|
871
|
+
readonly get: (id: string) => Effect.Effect<User, UserNotFound>;
|
|
872
|
+
}>()('app/Users') {}
|
|
864
873
|
|
|
865
874
|
const GetUser = Tool.make('GetUser', {
|
|
866
875
|
description: 'Retrieve user information by ID',
|
|
867
|
-
parameters:
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
success: Schema.Struct({
|
|
871
|
-
id: Schema.String,
|
|
872
|
-
name: Schema.String,
|
|
873
|
-
email: Schema.String
|
|
874
|
-
}),
|
|
875
|
-
failure: Schema.instanceOf(UserNotFound),
|
|
876
|
+
parameters: UserQuery,
|
|
877
|
+
success: User,
|
|
878
|
+
failure: UserNotFound,
|
|
876
879
|
failureMode: 'error',
|
|
877
|
-
dependencies: [
|
|
878
|
-
});
|
|
879
|
-
|
|
880
|
-
const CreateUser = Tool.make('CreateUser', {
|
|
881
|
-
description: 'Create a new user',
|
|
882
|
-
parameters: Schema.Struct({
|
|
883
|
-
name: Schema.String,
|
|
884
|
-
email: Schema.String
|
|
885
|
-
}),
|
|
886
|
-
success: Schema.Struct({
|
|
887
|
-
id: Schema.String,
|
|
888
|
-
name: Schema.String,
|
|
889
|
-
email: Schema.String
|
|
890
|
-
}),
|
|
891
|
-
dependencies: [Database]
|
|
880
|
+
dependencies: [Users]
|
|
892
881
|
});
|
|
893
882
|
|
|
894
883
|
const GetCurrentTime = Tool.make('GetCurrentTime', {
|
|
@@ -896,58 +885,26 @@ const GetCurrentTime = Tool.make('GetCurrentTime', {
|
|
|
896
885
|
success: Schema.Number
|
|
897
886
|
});
|
|
898
887
|
|
|
899
|
-
const UserToolkit = Toolkit.make(GetUser,
|
|
888
|
+
const UserToolkit = Toolkit.make(GetUser, GetCurrentTime);
|
|
900
889
|
|
|
901
890
|
const UserToolkitLive = UserToolkit.toLayer({
|
|
902
|
-
GetUser: ({ userId })
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
userId
|
|
908
|
-
);
|
|
909
|
-
|
|
910
|
-
if (!user) {
|
|
911
|
-
return yield* Effect.fail(new UserNotFound({ userId }));
|
|
912
|
-
}
|
|
913
|
-
|
|
914
|
-
return user as { id: string; name: string; email: string };
|
|
915
|
-
}),
|
|
916
|
-
|
|
917
|
-
CreateUser: ({ name, email }) =>
|
|
918
|
-
Effect.gen(function* () {
|
|
919
|
-
const db = yield* Database;
|
|
920
|
-
const id = crypto.randomUUID();
|
|
921
|
-
|
|
922
|
-
yield* db.query(
|
|
923
|
-
`INSERT INTO users (id, name, email) VALUES (?, ?, ?)`,
|
|
924
|
-
id,
|
|
925
|
-
name,
|
|
926
|
-
email
|
|
927
|
-
);
|
|
928
|
-
|
|
929
|
-
return { id, name, email };
|
|
930
|
-
}),
|
|
931
|
-
|
|
932
|
-
GetCurrentTime: () => Effect.succeed(Date.now())
|
|
891
|
+
GetUser: Effect.fn('Tools.GetUser')(function* ({ userId }) {
|
|
892
|
+
const users = yield* Users;
|
|
893
|
+
return yield* users.get(userId);
|
|
894
|
+
}),
|
|
895
|
+
GetCurrentTime: Effect.fn('Tools.GetCurrentTime')(() => Clock.currentTimeMillis)
|
|
933
896
|
});
|
|
934
897
|
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
898
|
+
// A complete, typed demo implementation; production supplies a repository adapter.
|
|
899
|
+
const exampleUser = new User({ id: 'user-123', name: 'Alice', email: 'alice@example.com' });
|
|
900
|
+
const UsersTest = Layer.succeed(Users, Users.of({
|
|
901
|
+
get: Effect.fn('Users.get')((userId: string): Effect.Effect<User, UserNotFound> => userId === exampleUser.id
|
|
902
|
+
? Effect.succeed(exampleUser)
|
|
903
|
+
: Effect.fail(new UserNotFound({ userId })))
|
|
904
|
+
}));
|
|
939
905
|
|
|
940
906
|
const program = Effect.gen(function* () {
|
|
941
|
-
const toolkit = yield*
|
|
942
|
-
|
|
943
|
-
const createStream = yield* toolkit.handle('CreateUser', {
|
|
944
|
-
name: 'Alice',
|
|
945
|
-
email: 'alice@example.com'
|
|
946
|
-
});
|
|
947
|
-
|
|
948
|
-
yield* createStream.pipe(
|
|
949
|
-
Stream.runForEach((result) => Effect.log('Created user:', result.result))
|
|
950
|
-
);
|
|
907
|
+
const toolkit = yield* UserToolkit;
|
|
951
908
|
|
|
952
909
|
const getStream = yield* toolkit.handle('GetUser', {
|
|
953
910
|
userId: 'user-123'
|
|
@@ -962,9 +919,15 @@ const program = Effect.gen(function* () {
|
|
|
962
919
|
yield* timeStream.pipe(
|
|
963
920
|
Stream.runForEach((result) => Effect.log('Current time:', result.result))
|
|
964
921
|
);
|
|
965
|
-
}).pipe(Effect.provide(
|
|
922
|
+
}).pipe(Effect.provide(UserToolkitLive), Effect.provide(UsersTest));
|
|
966
923
|
```
|
|
967
924
|
|
|
925
|
+
Yield the **Toolkit**, then provide its handler **Layer**. A Layer is not a
|
|
926
|
+
yieldable toolkit instance. Encode tagged failures with the error schema itself
|
|
927
|
+
rather than `Schema.instanceOf(ErrorClass)`, which only checks an existing
|
|
928
|
+
instance and cannot reconstruct it from JSON. Handlers get decoded class values;
|
|
929
|
+
manual `handle` calls accept their encoded representation.
|
|
930
|
+
|
|
968
931
|
## Import Patterns
|
|
969
932
|
|
|
970
933
|
**CRITICAL**: Always use namespace imports:
|
|
@@ -1005,7 +968,7 @@ import { make as makeToolkit } from 'effect/unstable/ai/Toolkit';
|
|
|
1005
968
|
- [ ] Tool.Readonly annotation for read-only tools
|
|
1006
969
|
- [ ] Tool.Destructive annotation for mutating operations
|
|
1007
970
|
- [ ] Tool.Idempotent annotation for safe retries
|
|
1008
|
-
- [ ] Custom annotations via
|
|
971
|
+
- [ ] Custom annotations via the tool's `.annotate` method
|
|
1009
972
|
- [ ] Provider-defined tools for native provider features
|
|
1010
973
|
- [ ] Toolkit.make with all tools (preferred in v4) or Toolkit.merge for combining tool collections
|
|
1011
974
|
- [ ] Error handling with catchTag in handlers
|
|
@@ -18,7 +18,14 @@ You are an Effect TypeScript expert specializing in `effect/unstable/reactivity/
|
|
|
18
18
|
Use this skill when building React (or Atom-based) frontends that consume an existing `RpcGroup`.
|
|
19
19
|
|
|
20
20
|
For the underlying RPC definitions, see the `effect-rpc-cluster` skill.
|
|
21
|
-
For Atom fundamentals (`Atom.make`, `family`, `keepAlive`, `AsyncResult`, hydration), see the `effect-atom-state`
|
|
21
|
+
For Atom fundamentals (`Atom.make`, `family`, `keepAlive`, `AsyncResult`, hydration), see the `effect-atom-state` skill.
|
|
22
|
+
|
|
23
|
+
The underlying RPC protocols require `codecFor` in rc.112. Built-in protocol
|
|
24
|
+
layers supply it; forward it when implementing a custom transport. You can pair
|
|
25
|
+
`RpcSerialization.layerSchemaBinary` on client/server without changing query or
|
|
26
|
+
mutation call sites; see `effect-rpc-client` for frame limits and compatibility.
|
|
27
|
+
Workflow proxy discard RPCs now return execution IDs, so their mutation success
|
|
28
|
+
type is `string`. This is distinct from the client's `{ discard: true }` option.
|
|
22
29
|
|
|
23
30
|
## Effect Source Reference
|
|
24
31
|
|
|
@@ -485,4 +492,4 @@ The `query`/`mutation` arguments differ (you pass `groupName, endpointName, requ
|
|
|
485
492
|
- Use `AsyncResult.builder(...).onWaiting(...).onError(...).onSuccess(...)` (or `AsyncResult.matchWithWaiting`) to render — never check `.waiting` and `.error` ad-hoc. Reserve `.onFailure((cause) => ...)` for whole-`Cause` fallbacks after typed error branches.
|
|
486
493
|
- For protocol layers that depend on auth tokens or other reactive state, use the `(get) => Layer` form of `protocol` so the client rebuilds when those atoms change.
|
|
487
494
|
- For typed error recovery, handle declared RPC errors, RPC middleware wire errors, and `RpcClientError`; branch on `_tag` only when the relevant errors are tagged.
|
|
488
|
-
- Cross-link to `effect-atom-state`
|
|
495
|
+
- Cross-link to `effect-atom-state` for atom-side patterns; this skill covers only the RPC bridge.
|
|
@@ -7,6 +7,11 @@ 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
|
|
11
|
+
was relaxed). This does not add React 18 support. Keep the adapter aligned with
|
|
12
|
+
the Effect release; core atoms still live in `effect/unstable/reactivity`, and
|
|
13
|
+
React bindings live in `@effect/atom-react` (`packages/atom/react` upstream).
|
|
14
|
+
|
|
10
15
|
## Effect Source Reference
|
|
11
16
|
|
|
12
17
|
The Effect v4 source is available at `~/.local/share/opencode/repos/github.com/Effect-TS/effect@main/`.
|
|
@@ -50,6 +50,7 @@ Choosing the right tool:
|
|
|
50
50
|
| Cache one value, no key | `Effect.cached` / `cachedWithTTL` / `cachedInvalidateWithTTL` (section 10) |
|
|
51
51
|
| Cache values by key | `Cache` |
|
|
52
52
|
| Cached value owns resources (connection, file handle, subprocess) | `ScopedCache` |
|
|
53
|
+
| Keyed resource retained until each caller's scope releases it | `RcMap` / `LayerMap` |
|
|
53
54
|
| Batch + deduplicate request-shaped fetches | see the effect-batching skill (`RequestResolver.withCache` / `asCache`) |
|
|
54
55
|
| Pool of N interchangeable resources checked out per use | `effect/Pool` (not keyed caching) |
|
|
55
56
|
| Scope/finalizer fundamentals | see the effect-scope skill |
|
|
@@ -362,6 +363,37 @@ The full method surface (`get`, `getOption`, `getSuccess`, `set`, `has`, `invali
|
|
|
362
363
|
|
|
363
364
|
## 9. Services in Lookups (requireServicesAt)
|
|
364
365
|
|
|
366
|
+
### Retain an existing keyed resource (rc.112)
|
|
367
|
+
|
|
368
|
+
`RcMap.getOption(map, key)` atomically retains a cached entry for the caller's
|
|
369
|
+
`Scope` before awaiting it. It returns `Option.none()` if the entry is missing or
|
|
370
|
+
the map is closed and never starts a lookup for a missing key. An existing
|
|
371
|
+
in-flight entry is awaited; cached/in-flight failures still fail with `E`.
|
|
372
|
+
This is not a non-blocking success-only peek and does not turn failures into absence.
|
|
373
|
+
|
|
374
|
+
Use `LayerMap.contextEffectOption(key)` for the same behavior over keyed layer
|
|
375
|
+
contexts. Its service-class accessor additionally requires the LayerMap service.
|
|
376
|
+
`has` followed by `get` is not an atomic substitute: the entry can disappear
|
|
377
|
+
between calls and `get` may allocate a replacement. Keep the retained resource's
|
|
378
|
+
use inside the caller scope. See `effect-scope` for `Pool.use` when resources are
|
|
379
|
+
interchangeable rather than keyed.
|
|
380
|
+
|
|
381
|
+
<!-- typecheck -->
|
|
382
|
+
```ts
|
|
383
|
+
import { Duration, Effect, Option, RcMap } from 'effect';
|
|
384
|
+
|
|
385
|
+
const program = Effect.gen(function* () {
|
|
386
|
+
const resources = yield* RcMap.make({
|
|
387
|
+
lookup: (key: string) => Effect.succeed(`resource:${key}`),
|
|
388
|
+
idleTimeToLive: Duration.minutes(1)
|
|
389
|
+
});
|
|
390
|
+
const missing = yield* RcMap.getOption(resources, 'database');
|
|
391
|
+
yield* Effect.scoped(RcMap.get(resources, 'database'));
|
|
392
|
+
const retained = yield* RcMap.getOption(resources, 'database');
|
|
393
|
+
return { missing: Option.isNone(missing), retained };
|
|
394
|
+
}).pipe(Effect.scoped);
|
|
395
|
+
```
|
|
396
|
+
|
|
365
397
|
Lookups can use services. `requireServicesAt` controls *where* the type system demands them:
|
|
366
398
|
|
|
367
399
|
- **default (`'construction'` behavior)**: `Cache.make` itself requires `R`; services are captured when the cache is built, and the cache type is `Cache<Key, A, E, never>` — `get` needs nothing.
|
|
@@ -201,20 +201,39 @@ Hidden flags parse normally, but generated help, shell completions, and typo sug
|
|
|
201
201
|
|
|
202
202
|
Bare boolean flags are required. `--verbose` produces `true`, `--no-verbose` produces `false`, and omission produces `CliError.MissingOption`. Add `Flag.withDefault(false)` for ordinary opt-in switch behavior, or use `Flag.optional` / a config or prompt fallback when absence has separate meaning.
|
|
203
203
|
|
|
204
|
-
### Prompt Defaults and
|
|
204
|
+
### Prompt Defaults and Themes
|
|
205
205
|
|
|
206
|
+
<!-- typecheck -->
|
|
206
207
|
```typescript
|
|
208
|
+
import { Effect } from 'effect';
|
|
207
209
|
import { Prompt } from 'effect/unstable/cli';
|
|
208
210
|
|
|
209
211
|
Prompt.integer({ message: 'Count', default: 42 });
|
|
210
212
|
Prompt.file({ message: 'Pick file', default: '/workspace/config.json' });
|
|
211
213
|
|
|
212
|
-
//
|
|
213
|
-
Prompt.text({ message: 'Name', prefix: '>' });
|
|
214
|
+
// A local override merges over the context theme.
|
|
215
|
+
const name = Prompt.text({ message: 'Name', theme: { prefix: '>' } });
|
|
216
|
+
|
|
217
|
+
// Provide once to theme all prompts in a command or application.
|
|
218
|
+
const themed = name.pipe(
|
|
219
|
+
Effect.provideService(Prompt.Theme, Prompt.makeTheme({ tick: '✓' }))
|
|
220
|
+
);
|
|
214
221
|
```
|
|
215
222
|
|
|
216
223
|
Integer prompt defaults are editable and Enter submits the default if unchanged. `Prompt.file` resolves/selects the default as the initial path.
|
|
217
224
|
|
|
225
|
+
In rc.112, per-prompt `prefix` is replaced by `theme?: Partial<Prompt.Theme>`.
|
|
226
|
+
`Prompt.Theme` is a context reference with platform defaults; `Prompt.makeTheme`
|
|
227
|
+
builds a complete theme. Local fields override the context theme. Theme fields
|
|
228
|
+
include prompt symbols, `passwordMask`, and ANSI color values. The default
|
|
229
|
+
prefix is `?`; `theme: { prefix: '' }` omits it.
|
|
230
|
+
|
|
231
|
+
`autoComplete` and `file` now insert `j`/`k` into filter text. Navigate with arrow
|
|
232
|
+
keys (or Ctrl-P/Ctrl-K for up, Ctrl-N for down). Wizard command output redacts
|
|
233
|
+
password prompt values. Completion generators preserve quoted/spaced/Unicode
|
|
234
|
+
choices across Bash (including 3.2), Fish, and Zsh; let the generator escape
|
|
235
|
+
choice values rather than pre-escaping your domain values.
|
|
236
|
+
|
|
218
237
|
## Commands
|
|
219
238
|
|
|
220
239
|
### Creating Commands
|
|
@@ -45,7 +45,7 @@ it.effect('fiber polling with yieldNow', () =>
|
|
|
45
45
|
|
|
46
46
|
yield* latch.open;
|
|
47
47
|
|
|
48
|
-
expect(yield*
|
|
48
|
+
expect(yield* Fiber.await(fiber)).toEqual(Exit.void);
|
|
49
49
|
})
|
|
50
50
|
);
|
|
51
51
|
```
|
|
@@ -212,12 +212,12 @@ it.effect('should publish user created event', () =>
|
|
|
212
212
|
type: 'created',
|
|
213
213
|
userId: 'user-123'
|
|
214
214
|
});
|
|
215
|
-
})
|
|
215
|
+
}).pipe(
|
|
216
|
+
Effect.provide(UserServiceLive),
|
|
217
|
+
Effect.provide(Layer.succeed(EventBus, pubsub))
|
|
218
|
+
)
|
|
216
219
|
);
|
|
217
|
-
})
|
|
218
|
-
Effect.provide(UserServiceLive),
|
|
219
|
-
Effect.provide(Layer.succeed(EventBus, pubsub))
|
|
220
|
-
)
|
|
220
|
+
})
|
|
221
221
|
);
|
|
222
222
|
```
|
|
223
223
|
|
|
@@ -333,9 +333,7 @@ it.effect('subscriptions are interruptible', () =>
|
|
|
333
333
|
|
|
334
334
|
const result = yield* Fiber.await(fiber);
|
|
335
335
|
|
|
336
|
-
expect(Exit.
|
|
337
|
-
true
|
|
338
|
-
);
|
|
336
|
+
expect(Exit.hasInterrupts(result)).toBe(true);
|
|
339
337
|
})
|
|
340
338
|
);
|
|
341
339
|
```
|