Skip to content

Commit

Permalink
feat: new interface for messageChannel and deprecates old, close #19
Browse files Browse the repository at this point in the history
  • Loading branch information
Jack-Works committed Jul 19, 2020
1 parent d241b34 commit 7022b8c
Show file tree
Hide file tree
Showing 33 changed files with 438 additions and 148 deletions.
24 changes: 20 additions & 4 deletions __tests__/async-call.ts
@@ -1,16 +1,32 @@
import { NoSerialization, JSONSerialization, AsyncCall, notify, batch } from '../src/Async-Call'
import { createServer, sleep, mockError } from './shared'
import { createServer, sleep, mockError, JestChannelDeprecated, JestChannelCallbackBased } from './shared'

test('AsyncCall basic test', async () => {
const snapshot = mockConsoleLog('no logs')
const c = createServer()
expect(await c.add(1, 3)).toBe(4)
expect(await c.undef()).toBe(undefined)
expect(c.throws()).rejects.toMatchInlineSnapshot(`[Error: impl error]`)
expect(c[0]()).rejects.toMatchInlineSnapshot(`[Error: Method not found]`)
await expect(c.throws()).rejects.toMatchInlineSnapshot(`[Error: impl error]`)
await expect(c[0]()).rejects.toMatchInlineSnapshot(`[Error: Method not found]`)
snapshot()
}, 2000)

test('AsyncCall deprecated MessageChannel interface', async () => {
const c = createServer({}, undefined, JestChannelDeprecated)
expect(await c.add(1, 3)).toBe(4)
expect(await c.undef()).toBe(undefined)
await expect(c.throws()).rejects.toMatchInlineSnapshot(`[Error: impl error]`)
await expect(c[0]()).rejects.toMatchInlineSnapshot(`[Error: Method not found]`)
}, 2000)

test('AsyncCall CallbackBased interface', async () => {
const c = createServer({}, undefined, JestChannelCallbackBased)
expect(await c.add(1, 3)).toBe(4)
expect(await c.undef()).toBe(undefined)
await expect(c.throws()).rejects.toMatchInlineSnapshot(`[Error: impl error]`)
await expect(c[0]()).rejects.toMatchInlineSnapshot(`[Error: Method not found]`)
}, 2000)

