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.
- 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 +15 -305
- package/guidance/progressive-disclosure-guidance.md +18 -24
- 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 +2 -2
- package/patterns/prefer-effect-fn.md +21 -65
- package/patterns/prefer-schema-class.md +4 -4
- 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
- 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/
|
|
11
|
-
`effect@4.0.0
|
|
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/
|
|
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/
|
|
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
|
|
88
|
-
|
|
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/
|
|
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/
|
|
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
|
-
|
|
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/
|
|
16
|
-
- `packages/effect/src/
|
|
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/
|
|
23
|
+
All SQL modules live under the `effect/sql` path:
|
|
22
24
|
|
|
23
25
|
```ts
|
|
24
|
-
import { SqlClient } from 'effect/
|
|
25
|
-
import * as SqlSchema from 'effect/
|
|
26
|
-
import * as SqlModel from 'effect/
|
|
27
|
-
import * as SqlResolver from 'effect/
|
|
28
|
-
import * as Migrator from 'effect/
|
|
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/
|
|
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/
|
|
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/
|
|
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.
|
|
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/
|
|
192
|
-
import * as SqlSchema from 'effect/
|
|
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/
|
|
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/
|
|
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/
|
|
334
|
-
import * as SqlResolver from 'effect/
|
|
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/
|
|
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/
|
|
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/
|
|
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/
|
|
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 (
|
|
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/
|
|
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/
|
|
688
|
-
import { SqlClient } from 'effect/
|
|
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/
|
|
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/
|
|
783
|
-
import { SqlClient } from 'effect/
|
|
784
|
-
import * as SqlModel from 'effect/
|
|
785
|
-
import * as Migrator from 'effect/
|
|
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/
|
|
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/
|
|
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/
|
|
398
|
+
Use `Stream.pipeThroughChannel` with codec channels from `effect/encoding`.
|
|
361
399
|
|
|
362
400
|
```ts
|
|
363
|
-
import { Ndjson, SchemaBinary } from 'effect/
|
|
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/
|
|
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
|
|
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/
|
|
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>) =>
|