-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathadapted.ts
134 lines (112 loc) · 4.06 KB
/
adapted.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
import {JsonCompatible, JsonValue} from '@croct/json';
import {hasher, HasherOptions} from 'node-object-hash';
import {CacheLoader, CacheProvider} from './cacheProvider';
export type Transformer<D, S> = (value: D) => Promise<S> | S;
export type HashAlgorithm = 'passthrough' | 'md5' | 'sha1';
const HASHER = hasher({
// Support classes and instances like `ResourceId`
coerce: true,
// Do not differentiate Objects, Sets and Maps constructed in different order
// `new Set([1,2,3])` should match `new Set([3,2,1])`
sort: {
array: false,
typedArray: false,
object: true,
set: true,
map: true,
bigint: true,
},
// Do not trim values, "foo " and "foo" should not match as the same key
trim: false,
});
type Configuration<K, V, IK, IV> = {
cache: CacheProvider<IK, IV>,
keyTransformer: Transformer<K, IK>,
valueInputTransformer: Transformer<V, IV>,
valueOutputTransformer: Transformer<IV, V>,
};
function identity<T>(value: T): T {
return value;
}
/**
* A cache provider to transform keys and values between composition layers.
*/
export class AdaptedCache<K, V, IK = K, IV = V> implements CacheProvider<K, V> {
private readonly cache: CacheProvider<IK, IV>;
private readonly keyTransformer: Transformer<K, IK>;
private readonly valueInputTransformer: Transformer<V, IV>;
private readonly valueOutputTransformer: Transformer<IV, V>;
public constructor(config: Configuration<K, V, IK, IV>) {
this.cache = config.cache;
this.keyTransformer = config.keyTransformer;
this.valueInputTransformer = config.valueInputTransformer;
this.valueOutputTransformer = config.valueOutputTransformer;
}
public static transformKeys<K, IK, V>(
cache: CacheProvider<IK, V>,
keyTransformer: Transformer<K, IK>,
): AdaptedCache<K, V, IK, V> {
return new AdaptedCache({
cache: cache,
keyTransformer: keyTransformer,
valueInputTransformer: identity,
valueOutputTransformer: identity,
});
}
public static transformValues<K, V, IV>(
cache: CacheProvider<K, IV>,
inputTransformer: Transformer<V, IV>,
outputTransformer: Transformer<IV, V>,
): AdaptedCache<K, V, K, IV> {
return new AdaptedCache({
cache: cache,
keyTransformer: identity,
valueInputTransformer: inputTransformer,
valueOutputTransformer: outputTransformer,
});
}
public async get(key: K, loader: CacheLoader<K, V>): Promise<V> {
return this.cache
.get(
await this.keyTransformer(key),
() => loader(key).then(this.valueInputTransformer),
)
.then(this.valueOutputTransformer);
}
public async set(key: K, value: V): Promise<void> {
return this.cache.set(
await this.keyTransformer(key),
await this.valueInputTransformer(value),
);
}
public async delete(key: K): Promise<void> {
return this.cache.delete(await this.keyTransformer(key));
}
public static createHashSerializer(algorithm?: HashAlgorithm): Transformer<any, string> {
if (algorithm === 'passthrough') {
// Do not hash when algorithm is set to `passthrough`
return HASHER.sort.bind(HASHER);
}
const options: HasherOptions = {
enc: 'base64',
alg: algorithm,
};
return (value: any): string => HASHER.hash(value, options);
}
/**
* Serializes a JSON compatible value to a string using JSON.stringify.
*
* This is a helper function for type safety.
*/
public static jsonSerializer<T extends JsonCompatible>(): Transformer<T, string> {
return JSON.stringify;
}
/**
* Deserializes a string into a JSON value using JSON.parse.
*
* This is a helper function for type safety.
*/
public static jsonDeserializer<T extends JsonValue>(): Transformer<string, T> {
return JSON.parse;
}
}