Skip to content

Commit 20113c1

Browse files
Jake ChampionJakeChampion
authored andcommitted
feat!: Rename SimpleCache.delete to SimpleCache.purge and require purge options to be supplied as the second parameter
We are renaming because "purge" is already a well-known and documented concept for removing content from Fastly's cache. The second argument allows the caller to decide what scope to purge the content from, currently they can choose to purge from all of Fastly ("global") or from the POP that contains the currently executing instance ("pop"). We do not provide a default option right now, in the future we may provide a default option, if we discover a common pattern is being used.
1 parent 0816c39 commit 20113c1

File tree

7 files changed

+572
-139
lines changed

7 files changed

+572
-139
lines changed

documentation/docs/fastly:cache/SimpleCache/delete.mdx

Lines changed: 0 additions & 74 deletions
This file was deleted.
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
---
2+
hide_title: false
3+
hide_table_of_contents: false
4+
pagination_next: null
5+
pagination_prev: null
6+
---
7+
8+
# SimpleCache.purge
9+
10+
purge the entry associated with the key `key` from the cache.
11+
12+
## Syntax
13+
14+
```js
15+
purge(key, options)
16+
```
17+
18+
### Parameters
19+
20+
- `key` _: string_
21+
- The key to purge from within the cache.
22+
23+
- `options` _: object_
24+
- `scope` _: string_
25+
- : Where to purge the content from.
26+
- Possible values are:
27+
- "global" - This will remove the content from all of Fastly.
28+
- "pop" - This will remove the content from the POP that contains the currently executing instance.
29+
- `headers`
30+
- : Any headers you want to add to your request, contained
31+
within a `Headers` object or an object literal with `String` values.
32+
- `body`
33+
- : Any body that you want to add to your request: this can be an `ArrayBuffer`, a `TypedArray`, a `DataView`, a `URLSearchParams`, string object or literal, or a `ReadableStream` object.
34+
- `backend` _**Fastly-specific**_
35+
- `cacheOverride` _**Fastly-specific**_
36+
- `cacheKey` _**Fastly-specific**_
37+
38+
### Return value
39+
40+
Returns `undefined`.
41+
42+
### Exceptions
43+
44+
- `TypeError`
45+
- If the provided `key`:
46+
- Is an empty string
47+
- Cannot be coerced to a string
48+
- Is longer than 8135 characters
49+
50+
## Examples
51+
52+
In this example, when a request contains a `purge` querystring parameter, we purge the an entry from the cache.
53+
54+
```js
55+
/// <reference types="@fastly/js-compute" />
56+
57+
import { SimpleCache } from 'fastly:cache';
58+
59+
addEventListener('fetch', event => event.respondWith(app(event)));
60+
61+
async function app(event) {
62+
const url = new URL(event.request);
63+
const path = url.pathname;
64+
if (url.searchParams.has('purge')) {
65+
SimpleCache.purge(path, { scope: "global" });
66+
return new Response(page, { status: 204 });
67+
}
68+
69+
let page = SimpleCache.getOrSet(path, async () => {
70+
return {
71+
value: await render(path),
72+
// Store the page in the cache for 1 minute.
73+
ttl: 60
74+
}
75+
});
76+
return new Response(page, {
77+
headers: {
78+
'content-type': 'text/plain;charset=UTF-8'
79+
}
80+
});
81+
}
82+
83+
async function render(path) {
84+
// expensive/slow function which constructs and returns the contents for a given path
85+
await new Promise(resolve => setTimeout(resolve, 10_000));
86+
return path;
87+
}
88+
89+
```

integration-tests/js-compute/fixtures/cache-simple/bin/index.js

