-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: add fastify cache and schema helpers (#22)
- Loading branch information
Showing
5 changed files
with
84 additions
and
2 deletions.
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,61 @@ | ||
import { FastifyReply } from 'fastify'; | ||
import { logger } from '../logger'; | ||
|
||
/** | ||
* A `Cache-Control` header used for re-validation based caching. | ||
* * `public` == allow proxies/CDNs to cache as opposed to only local browsers. | ||
* * `no-cache` == clients can cache a resource but should revalidate each time before using it. | ||
* * `must-revalidate` == somewhat redundant directive to assert that cache must be revalidated, required by some CDNs | ||
*/ | ||
export const CACHE_CONTROL_MUST_REVALIDATE = 'public, no-cache, must-revalidate'; | ||
|
||
export async function setResponseNonCacheable(reply: FastifyReply) { | ||
await reply.removeHeader('Cache-Control'); | ||
await reply.removeHeader('ETag'); | ||
} | ||
|
||
/** | ||
* Parses the etag values from a raw `If-None-Match` request header value. | ||
* The wrapping double quotes (if any) and validation prefix (if any) are stripped. | ||
* The parsing is permissive to account for commonly non-spec-compliant clients, proxies, CDNs, etc. | ||
* E.g. the value: | ||
* ```js | ||
* `"a", W/"b", c,d, "e", "f"` | ||
* ``` | ||
* Would be parsed and returned as: | ||
* ```js | ||
* ['a', 'b', 'c', 'd', 'e', 'f'] | ||
* ``` | ||
* @see https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/If-None-Match#syntax | ||
* ``` | ||
* If-None-Match: "etag_value" | ||
* If-None-Match: "etag_value", "etag_value", ... | ||
* If-None-Match: * | ||
* ``` | ||
* @param ifNoneMatchHeaderValue - raw header value | ||
* @returns an array of etag values | ||
*/ | ||
export function parseIfNoneMatchHeader( | ||
ifNoneMatchHeaderValue: string | undefined | ||
): string[] | undefined { | ||
if (!ifNoneMatchHeaderValue) { | ||
return undefined; | ||
} | ||
// Strip wrapping double quotes like `"hello"` and the ETag validation-prefix like `W/"hello"`. | ||
// The API returns compliant, strong-validation ETags (double quoted ASCII), but can't control what | ||
// clients, proxies, CDNs, etc may provide. | ||
const normalized = /^(?:"|W\/")?(.*?)"?$/gi.exec(ifNoneMatchHeaderValue.trim())?.[1]; | ||
if (!normalized) { | ||
// This should never happen unless handling a buggy request with something like `If-None-Match: ""`, | ||
// or if there's a flaw in the above code. Log warning for now. | ||
logger.warn(`Normalized If-None-Match header is falsy: ${ifNoneMatchHeaderValue}`); | ||
return undefined; | ||
} else if (normalized.includes(',')) { | ||
// Multiple etag values provided, likely irrelevant extra values added by a proxy/CDN. | ||
// Split on comma, also stripping quotes, weak-validation prefixes, and extra whitespace. | ||
return normalized.split(/(?:W\/"|")?(?:\s*),(?:\s*)(?:W\/"|")?/gi); | ||
} else { | ||
// Single value provided (the typical case) | ||
return [normalized]; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,4 @@ | ||
export * from './cache'; | ||
export * from './fastify'; | ||
export * from './openapi'; | ||
export * from './schemas'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
import { TSchema, Type } from '@sinclair/typebox'; | ||
|
||
export const Nullable = <T extends TSchema>(type: T) => Type.Union([type, Type.Null()]); | ||
export const Optional = <T extends TSchema>(type: T) => Type.Optional(type); | ||
|
||
export const PaginatedResponse = <T extends TSchema>(type: T, title: string) => | ||
Type.Object( | ||
{ | ||
limit: Type.Integer({ examples: [20] }), | ||
offset: Type.Integer({ examples: [0] }), | ||
total: Type.Integer({ examples: [1] }), | ||
results: Type.Array(type), | ||
}, | ||
{ title } | ||
); |