Skip to content

Commit

Permalink
Merge pull request #1 from Famcache/rc-1.0.0
Browse files Browse the repository at this point in the history
RC: 1.0.0
  • Loading branch information
shahen94 authored May 29, 2024
2 parents 3b4503d + 38ca451 commit d346f35
Show file tree
Hide file tree
Showing 16 changed files with 5,435 additions and 46 deletions.
41 changes: 41 additions & 0 deletions .github/workflows/lint_and_test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# This workflow will do a clean installation of node dependencies, cache/restore them, build the source code and run tests across different versions of node
# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-nodejs

name: Node.js CI

on:
push:
branches: [ "main" ]
pull_request:
branches: [ "main" ]

jobs:
lint:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [21.x]
steps:
- uses: actions/checkout@v4
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v3
with:
node-version: ${{ matrix.node-version }}
cache: 'npm'
- run: yarn
- run: yarn lint
test:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [21.x]
steps:
- uses: actions/checkout@v4
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v3
with:
node-version: ${{ matrix.node-version }}
cache: 'npm'
- run: yarn
- run: yarn test

27 changes: 27 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# This workflow will do a clean installation of node dependencies, cache/restore them, build the source code and run tests across different versions of node
# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-nodejs

name: Release

on:
push:
branches: [ "main" ]

jobs:
release:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [21.x]
steps:
- uses: actions/checkout@v4
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v3
with:
node-version: ${{ matrix.node-version }}
cache: 'npm'
- run: yarn
- run: yarn lint
- run: yarn test
- run: yarn build
- run: npx semantic-release
12 changes: 12 additions & 0 deletions .npmignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
src
node_modules
example
.github
.gitignore
.prettierrc
.prettierignore
eslint.config.mjs
jest.config.js
release.config.cjs
settings.json
tsconfig.json
6 changes: 6 additions & 0 deletions eslint.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,10 @@ export default [
{ languageOptions: { globals: globals.node } },
pluginJs.configs.recommended,
...tseslint.configs.recommended,
{
files: ['**/*.spec.ts'],
languageOptions: {
globals: globals.jest
}
},
];
10 changes: 9 additions & 1 deletion example/client.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import Famcache from 'famcache';
import Famcache from '../src';

