Skip to content
Merged
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
12 changes: 6 additions & 6 deletions src/map.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,12 @@ interface KeyValuePair<K, V> {
/**
* A Map implementation that supports deep equality for object keys.
*/
export class DeepMap<K, V> extends Map<K, V> implements Comparable<DeepMap<K, V>> {
private readonly normalizer: Normalizer<K, V>;
private readonly map: Map<Normalized<K>, KeyValuePair<K, V>>;
export class DeepMap<K, V, TxK = K, TxV = V> extends Map<K, V> implements Comparable<DeepMap<K, V, TxK, TxV>> {
private readonly normalizer: Normalizer<K, V, TxK, TxV>;
private readonly map: Map<Normalized<TxK>, KeyValuePair<K, V>>;

// NOTE: This is actually a thin wrapper. We're not using super other than to drive the (typed) API contract.
constructor(entries?: readonly (readonly [K, V])[] | null, options: Options<K, V> = {}) {
constructor(entries?: readonly (readonly [K, V])[] | null, options: Options<K, V, TxK, TxV> = {}) {
super();
this.normalizer = new Normalizer(options);
const transformedEntries = entries
Expand Down Expand Up @@ -142,11 +142,11 @@ export class DeepMap<K, V> extends Map<K, V> implements Comparable<DeepMap<K, V>

// PRIVATE METHODS FOLLOW...

private normalizeKey(input: K): Normalized<K> {
private normalizeKey(input: K): Normalized<TxK> {
return this.normalizer.normalizeKey(input);
}

private normalizeValue(input: V): Normalized<V> {
private normalizeValue(input: V): Normalized<TxV> {
return this.normalizer.normalizeValue(input);
}
}
14 changes: 7 additions & 7 deletions src/normalizer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,17 +11,17 @@ type HashedObject = string;
/**
* Type for normalized input.
*/
export type Normalized<T> = HashedObject | T | ReturnType<TransformFunction<T>>;
export type Normalized<T> = HashedObject | T;

/**
* Class that normalizes object types to strings via hashing
*/
export class Normalizer<K, V> {
export class Normalizer<K, V, TxK, TxV> {
private readonly objectHashOptions: ObjectHashOptions;
private readonly keyTransformer: TransformFunction<K>;
private readonly valueTransformer: TransformFunction<V>;
private readonly keyTransformer: TransformFunction<K, TxK>;
private readonly valueTransformer: TransformFunction<V, TxV>;

constructor(options: Options<K, V> = {}) {
constructor(options: Options<K, V, TxK, TxV> = {}) {
const { transformer, mapValueTransformer, useToJsonTransform, ...objectHashOptions } =
getOptionsWithDefaults(options);
this.keyTransformer = useToJsonTransform ? Transformers.jsonSerializeDeserialize : transformer;
Expand All @@ -34,7 +34,7 @@ export class Normalizer<K, V> {
* @param input the input to normalize
* @returns the normalized result
*/
normalizeKey(input: K): Normalized<K> {
normalizeKey(input: K): Normalized<TxK> {
return this.normalizeHelper(this.keyTransformer(input));
}

Expand All @@ -43,7 +43,7 @@ export class Normalizer<K, V> {
* @param input the input to normalize
* @returns the normalized result
*/
normalizeValue(input: V): Normalized<V> {
normalizeValue(input: V): Normalized<TxV> {
return this.normalizeHelper(this.valueTransformer(input));
}

Expand Down
14 changes: 7 additions & 7 deletions src/options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,20 +6,20 @@ import { Require } from './utils';
/**
* Library options
*/
interface DeepEqualityDataStructuresOptions<K, V> {
interface DeepEqualityDataStructuresOptions<K, V, TxK, TxV> {
/**
* A function that transforms Map keys or Set values prior to normalization.
*
* NOTE: The caller is responsible for not mutating object inputs.
*/
transformer?: TransformFunction<K>;
transformer?: TransformFunction<K, TxK>;

/**
* A function that transforms Map values prior to normalization.
*
* NOTE: The caller is responsible for not mutating object inputs.
*/
mapValueTransformer?: TransformFunction<V>;
mapValueTransformer?: TransformFunction<V, TxV>;

/**
* If true, objects will be JSON-serialized/deserialized into "plain" objects prior to hashing.
Expand All @@ -29,14 +29,14 @@ interface DeepEqualityDataStructuresOptions<K, V> {
useToJsonTransform?: boolean;
}

export type Options<K, V> = ObjectHashOptions & DeepEqualityDataStructuresOptions<K, V>;
export type Options<K, V, TxK, TxV> = ObjectHashOptions & DeepEqualityDataStructuresOptions<K, V, TxK, TxV>;

/**
* Given the specified options, resolve default values as appropriate.
*/
export function getOptionsWithDefaults<K, V>(
options: Options<K, V>
): Require<Options<K, V>, keyof DeepEqualityDataStructuresOptions<K, V>> {
export function getOptionsWithDefaults<K, V, TxK, TxV>(
options: Options<K, V, TxK, TxV>
): Require<Options<K, V, TxK, TxV>, keyof DeepEqualityDataStructuresOptions<K, V, TxK, TxV>> {
return {
// Default options
algorithm: 'md5' as const, // not a cryptographic usage, who cares
Expand Down
24 changes: 12 additions & 12 deletions src/set.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,12 @@ import { Options } from './options';
/**
* A Set implementation that supports deep equality for values.
*/
export class DeepSet<T> extends Set<T> implements Comparable<DeepSet<T>> {
export class DeepSet<V, TxV = V> extends Set<V> implements Comparable<DeepSet<V, TxV>> {
// Just piggy-back on a DeepMap that uses null values
private readonly map: DeepMap<T, null>;
private readonly map: DeepMap<V, null, TxV, null>;

// NOTE: This is actually a thin wrapper. We're not using super other than to drive the (typed) API contract.
constructor(values?: readonly T[] | null, options?: Options<T, null>) {
constructor(values?: readonly V[] | null, options?: Options<V, null, TxV, null>) {
super();
const transformedEntries = values ? values.map((el) => [el, null] as const) : null;
this.map = new DeepMap(transformedEntries, options);
Expand All @@ -26,22 +26,22 @@ export class DeepSet<T> extends Set<T> implements Comparable<DeepSet<T>> {
/**
* Returns true if the given value is present in the set.
*/
override has(key: T): boolean {
return this.map.has(key);
override has(val: V): boolean {
return this.map.has(val);
}

/**
* Store the given value.
*/
override add(val: T): this {
override add(val: V): this {
this.map.set(val, null);
return this;
}

/**
* Deletes the specified value.
*/
override delete(val: T): boolean {
override delete(val: V): boolean {
return this.map.delete(val);
}

Expand All @@ -55,7 +55,7 @@ export class DeepSet<T> extends Set<T> implements Comparable<DeepSet<T>> {
/**
* Standard forEach function.
*/
override forEach(callbackfn: (val: T, val2: T, set: Set<T>) => void): void {
override forEach(callbackfn: (val: V, val2: V, set: Set<V>) => void): void {
this.map.forEach((_mapVal, mapKey, _map) => {
callbackfn(mapKey, mapKey, this);
});
Expand All @@ -66,7 +66,7 @@ export class DeepSet<T> extends Set<T> implements Comparable<DeepSet<T>> {
*
* @yields the next value in the set
*/
override *[Symbol.iterator](): IterableIterator<T> {
override *[Symbol.iterator](): IterableIterator<V> {
for (const [key, _val] of this.map[Symbol.iterator]()) {
yield key;
}
Expand All @@ -77,7 +77,7 @@ export class DeepSet<T> extends Set<T> implements Comparable<DeepSet<T>> {
*
* @yields the next value-value pair in the set
*/
override *entries(): IterableIterator<[T, T]> {
override *entries(): IterableIterator<[V, V]> {
for (const val of this[Symbol.iterator]()) {
yield [val, val];
}
Expand All @@ -88,7 +88,7 @@ export class DeepSet<T> extends Set<T> implements Comparable<DeepSet<T>> {
*
* @yields the next key in the map
*/
override *keys(): IterableIterator<T> {
override *keys(): IterableIterator<V> {
for (const val of this[Symbol.iterator]()) {
yield val;
}
Expand All @@ -99,7 +99,7 @@ export class DeepSet<T> extends Set<T> implements Comparable<DeepSet<T>> {
*
* @yields the next value in the map
*/
override *values(): IterableIterator<T> {
override *values(): IterableIterator<V> {
yield* this.keys();
}

Expand Down
9 changes: 5 additions & 4 deletions src/transformers.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
export type TransformFunction<T> = (input: T) => unknown;
export type TransformFunction<T, R> = (input: T) => R;

export class Transformers {
static identity<T>(input: T): T {
return input;
static identity<T, R = T>(input: T): R {
// Just make the types happy :)
return input as unknown as R;
}

static jsonSerializeDeserialize<T>(obj: T): T {
static jsonSerializeDeserialize<T, R = T>(obj: T): R {
return JSON.parse(JSON.stringify(obj));
}
}