opencode-effect-enforcer 0.2.6 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (73) hide show
  1. package/README.md +3 -3
  2. package/docs/effect-4.0.0-changelog.md +3213 -0
  3. package/docs/effect-4.0.0.md +110 -0
  4. package/guidance/effect-first-development.md +15 -305
  5. package/guidance/progressive-disclosure-guidance.md +18 -24
  6. package/package.json +2 -2
  7. package/patterns/avoid-any.md +2 -2
  8. package/patterns/avoid-direct-json.md +6 -6
  9. package/patterns/avoid-native-fetch.md +8 -6
  10. package/patterns/avoid-node-imports.md +2 -2
  11. package/patterns/avoid-non-null-assertion.md +2 -2
  12. package/patterns/avoid-object-type.md +2 -2
  13. package/patterns/avoid-platform-coupling.md +1 -1
  14. package/patterns/avoid-process-env.md +3 -4
  15. package/patterns/avoid-ts-ignore.md +1 -1
  16. package/patterns/context-tag-extends.md +11 -8
  17. package/patterns/effect-promise-vs-trypromise.md +6 -7
  18. package/patterns/prefer-arr-sort.md +2 -2
  19. package/patterns/prefer-effect-fn.md +21 -65
  20. package/patterns/prefer-schema-class.md +4 -4
  21. package/patterns/throw-in-effect-gen.md +1 -1
  22. package/patterns/use-clock-service.md +4 -0
  23. package/patterns/use-command-executor-service.md +2 -2
  24. package/patterns/use-http-client-service.md +8 -6
  25. package/patterns/use-random-service.md +6 -7
  26. package/skills/effect-ai-chat/SKILL.md +13 -7
  27. package/skills/effect-ai-language-model/SKILL.md +50 -21
  28. package/skills/effect-ai-prompt/SKILL.md +25 -14
  29. package/skills/effect-ai-provider/SKILL.md +50 -22
  30. package/skills/effect-ai-streaming/SKILL.md +27 -12
  31. package/skills/effect-ai-tool/SKILL.md +37 -28
  32. package/skills/effect-atom-rpc/SKILL.md +57 -36
  33. package/skills/effect-atom-state/SKILL.md +57 -19
  34. package/skills/effect-batching/SKILL.md +5 -3
  35. package/skills/effect-cache/SKILL.md +19 -7
  36. package/skills/effect-cli/SKILL.md +17 -8
  37. package/skills/effect-command-executor/SKILL.md +115 -64
  38. package/skills/effect-concurrency-testing/SKILL.md +26 -6
  39. package/skills/effect-config/SKILL.md +53 -2
  40. package/skills/effect-context-witness/SKILL.md +6 -6
  41. package/skills/effect-domain-modeling/SKILL.md +8 -1
  42. package/skills/effect-error-handling/SKILL.md +15 -2
  43. package/skills/effect-fiber/SKILL.md +20 -25
  44. package/skills/effect-filesystem/SKILL.md +69 -57
  45. package/skills/effect-http-api/SKILL.md +72 -22
  46. package/skills/effect-http-client/SKILL.md +25 -21
  47. package/skills/effect-http-server/SKILL.md +51 -21
  48. package/skills/effect-incremental-migration/SKILL.md +17 -8
  49. package/skills/effect-layer-design/SKILL.md +8 -0
  50. package/skills/effect-managed-runtime/SKILL.md +6 -0
  51. package/skills/effect-mcp-server/SKILL.md +64 -24
  52. package/skills/effect-observability/SKILL.md +61 -15
  53. package/skills/effect-parallelization/SKILL.md +24 -7
  54. package/skills/effect-path/SKILL.md +8 -2
  55. package/skills/effect-platform-abstraction/SKILL.md +88 -66
  56. package/skills/effect-platform-layers/SKILL.md +68 -67
  57. package/skills/effect-pubsub-event-bus/SKILL.md +56 -60
  58. package/skills/effect-react-composition/SKILL.md +19 -6
  59. package/skills/effect-rpc-api/SKILL.md +24 -24
  60. package/skills/effect-rpc-client/SKILL.md +33 -28
  61. package/skills/effect-rpc-cluster/SKILL.md +122 -78
  62. package/skills/effect-rpc-server/SKILL.md +56 -20
  63. package/skills/effect-scheduling/SKILL.md +29 -1
  64. package/skills/effect-schema-composition/SKILL.md +31 -13
  65. package/skills/effect-schema-v4/SKILL.md +94 -10
  66. package/skills/effect-scope/SKILL.md +13 -5
  67. package/skills/effect-service-implementation/SKILL.md +1 -1
  68. package/skills/effect-socket/SKILL.md +52 -8
  69. package/skills/effect-sql/SKILL.md +67 -33
  70. package/skills/effect-stream/SKILL.md +50 -5
  71. package/skills/effect-testing/SKILL.md +91 -2
  72. package/skills/effect-workflow/SKILL.md +76 -39
  73. package/src/guidance.ts +0 -1
@@ -7,10 +7,12 @@ description: Build pull-based TCP, Unix, TLS, and WebSocket transports with Effe
7
7
 
