unacy 0.8.0 → 0.8.2
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/converters.d.ts +106 -17
- package/dist/converters.d.ts.map +1 -1
- package/dist/errors.d.ts +132 -5
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +132 -5
- package/dist/errors.js.map +1 -1
- package/dist/formatters.d.ts +96 -32
- package/dist/formatters.d.ts.map +1 -1
- package/dist/registry.d.ts +151 -21
- package/dist/registry.d.ts.map +1 -1
- package/dist/registry.js +52 -15
- package/dist/registry.js.map +1 -1
- package/dist/types.d.ts +275 -12
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +14 -0
- package/dist/types.js.map +1 -1
- package/dist/utils/graph.d.ts +72 -10
- package/dist/utils/graph.d.ts.map +1 -1
- package/dist/utils/graph.js +72 -10
- package/dist/utils/graph.js.map +1 -1
- package/dist/utils/validation.d.ts +73 -7
- package/dist/utils/validation.d.ts.map +1 -1
- package/dist/utils/validation.js +73 -7
- package/dist/utils/validation.js.map +1 -1
- package/package.json +7 -7
package/dist/converters.d.ts
CHANGED
|
@@ -6,22 +6,46 @@ import type { PrimitiveType, Relax as BaseRelax, Unwrap } from './types.js';
|
|
|
6
6
|
/**
|
|
7
7
|
* Unidirectional converter from one unit to another.
|
|
8
8
|
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
9
|
+
* A pure function that takes a value tagged with a source unit and returns a
|
|
10
|
+
* value tagged with a destination unit. Converters are registered in the
|
|
11
|
+
* `UnitRegistry` and composed automatically via BFS when no direct edge exists.
|
|
12
|
+
*
|
|
13
|
+
* @template TInput - Source unit-tagged type (e.g., `Celsius`)
|
|
14
|
+
* @template TOutput - Destination unit-tagged type (e.g., `Fahrenheit`)
|
|
11
15
|
*
|
|
12
16
|
* @param input - Value tagged with source unit
|
|
13
17
|
* @returns Value tagged with destination unit
|
|
14
18
|
*
|
|
15
19
|
* @remarks
|
|
16
|
-
* - Must be a pure function (no side effects)
|
|
17
|
-
* -
|
|
18
|
-
* -
|
|
20
|
+
* - Must be a pure function (no side effects, no external state reads)
|
|
21
|
+
* - Must be deterministic (same input always produces the same output)
|
|
22
|
+
* - Float arithmetic on chained conversions accumulates rounding error;
|
|
23
|
+
* document precision characteristics in the function's JSDoc
|
|
19
24
|
*
|
|
20
25
|
* @example
|
|
21
26
|
* ```typescript
|
|
22
|
-
* const c2f: Converter<Celsius, Fahrenheit> = (c) =>
|
|
23
|
-
*
|
|
27
|
+
* const c2f: Converter<Celsius, Fahrenheit> = (c) => (c * 9/5) + 32;
|
|
28
|
+
* const f2k: Converter<Fahrenheit, Kelvin> = (f) => ((f - 32) * 5/9) + 273.15;
|
|
24
29
|
* ```
|
|
30
|
+
*
|
|
31
|
+
* @useWhen You need a named, reusable conversion function to pass to
|
|
32
|
+
* `registry.register(from, to, converter)`.
|
|
33
|
+
*
|
|
34
|
+
* @avoidWhen You need both forward and reverse directions — use
|
|
35
|
+
* `BidirectionalConverter<A, B>` to register both in a single call.
|
|
36
|
+
*
|
|
37
|
+
* @pitfalls
|
|
38
|
+
* NEVER mutate the input value inside a converter — because values are
|
|
39
|
+
* primitives or phantom-typed plain objects, mutation is silent and will
|
|
40
|
+
* corrupt the original branded value at the call site.
|
|
41
|
+
*
|
|
42
|
+
* NEVER rely on closure state inside a converter for caching — the registry
|
|
43
|
+
* caches composed converters by path key, so stateful closures can lead to
|
|
44
|
+
* incorrect results on subsequent calls.
|
|
45
|
+
*
|
|
46
|
+
* @category Converters
|
|
47
|
+
* @see BidirectionalConverter
|
|
48
|
+
* @see RelaxedConverter
|
|
25
49
|
*/
|
|
26
50
|
export type Converter<TInput, TOutput> = (input: TInput) => TOutput;
|
|
27
51
|
export type RelaxConverter<ConverterType> = ConverterType extends Converter<infer A extends PrimitiveType, infer B extends PrimitiveType> ? (input: BaseRelax<A>) => BaseRelax<B> : (input: PrimitiveType) => PrimitiveType;
|
|
@@ -29,24 +53,51 @@ export type Relax<T extends PrimitiveType | Converter<any, any> | BidirectionalC
|
|
|
29
53
|
/**
|
|
30
54
|
* Bidirectional converter with forward and reverse transformations.
|
|
31
55
|
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
56
|
+
* Registers both directions in a single `registry.register(A, B, converter)` call.
|
|
57
|
+
* Under the hood, the registry splits `{ to, from }` into two unidirectional
|
|
58
|
+
* edges in the adjacency map.
|
|
59
|
+
*
|
|
60
|
+
* @template TInput - First unit type (the "from" direction)
|
|
61
|
+
* @template TOutput - Second unit type (the "to" direction)
|
|
34
62
|
*
|
|
35
63
|
* @property to - Forward converter (TInput → TOutput)
|
|
36
64
|
* @property from - Reverse converter (TOutput → TInput)
|
|
37
65
|
*
|
|
38
66
|
* @remarks
|
|
39
|
-
* - Round-trip conversions should preserve value within acceptable
|
|
40
|
-
*
|
|
41
|
-
* -
|
|
67
|
+
* - Round-trip conversions should preserve value within acceptable floating-point
|
|
68
|
+
* tolerance; test with `Math.abs(parse(format(x)) - x) < epsilon`
|
|
69
|
+
* - Both converters must be deterministic pure functions
|
|
42
70
|
*
|
|
43
71
|
* @example
|
|
44
72
|
* ```typescript
|
|
45
|
-
* const
|
|
46
|
-
* to: (
|
|
47
|
-
* from: (
|
|
73
|
+
* const celsiusFahrenheit: BidirectionalConverter<Celsius, Fahrenheit> = {
|
|
74
|
+
* to: (c) => (c * 9/5) + 32,
|
|
75
|
+
* from: (f) => (f - 32) * 5/9
|
|
48
76
|
* };
|
|
77
|
+
*
|
|
78
|
+
* const registry = createRegistry().register(CelsiusMeta, FahrenheitMeta, celsiusFahrenheit);
|
|
79
|
+
* registry.Celsius.to.Fahrenheit(0 as Celsius); // 32
|
|
80
|
+
* registry.Fahrenheit.to.Celsius(32 as Fahrenheit); // 0
|
|
49
81
|
* ```
|
|
82
|
+
*
|
|
83
|
+
* @useWhen Both conversion directions are commonly needed and you want to
|
|
84
|
+
* minimise the number of `register` calls.
|
|
85
|
+
*
|
|
86
|
+
* @avoidWhen Only one direction is needed — registering both wastes a graph
|
|
87
|
+
* edge and slightly enlarges the type-level union. Use a plain `Converter`
|
|
88
|
+
* and call `register` once.
|
|
89
|
+
*
|
|
90
|
+
* @pitfalls
|
|
91
|
+
* NEVER swap `to` and `from` in the object literal — the names are just
|
|
92
|
+
* conventions; the registry trusts the object structure, so a transposed
|
|
93
|
+
* pair silently registers the wrong direction for each edge.
|
|
94
|
+
*
|
|
95
|
+
* NEVER assume chained round-trips are exact — floating-point rounding means
|
|
96
|
+
* `from(to(x))` may differ from `x` by epsilon; use toleranced comparisons.
|
|
97
|
+
*
|
|
98
|
+
* @category Converters
|
|
99
|
+
* @see Converter
|
|
100
|
+
* @see RelaxedBidirectionalConverter
|
|
50
101
|
*/
|
|
51
102
|
export type BidirectionalConverter<TInput, TOutput> = {
|
|
52
103
|
to: Converter<TInput, TOutput>;
|
|
@@ -61,7 +112,7 @@ export type RelaxBidirectionalConverter<ConverterType> = ConverterType extends B
|
|
|
61
112
|
};
|
|
62
113
|
/**
|
|
63
114
|
* A converter that accepts the branded input type but returns
|
|
64
|
-
* unwrapped output. This eliminates the need to cast return values
|
|
115
|
+
* unwrapped (plain) output. This eliminates the need to cast return values
|
|
65
116
|
* to branded types inside converter functions, while preserving
|
|
66
117
|
* full autocompletion on the input parameter.
|
|
67
118
|
*
|
|
@@ -70,14 +121,52 @@ export type RelaxBidirectionalConverter<ConverterType> = ConverterType extends B
|
|
|
70
121
|
*
|
|
71
122
|
* @template TInput - Source unit-tagged type
|
|
72
123
|
* @template TOutput - Destination unit-tagged type
|
|
124
|
+
*
|
|
125
|
+
* @example
|
|
126
|
+
* ```typescript
|
|
127
|
+
* // No cast needed on the return value
|
|
128
|
+
* const c2f: RelaxedConverter<Celsius, Fahrenheit> = (c) => (c * 9/5) + 32;
|
|
129
|
+
* // returns number, not Fahrenheit
|
|
130
|
+
* ```
|
|
131
|
+
*
|
|
132
|
+
* @useWhen Writing converter implementations where casting the return value
|
|
133
|
+
* to a branded type is inconvenient. The registry handles branding internally.
|
|
134
|
+
*
|
|
135
|
+
* @avoidWhen The converter is used outside the registry — callers of a
|
|
136
|
+
* standalone `RelaxedConverter` receive an unwrapped value with no unit brand.
|
|
137
|
+
*
|
|
138
|
+
* @pitfalls
|
|
139
|
+
* NEVER pass a `RelaxedConverter` result directly to another function that
|
|
140
|
+
* expects a branded type without going through the registry — the unbranded
|
|
141
|
+
* return value defeats the type safety guarantee at the call site.
|
|
142
|
+
*
|
|
143
|
+
* @category Converters
|
|
144
|
+
* @see Converter
|
|
145
|
+
* @see RelaxedBidirectionalConverter
|
|
73
146
|
*/
|
|
74
147
|
export type RelaxedConverter<TInput, TOutput> = (input: TInput) => Unwrap<TOutput>;
|
|
75
148
|
/**
|
|
76
149
|
* A bidirectional converter with relaxed (unwrapped) output types.
|
|
77
|
-
* Input remains branded for full autocompletion
|
|
150
|
+
* Input remains branded for full autocompletion and type safety;
|
|
151
|
+
* return values are plain base types without branding.
|
|
78
152
|
*
|
|
79
153
|
* @template TInput - First unit-tagged type
|
|
80
154
|
* @template TOutput - Second unit-tagged type
|
|
155
|
+
*
|
|
156
|
+
* @example
|
|
157
|
+
* ```typescript
|
|
158
|
+
* const celsiusFahrenheit: RelaxedBidirectionalConverter<Celsius, Fahrenheit> = {
|
|
159
|
+
* to: (c) => (c * 9/5) + 32, // returns number, not Fahrenheit
|
|
160
|
+
* from: (f) => (f - 32) * 5/9 // returns number, not Celsius
|
|
161
|
+
* };
|
|
162
|
+
* ```
|
|
163
|
+
*
|
|
164
|
+
* @useWhen You want the ergonomics of `RelaxedConverter` (no return-value cast)
|
|
165
|
+
* for both directions in a single object, typically for `registry.register()`.
|
|
166
|
+
*
|
|
167
|
+
* @category Converters
|
|
168
|
+
* @see BidirectionalConverter
|
|
169
|
+
* @see RelaxedConverter
|
|
81
170
|
*/
|
|
82
171
|
export type RelaxedBidirectionalConverter<TInput, TOutput> = {
|
|
83
172
|
to: RelaxedConverter<TInput, TOutput>;
|
package/dist/converters.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"converters.d.ts","sourceRoot":"","sources":["../src/converters.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,KAAK,IAAI,SAAS,EAAE,MAAM,EAAE,MAAM,YAAY,CAAC;AAE5E
|
|
1
|
+
{"version":3,"file":"converters.d.ts","sourceRoot":"","sources":["../src/converters.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,KAAK,IAAI,SAAS,EAAE,MAAM,EAAE,MAAM,YAAY,CAAC;AAE5E;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2CG;AACH,MAAM,MAAM,SAAS,CAAC,MAAM,EAAE,OAAO,IAAI,CAAC,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC;AAEpE,MAAM,MAAM,cAAc,CAAC,aAAa,IACtC,aAAa,SAAS,SAAS,CAAC,MAAM,CAAC,SAAS,aAAa,EAAE,MAAM,CAAC,SAAS,aAAa,CAAC,GACzF,CAAC,KAAK,EAAE,SAAS,CAAC,CAAC,CAAC,KAAK,SAAS,CAAC,CAAC,CAAC,GACrC,CAAC,KAAK,EAAE,aAAa,KAAK,aAAa,CAAC;AAE9C,MAAM,MAAM,KAAK,CACf,CAAC,SAAS,aAAa,GAAG,SAAS,CAAC,GAAG,EAAE,GAAG,CAAC,GAAG,sBAAsB,CAAC,GAAG,EAAE,GAAG,CAAC,IAC9E,CAAC,SAAS,aAAa,GACvB,SAAS,CAAC,CAAC,CAAC,GACZ,CAAC,SAAS,SAAS,CAAC,OAAO,EAAE,OAAO,CAAC,GACnC,cAAc,CAAC,CAAC,CAAC,GACjB,2BAA2B,CAAC,CAAC,CAAC,CAAC;AAErC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgDG;AACH,MAAM,MAAM,sBAAsB,CAAC,MAAM,EAAE,OAAO,IAAI;IACpD,EAAE,EAAE,SAAS,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC/B,IAAI,EAAE,SAAS,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;CAClC,CAAC;AAEF,MAAM,MAAM,2BAA2B,CAAC,aAAa,IACnD,aAAa,SAAS,sBAAsB,CAC1C,MAAM,CAAC,SAAS,aAAa,EAC7B,MAAM,CAAC,SAAS,aAAa,CAC9B,GACG;IACE,EAAE,EAAE,CAAC,KAAK,EAAE,SAAS,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC;IAC/B,IAAI,EAAE,CAAC,KAAK,EAAE,SAAS,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC;CAClC,GACD;IACE,EAAE,EAAE,CAAC,KAAK,EAAE,aAAa,KAAK,aAAa,CAAC;IAC5C,IAAI,EAAE,CAAC,KAAK,EAAE,aAAa,KAAK,aAAa,CAAC;CAC/C,CAAC;AAER;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AACH,MAAM,MAAM,gBAAgB,CAAC,MAAM,EAAE,OAAO,IAAI,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,CAAC,OAAO,CAAC,CAAC;AAEnF;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,MAAM,MAAM,6BAA6B,CAAC,MAAM,EAAE,OAAO,IAAI;IAC3D,EAAE,EAAE,gBAAgB,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACtC,IAAI,EAAE,gBAAgB,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;CACzC,CAAC"}
|
package/dist/errors.d.ts
CHANGED
|
@@ -1,18 +1,90 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Base error class for all
|
|
2
|
+
* Base error class for all unacy errors.
|
|
3
|
+
*
|
|
4
|
+
* @remarks
|
|
5
|
+
* All unacy-specific errors extend `UnacyError`, so callers can catch the
|
|
6
|
+
* entire error family with a single `catch (e) { if (e instanceof UnacyError) ... }`
|
|
7
|
+
* guard while still discriminating by subclass when needed.
|
|
8
|
+
*
|
|
9
|
+
* `Object.setPrototypeOf` is called in the constructor to maintain a correct
|
|
10
|
+
* prototype chain in environments that compile to ES5.
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* ```typescript
|
|
14
|
+
* try {
|
|
15
|
+
* registry.convert(value, 'Celsius').to('Kelvin');
|
|
16
|
+
* } catch (e) {
|
|
17
|
+
* if (e instanceof UnacyError) {
|
|
18
|
+
* console.error('Unit conversion failed:', e.message);
|
|
19
|
+
* }
|
|
20
|
+
* }
|
|
21
|
+
* ```
|
|
22
|
+
*
|
|
23
|
+
* @category Errors
|
|
3
24
|
*/
|
|
4
25
|
export declare class UnacyError extends Error {
|
|
5
26
|
constructor(message: string);
|
|
6
27
|
}
|
|
7
28
|
/**
|
|
8
|
-
* Error thrown when a cycle is detected
|
|
29
|
+
* Error thrown when a cycle is detected during BFS path-finding.
|
|
30
|
+
*
|
|
31
|
+
* @remarks
|
|
32
|
+
* This error is thrown by `findShortestPath` when `from === to` (a unit
|
|
33
|
+
* being converted to itself). The registry's `getConverter` method re-throws
|
|
34
|
+
* `CycleError` rather than silently returning `undefined`.
|
|
35
|
+
*
|
|
36
|
+
* @example
|
|
37
|
+
* ```typescript
|
|
38
|
+
* try {
|
|
39
|
+
* registry.getConverter('Celsius', 'Celsius'); // same unit
|
|
40
|
+
* } catch (e) {
|
|
41
|
+
* if (e instanceof CycleError) {
|
|
42
|
+
* console.error('Cycle in path:', e.path.join(' → '));
|
|
43
|
+
* }
|
|
44
|
+
* }
|
|
45
|
+
* ```
|
|
46
|
+
*
|
|
47
|
+
* @pitfalls
|
|
48
|
+
* NEVER call `registry.convert(value, 'X').to('X')` — converting a unit to
|
|
49
|
+
* itself triggers cycle detection and throws `CycleError` at runtime.
|
|
50
|
+
*
|
|
51
|
+
* @category Errors
|
|
9
52
|
*/
|
|
10
53
|
export declare class CycleError extends UnacyError {
|
|
11
54
|
readonly path: PropertyKey[];
|
|
12
55
|
constructor(path: PropertyKey[]);
|
|
13
56
|
}
|
|
14
57
|
/**
|
|
15
|
-
* Error thrown when maximum conversion depth
|
|
58
|
+
* Error thrown when BFS path-finding exceeds the maximum conversion depth.
|
|
59
|
+
*
|
|
60
|
+
* @remarks
|
|
61
|
+
* The maximum depth is currently fixed at 5 hops. This prevents the BFS
|
|
62
|
+
* from exploring excessively large graphs and guards against near-cycles
|
|
63
|
+
* (long paths that would be impractical for production use anyway).
|
|
64
|
+
*
|
|
65
|
+
* If you hit this error, consider:
|
|
66
|
+
* 1. Using `allow(A, Z)` to cache the composed path once found, avoiding
|
|
67
|
+
* repeated BFS traversal.
|
|
68
|
+
* 2. Splitting a long dimension chain into domain-specific sub-registries.
|
|
69
|
+
* 3. Registering a direct edge for the problematic pair.
|
|
70
|
+
*
|
|
71
|
+
* @example
|
|
72
|
+
* ```typescript
|
|
73
|
+
* try {
|
|
74
|
+
* registry.getConverter('A', 'F'); // requires 6-hop path
|
|
75
|
+
* } catch (e) {
|
|
76
|
+
* if (e instanceof MaxDepthError) {
|
|
77
|
+
* console.error(`Path A→F exceeds max depth of ${e.maxDepth}`);
|
|
78
|
+
* }
|
|
79
|
+
* }
|
|
80
|
+
* ```
|
|
81
|
+
*
|
|
82
|
+
* @pitfalls
|
|
83
|
+
* NEVER design a conversion graph that relies on paths longer than 5 hops —
|
|
84
|
+
* `MaxDepthError` is thrown at runtime and cannot be caught as a type error.
|
|
85
|
+
* Register intermediate direct edges or use `allow()` to cap the chain.
|
|
86
|
+
*
|
|
87
|
+
* @category Errors
|
|
16
88
|
*/
|
|
17
89
|
export declare class MaxDepthError extends UnacyError {
|
|
18
90
|
readonly from: PropertyKey;
|
|
@@ -21,7 +93,36 @@ export declare class MaxDepthError extends UnacyError {
|
|
|
21
93
|
constructor(from: PropertyKey, to: PropertyKey, maxDepth: number);
|
|
22
94
|
}
|
|
23
95
|
/**
|
|
24
|
-
* Error thrown when a conversion cannot be performed
|
|
96
|
+
* Error thrown when a conversion cannot be performed.
|
|
97
|
+
*
|
|
98
|
+
* @remarks
|
|
99
|
+
* `ConversionError` is the most common error consumers encounter. It is thrown
|
|
100
|
+
* when no direct edge and no BFS-discoverable path exists between two units, or
|
|
101
|
+
* when `allow()` is called for a pair with no reachable path.
|
|
102
|
+
*
|
|
103
|
+
* Inspect `e.from` and `e.to` to determine which units are missing a path, then
|
|
104
|
+
* register the required converter with `registry.register(from, to, fn)`.
|
|
105
|
+
*
|
|
106
|
+
* @example
|
|
107
|
+
* ```typescript
|
|
108
|
+
* try {
|
|
109
|
+
* registry.convert(temp, 'Celsius').to('Miles'); // no path
|
|
110
|
+
* } catch (e) {
|
|
111
|
+
* if (e instanceof ConversionError) {
|
|
112
|
+
* console.error(`No path from ${String(e.from)} to ${String(e.to)}`);
|
|
113
|
+
* }
|
|
114
|
+
* }
|
|
115
|
+
* ```
|
|
116
|
+
*
|
|
117
|
+
* @pitfalls
|
|
118
|
+
* NEVER call `registry.convert(value, fromUnit).to(toUnit)` without handling
|
|
119
|
+
* `ConversionError` — the path may not exist even if both units are registered.
|
|
120
|
+
*
|
|
121
|
+
* NEVER confuse a missing converter with a wrong-direction converter — if
|
|
122
|
+
* `A → B` is registered but `B → A` is not, converting `B` to `A` throws
|
|
123
|
+
* `ConversionError`, not a type error.
|
|
124
|
+
*
|
|
125
|
+
* @category Errors
|
|
25
126
|
*/
|
|
26
127
|
export declare class ConversionError extends UnacyError {
|
|
27
128
|
readonly from: PropertyKey;
|
|
@@ -29,7 +130,33 @@ export declare class ConversionError extends UnacyError {
|
|
|
29
130
|
constructor(from: PropertyKey, to: PropertyKey, reason?: string);
|
|
30
131
|
}
|
|
31
132
|
/**
|
|
32
|
-
* Error thrown when parsing a string into a tagged
|
|
133
|
+
* Error thrown when parsing a string into a format-tagged value fails.
|
|
134
|
+
*
|
|
135
|
+
* @remarks
|
|
136
|
+
* Thrown by `Parser<T>` implementations (and `createParserWithSchema`) when
|
|
137
|
+
* input validation fails. Carries the format name, original input, and a
|
|
138
|
+
* human-readable reason to help callers produce user-facing error messages.
|
|
139
|
+
*
|
|
140
|
+
* Input strings longer than 50 characters are truncated with `...` in the
|
|
141
|
+
* error message to keep logs readable.
|
|
142
|
+
*
|
|
143
|
+
* @example
|
|
144
|
+
* ```typescript
|
|
145
|
+
* try {
|
|
146
|
+
* parseISO('not-a-date');
|
|
147
|
+
* } catch (e) {
|
|
148
|
+
* if (e instanceof ParseError) {
|
|
149
|
+
* console.error(`Failed to parse ${e.format}: ${e.reason}`);
|
|
150
|
+
* console.error(`Input was: ${e.input}`);
|
|
151
|
+
* }
|
|
152
|
+
* }
|
|
153
|
+
* ```
|
|
154
|
+
*
|
|
155
|
+
* @pitfalls
|
|
156
|
+
* NEVER catch `ParseError` silently and return a default value without logging —
|
|
157
|
+
* silent coercion hides data-integrity issues that may corrupt downstream state.
|
|
158
|
+
*
|
|
159
|
+
* @category Errors
|
|
33
160
|
*/
|
|
34
161
|
export declare class ParseError extends UnacyError {
|
|
35
162
|
readonly format: string;
|
package/dist/errors.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,qBAAa,UAAW,SAAQ,KAAK;IACnC,YAAY,OAAO,EAAE,MAAM,EAM1B;CACF;AAED;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,qBAAa,UAAW,SAAQ,UAAU;IACxC,SAAgB,IAAI,EAAE,WAAW,EAAE,CAAC;IAEpC,YAAY,IAAI,EAAE,WAAW,EAAE,EAK9B;CACF;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,qBAAa,aAAc,SAAQ,UAAU;IAC3C,SAAgB,IAAI,EAAE,WAAW,CAAC;IAClC,SAAgB,EAAE,EAAE,WAAW,CAAC;IAChC,SAAgB,QAAQ,EAAE,MAAM,CAAC;IAEjC,YAAY,IAAI,EAAE,WAAW,EAAE,EAAE,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,EAQ/D;CACF;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,qBAAa,eAAgB,SAAQ,UAAU;IAC7C,SAAgB,IAAI,EAAE,WAAW,CAAC;IAClC,SAAgB,EAAE,EAAE,WAAW,CAAC;IAEhC,YAAY,IAAI,EAAE,WAAW,EAAE,EAAE,EAAE,WAAW,EAAE,MAAM,CAAC,EAAE,MAAM,EAM9D;CACF;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,qBAAa,UAAW,SAAQ,UAAU;IACxC,SAAgB,MAAM,EAAE,MAAM,CAAC;IAC/B,SAAgB,KAAK,EAAE,MAAM,CAAC;IAC9B,SAAgB,MAAM,EAAE,MAAM,CAAC;IAE/B,YAAY,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EASxD;CACF"}
|
package/dist/errors.js
CHANGED
|
@@ -1,5 +1,26 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Base error class for all
|
|
2
|
+
* Base error class for all unacy errors.
|
|
3
|
+
*
|
|
4
|
+
* @remarks
|
|
5
|
+
* All unacy-specific errors extend `UnacyError`, so callers can catch the
|
|
6
|
+
* entire error family with a single `catch (e) { if (e instanceof UnacyError) ... }`
|
|
7
|
+
* guard while still discriminating by subclass when needed.
|
|
8
|
+
*
|
|
9
|
+
* `Object.setPrototypeOf` is called in the constructor to maintain a correct
|
|
10
|
+
* prototype chain in environments that compile to ES5.
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* ```typescript
|
|
14
|
+
* try {
|
|
15
|
+
* registry.convert(value, 'Celsius').to('Kelvin');
|
|
16
|
+
* } catch (e) {
|
|
17
|
+
* if (e instanceof UnacyError) {
|
|
18
|
+
* console.error('Unit conversion failed:', e.message);
|
|
19
|
+
* }
|
|
20
|
+
* }
|
|
21
|
+
* ```
|
|
22
|
+
*
|
|
23
|
+
* @category Errors
|
|
3
24
|
*/
|
|
4
25
|
export class UnacyError extends Error {
|
|
5
26
|
constructor(message) {
|
|
@@ -10,7 +31,29 @@ export class UnacyError extends Error {
|
|
|
10
31
|
}
|
|
11
32
|
}
|
|
12
33
|
/**
|
|
13
|
-
* Error thrown when a cycle is detected
|
|
34
|
+
* Error thrown when a cycle is detected during BFS path-finding.
|
|
35
|
+
*
|
|
36
|
+
* @remarks
|
|
37
|
+
* This error is thrown by `findShortestPath` when `from === to` (a unit
|
|
38
|
+
* being converted to itself). The registry's `getConverter` method re-throws
|
|
39
|
+
* `CycleError` rather than silently returning `undefined`.
|
|
40
|
+
*
|
|
41
|
+
* @example
|
|
42
|
+
* ```typescript
|
|
43
|
+
* try {
|
|
44
|
+
* registry.getConverter('Celsius', 'Celsius'); // same unit
|
|
45
|
+
* } catch (e) {
|
|
46
|
+
* if (e instanceof CycleError) {
|
|
47
|
+
* console.error('Cycle in path:', e.path.join(' → '));
|
|
48
|
+
* }
|
|
49
|
+
* }
|
|
50
|
+
* ```
|
|
51
|
+
*
|
|
52
|
+
* @pitfalls
|
|
53
|
+
* NEVER call `registry.convert(value, 'X').to('X')` — converting a unit to
|
|
54
|
+
* itself triggers cycle detection and throws `CycleError` at runtime.
|
|
55
|
+
*
|
|
56
|
+
* @category Errors
|
|
14
57
|
*/
|
|
15
58
|
export class CycleError extends UnacyError {
|
|
16
59
|
path;
|
|
@@ -22,7 +65,36 @@ export class CycleError extends UnacyError {
|
|
|
22
65
|
}
|
|
23
66
|
}
|
|
24
67
|
/**
|
|
25
|
-
* Error thrown when maximum conversion depth
|
|
68
|
+
* Error thrown when BFS path-finding exceeds the maximum conversion depth.
|
|
69
|
+
*
|
|
70
|
+
* @remarks
|
|
71
|
+
* The maximum depth is currently fixed at 5 hops. This prevents the BFS
|
|
72
|
+
* from exploring excessively large graphs and guards against near-cycles
|
|
73
|
+
* (long paths that would be impractical for production use anyway).
|
|
74
|
+
*
|
|
75
|
+
* If you hit this error, consider:
|
|
76
|
+
* 1. Using `allow(A, Z)` to cache the composed path once found, avoiding
|
|
77
|
+
* repeated BFS traversal.
|
|
78
|
+
* 2. Splitting a long dimension chain into domain-specific sub-registries.
|
|
79
|
+
* 3. Registering a direct edge for the problematic pair.
|
|
80
|
+
*
|
|
81
|
+
* @example
|
|
82
|
+
* ```typescript
|
|
83
|
+
* try {
|
|
84
|
+
* registry.getConverter('A', 'F'); // requires 6-hop path
|
|
85
|
+
* } catch (e) {
|
|
86
|
+
* if (e instanceof MaxDepthError) {
|
|
87
|
+
* console.error(`Path A→F exceeds max depth of ${e.maxDepth}`);
|
|
88
|
+
* }
|
|
89
|
+
* }
|
|
90
|
+
* ```
|
|
91
|
+
*
|
|
92
|
+
* @pitfalls
|
|
93
|
+
* NEVER design a conversion graph that relies on paths longer than 5 hops —
|
|
94
|
+
* `MaxDepthError` is thrown at runtime and cannot be caught as a type error.
|
|
95
|
+
* Register intermediate direct edges or use `allow()` to cap the chain.
|
|
96
|
+
*
|
|
97
|
+
* @category Errors
|
|
26
98
|
*/
|
|
27
99
|
export class MaxDepthError extends UnacyError {
|
|
28
100
|
from;
|
|
@@ -37,7 +109,36 @@ export class MaxDepthError extends UnacyError {
|
|
|
37
109
|
}
|
|
38
110
|
}
|
|
39
111
|
/**
|
|
40
|
-
* Error thrown when a conversion cannot be performed
|
|
112
|
+
* Error thrown when a conversion cannot be performed.
|
|
113
|
+
*
|
|
114
|
+
* @remarks
|
|
115
|
+
* `ConversionError` is the most common error consumers encounter. It is thrown
|
|
116
|
+
* when no direct edge and no BFS-discoverable path exists between two units, or
|
|
117
|
+
* when `allow()` is called for a pair with no reachable path.
|
|
118
|
+
*
|
|
119
|
+
* Inspect `e.from` and `e.to` to determine which units are missing a path, then
|
|
120
|
+
* register the required converter with `registry.register(from, to, fn)`.
|
|
121
|
+
*
|
|
122
|
+
* @example
|
|
123
|
+
* ```typescript
|
|
124
|
+
* try {
|
|
125
|
+
* registry.convert(temp, 'Celsius').to('Miles'); // no path
|
|
126
|
+
* } catch (e) {
|
|
127
|
+
* if (e instanceof ConversionError) {
|
|
128
|
+
* console.error(`No path from ${String(e.from)} to ${String(e.to)}`);
|
|
129
|
+
* }
|
|
130
|
+
* }
|
|
131
|
+
* ```
|
|
132
|
+
*
|
|
133
|
+
* @pitfalls
|
|
134
|
+
* NEVER call `registry.convert(value, fromUnit).to(toUnit)` without handling
|
|
135
|
+
* `ConversionError` — the path may not exist even if both units are registered.
|
|
136
|
+
*
|
|
137
|
+
* NEVER confuse a missing converter with a wrong-direction converter — if
|
|
138
|
+
* `A → B` is registered but `B → A` is not, converting `B` to `A` throws
|
|
139
|
+
* `ConversionError`, not a type error.
|
|
140
|
+
*
|
|
141
|
+
* @category Errors
|
|
41
142
|
*/
|
|
42
143
|
export class ConversionError extends UnacyError {
|
|
43
144
|
from;
|
|
@@ -51,7 +152,33 @@ export class ConversionError extends UnacyError {
|
|
|
51
152
|
}
|
|
52
153
|
}
|
|
53
154
|
/**
|
|
54
|
-
* Error thrown when parsing a string into a tagged
|
|
155
|
+
* Error thrown when parsing a string into a format-tagged value fails.
|
|
156
|
+
*
|
|
157
|
+
* @remarks
|
|
158
|
+
* Thrown by `Parser<T>` implementations (and `createParserWithSchema`) when
|
|
159
|
+
* input validation fails. Carries the format name, original input, and a
|
|
160
|
+
* human-readable reason to help callers produce user-facing error messages.
|
|
161
|
+
*
|
|
162
|
+
* Input strings longer than 50 characters are truncated with `...` in the
|
|
163
|
+
* error message to keep logs readable.
|
|
164
|
+
*
|
|
165
|
+
* @example
|
|
166
|
+
* ```typescript
|
|
167
|
+
* try {
|
|
168
|
+
* parseISO('not-a-date');
|
|
169
|
+
* } catch (e) {
|
|
170
|
+
* if (e instanceof ParseError) {
|
|
171
|
+
* console.error(`Failed to parse ${e.format}: ${e.reason}`);
|
|
172
|
+
* console.error(`Input was: ${e.input}`);
|
|
173
|
+
* }
|
|
174
|
+
* }
|
|
175
|
+
* ```
|
|
176
|
+
*
|
|
177
|
+
* @pitfalls
|
|
178
|
+
* NEVER catch `ParseError` silently and return a default value without logging —
|
|
179
|
+
* silent coercion hides data-integrity issues that may corrupt downstream state.
|
|
180
|
+
*
|
|
181
|
+
* @category Errors
|
|
55
182
|
*/
|
|
56
183
|
export class ParseError extends UnacyError {
|
|
57
184
|
format;
|
package/dist/errors.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"errors.js","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"errors.js","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,MAAM,OAAO,UAAW,SAAQ,KAAK;IACnC,YAAY,OAAe;QACzB,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,YAAY,CAAC;QAEzB,wDAAwD;QACxD,MAAM,CAAC,cAAc,CAAC,IAAI,EAAE,IAAI,MAAM,CAAC,SAAS,CAAC,CAAC;IACpD,CAAC;CACF;AAED;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,MAAM,OAAO,UAAW,SAAQ,UAAU;IACxB,IAAI,CAAgB;IAEpC,YAAY,IAAmB;QAC7B,MAAM,OAAO,GAAG,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QAC7C,KAAK,CAAC,sCAAsC,OAAO,EAAE,CAAC,CAAC;QACvD,IAAI,CAAC,IAAI,GAAG,YAAY,CAAC;QACzB,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;IACnB,CAAC;CACF;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,MAAM,OAAO,aAAc,SAAQ,UAAU;IAC3B,IAAI,CAAc;IAClB,EAAE,CAAc;IAChB,QAAQ,CAAS;IAEjC,YAAY,IAAiB,EAAE,EAAe,EAAE,QAAgB;QAC9D,KAAK,CACH,+BAA+B,QAAQ,kCAAkC,MAAM,CAAC,IAAI,CAAC,OAAO,MAAM,CAAC,EAAE,CAAC,EAAE,CACzG,CAAC;QACF,IAAI,CAAC,IAAI,GAAG,eAAe,CAAC;QAC5B,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;QACjB,IAAI,CAAC,EAAE,GAAG,EAAE,CAAC;QACb,IAAI,CAAC,QAAQ,GAAG,QAAQ,CAAC;IAC3B,CAAC;CACF;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,MAAM,OAAO,eAAgB,SAAQ,UAAU;IAC7B,IAAI,CAAc;IAClB,EAAE,CAAc;IAEhC,YAAY,IAAiB,EAAE,EAAe,EAAE,MAAe;QAC7D,MAAM,SAAS,GAAG,MAAM,CAAC,CAAC,CAAC,KAAK,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QAC9C,KAAK,CAAC,uBAAuB,MAAM,CAAC,IAAI,CAAC,OAAO,MAAM,CAAC,EAAE,CAAC,GAAG,SAAS,EAAE,CAAC,CAAC;QAC1E,IAAI,CAAC,IAAI,GAAG,iBAAiB,CAAC;QAC9B,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;QACjB,IAAI,CAAC,EAAE,GAAG,EAAE,CAAC;IACf,CAAC;CACF;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,MAAM,OAAO,UAAW,SAAQ,UAAU;IACxB,MAAM,CAAS;IACf,KAAK,CAAS;IACd,MAAM,CAAS;IAE/B,YAAY,MAAc,EAAE,KAAa,EAAE,MAAc;QACvD,MAAM,cAAc,GAAG,KAAK,CAAC,MAAM,GAAG,EAAE,CAAC,CAAC,CAAC,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC;QAC9E,MAAM,YAAY,GAAG,KAAK,KAAK,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,cAAc,CAAC;QAE1D,KAAK,CAAC,iBAAiB,YAAY,QAAQ,MAAM,KAAK,MAAM,EAAE,CAAC,CAAC;QAChE,IAAI,CAAC,IAAI,GAAG,YAAY,CAAC;QACzB,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;QACrB,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC;QACnB,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;IACvB,CAAC;CACF"}
|