opencode-effect-enforcer 0.2.8 → 0.4.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 +6 -5
- package/docs/effect-4.0.0-changelog.md +3213 -0
- package/docs/effect-4.0.0.md +110 -0
- package/guidance/effect-first-development.md +8 -6
- package/guidance/progressive-disclosure-guidance.md +15 -7
- package/package.json +6 -2
- package/patterns/avoid-any.md +2 -2
- package/patterns/avoid-direct-json.md +6 -6
- package/patterns/avoid-native-fetch.md +8 -6
- package/patterns/avoid-node-imports.md +2 -2
- package/patterns/avoid-non-null-assertion.md +2 -2
- package/patterns/avoid-object-type.md +2 -2
- package/patterns/avoid-platform-coupling.md +1 -1
- package/patterns/avoid-process-env.md +3 -4
- package/patterns/avoid-ts-ignore.md +1 -1
- package/patterns/context-tag-extends.md +11 -8
- package/patterns/effect-promise-vs-trypromise.md +6 -7
- package/patterns/prefer-arr-sort.md +1 -1
- package/patterns/prefer-effect-fn.md +21 -65
- package/patterns/prefer-schema-class.md +3 -3
- package/patterns/throw-in-effect-gen.md +1 -1
- package/patterns/use-clock-service.md +4 -0
- package/patterns/use-command-executor-service.md +2 -2
- package/patterns/use-http-client-service.md +8 -6
- package/patterns/use-random-service.md +6 -7
- package/skills/effect-ai-chat/SKILL.md +13 -7
- package/skills/effect-ai-decision-model/SKILL.md +301 -0
- package/skills/effect-ai-decision-model/openrouter.md +70 -0
- package/skills/effect-ai-language-model/SKILL.md +53 -21
- package/skills/effect-ai-prompt/SKILL.md +25 -14
- package/skills/effect-ai-provider/SKILL.md +53 -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
|
@@ -8,13 +8,15 @@ You are an Effect TypeScript expert specializing in building MCP (Model Context
|
|
|
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 `git show effect@4.0.0:<path>` in that checkout for this baseline; main
|
|
12
|
+
may be ahead. Keep Effect-family package versions aligned. The MCP/AI APIs remain
|
|
13
|
+
`@stability unstable` and can change incompatibly in minor releases.
|
|
12
14
|
|
|
13
15
|
Reference these files:
|
|
14
16
|
|
|
15
17
|
- `packages/effect/MCP.md` — primary MCP guide with full examples
|
|
16
|
-
- `packages/effect/src/
|
|
17
|
-
- `packages/effect/src/
|
|
18
|
+
- `packages/effect/src/ai/McpServer.ts` — server implementation and API
|
|
19
|
+
- `packages/effect/src/ai/McpSchema.ts` — schema types, param helper, error classes
|
|
18
20
|
|
|
19
21
|
## What is MCP
|
|
20
22
|
|
|
@@ -25,7 +27,7 @@ Model Context Protocol (MCP) is a standard protocol for LLM tool integration. It
|
|
|
25
27
|
```typescript
|
|
26
28
|
import { Cause, Context, Effect, Layer, Logger } from 'effect';
|
|
27
29
|
import { Schema } from 'effect';
|
|
28
|
-
import { McpProtocol, McpServer, McpSchema, Tool, Toolkit } from 'effect/
|
|
30
|
+
import { McpProtocol, McpServer, McpSchema, Tool, Toolkit } from 'effect/ai';
|
|
29
31
|
```
|
|
30
32
|
|
|
31
33
|
For platform-specific transports:
|
|
@@ -72,7 +74,8 @@ const protocols = [
|
|
|
72
74
|
- `2024-11-05` and `2025-03-26` are compatibility revisions.
|
|
73
75
|
- `2025-06-18` supports form elicitation.
|
|
74
76
|
- `2025-11-25` adds sampling with tools, independently advertised form/URL elicitation modes, descriptor icons, and elicitation-complete notifications.
|
|
75
|
-
- `2026-07-28` is available as `McpProtocol.v2026_07_28
|
|
77
|
+
- `2026-07-28` is available as `McpProtocol.v2026_07_28`; it uses stateless
|
|
78
|
+
request-scoped execution rather than the older initialized-session lifecycle.
|
|
76
79
|
- Duplicate versions or an empty protocol declaration fail layer construction with `Cause.IllegalArgumentError`.
|
|
77
80
|
- `v2024_11_05` over `layerHttp` uses Effect's single-endpoint Streamable HTTP compatibility transport. It does not recreate the historical two-endpoint HTTP+SSE transport, GET SSE, event resumption, session expiry, or client session termination.
|
|
78
81
|
|
|
@@ -84,34 +87,60 @@ Server options accept `instructions` for initialization/discovery. Prompt
|
|
|
84
87
|
registration accepts `title`; callbacks receive decoded prompt parameters.
|
|
85
88
|
|
|
86
89
|
`Tool.Strict` controls MCP input schema generation and argument validation.
|
|
87
|
-
Strict
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
90
|
+
Strict tools reject unknown keys; non-strict tools ignore them during decoding.
|
|
91
|
+
Both report all missing/invalid fields together (`errors: 'all'`), including
|
|
92
|
+
unknown keys for strict tools. Strict dynamic tools require Effect schemas;
|
|
93
|
+
raw JSON Schema cannot supply runtime strict validation and is rejected at registration.
|
|
94
|
+
|
|
95
|
+
Parameters must encode to a JSON Schema with `type: 'object'` at the root. Use
|
|
96
|
+
`Schema.Class` or `Schema.Struct`, or omit parameters/use `Tool.EmptyParams` for
|
|
97
|
+
parameterless tools. Identified object schemas are supported: the adapter inlines
|
|
98
|
+
top-level references for input/output schemas and form elicitation, retaining
|
|
99
|
+
referenced definitions. It does not turn primitive result types into objects.
|
|
100
|
+
|
|
101
|
+
Tool failure projection depends on the negotiated protocol:
|
|
102
|
+
|
|
103
|
+
| Failure | 2024-11-05 through 2025-06-18 | 2025-11-25 and 2026-07-28 |
|
|
104
|
+
| --- | --- | --- |
|
|
105
|
+
| Invalid arguments | `-32602 Invalid params` | `isError: true` tool result |
|
|
106
|
+
| Internal handler/encoding failure | `-32603 Internal error` | `isError: true` tool result |
|
|
107
|
+
| Declared tool failure | `isError: true` tool result | `isError: true` tool result |
|
|
108
|
+
|
|
109
|
+
Declared failures omit `structuredContent`; an error with an empty `message`
|
|
110
|
+
uses its encoded failure value rather than returning empty text. Internal defects
|
|
111
|
+
and encoding failures are reported internally with generic client-facing messages.
|
|
112
|
+
|
|
113
|
+
Success projection is also version-specific. The 2024-11-05/2025-03-26 adapters
|
|
114
|
+
omit structured output. The 2025-06-18/2025-11-25 adapters expose object-root
|
|
115
|
+
output schemas and object-valued `structuredContent`, omitting primitive/array
|
|
116
|
+
structured values. The 2026-07-28 adapter supports JSON-valued structured output.
|
|
117
|
+
When a protocol omits string structured content, a single text block that exactly
|
|
118
|
+
mirrors its JSON encoding is unquoted to plain text; other text blocks are preserved.
|
|
94
119
|
|
|
95
120
|
### Defining Tools
|
|
96
121
|
|
|
97
122
|
Tools are defined with `Tool.make` specifying a name, description, parameter schemas, and success schema:
|
|
98
123
|
|
|
124
|
+
<!-- typecheck -->
|
|
99
125
|
```typescript
|
|
126
|
+
import * as Schema from 'effect/Schema';
|
|
127
|
+
import { Tool } from 'effect/ai';
|
|
128
|
+
|
|
100
129
|
const GreetTool = Tool.make('GreetTool', {
|
|
101
130
|
description: 'Generate a greeting message',
|
|
102
|
-
parameters: {
|
|
131
|
+
parameters: Schema.Struct({
|
|
103
132
|
name: Schema.String,
|
|
104
133
|
style: Schema.Union([
|
|
105
134
|
Schema.Literal('formal'),
|
|
106
135
|
Schema.Literal('casual')
|
|
107
136
|
])
|
|
108
|
-
},
|
|
137
|
+
}),
|
|
109
138
|
success: Schema.String
|
|
110
139
|
});
|
|
111
140
|
|
|
112
141
|
const CalculatorTool = Tool.make('CalculatorTool', {
|
|
113
142
|
description: 'Perform basic arithmetic',
|
|
114
|
-
parameters: {
|
|
143
|
+
parameters: Schema.Struct({
|
|
115
144
|
operation: Schema.Union([
|
|
116
145
|
Schema.Literal('add'),
|
|
117
146
|
Schema.Literal('subtract'),
|
|
@@ -120,16 +149,16 @@ const CalculatorTool = Tool.make('CalculatorTool', {
|
|
|
120
149
|
]),
|
|
121
150
|
a: Schema.Number,
|
|
122
151
|
b: Schema.Number
|
|
123
|
-
},
|
|
152
|
+
}),
|
|
124
153
|
success: Schema.Number
|
|
125
154
|
});
|
|
126
155
|
|
|
127
156
|
const NotifyTool = Tool.make('NotifyTool', {
|
|
128
157
|
description: 'Send an optional notification',
|
|
129
|
-
parameters: {
|
|
158
|
+
parameters: Schema.Struct({
|
|
130
159
|
message: Schema.String,
|
|
131
160
|
channel: Schema.optionalKey(Schema.String)
|
|
132
|
-
},
|
|
161
|
+
}),
|
|
133
162
|
success: Schema.Void
|
|
134
163
|
});
|
|
135
164
|
```
|
|
@@ -184,6 +213,8 @@ Effect tool annotations are emitted as MCP tool hints:
|
|
|
184
213
|
- `Tool.Destructive` → `destructiveHint`, default `true`
|
|
185
214
|
- `Tool.Idempotent` → `idempotentHint`, default `false`
|
|
186
215
|
- `Tool.OpenWorld` → `openWorldHint`, default `true`
|
|
216
|
+
- `Tool.Title` → annotation title; 2025-06-18 and 2025-11-25 preserve it in
|
|
217
|
+
`tools/list` alongside the descriptor's top-level title and behavioral hints
|
|
187
218
|
|
|
188
219
|
These hints help clients decide how to present tools, but they are not authorization decisions. Always enforce access control in your server handlers.
|
|
189
220
|
|
|
@@ -282,6 +313,8 @@ const result = McpServer.elicit({
|
|
|
282
313
|
- Returns `Effect<S["Type"], ElicitationDeclined, McpServerClient>`
|
|
283
314
|
- Handle `ElicitationDeclined` with `catchTag` for fallback behavior
|
|
284
315
|
- If the user cancels, the effect is interrupted
|
|
316
|
+
- Identified object schemas (including named `Schema.Class` values) are accepted;
|
|
317
|
+
the requested form still has to fit the negotiated revision's supported field shapes.
|
|
285
318
|
- The negotiated client must advertise form elicitation. In `2025-11-25`, an empty `elicitation` capability is treated as form support; explicit `elicitation.form` and `elicitation.url` capabilities are otherwise gated independently.
|
|
286
319
|
|
|
287
320
|
URL elicitation is a `2025-11-25` reverse-client operation. Use the scoped client facade, then notify that specific client when the external flow completes:
|
|
@@ -380,6 +413,9 @@ For stdio transport, route logs to stderr with `Logger.LogToStderr`. Stdout is
|
|
|
380
413
|
reserved for protocol messages. `Logger.consolePretty` controls formatting;
|
|
381
414
|
the context reference controls its destination.
|
|
382
415
|
|
|
416
|
+
With a stateful protocol configured, pre-initialization stdio `ping` returns an
|
|
417
|
+
empty result without creating a session, even when a stateless adapter is listed first.
|
|
418
|
+
|
|
383
419
|
### HTTP Transport
|
|
384
420
|
|
|
385
421
|
For web-based MCP servers using Streamable HTTP:
|
|
@@ -396,7 +432,9 @@ McpServer.layerHttp({
|
|
|
396
432
|
- Requires `HttpRouter.HttpRouter` in the context
|
|
397
433
|
- Implements single-endpoint Streamable HTTP with JSON-RPC
|
|
398
434
|
- The `path` parameter sets the HTTP endpoint path
|
|
399
|
-
-
|
|
435
|
+
- For stateful revisions, non-`initialize` HTTP requests with no session id return
|
|
436
|
+
`400`; an unknown `Mcp-Session-Id` returns `404`. Keep and resend the session id
|
|
437
|
+
from initialization. The 2026-07-28 stateless adapter uses request metadata instead.
|
|
400
438
|
|
|
401
439
|
### Type signatures
|
|
402
440
|
|
|
@@ -442,7 +480,7 @@ Use `McpSchema.EnabledWhen` to conditionally list prompts, resources, resource t
|
|
|
442
480
|
```typescript
|
|
443
481
|
import { Context, Effect, Layer } from 'effect';
|
|
444
482
|
import { Schema } from 'effect';
|
|
445
|
-
import { McpSchema, McpServer, Tool } from 'effect/
|
|
483
|
+
import { McpSchema, McpServer, Tool } from 'effect/ai';
|
|
446
484
|
|
|
447
485
|
const requiresRoots = Context.make(
|
|
448
486
|
McpSchema.EnabledWhen,
|
|
@@ -481,12 +519,12 @@ const WorkspaceResource = Layer.effectDiscard(
|
|
|
481
519
|
import { NodeRuntime, NodeStdio } from '@effect/platform-node';
|
|
482
520
|
import { Effect, Layer, Logger } from 'effect';
|
|
483
521
|
import { Schema } from 'effect';
|
|
484
|
-
import { McpProtocol, McpSchema, McpServer, Tool, Toolkit } from 'effect/
|
|
522
|
+
import { McpProtocol, McpSchema, McpServer, Tool, Toolkit } from 'effect/ai';
|
|
485
523
|
|
|
486
524
|
// --- Tools ---
|
|
487
525
|
const GreetTool = Tool.make('GreetTool', {
|
|
488
526
|
description: 'Generate a greeting',
|
|
489
|
-
parameters: { name: Schema.String },
|
|
527
|
+
parameters: Schema.Struct({ name: Schema.String }),
|
|
490
528
|
success: Schema.String
|
|
491
529
|
});
|
|
492
530
|
|
|
@@ -555,7 +593,7 @@ Layer.launch(ServerLayer).pipe(NodeRuntime.runMain);
|
|
|
555
593
|
```typescript
|
|
556
594
|
const FetchTool = Tool.make('FetchData', {
|
|
557
595
|
description: 'Fetch data from database',
|
|
558
|
-
parameters: { id: Schema.String },
|
|
596
|
+
parameters: Schema.Struct({ id: Schema.String }),
|
|
559
597
|
success: Schema.String
|
|
560
598
|
});
|
|
561
599
|
|
|
@@ -617,7 +655,9 @@ const ConfigResource = McpServer.resource({
|
|
|
617
655
|
2. **Provide transport last** — `Layer.provide(McpServer.layerStdio(...))` or `Layer.provide(McpServer.layerHttp(...))`
|
|
618
656
|
3. **stderr for stdio** — Never log to stdout when using stdio transport
|
|
619
657
|
4. **Toolkit pattern** — `McpServer.toolkit(tk).pipe(Layer.provideMerge(tk.toLayer({...})))` is the canonical pattern
|
|
620
|
-
5. **Schema for parameters** —
|
|
658
|
+
5. **Schema for parameters** — `Tool.make` takes a schema with an encoded object
|
|
659
|
+
root, not a raw field record. `McpServer.prompt` still takes a field record;
|
|
660
|
+
resource template parameters take individual codecs.
|
|
621
661
|
6. **`McpSchema.param`** — Use for resource URI template parameters with automatic string codec
|
|
622
662
|
7. **`Effect.fn`** — Use for resource template content handlers that receive multiple arguments
|
|
623
663
|
8. **Launch with `Layer.launch`** — The server runs as a long-lived layer: `Layer.launch(ServerLayer).pipe(NodeRuntime.runMain)`
|
|
@@ -8,14 +8,17 @@ You are an Effect TypeScript expert specializing in observability — structured
|
|
|
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 `git show effect@4.0.0:<path>` there for this baseline; main may be ahead.
|
|
12
|
+
Keep `effect` and `@effect/*` packages on the same version. The OTLP modules and
|
|
13
|
+
`@effect/opentelemetry` APIs are tagged `@stability unstable` and may break in
|
|
14
|
+
minor releases; the shorter `effect/observability` import path is not a stability guarantee.
|
|
12
15
|
|
|
13
16
|
Reference this for:
|
|
14
17
|
|
|
15
18
|
- `Logger` module: `packages/effect/src/Logger.ts`
|
|
16
19
|
- `Tracer` module: `packages/effect/src/Tracer.ts`
|
|
17
20
|
- `Metric` module: `packages/effect/src/Metric.ts`
|
|
18
|
-
- OTLP export: `packages/effect/src/
|
|
21
|
+
- OTLP export: `packages/effect/src/observability/`
|
|
19
22
|
- Observability examples: `ai-docs/src/08_observability/`
|
|
20
23
|
|
|
21
24
|
---
|
|
@@ -178,7 +181,7 @@ const BatchedLoggerLayer = Logger.layer([batchedLogger]);
|
|
|
178
181
|
|
|
179
182
|
### File Logging
|
|
180
183
|
|
|
181
|
-
Write logs directly to a file (requires `FileSystem` from
|
|
184
|
+
Write logs directly to a file (requires the `FileSystem` service from `effect`):
|
|
182
185
|
|
|
183
186
|
```ts
|
|
184
187
|
import { NodeFileSystem } from '@effect/platform-node';
|
|
@@ -395,7 +398,7 @@ for (const metric of snapshots) {
|
|
|
395
398
|
|
|
396
399
|
## 5. OTLP Export
|
|
397
400
|
|
|
398
|
-
Effect v4 includes built-in OTLP exporters in `effect/
|
|
401
|
+
Effect v4 includes built-in OTLP exporters in `effect/observability`. No external OpenTelemetry SDK needed.
|
|
399
402
|
|
|
400
403
|
### All-in-One: `Otlp.layerJson`
|
|
401
404
|
|
|
@@ -403,8 +406,8 @@ The simplest setup — exports traces, logs, and metrics to a single OTLP endpoi
|
|
|
403
406
|
|
|
404
407
|
```ts
|
|
405
408
|
import { Layer } from 'effect';
|
|
406
|
-
import { FetchHttpClient } from 'effect/
|
|
407
|
-
import { Otlp } from 'effect/
|
|
409
|
+
import { FetchHttpClient } from 'effect/http';
|
|
410
|
+
import { Otlp } from 'effect/observability';
|
|
408
411
|
|
|
409
412
|
const ObservabilityLayer = Otlp.layerJson({
|
|
410
413
|
baseUrl: 'http://localhost:4318',
|
|
@@ -426,19 +429,26 @@ Variants:
|
|
|
426
429
|
- `Otlp.layerProtobuf` — Protobuf serialization (more efficient)
|
|
427
430
|
- `Otlp.layer` — requires you to provide `OtlpSerialization` separately
|
|
428
431
|
|
|
432
|
+
Use `Otlp.layerFromConfig()` for standard OpenTelemetry environment
|
|
433
|
+
configuration (`OTEL_EXPORTER_OTLP_ENDPOINT`, signal-specific endpoints,
|
|
434
|
+
headers, exporter selection, and `OTEL_SDK_DISABLED`). Provide both
|
|
435
|
+
`OtlpSerialization.layerJson` (or protobuf) and an `HttpClient` layer. Explicit
|
|
436
|
+
`layerJson({ baseUrl })` is the programmatic endpoint path; it is not the
|
|
437
|
+
environment-selected exporter setup.
|
|
438
|
+
|
|
429
439
|
### Individual OTLP Exporters
|
|
430
440
|
|
|
431
441
|
For fine-grained control, configure each exporter independently:
|
|
432
442
|
|
|
433
443
|
```ts
|
|
434
444
|
import { Layer } from 'effect';
|
|
435
|
-
import { FetchHttpClient } from 'effect/
|
|
445
|
+
import { FetchHttpClient } from 'effect/http';
|
|
436
446
|
import {
|
|
437
447
|
OtlpLogger,
|
|
438
448
|
OtlpMetrics,
|
|
439
449
|
OtlpSerialization,
|
|
440
450
|
OtlpTracer
|
|
441
|
-
} from 'effect/
|
|
451
|
+
} from 'effect/observability';
|
|
442
452
|
|
|
443
453
|
const OtlpTracingLayer = OtlpTracer.layer({
|
|
444
454
|
url: 'http://localhost:4318/v1/traces',
|
|
@@ -482,7 +492,7 @@ Each OTLP signal layer now outputs the shared `OtlpExporter.Flusher` service. Us
|
|
|
482
492
|
|
|
483
493
|
```ts
|
|
484
494
|
import { Effect } from 'effect';
|
|
485
|
-
import { OtlpExporter } from 'effect/
|
|
495
|
+
import { OtlpExporter } from 'effect/observability';
|
|
486
496
|
|
|
487
497
|
const flushTelemetry = Effect.gen(function* () {
|
|
488
498
|
const flusher = yield* OtlpExporter.Flusher;
|
|
@@ -490,7 +500,42 @@ const flushTelemetry = Effect.gen(function* () {
|
|
|
490
500
|
});
|
|
491
501
|
```
|
|
492
502
|
|
|
493
|
-
All signal layers in the same memoized layer graph share one flusher registry, so
|
|
503
|
+
All signal layers in the same memoized layer graph share one flusher registry, so
|
|
504
|
+
one call requests exports concurrently. `flush` cannot fail and has no built-in
|
|
505
|
+
timeout; it waits only for exports it starts, not exports already in flight.
|
|
506
|
+
Exporters in their temporary-disable window are skipped, so completion is not a
|
|
507
|
+
delivery acknowledgement. Successful delta-metric flushes advance their aggregation window.
|
|
508
|
+
|
|
509
|
+
The combined `Otlp.layer*` signatures expose `never` as their output service.
|
|
510
|
+
To access `Flusher` with the combined layer, merge `OtlpExporter.layerFlusher`
|
|
511
|
+
explicitly; memoization shares that registry with the signal layers:
|
|
512
|
+
|
|
513
|
+
<!-- typecheck -->
|
|
514
|
+
```ts
|
|
515
|
+
import { Effect, Layer } from 'effect';
|
|
516
|
+
import { FetchHttpClient } from 'effect/http';
|
|
517
|
+
import { Otlp, OtlpExporter } from 'effect/observability';
|
|
518
|
+
|
|
519
|
+
const telemetry = Layer.mergeAll(
|
|
520
|
+
Otlp.layerJson({ baseUrl: 'http://localhost:4318' }),
|
|
521
|
+
OtlpExporter.layerFlusher
|
|
522
|
+
).pipe(Layer.provide(FetchHttpClient.layer));
|
|
523
|
+
|
|
524
|
+
const flush = Effect.gen(function* () {
|
|
525
|
+
const flusher = yield* OtlpExporter.Flusher;
|
|
526
|
+
return yield* flusher.flush.pipe(Effect.timeoutOption('5 seconds'));
|
|
527
|
+
}).pipe(Effect.provide(telemetry));
|
|
528
|
+
```
|
|
529
|
+
|
|
530
|
+
### Exporter lifecycle
|
|
531
|
+
|
|
532
|
+
Keep the exporter layer alive for the application's lifetime. Scope finalization
|
|
533
|
+
starts a final export and waits for tracked in-flight exports, bounded by
|
|
534
|
+
`shutdownTimeout`; a manual flush has the narrower semantics described above.
|
|
535
|
+
The HTTP exporter drains response bodies before completing or retrying requests,
|
|
536
|
+
including error responses that carry a body. Custom/mock HTTP clients must
|
|
537
|
+
supply a consumable response body rather than only a status code. This releases
|
|
538
|
+
connections between export attempts; callers need no separate drain step.
|
|
494
539
|
|
|
495
540
|
### OTLP Layer Options
|
|
496
541
|
|
|
@@ -547,7 +592,7 @@ Export Effect metrics in Prometheus exposition format:
|
|
|
547
592
|
|
|
548
593
|
```ts
|
|
549
594
|
import { Effect, Metric } from 'effect';
|
|
550
|
-
import * as PrometheusMetrics from 'effect/
|
|
595
|
+
import * as PrometheusMetrics from 'effect/observability/PrometheusMetrics';
|
|
551
596
|
|
|
552
597
|
const program = Effect.gen(function* () {
|
|
553
598
|
const counter = Metric.counter('http_requests_total', {
|
|
@@ -572,7 +617,7 @@ const program = Effect.gen(function* () {
|
|
|
572
617
|
Automatically register a `/metrics` endpoint on your HTTP router:
|
|
573
618
|
|
|
574
619
|
```ts
|
|
575
|
-
import * as PrometheusMetrics from 'effect/
|
|
620
|
+
import * as PrometheusMetrics from 'effect/observability/PrometheusMetrics';
|
|
576
621
|
|
|
577
622
|
// Default: GET /metrics
|
|
578
623
|
const PrometheusLayer = PrometheusMetrics.layerHttp();
|
|
@@ -633,8 +678,8 @@ Use a test tracer or assert on span attributes captured via `Effect.withSpan`.
|
|
|
633
678
|
|
|
634
679
|
```ts
|
|
635
680
|
import { Config, Effect, Layer, Logger, References } from 'effect';
|
|
636
|
-
import { FetchHttpClient } from 'effect/
|
|
637
|
-
import { Otlp } from 'effect/
|
|
681
|
+
import { FetchHttpClient } from 'effect/http';
|
|
682
|
+
import { Otlp } from 'effect/observability';
|
|
638
683
|
|
|
639
684
|
const DevObservability = Logger.layer([Logger.consolePretty()]);
|
|
640
685
|
|
|
@@ -715,5 +760,6 @@ class Checkout extends Context.Service<
|
|
|
715
760
|
7. **Metric names should follow conventions** — snake_case with units suffix (e.g., `http_request_duration_ms`).
|
|
716
761
|
8. **`Metric.withAttributes` creates a tagged variant** — it does not mutate the original metric.
|
|
717
762
|
9. **`OtlpMetrics` temporality** — use `"delta"` for backends like Datadog/Dynatrace, `"cumulative"` (default) for Prometheus-style backends.
|
|
718
|
-
10. **
|
|
763
|
+
10. **OTLP modules are under `effect/observability`** — they remain tagged
|
|
764
|
+
`@stability unstable`; inspect the matching release when upgrading.
|
|
719
765
|
11. **Use `OtlpExporter.Flusher` for manual drains** — bound `flusher.flush` with `Effect.timeoutOption` when shutdown latency must be capped.
|
|
@@ -120,9 +120,9 @@ yield* Effect.all([logA, logB, logC], { concurrency: 'unbounded', discard: true
|
|
|
120
120
|
// Effect<void, E, R>
|
|
121
121
|
```
|
|
122
122
|
|
|
123
|
-
### `mode: 'result'` —
|
|
123
|
+
### `mode: 'result'` — collect typed outcomes
|
|
124
124
|
|
|
125
|
-
Each slot becomes a `Result<A, E>` and the error channel becomes `never`.
|
|
125
|
+
Each slot becomes a `Result<A, E>` and the error channel becomes `never`. Typed failures do not short-circuit the batch; defects and interruption still terminate it:
|
|
126
126
|
|
|
127
127
|
```ts
|
|
128
128
|
const results = yield* Effect.all(
|
|
@@ -233,6 +233,7 @@ const settled = yield* Effect.raceFirst(primary, secondary);
|
|
|
233
233
|
Semantics (verified in `internal/effect.ts`):
|
|
234
234
|
|
|
235
235
|
- **Loser interruption is awaited**: when a winner settles, the remaining fibers are interrupted uninterruptibly and the race only resumes with the winning exit after those interruptions (including finalizers) complete.
|
|
236
|
+
- Cleanup also covers losers still starting when the race settles or the caller is interrupted. Use the race combinators directly rather than custom start/cancel bookkeeping.
|
|
236
237
|
- `race`/`raceAll`: early failures do not finish the race — the race keeps waiting until one effect succeeds or all have failed. If all fail, the failure reasons are collected into one combined `Cause`.
|
|
237
238
|
- `raceFirst`/`raceAllFirst`: the first fiber to settle (success *or* failure) decides the outcome.
|
|
238
239
|
- Effects are forked in iteration order; if an early effect completes synchronously, later effects may never start at all.
|
|
@@ -300,17 +301,33 @@ const strong = yield* Effect.filterMapEffect(
|
|
|
300
301
|
// Array of kept, transformed values
|
|
301
302
|
```
|
|
302
303
|
|
|
303
|
-
### `Effect.partition` —
|
|
304
|
+
### `Effect.partition` — collect successes and typed failures
|
|
304
305
|
|
|
305
|
-
|
|
306
|
+
Typed failures do not short-circuit. Returns `[passes, fails]` — **successes first**. Both arrays preserve input order; defects and interruption still propagate:
|
|
306
307
|
|
|
307
308
|
```ts
|
|
308
|
-
const [
|
|
309
|
+
const [users, failures] = yield* Effect.partition(
|
|
309
310
|
userIds,
|
|
310
311
|
(id) => fetchUser(id),
|
|
311
312
|
{ concurrency: 8 }
|
|
312
313
|
);
|
|
313
|
-
// Effect<[
|
|
314
|
+
// Effect<[passes: Array<User>, fails: Array<E>], never, R>
|
|
315
|
+
```
|
|
316
|
+
|
|
317
|
+
`Arr.partition`, `Arr.separate`, the corresponding `Chunk` / `Record` helpers,
|
|
318
|
+
and `Option.partitionMap` also put successes first, matching `Stream.partition`.
|
|
319
|
+
Name destructured tuple elements by meaning rather than assuming the older order.
|
|
320
|
+
|
|
321
|
+
<!-- typecheck -->
|
|
322
|
+
```ts
|
|
323
|
+
import { Effect } from 'effect';
|
|
324
|
+
|
|
325
|
+
const partitioned = Effect.partition(
|
|
326
|
+
[1, 2, 3],
|
|
327
|
+
(n) => n === 2 ? Effect.fail('rejected') : Effect.succeed(n),
|
|
328
|
+
{ concurrency: 2 }
|
|
329
|
+
);
|
|
330
|
+
// Success value: [[1, 3], ['rejected']]
|
|
314
331
|
```
|
|
315
332
|
|
|
316
333
|
### `Effect.validate` — accumulate all failures
|
|
@@ -518,7 +535,7 @@ import { Effect } from 'effect';
|
|
|
518
535
|
|
|
519
536
|
const syncAllUsers = (userIds: ReadonlyArray<string>) =>
|
|
520
537
|
Effect.gen(function* () {
|
|
521
|
-
const [
|
|
538
|
+
const [synced, failures] = yield* Effect.partition(
|
|
522
539
|
userIds,
|
|
523
540
|
(id) => syncUser(id),
|
|
524
541
|
{ concurrency: 8 }
|
|
@@ -7,6 +7,8 @@ description: Use effect Path for platform-abstract file path operations includin
|
|
|
7
7
|
|
|
8
8
|
Use effect Path abstraction for platform-abstract file path operations. Apply this skill when working with file paths, joining segments, resolving absolute paths, or converting between file URLs and paths. `Path.layer` supplies POSIX semantics from `effect`; Node.js and Bun platform layers provide host-specific path semantics. `@effect/platform-browser` does not provide a `BrowserPath` layer, so browser code should provide `Path.layer` or a custom layer explicitly.
|
|
9
9
|
|
|
10
|
+
Targets **Effect 4.0.0** (`effect@4.0.0` source tag). In browsers without `process.cwd()`, supply an absolute POSIX base to `resolve`/`toFileUrl` when using `Path.layer`; it does not invent a browser working directory.
|
|
11
|
+
|
|
10
12
|
## Import Pattern
|
|
11
13
|
|
|
12
14
|
```typescript
|
|
@@ -195,6 +197,7 @@ Both operations can fail with `BadArgument` error.
|
|
|
195
197
|
|
|
196
198
|
### fromFileUrl - Convert file URL to path
|
|
197
199
|
|
|
200
|
+
<!-- typecheck -->
|
|
198
201
|
```typescript
|
|
199
202
|
import { Path } from 'effect';
|
|
200
203
|
import { Effect } from 'effect';
|
|
@@ -205,12 +208,13 @@ const program = Effect.gen(function* () {
|
|
|
205
208
|
new URL('file:///home/user/file.txt')
|
|
206
209
|
);
|
|
207
210
|
// "/home/user/file.txt" on Unix
|
|
208
|
-
//
|
|
211
|
+
// On Windows, use a drive-qualified URL such as file:///C:/home/user/file.txt.
|
|
209
212
|
});
|
|
210
213
|
```
|
|
211
214
|
|
|
212
215
|
### toFileUrl - Convert path to file URL
|
|
213
216
|
|
|
217
|
+
<!-- typecheck -->
|
|
214
218
|
```typescript
|
|
215
219
|
import { Path } from 'effect';
|
|
216
220
|
import { Effect } from 'effect';
|
|
@@ -224,6 +228,7 @@ const program = Effect.gen(function* () {
|
|
|
224
228
|
|
|
225
229
|
## Complete Example
|
|
226
230
|
|
|
231
|
+
<!-- typecheck -->
|
|
227
232
|
```typescript
|
|
228
233
|
import { Path } from 'effect';
|
|
229
234
|
import { Effect } from 'effect';
|
|
@@ -253,9 +258,10 @@ const buildOutputPath = Effect.gen(function* () {
|
|
|
253
258
|
|
|
254
259
|
In Effect v4, `Migrator.fromFileSystem(directory)` requires both `FileSystem.FileSystem` and `Path.Path`. Migration modules are imported through `path.toFileUrl(path.join(directory, file))` so absolute Windows paths are valid ESM specifiers.
|
|
255
260
|
|
|
261
|
+
<!-- typecheck -->
|
|
256
262
|
```typescript
|
|
257
263
|
import { FileSystem, Path } from 'effect';
|
|
258
|
-
import * as Migrator from 'effect/
|
|
264
|
+
import * as Migrator from 'effect/sql/Migrator';
|
|
259
265
|
|
|
260
266
|
const loader: Migrator.Loader<FileSystem.FileSystem | Path.Path> =
|
|
261
267
|
Migrator.fromFileSystem('./migrations');
|