opencode-effect-enforcer 0.2.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/LICENSE +21 -0
- package/README.md +278 -0
- package/guidance/effect-first-development.md +1247 -0
- package/guidance/post__effect-and-the-near-inexpressible-majesty-of-layers.md +490 -0
- package/guidance/post__parse-dont-validate.md +109 -0
- package/guidance/progressive-disclosure-guidance.md +38 -0
- package/package.json +63 -0
- package/patterns/avoid-any.md +37 -0
- package/patterns/avoid-data-tagged-error.md +34 -0
- package/patterns/avoid-direct-json.md +51 -0
- package/patterns/avoid-direct-tag-checks.md +54 -0
- package/patterns/avoid-expect-in-if.md +52 -0
- package/patterns/avoid-mutable-state.md +70 -0
- package/patterns/avoid-native-fetch.md +61 -0
- package/patterns/avoid-node-imports.md +86 -0
- package/patterns/avoid-non-null-assertion.md +44 -0
- package/patterns/avoid-object-type.md +46 -0
- package/patterns/avoid-option-getorthrow.md +39 -0
- package/patterns/avoid-platform-coupling.md +43 -0
- package/patterns/avoid-process-env.md +43 -0
- package/patterns/avoid-react-hooks.md +73 -0
- package/patterns/avoid-schema-suffix.md +45 -0
- package/patterns/avoid-sync-fs.md +68 -0
- package/patterns/avoid-try-catch.md +47 -0
- package/patterns/avoid-ts-ignore.md +38 -0
- package/patterns/avoid-untagged-errors.md +67 -0
- package/patterns/avoid-yield-ref.md +46 -0
- package/patterns/casting-awareness.md +46 -0
- package/patterns/context-tag-extends.md +84 -0
- package/patterns/effect-catchall-default.md +61 -0
- package/patterns/effect-promise-vs-trypromise.md +47 -0
- package/patterns/effect-run-in-body.md +58 -0
- package/patterns/imperative-loops.md +76 -0
- package/patterns/prefer-arr-sort.md +52 -0
- package/patterns/prefer-duration-values.md +56 -0
- package/patterns/prefer-effect-fn.md +161 -0
- package/patterns/prefer-match-over-switch.md +48 -0
- package/patterns/prefer-option-over-null.md +56 -0
- package/patterns/prefer-redacted-config.md +70 -0
- package/patterns/prefer-schema-class.md +54 -0
- package/patterns/require-effect-concurrency.md +83 -0
- package/patterns/stream-large-files.md +63 -0
- package/patterns/throw-in-effect-gen.md +62 -0
- package/patterns/use-clock-service.md +45 -0
- package/patterns/use-command-executor-service.md +54 -0
- package/patterns/use-console-service.md +54 -0
- package/patterns/use-filesystem-service.md +59 -0
- package/patterns/use-http-client-service.md +77 -0
- package/patterns/use-path-service.md +53 -0
- package/patterns/use-random-service.md +45 -0
- package/patterns/use-temp-file-scoped.md +66 -0
- package/patterns/vm-in-wrong-file.md +51 -0
- package/patterns/yield-in-for-loop.md +61 -0
- package/skills/effect-ai-chat/SKILL.md +472 -0
- package/skills/effect-ai-language-model/SKILL.md +652 -0
- package/skills/effect-ai-prompt/SKILL.md +752 -0
- package/skills/effect-ai-provider/SKILL.md +668 -0
- package/skills/effect-ai-streaming/SKILL.md +418 -0
- package/skills/effect-ai-tool/SKILL.md +1132 -0
- package/skills/effect-atom-rpc/SKILL.md +488 -0
- package/skills/effect-atom-state/SKILL.md +640 -0
- package/skills/effect-batching/SKILL.md +614 -0
- package/skills/effect-cache/SKILL.md +570 -0
- package/skills/effect-cli/SKILL.md +523 -0
- package/skills/effect-command-executor/SKILL.md +675 -0
- package/skills/effect-concurrency-testing/SKILL.md +612 -0
- package/skills/effect-config/SKILL.md +580 -0
- package/skills/effect-context-witness/SKILL.md +274 -0
- package/skills/effect-domain-modeling/SKILL.md +1212 -0
- package/skills/effect-domain-predicates/SKILL.md +867 -0
- package/skills/effect-error-handling/SKILL.md +1581 -0
- package/skills/effect-fiber/SKILL.md +731 -0
- package/skills/effect-filesystem/SKILL.md +624 -0
- package/skills/effect-graph/SKILL.md +571 -0
- package/skills/effect-http-api/SKILL.md +1760 -0
- package/skills/effect-http-client/SKILL.md +989 -0
- package/skills/effect-http-server/SKILL.md +920 -0
- package/skills/effect-incremental-migration/SKILL.md +362 -0
- package/skills/effect-layer-design/SKILL.md +642 -0
- package/skills/effect-managed-runtime/SKILL.md +395 -0
- package/skills/effect-mcp-server/SKILL.md +608 -0
- package/skills/effect-observability/SKILL.md +719 -0
- package/skills/effect-optics/SKILL.md +554 -0
- package/skills/effect-parallelization/SKILL.md +668 -0
- package/skills/effect-path/SKILL.md +296 -0
- package/skills/effect-pattern-matching/SKILL.md +914 -0
- package/skills/effect-platform-abstraction/SKILL.md +1175 -0
- package/skills/effect-platform-layers/SKILL.md +514 -0
- package/skills/effect-pubsub-event-bus/SKILL.md +384 -0
- package/skills/effect-react-composition/SKILL.md +986 -0
- package/skills/effect-react-vm/SKILL.md +675 -0
- package/skills/effect-rpc-api/SKILL.md +624 -0
- package/skills/effect-rpc-client/SKILL.md +666 -0
- package/skills/effect-rpc-cluster/SKILL.md +1623 -0
- package/skills/effect-rpc-server/SKILL.md +767 -0
- package/skills/effect-scheduling/SKILL.md +124 -0
- package/skills/effect-schema-composition/SKILL.md +975 -0
- package/skills/effect-schema-v4/SKILL.md +691 -0
- package/skills/effect-scope/SKILL.md +682 -0
- package/skills/effect-service-implementation/SKILL.md +656 -0
- package/skills/effect-socket/SKILL.md +703 -0
- package/skills/effect-sql/SKILL.md +781 -0
- package/skills/effect-stream/SKILL.md +765 -0
- package/skills/effect-testing/SKILL.md +1331 -0
- package/skills/effect-typeclass-design/SKILL.md +161 -0
- package/skills/effect-wide-events/Article.md +66 -0
- package/skills/effect-wide-events/SKILL.md +95 -0
- package/skills/effect-workflow/SKILL.md +810 -0
- package/src/agent-policy.ts +22 -0
- package/src/enforcer.ts +104 -0
- package/src/frontmatter.ts +34 -0
- package/src/guidance.ts +66 -0
- package/src/index.ts +38 -0
- package/src/pattern-catalog.ts +115 -0
- package/src/pattern-matcher.ts +178 -0
- package/src/pattern.ts +97 -0
- package/src/skills.ts +29 -0
- package/src/write-projection.ts +66 -0
|
@@ -0,0 +1,691 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: effect-schema-v4
|
|
3
|
+
description: Authoritative reference for Effect Schema v4 API changes and migration patterns from v3. Use this skill when writing Schema code to avoid v3 patterns, when migrating existing v3 Schema code, or when unsure about current Schema API names.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
You are an Effect TypeScript expert. This skill is a **migration reference** — it documents the key v4 Schema API changes so you don't accidentally use v3 patterns.
|
|
7
|
+
|
|
8
|
+
## Effect Source Reference
|
|
9
|
+
|
|
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.
|
|
12
|
+
|
|
13
|
+
Reference this for:
|
|
14
|
+
|
|
15
|
+
- Full Schema API: `packages/effect/SCHEMA.md`
|
|
16
|
+
- Migration guide: `migration/schema.md`
|
|
17
|
+
- SchemaTransformation module: `packages/effect/src/SchemaTransformation.ts`
|
|
18
|
+
- SchemaGetter module: `packages/effect/src/SchemaGetter.ts`
|
|
19
|
+
- Schema representation model and reference policy: `packages/effect/src/SchemaRepresentation.ts`
|
|
20
|
+
|
|
21
|
+
## 1. Key Renames (find-and-replace safe)
|
|
22
|
+
|
|
23
|
+
| v3 | v4 | Notes |
|
|
24
|
+
| ----------------------------- | ----------------------------------- | ----------------------------------------- |
|
|
25
|
+
| `annotations(ann)` | `annotate(ann)` | |
|
|
26
|
+
| `compose(schemaB)` | `decodeTo(schemaB)` or `decodeTo(schemaB, transformation)` | Transformation is optional; omitted uses passthrough composition |
|
|
27
|
+
| `typeSchema(schema)` | `toType(schema)` | |
|
|
28
|
+
| `asSchema(schema)` | `revealCodec(schema)` | |
|
|
29
|
+
| `equivalence()` | `toEquivalence()` | |
|
|
30
|
+
| `arbitrary()` | `toArbitrary()` | Returns a factory that accepts the `fast-check` module |
|
|
31
|
+
| `pretty()` | `toFormatter()` | |
|
|
32
|
+
| `parseJson()` | `fromJsonString(Schema.Unknown)` | `UnknownFromJsonString` is internal as of beta.103 |
|
|
33
|
+
| `parseJson(schema)` | `fromJsonString(schema)` | With-schema version |
|
|
34
|
+
| `TaggedErrorClass` | `TaggedError` | Renamed in beta.104 |
|
|
35
|
+
| `ErrorClass` | `Error` | Renamed in beta.104 |
|
|
36
|
+
| `Error` (instance schema) | `ErrorInstance` | Renamed in beta.104 |
|
|
37
|
+
| `BigIntFromSelf` | `BigInt` | |
|
|
38
|
+
| `SymbolFromSelf` | `Symbol` | |
|
|
39
|
+
| `URLFromSelf` | `URL` | |
|
|
40
|
+
| `DateFromSelf` | `Date` | All `*FromSelf` drop the suffix |
|
|
41
|
+
| `DurationFromSelf` | `Duration` | |
|
|
42
|
+
| `OptionFromSelf` | `Option` | |
|
|
43
|
+
| `EitherFromSelf` | `Result` | Also renamed from Either to Result |
|
|
44
|
+
| `RedactedFromSelf` | `Redacted` | Expects `Redacted` values; JSON encoding is allowed by default |
|
|
45
|
+
| `Redacted` | `RedactedFromValue` | Raw value → `Redacted`; encoding is allowed by default |
|
|
46
|
+
| `ChunkFromSelf` | `Chunk` | `*FromSelf` suffix removed |
|
|
47
|
+
| `ReadonlyMapFromSelf` | `ReadonlyMap` | `*FromSelf` suffix removed |
|
|
48
|
+
| `ReadonlySetFromSelf` | `ReadonlySet` | `*FromSelf` suffix removed |
|
|
49
|
+
| `HashMapFromSelf` | `HashMap` | `*FromSelf` suffix removed |
|
|
50
|
+
| `HashSetFromSelf` | `HashSet` | `*FromSelf` suffix removed |
|
|
51
|
+
| `BigDecimalFromSelf` | `BigDecimal` | `*FromSelf` suffix removed |
|
|
52
|
+
| `CauseFromSelf` | `Cause` | `*FromSelf` suffix removed |
|
|
53
|
+
| `ExitFromSelf` | `Exit` | `*FromSelf` suffix removed |
|
|
54
|
+
| `RegExpFromSelf` | `RegExp` | `*FromSelf` suffix removed |
|
|
55
|
+
| `encodedSchema(schema)` | `toEncoded(schema)` | |
|
|
56
|
+
| `decodingFallback` annotation | `catchDecoding(...)` | Annotation replaced by combinator |
|
|
57
|
+
| `Literal(null)` | `Null` | Standalone schema for null |
|
|
58
|
+
| `standardSchemaV1` | `toStandardSchemaV1` | |
|
|
59
|
+
| `nonEmptyString` | `isNonEmpty()` | Now used with `.check()` |
|
|
60
|
+
| `disableValidation` | `disableChecks` | In `MakeOptions` for Class constructors |
|
|
61
|
+
| standalone `SchemaError` module | `Schema.SchemaError` | The root `SchemaError` namespace export was removed in rc.108; use `Schema.isSchemaError` to narrow |
|
|
62
|
+
|
|
63
|
+
### Parser/Codec Function Renames
|
|
64
|
+
|
|
65
|
+
All parsing functions were renamed to clarify whether they return an `Effect` or an `Exit`:
|
|
66
|
+
|
|
67
|
+
| v3 | v4 |
|
|
68
|
+
| --------------------- | --------------------- |
|
|
69
|
+
| `decodeUnknown` | `decodeUnknownEffect` |
|
|
70
|
+
| `decode` | `decodeEffect` |
|
|
71
|
+
| `decodeUnknownEither` | `decodeUnknownExit` |
|
|
72
|
+
| `decodeEither` | `decodeExit` |
|
|
73
|
+
| `encodeUnknown` | `encodeUnknownEffect` |
|
|
74
|
+
| `encode` | `encodeEffect` |
|
|
75
|
+
| `encodeUnknownEither` | `encodeUnknownExit` |
|
|
76
|
+
| `encodeEither` | `encodeExit` |
|
|
77
|
+
|
|
78
|
+
Note: `decodeUnknownSync` and `encodeSync` are **unchanged** — they still exist on `Schema`.
|
|
79
|
+
|
|
80
|
+
**Redacted encoding:** `Schema.Redacted` and `Schema.RedactedFromValue` encode by default. Opt out with `Schema.Redacted(schema, { disallowJsonEncode: true })` for JSON encoding or `Schema.RedactedFromValue(schema, { disallowEncode: true })` for all encoding.
|
|
81
|
+
|
|
82
|
+
## 2. Variadic → Array Arguments
|
|
83
|
+
|
|
84
|
+
Several APIs that accepted variadic args now take arrays:
|
|
85
|
+
|
|
86
|
+
```ts
|
|
87
|
+
// v3
|
|
88
|
+
Schema.Literal('a', 'b');
|
|
89
|
+
Schema.Union(A, B);
|
|
90
|
+
Schema.Tuple(A, B);
|
|
91
|
+
Schema.TemplateLiteral(A, B);
|
|
92
|
+
|
|
93
|
+
// v4
|
|
94
|
+
Schema.Literals(['a', 'b']); // Note: single Literal("a") still exists
|
|
95
|
+
Schema.Union([A, B]); // Array form is the canonical v4 signature
|
|
96
|
+
Schema.Tuple([A, B]);
|
|
97
|
+
Schema.TemplateLiteral([A, B]);
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
### Record: Object → Positional Args
|
|
101
|
+
|
|
102
|
+
```ts
|
|
103
|
+
// v3
|
|
104
|
+
Schema.Record({ key: Schema.String, value: Schema.Number });
|
|
105
|
+
|
|
106
|
+
// v4
|
|
107
|
+
Schema.Record(Schema.String, Schema.Number);
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
## 3. Filter → Check Migration
|
|
111
|
+
|
|
112
|
+
The `.pipe(Schema.filter(...))` pattern is replaced by `.check()` with `is*` prefixed validators:
|
|
113
|
+
|
|
114
|
+
```ts
|
|
115
|
+
// v3
|
|
116
|
+
Schema.String.pipe(Schema.minLength(5));
|
|
117
|
+
Schema.Number.pipe(Schema.int());
|
|
118
|
+
Schema.Number.pipe(Schema.greaterThan(0));
|
|
119
|
+
|
|
120
|
+
// v4
|
|
121
|
+
Schema.String.check(Schema.isMinLength(5));
|
|
122
|
+
Schema.Number.check(Schema.isInt());
|
|
123
|
+
Schema.Number.check(Schema.isGreaterThan(0));
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
### Complete Filter Rename Table
|
|
127
|
+
|
|
128
|
+
| v3 filter | v4 check |
|
|
129
|
+
| ------------------------- | --------------------------------- |
|
|
130
|
+
| `greaterThan(n)` | `isGreaterThan(n)` |
|
|
131
|
+
| `greaterThanOrEqualTo(n)` | `isGreaterThanOrEqualTo(n)` |
|
|
132
|
+
| `lessThan(n)` | `isLessThan(n)` |
|
|
133
|
+
| `lessThanOrEqualTo(n)` | `isLessThanOrEqualTo(n)` |
|
|
134
|
+
| `between(min, max)` | `isBetween({ minimum, maximum })` |
|
|
135
|
+
| `int()` | `isInt()` |
|
|
136
|
+
| `multipleOf(n)` | `isMultipleOf(n)` |
|
|
137
|
+
| `finite` | `isFinite()` |
|
|
138
|
+
| `minLength(n)` | `isMinLength(n)` |
|
|
139
|
+
| `maxLength(n)` | `isMaxLength(n)` |
|
|
140
|
+
| `length(n)` | `isLengthBetween(n, n)` |
|
|
141
|
+
| `pattern(regex)` | `isPattern(regex)` |
|
|
142
|
+
| `nonEmptyString` | `isNonEmpty()` |
|
|
143
|
+
|
|
144
|
+
### Removed Filters (no v4 equivalent)
|
|
145
|
+
|
|
146
|
+
`positive`, `negative`, `nonNegative`, `nonPositive` — build these yourself:
|
|
147
|
+
|
|
148
|
+
```ts
|
|
149
|
+
// v4: replace removed convenience filters
|
|
150
|
+
const isPositive = Schema.isGreaterThan(0);
|
|
151
|
+
const isNonNegative = Schema.isGreaterThanOrEqualTo(0);
|
|
152
|
+
const isNegative = Schema.isLessThan(0);
|
|
153
|
+
const isNonPositive = Schema.isLessThanOrEqualTo(0);
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
For the common non-negative safe-integer domain, use the canonical `Schema.Natural` added in beta.102 instead of composing checks manually.
|
|
157
|
+
|
|
158
|
+
### Custom Filters
|
|
159
|
+
|
|
160
|
+
```ts
|
|
161
|
+
// v3: inline predicate filter
|
|
162
|
+
Schema.String.pipe(Schema.filter((s) => s.length > 0));
|
|
163
|
+
|
|
164
|
+
// v4: use makeFilter
|
|
165
|
+
Schema.String.check(Schema.makeFilter((s) => s.length > 0));
|
|
166
|
+
|
|
167
|
+
// v3: refinement filter
|
|
168
|
+
Schema.Option(Schema.String).pipe(Schema.filter(Option.isSome));
|
|
169
|
+
|
|
170
|
+
// v4: use refine for type-narrowing predicates
|
|
171
|
+
Schema.Option(Schema.String).pipe(Schema.refine(Option.isSome));
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
### String Transforms (not filters)
|
|
175
|
+
|
|
176
|
+
```ts
|
|
177
|
+
// v4: string transformations use SchemaTransformation + .decode()
|
|
178
|
+
import { Schema, SchemaTransformation } from 'effect';
|
|
179
|
+
|
|
180
|
+
Schema.String.pipe(Schema.decode(SchemaTransformation.trim()));
|
|
181
|
+
Schema.String.pipe(Schema.decode(SchemaTransformation.toLowerCase()));
|
|
182
|
+
Schema.String.pipe(Schema.decode(SchemaTransformation.toUpperCase()));
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
## 4. Transform Migration
|
|
186
|
+
|
|
187
|
+
`Schema.transform` and `Schema.transformOrFail` no longer exist as standalone functions. Use `Schema.decodeTo` with `SchemaTransformation`:
|
|
188
|
+
|
|
189
|
+
### Pure Transform
|
|
190
|
+
|
|
191
|
+
```ts
|
|
192
|
+
// v3
|
|
193
|
+
const BoolFromString = Schema.transform(
|
|
194
|
+
Schema.Literal('on', 'off'),
|
|
195
|
+
Schema.Boolean,
|
|
196
|
+
{
|
|
197
|
+
strict: true,
|
|
198
|
+
decode: (literal) => literal === 'on',
|
|
199
|
+
encode: (bool) => (bool ? 'on' : 'off')
|
|
200
|
+
}
|
|
201
|
+
);
|
|
202
|
+
|
|
203
|
+
// v4
|
|
204
|
+
import { Schema, SchemaTransformation } from 'effect';
|
|
205
|
+
|
|
206
|
+
const BoolFromString = Schema.Literals(['on', 'off']).pipe(
|
|
207
|
+
Schema.decodeTo(
|
|
208
|
+
Schema.Boolean,
|
|
209
|
+
SchemaTransformation.transform({
|
|
210
|
+
decode: (literal) => literal === 'on',
|
|
211
|
+
encode: (bool) => (bool ? 'on' : 'off')
|
|
212
|
+
})
|
|
213
|
+
)
|
|
214
|
+
);
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
### Fallible Transform
|
|
218
|
+
|
|
219
|
+
```ts
|
|
220
|
+
// v3
|
|
221
|
+
const NumberFromString = Schema.transformOrFail(Schema.String, Schema.Number, {
|
|
222
|
+
strict: true,
|
|
223
|
+
decode: (input, _, ast) => {
|
|
224
|
+
const parsed = parseFloat(input);
|
|
225
|
+
if (isNaN(parsed)) {
|
|
226
|
+
return ParseResult.fail(
|
|
227
|
+
new ParseResult.Type(ast, input, 'Not a number')
|
|
228
|
+
);
|
|
229
|
+
}
|
|
230
|
+
return ParseResult.succeed(parsed);
|
|
231
|
+
},
|
|
232
|
+
encode: (input) => ParseResult.succeed(input.toString())
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
// v4
|
|
236
|
+
import {
|
|
237
|
+
Effect,
|
|
238
|
+
Number,
|
|
239
|
+
Option,
|
|
240
|
+
Schema,
|
|
241
|
+
SchemaGetter,
|
|
242
|
+
SchemaIssue
|
|
243
|
+
} from 'effect';
|
|
244
|
+
|
|
245
|
+
const NumberFromString = Schema.String.pipe(
|
|
246
|
+
Schema.decodeTo(Schema.Number, {
|
|
247
|
+
decode: SchemaGetter.transformOrFail((s) =>
|
|
248
|
+
Option.match(Number.parse(s), {
|
|
249
|
+
onNone: () =>
|
|
250
|
+
Effect.fail(
|
|
251
|
+
new SchemaIssue.InvalidValue(Option.some(s))
|
|
252
|
+
),
|
|
253
|
+
onSome: (n) => Effect.succeed(n)
|
|
254
|
+
})
|
|
255
|
+
),
|
|
256
|
+
encode: SchemaGetter.String()
|
|
257
|
+
})
|
|
258
|
+
);
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
### Literal Transforms
|
|
262
|
+
|
|
263
|
+
```ts
|
|
264
|
+
// v3
|
|
265
|
+
Schema.transformLiteral(0, 'a');
|
|
266
|
+
Schema.transformLiterals([0, 'a'], [1, 'b']);
|
|
267
|
+
|
|
268
|
+
// v4
|
|
269
|
+
Schema.Literal(0).transform('a');
|
|
270
|
+
Schema.Literals([0, 1]).transform(['a', 'b']);
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
## 5. Schema.Data Removal
|
|
274
|
+
|
|
275
|
+
`Schema.Data` is **removed** in v4. No replacement needed — `Equal.equals` now does deep structural comparison on plain objects by default.
|
|
276
|
+
|
|
277
|
+
```ts
|
|
278
|
+
// v3: needed Schema.Data for structural equality
|
|
279
|
+
const PersonData = Schema.Data(Schema.Struct({ name: Schema.String }));
|
|
280
|
+
|
|
281
|
+
// v4: just use the struct directly — equality works out of the box
|
|
282
|
+
const Person = Schema.Struct({ name: Schema.String });
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
## 6. Structural Operations via mapFields
|
|
286
|
+
|
|
287
|
+
`pick`, `omit`, `partial`, `required`, and `extend` are now expressed through `mapFields`:
|
|
288
|
+
|
|
289
|
+
```ts
|
|
290
|
+
import { Schema, Struct } from 'effect';
|
|
291
|
+
|
|
292
|
+
const base = Schema.Struct({
|
|
293
|
+
a: Schema.String,
|
|
294
|
+
b: Schema.Number,
|
|
295
|
+
c: Schema.Boolean
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
// pick
|
|
299
|
+
base.mapFields(Struct.pick(['a']));
|
|
300
|
+
|
|
301
|
+
// omit
|
|
302
|
+
base.mapFields(Struct.omit(['b']));
|
|
303
|
+
|
|
304
|
+
// partial (allows undefined)
|
|
305
|
+
base.mapFields(Struct.map(Schema.optional));
|
|
306
|
+
|
|
307
|
+
// partial exact (key can be absent, no undefined)
|
|
308
|
+
base.mapFields(Struct.map(Schema.optionalKey));
|
|
309
|
+
|
|
310
|
+
// partial subset
|
|
311
|
+
base.mapFields(Struct.mapPick(['a'], Schema.optional));
|
|
312
|
+
|
|
313
|
+
// required
|
|
314
|
+
base.mapFields(Struct.map(Schema.requiredKey));
|
|
315
|
+
|
|
316
|
+
// extend (add fields)
|
|
317
|
+
base.mapFields(Struct.assign({ d: Schema.Date }));
|
|
318
|
+
// or:
|
|
319
|
+
base.pipe(Schema.fieldsAssign({ d: Schema.Date }));
|
|
320
|
+
```
|
|
321
|
+
|
|
322
|
+
### attachPropertySignature → mapFields + tagDefaultOmit
|
|
323
|
+
|
|
324
|
+
```ts
|
|
325
|
+
// v3
|
|
326
|
+
Circle.pipe(Schema.attachPropertySignature('kind', 'circle'));
|
|
327
|
+
|
|
328
|
+
// v4
|
|
329
|
+
Circle.mapFields((fields) => ({
|
|
330
|
+
...fields,
|
|
331
|
+
kind: Schema.tagDefaultOmit('circle')
|
|
332
|
+
}));
|
|
333
|
+
```
|
|
334
|
+
|
|
335
|
+
## 7. Optional Keys: optionalKey vs optional
|
|
336
|
+
|
|
337
|
+
v4 distinguishes between two kinds of optional struct fields:
|
|
338
|
+
|
|
339
|
+
| API | TypeScript type | Meaning |
|
|
340
|
+
| ----------------------- | ----------------------------- | ------------------------------------------- |
|
|
341
|
+
| `Schema.optionalKey(S)` | `readonly a?: T` | Key may be absent (exact optional) |
|
|
342
|
+
| `Schema.optional(S)` | `readonly a?: T \| undefined` | Key may be absent OR explicitly `undefined` |
|
|
343
|
+
| `Schema.mutableKey(S)` | `a: T` | Writable (removes `readonly`) |
|
|
344
|
+
|
|
345
|
+
Use `Schema.withDecodingDefaultKey` / `Schema.withDecodingDefault` when the default is on the schema **Encoded** side. For transformed schemas (for example `Schema.FiniteFromString`), that means the default is the pre-decoded input such as `'1'`.
|
|
346
|
+
|
|
347
|
+
Use `Schema.withDecodingDefaultTypeKey` / `Schema.withDecodingDefaultType` when the default is on the decoded **Type** side, such as `1` for `Schema.FiniteFromString`.
|
|
348
|
+
|
|
349
|
+
Defaults are `Effect` values: they may require services and may fail with `Schema.SchemaError`.
|
|
350
|
+
|
|
351
|
+
```ts
|
|
352
|
+
import { Effect, Schema, SchemaGetter } from 'effect';
|
|
353
|
+
|
|
354
|
+
const User = Schema.Struct({
|
|
355
|
+
name: Schema.String.pipe(
|
|
356
|
+
Schema.withDecodingDefaultKey(Effect.succeed('anonymous'))
|
|
357
|
+
),
|
|
358
|
+
role: Schema.String.pipe(
|
|
359
|
+
Schema.withDecodingDefault(Effect.succeed('viewer'))
|
|
360
|
+
),
|
|
361
|
+
retriesEncoded: Schema.FiniteFromString.pipe(
|
|
362
|
+
Schema.withDecodingDefault(Effect.succeed('1'))
|
|
363
|
+
),
|
|
364
|
+
retriesType: Schema.FiniteFromString.pipe(
|
|
365
|
+
Schema.withDecodingDefaultType(Effect.succeed(1))
|
|
366
|
+
),
|
|
367
|
+
quotaTypeKey: Schema.FiniteFromString.pipe(
|
|
368
|
+
Schema.withDecodingDefaultTypeKey(Effect.succeed(10))
|
|
369
|
+
)
|
|
370
|
+
});
|
|
371
|
+
|
|
372
|
+
const fallback = SchemaGetter.withDefault(Effect.succeed('viewer'));
|
|
373
|
+
```
|
|
374
|
+
|
|
375
|
+
### Later v4 Updates
|
|
376
|
+
|
|
377
|
+
- `Schema.makeEffect(input, options?)` on schemas and schema-backed classes returns an `Effect` that fails directly with `SchemaIssue.Issue`, not `Schema.SchemaError`.
|
|
378
|
+
- `Schema.resolveInto` was renamed to `Schema.resolveAnnotations`.
|
|
379
|
+
- `Schema.resolveAnnotationsKey(schema)` returns key-level annotations.
|
|
380
|
+
- `Schema.annotateEncoded({...})` annotates the encoded side of a transformed schema; use `Schema.annotate({...})` for the decoded Type side.
|
|
381
|
+
- Schemas are directly extendable as classes; `Schema.asClass` was removed in beta.102.
|
|
382
|
+
- `Schema.toArbitrary(schema)` returns a factory that must be called with the `fast-check` module; `Schema.toArbitraryLazy` and arbitrary derivation reports were removed in beta.106.
|
|
383
|
+
- New built-in schemas:
|
|
384
|
+
- `Schema.DateFromString`
|
|
385
|
+
- `Schema.BigIntFromString`
|
|
386
|
+
- `Schema.BigDecimalFromString`
|
|
387
|
+
- `Schema.TimeZoneNamedFromString`
|
|
388
|
+
- `Schema.TimeZoneFromString`
|
|
389
|
+
- `Schema.DateTimeZonedFromString`
|
|
390
|
+
- `Schema.DurationFromString`
|
|
391
|
+
- `Schema.StringFromBase64`
|
|
392
|
+
- `Schema.StringFromBase64Url`
|
|
393
|
+
- `Schema.StringFromHex`
|
|
394
|
+
- `Schema.StringFromUriComponent`
|
|
395
|
+
- `Schema.JsonObject`
|
|
396
|
+
|
|
397
|
+
```ts
|
|
398
|
+
import { Effect, Schema } from 'effect';
|
|
399
|
+
import * as FastCheck from 'fast-check';
|
|
400
|
+
|
|
401
|
+
class UserName extends Schema.NonEmptyString {
|
|
402
|
+
static readonly decodeUnknownSync = Schema.decodeUnknownSync(this);
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
const name = UserName.decodeUnknownSync('alice');
|
|
406
|
+
|
|
407
|
+
const resolved = Schema.resolveAnnotations(
|
|
408
|
+
Schema.String.annotate({ description: 'Display name' })
|
|
409
|
+
);
|
|
410
|
+
|
|
411
|
+
const resolvedKey = Schema.resolveAnnotationsKey(
|
|
412
|
+
Schema.String.annotateKey({ description: 'Primary user id' })
|
|
413
|
+
);
|
|
414
|
+
|
|
415
|
+
const annotatedEncoded = Schema.NumberFromString.pipe(
|
|
416
|
+
Schema.annotateEncoded({ description: 'Numeric string input' })
|
|
417
|
+
);
|
|
418
|
+
|
|
419
|
+
const duration = Schema.DurationFromString;
|
|
420
|
+
|
|
421
|
+
const parsed = Schema.String.makeEffect('alice');
|
|
422
|
+
|
|
423
|
+
const makeNameArbitrary = Schema.toArbitrary(Schema.NonEmptyString);
|
|
424
|
+
const nameArbitrary = makeNameArbitrary(FastCheck);
|
|
425
|
+
```
|
|
426
|
+
|
|
427
|
+
### Graph Schemas
|
|
428
|
+
|
|
429
|
+
`Schema.Graph(kind, node, edge)` models immutable Effect graphs. Its canonical JSON codec encodes the active indexed snapshot, preserving sparse active node and edge indexes, isolated nodes, parallel edges, self-loops, and stored edge orientation:
|
|
430
|
+
|
|
431
|
+
```ts
|
|
432
|
+
import { Graph, Schema } from 'effect';
|
|
433
|
+
|
|
434
|
+
const DirectedGraphJson = Schema.toCodecJson(
|
|
435
|
+
Schema.Graph('directed', Schema.String, Schema.Number)
|
|
436
|
+
);
|
|
437
|
+
|
|
438
|
+
const graph = Graph.fromSnapshot({
|
|
439
|
+
type: 'directed',
|
|
440
|
+
nodes: [
|
|
441
|
+
{ index: 2, data: 'A' },
|
|
442
|
+
{ index: 5, data: 'B' }
|
|
443
|
+
],
|
|
444
|
+
edges: [{ index: 3, source: 2, target: 5, data: 1 }]
|
|
445
|
+
});
|
|
446
|
+
|
|
447
|
+
const snapshot = Schema.encodeSync(DirectedGraphJson)(graph);
|
|
448
|
+
const restored = Schema.decodeUnknownSync(DirectedGraphJson)(snapshot);
|
|
449
|
+
```
|
|
450
|
+
|
|
451
|
+
Encoding rejects mutable graphs and graphs of the wrong kind. Decoding requires strictly increasing non-negative safe-integer indexes and valid edge endpoints. Removed-ID allocator history is not encoded; future allocation resumes after the greatest active index. Do not use `graph.toJSON()` for persistence because it is only an inspection summary.
|
|
452
|
+
|
|
453
|
+
### JSON Object Schema
|
|
454
|
+
|
|
455
|
+
Use `Schema.JsonObject` for a readonly string-keyed record containing JSON-compatible values. It is the canonical equivalent of `Schema.Record(Schema.String, Schema.Json)` and rejects arrays and primitive JSON values; use `Schema.Json` when any JSON value is allowed.
|
|
456
|
+
|
|
457
|
+
```ts
|
|
458
|
+
Schema.decodeUnknownOption(Schema.JsonObject)({ key: [1, true, null] });
|
|
459
|
+
Schema.decodeUnknownOption(Schema.JsonObject)([1, 2, 3]); // Option.none()
|
|
460
|
+
```
|
|
461
|
+
|
|
462
|
+
### Representation Reference Policy
|
|
463
|
+
|
|
464
|
+
`Schema.toRepresentation`, `SchemaRepresentation.toRepresentation`, and `SchemaRepresentation.toRepresentations` accept `{ referencePolicy }`. The callback runs once per encoded-side AST candidate after occurrences have been counted:
|
|
465
|
+
|
|
466
|
+
```ts
|
|
467
|
+
import { Schema, SchemaRepresentation } from 'effect';
|
|
468
|
+
|
|
469
|
+
const Item = Schema.Struct({ name: Schema.String });
|
|
470
|
+
|
|
471
|
+
const document = SchemaRepresentation.toRepresentations(
|
|
472
|
+
[Item.ast, Item.ast],
|
|
473
|
+
{
|
|
474
|
+
referencePolicy: ({ ast, identifier, occurrences }) =>
|
|
475
|
+
identifier ?? (occurrences > 1 ? `${ast._tag}_` : undefined)
|
|
476
|
+
}
|
|
477
|
+
);
|
|
478
|
+
```
|
|
479
|
+
|
|
480
|
+
The input is `{ ast, occurrences, identifier }`; return a name to extract that candidate into `references`, or `undefined` to keep it inline. By default only candidates with resolved identifiers become references. Candidate identity is AST identity, not structural equality; recursive candidates always receive a reference, with a synthetic name when needed, and colliding requested names receive numeric suffixes.
|
|
481
|
+
|
|
482
|
+
`Schema.toJsonSchemaDocument(schema, options)` applies the policy after deriving the canonical JSON codec, so the callback observes canonical JSON-encoded ASTs. A policy passed to the lower-level `SchemaRepresentation.toJsonSchemaDocument(document)` cannot reallocate references already fixed in that document; pass it while creating the `Document` / `MultiDocument` instead.
|
|
483
|
+
|
|
484
|
+
### SchemaError Location
|
|
485
|
+
|
|
486
|
+
The standalone `SchemaError` root module was removed. Parser adapters such as `Schema.decodeUnknownEffect` fail with `Schema.SchemaError`, which contains the structured `issue`; narrow unknown failures with `Schema.isSchemaError`. By contrast, schema/class `makeEffect` and constructor defaults fail directly with `SchemaIssue.Issue`.
|
|
487
|
+
|
|
488
|
+
## 8. New Modules
|
|
489
|
+
|
|
490
|
+
### SchemaTransformation
|
|
491
|
+
|
|
492
|
+
Bidirectional transformation pairs (decode + encode getters). Key exports:
|
|
493
|
+
|
|
494
|
+
- `transform({ decode, encode })` — pure bidirectional transform
|
|
495
|
+
- `passthrough()` — identity (no conversion)
|
|
496
|
+
- `trim()`, `toLowerCase()`, `toUpperCase()`, `capitalize()` — string transforms
|
|
497
|
+
- `numberFromString`, `bigintFromString` — parsing transforms
|
|
498
|
+
- `durationFromString` — string ↔ `Duration.Duration` transform
|
|
499
|
+
- `optionFromNullOr()`, `optionFromOptionalKey()` — Option wrapping
|
|
500
|
+
- `fromJsonString` — JSON string codec
|
|
501
|
+
- `Middleware` class — wraps the full parsing Effect pipeline (for fallbacks, retries)
|
|
502
|
+
|
|
503
|
+
```ts
|
|
504
|
+
import { Schema, SchemaTransformation } from 'effect';
|
|
505
|
+
|
|
506
|
+
// Basic usage: always pipe through Schema.decodeTo
|
|
507
|
+
const Cents = Schema.Number.pipe(
|
|
508
|
+
Schema.decodeTo(
|
|
509
|
+
Schema.Number,
|
|
510
|
+
SchemaTransformation.transform({
|
|
511
|
+
decode: (dollars) => dollars * 100,
|
|
512
|
+
encode: (cents) => cents / 100
|
|
513
|
+
})
|
|
514
|
+
)
|
|
515
|
+
);
|
|
516
|
+
|
|
517
|
+
const DurationFromString = Schema.String.pipe(
|
|
518
|
+
Schema.decodeTo(Schema.Duration, SchemaTransformation.durationFromString)
|
|
519
|
+
);
|
|
520
|
+
```
|
|
521
|
+
|
|
522
|
+
### SchemaGetter
|
|
523
|
+
|
|
524
|
+
Single-direction transform primitives. A `Getter<T, E, R>` is `Option<E> → Effect<Option<T>, Issue, R>`. Key exports:
|
|
525
|
+
|
|
526
|
+
- `transform(fn)` — pure map over present values
|
|
527
|
+
- `transformOrFail(fn)` — fallible map returning `Effect`
|
|
528
|
+
- `transformOptional(fn)` — full `Option<E> → Option<T>` control (for optional field transforms)
|
|
529
|
+
- `passthrough()` — identity getter
|
|
530
|
+
- `withDefault(effect)` — provide a default `Effect` for missing values
|
|
531
|
+
- `required()` — fail if value is missing
|
|
532
|
+
- `checkEffect(fn)` — effectful validation
|
|
533
|
+
- `String()`, `Number()`, `Boolean()`, `BigInt()`, `Date()` — coercion getters
|
|
534
|
+
|
|
535
|
+
```ts
|
|
536
|
+
import { Schema, SchemaGetter } from 'effect';
|
|
537
|
+
|
|
538
|
+
// Used as decode/encode args in Schema.decodeTo
|
|
539
|
+
const NumberFromString = Schema.String.pipe(
|
|
540
|
+
Schema.decodeTo(Schema.Number, {
|
|
541
|
+
decode: SchemaGetter.transform((s) => Number(s)),
|
|
542
|
+
encode: SchemaGetter.transform((n) => String(n))
|
|
543
|
+
})
|
|
544
|
+
);
|
|
545
|
+
```
|
|
546
|
+
|
|
547
|
+
## 9. Class Schemas
|
|
548
|
+
|
|
549
|
+
Classes still use the same pattern — `Schema.Class<Self>(tag)(fields)`:
|
|
550
|
+
|
|
551
|
+
```ts
|
|
552
|
+
import { Schema } from 'effect';
|
|
553
|
+
|
|
554
|
+
class User extends Schema.Class<User>('User')({
|
|
555
|
+
name: Schema.String,
|
|
556
|
+
email: Schema.String
|
|
557
|
+
}) {}
|
|
558
|
+
|
|
559
|
+
// With validation on the whole struct
|
|
560
|
+
class User2 extends Schema.Class<User2>('User2')(
|
|
561
|
+
Schema.Struct({
|
|
562
|
+
name: Schema.String,
|
|
563
|
+
age: Schema.Number
|
|
564
|
+
}).check(
|
|
565
|
+
Schema.makeFilter(({ age }) => age >= 0, { title: 'non-negative age' })
|
|
566
|
+
)
|
|
567
|
+
) {}
|
|
568
|
+
|
|
569
|
+
// Extending
|
|
570
|
+
class Admin extends User.extend<Admin>('Admin')({
|
|
571
|
+
role: Schema.Literal('admin')
|
|
572
|
+
}) {}
|
|
573
|
+
|
|
574
|
+
// Branded
|
|
575
|
+
class UserId extends Schema.Class<UserId, { readonly brand: unique symbol }>(
|
|
576
|
+
'UserId'
|
|
577
|
+
)({
|
|
578
|
+
id: Schema.String
|
|
579
|
+
}) {}
|
|
580
|
+
|
|
581
|
+
// Recursive
|
|
582
|
+
class Category extends Schema.Class<Category>('Category')(
|
|
583
|
+
Schema.Struct({
|
|
584
|
+
name: Schema.String,
|
|
585
|
+
children: Schema.Array(
|
|
586
|
+
Schema.suspend((): Schema.Codec<Category> => Category)
|
|
587
|
+
)
|
|
588
|
+
})
|
|
589
|
+
) {}
|
|
590
|
+
```
|
|
591
|
+
|
|
592
|
+
### MakeOptions: `disableChecks`
|
|
593
|
+
|
|
594
|
+
Class constructors, `make`, `makeEffect`, and `makeOption` accept an optional second argument with `MakeOptions`:
|
|
595
|
+
|
|
596
|
+
```ts
|
|
597
|
+
interface MakeOptions {
|
|
598
|
+
readonly parseOptions?: AST.ParseOptions | undefined;
|
|
599
|
+
readonly disableChecks?: boolean | undefined;
|
|
600
|
+
}
|
|
601
|
+
```
|
|
602
|
+
|
|
603
|
+
When `disableChecks: true`, schema checks are skipped during construction. Constructor defaults (`withConstructorDefault`) are **still applied** even when checks are disabled. This was renamed from `disableValidation` in v3.
|
|
604
|
+
|
|
605
|
+
```ts
|
|
606
|
+
import { Effect, Schema } from 'effect';
|
|
607
|
+
|
|
608
|
+
class User extends Schema.Class<User>('User')({
|
|
609
|
+
name: Schema.String,
|
|
610
|
+
role: Schema.String.pipe(
|
|
611
|
+
Schema.withConstructorDefault(Effect.succeed('viewer'))
|
|
612
|
+
)
|
|
613
|
+
}) {}
|
|
614
|
+
|
|
615
|
+
// Normal construction — validates input
|
|
616
|
+
const user1 = new User({ name: 'Alice' });
|
|
617
|
+
|
|
618
|
+
// Skip checks — trusts the data, but still applies constructor defaults
|
|
619
|
+
const user2 = new User({ name: 'Alice' }, { disableChecks: true });
|
|
620
|
+
|
|
621
|
+
// Also available on make, makeEffect, and makeOption:
|
|
622
|
+
const user3 = User.make({ name: 'Alice' }, { disableChecks: true });
|
|
623
|
+
const user4 =
|
|
624
|
+
yield* User.makeEffect({ name: 'Alice' }, { disableChecks: true });
|
|
625
|
+
const user5 = User.makeOption({ name: 'Alice' }, { disableChecks: true });
|
|
626
|
+
```
|
|
627
|
+
|
|
628
|
+
When a subclass `extend`s a parent, the parent constructor is automatically called with `disableChecks: true` after the child validates, avoiding double-validation.
|
|
629
|
+
|
|
630
|
+
### TaggedClass (auto `_tag` field)
|
|
631
|
+
|
|
632
|
+
```ts
|
|
633
|
+
class Cat extends Schema.TaggedClass<Cat>()('Cat', {
|
|
634
|
+
lives: Schema.Number
|
|
635
|
+
}) {}
|
|
636
|
+
// new Cat({ lives: 9 }) → { _tag: "Cat", lives: 9 }
|
|
637
|
+
```
|
|
638
|
+
|
|
639
|
+
## 10. TaggedError
|
|
640
|
+
|
|
641
|
+
`Schema.TaggedErrorClass` was renamed to `Schema.TaggedError` in beta.104. The constructor pattern is unchanged:
|
|
642
|
+
|
|
643
|
+
```ts
|
|
644
|
+
import { Effect, Schema } from 'effect';
|
|
645
|
+
|
|
646
|
+
class HttpError extends Schema.TaggedError<HttpError>()('HttpError', {
|
|
647
|
+
status: Schema.Number,
|
|
648
|
+
message: Schema.String
|
|
649
|
+
}) {}
|
|
650
|
+
|
|
651
|
+
// Usage
|
|
652
|
+
const program = Effect.gen(function* () {
|
|
653
|
+
yield* new HttpError({ status: 404, message: 'Not found' });
|
|
654
|
+
});
|
|
655
|
+
|
|
656
|
+
const recovered = program.pipe(
|
|
657
|
+
Effect.catchTag('HttpError', (err) =>
|
|
658
|
+
Effect.succeed(`Caught: ${err.status} ${err.message}`)
|
|
659
|
+
)
|
|
660
|
+
);
|
|
661
|
+
```
|
|
662
|
+
|
|
663
|
+
## 11. Other Removed APIs
|
|
664
|
+
|
|
665
|
+
| v3 API | Status | Replacement |
|
|
666
|
+
| -------------------------------- | ------- | ------------------------------------- |
|
|
667
|
+
| `validate*` (validateSync, etc.) | removed | `Schema.decode*` + `Schema.toType` |
|
|
668
|
+
| `keyof` | removed | — |
|
|
669
|
+
| non-empty array ensure helper | not present | `Schema.NonEmptyArray(S)` or `Schema.Array(S).check(Schema.isMinLength(1))` |
|
|
670
|
+
| `withDefaults` | removed | — |
|
|
671
|
+
| `fromKey` | removed | — |
|
|
672
|
+
| `Data(schema)` | removed | Not needed (deep equality is default) |
|
|
673
|
+
|
|
674
|
+
## 12. Quick Decision Guide
|
|
675
|
+
|
|
676
|
+
**"I need to..."**
|
|
677
|
+
|
|
678
|
+
| Task | v4 Pattern |
|
|
679
|
+
| -------------------------- | -------------------------------------------------------------------------------------------------------- |
|
|
680
|
+
| Validate a primitive | `Schema.String.check(Schema.isMinLength(1))` |
|
|
681
|
+
| Transform between types | `from.pipe(Schema.decodeTo(to, SchemaTransformation.transform({...})))` |
|
|
682
|
+
| Make fields optional | `struct.mapFields(Struct.map(Schema.optionalKey))` |
|
|
683
|
+
| Pick/omit fields | `struct.mapFields(Struct.pick(["a"]))` |
|
|
684
|
+
| Extend a struct | `struct.mapFields(Struct.assign({ newField: Schema.X }))` |
|
|
685
|
+
| Parse JSON string | `Schema.fromJsonString(Schema.Unknown)` or `Schema.fromJsonString(schema)` |
|
|
686
|
+
| Add default value | `Schema.withDecodingDefault(Effect.succeed(encoded))` or `Schema.withDecodingDefaultType(Effect.succeed(type))` |
|
|
687
|
+
| Create tagged error | `class E extends Schema.TaggedError<E>()("E", { ... }) {}` |
|
|
688
|
+
| Rename fields | `struct.pipe(Schema.encodeKeys({ oldName: "newName" }))` |
|
|
689
|
+
| Discriminated union | `Schema.Union([TaggedClassA, TaggedClassB])` |
|
|
690
|
+
| Decode unknown safely | `Schema.decodeUnknownSync(schema)(input)` (sync) or `Schema.decodeUnknownEffect(schema)(input)` (Effect) |
|
|
691
|
+
| Get Exit instead of Either | `Schema.decodeUnknownExit(schema)(input)` |
|