-
Notifications
You must be signed in to change notification settings - Fork 19
/
Copy pathmetadata-manager.ts
114 lines (94 loc) · 3.49 KB
/
metadata-manager.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
import { DeleteSettingsValue, SettingsManager, SettingsValue } from "./settings-manager";
export type MetadataEntry = {
key: string;
value: string;
};
/**
* TODO Rename "Callback" suffixes, these are not callbacks
*/
export type FetchMetadataCallback = () => Promise<MetadataEntry[]>;
export type MutateMetadataCallback = (metadata: MetadataEntry[]) => Promise<MetadataEntry[]>;
export type DeleteMetadataCallback = (keys: string[]) => Promise<void>;
const deserializeMetadata = ({ key, value }: MetadataEntry): SettingsValue => {
// domain specific metadata use convention key__domain, e.g. `secret_key__example.com`
const [newKey, domain] = key.split("__");
return {
key: newKey,
domain,
value,
};
};
const constructDomainSpecificKey = (key: string, saleorApiUrl: string) =>
[key, saleorApiUrl].join("__");
const serializeSettingsToMetadata = ({ key, value, domain }: SettingsValue): MetadataEntry => {
// domain specific metadata use convention key__domain, e.g. `secret_key__example.com`
if (!domain) {
return { key, value };
}
return {
key: constructDomainSpecificKey(key, domain),
value,
};
};
export interface MetadataManagerConfig {
fetchMetadata: FetchMetadataCallback;
mutateMetadata: MutateMetadataCallback;
deleteMetadata: DeleteMetadataCallback;
}
/**
* Metadata Manager use app metadata to store settings.
* To minimize network calls, once fetched metadata are cached.
* Cache invalidation occurs if any value is set.
*
*
*/
export class MetadataManager implements SettingsManager {
private settings: SettingsValue[] = [];
private fetchMetadata: FetchMetadataCallback;
private mutateMetadata: MutateMetadataCallback;
private deleteMetadata: DeleteMetadataCallback;
constructor({ fetchMetadata, mutateMetadata, deleteMetadata }: MetadataManagerConfig) {
this.fetchMetadata = fetchMetadata;
this.mutateMetadata = mutateMetadata;
this.deleteMetadata = deleteMetadata;
}
async get(key: string, domain?: string) {
if (!this.settings.length) {
const metadata = await this.fetchMetadata();
this.settings = metadata.map(deserializeMetadata);
}
const setting = this.settings.find((md) => md.key === key && md.domain === domain);
return setting?.value;
}
async set(settings: SettingsValue[] | SettingsValue) {
let serializedMetadata = [];
if (Array.isArray(settings)) {
serializedMetadata = settings.map(serializeSettingsToMetadata);
} else {
serializedMetadata = [serializeSettingsToMetadata(settings)];
}
// changes should update cache
const metadata = await this.mutateMetadata(serializedMetadata);
this.settings = metadata.map(deserializeMetadata);
}
/**
* Typescript doesn't properly infer arguments, so they have to be rewritten explicitly
*/
async delete(args: DeleteSettingsValue | DeleteSettingsValue[] | string | string[]) {
if (!this.deleteMetadata) {
throw new Error(
"Delete not implemented. Ensure MetadataManager is configured with deleteMetadata param in constructor",
);
}
const argsArray = Array.isArray(args) ? args : [args];
const keysToDelete = argsArray.map((keyOrDomainPair) => {
if (typeof keyOrDomainPair === "string") {
return keyOrDomainPair;
}
const { key, domain } = keyOrDomainPair;
return constructDomainSpecificKey(key, domain);
});
await this.deleteMetadata(keysToDelete);
this.settings = this.settings.filter((setting) => !argsArray.includes(setting.key));
}
}