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/types.d.ts CHANGED
@@ -9,7 +9,21 @@ import type { GetTagMetadata, Simplify, Tagged, UnwrapTagged } from 'type-fest';
9
9
  * giving us the `Tag` constraint without importing the internal `Tag` type.
10
10
  */
11
11
  type AnyTaggedUnit = Tagged<unknown, typeof UNITS, any>;
12
+ /**
13
+ * Internal phantom-type brand key used to tag values with unit metadata.
14
+ * Consumers should not use this symbol directly — use `WithUnits<T, M>` instead.
15
+ *
16
+ * @category Branding
17
+ * @internal
18
+ */
12
19
  export declare const UNITS: unique symbol;
20
+ /**
21
+ * Reserved symbol for future definition attachment on unit metadata.
22
+ * Not used in the current public API.
23
+ *
24
+ * @category Branding
25
+ * @internal
26
+ */
13
27
  export declare const DEFINITION: unique symbol;
14
28
  /**
15
29
  * Resolve a schema/constructor/enum type to its runtime value type.
@@ -19,6 +33,8 @@ export declare const DEFINITION: unique symbol;
19
33
  * - **Record schemas** → `InferFromRecordSchema` (`{ x: 'number' }` → `{ x: number }`)
20
34
  * - **Enum objects** → `T[keyof T]` (enum value type)
21
35
  * - **Primitives** → passed through unchanged
36
+ *
37
+ * @category Type Utilities
22
38
  */
23
39
  export type ResolveValueType<T> = T extends readonly string[] ? InferFromTupleSchema<T> : T extends ClassType ? InstanceType<T> : T extends RecordSchema ? T[keyof T] extends keyof PrimitiveTypeMap | RecordSchema ? InferFromRecordSchema<T> : T[keyof T] : T extends EnumType ? T[keyof T] : T;
24
40
  /**
@@ -37,6 +53,18 @@ export type ResolveValueType<T> = T extends readonly string[] ? InferFromTupleSc
37
53
  * type Celsius = WithTypedUnits<typeof CelsiusMeta>;
38
54
  * // Celsius = Tagged<number, UNITS, typeof CelsiusMeta>
39
55
  * ```
56
+ *
57
+ * @useWhen You have a metadata object with an explicit `type` field and want
58
+ * the branded type to reflect the correct base type automatically.
59
+ *
60
+ * @avoidWhen You only have a `BaseMetadata` object without a `type` field — use
61
+ * `WithUnits<T, M>` directly, specifying the base type explicitly.
62
+ *
63
+ * @pitfalls
64
+ * NEVER pass `any` as the type parameter — `WithTypedUnits<any>` widens to
65
+ * `WithUnits<any, any>`, bypassing all type checks.
66
+ *
67
+ * @category Branding
40
68
  */
