next-boost
Version:
Add a cache layer for next.js SSR pages. Use stale-while-revalidate to boost the performance.
76 lines (75 loc) • 2.94 kB
JavaScript
;
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.serveCache = void 0;
const stream_1 = require("stream");
const utils_1 = require("./utils");
const MAX_WAIT = 10000; // 10 seconds
const INTERVAL = 10; // 10 ms
function serveCache(cache, lock, key, forced, res) {
return __awaiter(this, void 0, void 0, function* () {
const rv = { status: 'force', stop: false };
if (forced)
return rv;
rv.status = yield cache.has('body:' + key);
// forced to skip cache or first-time miss
if (!lock.has(key) && rv.status === 'miss')
return rv;
// non first-time miss, wait for the cache
if (rv.status === 'miss')
yield waitAndServe(() => lock.has(key), rv);
const payload = { body: null, headers: null };
if (!rv.stop) {
payload.body = yield cache.get('body:' + key);
payload.headers = JSON.parse((yield cache.get('header:' + key)).toString());
}
send(payload, res);
// no need to run update again
if ((lock.has(key) && rv.status === 'stale') || rv.status === 'hit') {
rv.stop = true;
}
return rv;
});
}
exports.serveCache = serveCache;
function waitAndServe(hasLock, rv) {
return __awaiter(this, void 0, void 0, function* () {
const start = new Date().getTime();
while (hasLock()) {
yield (0, utils_1.sleep)(INTERVAL);
const now = new Date().getTime();
// to protect the server from heavy payload
if (now - start > MAX_WAIT) {
rv.stop = true;
rv.status = 'error';
return;
}
}
rv.status = 'hit';
});
}
function send(payload, res) {
const { body, headers } = payload;
if (!body) {
res.statusCode = 504;
return res.end();
}
for (const k in headers) {
res.setHeader(k, headers[k]);
}
res.statusCode = 200;
res.removeHeader('transfer-encoding');
res.setHeader('content-length', Buffer.byteLength(body));
res.setHeader('content-encoding', 'gzip');
const stream = new stream_1.PassThrough();
stream.pipe(res);
stream.end(body);
}