opencode-effect-enforcer 0.2.5 → 0.2.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +42 -140
- package/docs/effect-4.0.0-rc.116-changelog.md +2654 -0
- package/docs/effect-4.0.0-rc.116.md +102 -0
- package/guidance/effect-first-development.md +23 -14
- package/guidance/progressive-disclosure-guidance.md +3 -3
- package/package.json +2 -2
- package/patterns/avoid-direct-tag-checks.md +1 -1
- package/patterns/avoid-process-env.md +4 -4
- package/patterns/context-tag-extends.md +4 -4
- package/patterns/prefer-redacted-config.md +10 -10
- package/patterns/require-effect-concurrency.md +1 -1
- package/skills/effect-ai-chat/SKILL.md +2 -2
- package/skills/effect-ai-language-model/SKILL.md +36 -4
- package/skills/effect-ai-prompt/SKILL.md +1 -1
- package/skills/effect-ai-provider/SKILL.md +23 -12
- package/skills/effect-ai-tool/SKILL.md +13 -0
- package/skills/effect-atom-rpc/SKILL.md +7 -1
- package/skills/effect-atom-state/SKILL.md +8 -2
- package/skills/effect-cache/SKILL.md +10 -1
- package/skills/effect-cli/SKILL.md +105 -94
- package/skills/effect-command-executor/SKILL.md +7 -1
- package/skills/effect-config/SKILL.md +67 -44
- package/skills/effect-domain-modeling/SKILL.md +3 -3
- package/skills/effect-error-handling/SKILL.md +2 -2
- package/skills/effect-fiber/SKILL.md +2 -2
- package/skills/effect-filesystem/SKILL.md +34 -4
- package/skills/effect-http-api/SKILL.md +17 -2
- package/skills/effect-http-client/SKILL.md +11 -2
- package/skills/effect-http-server/SKILL.md +28 -11
- package/skills/effect-layer-design/SKILL.md +6 -2
- package/skills/effect-mcp-server/SKILL.md +21 -4
- package/skills/effect-observability/SKILL.md +2 -2
- package/skills/effect-optics/SKILL.md +1 -1
- package/skills/effect-parallelization/SKILL.md +2 -2
- package/skills/effect-pattern-matching/SKILL.md +1 -1
- package/skills/effect-platform-abstraction/SKILL.md +3 -3
- package/skills/effect-rpc-api/SKILL.md +3 -3
- package/skills/effect-rpc-client/SKILL.md +14 -13
- package/skills/effect-rpc-cluster/SKILL.md +15 -12
- package/skills/effect-rpc-server/SKILL.md +11 -13
- package/skills/effect-scheduling/SKILL.md +7 -0
- package/skills/effect-schema-composition/SKILL.md +12 -4
- package/skills/effect-schema-v4/SKILL.md +75 -20
- package/skills/effect-scope/SKILL.md +4 -4
- package/skills/effect-socket/SKILL.md +161 -658
- package/skills/effect-sql/SKILL.md +50 -12
- package/skills/effect-stream/SKILL.md +19 -24
- package/skills/effect-testing/SKILL.md +35 -22
- package/skills/effect-workflow/SKILL.md +13 -2
|
@@ -560,7 +560,7 @@ Effect SQL uses driver-specific packages that provide `SqlClient` layers.
|
|
|
560
560
|
|
|
561
561
|
| Package | Database |
|
|
562
562
|
| ------------------------- | ------------------------------------ |
|
|
563
|
-
| `@effect/sql-pg` | PostgreSQL (
|
|
563
|
+
| `@effect/sql-pg` | PostgreSQL (native Effect protocol client) |
|
|
564
564
|
| `@effect/sql-pglite` | Embedded PostgreSQL/PGlite |
|
|
565
565
|
| `@effect/sql-mysql2` | MySQL (via `mysql2`) |
|
|
566
566
|
| `@effect/sql-sqlite-node` | SQLite (via `better-sqlite3`) |
|
|
@@ -591,14 +591,14 @@ const DatabaseLayer = PgClient.layer({
|
|
|
591
591
|
|
|
592
592
|
// From Config (reads from environment/config provider)
|
|
593
593
|
const DatabaseLayerConfig = PgClient.layerConfig({
|
|
594
|
-
url: Config.
|
|
594
|
+
url: Config.Redacted('DATABASE_URL')
|
|
595
595
|
});
|
|
596
596
|
|
|
597
597
|
// The layer provides both PgClient and SqlClient services
|
|
598
598
|
const program = Effect.gen(function* () {
|
|
599
599
|
const sql = yield* SqlClient; // generic interface
|
|
600
600
|
// or
|
|
601
|
-
const pg = yield* PgClient; //
|
|
601
|
+
const pg = yield* PgClient.PgClient; // PostgreSQL-specific service
|
|
602
602
|
});
|
|
603
603
|
|
|
604
604
|
const main = program.pipe(Effect.provide(DatabaseLayer));
|
|
@@ -606,10 +606,35 @@ const main = program.pipe(Effect.provide(DatabaseLayer));
|
|
|
606
606
|
|
|
607
607
|
### PgClient-Specific Features
|
|
608
608
|
|
|
609
|
-
###
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
609
|
+
### Native PostgreSQL client and codecs
|
|
610
|
+
|
|
611
|
+
`PgClient` uses `PgConnection` and `PgPool` for native binary queries, prepared
|
|
612
|
+
statements, pipelining, streaming, cancellation, and notifications. Use `make`
|
|
613
|
+
for a pool or `makeClient` for one connection. `types` accepts a `PgTypes.Registry`.
|
|
614
|
+
Bind JSON explicitly with `sql.json`, and send one statement per query string.
|
|
615
|
+
Named preparation is enabled by default; set `prepare: false` for incompatible
|
|
616
|
+
poolers, or use statement-level `unprepared` / `valuesUnprepared`.
|
|
617
|
+
|
|
618
|
+
Decode rows according to the actual driver boundary: `int8` is `bigint`, `date`
|
|
619
|
+
is a string, timestamps are `Date` (millisecond precision), and `bytea` is
|
|
620
|
+
`Uint8Array`. Infinite/out-of-range timestamps decode to invalid Dates; validate
|
|
621
|
+
before constructing domain instants. Timestamp encoders accept Date or epoch
|
|
622
|
+
milliseconds; invalid Date encoding fails. A Date binds as `timestamptz`, so use
|
|
623
|
+
UTC session time or `PgTypes.timestamp(value)` for UTC fields in a timestamp column.
|
|
624
|
+
`executeRaw` returns `PgConnection.Result`.
|
|
625
|
+
|
|
626
|
+
Unregistered OIDs decode as UTF-8 text, suitable for scalar enum labels but not
|
|
627
|
+
arbitrary binary types. Register custom scalar/array codecs through
|
|
628
|
+
`PgTypes.makeRegistry().register(elementOid, codec, { arrayOid })` and pass the
|
|
629
|
+
registry as `types`. Decode failures close the connection and fail pending work.
|
|
630
|
+
`inet` uses `IpInterface`; `cidr` uses `IpNetwork` and rejects host bits.
|
|
631
|
+
|
|
632
|
+
Use structured `startupParameters` and opaque `startupOptions` for per-connection
|
|
633
|
+
session defaults. Passwords may be infallible, service-free Effects reevaluated
|
|
634
|
+
per physical connection. `sslmode=prefer` and `allow` try TLS first and fall back
|
|
635
|
+
only when the server declines SSLRequest; handshake/certificate failures remain
|
|
636
|
+
fatal. Explicit SSL options take precedence. `maxMessageSize` defaults to 16 MiB;
|
|
637
|
+
when multiplexing is enabled, `multiplexConcurrency` defaults to 32.
|
|
613
638
|
|
|
614
639
|
- `PgProtocol`: PostgreSQL 3.0 frontend encoding and incremental backend-frame
|
|
615
640
|
parsing. Stateful parser failures are terminal and synchronous; lift a parser
|
|
@@ -629,16 +654,29 @@ keep the Effect-family package versions aligned when upgrading those adapters.
|
|
|
629
654
|
### PgClient JSON and Notifications
|
|
630
655
|
|
|
631
656
|
```ts
|
|
632
|
-
const pg = yield* PgClient;
|
|
657
|
+
const pg = yield* PgClient.PgClient;
|
|
633
658
|
|
|
634
659
|
// JSON parameter helper
|
|
635
660
|
sql`INSERT INTO data ${sql.insert({ metadata: pg.json({ key: 'value' }) })}`;
|
|
636
661
|
|
|
637
662
|
// LISTEN/NOTIFY
|
|
638
|
-
const notifications = pg.listen('my_channel'); //
|
|
663
|
+
const notifications = yield* pg.listen('my_channel'); // scoped Dequeue<string, SqlError>
|
|
639
664
|
yield* pg.notify('my_channel', 'hello');
|
|
665
|
+
const messages = Stream.fromQueue(notifications);
|
|
640
666
|
```
|
|
641
667
|
|
|
668
|
+
Listener acquisition completes after registration, giving an explicit readiness
|
|
669
|
+
boundary. PostgreSQL listener queues carry `SqlError` when the connection fails;
|
|
670
|
+
retry a scoped effect that reacquires the listener, not the failed queue. Intentional
|
|
671
|
+
scope closure interrupts consumers. PGlite also returns a scoped notification dequeue.
|
|
672
|
+
|
|
673
|
+
`SqlModel.makeResolvers().insert` requires both input encoding and row decoding
|
|
674
|
+
services; `insertVoid` needs only input encoding services. D1 statement `.raw`
|
|
675
|
+
returns the complete native result; read `.results` for rows. Durable Object SQL
|
|
676
|
+
transactions support nested child rollback but no explicit async operations inside
|
|
677
|
+
the transaction. Use `Statement.SpanPropagationEnabled` to opt into driver span
|
|
678
|
+
parenting under `sql.execute`.
|
|
679
|
+
|
|
642
680
|
### PGlite Setup
|
|
643
681
|
|
|
644
682
|
Use `@effect/sql-pglite` for embedded PostgreSQL-compatible databases backed by `@electric-sql/pglite`. Its layer provides both the PGlite-specific service and the generic `SqlClient` service.
|
|
@@ -654,7 +692,7 @@ const PgliteLayer = PgliteClient.layer({
|
|
|
654
692
|
});
|
|
655
693
|
|
|
656
694
|
const PgliteLayerConfig = PgliteClient.layerConfig({
|
|
657
|
-
dataDir: Config.
|
|
695
|
+
dataDir: Config.String('PGLITE_DATA_DIR')
|
|
658
696
|
});
|
|
659
697
|
|
|
660
698
|
const program = Effect.gen(function* () {
|
|
@@ -662,7 +700,7 @@ const program = Effect.gen(function* () {
|
|
|
662
700
|
const pglite = yield* PgliteClient.PgliteClient;
|
|
663
701
|
|
|
664
702
|
yield* sql`INSERT INTO data ${sql.insert({ metadata: pglite.json({ key: 'value' }) })}`;
|
|
665
|
-
const notifications = pglite.listen('my_channel');
|
|
703
|
+
const notifications = yield* pglite.listen('my_channel');
|
|
666
704
|
yield* pglite.notify('my_channel', 'hello');
|
|
667
705
|
yield* pglite.refreshArrayTypes;
|
|
668
706
|
const snapshot = yield* pglite.dumpDataDir('gzip');
|
|
@@ -704,7 +742,7 @@ yield*
|
|
|
704
742
|
Stream.runCollect
|
|
705
743
|
);
|
|
706
744
|
|
|
707
|
-
//
|
|
745
|
+
// Streaming uses the driver's bounded result batching.
|
|
708
746
|
```
|
|
709
747
|
|
|
710
748
|
## Error Handling
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: effect-stream
|
|
3
|
-
description: Build effectful pull-based streaming pipelines with Effect Stream — creation, transformation, consumption, NDJSON/
|
|
3
|
+
description: Build effectful pull-based streaming pipelines with Effect Stream — creation, transformation, consumption, NDJSON/SchemaBinary encoding, concurrency, resource safety. Use when working with values produced over time, paginated APIs, event listeners, or streaming I/O.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
You are an Effect TypeScript expert specializing in pull-based streaming with `Stream`, `Sink`, and `Channel`.
|
|
@@ -15,7 +15,7 @@ Reference this for:
|
|
|
15
15
|
- Stream constructors and combinators (`packages/effect/src/Stream.ts`)
|
|
16
16
|
- Creating streams from various sources (`ai-docs/src/02_stream/10_creating-streams.ts`)
|
|
17
17
|
- Consuming and transforming streams (`ai-docs/src/02_stream/20_consuming-streams.ts`)
|
|
18
|
-
- Encoding/decoding with NDJSON and
|
|
18
|
+
- Encoding/decoding with NDJSON and SchemaBinary (`packages/effect/src/unstable/encoding/`)
|
|
19
19
|
|
|
20
20
|
## Core Model
|
|
21
21
|
|
|
@@ -25,7 +25,7 @@ Use a stream when values are naturally many-valued and ordered over time. For on
|
|
|
25
25
|
|
|
26
26
|
```ts
|
|
27
27
|
import { Effect, Schedule, Schema, Sink, Stream } from 'effect';
|
|
28
|
-
import { Ndjson,
|
|
28
|
+
import { Ndjson, SchemaBinary } from 'effect/unstable/encoding';
|
|
29
29
|
```
|
|
30
30
|
|
|
31
31
|
For Node.js readable streams:
|
|
@@ -249,11 +249,11 @@ Stream.make('US', 'CA', 'NZ').pipe(
|
|
|
249
249
|
```ts
|
|
250
250
|
// Running accumulator — emits initial state plus each accumulated state
|
|
251
251
|
// Output: [0, 1, 3, 6]
|
|
252
|
-
Stream.make(1, 2, 3).pipe(Stream.scan(0, (acc, n) => acc + n));
|
|
252
|
+
Stream.make(1, 2, 3).pipe(Stream.scan(() => 0, (acc, n) => acc + n));
|
|
253
253
|
|
|
254
254
|
// Effectful variant
|
|
255
255
|
Stream.make(1, 2, 3).pipe(
|
|
256
|
-
Stream.scanEffect(0, (acc, n) => Effect.succeed(acc + n))
|
|
256
|
+
Stream.scanEffect(() => 0, (acc, n) => Effect.succeed(acc + n))
|
|
257
257
|
);
|
|
258
258
|
```
|
|
259
259
|
|
|
@@ -355,15 +355,15 @@ stream.pipe(
|
|
|
355
355
|
|
|
356
356
|
---
|
|
357
357
|
|
|
358
|
-
## 4. Encoding & Decoding (NDJSON /
|
|
358
|
+
## 4. Encoding & Decoding (NDJSON / SchemaBinary)
|
|
359
359
|
|
|
360
360
|
Use `Stream.pipeThroughChannel` with codec channels from `effect/unstable/encoding`.
|
|
361
361
|
|
|
362
362
|
```ts
|
|
363
|
-
import { Ndjson,
|
|
363
|
+
import { Ndjson, SchemaBinary } from 'effect/unstable/encoding';
|
|
364
364
|
```
|
|
365
365
|
|
|
366
|
-
### Schema-derived binary frames
|
|
366
|
+
### Schema-derived binary frames
|
|
367
367
|
|
|
368
368
|
<!-- typecheck -->
|
|
369
369
|
```ts
|
|
@@ -446,20 +446,8 @@ objectStream.pipe(Stream.pipeThroughChannel(Ndjson.encode())); // objects → Ui
|
|
|
446
446
|
Stream.pipeThroughChannel(Ndjson.decodeString({ ignoreEmptyLines: true }));
|
|
447
447
|
```
|
|
448
448
|
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
Same API shape — replace `Ndjson` with `Msgpack`. Note that `Msgpack.decodeSchema(schema)` is curried: it returns a factory you must invoke (`()`) to get the `Channel` value passed to `Stream.pipeThroughChannel`, exactly like the NDJSON schema helpers.
|
|
452
|
-
|
|
453
|
-
```ts
|
|
454
|
-
const decoder = Msgpack.decodeSchema(
|
|
455
|
-
Schema.Struct({
|
|
456
|
-
id: Schema.Number,
|
|
457
|
-
name: Schema.String
|
|
458
|
-
})
|
|
459
|
-
)();
|
|
460
|
-
|
|
461
|
-
binaryStream.pipe(Stream.pipeThroughChannel(decoder), Stream.runCollect);
|
|
462
|
-
```
|
|
449
|
+
Use SchemaBinary for a schema-derived binary contract and NDJSON for JSON
|
|
450
|
+
interoperability. Both peers must agree on the wire format and schema.
|
|
463
451
|
|
|
464
452
|
### Realistic pipeline: decode → transform → re-encode
|
|
465
453
|
|
|
@@ -545,6 +533,13 @@ stream.pipe(Stream.orElseSucceed((error) => defaultValue));
|
|
|
545
533
|
|
|
546
534
|
Prefer natural backpressure. Add `Stream.buffer` only to deliberately decouple producer and consumer: `"suspend"` backpressures when full, `"dropping"` drops new values, and `"sliding"` drops old values to retain the latest. Avoid `capacity: "unbounded"` unless growth is bounded elsewhere and documented.
|
|
547
535
|
|
|
536
|
+
`Stream.partition` returns `[passes, fails]` and accepts `{ capacity }` (default
|
|
537
|
+
16). Consume both sides concurrently to avoid blocking a full partition.
|
|
538
|
+
`Stream.mapBoth` takes `onElement` / `onError`. `catchTags` rejects unknown tag
|
|
539
|
+
keys; `catchDefect` handles defects separately from typed failures/interruption.
|
|
540
|
+
`tapDefect`, `tapErrorTag`, and `unwrapReason` provide targeted observation/recovery.
|
|
541
|
+
Use `Channel.runDrain` to consume channel output and return its completion value.
|
|
542
|
+
|
|
548
543
|
### merge
|
|
549
544
|
|
|
550
545
|
Interleave elements from two streams concurrently in arrival order.
|
|
@@ -625,7 +620,7 @@ Because the producer starts immediately, subscribers that attach after the sourc
|
|
|
625
620
|
|
|
626
621
|
### broadcastN
|
|
627
622
|
|
|
628
|
-
Fixed-fanout multicast
|
|
623
|
+
Fixed-fanout multicast produces a tuple of `n` streams; the source starts only **after all `n` downstream streams have been subscribed**, so every consumer sees the full sequence without needing `replay`. If a downstream stream is interrupted, it unsubscribes and no longer contributes backpressure.
|
|
629
624
|
|
|
630
625
|
```ts
|
|
631
626
|
Effect.scoped(
|
|
@@ -679,7 +674,7 @@ const safeStream = Stream.scoped(
|
|
|
679
674
|
// Stream<string, never, never> — Scope is eliminated
|
|
680
675
|
```
|
|
681
676
|
|
|
682
|
-
|
|
677
|
+
`Stream.scoped` provides its managed scope to **pull effects**, including effects created by `Stream.fromEffect` and sequential `Stream.mapEffect`. `Effect.acquireRelease` finalizers inside those pulls run when the stream completes.
|
|
683
678
|
|
|
684
679
|
### unwrap
|
|
685
680
|
|
|
@@ -21,11 +21,16 @@ Reference this for:
|
|
|
21
21
|
|
|
22
22
|
## Framework Selection
|
|
23
23
|
|
|
24
|
-
Keep `@effect/vitest` aligned with the Effect release.
|
|
25
|
-
Vitest peer range is `>=
|
|
24
|
+
Keep `@effect/vitest` aligned with the Effect release. Its
|
|
25
|
+
Vitest peer range is `>=5.0.0 <6.0.0`; do not install it into a Vitest 3 or 4 project
|
|
26
26
|
without migrating that project's test framework. Plain compiler/inventory tests
|
|
27
27
|
can keep using their existing Vitest runner without the adapter.
|
|
28
28
|
|
|
29
|
+
The adapter requires Node.js `^22.12.0 || ^24.0.0 || >=26.0.0`. Use
|
|
30
|
+
`{ concurrent: false }` for sequential suites, including named `layer` / `it.layer`
|
|
31
|
+
suites. Await asynchronous assertions and finalizers. Define custom matchers
|
|
32
|
+
through `vitest.Matchers` and import reporter types from `vitest/node`.
|
|
33
|
+
|
|
29
34
|
**CRITICAL**: Choose the correct testing framework based on the code being tested.
|
|
30
35
|
|
|
31
36
|
### Use @effect/vitest for Effect Code
|
|
@@ -367,7 +372,7 @@ layer(DatabaseLayer)((it) => {
|
|
|
367
372
|
});
|
|
368
373
|
```
|
|
369
374
|
|
|
370
|
-
A nested `it.layer` suite **reuses** the parent
|
|
375
|
+
A nested `it.layer` suite **reuses** the parent's memoized allocations and **forks its own memo map**. Parent layers are built once; local allocations are isolated from sibling suites and released when their suite finishes, including concurrent suites.
|
|
371
376
|
|
|
372
377
|
### Excluding Test Services
|
|
373
378
|
|
|
@@ -604,19 +609,21 @@ it.effect('should fail with specific error', () =>
|
|
|
604
609
|
### Using it.prop for Pure Properties
|
|
605
610
|
|
|
606
611
|
```typescript
|
|
607
|
-
import
|
|
612
|
+
import * as Schema from 'effect/Schema';
|
|
608
613
|
import { it } from '@effect/vitest';
|
|
609
614
|
|
|
610
615
|
it.prop(
|
|
611
616
|
'addition is commutative',
|
|
612
|
-
[
|
|
617
|
+
[Schema.Int, Schema.Int],
|
|
613
618
|
([a, b]) => a + b === b + a
|
|
614
619
|
);
|
|
615
620
|
|
|
616
621
|
// With object syntax
|
|
617
622
|
it.prop(
|
|
618
623
|
'multiplication distributes',
|
|
619
|
-
{ a:
|
|
624
|
+
{ a: Schema.Int.check(Schema.isBetween({ minimum: -100, maximum: 100 })),
|
|
625
|
+
b: Schema.Int.check(Schema.isBetween({ minimum: -100, maximum: 100 })),
|
|
626
|
+
c: Schema.Int.check(Schema.isBetween({ minimum: -100, maximum: 100 })) },
|
|
620
627
|
({ a, b, c }) => a * (b + c) === a * b + a * c
|
|
621
628
|
);
|
|
622
629
|
```
|
|
@@ -626,7 +633,7 @@ it.prop(
|
|
|
626
633
|
```typescript
|
|
627
634
|
import { it } from '@effect/vitest';
|
|
628
635
|
import { Effect, Context } from 'effect';
|
|
629
|
-
import
|
|
636
|
+
import * as Schema from 'effect/Schema';
|
|
630
637
|
|
|
631
638
|
class Database extends Context.Service<
|
|
632
639
|
Database,
|
|
@@ -638,7 +645,7 @@ class Database extends Context.Service<
|
|
|
638
645
|
|
|
639
646
|
it.effect.prop(
|
|
640
647
|
'database operations are idempotent',
|
|
641
|
-
[
|
|
648
|
+
[Schema.String, Schema.Int],
|
|
642
649
|
([key, value]) =>
|
|
643
650
|
Effect.gen(function* () {
|
|
644
651
|
const db = yield* Database;
|
|
@@ -674,35 +681,41 @@ it.effect.prop('user validation works', { user: User }, ({ user }) =>
|
|
|
674
681
|
);
|
|
675
682
|
```
|
|
676
683
|
|
|
677
|
-
`it.prop` and `it.effect.prop` accept schemas
|
|
684
|
+
`it.prop` and `it.effect.prop` accept schemas and native Arbitraries, composed
|
|
685
|
+
with `Arbitrary.all`. For manual sampling use the interruptible native runner:
|
|
678
686
|
|
|
679
687
|
```typescript
|
|
680
|
-
import {
|
|
681
|
-
import { FastCheck } from 'effect/testing';
|
|
688
|
+
import { Arbitrary } from 'effect/unstable/arbitrary';
|
|
682
689
|
|
|
683
|
-
const
|
|
684
|
-
const samples =
|
|
690
|
+
const userArbitrary = Arbitrary.schema(User);
|
|
691
|
+
const samples = Arbitrary.sampleEffect(userArbitrary, { count: 10, seed: 'users' });
|
|
685
692
|
```
|
|
686
693
|
|
|
687
|
-
|
|
694
|
+
Generation is from the decoded schema Type. Compose with `map`, `flatMap`,
|
|
695
|
+
`filter`, `filterMap`, `all`, and `array(item, { minLength, maxLength })`.
|
|
696
|
+
Prefer constructive schemas over filters that exhaust the discard budget.
|
|
697
|
+
`checkEffect` returns `Passed`, `Falsified`, `Exhausted`, or an invalid-replay
|
|
698
|
+
result; inspect the outcome rather than treating completion as success. Preserve
|
|
699
|
+
seeds, replay tokens, and important failing inputs. Replay paths can change when
|
|
700
|
+
the generator/shrinker changes. In the Vitest adapter, thrown exceptions, typed
|
|
701
|
+
failures, and defects are shrinkable falsifications; interruption stays interruption.
|
|
688
702
|
|
|
689
|
-
### Configuring
|
|
703
|
+
### Configuring native property checks
|
|
690
704
|
|
|
691
705
|
```typescript
|
|
692
706
|
import { it } from '@effect/vitest';
|
|
693
707
|
import { Effect } from 'effect';
|
|
694
|
-
import
|
|
708
|
+
import * as Schema from 'effect/Schema';
|
|
695
709
|
|
|
696
710
|
it.effect.prop(
|
|
697
711
|
'property test',
|
|
698
|
-
[
|
|
712
|
+
[Schema.Int],
|
|
699
713
|
([n]) => Effect.succeed(n >= 0 || n < 0),
|
|
700
714
|
{
|
|
701
715
|
timeout: 10000,
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
seed: 42
|
|
705
|
-
verbose: true
|
|
716
|
+
arbitrary: {
|
|
717
|
+
runs: 1000,
|
|
718
|
+
seed: 42
|
|
706
719
|
}
|
|
707
720
|
}
|
|
708
721
|
);
|
|
@@ -1155,7 +1168,7 @@ Key differences from `@effect/vitest`:
|
|
|
1155
1168
|
- Layer composition uses `Layer.provideMerge` here because the harness intentionally exposes both application and test services; do not use it blindly
|
|
1156
1169
|
- Keep `effect` as the default; use `live` only when real time or live runtime services are under test
|
|
1157
1170
|
|
|
1158
|
-
|
|
1171
|
+
`TestClock.layer()` works when provided directly to a program executed with `Effect.runPromise`, without an ambient Scope. The harness above merges that layer into the test stack so `TestClock.adjust` controls time.
|
|
1159
1172
|
|
|
1160
1173
|
## Testing Checklist
|
|
1161
1174
|
|
|
@@ -125,7 +125,7 @@ yield* SendEmail.resume(executionId);
|
|
|
125
125
|
|
|
126
126
|
### Deterministic Execution ID
|
|
127
127
|
|
|
128
|
-
|
|
128
|
+
Generated `WorkflowProxy.toRpcGroup` `<Name>Discard` RPCs and
|
|
129
129
|
`WorkflowProxy.toHttpApiGroup` discard HTTP endpoints also return the execution
|
|
130
130
|
ID (`Schema.String`). Consumers can persist that ID to poll/resume the workflow.
|
|
131
131
|
Update generated client response types and tests that expected `void`; ordinary
|
|
@@ -192,7 +192,7 @@ const sendWithRetry = Activity.make({
|
|
|
192
192
|
|
|
193
193
|
`Activity.retry` accepts the same options as `Effect.retry` *minus* `schedule` — the activity owns the attempt counter, so retries are attempt-based (`times`, `until`, `while`, `catch`, etc.) rather than schedule-based.
|
|
194
194
|
|
|
195
|
-
|
|
195
|
+
An `Activity` *is* an `Effect`; pipe it directly through compensation/retry combinators:
|
|
196
196
|
|
|
197
197
|
```ts
|
|
198
198
|
yield* SomeActivity.pipe(
|
|
@@ -460,6 +460,17 @@ const AppLayer = Layer.mergeAll(MyWorkflowLayer, ApiWorker).pipe(
|
|
|
460
460
|
|
|
461
461
|
Delivery is **at least once** per the backing `PersistedQueue`, so worker handlers must be **idempotent** and tolerate retries, duplicate observations, and worker restarts.
|
|
462
462
|
|
|
463
|
+
Persisted queue processing policy belongs on `PersistedQueue.make`: `maxAttempts`
|
|
464
|
+
defaults to 10 and `retrySchedule` determines redelivery delay. Attempts count on
|
|
465
|
+
claim; exhausted and undecodable items are dead-lettered. Distinguish this policy
|
|
466
|
+
from retrying a transient failure to offer an item.
|
|
467
|
+
|
|
468
|
+
`DurableDeferred.into` requires the schema encoding services needed to persist the
|
|
469
|
+
exit. Cluster handoff interrupts an abandoned workflow run attempt without
|
|
470
|
+
persisting a business failure or running compensations; replay continues under
|
|
471
|
+
the next owner. Keep durable completion and activity state under the workflow
|
|
472
|
+
engine rather than adding local retry/wakeup bookkeeping.
|
|
473
|
+
|
|
463
474
|
## Compensation (Saga Pattern)
|
|
464
475
|
|
|
465
476
|
`Workflow.withCompensation` registers rollback logic that runs if the **entire workflow** fails. This enables the saga pattern for distributed transactions.
|