const cache = new Famcache({
host: 'localhost',
Expand All @@ -9,6 +9,14 @@ cache
.connect()
.then(() => {
console.log('Connected!');

cache.set('key', '10', 3000)
.then(() => {
return cache.get('key');
})
.then((data) => {
console.log('Received', data);
});
})
.catch((e) => {
console.log('Failed to connect');
Expand Down
5 changes: 5 additions & 0 deletions jest.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
/** @type {import('ts-jest').JestConfigWithTsJest} */
module.exports = {
preset: 'ts-jest',
testEnvironment: 'node',
};
9 changes: 7 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"name": "famcache",
"name": "@famcache/famcache",
"version": "1.0.0",
"description": "",
"main": "dist/index.js",
Expand All @@ -9,20 +9,25 @@
"lint": "eslint .",
"lint:fix": "eslint . --fix",
"format": "prettier --write .",
"test": "echo \"Error: no test specified\" && exit 1"
"test": "jest"
},
"keywords": [],
"author": "",
"license": "ISC",
"devDependencies": {
"@eslint/js": "^9.3.0",
"@semantic-release/changelog": "^6.0.3",
"@types/jest": "^29.5.12",
"@types/node": "^20.12.12",
"eslint": "9.x",
"eslint-config-prettier": "^9.1.0",
"eslint-plugin-prettier": "^5.1.3",
"globals": "^15.3.0",
"jest": "^29.7.0",
"nodemon": "^3.1.1",
"prettier": "^3.2.5",
"semantic-release": "^23.1.1",
"ts-jest": "^29.1.4",
"ts-node": "^10.9.2",
"typescript": "^5.4.5",
"typescript-eslint": "^7.10.0"
Expand Down
13 changes: 13 additions & 0 deletions release.config.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
/**
* @type {import('semantic-release').GlobalConfig}
*/
module.exports = {
branches: ["main"],
plugins: [
"@semantic-release/commit-analyzer",
"@semantic-release/release-notes-generator",
"@semantic-release/changelog",
"@semantic-release/npm",
"@semantic-release/github"
]
};
26 changes: 26 additions & 0 deletions src/cache-query.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import CacheQuery from "./cache-query";
describe('CacheQuery', () => {
it('should parse response string that contains data', () => {
const query = CacheQuery.fromString('1 OK 10');

expect(query.id).toBe('1');
expect(query.isOk()).toBe(true);
expect(query.data).toBe('10');
})

it('should parse response string that contains error', () => {
const query = CacheQuery.fromString('1 ERROR');

expect(query.id).toBe('1');
expect(query.isError()).toBe(true)
expect(query.data).toBe('');
});

it('should parse response string that contains data with spaces', () => {
const query = CacheQuery.fromString('1 OK 10 20 30');

expect(query.id).toBe('1');
expect(query.isOk()).toBe(true);
expect(query.data).toBe('10 20 30');
});
});
30 changes: 30 additions & 0 deletions src/cache-query.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
type QueryStatus = 'OK' | 'ERROR';

class CacheQuery {
public id: string;
private status: QueryStatus;

public data?: string;

static fromString(data: string): CacheQuery {
const [id, status, ...rest] = data.split(' ');

return new CacheQuery(id, status as QueryStatus, rest.join(' '));
}

constructor(id: string, status: QueryStatus, data: string) {
this.id = id;
this.status = status;
this.data = data;
}

isOk() {
return this.status === 'OK';
}

isError() {
return this.status === 'ERROR';
}
}

export default CacheQuery;
27 changes: 27 additions & 0 deletions src/commands.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { set, get, del } from "./commands";

describe('commands', () => {
it('should generate get command', () => {
const command = get('1', 'key');

expect(command).toBe('1 GET key\n');
});

it('should generate set command', () => {
const command = set('1', 'key', 'value', 1000);

expect(command).toBe('1 SET key value 1000\n');
});

it('should generate set command without ttl', () => {
const command = set('1', 'key', 'value');

expect(command).toBe('1 SET key value\n');
});

it('should generate del command', () => {
const command = del('1', 'key');

expect(command).toBe('1 DEL key\n');
});
});
23 changes: 17 additions & 6 deletions src/commands.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,22 @@
export function get(key: string): string {
return `GET ${key}`;
export function get(id: string, key: string): string {
return `${id} GET ${key}\n`;
}

export function set(key: string, value: string, ttl?: number): string {
return `SET ${key} ${value} ${ttl}`;
export function set(
id: string,
key: string,
value: string,
ttl?: number,
): string {
const args = [key, value];

if (ttl) {
args.push(ttl.toString());
}

return `${id} SET ${args.join(' ')}\n`;
}

export function del(key: string): string {
return `DEL ${key}`;
export function del(id: string, key: string): string {
return `${id} DEL ${key}\n`;
}
63 changes: 59 additions & 4 deletions src/famcache.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,53 @@
import { Socket } from 'net';
import { randomUUID } from 'crypto';
import type { ConnectionParams } from './params';
import { get, set, del } from './commands';
import type { QueueResolver } from './types';
import CacheQuery from './cache-query';

class Famcache {
private socket: Socket;
private params: ConnectionParams;
private queue: Map<string, QueueResolver>;

constructor(params: ConnectionParams) {
this.socket = new Socket();
this.queue = new Map();

this.params = params;
}

private genId() {
return randomUUID();
}

private listen() {
this.socket.on('data', (data) => {
const payload = data.toString();
const query = CacheQuery.fromString(payload);

const resolver = this.queue.get(query.id);

if (!resolver) {
this.queue.delete(query.id);
return;
}

if (query.isError()) {
resolver.reject(query.data);
} else {
resolver.resolve(query.data);
}

this.queue.delete(query.id);
});
}

connect(): Promise<void> {
return new Promise((resolve, reject) => {
this.socket.connect(this.params.port, this.params.host, () => {
this.listen();

resolve();
});
this.socket.on('error', (err) => {
Expand All @@ -23,15 +57,36 @@ class Famcache {
}

set(key: string, value: string, ttl?: number) {
this.socket.emit(set(key, value, ttl));
return new Promise<void>((resolve, reject) => {
const queryId = this.genId();

this.socket.write(set(queryId, key, value, ttl));

this.queue.set(queryId, { resolve: () => resolve(), reject });
});
}

get(key: string) {
this.socket.emit(get(key));
get<T>(key: string) {
return new Promise<T>((resolve, reject) => {
const queryId = this.genId();

this.socket.write(get(queryId, key));

this.queue.set(queryId, {
resolve: (data) => resolve(data as T),
reject,
});
});
}

del(key: string) {
this.socket.emit(del(key));
return new Promise<void>((resolve, reject) => {
const queryId = this.genId();

this.socket.write(del(queryId, key));

this.queue.set(queryId, { resolve: () => resolve(), reject });
});
}
}

Expand Down
6 changes: 6 additions & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export type Optional<T> = T | null | undefined | void;

export type QueueResolver = {
resolve: (value: Optional<string>) => void;
reject: (reason: Optional<string>) => void;
};
Loading

0 comments on commit d346f35

Please sign in to comment.