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.
Files changed (118) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +278 -0
  3. package/guidance/effect-first-development.md +1247 -0
  4. package/guidance/post__effect-and-the-near-inexpressible-majesty-of-layers.md +490 -0
  5. package/guidance/post__parse-dont-validate.md +109 -0
  6. package/guidance/progressive-disclosure-guidance.md +38 -0
  7. package/package.json +63 -0
  8. package/patterns/avoid-any.md +37 -0
  9. package/patterns/avoid-data-tagged-error.md +34 -0
  10. package/patterns/avoid-direct-json.md +51 -0
  11. package/patterns/avoid-direct-tag-checks.md +54 -0
  12. package/patterns/avoid-expect-in-if.md +52 -0
  13. package/patterns/avoid-mutable-state.md +70 -0
  14. package/patterns/avoid-native-fetch.md +61 -0
  15. package/patterns/avoid-node-imports.md +86 -0
  16. package/patterns/avoid-non-null-assertion.md +44 -0
  17. package/patterns/avoid-object-type.md +46 -0
  18. package/patterns/avoid-option-getorthrow.md +39 -0
  19. package/patterns/avoid-platform-coupling.md +43 -0
  20. package/patterns/avoid-process-env.md +43 -0
  21. package/patterns/avoid-react-hooks.md +73 -0
  22. package/patterns/avoid-schema-suffix.md +45 -0
  23. package/patterns/avoid-sync-fs.md +68 -0
  24. package/patterns/avoid-try-catch.md +47 -0
  25. package/patterns/avoid-ts-ignore.md +38 -0
  26. package/patterns/avoid-untagged-errors.md +67 -0
  27. package/patterns/avoid-yield-ref.md +46 -0
  28. package/patterns/casting-awareness.md +46 -0
  29. package/patterns/context-tag-extends.md +84 -0
  30. package/patterns/effect-catchall-default.md +61 -0
  31. package/patterns/effect-promise-vs-trypromise.md +47 -0
  32. package/patterns/effect-run-in-body.md +58 -0
  33. package/patterns/imperative-loops.md +76 -0
  34. package/patterns/prefer-arr-sort.md +52 -0
  35. package/patterns/prefer-duration-values.md +56 -0
  36. package/patterns/prefer-effect-fn.md +161 -0
  37. package/patterns/prefer-match-over-switch.md +48 -0
  38. package/patterns/prefer-option-over-null.md +56 -0
  39. package/patterns/prefer-redacted-config.md +70 -0
  40. package/patterns/prefer-schema-class.md +54 -0
  41. package/patterns/require-effect-concurrency.md +83 -0
  42. package/patterns/stream-large-files.md +63 -0
  43. package/patterns/throw-in-effect-gen.md +62 -0
  44. package/patterns/use-clock-service.md +45 -0
  45. package/patterns/use-command-executor-service.md +54 -0
  46. package/patterns/use-console-service.md +54 -0
  47. package/patterns/use-filesystem-service.md +59 -0
  48. package/patterns/use-http-client-service.md +77 -0
  49. package/patterns/use-path-service.md +53 -0
  50. package/patterns/use-random-service.md +45 -0
  51. package/patterns/use-temp-file-scoped.md +66 -0
  52. package/patterns/vm-in-wrong-file.md +51 -0
  53. package/patterns/yield-in-for-loop.md +61 -0
  54. package/skills/effect-ai-chat/SKILL.md +472 -0
  55. package/skills/effect-ai-language-model/SKILL.md +652 -0
  56. package/skills/effect-ai-prompt/SKILL.md +752 -0
  57. package/skills/effect-ai-provider/SKILL.md +668 -0
  58. package/skills/effect-ai-streaming/SKILL.md +418 -0
  59. package/skills/effect-ai-tool/SKILL.md +1132 -0
  60. package/skills/effect-atom-rpc/SKILL.md +488 -0
  61. package/skills/effect-atom-state/SKILL.md +640 -0
  62. package/skills/effect-batching/SKILL.md +614 -0
  63. package/skills/effect-cache/SKILL.md +570 -0
  64. package/skills/effect-cli/SKILL.md +523 -0
  65. package/skills/effect-command-executor/SKILL.md +675 -0
  66. package/skills/effect-concurrency-testing/SKILL.md +612 -0
  67. package/skills/effect-config/SKILL.md +580 -0
  68. package/skills/effect-context-witness/SKILL.md +274 -0
  69. package/skills/effect-domain-modeling/SKILL.md +1212 -0
  70. package/skills/effect-domain-predicates/SKILL.md +867 -0
  71. package/skills/effect-error-handling/SKILL.md +1581 -0
  72. package/skills/effect-fiber/SKILL.md +731 -0
  73. package/skills/effect-filesystem/SKILL.md +624 -0
  74. package/skills/effect-graph/SKILL.md +571 -0
  75. package/skills/effect-http-api/SKILL.md +1760 -0
  76. package/skills/effect-http-client/SKILL.md +989 -0
  77. package/skills/effect-http-server/SKILL.md +920 -0
  78. package/skills/effect-incremental-migration/SKILL.md +362 -0
  79. package/skills/effect-layer-design/SKILL.md +642 -0
  80. package/skills/effect-managed-runtime/SKILL.md +395 -0
  81. package/skills/effect-mcp-server/SKILL.md +608 -0
  82. package/skills/effect-observability/SKILL.md +719 -0
  83. package/skills/effect-optics/SKILL.md +554 -0
  84. package/skills/effect-parallelization/SKILL.md +668 -0
  85. package/skills/effect-path/SKILL.md +296 -0
  86. package/skills/effect-pattern-matching/SKILL.md +914 -0
  87. package/skills/effect-platform-abstraction/SKILL.md +1175 -0
  88. package/skills/effect-platform-layers/SKILL.md +514 -0
  89. package/skills/effect-pubsub-event-bus/SKILL.md +384 -0
  90. package/skills/effect-react-composition/SKILL.md +986 -0
  91. package/skills/effect-react-vm/SKILL.md +675 -0
  92. package/skills/effect-rpc-api/SKILL.md +624 -0
  93. package/skills/effect-rpc-client/SKILL.md +666 -0
  94. package/skills/effect-rpc-cluster/SKILL.md +1623 -0
  95. package/skills/effect-rpc-server/SKILL.md +767 -0
  96. package/skills/effect-scheduling/SKILL.md +124 -0
  97. package/skills/effect-schema-composition/SKILL.md +975 -0
  98. package/skills/effect-schema-v4/SKILL.md +691 -0
  99. package/skills/effect-scope/SKILL.md +682 -0
  100. package/skills/effect-service-implementation/SKILL.md +656 -0
  101. package/skills/effect-socket/SKILL.md +703 -0
  102. package/skills/effect-sql/SKILL.md +781 -0
  103. package/skills/effect-stream/SKILL.md +765 -0
  104. package/skills/effect-testing/SKILL.md +1331 -0
  105. package/skills/effect-typeclass-design/SKILL.md +161 -0
  106. package/skills/effect-wide-events/Article.md +66 -0
  107. package/skills/effect-wide-events/SKILL.md +95 -0
  108. package/skills/effect-workflow/SKILL.md +810 -0
  109. package/src/agent-policy.ts +22 -0
  110. package/src/enforcer.ts +104 -0
  111. package/src/frontmatter.ts +34 -0
  112. package/src/guidance.ts +66 -0
  113. package/src/index.ts +38 -0
  114. package/src/pattern-catalog.ts +115 -0
  115. package/src/pattern-matcher.ts +178 -0
  116. package/src/pattern.ts +97 -0
  117. package/src/skills.ts +29 -0
  118. 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)` |