-
Notifications
You must be signed in to change notification settings - Fork 0
/
adapter.ts
218 lines (197 loc) · 5.58 KB
/
adapter.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
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
import { Key } from 'swr';
/**
* Adapter for a resource.
*
* Responsible for adapting a RESTful collections & resources into a common type.
* For a resource, it's responsible for determining the id of a given resource with
* the `id` method. For a collection, it's responsible for upserting or removing a
* resource from the collection, or adapting a collection to a common "array of resources"
* representation. This enables CRS to work flexibly for a variety of RESTful representations.
*
* @template LP List params
* @template CT Collection type
* @template RT Resource type
*/
export interface ResourceAdapter<LP, CT, RT> {
/**
* Get the id for a given resource.
*
* @param resource Resource to get the id for.
* @returns Id of the resource.
*/
id(resource: RT): string;
/**
* Create the SWR key for the given resource or resource id.
*
* The function has to handle both cases because there are methods
* like `delete` which don't have resources to act on but are given
* ids. We need to be able to create the cache key for both cases.
*
* @param resource Resource to create the key for.
* @returns SWR key for the resource.
*/
resourceKey(resource: RT | string): Key;
/**
* Create the SWR key for the given collection params.
*
* @param params List parameters to filter the API by.
* @returns SWR key for the collection.
*/
collectionKey(params?: LP): Key;
/**
* Map the collection type into an array of resources.
*
* This list is used to push the resources into the cache after
* loading from the collection, so we need to map it down to a
* list so we can cache everything.
*
* @param collection Collection to read from.
* @returns Array of resources.
*/
all(collection: CT): RT[];
/**
* Update or insert ("upsert") a resource in a collection.
*
* This can use `defaultUpsert` to pull the array of resources
* and run it through the default logic for upserting a resource,
* using the `adapter` to match the `id`.
*
* @param resource Resource to upsert into the collection.
* @param collection Collection to upsert into.
* @returns Upserted collection.
*/
upsert(resource: RT, collection: CT): CT;
/**
* Remove a resource from a collection.
*
* This can use `defaultFilter` to remove a resource from
* an array of resources, using the `adapter` to match the `id`.
*
* @param id Id of the resource to remove.
* @param collection Collection to remove resource from.
* @returns Collection with resource removed.
*/
remove(id: string, collection: CT): CT;
}
/**
* Default upsert function for adding a resource to a collection.
*
* @template RT Resource type.
* @param adapt Adapter for the resource.
* @param resource Resource to upsert.
* @param collection Collection to upsert into.
* @returns Whether it was inserted & result of the upsert.
*/
export function defaultUpsert<RT>(
adapt: ResourceAdapter<any, any, RT>,
resource: RT,
collection: RT[],
): {
inserted: boolean;
result: RT[];
} {
let inserted = true;
const updated = collection.map(r => {
const isElement = adapt.id(r) === adapt.id(resource);
if (isElement) {
inserted = false;
return resource;
}
return r;
});
const result = (!inserted ? updated : [...collection, resource]) as RT[];
return { inserted, result };
}
/**
* Default filter function for removing a resource from a collection.
*
* @template RT Resource type.
* @param adapt Adapter for the resource.
* @param id Id of resource to remove from the collection.
* @param collection Collection to remove resource from.
* @returns Whether resource was removed & result of the filter.
*/
export function defaultFilter<RT>(
adapt: ResourceAdapter<any, any, RT>,
id: string,
collection: RT[],
): {
removed: boolean;
result: RT[];
} {
let removed = false;
const result = collection.filter(r => {
const isElement = adapt.id(r) === id;
if (isElement) {
removed = true;
}
return !isElement;
});
return { removed, result };
}
/**
* Default implementation of a REST adapter.
*
* `cacheKey` & `idKey` are used for basic examples, where the collection
* is an array of resources & the resources have a property (`idKey`) on
* them as their unique identifier. `cacheKey` can be any unique string
* but is typically the collection url.
*
* @template LP List params.
* @template RK Resource key.
* @template CT Collection type.
* @template RT Resource type.
*/
export class DefaultRestAdapter<
LP,
RK extends string,
CT extends RT[],
RT extends { [K in RK]: string }
> implements ResourceAdapter<LP, CT, RT> {
/**
* Construct a configured rest adapter.
*
* @param cacheKey SWR cache key prefix.
* @param idKey Property on the resource with unique identifier.
*/
constructor(private cacheKey: string, private idKey: RK) {}
/**
* @inheritdoc
*/
resourceKey(resource: RT | string): Key {
return [
this.cacheKey,
typeof resource === 'string' ? resource : this.id(resource),
];
}
/**
* @inheritdoc
*/
collectionKey(params?: LP): Key {
return [this.cacheKey, params];
}
/**
* @inheritdoc
*/
id(resource: RT) {
return resource[this.idKey];
}
/**
* @inheritdoc
*/
all(collection: CT) {
return collection;
}
/**
* @inheritdoc
*/
upsert(resource: RT, collection: CT): CT {
return defaultUpsert(this, resource, collection).result as CT;
}
/**
* @inheritdoc
*/
remove(id: string, collection: CT): CT {
return defaultFilter(this, id, collection).result as CT;
}
}