8
8
  ## Source reference
9
9
 
10
- Read `packages/effect/src/unstable/socket/{Socket,SocketServer}.ts` at
11
- `effect@4.0.0-rc.116` in the Effect reference. Platform adapters live in
10
+ Read `packages/effect/src/socket/{Socket,SocketServer}.ts` at
11
+ `effect@4.0.0` in the Effect reference. Platform adapters live in
12
12
  `packages/platform/node-shared/src/{NodeSocket,NodeSocketServer}.ts`; Node and
13
13
  Bun expose these as `NodeSocket` / `BunSocket` and their server counterparts.
14
+ These APIs remain `@stability unstable` and may break in minor releases despite
15
+ the stable package version. Keep Effect-family packages on the same release.
14
16
  Load `effect-scope` and `effect-fiber` for lifecycle ownership,
15
17
  `effect-stream` for framing, and `effect-scheduling` for reconnect policy.
16
18
 
@@ -38,7 +40,7 @@ There are no callback-driven `run`, `runString`, or `runRaw` methods on a socket
38
40
  <!-- typecheck -->
39
41
  ```ts
40
42
  import { Effect } from 'effect';
41
- import { Socket } from 'effect/unstable/socket';
43
+ import { Socket } from 'effect/socket';
42
44
 
43
45
  const echo = Effect.fn('Socket.echo')(function* (socket: Socket.Socket) {
44
46
  const { pull } = yield* socket.reader;
@@ -57,7 +59,7 @@ and streaming `Stream.decodeText` to preserve split UTF-8 characters.
57
59
  <!-- typecheck -->
58
60
  ```ts
59
61
  import { Duration, Effect } from 'effect';
60
- import { Socket } from 'effect/unstable/socket';
62
+ import { Socket } from 'effect/socket';
61
63
 
