hamok
Version:
Lightweight Distributed Object Storage on RAFT consensus algorithm
169 lines (145 loc) • 4.81 kB
text/typescript
import { Hamok, HamokMessage, setHamokLogLevel } from 'hamok';
import Redis from 'ioredis';
import * as pino from 'pino';
import { createRedisRemoteMap } from './utils/RedisRemoteMap';
import EventEmitter from 'events';
const logger = pino.pino({
name: 'redis-remote-requestmap-example',
level: 'debug',
});;
type RequestMap = {
'my-request-type': {
parameters: [string, number];
response: string;
},
'my-other-request-type': {
parameters: [string];
response: number;
},
}
type PendingRequest = {
requestId: string;
type: keyof RequestMap;
parameters: unknown[];
result?: unknown;
error?: string;
}
const publisher = new Redis();
const subscriber = new Redis();
const channelId = 'hamok-channel';
const mapId = 'cached-items' + Math.random();
export async function run() {
setHamokLogLevel('warn');
const [
{ server: server_1, requestor: requestor_1 },
{ server: server_2, requestor: requestor_2 },
] = await Promise.all([
createTools(),
createTools(),
// last
subscriber.subscribe(channelId),
]);
await Promise.all([
server_1.join(),
server_2.join(),
]);
requestor_1.on('my-request-type', (param1, param2, resolve, reject) => {
logger.info('Received request my-request-type with parameters %s and %s', param1, param2);
resolve('result');
});
requestor_2.on('my-other-request-type', (param1, resolve, reject) => {
logger.info('Received request my-other-request-type with parameter %s', param1);
resolve(42);
});
const result_1 = await requestor_2.request('my-request-type', 'param1', 42);
const result_2 = await requestor_1.request('my-other-request-type', 'param1');
logger.info('Result of my-request-type: %s', result_1);
logger.info('Result of my-other-request-type: %s', result_2);
await server_1.waitUntilCommitHead();
await server_2.waitUntilCommitHead();
server_1.close();
server_2.close();
}
async function createTools() {
type RequestEventMap = {
[key in keyof RequestMap]: [...RequestMap[key]['parameters'], (result: RequestMap[key]['response']) => void, (error: string) => void];
}
const server = new Hamok();
const storage = server.createRemoteMap<string, PendingRequest>({
mapId,
remoteMap: createRedisRemoteMap<PendingRequest>(mapId, publisher),
});
const ownedRequests = new Map<string, { resolve: (result: any) => void, reject: (error: string) => void}>();
const requestor = new class extends EventEmitter<RequestEventMap> {
request<T extends keyof RequestMap>(requestType: T, ...parameters: RequestMap[T]['parameters']): Promise<RequestMap[T]['response']> {
const requestId = Math.random().toString(36).slice(2);
return new Promise((resolve, reject) => {
const request: PendingRequest = {
requestId,
type: requestType,
parameters,
};
ownedRequests.set(requestId, { resolve, reject });
storage.set(requestId, request);
});
}
}
const resolveRequest = async (request: PendingRequest, result: unknown) => {
if (!request) {
return;
}
request.result = result;
try {
await storage.set(request.requestId, request);
} catch (err) {
logger.error('Failed to resolve request: %s', err);
}
}
const rejectRequest = async (request: PendingRequest, error: string) => {
if (!request) {
return;
}
request.error = error;
try {
await storage.set(request.requestId, request);
} catch (err) {
logger.error('Failed to reject request: %s', err);
}
}
const onRequestChanged = (newValue: PendingRequest, oldValue?: PendingRequest) => {
if (!oldValue) {
const resolve = (result: unknown) => resolveRequest(newValue, result);
const reject = (error: string) => rejectRequest(newValue, error);
const parameters = [
...newValue.parameters,
resolve,
reject,
];
return requestor.emit(newValue.type, ...(parameters as any));
}
const pendingRequest = ownedRequests.get(newValue.requestId);
if (!pendingRequest) return;
if (newValue.result !== undefined) {
pendingRequest.resolve(newValue.result);
storage.delete(newValue.requestId).catch(err => logger.error('Failed to delete request: %s', err));
} else if (newValue.error) {
pendingRequest.reject(newValue.error);
storage.delete(newValue.requestId).catch(err => logger.error('Failed to delete request: %s', err));
}
}
subscriber.on('messageBuffer', (channel, buffer) => {
server.accept(HamokMessage.fromBinary(buffer));
});
server.on('message', message => publisher.publish(channelId, Buffer.from(message.toBinary())));
storage.on('insert', (key, value) => onRequestChanged(value));
storage.on('update', (key, oldValue, newValue) => onRequestChanged(newValue, oldValue));
return {
server,
requestor,
};
}
if (require.main === module) {
logger.info('Running from module file');
setHamokLogLevel('info');
run();
}