@martinffx/atelier 2.0.0 → 3.0.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/CHANGELOG.md +14 -0
- package/README.md +94 -253
- package/package.json +1 -1
- package/skills/{spec-orchestrator → atelier-orchestrator}/SKILL.md +3 -3
- package/skills/atelier-setup/SKILL.md +80 -0
- package/skills/code-review/references/reviewers.md +3 -3
- package/skills/code-review/references/rq.md +6 -6
- package/skills/spec-brainstorm/SKILL.md +5 -5
- package/skills/spec-implement/SKILL.md +2 -3
- package/skills/spec-plan/SKILL.md +21 -29
- package/skills/spec-plan/references/plan_template.md +77 -0
- package/skills/python-architecture/SKILL.md +0 -148
- package/skills/python-architecture/references/data-modeling.md +0 -558
- package/skills/python-architecture/references/ddd.md +0 -569
- package/skills/python-architecture/references/functional-core.md +0 -337
- package/skills/python-build-tools/SKILL.md +0 -153
- package/skills/python-build-tools/references/basedpyright.md +0 -321
- package/skills/python-build-tools/references/ruff.md +0 -225
- package/skills/python-build-tools/references/uv.md +0 -257
- package/skills/python-fastapi/SKILL.md +0 -184
- package/skills/python-fastapi/references/api-design.md +0 -563
- package/skills/python-fastapi/references/dependencies.md +0 -479
- package/skills/python-fastapi/references/middleware.md +0 -475
- package/skills/python-fastapi/references/validation.md +0 -497
- package/skills/python-modern-python/SKILL.md +0 -201
- package/skills/python-modern-python/references/async.md +0 -76
- package/skills/python-modern-python/references/pattern-matching.md +0 -89
- package/skills/python-modern-python/references/typing.md +0 -96
- package/skills/python-monorepo/SKILL.md +0 -146
- package/skills/python-monorepo/references/docker.md +0 -515
- package/skills/python-monorepo/references/namespace-packages.md +0 -426
- package/skills/python-monorepo/references/workspace-config.md +0 -341
- package/skills/python-sqlalchemy/SKILL.md +0 -119
- package/skills/python-sqlalchemy/references/async.md +0 -51
- package/skills/python-sqlalchemy/references/models.md +0 -67
- package/skills/python-sqlalchemy/references/queries.md +0 -57
- package/skills/python-temporal/SKILL.md +0 -128
- package/skills/python-temporal/references/patterns.md +0 -87
- package/skills/python-temporal/references/testing.md +0 -54
- package/skills/python-testing/SKILL.md +0 -161
- package/skills/python-testing/references/boundaries.md +0 -449
- package/skills/python-testing/references/mocking.md +0 -449
- package/skills/python-testing/references/pytest.md +0 -524
- package/skills/typescript-api-design/SKILL.md +0 -396
- package/skills/typescript-api-design/references/error-responses.md +0 -469
- package/skills/typescript-build-tools/SKILL.md +0 -447
- package/skills/typescript-build-tools/references/biome.md +0 -436
- package/skills/typescript-build-tools/references/bun.md +0 -432
- package/skills/typescript-build-tools/references/turborepo.md +0 -426
- package/skills/typescript-build-tools/references/vitest.md +0 -518
- package/skills/typescript-drizzle-orm/SKILL.md +0 -398
- package/skills/typescript-drizzle-orm/references/cloudflare.md +0 -392
- package/skills/typescript-drizzle-orm/references/entity-pattern.md +0 -412
- package/skills/typescript-drizzle-orm/references/postgresql.md +0 -191
- package/skills/typescript-drizzle-orm/references/repository-pattern.md +0 -479
- package/skills/typescript-drizzle-orm/references/sqlite.md +0 -291
- package/skills/typescript-dynamodb-toolbox/SKILL.md +0 -370
- package/skills/typescript-dynamodb-toolbox/references/entity-layer.md +0 -574
- package/skills/typescript-dynamodb-toolbox/references/error-handling.md +0 -495
- package/skills/typescript-dynamodb-toolbox/references/modeling.md +0 -332
- package/skills/typescript-dynamodb-toolbox/references/repository-layer.md +0 -631
- package/skills/typescript-dynamodb-toolbox/references/testing.md +0 -718
- package/skills/typescript-dynamodb-toolbox/references/transactions.md +0 -423
- package/skills/typescript-effect-ts/SKILL.md +0 -341
- package/skills/typescript-effect-ts/references/core.md +0 -412
- package/skills/typescript-effect-ts/references/error-handling.md +0 -468
- package/skills/typescript-effect-ts/references/resources.md +0 -476
- package/skills/typescript-effect-ts/references/schema.md +0 -536
- package/skills/typescript-effect-ts/references/services.md +0 -461
- package/skills/typescript-fastify/SKILL.md +0 -405
- package/skills/typescript-fastify/references/plugins.md +0 -329
- package/skills/typescript-fastify/references/typeid.md +0 -315
- package/skills/typescript-functional-patterns/SKILL.md +0 -402
- package/skills/typescript-functional-patterns/references/adts.md +0 -497
- package/skills/typescript-functional-patterns/references/branded-types.md +0 -542
- package/skills/typescript-functional-patterns/references/migration-guide.md +0 -695
- package/skills/typescript-functional-patterns/references/option-result.md +0 -585
- package/skills/typescript-testing/SKILL.md +0 -353
- package/skills/typescript-testing/references/mocking.md +0 -675
- package/skills/typescript-testing/references/msw.md +0 -852
- package/skills/typescript-testing/references/snapshot-testing.md +0 -774
|
@@ -1,536 +0,0 @@
|
|
|
1
|
-
# Effect Schema
|
|
2
|
-
|
|
3
|
-
Effect Schema is a powerful library for defining, validating, and transforming data structures with full TypeScript type inference.
|
|
4
|
-
|
|
5
|
-
## Basic Schemas
|
|
6
|
-
|
|
7
|
-
### Primitive Types
|
|
8
|
-
|
|
9
|
-
```typescript
|
|
10
|
-
import { Schema } from "effect"
|
|
11
|
-
|
|
12
|
-
// Basic primitives
|
|
13
|
-
const str = Schema.String // Schema<string>
|
|
14
|
-
const num = Schema.Number // Schema<number>
|
|
15
|
-
const bool = Schema.Boolean // Schema<boolean>
|
|
16
|
-
const bigint = Schema.BigInt // Schema<bigint>
|
|
17
|
-
|
|
18
|
-
// Literals
|
|
19
|
-
const literal = Schema.Literal("active", "inactive")
|
|
20
|
-
// Schema<"active" | "inactive">
|
|
21
|
-
|
|
22
|
-
// Unknown and Any
|
|
23
|
-
const unknown = Schema.Unknown // Schema<unknown>
|
|
24
|
-
const any = Schema.Any // Schema<any>
|
|
25
|
-
|
|
26
|
-
// Void and Undefined
|
|
27
|
-
const voidSchema = Schema.Void
|
|
28
|
-
const undef = Schema.Undefined
|
|
29
|
-
const nil = Schema.Null
|
|
30
|
-
```
|
|
31
|
-
|
|
32
|
-
### Objects (Struct)
|
|
33
|
-
|
|
34
|
-
```typescript
|
|
35
|
-
import { Schema } from "effect"
|
|
36
|
-
|
|
37
|
-
const User = Schema.Struct({
|
|
38
|
-
id: Schema.String,
|
|
39
|
-
name: Schema.String,
|
|
40
|
-
email: Schema.String,
|
|
41
|
-
age: Schema.Number,
|
|
42
|
-
isActive: Schema.Boolean
|
|
43
|
-
})
|
|
44
|
-
|
|
45
|
-
// Infer TypeScript type
|
|
46
|
-
type User = Schema.Schema.Type<typeof User>
|
|
47
|
-
// { id: string; name: string; email: string; age: number; isActive: boolean }
|
|
48
|
-
```
|
|
49
|
-
|
|
50
|
-
### Optional Fields
|
|
51
|
-
|
|
52
|
-
```typescript
|
|
53
|
-
import { Schema } from "effect"
|
|
54
|
-
|
|
55
|
-
const User = Schema.Struct({
|
|
56
|
-
id: Schema.String,
|
|
57
|
-
name: Schema.String,
|
|
58
|
-
bio: Schema.optional(Schema.String), // string | undefined
|
|
59
|
-
age: Schema.optional(Schema.Number, { exact: true }), // number | undefined (no null)
|
|
60
|
-
nickname: Schema.optionalWith(Schema.String, { default: () => "Anonymous" })
|
|
61
|
-
})
|
|
62
|
-
|
|
63
|
-
// With default value
|
|
64
|
-
const Config = Schema.Struct({
|
|
65
|
-
host: Schema.String,
|
|
66
|
-
port: Schema.optionalWith(Schema.Number, { default: () => 3000 }),
|
|
67
|
-
debug: Schema.optionalWith(Schema.Boolean, { default: () => false })
|
|
68
|
-
})
|
|
69
|
-
```
|
|
70
|
-
|
|
71
|
-
### Arrays and Tuples
|
|
72
|
-
|
|
73
|
-
```typescript
|
|
74
|
-
import { Schema } from "effect"
|
|
75
|
-
|
|
76
|
-
// Array
|
|
77
|
-
const Numbers = Schema.Array(Schema.Number)
|
|
78
|
-
// Schema<number[]>
|
|
79
|
-
|
|
80
|
-
// Non-empty array
|
|
81
|
-
const NonEmptyNumbers = Schema.NonEmptyArray(Schema.Number)
|
|
82
|
-
// Schema<[number, ...number[]]>
|
|
83
|
-
|
|
84
|
-
// Tuple
|
|
85
|
-
const Point = Schema.Tuple(Schema.Number, Schema.Number)
|
|
86
|
-
// Schema<[number, number]>
|
|
87
|
-
|
|
88
|
-
// Tuple with rest
|
|
89
|
-
const Args = Schema.Tuple(
|
|
90
|
-
[Schema.String], // First element
|
|
91
|
-
Schema.Number // Rest elements
|
|
92
|
-
)
|
|
93
|
-
// Schema<[string, ...number[]]>
|
|
94
|
-
```
|
|
95
|
-
|
|
96
|
-
### Unions
|
|
97
|
-
|
|
98
|
-
```typescript
|
|
99
|
-
import { Schema } from "effect"
|
|
100
|
-
|
|
101
|
-
// Union of schemas
|
|
102
|
-
const StringOrNumber = Schema.Union(Schema.String, Schema.Number)
|
|
103
|
-
// Schema<string | number>
|
|
104
|
-
|
|
105
|
-
// Discriminated union
|
|
106
|
-
const Shape = Schema.Union(
|
|
107
|
-
Schema.Struct({
|
|
108
|
-
type: Schema.Literal("circle"),
|
|
109
|
-
radius: Schema.Number
|
|
110
|
-
}),
|
|
111
|
-
Schema.Struct({
|
|
112
|
-
type: Schema.Literal("rectangle"),
|
|
113
|
-
width: Schema.Number,
|
|
114
|
-
height: Schema.Number
|
|
115
|
-
})
|
|
116
|
-
)
|
|
117
|
-
// Schema<{ type: "circle"; radius: number } | { type: "rectangle"; width: number; height: number }>
|
|
118
|
-
```
|
|
119
|
-
|
|
120
|
-
### Records
|
|
121
|
-
|
|
122
|
-
```typescript
|
|
123
|
-
import { Schema } from "effect"
|
|
124
|
-
|
|
125
|
-
// Record with string keys
|
|
126
|
-
const StringRecord = Schema.Record({
|
|
127
|
-
key: Schema.String,
|
|
128
|
-
value: Schema.Number
|
|
129
|
-
})
|
|
130
|
-
// Schema<Record<string, number>>
|
|
131
|
-
|
|
132
|
-
// Record with literal keys
|
|
133
|
-
const StatusCounts = Schema.Record({
|
|
134
|
-
key: Schema.Literal("active", "inactive", "pending"),
|
|
135
|
-
value: Schema.Number
|
|
136
|
-
})
|
|
137
|
-
// Schema<Record<"active" | "inactive" | "pending", number>>
|
|
138
|
-
```
|
|
139
|
-
|
|
140
|
-
## Decoding and Encoding
|
|
141
|
-
|
|
142
|
-
### Synchronous Decoding
|
|
143
|
-
|
|
144
|
-
```typescript
|
|
145
|
-
import { Schema } from "effect"
|
|
146
|
-
|
|
147
|
-
const User = Schema.Struct({
|
|
148
|
-
id: Schema.String,
|
|
149
|
-
name: Schema.String,
|
|
150
|
-
age: Schema.Number
|
|
151
|
-
})
|
|
152
|
-
|
|
153
|
-
// Decode unknown data (throws on failure)
|
|
154
|
-
const user = Schema.decodeUnknownSync(User)({
|
|
155
|
-
id: "123",
|
|
156
|
-
name: "Alice",
|
|
157
|
-
age: 30
|
|
158
|
-
})
|
|
159
|
-
// { id: "123", name: "Alice", age: 30 }
|
|
160
|
-
|
|
161
|
-
// Throws ParseError on invalid data
|
|
162
|
-
try {
|
|
163
|
-
Schema.decodeUnknownSync(User)({ id: 123 }) // id should be string
|
|
164
|
-
} catch (e) {
|
|
165
|
-
console.log(e) // ParseError
|
|
166
|
-
}
|
|
167
|
-
```
|
|
168
|
-
|
|
169
|
-
### Effect-based Decoding
|
|
170
|
-
|
|
171
|
-
```typescript
|
|
172
|
-
import { Schema, Effect } from "effect"
|
|
173
|
-
|
|
174
|
-
const User = Schema.Struct({
|
|
175
|
-
id: Schema.String,
|
|
176
|
-
name: Schema.String
|
|
177
|
-
})
|
|
178
|
-
|
|
179
|
-
// Returns Effect<User, ParseError, never>
|
|
180
|
-
const decode = Schema.decodeUnknown(User)
|
|
181
|
-
|
|
182
|
-
const program = Effect.gen(function* () {
|
|
183
|
-
const user = yield* decode({ id: "123", name: "Alice" })
|
|
184
|
-
return user
|
|
185
|
-
})
|
|
186
|
-
```
|
|
187
|
-
|
|
188
|
-
### Either-based Decoding
|
|
189
|
-
|
|
190
|
-
```typescript
|
|
191
|
-
import { Schema, Either } from "effect"
|
|
192
|
-
|
|
193
|
-
const User = Schema.Struct({
|
|
194
|
-
id: Schema.String,
|
|
195
|
-
name: Schema.String
|
|
196
|
-
})
|
|
197
|
-
|
|
198
|
-
// Returns Either<User, ParseError>
|
|
199
|
-
const result = Schema.decodeUnknownEither(User)({
|
|
200
|
-
id: "123",
|
|
201
|
-
name: "Alice"
|
|
202
|
-
})
|
|
203
|
-
|
|
204
|
-
if (Either.isRight(result)) {
|
|
205
|
-
console.log(result.right) // User
|
|
206
|
-
} else {
|
|
207
|
-
console.log(result.left) // ParseError
|
|
208
|
-
}
|
|
209
|
-
```
|
|
210
|
-
|
|
211
|
-
### Encoding
|
|
212
|
-
|
|
213
|
-
```typescript
|
|
214
|
-
import { Schema } from "effect"
|
|
215
|
-
|
|
216
|
-
const User = Schema.Struct({
|
|
217
|
-
id: Schema.String,
|
|
218
|
-
createdAt: Schema.Date // Encodes to ISO string
|
|
219
|
-
})
|
|
220
|
-
|
|
221
|
-
type User = Schema.Schema.Type<typeof User>
|
|
222
|
-
// { id: string; createdAt: Date }
|
|
223
|
-
|
|
224
|
-
type UserEncoded = Schema.Schema.Encoded<typeof User>
|
|
225
|
-
// { id: string; createdAt: string }
|
|
226
|
-
|
|
227
|
-
const user: User = { id: "123", createdAt: new Date() }
|
|
228
|
-
|
|
229
|
-
// Encode to serializable form
|
|
230
|
-
const encoded = Schema.encodeSync(User)(user)
|
|
231
|
-
// { id: "123", createdAt: "2024-01-15T..." }
|
|
232
|
-
```
|
|
233
|
-
|
|
234
|
-
## Transformations
|
|
235
|
-
|
|
236
|
-
### Built-in Transformations
|
|
237
|
-
|
|
238
|
-
```typescript
|
|
239
|
-
import { Schema } from "effect"
|
|
240
|
-
|
|
241
|
-
// String to Number
|
|
242
|
-
const NumberFromString = Schema.NumberFromString
|
|
243
|
-
Schema.decodeUnknownSync(NumberFromString)("42") // 42
|
|
244
|
-
|
|
245
|
-
// Date from string
|
|
246
|
-
const DateFromString = Schema.Date
|
|
247
|
-
Schema.decodeUnknownSync(DateFromString)("2024-01-15") // Date object
|
|
248
|
-
|
|
249
|
-
// Trim whitespace
|
|
250
|
-
const Trimmed = Schema.Trimmed
|
|
251
|
-
Schema.decodeUnknownSync(Trimmed)(" hello ") // "hello"
|
|
252
|
-
|
|
253
|
-
// Lowercase
|
|
254
|
-
const Lowercased = Schema.Lowercased
|
|
255
|
-
Schema.decodeUnknownSync(Lowercased)("HELLO") // "hello"
|
|
256
|
-
```
|
|
257
|
-
|
|
258
|
-
### Custom Transformations
|
|
259
|
-
|
|
260
|
-
```typescript
|
|
261
|
-
import { Schema } from "effect"
|
|
262
|
-
|
|
263
|
-
// Transform between encoded and decoded types
|
|
264
|
-
const Cents = Schema.transform(
|
|
265
|
-
Schema.Number, // From (encoded)
|
|
266
|
-
Schema.Number, // To (decoded)
|
|
267
|
-
{
|
|
268
|
-
decode: (n) => Math.round(n * 100), // dollars to cents
|
|
269
|
-
encode: (n) => n / 100 // cents to dollars
|
|
270
|
-
}
|
|
271
|
-
)
|
|
272
|
-
|
|
273
|
-
const price = Schema.decodeUnknownSync(Cents)(19.99) // 1999
|
|
274
|
-
const encoded = Schema.encodeSync(Cents)(1999) // 19.99
|
|
275
|
-
```
|
|
276
|
-
|
|
277
|
-
### Transform with Effects
|
|
278
|
-
|
|
279
|
-
```typescript
|
|
280
|
-
import { Schema, Effect } from "effect"
|
|
281
|
-
|
|
282
|
-
// Async transformation
|
|
283
|
-
const UserFromId = Schema.transformOrFail(
|
|
284
|
-
Schema.String,
|
|
285
|
-
Schema.Struct({ id: Schema.String, name: Schema.String }),
|
|
286
|
-
{
|
|
287
|
-
decode: (id) => Effect.tryPromise({
|
|
288
|
-
try: () => fetchUser(id),
|
|
289
|
-
catch: () => new Error(`User not found: ${id}`)
|
|
290
|
-
}),
|
|
291
|
-
encode: (user) => Effect.succeed(user.id)
|
|
292
|
-
}
|
|
293
|
-
)
|
|
294
|
-
```
|
|
295
|
-
|
|
296
|
-
## Refinements and Filters
|
|
297
|
-
|
|
298
|
-
### Built-in Refinements
|
|
299
|
-
|
|
300
|
-
```typescript
|
|
301
|
-
import { Schema } from "effect"
|
|
302
|
-
|
|
303
|
-
// String refinements
|
|
304
|
-
const NonEmpty = Schema.NonEmptyString
|
|
305
|
-
const Email = Schema.String.pipe(Schema.pattern(/^[^\s@]+@[^\s@]+\.[^\s@]+$/))
|
|
306
|
-
const MinLength = Schema.String.pipe(Schema.minLength(3))
|
|
307
|
-
const MaxLength = Schema.String.pipe(Schema.maxLength(100))
|
|
308
|
-
|
|
309
|
-
// Number refinements
|
|
310
|
-
const Positive = Schema.Number.pipe(Schema.positive())
|
|
311
|
-
const NonNegative = Schema.Number.pipe(Schema.nonNegative())
|
|
312
|
-
const Int = Schema.Number.pipe(Schema.int())
|
|
313
|
-
const Between = Schema.Number.pipe(Schema.between(1, 100))
|
|
314
|
-
|
|
315
|
-
// Array refinements
|
|
316
|
-
const NonEmptyArray = Schema.Array(Schema.String).pipe(Schema.minItems(1))
|
|
317
|
-
const MaxItems = Schema.Array(Schema.Number).pipe(Schema.maxItems(10))
|
|
318
|
-
```
|
|
319
|
-
|
|
320
|
-
### Custom Refinements
|
|
321
|
-
|
|
322
|
-
```typescript
|
|
323
|
-
import { Schema } from "effect"
|
|
324
|
-
|
|
325
|
-
// Filter with predicate
|
|
326
|
-
const EvenNumber = Schema.Number.pipe(
|
|
327
|
-
Schema.filter((n) => n % 2 === 0, {
|
|
328
|
-
message: () => "Expected even number"
|
|
329
|
-
})
|
|
330
|
-
)
|
|
331
|
-
|
|
332
|
-
// Brand for nominal typing
|
|
333
|
-
const UserId = Schema.String.pipe(Schema.brand("UserId"))
|
|
334
|
-
type UserId = Schema.Schema.Type<typeof UserId>
|
|
335
|
-
// string & Brand<"UserId">
|
|
336
|
-
|
|
337
|
-
const OrderId = Schema.String.pipe(Schema.brand("OrderId"))
|
|
338
|
-
type OrderId = Schema.Schema.Type<typeof OrderId>
|
|
339
|
-
|
|
340
|
-
// Cannot mix: userId = orderId would be type error
|
|
341
|
-
```
|
|
342
|
-
|
|
343
|
-
## Common Patterns
|
|
344
|
-
|
|
345
|
-
### API Request/Response Schemas
|
|
346
|
-
|
|
347
|
-
```typescript
|
|
348
|
-
import { Schema } from "effect"
|
|
349
|
-
|
|
350
|
-
// Request schema
|
|
351
|
-
const CreateUserRequest = Schema.Struct({
|
|
352
|
-
name: Schema.NonEmptyString,
|
|
353
|
-
email: Schema.String.pipe(
|
|
354
|
-
Schema.pattern(/^[^\s@]+@[^\s@]+\.[^\s@]+$/)
|
|
355
|
-
),
|
|
356
|
-
age: Schema.Number.pipe(Schema.int(), Schema.between(0, 150))
|
|
357
|
-
})
|
|
358
|
-
|
|
359
|
-
// Response schema (includes generated fields)
|
|
360
|
-
const UserResponse = Schema.Struct({
|
|
361
|
-
id: Schema.String,
|
|
362
|
-
name: Schema.String,
|
|
363
|
-
email: Schema.String,
|
|
364
|
-
age: Schema.Number,
|
|
365
|
-
createdAt: Schema.Date
|
|
366
|
-
})
|
|
367
|
-
|
|
368
|
-
// List response with pagination
|
|
369
|
-
const UsersListResponse = Schema.Struct({
|
|
370
|
-
data: Schema.Array(UserResponse),
|
|
371
|
-
total: Schema.Number,
|
|
372
|
-
page: Schema.Number,
|
|
373
|
-
pageSize: Schema.Number
|
|
374
|
-
})
|
|
375
|
-
```
|
|
376
|
-
|
|
377
|
-
### Configuration Schema
|
|
378
|
-
|
|
379
|
-
```typescript
|
|
380
|
-
import { Schema } from "effect"
|
|
381
|
-
|
|
382
|
-
const DatabaseConfig = Schema.Struct({
|
|
383
|
-
host: Schema.NonEmptyString,
|
|
384
|
-
port: Schema.Number.pipe(Schema.int(), Schema.between(1, 65535)),
|
|
385
|
-
database: Schema.NonEmptyString,
|
|
386
|
-
username: Schema.NonEmptyString,
|
|
387
|
-
password: Schema.NonEmptyString,
|
|
388
|
-
ssl: Schema.optionalWith(Schema.Boolean, { default: () => false }),
|
|
389
|
-
poolSize: Schema.optionalWith(
|
|
390
|
-
Schema.Number.pipe(Schema.int(), Schema.positive()),
|
|
391
|
-
{ default: () => 10 }
|
|
392
|
-
)
|
|
393
|
-
})
|
|
394
|
-
|
|
395
|
-
const AppConfig = Schema.Struct({
|
|
396
|
-
env: Schema.Literal("development", "staging", "production"),
|
|
397
|
-
port: Schema.Number.pipe(Schema.int(), Schema.between(1, 65535)),
|
|
398
|
-
database: DatabaseConfig,
|
|
399
|
-
logLevel: Schema.optionalWith(
|
|
400
|
-
Schema.Literal("debug", "info", "warn", "error"),
|
|
401
|
-
{ default: () => "info" as const }
|
|
402
|
-
)
|
|
403
|
-
})
|
|
404
|
-
```
|
|
405
|
-
|
|
406
|
-
### Discriminated Union for Events
|
|
407
|
-
|
|
408
|
-
```typescript
|
|
409
|
-
import { Schema } from "effect"
|
|
410
|
-
|
|
411
|
-
const UserCreated = Schema.Struct({
|
|
412
|
-
type: Schema.Literal("UserCreated"),
|
|
413
|
-
userId: Schema.String,
|
|
414
|
-
email: Schema.String,
|
|
415
|
-
timestamp: Schema.Date
|
|
416
|
-
})
|
|
417
|
-
|
|
418
|
-
const UserUpdated = Schema.Struct({
|
|
419
|
-
type: Schema.Literal("UserUpdated"),
|
|
420
|
-
userId: Schema.String,
|
|
421
|
-
changes: Schema.Record({ key: Schema.String, value: Schema.Unknown }),
|
|
422
|
-
timestamp: Schema.Date
|
|
423
|
-
})
|
|
424
|
-
|
|
425
|
-
const UserDeleted = Schema.Struct({
|
|
426
|
-
type: Schema.Literal("UserDeleted"),
|
|
427
|
-
userId: Schema.String,
|
|
428
|
-
timestamp: Schema.Date
|
|
429
|
-
})
|
|
430
|
-
|
|
431
|
-
const UserEvent = Schema.Union(UserCreated, UserUpdated, UserDeleted)
|
|
432
|
-
type UserEvent = Schema.Schema.Type<typeof UserEvent>
|
|
433
|
-
|
|
434
|
-
// Handle events with exhaustive matching
|
|
435
|
-
const handleEvent = (event: UserEvent) => {
|
|
436
|
-
switch (event.type) {
|
|
437
|
-
case "UserCreated":
|
|
438
|
-
return `User ${event.userId} created`
|
|
439
|
-
case "UserUpdated":
|
|
440
|
-
return `User ${event.userId} updated`
|
|
441
|
-
case "UserDeleted":
|
|
442
|
-
return `User ${event.userId} deleted`
|
|
443
|
-
}
|
|
444
|
-
}
|
|
445
|
-
```
|
|
446
|
-
|
|
447
|
-
### Extend and Compose Schemas
|
|
448
|
-
|
|
449
|
-
```typescript
|
|
450
|
-
import { Schema } from "effect"
|
|
451
|
-
|
|
452
|
-
// Base schema
|
|
453
|
-
const BaseEntity = Schema.Struct({
|
|
454
|
-
id: Schema.String,
|
|
455
|
-
createdAt: Schema.Date,
|
|
456
|
-
updatedAt: Schema.Date
|
|
457
|
-
})
|
|
458
|
-
|
|
459
|
-
// Extend with additional fields
|
|
460
|
-
const User = Schema.Struct({
|
|
461
|
-
...BaseEntity.fields,
|
|
462
|
-
name: Schema.String,
|
|
463
|
-
email: Schema.String
|
|
464
|
-
})
|
|
465
|
-
|
|
466
|
-
// Pick specific fields
|
|
467
|
-
const UserSummary = Schema.pick(User, "id", "name")
|
|
468
|
-
// Schema<{ id: string; name: string }>
|
|
469
|
-
|
|
470
|
-
// Omit specific fields
|
|
471
|
-
const UserWithoutDates = Schema.omit(User, "createdAt", "updatedAt")
|
|
472
|
-
// Schema<{ id: string; name: string; email: string }>
|
|
473
|
-
|
|
474
|
-
// Make all fields optional
|
|
475
|
-
const PartialUser = Schema.partial(User)
|
|
476
|
-
// Schema<{ id?: string; name?: string; ... }>
|
|
477
|
-
```
|
|
478
|
-
|
|
479
|
-
## Error Handling
|
|
480
|
-
|
|
481
|
-
### Parse Errors
|
|
482
|
-
|
|
483
|
-
```typescript
|
|
484
|
-
import { Schema, Either } from "effect"
|
|
485
|
-
|
|
486
|
-
const User = Schema.Struct({
|
|
487
|
-
id: Schema.String,
|
|
488
|
-
age: Schema.Number.pipe(Schema.positive())
|
|
489
|
-
})
|
|
490
|
-
|
|
491
|
-
const result = Schema.decodeUnknownEither(User)({
|
|
492
|
-
id: 123, // Wrong type
|
|
493
|
-
age: -5 // Negative
|
|
494
|
-
})
|
|
495
|
-
|
|
496
|
-
if (Either.isLeft(result)) {
|
|
497
|
-
// Access detailed error information
|
|
498
|
-
const error = result.left
|
|
499
|
-
console.log(error.message)
|
|
500
|
-
// Contains path, expected type, actual value
|
|
501
|
-
}
|
|
502
|
-
```
|
|
503
|
-
|
|
504
|
-
### Custom Error Messages
|
|
505
|
-
|
|
506
|
-
```typescript
|
|
507
|
-
import { Schema } from "effect"
|
|
508
|
-
|
|
509
|
-
const Email = Schema.String.pipe(
|
|
510
|
-
Schema.pattern(/^[^\s@]+@[^\s@]+\.[^\s@]+$/, {
|
|
511
|
-
message: () => "Invalid email format"
|
|
512
|
-
})
|
|
513
|
-
)
|
|
514
|
-
|
|
515
|
-
const Age = Schema.Number.pipe(
|
|
516
|
-
Schema.between(0, 150, {
|
|
517
|
-
message: () => "Age must be between 0 and 150"
|
|
518
|
-
})
|
|
519
|
-
)
|
|
520
|
-
|
|
521
|
-
const Password = Schema.String.pipe(
|
|
522
|
-
Schema.minLength(8, { message: () => "Password must be at least 8 characters" }),
|
|
523
|
-
Schema.pattern(/[A-Z]/, { message: () => "Password must contain uppercase" }),
|
|
524
|
-
Schema.pattern(/[0-9]/, { message: () => "Password must contain a number" })
|
|
525
|
-
)
|
|
526
|
-
```
|
|
527
|
-
|
|
528
|
-
## Best Practices
|
|
529
|
-
|
|
530
|
-
1. **Infer types from schemas** - Use `Schema.Schema.Type<typeof X>` instead of manual types
|
|
531
|
-
2. **Use brands for domain types** - Prevents mixing IDs of different entities
|
|
532
|
-
3. **Validate at boundaries** - Decode external data early, work with typed data internally
|
|
533
|
-
4. **Compose schemas** - Build complex schemas from simpler ones
|
|
534
|
-
5. **Add custom error messages** - Make validation errors user-friendly
|
|
535
|
-
6. **Use transformations** - Handle format differences between API and domain
|
|
536
|
-
7. **Keep schemas close to usage** - Define schemas where they're used or in a shared module
|