functype 0.9.3 → 0.9.4
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/dist/{chunk-4WBZOSQH.mjs → chunk-Q45K2ZSV.mjs} +24 -24
- package/dist/chunk-Q45K2ZSV.mjs.map +1 -0
- package/dist/either/index.mjs +1 -1
- package/dist/fpromise/index.mjs +1 -1
- package/dist/index.d.ts +23 -20
- package/dist/index.mjs +1 -1
- package/dist/list/index.mjs +1 -1
- package/dist/map/index.mjs +1 -1
- package/dist/option/index.mjs +1 -1
- package/dist/set/index.mjs +1 -1
- package/dist/try/index.mjs +1 -1
- package/package.json +1 -1
- package/readme/BRAND_MIGRATION_GUIDE.md +230 -0
- package/dist/chunk-4WBZOSQH.mjs.map +0 -1
package/dist/either/index.mjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export{q as Either,j as Left,i as Right,o as TypeCheckLeft,n as TypeCheckRight,l as isLeft,k as isRight,m as tryCatch,p as tryCatchAsync}from'../chunk-
|
|
1
|
+
export{q as Either,j as Left,i as Right,o as TypeCheckLeft,n as TypeCheckRight,l as isLeft,k as isRight,m as tryCatch,p as tryCatchAsync}from'../chunk-Q45K2ZSV.mjs';import'../chunk-BQJB6CCW.mjs';import'../chunk-ECL55NTP.mjs';//# sourceMappingURL=index.mjs.map
|
|
2
2
|
//# sourceMappingURL=index.mjs.map
|
package/dist/fpromise/index.mjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export{K as FPromise,J as FPromiseCompanion}from'../chunk-
|
|
1
|
+
export{K as FPromise,J as FPromiseCompanion}from'../chunk-Q45K2ZSV.mjs';import'../chunk-BQJB6CCW.mjs';import'../chunk-ECL55NTP.mjs';//# sourceMappingURL=index.mjs.map
|
|
2
2
|
//# sourceMappingURL=index.mjs.map
|
package/dist/index.d.ts
CHANGED
|
@@ -11,16 +11,19 @@ export { TypeNames } from './try/index.js';
|
|
|
11
11
|
export { Map, SafeTraversable } from './map/index.js';
|
|
12
12
|
export { Tuple } from './tuple/index.js';
|
|
13
13
|
|
|
14
|
-
interface
|
|
14
|
+
interface ValidatedBrandCompanion<K extends string, T> {
|
|
15
15
|
readonly brand: K;
|
|
16
16
|
readonly validate: (value: T) => boolean;
|
|
17
|
-
readonly of: (value: T) => Option<
|
|
18
|
-
readonly from: (value: T) => Either<string,
|
|
19
|
-
readonly unsafeOf: (value: T) =>
|
|
20
|
-
readonly is: (value: unknown) => value is
|
|
17
|
+
readonly of: (value: T) => Option<ValidatedBrand<K, T>>;
|
|
18
|
+
readonly from: (value: T) => Either<string, ValidatedBrand<K, T>>;
|
|
19
|
+
readonly unsafeOf: (value: T) => ValidatedBrand<K, T>;
|
|
20
|
+
readonly is: (value: unknown) => value is ValidatedBrand<K, T>;
|
|
21
21
|
readonly unwrap: (branded: Brand<K, T>) => T;
|
|
22
|
-
readonly refine: <K2 extends string>(brand: K2, validate: (value: Brand<K, T>) => boolean) =>
|
|
22
|
+
readonly refine: <K2 extends string>(brand: K2, validate: (value: Brand<K, T>) => boolean) => ValidatedBrandCompanion<K2, Brand<K, T>>;
|
|
23
23
|
}
|
|
24
|
+
type ValidatedBrand<K extends string, T> = Brand<K, T> & {
|
|
25
|
+
readonly __validated: true;
|
|
26
|
+
};
|
|
24
27
|
/**
|
|
25
28
|
* Create a validated brand with runtime validation
|
|
26
29
|
* @example
|
|
@@ -40,7 +43,7 @@ interface ValidatedBrand<K extends string, T> {
|
|
|
40
43
|
* // value is Brand<"Email", string>
|
|
41
44
|
* }
|
|
42
45
|
*/
|
|
43
|
-
declare function ValidatedBrand<K extends string, T>(brand: K, validate: (value: T) => boolean):
|
|
46
|
+
declare function ValidatedBrand<K extends string, T>(brand: K, validate: (value: T) => boolean): ValidatedBrandCompanion<K, T>;
|
|
44
47
|
/**
|
|
45
48
|
* Positive number brand (> 0)
|
|
46
49
|
* @example
|
|
@@ -48,17 +51,17 @@ declare function ValidatedBrand<K extends string, T>(brand: K, validate: (value:
|
|
|
48
51
|
* const invalid = PositiveNumber.of(-5) // None
|
|
49
52
|
* const checked = PositiveNumber.from(0) // Left("Invalid PositiveNumber: validation failed")
|
|
50
53
|
*/
|
|
51
|
-
declare const PositiveNumber:
|
|
52
|
-
declare const NonNegativeNumber:
|
|
53
|
-
declare const IntegerNumber:
|
|
54
|
-
declare const PositiveInteger:
|
|
54
|
+
declare const PositiveNumber: ValidatedBrandCompanion<"PositiveNumber", number>;
|
|
55
|
+
declare const NonNegativeNumber: ValidatedBrandCompanion<"NonNegativeNumber", number>;
|
|
56
|
+
declare const IntegerNumber: ValidatedBrandCompanion<"IntegerNumber", number>;
|
|
57
|
+
declare const PositiveInteger: ValidatedBrandCompanion<"PositiveInteger", Brand<"PositiveNumber", number>>;
|
|
55
58
|
/**
|
|
56
59
|
* Non-empty string brand
|
|
57
60
|
* @example
|
|
58
61
|
* const name = NonEmptyString.of("John") // Some(Brand<"NonEmptyString", string>)
|
|
59
62
|
* const empty = NonEmptyString.of("") // None
|
|
60
63
|
*/
|
|
61
|
-
declare const NonEmptyString:
|
|
64
|
+
declare const NonEmptyString: ValidatedBrandCompanion<"NonEmptyString", string>;
|
|
62
65
|
/**
|
|
63
66
|
* Email address brand with basic validation
|
|
64
67
|
* @example
|
|
@@ -73,10 +76,10 @@ declare const NonEmptyString: ValidatedBrand<"NonEmptyString", string>;
|
|
|
73
76
|
* .getOrElse("Invalid email address")
|
|
74
77
|
* }
|
|
75
78
|
*/
|
|
76
|
-
declare const EmailAddress:
|
|
77
|
-
declare const UrlString:
|
|
78
|
-
declare const UUID:
|
|
79
|
-
declare const ISO8601Date:
|
|
79
|
+
declare const EmailAddress: ValidatedBrandCompanion<"EmailAddress", string>;
|
|
80
|
+
declare const UrlString: ValidatedBrandCompanion<"UrlString", string>;
|
|
81
|
+
declare const UUID: ValidatedBrandCompanion<"UUID", string>;
|
|
82
|
+
declare const ISO8601Date: ValidatedBrandCompanion<"ISO8601Date", string>;
|
|
80
83
|
/**
|
|
81
84
|
* Create a number brand with min/max bounds
|
|
82
85
|
* @example
|
|
@@ -89,7 +92,7 @@ declare const ISO8601Date: ValidatedBrand<"ISO8601Date", string>;
|
|
|
89
92
|
* const httpPort = Port.unsafeOf(80) // Brand<"Port", number>
|
|
90
93
|
* // Port.unsafeOf(70000) // throws Error
|
|
91
94
|
*/
|
|
92
|
-
declare function BoundedNumber(brand: string, min: number, max: number):
|
|
95
|
+
declare function BoundedNumber(brand: string, min: number, max: number): ValidatedBrandCompanion<string, number>;
|
|
93
96
|
/**
|
|
94
97
|
* Create a string brand with length constraints
|
|
95
98
|
* @example
|
|
@@ -98,7 +101,7 @@ declare function BoundedNumber(brand: string, min: number, max: number): Validat
|
|
|
98
101
|
* const tooShort = Username.of("jo") // None
|
|
99
102
|
* const tooLong = Username.of("verylongusernamethatexceedslimit") // None
|
|
100
103
|
*/
|
|
101
|
-
declare function BoundedString(brand: string, minLength: number, maxLength: number):
|
|
104
|
+
declare function BoundedString(brand: string, minLength: number, maxLength: number): ValidatedBrandCompanion<string, string>;
|
|
102
105
|
/**
|
|
103
106
|
* Create a string brand that matches a regex pattern
|
|
104
107
|
* @example
|
|
@@ -112,7 +115,7 @@ declare function BoundedString(brand: string, minLength: number, maxLength: numb
|
|
|
112
115
|
* .map(p => formatPhoneNumber(p))
|
|
113
116
|
* .getOrElse("Invalid phone number")
|
|
114
117
|
*/
|
|
115
|
-
declare function PatternString(brand: string, pattern: RegExp):
|
|
118
|
+
declare function PatternString(brand: string, pattern: RegExp): ValidatedBrandCompanion<string, string>;
|
|
116
119
|
|
|
117
120
|
/**
|
|
118
121
|
* Creates a function-object hybrid similar to Scala's companion objects.
|
|
@@ -1734,4 +1737,4 @@ declare const Stack: (<A extends Type>(values?: A[]) => Stack<A>) & {
|
|
|
1734
1737
|
fromBinary: <A>(binary: string) => Stack<A>;
|
|
1735
1738
|
};
|
|
1736
1739
|
|
|
1737
|
-
export { type Async, Base, BoundedNumber, BoundedString, Brand, type CancellationToken, type CancellationTokenSource, Companion, Cond, ESMap, type ESMapType, Either, type EitherKind, EmailAddress, type ErrorChainElement, type ErrorCode, type ErrorFormatterOptions, type ErrorMessage, type ErrorStatus, type ErrorWithTaskInfo, Extractable, FPromise, type FieldValidation, Foldable, FoldableUtils, type FormValidation, FunctypeBase, HKT, ISO8601Date, Identity, IntegerNumber, type Kind, Lazy, LazyList, Lazy as LazyType, List, type ListKind, Match, Matchable, NAME, NonEmptyString, NonNegativeNumber, Option, type OptionKind, ParseError, PatternString, Pipe, PositiveInteger, PositiveNumber, Ref, Ref as RefType, Serializable, Stack, type Sync, type TaggedThrowable, Task, type TaskErrorInfo, TaskException, type TaskInfo, type TaskParams, TaskResult, Throwable, type ThrowableType, Traversable, Try, type TryKind, Type, Typeable, TypedError, type TypedErrorContext, UUID, type UniversalContainer, UrlString, ValidatedBrand, Validation, type ValidationRule, type Validator, Valuable, type ValuableParams, createCancellationTokenSource, createErrorSerializer, formatError, formatStackTrace, isTaggedThrowable, safeStringify };
|
|
1740
|
+
export { type Async, Base, BoundedNumber, BoundedString, Brand, type CancellationToken, type CancellationTokenSource, Companion, Cond, ESMap, type ESMapType, Either, type EitherKind, EmailAddress, type ErrorChainElement, type ErrorCode, type ErrorFormatterOptions, type ErrorMessage, type ErrorStatus, type ErrorWithTaskInfo, Extractable, FPromise, type FieldValidation, Foldable, FoldableUtils, type FormValidation, FunctypeBase, HKT, ISO8601Date, Identity, IntegerNumber, type Kind, Lazy, LazyList, Lazy as LazyType, List, type ListKind, Match, Matchable, NAME, NonEmptyString, NonNegativeNumber, Option, type OptionKind, ParseError, PatternString, Pipe, PositiveInteger, PositiveNumber, Ref, Ref as RefType, Serializable, Stack, type Sync, type TaggedThrowable, Task, type TaskErrorInfo, TaskException, type TaskInfo, type TaskParams, TaskResult, Throwable, type ThrowableType, Traversable, Try, type TryKind, Type, Typeable, TypedError, type TypedErrorContext, UUID, type UniversalContainer, UrlString, ValidatedBrand, type ValidatedBrandCompanion, Validation, type ValidationRule, type Validator, Valuable, type ValuableParams, createCancellationTokenSource, createErrorSerializer, formatError, formatStackTrace, isTaggedThrowable, safeStringify };
|
package/dist/index.mjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export{G as Base,B as BoundedNumber,C as BoundedString,E as Cond,ca as ESMap,q as Either,x as EmailAddress,K as FPromise,J as FPromiseCompanion,Z as FoldableUtils,$ as HKT,A as ISO8601Date,aa as Identity,u as IntegerNumber,ba as Lazy,W as LazyList,j as Left,h as List,da as Map,F as Match,ea as MatchableUtils,H as NAME,w as NonEmptyString,t as NonNegativeNumber,b as None,d as Option,c as OptionConstructor,U as ParseError,D as PatternString,v as PositiveInteger,s as PositiveNumber,fa as Ref,i as Right,e as Set,a as Some,ha as Stack,P as Task,M as TaskException,N as TaskResult,I as Throwable,_ as Try,o as TypeCheckLeft,n as TypeCheckRight,f as Typeable,V as TypedError,z as UUID,y as UrlString,r as ValidatedBrand,X as Validation,ga as Valuable,O as createCancellationTokenSource,T as createErrorSerializer,S as formatError,R as formatStackTrace,Y as isExtractable,l as isLeft,k as isRight,L as isTaggedThrowable,g as isTypeable,Q as safeStringify,m as tryCatch,p as tryCatchAsync}from'./chunk-
|
|
1
|
+
export{G as Base,B as BoundedNumber,C as BoundedString,E as Cond,ca as ESMap,q as Either,x as EmailAddress,K as FPromise,J as FPromiseCompanion,Z as FoldableUtils,$ as HKT,A as ISO8601Date,aa as Identity,u as IntegerNumber,ba as Lazy,W as LazyList,j as Left,h as List,da as Map,F as Match,ea as MatchableUtils,H as NAME,w as NonEmptyString,t as NonNegativeNumber,b as None,d as Option,c as OptionConstructor,U as ParseError,D as PatternString,v as PositiveInteger,s as PositiveNumber,fa as Ref,i as Right,e as Set,a as Some,ha as Stack,P as Task,M as TaskException,N as TaskResult,I as Throwable,_ as Try,o as TypeCheckLeft,n as TypeCheckRight,f as Typeable,V as TypedError,z as UUID,y as UrlString,r as ValidatedBrand,X as Validation,ga as Valuable,O as createCancellationTokenSource,T as createErrorSerializer,S as formatError,R as formatStackTrace,Y as isExtractable,l as isLeft,k as isRight,L as isTaggedThrowable,g as isTypeable,Q as safeStringify,m as tryCatch,p as tryCatchAsync}from'./chunk-Q45K2ZSV.mjs';export{a as Companion,b as Tuple}from'./chunk-BQJB6CCW.mjs';export{a as Brand,g as BrandedBoolean,f as BrandedNumber,e as BrandedString,d as createBrander,c as hasBrand,b as unbrand}from'./chunk-ECL55NTP.mjs';//# sourceMappingURL=index.mjs.map
|
|
2
2
|
//# sourceMappingURL=index.mjs.map
|
package/dist/list/index.mjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export{h as List}from'../chunk-
|
|
1
|
+
export{h as List}from'../chunk-Q45K2ZSV.mjs';import'../chunk-BQJB6CCW.mjs';import'../chunk-ECL55NTP.mjs';//# sourceMappingURL=index.mjs.map
|
|
2
2
|
//# sourceMappingURL=index.mjs.map
|
package/dist/map/index.mjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export{da as Map}from'../chunk-
|
|
1
|
+
export{da as Map}from'../chunk-Q45K2ZSV.mjs';import'../chunk-BQJB6CCW.mjs';import'../chunk-ECL55NTP.mjs';//# sourceMappingURL=index.mjs.map
|
|
2
2
|
//# sourceMappingURL=index.mjs.map
|
package/dist/option/index.mjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export{b as None,d as Option,c as OptionConstructor,a as Some}from'../chunk-
|
|
1
|
+
export{b as None,d as Option,c as OptionConstructor,a as Some}from'../chunk-Q45K2ZSV.mjs';import'../chunk-BQJB6CCW.mjs';import'../chunk-ECL55NTP.mjs';//# sourceMappingURL=index.mjs.map
|
|
2
2
|
//# sourceMappingURL=index.mjs.map
|
package/dist/set/index.mjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export{e as Set}from'../chunk-
|
|
1
|
+
export{e as Set}from'../chunk-Q45K2ZSV.mjs';import'../chunk-BQJB6CCW.mjs';import'../chunk-ECL55NTP.mjs';//# sourceMappingURL=index.mjs.map
|
|
2
2
|
//# sourceMappingURL=index.mjs.map
|
package/dist/try/index.mjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export{_ as Try}from'../chunk-
|
|
1
|
+
export{_ as Try}from'../chunk-Q45K2ZSV.mjs';import'../chunk-BQJB6CCW.mjs';import'../chunk-ECL55NTP.mjs';//# sourceMappingURL=index.mjs.map
|
|
2
2
|
//# sourceMappingURL=index.mjs.map
|
package/package.json
CHANGED
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
# Brand Type Migration Guide
|
|
2
|
+
|
|
3
|
+
This guide helps you migrate from the old object-based Brand implementation to the new phantom type implementation.
|
|
4
|
+
|
|
5
|
+
## What Changed?
|
|
6
|
+
|
|
7
|
+
### Old Implementation (Object Wrappers)
|
|
8
|
+
|
|
9
|
+
- Branded values were **objects** with methods
|
|
10
|
+
- Required `.unbrand()` or `.unwrap()` to access the primitive value
|
|
11
|
+
- `typeof brandedString === "object"`
|
|
12
|
+
- Object overhead and indexed properties
|
|
13
|
+
|
|
14
|
+
### New Implementation (Phantom Types)
|
|
15
|
+
|
|
16
|
+
- Branded values **ARE** the primitive values
|
|
17
|
+
- No unwrapping needed - use directly
|
|
18
|
+
- `typeof brandedString === "string"`
|
|
19
|
+
- Zero runtime overhead
|
|
20
|
+
|
|
21
|
+
## Migration Steps
|
|
22
|
+
|
|
23
|
+
### 1. Remove `.unbrand()` and `.unwrap()` calls
|
|
24
|
+
|
|
25
|
+
**Before:**
|
|
26
|
+
|
|
27
|
+
```typescript
|
|
28
|
+
const userId = Brand("UserId", "user-123")
|
|
29
|
+
const id = userId.unbrand() // Had to unwrap
|
|
30
|
+
console.log(`User: ${userId.unbrand()}`)
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
**After:**
|
|
34
|
+
|
|
35
|
+
```typescript
|
|
36
|
+
const userId = Brand("UserId", "user-123")
|
|
37
|
+
const id = userId // It IS the string!
|
|
38
|
+
console.log(`User: ${userId}`)
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
### 2. Remove `.toString()` for custom formatting
|
|
42
|
+
|
|
43
|
+
**Before:**
|
|
44
|
+
|
|
45
|
+
```typescript
|
|
46
|
+
const userId = Brand("UserId", "user-123")
|
|
47
|
+
console.log(userId.toString()) // "UserId(user-123)"
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
**After:**
|
|
51
|
+
|
|
52
|
+
```typescript
|
|
53
|
+
const userId = Brand("UserId", "user-123")
|
|
54
|
+
console.log(userId.toString()) // "user-123" - standard string method
|
|
55
|
+
console.log(userId) // "user-123"
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
### 3. Update ValidatedBrand usage
|
|
59
|
+
|
|
60
|
+
**Before:**
|
|
61
|
+
|
|
62
|
+
```typescript
|
|
63
|
+
const email = EmailAddress.of("user@example.com")
|
|
64
|
+
if (!email.isEmpty) {
|
|
65
|
+
const branded = email.get()
|
|
66
|
+
sendEmail(branded.unbrand()) // Had to unwrap
|
|
67
|
+
}
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
**After:**
|
|
71
|
+
|
|
72
|
+
```typescript
|
|
73
|
+
const email = EmailAddress.of("user@example.com")
|
|
74
|
+
if (!email.isEmpty) {
|
|
75
|
+
const branded = email.get()
|
|
76
|
+
sendEmail(branded) // It IS a string!
|
|
77
|
+
}
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
### 4. Update refine validators
|
|
81
|
+
|
|
82
|
+
**Before:**
|
|
83
|
+
|
|
84
|
+
```typescript
|
|
85
|
+
const SmallPositive = PositiveNumber.refine("SmallPositive", (n) => {
|
|
86
|
+
return n.unbrand() < 100 // Had to unwrap
|
|
87
|
+
})
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
**After:**
|
|
91
|
+
|
|
92
|
+
```typescript
|
|
93
|
+
const SmallPositive = PositiveNumber.refine("SmallPositive", (n) => {
|
|
94
|
+
return n < 100 // n IS a number!
|
|
95
|
+
})
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
### 5. Remove type assertions for external APIs
|
|
99
|
+
|
|
100
|
+
**Before:**
|
|
101
|
+
|
|
102
|
+
```typescript
|
|
103
|
+
const config = {
|
|
104
|
+
userId: userId.unbrand(),
|
|
105
|
+
port: port.unbrand(),
|
|
106
|
+
}
|
|
107
|
+
await api.request(config)
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
**After:**
|
|
111
|
+
|
|
112
|
+
```typescript
|
|
113
|
+
const config = {
|
|
114
|
+
userId, // Already a string!
|
|
115
|
+
port, // Already a number!
|
|
116
|
+
}
|
|
117
|
+
await api.request(config)
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
## Common Patterns
|
|
121
|
+
|
|
122
|
+
### Working with Option/Either
|
|
123
|
+
|
|
124
|
+
When extracting branded values from Option or Either, you may need to adjust default values:
|
|
125
|
+
|
|
126
|
+
**Before:**
|
|
127
|
+
|
|
128
|
+
```typescript
|
|
129
|
+
const email = EmailAddress.of(input)
|
|
130
|
+
const value = email.map((e) => e.unbrand()).getOrElse("")
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
**After (Option 1 - Keep branded type):**
|
|
134
|
+
|
|
135
|
+
```typescript
|
|
136
|
+
const email = EmailAddress.of(input)
|
|
137
|
+
const value = email.getOrElse("" as Brand<"EmailAddress", string>)
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
**After (Option 2 - Use fold for clean extraction):**
|
|
141
|
+
|
|
142
|
+
```typescript
|
|
143
|
+
const email = EmailAddress.of(input)
|
|
144
|
+
const value = email.fold(
|
|
145
|
+
() => "", // Default value
|
|
146
|
+
(e) => e, // e IS already a string!
|
|
147
|
+
)
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
### Type-safe functions
|
|
151
|
+
|
|
152
|
+
Function signatures don't change, but implementation is cleaner:
|
|
153
|
+
|
|
154
|
+
**Before:**
|
|
155
|
+
|
|
156
|
+
```typescript
|
|
157
|
+
function processUser(userId: Brand<"UserId", string>) {
|
|
158
|
+
const id = userId.unbrand()
|
|
159
|
+
return db.query(`SELECT * FROM users WHERE id = '${id}'`)
|
|
160
|
+
}
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
**After:**
|
|
164
|
+
|
|
165
|
+
```typescript
|
|
166
|
+
function processUser(userId: Brand<"UserId", string>) {
|
|
167
|
+
return db.query(`SELECT * FROM users WHERE id = '${userId}'`)
|
|
168
|
+
}
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
## Benefits After Migration
|
|
172
|
+
|
|
173
|
+
1. **Better Performance**: No object allocation overhead
|
|
174
|
+
2. **Cleaner Code**: No more `.unbrand()` calls everywhere
|
|
175
|
+
3. **Natural JavaScript**: String interpolation, JSON serialization work directly
|
|
176
|
+
4. **Smaller Bundles**: Less code to ship
|
|
177
|
+
5. **Better Debugging**: Values show as primitives in debugger
|
|
178
|
+
|
|
179
|
+
## Compatibility
|
|
180
|
+
|
|
181
|
+
### If you need the old behavior
|
|
182
|
+
|
|
183
|
+
The `unbrand` utility function still exists for compatibility:
|
|
184
|
+
|
|
185
|
+
```typescript
|
|
186
|
+
import { unbrand } from "@/branded"
|
|
187
|
+
|
|
188
|
+
const userId = Brand("UserId", "user-123")
|
|
189
|
+
const plain = unbrand(userId) // Works but unnecessary
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
ValidatedBrand also provides an `unwrap` method:
|
|
193
|
+
|
|
194
|
+
```typescript
|
|
195
|
+
const email = EmailAddress.unsafeOf("user@example.com")
|
|
196
|
+
const plain = EmailAddress.unwrap(email) // Works but unnecessary
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
## Quick Reference
|
|
200
|
+
|
|
201
|
+
| Old Code | New Code |
|
|
202
|
+
| ------------------------------------ | --------------------------- |
|
|
203
|
+
| `value.unbrand()` | `value` |
|
|
204
|
+
| `value.unwrap()` | `value` |
|
|
205
|
+
| `value.toString()` | `value` or `String(value)` |
|
|
206
|
+
| `typeof value === "object"` | `typeof value === "string"` |
|
|
207
|
+
| `${value.unbrand()}` | `${value}` |
|
|
208
|
+
| `JSON.stringify({id: id.unbrand()})` | `JSON.stringify({id})` |
|
|
209
|
+
|
|
210
|
+
## Testing
|
|
211
|
+
|
|
212
|
+
Update your tests to expect primitives:
|
|
213
|
+
|
|
214
|
+
**Before:**
|
|
215
|
+
|
|
216
|
+
```typescript
|
|
217
|
+
expect(typeof userId).toBe("object")
|
|
218
|
+
expect(userId.unbrand()).toBe("user-123")
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
**After:**
|
|
222
|
+
|
|
223
|
+
```typescript
|
|
224
|
+
expect(typeof userId).toBe("string")
|
|
225
|
+
expect(userId).toBe("user-123")
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
## Summary
|
|
229
|
+
|
|
230
|
+
The migration is mostly about **removing code**. Branded values now work exactly like their underlying primitive types, making the code cleaner and more performant. The type safety remains unchanged - you still get full compile-time protection against mixing different branded types.
|