test('Should preserve this binding', async () => {
class X {
f = 1
Expand All @@ -19,7 +35,7 @@ test('Should preserve this binding', async () => {
}
}
const c = createServer({}, new X())
expect(c.n()).resolves.toBe(1)
await expect(c.n()).resolves.toBe(1)
}, 2000)

test('AsyncCall Serialization', async () => {
Expand Down
14 changes: 7 additions & 7 deletions __tests__/raw-messages.ts
Expand Up @@ -71,12 +71,12 @@ test('AsyncGeneratorCall non strict', async () => {
async function channelPeak(buildServer: (channel: JestChannel) => void, out: unknown[]) {
const { client, server } = createChannelPair()
buildServer(server)
const income = []
const outcome = []
server.addListener('message', (e) => outcome.push(e))
client.addListener('message', (e) => income.push(e))
out.forEach((x) => client.emit('message', x))
const logToClient = []
const logToServer = []
server.log.addListener('message', (e) => logToServer.push(e))
client.log.addListener('message', (e) => logToClient.push(e))
out.forEach((x) => client.send(x))
await sleep(200)
expect(income).toMatchSnapshot('in')
expect(outcome).toMatchSnapshot('out')
expect(logToClient).toMatchSnapshot('in')
expect(logToServer).toMatchSnapshot('out')
}
42 changes: 35 additions & 7 deletions __tests__/shared.ts
@@ -1,5 +1,5 @@
import { EventEmitter } from 'events'
import { AsyncCallOptions, AsyncCall, MessageChannel } from '../src/Async-Call'
import { AsyncCallOptions, AsyncCall, MessageChannel, EventBasedChannel, CallbackBasedChannel } from '../src/Async-Call'
import { AsyncGeneratorCall } from '../src/Async-Call-Generator'

const impl = {
Expand All @@ -10,25 +10,53 @@ const impl = {
throw new Error('impl error')
},
}
export class JestChannel extends EventEmitter implements MessageChannel {
constructor(public otherSide: JestChannel) {
export class JestChannel implements EventBasedChannel {
log = new EventEmitter()
constructor(public otherSide: JestChannel) {}
on(callback: any) {
this.log.addListener('message', callback)
return () => this.log.removeListener('message', callback)
}
send(data: any) {
this.otherSide.log.emit('message', data)
}
}
/** @deprecated */
export class JestChannelDeprecated extends EventEmitter implements MessageChannel {
constructor(public otherSide: JestChannelDeprecated) {
super()
}
emit(event: any, data: any): boolean {
return super.emit.call(this.otherSide, event, data)
}
}
export function createChannelPair() {
const server = new JestChannel(undefined!)
const client = new JestChannel(server)
export class JestChannelCallbackBased extends JestChannel implements CallbackBasedChannel {
constructor(ctor) {
super(ctor)
if (ctor === undefined) {
Object.defineProperty(this, 'send', { value: undefined })
} else {
Object.defineProperty(this, 'setup', { value: undefined })
}
}
setup(cb) {
this.log.addListener('message', async (msg) => this.otherSide.log.emit('message', await cb(msg)))
}
}
type ctor = typeof JestChannelDeprecated | typeof JestChannel | typeof JestChannelCallbackBased
export function createChannelPair(ctor: ctor = JestChannel) {
const server = new ctor(undefined!) as JestChannel
const client = new ctor(server as any) as JestChannel
// @ts-ignore
server.otherSide = client
return { server, client }
}
export function createServer<T extends object = typeof impl>(
opt: Omit<AsyncCallOptions, 'messageChannel'> = {},
_: T = impl as any,
ctor: ctor = JestChannel,
) {
const { client, server } = createChannelPair()
const { client, server } = createChannelPair(ctor)
AsyncCall(Math.random() > 0.5 ? _ : sleep(100).then(() => _), { messageChannel: server, log: false, ...opt })
return AsyncCall<T>({}, { messageChannel: client, log: false, ...opt })
}
Expand Down
14 changes: 1 addition & 13 deletions docs/async-call-rpc.asynccalloptions.key.md
Expand Up @@ -14,17 +14,5 @@ key?: string;

## Remarks

The value can be anything, but need to be same on both sides.

This option is useful when you want to run multiple AsyncCall instances on the same message channel.

## Example

these two AsyncCall run on the same channel but they won't affect each other.

```ts
AsyncCall({}, { messageChannel, key: 'app1' })
AsyncCall({}, { messageChannel, key: 'app2' })

```
The value can be anything, but need to be same on both sides if you're using the deprecated MessageChannel interface. If you're using other recommended interface for messageChannel like EventBasedChannel or CallbackBasedChannel, this option will only used for better logging.

2 changes: 1 addition & 1 deletion docs/async-call-rpc.asynccalloptions.md
Expand Up @@ -20,7 +20,7 @@ export interface AsyncCallOptions
| [log](./async-call-rpc.asynccalloptions.log.md) | [AsyncCallLogLevel](./async-call-rpc.asynccallloglevel.md) \| boolean | Choose log level. See [AsyncCallLogLevel](./async-call-rpc.asynccallloglevel.md) |
| [logger](./async-call-rpc.asynccalloptions.logger.md) | [Console](./async-call-rpc.console.md) | The logger of AsyncCall |
| [mapError](./async-call-rpc.asynccalloptions.maperror.md) | [ErrorMapFunction](./async-call-rpc.errormapfunction.md)<!-- -->&lt;unknown&gt; | Control the error response data |
| [messageChannel](./async-call-rpc.asynccalloptions.messagechannel.md) | [MessageChannel](./async-call-rpc.messagechannel.md) | The message channel can let you transport messages between server and client |
| [messageChannel](./async-call-rpc.asynccalloptions.messagechannel.md) | [MessageChannel](./async-call-rpc.messagechannel.md) \| [CallbackBasedChannel](./async-call-rpc.callbackbasedchannel.md) \| [EventBasedChannel](./async-call-rpc.eventbasedchannel.md) | The message channel can let you transport messages between server and client |
| [parameterStructures](./async-call-rpc.asynccalloptions.parameterstructures.md) | 'by-position' \| 'by-name' | How parameters passed to remote |
| [preferLocalImplementation](./async-call-rpc.asynccalloptions.preferlocalimplementation.md) | boolean | Prefer local implementation than remote. |
| [preservePauseOnException](./async-call-rpc.asynccalloptions.preservepauseonexception.md) | boolean | (Browser) Try to preserve the browser "pause on uncaught exception". |
Expand Down
2 changes: 1 addition & 1 deletion docs/async-call-rpc.asynccalloptions.messagechannel.md
Expand Up @@ -9,7 +9,7 @@ The message channel can let you transport messages between server and client
<b>Signature:</b>

```typescript
messageChannel: MessageChannel;
messageChannel: MessageChannel | CallbackBasedChannel | EventBasedChannel;
```

## Example
Expand Down
2 changes: 1 addition & 1 deletion docs/async-call-rpc.batch.md
Expand Up @@ -16,7 +16,7 @@ export declare function batch<T extends object>(asyncCallInstance: T): [T, () =>

| Parameter | Type | Description |
| --- | --- | --- |
| asyncCallInstance | T | |
| asyncCallInstance | T | The AsyncCall instance |

<b>Returns:</b>

Expand Down
25 changes: 25 additions & 0 deletions docs/async-call-rpc.callbackbasedchannel.md
@@ -0,0 +1,25 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->

[Home](./index.md) &gt; [async-call-rpc](./async-call-rpc.md) &gt; [CallbackBasedChannel](./async-call-rpc.callbackbasedchannel.md)

## CallbackBasedChannel interface

This interface represents a "callback" model.

<b>Signature:</b>

```typescript
export interface CallbackBasedChannel<Data = unknown> extends Partial<EventBasedChannel<Data>>
```
<b>Extends:</b> Partial&lt;[EventBasedChannel](./async-call-rpc.eventbasedchannel.md)<!-- -->&lt;Data&gt;&gt;
## Remarks
Usually used for there are many remotes (act like a server).
## Methods
| Method | Description |
| --- | --- |
| [setup(jsonRPCHandlerCallback, isValidJSONRPCPayload)](./async-call-rpc.callbackbasedchannel.setup.md) | Setup the CallbackBasedChannel.. |
25 changes: 25 additions & 0 deletions docs/async-call-rpc.callbackbasedchannel.setup.md
@@ -0,0 +1,25 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->

[Home](./index.md) &gt; [async-call-rpc](./async-call-rpc.md) &gt; [CallbackBasedChannel](./async-call-rpc.callbackbasedchannel.md) &gt; [setup](./async-call-rpc.callbackbasedchannel.setup.md)

## CallbackBasedChannel.setup() method

Setup the CallbackBasedChannel..

<b>Signature:</b>

```typescript
setup(jsonRPCHandlerCallback: (jsonRPCPayload: unknown) => Promise<unknown | undefined>, isValidJSONRPCPayload: (data: unknown) => boolean | Promise<boolean>): (() => void) | void;
```

## Parameters

| Parameter | Type | Description |
| --- | --- | --- |
| jsonRPCHandlerCallback | (jsonRPCPayload: unknown) =&gt; Promise&lt;unknown \| undefined&gt; | A function that will execute the JSON RPC request then give the result back. If the result is undefined, it means no response is created. |
| isValidJSONRPCPayload | (data: unknown) =&gt; boolean \| Promise&lt;boolean&gt; | |

<b>Returns:</b>

(() =&gt; void) \| void

1 change: 1 addition & 0 deletions docs/async-call-rpc.console.md
Expand Up @@ -21,4 +21,5 @@ export interface Console
| [groupCollapsed(args)](./async-call-rpc.console.groupcollapsed.md) | |
| [groupEnd(args)](./async-call-rpc.console.groupend.md) | |
| [log(args)](./async-call-rpc.console.log.md) | |
| [warn(args)](./async-call-rpc.console.warn.md) | |

22 changes: 22 additions & 0 deletions docs/async-call-rpc.console.warn.md
@@ -0,0 +1,22 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->

[Home](./index.md) &gt; [async-call-rpc](./async-call-rpc.md) &gt; [Console](./async-call-rpc.console.md) &gt; [warn](./async-call-rpc.console.warn.md)

## Console.warn() method

<b>Signature:</b>

```typescript
warn?(...args: unknown[]): void;
```
## Parameters
| Parameter | Type | Description |
| --- | --- | --- |
| args | unknown\[\] | |
<b>Returns:</b>
void
25 changes: 25 additions & 0 deletions docs/async-call-rpc.eventbasedchannel.md
@@ -0,0 +1,25 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->

[Home](./index.md) &gt; [async-call-rpc](./async-call-rpc.md) &gt; [EventBasedChannel](./async-call-rpc.eventbasedchannel.md)

## EventBasedChannel interface

This interface represents a "on message"-"send response" model.

<b>Signature:</b>

```typescript
export interface EventBasedChannel<Data = unknown>
```

## Remarks

Usually used for there is only 1 remote (act like a client).

## Methods

| Method | Description |
| --- | --- |
| [on(listener)](./async-call-rpc.eventbasedchannel.on.md) | Register the message listener. |
| [send(data)](./async-call-rpc.eventbasedchannel.send.md) | Send the data to the remote side. |

26 changes: 26 additions & 0 deletions docs/async-call-rpc.eventbasedchannel.on.md
@@ -0,0 +1,26 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->

[Home](./index.md) &gt; [async-call-rpc](./async-call-rpc.md) &gt; [EventBasedChannel](./async-call-rpc.eventbasedchannel.md) &gt; [on](./async-call-rpc.eventbasedchannel.on.md)

## EventBasedChannel.on() method

Register the message listener.

<b>Signature:</b>

```typescript
on(listener: (data: Data) => void): void | (() => void);
```

## Parameters

| Parameter | Type | Description |
| --- | --- | --- |
| listener | (data: Data) =&gt; void | The message listener. |

<b>Returns:</b>

void \| (() =&gt; void)

a function that unregister the listener.

24 changes: 24 additions & 0 deletions docs/async-call-rpc.eventbasedchannel.send.md
@@ -0,0 +1,24 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->

[Home](./index.md) &gt; [async-call-rpc](./async-call-rpc.md) &gt; [EventBasedChannel](./async-call-rpc.eventbasedchannel.md) &gt; [send](./async-call-rpc.eventbasedchannel.send.md)

## EventBasedChannel.send() method

Send the data to the remote side.

<b>Signature:</b>

```typescript
send(data: Data): void;
```

## Parameters

| Parameter | Type | Description |
| --- | --- | --- |
| data | Data | The data should send to the remote side. |

<b>Returns:</b>

void

2 changes: 2 additions & 0 deletions docs/async-call-rpc.md
Expand Up @@ -26,7 +26,9 @@ See the introduction at [Github](https://github.com/Jack-Works/async-call)
| [AsyncCallLogLevel](./async-call-rpc.asynccallloglevel.md) | What should AsyncCall log to console. |
| [AsyncCallOptions](./async-call-rpc.asynccalloptions.md) | Options for [AsyncCall()](./async-call-rpc.asynccall.md) |
| [AsyncCallStrictJSONRPC](./async-call-rpc.asynccallstrictjsonrpc.md) | Control the behavior that different from the JSON RPC spec. |
| [CallbackBasedChannel](./async-call-rpc.callbackbasedchannel.md) | This interface represents a "callback" model. |
| [Console](./async-call-rpc.console.md) | The minimal Console interface that AsyncCall needs. |
| [EventBasedChannel](./async-call-rpc.eventbasedchannel.md) | This interface represents a "on message"-"send response" model. |
| [MessageChannel](./async-call-rpc.messagechannel.md) | The message channel interface that allows |
| [Serialization](./async-call-rpc.serialization.md) | Serialization and deserialization of the JSON RPC payload |

Expand Down
5 changes: 5 additions & 0 deletions docs/async-call-rpc.messagechannel.md
Expand Up @@ -4,6 +4,11 @@

## MessageChannel interface

> Warning: This API is now obsolete.
>
> Will be removed in the next major version.
>
The message channel interface that allows

<b>Signature:</b>
Expand Down

0 comments on commit 7022b8c

Please sign in to comment.