Skip to content

Commit

Permalink
feat: postgres storage (#10)
Browse files Browse the repository at this point in the history
  • Loading branch information
boredland committed Oct 3, 2022
1 parent 1c572e2 commit 779bdc7
Show file tree
Hide file tree
Showing 40 changed files with 2,916 additions and 1,022 deletions.
9 changes: 9 additions & 0 deletions .changeset/wise-bulldogs-double.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
"@boredland/node-ts-cache": major
"@boredland/node-ts-cache-storage-ioredis": major
"@boredland/node-ts-cache-storage-memory": major
"@boredland/node-ts-cache-storage-node-fs": major
"@boredland/node-ts-cache-storage-pg": major
---

feat: postgres storage
2 changes: 2 additions & 0 deletions .eslintignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
**/dist
.eslintrc.js
10 changes: 10 additions & 0 deletions .eslintrc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
module.exports = {
extends: [
"eslint:recommended",
"plugin:@typescript-eslint/recommended",
"plugin:prettier/recommended",
],
parser: "@typescript-eslint/parser",
plugins: ["@typescript-eslint"],
root: true,
};
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ _Note: The underlying storage layer must be installed separately._
| [memory](https://www.npmjs.com/package/@boredland/node-ts-cache-storage-memory)| ```yarn add @boredland/node-ts-cache-storage-memory```|
| [node-fs](https://www.npmjs.com/package/@boredland/node-ts-cache-storage-node-fs)| ```yarn add @boredland/node-ts-cache-storage-node-fs```|
| [ioredis](https://www.npmjs.com/package/@boredland/node-ts-cache-storage-ioredis)| ```yarn add @boredland/node-ts-cache-storage-ioredis```|
| [postgres](https://www.npmjs.com/package/@boredland/node-ts-cache-storage-pg)| ```yarn add @boredland/node-ts-cache-storage-pg```|

## Usage

Expand Down
35 changes: 22 additions & 13 deletions jest.config.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,25 @@
import type { Config } from "@jest/types"
import type { Config } from "@jest/types";

const config: Config.InitialOptions = {
verbose: true,
preset: "ts-jest",
testEnvironment: "node",
ci: true,
moduleNameMapper: {
'@boredland/node-ts-cache': '<rootDir>/packages/core/src'
},
collectCoverageFrom: [
"**/*.ts"
]
}
verbose: true,
rootDir: process.cwd(),
moduleFileExtensions: ["js", "ts"],
coveragePathIgnorePatterns: ["jest.config.ts", ".*(TestFactory.ts)$"],
transform: {
".*\\.(ts?)$": [
"@swc/jest",
{
jsc: {
parser: {
decorators: true,
syntax: "typescript",
},
},
},
],
},
ci: true,
collectCoverageFrom: ["**/*.ts"],
};

export default config
export default config;
9 changes: 8 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,17 @@
},
"devDependencies": {
"@changesets/cli": "^2.24.4",
"@swc/core": "^1.3.4",
"@swc/jest": "^0.2.23",
"@types/jest": "^29.1.0",
"@types/node": "^16",
"@typescript-eslint/eslint-plugin": "^5.38.1",
"@typescript-eslint/parser": "^5.38.1",
"eslint": "^8.24.0",
"eslint-config-prettier": "^8.5.0",
"eslint-plugin-prettier": "^4.2.1",
"jest": "^29.1.1",
"ts-jest": "^29.0.3",
"prettier": "^2.7.1",
"ts-node": "^10.9.1",
"turbo": "^1.5.5",
"typescript": "^4.8.4"
Expand Down
46 changes: 24 additions & 22 deletions packages/core/src/cache-container/cache-container-types.ts
Original file line number Diff line number Diff line change
@@ -1,26 +1,28 @@
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export type CachedItem<T = any> = {
content: T
meta: {
createdAt: number
ttl: number
isLazy: boolean
}
}
content: T;
meta: {
createdAt: number;
ttl: number | null;
isLazy: boolean;
};
};

export type CachingOptions = {
/** (Default: 60) Number of seconds to expire the cachte item */
ttl: number
/** (Default: true) If true, expired cache entries will be deleted on touch and returned anyway. If false, entries will be deleted after the given ttl. */
isLazy: boolean
/** (Default: false) If true, cache entry has no expiration. */
isCachedForever: boolean
/** (Default: JSON.stringify combination of className, methodName and call args) */
calculateKey: (data: {
/** The class name for the method being decorated */
className: string
/** The method name being decorated */
methodName: string
/** The arguments passed to the method when called */
args: any[]
}) => string
/** (Default: 60) Number of seconds to expire the cachte item */
ttl: number;
/** (Default: true) If true, expired cache entries will be deleted on touch and returned anyway. If false, entries will be deleted after the given ttl. */
isLazy: boolean;
/** (Default: false) If true, cache entry has no expiration. */
isCachedForever: boolean;
/** (Default: JSON.stringify combination of className, methodName and call args) */
calculateKey: (data: {
/** The class name for the method being decorated */
className: string;
/** The method name being decorated */
methodName: string;
/** The arguments passed to the method when called */
// eslint-disable-next-line @typescript-eslint/no-explicit-any
args: any[];
}) => string;
};
130 changes: 66 additions & 64 deletions packages/core/src/cache-container/cache-container.ts
Original file line number Diff line number Diff line change
@@ -1,69 +1,71 @@
import Debug from "debug"
import type { IStorage } from "../storage"
import type { CachedItem, CachingOptions } from "./cache-container-types"
import Debug from "debug";
import type { Storage } from "../storage";
import type { CachedItem, CachingOptions } from "./cache-container-types";

const debug = Debug("node-ts-cache")
const debug = Debug("node-ts-cache");

const DEFAULT_TTL_SECONDS = 60
const DEFAULT_TTL_SECONDS = 60;

export class CacheContainer {
constructor(private storage: IStorage) { }

public async getItem<T>(key: string): Promise<{ content: T, meta: { expired: boolean, createdAt: number } } | undefined> {
const item = await this.storage.getItem(key)

if (!item) return;

const result = {
content: item.content,
meta: {
createdAt: item.meta.createdAt,
expired: this.isItemExpired(item)
}
}

if (result.meta.expired)
await this.unsetKey(key);

if (result.meta.expired && !item.meta.isLazy)
return undefined;

return result;
}

public async setItem(
key: string,
content: any,
options: Partial<CachingOptions>
): Promise<void> {
const finalOptions = {
ttl: DEFAULT_TTL_SECONDS,
isLazy: true,
isCachedForever: false,
...options
}

const meta: CachedItem<typeof content>["meta"] = {
createdAt: Date.now(),
isLazy: finalOptions.isLazy,
ttl: finalOptions.isCachedForever ? Infinity : finalOptions.ttl * 1000
}

await this.storage.setItem(key, { meta, content })
}

public async clear(): Promise<void> {
await this.storage.clear()

debug("Cleared cache")
}

private isItemExpired(item: CachedItem): boolean {
if (item.meta.ttl === Infinity) return false;
return Date.now() > item.meta.createdAt + item.meta.ttl
}

private async unsetKey(key: string): Promise<void> {
await this.storage.setItem(key, undefined)
}
constructor(private storage: Storage) {}

public async getItem<T>(
key: string
): Promise<
{ content: T; meta: { expired: boolean; createdAt: number } } | undefined
> {
const item = await this.storage.getItem(key);

if (!item) return;

const result = {
content: item.content,
meta: {
createdAt: item.meta.createdAt,
expired: this.isItemExpired(item),
},
};

if (result.meta.expired) await this.unsetKey(key);

if (result.meta.expired && !item.meta.isLazy) return undefined;

return result;
}

public async setItem(
key: string,
content: unknown,
options?: Partial<CachingOptions>
): Promise<void> {
const finalOptions = {
ttl: DEFAULT_TTL_SECONDS,
isLazy: true,
isCachedForever: false,
...options,
};

const meta: CachedItem<typeof content>["meta"] = {
createdAt: Date.now(),
isLazy: finalOptions.isLazy,
ttl: finalOptions.isCachedForever ? null : finalOptions.ttl * 1000,
};

await this.storage.setItem(key, { meta, content });
}

public async clear(): Promise<void> {
await this.storage.clear();

debug("Cleared cache");
}

private isItemExpired(item: CachedItem): boolean {
if (item.meta.ttl === null) return false;
return Date.now() > item.meta.createdAt + item.meta.ttl;
}

public async unsetKey(key: string): Promise<void> {
await this.storage.removeItem(key);
}
}
4 changes: 2 additions & 2 deletions packages/core/src/cache-container/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
export * from "./cache-container"
export * from "./cache-container-types"
export * from "./cache-container";
export * from "./cache-container-types";
Loading

0 comments on commit 779bdc7

Please sign in to comment.