41
69
  export type WithTypedUnits<M extends TypedMetadata<any>> = unknown extends M ? WithUnits<any, any> : M extends {
42
70
  name: string;
@@ -45,29 +73,89 @@ export type WithTypedUnits<M extends TypedMetadata<any>> = unknown extends M ? W
45
73
  /**
46
74
  * Brand a value with a unit identifier for compile-time unit safety.
47
75
  *
76
+ * The branding is purely a compile-time phantom — at runtime, values are plain
77
+ * `T` (number, string, etc.) with zero overhead. Branded values extend their
78
+ * base type, so they are assignable to plain `T` but not vice versa.
79
+ *
48
80
  * @template T - Base type (e.g., number, bigint, record, tuple, class instance)
49
81
  * @template M - Metadata type (must extend BaseMetadata with required name property)
50
82
  *
51
83
  * @example
52
84
  * ```typescript
53
- * const Celsius = { name: 'Celsius' as const, symbol: '°C' } satisfies BaseMetadata;
54
- * type Celsius = WithUnits<number, typeof Celsius>;
85
+ * const CelsiusMeta = { name: 'Celsius' as const, symbol: '°C' } satisfies BaseMetadata;
86
+ * type Celsius = WithUnits<number, typeof CelsiusMeta>;
55
87
  * const temp: Celsius = 25 as Celsius;
88
+ *
89
+ * // Celsius is assignable to number, but number is not assignable to Celsius
90
+ * const raw: number = temp; // OK
91
+ * const invalid: Celsius = raw; // TS error
56
92
  * ```
93
+ *
94
+ * @useWhen You need a branded unit type and your metadata does not have a `type`
95
+ * field (or you want to specify the base type explicitly).
96
+ *
97
+ * @avoidWhen Your metadata already carries a `type` field — prefer `WithTypedUnits<M>`
98
+ * to let the compiler infer the correct base type automatically.
99
+ *
100
+ * @pitfalls
101
+ * NEVER use `as` to cast a plain `number` to `WithUnits<number, M>` in
102
+ * application code without validating the value first — the cast bypasses
103
+ * every compile-time guarantee that `WithUnits` provides.
104
+ *
105
+ * NEVER assign a value of `WithUnits<number, CelsiusMetadata>` to a variable
106
+ * typed `WithUnits<number, FahrenheitMetadata>` via `as` — the phantom types
107
+ * become meaningless if the brand is forged at assignment sites.
108
+ *
109
+ * @category Branding
110
+ * @see WithTypedUnits
57
111
  */
58
112
  export type WithUnits<T, M extends BaseMetadata = BaseMetadata> = Tagged<T, typeof UNITS, M>;
59
- /** Primitive JavaScript types that can be used as unit base types. */
113
+ /**
114
+ * Primitive JavaScript types that can be used as unit base types.
115
+ *
116
+ * @remarks
117
+ * Covers the four scalar primitive types that unacy supports as direct base
118
+ * types for `WithUnits<T, M>`. Non-primitive base types (classes, enums,
119
+ * records, tuples) are handled via `SupportedType`.
120
+ *
121
+ * @category Types
122
+ */
60
123
  export type PrimitiveType = number | string | boolean | bigint;
61
124
  /**
62
125
  * A TypeScript enum object at runtime — an object whose values are all
63
126
  * strings (string enum) or all numbers (numeric enum).
64
127
  * Mixed enums (both string and number values) are rejected at validation.
128
+ *
129
+ * @remarks
130
+ * TypeScript numeric enums produce reverse-mapped keys at runtime
131
+ * (e.g., `{ DEBUG: 0, 0: 'DEBUG' }`). `validateEnum` filters those out
132
+ * automatically — you do not need to handle them yourself.
133
+ *
134
+ * @pitfalls
135
+ * NEVER use an empty object (`{}`) as an enum type — `validateEnum` rejects
136
+ * it, and the registry will throw at registration time.
137
+ *
138
+ * NEVER mix numeric and string values in a single enum — unacy rejects mixed
139
+ * enums at validation time because the value type would be ambiguous.
140
+ *
141
+ * @category Types
65
142
  */
66
143
  export type EnumType = Record<string, string | number>;
67
144
  /**
68
145
  * A class constructor (including abstract classes) that can serve as a
69
146
  * unit's type identity. At runtime, the constructor itself is stored
70
147
  * in the metadata `type` field.
148
+ *
149
+ * @remarks
150
+ * When the registry detects a class-typed unit (via `isClassMetadata`), the
151
+ * unit accessor's brand function calls `new Constructor(...args)` so that
152
+ * `registry.MyUnit(arg1, arg2)` returns a properly constructed instance.
153
+ *
154
+ * @pitfalls
155
+ * NEVER pass an arrow function or a bound function as a `ClassType` — they
156
+ * lack a `prototype` property and will fail `validateClass` at registration.
157
+ *
158
+ * @category Types
71
159
  */
72
160
  export type ClassType = abstract new (...args: any[]) => any;
73
161
  /** A record schema value — either a primitive type name or a nested schema. */
@@ -80,7 +168,17 @@ export type RecordSchemaValue = string | RecordSchema;
80
168
  * @example
81
169
  * ```typescript
82
170
  * const PointSchema = { x: 'number', y: 'number' } satisfies RecordSchema;
171
+ * const NestedSchema = { pos: { x: 'number', y: 'number' }, label: 'string' } satisfies RecordSchema;
83
172
  * ```
173
+ *
174
+ * @pitfalls
175
+ * NEVER use a circular schema object — `validateRecordSchema` detects circular
176
+ * references and throws, so the registry will refuse to register such a unit.
177
+ *
178
+ * NEVER use non-primitive type names as leaf values (e.g., `'Date'`) — only
179
+ * `'number'`, `'string'`, `'boolean'`, and `'bigint'` are accepted.
180
+ *
181
+ * @category Types
84
182
  */
85
183
  export type RecordSchema = {
86
184
  [key: string]: RecordSchemaValue;
@@ -92,12 +190,33 @@ export type RecordSchema = {
92
190
  * @example
93
191
  * ```typescript
94
192
  * const RGBSchema = ['number', 'number', 'number'] as const satisfies TupleSchema;
193
+ * const FlexSchema = ['string', 'number?', '...boolean'] as const satisfies TupleSchema;
95
194
  * ```
195
+ *
196
+ * @remarks
197
+ * When the registry detects a tuple-typed unit (via `isTupleMetadata`), the
198
+ * brand function collects spread arguments into an array. Pass each tuple
199
+ * member as a separate argument: `registry.RGB(255, 128, 0)` instead of
200
+ * `registry.RGB([255, 128, 0])`.
201
+ *
202
+ * @pitfalls
203
+ * NEVER declare a rest element (`'...type'`) in a non-terminal position —
204
+ * `validateTupleSchema` accepts it syntactically, but `InferFromTupleSchema`
205
+ * only handles rest at the last position and returns `never` otherwise.
206
+ *
207
+ * @category Types
96
208
  */
97
209
  export type TupleSchema = readonly string[];
98
210
  /**
99
211
  * Union of all types that can be used as a unit's base type.
100
212
  * Includes primitives and non-primitive categories (enum, class, record, tuple).
213
+ *
214
+ * @remarks
215
+ * `SupportedType` is the constraint on the `T` parameter of `WithUnits<T, M>`
216
+ * and on the `type` field of `TypedMetadata<T>`. The registry uses
217
+ * `detectMetadataKind` to dispatch on which member of the union is present.
218
+ *
219
+ * @category Types
101
220
  */
102
221
  export type SupportedType = PrimitiveType | EnumType | ClassType | RecordSchema | TupleSchema;
103
222
  export type PrimitiveTypeMap = {
@@ -106,6 +225,11 @@ export type PrimitiveTypeMap = {
106
225
  boolean: boolean;
107
226
  bigint: bigint;
108
227
  };
228
+ /**
229
+ * Map a primitive TypeScript type to its corresponding type name string.
230
+ * For example, `number` → `'number'`, `string` → `'string'`, `boolean` → `'boolean'`, `bigint` → `'bigint'`.
231
+ * Returns `never` for non-primitive types.
232
+ */
109
233
  export type ToPrimitiveTypeName<T> = T extends PrimitiveTypeMap[infer U extends keyof PrimitiveTypeMap] ? U : never;
110
234
  export type OptionalWithUnits<T, M extends BaseMetadata = BaseMetadata> = T | WithUnits<T, M>;
111
235
  export type Unwrap<T> = T extends AnyTaggedUnit ? UnwrapTagged<T> : T;
@@ -123,18 +247,71 @@ export type Unwrap<T> = T extends AnyTaggedUnit ? UnwrapTagged<T> : T;
123
247
  export type InferCallableArgs<From> = From extends AnyTaggedUnit ? GetTagMetadata<From, typeof UNITS> extends {
124
248
  type: infer TypeField;
125
249
  } ? TypeField extends readonly string[] ? InferFromTupleSchema<TypeField> : TypeField extends ClassType ? ConstructorParameters<TypeField> : TypeField extends RecordSchema ? TypeField[keyof TypeField] extends keyof PrimitiveTypeMap | RecordSchema ? [InferFromRecordSchema<TypeField>] : [TypeField[keyof TypeField]] : TypeField extends EnumType ? [TypeField[keyof TypeField]] : [Unwrap<From>] : [Unwrap<From>] : [Unwrap<From>];
250
+ /**
251
+ * Relax a branded unit type to accept either the branded form or its raw unwrapped value.
252
+ * Useful for APIs that should accept both `WithUnits<T, M>` and plain `T` interchangeably.
253
+ *
254
+ * @remarks
255
+ * Use `Relax<T>` in converter or utility signatures where callers may hold a
256
+ * plain value (e.g., coming from a JSON payload) alongside properly branded
257
+ * values. The type still communicates intent while remaining ergonomic.
258
+ *
259
+ * @example
260
+ * ```typescript
261
+ * function display(temp: Relax<Celsius>): string {
262
+ * return `${temp}°C`; // accepts both: Celsius brand or plain number
263
+ * }
264
+ * display(25); // OK — plain number
265
+ * display(25 as Celsius); // OK — branded
266
+ * ```
267
+ *
268
+ * @useWhen Writing utility functions that wrap or log branded values and
269
+ * should remain usable before the caller has set up a full registry.
270
+ *
271
+ * @avoidWhen Writing converter functions that must enforce the source brand —
272
+ * use the explicit branded type (`Celsius`) to preserve the safety guarantee.
273
+ *
274
+ * @category Branding
275
+ */
126
276
  export type Relax<T> = T | Unwrap<T>;
127
277
  /**
128
278
  * Brand a value with a format identifier for compile-time format safety.
129
279
  *
280
+ * Analogous to `WithUnits<T, M>` but for format tags rather than unit
281
+ * metadata. Ensures a formatted string, `Date`, or number cannot be
282
+ * passed to a formatter/parser expecting a different format.
283
+ *
130
284
  * @template T - Base type (e.g., Date, number, string)
131
- * @template F - Format identifier (e.g., 'ISO8601', 'UnixTimestamp')
285
+ * @template F - Format identifier string literal (e.g., `'ISO8601'`, `'UnixTimestamp'`)
132
286
  *
133
287
  * @example
134
288
  * ```typescript
135
- * type ISO8601 = WithFormat<Date, 'ISO8601'>;
136
- * const date: ISO8601 = new Date() as ISO8601;
289
+ * type ISO8601Date = WithFormat<Date, 'ISO8601'>;
290
+ * type UnixTimestamp = WithFormat<number, 'UnixTimestamp'>;
291
+ *
292
+ * const parseISO: Parser<ISO8601Date> = (s) => new Date(s) as ISO8601Date;
293
+ * const formatISO: Formatter<ISO8601Date> = (d) => d.toISOString();
137
294
  * ```
295
+ *
296
+ * @useWhen You need compile-time guarantees that a value has been validated
297
+ * and tagged with a specific serialisation format before it can be formatted
298
+ * or passed into format-aware APIs.
299
+ *
300
+ * @avoidWhen The value is plain and needs no format guarantee — use the bare
301
+ * base type (`Date`, `number`, `string`) directly.
302
+ *
303
+ * @pitfalls
304
+ * NEVER cast an unvalidated value to `WithFormat<T, F>` — use a `Parser`
305
+ * that validates the input string first and only then tags the result.
306
+ *
307
+ * NEVER assume that round-tripping through `format` then `parse` is
308
+ * lossless for all inputs — floating-point serialisation and timezone
309
+ * handling can introduce discrepancies.
310
+ *
311
+ * @category Branding
312
+ * @see Formatter
313
+ * @see Parser
314
+ * @see FormatterParser
138
315
  */
139
316
  export type WithFormat<T, F extends string> = Tagged<T, typeof UNITS, F>;
140
317
  export type UnitsOf<T> = T extends AnyTaggedUnit ? GetTagMetadata<T, typeof UNITS> : never;
@@ -149,21 +326,45 @@ export type MetadataOf<T> = T extends AnyTaggedUnit ? GetTagMetadata<T, typeof U
149
326
  * Base metadata type that all unit metadata must extend.
150
327
  * Requires a `name` property and allows arbitrary additional properties.
151
328
  *
329
+ * @remarks
330
+ * `name` is the registry key — it must be a string literal (`as const`) so
331
+ * that the type-level accessor map (`UnitMap`) can index by it. At runtime it
332
+ * is also the adjacency-map key used for BFS path finding, so names must be
333
+ * unique within a registry.
334
+ *
152
335
  * @example
153
336
  * ```typescript
154
- * const Celsius = {
155
- * name: 'Celsius' as const,
337
+ * const CelsiusMeta = {
338
+ * name: 'Celsius' as const, // must be a literal
156
339
  * symbol: '°C',
157
340
  * description: 'Temperature in Celsius'
158
341
  * } satisfies BaseMetadata;
159
342
  * ```
343
+ *
344
+ * @useWhen Defining metadata for a unit that only needs a name and optional
345
+ * display fields (symbol, abbreviation, description). For units where the
346
+ * base type matters in conversions, use `TypedMetadata<T>`.
347
+ *
348
+ * @pitfalls
349
+ * NEVER use a non-literal string for `name` (e.g., `name: string` instead of
350
+ * `name: 'Celsius' as const`) — the registry key becomes `string` and the
351
+ * compile-time accessor `registry.Celsius.to.Fahrenheit` stops resolving.
352
+ *
353
+ * NEVER reuse the same `name` value across two different unit objects in the
354
+ * same registry — the second registration silently overwrites the first.
355
+ *
356
+ * @config
357
+ * Additional properties (e.g., `symbol`, `abbreviation`, `description`) are
358
+ * accessible as `registry.<UnitName>.<property>` after registration.
359
+ *
360
+ * @category Metadata
160
361
  */
161
362
  export type BaseMetadata = {
162
363
  /** Unique identifier for the unit (replaces tag) */
163
364
  name: string;
164
365
  };
165
366
  /**
166
- * Metadata type for units with type information.
367
+ * Metadata type for units with explicit type information.
167
368
  *
168
369
  * For primitive types, `type` is the type name string (e.g., `'number'`).
169
370
  * For non-primitive types, `type` IS the actual value:
@@ -171,15 +372,77 @@ export type BaseMetadata = {
171
372
  * - Class: the class constructor
172
373
  * - Record: the schema object `{ x: 'number', y: 'string' }`
173
374
  * - Tuple: the tuple schema array `['number', 'string']`
375
+ *
376
+ * @template T - The `SupportedType` that this metadata describes
377
+ *
378
+ * @example
379
+ * ```typescript
380
+ * // Primitive
381
+ * const CelsiusMeta = { name: 'Celsius' as const, type: 'number' as const } satisfies TypedMetadata<number>;
382
+ *
383
+ * // Enum
384
+ * enum Direction { North, South, East, West }
385
+ * const DirectionMeta = { name: 'Direction' as const, type: Direction } satisfies TypedMetadata<typeof Direction>;
386
+ *
387
+ * // Record schema
388
+ * const PointMeta = { name: 'Point' as const, type: { x: 'number', y: 'number' } as const } satisfies TypedMetadata<{ x: number; y: number }>;
389
+ * ```
390
+ *
391
+ * @useWhen You want `WithTypedUnits<M>` to automatically resolve the correct
392
+ * base type from the metadata, avoiding the need to specify it manually.
393
+ *
394
+ * @pitfalls
395
+ * NEVER widen the `type` field to a general `string` or `object` — the type
396
+ * inference chain from `TypedMetadata` → `WithTypedUnits` → `UnitAccessor`
397
+ * depends on the literal or structural type being preserved at compile time.
398
+ *
399
+ * @config
400
+ * Additional fields beyond `name` and `type` (e.g., `symbol`, `description`)
401
+ * are permitted and accessible via the registry accessor.
402
+ *
403
+ * @category Metadata
404
+ * @see WithTypedUnits
174
405
  */
175
406
  export type TypedMetadata<T extends SupportedType> = Simplify<{
176
407
  name: string;
177
408
  type: T extends PrimitiveType ? ToPrimitiveTypeName<T> : T;
178
409
  }>;
179
410
  /**
180
- * Metadata that can be attached to units in the registry
181
- * Supports common properties like abbreviation, format, description,
182
- * and allows arbitrary custom properties via index signature
411
+ * Display and descriptive metadata that can be attached to units in the registry.
412
+ *
413
+ * Supports common properties like abbreviation, format template, description,
414
+ * and symbol, plus an index signature for arbitrary custom fields.
415
+ *
416
+ * @remarks
417
+ * `UnitMetadata` is the internal store type used by `ConverterRegistryImpl`.
418
+ * Access registered metadata via the unit accessor:
419
+ * `registry.Celsius.symbol`, `registry.Celsius.abbreviation`, etc.
420
+ *
421
+ * @example
422
+ * ```typescript
423
+ * const registry = createRegistry()
424
+ * .register({
425
+ * name: 'Celsius' as const,
426
+ * symbol: '°C',
427
+ * abbreviation: '°C',
428
+ * description: 'Degrees Celsius',
429
+ * });
430
+ *
431
+ * registry.Celsius.symbol; // '°C'
432
+ * registry.Celsius.description; // 'Degrees Celsius'
433
+ * ```
434
+ *
435
+ * @config
436
+ * - `symbol` — SI / conventional symbol (e.g., `'°C'`, `'m'`, `'kg'`)
437
+ * - `abbreviation` — short display abbreviation
438
+ * - `format` — optional format template string
439
+ * - `description` — human-readable description
440
+ * - `[key: string]` — any additional custom fields
441
+ *
442
+ * @useWhen You want to attach human-readable labels or display hints to a
443
+ * unit so consumers can render values without hard-coding strings.
444
+ *
445
+ * @category Metadata
183
446
  */
184
447
  export interface UnitMetadata {
185
448
  /** Short abbreviation for the unit (e.g., "°C", "m", "kg") */
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
package/dist/types.js CHANGED
@@ -2,6 +2,20 @@
2
2
  * Core type branding utilities for unit and format safety
3
3
  * @packageDocumentation
4
4
  */
5
+ /**
6
+ * Internal phantom-type brand key used to tag values with unit metadata.
7
+ * Consumers should not use this symbol directly — use `WithUnits<T, M>` instead.
8
+ *
9
+ * @category Branding
10
+ * @internal
11
+ */
5
12
  export const UNITS = Symbol('UNITS');
13
+ /**
14
+ * Reserved symbol for future definition attachment on unit metadata.
15
+ * Not used in the current public API.
16
+ *
17
+ * @category Branding
18
+ * @internal
19
+ */
6
20
  export const DEFINITION = Symbol('DEFINITION');
7
21
  //# sourceMappingURL=types.js.map
package/dist/types.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAWH,MAAM,CAAC,MAAM,KAAK,GAAkB,MAAM,CAAC,OAAO,CAAC,CAAC;AAEpD,MAAM,CAAC,MAAM,UAAU,GAAkB,MAAM,CAAC,YAAY,CAAC,CAAC"}
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAWH;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,KAAK,GAAkB,MAAM,CAAC,OAAO,CAAC,CAAC;AAEpD;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,UAAU,GAAkB,MAAM,CAAC,YAAY,CAAC,CAAC"}
@@ -6,21 +6,83 @@ import type { Converter } from '../converters.js';
6
6
  /**
7
7
  * Find the shortest path between two nodes using BFS.
8
8
  *
9
- * @param from - Starting node
10
- * @param to - Target node
11
- * @param adjacencyMap - Graph represented as adjacency list
12
- * @returns Array of nodes representing the shortest path, or null if no path exists
13
- * @throws {CycleError} If a cycle is detected during traversal
14
- * @throws {MaxDepthError} If path depth exceeds MAX_DEPTH
9
+ * Traverses the unit conversion graph breadth-first to find the minimum-hop
10
+ * path from `from` to `to`. The result is used by `composeConverters` to build
11
+ * a composed converter function for multi-hop conversions.
12
+ *
13
+ * @param from - Starting node (unit name key)
14
+ * @param to - Target node (unit name key)
15
+ * @param adjacencyMap - Graph represented as a nested adjacency list
16
+ * @returns Array of nodes representing the shortest path (including `from` and `to`),
17
+ * or `null` if no path exists
18
+ *
19
+ * @throws {CycleError} If `from === to` (self-conversion cycle)
20
+ * @throws {MaxDepthError} If any discovered path would exceed `MAX_DEPTH` (5) edges
21
+ *
22
+ * @remarks
23
+ * The BFS visits each node at most once (`visited` set). Self-loops are detected
24
+ * eagerly (`from === to` check) rather than during traversal.
25
+ *
26
+ * The maximum depth limit (`MAX_DEPTH = 5`) guards against sparse but deeply
27
+ * connected graphs. If you regularly need 5+ hop chains, redesign the graph by
28
+ * registering intermediate direct edges or using `allow()` to pre-compose paths.
29
+ *
30
+ * @example
31
+ * ```typescript
32
+ * // Internal usage in ConverterRegistryImpl.getConverter():
33
+ * const path = findShortestPath('Celsius', 'Fahrenheit', graph);
34
+ * // path = ['Celsius', 'Kelvin', 'Fahrenheit'] if only C→K and K→F are registered
35
+ * ```
36
+ *
37
+ * @pitfalls
38
+ * NEVER call this function directly from application code — use
39
+ * `registry.convert(value, from).to(to)` or `registry.getConverter(from, to)`,
40
+ * which add BFS caching and handle error propagation correctly.
41
+ *
42
+ * @category Graph
43
+ * @see composeConverters
15
44
  */
16
45
  export declare function findShortestPath(from: PropertyKey, to: PropertyKey, adjacencyMap: Map<PropertyKey, Map<PropertyKey, unknown>>): PropertyKey[] | null;
17
46
  /**
18
47
  * Compose multiple converters along a path into a single converter.
19
48
  *
20
- * @param path - Array of nodes representing the conversion path
21
- * @param registry - Map of converters keyed by [from, to]
22
- * @returns Composed converter function
23
- * @throws {Error} If any converter in the path is missing
49
+ * Given a path `[A, B, C]` and a registry containing `A→B` and `B→C`
50
+ * converters, produces a single `Converter<A, C>` that applies them left-to-right.
51
+ *
52
+ * @param path - Array of unit-name nodes representing the conversion path
53
+ * (at least 2 elements required: `[from, ..., to]`)
54
+ * @param registry - Adjacency map of converters keyed by source unit name
55
+ * @returns Composed converter function that applies each step in sequence
56
+ *
57
+ * @throws {Error} If the path has fewer than 2 nodes
58
+ * @throws {Error} If any converter along the path is missing from the registry
59
+ *
60
+ * @remarks
61
+ * The composition is a simple `reduce` — values pass through each converter in
62
+ * order. Each intermediate result is an unbranded `any` at runtime; the type
63
+ * safety is enforced at the call site by the registry's type signature.
64
+ *
65
+ * Floating-point precision accumulates with each hop. For chains where precision
66
+ * matters, prefer registering a direct edge over relying on BFS composition.
67
+ *
68
+ * @example
69
+ * ```typescript
70
+ * // Internal usage: path found by findShortestPath
71
+ * const composed = composeConverters(['Celsius', 'Kelvin', 'Fahrenheit'], graph);
72
+ * composed(0); // applies C→K then K→F, returning 32
73
+ * ```
74
+ *
75
+ * @pitfalls
76
+ * NEVER call this function with a path containing a unit that has no outgoing
77
+ * edge in the registry — it throws immediately with a non-descriptive `Error`.
78
+ * Always confirm paths with `findShortestPath` first.
79
+ *
80
+ * NEVER rely on composed-converter precision for high-accuracy domains (e.g.,
81
+ * financial or scientific calculations) — each hop introduces floating-point
82
+ * rounding error. Register a direct converter instead.
83
+ *
84
+ * @category Graph
85
+ * @see findShortestPath
24
86
  */
25
87
  export declare function composeConverters(path: PropertyKey[], registry: Map<PropertyKey, Map<PropertyKey, Converter<any, any>>>): Converter<any, any>;
26
88
  //# sourceMappingURL=graph.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"graph.d.ts","sourceRoot":"","sources":["../../src/utils/graph.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAOlD;;;;;;;;;GASG;AACH,wBAAgB,gBAAgB,CAC9B,IAAI,EAAE,WAAW,EACjB,EAAE,EAAE,WAAW,EACf,YAAY,EAAE,GAAG,CAAC,WAAW,EAAE,GAAG,CAAC,WAAW,EAAE,OAAO,CAAC,CAAC,GACxD,WAAW,EAAE,GAAG,IAAI,CA8CtB;AAED;;;;;;;GAOG;AACH,wBAAgB,iBAAiB,CAC/B,IAAI,EAAE,WAAW,EAAE,EACnB,QAAQ,EAAE,GAAG,CAAC,WAAW,EAAE,GAAG,CAAC,WAAW,EAAE,SAAS,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC,CAAC,GAChE,SAAS,CAAC,GAAG,EAAE,GAAG,CAAC,CA4BrB"}
1
+ {"version":3,"file":"graph.d.ts","sourceRoot":"","sources":["../../src/utils/graph.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAOlD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsCG;AACH,wBAAgB,gBAAgB,CAC9B,IAAI,EAAE,WAAW,EACjB,EAAE,EAAE,WAAW,EACf,YAAY,EAAE,GAAG,CAAC,WAAW,EAAE,GAAG,CAAC,WAAW,EAAE,OAAO,CAAC,CAAC,GACxD,WAAW,EAAE,GAAG,IAAI,CA8CtB;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwCG;AACH,wBAAgB,iBAAiB,CAC/B,IAAI,EAAE,WAAW,EAAE,EACnB,QAAQ,EAAE,GAAG,CAAC,WAAW,EAAE,GAAG,CAAC,WAAW,EAAE,SAAS,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC,CAAC,GAChE,SAAS,CAAC,GAAG,EAAE,GAAG,CAAC,CA4BrB"}
@@ -10,12 +10,41 @@ const MAX_DEPTH = 5;
10
10
  /**
11
11
  * Find the shortest path between two nodes using BFS.
12
12
  *
13
- * @param from - Starting node
14
- * @param to - Target node
15
- * @param adjacencyMap - Graph represented as adjacency list
16
- * @returns Array of nodes representing the shortest path, or null if no path exists
17
- * @throws {CycleError} If a cycle is detected during traversal
18
- * @throws {MaxDepthError} If path depth exceeds MAX_DEPTH
13
+ * Traverses the unit conversion graph breadth-first to find the minimum-hop
14
+ * path from `from` to `to`. The result is used by `composeConverters` to build
15
+ * a composed converter function for multi-hop conversions.
16
+ *
17
+ * @param from - Starting node (unit name key)
18
+ * @param to - Target node (unit name key)
19
+ * @param adjacencyMap - Graph represented as a nested adjacency list
20
+ * @returns Array of nodes representing the shortest path (including `from` and `to`),
21
+ * or `null` if no path exists
22
+ *
23
+ * @throws {CycleError} If `from === to` (self-conversion cycle)
24
+ * @throws {MaxDepthError} If any discovered path would exceed `MAX_DEPTH` (5) edges
25
+ *
26
+ * @remarks
27
+ * The BFS visits each node at most once (`visited` set). Self-loops are detected
28
+ * eagerly (`from === to` check) rather than during traversal.
29
+ *
30
+ * The maximum depth limit (`MAX_DEPTH = 5`) guards against sparse but deeply
31
+ * connected graphs. If you regularly need 5+ hop chains, redesign the graph by
32
+ * registering intermediate direct edges or using `allow()` to pre-compose paths.
33
+ *
34
+ * @example
35
+ * ```typescript
36
+ * // Internal usage in ConverterRegistryImpl.getConverter():
37
+ * const path = findShortestPath('Celsius', 'Fahrenheit', graph);
38
+ * // path = ['Celsius', 'Kelvin', 'Fahrenheit'] if only C→K and K→F are registered
39
+ * ```
40
+ *
41
+ * @pitfalls
42
+ * NEVER call this function directly from application code — use
43
+ * `registry.convert(value, from).to(to)` or `registry.getConverter(from, to)`,
44
+ * which add BFS caching and handle error propagation correctly.
45
+ *
46
+ * @category Graph
47
+ * @see composeConverters
19
48
  */
20
49
  export function findShortestPath(from, to, adjacencyMap) {
21
50
  // Handle self-conversion (cycle detection)
@@ -59,10 +88,43 @@ export function findShortestPath(from, to, adjacencyMap) {
59
88
  /**
60
89
  * Compose multiple converters along a path into a single converter.
61
90
  *
62
- * @param path - Array of nodes representing the conversion path
63
- * @param registry - Map of converters keyed by [from, to]
64
- * @returns Composed converter function
65
- * @throws {Error} If any converter in the path is missing
91
+ * Given a path `[A, B, C]` and a registry containing `A→B` and `B→C`
92
+ * converters, produces a single `Converter<A, C>` that applies them left-to-right.
93
+ *
94
+ * @param path - Array of unit-name nodes representing the conversion path
95
+ * (at least 2 elements required: `[from, ..., to]`)
96
+ * @param registry - Adjacency map of converters keyed by source unit name
97
+ * @returns Composed converter function that applies each step in sequence
98
+ *
99
+ * @throws {Error} If the path has fewer than 2 nodes
100
+ * @throws {Error} If any converter along the path is missing from the registry
101
+ *
102
+ * @remarks
103
+ * The composition is a simple `reduce` — values pass through each converter in
104
+ * order. Each intermediate result is an unbranded `any` at runtime; the type
105
+ * safety is enforced at the call site by the registry's type signature.
106
+ *
107
+ * Floating-point precision accumulates with each hop. For chains where precision
108
+ * matters, prefer registering a direct edge over relying on BFS composition.
109
+ *
110
+ * @example
111
+ * ```typescript
112
+ * // Internal usage: path found by findShortestPath
113
+ * const composed = composeConverters(['Celsius', 'Kelvin', 'Fahrenheit'], graph);
114
+ * composed(0); // applies C→K then K→F, returning 32
115
+ * ```
116
+ *
117
+ * @pitfalls
118
+ * NEVER call this function with a path containing a unit that has no outgoing
119
+ * edge in the registry — it throws immediately with a non-descriptive `Error`.
120
+ * Always confirm paths with `findShortestPath` first.
121
+ *
122
+ * NEVER rely on composed-converter precision for high-accuracy domains (e.g.,
123
+ * financial or scientific calculations) — each hop introduces floating-point
124
+ * rounding error. Register a direct converter instead.
125
+ *
126
+ * @category Graph
127
+ * @see findShortestPath
66
128
  */
67
129
  export function composeConverters(path, registry) {
68
130
  if (path.length < 2) {