@danieljvdm/dev-kit 0.11.3 → 0.12.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 +70 -76
- package/dev-kit.example.jsonc +0 -4
- package/package.json +10 -6
- package/schema/dev-kit.schema.json +1 -46
- package/skills/build-effect-apis/SKILL.md +77 -0
- package/skills/build-effect-apis/agents/openai.yaml +4 -0
- package/skills/build-effect-apis/references/cloudflare-workers.md +71 -0
- package/skills/build-effect-apis/references/effect-atom-client.md +161 -0
- package/skills/build-effect-apis/references/effect-atom-lifecycle.md +78 -0
- package/skills/build-effect-apis/references/effect-atom-testing.md +74 -0
- package/skills/build-effect-apis/references/runtime-assembly.md +56 -0
- package/skills/build-effect-apis/references/server-and-middleware.md +174 -0
- package/skills/build-effect-apis/references/shared-contracts.md +108 -0
- package/skills/build-effect-apis/references/tanstack-start.md +86 -0
- package/skills/build-effect-apis/references/verification.md +50 -0
- package/skills/dev-kit/SKILL.md +58 -46
- package/skills/effect-architecture-audit/SKILL.md +26 -0
- package/skills/effect-architecture-audit/agents/openai.yaml +4 -0
- package/skills/effect-architecture-audit/references/service-and-boundary-audit.md +150 -0
- package/skills/effect-ts/SKILL.md +21 -256
- package/skills/effect-ts/agents/openai.yaml +3 -3
- package/skills/testing/SKILL.md +5 -0
- package/src/catalog-manager.ts +16 -17
- package/src/catalog.ts +71 -16
- package/src/effect-source.ts +46 -24
- package/src/effect-tsgo.ts +49 -24
- package/src/gitignore.ts +5 -5
- package/src/index.ts +0 -6
- package/src/manifest.ts +0 -34
- package/src/node-symbolic-link.ts +2 -2
- package/src/oxfmt.js +5 -0
- package/src/oxfmt.ts +5 -0
- package/src/oxlint.js +5 -0
- package/src/oxlint.ts +5 -0
- package/src/package-skill-source.ts +51 -59
- package/src/path-digest.ts +7 -7
- package/src/project-package.ts +8 -7
- package/src/project-process-lock.ts +17 -12
- package/src/project-state.ts +1 -1
- package/src/skill-manager.ts +16 -14
- package/src/skill-selector.ts +12 -0
- package/src/sync.ts +170 -120
- package/src/tool-ignore-patterns.js +9 -0
- package/src/tool-ignore-patterns.ts +15 -0
- package/src/vendor.ts +67 -61
- package/src/vite-plus-dependency.ts +10 -11
- package/src/vite-plus-hooks.ts +24 -14
- package/src/vite-plus-quality.ts +21 -172
- package/src/vite-plus.js +81 -0
- package/src/vite-plus.ts +102 -0
- package/templates/AGENTS.md +1 -1
- package/skills/effect-ts/UPSTREAM.md +0 -28
- package/skills/effect-ts/references/atom-cache-lifecycle.md +0 -78
- package/skills/effect-ts/references/atom-http-and-invalidation.md +0 -97
- package/skills/effect-ts/references/atom-tanstack-start.md +0 -69
- package/skills/effect-ts/references/atom-testing.md +0 -67
- package/skills/effect-ts/references/audit-services.md +0 -144
- package/skills/effect-ts/references/features.md +0 -525
- package/skills/effect-ts/references/guide-atom-data-fetching.md +0 -44
- package/skills/effect-ts/references/guide-cli.md +0 -107
- package/skills/effect-ts/references/guide-datetime.md +0 -72
- package/skills/effect-ts/references/guide-effect.md +0 -440
- package/skills/effect-ts/references/guide-error-handling.md +0 -565
- package/skills/effect-ts/references/guide-http-boundaries.md +0 -55
- package/skills/effect-ts/references/guide-layers.md +0 -989
- package/skills/effect-ts/references/guide-observability.md +0 -746
- package/skills/effect-ts/references/guide-retries.md +0 -434
- package/skills/effect-ts/references/guide-schedule.md +0 -343
- package/skills/effect-ts/references/guide-schema.md +0 -664
- package/skills/effect-ts/references/guide-sql.md +0 -536
- package/skills/effect-ts/references/guide-testing.md +0 -532
- package/skills/effect-ts/references/guide-type-safety-and-boundaries.md +0 -131
- package/skills/effect-ts/references/version-and-source.md +0 -86
- package/templates/vite-plus/vite.config.ts +0 -22
|
@@ -1,664 +0,0 @@
|
|
|
1
|
-
# Schema Guide
|
|
2
|
-
|
|
3
|
-
This guide covers Schema APIs and the application modeling policies built on
|
|
4
|
-
them.
|
|
5
|
-
|
|
6
|
-
Key source files:
|
|
7
|
-
|
|
8
|
-
- `packages/effect/src/Schema.ts`
|
|
9
|
-
- `packages/effect/src/SchemaTransformation.ts`
|
|
10
|
-
- `packages/effect/src/SchemaGetter.ts`
|
|
11
|
-
- `packages/effect/src/SchemaIssue.ts`
|
|
12
|
-
- `packages/effect/src/JsonSchema.ts`
|
|
13
|
-
|
|
14
|
-
Representative repo usage:
|
|
15
|
-
|
|
16
|
-
- `packages/tools/ai-codegen/src/Config.ts`
|
|
17
|
-
- `packages/platform-node/test/fixtures/rpc-schemas.ts`
|
|
18
|
-
- `packages/platform-browser/test/IndexedDbQueryBuilder.test.ts`
|
|
19
|
-
- `packages/tools/openapi-generator/`
|
|
20
|
-
|
|
21
|
-
## Mental Model
|
|
22
|
-
|
|
23
|
-
Schema is the standard way to:
|
|
24
|
-
|
|
25
|
-
- define data shapes
|
|
26
|
-
- validate unknown input
|
|
27
|
-
- encode typed values back to serialized form
|
|
28
|
-
- transform between encoded and decoded representations
|
|
29
|
-
- attach metadata and constraints
|
|
30
|
-
|
|
31
|
-
The repo uses Schema pervasively for:
|
|
32
|
-
|
|
33
|
-
- protocol payloads
|
|
34
|
-
- configuration
|
|
35
|
-
- HTTP and RPC contracts
|
|
36
|
-
- database row decoding
|
|
37
|
-
- error types
|
|
38
|
-
- derived tooling such as JSON Schema and arbitrary generation
|
|
39
|
-
|
|
40
|
-
## Preferred Rule
|
|
41
|
-
|
|
42
|
-
Prefer Schema-based types whenever data crosses a boundary or should be validated, transformed, documented, or encoded.
|
|
43
|
-
|
|
44
|
-
Typical boundaries:
|
|
45
|
-
|
|
46
|
-
- HTTP requests and responses
|
|
47
|
-
- RPC payloads
|
|
48
|
-
- database rows
|
|
49
|
-
- config files
|
|
50
|
-
- worker messages
|
|
51
|
-
- persisted data
|
|
52
|
-
- domain errors
|
|
53
|
-
|
|
54
|
-
## Application Model Ownership
|
|
55
|
-
|
|
56
|
-
Make Schema the source of truth for application data. Export the schema value
|
|
57
|
-
and derive its decoded TypeScript type from `.Type` under the same name.
|
|
58
|
-
|
|
59
|
-
```ts
|
|
60
|
-
export const ArtifactId = Schema.NonEmptyString.pipe(Schema.brand("@acme/ArtifactId"));
|
|
61
|
-
export type ArtifactId = typeof ArtifactId.Type;
|
|
62
|
-
|
|
63
|
-
export const GenerateInput = Schema.Struct({
|
|
64
|
-
artifactId: ArtifactId,
|
|
65
|
-
prompt: Schema.String,
|
|
66
|
-
});
|
|
67
|
-
export type GenerateInput = typeof GenerateInput.Type;
|
|
68
|
-
```
|
|
69
|
-
|
|
70
|
-
Use schema classes for named reusable models when their validated construction,
|
|
71
|
-
methods, or identity are useful. Use `Schema.Struct` for ordinary record-shaped
|
|
72
|
-
contracts and inline fragments. Do not maintain a parallel interface or
|
|
73
|
-
handwritten structural alias for fields already owned by a schema.
|
|
74
|
-
|
|
75
|
-
Export schemas for every data-bearing service input and result, including
|
|
76
|
-
intermediate application results that have not yet crossed a network boundary.
|
|
77
|
-
Service interfaces should refer to the schema-derived types so the schema
|
|
78
|
-
remains reusable for fixtures, persistence, and future transports.
|
|
79
|
-
|
|
80
|
-
Interfaces remain appropriate for runtime capabilities containing functions,
|
|
81
|
-
resources, and behavior rather than serializable application data.
|
|
82
|
-
|
|
83
|
-
Give each semantically distinct identifier its own branded schema and reuse it
|
|
84
|
-
for every corresponding field and parameter. During a change, inventory every
|
|
85
|
-
added or modified `id`, `*Id`, and `*Ids` field; each should resolve to its
|
|
86
|
-
semantic brand rather than a plain primitive.
|
|
87
|
-
|
|
88
|
-
Decode external `unknown` input once at the earliest boundary that owns it.
|
|
89
|
-
Pass the decoded type through internal services, and encode with the same
|
|
90
|
-
schema when writing an external representation. Use `.Encoded` only in code
|
|
91
|
-
that explicitly handles the encoded form.
|
|
92
|
-
|
|
93
|
-
## What A Schema Actually Is
|
|
94
|
-
|
|
95
|
-
A schema is not just a static shape.
|
|
96
|
-
|
|
97
|
-
It is a contract between:
|
|
98
|
-
|
|
99
|
-
- the decoded in-memory value you want to work with
|
|
100
|
-
- the encoded representation that comes from or goes to some boundary
|
|
101
|
-
|
|
102
|
-
This is the most important thing many implementations get wrong.
|
|
103
|
-
|
|
104
|
-
Do not think of Schema as “a typed struct definition.”
|
|
105
|
-
Think of it as:
|
|
106
|
-
|
|
107
|
-
- validation
|
|
108
|
-
- decoding
|
|
109
|
-
- encoding
|
|
110
|
-
- transformation
|
|
111
|
-
- metadata
|
|
112
|
-
- reuse across boundaries
|
|
113
|
-
|
|
114
|
-
Because of that, schemas should not be duplicated unless there is a real semantic difference.
|
|
115
|
-
|
|
116
|
-
If two schemas describe the same logical model but differ only because one boundary encodes a field differently, prefer one schema with transformations instead of two parallel schemas.
|
|
117
|
-
|
|
118
|
-
## Avoid Duplicating Schemas
|
|
119
|
-
|
|
120
|
-
Do not create multiple parallel schemas for the same logical entity unless they truly represent different models.
|
|
121
|
-
|
|
122
|
-
Bad pattern:
|
|
123
|
-
|
|
124
|
-
```ts
|
|
125
|
-
const Todo = Schema.Struct({
|
|
126
|
-
id: Schema.Number,
|
|
127
|
-
title: Schema.String,
|
|
128
|
-
completed: Schema.Boolean,
|
|
129
|
-
});
|
|
130
|
-
|
|
131
|
-
const TodoSql = Schema.Struct({
|
|
132
|
-
id: Schema.Number,
|
|
133
|
-
title: Schema.String,
|
|
134
|
-
completed: Schema.BooleanFromBit,
|
|
135
|
-
});
|
|
136
|
-
```
|
|
137
|
-
|
|
138
|
-
This is usually a sign that transformations are not being used properly.
|
|
139
|
-
|
|
140
|
-
If the model is still “Todo”, do not define a second schema just because one boundary stores `completed` as a bit.
|
|
141
|
-
|
|
142
|
-
Prefer deriving or transforming the representation instead.
|
|
143
|
-
|
|
144
|
-
Why duplication is bad:
|
|
145
|
-
|
|
146
|
-
- the same model is now maintained in multiple places
|
|
147
|
-
- fields drift over time
|
|
148
|
-
- boundary logic gets copied instead of centralized
|
|
149
|
-
- refactors become error-prone
|
|
150
|
-
|
|
151
|
-
Only duplicate schemas when there is a real semantic difference, for example:
|
|
152
|
-
|
|
153
|
-
- a creation payload really is a different model from a persisted entity
|
|
154
|
-
- a public API contract intentionally differs from an internal domain model
|
|
155
|
-
- a projection or partial view is intentionally a different type
|
|
156
|
-
|
|
157
|
-
If the difference is only encoding, use a transformation.
|
|
158
|
-
|
|
159
|
-
## Prefer `Class` Variants Over `Struct` Variants When Possible
|
|
160
|
-
|
|
161
|
-
When a schema represents a named domain model, reusable payload, or long-lived API shape, prefer `Schema.Class`, `Schema.TaggedClass`, or `Schema.TaggedErrorClass` over a bare `Schema.Struct`.
|
|
162
|
-
|
|
163
|
-
Prefer:
|
|
164
|
-
|
|
165
|
-
```ts
|
|
166
|
-
import { Schema } from "effect";
|
|
167
|
-
|
|
168
|
-
export class User extends Schema.Class<User>("User")({
|
|
169
|
-
id: Schema.String,
|
|
170
|
-
name: Schema.String,
|
|
171
|
-
}) {}
|
|
172
|
-
```
|
|
173
|
-
|
|
174
|
-
Over:
|
|
175
|
-
|
|
176
|
-
```ts
|
|
177
|
-
import { Schema } from "effect";
|
|
178
|
-
|
|
179
|
-
export const User = Schema.Struct({
|
|
180
|
-
id: Schema.String,
|
|
181
|
-
name: Schema.String,
|
|
182
|
-
});
|
|
183
|
-
```
|
|
184
|
-
|
|
185
|
-
Why `Class` variants are usually better:
|
|
186
|
-
|
|
187
|
-
- the schema has a stable, named identity
|
|
188
|
-
- reusable models are easier to recognize in code and traces
|
|
189
|
-
- constructors and validation are packaged together
|
|
190
|
-
- extension patterns are clearer
|
|
191
|
-
- named schemas read better in contracts and tooling output
|
|
192
|
-
|
|
193
|
-
Use `Struct` when:
|
|
194
|
-
|
|
195
|
-
- the shape is local and anonymous
|
|
196
|
-
- it is a small inline request or response shape
|
|
197
|
-
- introducing a class would add unnecessary ceremony
|
|
198
|
-
- the schema is primarily a one-off composition fragment
|
|
199
|
-
|
|
200
|
-
Good rule of thumb:
|
|
201
|
-
|
|
202
|
-
- reusable named model: `Class`
|
|
203
|
-
- reusable tagged union member: `TaggedClass`
|
|
204
|
-
- reusable error payload: `TaggedErrorClass`
|
|
205
|
-
- small inline object shape: `Struct`
|
|
206
|
-
|
|
207
|
-
## One Logical Model, Multiple Representations
|
|
208
|
-
|
|
209
|
-
The right Schema mindset is:
|
|
210
|
-
|
|
211
|
-
- one logical model
|
|
212
|
-
- multiple encoded forms when needed
|
|
213
|
-
- transformations connecting them
|
|
214
|
-
|
|
215
|
-
For example, a `Todo` may be:
|
|
216
|
-
|
|
217
|
-
- a boolean in memory
|
|
218
|
-
- a bit in SQL
|
|
219
|
-
- a string in some external API
|
|
220
|
-
|
|
221
|
-
That does not automatically mean you need three separate top-level schemas.
|
|
222
|
-
|
|
223
|
-
Prefer:
|
|
224
|
-
|
|
225
|
-
- one main schema for the logical model
|
|
226
|
-
- transformed field schemas or transformed object schemas for boundary-specific encoding
|
|
227
|
-
- derived request/result schemas when the shape is actually different
|
|
228
|
-
|
|
229
|
-
## Common Schema Building Blocks
|
|
230
|
-
|
|
231
|
-
Common primitives and collections used throughout the repo:
|
|
232
|
-
|
|
233
|
-
- `Schema.String`
|
|
234
|
-
- `Schema.Number`
|
|
235
|
-
- `Schema.Boolean`
|
|
236
|
-
- `Schema.BigInt`
|
|
237
|
-
- `Schema.Array(...)`
|
|
238
|
-
- `Schema.Record(key, value)`
|
|
239
|
-
- `Schema.Tuple([...])`
|
|
240
|
-
- `Schema.Struct({...})`
|
|
241
|
-
- `Schema.Union([...])`
|
|
242
|
-
|
|
243
|
-
Example:
|
|
244
|
-
|
|
245
|
-
```ts
|
|
246
|
-
const Todo = Schema.Struct({
|
|
247
|
-
id: Schema.Number,
|
|
248
|
-
title: Schema.String,
|
|
249
|
-
completed: Schema.Boolean,
|
|
250
|
-
});
|
|
251
|
-
```
|
|
252
|
-
|
|
253
|
-
## `Class`, `TaggedClass`, and `TaggedErrorClass`
|
|
254
|
-
|
|
255
|
-
### `Schema.Class`
|
|
256
|
-
|
|
257
|
-
Use for named reusable schema-backed models.
|
|
258
|
-
|
|
259
|
-
```ts
|
|
260
|
-
class Product extends Schema.Class<Product>("Product")({
|
|
261
|
-
id: Schema.String,
|
|
262
|
-
price: Schema.Number,
|
|
263
|
-
}) {}
|
|
264
|
-
```
|
|
265
|
-
|
|
266
|
-
### Constructor Rule
|
|
267
|
-
|
|
268
|
-
When constructing schema classes, prefer `X.make(...)` over `new X(...)`.
|
|
269
|
-
|
|
270
|
-
Prefer:
|
|
271
|
-
|
|
272
|
-
```ts
|
|
273
|
-
const todo = Todo.make({
|
|
274
|
-
id: 1,
|
|
275
|
-
title: "write docs",
|
|
276
|
-
completed: false,
|
|
277
|
-
});
|
|
278
|
-
```
|
|
279
|
-
|
|
280
|
-
Over:
|
|
281
|
-
|
|
282
|
-
```ts
|
|
283
|
-
const todo = new Todo({
|
|
284
|
-
id: 1,
|
|
285
|
-
title: "write docs",
|
|
286
|
-
completed: false,
|
|
287
|
-
});
|
|
288
|
-
```
|
|
289
|
-
|
|
290
|
-
Why:
|
|
291
|
-
|
|
292
|
-
- it is the intended schema-class construction style
|
|
293
|
-
- it makes schema-backed construction explicit
|
|
294
|
-
- it keeps the codebase consistent
|
|
295
|
-
- it reads better across `Class`, `TaggedClass`, and `TaggedErrorClass`
|
|
296
|
-
|
|
297
|
-
Use this rule consistently for:
|
|
298
|
-
|
|
299
|
-
- `Schema.Class`
|
|
300
|
-
- `Schema.TaggedClass`
|
|
301
|
-
- `Schema.TaggedErrorClass`
|
|
302
|
-
|
|
303
|
-
### `Schema.TaggedClass`
|
|
304
|
-
|
|
305
|
-
Use for members of tagged unions.
|
|
306
|
-
|
|
307
|
-
```ts
|
|
308
|
-
class Circle extends Schema.TaggedClass<Circle>()("Circle", {
|
|
309
|
-
radius: Schema.Number,
|
|
310
|
-
}) {}
|
|
311
|
-
|
|
312
|
-
class Rectangle extends Schema.TaggedClass<Rectangle>()("Rectangle", {
|
|
313
|
-
width: Schema.Number,
|
|
314
|
-
height: Schema.Number,
|
|
315
|
-
}) {}
|
|
316
|
-
```
|
|
317
|
-
|
|
318
|
-
### `Schema.TaggedErrorClass`
|
|
319
|
-
|
|
320
|
-
Use for schema-backed typed errors.
|
|
321
|
-
|
|
322
|
-
```ts
|
|
323
|
-
class NotFound extends Schema.TaggedErrorClass<NotFound>()("NotFound", {
|
|
324
|
-
id: Schema.String,
|
|
325
|
-
}) {}
|
|
326
|
-
```
|
|
327
|
-
|
|
328
|
-
## Optional Fields
|
|
329
|
-
|
|
330
|
-
Be precise about optionality.
|
|
331
|
-
|
|
332
|
-
Important rule from the canonical docs:
|
|
333
|
-
|
|
334
|
-
- `Schema.optional(schema)` means `T | undefined`
|
|
335
|
-
- `Schema.optionalKey(schema)` means an exact optional property in a struct
|
|
336
|
-
|
|
337
|
-
Prefer `optionalKey` for object fields.
|
|
338
|
-
|
|
339
|
-
Prefer:
|
|
340
|
-
|
|
341
|
-
```ts
|
|
342
|
-
const Query = Schema.Struct({
|
|
343
|
-
search: Schema.optionalKey(Schema.String),
|
|
344
|
-
});
|
|
345
|
-
```
|
|
346
|
-
|
|
347
|
-
Use `optional` when the value itself should be `A | undefined`, not just an omitted field.
|
|
348
|
-
|
|
349
|
-
## Unions
|
|
350
|
-
|
|
351
|
-
Use `Schema.Union([...])` for ordinary unions.
|
|
352
|
-
|
|
353
|
-
```ts
|
|
354
|
-
const Id = Schema.Union([Schema.String, Schema.Number]);
|
|
355
|
-
```
|
|
356
|
-
|
|
357
|
-
Prefer tagged unions for domain variants.
|
|
358
|
-
|
|
359
|
-
```ts
|
|
360
|
-
class Created extends Schema.TaggedClass<Created>()("Created", {
|
|
361
|
-
id: Schema.String,
|
|
362
|
-
}) {}
|
|
363
|
-
|
|
364
|
-
class Deleted extends Schema.TaggedClass<Deleted>()("Deleted", {
|
|
365
|
-
id: Schema.String,
|
|
366
|
-
}) {}
|
|
367
|
-
|
|
368
|
-
const TodoEvent = Schema.Union([Created, Deleted]);
|
|
369
|
-
```
|
|
370
|
-
|
|
371
|
-
Why:
|
|
372
|
-
|
|
373
|
-
- decoding and branching are clearer
|
|
374
|
-
- `_tag`-based matching aligns with Effect code style
|
|
375
|
-
|
|
376
|
-
## Recursive Schemas
|
|
377
|
-
|
|
378
|
-
Use `Schema.suspend` for recursive schemas.
|
|
379
|
-
|
|
380
|
-
```ts
|
|
381
|
-
type Tree = {
|
|
382
|
-
readonly name: string;
|
|
383
|
-
readonly children: ReadonlyArray<Tree>;
|
|
384
|
-
};
|
|
385
|
-
|
|
386
|
-
const Tree: Schema.Schema<Tree> = Schema.Struct({
|
|
387
|
-
name: Schema.String,
|
|
388
|
-
children: Schema.Array(Schema.suspend((): Schema.Schema<Tree> => Tree)),
|
|
389
|
-
});
|
|
390
|
-
```
|
|
391
|
-
|
|
392
|
-
Use it whenever a schema refers to itself, directly or indirectly.
|
|
393
|
-
|
|
394
|
-
Without `suspend`, recursive definitions will not work correctly.
|
|
395
|
-
|
|
396
|
-
## Transformations
|
|
397
|
-
|
|
398
|
-
Transformations are one of the most important Schema features.
|
|
399
|
-
|
|
400
|
-
Use them when decoded and encoded shapes differ.
|
|
401
|
-
|
|
402
|
-
This is the main tool that avoids needless schema duplication.
|
|
403
|
-
|
|
404
|
-
If your instinct is “I need another schema because this boundary encodes the same value differently”, stop and first ask whether this should be one schema with a transformation instead.
|
|
405
|
-
|
|
406
|
-
### `Schema.decodeTo`
|
|
407
|
-
|
|
408
|
-
Use `decodeTo` when you want one schema to decode into another schema's type.
|
|
409
|
-
|
|
410
|
-
```ts
|
|
411
|
-
const TrimmedString = Schema.String.pipe(
|
|
412
|
-
Schema.decodeTo(Schema.String, {
|
|
413
|
-
decode: (value) => value.trim(),
|
|
414
|
-
encode: (value) => value,
|
|
415
|
-
}),
|
|
416
|
-
);
|
|
417
|
-
```
|
|
418
|
-
|
|
419
|
-
The canonical docs explicitly note that `decodeTo` is curried and should be used with `pipe`.
|
|
420
|
-
|
|
421
|
-
### `Schema.encodeTo`
|
|
422
|
-
|
|
423
|
-
Use `encodeTo` when the reverse direction reads more clearly.
|
|
424
|
-
|
|
425
|
-
### `SchemaTransformation.transformOrFail`
|
|
426
|
-
|
|
427
|
-
Use `transformOrFail` when the transformation itself is effectful or may fail.
|
|
428
|
-
|
|
429
|
-
```ts
|
|
430
|
-
import * as Effect from "effect/Effect";
|
|
431
|
-
import * as Schema from "effect/Schema";
|
|
432
|
-
import * as SchemaTransformation from "effect/SchemaTransformation";
|
|
433
|
-
|
|
434
|
-
const VerifiedString = Schema.String.pipe(
|
|
435
|
-
Schema.decodeTo(
|
|
436
|
-
Schema.String,
|
|
437
|
-
SchemaTransformation.transformOrFail({
|
|
438
|
-
decode: (value) => Effect.succeed(value.trim()),
|
|
439
|
-
encode: (value) => Effect.succeed(value),
|
|
440
|
-
}),
|
|
441
|
-
),
|
|
442
|
-
);
|
|
443
|
-
```
|
|
444
|
-
|
|
445
|
-
Use this when:
|
|
446
|
-
|
|
447
|
-
- validation depends on services or effects
|
|
448
|
-
- decoding can fail with structured issues
|
|
449
|
-
- encoding also needs logic beyond identity
|
|
450
|
-
|
|
451
|
-
## Field-Level Transformations
|
|
452
|
-
|
|
453
|
-
Very often, the right answer is not a second object schema but a transformed field schema.
|
|
454
|
-
|
|
455
|
-
Example shape:
|
|
456
|
-
|
|
457
|
-
```ts
|
|
458
|
-
const Completed = Schema.BooleanFromBit;
|
|
459
|
-
|
|
460
|
-
const Todo = Schema.Struct({
|
|
461
|
-
id: Schema.Number,
|
|
462
|
-
title: Schema.String,
|
|
463
|
-
completed: Completed,
|
|
464
|
-
});
|
|
465
|
-
```
|
|
466
|
-
|
|
467
|
-
In this pattern:
|
|
468
|
-
|
|
469
|
-
- the logical model still has `completed: boolean`
|
|
470
|
-
- the encoded SQL-facing representation can still be a bit
|
|
471
|
-
- the transformation lives at the field where it belongs
|
|
472
|
-
|
|
473
|
-
This is usually better than defining `Todo` and `TodoSql` as separate object schemas.
|
|
474
|
-
|
|
475
|
-
## Object-Level Transformations
|
|
476
|
-
|
|
477
|
-
Use object-level transformations when the whole object encoding differs, not just one field.
|
|
478
|
-
|
|
479
|
-
Good use cases:
|
|
480
|
-
|
|
481
|
-
- external keys differ from internal keys
|
|
482
|
-
- several fields need coordinated transformation
|
|
483
|
-
- the encoded shape is a structurally different representation of the same model
|
|
484
|
-
|
|
485
|
-
Still prefer a single logical schema plus a transformation pipeline over maintaining multiple duplicated top-level schemas.
|
|
486
|
-
|
|
487
|
-
## Rename Keys
|
|
488
|
-
|
|
489
|
-
Schema supports key renaming through struct transformations.
|
|
490
|
-
|
|
491
|
-
The canonical `Schema.ts` implements key renaming by mapping fields and using
|
|
492
|
-
decode/encode transformations with renamed key maps.
|
|
493
|
-
|
|
494
|
-
Use key renaming when:
|
|
495
|
-
|
|
496
|
-
- external payload keys differ from internal keys
|
|
497
|
-
- you want stable internal names while honoring external contract names
|
|
498
|
-
|
|
499
|
-
Preferred pattern:
|
|
500
|
-
|
|
501
|
-
- keep the internal decoded shape idiomatic
|
|
502
|
-
- use schema-level transformation or field-mapping to adapt external keys
|
|
503
|
-
|
|
504
|
-
This is another example of avoiding duplication. If the only difference is key naming, do not define a second schema just to rename fields manually later.
|
|
505
|
-
|
|
506
|
-
In practice, use struct field mapping helpers and transformation composition rather than manual post-parse object rewriting.
|
|
507
|
-
|
|
508
|
-
## Opaque And Branded Types
|
|
509
|
-
|
|
510
|
-
Use opaque or branded schemas when a value should stay distinct from its structural base type.
|
|
511
|
-
|
|
512
|
-
### `Schema.brand`
|
|
513
|
-
|
|
514
|
-
Use `brand` for refined nominal distinctions.
|
|
515
|
-
|
|
516
|
-
```ts
|
|
517
|
-
const UserId = Schema.String.pipe(Schema.brand("UserId"));
|
|
518
|
-
```
|
|
519
|
-
|
|
520
|
-
This is useful for:
|
|
521
|
-
|
|
522
|
-
- IDs
|
|
523
|
-
- validated domain scalars
|
|
524
|
-
- preventing accidental interchange of same-shaped values
|
|
525
|
-
|
|
526
|
-
### `Schema.Opaque`
|
|
527
|
-
|
|
528
|
-
Use `Opaque` when you want an opaque schema-backed type with the same structure as its underlying schema.
|
|
529
|
-
|
|
530
|
-
This is especially useful when the type should remain distinct at the type level without changing its runtime shape.
|
|
531
|
-
|
|
532
|
-
## Picking, Omitting, Partial Shapes, And Mutability
|
|
533
|
-
|
|
534
|
-
Common struct operations include:
|
|
535
|
-
|
|
536
|
-
- `pick`
|
|
537
|
-
- `omit`
|
|
538
|
-
- `partial`
|
|
539
|
-
- `mutable`
|
|
540
|
-
|
|
541
|
-
Use them to derive variations instead of redefining near-identical schemas manually.
|
|
542
|
-
|
|
543
|
-
Good examples:
|
|
544
|
-
|
|
545
|
-
- request subset from a domain model
|
|
546
|
-
- patch/update payloads
|
|
547
|
-
- mutable representations for specific adapters
|
|
548
|
-
|
|
549
|
-
Prefer deriving from one source schema rather than maintaining parallel copies.
|
|
550
|
-
|
|
551
|
-
This is the second major tool for avoiding duplication:
|
|
552
|
-
|
|
553
|
-
- use transformations when encoded and decoded representations differ
|
|
554
|
-
- use derivation when one schema is a subset, superset, or variation of another
|
|
555
|
-
|
|
556
|
-
## Constraints And Validation
|
|
557
|
-
|
|
558
|
-
Use schema checks and filters for validation.
|
|
559
|
-
|
|
560
|
-
Examples from the module docs include:
|
|
561
|
-
|
|
562
|
-
- `isMinLength`
|
|
563
|
-
- `isGreaterThan`
|
|
564
|
-
- `isPattern`
|
|
565
|
-
- `isUUID`
|
|
566
|
-
|
|
567
|
-
Attach them with `.check(...)`.
|
|
568
|
-
|
|
569
|
-
Use this when:
|
|
570
|
-
|
|
571
|
-
- the validation is intrinsic to the schema
|
|
572
|
-
- the rule belongs to the data contract
|
|
573
|
-
|
|
574
|
-
For business-rule validation that depends on services or current state, prefer effectful logic outside the schema or use effectful transformations.
|
|
575
|
-
|
|
576
|
-
## Decoding And Encoding
|
|
577
|
-
|
|
578
|
-
Common operations:
|
|
579
|
-
|
|
580
|
-
- `Schema.decodeUnknownSync`
|
|
581
|
-
- `Schema.decodeUnknownEffect`
|
|
582
|
-
- `Schema.decodeUnknownExit`
|
|
583
|
-
- `Schema.encodeUnknownSync`
|
|
584
|
-
- `Schema.encodeUnknownEffect`
|
|
585
|
-
|
|
586
|
-
Preferred rule:
|
|
587
|
-
|
|
588
|
-
- use `decodeUnknownEffect` and `encodeUnknownEffect` in Effect code
|
|
589
|
-
- avoid throwing sync decode APIs in application flows unless you are intentionally at a sync boundary
|
|
590
|
-
|
|
591
|
-
Good pattern:
|
|
592
|
-
|
|
593
|
-
```ts
|
|
594
|
-
const decodeUser = Schema.decodeUnknownEffect(User);
|
|
595
|
-
```
|
|
596
|
-
|
|
597
|
-
## Schema Metadata And Derived Tooling
|
|
598
|
-
|
|
599
|
-
Schema is also used for:
|
|
600
|
-
|
|
601
|
-
- annotations and documentation metadata
|
|
602
|
-
- JSON Schema generation
|
|
603
|
-
- arbitrary generation for tests
|
|
604
|
-
- derived equivalence
|
|
605
|
-
|
|
606
|
-
Useful operations from the module docs:
|
|
607
|
-
|
|
608
|
-
- `.annotate(...)`
|
|
609
|
-
- `Schema.toJsonSchemaDocument(...)`
|
|
610
|
-
- `Schema.toArbitrary(...)`
|
|
611
|
-
- `Schema.toEquivalence(...)`
|
|
612
|
-
|
|
613
|
-
Use annotations when the schema participates in:
|
|
614
|
-
|
|
615
|
-
- API docs
|
|
616
|
-
- codegen
|
|
617
|
-
- contract generation
|
|
618
|
-
|
|
619
|
-
## Common Repo Patterns
|
|
620
|
-
|
|
621
|
-
Patterns visible in the canonical source:
|
|
622
|
-
|
|
623
|
-
- `Schema.Class` for named reusable contract types
|
|
624
|
-
- `Schema.Struct` for inline shapes and anonymous fragments
|
|
625
|
-
- `Schema.Union` for alternative payloads
|
|
626
|
-
- `Schema.optionalKey` for request/query/body optional fields
|
|
627
|
-
- `Schema.suspend` for recursive generated schemas
|
|
628
|
-
- `Schema.decodeTo` and `transformOrFail` for non-trivial decode/encode logic
|
|
629
|
-
- `Schema.TaggedErrorClass` for typed error payloads
|
|
630
|
-
|
|
631
|
-
## Best Practices
|
|
632
|
-
|
|
633
|
-
1. Prefer `Class` variants over plain `Struct` for named reusable schemas.
|
|
634
|
-
2. Prefer tagged variants for unions and errors.
|
|
635
|
-
3. Prefer `optionalKey` for optional object properties.
|
|
636
|
-
4. Do not duplicate schemas unless there is a real semantic difference.
|
|
637
|
-
5. Prefer schema-level transformations over ad hoc post-parse object rewriting.
|
|
638
|
-
6. Prefer deriving schema variants with `pick`, `omit`, `partial`, and `mutable` instead of duplicating definitions.
|
|
639
|
-
7. Prefer field-level transformations when only a field encoding differs.
|
|
640
|
-
8. Prefer branded or opaque types for important domain identifiers.
|
|
641
|
-
9. Prefer `decodeUnknownEffect` in application code.
|
|
642
|
-
10. Keep internal decoded shapes idiomatic and use schema transforms for external representation differences.
|
|
643
|
-
11. Give every data-bearing service input and result an exported schema.
|
|
644
|
-
12. Audit changed identifier fields for the correct semantic brand.
|
|
645
|
-
13. Decode external data once at its earliest owning boundary.
|
|
646
|
-
|
|
647
|
-
## Anti-Patterns
|
|
648
|
-
|
|
649
|
-
- using plain `Struct` for every reusable domain model even when `Class` would give a clearer named type
|
|
650
|
-
- duplicating whole schemas when only one field encoding differs
|
|
651
|
-
- creating `Foo` and `FooSql` schemas for the same logical model when a transformation would do
|
|
652
|
-
- using `optional` when you actually want an optional key
|
|
653
|
-
- duplicating near-identical schemas instead of deriving variants
|
|
654
|
-
- rewriting keys manually after decode instead of using schema transformations
|
|
655
|
-
- hand-validating external data after decode when the constraint belongs in the schema
|
|
656
|
-
- exposing unvalidated external payloads deep into business logic
|
|
657
|
-
|
|
658
|
-
## Good Repo Examples To Study
|
|
659
|
-
|
|
660
|
-
- `packages/tools/ai-codegen/src/Config.ts`
|
|
661
|
-
- `packages/platform-node/test/fixtures/rpc-schemas.ts`
|
|
662
|
-
- `packages/platform-browser/test/IndexedDbQueryBuilder.test.ts`
|
|
663
|
-
- `packages/tools/openapi-generator/src/JsonSchemaGenerator.ts`
|
|
664
|
-
- `packages/effect/src/Schema.ts`
|