Skip to content

Commit

Permalink
feat (types): add typescript definitions and tests
Browse files Browse the repository at this point in the history
  • Loading branch information
darkgl0w authored Feb 28, 2022
2 parents b26b9de + 684c6d6 commit ffa6a2c
Show file tree
Hide file tree
Showing 3 changed files with 190 additions and 4 deletions.
18 changes: 14 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,15 @@
"version": "6.2.0",
"description": "A plugin for Fastify to enable management of cache control headers",
"main": "plugin.js",
"types": "plugin.d.ts",
"scripts": {
"lint": "standard --verbose | snazzy",
"lint:ci": "standard",
"test": "tap test/*.test.js"
"test": "npm run unit && npm run test:types",
"test:types": "tsd",
"unit": "tap -J \"test/*.test.js\"",
"unit:report": "npm run unit -- --coverage-report=html",
"unit:verbose": "npm run unit -- -Rspec"
},
"precommit": [
"lint",
Expand All @@ -28,15 +33,20 @@
},
"homepage": "https://github.com/fastify/fastify-caching#readme",
"devDependencies": {
"fastify": "^3.24.1",
"@types/node": "^17.0.21",
"fastify": "^3.27.2",
"pre-commit": "^1.2.2",
"snazzy": "^9.0.0",
"standard": "^16.0.4",
"tap": "^15.1.5"
"tap": "^15.1.6",
"tsd": "^0.19.1"
},
"dependencies": {
"abstract-cache": "^1.0.1",
"fastify-plugin": "^3.0.0",
"fastify-plugin": "^3.0.1",
"uid-safe": "^2.1.5"
},
"tsd": {
"directory": "test/types"
}
}
124 changes: 124 additions & 0 deletions plugin.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
/// <reference types='node' />

import { FastifyPluginCallback } from 'fastify';

declare module 'fastify' {
interface FastifyInstance {
cache: AbstractCacheCompliantObject;
cacheSegment: string;
etagMaxLife: number | undefined;
}

interface FastifyReply {
/**
* This method allows setting of the `expires` header.
*
* @link [reply.expires() documentation](https://github.com/fastify/fastify-caching#replyexpiresdate)
*
* @param date A regular `Date` object, or a valid date string according to [RFC 2616 section 14.21](https://datatracker.ietf.org/doc/html/rfc2616#section-14.21).
*/
expires(date?: Date): this;

/**
* This method allows setting of the `etag` header.
*
* @link [reply.etag() documentation](https://github.com/fastify/fastify-caching#replyetagstring-number)
*
* @param tag Any arbitrary string that is valid for HTTP headers.
* @param timeToLive The time must be specified in milliseconds. The default lifetime, when the parameter is not specified, is `3600000`.
*/
etag(tag?: string, timeToLive?: number): this;
}
}

/**
* @link [`abstract-cache` protocol documentation](https://github.com/jsumners/abstract-cache#protocol)
*/
export interface AbstractCacheCompliantObject {
get(
key: string | { id: string; segment: string },
callback?: (error: unknown, result: unknown) => void
): void;
set(
key: string | { id: string; segment: string },
value: unknown,
timeToLive: number,
callback?: (error: unknown, result: unknown) => void
): void;
}

export interface Privacy {
NOCACHE: 'no-cache';
PRIVATE: 'private';
PUBLIC: 'public';
}

/**
* @link [`fastify-caching` options documentation](https://github.com/fastify/fastify-caching#options)
*/
export interface FastifyCachingPluginOptions {
/**
* An [abstract-cache](https://www.npmjs.com/package/abstract-cache) protocol compliant cache object.
* Note: the plugin requires a cache instance to properly support the ETag mechanism.
* Therefore, if a falsy value is supplied the default will be used.
*
* - Default value: `abstract-cache.memclient`
*/
cache?: AbstractCacheCompliantObject;

/**
* The segment identifier to use when communicating with the cache.
*
* - Default value: `fastify-caching`
*/
cacheSegment?: string;

etagMaxLife?: number;

/**
* A value, in seconds, for the max-age the resource may be cached.
* When this is set, and privacy is not set to no-cache, then ', max-age=<value>'
* will be appended to the cache-control header.
*
* - Default value: `undefined`
*/
expiresIn?: number;

/**
* It can be set to any string that is valid for a cache-response-directive as
* defined by [RFC 2616](https://datatracker.ietf.org/doc/html/rfc2616#section-14.9).
*
* - Default value: `undefined`
*
* @link [MDN Cache-Control - Response Directives](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control#response_directives)
*
* @example
* const fastifyCaching = require('fastify-caching');
*
* // Disabling client side caching of all routes.
* fastify.register(fastifyCaching, { privacy: fastifyCaching.privacy.NOCACHE });
*/
privacy?: string;

/**
* A value, in seconds, for the length of time the resource is fresh and may be
* held in a shared cache (e.g. a CDN). Shared caches will ignore max-age when
* this is specified, though browsers will continue to use max-age. Should be
* used with expiresIn, not in place of it. When this is set, and privacy is set
* to public, then ', s-maxage=<value>' will be appended to the cache-control header.
*
* - Default value: `undefined`
*/
serverExpiresIn?: number;
}

declare const fastifyCaching: FastifyPluginCallback<FastifyCachingPluginOptions> & {
privacy: Privacy;
};

declare const privacy: {
privacy: Privacy;
};

export default fastifyCaching;
export { fastifyCaching, privacy };
52 changes: 52 additions & 0 deletions test/types/plugin.test-d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import Fastify, { FastifyReply } from 'fastify';
import { expectAssignable, expectError, expectType } from 'tsd';
import fastifyCaching, {
AbstractCacheCompliantObject,
FastifyCachingPluginOptions,
} from '../..';

const fastify = Fastify({ logger: true });

const cachingOptions: FastifyCachingPluginOptions = {
privacy: fastifyCaching.privacy.PUBLIC,
expiresIn: 300,
cacheSegment: 'fastify-caching',
};
expectAssignable<FastifyCachingPluginOptions>(cachingOptions);

fastify.register(fastifyCaching, cachingOptions);

expectType<AbstractCacheCompliantObject>(fastify.cache);
expectType<AbstractCacheCompliantObject['get']>(fastify.cache.get);
expectType<AbstractCacheCompliantObject['set']>(fastify.cache.set);
expectType<string>(fastify.cacheSegment);
// expectType<number>(fastify.etagMaxLife);

fastify.get('/one', async (request, reply) => {
expectType<(tag?: string, timeToLive?: number) => FastifyReply>(reply.etag);
expectType<(date?: Date) => FastifyReply>(reply.expires);

expectType<FastifyReply>(reply.etag('hello', 6000));
expectType<FastifyReply>(reply.expires(new Date(Date.now() + 6000)));

return { message: 'one' };
});

fastify.get('/two', async (request, reply) => {
expectType<FastifyReply>(
reply.etag('hello', 6000).expires(new Date(Date.now() + 6000))
);

return { message: 'two' };
});

// We register a new instance that should trigger a typescript error.
const shouldErrorApp = Fastify({ logger: true });

const badCachingOptions = {
privacy: fastifyCaching.privacy.PRIVATE,
expiresIn: 'a string instead of a number of second',
cacheSegment: 'fastify-caching',
};

expectError(shouldErrorApp.register(fastifyCaching, badCachingOptions));

0 comments on commit ffa6a2c

Please sign in to comment.