@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.
Files changed (130) hide show
  1. package/CHANGELOG.md +38 -0
  2. package/LICENSE +21 -0
  3. package/README.md +285 -0
  4. package/agents/architect.md +38 -0
  5. package/agents/oracle.md +40 -0
  6. package/agents/recon.md +39 -0
  7. package/dist/atelier.js +33082 -0
  8. package/package.json +44 -0
  9. package/skills/code-conventional-commit/SKILL.md +134 -0
  10. package/skills/code-conventional-commit/evals/evals.json +23 -0
  11. package/skills/code-conventional-commit/workspace/iteration-1/eval-0/with_skill/outputs/commit-message.txt +4 -0
  12. package/skills/code-conventional-commit/workspace/iteration-1/eval-0/with_skill/outputs/eval_metadata.json +6 -0
  13. package/skills/code-conventional-commit/workspace/iteration-1/eval-1/with_skill/outputs/eval_metadata.json +6 -0
  14. package/skills/code-conventional-commit/workspace/iteration-1/eval-1/with_skill/outputs/validation-result.txt +16 -0
  15. package/skills/code-conventional-commit/workspace/iteration-1/eval-2/with_skill/outputs/commit-message.txt +8 -0
  16. package/skills/code-conventional-commit/workspace/iteration-1/eval-2/with_skill/outputs/eval_metadata.json +6 -0
  17. package/skills/code-conventional-commit/workspace/iteration-1/report.html +1325 -0
  18. package/skills/code-debug/SKILL.md +271 -0
  19. package/skills/code-debug/references/language-tools.md +139 -0
  20. package/skills/code-debug/references/techniques.md +117 -0
  21. package/skills/code-docs/SKILL.md +171 -0
  22. package/skills/code-docs/references/api-docs.md +116 -0
  23. package/skills/code-docs/references/changelog.md +64 -0
  24. package/skills/code-docs/references/readme-structure.md +94 -0
  25. package/skills/code-git-worktrees/SKILL.md +218 -0
  26. package/skills/code-perf/SKILL.md +163 -0
  27. package/skills/code-perf/references/common-bottlenecks.md +124 -0
  28. package/skills/code-perf/references/optimization-patterns.md +167 -0
  29. package/skills/code-perf/references/profiling-tools.md +126 -0
  30. package/skills/code-review/SKILL.md +78 -0
  31. package/skills/code-review/references/output.md +200 -0
  32. package/skills/code-review/references/reviewers.md +719 -0
  33. package/skills/code-review/references/rq.md +357 -0
  34. package/skills/code-review/references/rs.md +30 -0
  35. package/skills/code-security/SKILL.md +183 -0
  36. package/skills/code-security/references/owasp-top-10.md +129 -0
  37. package/skills/code-security/references/security-tools.md +102 -0
  38. package/skills/code-security/references/vulnerability-patterns.md +129 -0
  39. package/skills/code-stacked-commit/SKILL.md +155 -0
  40. package/skills/code-subagents/SKILL.md +184 -0
  41. package/skills/code-subagents/references/code-quality-reviewer-prompt.md +48 -0
  42. package/skills/code-subagents/references/implementor-prompt.md +79 -0
  43. package/skills/code-subagents/references/spec-reviewer-prompt.md +62 -0
  44. package/skills/oracle-architect/SKILL.md +246 -0
  45. package/skills/oracle-architect/references/api-design.md +345 -0
  46. package/skills/oracle-architect/references/data-modeling.md +318 -0
  47. package/skills/oracle-architect/references/ddd-patterns.md +247 -0
  48. package/skills/oracle-challenge/SKILL.md +126 -0
  49. package/skills/oracle-testing/SKILL.md +237 -0
  50. package/skills/oracle-testing/references/anti-patterns.md +618 -0
  51. package/skills/oracle-testing/references/boundaries.md +579 -0
  52. package/skills/oracle-testing/references/core-testing.md +691 -0
  53. package/skills/oracle-testing/references/edge-testing.md +794 -0
  54. package/skills/oracle-testing/references/stub-driven-tdd.md +322 -0
  55. package/skills/oracle-thinkdeep/SKILL.md +164 -0
  56. package/skills/python-architecture/SKILL.md +148 -0
  57. package/skills/python-architecture/references/data-modeling.md +558 -0
  58. package/skills/python-architecture/references/ddd.md +569 -0
  59. package/skills/python-architecture/references/functional-core.md +337 -0
  60. package/skills/python-build-tools/SKILL.md +153 -0
  61. package/skills/python-build-tools/references/basedpyright.md +321 -0
  62. package/skills/python-build-tools/references/ruff.md +225 -0
  63. package/skills/python-build-tools/references/uv.md +257 -0
  64. package/skills/python-fastapi/SKILL.md +184 -0
  65. package/skills/python-fastapi/references/api-design.md +563 -0
  66. package/skills/python-fastapi/references/dependencies.md +479 -0
  67. package/skills/python-fastapi/references/middleware.md +475 -0
  68. package/skills/python-fastapi/references/validation.md +497 -0
  69. package/skills/python-modern-python/SKILL.md +201 -0
  70. package/skills/python-modern-python/references/async.md +76 -0
  71. package/skills/python-modern-python/references/pattern-matching.md +89 -0
  72. package/skills/python-modern-python/references/typing.md +96 -0
  73. package/skills/python-monorepo/SKILL.md +146 -0
  74. package/skills/python-monorepo/references/docker.md +515 -0
  75. package/skills/python-monorepo/references/namespace-packages.md +426 -0
  76. package/skills/python-monorepo/references/workspace-config.md +341 -0
  77. package/skills/python-sqlalchemy/SKILL.md +119 -0
  78. package/skills/python-sqlalchemy/references/async.md +51 -0
  79. package/skills/python-sqlalchemy/references/models.md +67 -0
  80. package/skills/python-sqlalchemy/references/queries.md +57 -0
  81. package/skills/python-temporal/SKILL.md +128 -0
  82. package/skills/python-temporal/references/patterns.md +87 -0
  83. package/skills/python-temporal/references/testing.md +54 -0
  84. package/skills/python-testing/SKILL.md +161 -0
  85. package/skills/python-testing/references/boundaries.md +449 -0
  86. package/skills/python-testing/references/mocking.md +449 -0
  87. package/skills/python-testing/references/pytest.md +524 -0
  88. package/skills/spec-brainstorm/SKILL.md +325 -0
  89. package/skills/spec-finish/SKILL.md +159 -0
  90. package/skills/spec-implement/SKILL.md +296 -0
  91. package/skills/spec-orchestrator/SKILL.md +156 -0
  92. package/skills/spec-plan/SKILL.md +248 -0
  93. package/skills/typescript-api-design/SKILL.md +396 -0
  94. package/skills/typescript-api-design/references/error-responses.md +469 -0
  95. package/skills/typescript-build-tools/SKILL.md +447 -0
  96. package/skills/typescript-build-tools/references/biome.md +436 -0
  97. package/skills/typescript-build-tools/references/bun.md +432 -0
  98. package/skills/typescript-build-tools/references/turborepo.md +426 -0
  99. package/skills/typescript-build-tools/references/vitest.md +518 -0
  100. package/skills/typescript-drizzle-orm/SKILL.md +398 -0
  101. package/skills/typescript-drizzle-orm/references/cloudflare.md +392 -0
  102. package/skills/typescript-drizzle-orm/references/entity-pattern.md +412 -0
  103. package/skills/typescript-drizzle-orm/references/postgresql.md +191 -0
  104. package/skills/typescript-drizzle-orm/references/repository-pattern.md +479 -0
  105. package/skills/typescript-drizzle-orm/references/sqlite.md +291 -0
  106. package/skills/typescript-dynamodb-toolbox/SKILL.md +370 -0
  107. package/skills/typescript-dynamodb-toolbox/references/entity-layer.md +574 -0
  108. package/skills/typescript-dynamodb-toolbox/references/error-handling.md +495 -0
  109. package/skills/typescript-dynamodb-toolbox/references/modeling.md +332 -0
  110. package/skills/typescript-dynamodb-toolbox/references/repository-layer.md +631 -0
  111. package/skills/typescript-dynamodb-toolbox/references/testing.md +718 -0
  112. package/skills/typescript-dynamodb-toolbox/references/transactions.md +423 -0
  113. package/skills/typescript-effect-ts/SKILL.md +341 -0
  114. package/skills/typescript-effect-ts/references/core.md +412 -0
  115. package/skills/typescript-effect-ts/references/error-handling.md +468 -0
  116. package/skills/typescript-effect-ts/references/resources.md +476 -0
  117. package/skills/typescript-effect-ts/references/schema.md +536 -0
  118. package/skills/typescript-effect-ts/references/services.md +461 -0
  119. package/skills/typescript-fastify/SKILL.md +405 -0
  120. package/skills/typescript-fastify/references/plugins.md +329 -0
  121. package/skills/typescript-fastify/references/typeid.md +315 -0
  122. package/skills/typescript-functional-patterns/SKILL.md +402 -0
  123. package/skills/typescript-functional-patterns/references/adts.md +497 -0
  124. package/skills/typescript-functional-patterns/references/branded-types.md +542 -0
  125. package/skills/typescript-functional-patterns/references/migration-guide.md +695 -0
  126. package/skills/typescript-functional-patterns/references/option-result.md +585 -0
  127. package/skills/typescript-testing/SKILL.md +353 -0
  128. package/skills/typescript-testing/references/mocking.md +675 -0
  129. package/skills/typescript-testing/references/msw.md +852 -0
  130. package/skills/typescript-testing/references/snapshot-testing.md +774 -0
