@penner/smart-primitive 0.0.1 → 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/README.md +215 -180
- package/dist/SmartPrimitive.d.ts +87 -37
- package/dist/SmartPrimitive.d.ts.map +1 -1
- package/dist/SmartPrimitive.test-d.d.ts.map +1 -0
- package/dist/SmartPrimitiveConfig.test-d.d.ts +2 -0
- package/dist/SmartPrimitiveConfig.test-d.d.ts.map +1 -0
- package/dist/convert.cjs.js +2 -0
- package/dist/convert.cjs.js.map +1 -0
- package/dist/convert.d.ts +136 -0
- package/dist/convert.d.ts.map +1 -0
- package/dist/convert.es.js +51 -0
- package/dist/convert.es.js.map +1 -0
- package/dist/convert.test-d.d.ts +2 -0
- package/dist/convert.test-d.d.ts.map +1 -0
- package/dist/index.cjs.js +1 -1
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.d.ts +7 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.es.js +65 -2
- package/dist/index.es.js.map +1 -1
- package/dist/strings/css.d.ts +143 -0
- package/dist/strings/css.d.ts.map +1 -0
- package/dist/strings/data.d.ts +140 -0
- package/dist/strings/data.d.ts.map +1 -0
- package/dist/strings/index.cjs.js +2 -0
- package/dist/strings/index.cjs.js.map +1 -0
- package/dist/strings/index.d.ts +14 -0
- package/dist/strings/index.d.ts.map +1 -0
- package/dist/strings/index.es.js +2 -0
- package/dist/strings/index.es.js.map +1 -0
- package/dist/strings/web.d.ts +55 -0
- package/dist/strings/web.d.ts.map +1 -0
- package/dist/trait-utils.d.ts +107 -0
- package/dist/trait-utils.d.ts.map +1 -0
- package/dist/traits.d.ts +14 -0
- package/dist/traits.d.ts.map +1 -0
- package/dist/traits.examples.d.ts +7 -0
- package/dist/traits.examples.d.ts.map +1 -0
- package/dist/traits.test-d.d.ts +5 -0
- package/dist/traits.test-d.d.ts.map +1 -0
- package/dist/units/angle.d.ts +43 -0
- package/dist/units/angle.d.ts.map +1 -0
- package/dist/units/angle.test-d.d.ts +2 -0
- package/dist/units/angle.test-d.d.ts.map +1 -0
- package/dist/units/color.d.ts +35 -0
- package/dist/units/color.d.ts.map +1 -0
- package/dist/units/color.test-d.d.ts +2 -0
- package/dist/units/color.test-d.d.ts.map +1 -0
- package/dist/units/index.cjs.js +2 -0
- package/dist/units/index.cjs.js.map +1 -0
- package/dist/units/index.d.ts +19 -0
- package/dist/units/index.d.ts.map +1 -0
- package/dist/units/index.es.js +86 -0
- package/dist/units/index.es.js.map +1 -0
- package/dist/units/integer.d.ts +12 -0
- package/dist/units/integer.d.ts.map +1 -0
- package/dist/units/integer.test-d.d.ts +2 -0
- package/dist/units/integer.test-d.d.ts.map +1 -0
- package/dist/units/length.d.ts +101 -0
- package/dist/units/length.d.ts.map +1 -0
- package/dist/units/length.test-d.d.ts +2 -0
- package/dist/units/length.test-d.d.ts.map +1 -0
- package/dist/units/normalized.d.ts +111 -0
- package/dist/units/normalized.d.ts.map +1 -0
- package/dist/units/normalized.test-d.d.ts +2 -0
- package/dist/units/normalized.test-d.d.ts.map +1 -0
- package/dist/units/temperature.d.ts +16 -0
- package/dist/units/temperature.d.ts.map +1 -0
- package/dist/units/temperature.test-d.d.ts +2 -0
- package/dist/units/temperature.test-d.d.ts.map +1 -0
- package/dist/units/time.d.ts +38 -0
- package/dist/units/time.d.ts.map +1 -0
- package/dist/units/time.test-d.d.ts +2 -0
- package/dist/units/time.test-d.d.ts.map +1 -0
- package/dist/units/velocity.d.ts +78 -0
- package/dist/units/velocity.d.ts.map +1 -0
- package/dist/units/velocity.test-d.d.ts +2 -0
- package/dist/units/velocity.test-d.d.ts.map +1 -0
- package/package.json +39 -21
- package/dist/__tests__/SmartPrimitive.test-d.d.ts.map +0 -1
- /package/dist/{__tests__/SmartPrimitive.test-d.d.ts → SmartPrimitive.test-d.d.ts} +0 -0
package/README.md
CHANGED
|
@@ -1,267 +1,302 @@
|
|
|
1
1
|
# @penner/smart-primitive
|
|
2
2
|
|
|
3
|
-
Type-safe
|
|
3
|
+
Type-safe primitives for TypeScript. Catch unit mix-ups at compile time with zero runtime cost.
|
|
4
4
|
|
|
5
|
-
##
|
|
5
|
+
## Installation
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
```bash
|
|
8
|
+
npm install @penner/smart-primitive
|
|
9
|
+
```
|
|
8
10
|
|
|
9
|
-
|
|
10
|
-
import { SmartNumber, SmartString } from '@penner/smart-primitive';
|
|
11
|
+
## Quick Start
|
|
11
12
|
|
|
12
|
-
|
|
13
|
-
type Milliseconds = SmartNumber<'Milliseconds'>;
|
|
14
|
-
type URL = SmartString<'URL'>;
|
|
15
|
-
type CSSSelector = SmartString<'CSSSelector'>;
|
|
13
|
+
Import ready-to-use types, annotate your variables and parameters, and let TypeScript catch mistakes:
|
|
16
14
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
let delay: Milliseconds = 500;
|
|
20
|
-
let link: URL = 'https://example.com';
|
|
21
|
-
let selector: CSSSelector = '.button';
|
|
15
|
+
```typescript
|
|
16
|
+
import { Pixels, Milliseconds, Degrees } from '@penner/smart-primitive';
|
|
22
17
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
18
|
+
function animate(
|
|
19
|
+
element: HTMLElement,
|
|
20
|
+
distance: Pixels,
|
|
21
|
+
duration: Milliseconds,
|
|
22
|
+
rotation: Degrees,
|
|
23
|
+
) {
|
|
24
|
+
// ...
|
|
25
|
+
}
|
|
27
26
|
|
|
28
|
-
|
|
27
|
+
const dist: Pixels = 100;
|
|
28
|
+
const time: Milliseconds = 500;
|
|
29
|
+
const angle: Degrees = 90;
|
|
29
30
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
- ✅ **Toggleable type safety** - Turn off all smart typing with one flag
|
|
34
|
-
- ✅ **Utility functions** - `Unbrand`, `BaseOf`, `UnbrandFn` for working with branded types
|
|
35
|
-
- ✅ **Clean tooltips** - TypeScript shows clean type names, not implementation details
|
|
31
|
+
animate(el, dist, time, angle); // ✅ correct
|
|
32
|
+
animate(el, time, dist, angle); // ❌ Error! Can't use Milliseconds where Pixels expected
|
|
33
|
+
```
|
|
36
34
|
|
|
37
|
-
|
|
35
|
+
Plain values are always assignable — no casting or wrapping required:
|
|
38
36
|
|
|
39
|
-
```
|
|
40
|
-
|
|
37
|
+
```typescript
|
|
38
|
+
const width: Pixels = 300; // ✅ plain number works
|
|
39
|
+
const delay: Milliseconds = 1000; // ✅ plain number works
|
|
40
|
+
const opacity: Alpha = 0.8; // ✅ plain number works
|
|
41
41
|
```
|
|
42
42
|
|
|
43
|
-
|
|
43
|
+
### Available Unit Types
|
|
44
44
|
|
|
45
|
-
|
|
45
|
+
| Category | Types |
|
|
46
|
+
| ----------- | -------------------------------------------------------------------------------------------------------------------- |
|
|
47
|
+
| Time | `Seconds`, `Milliseconds`, `Minutes`, `Hours` |
|
|
48
|
+
| Length | `Pixels`, `Ems`, `Rems`, `Vw`, `Vh`, `Percent`, `Points`, `Inches`, `Centimeters`, `Millimeters`, `Meters` |
|
|
49
|
+
| Angle | `Degrees`, `Radians`, `Turns` |
|
|
50
|
+
| Normalized | `Normalized` (0–1), `SignedNormalized` (−1–1), `ClampedNormalized`, `Percentage` (0–100), `Alpha`, `Ratio`, `Factor` |
|
|
51
|
+
| Temperature | `Celsius`, `Fahrenheit` |
|
|
52
|
+
| Color | `ColorByte` (0–255), `RGBTuple`, `RGBATuple` |
|
|
53
|
+
| Integer | `Index` |
|
|
54
|
+
| Animation | `NormalizedTime` (clamped 0–1), `NormalizedProgress` (can overshoot), `PercentProgress` |
|
|
46
55
|
|
|
47
|
-
|
|
56
|
+
All types are importable from the package root or from `@penner/smart-primitive/units`.
|
|
48
57
|
|
|
49
|
-
|
|
50
|
-
import { SmartNumber } from '@penner/smart-primitive';
|
|
58
|
+
## The Trait System
|
|
51
59
|
|
|
52
|
-
|
|
53
|
-
type Milliseconds = SmartNumber<'Milliseconds'>;
|
|
54
|
-
type Degrees = SmartNumber<'Degrees'>;
|
|
60
|
+
These types are built from a small set of composable **traits** — phantom interfaces combined via TypeScript intersection (`&`).
|
|
55
61
|
|
|
56
|
-
|
|
57
|
-
console.log(`Move ${distance}px over ${duration}ms, rotate ${rotation}°`);
|
|
58
|
-
}
|
|
62
|
+
### How Types Are Composed
|
|
59
63
|
|
|
60
|
-
|
|
61
|
-
animate(100, 500, 500); // ✅ works (but is it degrees or milliseconds? TypeScript doesn't know yet)
|
|
64
|
+
A simple type like `Pixels` needs just a unit and kind:
|
|
62
65
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
+
```typescript
|
|
67
|
+
// Pixels = number with unit 'px' and kind 'length'
|
|
68
|
+
type Pixels = WithUnit<'px'> & WithKind<'length'>;
|
|
66
69
|
```
|
|
67
70
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
Distinguish between different kinds of strings:
|
|
71
|
+
A richer type layers on more traits:
|
|
71
72
|
|
|
72
73
|
```typescript
|
|
73
|
-
|
|
74
|
+
// Degrees = number with unit 'deg', range [0, 360), periodic wrapping, kind 'angle'
|
|
75
|
+
type Degrees = WithUnit<'deg'> &
|
|
76
|
+
WithRange<0, 360> &
|
|
77
|
+
WithPeriodic &
|
|
78
|
+
WithKind<'angle'>;
|
|
79
|
+
```
|
|
74
80
|
|
|
75
|
-
|
|
76
|
-
type EmailAddress = SmartString<'EmailAddress'>;
|
|
77
|
-
type CSSSelector = SmartString<'CSSSelector'>;
|
|
81
|
+
Some types only need a range:
|
|
78
82
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
83
|
+
```typescript
|
|
84
|
+
// Normalized = number in the range [0, 1], unclamped (can overshoot)
|
|
85
|
+
type Normalized = WithRange<0, 1>;
|
|
82
86
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
87
|
+
// ClampedNormalized = Normalized that's guaranteed within bounds
|
|
88
|
+
type ClampedNormalized = Normalized & WithClamped;
|
|
89
|
+
```
|
|
86
90
|
|
|
87
|
-
|
|
88
|
-
let email: EmailAddress = 'user@example.com';
|
|
91
|
+
### Core Traits
|
|
89
92
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
+
| Trait | Purpose |
|
|
94
|
+
| --------------------- | ---------------------------------------------------------------------------- |
|
|
95
|
+
| `WithUnit<U>` | Labels the measurement unit (`'px'`, `'ms'`, `'deg'`, …) |
|
|
96
|
+
| `WithRange<Min, Max>` | Documents the reference range (informational) |
|
|
97
|
+
| `WithClamped` | Marks the value as constrained to its range |
|
|
98
|
+
| `WithPeriodic` | Marks the value as wrapping at range boundaries |
|
|
99
|
+
| `WithKind<K>` | Groups related types under an archetype (`'length'`, `'time'`, `'angle'`, …) |
|
|
100
|
+
| `WithInteger` | Marks the value as a whole number |
|
|
93
101
|
|
|
94
|
-
###
|
|
102
|
+
### Defining Custom Types
|
|
95
103
|
|
|
96
|
-
|
|
104
|
+
Compose traits to define your own domain types:
|
|
97
105
|
|
|
98
106
|
```typescript
|
|
99
|
-
import {
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
107
|
+
import {
|
|
108
|
+
WithUnit,
|
|
109
|
+
WithRange,
|
|
110
|
+
WithClamped,
|
|
111
|
+
WithKind,
|
|
112
|
+
} from '@penner/smart-primitive';
|
|
103
113
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
}
|
|
114
|
+
// A simple branded unit
|
|
115
|
+
type Frames = WithUnit<'frames'> & WithKind<'time'>;
|
|
107
116
|
|
|
108
|
-
|
|
109
|
-
|
|
117
|
+
// A bounded value
|
|
118
|
+
type Volume = WithRange<0, 100> & WithUnit<'percent'> & WithClamped;
|
|
110
119
|
|
|
111
|
-
|
|
112
|
-
|
|
120
|
+
// An unbounded measurement
|
|
121
|
+
type Force = WithUnit<'N'> & WithKind<'force'>;
|
|
113
122
|
```
|
|
114
123
|
|
|
115
|
-
|
|
124
|
+
### Trait Extraction
|
|
116
125
|
|
|
117
|
-
|
|
126
|
+
Query type-level metadata from trait-based types:
|
|
118
127
|
|
|
119
|
-
|
|
128
|
+
```typescript
|
|
129
|
+
import type {
|
|
130
|
+
UnitOf,
|
|
131
|
+
RangeOf,
|
|
132
|
+
MinOf,
|
|
133
|
+
MaxOf,
|
|
134
|
+
IsClamped,
|
|
135
|
+
IsPeriodic,
|
|
136
|
+
KindOf,
|
|
137
|
+
} from '@penner/smart-primitive';
|
|
138
|
+
|
|
139
|
+
type U = UnitOf<Degrees>; // 'deg'
|
|
140
|
+
type R = RangeOf<Degrees>; // readonly [0, 360]
|
|
141
|
+
type K = KindOf<Pixels>; // 'length'
|
|
142
|
+
type C = IsClamped<Alpha>; // false
|
|
143
|
+
type P = IsPeriodic<Degrees>; // true
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
### Trait Modification
|
|
120
147
|
|
|
121
|
-
|
|
148
|
+
Transform types by adding, removing, or changing traits:
|
|
122
149
|
|
|
123
150
|
```typescript
|
|
124
|
-
import {
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
type
|
|
133
|
-
// Result: { width: number; duration: number; url: string; }
|
|
134
|
-
|
|
135
|
-
const config: PlainConfig = {
|
|
136
|
-
width: 100,
|
|
137
|
-
duration: 500,
|
|
138
|
-
url: 'https://example.com',
|
|
139
|
-
};
|
|
151
|
+
import type {
|
|
152
|
+
WithoutClamped,
|
|
153
|
+
ChangeUnits,
|
|
154
|
+
ChangeRange,
|
|
155
|
+
} from '@penner/smart-primitive';
|
|
156
|
+
|
|
157
|
+
type Unclamped = WithoutClamped<ClampedNormalized>; // remove clamping
|
|
158
|
+
type InInches = ChangeUnits<Pixels, 'in'>; // swap units
|
|
159
|
+
type WiderRange = ChangeRange<Normalized, -1, 2>; // change range
|
|
140
160
|
```
|
|
141
161
|
|
|
142
|
-
|
|
162
|
+
### Runtime Utilities
|
|
143
163
|
|
|
144
|
-
|
|
164
|
+
The `clamp` and `wrap` functions operate on trait-based types and refine the output type accordingly:
|
|
145
165
|
|
|
146
166
|
```typescript
|
|
147
|
-
import {
|
|
167
|
+
import { clamp, wrap } from '@penner/smart-primitive';
|
|
148
168
|
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
type IsVisibleBase = BaseOf<IsVisible>; // boolean
|
|
169
|
+
const v = clamp(1.5, 0, 1); // value: 1, type: WithRange<0, 1> & WithClamped
|
|
170
|
+
const w = wrap(450, 0, 360); // value: 90, type: WithRange<0, 360> & WithPeriodic
|
|
152
171
|
```
|
|
153
172
|
|
|
154
|
-
|
|
173
|
+
## Conversions
|
|
155
174
|
|
|
156
|
-
|
|
175
|
+
Each unit module ships pre-built converter functions:
|
|
157
176
|
|
|
158
177
|
```typescript
|
|
159
|
-
import {
|
|
178
|
+
import { degreesToRadians, radiansToDegrees } from '@penner/smart-primitive';
|
|
179
|
+
import {
|
|
180
|
+
secondsToMilliseconds,
|
|
181
|
+
celsiusToFahrenheit,
|
|
182
|
+
} from '@penner/smart-primitive';
|
|
183
|
+
import {
|
|
184
|
+
normalizedToPercentage,
|
|
185
|
+
metersToCentimeters,
|
|
186
|
+
} from '@penner/smart-primitive';
|
|
187
|
+
|
|
188
|
+
const rad: Radians = degreesToRadians(90); // π/2
|
|
189
|
+
const ms: Milliseconds = secondsToMilliseconds(2); // 2000
|
|
190
|
+
const pct: Percentage = normalizedToPercentage(0.75); // 75
|
|
191
|
+
```
|
|
160
192
|
|
|
161
|
-
|
|
162
|
-
// implementation
|
|
163
|
-
}
|
|
193
|
+
For custom conversions, use the factory functions:
|
|
164
194
|
|
|
165
|
-
|
|
166
|
-
|
|
195
|
+
```typescript
|
|
196
|
+
import {
|
|
197
|
+
createConverter,
|
|
198
|
+
createBiConverter,
|
|
199
|
+
createLinearConverter,
|
|
200
|
+
} from '@penner/smart-primitive';
|
|
201
|
+
|
|
202
|
+
// One-way converter
|
|
203
|
+
const framesToSeconds = createConverter<Frames, Seconds>(f => f / 60);
|
|
204
|
+
|
|
205
|
+
// Bidirectional converter
|
|
206
|
+
const pixelsRems = createBiConverter<Pixels, Rems>(
|
|
207
|
+
px => px / 16,
|
|
208
|
+
rem => rem * 16,
|
|
209
|
+
);
|
|
210
|
+
pixelsRems.to(32); // 2 Rems
|
|
211
|
+
pixelsRems.from(1.5); // 24 Pixels
|
|
212
|
+
|
|
213
|
+
// Linear ratio converter
|
|
214
|
+
const metersCm = createLinearConverter<Meters, Centimeters>(100);
|
|
167
215
|
```
|
|
168
216
|
|
|
169
|
-
|
|
217
|
+
Advanced patterns like `chain` (compose converters) and `ConverterRegistry` (runtime lookup) are also available.
|
|
170
218
|
|
|
171
|
-
|
|
219
|
+
## String Types
|
|
172
220
|
|
|
173
|
-
-
|
|
174
|
-
- Debugging type issues
|
|
175
|
-
- Gradual migration
|
|
176
|
-
- Bundle size optimization
|
|
221
|
+
The library includes semantic string types via `@penner/smart-primitive/strings`, with template literal validation where possible:
|
|
177
222
|
|
|
178
223
|
```typescript
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
width = delay; // ✅ Now allowed! (when flag is true)
|
|
224
|
+
import type {
|
|
225
|
+
CSSLength,
|
|
226
|
+
CSSCustomProperty,
|
|
227
|
+
UUID,
|
|
228
|
+
EmailAddress,
|
|
229
|
+
} from '@penner/smart-primitive/strings';
|
|
230
|
+
|
|
231
|
+
const width: CSSLength = '16px'; // template: `${number}${CSSLengthUnit}`
|
|
232
|
+
const prop: CSSCustomProperty = '--main-color'; // template: `--${string}`
|
|
233
|
+
const id: UUID = '550e8400-e29b-41d4-a716-446655440000';
|
|
234
|
+
const email: EmailAddress = 'user@example.com';
|
|
191
235
|
```
|
|
192
236
|
|
|
193
|
-
|
|
237
|
+
See the [strings module](./src/strings/) for the full list of CSS, web, and data format types.
|
|
194
238
|
|
|
195
|
-
|
|
239
|
+
## SmartPrimitive & SmartNumber
|
|
196
240
|
|
|
197
|
-
|
|
198
|
-
export type SmartPrimitive<
|
|
199
|
-
Base extends string | number | boolean | bigint | symbol,
|
|
200
|
-
BrandName extends string,
|
|
201
|
-
> = Base & { readonly __brand?: BrandName };
|
|
202
|
-
```
|
|
241
|
+
For quick, ad-hoc branded types that don't need trait composition, `SmartNumber` and `SmartString` are still available:
|
|
203
242
|
|
|
204
|
-
|
|
243
|
+
```typescript
|
|
244
|
+
import { SmartNumber, SmartString } from '@penner/smart-primitive';
|
|
205
245
|
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
- **Never actually exists at runtime** - TypeScript-only, zero overhead
|
|
246
|
+
type Score = SmartNumber<'Score'>;
|
|
247
|
+
type SessionToken = SmartString<'SessionToken'>;
|
|
209
248
|
|
|
210
|
-
|
|
249
|
+
const points: Score = 42;
|
|
250
|
+
const token: SessionToken = 'abc123';
|
|
251
|
+
```
|
|
211
252
|
|
|
212
|
-
|
|
253
|
+
> **Note:** All pre-built unit types (`Pixels`, `Degrees`, `Milliseconds`, etc.) are now defined using the trait system rather than `SmartNumber`. Trait-based types carry richer metadata (units, ranges, kinds) and support extraction utilities like `UnitOf` and `RangeOf`. For a discussion of the tradeoffs between the two approaches, see [SmartPrimitive vs Traits analysis](../../docs/research/smart-primitive-vs-traits.md).
|
|
213
254
|
|
|
214
|
-
##
|
|
255
|
+
## Feature Flag: Toggle Type Safety
|
|
215
256
|
|
|
216
|
-
|
|
257
|
+
Disable all smart typing across your project via module augmentation:
|
|
217
258
|
|
|
218
259
|
```typescript
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
start: Pixels;
|
|
226
|
-
end: Pixels;
|
|
227
|
-
};
|
|
228
|
-
rotation: Degrees;
|
|
229
|
-
};
|
|
230
|
-
|
|
231
|
-
const config: AnimationConfig = {
|
|
232
|
-
timing: { duration: 1000, delay: 200 },
|
|
233
|
-
position: { start: 0, end: 500 },
|
|
234
|
-
rotation: 180,
|
|
235
|
-
};
|
|
260
|
+
// In a .d.ts file in your project (e.g., smart-primitive.d.ts):
|
|
261
|
+
declare module '@penner/smart-primitive' {
|
|
262
|
+
interface SmartPrimitiveConfig {
|
|
263
|
+
usePlainPrimitives: true;
|
|
264
|
+
}
|
|
265
|
+
}
|
|
236
266
|
```
|
|
237
267
|
|
|
238
|
-
|
|
268
|
+
When enabled, all smart types collapse to plain primitives (`number`, `string`, etc.) and all cross-type assignments are allowed. Useful for debugging type issues or gradual migration.
|
|
239
269
|
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
): void {
|
|
246
|
-
// TypeScript ensures you can't accidentally swap parameters
|
|
247
|
-
}
|
|
270
|
+
## Utility Types
|
|
271
|
+
|
|
272
|
+
### `Unbrand<T>`
|
|
273
|
+
|
|
274
|
+
Strip branding from types, recursing into object structures:
|
|
248
275
|
|
|
249
|
-
|
|
250
|
-
|
|
276
|
+
```typescript
|
|
277
|
+
import type { Unbrand } from '@penner/smart-primitive';
|
|
251
278
|
|
|
252
|
-
|
|
253
|
-
|
|
279
|
+
type Config = { width: Pixels; duration: Milliseconds };
|
|
280
|
+
type Plain = Unbrand<Config>;
|
|
281
|
+
// { width: number; duration: number }
|
|
254
282
|
```
|
|
255
283
|
|
|
256
|
-
|
|
284
|
+
### `UnbrandFn<F>`
|
|
257
285
|
|
|
258
|
-
|
|
286
|
+
Strip branding from function parameter types:
|
|
259
287
|
|
|
260
|
-
|
|
288
|
+
```typescript
|
|
289
|
+
import type { UnbrandFn } from '@penner/smart-primitive';
|
|
261
290
|
|
|
262
|
-
|
|
263
|
-
|
|
291
|
+
declare function move(distance: Pixels, duration: Milliseconds): void;
|
|
292
|
+
type PlainMove = UnbrandFn<typeof move>;
|
|
293
|
+
// (distance: number, duration: number) => void
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
## TypeScript Compatibility
|
|
264
297
|
|
|
265
|
-
|
|
298
|
+
Requires TypeScript 4.5 or higher.
|
|
266
299
|
|
|
267
|
-
|
|
300
|
+
## License
|
|
301
|
+
|
|
302
|
+
MIT
|