unacy 0.8.1 → 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.
@@ -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
- * @template TInput - Source unit-tagged type
10
- * @template TOutput - Destination unit-tagged type
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
- * - Should be deterministic (same input same output)
18
- * - Document precision loss if applicable
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
- * ((c * 9/5) + 32) as Fahrenheit;
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
- * @template TInput - First unit type
33
- * @template TOutput - Second unit type
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 tolerance
40
- * - Both converters must be deterministic
41
- * - Use when both conversion directions are commonly needed
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 meterKilometer: BidirectionalConverter<Meters, Kilometers> = {
46
- * to: (m) => (m / 1000) as Kilometers,
47
- * from: (km) => (km * 1000) as Meters
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>;
@@ -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;;;;;;;;;;;;;;;;;;;GAmBG;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;;;;;;;;;;;;;;;;;;;;;GAqBG;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;;;;;;;;;;;GAWG;AACH,MAAM,MAAM,gBAAgB,CAAC,MAAM,EAAE,OAAO,IAAI,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,CAAC,OAAO,CAAC,CAAC;AAEnF;;;;;;GAMG;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"}
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 Unacy errors
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 in the conversion graph
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 is exceeded
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 format fails
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;
@@ -1 +1 @@
1
- {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,qBAAa,UAAW,SAAQ,KAAK;IACnC,YAAY,OAAO,EAAE,MAAM,EAM1B;CACF;AAED;;GAEG;AACH,qBAAa,UAAW,SAAQ,UAAU;IACxC,SAAgB,IAAI,EAAE,WAAW,EAAE,CAAC;IAEpC,YAAY,IAAI,EAAE,WAAW,EAAE,EAK9B;CACF;AAED;;GAEG;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;;GAEG;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;;GAEG;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"}
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 Unacy errors
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 in the conversion graph
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 is exceeded
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 format fails
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;
@@ -1 +1 @@
1
- {"version":3,"file":"errors.js","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,MAAM,OAAO,UAAW,SAAQ,KAAK;IACnC,YAAY,OAAe,EAAE;QAC3B,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;IAAA,CACnD;CACF;AAED;;GAEG;AACH,MAAM,OAAO,UAAW,SAAQ,UAAU;IACxB,IAAI,CAAgB;IAEpC,YAAY,IAAmB,EAAE;QAC/B,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;IAAA,CAClB;CACF;AAED;;GAEG;AACH,MAAM,OAAO,aAAc,SAAQ,UAAU;IAC3B,IAAI,CAAc;IAClB,EAAE,CAAc;IAChB,QAAQ,CAAS;IAEjC,YAAY,IAAiB,EAAE,EAAe,EAAE,QAAgB,EAAE;QAChE,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;IAAA,CAC1B;CACF;AAED;;GAEG;AACH,MAAM,OAAO,eAAgB,SAAQ,UAAU;IAC7B,IAAI,CAAc;IAClB,EAAE,CAAc;IAEhC,YAAY,IAAiB,EAAE,EAAe,EAAE,MAAe,EAAE;QAC/D,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;IAAA,CACd;CACF;AAED;;GAEG;AACH,MAAM,OAAO,UAAW,SAAQ,UAAU;IACxB,MAAM,CAAS;IACf,KAAK,CAAS;IACd,MAAM,CAAS;IAE/B,YAAY,MAAc,EAAE,KAAa,EAAE,MAAc,EAAE;QACzD,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;IAAA,CACtB;CACF"}
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"}