@martinffx/atelier 1.4.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.
Files changed (93) hide show
  1. package/CHANGELOG.md +38 -0
  2. package/README.md +95 -220
  3. package/agents/architect.md +6 -6
  4. package/agents/oracle.md +1 -1
  5. package/agents/{recon.md → sentinel.md} +3 -3
  6. package/dist/atelier.js +167 -26
  7. package/package.json +2 -1
  8. package/skills/atelier-orchestrator/SKILL.md +180 -0
  9. package/skills/atelier-setup/SKILL.md +80 -0
  10. package/skills/code-review/SKILL.md +2 -2
  11. package/skills/code-review/references/reviewers.md +3 -3
  12. package/skills/code-review/references/rq.md +9 -9
  13. package/skills/code-subagents/SKILL.md +44 -75
  14. package/skills/code-subagents/references/batch-reviewer-prompt.md +44 -0
  15. package/skills/code-subagents/references/implementor-prompt.md +10 -11
  16. package/skills/spec-brainstorm/SKILL.md +21 -16
  17. package/skills/spec-finish/SKILL.md +14 -14
  18. package/skills/spec-implement/SKILL.md +42 -42
  19. package/skills/spec-plan/SKILL.md +72 -26
  20. package/skills/spec-plan/references/plan_template.md +77 -0
  21. package/skills/code-subagents/references/code-quality-reviewer-prompt.md +0 -48
  22. package/skills/code-subagents/references/spec-reviewer-prompt.md +0 -62
  23. package/skills/python-architecture/SKILL.md +0 -148
  24. package/skills/python-architecture/references/data-modeling.md +0 -558
  25. package/skills/python-architecture/references/ddd.md +0 -569
  26. package/skills/python-architecture/references/functional-core.md +0 -337
  27. package/skills/python-build-tools/SKILL.md +0 -153
  28. package/skills/python-build-tools/references/basedpyright.md +0 -321
  29. package/skills/python-build-tools/references/ruff.md +0 -225
  30. package/skills/python-build-tools/references/uv.md +0 -257
  31. package/skills/python-fastapi/SKILL.md +0 -184
  32. package/skills/python-fastapi/references/api-design.md +0 -563
  33. package/skills/python-fastapi/references/dependencies.md +0 -479
  34. package/skills/python-fastapi/references/middleware.md +0 -475
  35. package/skills/python-fastapi/references/validation.md +0 -497
  36. package/skills/python-modern-python/SKILL.md +0 -201
  37. package/skills/python-modern-python/references/async.md +0 -76
  38. package/skills/python-modern-python/references/pattern-matching.md +0 -89
  39. package/skills/python-modern-python/references/typing.md +0 -96
  40. package/skills/python-monorepo/SKILL.md +0 -146
  41. package/skills/python-monorepo/references/docker.md +0 -515
  42. package/skills/python-monorepo/references/namespace-packages.md +0 -426
  43. package/skills/python-monorepo/references/workspace-config.md +0 -341
  44. package/skills/python-sqlalchemy/SKILL.md +0 -119
  45. package/skills/python-sqlalchemy/references/async.md +0 -51
  46. package/skills/python-sqlalchemy/references/models.md +0 -67
  47. package/skills/python-sqlalchemy/references/queries.md +0 -57
  48. package/skills/python-temporal/SKILL.md +0 -128
  49. package/skills/python-temporal/references/patterns.md +0 -87
  50. package/skills/python-temporal/references/testing.md +0 -54
  51. package/skills/python-testing/SKILL.md +0 -161
  52. package/skills/python-testing/references/boundaries.md +0 -449
  53. package/skills/python-testing/references/mocking.md +0 -449
  54. package/skills/python-testing/references/pytest.md +0 -524
  55. package/skills/spec-orchestrator/SKILL.md +0 -145
  56. package/skills/typescript-api-design/SKILL.md +0 -396
  57. package/skills/typescript-api-design/references/error-responses.md +0 -469
  58. package/skills/typescript-build-tools/SKILL.md +0 -447
  59. package/skills/typescript-build-tools/references/biome.md +0 -436
  60. package/skills/typescript-build-tools/references/bun.md +0 -432
  61. package/skills/typescript-build-tools/references/turborepo.md +0 -426
  62. package/skills/typescript-build-tools/references/vitest.md +0 -518
  63. package/skills/typescript-drizzle-orm/SKILL.md +0 -398
  64. package/skills/typescript-drizzle-orm/references/cloudflare.md +0 -392
  65. package/skills/typescript-drizzle-orm/references/entity-pattern.md +0 -412
  66. package/skills/typescript-drizzle-orm/references/postgresql.md +0 -191
  67. package/skills/typescript-drizzle-orm/references/repository-pattern.md +0 -479
  68. package/skills/typescript-drizzle-orm/references/sqlite.md +0 -291
  69. package/skills/typescript-dynamodb-toolbox/SKILL.md +0 -370
  70. package/skills/typescript-dynamodb-toolbox/references/entity-layer.md +0 -574
  71. package/skills/typescript-dynamodb-toolbox/references/error-handling.md +0 -495
  72. package/skills/typescript-dynamodb-toolbox/references/modeling.md +0 -332
  73. package/skills/typescript-dynamodb-toolbox/references/repository-layer.md +0 -631
  74. package/skills/typescript-dynamodb-toolbox/references/testing.md +0 -718
  75. package/skills/typescript-dynamodb-toolbox/references/transactions.md +0 -423
  76. package/skills/typescript-effect-ts/SKILL.md +0 -341
  77. package/skills/typescript-effect-ts/references/core.md +0 -412
  78. package/skills/typescript-effect-ts/references/error-handling.md +0 -468
  79. package/skills/typescript-effect-ts/references/resources.md +0 -476
  80. package/skills/typescript-effect-ts/references/schema.md +0 -536
  81. package/skills/typescript-effect-ts/references/services.md +0 -461
  82. package/skills/typescript-fastify/SKILL.md +0 -405
  83. package/skills/typescript-fastify/references/plugins.md +0 -329
  84. package/skills/typescript-fastify/references/typeid.md +0 -315
  85. package/skills/typescript-functional-patterns/SKILL.md +0 -402
  86. package/skills/typescript-functional-patterns/references/adts.md +0 -497
  87. package/skills/typescript-functional-patterns/references/branded-types.md +0 -542
  88. package/skills/typescript-functional-patterns/references/migration-guide.md +0 -695
  89. package/skills/typescript-functional-patterns/references/option-result.md +0 -585
  90. package/skills/typescript-testing/SKILL.md +0 -353
  91. package/skills/typescript-testing/references/mocking.md +0 -675
  92. package/skills/typescript-testing/references/msw.md +0 -852
  93. package/skills/typescript-testing/references/snapshot-testing.md +0 -774
