Skip to content

Commit

Permalink
open-sourced it
Browse files Browse the repository at this point in the history
  • Loading branch information
highfivedenis committed Mar 9, 2018
1 parent c8d46cc commit fa65a08
Show file tree
Hide file tree
Showing 31 changed files with 4,934 additions and 1 deletion.
5 changes: 5 additions & 0 deletions .babelrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"presets": [
"env"
]
}
17 changes: 17 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# top-most EditorConfig file
root = true

# Unix-style newlines with a newline ending every file
[*]
charset = utf-8
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true

# Matches multiple files with brace expansion notation
[*.{ts,js}]
indent_style = space
indent_size = 4

[*.json]
indent_size = 2
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
build
node_modules
1 change: 1 addition & 0 deletions .npmrc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
sign-git-tag=true
6 changes: 6 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
language: node_js
node_js:
- "7"
script:
"npm run ci"
sudo: false
194 changes: 193 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,194 @@
# typescript-event-bus
# ts-event-bus
[<img src="./by_dashlane.svg" width="220"/>](https://www.dashlane.com/)

[![Build Status](https://travis-ci.org/Dashlane/ts-event-bus.svg?branch=master)](https://travis-ci.org/Dashlane/ts-event-bus)

Distributed messaging in Typescript

`ts-event-bus` is a lightweight distributed messaging system. It allows several modules, potentially distributed over different runtime spaces to communicate through typed messages.

## Getting started

### Declare your events

Using `ts-event-bus` starts with the declaration of the interface that your components share:

```typescript
// MyEvents.ts
import { slot, Slot } from 'ts-event-bus'

const MyEvents = {
sayHello: slot<string>(),
getTime: slot<null, string>(),
multiply: slot<{a: number, b: number}, number>(),
ping: slot<void>(),
}

export default MyEvents
```

### Create EventBus
Your components will then instantiate an event bus based on this declaration, using whatever channel they may want to communicate on.
If you specify no `Channel`, it means that you will exchange events in the same memory space.

For instance, one could connect two node processes over WebSocket:

```typescript
// firstModule.EventBus.ts
import { createEventBus } from 'ts-event-bus'
import MyEvents from './MyEvents.ts'
import MyBasicWebSocketChannel from './MyBasicWebSocketChannel.ts'

const EventBus = createEventBus({
events: MyEvents,
channels: [ new MyBasicWebSocketChannel('ws://your_host') ]
})

export default EventBus
```

```typescript
// secondModule.EventBus.ts
import { createEventBus } from 'ts-event-bus'
import MyEvents from './MyEvents.ts'
import MyBasicWebSocketChannel from './MyBasicWebSocketChannel.ts'

const EventBus = createEventBus({
events: MyEvents,
channels: [ new MyBasicWebSocketChannel('ws://your_host') ]
})
```

### Usage

Once connected, the clients can start by using the slots on the event bus

```typescript
// firstModule.ts
import EventBus from './firstModule.EventBus.ts'

// Triggering an event always returns a promise
EventBus.sayHello('michel').then(() => {
...
})

EventBus.getTime().then((time) => {
...
})

EventBus.multiply({a: 2, b: 5 }).then((result) => {
...
})

EventBus.ping()
```

```typescript
// secondModule.ts
import EventBus from './secondModule.EventBus.ts'

EventBus.ping().on(() => {
console.log('pong')
})

EventBus.sayHello.on(name => {
console.log(`${name} said hello!`)
})

// Event subscribers can respond to the event synchronously (by returning a value)
EventBus.getTime.on(() => new Date().toString)

// Or asynchronously (by returning a Promise that resolves with the value).
EventBus.multiply.on(({ a, b }) => new Promise((resolve, reject) => {
AsynchronousMultiplier(a, b, (err, result) => {
if (err) {
return reject(err)
}
resolve(result)
})
}))
```

Calls and subscriptions on slots are typechecked
```typescript
EventBus.multiply({a: 1, c: 2}) // Compile error: property 'c' does not exist on type {a: number, b: number}

EventBus.multiply.on(({a, b}) => {
if (a.length > 2) { // Compile error: property 'length' does not exist on type 'number'
...
}
})
```

### Syntactic sugar

You can combine events from different sources.
```typescript
import { combineEvents } from 'ts-event-bus'
import MyEvents from './MyEvents.ts'
import MyOtherEvents from './MyOtherEvents.ts'

const MyCombinedEvents = combineEvents(
MyEvents,
MyOtherEvents,
)

export default MyCombinedEvents
```

## Using and Implementing Channels

`ts-event-bus` comes with an abstract class [GenericChannel](./src/Channel.ts).
To implement your own channel create a new class extending `GenericChannel`, and call the method given by the abstract class: _connected(), _disconnected(), _error(e: Error) and _messageReceived(data: any).

Basic WebSocket Channel example:
```typescript
import { GenericChannel } from 'ts-event-bus'

export class MyBasicWebSocketChannel extends GenericChannel {
private _ws: WebSocket | null = null
private _host: string

constructor(host: string) {
super()
this._host = host
this._init()
}

private _init(): void {
const ws = new WebSocket(this._host)

ws.onopen = (e: Event) => {
this._connected()
this._ws = ws
}

ws.onerror = (e: Event) => {
this._ws = null
this._error(e)
this._disconnected()
setTimeout(() => {
this._init()
}, 2000)
}

ws.onclose = (e: CloseEvent) => {
if (ws === this._ws) {
this._ws = null
this._disconnected()
this._init()
}
}

ws.onmessage = (e: MessageEvent) => {
this._messageReceived(e.data)
}
}
}
```


## Examples

- [Channel implementation](./examples/channels)
- [Usage](./examples/usage)
6 changes: 6 additions & 0 deletions by_dashlane.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
69 changes: 69 additions & 0 deletions examples/channels/NativeWebSocket.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import { GenericChannel } from './../../src/Channel'

export class WebSocketClientChannel extends GenericChannel {
private static MAX_RETRIES = 3

private _ws: WebSocket | null = null

constructor(private _host: string) {
super()
this._tryToConnect()
}

public send(message: {}): void {
if (!this._ws) {
return
}
const stringified = JSON.stringify(message)
this._ws.send(stringified)
}

private _tryToConnect(attemptsLeft: number = WebSocketClientChannel.MAX_RETRIES): void {
this._init(attemptsLeft)
}

private _onWsMessage(data: string): void {
let parsedData: any
try {
parsedData = JSON.parse(data)
} catch (err) {
this._error(err)
return
}
this._messageReceived(parsedData)
}

private _init(attemptsLeft: number): void {
if (attemptsLeft === 0) {
return
}

const ws = new WebSocket(this._host)

ws.onopen = (e: Event) => {
this._connected()
this._ws = ws
}

ws.onerror = (e: Event) => {
this._ws = null
this._error(e)
this._disconnected()
setTimeout(() => {
this._tryToConnect(attemptsLeft - 1)
}, 1500)
}

ws.onclose = (e: CloseEvent) => {
if (ws === this._ws) {
this._ws = null
this._disconnected()
this._tryToConnect()
}
}

ws.onmessage = (e: MessageEvent) => {
this._onWsMessage(e.data)
}
}
}
Loading

0 comments on commit fa65a08

Please sign in to comment.