unacy 0.1.0 → 0.1.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/__tests__/converters.test.d.ts +2 -0
- package/dist/__tests__/converters.test.d.ts.map +1 -0
- package/dist/__tests__/converters.test.js +128 -0
- package/dist/__tests__/converters.test.js.map +1 -0
- package/dist/__tests__/errors.test.d.ts +2 -0
- package/dist/__tests__/errors.test.d.ts.map +1 -0
- package/dist/__tests__/errors.test.js +93 -0
- package/dist/__tests__/errors.test.js.map +1 -0
- package/dist/__tests__/formatters.test.d.ts +2 -0
- package/dist/__tests__/formatters.test.d.ts.map +1 -0
- package/dist/__tests__/formatters.test.js +244 -0
- package/dist/__tests__/formatters.test.js.map +1 -0
- package/dist/__tests__/registry.test.d.ts +2 -0
- package/dist/__tests__/registry.test.d.ts.map +1 -0
- package/dist/__tests__/registry.test.js +250 -0
- package/dist/__tests__/registry.test.js.map +1 -0
- package/dist/__tests__/types.test.d.ts +2 -0
- package/dist/__tests__/types.test.js.map +1 -1
- package/dist/converters.d.ts +53 -0
- package/dist/converters.d.ts.map +1 -0
- package/dist/converters.js +6 -0
- package/dist/converters.js.map +1 -0
- package/dist/errors.d.ts +40 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +70 -0
- package/dist/errors.js.map +1 -0
- package/dist/formatters.d.ts +82 -0
- package/dist/formatters.d.ts.map +1 -0
- package/dist/formatters.js +6 -0
- package/dist/formatters.js.map +1 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +10 -0
- package/dist/index.js.map +1 -0
- package/dist/registry.d.ts +141 -0
- package/dist/registry.d.ts.map +1 -0
- package/dist/registry.js +212 -0
- package/dist/registry.js.map +1 -0
- package/dist/types.d.ts +36 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +6 -0
- package/dist/types.js.map +1 -0
- package/dist/utils/graph.d.ts +26 -0
- package/dist/utils/graph.d.ts.map +1 -0
- package/dist/utils/graph.js +91 -0
- package/dist/utils/graph.js.map +1 -0
- package/dist/utils/validation.d.ts +26 -0
- package/dist/utils/validation.d.ts.map +1 -0
- package/dist/utils/validation.js +35 -0
- package/dist/utils/validation.js.map +1 -0
- package/package.json +7 -7
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Converter registry with auto-composition via BFS
|
|
3
|
+
* @packageDocumentation
|
|
4
|
+
*/
|
|
5
|
+
import type { Converter, BidirectionalConverter } from './converters';
|
|
6
|
+
import type { RelaxedWithUnits, UnitsFor, WithUnits } from './types';
|
|
7
|
+
/**
|
|
8
|
+
* Represents a conversion edge from one unit to another
|
|
9
|
+
*/
|
|
10
|
+
type Edge<From extends string = string, To extends string = string> = readonly [From, To];
|
|
11
|
+
/**
|
|
12
|
+
* Extract all unique 'from' units from a list of edges
|
|
13
|
+
*/
|
|
14
|
+
type FromUnits<Edges extends readonly Edge[]> = Edges[number][0];
|
|
15
|
+
/**
|
|
16
|
+
* Extract all 'to' units for a specific 'from' unit string
|
|
17
|
+
*/
|
|
18
|
+
type ToUnitsFor<Edges extends readonly Edge[], FromUnit extends string> = Extract<Edges[number], readonly [FromUnit, any]>[1];
|
|
19
|
+
/**
|
|
20
|
+
* Type for unit-based conversion accessors
|
|
21
|
+
* Provides the shape: registry.Celsius.to.Fahrenheit(value)
|
|
22
|
+
* Only allows conversions that have been registered
|
|
23
|
+
*/
|
|
24
|
+
export type ConverterMap<Edges extends readonly Edge[]> = {
|
|
25
|
+
[From in FromUnits<Edges>]: {
|
|
26
|
+
to: {
|
|
27
|
+
[To in ToUnitsFor<Edges, From>]: (value: RelaxedWithUnits<number, From>) => WithUnits<number, To>;
|
|
28
|
+
};
|
|
29
|
+
};
|
|
30
|
+
};
|
|
31
|
+
/**
|
|
32
|
+
* Registry for managing and composing unit converters
|
|
33
|
+
*/
|
|
34
|
+
export interface ConverterRegistry<Edges extends Edge[] = []> {
|
|
35
|
+
/**
|
|
36
|
+
* Register a unidirectional converter
|
|
37
|
+
*
|
|
38
|
+
* @param from - Source unit
|
|
39
|
+
* @param to - Destination unit
|
|
40
|
+
* @param converter - Converter function
|
|
41
|
+
* @returns New registry instance with the converter registered
|
|
42
|
+
*/
|
|
43
|
+
register<From extends WithUnits<number, string>, To extends WithUnits<number, string>>(from: UnitsFor<From>, to: UnitsFor<To>, converter: Converter<From, To>): ConverterRegistry<[...Edges, Edge<UnitsFor<From>, UnitsFor<To>>]> & ConverterMap<[...Edges, Edge<UnitsFor<From>, UnitsFor<To>>]>;
|
|
44
|
+
/**
|
|
45
|
+
* Register a bidirectional converter (both directions)
|
|
46
|
+
*
|
|
47
|
+
* @param from - First unit
|
|
48
|
+
* @param to - Second unit
|
|
49
|
+
* @param converter - Bidirectional converter object
|
|
50
|
+
* @returns New registry instance with both converters registered
|
|
51
|
+
*/
|
|
52
|
+
register<From extends WithUnits<number, string>, To extends WithUnits<number, string>>(from: UnitsFor<From>, to: UnitsFor<To>, converter: BidirectionalConverter<From, To>): ConverterRegistry<[
|
|
53
|
+
...Edges,
|
|
54
|
+
Edge<UnitsFor<From>, UnitsFor<To>>,
|
|
55
|
+
Edge<UnitsFor<To>, UnitsFor<From>>
|
|
56
|
+
]> & ConverterMap<[
|
|
57
|
+
...Edges,
|
|
58
|
+
Edge<UnitsFor<From>, UnitsFor<To>>,
|
|
59
|
+
Edge<UnitsFor<To>, UnitsFor<From>>
|
|
60
|
+
]>;
|
|
61
|
+
/**
|
|
62
|
+
* Register a bidirectional converter (both directions)
|
|
63
|
+
* @deprecated Use register() with BidirectionalConverter instead
|
|
64
|
+
*
|
|
65
|
+
* @param from - First unit
|
|
66
|
+
* @param to - Second unit
|
|
67
|
+
* @param converter - Bidirectional converter object
|
|
68
|
+
* @returns New registry instance with both converters registered
|
|
69
|
+
*/
|
|
70
|
+
registerBidirectional<From extends WithUnits<number, string>, To extends WithUnits<number, string>>(from: UnitsFor<From>, to: UnitsFor<To>, converter: BidirectionalConverter<From, To>): ConverterRegistry<[
|
|
71
|
+
...Edges,
|
|
72
|
+
Edge<UnitsFor<From>, UnitsFor<To>>,
|
|
73
|
+
Edge<UnitsFor<To>, UnitsFor<From>>
|
|
74
|
+
]> & ConverterMap<[
|
|
75
|
+
...Edges,
|
|
76
|
+
Edge<UnitsFor<From>, UnitsFor<To>>,
|
|
77
|
+
Edge<UnitsFor<To>, UnitsFor<From>>
|
|
78
|
+
]>;
|
|
79
|
+
/**
|
|
80
|
+
* Explicitly allow a conversion path in the type system (for multi-hop conversions)
|
|
81
|
+
*
|
|
82
|
+
* This method verifies that a conversion path exists at runtime (via BFS) and adds it
|
|
83
|
+
* to the type system so it can be used with type-safe accessor syntax.
|
|
84
|
+
*
|
|
85
|
+
* @param from - Source unit string
|
|
86
|
+
* @param to - Destination unit string
|
|
87
|
+
* @returns New registry instance with the conversion path enabled in types
|
|
88
|
+
* @throws ConversionError if no path exists between the units
|
|
89
|
+
*
|
|
90
|
+
* @example
|
|
91
|
+
* ```typescript
|
|
92
|
+
* const registry = createRegistry()
|
|
93
|
+
* .register('Celsius', 'Kelvin', c => (c + 273.15) as Kelvin)
|
|
94
|
+
* .register('Kelvin', 'Fahrenheit', k => ((k - 273.15) * 9/5 + 32) as Fahrenheit)
|
|
95
|
+
* .allow('Celsius', 'Fahrenheit'); // Enable multi-hop path in types
|
|
96
|
+
*
|
|
97
|
+
* // Now type-safe:
|
|
98
|
+
* const f = registry.Celsius.to.Fahrenheit(temp);
|
|
99
|
+
* ```
|
|
100
|
+
*/
|
|
101
|
+
allow<From extends string, To extends string>(from: From, to: To): ConverterRegistry<[...Edges, Edge<From, To>]> & ConverterMap<[...Edges, Edge<From, To>]>;
|
|
102
|
+
/**
|
|
103
|
+
* Get a converter (direct or composed via BFS)
|
|
104
|
+
*
|
|
105
|
+
* @param from - Source unit
|
|
106
|
+
* @param to - Destination unit
|
|
107
|
+
* @returns Converter function, or undefined if no path exists
|
|
108
|
+
*/
|
|
109
|
+
getConverter<From extends WithUnits<number, string>, To extends WithUnits<number, string>>(from: UnitsFor<From>, to: UnitsFor<To>): Converter<From, To> | undefined;
|
|
110
|
+
/**
|
|
111
|
+
* Convert a value using fluent API
|
|
112
|
+
*
|
|
113
|
+
* @param value - Value to convert
|
|
114
|
+
* @param fromUnit - Source unit
|
|
115
|
+
* @returns Object with to() method for conversion
|
|
116
|
+
*/
|
|
117
|
+
convert<From extends WithUnits<number, string>>(value: From, fromUnit: UnitsFor<From>): {
|
|
118
|
+
to<To extends WithUnits<number, string>>(unit: UnitsFor<To>): To;
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Create a new converter registry
|
|
123
|
+
*
|
|
124
|
+
* @returns Empty converter registry with unit-based accessors
|
|
125
|
+
*
|
|
126
|
+
* @example
|
|
127
|
+
* ```typescript
|
|
128
|
+
* type Celsius = WithUnits<number, 'Celsius'>;
|
|
129
|
+
* type Fahrenheit = WithUnits<number, 'Fahrenheit'>;
|
|
130
|
+
*
|
|
131
|
+
* const registry = createRegistry()
|
|
132
|
+
* .register('Celsius', 'Fahrenheit', (c: Celsius) => ((c * 9/5) + 32) as Fahrenheit);
|
|
133
|
+
*
|
|
134
|
+
* const temp: Celsius = 25 as Celsius;
|
|
135
|
+
* const fahrenheit = registry.Celsius.to.Fahrenheit(temp);
|
|
136
|
+
* console.log(fahrenheit); // 77
|
|
137
|
+
* ```
|
|
138
|
+
*/
|
|
139
|
+
export declare function createRegistry(): ConverterRegistry<[]> & ConverterMap<[]>;
|
|
140
|
+
export {};
|
|
141
|
+
//# sourceMappingURL=registry.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../src/registry.ts"],"names":[],"mappings":"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"}
|
package/dist/registry.js
ADDED
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Converter registry with auto-composition via BFS
|
|
3
|
+
* @packageDocumentation
|
|
4
|
+
*/
|
|
5
|
+
import { ConversionError } from './errors';
|
|
6
|
+
import { findShortestPath, composeConverters } from './utils/graph';
|
|
7
|
+
/**
|
|
8
|
+
* Internal implementation of ConverterRegistry
|
|
9
|
+
*/
|
|
10
|
+
class ConverterRegistryImpl {
|
|
11
|
+
graph;
|
|
12
|
+
pathCache;
|
|
13
|
+
unitAccessors;
|
|
14
|
+
constructor(graph, pathCache) {
|
|
15
|
+
this.graph = graph || new Map();
|
|
16
|
+
this.pathCache = pathCache || new Map();
|
|
17
|
+
this.unitAccessors = new Map();
|
|
18
|
+
// Build unit accessors dynamically
|
|
19
|
+
this.buildUnitAccessors();
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Build dynamic unit accessors for fluent API: registry.Celsius.to.Fahrenheit(value)
|
|
23
|
+
*/
|
|
24
|
+
buildUnitAccessors() {
|
|
25
|
+
// Get all unique units from the graph (both from and to)
|
|
26
|
+
const allUnits = new Set();
|
|
27
|
+
for (const from of this.graph.keys()) {
|
|
28
|
+
allUnits.add(from);
|
|
29
|
+
const toMap = this.graph.get(from);
|
|
30
|
+
if (toMap) {
|
|
31
|
+
for (const to of toMap.keys()) {
|
|
32
|
+
allUnits.add(to);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
// For each unit, create a `to` object with converter functions
|
|
37
|
+
for (const fromUnit of allUnits) {
|
|
38
|
+
const toAccessors = {};
|
|
39
|
+
for (const toUnit of allUnits) {
|
|
40
|
+
if (fromUnit !== toUnit) {
|
|
41
|
+
// Create converter function that will look up the converter at call time
|
|
42
|
+
toAccessors[toUnit] = (value) => {
|
|
43
|
+
const converter = this.getConverter(fromUnit, toUnit);
|
|
44
|
+
if (!converter) {
|
|
45
|
+
throw new ConversionError(fromUnit, toUnit, 'No converter found');
|
|
46
|
+
}
|
|
47
|
+
return converter(value);
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
// Wrap toAccessors in a Proxy to handle unknown units dynamically
|
|
52
|
+
const toProxy = new Proxy(toAccessors, {
|
|
53
|
+
get: (target, toProp) => {
|
|
54
|
+
if (typeof toProp === 'symbol') {
|
|
55
|
+
return target[toProp];
|
|
56
|
+
}
|
|
57
|
+
// If accessor exists, return it
|
|
58
|
+
if (toProp in target) {
|
|
59
|
+
return target[toProp];
|
|
60
|
+
}
|
|
61
|
+
// Otherwise, create a dynamic converter function
|
|
62
|
+
return (value) => {
|
|
63
|
+
const converter = this.getConverter(fromUnit, toProp);
|
|
64
|
+
if (!converter) {
|
|
65
|
+
throw new ConversionError(fromUnit, toProp, 'No converter found');
|
|
66
|
+
}
|
|
67
|
+
return converter(value);
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
this.unitAccessors.set(fromUnit, { to: toProxy });
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
register(from, to, converter) {
|
|
75
|
+
// Check if it's a bidirectional converter
|
|
76
|
+
if (typeof converter === 'object' && 'to' in converter && 'from' in converter) {
|
|
77
|
+
// Handle bidirectional converter
|
|
78
|
+
const biConverter = converter;
|
|
79
|
+
return this.register(from, to, biConverter.to).register(to, from, biConverter.from);
|
|
80
|
+
}
|
|
81
|
+
// Handle unidirectional converter
|
|
82
|
+
const newGraph = new Map(this.graph);
|
|
83
|
+
if (!newGraph.has(from)) {
|
|
84
|
+
newGraph.set(from, new Map());
|
|
85
|
+
}
|
|
86
|
+
const fromMap = new Map(newGraph.get(from));
|
|
87
|
+
fromMap.set(to, converter);
|
|
88
|
+
newGraph.set(from, fromMap);
|
|
89
|
+
// Return new registry instance (immutable) with proxy
|
|
90
|
+
return createRegistryFromGraph(newGraph, new Map());
|
|
91
|
+
}
|
|
92
|
+
registerBidirectional(from, to, converter) {
|
|
93
|
+
return this.register(from, to, converter.to).register(to, from, converter.from);
|
|
94
|
+
}
|
|
95
|
+
allow(from, to) {
|
|
96
|
+
// Verify that a conversion path exists at runtime
|
|
97
|
+
const converter = this.getConverter(from, to);
|
|
98
|
+
if (!converter) {
|
|
99
|
+
throw new ConversionError(from, to, 'No conversion path exists');
|
|
100
|
+
}
|
|
101
|
+
// Return the same registry instance with updated type information
|
|
102
|
+
// The actual conversion already works via BFS, we just need to expose it in types
|
|
103
|
+
return this;
|
|
104
|
+
}
|
|
105
|
+
getConverter(from, to) {
|
|
106
|
+
// Check cache first
|
|
107
|
+
const cacheKey = `${String(from)}->${String(to)}`;
|
|
108
|
+
const cached = this.pathCache.get(cacheKey);
|
|
109
|
+
if (cached) {
|
|
110
|
+
return cached;
|
|
111
|
+
}
|
|
112
|
+
// Try direct lookup (O(1))
|
|
113
|
+
const fromMap = this.graph.get(from);
|
|
114
|
+
if (fromMap) {
|
|
115
|
+
const direct = fromMap.get(to);
|
|
116
|
+
if (direct) {
|
|
117
|
+
this.pathCache.set(cacheKey, direct);
|
|
118
|
+
return direct;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
// Fallback to BFS for multi-hop path
|
|
122
|
+
try {
|
|
123
|
+
const path = findShortestPath(from, to, this.graph);
|
|
124
|
+
if (!path) {
|
|
125
|
+
return undefined;
|
|
126
|
+
}
|
|
127
|
+
const composed = composeConverters(path, this.graph);
|
|
128
|
+
this.pathCache.set(cacheKey, composed);
|
|
129
|
+
return composed;
|
|
130
|
+
}
|
|
131
|
+
catch (error) {
|
|
132
|
+
// For cycle and max depth errors, we should propagate them
|
|
133
|
+
// For other errors, return undefined
|
|
134
|
+
if (error instanceof Error &&
|
|
135
|
+
(error.constructor.name === 'CycleError' || error.constructor.name === 'MaxDepthError')) {
|
|
136
|
+
throw error;
|
|
137
|
+
}
|
|
138
|
+
return undefined;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
convert(value, fromUnit) {
|
|
142
|
+
return {
|
|
143
|
+
to: (unit) => {
|
|
144
|
+
const converter = this.getConverter(fromUnit, unit);
|
|
145
|
+
if (!converter) {
|
|
146
|
+
throw new ConversionError(fromUnit, unit, 'No converter found');
|
|
147
|
+
}
|
|
148
|
+
return converter(value);
|
|
149
|
+
}
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* Create a new converter registry
|
|
155
|
+
*
|
|
156
|
+
* @returns Empty converter registry with unit-based accessors
|
|
157
|
+
*
|
|
158
|
+
* @example
|
|
159
|
+
* ```typescript
|
|
160
|
+
* type Celsius = WithUnits<number, 'Celsius'>;
|
|
161
|
+
* type Fahrenheit = WithUnits<number, 'Fahrenheit'>;
|
|
162
|
+
*
|
|
163
|
+
* const registry = createRegistry()
|
|
164
|
+
* .register('Celsius', 'Fahrenheit', (c: Celsius) => ((c * 9/5) + 32) as Fahrenheit);
|
|
165
|
+
*
|
|
166
|
+
* const temp: Celsius = 25 as Celsius;
|
|
167
|
+
* const fahrenheit = registry.Celsius.to.Fahrenheit(temp);
|
|
168
|
+
* console.log(fahrenheit); // 77
|
|
169
|
+
* ```
|
|
170
|
+
*/
|
|
171
|
+
export function createRegistry() {
|
|
172
|
+
return createRegistryFromGraph();
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* Internal helper to create a registry with proxy from an existing graph
|
|
176
|
+
*/
|
|
177
|
+
function createRegistryFromGraph(graph, pathCache) {
|
|
178
|
+
const registryImpl = new ConverterRegistryImpl(graph, pathCache);
|
|
179
|
+
// Create a Proxy to intercept property access for unit accessors
|
|
180
|
+
return new Proxy(registryImpl, {
|
|
181
|
+
get(target, prop) {
|
|
182
|
+
// If the property exists on the registry implementation, return it
|
|
183
|
+
if (prop in target || typeof prop === 'symbol') {
|
|
184
|
+
return target[prop];
|
|
185
|
+
}
|
|
186
|
+
// Otherwise, check if it's a unit accessor
|
|
187
|
+
const unitAccessor = target.unitAccessors.get(prop);
|
|
188
|
+
if (unitAccessor) {
|
|
189
|
+
return unitAccessor;
|
|
190
|
+
}
|
|
191
|
+
// For unknown units, create a dynamic accessor that will throw ConversionError
|
|
192
|
+
// This handles cases where a unit is referenced but not in the registry
|
|
193
|
+
return {
|
|
194
|
+
to: new Proxy({}, {
|
|
195
|
+
get: (_, toProp) => {
|
|
196
|
+
if (typeof toProp === 'symbol') {
|
|
197
|
+
return undefined;
|
|
198
|
+
}
|
|
199
|
+
return (value) => {
|
|
200
|
+
const converter = target.getConverter(prop, toProp);
|
|
201
|
+
if (!converter) {
|
|
202
|
+
throw new ConversionError(prop, toProp, 'No converter found');
|
|
203
|
+
}
|
|
204
|
+
return converter(value);
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
})
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
//# sourceMappingURL=registry.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"registry.js","sourceRoot":"","sources":["../src/registry.ts"],"names":[],"mappings":"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"}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Core type branding utilities for unit and format safety
|
|
3
|
+
* @packageDocumentation
|
|
4
|
+
*/
|
|
5
|
+
import type { GetTagMetadata, Tagged } from 'type-fest';
|
|
6
|
+
export declare const UNITS: unique symbol;
|
|
7
|
+
/**
|
|
8
|
+
* Brand a value with a unit identifier for compile-time unit safety.
|
|
9
|
+
*
|
|
10
|
+
* @template T - Base type (e.g., number, bigint)
|
|
11
|
+
* @template U - Unit identifier (e.g., 'Celsius', 'meters')
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* ```typescript
|
|
15
|
+
* type Celsius = WithUnits<number, 'Celsius'>;
|
|
16
|
+
* const temp: Celsius = 25 as Celsius;
|
|
17
|
+
* ```
|
|
18
|
+
*/
|
|
19
|
+
export type WithUnits<T, U extends string> = Tagged<T, typeof UNITS, U>;
|
|
20
|
+
export type RelaxedWithUnits<T, U extends string> = T | WithUnits<T, U>;
|
|
21
|
+
export type Relax<T> = T extends WithUnits<infer U, string> ? U : T;
|
|
22
|
+
/**
|
|
23
|
+
* Brand a value with a format identifier for compile-time format safety.
|
|
24
|
+
*
|
|
25
|
+
* @template T - Base type (e.g., Date, number, string)
|
|
26
|
+
* @template F - Format identifier (e.g., 'ISO8601', 'UnixTimestamp')
|
|
27
|
+
*
|
|
28
|
+
* @example
|
|
29
|
+
* ```typescript
|
|
30
|
+
* type ISO8601 = WithFormat<Date, 'ISO8601'>;
|
|
31
|
+
* const date: ISO8601 = new Date() as ISO8601;
|
|
32
|
+
* ```
|
|
33
|
+
*/
|
|
34
|
+
export type WithFormat<T, F extends string> = Tagged<T, typeof UNITS, F>;
|
|
35
|
+
export type UnitsFor<T extends WithUnits<unknown, string>> = GetTagMetadata<T, typeof UNITS>;
|
|
36
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,EAAE,MAAM,WAAW,CAAC;AAExD,eAAO,MAAM,KAAK,EAAE,OAAO,MAAwB,CAAC;AACpD;;;;;;;;;;;GAWG;AACH,MAAM,MAAM,SAAS,CAAC,CAAC,EAAE,CAAC,SAAS,MAAM,IAAI,MAAM,CAAC,CAAC,EAAE,OAAO,KAAK,EAAE,CAAC,CAAC,CAAC;AAExE,MAAM,MAAM,gBAAgB,CAAC,CAAC,EAAE,CAAC,SAAS,MAAM,IAAI,CAAC,GAAG,SAAS,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;AAExE,MAAM,MAAM,KAAK,CAAC,CAAC,IAAI,CAAC,SAAS,SAAS,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;AAEpE;;;;;;;;;;;GAWG;AACH,MAAM,MAAM,UAAU,CAAC,CAAC,EAAE,CAAC,SAAS,MAAM,IAAI,MAAM,CAAC,CAAC,EAAE,OAAO,KAAK,EAAE,CAAC,CAAC,CAAC;AAEzE,MAAM,MAAM,QAAQ,CAAC,CAAC,SAAS,SAAS,CAAC,OAAO,EAAE,MAAM,CAAC,IAAI,cAAc,CAAC,CAAC,EAAE,OAAO,KAAK,CAAC,CAAC"}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAIH,MAAM,CAAC,MAAM,KAAK,GAAkB,MAAM,CAAC,OAAO,CAAC,CAAC"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Graph traversal utilities for converter registry
|
|
3
|
+
* @packageDocumentation
|
|
4
|
+
*/
|
|
5
|
+
import type { Converter } from '../converters';
|
|
6
|
+
/**
|
|
7
|
+
* Find the shortest path between two nodes using BFS.
|
|
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
|
|
15
|
+
*/
|
|
16
|
+
export declare function findShortestPath(from: PropertyKey, to: PropertyKey, adjacencyMap: Map<PropertyKey, Map<PropertyKey, unknown>>): PropertyKey[] | null;
|
|
17
|
+
/**
|
|
18
|
+
* Compose multiple converters along a path into a single converter.
|
|
19
|
+
*
|
|
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
|
|
24
|
+
*/
|
|
25
|
+
export declare function composeConverters(path: PropertyKey[], registry: Map<PropertyKey, Map<PropertyKey, Converter<any, any>>>): Converter<any, any>;
|
|
26
|
+
//# sourceMappingURL=graph.d.ts.map
|
|
@@ -0,0 +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,eAAe,CAAC;AAO/C;;;;;;;;;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"}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Graph traversal utilities for converter registry
|
|
3
|
+
* @packageDocumentation
|
|
4
|
+
*/
|
|
5
|
+
import { CycleError, MaxDepthError } from '../errors';
|
|
6
|
+
/**
|
|
7
|
+
* Maximum allowed conversion path depth to prevent infinite loops
|
|
8
|
+
*/
|
|
9
|
+
const MAX_DEPTH = 5;
|
|
10
|
+
/**
|
|
11
|
+
* Find the shortest path between two nodes using BFS.
|
|
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
|
|
19
|
+
*/
|
|
20
|
+
export function findShortestPath(from, to, adjacencyMap) {
|
|
21
|
+
// Handle self-conversion (cycle detection)
|
|
22
|
+
if (from === to) {
|
|
23
|
+
throw new CycleError([from, to]);
|
|
24
|
+
}
|
|
25
|
+
// BFS queue: [currentNode, path]
|
|
26
|
+
const queue = [[from, [from]]];
|
|
27
|
+
const visited = new Set();
|
|
28
|
+
visited.add(from);
|
|
29
|
+
while (queue.length > 0) {
|
|
30
|
+
const [current, path] = queue.shift();
|
|
31
|
+
// Get neighbors
|
|
32
|
+
const neighbors = adjacencyMap.get(current);
|
|
33
|
+
if (!neighbors) {
|
|
34
|
+
continue;
|
|
35
|
+
}
|
|
36
|
+
// Explore neighbors
|
|
37
|
+
for (const neighbor of neighbors.keys()) {
|
|
38
|
+
// Check max depth before extending path
|
|
39
|
+
// path currently has N nodes (N-1 edges); adding neighbor would make N+1 nodes (N edges)
|
|
40
|
+
const numEdges = path.length; // Number of edges after adding neighbor
|
|
41
|
+
if (numEdges > MAX_DEPTH) {
|
|
42
|
+
throw new MaxDepthError(from, to, MAX_DEPTH);
|
|
43
|
+
}
|
|
44
|
+
// Found target
|
|
45
|
+
if (neighbor === to) {
|
|
46
|
+
return [...path, neighbor];
|
|
47
|
+
}
|
|
48
|
+
// Cycle detection: if we've already visited this node in this path, skip it
|
|
49
|
+
if (visited.has(neighbor)) {
|
|
50
|
+
continue;
|
|
51
|
+
}
|
|
52
|
+
visited.add(neighbor);
|
|
53
|
+
queue.push([neighbor, [...path, neighbor]]);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
// No path found
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Compose multiple converters along a path into a single converter.
|
|
61
|
+
*
|
|
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
|
|
66
|
+
*/
|
|
67
|
+
export function composeConverters(path, registry) {
|
|
68
|
+
if (path.length < 2) {
|
|
69
|
+
throw new Error('Path must contain at least 2 nodes');
|
|
70
|
+
}
|
|
71
|
+
// Build array of converters
|
|
72
|
+
const converters = [];
|
|
73
|
+
for (let i = 0; i < path.length - 1; i++) {
|
|
74
|
+
const from = path[i];
|
|
75
|
+
const to = path[i + 1];
|
|
76
|
+
const converterMap = registry.get(from);
|
|
77
|
+
if (!converterMap) {
|
|
78
|
+
throw new Error(`No converters registered from ${String(from)}`);
|
|
79
|
+
}
|
|
80
|
+
const converter = converterMap.get(to);
|
|
81
|
+
if (!converter) {
|
|
82
|
+
throw new Error(`No converter registered from ${String(from)} to ${String(to)}`);
|
|
83
|
+
}
|
|
84
|
+
converters.push(converter);
|
|
85
|
+
}
|
|
86
|
+
// Compose converters: apply them left-to-right
|
|
87
|
+
return (input) => {
|
|
88
|
+
return converters.reduce((value, converter) => converter(value), input);
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
//# sourceMappingURL=graph.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"graph.js","sourceRoot":"","sources":["../../src/utils/graph.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Runtime validation helpers for parsers
|
|
3
|
+
* @packageDocumentation
|
|
4
|
+
*/
|
|
5
|
+
import type { ZodSchema } from 'zod';
|
|
6
|
+
import type { Parser } from '../formatters';
|
|
7
|
+
import type { WithFormat } from '../types';
|
|
8
|
+
/**
|
|
9
|
+
* Create a parser with Zod schema validation.
|
|
10
|
+
*
|
|
11
|
+
* @template F - Format identifier
|
|
12
|
+
* @template T - Base type
|
|
13
|
+
* @param schema - Zod schema for validation
|
|
14
|
+
* @param format - Format identifier string
|
|
15
|
+
* @returns Parser function that validates and tags values
|
|
16
|
+
*
|
|
17
|
+
* @example
|
|
18
|
+
* ```typescript
|
|
19
|
+
* const parseHex = createParserWithSchema(
|
|
20
|
+
* z.string().regex(/^#[0-9A-Fa-f]{6}$/),
|
|
21
|
+
* 'HexColor'
|
|
22
|
+
* );
|
|
23
|
+
* ```
|
|
24
|
+
*/
|
|
25
|
+
export declare function createParserWithSchema<F extends string, T>(schema: ZodSchema<T>, format: F): Parser<WithFormat<T, F>>;
|
|
26
|
+
//# sourceMappingURL=validation.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"validation.d.ts","sourceRoot":"","sources":["../../src/utils/validation.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,KAAK,CAAC;AACrC,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,eAAe,CAAC;AAC5C,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AAG3C;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,sBAAsB,CAAC,CAAC,SAAS,MAAM,EAAE,CAAC,EACxD,MAAM,EAAE,SAAS,CAAC,CAAC,CAAC,EACpB,MAAM,EAAE,CAAC,GACR,MAAM,CAAC,UAAU,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAU1B"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Runtime validation helpers for parsers
|
|
3
|
+
* @packageDocumentation
|
|
4
|
+
*/
|
|
5
|
+
import { ParseError } from '../errors';
|
|
6
|
+
/**
|
|
7
|
+
* Create a parser with Zod schema validation.
|
|
8
|
+
*
|
|
9
|
+
* @template F - Format identifier
|
|
10
|
+
* @template T - Base type
|
|
11
|
+
* @param schema - Zod schema for validation
|
|
12
|
+
* @param format - Format identifier string
|
|
13
|
+
* @returns Parser function that validates and tags values
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* ```typescript
|
|
17
|
+
* const parseHex = createParserWithSchema(
|
|
18
|
+
* z.string().regex(/^#[0-9A-Fa-f]{6}$/),
|
|
19
|
+
* 'HexColor'
|
|
20
|
+
* );
|
|
21
|
+
* ```
|
|
22
|
+
*/
|
|
23
|
+
export function createParserWithSchema(schema, format) {
|
|
24
|
+
return (input) => {
|
|
25
|
+
try {
|
|
26
|
+
const validated = schema.parse(input);
|
|
27
|
+
return validated;
|
|
28
|
+
}
|
|
29
|
+
catch (err) {
|
|
30
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
31
|
+
throw new ParseError(format, input, message);
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
//# sourceMappingURL=validation.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"validation.js","sourceRoot":"","sources":["../../src/utils/validation.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAKH,OAAO,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AAEvC;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,UAAU,sBAAsB,CACpC,MAAoB,EACpB,MAAS,EACiB;IAC1B,OAAO,CAAC,KAAa,EAAoB,EAAE,CAAC;QAC1C,IAAI,CAAC;YACH,MAAM,SAAS,GAAG,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;YACtC,OAAO,SAA6B,CAAC;QACvC,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,MAAM,OAAO,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;YACjE,MAAM,IAAI,UAAU,CAAC,MAAM,EAAE,KAAK,EAAE,OAAO,CAAC,CAAC;QAC/C,CAAC;IAAA,CACF,CAAC;AAAA,CACH"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "unacy",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"description": "Type-safe unit and format conversion library",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"private": false,
|
|
@@ -20,21 +20,21 @@
|
|
|
20
20
|
"README.md"
|
|
21
21
|
],
|
|
22
22
|
"dependencies": {
|
|
23
|
-
"type-fest": "^5.3.1"
|
|
24
|
-
"zod": "^3.24.1"
|
|
23
|
+
"type-fest": "^5.3.1"
|
|
25
24
|
},
|
|
26
25
|
"devDependencies": {
|
|
27
26
|
"@types/node": "^25.0.3",
|
|
28
|
-
"typescript": "
|
|
29
|
-
"vitest": "^4.0.16"
|
|
27
|
+
"@typescript/native-preview": "7.0.0-dev.20260107.1",
|
|
28
|
+
"vitest": "^4.0.16",
|
|
29
|
+
"zod": "^4.3.5"
|
|
30
30
|
},
|
|
31
31
|
"engines": {
|
|
32
32
|
"node": ">=20.0.0"
|
|
33
33
|
},
|
|
34
34
|
"scripts": {
|
|
35
|
-
"build": "
|
|
35
|
+
"build": "tsgo",
|
|
36
36
|
"clean": "rm -rf dist",
|
|
37
|
-
"type-check": "
|
|
37
|
+
"type-check": "tsgo --noEmit",
|
|
38
38
|
"test": "vitest run",
|
|
39
39
|
"test:watch": "vitest"
|
|
40
40
|
}
|