Lines changed: 61 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import { SimpleCache, SimpleCacheEntry } from 'fastly:cache';
88
let error;
99
routes.set("/simple-cache/interface", () => {
1010
let actual = Reflect.ownKeys(SimpleCache)
11-
let expected = ["prototype", "delete", "get", "getOrSet", "set", "length", "name"]
11+
let expected = ["prototype", "purge", "get", "getOrSet", "set", "length", "name"]
1212
error = assert(actual, expected, `Reflect.ownKeys(SimpleCache)`)
1313
if (error) { return error }
1414

@@ -163,34 +163,34 @@ routes.set("/simple-cache/interface", () => {
163163
if (error) { return error }
164164
}
165165

166-
// Check the delete static method has correct descriptors, length and name
166+
// Check the purge static method has correct descriptors, length and name
167167
{
168-
actual = Reflect.getOwnPropertyDescriptor(SimpleCache, 'delete')
169-
expected = { "writable": true, "enumerable": true, "configurable": true, value: SimpleCache.delete }
170-
error = assert(actual, expected, `Reflect.getOwnPropertyDescriptor(SimpleCache, 'delete')`)
168+
actual = Reflect.getOwnPropertyDescriptor(SimpleCache, 'purge')
169+
expected = { "writable": true, "enumerable": true, "configurable": true, value: SimpleCache.purge }
170+
error = assert(actual, expected, `Reflect.getOwnPropertyDescriptor(SimpleCache, 'purge')`)
171171
if (error) { return error }
172172

173-
error = assert(typeof SimpleCache.delete, 'function', `typeof SimpleCache.delete`)
173+
error = assert(typeof SimpleCache.purge, 'function', `typeof SimpleCache.purge`)
174174
if (error) { return error }
175175

176-
actual = Reflect.getOwnPropertyDescriptor(SimpleCache.delete, 'length')
176+
actual = Reflect.getOwnPropertyDescriptor(SimpleCache.purge, 'length')
177177
expected = {
178-
"value": 1,
178+
"value": 2,
179179
"writable": false,
180180
"enumerable": false,
181181
"configurable": true
182182
}
183-
error = assert(actual, expected, `Reflect.getOwnPropertyDescriptor(SimpleCache.delete, 'length')`)
183+
error = assert(actual, expected, `Reflect.getOwnPropertyDescriptor(SimpleCache.purge, 'length')`)
184184
if (error) { return error }
185185

186-
actual = Reflect.getOwnPropertyDescriptor(SimpleCache.delete, 'name')
186+
actual = Reflect.getOwnPropertyDescriptor(SimpleCache.purge, 'name')
187187
expected = {
188-
"value": "delete",
188+
"value": "purge",
189189
"writable": false,
190190
"enumerable": false,
191191
"configurable": true
192192
}
193-
error = assert(actual, expected, `Reflect.getOwnPropertyDescriptor(SimpleCache.delete, 'name')`)
193+
error = assert(actual, expected, `Reflect.getOwnPropertyDescriptor(SimpleCache.purge, 'name')`)
194194
if (error) { return error }
195195
}
196196

@@ -245,19 +245,19 @@ routes.set("/simple-cache/interface", () => {
245245
});
246246
}
247247

248-
// SimpleCache delete static method
249-
// static delete(key: string): undefined;
248+
// SimpleCache purge static method
249+
// static purge(key: string, options: PurgeOptions): undefined;
250250
{
251-
routes.set("/simple-cache/delete/called-as-constructor", () => {
251+
routes.set("/simple-cache/purge/called-as-constructor", () => {
252252
error = assertThrows(() => {
253-
new SimpleCache.delete('1')
254-
}, TypeError, `SimpleCache.delete is not a constructor`)
253+
new SimpleCache.purge('1', {scope:"global"})
254+
}, TypeError, `SimpleCache.purge is not a constructor`)
255255
if (error) { return error }
256256
return pass()
257257
});
258258
// Ensure we correctly coerce the parameter to a string as according to
259259
// https://tc39.es/ecma262/#sec-tostring
260-
routes.set("/simple-cache/delete/key-parameter-calls-7.1.17-ToString", () => {
260+
routes.set("/simple-cache/purge/key-parameter-calls-7.1.17-ToString", () => {
261261
let sentinel;
262262
const test = () => {
263263
sentinel = Symbol('sentinel');
@@ -266,7 +266,7 @@ routes.set("/simple-cache/interface", () => {
266266
throw sentinel;
267267
}
268268
}
269-
SimpleCache.delete(key)
269+
SimpleCache.purge(key, {scope:"global"})
270270
}
271271
error = assertThrows(test)
272272
if (error) { return error }
@@ -277,43 +277,71 @@ routes.set("/simple-cache/interface", () => {
277277
if (error) { return error }
278278
}
279279
error = assertThrows(() => {
280-
SimpleCache.delete(Symbol())
280+
SimpleCache.purge(Symbol(),{scope:"global"})
281281
}, TypeError, `can't convert symbol to string`)
282282
if (error) { return error }
283283
return pass()
284284
});
285-
routes.set("/simple-cache/delete/key-parameter-not-supplied", () => {
285+
routes.set("/simple-cache/purge/key-parameter-not-supplied", () => {
286286
error = assertThrows(() => {
287-
SimpleCache.delete()
288-
}, TypeError, `SimpleCache.delete: At least 1 argument required, but only 0 passed`)
287+
SimpleCache.purge()
288+
}, TypeError, `SimpleCache.purge: At least 2 arguments required, but only 0 passed`)
289289
if (error) { return error }
290290
return pass()
291291
});
292-
routes.set("/simple-cache/delete/key-parameter-empty-string", () => {
292+
routes.set("/simple-cache/purge/key-parameter-empty-string", () => {
293293
error = assertThrows(() => {
294-
SimpleCache.delete('')
295-
}, Error, `SimpleCache.delete: key can not be an empty string`)
294+
SimpleCache.purge('',{scope:"global"})
295+
}, Error, `SimpleCache.purge: key can not be an empty string`)
296296
if (error) { return error }
297297
return pass()
298298
});
299-
routes.set("/simple-cache/delete/key-parameter-8135-character-string", () => {
299+
routes.set("/simple-cache/purge/key-parameter-8135-character-string", () => {
300300
error = assertDoesNotThrow(() => {
301301
const key = 'a'.repeat(8135)
302-
SimpleCache.delete(key)
302+
SimpleCache.purge(key,{scope:"global"})
303303
})
304304
if (error) { return error }
305305
return pass()
306306
});
307-
routes.set("/simple-cache/delete/key-parameter-8136-character-string", () => {
307+
routes.set("/simple-cache/purge/key-parameter-8136-character-string", () => {
308308
error = assertThrows(() => {
309309
const key = 'a'.repeat(8136)
310-
SimpleCache.delete(key)
311-
}, Error, `SimpleCache.delete: key is too long, the maximum allowed length is 8135.`)
310+
SimpleCache.purge(key,{scope:"global"})
311+
}, Error, `SimpleCache.purge: key is too long, the maximum allowed length is 8135.`)
312+
if (error) { return error }
313+
return pass()
314+
});
315+
routes.set("/simple-cache/purge/options-parameter", () => {
316+
error = assertThrows(() => {
317+
const key = 'a'
318+
SimpleCache.purge(key, "hello")
319+
}, Error, `SimpleCache.purge: options parameter is not an object.`)
320+
if (error) { return error }
321+
error = assertThrows(() => {
322+
const key = 'a'
323+
SimpleCache.purge(key, {scope: Symbol()})
324+
}, Error, `can't convert symbol to string`)
325+
if (error) { return error }
326+
error = assertThrows(() => {
327+
const key = 'a'
328+
SimpleCache.purge(key, {scope: ""})
329+
}, Error, `SimpleCache.purge: scope field of options parameter must be either 'pop', or 'global'.`)
330+
if (error) { return error }
331+
error = assertDoesNotThrow(() => {
332+
const key = 'a'
333+
SimpleCache.purge(key, {scope: "pop"})
334+
})
335+
if (error) { return error }
336+
error = assertDoesNotThrow(() => {
337+
const key = 'a'
338+
SimpleCache.purge(key, {scope: "global"})
339+
})
312340
if (error) { return error }
313341
return pass()
314342
});
315-
routes.set("/simple-cache/delete/returns-undefined", () => {
316-
error = assert(SimpleCache.delete('meow'), undefined, "SimpleCache.delete('meow') === undefined")
343+
routes.set("/simple-cache/purge/returns-undefined", () => {
344+
error = assert(SimpleCache.purge('meow', {scope:"global"}), undefined, "SimpleCache.purge('meow', {scope'global'})")
317345
if (error) { return error }
318346
return pass()
319347
});

0 commit comments

Comments
 (0)