@fastify/caching 9.0.2 → 9.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/dependabot.yml +41 -1
- package/.github/workflows/ci.yml +12 -2
- package/.github/workflows/lock-threads.yml +19 -0
- package/LICENSE +2 -1
- package/{Readme.md → README.md} +29 -13
- package/index.js +12 -5
- package/package.json +39 -18
- package/test/cache.test.js +17 -17
- package/test/headers.test.js +113 -14
- package/types/index.d.ts +0 -2
- package/types/index.tst.ts +68 -0
- package/.github/stale.yml +0 -21
- package/types/index.test-d.ts +0 -69
package/.github/dependabot.yml
CHANGED
|
@@ -2,12 +2,52 @@ version: 2
|
|
|
2
2
|
updates:
|
|
3
3
|
- package-ecosystem: "github-actions"
|
|
4
4
|
directory: "/"
|
|
5
|
+
commit-message:
|
|
6
|
+
# Prefix all commit messages with "chore: "
|
|
7
|
+
prefix: "chore"
|
|
5
8
|
schedule:
|
|
6
|
-
interval: "
|
|
9
|
+
interval: "weekly"
|
|
10
|
+
cooldown:
|
|
11
|
+
default-days: 7
|
|
12
|
+
allow:
|
|
13
|
+
- dependency-name: "*"
|
|
14
|
+
update-types:
|
|
15
|
+
- "version-update:semver-major"
|
|
7
16
|
open-pull-requests-limit: 10
|
|
8
17
|
|
|
9
18
|
- package-ecosystem: "npm"
|
|
10
19
|
directory: "/"
|
|
20
|
+
commit-message:
|
|
21
|
+
# Prefix all commit messages with "chore: "
|
|
22
|
+
prefix: "chore"
|
|
11
23
|
schedule:
|
|
12
24
|
interval: "weekly"
|
|
25
|
+
cooldown:
|
|
26
|
+
default-days: 7
|
|
27
|
+
versioning-strategy: "increase-if-necessary"
|
|
28
|
+
allow:
|
|
29
|
+
- dependency-name: "*"
|
|
30
|
+
update-types:
|
|
31
|
+
- "version-update:semver-major"
|
|
32
|
+
ignore:
|
|
33
|
+
# TODO: remove ignore until neostandard support ESLint 10
|
|
34
|
+
- dependency-name: "eslint"
|
|
35
|
+
- dependency-name: "neostandard"
|
|
36
|
+
- dependency-name: "@stylistic/*"
|
|
13
37
|
open-pull-requests-limit: 10
|
|
38
|
+
groups:
|
|
39
|
+
# Production dependencies with breaking changes
|
|
40
|
+
dependencies:
|
|
41
|
+
dependency-type: "production"
|
|
42
|
+
# ESLint related dependencies
|
|
43
|
+
dev-dependencies-eslint:
|
|
44
|
+
patterns:
|
|
45
|
+
- "eslint"
|
|
46
|
+
- "neostandard"
|
|
47
|
+
- "@stylistic/*"
|
|
48
|
+
# TypeScript related dependencies
|
|
49
|
+
dev-dependencies-typescript:
|
|
50
|
+
patterns:
|
|
51
|
+
- "@types/*"
|
|
52
|
+
- "tstyche"
|
|
53
|
+
- "typescript"
|
package/.github/workflows/ci.yml
CHANGED
|
@@ -4,7 +4,6 @@ on:
|
|
|
4
4
|
push:
|
|
5
5
|
branches:
|
|
6
6
|
- main
|
|
7
|
-
- master
|
|
8
7
|
- next
|
|
9
8
|
- 'v*'
|
|
10
9
|
paths-ignore:
|
|
@@ -15,9 +14,20 @@ on:
|
|
|
15
14
|
- 'docs/**'
|
|
16
15
|
- '*.md'
|
|
17
16
|
|
|
17
|
+
# This allows a subsequently queued workflow run to interrupt previous runs
|
|
18
|
+
concurrency:
|
|
19
|
+
group: "${{ github.workflow }}-${{ github.event.pull_request.head.label || github.head_ref || github.ref }}"
|
|
20
|
+
cancel-in-progress: true
|
|
21
|
+
|
|
22
|
+
permissions:
|
|
23
|
+
contents: read
|
|
24
|
+
|
|
18
25
|
jobs:
|
|
19
26
|
test:
|
|
20
|
-
|
|
27
|
+
permissions:
|
|
28
|
+
contents: write
|
|
29
|
+
pull-requests: write
|
|
30
|
+
uses: fastify/workflows/.github/workflows/plugins-ci.yml@ef591e2186785d5ab36b9fe6a79c7ce2f1d94e57 # v7.0.0
|
|
21
31
|
with:
|
|
22
32
|
license-check: true
|
|
23
33
|
lint: true
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
name: Lock Threads
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
schedule:
|
|
5
|
+
- cron: '0 0 1 * *'
|
|
6
|
+
workflow_dispatch:
|
|
7
|
+
|
|
8
|
+
concurrency:
|
|
9
|
+
group: lock
|
|
10
|
+
|
|
11
|
+
permissions:
|
|
12
|
+
contents: read
|
|
13
|
+
|
|
14
|
+
jobs:
|
|
15
|
+
lock-threads:
|
|
16
|
+
permissions:
|
|
17
|
+
issues: write
|
|
18
|
+
pull-requests: write
|
|
19
|
+
uses: fastify/workflows/.github/workflows/lock-threads.yml@ef591e2186785d5ab36b9fe6a79c7ce2f1d94e57 # v7.0.0
|
package/LICENSE
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
MIT License
|
|
2
2
|
|
|
3
|
-
Copyright (c) 2018 James Sumners <james.sumners@gmail.com>
|
|
3
|
+
Copyright (c) 2018-present James Sumners <james.sumners@gmail.com>
|
|
4
|
+
Copyright (c) 2018-present The Fastify team <https://github.com/fastify/fastify#team>
|
|
4
5
|
|
|
5
6
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
7
|
of this software and associated documentation files (the "Software"), to deal
|
package/{Readme.md → README.md}
RENAMED
|
@@ -1,15 +1,12 @@
|
|
|
1
1
|
# @fastify/caching
|
|
2
2
|
|
|
3
|
-
[](https://github.com/fastify/fastify-caching/actions/workflows/ci.yml)
|
|
4
4
|
[](https://www.npmjs.com/package/@fastify/caching)
|
|
5
5
|
[](https://github.com/neostandard/neostandard)
|
|
6
6
|
|
|
7
|
-
*@fastify/caching* is a plugin for the [Fastify](
|
|
7
|
+
*@fastify/caching* is a plugin for the [Fastify](https://fastify.dev/) framework
|
|
8
8
|
that provides server-side caching and mechanisms for manipulating HTTP cache headers according to
|
|
9
|
-
[RFC 2616 §14.9](https://
|
|
10
|
-
|
|
11
|
-
Supports Fastify versions ^3.0.0. Version v5.x supports Fastify ^3.0.0 in
|
|
12
|
-
the [`v5.x` branch](https://github.com/fastify/fastify-caching/tree/v5.x).
|
|
9
|
+
[RFC 2616 §14.9](https://datatracker.ietf.org/doc/html/rfc2616#section-14.9).
|
|
13
10
|
|
|
14
11
|
This plugin fully supports Fastify's encapsulation. Therefore, routes that
|
|
15
12
|
should have differing cache settings should be registered within different
|
|
@@ -19,14 +16,33 @@ In addition to providing header manipulation, the plugin also decorates the
|
|
|
19
16
|
server instance with an object that can be used for caching items. **Note:**
|
|
20
17
|
the default cache should not be used in a "production" environment. It is
|
|
21
18
|
an LRU, in-memory cache that is capped at 100,000 items. It is *highly*
|
|
22
|
-
recommended that a full
|
|
19
|
+
recommended that a full-featured cache object be supplied, e.g.
|
|
23
20
|
[abstract-cache-redis][acache-redis].
|
|
24
21
|
|
|
25
22
|
[acache-redis]: https://www.npmjs.com/package/abstract-cache-redis
|
|
26
23
|
|
|
24
|
+
## Install
|
|
25
|
+
```
|
|
26
|
+
npm i @fastify/caching
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
### Compatibility
|
|
30
|
+
| Plugin version | Fastify version |
|
|
31
|
+
| ---------------|-----------------|
|
|
32
|
+
| `>=9.x` | `^5.x` |
|
|
33
|
+
| `^8.x` | `^4.x` |
|
|
34
|
+
| `>=6.x <8.x` | `^3.x` |
|
|
35
|
+
| `>=4.x <6.x` | `^2.x` |
|
|
36
|
+
| `>=1.x <4.x` | `^1.x` |
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
Please note that if a Fastify version is out of support, then so are the corresponding versions of this plugin
|
|
40
|
+
in the table above.
|
|
41
|
+
See [Fastify's LTS policy](https://github.com/fastify/fastify/blob/main/docs/Reference/LTS.md) for more details.
|
|
42
|
+
|
|
27
43
|
## Example
|
|
28
44
|
|
|
29
|
-
This example shows using the plugin to disable client
|
|
45
|
+
This example shows using the plugin to disable client-side caching of all
|
|
30
46
|
routes.
|
|
31
47
|
|
|
32
48
|
```js
|
|
@@ -107,7 +123,7 @@ for a *cache-response-directive* as defined by RFC 2616.
|
|
|
107
123
|
resource may be cached. When this is set, and `privacy` is not set to `no-cache`,
|
|
108
124
|
then `', max-age=<value>'` will be appended to the `cache-control` header.
|
|
109
125
|
+ `cache` (Default: `abstract-cache.memclient`): an [abstract-cache][acache]
|
|
110
|
-
protocol
|
|
126
|
+
protocol-compliant cache object. Note: the plugin requires a cache instance to
|
|
111
127
|
properly support the ETag mechanism. Therefore, if a falsy value is supplied
|
|
112
128
|
the default will be used.
|
|
113
129
|
+ `cacheSegment` (Default: `'@fastify/caching'`): segment identifier to use when
|
|
@@ -128,7 +144,7 @@ recommended to always supply a value to this method to avoid this operation.
|
|
|
128
144
|
All incoming requests to paths affected by this plugin will be inspected for
|
|
129
145
|
the `if-none-match` header. If the header is present, the value will be used
|
|
130
146
|
to lookup the tag within the cache associated with this plugin. If the tag is
|
|
131
|
-
found, then the response will
|
|
147
|
+
found, then the response will end with a 304 status code sent to
|
|
132
148
|
the client.
|
|
133
149
|
|
|
134
150
|
Tags will be cached according to the second parameter. If the second parameter
|
|
@@ -142,11 +158,11 @@ milliseconds. The default lifetime, when the parameter is not specified, is
|
|
|
142
158
|
### `reply.expires(date)`
|
|
143
159
|
|
|
144
160
|
This method allows setting of the `expires` header. It accepts a regular `Date`
|
|
145
|
-
object
|
|
161
|
+
object or a string that is a valid date string according to
|
|
146
162
|
[RFC 2616 §14.21][sec14.21].
|
|
147
163
|
|
|
148
|
-
[sec14.21]: https://
|
|
164
|
+
[sec14.21]: https://datatracker.ietf.org/doc/html/rfc2616#section-14.21
|
|
149
165
|
|
|
150
166
|
## License
|
|
151
167
|
|
|
152
|
-
[MIT
|
|
168
|
+
Licensed under [MIT](./LICENSE).
|
package/index.js
CHANGED
|
@@ -38,7 +38,7 @@ function cachingLoadByEtag (req, res, next) {
|
|
|
38
38
|
})
|
|
39
39
|
}
|
|
40
40
|
|
|
41
|
-
function cachingStoreByEtag (
|
|
41
|
+
function cachingStoreByEtag (_req, res, payload, next) {
|
|
42
42
|
const etag = res.getHeader('etag')
|
|
43
43
|
if (!etag || !res._etagLife) return next()
|
|
44
44
|
this.cache.set(
|
|
@@ -60,7 +60,7 @@ function fastifyCaching (instance, options, next) {
|
|
|
60
60
|
if (!_options.cache) _options.cache = abstractCache()
|
|
61
61
|
|
|
62
62
|
if (_options.privacy) {
|
|
63
|
-
// https://
|
|
63
|
+
// https://datatracker.ietf.org/doc/html/rfc2616#section-14.9.4
|
|
64
64
|
let value = _options.privacy
|
|
65
65
|
if (_options.privacy.toLowerCase() !== 'no-cache' && _options.expiresIn) {
|
|
66
66
|
value = `${_options.privacy}, max-age=${_options.expiresIn}`
|
|
@@ -70,9 +70,16 @@ function fastifyCaching (instance, options, next) {
|
|
|
70
70
|
value += `, s-maxage=${_options.serverExpiresIn}`
|
|
71
71
|
}
|
|
72
72
|
|
|
73
|
-
instance.addHook('
|
|
74
|
-
if (!
|
|
75
|
-
|
|
73
|
+
instance.addHook('onSend', function cachingSetCacheControlHeader (_req, reply, _payload, next) {
|
|
74
|
+
if (!reply.hasHeader('Cache-control')) {
|
|
75
|
+
// Only apply the caching value to successful (2xx) responses.
|
|
76
|
+
// Error responses must not be cached, otherwise browsers may serve
|
|
77
|
+
// a stale 4xx/5xx for the duration of max-age.
|
|
78
|
+
if (reply.statusCode >= 200 && reply.statusCode < 300) {
|
|
79
|
+
reply.header('Cache-control', value)
|
|
80
|
+
} else {
|
|
81
|
+
reply.header('Cache-control', 'no-store')
|
|
82
|
+
}
|
|
76
83
|
}
|
|
77
84
|
next()
|
|
78
85
|
})
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fastify/caching",
|
|
3
|
-
"version": "9.0.
|
|
3
|
+
"version": "9.0.4",
|
|
4
4
|
"description": "A plugin for Fastify to enable management of cache control headers",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"type": "commonjs",
|
|
@@ -9,18 +9,14 @@
|
|
|
9
9
|
"lint": "eslint",
|
|
10
10
|
"lint:fix": "eslint --fix",
|
|
11
11
|
"test": "npm run test:unit && npm run test:typescript",
|
|
12
|
-
"test:typescript": "
|
|
12
|
+
"test:typescript": "tstyche",
|
|
13
13
|
"test:unit": "c8 --100 node --test",
|
|
14
14
|
"test:unit:report": "npm run test:unit -- --coverage-report=html",
|
|
15
15
|
"test:unit:verbose": "npm run test:unit -- -Rspec"
|
|
16
16
|
},
|
|
17
|
-
"precommit": [
|
|
18
|
-
"lint",
|
|
19
|
-
"test"
|
|
20
|
-
],
|
|
21
17
|
"repository": {
|
|
22
18
|
"type": "git",
|
|
23
|
-
"url": "git+
|
|
19
|
+
"url": "git+https://github.com/fastify/fastify-caching.git"
|
|
24
20
|
},
|
|
25
21
|
"keywords": [
|
|
26
22
|
"fastify",
|
|
@@ -28,29 +24,54 @@
|
|
|
28
24
|
"caching"
|
|
29
25
|
],
|
|
30
26
|
"author": "James Sumners <james.sumners@gmail.com>",
|
|
27
|
+
"contributors": [
|
|
28
|
+
{
|
|
29
|
+
"name": "Matteo Collina",
|
|
30
|
+
"email": "hello@matteocollina.com"
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
"name": "Manuel Spigolon",
|
|
34
|
+
"email": "behemoth89@gmail.com"
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
"name": "Aras Abbasi",
|
|
38
|
+
"email": "aras.abbasi@gmail.com"
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
"name": "Frazer Smith",
|
|
42
|
+
"email": "frazer.dev@icloud.com",
|
|
43
|
+
"url": "https://github.com/fdawgs"
|
|
44
|
+
}
|
|
45
|
+
],
|
|
31
46
|
"license": "MIT",
|
|
32
47
|
"bugs": {
|
|
33
48
|
"url": "https://github.com/fastify/fastify-caching/issues"
|
|
34
49
|
},
|
|
35
50
|
"homepage": "https://github.com/fastify/fastify-caching#readme",
|
|
51
|
+
"funding": [
|
|
52
|
+
{
|
|
53
|
+
"type": "github",
|
|
54
|
+
"url": "https://github.com/sponsors/fastify"
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
"type": "opencollective",
|
|
58
|
+
"url": "https://opencollective.com/fastify"
|
|
59
|
+
}
|
|
60
|
+
],
|
|
36
61
|
"devDependencies": {
|
|
37
|
-
"@
|
|
38
|
-
"
|
|
39
|
-
"
|
|
62
|
+
"@types/node": "^26.0.1",
|
|
63
|
+
"c8": "^12.0.0",
|
|
64
|
+
"eslint": "^9.17.0",
|
|
40
65
|
"fastify": "^5.0.0",
|
|
41
|
-
"neostandard": "^0.
|
|
42
|
-
"
|
|
66
|
+
"neostandard": "^0.13.0",
|
|
67
|
+
"tstyche": "^7.0.0"
|
|
43
68
|
},
|
|
44
69
|
"dependencies": {
|
|
45
70
|
"abstract-cache": "^1.0.1",
|
|
46
|
-
"fastify-plugin": "^
|
|
71
|
+
"fastify-plugin": "^6.0.0",
|
|
47
72
|
"uid-safe": "^2.1.5"
|
|
48
73
|
},
|
|
49
74
|
"publishConfig": {
|
|
50
75
|
"access": "public"
|
|
51
|
-
}
|
|
52
|
-
"pre-commit": [
|
|
53
|
-
"lint",
|
|
54
|
-
"test"
|
|
55
|
-
]
|
|
76
|
+
}
|
|
56
77
|
}
|
package/test/cache.test.js
CHANGED
|
@@ -21,25 +21,25 @@ test('cache is usable', async (t) => {
|
|
|
21
21
|
t.plan(4)
|
|
22
22
|
|
|
23
23
|
const fastify = Fastify()
|
|
24
|
-
await fastify.register(async (instance
|
|
25
|
-
instance.addHook('onRequest', async function checkCachingRegistered (
|
|
24
|
+
await fastify.register(async (instance) => {
|
|
25
|
+
instance.addHook('onRequest', async function checkCachingRegistered () {
|
|
26
26
|
t.assert.ifError(instance[Symbol.for('fastify-caching.registered')])
|
|
27
27
|
})
|
|
28
28
|
})
|
|
29
29
|
await fastify.register(plugin)
|
|
30
30
|
|
|
31
|
-
fastify.addHook('onRequest', async function checkCachingRegistered (
|
|
31
|
+
fastify.addHook('onRequest', async function checkCachingRegistered () {
|
|
32
32
|
t.assert.strictEqual(this[Symbol.for('fastify-caching.registered')], true)
|
|
33
33
|
})
|
|
34
34
|
|
|
35
|
-
fastify.get('/one', (
|
|
35
|
+
fastify.get('/one', (_req, reply) => {
|
|
36
36
|
fastify.cache.set('one', { one: true }, 1000, (err) => {
|
|
37
37
|
if (err) return reply.send(err)
|
|
38
38
|
reply.redirect('/two')
|
|
39
39
|
})
|
|
40
40
|
})
|
|
41
41
|
|
|
42
|
-
fastify.get('/two', (
|
|
42
|
+
fastify.get('/two', (_req, reply) => {
|
|
43
43
|
fastify.cache.get('one', (err, obj) => {
|
|
44
44
|
t.assert.ifError(err)
|
|
45
45
|
t.assert.strictEqual(obj.item, { one: true })
|
|
@@ -70,25 +70,25 @@ test('cache is usable with function as plugin default options input', async (t)
|
|
|
70
70
|
t.plan(4)
|
|
71
71
|
|
|
72
72
|
const fastify = Fastify()
|
|
73
|
-
await fastify.register(async (instance
|
|
74
|
-
instance.addHook('onRequest', async function checkCachingNotRegistered (
|
|
73
|
+
await fastify.register(async (instance) => {
|
|
74
|
+
instance.addHook('onRequest', async function checkCachingNotRegistered () {
|
|
75
75
|
t.assert.failure(instance[Symbol.for('fastify-caching.registered')])
|
|
76
76
|
})
|
|
77
77
|
})
|
|
78
78
|
await fastify.register(plugin, () => () => {})
|
|
79
79
|
|
|
80
|
-
fastify.addHook('onRequest', async function checkCachingRegistered (
|
|
80
|
+
fastify.addHook('onRequest', async function checkCachingRegistered () {
|
|
81
81
|
t.assert.strictEqual(this[Symbol.for('fastify-caching.registered')], true)
|
|
82
82
|
})
|
|
83
83
|
|
|
84
|
-
fastify.get('/one', (
|
|
84
|
+
fastify.get('/one', (_req, reply) => {
|
|
85
85
|
fastify.cache.set('one', { one: true }, 1000, (err) => {
|
|
86
86
|
if (err) return reply.send(err)
|
|
87
87
|
reply.redirect('/two')
|
|
88
88
|
})
|
|
89
89
|
})
|
|
90
90
|
|
|
91
|
-
fastify.get('/two', (
|
|
91
|
+
fastify.get('/two', (_req, reply) => {
|
|
92
92
|
fastify.cache.get('one', (err, obj) => {
|
|
93
93
|
t.assert.ifError(err)
|
|
94
94
|
t.assert.strictEqual(obj.item, { one: true })
|
|
@@ -120,21 +120,21 @@ test('getting cache item with error returns error', async (t) => {
|
|
|
120
120
|
t.plan(1)
|
|
121
121
|
|
|
122
122
|
const mockCache = {
|
|
123
|
-
get: (
|
|
124
|
-
set: (
|
|
123
|
+
get: (_info, callback) => callback(new Error('cache.get always errors')),
|
|
124
|
+
set: (_key, _value, _ttl, callback) => callback()
|
|
125
125
|
}
|
|
126
126
|
|
|
127
127
|
const fastify = Fastify()
|
|
128
128
|
await fastify.register(plugin, { cache: mockCache })
|
|
129
129
|
|
|
130
|
-
fastify.get('/one', (
|
|
130
|
+
fastify.get('/one', (_req, reply) => {
|
|
131
131
|
fastify.cache.set('one', { one: true }, 1000, (err) => {
|
|
132
132
|
if (err) return reply.send(err)
|
|
133
133
|
return reply.etag('123456').send({ hello: 'world' })
|
|
134
134
|
})
|
|
135
135
|
})
|
|
136
136
|
|
|
137
|
-
fastify.get('/two', (
|
|
137
|
+
fastify.get('/two', () => {
|
|
138
138
|
fastify.cache.get('one', (err, obj) => {
|
|
139
139
|
t.assert.failure(err)
|
|
140
140
|
t.assert.failure(obj)
|
|
@@ -164,7 +164,7 @@ test('etags get stored in cache', async (t) => {
|
|
|
164
164
|
const fastify = Fastify()
|
|
165
165
|
await fastify.register(plugin)
|
|
166
166
|
|
|
167
|
-
fastify.get('/one', (
|
|
167
|
+
fastify.get('/one', (_req, reply) => {
|
|
168
168
|
reply.etag('123456').send({ hello: 'world' })
|
|
169
169
|
})
|
|
170
170
|
|
|
@@ -191,7 +191,7 @@ test('etag cache life is customizable', async (t) => {
|
|
|
191
191
|
const fastify = Fastify()
|
|
192
192
|
await fastify.register(plugin)
|
|
193
193
|
|
|
194
|
-
fastify.get('/one', function (
|
|
194
|
+
fastify.get('/one', function (_req, reply) {
|
|
195
195
|
reply
|
|
196
196
|
// We set a cache lifetime of 50 milliseconds
|
|
197
197
|
.etag('123456', 50)
|
|
@@ -237,7 +237,7 @@ test('returns response payload', async (t) => {
|
|
|
237
237
|
const fastify = Fastify()
|
|
238
238
|
await fastify.register(plugin)
|
|
239
239
|
|
|
240
|
-
fastify.get('/one', (
|
|
240
|
+
fastify.get('/one', (_req, reply) => {
|
|
241
241
|
reply.etag('123456', 300).send({ hello: 'world' })
|
|
242
242
|
})
|
|
243
243
|
|
package/test/headers.test.js
CHANGED
|
@@ -10,7 +10,7 @@ test('decorators get added', async (t) => {
|
|
|
10
10
|
const fastify = Fastify()
|
|
11
11
|
await fastify.register(plugin)
|
|
12
12
|
|
|
13
|
-
fastify.get('/', (
|
|
13
|
+
fastify.get('/', (_req, reply) => {
|
|
14
14
|
t.assert.ok(reply.etag)
|
|
15
15
|
reply.send()
|
|
16
16
|
})
|
|
@@ -31,7 +31,7 @@ test('decorators add headers', async (t) => {
|
|
|
31
31
|
const fastify = Fastify()
|
|
32
32
|
await fastify.register(plugin)
|
|
33
33
|
|
|
34
|
-
fastify.get('/', (
|
|
34
|
+
fastify.get('/', (_req, reply) => {
|
|
35
35
|
reply.etag(tag).send()
|
|
36
36
|
})
|
|
37
37
|
|
|
@@ -51,7 +51,7 @@ test('sets etag header for falsy argument', async (t) => {
|
|
|
51
51
|
const fastify = Fastify()
|
|
52
52
|
await fastify.register(plugin)
|
|
53
53
|
|
|
54
|
-
fastify.get('/', (
|
|
54
|
+
fastify.get('/', (_req, reply) => {
|
|
55
55
|
reply.etag().send()
|
|
56
56
|
})
|
|
57
57
|
|
|
@@ -70,7 +70,7 @@ test('sets no-cache header', async (t) => {
|
|
|
70
70
|
const fastify = Fastify()
|
|
71
71
|
await fastify.register(plugin, { privacy: plugin.privacy.NOCACHE })
|
|
72
72
|
|
|
73
|
-
fastify.get('/', (
|
|
73
|
+
fastify.get('/', (_req, reply) => {
|
|
74
74
|
reply.send({ hello: 'world' })
|
|
75
75
|
})
|
|
76
76
|
|
|
@@ -95,7 +95,7 @@ test('sets private with max-age header', async (t) => {
|
|
|
95
95
|
const fastify = Fastify()
|
|
96
96
|
await fastify.register(plugin, opts)
|
|
97
97
|
|
|
98
|
-
fastify.get('/', (
|
|
98
|
+
fastify.get('/', (_req, reply) => {
|
|
99
99
|
reply.send({ hello: 'world' })
|
|
100
100
|
})
|
|
101
101
|
|
|
@@ -124,7 +124,7 @@ test('sets public with max-age and s-maxage header', async (t) => {
|
|
|
124
124
|
const fastify = Fastify()
|
|
125
125
|
await fastify.register(plugin, opts)
|
|
126
126
|
|
|
127
|
-
fastify.get('/', (
|
|
127
|
+
fastify.get('/', (_req, reply) => {
|
|
128
128
|
reply.send({ hello: 'world' })
|
|
129
129
|
})
|
|
130
130
|
|
|
@@ -151,12 +151,12 @@ test('do not set headers if another upstream plugin already sets it', async (t)
|
|
|
151
151
|
}
|
|
152
152
|
|
|
153
153
|
const fastify = Fastify()
|
|
154
|
-
fastify.addHook('onRequest', async function checkCachingDoesNotOverrideCacheControlHeader (
|
|
154
|
+
fastify.addHook('onRequest', async function checkCachingDoesNotOverrideCacheControlHeader (_req, reply) {
|
|
155
155
|
reply.header('cache-control', 'do not override')
|
|
156
156
|
})
|
|
157
157
|
await fastify.register(plugin, opts)
|
|
158
158
|
|
|
159
|
-
fastify.get('/', (
|
|
159
|
+
fastify.get('/', (_req, reply) => {
|
|
160
160
|
reply.send({ hello: 'world' })
|
|
161
161
|
})
|
|
162
162
|
|
|
@@ -182,7 +182,7 @@ test('only sets max-age and ignores s-maxage with private header', async (t) =>
|
|
|
182
182
|
const fastify = Fastify()
|
|
183
183
|
await fastify.register(plugin, opts)
|
|
184
184
|
|
|
185
|
-
fastify.get('/', (
|
|
185
|
+
fastify.get('/', (_req, reply) => {
|
|
186
186
|
reply.send({ hello: 'world' })
|
|
187
187
|
})
|
|
188
188
|
|
|
@@ -210,7 +210,7 @@ test('s-maxage is optional with public header', async (t) => {
|
|
|
210
210
|
const fastify = Fastify()
|
|
211
211
|
await fastify.register(plugin, opts)
|
|
212
212
|
|
|
213
|
-
fastify.get('/', (
|
|
213
|
+
fastify.get('/', (_req, reply) => {
|
|
214
214
|
reply.send({ hello: 'world' })
|
|
215
215
|
})
|
|
216
216
|
|
|
@@ -230,7 +230,7 @@ test('sets no-store with max-age header', async (t) => {
|
|
|
230
230
|
const fastify = Fastify()
|
|
231
231
|
await fastify.register(plugin, { privacy: 'no-store', expiresIn: 300 })
|
|
232
232
|
|
|
233
|
-
fastify.get('/', (
|
|
233
|
+
fastify.get('/', (_req, reply) => {
|
|
234
234
|
reply.send({ hello: 'world' })
|
|
235
235
|
})
|
|
236
236
|
|
|
@@ -247,6 +247,105 @@ test('sets no-store with max-age header', async (t) => {
|
|
|
247
247
|
)
|
|
248
248
|
})
|
|
249
249
|
|
|
250
|
+
test('does not cache 5xx error responses', async (t) => {
|
|
251
|
+
t.plan(2)
|
|
252
|
+
|
|
253
|
+
const opts = {
|
|
254
|
+
privacy: plugin.privacy.PRIVATE,
|
|
255
|
+
expiresIn: 300
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
const fastify = Fastify()
|
|
259
|
+
await fastify.register(plugin, opts)
|
|
260
|
+
|
|
261
|
+
fastify.get('/', () => {
|
|
262
|
+
throw new Error('kaboom')
|
|
263
|
+
})
|
|
264
|
+
|
|
265
|
+
await fastify.ready()
|
|
266
|
+
|
|
267
|
+
const response = await fastify.inject({
|
|
268
|
+
method: 'GET',
|
|
269
|
+
path: '/'
|
|
270
|
+
})
|
|
271
|
+
t.assert.strictEqual(response.statusCode, 500)
|
|
272
|
+
t.assert.strictEqual(response.headers['cache-control'], 'no-store')
|
|
273
|
+
})
|
|
274
|
+
|
|
275
|
+
test('does not cache 4xx error responses', async (t) => {
|
|
276
|
+
t.plan(2)
|
|
277
|
+
|
|
278
|
+
const opts = {
|
|
279
|
+
privacy: plugin.privacy.PRIVATE,
|
|
280
|
+
expiresIn: 300
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
const fastify = Fastify()
|
|
284
|
+
await fastify.register(plugin, opts)
|
|
285
|
+
|
|
286
|
+
fastify.get('/', (_req, reply) => {
|
|
287
|
+
reply.code(400).send({ error: 'bad request' })
|
|
288
|
+
})
|
|
289
|
+
|
|
290
|
+
await fastify.ready()
|
|
291
|
+
|
|
292
|
+
const response = await fastify.inject({
|
|
293
|
+
method: 'GET',
|
|
294
|
+
path: '/'
|
|
295
|
+
})
|
|
296
|
+
t.assert.strictEqual(response.statusCode, 400)
|
|
297
|
+
t.assert.strictEqual(response.headers['cache-control'], 'no-store')
|
|
298
|
+
})
|
|
299
|
+
|
|
300
|
+
test('does not cache framework 404 responses', async (t) => {
|
|
301
|
+
t.plan(2)
|
|
302
|
+
|
|
303
|
+
const opts = {
|
|
304
|
+
privacy: plugin.privacy.PRIVATE,
|
|
305
|
+
expiresIn: 300
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
const fastify = Fastify()
|
|
309
|
+
await fastify.register(plugin, opts)
|
|
310
|
+
|
|
311
|
+
await fastify.ready()
|
|
312
|
+
|
|
313
|
+
const response = await fastify.inject({
|
|
314
|
+
method: 'GET',
|
|
315
|
+
path: '/does-not-exist'
|
|
316
|
+
})
|
|
317
|
+
t.assert.strictEqual(response.statusCode, 404)
|
|
318
|
+
t.assert.strictEqual(response.headers['cache-control'], 'no-store')
|
|
319
|
+
})
|
|
320
|
+
|
|
321
|
+
test('does not override a Cache-control header set upstream on error responses', async (t) => {
|
|
322
|
+
t.plan(2)
|
|
323
|
+
|
|
324
|
+
const opts = {
|
|
325
|
+
privacy: plugin.privacy.PRIVATE,
|
|
326
|
+
expiresIn: 300
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
const fastify = Fastify()
|
|
330
|
+
fastify.addHook('onRequest', async function presetCacheControl (_req, reply) {
|
|
331
|
+
reply.header('cache-control', 'private, max-age=10')
|
|
332
|
+
})
|
|
333
|
+
await fastify.register(plugin, opts)
|
|
334
|
+
|
|
335
|
+
fastify.get('/', () => {
|
|
336
|
+
throw new Error('kaboom')
|
|
337
|
+
})
|
|
338
|
+
|
|
339
|
+
await fastify.ready()
|
|
340
|
+
|
|
341
|
+
const response = await fastify.inject({
|
|
342
|
+
method: 'GET',
|
|
343
|
+
path: '/'
|
|
344
|
+
})
|
|
345
|
+
t.assert.strictEqual(response.statusCode, 500)
|
|
346
|
+
t.assert.strictEqual(response.headers['cache-control'], 'private, max-age=10')
|
|
347
|
+
})
|
|
348
|
+
|
|
250
349
|
test('sets the expires header', async (t) => {
|
|
251
350
|
t.plan(2)
|
|
252
351
|
|
|
@@ -255,7 +354,7 @@ test('sets the expires header', async (t) => {
|
|
|
255
354
|
const fastify = Fastify()
|
|
256
355
|
await fastify.register(plugin, { privacy: plugin.privacy.NOCACHE })
|
|
257
356
|
|
|
258
|
-
fastify.get('/', (
|
|
357
|
+
fastify.get('/', (_req, reply) => {
|
|
259
358
|
reply.expires(now).send({ hello: 'world' })
|
|
260
359
|
})
|
|
261
360
|
|
|
@@ -275,7 +374,7 @@ test('sets the expires header to a falsy value', async (t) => {
|
|
|
275
374
|
const fastify = Fastify()
|
|
276
375
|
await fastify.register(plugin, { privacy: plugin.privacy.NOCACHE })
|
|
277
376
|
|
|
278
|
-
fastify.get('/', (
|
|
377
|
+
fastify.get('/', (_req, reply) => {
|
|
279
378
|
reply.expires().send({ hello: 'world' })
|
|
280
379
|
})
|
|
281
380
|
|
|
@@ -294,7 +393,7 @@ test('sets the expires header to a custom value', async (t) => {
|
|
|
294
393
|
const fastify = Fastify()
|
|
295
394
|
await fastify.register(plugin, { privacy: plugin.privacy.NOCACHE })
|
|
296
395
|
|
|
297
|
-
fastify.get('/', (
|
|
396
|
+
fastify.get('/', (_req, reply) => {
|
|
298
397
|
reply.expires('foobar').send({ hello: 'world' })
|
|
299
398
|
})
|
|
300
399
|
|
package/types/index.d.ts
CHANGED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import Fastify, { FastifyReply } from 'fastify'
|
|
2
|
+
import { expect } from 'tstyche'
|
|
3
|
+
import fastifyCaching, {
|
|
4
|
+
AbstractCacheCompliantObject,
|
|
5
|
+
FastifyCachingPluginOptions,
|
|
6
|
+
} from '..'
|
|
7
|
+
|
|
8
|
+
const fastify = Fastify({ logger: true })
|
|
9
|
+
|
|
10
|
+
const cachingOptions: FastifyCachingPluginOptions = {
|
|
11
|
+
privacy: fastifyCaching.privacy.PUBLIC,
|
|
12
|
+
expiresIn: 300,
|
|
13
|
+
cacheSegment: 'fastify-caching',
|
|
14
|
+
}
|
|
15
|
+
expect(cachingOptions).type.toBeAssignableTo<FastifyCachingPluginOptions>()
|
|
16
|
+
|
|
17
|
+
fastify.register(fastifyCaching, cachingOptions)
|
|
18
|
+
|
|
19
|
+
expect(fastify.cache).type.toBe<AbstractCacheCompliantObject>()
|
|
20
|
+
expect(fastify.cache.get).type.toBe<AbstractCacheCompliantObject['get']>()
|
|
21
|
+
expect(fastify.cache.set).type.toBe<AbstractCacheCompliantObject['set']>()
|
|
22
|
+
expect(fastify.cacheSegment).type.toBe<string>()
|
|
23
|
+
|
|
24
|
+
fastify.get('/one', async (_request, reply) => {
|
|
25
|
+
expect(reply.etag).type.toBe<(tag?: string, timeToLive?: number) => FastifyReply>()
|
|
26
|
+
expect(reply.expires).type.toBe<(date?: Date) => FastifyReply>()
|
|
27
|
+
|
|
28
|
+
expect(reply.etag('hello', 6000)).type.toBe<FastifyReply>()
|
|
29
|
+
expect(reply.expires(new Date(Date.now() + 6000))).type.toBe<FastifyReply>()
|
|
30
|
+
|
|
31
|
+
return { message: 'one' }
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
fastify.get('/two', async (_request, reply) => {
|
|
35
|
+
expect(
|
|
36
|
+
reply.etag('hello', 6000).expires(new Date(Date.now() + 6000))
|
|
37
|
+
).type.toBe<FastifyReply>()
|
|
38
|
+
|
|
39
|
+
return { message: 'two' }
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
// We register a new instance that should trigger a typescript error.
|
|
43
|
+
const shouldErrorApp = Fastify({ logger: true })
|
|
44
|
+
|
|
45
|
+
const badCachingOptions = {
|
|
46
|
+
privacy: fastifyCaching.privacy.PRIVATE,
|
|
47
|
+
expiresIn: 'a string instead of a number of second',
|
|
48
|
+
cacheSegment: 'fastify-caching',
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
expect(shouldErrorApp.register).type.not.toBeCallableWith(fastifyCaching, badCachingOptions)
|
|
52
|
+
|
|
53
|
+
fastify.get('/three', async () => {
|
|
54
|
+
expect(fastify.cache.get('well-known')).type.toBeAssignableTo<Promise<unknown>>()
|
|
55
|
+
|
|
56
|
+
expect(fastify.cache.get<string>('well-known')).type.toBe<
|
|
57
|
+
Promise<{ item: string; stored: number; ttl: number; } | null>
|
|
58
|
+
>()
|
|
59
|
+
|
|
60
|
+
const result = fastify.cache.get<string>('well-known', (err, value) => {
|
|
61
|
+
expect(err).type.toBe<unknown>()
|
|
62
|
+
expect(value).type.toBe<{ item: string; stored: number; ttl: number; } | null>()
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
expect(result).type.toBe<void>()
|
|
66
|
+
|
|
67
|
+
return { message: 'two' }
|
|
68
|
+
})
|
package/.github/stale.yml
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
# Number of days of inactivity before an issue becomes stale
|
|
2
|
-
daysUntilStale: 15
|
|
3
|
-
# Number of days of inactivity before a stale issue is closed
|
|
4
|
-
daysUntilClose: 7
|
|
5
|
-
# Issues with these labels will never be considered stale
|
|
6
|
-
exemptLabels:
|
|
7
|
-
- "discussion"
|
|
8
|
-
- "feature request"
|
|
9
|
-
- "bug"
|
|
10
|
-
- "help wanted"
|
|
11
|
-
- "plugin suggestion"
|
|
12
|
-
- "good first issue"
|
|
13
|
-
# Label to use when marking an issue as stale
|
|
14
|
-
staleLabel: stale
|
|
15
|
-
# Comment to post when marking an issue as stale. Set to `false` to disable
|
|
16
|
-
markComment: >
|
|
17
|
-
This issue has been automatically marked as stale because it has not had
|
|
18
|
-
recent activity. It will be closed if no further activity occurs. Thank you
|
|
19
|
-
for your contributions.
|
|
20
|
-
# Comment to post when closing a stale issue. Set to `false` to disable
|
|
21
|
-
closeComment: false
|
package/types/index.test-d.ts
DELETED
|
@@ -1,69 +0,0 @@
|
|
|
1
|
-
import Fastify, { FastifyReply } from 'fastify'
|
|
2
|
-
import { expectAssignable, expectError, expectType } from 'tsd'
|
|
3
|
-
import fastifyCaching, {
|
|
4
|
-
AbstractCacheCompliantObject,
|
|
5
|
-
FastifyCachingPluginOptions,
|
|
6
|
-
} from '..'
|
|
7
|
-
|
|
8
|
-
const fastify = Fastify({ logger: true })
|
|
9
|
-
|
|
10
|
-
const cachingOptions: FastifyCachingPluginOptions = {
|
|
11
|
-
privacy: fastifyCaching.privacy.PUBLIC,
|
|
12
|
-
expiresIn: 300,
|
|
13
|
-
cacheSegment: 'fastify-caching',
|
|
14
|
-
}
|
|
15
|
-
expectAssignable<FastifyCachingPluginOptions>(cachingOptions)
|
|
16
|
-
|
|
17
|
-
fastify.register(fastifyCaching, cachingOptions)
|
|
18
|
-
|
|
19
|
-
expectType<AbstractCacheCompliantObject>(fastify.cache)
|
|
20
|
-
expectType<AbstractCacheCompliantObject['get']>(fastify.cache.get)
|
|
21
|
-
expectType<AbstractCacheCompliantObject['set']>(fastify.cache.set)
|
|
22
|
-
expectType<string>(fastify.cacheSegment)
|
|
23
|
-
// expectType<number>(fastify.etagMaxLife);
|
|
24
|
-
|
|
25
|
-
fastify.get('/one', async (request, reply) => {
|
|
26
|
-
expectType<(tag?: string, timeToLive?: number) => FastifyReply>(reply.etag)
|
|
27
|
-
expectType<(date?: Date) => FastifyReply>(reply.expires)
|
|
28
|
-
|
|
29
|
-
expectType<FastifyReply>(reply.etag('hello', 6000))
|
|
30
|
-
expectType<FastifyReply>(reply.expires(new Date(Date.now() + 6000)))
|
|
31
|
-
|
|
32
|
-
return { message: 'one' }
|
|
33
|
-
})
|
|
34
|
-
|
|
35
|
-
fastify.get('/two', async (request, reply) => {
|
|
36
|
-
expectType<FastifyReply>(
|
|
37
|
-
reply.etag('hello', 6000).expires(new Date(Date.now() + 6000))
|
|
38
|
-
)
|
|
39
|
-
|
|
40
|
-
return { message: 'two' }
|
|
41
|
-
})
|
|
42
|
-
|
|
43
|
-
// We register a new instance that should trigger a typescript error.
|
|
44
|
-
const shouldErrorApp = Fastify({ logger: true })
|
|
45
|
-
|
|
46
|
-
const badCachingOptions = {
|
|
47
|
-
privacy: fastifyCaching.privacy.PRIVATE,
|
|
48
|
-
expiresIn: 'a string instead of a number of second',
|
|
49
|
-
cacheSegment: 'fastify-caching',
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
expectError(shouldErrorApp.register(fastifyCaching, badCachingOptions))
|
|
53
|
-
|
|
54
|
-
fastify.get('/three', async (request, reply) => {
|
|
55
|
-
expectAssignable<Promise<unknown>>(
|
|
56
|
-
fastify.cache.get('well-known')
|
|
57
|
-
)
|
|
58
|
-
expectAssignable<Promise<{ item: string; stored: number; ttl: number; } | null>>(
|
|
59
|
-
fastify.cache.get<string>('well-known')
|
|
60
|
-
)
|
|
61
|
-
expectType<void>(
|
|
62
|
-
fastify.cache.get<string>('well-known', (err, value) => {
|
|
63
|
-
expectType<unknown>(err)
|
|
64
|
-
expectAssignable<{ item: string; stored: number; ttl: number; } | null>(value)
|
|
65
|
-
})
|
|
66
|
-
)
|
|
67
|
-
|
|
68
|
-
return { message: 'two' }
|
|
69
|
-
})
|