Skip to content

Commit

Permalink
chore: cache abstraction
Browse files Browse the repository at this point in the history
  • Loading branch information
Ade Yahya Prasetyo authored and mirza adipradhana committed Apr 8, 2021
1 parent a77d440 commit 8cd81ec
Show file tree
Hide file tree
Showing 16 changed files with 138 additions and 75 deletions.
1 change: 1 addition & 0 deletions .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ module.exports = {
'@typescript-eslint/no-var-requires': 'off',
'react/prop-types': 'off',
'@typescript-eslint/no-explicit-any': 'off',
'@typescript-eslint/ban-ts-comment': 'off',
},
env: {
browser: true,
Expand Down
File renamed without changes.
1 change: 1 addition & 0 deletions jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ module.exports = {
transform: { '.(ts|tsx)$': 'ts-jest/dist' },
transformIgnorePatterns: ['[/\\\\]node_modules[/\\\\].+\\.(js|jsx)$'],
testMatch: ['**/__tests__/**/*.spec.[jt]s?(x)'],
testTimeout: 15000,
globals: {
'ts-jest': {
tsconfig: 'tsconfig.json',
Expand Down
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,7 @@
"git-notify": "0.2.3",
"html-webpack-plugin": "4.5.0",
"husky": "5.0.9",
"hyper-ts": "^0.6.1",
"identity-obj-proxy": "3.0.0",
"is-ci": "3.0.0",
"jest": "^26.6.3",
Expand Down Expand Up @@ -182,6 +183,7 @@
"tslib": "^2.1.0",
"typescript": "^4.2.3",
"url-loader": "4.1.1",
"uuid": "^8.3.2",
"web-vitals": "^1.0.1",
"webpack": "4.44.2",
"webpack-dev-server": "3.11.1",
Expand Down
6 changes: 2 additions & 4 deletions src/express/coreMiddleware.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,9 @@ import restMiddleware from './restMiddleware';
import graphqlMiddleware from './grapqhlMiddleware';

import { parseUrl } from '../utils';
import { ICacheDependency } from '../libs/cache';
import { ICache } from '../libs/cache';

const coreMiddleware = (cache: ICacheDependency): RequestHandler => (
...handler
) => {
const coreMiddleware = (cache: ICache): RequestHandler => (...handler) => {
const [req, , next] = handler;
const maybeUrl = parseUrl(req.path.slice(1));

Expand Down
42 changes: 25 additions & 17 deletions src/express/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,7 @@ import { logger } from '../logger';
import event from '../cli/event';
import apiRoutes from './routes/api';

import LMDB, {
_createCacheDirectoryThenGeneratePathOptions,
} from '../libs/lmdb';
import { createCacheInstance } from '../libs/cache';
import { buildDirPath } from '../utils/fs';
import { WARLOCK_CACHE_DIR_NAME } from '../constant';

Expand All @@ -26,25 +24,34 @@ const forms = multer();

const CACHE_BASE_PATH = os.homedir();

const lmdbOpts = pipe(
WARLOCK_CACHE_DIR_NAME,
buildDirPath(CACHE_BASE_PATH),
_createCacheDirectoryThenGeneratePathOptions,
const CACHE_PATH = pipe(WARLOCK_CACHE_DIR_NAME, buildDirPath(CACHE_BASE_PATH));

const cacheInstance = createCacheInstance(
IS_TEST_ENV
? {
type: 'memory',
options: {
max: 50,
},
}
: {
type: 'file',
options: {
path: CACHE_PATH,
maxDbs: 1,
},
},
);

const lmdbInstance = new LMDB(lmdbOpts);

export const purgeCache = () => {
const maybePurgedCache = lmdbInstance.reset();
export const purgeCache = () =>
E.fold(
(e) => {
!IS_TEST_ENV && logger.error(e);
},
() => {
!IS_TEST_ENV && logger.info('cache purged');
},
)(maybePurgedCache);
};
)(cacheInstance.reset());

event.on('purge', purgeCache);

Expand All @@ -55,10 +62,10 @@ app.use(bodyParser.urlencoded({ extended: true }));
app.use(bodyParser.json());
app.use('/api', apiRoutes);

app.use(coreMiddleware(lmdbInstance));
app.use(coreMiddleware(cacheInstance));
app.use(resolverMiddleware);
app.use((_, res) => {
if (typeof res.locals && Object.keys(res.locals).length === 0) {
if (typeof res.locals === 'object' && Object.keys(res.locals).length === 0) {
return res.sendFile(path.join(__dirname, '../../www/index.html'));
}

Expand Down Expand Up @@ -92,11 +99,12 @@ export const cleanup = () => {
console.info('shutdown signal is received');
console.info('will shutdown in 250ms');

cacheInstance?.close?.();

setTimeout(() => {
console.info('shutting down...');
lmdbInstance.close();
process.exit();
}, 10);
}, 250);
};

process.on('SIGINT', cleanup);
Expand Down
12 changes: 7 additions & 5 deletions src/express/restMiddleware.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { RequestHandler } from 'express';
import axios, { Method } from 'axios';
import * as E from 'fp-ts/Either';
import { ICacheDependency } from '../libs/cache';
import { ICache } from '../libs/cache';
import { getCacheKey, encode, decode } from '../libs/cacheHttp';
import { concatHeaders } from '../libs/http';
import { logger, debugable } from '../logger';
Expand All @@ -13,12 +13,14 @@ const instance = axios.create({
validateStatus: () => true,
});

const restMiddleware = (cache: ICacheDependency) => (
url: URL,
): RequestHandler => (req, res, next) => {
const restMiddleware = (cache: ICache) => (url: URL): RequestHandler => (
req,
res,
next,
) => {
const cacheKey = getCacheKey(req);
const responseWithCache = (cacheKey: string) => {
const cachedResponse = decode(cache.get(cacheKey));
const cachedResponse = decode(cache.get(cacheKey) ?? '{}');
if (E.isRight(cachedResponse)) {
const _cachedResponse = cachedResponse.right;
res.locals = _cachedResponse?.data;
Expand Down
8 changes: 8 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,9 @@
import express from 'express';

import { parserMiddleware } from './middleware';

export * from './types';
export const app = express();

// body parser and multer
app.use(parserMiddleware);
67 changes: 23 additions & 44 deletions src/libs/cache.ts
Original file line number Diff line number Diff line change
@@ -1,56 +1,35 @@
import { Reader } from 'fp-ts/lib/Reader';
import { pipe } from 'fp-ts/lib/pipeable';
import { IO } from 'fp-ts/lib/IO';
import LRU, { LRUOption } from './lru';
import * as E from 'fp-ts/Either';
import { fromNullable, Option } from 'fp-ts/lib/Option';

import { sortURLSearchParams } from '../utils/url';
import { toString, hashStr } from '../utils/generic';
import LMDB, { LMDBOptions } from './lmdb';

// @TODO Cache suppose to be modular, where we can select whether the cache will be stored on the memory, db, or file
// type CacheOption = {
// store: 'memory' | 'db' | 'file',
// max: number,
// ttl: number,
// logger: () => void,
// }

export interface ICacheDependency {
export interface ICache {
has: (key: string) => boolean;
get: (key: string) => string;
get: (key: string) => string | undefined;
set: (key: string, value: string) => boolean;
reset: () => E.Either<Error, null> | void;
reset: () => E.Either<Error, null>;
close?: () => void;
}

export interface ICache {
setItem: <T>(key: string, value: unknown) => E.Either<unknown, T>;
getItem: <T>(key: string) => E.Either<unknown, T>;
export interface MemoryOption {
type: 'memory';
options: LRUOption;
}

export const getItem = (
key: string,
): Reader<ICacheDependency, IO<Option<string>>> => (deps) => () =>
fromNullable(deps.get(key));

export const hasItem = (key: string): Reader<ICacheDependency, IO<boolean>> => (
deps,
) => () => deps.has(key);

export const setItem = (
key: string,
value: string,
): Reader<ICacheDependency, IO<boolean>> => (deps) => () =>
deps.set(key, value);
export interface FileOption {
type: 'file';
options: LMDBOptions;
}

export const getGetRequest = (url: URL) =>
pipe(sortURLSearchParams(url), toString, hashStr, (key) => (deps) =>
getItem(key)(deps),
);
export type CacheOptions = MemoryOption | FileOption;

export const hasGetRequest = (url: URL) =>
pipe(sortURLSearchParams(url), toString, hashStr, hasItem);
export const createCacheInstance = (options: CacheOptions): ICache => {
if (options.type === 'memory') {
return new LRU(options.options);
}
if (options.type === 'file') {
return new LMDB(options.options);
}

export const setGetRequest = (url: URL, responsePayload: string) =>
pipe(sortURLSearchParams(url), toString, hashStr, (key) => (deps) =>
setItem(key, responsePayload)(deps),
);
return new LRU({ max: 50 });
};
6 changes: 3 additions & 3 deletions src/libs/lmdb.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,14 @@ import { pipe } from 'fp-ts/function';
import * as E from 'fp-ts/Either';
import * as O from 'fp-ts/Option';

import { ICacheDependency } from './cache';
import { ICache } from './cache';
import { createDirIfNotExist, removeDirIfExist } from '../utils/fs';

interface LMDBOptions {
export interface LMDBOptions {
path: string;
maxDbs: number;
}
export default class LMDB implements ICacheDependency {
export default class LMDB implements ICache {
'env': any;
'dbi': any;
'txn': any;
Expand Down
36 changes: 36 additions & 0 deletions src/libs/lru.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import LRUBase from 'lru-cache';
import * as E from 'fp-ts/Either';
import { ICache } from './cache';

export interface LRUOption {
max: number;
}

export default class LRU implements ICache {
env: LRUBase<string, string>;

constructor(options: LRUOption) {
this.env = new LRUBase(options.max);
}

public get = (key: string) => {
return this.env.get(key);
};

public set = (key: string, value: string) => {
return this.env.set(key, value);
};

public has = (key: string) => {
return this.env.has(key);
};

public reset = () => {
this.env.reset();
return E.right(null);
};

public close = () => {
return null;
};
}
1 change: 0 additions & 1 deletion src/middleware/errorHandler.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
// @ts-nocheck
// @TODO {proxy} need following @types/koa-bodyparser
const errorHandler = async (ctx, next) => {
return next().catch((err) => {
Expand Down
1 change: 1 addition & 0 deletions src/middleware/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './parser';
12 changes: 12 additions & 0 deletions src/middleware/parser.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { Router } from 'express';
import bodyParser from 'body-parser';
import multer from 'multer';

const forms = multer();

const router = Router();
router.use(forms.any());
router.use(bodyParser.urlencoded({ extended: true }));
router.use(bodyParser.json());

export const parserMiddleware = router;
11 changes: 11 additions & 0 deletions src/store.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import * as O from 'fp-ts/Option';

import { ICache } from './libs/cache';
import { Config } from './types';

export interface Store {
config: Config;
cacheStorage: ICache;
locals: Record<string, O.Option<unknown>>;
getLocal: (key: string) => O.Option<unknown>;
}
7 changes: 6 additions & 1 deletion yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -10112,6 +10112,11 @@ [email protected]:
resolved "https://registry.yarnpkg.com/husky/-/husky-5.0.9.tgz#6d38706643d66ed395bcd4ee952d02e3f15eb3a3"
integrity sha512-0SjcaY21a+IRdx7p7r/X33Vc09UR2m8SbP8yfkhUX2/jAmwcz+GR7i9jXkp2pP3GfX23JhMkVP6SWwXB18uXtg==

hyper-ts@^0.6.1:
version "0.6.1"
resolved "https://registry.yarnpkg.com/hyper-ts/-/hyper-ts-0.6.1.tgz#a219ec6a170b5d6c7d665561c0a5b1e0b50c758b"
integrity sha512-rhtf4s/VdDQSxKRvgFm4AVMPyuyOdAyeoZ641r3+fHnHyrRk9oSwHzVI8KjR1FFN9fUvSZQdGzgA7/p8eV+I6Q==

[email protected], iconv-lite@^0.4.24:
version "0.4.24"
resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b"
Expand Down Expand Up @@ -17396,7 +17401,7 @@ uuid@^3.3.2, uuid@^3.4.0:
resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.4.0.tgz#b23e4358afa8a202fe7a100af1f5f883f02007ee"
integrity sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==

uuid@^8.3.0:
uuid@^8.3.0, uuid@^8.3.2:
version "8.3.2"
resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2"
integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==
Expand Down

0 comments on commit 8cd81ec

Please sign in to comment.