Comparing version 4.0.0-beta.1 to 4.0.0-beta.2
@@ -1,5 +0,8 @@ | ||
const isNot = require('../lib/isNot'); | ||
const Middleware = require('./Middleware'); | ||
const Client = require('./Client'); | ||
const isNot = require('../lib/isNot'); | ||
const isActionOfType = require('../lib/isActionOfType'); | ||
const createAction = require('../lib/createAction'); | ||
/** | ||
@@ -13,2 +16,3 @@ * Handler type for a middleware or watcher | ||
* @prop {String} type | ||
* @prop {Mixed} [payload] | ||
*/ | ||
@@ -33,4 +37,4 @@ | ||
*/ | ||
watch(type, watcher) { | ||
this.watchers.use(type, watcher); | ||
watch(/* type, watcher */) { | ||
this.watchers.use(...arguments); | ||
// It is need for chaining | ||
@@ -58,3 +62,3 @@ return this; | ||
*/ | ||
use() { | ||
use(/* type, middleware */) { | ||
return this.before.use(...arguments); | ||
@@ -102,3 +106,8 @@ } | ||
Redbone.prototype.is = isActionOfType; | ||
Redbone.prototype.createAction = createAction; | ||
Redbone.is = isActionOfType; | ||
Redbone.createAction = createAction; | ||
Redbone.Client = Client; | ||
module.exports = Redbone; |
{ | ||
"name": "redbone", | ||
"version": "4.0.0-beta.1", | ||
"version": "4.0.0-beta.2", | ||
"description": "Polymorphic library for two-way dispatching of actions", | ||
@@ -5,0 +5,0 @@ "main": "index.js", |
166
README.md
@@ -130,13 +130,14 @@ # Redbone | ||
```js | ||
// We will look it closer later | ||
const { Server } = require('net'); | ||
const Redbone = require('redbone'); | ||
// We will look it later | ||
const Client = require('./Client'); | ||
// Transport should be an EventEmitter child | ||
class TcpTransport { | ||
#server = null; | ||
const Types = { | ||
CONNECTION: 'connection', | ||
DISCONNECT: 'disconnect' | ||
} | ||
// the constructor will be his own for each transport | ||
constructor(server) { | ||
super(); | ||
// this field will be added when transport will be added to a Redbone's instance | ||
class RedboneTransportTCP { | ||
constructor(options) { | ||
this.redbone = new Redbone(); | ||
@@ -146,13 +147,17 @@ | ||
this.onError = this.onError.bind(this); | ||
this.server = server; | ||
this.onRedboneError = this.onRedboneError.bind(this); | ||
this.server = new Server(options); | ||
// Add redbone's catcher | ||
this.redbone.catch(this.onRedboneError); | ||
} | ||
set server(server) { | ||
if (this.#server) this._unsub(this.#server); | ||
if (this._server) this._unsub(this._server); | ||
this._sub(server); | ||
this.#server = server; | ||
this._server = server; | ||
} | ||
get server() { | ||
return this.#server; | ||
return this._server || null; | ||
} | ||
@@ -166,43 +171,115 @@ | ||
_unsub(server) { | ||
server.removeListener('connection', this.onConnection); | ||
server.removeListener('error', this.onError); | ||
server.off('connection', this.onConnection); | ||
server.off('error', this.onError); | ||
} | ||
listen(port) { | ||
if (!this._server) throw new ReferenceError('no server for listen') | ||
return new Promise((resolve, reject) => { | ||
this._server.listen(port, (err) => { | ||
if (err) return reject(err); | ||
return resolve(); | ||
}); | ||
}); | ||
} | ||
close() { | ||
if (!this._server) return; | ||
return new Promise((resolve, reject) => { | ||
this._server.close((err) => { | ||
if (err) return reject(err); | ||
return resolve(); | ||
}); | ||
}); | ||
} | ||
_processAction(client, data) { | ||
try { | ||
// process action every message | ||
const action = JSON.parse(data); | ||
// When transport has a client and an action | ||
// it should call redbone's dispatch | ||
this.redbone.dispatch(client, action); | ||
} catch (err) { | ||
// If JSON is invalid | ||
this.redbone.catcher(err, client, action); | ||
this.onError(err); | ||
} | ||
} | ||
onConnection(socket) { | ||
// client is an instance of Client class. | ||
// Every platform shoud implement Client class, inherited from Redbone's Client, | ||
// we will look it closer later | ||
const client = new Client({ transport: this, native: socket }); | ||
this.redbone.dispatch(client, { type: 'connection' }); | ||
_changeSocketSub(socket, onData, onDisconnect, onError, on = true) { | ||
const method = on ? 'on' : 'off'; | ||
socket[method]('data', onData); | ||
socket[method]('error', onError); | ||
socket[method]('close', onDisconnect); | ||
} | ||
_createClient(socket) { | ||
socket.setEncoding('utf8'); | ||
socket.on('data', (data) => this._processAction(data, client)); | ||
socket.on('error', (err) => console.error(err)); | ||
socket.on('disconnect', () => { | ||
this.redbone.dispatch(client, { type: 'disconnect' }); | ||
// create client | ||
return new Client({ | ||
transport: this, | ||
native: socket | ||
}); | ||
} | ||
_createOnData(client) { | ||
return (data) => { | ||
this._processAction(client, data); | ||
}; | ||
} | ||
_createOnDisconnect(client, onData) { | ||
const { DISCONNECT } = this.constructor.Types; | ||
const onDisconnect = () => { | ||
// dispatch disconnect action | ||
this.redbone.dispatch(client, { type: DISCONNECT }); | ||
this._changeSocketSub( | ||
client.native, onData, onDisconnect, this.onError, false | ||
); | ||
} | ||
return onDisconnect; | ||
} | ||
_subClient(client) { | ||
const onData = this._createOnData(client); | ||
const onDisconnect = this._createOnDisconnect(client, onData); | ||
this._changeSocketSub( | ||
client.native, onData, onDisconnect, this.onError | ||
); | ||
} | ||
onConnection(socket) { | ||
const { CONNECTION } = this.constructor.Types; | ||
const client = this._createClient(socket); | ||
this._subClient(client); | ||
// dispatch connection action | ||
return this.redbone.dispatch(client, { type: CONNECTION }); | ||
} | ||
onError(err) { | ||
console.error(err); | ||
} | ||
onRedboneError(err, client) { | ||
if (!err.statusCode) { | ||
throw err; | ||
} | ||
client.dispatch({ | ||
type: 'error', | ||
code: err.statusCode || 500 | ||
}); | ||
} | ||
} | ||
RedboneTransportTCP.Types = Types; | ||
module.exports = RedboneTransportTCP; | ||
``` | ||
Redbone has several transports out of the box. | ||
Redbone has several transports in different modules. | ||
You can examine the examples in detail in a [separate repository](https://github.com/ya-kostik/redbone-examples). | ||
## Clients | ||
Redbone needs clients to get a way to send reaction from it to client. | ||
Clients objects lives until connection is broken. | ||
Clients objects lives until connection is closed. | ||
@@ -213,12 +290,33 @@ Every transport should implement own Client class, inherited from Redbone's Client class. | ||
```js | ||
const { Client: ClientMain } = require('redbone'); | ||
const Redbone = require('../../'); | ||
const { write } = require('./lib/socket'); | ||
class Client extends ClientMain { | ||
// send is the only method the Сlient should implement | ||
const PERMITTED_ERRORS = new Set([ | ||
'EPIPE' // Send action after socket closed | ||
]); | ||
class Client extends Redbone.Client { | ||
send(action) { | ||
this.native.write(JSON.stringify(action)); | ||
return this.write(action). | ||
catch((err) => { | ||
if (PERMITTED_ERRORS.has(err.code)) return; | ||
return this.transport.onError(err); | ||
}); | ||
} | ||
write(action) { | ||
return new Promise((resolve, reject) => { | ||
const message = JSON.stringify(action); | ||
this.native.once('error', reject); | ||
return this.native.write(action, (err) => { | ||
if (err) return reject(err); | ||
return resolve(); | ||
}); | ||
}); | ||
} | ||
} | ||
module.exports = Client; | ||
``` | ||
`send` calls every time, when `client.dispatch(action)` occurs. |
/* global jest describe test expect */ | ||
const ACTION_ERROR_TEXT = 'is not a valid action'; | ||
const Redbone = require('../classes/Redbone'); | ||
@@ -9,25 +7,8 @@ const MainClient = require('../classes/Client'); | ||
function getTestWatcher(redbone, client, type) { | ||
return (client, action) => { | ||
expect(action.type).toBe(type); | ||
expect(client).toBe(client); | ||
expect(client.redbone).toBe(redbone); | ||
}; | ||
} | ||
const createMiddlewares = require('./lib/createMiddlewares'); | ||
const getTestWatcher = require('./lib/getTestWatcher'); | ||
const inspectMiddlewares = require('./lib/inspectMiddlewares'); | ||
function createMiddlewares(count, cb) { | ||
const middlewares = []; | ||
for (let i = 0; i < count; i++) { | ||
middlewares.push(jest.fn(cb)); | ||
} | ||
const ACTION_ERROR_TEXT = 'is not a valid action'; | ||
return middlewares; | ||
} | ||
function inspectMiddlewares(middlewares, count = 1) { | ||
for (const middleware of middlewares) { | ||
expect(middleware.mock.calls.length).toBe(count); | ||
} | ||
} | ||
function createMiddlewaresTest(firstUse, secondUse) { | ||
@@ -258,2 +239,39 @@ return async () => { | ||
})); | ||
test('creates boilerplate action', () => { | ||
const type = 'test'; | ||
const actionWithoutPayload = Redbone.createAction(type); | ||
const actionWithPayload = Redbone.createAction(type, { message: type }); | ||
const actionWithNullPayload = Redbone.createAction(type, null); | ||
const redbone = new Redbone(); | ||
const actionCreatedByInstance = redbone.createAction(type); | ||
expect(actionWithoutPayload).toEqual({ type }); | ||
expect(actionWithPayload).toEqual({ | ||
type, | ||
payload: { message: type } | ||
}); | ||
expect(actionWithNullPayload).toEqual({ type, payload: null }); | ||
expect(actionCreatedByInstance).toEqual(actionWithoutPayload); | ||
}); | ||
test('compares action and type', () => { | ||
const firstType = 'first test'; | ||
const secondType = 'second test'; | ||
const thirdType = 'crazy type'; | ||
const regexpType = /test/; | ||
const firstAction = Redbone.createAction(firstType); | ||
const secondAction = Redbone.createAction(secondType); | ||
const thirdAction = Redbone.createAction(thirdType); | ||
expect(Redbone.is(firstAction, firstType)).toBe(true); | ||
expect(Redbone.is(firstAction, secondType)).toBe(false); | ||
expect(Redbone.is(firstAction, regexpType)).toBe(true); | ||
expect(Redbone.is(secondAction, regexpType)).toBe(true); | ||
expect(Redbone.is(thirdAction, regexpType)).toBe(false); | ||
}); | ||
}); |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
21
639
319
0
29606