@martinffx/atelier 0.1.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 +38 -0
- package/LICENSE +21 -0
- package/README.md +285 -0
- package/agents/architect.md +38 -0
- package/agents/oracle.md +40 -0
- package/agents/recon.md +39 -0
- package/dist/atelier.js +33082 -0
- package/package.json +44 -0
- package/skills/code-conventional-commit/SKILL.md +134 -0
- package/skills/code-conventional-commit/evals/evals.json +23 -0
- package/skills/code-conventional-commit/workspace/iteration-1/eval-0/with_skill/outputs/commit-message.txt +4 -0
- package/skills/code-conventional-commit/workspace/iteration-1/eval-0/with_skill/outputs/eval_metadata.json +6 -0
- package/skills/code-conventional-commit/workspace/iteration-1/eval-1/with_skill/outputs/eval_metadata.json +6 -0
- package/skills/code-conventional-commit/workspace/iteration-1/eval-1/with_skill/outputs/validation-result.txt +16 -0
- package/skills/code-conventional-commit/workspace/iteration-1/eval-2/with_skill/outputs/commit-message.txt +8 -0
- package/skills/code-conventional-commit/workspace/iteration-1/eval-2/with_skill/outputs/eval_metadata.json +6 -0
- package/skills/code-conventional-commit/workspace/iteration-1/report.html +1325 -0
- package/skills/code-debug/SKILL.md +271 -0
- package/skills/code-debug/references/language-tools.md +139 -0
- package/skills/code-debug/references/techniques.md +117 -0
- package/skills/code-docs/SKILL.md +171 -0
- package/skills/code-docs/references/api-docs.md +116 -0
- package/skills/code-docs/references/changelog.md +64 -0
- package/skills/code-docs/references/readme-structure.md +94 -0
- package/skills/code-git-worktrees/SKILL.md +218 -0
- package/skills/code-perf/SKILL.md +163 -0
- package/skills/code-perf/references/common-bottlenecks.md +124 -0
- package/skills/code-perf/references/optimization-patterns.md +167 -0
- package/skills/code-perf/references/profiling-tools.md +126 -0
- package/skills/code-review/SKILL.md +78 -0
- package/skills/code-review/references/output.md +200 -0
- package/skills/code-review/references/reviewers.md +719 -0
- package/skills/code-review/references/rq.md +357 -0
- package/skills/code-review/references/rs.md +30 -0
- package/skills/code-security/SKILL.md +183 -0
- package/skills/code-security/references/owasp-top-10.md +129 -0
- package/skills/code-security/references/security-tools.md +102 -0
- package/skills/code-security/references/vulnerability-patterns.md +129 -0
- package/skills/code-stacked-commit/SKILL.md +155 -0
- package/skills/code-subagents/SKILL.md +184 -0
- package/skills/code-subagents/references/code-quality-reviewer-prompt.md +48 -0
- package/skills/code-subagents/references/implementor-prompt.md +79 -0
- package/skills/code-subagents/references/spec-reviewer-prompt.md +62 -0
- package/skills/oracle-architect/SKILL.md +246 -0
- package/skills/oracle-architect/references/api-design.md +345 -0
- package/skills/oracle-architect/references/data-modeling.md +318 -0
- package/skills/oracle-architect/references/ddd-patterns.md +247 -0
- package/skills/oracle-challenge/SKILL.md +126 -0
- package/skills/oracle-testing/SKILL.md +237 -0
- package/skills/oracle-testing/references/anti-patterns.md +618 -0
- package/skills/oracle-testing/references/boundaries.md +579 -0
- package/skills/oracle-testing/references/core-testing.md +691 -0
- package/skills/oracle-testing/references/edge-testing.md +794 -0
- package/skills/oracle-testing/references/stub-driven-tdd.md +322 -0
- package/skills/oracle-thinkdeep/SKILL.md +164 -0
- package/skills/python-architecture/SKILL.md +148 -0
- package/skills/python-architecture/references/data-modeling.md +558 -0
- package/skills/python-architecture/references/ddd.md +569 -0
- package/skills/python-architecture/references/functional-core.md +337 -0
- package/skills/python-build-tools/SKILL.md +153 -0
- package/skills/python-build-tools/references/basedpyright.md +321 -0
- package/skills/python-build-tools/references/ruff.md +225 -0
- package/skills/python-build-tools/references/uv.md +257 -0
- package/skills/python-fastapi/SKILL.md +184 -0
- package/skills/python-fastapi/references/api-design.md +563 -0
- package/skills/python-fastapi/references/dependencies.md +479 -0
- package/skills/python-fastapi/references/middleware.md +475 -0
- package/skills/python-fastapi/references/validation.md +497 -0
- package/skills/python-modern-python/SKILL.md +201 -0
- package/skills/python-modern-python/references/async.md +76 -0
- package/skills/python-modern-python/references/pattern-matching.md +89 -0
- package/skills/python-modern-python/references/typing.md +96 -0
- package/skills/python-monorepo/SKILL.md +146 -0
- package/skills/python-monorepo/references/docker.md +515 -0
- package/skills/python-monorepo/references/namespace-packages.md +426 -0
- package/skills/python-monorepo/references/workspace-config.md +341 -0
- package/skills/python-sqlalchemy/SKILL.md +119 -0
- package/skills/python-sqlalchemy/references/async.md +51 -0
- package/skills/python-sqlalchemy/references/models.md +67 -0
- package/skills/python-sqlalchemy/references/queries.md +57 -0
- package/skills/python-temporal/SKILL.md +128 -0
- package/skills/python-temporal/references/patterns.md +87 -0
- package/skills/python-temporal/references/testing.md +54 -0
- package/skills/python-testing/SKILL.md +161 -0
- package/skills/python-testing/references/boundaries.md +449 -0
- package/skills/python-testing/references/mocking.md +449 -0
- package/skills/python-testing/references/pytest.md +524 -0
- package/skills/spec-brainstorm/SKILL.md +325 -0
- package/skills/spec-finish/SKILL.md +159 -0
- package/skills/spec-implement/SKILL.md +296 -0
- package/skills/spec-orchestrator/SKILL.md +156 -0
- package/skills/spec-plan/SKILL.md +248 -0
- package/skills/typescript-api-design/SKILL.md +396 -0
- package/skills/typescript-api-design/references/error-responses.md +469 -0
- package/skills/typescript-build-tools/SKILL.md +447 -0
- package/skills/typescript-build-tools/references/biome.md +436 -0
- package/skills/typescript-build-tools/references/bun.md +432 -0
- package/skills/typescript-build-tools/references/turborepo.md +426 -0
- package/skills/typescript-build-tools/references/vitest.md +518 -0
- package/skills/typescript-drizzle-orm/SKILL.md +398 -0
- package/skills/typescript-drizzle-orm/references/cloudflare.md +392 -0
- package/skills/typescript-drizzle-orm/references/entity-pattern.md +412 -0
- package/skills/typescript-drizzle-orm/references/postgresql.md +191 -0
- package/skills/typescript-drizzle-orm/references/repository-pattern.md +479 -0
- package/skills/typescript-drizzle-orm/references/sqlite.md +291 -0
- package/skills/typescript-dynamodb-toolbox/SKILL.md +370 -0
- package/skills/typescript-dynamodb-toolbox/references/entity-layer.md +574 -0
- package/skills/typescript-dynamodb-toolbox/references/error-handling.md +495 -0
- package/skills/typescript-dynamodb-toolbox/references/modeling.md +332 -0
- package/skills/typescript-dynamodb-toolbox/references/repository-layer.md +631 -0
- package/skills/typescript-dynamodb-toolbox/references/testing.md +718 -0
- package/skills/typescript-dynamodb-toolbox/references/transactions.md +423 -0
- package/skills/typescript-effect-ts/SKILL.md +341 -0
- package/skills/typescript-effect-ts/references/core.md +412 -0
- package/skills/typescript-effect-ts/references/error-handling.md +468 -0
- package/skills/typescript-effect-ts/references/resources.md +476 -0
- package/skills/typescript-effect-ts/references/schema.md +536 -0
- package/skills/typescript-effect-ts/references/services.md +461 -0
- package/skills/typescript-fastify/SKILL.md +405 -0
- package/skills/typescript-fastify/references/plugins.md +329 -0
- package/skills/typescript-fastify/references/typeid.md +315 -0
- package/skills/typescript-functional-patterns/SKILL.md +402 -0
- package/skills/typescript-functional-patterns/references/adts.md +497 -0
- package/skills/typescript-functional-patterns/references/branded-types.md +542 -0
- package/skills/typescript-functional-patterns/references/migration-guide.md +695 -0
- package/skills/typescript-functional-patterns/references/option-result.md +585 -0
- package/skills/typescript-testing/SKILL.md +353 -0
- package/skills/typescript-testing/references/mocking.md +675 -0
- package/skills/typescript-testing/references/msw.md +852 -0
- package/skills/typescript-testing/references/snapshot-testing.md +774 -0
|
@@ -0,0 +1,402 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: typescript-functional-patterns
|
|
3
|
+
description: Functional programming patterns for reliable TypeScript. Use when modeling state machines, discriminated unions, Result/Option types, branded types, or building type-safe domain models.
|
|
4
|
+
user-invocable: false
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Functional Patterns for Reliable TypeScript
|
|
8
|
+
|
|
9
|
+
Build reliable systems using Algebraic Data Types (ADTs), discriminated unions, Result/Option types, and branded types. These patterns enable the compiler to prove correctness, prevent runtime errors, and make illegal states unrepresentable.
|
|
10
|
+
|
|
11
|
+
## Why Functional Patterns?
|
|
12
|
+
|
|
13
|
+
**Reliability through types**: Use the type system to encode business rules, making invalid states impossible to construct. The compiler becomes your safety net, catching errors at build time rather than runtime.
|
|
14
|
+
|
|
15
|
+
**Key benefits:**
|
|
16
|
+
- Exhaustiveness checking prevents missing cases
|
|
17
|
+
- Impossible states become unrepresentable
|
|
18
|
+
- Business logic encoded in types, not runtime checks
|
|
19
|
+
- Refactoring becomes safe and mechanical
|
|
20
|
+
- Self-documenting code through types
|
|
21
|
+
|
|
22
|
+
## Quick Reference
|
|
23
|
+
|
|
24
|
+
For detailed patterns and examples, see:
|
|
25
|
+
- [ADTs (Algebraic Data Types)](./references/adts.md) - Sum types, product types, discriminated unions
|
|
26
|
+
- [Option & Result](./references/option-result.md) - Type-safe error handling and nullable values
|
|
27
|
+
- [Branded Types](./references/branded-types.md) - Smart constructors and nominal typing
|
|
28
|
+
- [Migration Guide](./references/migration-guide.md) - Step-by-step adoption playbook
|
|
29
|
+
|
|
30
|
+
## Core Patterns Overview
|
|
31
|
+
|
|
32
|
+
### 1. Discriminated Unions (Sum Types)
|
|
33
|
+
|
|
34
|
+
Model "one of several variants" with exhaustive pattern matching:
|
|
35
|
+
|
|
36
|
+
```typescript
|
|
37
|
+
type PaymentMethod =
|
|
38
|
+
| { kind: "card"; last4: string; brand: string }
|
|
39
|
+
| { kind: "ach"; accountNumber: string; routingNumber: string }
|
|
40
|
+
| { kind: "wallet"; provider: "apple" | "google" }
|
|
41
|
+
|
|
42
|
+
function processPayment(method: PaymentMethod): void {
|
|
43
|
+
switch (method.kind) {
|
|
44
|
+
case "card":
|
|
45
|
+
// TypeScript knows: method.last4 and method.brand exist
|
|
46
|
+
return processCard(method.last4, method.brand)
|
|
47
|
+
case "ach":
|
|
48
|
+
// TypeScript knows: method.accountNumber and method.routingNumber exist
|
|
49
|
+
return processACH(method.accountNumber, method.routingNumber)
|
|
50
|
+
case "wallet":
|
|
51
|
+
// TypeScript knows: method.provider exists
|
|
52
|
+
return processWallet(method.provider)
|
|
53
|
+
default:
|
|
54
|
+
assertNever(method) // Compiler error if cases missing
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
### 2. Option Type (Nullable Values)
|
|
60
|
+
|
|
61
|
+
Explicit handling of "value may be absent":
|
|
62
|
+
|
|
63
|
+
```typescript
|
|
64
|
+
type Option<T> = { _tag: "None" } | { _tag: "Some"; value: T }
|
|
65
|
+
|
|
66
|
+
function findUser(id: string): Option<User> {
|
|
67
|
+
const user = database.get(id)
|
|
68
|
+
return user ? Some(user) : None
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const result = findUser("123")
|
|
72
|
+
switch (result._tag) {
|
|
73
|
+
case "Some":
|
|
74
|
+
console.log(result.value.name) // Type-safe access
|
|
75
|
+
break
|
|
76
|
+
case "None":
|
|
77
|
+
console.log("User not found")
|
|
78
|
+
break
|
|
79
|
+
}
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
### 3. Result Type (Error Handling)
|
|
83
|
+
|
|
84
|
+
Explicit error handling without exceptions:
|
|
85
|
+
|
|
86
|
+
```typescript
|
|
87
|
+
type Result<T, E> = { _tag: "Ok"; value: T } | { _tag: "Err"; error: E }
|
|
88
|
+
|
|
89
|
+
function parseConfig(raw: string): Result<Config, ParseError> {
|
|
90
|
+
try {
|
|
91
|
+
const data = JSON.parse(raw)
|
|
92
|
+
return Ok(validateConfig(data))
|
|
93
|
+
} catch (e) {
|
|
94
|
+
return Err({ message: "Invalid JSON", cause: e })
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const result = parseConfig(rawConfig)
|
|
99
|
+
switch (result._tag) {
|
|
100
|
+
case "Ok":
|
|
101
|
+
startServer(result.value)
|
|
102
|
+
break
|
|
103
|
+
case "Err":
|
|
104
|
+
logger.error(result.error.message)
|
|
105
|
+
break
|
|
106
|
+
}
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
### 4. Branded Types (Type-Safe Units)
|
|
110
|
+
|
|
111
|
+
Prevent unit confusion and invalid values:
|
|
112
|
+
|
|
113
|
+
```typescript
|
|
114
|
+
type Brand<K, T> = K & { __brand: T }
|
|
115
|
+
type Cents = Brand<number, "Cents">
|
|
116
|
+
type Dollars = Brand<number, "Dollars">
|
|
117
|
+
|
|
118
|
+
const Cents = (n: number): Cents => {
|
|
119
|
+
if (!Number.isInteger(n) || n < 0) throw new Error("Invalid cents")
|
|
120
|
+
return n as Cents
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const Dollars = (n: number): Dollars => {
|
|
124
|
+
if (n < 0) throw new Error("Invalid dollars")
|
|
125
|
+
return n as Dollars
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Compiler prevents mixing units:
|
|
129
|
+
const price: Cents = Cents(100)
|
|
130
|
+
const budget: Dollars = Dollars(10)
|
|
131
|
+
const total: Cents = price + budget // Type error! Cannot mix Cents and Dollars
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
## When to Use
|
|
135
|
+
|
|
136
|
+
### Use Discriminated Unions When:
|
|
137
|
+
- Modeling state machines (pending → settled → reconciled)
|
|
138
|
+
- Representing mutually exclusive variants (payment methods, user roles)
|
|
139
|
+
- Building domain models with distinct states
|
|
140
|
+
- Replacing boolean flags with explicit states
|
|
141
|
+
|
|
142
|
+
### Use Option When:
|
|
143
|
+
- Value may be absent (but absence is expected/valid)
|
|
144
|
+
- Replacing `null` or `undefined` checks
|
|
145
|
+
- Chaining operations that may fail to find values
|
|
146
|
+
- Making nullability explicit in APIs
|
|
147
|
+
|
|
148
|
+
### Use Result When:
|
|
149
|
+
- Operation may fail with recoverable errors
|
|
150
|
+
- You need to propagate error context
|
|
151
|
+
- Replacing try/catch for expected failures
|
|
152
|
+
- Building error handling into function signatures
|
|
153
|
+
|
|
154
|
+
### Use Branded Types When:
|
|
155
|
+
- Preventing unit confusion (cents vs dollars, ms vs seconds)
|
|
156
|
+
- Enforcing validation invariants (email format, positive numbers)
|
|
157
|
+
- Creating type-safe IDs (UserId vs OrderId)
|
|
158
|
+
- Domain-driven design with value objects
|
|
159
|
+
|
|
160
|
+
## Quick Start - Paste-Ready Helpers
|
|
161
|
+
|
|
162
|
+
Copy these into your project to start using functional patterns:
|
|
163
|
+
|
|
164
|
+
```typescript
|
|
165
|
+
// ============================================
|
|
166
|
+
// Option Type
|
|
167
|
+
// ============================================
|
|
168
|
+
type None = { _tag: "None" }
|
|
169
|
+
type Some<T> = { _tag: "Some"; value: T }
|
|
170
|
+
type Option<T> = None | Some<T>
|
|
171
|
+
|
|
172
|
+
const None: None = { _tag: "None" }
|
|
173
|
+
const Some = <T>(value: T): Option<T> => ({ _tag: "Some", value })
|
|
174
|
+
|
|
175
|
+
// Utilities
|
|
176
|
+
const isNone = <T>(opt: Option<T>): opt is None => opt._tag === "None"
|
|
177
|
+
const isSome = <T>(opt: Option<T>): opt is Some<T> => opt._tag === "Some"
|
|
178
|
+
|
|
179
|
+
const getOrElse = <T>(opt: Option<T>, defaultValue: T): T =>
|
|
180
|
+
opt._tag === "Some" ? opt.value : defaultValue
|
|
181
|
+
|
|
182
|
+
const map = <T, U>(opt: Option<T>, fn: (value: T) => U): Option<U> =>
|
|
183
|
+
opt._tag === "Some" ? Some(fn(opt.value)) : None
|
|
184
|
+
|
|
185
|
+
const flatMap = <T, U>(opt: Option<T>, fn: (value: T) => Option<U>): Option<U> =>
|
|
186
|
+
opt._tag === "Some" ? fn(opt.value) : None
|
|
187
|
+
|
|
188
|
+
// ============================================
|
|
189
|
+
// Result Type
|
|
190
|
+
// ============================================
|
|
191
|
+
type Ok<T> = { _tag: "Ok"; value: T }
|
|
192
|
+
type Err<E> = { _tag: "Err"; error: E }
|
|
193
|
+
type Result<T, E> = Ok<T> | Err<E>
|
|
194
|
+
|
|
195
|
+
const Ok = <T>(value: T): Result<T, never> => ({ _tag: "Ok", value })
|
|
196
|
+
const Err = <E>(error: E): Result<never, E> => ({ _tag: "Err", error })
|
|
197
|
+
|
|
198
|
+
// Utilities
|
|
199
|
+
const isOk = <T, E>(result: Result<T, E>): result is Ok<T> => result._tag === "Ok"
|
|
200
|
+
const isErr = <T, E>(result: Result<T, E>): result is Err<E> => result._tag === "Err"
|
|
201
|
+
|
|
202
|
+
const mapResult = <T, U, E>(result: Result<T, E>, fn: (value: T) => U): Result<U, E> =>
|
|
203
|
+
result._tag === "Ok" ? Ok(fn(result.value)) : result
|
|
204
|
+
|
|
205
|
+
const flatMapResult = <T, U, E>(
|
|
206
|
+
result: Result<T, E>,
|
|
207
|
+
fn: (value: T) => Result<U, E>
|
|
208
|
+
): Result<U, E> =>
|
|
209
|
+
result._tag === "Ok" ? fn(result.value) : result
|
|
210
|
+
|
|
211
|
+
// ============================================
|
|
212
|
+
// Exhaustiveness Checking
|
|
213
|
+
// ============================================
|
|
214
|
+
const assertNever = (x: never): never => {
|
|
215
|
+
throw new Error(`Unhandled variant: ${JSON.stringify(x)}`)
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// ============================================
|
|
219
|
+
// Branded Types
|
|
220
|
+
// ============================================
|
|
221
|
+
type Brand<K, T> = K & { __brand: T }
|
|
222
|
+
|
|
223
|
+
// Example: Cents (integer cents to prevent floating point errors)
|
|
224
|
+
type Cents = Brand<number, "Cents">
|
|
225
|
+
const Cents = (n: number): Cents => {
|
|
226
|
+
if (!Number.isInteger(n)) throw new Error("Cents must be integer")
|
|
227
|
+
if (n < 0) throw new Error("Cents cannot be negative")
|
|
228
|
+
return n as Cents
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// Example: Email (validated email address)
|
|
232
|
+
type Email = Brand<string, "Email">
|
|
233
|
+
const Email = (s: string): Email => {
|
|
234
|
+
if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(s)) throw new Error("Invalid email")
|
|
235
|
+
return s as Email
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// Example: Millis (timestamp in milliseconds)
|
|
239
|
+
type Millis = Brand<number, "Millis">
|
|
240
|
+
const Millis = (n: number): Millis => {
|
|
241
|
+
if (n < 0) throw new Error("Millis cannot be negative")
|
|
242
|
+
return n as Millis
|
|
243
|
+
}
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
## Guidelines
|
|
247
|
+
|
|
248
|
+
### Pattern Matching Best Practices
|
|
249
|
+
|
|
250
|
+
1. **Always use `assertNever` in default case** for exhaustiveness checking:
|
|
251
|
+
```typescript
|
|
252
|
+
switch (variant.kind) {
|
|
253
|
+
case "a": return handleA(variant)
|
|
254
|
+
case "b": return handleB(variant)
|
|
255
|
+
default: assertNever(variant) // Compiler error if cases missing
|
|
256
|
+
}
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
2. **Use discriminant field consistently** (`kind`, `type`, `_tag`):
|
|
260
|
+
```typescript
|
|
261
|
+
// Good: consistent discriminant
|
|
262
|
+
type Result<T, E> = { _tag: "Ok"; value: T } | { _tag: "Err"; error: E }
|
|
263
|
+
|
|
264
|
+
// Avoid: mixing discriminants
|
|
265
|
+
type Bad = { kind: "a" } | { type: "b" } // Inconsistent!
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
3. **Narrow types early** to unlock type safety:
|
|
269
|
+
```typescript
|
|
270
|
+
if (result._tag === "Ok") {
|
|
271
|
+
// TypeScript knows: result.value exists
|
|
272
|
+
return result.value.data
|
|
273
|
+
}
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
### Error Handling Strategy
|
|
277
|
+
|
|
278
|
+
1. **Use Option for expected absence**:
|
|
279
|
+
```typescript
|
|
280
|
+
function findUser(id: string): Option<User>
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
2. **Use Result for recoverable errors**:
|
|
284
|
+
```typescript
|
|
285
|
+
function parseConfig(raw: string): Result<Config, ParseError>
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
3. **Use exceptions for programmer errors**:
|
|
289
|
+
```typescript
|
|
290
|
+
function unreachable(message: string): never {
|
|
291
|
+
throw new Error(`Unreachable: ${message}`)
|
|
292
|
+
}
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
### Branded Types Guidelines
|
|
296
|
+
|
|
297
|
+
1. **Validate in smart constructor**:
|
|
298
|
+
```typescript
|
|
299
|
+
const PositiveInt = (n: number): PositiveInt => {
|
|
300
|
+
if (!Number.isInteger(n) || n <= 0) throw new Error("Must be positive integer")
|
|
301
|
+
return n as PositiveInt
|
|
302
|
+
}
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
2. **Use branded types for domain concepts**:
|
|
306
|
+
```typescript
|
|
307
|
+
type UserId = Brand<string, "UserId">
|
|
308
|
+
type OrderId = Brand<string, "OrderId">
|
|
309
|
+
// Compiler prevents: const userId: UserId = orderId
|
|
310
|
+
```
|
|
311
|
+
|
|
312
|
+
3. **Prevent unit confusion**:
|
|
313
|
+
```typescript
|
|
314
|
+
type Seconds = Brand<number, "Seconds">
|
|
315
|
+
type Millis = Brand<number, "Millis">
|
|
316
|
+
// Compiler prevents: const s: Seconds = millis
|
|
317
|
+
```
|
|
318
|
+
|
|
319
|
+
### Migration Strategy
|
|
320
|
+
|
|
321
|
+
Start small and expand:
|
|
322
|
+
1. New features: Use functional patterns from day one
|
|
323
|
+
2. Bug fixes: Refactor to discriminated unions when touching code
|
|
324
|
+
3. High-risk areas: Prioritize financial calculations, state machines
|
|
325
|
+
4. Team adoption: Share paste-ready helpers, pair on first implementations
|
|
326
|
+
|
|
327
|
+
Enable TypeScript strict mode flags:
|
|
328
|
+
- `strictNullChecks: true` - Make nullability explicit
|
|
329
|
+
- `noImplicitReturns: true` - Ensure all code paths return
|
|
330
|
+
- `strictFunctionTypes: true` - Safer function signatures
|
|
331
|
+
|
|
332
|
+
## Examples by Domain
|
|
333
|
+
|
|
334
|
+
### State Machine (Transaction Lifecycle)
|
|
335
|
+
```typescript
|
|
336
|
+
type TxnState =
|
|
337
|
+
| { kind: "pending"; createdAt: Millis }
|
|
338
|
+
| { kind: "settled"; ledgerId: string; settledAt: Millis }
|
|
339
|
+
| { kind: "failed"; reason: FailureReason; failedAt: Millis }
|
|
340
|
+
| { kind: "reversed"; originalLedgerId: string; reversedAt: Millis }
|
|
341
|
+
|
|
342
|
+
function canReverse(state: TxnState): boolean {
|
|
343
|
+
switch (state.kind) {
|
|
344
|
+
case "pending": return false
|
|
345
|
+
case "settled": return true
|
|
346
|
+
case "failed": return false
|
|
347
|
+
case "reversed": return false
|
|
348
|
+
default: assertNever(state)
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
```
|
|
352
|
+
|
|
353
|
+
### Configuration Parsing
|
|
354
|
+
```typescript
|
|
355
|
+
type ConfigError = { field: string; message: string }
|
|
356
|
+
|
|
357
|
+
function parsePort(raw: unknown): Result<number, ConfigError> {
|
|
358
|
+
if (typeof raw !== "number") {
|
|
359
|
+
return Err({ field: "port", message: "must be number" })
|
|
360
|
+
}
|
|
361
|
+
if (raw < 1 || raw > 65535) {
|
|
362
|
+
return Err({ field: "port", message: "must be 1-65535" })
|
|
363
|
+
}
|
|
364
|
+
return Ok(raw)
|
|
365
|
+
}
|
|
366
|
+
```
|
|
367
|
+
|
|
368
|
+
### Financial Calculations
|
|
369
|
+
```typescript
|
|
370
|
+
type Cents = Brand<number, "Cents">
|
|
371
|
+
|
|
372
|
+
function addCents(a: Cents, b: Cents): Cents {
|
|
373
|
+
return Cents(a + b) // Smart constructor validates result
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
function calculateFee(amount: Cents, bps: number): Cents {
|
|
377
|
+
const feeAmount = Math.round((amount * bps) / 10000)
|
|
378
|
+
return Cents(feeAmount)
|
|
379
|
+
}
|
|
380
|
+
```
|
|
381
|
+
|
|
382
|
+
## Further Reading
|
|
383
|
+
|
|
384
|
+
- [ADT Reference](./references/adts.md) - Deep dive on sum types, product types, and pattern matching
|
|
385
|
+
- [Option & Result Reference](./references/option-result.md) - Comprehensive error handling patterns
|
|
386
|
+
- [Branded Types Reference](./references/branded-types.md) - Advanced nominal typing techniques
|
|
387
|
+
- [Migration Guide](./references/migration-guide.md) - Step-by-step adoption playbook
|
|
388
|
+
|
|
389
|
+
## Credits
|
|
390
|
+
|
|
391
|
+
These patterns are inspired by **[Why Reliability Demands Functional Programming, ADTs, Safety and Critical Infrastructure](https://rastrian.com/why-reliability-demands-functional-programming-adts-safety-and-critical-infrastructure/)** by Rastrian. The blog post explores how functional programming techniques and Algebraic Data Types enable building reliable systems in critical infrastructure contexts.
|
|
392
|
+
|
|
393
|
+
## When This Skill Loads
|
|
394
|
+
|
|
395
|
+
This skill automatically loads when discussing:
|
|
396
|
+
- Discriminated unions and sum types
|
|
397
|
+
- State machine modeling
|
|
398
|
+
- Result/Option types and error handling
|
|
399
|
+
- Branded types and smart constructors
|
|
400
|
+
- Type-safe domain models
|
|
401
|
+
- Making illegal states unrepresentable
|
|
402
|
+
- Functional programming in TypeScript
|