@@ -0,0 +1,542 @@
1
+ # Branded Types and Smart Constructors
2
+
3
+ Branded types (also called nominal types or opaque types) prevent mixing semantically different values that share the same primitive type. They use smart constructors to enforce invariants at creation time.
4
+
5
+ ## The Problem: Primitive Obsession
6
+
7
+ TypeScript uses structural typing - two types with the same structure are compatible:
8
+
9
+ ```typescript
10
+ type UserId = string
11
+ type OrderId = string
12
+
13
+ function getUser(id: UserId): User { /* ... */ }
14
+
15
+ const orderId: OrderId = "ord_123"
16
+ getUser(orderId) // Compiles! But logically wrong
17
+ ```
18
+
19
+ The compiler allows passing an `OrderId` where a `UserId` is expected because both are `string`.
20
+
21
+ **Common issues:**
22
+ - Mixing units (cents vs dollars, seconds vs milliseconds)
23
+ - Mixing IDs (UserId vs OrderId vs ProductId)
24
+ - Using unvalidated values (raw email string vs validated Email)
25
+
26
+ ## The Brand Pattern
27
+
28
+ Add a phantom brand property to create nominal types:
29
+
30
+ ```typescript
31
+ type Brand<K, T> = K & { __brand: T }
32
+
33
+ type UserId = Brand<string, "UserId">
34
+ type OrderId = Brand<string, "OrderId">
35
+
36
+ function getUser(id: UserId): User { /* ... */ }
37
+
38
+ const orderId: OrderId = "ord_123" as OrderId
39
+ getUser(orderId) // Type error! OrderId is not assignable to UserId
40
+ ```
41
+
42
+ The `__brand` property exists only at compile time. At runtime, branded types are just their base types.
43
+
44
+ ## Smart Constructors
45
+
46
+ Smart constructors enforce invariants and create branded values:
47
+
48
+ ```typescript
49
+ type Email = Brand<string, "Email">
50
+
51
+ const Email = (s: string): Email => {
52
+ if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(s)) {
53
+ throw new Error("Invalid email format")
54
+ }
55
+ return s as Email
56
+ }
57
+
58
+ // Usage
59
+ const email = Email("alice@example.com") // OK
60
+ const invalid = Email("not-an-email") // Throws error
61
+ ```
62
+
63
+ **Benefits:**
64
+ - Validation happens once at construction
65
+ - Invalid values can't be created
66
+ - Type system proves all Email values are valid
67
+
68
+ ## Common Branded Type Patterns
69
+
70
+ ### Financial Units (Prevent Floating Point Errors)
71
+
72
+ ```typescript
73
+ type Cents = Brand<number, "Cents">
74
+ type Dollars = Brand<number, "Dollars">
75
+
76
+ const Cents = (n: number): Cents => {
77
+ if (!Number.isInteger(n)) {
78
+ throw new Error("Cents must be integer")
79
+ }
80
+ if (n < 0) {
81
+ throw new Error("Cents cannot be negative")
82
+ }
83
+ return n as Cents
84
+ }
85
+
86
+ const Dollars = (n: number): Dollars => {
87
+ if (n < 0) {
88
+ throw new Error("Dollars cannot be negative")
89
+ }
90
+ return n as Dollars
91
+ }
92
+
93
+ // Convert between units
94
+ const dollarsFromCents = (cents: Cents): Dollars => {
95
+ return Dollars(cents / 100)
96
+ }
97
+
98
+ const centsFromDollars = (dollars: Dollars): Cents => {
99
+ return Cents(Math.round(dollars * 100))
100
+ }
101
+
102
+ // Type-safe arithmetic
103
+ const addCents = (a: Cents, b: Cents): Cents => Cents(a + b)
104
+ const subtractCents = (a: Cents, b: Cents): Cents => Cents(a - b)
105
+
106
+ // Usage
107
+ const price = Cents(1000) // $10.00
108
+ const tax = Cents(100) // $1.00
109
+ const total = addCents(price, tax) // Cents(1100)
110
+
111
+ const inDollars = dollarsFromCents(total) // Dollars(11)
112
+
113
+ // This won't compile:
114
+ const wrong = Cents(10) + Dollars(5) // Type error!
115
+ ```
116
+
117
+ ### Time Units (Prevent Unit Confusion)
118
+
119
+ ```typescript
120
+ type Millis = Brand<number, "Millis">
121
+ type Seconds = Brand<number, "Seconds">
122
+
123
+ const Millis = (n: number): Millis => {
124
+ if (n < 0) throw new Error("Millis cannot be negative")
125
+ return n as Millis
126
+ }
127
+
128
+ const Seconds = (n: number): Seconds => {
129
+ if (n < 0) throw new Error("Seconds cannot be negative")
130
+ return n as Seconds
131
+ }
132
+
133
+ const secondsFromMillis = (ms: Millis): Seconds => Seconds(ms / 1000)
134
+ const millisFromSeconds = (s: Seconds): Millis => Millis(s * 1000)
135
+
136
+ // Usage
137
+ const timeout = Millis(5000)
138
+ const wait = Seconds(5)
139
+
140
+ setTimeout(() => console.log("Done"), timeout) // OK
141
+ setTimeout(() => console.log("Done"), wait) // Type error!
142
+ ```
143
+
144
+ ### Validated IDs
145
+
146
+ ```typescript
147
+ type UserId = Brand<string, "UserId">
148
+ type OrderId = Brand<string, "OrderId">
149
+ type ProductId = Brand<string, "ProductId">
150
+
151
+ const UserId = (s: string): UserId => {
152
+ if (!/^usr_[a-zA-Z0-9]+$/.test(s)) {
153
+ throw new Error("Invalid UserId format")
154
+ }
155
+ return s as UserId
156
+ }
157
+
158
+ const OrderId = (s: string): OrderId => {
159
+ if (!/^ord_[a-zA-Z0-9]+$/.test(s)) {
160
+ throw new Error("Invalid OrderId format")
161
+ }
162
+ return s as OrderId
163
+ }
164
+
165
+ // Functions are type-safe
166
+ function getUser(id: UserId): User { /* ... */ }
167
+ function getOrder(id: OrderId): Order { /* ... */ }
168
+
169
+ const userId = UserId("usr_123")
170
+ const orderId = OrderId("ord_456")
171
+
172
+ getUser(userId) // OK
173
+ getUser(orderId) // Type error! OrderId is not UserId
174
+ ```
175
+
176
+ ### Email Addresses
177
+
178
+ ```typescript
179
+ type Email = Brand<string, "Email">
180
+
181
+ const Email = (s: string): Email => {
182
+ const trimmed = s.trim().toLowerCase()
183
+ if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(trimmed)) {
184
+ throw new Error("Invalid email format")
185
+ }
186
+ return trimmed as Email
187
+ }
188
+
189
+ // Email is always validated and normalized
190
+ function sendEmail(to: Email, subject: string, body: string): void {
191
+ // No need to validate 'to' - type guarantees it's valid
192
+ }
193
+
194
+ const email = Email("Alice@Example.com") // Normalized to "alice@example.com"
195
+ sendEmail(email, "Hello", "World")
196
+ ```
197
+
198
+ ### Non-Empty Strings
199
+
200
+ ```typescript
201
+ type NonEmptyString = Brand<string, "NonEmptyString">
202
+
203
+ const NonEmptyString = (s: string): NonEmptyString => {
204
+ if (s.length === 0) {
205
+ throw new Error("String cannot be empty")
206
+ }
207
+ return s as NonEmptyString
208
+ }
209
+
210
+ type User = {
211
+ id: UserId
212
+ name: NonEmptyString // Guaranteed never empty
213
+ email: Email // Guaranteed valid format
214
+ }
215
+ ```
216
+
217
+ ### Positive Numbers
218
+
219
+ ```typescript
220
+ type PositiveInt = Brand<number, "PositiveInt">
221
+ type PositiveFloat = Brand<number, "PositiveFloat">
222
+
223
+ const PositiveInt = (n: number): PositiveInt => {
224
+ if (!Number.isInteger(n) || n <= 0) {
225
+ throw new Error("Must be positive integer")
226
+ }
227
+ return n as PositiveInt
228
+ }
229
+
230
+ const PositiveFloat = (n: number): PositiveFloat => {
231
+ if (n <= 0) {
232
+ throw new Error("Must be positive number")
233
+ }
234
+ return n as PositiveFloat
235
+ }
236
+
237
+ // Usage: Quantity must be positive integer
238
+ type CartItem = {
239
+ productId: ProductId
240
+ quantity: PositiveInt
241
+ price: Cents
242
+ }
243
+ ```
244
+
245
+ ### URL and URI
246
+
247
+ ```typescript
248
+ type Url = Brand<string, "Url">
249
+ type Uri = Brand<string, "Uri">
250
+
251
+ const Url = (s: string): Url => {
252
+ try {
253
+ new URL(s) // Validates URL format
254
+ return s as Url
255
+ } catch {
256
+ throw new Error("Invalid URL")
257
+ }
258
+ }
259
+
260
+ const Uri = (s: string): Uri => {
261
+ if (!/^[a-zA-Z][a-zA-Z0-9+.-]*:/.test(s)) {
262
+ throw new Error("Invalid URI format")
263
+ }
264
+ return s as Uri
265
+ }
266
+
267
+ function fetchData(url: Url): Promise<Response> {
268
+ // Guaranteed 'url' is valid URL
269
+ return fetch(url)
270
+ }
271
+ ```
272
+
273
+ ## Result-Returning Smart Constructors
274
+
275
+ For user input validation, return `Result` instead of throwing:
276
+
277
+ ```typescript
278
+ import { Result, Ok, Err } from "./result"
279
+
280
+ type Email = Brand<string, "Email">
281
+
282
+ const Email = {
283
+ // Throwing version for internal use
284
+ unsafe: (s: string): Email => {
285
+ const trimmed = s.trim().toLowerCase()
286
+ if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(trimmed)) {
287
+ throw new Error("Invalid email format")
288
+ }
289
+ return trimmed as Email
290
+ },
291
+
292
+ // Result version for external input
293
+ parse: (s: string): Result<Email, string> => {
294
+ try {
295
+ return Ok(Email.unsafe(s))
296
+ } catch (e) {
297
+ return Err("Invalid email format")
298
+ }
299
+ },
300
+ }
301
+
302
+ // Usage
303
+ const emailResult = Email.parse(userInput)
304
+ switch (emailResult._tag) {
305
+ case "Ok":
306
+ saveUser({ email: emailResult.value })
307
+ break
308
+ case "Err":
309
+ showError(emailResult.error)
310
+ break
311
+ }
312
+ ```
313
+
314
+ ## Advanced Patterns
315
+
316
+ ### Multiple Brands
317
+
318
+ Combine multiple brands for stronger guarantees:
319
+
320
+ ```typescript
321
+ type Positive = Brand<number, "Positive">
322
+ type Integer = Brand<number, "Integer">
323
+ type PositiveInt = Positive & Integer
324
+
325
+ const PositiveInt = (n: number): PositiveInt => {
326
+ if (!Number.isInteger(n)) throw new Error("Must be integer")
327
+ if (n <= 0) throw new Error("Must be positive")
328
+ return n as PositiveInt
329
+ }
330
+ ```
331
+
332
+ ### Generic Branded Collections
333
+
334
+ ```typescript
335
+ type NonEmptyArray<T> = Brand<T[], "NonEmptyArray">
336
+
337
+ const NonEmptyArray = <T>(arr: T[]): NonEmptyArray<T> => {
338
+ if (arr.length === 0) {
339
+ throw new Error("Array cannot be empty")
340
+ }
341
+ return arr as NonEmptyArray<T>
342
+ }
343
+
344
+ // Safe head/tail operations
345
+ const head = <T>(arr: NonEmptyArray<T>): T => arr[0]
346
+ const tail = <T>(arr: NonEmptyArray<T>): T[] => arr.slice(1)
347
+
348
+ const numbers = NonEmptyArray([1, 2, 3])
349
+ const first = head(numbers) // No need to check undefined
350
+ ```
351
+
352
+ ### Range-Constrained Numbers
353
+
354
+ ```typescript
355
+ type Percentage = Brand<number, "Percentage">
356
+
357
+ const Percentage = (n: number): Percentage => {
358
+ if (n < 0 || n > 100) {
359
+ throw new Error("Percentage must be 0-100")
360
+ }
361
+ return n as Percentage
362
+ }
363
+
364
+ type Port = Brand<number, "Port">
365
+
366
+ const Port = (n: number): Port => {
367
+ if (!Number.isInteger(n) || n < 1 || n > 65535) {
368
+ throw new Error("Port must be 1-65535")
369
+ }
370
+ return n as Port
371
+ }
372
+ ```
373
+
374
+ ### JSON-Safe Branded Types
375
+
376
+ ```typescript
377
+ type UserId = Brand<string, "UserId">
378
+
379
+ // Serialization
380
+ function userIdToJSON(id: UserId): string {
381
+ return id // Just a string at runtime
382
+ }
383
+
384
+ // Deserialization (with validation)
385
+ function userIdFromJSON(json: string): Result<UserId, string> {
386
+ if (!/^usr_[a-zA-Z0-9]+$/.test(json)) {
387
+ return Err("Invalid UserId format")
388
+ }
389
+ return Ok(json as UserId)
390
+ }
391
+ ```
392
+
393
+ ## Testing Branded Types
394
+
395
+ ```typescript
396
+ describe("Email", () => {
397
+ it("accepts valid email", () => {
398
+ expect(() => Email("alice@example.com")).not.toThrow()
399
+ })
400
+
401
+ it("rejects invalid email", () => {
402
+ expect(() => Email("not-an-email")).toThrow("Invalid email format")
403
+ })
404
+
405
+ it("normalizes email", () => {
406
+ const email = Email("Alice@Example.COM")
407
+ expect(email).toBe("alice@example.com")
408
+ })
409
+
410
+ it("trims whitespace", () => {
411
+ const email = Email(" alice@example.com ")
412
+ expect(email).toBe("alice@example.com")
413
+ })
414
+ })
415
+
416
+ describe("Cents", () => {
417
+ it("rejects non-integers", () => {
418
+ expect(() => Cents(10.5)).toThrow("Cents must be integer")
419
+ })
420
+
421
+ it("rejects negative values", () => {
422
+ expect(() => Cents(-100)).toThrow("Cents cannot be negative")
423
+ })
424
+
425
+ it("allows zero", () => {
426
+ expect(() => Cents(0)).not.toThrow()
427
+ })
428
+ })
429
+ ```
430
+
431
+ ## Integration with Domain Models
432
+
433
+ ```typescript
434
+ type Money = Brand<number, "Money"> // Always in cents
435
+
436
+ type User = {
437
+ id: UserId
438
+ email: Email
439
+ name: NonEmptyString
440
+ createdAt: Millis
441
+ }
442
+
443
+ type Order = {
444
+ id: OrderId
445
+ userId: UserId
446
+ items: NonEmptyArray<OrderItem>
447
+ total: Money
448
+ createdAt: Millis
449
+ }
450
+
451
+ type OrderItem = {
452
+ productId: ProductId
453
+ quantity: PositiveInt
454
+ pricePerUnit: Money
455
+ }
456
+
457
+ // All invariants enforced at type level:
458
+ // - IDs are validated and can't be mixed
459
+ // - Email is valid format
460
+ // - Name is never empty
461
+ // - Orders always have at least one item
462
+ // - Quantities are positive integers
463
+ // - Money is always in cents (no float errors)
464
+ ```
465
+
466
+ ## Best Practices
467
+
468
+ 1. **Use smart constructors for all branded types**
469
+ - Enforce invariants at creation time
470
+ - Never cast directly to branded type in application code
471
+
472
+ 2. **Provide both throwing and Result versions**
473
+ - Throwing for internal code (programmer error if invalid)
474
+ - Result for external input (user error expected)
475
+
476
+ 3. **Document invariants**
477
+ ```typescript
478
+ /**
479
+ * Cents - Money amount in integer cents (prevents floating point errors)
480
+ * Invariants:
481
+ * - Must be integer (no fractional cents)
482
+ * - Must be non-negative
483
+ */
484
+ type Cents = Brand<number, "Cents">
485
+ ```
486
+
487
+ 4. **Use branded types in domain models**
488
+ - Replace primitive types with branded types in entities
489
+ - Make illegal states unrepresentable
490
+
491
+ 5. **Provide conversion functions between units**
492
+ ```typescript
493
+ const dollarsFromCents = (cents: Cents): Dollars => Dollars(cents / 100)
494
+ const centsFromDollars = (dollars: Dollars): Cents => Cents(dollars * 100)
495
+ ```
496
+
497
+ 6. **Test smart constructor validation**
498
+ - Test valid inputs don't throw
499
+ - Test invalid inputs throw with correct message
500
+ - Test normalization (trim, lowercase, etc.)
501
+
502
+ 7. **Consider runtime brand checking for untrusted data**
503
+ ```typescript
504
+ function isCents(value: unknown): value is Cents {
505
+ return typeof value === "number" && Number.isInteger(value) && value >= 0
506
+ }
507
+ ```
508
+
509
+ ## Common Use Cases
510
+
511
+ ### Financial Systems
512
+ - `Cents` - Integer cents (no float errors)
513
+ - `Dollars` - Dollar amounts
514
+ - `BasisPoints` - Fee percentages (1 bps = 0.01%)
515
+
516
+ ### Time/Duration
517
+ - `Millis` - Milliseconds since epoch
518
+ - `Seconds` - Seconds
519
+ - `IsoDateString` - ISO 8601 date string
520
+
521
+ ### Identifiers
522
+ - `UserId`, `OrderId`, `ProductId` - Prevent ID confusion
523
+ - `SessionToken` - Validated token format
524
+ - `ApiKey` - Validated API key
525
+
526
+ ### Validated Strings
527
+ - `Email` - Valid email format
528
+ - `Url` - Valid URL format
529
+ - `PhoneNumber` - Valid phone format
530
+ - `NonEmptyString` - Never empty
531
+
532
+ ### Numeric Constraints
533
+ - `PositiveInt` - Integer > 0
534
+ - `NonNegativeInt` - Integer >= 0
535
+ - `Percentage` - 0-100
536
+ - `Port` - 1-65535
537
+
538
+ ## Further Reading
539
+
540
+ - [ADTs](./adts.md) - Algebraic Data Types and discriminated unions
541
+ - [Option & Result](./option-result.md) - Type-safe error handling
542
+ - [Migration Guide](./migration-guide.md) - Step-by-step adoption playbook