Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions docs/examples/genericMeasureWrapped.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { WrappedNumber, wrap } from "./genericMeasureIntro";

// START
import { createMeasureType, GenericMeasure, Unit } from "safe-units";
import { type BasisType, createMeasureType, GenericMeasure, Unit } from "safe-units";

type WrappedMeasure<B, U extends Unit<B>> = GenericMeasure<WrappedNumber, B, U>;
type WrappedMeasure<B extends BasisType, U extends Unit<B>> = GenericMeasure<WrappedNumber, B, U>;
const WrappedMeasure = createMeasureType<WrappedNumber>({
one: () => wrap(1),
neg: x => wrap(-x.value),
Expand Down
12 changes: 10 additions & 2 deletions docs/examples/genericMeasuresStaticMethods.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,15 @@
import { GenericMeasure, Mass, NumericOperations, Unit, createMeasureType, wrapUnaryFn } from "safe-units";
import {
type BasisType,
GenericMeasure,
Mass,
NumericOperations,
Unit,
createMeasureType,
wrapUnaryFn,
} from "safe-units";

class WrappedNumber {}
type WrappedMeasure<B, U extends Unit<B>> = GenericMeasure<WrappedNumber, B, U>;
type WrappedMeasure<B extends BasisType, U extends Unit<B>> = GenericMeasure<WrappedNumber, B, U>;
declare const numericOperations: NumericOperations<WrappedNumber>;

// START
Expand Down
4 changes: 2 additions & 2 deletions docs/generic-measures.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,13 @@ We can then use this class just as we would use `Measure`, except anywhere we'd
Let's deconstruct this example to explain what's going on. First we start with this type definition:

```ts
type WrappedMeasure<B, U extends Unit<B>> = GenericMeasure<WrappedNumber, B, U>;
type WrappedMeasure<B extends BasisType, U extends Unit<B>> = GenericMeasure<WrappedNumber, B, U>;
```

This line isn't strictly necessary, but it is often useful to have our `WrappedMeasure` available as a type. Having a type for the measure is useful for writing generic functions on wrapped measures. All this line does is bind the numeric type of `GenericMeasure`. Similarly, the `Measure` type has the following definition:

```ts
type Measure<B, U extends Unit<B>> = GenericMeasure<number, B, U>;
type Measure<B extends BasisType, U extends Unit<B>> = GenericMeasure<number, B, U>;
```

After we've defined the type of `WrappedMeasure` we now define the class itself by calling `createMeasureType`. This function takes an object which let's the generic measure type know how to perform operations on our numeric type. Note that for this simple example, we generally just unwrap the value, perform the arithmetic operation and then wrap it back up. Most of these operations should be self-explanatory, however some require some further explanation:
Expand Down
4 changes: 2 additions & 2 deletions src/measure/format.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { UnitSystem } from "./unitSystem";
import { type BasisType, UnitSystem } from "./unitSystem";
import { Unit } from "./unitTypeArithmetic";

type SymbolAndExponent = [symbol: string, exponent: number];

export function defaultFormatUnit<Basis>(unit: Unit<Basis>, unitSystem: UnitSystem<Basis>): string {
export function defaultFormatUnit<Basis extends BasisType>(unit: Unit<Basis>, unitSystem: UnitSystem<Basis>): string {
const positive: SymbolAndExponent[] = [];
const negative: SymbolAndExponent[] = [];
unitSystem.getDimensions().forEach(dimension => {
Expand Down
6 changes: 3 additions & 3 deletions src/measure/genericMeasure.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { UnitSystem } from "./unitSystem";
import { type BasisType, UnitSystem } from "./unitSystem";
import { CubeUnit, DivideUnits, MultiplyUnits, ReciprocalUnit, SquareUnit, Unit } from "./unitTypeArithmetic";

export interface MeasureFormatter<N> {
formatValue?: (value: N) => string;
formatUnit?: <Basis>(unit: Unit<Basis>, unitSystem: UnitSystem<Basis>) => string;
formatUnit?: <Basis extends BasisType>(unit: Unit<Basis>, unitSystem: UnitSystem<Basis>) => string;
}

/** The set of numeric operations required to fully represent a `GenericMeasure` for a given numeric type */
Expand All @@ -29,7 +29,7 @@ export interface NumericOperations<N> {
}

/** A numeric value with a corresponding unit of measurement. */
export interface GenericMeasure<N, Basis, U extends Unit<Basis>> {
export interface GenericMeasure<N, Basis extends BasisType, U extends Unit<Basis>> {
/** The numeric value of this measure */
readonly value: N;
/** The unit of this measure */
Expand Down
6 changes: 3 additions & 3 deletions src/measure/genericMeasureClass.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { defaultFormatUnit } from "./format";
import { GenericMeasure, MeasureFormatter, NumericOperations } from "./genericMeasure";
import { UnitSystem } from "./unitSystem";
import { type BasisType, UnitSystem } from "./unitSystem";
import { DivideUnits, MultiplyUnits, ReciprocalUnit, SquareUnit, Unit, CubeUnit } from "./unitTypeArithmetic";

interface GenericMeasureClass<N> {
createMeasure: <Basis, U extends Unit<Basis>>(
createMeasure: <Basis extends BasisType, U extends Unit<Basis>>(
value: N,
unit: U,
unitSystem: UnitSystem<Basis>,
Expand All @@ -28,7 +28,7 @@ export function createMeasureClass<N>(num: NumericOperations<N>): GenericMeasure
}
}

class Measure<Basis, U extends Unit<Basis>> implements GenericMeasure<N, Basis, U> {
class Measure<Basis extends BasisType, U extends Unit<Basis>> implements GenericMeasure<N, Basis, U> {
constructor(
public readonly value: N,
public readonly unit: U,
Expand Down
11 changes: 7 additions & 4 deletions src/measure/genericMeasureFactory.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { GenericMeasure, NumericOperations } from "./genericMeasure";
import { createMeasureClass } from "./genericMeasureClass";
import { GenericMeasureStatic, getGenericMeasureStaticMethods } from "./genericMeasureStatic";
import { UnitSystem } from "./unitSystem";
import { type BasisType, UnitSystem } from "./unitSystem";
import { DimensionUnit, DimensionlessUnit, Unit } from "./unitTypeArithmetic";

/** The functions needed to construct a measure of a given numeric type */
Expand All @@ -16,7 +16,7 @@ interface GenericMeasureFactory<N> {
* @param symbol the symbol of the base unit of the dimension (e.g. "m")
* @returns A measure representing 1 base unit of the dimension (1 m)
*/
dimension<Basis, Dimension extends keyof Basis>(
dimension<Basis extends BasisType, Dimension extends keyof Basis>(
unitSystem: UnitSystem<Basis>,
dimension: Dimension,
symbol?: string,
Expand All @@ -28,7 +28,10 @@ interface GenericMeasureFactory<N> {
* @param value the value of the measure
* @returns a measure with no dimensions
*/
dimensionless<Basis>(unitSystem: UnitSystem<Basis>, value: N): GenericMeasure<N, Basis, DimensionlessUnit<Basis>>;
dimensionless<Basis extends BasisType>(
unitSystem: UnitSystem<Basis>,
value: N,
): GenericMeasure<N, Basis, DimensionlessUnit<Basis>>;

/**
* Creates a measure as a multiple of another measure.
Expand All @@ -37,7 +40,7 @@ interface GenericMeasureFactory<N> {
* @param symbol an optional unit symbol for this measure
* @returns a measure of value number of quantities.
*/
of<Basis, U extends Unit<Basis>>(
of<Basis extends BasisType, U extends Unit<Basis>>(
value: N,
quantity: GenericMeasure<N, Basis, U>,
symbol?: string,
Expand Down
5 changes: 3 additions & 2 deletions src/measure/genericMeasureStatic.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { GenericMeasure, NumericOperations } from "./genericMeasure";
import { BinaryFn, PrefixFn, SpreadFn, wrapBinaryFn, wrapReducerFn } from "./genericMeasureUtils";
import type { BasisType } from "./unitSystem";
import { DivideUnits, MultiplyUnits, Unit } from "./unitTypeArithmetic";

export interface GenericMeasureStatic<N> {
Expand All @@ -19,13 +20,13 @@ export interface GenericMeasureStatic<N> {
subtract: BinaryFn<N>;

/** Static version of `left.times(right)` */
multiply<Basis, Left extends Unit<Basis>, Right extends Unit<Basis>>(
multiply<Basis extends BasisType, Left extends Unit<Basis>, Right extends Unit<Basis>>(
left: GenericMeasure<N, Basis, Left>,
right: GenericMeasure<N, Basis, Right>,
): GenericMeasure<N, Basis, MultiplyUnits<Basis, Left, Right>>;

/** Static version of `left.div(right)` */
divide<Basis, Left extends Unit<Basis>, Right extends Unit<Basis>>(
divide<Basis extends BasisType, Left extends Unit<Basis>, Right extends Unit<Basis>>(
left: GenericMeasure<N, Basis, Left>,
right: GenericMeasure<N, Basis, Right>,
): GenericMeasure<N, Basis, DivideUnits<Basis, Left, Right>>;
Expand Down
9 changes: 5 additions & 4 deletions src/measure/genericMeasureUtils.ts
Original file line number Diff line number Diff line change
@@ -1,24 +1,25 @@
import { GenericMeasure } from "./genericMeasure";
import type { BasisType } from "./unitSystem";
import { Unit } from "./unitTypeArithmetic";

/** A function which applies a symbol prefix and multiplier to a given measure. */
export type PrefixFn<N = number> = <Basis, U extends Unit<Basis>>(
export type PrefixFn<N = number> = <Basis extends BasisType, U extends Unit<Basis>>(
measure: GenericMeasure<N, Basis, U>,
) => GenericMeasure<N, Basis, U>;

/** A function which transforms a single measure into another measure with the same unit. */
export type UnaryFn<N = number> = <Basis, U extends Unit<Basis>>(
export type UnaryFn<N = number> = <Basis extends BasisType, U extends Unit<Basis>>(
x: GenericMeasure<N, Basis, U>,
) => GenericMeasure<N, Basis, U>;

/** A function which transforms two measures with same unit into a single measure with the same unit. */
export type BinaryFn<N = number> = <Basis, U extends Unit<any>>(
export type BinaryFn<N = number> = <Basis extends BasisType, U extends Unit<any>>(
left: GenericMeasure<N, Basis, U>,
right: GenericMeasure<N, Basis, U>,
) => GenericMeasure<N, Basis, U>;

/** A function which transforms one or more measure with the same unit into a single measure with the same unit. */
export type SpreadFn<N = number> = <Basis, U extends Unit<Basis>>(
export type SpreadFn<N = number> = <Basis extends BasisType, U extends Unit<Basis>>(
first: GenericMeasure<N, Basis, U>,
...rest: Array<GenericMeasure<N, Basis, U>>
) => GenericMeasure<N, Basis, U>;
Expand Down
2 changes: 1 addition & 1 deletion src/measure/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ export {
wrapUnaryFn,
} from "./genericMeasureUtils";
export { Measure } from "./numberMeasure";
export { UnitSystem } from "./unitSystem";
export { BasisType, UnitSystem } from "./unitSystem";
export {
CubeUnit,
DimensionlessUnit,
Expand Down
3 changes: 2 additions & 1 deletion src/measure/numberMeasure.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { GenericMeasure, NumericOperations } from "./genericMeasure";
import { createMeasureType, GenericMeasureType } from "./genericMeasureFactory";
import { SpreadFn, UnaryFn, wrapSpreadFn, wrapUnaryFn } from "./genericMeasureUtils";
import type { BasisType } from "./unitSystem";
import { Unit } from "./unitTypeArithmetic";

interface MeasureStaticMethods {
Expand Down Expand Up @@ -35,5 +36,5 @@ const numericOps: NumericOperations<number> = {
format: x => `${x}`,
};

export type Measure<Basis, U extends Unit<Basis>> = GenericMeasure<number, Basis, U>;
export type Measure<Basis extends BasisType, U extends Unit<Basis>> = GenericMeasure<number, Basis, U>;
export const Measure: GenericMeasureType<number, MeasureStaticMethods> = createMeasureType(numericOps, staticMethods);
12 changes: 9 additions & 3 deletions src/measure/unitSystem.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,13 @@ import {
Unit,
} from "./unitTypeArithmetic";

type UnitSystemResult<Basis> = [Basis[keyof Basis]] extends [string]
/**
* The keys of a type system's basis are the names of the dimensions, and the values are the symbols
* of each dimension's base unit.
*/
export type BasisType = Record<string, string>;

type UnitSystemResult<Basis extends BasisType> = [Basis[keyof Basis]] extends [string]
? UnitSystem<Basis>
: `Dimension '${NonStringValuedKeys<Basis>}' does not have a valid symbol`;

Expand All @@ -20,7 +26,7 @@ type NonStringValuedKeys<T> = keyof {
* are defined by the keys of the Basis type parameter. The base units are given by the symbol map passed into the
* constructor.
*/
export class UnitSystem<Basis> implements UnitSystem<Basis> {
export class UnitSystem<Basis extends BasisType> implements UnitSystem<Basis> {
private readonly dimensions: Array<keyof Basis>;

/**
Expand Down Expand Up @@ -48,7 +54,7 @@ export class UnitSystem<Basis> implements UnitSystem<Basis> {
* ...
* });
*/
public static from<Basis>(symbols: Basis): UnitSystemResult<Basis> {
public static from<Basis extends BasisType>(symbols: Basis): UnitSystemResult<Basis> {
return new UnitSystem(symbols) as UnitSystemResult<Basis>;
}

Expand Down
2 changes: 1 addition & 1 deletion test/measureTypeTests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,5 +84,5 @@ const validUnitSystem = UnitSystem.from({ length: "m", mass: "kg", time: "s" } a
expectTrue(value(validUnitSystem).hasType<UnitSystem<{ length: "m"; mass: "kg"; time: "s" }>>());

const errorBasis = { length: "m", mass: 3, time: "kg" };
// @ts-expect-error errorBasis is not a valid basis for a type system
const errorUnitSystem = UnitSystem.from(errorBasis);
expectTrue(value(errorUnitSystem).hasType<"Dimension 'mass' does not have a valid symbol">());
12 changes: 2 additions & 10 deletions tsconfig.base.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,5 @@
"es2015",
"es2015.core"
]
},
"include": [
"src"
],
"exclude": [
"dist",
"node_modules",
"test"
]
}
}
}
4 changes: 2 additions & 2 deletions tsconfig.cjs.json
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
{
"extends": "./tsconfig.base.json",
"extends": "./tsconfig.src.base.json",
"compilerOptions": {
"module": "commonjs",
"outDir": "./dist/cjs",
"target": "ES2015"
}
}
}
4 changes: 2 additions & 2 deletions tsconfig.esm.json
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
{
"extends": "./tsconfig.base.json",
"extends": "./tsconfig.src.base.json",
"compilerOptions": {
"module": "es6",
"outDir": "./dist/esm",
"target": "es6"
}
}
}
11 changes: 11 additions & 0 deletions tsconfig.src.base.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"extends": "./tsconfig.base.json",
"include": [
"src"
],
"exclude": [
"dist",
"node_modules",
"test"
]
}