@@ -1,695 +0,0 @@
1
- # Migration Guide: Adopting Functional Patterns
2
-
3
- This guide provides a practical, incremental approach to introducing functional patterns (ADTs, Option, Result, branded types) into an existing TypeScript codebase.
4
-
5
- ## Migration Philosophy
6
-
7
- **Start small, prove value, expand gradually.**
8
-
9
- Don't rewrite everything at once. Instead:
10
- 1. Apply patterns to new features immediately
11
- 2. Refactor existing code opportunistically (when touching it)
12
- 3. Prioritize high-risk areas (financial calculations, state machines)
13
- 4. Build team familiarity through examples and pairing
14
-
15
- ## Stage 1: Setup and Foundation
16
-
17
- ### Enable TypeScript Strict Mode
18
-
19
- Add these flags to `tsconfig.json`:
20
-
21
- ```json
22
- {
23
- "compilerOptions": {
24
- "strict": true,
25
- "strictNullChecks": true,
26
- "noImplicitReturns": true,
27
- "strictFunctionTypes": true,
28
- "noUncheckedIndexedAccess": true
29
- }
30
- }
31
- ```
32
-
33
- **Why:** These flags make the type system stricter, enabling better safety and forcing you to handle edge cases.
34
-
35
- **How to migrate:**
36
- 1. Start with `strictNullChecks: true`
37
- 2. Fix all type errors (replace `T` with `T | null | undefined` where needed)
38
- 3. Gradually enable other flags
39
- 4. Use `// @ts-expect-error` for complex migrations (remove later)
40
-
41
- ### Create Utility Files
42
-
43
- Create a `lib/functional.ts` file with core utilities:
44
-
45
- ```typescript
46
- // ============================================
47
- // Option Type
48
- // ============================================
49
- export type None = { _tag: "None" }
50
- export type Some<T> = { _tag: "Some"; value: T }
51
- export type Option<T> = None | Some<T>
52
-
53
- export const None: None = { _tag: "None" }
54
- export const Some = <T>(value: T): Option<T> => ({ _tag: "Some", value })
55
-
56
- export const isNone = <T>(opt: Option<T>): opt is None => opt._tag === "None"
57
- export const isSome = <T>(opt: Option<T>): opt is Some<T> => opt._tag === "Some"
58
-
59
- export const getOrElse = <T>(opt: Option<T>, defaultValue: T): T =>
60
- opt._tag === "Some" ? opt.value : defaultValue
61
-
62
- export const map = <T, U>(opt: Option<T>, fn: (value: T) => U): Option<U> =>
63
- opt._tag === "Some" ? Some(fn(opt.value)) : None
64
-
65
- export const flatMap = <T, U>(opt: Option<T>, fn: (value: T) => Option<U>): Option<U> =>
66
- opt._tag === "Some" ? fn(opt.value) : None
67
-
68
- // ============================================
69
- // Result Type
70
- // ============================================
71
- export type Ok<T> = { _tag: "Ok"; value: T }
72
- export type Err<E> = { _tag: "Err"; error: E }
73
- export type Result<T, E> = Ok<T> | Err<E>
74
-
75
- export const Ok = <T>(value: T): Result<T, never> => ({ _tag: "Ok", value })
76
- export const Err = <E>(error: E): Result<never, E> => ({ _tag: "Err", error })
77
-
78
- export const isOk = <T, E>(result: Result<T, E>): result is Ok<T> => result._tag === "Ok"
79
- export const isErr = <T, E>(result: Result<T, E>): result is Err<E> => result._tag === "Err"
80
-
81
- export const mapResult = <T, U, E>(result: Result<T, E>, fn: (value: T) => U): Result<U, E> =>
82
- result._tag === "Ok" ? Ok(fn(result.value)) : result
83
-
84
- export const flatMapResult = <T, U, E>(
85
- result: Result<T, E>,
86
- fn: (value: T) => Result<U, E>
87
- ): Result<U, E> =>
88
- result._tag === "Ok" ? fn(result.value) : result
89
-
90
- // ============================================
91
- // Exhaustiveness Checking
92
- // ============================================
93
- export const assertNever = (x: never): never => {
94
- throw new Error(`Unhandled variant: ${JSON.stringify(x)}`)
95
- }
96
-
97
- // ============================================
98
- // Branded Types
99
- // ============================================
100
- export type Brand<K, T> = K & { __brand: T }
101
- ```
102
-
103
- Import from this file across your codebase:
104
-
105
- ```typescript
106
- import { Option, Some, None, Result, Ok, Err } from "@/lib/functional"
107
- ```
108
-
109
- ## Stage 2: Identify Code Smells
110
-
111
- Look for these patterns that benefit from functional refactoring:
112
-
113
- ### 1. Boolean Soup
114
-
115
- **Before:**
116
- ```typescript
117
- type Order = {
118
- id: string
119
- isPaid: boolean
120
- isShipped: boolean
121
- isCancelled: boolean
122
- }
123
- // What does { isPaid: true, isCancelled: true } mean?
124
- ```
125
-
126
- **After:**
127
- ```typescript
128
- type OrderStatus =
129
- | { kind: "pending" }
130
- | { kind: "paid"; paidAt: Date }
131
- | { kind: "shipped"; trackingNumber: string }
132
- | { kind: "delivered"; deliveredAt: Date }
133
- | { kind: "cancelled"; reason: string }
134
-
135
- type Order = {
136
- id: string
137
- status: OrderStatus
138
- }
139
- ```
140
-
141
- ### 2. Null/Undefined Checks
142
-
143
- **Before:**
144
- ```typescript
145
- function findUser(id: string): User | null {
146
- // ...
147
- }
148
-
149
- const user = findUser("123")
150
- if (user !== null) {
151
- console.log(user.name)
152
- } else {
153
- console.log("Not found")
154
- }
155
- ```
156
-
157
- **After:**
158
- ```typescript
159
- function findUser(id: string): Option<User> {
160
- const user = database.get(id)
161
- return user ? Some(user) : None
162
- }
163
-
164
- const user = findUser("123")
165
- switch (user._tag) {
166
- case "Some":
167
- console.log(user.value.name)
168
- break
169
- case "None":
170
- console.log("Not found")
171
- break
172
- }
173
- ```
174
-
175
- ### 3. Error Handling with Exceptions
176
-
177
- **Before:**
178
- ```typescript
179
- function parseConfig(raw: string): Config {
180
- try {
181
- return JSON.parse(raw)
182
- } catch (e) {
183
- // Caller doesn't know this throws
184
- throw new Error("Invalid config")
185
- }
186
- }
187
- ```
188
-
189
- **After:**
190
- ```typescript
191
- type ParseError = { message: string; cause?: unknown }
192
-
193
- function parseConfig(raw: string): Result<Config, ParseError> {
194
- try {
195
- const data = JSON.parse(raw)
196
- return Ok(data)
197
- } catch (e) {
198
- return Err({ message: "Invalid config", cause: e })
199
- }
200
- }
201
- ```
202
-
203
- ### 4. Primitive Obsession
204
-
205
- **Before:**
206
- ```typescript
207
- function calculateTotal(priceInCents: number, taxInDollars: number): number {
208
- // Mix of units - error-prone!
209
- return priceInCents + taxInDollars * 100
210
- }
211
- ```
212
-
213
- **After:**
214
- ```typescript
215
- type Cents = Brand<number, "Cents">
216
- const Cents = (n: number): Cents => {
217
- if (!Number.isInteger(n) || n < 0) throw new Error("Invalid cents")
218
- return n as Cents
219
- }
220
-
221
- function calculateTotal(price: Cents, tax: Cents): Cents {
222
- return Cents(price + tax) // Type-safe
223
- }
224
- ```
225
-
226
- ## Stage 3: Incremental Adoption Strategy
227
-
228
- ### Week 1-2: New Features Only
229
-
230
- Apply patterns to all new features:
231
- - Use discriminated unions for state machines
232
- - Use Option for nullable values
233
- - Use Result for operations that may fail
234
- - Use branded types for domain IDs and units
235
-
236
- **Goal:** Build team familiarity without touching existing code.
237
-
238
- ### Week 3-4: Bug Fixes and Refactoring
239
-
240
- When fixing bugs, refactor to functional patterns:
241
- - Boolean soup → Discriminated unions
242
- - Null checks → Option
243
- - Try/catch → Result
244
- - Primitive types → Branded types
245
-
246
- **Goal:** Improve code quality while fixing issues.
247
-
248
- ### Week 5-8: High-Risk Areas
249
-
250
- Prioritize areas where bugs have high impact:
251
- - **Financial calculations**: Use branded types for Cents, Dollars, BasisPoints
252
- - **State machines**: Use discriminated unions for transaction states
253
- - **Authentication**: Use branded types for tokens, session IDs
254
- - **Configuration**: Use Result for parsing and validation
255
-
256
- **Goal:** Reduce risk in critical paths.
257
-
258
- ### Month 3+: Gradual Expansion
259
-
260
- Continue applying patterns when touching code. Measure impact:
261
- - Fewer null pointer errors
262
- - Fewer unit confusion bugs
263
- - Fewer missing case errors (exhaustiveness)
264
- - Easier refactoring (type safety)
265
-
266
- ## Stage 4: Pattern-Specific Migration
267
-
268
- ### Migrating to Discriminated Unions
269
-
270
- **Step 1: Identify boolean soup**
271
- ```typescript
272
- // Before
273
- type User = {
274
- isActive: boolean
275
- isBanned: boolean
276
- }
277
- ```
278
-
279
- **Step 2: Define discriminated union**
280
- ```typescript
281
- // After
282
- type UserStatus =
283
- | { kind: "active" }
284
- | { kind: "banned"; reason: string; bannedAt: Date }
285
- | { kind: "inactive"; inactiveAt: Date }
286
- ```
287
-
288
- **Step 3: Update type definition**
289
- ```typescript
290
- type User = {
291
- id: UserId
292
- email: Email
293
- status: UserStatus // Replace booleans with status
294
- }
295
- ```
296
-
297
- **Step 4: Update creation code**
298
- ```typescript
299
- // Before
300
- const user = { id: "123", isActive: true, isBanned: false }
301
-
302
- // After
303
- const user = { id: UserId("123"), email: Email("user@example.com"), status: { kind: "active" } }
304
- ```
305
-
306
- **Step 5: Update consumption code**
307
- ```typescript
308
- // Before
309
- if (user.isActive && !user.isBanned) {
310
- allowLogin(user)
311
- }
312
-
313
- // After
314
- switch (user.status.kind) {
315
- case "active":
316
- allowLogin(user)
317
- break
318
- case "banned":
319
- showBannedMessage(user.status.reason)
320
- break
321
- case "inactive":
322
- showInactiveMessage()
323
- break
324
- default:
325
- assertNever(user.status)
326
- }
327
- ```
328
-
329
- ### Migrating to Option
330
-
331
- **Step 1: Identify nullable types**
332
- ```typescript
333
- function findUser(id: string): User | null
334
- ```
335
-
336
- **Step 2: Change return type to Option**
337
- ```typescript
338
- function findUser(id: string): Option<User>
339
- ```
340
-
341
- **Step 3: Update implementation**
342
- ```typescript
343
- // Before
344
- function findUser(id: string): User | null {
345
- const user = database.get(id)
346
- return user ?? null
347
- }
348
-
349
- // After
350
- function findUser(id: string): Option<User> {
351
- const user = database.get(id)
352
- return user ? Some(user) : None
353
- }
354
- ```
355
-
356
- **Step 4: Update call sites**
357
- ```typescript
358
- // Before
359
- const user = findUser("123")
360
- if (user !== null) {
361
- console.log(user.name)
362
- }
363
-
364
- // After
365
- const user = findUser("123")
366
- switch (user._tag) {
367
- case "Some":
368
- console.log(user.value.name)
369
- break
370
- case "None":
371
- console.log("Not found")
372
- break
373
- }
374
- ```
375
-
376
- ### Migrating to Result
377
-
378
- **Step 1: Identify throwing functions**
379
- ```typescript
380
- function parseConfig(raw: string): Config {
381
- // Throws on error
382
- }
383
- ```
384
-
385
- **Step 2: Define error type**
386
- ```typescript
387
- type ParseError = { message: string; field?: string }
388
- ```
389
-
390
- **Step 3: Change return type to Result**
391
- ```typescript
392
- function parseConfig(raw: string): Result<Config, ParseError>
393
- ```
394
-
395
- **Step 4: Update implementation**
396
- ```typescript
397
- // Before
398
- function parseConfig(raw: string): Config {
399
- try {
400
- return JSON.parse(raw)
401
- } catch (e) {
402
- throw new Error("Invalid config")
403
- }
404
- }
405
-
406
- // After
407
- function parseConfig(raw: string): Result<Config, ParseError> {
408
- try {
409
- const data = JSON.parse(raw)
410
- return Ok(data)
411
- } catch (e) {
412
- return Err({ message: "Invalid JSON" })
413
- }
414
- }
415
- ```
416
-
417
- **Step 5: Update call sites**
418
- ```typescript
419
- // Before
420
- try {
421
- const config = parseConfig(rawConfig)
422
- startServer(config)
423
- } catch (e) {
424
- console.error("Config error:", e)
425
- }
426
-
427
- // After
428
- const configResult = parseConfig(rawConfig)
429
- switch (configResult._tag) {
430
- case "Ok":
431
- startServer(configResult.value)
432
- break
433
- case "Err":
434
- console.error("Config error:", configResult.error.message)
435
- break
436
- }
437
- ```
438
-
439
- ### Migrating to Branded Types
440
-
441
- **Step 1: Identify primitive obsession**
442
- ```typescript
443
- function getUser(id: string): User
444
- function getOrder(id: string): Order
445
-
446
- // Problem: Can mix up IDs
447
- const orderId = "ord_123"
448
- getUser(orderId) // Compiles but wrong!
449
- ```
450
-
451
- **Step 2: Define branded types**
452
- ```typescript
453
- type UserId = Brand<string, "UserId">
454
- type OrderId = Brand<string, "OrderId">
455
-
456
- const UserId = (s: string): UserId => {
457
- if (!/^usr_/.test(s)) throw new Error("Invalid UserId")
458
- return s as UserId
459
- }
460
-
461
- const OrderId = (s: string): OrderId => {
462
- if (!/^ord_/.test(s)) throw new Error("Invalid OrderId")
463
- return s as OrderId
464
- }
465
- ```
466
-
467
- **Step 3: Update function signatures**
468
- ```typescript
469
- function getUser(id: UserId): User
470
- function getOrder(id: OrderId): Order
471
- ```
472
-
473
- **Step 4: Update call sites**
474
- ```typescript
475
- // Before
476
- const userId = "usr_123"
477
- getUser(userId)
478
-
479
- // After
480
- const userId = UserId("usr_123")
481
- getUser(userId)
482
- ```
483
-
484
- ## Stage 5: CI/CD Enforcement
485
-
486
- ### Linting Rules
487
-
488
- Create ESLint rules to enforce patterns:
489
-
490
- ```javascript
491
- // .eslintrc.js
492
- module.exports = {
493
- rules: {
494
- // Prefer switch over if-else for discriminated unions
495
- "no-else-return": "error",
496
-
497
- // Require default case in switch
498
- "default-case": "error",
499
- },
500
- }
501
- ```
502
-
503
- ### Type Coverage
504
-
505
- Measure type safety with `type-coverage`:
506
-
507
- ```bash
508
- npm install -D type-coverage
509
- npx type-coverage --at-least 95
510
- ```
511
-
512
- ### Pre-commit Hooks
513
-
514
- Use `husky` to run type checks before commit:
515
-
516
- ```json
517
- {
518
- "husky": {
519
- "hooks": {
520
- "pre-commit": "tsc --noEmit"
521
- }
522
- }
523
- }
524
- ```
525
-
526
- ## Stage 6: Team Adoption
527
-
528
- ### Documentation
529
-
530
- Create internal docs with examples:
531
- - Why we use these patterns (reliability, safety)
532
- - When to use each pattern (decision guide)
533
- - How to migrate existing code (this guide)
534
- - Common mistakes and solutions
535
-
536
- ### Code Reviews
537
-
538
- Review checklist:
539
- - [ ] New state modeled with discriminated unions (not booleans)
540
- - [ ] Nullable values use Option (not `| null`)
541
- - [ ] Fallible operations use Result (not throwing)
542
- - [ ] Domain IDs use branded types (not raw strings)
543
- - [ ] All switch statements have `assertNever` in default case
544
- - [ ] Smart constructors validate invariants
545
-
546
- ### Pairing Sessions
547
-
548
- Pair with team members on:
549
- - First discriminated union implementation
550
- - First Option/Result migration
551
- - First branded type creation
552
- - Code review of functional patterns
553
-
554
- ### Internal Talks
555
-
556
- Host brown-bag sessions:
557
- - "Why functional patterns matter" (30 min)
558
- - "Migrating to discriminated unions" (45 min)
559
- - "Option vs Result vs Exceptions" (30 min)
560
- - "Branded types for domain modeling" (45 min)
561
-
562
- ## Common Migration Challenges
563
-
564
- ### Challenge 1: "Too much boilerplate"
565
-
566
- **Problem:** Pattern matching feels verbose compared to `if (x != null)`.
567
-
568
- **Solution:** Accept some verbosity for safety. The compiler proves correctness. Consider:
569
- - Null checks miss cases (forgot to check before accessing)
570
- - Switch with `assertNever` proves exhaustiveness
571
- - Cost: 2 extra lines. Benefit: Compiler-proven correctness.
572
-
573
- ### Challenge 2: "Existing code uses null everywhere"
574
-
575
- **Problem:** Can't migrate everything at once.
576
-
577
- **Solution:** Create adapter functions:
578
-
579
- ```typescript
580
- // Adapter for legacy code
581
- function optionToNullable<T>(opt: Option<T>): T | null {
582
- return opt._tag === "Some" ? opt.value : null
583
- }
584
-
585
- function nullableToOption<T>(value: T | null): Option<T> {
586
- return value != null ? Some(value) : None
587
- }
588
-
589
- // Use at boundaries
590
- const user = findUser("123") // Returns Option<User>
591
- legacyFunction(optionToNullable(user)) // Legacy expects User | null
592
- ```
593
-
594
- ### Challenge 3: "Team not convinced"
595
-
596
- **Problem:** Developers resistant to change.
597
-
598
- **Solution:**
599
- 1. Start with new features (no risk to existing code)
600
- 2. Show concrete bug prevented by pattern (e.g., missing switch case)
601
- 3. Measure impact: fewer null errors, fewer unit mix-ups
602
- 4. Demonstrate refactoring safety (add variant, compiler shows all needed changes)
603
- 5. Share success stories from team
604
-
605
- ### Challenge 4: "Performance concerns"
606
-
607
- **Problem:** Worried about extra object allocations.
608
-
609
- **Solution:**
610
- - Branded types have zero runtime cost (compile-time only)
611
- - Option/Result overhead is negligible (one extra property)
612
- - TypeScript optimizes discriminated unions well
613
- - Profile before optimizing
614
- - Correctness > micro-optimizations in most code
615
-
616
- ### Challenge 5: "JSON serialization breaks"
617
-
618
- **Problem:** Branded types don't serialize to JSON correctly.
619
-
620
- **Solution:** Create serialization helpers:
621
-
622
- ```typescript
623
- type UserId = Brand<string, "UserId">
624
-
625
- // Serialize: strip brand (it's just a string)
626
- function userIdToJSON(id: UserId): string {
627
- return id
628
- }
629
-
630
- // Deserialize: validate and brand
631
- function userIdFromJSON(json: string): Result<UserId, string> {
632
- if (!/^usr_/.test(json)) {
633
- return Err("Invalid UserId format")
634
- }
635
- return Ok(json as UserId)
636
- }
637
-
638
- // In practice
639
- const json = JSON.stringify({ userId: userIdToJSON(userId) })
640
- const parsed = JSON.parse(json)
641
- const userIdResult = userIdFromJSON(parsed.userId)
642
- ```
643
-
644
- ## Measuring Success
645
-
646
- Track these metrics before and after adoption:
647
-
648
- ### Bugs Prevented
649
- - Null pointer errors
650
- - Unit confusion (cents vs dollars)
651
- - Missing switch cases
652
- - Invalid state combinations (boolean soup)
653
-
654
- ### Code Quality
655
- - Type coverage percentage
656
- - Number of `any` types
657
- - Number of `// @ts-ignore` comments
658
-
659
- ### Developer Experience
660
- - Time to refactor features (should decrease)
661
- - Confidence in refactoring (should increase)
662
- - Time spent debugging type errors (should decrease)
663
- - Time to onboard new developers (may increase initially, then decrease)
664
-
665
- ## Timeline Example
666
-
667
- **Month 1:**
668
- - Enable strict mode
669
- - Create utility files
670
- - Apply to 2-3 new features
671
- - Team training (brown-bag)
672
-
673
- **Month 2:**
674
- - Apply to all new features
675
- - Refactor 5-10 bug fixes
676
- - Pair with team members
677
- - Measure initial impact
678
-
679
- **Month 3:**
680
- - Migrate high-risk areas (financial, auth)
681
- - Expand to 50% of active development
682
- - Add CI checks
683
- - Review and iterate
684
-
685
- **Month 4+:**
686
- - Standard practice for all code
687
- - Continue opportunistic refactoring
688
- - Measure long-term impact
689
- - Share learnings
690
-
691
- ## Further Reading
692
-
693
- - [ADTs](./adts.md) - Algebraic Data Types and discriminated unions
694
- - [Option & Result](./option-result.md) - Type-safe error handling
695
- - [Branded Types](./branded-types.md) - Smart constructors and nominal typing