Skip to content
This repository has been archived by the owner on Nov 9, 2023. It is now read-only.

Migrate to TypeScript #11

Merged
merged 5 commits into from
Dec 7, 2020
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,13 @@ workflows:
- test-lint:
requires:
- prep-deps
- test-unit:
- test-build-unit:
requires:
- prep-deps
- all-tests-pass:
requires:
- test-lint
- test-unit
- test-build-unit

jobs:
prep-deps:
Expand Down Expand Up @@ -46,7 +46,7 @@ jobs:
name: Lint
command: yarn lint

test-unit:
test-build-unit:
docker:
- image: circleci/node:10
steps:
Expand All @@ -55,7 +55,7 @@ jobs:
at: .
- run:
name: Unit tests
command: yarn test
command: yarn build && yarn test

all-tests-pass:
docker:
Expand Down
19 changes: 17 additions & 2 deletions .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,24 @@ module.exports = {
extends: [
'@metamask/eslint-config',
'@metamask/eslint-config/config/nodejs',
'@metamask/eslint-config/config/typescript',
],
overrides: [{
files: [
'*.js',
'*.json',
],
parserOptions: {
sourceType: 'script',
},
rules: {
'@typescript-eslint/no-require-imports': 'off',
'@typescript-eslint/no-var-requires': 'off',
},
}],
ignorePatterns: [
'!.eslintrc.js',
'!eslintrc.js',
'dist/',
'node_modules/',
],
}
};
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@

dist
package-lock.json

# Created by https://www.gitignore.io/api/osx,node
Expand Down
28 changes: 0 additions & 28 deletions engineStream.js

This file was deleted.

59 changes: 0 additions & 59 deletions index.js

This file was deleted.

20 changes: 13 additions & 7 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,29 +1,35 @@
{
"name": "json-rpc-middleware-stream",
"version": "2.1.1",
"description": "A small toolset for streaming JSON RPC data and matching requests and responses.",
"description": "A small toolset for streaming JSON-RPC data and matching requests and responses.",
"license": "ISC",
"main": "index.js",
"files": [
"index.js",
"engineStream.js"
],
"scripts": {
"build": "tsc --project .",
"test": "node test/index.js",
"lint": "eslint . --ext js,json",
"lint:fix": "eslint . --ext js,json --fix"
},
"dependencies": {
"readable-stream": "^2.3.3",
"safe-event-emitter": "^1.0.1"
"@metamask/safe-event-emitter": "^2.0.0",
"readable-stream": "^2.3.3"
},
"devDependencies": {
"@metamask/eslint-config": "^2.1.1",
"eslint": "^7.0.0",
"@metamask/eslint-config": "^4.1.0",
"@types/readable-stream": "^2.3.9",
"@typescript-eslint/eslint-plugin": "^4.9.0",
"@typescript-eslint/parser": "^4.9.0",
"eslint": "^7.14.0",
"eslint-plugin-import": "^2.20.2",
"eslint-plugin-json": "^2.1.1",
"json-rpc-engine": "^5.1.8",
"tape": "^5.0.0"
"eslint-plugin-node": "^11.1.0",
"json-rpc-engine": "^6.1.0",
"tape": "^5.0.0",
"typescript": "^4.1.2"
},
"repository": {
"type": "git",
Expand Down
44 changes: 44 additions & 0 deletions src/createEngineStream.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { Duplex } from 'readable-stream';
import { JsonRpcEngine, JsonRpcRequest } from 'json-rpc-engine';

interface EngineStreamOptions {
engine: JsonRpcEngine;
}

/**
* Takes a JsonRpcEngine and returns a Duplex stream wrapping it.
*
rekmarks marked this conversation as resolved.
Show resolved Hide resolved
* @param opts - Options bag.
* @param opts.engine - The JsonRpcEngine to wrap in a stream.
* @returns The stream wrapping the engine.
*/
export default function createEngineStream(opts: EngineStreamOptions): Duplex {
if (!opts || !opts.engine) {
throw new Error('Missing engine parameter!');
}

const { engine } = opts;
const stream = new Duplex({ objectMode: true, read, write });
// forward notifications
if (engine.on) {
engine.on('notification', (message) => {
stream.push(message);
});
}
return stream;

function read() {
return undefined;
}

function write(
req: JsonRpcRequest<unknown>,
_encoding: unknown,
cb: (error?: Error | null) => void
rekmarks marked this conversation as resolved.
Show resolved Hide resolved
) {
engine.handle(req, (_err, res) => {
stream.push(res);
});
cb();
}
}
96 changes: 96 additions & 0 deletions src/createStreamMiddleware.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
import SafeEventEmitter from '@metamask/safe-event-emitter';
import { Duplex } from 'readable-stream';
import {
JsonRpcEngineNextCallback,
JsonRpcEngineEndCallback,
JsonRpcNotification,
JsonRpcMiddleware,
JsonRpcRequest,
PendingJsonRpcResponse,
} from 'json-rpc-engine';

interface IdMapValue {
req: JsonRpcRequest<unknown>;
res: PendingJsonRpcResponse<unknown>;
next: JsonRpcEngineNextCallback;
end: JsonRpcEngineEndCallback;
}

interface IdMap {
[requestId: string]: IdMapValue;
}

/**
* Creates a JsonRpcEngine middleware with an associated Duplex stream and
* EventEmitter. The middleware, and by extension stream, assume that middleware
* parameters are properly formatted. No runtime type checking or validation is
* performed.
*
rekmarks marked this conversation as resolved.
Show resolved Hide resolved
* @returns The event emitter, middleware, and stream.
*/
export default function createStreamMiddleware() {
const idMap: IdMap = {};
const stream = new Duplex({
objectMode: true,
read: readNoop,
write: processMessage,
});

const events = new SafeEventEmitter();

const middleware: JsonRpcMiddleware<unknown, unknown> = (
req,
res,
next,
end
rekmarks marked this conversation as resolved.
Show resolved Hide resolved
) => {
// write req to stream
stream.push(req);
// register request on id map
idMap[(req.id as unknown) as string] = { req, res, next, end };
};

return { events, middleware, stream };

function readNoop() {
return false;
}

function processMessage(
res: PendingJsonRpcResponse<unknown>,
_encoding: unknown,
cb: (error?: Error | null) => void
rekmarks marked this conversation as resolved.
Show resolved Hide resolved
) {
let err;
try {
const isNotification = !res.id;
if (isNotification) {
processNotification((res as unknown) as JsonRpcNotification<unknown>);
} else {
processResponse(res);
}
} catch (_err) {
err = _err;
}
// continue processing stream
cb(err);
}

function processResponse(res: PendingJsonRpcResponse<unknown>) {
const context = idMap[(res.id as unknown) as string];
if (!context) {
throw new Error(`StreamMiddleware - Unknown response id "${res.id}"`);
}

delete idMap[(res.id as unknown) as string];
// copy whole res onto original res
Object.assign(context.res, res);
// run callback on empty stack,
// prevent internal stream-handler from catching errors
setTimeout(context.end);
}

function processNotification(res: JsonRpcNotification<unknown>) {
events.emit('notification', res);
}
}
4 changes: 4 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import createEngineStream from './createEngineStream';
import createStreamMiddleware from './createStreamMiddleware';

export { createEngineStream, createStreamMiddleware };
Loading