62
64
  const receive = Effect.gen(function* () {
63
65
  const socket = yield* Socket.makeWebSocket('wss://example.com/feed', {
@@ -84,8 +86,9 @@ casts; opening-handshake headers are available where the platform supports them.
84
86
 
85
87
  An effectful URL is reevaluated on each connection. `fromWebSocket(acquire, options)`
86
88
  wraps a scoped transport acquisition. Pausable transports pause at `highWaterMark`
87
- (default 64 KiB) and resume when drained. Browser WebSockets cannot pause and
88
- fail with `SocketReadError` when the configured bound is exceeded. Text-frame
89
+ (default 64 KiB) and resume when drained. Browser WebSockets cannot pause; their
90
+ default buffer is unbounded. Set `highWaterMark` explicitly to fail with
91
+ `SocketReadError` when that byte bound is exceeded. Text-frame
89
92
  buffering counts UTF-8 bytes, not string length.
90
93
 
91
94
  ## TCP, Unix, and TLS
@@ -127,7 +130,7 @@ reconnect. Use a new socket value for an independent concurrent client connectio
127
130
  <!-- typecheck -->
128
131
  ```ts
129
132
  import { Duration, Effect, Schedule } from 'effect';
130
- import { Socket } from 'effect/unstable/socket';
133
+ import { Socket } from 'effect/socket';
131
134
 
132
135
  const consume = Effect.fn('Socket.consume')(function* (socket: Socket.Socket) {
133
136
  const pull = yield* Socket.readerString(socket);
@@ -151,6 +154,12 @@ const reconnect = (socket: Socket.Socket) => consume(socket).pipe(
151
154
  WebSocket close codes are protocol data; TCP does not transmit them. Prefer
152
155
  writer-scope release for a graceful TCP half-close rather than a close event.
153
156
 
157
+ Node/Bun HTTP upgrade adapters choose server close codes from the owning scope
158
+ exit: `1000` for success, `1001` for interruption-only exits, and `1011` for other
159
+ failures. An explicit code already sent is preserved. HTTP response conversion
160
+ and observation middleware retain the handler failure for request cleanup. This
161
+ is server-upgrade behavior, not a configurable clean-close predicate on sockets.
162
+
154
163
  ## Streams, channels, and framing
155
164
 
156
165
  `Socket.toStream(socket)` provides read-only byte consumption.
@@ -183,7 +192,7 @@ rather than becoming accept-loop failures; add application-level supervision
183
192
  where they must stop the service. HTTP upgrades yield the same Socket interface;
184
193
  see `effect-http-server`.
185
194
 
186
- Server addresses are `NetAddress.SocketAddress` from `effect/unstable/net`.
195
+ Server addresses are `NetAddress.SocketAddress` from `effect/net`.
187
196
  Narrow to `InetAddress` before reading `port`; format its IP with `formatIp`,
188
197
  or use `formatHost` when passing a host and port separately (preserves IPv6 scopes).
189
198
  Unix path addresses expose `path`. Use `inetAddressFromHostString` to parse numeric
@@ -191,10 +200,45 @@ hosts and `scopeIdsFromInterfaces` with supplied interface data for named zones.
191
200
  URL helpers bracket IPv6 and reject scoped IPv6; do not assemble URLs by casting
192
201
  an address to the removed `TcpAddress` type.
193
202
 
203
+ Treat IP values as opaque: IPv4 now stores one unsigned 32-bit number and IPv6
204
+ four words internally, not public `.bytes`. Use `ipv4ToOctets`, `ipv6ToOctets`,
205
+ `ipv6ToSegments`, and formatting/construction helpers. Do not persist internal
206
+ fields or hash values as a wire representation.
207
+
208
+ `NetAddress.Family<A>` selects the IPv4/IPv6 address family, `Inet<A>` the matching
209
+ port-carrying address, and `MulticastInterface<A>` an IPv4 interface address or
210
+ IPv6 numeric interface index (`ipv4Unspecified`/`0` choose the OS default).
211
+ Classification predicates such as `isMulticast`, `isUnicast`, `isLoopback`, and
212
+ `isLinkLocal` preserve the input family while refining it to the corresponding
213
+ branded type. Use the named schemas in `effect/Schema` for boundary validation
214
+ rather than asserting these refinements.
215
+
216
+ <!-- typecheck -->
217
+ ```ts
218
+ import { Effect } from 'effect';
219
+ import * as Schema from 'effect/Schema';
220
+ import { NetAddress } from 'effect/net';
221
+
222
+ const parseMulticast = Schema.decodeUnknownEffect(Schema.IpMulticastAddressFromString);
223
+ const displayMulticast = Effect.fn('Socket.displayMulticast')(function* (input: unknown) {
224
+ const address = yield* parseMulticast(input);
225
+ return NetAddress.formatIp(address);
226
+ });
227
+ ```
228
+
229
+ For native adapters, `formatNativeHost(address, scopeIds, platform?)` and
230
+ `formatMulticastInterface(selector, scopeIds, platform?)` use an explicitly
231
+ supplied interface-name-to-index map. Non-Windows output prefers a matching
232
+ interface name; Windows uses numeric zones. `toCanonical` accepts both IP and
233
+ internet addresses: mapped IPv6 becomes IPv4 while retaining its port; other
234
+ values retain identity and IPv6 scope metadata.
235
+
194
236
  ## Higher-level transports and tests
195
237
 
196
238
  - RPC: `RpcClient.layerProtocolSocket` and `RpcServer.layerProtocolSocketServer`;
197
239
  provide matching `RpcSerialization` layers and the socket/server layer.
240
+ Missed pongs are read failures that fail in-flight calls even with transient
241
+ open-error retries enabled; reconnect does not replay those calls.
198
242
  - Cluster: `NodeClusterSocket` / `BunClusterSocket`; SchemaBinary is the default,
199
243
  NDJSON is explicitly selectable.
200
244
  - In-memory tests: `Socket.fromTransformStream` wraps a readable/writable pair;
@@ -8,37 +8,39 @@ You are an Effect TypeScript expert specializing in type-safe SQL database acces
8
8
  ## Effect Source Reference
9
9
 
10
10
  The Effect v4 source is available at `~/.local/share/opencode/repos/github.com/Effect-TS/effect@main/`.
11
- Browse and read files there directly to look up APIs, types, and implementations.
11
+ Inspect the `effect@4.0.0` tag for this skill; main may be newer. SQL APIs remain
12
+ `@stability unstable` and may break in minor releases. Keep `effect` and all
13
+ `@effect/sql-*` and platform dependencies on the same release.
12
14
 
13
15
  Reference this for:
14
16
 
15
- - `packages/effect/src/unstable/sql/` — Core SQL modules (SqlClient, SqlSchema, SqlModel, SqlResolver, Migrator, Statement)
16
- - `packages/effect/src/unstable/schema/Model.ts` — Model class with variant schemas
17
+ - `packages/effect/src/sql/` — Core SQL modules (SqlClient, SqlSchema, SqlModel, SqlResolver, Migrator, Statement)
18
+ - `packages/effect/src/schema/Model.ts` — Model class with variant schemas
17
19
  - `packages/sql/pg/src/PgClient.ts` — PostgreSQL driver example
18
20
 
19
21
  ## Core Imports
20
22
 
21
- All SQL modules live under the `effect/unstable/sql` path:
23
+ All SQL modules live under the `effect/sql` path:
22
24
 
23
25
  ```ts
24
- import { SqlClient } from 'effect/unstable/sql/SqlClient';
25
- import * as SqlSchema from 'effect/unstable/sql/SqlSchema';
26
- import * as SqlModel from 'effect/unstable/sql/SqlModel';
27
- import * as SqlResolver from 'effect/unstable/sql/SqlResolver';
28
- import * as Migrator from 'effect/unstable/sql/Migrator';
26
+ import { SqlClient } from 'effect/sql/SqlClient';
27
+ import * as SqlSchema from 'effect/sql/SqlSchema';
28
+ import * as SqlModel from 'effect/sql/SqlModel';
29
+ import * as SqlResolver from 'effect/sql/SqlResolver';
30
+ import * as Migrator from 'effect/sql/Migrator';
29
31
  ```
30
32
 
31
33
  Alternatively, the barrel exports namespace modules:
32
34
 
33
35
  ```ts
34
- import { SqlClient, SqlSchema, SqlModel, SqlResolver, Migrator } from 'effect/unstable/sql';
36
+ import { SqlClient, SqlSchema, SqlModel, SqlResolver, Migrator } from 'effect/sql';
35
37
  // With the barrel, the service is SqlClient.SqlClient.
36
38
  ```
37
39
 
38
40
  For Model schemas (used with SqlModel):
39
41
 
40
42
  ```ts
41
- import { Model } from 'effect/unstable/schema';
43
+ import { Model } from 'effect/schema';
42
44
  ```
43
45
 
44
46
  ## SqlClient — Tagged Template Queries
@@ -49,7 +51,7 @@ import { Model } from 'effect/unstable/schema';
49
51
 
50
52
  ```ts
51
53
  import { Effect } from 'effect';
52
- import { SqlClient } from 'effect/unstable/sql/SqlClient';
54
+ import { SqlClient } from 'effect/sql/SqlClient';
53
55
 
54
56
  const program = Effect.gen(function* () {
55
57
  const sql = yield* SqlClient;
@@ -157,7 +159,28 @@ yield*
157
159
 
158
160
  Transaction context is attached to the active `SqlClient` service instance. Queries join a transaction only when they run with that same client; avoid mixing clients or manually reserved connections for one atomic unit of work.
159
161
 
160
- A failed top-level `BEGIN` or nested `SAVEPOINT` is propagated as a typed `SqlError`; the wrapped effect does not run, and no rollback is attempted for the transaction or savepoint that never started. If a nested `SAVEPOINT` failure escapes the outer transaction body, the already-started outer transaction rolls back. Because the failure is typed, outer code may catch it and continue the transaction instead. Commit and rollback command failures are treated as defects.
162
+ A failed top-level `BEGIN` or nested `SAVEPOINT` is propagated as a typed `SqlError`; the wrapped effect does not run, and no rollback is attempted for the transaction or savepoint that never started. If a nested `SAVEPOINT` failure escapes the outer transaction body, the already-started outer transaction rolls back. Catching this typed failure does not repair the database transaction state: continue only if the driver/database still permits it. Commit and rollback command failures are treated as defects.
163
+
164
+ Nested savepoints are released after success or successful rollback by PostgreSQL,
165
+ PGlite, MySQL, libSQL, and Node/Bun/React Native/WASM SQLite drivers. A release
166
+ failure is also a defect. Custom clients opt in with
167
+ `SqlClient.make({ releaseSavepoint: (name) => ... })`; omitting it leaves the
168
+ previous behavior, suitable for dialects such as MSSQL without release support.
169
+
170
+ `commit` accepts either SQL text or `(connection) => Effect<void, SqlError>` so
171
+ drivers can inspect completion, while `onCommitFailure` performs cleanup on the
172
+ same connection before release. Failed commit/cleanup still surface as defects,
173
+ not a successful transaction result. SQLite drivers roll back transactions left
174
+ open by failed COMMIT (for example deferred foreign-key violations). A failed
175
+ cleanup prevents reuse until a subsequent acquisition successfully retries that
176
+ rollback; the database is not silently reopened.
177
+
178
+ PostgreSQL can return a `ROLLBACK` command result for `COMMIT` after a caught
179
+ statement error aborted the transaction. `PgClient` detects this and fails the
180
+ transaction with a `SqlError` defect (`UnknownError`, operation `commit`). Catching
181
+ a statement failure in application code does not restore PostgreSQL transaction
182
+ state. Use a nested transaction/savepoint around a recoverable operation when the
183
+ outer transaction must continue.
161
184
 
162
185
  ### Dialect Branching
163
186
 
@@ -188,8 +211,8 @@ sql.onDialect({
188
211
 
189
212
  ```ts
190
213
  import { Schema } from 'effect';
191
- import { SqlClient } from 'effect/unstable/sql/SqlClient';
192
- import * as SqlSchema from 'effect/unstable/sql/SqlSchema';
214
+ import { SqlClient } from 'effect/sql/SqlClient';
215
+ import * as SqlSchema from 'effect/sql/SqlSchema';
193
216
 
194
217
  const sql = yield* SqlClient;
195
218
 
@@ -238,7 +261,7 @@ The `Model` module provides a schema class system with built-in variants for dat
238
261
 
239
262
  ```ts
240
263
  import { Schema } from 'effect';
241
- import { Model } from 'effect/unstable/schema';
264
+ import { Model } from 'effect/schema';
242
265
 
243
266
  const UserId = Schema.Number.pipe(Schema.brand('UserId'));
244
267
 
@@ -294,7 +317,7 @@ Use `GeneratedByDb` only for fields that are truly read-only after selection, su
294
317
  `SqlModel.makeRepository` generates a complete CRUD interface from a Model class.
295
318
 
296
319
  ```ts
297
- import * as SqlModel from 'effect/unstable/sql/SqlModel';
320
+ import * as SqlModel from 'effect/sql/SqlModel';
298
321
 
299
322
  const UserRepo =
300
323
  yield*
@@ -330,8 +353,8 @@ yield* UserRepo.delete(userId);
330
353
 
331
354
  ```ts
332
355
  import { RequestResolver } from 'effect';
333
- import * as SqlModel from 'effect/unstable/sql/SqlModel';
334
- import * as SqlResolver from 'effect/unstable/sql/SqlResolver';
356
+ import * as SqlModel from 'effect/sql/SqlModel';
357
+ import * as SqlResolver from 'effect/sql/SqlResolver';
335
358
 
336
359
  const UserResolvers =
337
360
  yield*
@@ -393,7 +416,7 @@ Resolver versions created by `SqlModel.makeResolvers` honor the same soft-delete
393
416
  Results map 1:1 to requests by position. Result count must match request count.
394
417
 
395
418
  ```ts
396
- import * as SqlResolver from 'effect/unstable/sql/SqlResolver';
419
+ import * as SqlResolver from 'effect/sql/SqlResolver';
397
420
 
398
421
  const insertResolver = SqlResolver.ordered({
399
422
  Request: User.insert,
@@ -476,7 +499,7 @@ Each migration file exports a default Effect:
476
499
  ```ts
477
500
  // migrations/0001_create_users.ts
478
501
  import { Effect } from 'effect';
479
- import { SqlClient } from 'effect/unstable/sql/SqlClient';
502
+ import { SqlClient } from 'effect/sql/SqlClient';
480
503
 
481
504
  export default Effect.gen(function* () {
482
505
  const sql = yield* SqlClient;
@@ -494,7 +517,7 @@ export default Effect.gen(function* () {
494
517
  ### Running Migrations
495
518
 
496
519
  ```ts
497
- import * as Migrator from 'effect/unstable/sql/Migrator';
520
+ import * as Migrator from 'effect/sql/Migrator';
498
521
 
499
522
  // Create a migrator (optionally with schema dump support)
500
523
  const migrate = Migrator.make({
@@ -542,7 +565,7 @@ Migrator.fromBabelGlob(migrations);
542
565
  ### Migration Errors
543
566
 
544
567
  ```ts
545
- import * as Migrator from 'effect/unstable/sql/Migrator';
568
+ import * as Migrator from 'effect/sql/Migrator';
546
569
 
547
570
  // MigrationError has a `kind` discriminator:
548
571
  // - "BadState" — migrations table in unexpected state
@@ -563,7 +586,7 @@ Effect SQL uses driver-specific packages that provide `SqlClient` layers.
563
586
  | `@effect/sql-pg` | PostgreSQL (native Effect protocol client) |
564
587
  | `@effect/sql-pglite` | Embedded PostgreSQL/PGlite |
565
588
  | `@effect/sql-mysql2` | MySQL (via `mysql2`) |
566
- | `@effect/sql-sqlite-node` | SQLite (via `better-sqlite3`) |
589
+ | `@effect/sql-sqlite-node` | SQLite (`node:sqlite`; Node 22.16+) |
567
590
  | `@effect/sql-libsql` | libSQL / Turso |
568
591
  | `@effect/sql-mssql` | Microsoft SQL Server |
569
592
  | `@effect/sql-clickhouse` | ClickHouse |
@@ -573,7 +596,7 @@ Effect SQL uses driver-specific packages that provide `SqlClient` layers.
573
596
  ```ts
574
597
  import { Effect, Layer } from 'effect';
575
598
  import { PgClient } from '@effect/sql-pg';
576
- import { SqlClient } from 'effect/unstable/sql/SqlClient';
599
+ import { SqlClient } from 'effect/sql/SqlClient';
577
600
 
578
601
  // Static config
579
602
  const DatabaseLayer = PgClient.layer({
@@ -615,13 +638,24 @@ Bind JSON explicitly with `sql.json`, and send one statement per query string.
615
638
  Named preparation is enabled by default; set `prepare: false` for incompatible
616
639
  poolers, or use statement-level `unprepared` / `valuesUnprepared`.
617
640
 
641
+ Prepared names are namespaced per physical connection to avoid collisions when
642
+ backend sessions are shared. This does not replace checking your pooler's support
643
+ for named prepared statements. Pools retire fatal sessions before the next borrow
644
+ and retire sessions with an unconfirmed `CancelRequest`, including cancellation
645
+ while idle, so a delayed cancel cannot hit a later checkout. The current checkout
646
+ and unpooled sessions can still encounter their own late cancellation. Failed
647
+ background pool acquisitions are retried on borrow; callers already waiting on
648
+ their own failed acquisition receive that failure.
649
+
618
650
  Decode rows according to the actual driver boundary: `int8` is `bigint`, `date`
619
651
  is a string, timestamps are `Date` (millisecond precision), and `bytea` is
620
652
  `Uint8Array`. Infinite/out-of-range timestamps decode to invalid Dates; validate
621
653
  before constructing domain instants. Timestamp encoders accept Date or epoch
622
654
  milliseconds; invalid Date encoding fails. A Date binds as `timestamptz`, so use
623
655
  UTC session time or `PgTypes.timestamp(value)` for UTC fields in a timestamp column.
624
- `executeRaw` returns `PgConnection.Result`.
656
+ `executeRaw` returns `PgConnection.Result`. Binary `regclass` and `regclass[]`
657
+ decode as unsigned numeric OIDs; cast to `text` in SQL when relation names are
658
+ required.
625
659
 
626
660
  Unregistered OIDs decode as UTF-8 text, suitable for scalar enum labels but not
627
661
  arbitrary binary types. Register custom scalar/array codecs through
@@ -684,8 +718,8 @@ Use `@effect/sql-pglite` for embedded PostgreSQL-compatible databases backed by
684
718
  ```ts
685
719
  import { Config, Effect } from 'effect';
686
720
  import { PgliteClient, PgliteMigrator } from '@effect/sql-pglite';
687
- import * as Migrator from 'effect/unstable/sql/Migrator';
688
- import { SqlClient } from 'effect/unstable/sql/SqlClient';
721
+ import * as Migrator from 'effect/sql/Migrator';
722
+ import { SqlClient } from 'effect/sql/SqlClient';
689
723
 
690
724
  const PgliteLayer = PgliteClient.layer({
691
725
  dataDir: 'idb://myapp'
@@ -750,7 +784,7 @@ yield*
750
784
  All SQL operations can fail with `SqlError`:
751
785
 
752
786
  ```ts
753
- import { SqlError } from 'effect/unstable/sql/SqlError';
787
+ import { SqlError } from 'effect/sql/SqlError';
754
788
 
755
789
  yield*
756
790
  sql`SELECT * FROM users`.pipe(
@@ -779,10 +813,10 @@ Keep non-unique integrity failures on their own paths; they remain `ConstraintEr
779
813
 
780
814
  ```ts
781
815
  import { Effect, Layer, Schema } from 'effect';
782
- import { Model } from 'effect/unstable/schema';
783
- import { SqlClient } from 'effect/unstable/sql/SqlClient';
784
- import * as SqlModel from 'effect/unstable/sql/SqlModel';
785
- import * as Migrator from 'effect/unstable/sql/Migrator';
816
+ import { Model } from 'effect/schema';
817
+ import { SqlClient } from 'effect/sql/SqlClient';
818
+ import * as SqlModel from 'effect/sql/SqlModel';
819
+ import * as Migrator from 'effect/sql/Migrator';
786
820
  import { PgClient } from '@effect/sql-pg';
787
821
 
788
822
  // 1. Define Model
@@ -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 SchemaBinary (`packages/effect/src/unstable/encoding/`)
18
+ - Encoding/decoding with NDJSON and SchemaBinary (`packages/effect/src/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, SchemaBinary } from 'effect/unstable/encoding';
28
+ import { Ndjson, SchemaBinary } from 'effect/encoding';
29
29
  ```
30
30
 
31
31
  For Node.js readable streams:
@@ -84,6 +84,11 @@ const samples = Stream.fromEffectSchedule(
84
84
  const forever = Stream.fromEffectRepeat(Effect.succeed('tick'));
85
85
  ```
86
86
 
87
+ `Stream.repeat(source, schedule)` and `Stream.forever(source)` repeat the whole
88
+ source, closing each completed run's scope before starting the next one. Keep
89
+ per-run acquisitions inside the source so they release between iterations;
90
+ resources that must span repetitions belong to an outer owning scope.
91
+
87
92
  ### Paginated APIs
88
93
 
89
94
  `Stream.paginate` drives cursor-based pagination. Return the current page and `Option.some(nextCursor)` or `Option.none()` to stop.
@@ -152,6 +157,35 @@ const callbackStream = Stream.callback<PointerEvent>(
152
157
 
153
158
  Options: `{ bufferSize?: number, strategy?: "sliding" | "dropping" | "suspend" }`
154
159
 
160
+ ### Queue completion and terminal batches
161
+
162
+ For a queue-backed source, `Queue.end` stops new offers but lets buffered values
163
+ drain before `Cause.Done`; `Queue.fail` likewise drains before its error.
164
+ `Queue.takeN` / `takeBetween` can return a final batch smaller than their requested
165
+ minimum once closing begins. An open queue with a partial batch stays suspended
166
+ until its batching threshold is reached; it does not busy-loop on each offer.
167
+ Do not interpret a short terminal batch as malformed input.
168
+
169
+ <!-- typecheck -->
170
+ ```ts
171
+ import { Cause, Effect, Queue } from 'effect';
172
+
173
+ const program = Effect.gen(function* () {
174
+ const queue = yield* Queue.bounded<number, Cause.Done>(8);
175
+ yield* Queue.offerAll(queue, [1, 2]);
176
+ yield* Queue.end(queue);
177
+ const finalBatch = yield* Queue.takeN(queue, 5); // [1, 2]
178
+ const terminal = yield* Effect.exit(Queue.take(queue)); // Done
179
+ return { finalBatch, terminal };
180
+ });
181
+ ```
182
+
183
+ Interrupted pending `offer` / `offerAll` producers withdraw their unaccepted
184
+ values even during closing, allowing the queue to finish. `Queue.shutdown`
185
+ discards buffered messages immediately; `shutdownUnsafe` is the synchronous
186
+ callback equivalent. Both return `false` if already completed or shut down.
187
+ They preserve an existing closing cause; an open queue shuts down by interruption.
188
+
155
189
  ### From ReadableStream (DOM/Web)
156
190
 
157
191
  ```ts
@@ -228,6 +262,10 @@ stream.pipe(
228
262
  );
229
263
  ```
230
264
 
265
+ A synchronous throw from a concurrent mapping callback is reported as a defect
266
+ inside its worker fiber; it does not become a typed mapping error. Lift expected
267
+ throwable operations with `Effect.try` / `tryPromise` at their boundary.
268
+
231
269
  ### FlatMap
232
270
 
233
271
  Transform each element into a stream and flatten. Supports concurrency.
@@ -357,10 +395,10 @@ stream.pipe(
357
395
 
358
396
  ## 4. Encoding & Decoding (NDJSON / SchemaBinary)
359
397
 
360
- Use `Stream.pipeThroughChannel` with codec channels from `effect/unstable/encoding`.
398
+ Use `Stream.pipeThroughChannel` with codec channels from `effect/encoding`.
361
399
 
362
400
  ```ts
363
- import { Ndjson, SchemaBinary } from 'effect/unstable/encoding';
401
+ import { Ndjson, SchemaBinary } from 'effect/encoding';
364
402
  ```
365
403
 
366
404
  ### Schema-derived binary frames
@@ -369,7 +407,7 @@ import { Ndjson, SchemaBinary } from 'effect/unstable/encoding';
369
407
  ```ts
370
408
  import { Stream } from 'effect';
371
409
  import * as Schema from 'effect/Schema';
372
- import { SchemaBinary } from 'effect/unstable/encoding';
410
+ import { SchemaBinary } from 'effect/encoding';
373
411
 
374
412
  class Reading extends Schema.Class<Reading>('Reading')({
375
413
  id: Schema.String,
@@ -618,6 +656,13 @@ Options: `{ capacity: number | "unbounded", strategy?: "sliding" | "dropping" |
618
656
 
619
657
  Because the producer starts immediately, subscribers that attach after the source has already emitted will miss earlier values unless `replay` is configured (and `replay` only retains the most recent N values — it is not a full log). For a **fixed, known set of consumers**, prefer `broadcastN`: it subscribes all downstream streams before starting the source, so none of them miss values.
620
658
 
659
+ Completion and failure are retained separately from ordinary values:
660
+ `broadcast`, `broadcastN`, `share`, and `toPubSubTake` end their underlying PubSub
661
+ with the source's terminal exit. Current and late subscribers observe that exit
662
+ after buffered/replayed values, even if a dropping buffer was full. This does
663
+ not replay earlier data. For `toPubSubTake`, consume with `Stream.fromPubSubTake`
664
+ so the terminal `Take` is interpreted as completion/failure.
665
+
621
666
  ### broadcastN
622
667
 
623
668
  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.
@@ -124,6 +124,41 @@ it.live('test with real time', () =>
124
124
 
125
125
  Use `it.live` only when real time or live runtime services are behavior under test. It scopes the test without installing `TestClock` or `TestConsole`. Real databases, HTTP clients, and filesystems still require their explicit layers; `it.live` does not provide them.
126
126
 
127
+ ### Vitest Fixtures
128
+
129
+ Build helpers with `makeMethods(test.extend(...))` when using Vitest fixtures.
130
+ Destructure the fixtures in the callback: Vitest reads those names to select
131
+ setup and rejects a plain `(ctx) =>` parameter once fixtures are defined.
132
+
133
+ ```typescript
134
+ import { expect, makeMethods, test } from '@effect/vitest';
135
+ import { Effect } from 'effect';
136
+
137
+ const it = makeMethods(
138
+ test.extend('config', { scope: 'file' }, () => ({ port: 3000 }))
139
+ );
140
+
141
+ it.effect('reads the config fixture', ({ config }) =>
142
+ Effect.sync(() => {
143
+ expect(config.port).toBe(3000);
144
+ })
145
+ );
146
+
147
+ it.effect.each([3000])('uses port %s', (port, { config }) =>
148
+ Effect.sync(() => {
149
+ expect(config.port).toBe(port);
150
+ })
151
+ );
152
+ ```
153
+
154
+ `it.effect.each` passes the test case first and context second. Named and
155
+ anonymous `it.layer` suites preserve fixtures; fixture teardown follows the
156
+ Effect test scope's closure. Property tests cannot request fixtures, though auto
157
+ fixtures still run. Build `makeMethods` from `test` or `test.extend(...)`, not
158
+ the suite-bound test passed to a `describe` callback, which can register nested
159
+ layer tests outside their intended suite. In concurrent tests, destructure and
160
+ use the callback's `expect` for per-test snapshots and assertion counts.
161
+
127
162
  ### Resource Management in Tests
128
163
 
129
164
  `it.effect` already handles scoping internally — there is no separate `it.scoped` or `it.scopedLive` variant. Use `Effect.acquireRelease` or `Effect.scoped` directly within `it.effect`:
@@ -606,6 +641,11 @@ it.effect('should fail with specific error', () =>
606
641
 
607
642
  ## Property-Based Testing
608
643
 
644
+ Native `Arbitrary` is exported from `effect/Arbitrary` or the `effect` root;
645
+ it does not use fast-check. It remains marked `@stability unstable`, so preserve
646
+ important failing inputs rather than relying on replay compatibility across
647
+ releases.
648
+
609
649
  ### Using it.prop for Pure Properties
610
650
 
611
651
  ```typescript
@@ -685,7 +725,7 @@ it.effect.prop('user validation works', { user: User }, ({ user }) =>
685
725
  with `Arbitrary.all`. For manual sampling use the interruptible native runner:
686
726
 
687
727
  ```typescript
688
- import { Arbitrary } from 'effect/unstable/arbitrary';
728
+ import * as Arbitrary from 'effect/Arbitrary';
689
729
 
690
730
  const userArbitrary = Arbitrary.schema(User);
691
731
  const samples = Arbitrary.sampleEffect(userArbitrary, { count: 10, seed: 'users' });
@@ -699,9 +739,22 @@ result; inspect the outcome rather than treating completion as success. Preserve
699
739
  seeds, replay tokens, and important failing inputs. Replay paths can change when
700
740
  the generator/shrinker changes. In the Vitest adapter, thrown exceptions, typed
701
741
  failures, and defects are shrinkable falsifications; interruption stays interruption.
742
+ The direct `Arbitrary.checkEffect` runner instead propagates defects and
743
+ interruption; only `false` and typed Effect failures become falsifications.
744
+ Generated values are not cloned. Keep properties deterministic and immutable,
745
+ and acquire/reset stateful fixtures inside each evaluation: shrinking and replay
746
+ run the property repeatedly without resetting shared services for you.
702
747
 
703
748
  ### Configuring native property checks
704
749
 
750
+ Set suite-wide defaults before concurrent tests start with
751
+ `Arbitrary.configureGlobal({ check: { runs: 1000 }, sample: { count: 10 } })`.
752
+ This replaces the previous configuration; `{}` restores built-in defaults.
753
+ Non-`undefined` per-call options win. Defaults are read when an execution starts,
754
+ including for Effects created earlier, and do not change active runs. Replay
755
+ tokens still control replayed checks. Vitest's `arbitrary` options use these
756
+ same defaults.
757
+
705
758
  ```typescript
706
759
  import { it } from '@effect/vitest';
707
760
  import { Effect } from 'effect';
@@ -721,6 +774,42 @@ it.effect.prop(
721
774
  );
722
775
  ```
723
776
 
777
+ ## Schema Assertions
778
+
779
+ `TestSchema` from `effect/testing` is marked `@stability unstable`.
780
+ `new TestSchema.Asserts(schema)` provides `make()`, `decoding()`, and `encoding()`
781
+ assertions. Their `succeedEffect` and `failEffect` methods are lazy Effects;
782
+ decoding/encoding assertions use the caller's services, test clock, and
783
+ interruption. Prefer them inside `it.effect` over starting a separate runtime
784
+ through Promise helpers.
785
+
786
+ <!-- typecheck -->
787
+ ```typescript
788
+ import { Effect } from 'effect';
789
+ import * as Schema from 'effect/Schema';
790
+ import { TestSchema } from 'effect/testing';
791
+
792
+ const asserts = new TestSchema.Asserts(Schema.FiniteFromString);
793
+ const verify = Effect.gen(function* () {
794
+ yield* asserts.decoding().succeedEffect('42', 42);
795
+ yield* asserts.encoding().succeedEffect(42, '42');
796
+ yield* asserts.verifyRoundTripEffect({ seed: 1, runs: 20 });
797
+ });
798
+ ```
799
+
800
+ `verifyRoundTripEffect` generates decoded values and checks encode-then-decode
801
+ with strict deep equality; it requires both encoding and decoding services.
802
+ It does not promise to preserve arbitrary original wire spellings. The Promise
803
+ counterpart is `verifyRoundTrip`, replacing `verifyLosslessTransformation`.
804
+ Promise `succeed` / `fail` remain available for any test runner after satisfying
805
+ their services with the assertion object's `.provide(...)` method.
806
+
807
+ Success assertions compare against the input when only one argument is supplied;
808
+ pass an explicit expected value for transformations. Failure assertions compare
809
+ the formatted schema issue, not arbitrary failures. Assertion mismatches are
810
+ defects. Schema defects and interruption propagate, even when combined with
811
+ validation failures, rather than satisfying a failure assertion.
812
+
724
813
  ## Test Control
725
814
 
726
815
  ### Skipping Tests
@@ -973,7 +1062,7 @@ For SSE endpoints, use `HttpServerResponse.stream` with different `Stream` const
973
1062
 
974
1063
  ```typescript
975
1064
  import { Effect, Stream } from 'effect';
976
- import { HttpServerResponse } from 'effect/unstable/http';
1065
+ import { HttpServerResponse } from 'effect/http';
977
1066
 
978
1067
  // Normal SSE response — stream JSON lines, then [DONE]
979
1068
  const sse = (lines: ReadonlyArray<unknown>) =>