diff --git a/.gitignore b/.gitignore index 8cb10b33..5f3170d7 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,5 @@ *.swp node_modules *.bak -dist/comlink.ts -dist/README.md -dist/package.json +dist +.*_cache* diff --git a/Dockerfile b/Dockerfile index 58e30146..dc12226d 100644 --- a/Dockerfile +++ b/Dockerfile @@ -10,7 +10,7 @@ RUN apt-get update -qqy \ ENV NVM_DIR /usr/local/nvm RUN wget -qO- https://raw.githubusercontent.com/creationix/nvm/v0.33.2/install.sh | bash \ && source $NVM_DIR/nvm.sh \ - && nvm install v8 + && nvm install v11 ENV CHROME_BIN /opt/google/chrome/chrome ENV INSIDE_DOCKER=1 diff --git a/README.md b/README.md index e671a028..d03f399c 100644 --- a/README.md +++ b/README.md @@ -1,28 +1,15 @@ # Comlink -Comlink’s goal is to make [WebWorkers][webworker] enjoyable. Comlink removes the mental barrier of thinking about `postMessage` and hides the fact that you are working with workers. +Comlink makes [WebWorkers][webworker] enjoyable. Comlink is a **tiny library (1.1kB)**, that removes the mental barrier of thinking about `postMessage` and hides the fact that you are working with workers. -> Note: Comlink’s goal is to be a building-block for higher-level abstraction libraries. For example, take a look at [Clooney]. +At a more abstract level it is an RPC implementation for `postMessage` and [ES6 Proxies][es6 proxy]. -```js -// main.js -const MyClass = Comlink.proxy(new Worker("worker.js")); -// `instance` is an instance of `MyClass` that lives in the worker! -const instance = await new MyClass(); -await instance.logSomething(); // logs “myValue = 42” ``` - -```js -// worker.js -const myValue = 42; -class MyClass { - logSomething() { - console.log(`myValue = ${myValue}`); - } -} -Comlink.expose(MyClass, self); +$ npm install --save comlink@alpha ``` +![Comlink in action](https://user-images.githubusercontent.com/234957/54164510-cdab2d80-4454-11e9-92d0-7356aa6c5746.png) + ## Browsers support & bundle size ![Chrome 56+](https://img.shields.io/badge/Chrome-56+-green.svg?style=flat-square) @@ -34,82 +21,84 @@ Comlink.expose(MyClass, self); Browsers without [ES6 Proxy] support can use the [proxy-polyfill]. -**Size**: ~3.9k, ~1.6k gzip’d +**Size**: ~2.5k, ~1.2k gzip’d, ~1.1k brotli’d ## Introduction -WebWorkers are a web API that allow you to run code in a separate thread. To communicate with another thread, WebWorkers offer the `postMessage` API. You can send messages in form of [transferable] JavaScript objects using `myWorker.postMessage(someObject)`, triggering a `message` event inside the worker. - -Comlink turns this messaged-based API into a something more developer-friendly: Values from one thread can be used within the other thread (and vice versa) just like local values. - -Comlink can be used with anything that offers `postMessage` like windows, iframes and ServiceWorkers. +On mobile phones, and especially on low-end mobile phones, it is important to keep the main thread as idle as possible so it can respond to user interactions quickly and provide a jank-free experience. **The UI thread ought to be for UI work only**. WebWorkers are a web API that allow you to run code in a separate thread. To communicate with another thread, WebWorkers offer the `postMessage` API. You can send JavaScript objects as messages using `myWorker.postMessage(someObject)`, triggering a `message` event inside the worker. -## Download +Comlink turns this messaged-based API into a something more developer-friendly by providing an RPC implementation: Values from one thread can be used within the other thread (and vice versa) just like local values. -You can download Comlink from the [dist folder][dist]. Alternatively, you can -install it via npm +## API -``` -$ npm install --save comlinkjs -``` +### `Comlink.wrap(endpoint)` and `Comlink.expose(value, endpoint?)` -or use a CDN like [delivrjs]: +Comlink’s goal is to make _exposed_ values from one thread available in the other. `expose` exposes `value` on `endpoint`, where `endpoint` is a [`postMessage`-like interface][endpoint]. -``` -https://cdn.jsdelivr.net/npm/comlinkjs@3.1.1/umd/comlink.js -``` +`wrap` wraps the _other_ end of the message channel and returns a proxy. The proxy will have all properties and functions of the exposed value, but access and invocations are inherintly asynchronous. This means that a function that returns a number will now return _a promise_ for a number. **As a rule of thumb: If you are using the proxy, put `await` in front of it.** Exceptions will be caught and re-thrown on the other side. -## Examples +### `Comlink.transfer(value, transferables)` and `Comlink.proxy(value)` -There’s a collection of examples in the [examples directory][examples]. +By default, every function parameter, return value and object property value is copied, in the sense of [structured cloning]. Structured cloning can be thought of as deep copying, but has some limitations. See [this table][structured clone table] for details. -## API +If you want a value to be transferred rather than copied — provided the value is or contains a [`Transferable`][transferable] — you can wrap the value in a `transfer()` call and provide a list of transferable values: -The Comlink module exports 3 functions: +```js +const data = new Uint8Array([1, 2, 3, 4, 5]); +await myProxy.someFunction(Comlink.transfer(data, [data.buffer])); +``` -### `Comlink.proxy(endpoint)` +Lastly, you can use `Comlink.proxy(value)`. When using this Comlink will neither copy nor transfer the value, but instead send a proxy. Both threads now work on the same value. This is useful for callbacks, for example, as functions are neither structured cloneable nor transferable. -> Returns the value that is exposed on the other side of `endpoint`. +```js +myProxy.onready = Comlink.proxy(data => { + /* ... */ +}); +``` -`proxy` creates an ES6 proxy and sends all operations performed on that proxy through `endpoint`. `endpoint` can be a `Window`, a `Worker` or a `MessagePort`.\* The other endpoint of the channel should be passed to `Comlink.expose`. +### Transfer handlers and event listeners -If you invoke function, all parameters will be structurally cloned or transferred if they are [transferable]. If you want to pass a function as a parameters (e.g. callbacks), make sure to use `proxyValue` (see below). Same applies to the return value of a function. +It is common that you want to use Comlink to add an event listener, where the event source is on another thread: -\*) Technically it can be any object with `postMessage`, `addEventListener` and -`removeEventListener`. +```js +button.addEventListener("click", myProxy.onClick.bind(myProxy)); +``` -### `Comlink.expose(obj, endpoint)` +While this won’t throw immediately, `onClick` will never actually be called. This is because [`Event`][event] is neither structured cloneable nor transferable. As a workaround, Comlink offers transfer handlers. -> Exposes `obj` to `endpoint`. Use `Comlink.proxy` on the other end of `endpoint`. +Each function parameter and return value is given to _all_ registered transfer handlers. If one of the event handler signals that it can process the value by returning `true` from `canHandle()`, it is now responsible for serializing the value to sturctured cloneable data and for deserializing the value. A transfer handler has be set up on _both sides_ of the message channel. Here’s an example transfer handler for events: -`expose` is the counter-part to `proxy`. It listens for RPC messages on `endpoint` and applies the operations to `obj`. Return values of functions will be structurally cloned or transfered if they are [transferable]. +```js +Comlink.transferHandlers.set("EVENT", { + canHandle: obj => obj instanceof Event, + serialize: ev => { + return [{ + target: { + id: ev.target.id + classList: [...ev.target.classList] + } + }, []]; + }, + deserialize: obj => obj, +}); +``` -### `Comlink.proxyValue(value)` +Note that this particular transfer handler won’t create an actual `Event`, but just an object that has the `event.target.id` and `event.target.classList` property. Often, this enough. If not, the transfer handler can be easily augmented to provide all necessary data. -> Makes sure a parameter or return value is proxied, not copied. +### `Comlink.createEndpoint` -By default, all parameters to a function that are not [transferable] are copied (structural clone): +Every proxy created by Comlink has the `[createEndpoint]` method. +Calling it will return a new `MessagePort`, that has been hooked up to the same object as the proxy that `[createEndpoint]` has been called on. ```js -// main.js -const api = Comlink.proxy(new Worker("worker.js")); -const obj = { x: 0 }; -await api.setXto4(obj); -console.log(obj.x); // logs 0 -``` - -The worker receives a copy of `obj`, so any mutation of `obj` done by the worker won’t affect the original object. If the value should _not_ be copied but instead be proxied, use `Comlink.proxyValue`: - -```diff -- await api.setXto4(obj); -+ await api.setXto4(Comlink.proxyValue(obj)); +const port = myProxy[Comlink.createEndpoint](); +const newProxy = Comlink.wrap(port); ``` -`console.log(obj.x)` will now log 4. +## Node -Keep in mind that functions cannot be copied. Unless they are used in combination with `Comlink.proxyValue`, they will get discarded during copy. +Comlink works with Node’s [`worker_threads`][worker_threads] module. Take a look at the example in the `docs` folder. -[clooney]: https://github.com/GoogleChromeLabs/clooney [webworker]: https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API [umd]: https://github.com/umdjs/umd [transferable]: https://developer.mozilla.org/en-US/docs/Web/API/Transferable @@ -119,6 +108,11 @@ Keep in mind that functions cannot be copied. Unless they are used in combinatio [delivrjs]: https://cdn.jsdelivr.net/ [es6 proxy]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy [proxy-polyfill]: https://github.com/GoogleChrome/proxy-polyfill +[endpoint]: src/protocol.js +[structured cloning]: https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Structured_clone_algorithm +[structured clone table]: structured-clone-table.md +[event]: https://developer.mozilla.org/en-US/docs/Web/API/Event +[worker_threads]: https://nodejs.org/api/worker_threads.html --- diff --git a/comlink.ts b/comlink.ts deleted file mode 100644 index 71e3c7a4..00000000 --- a/comlink.ts +++ /dev/null @@ -1,533 +0,0 @@ -/** - * Copyright 2017 Google Inc. All Rights Reserved. - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0 - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -export interface Endpoint { - postMessage(message: any, transfer?: any[]): void; - addEventListener( - type: string, - listener: EventListenerOrEventListenerObject, - options?: {} - ): void; - removeEventListener( - type: string, - listener: EventListenerOrEventListenerObject, - options?: {} - ): void; -} - -// To avoid Promise> -type Promisify = T extends Promise ? T : Promise; - -// ProxiedObject is equivalent to T, except that all properties are now promises and -// all functions now return promises. It effectively async-ifies an object. -type ProxiedObject = { - [P in keyof T]: T[P] extends (...args: infer Arguments) => infer R - ? (...args: Arguments) => Promisify - : Promisify -}; - -// ProxyResult is an augmentation of ProxyObject that also handles raw functions -// and classes correctly. -export type ProxyResult = ProxiedObject & - (T extends (...args: infer Arguments) => infer R - ? (...args: Arguments) => Promisify - : unknown) & - (T extends { new (...args: infer ArgumentsType): infer InstanceType } - ? { new (...args: ArgumentsType): Promisify> } - : unknown); - -export type Proxy = Function; -type CBProxyCallback = (bpcd: CBProxyCallbackDescriptor) => {}; // eslint-disable-line no-unused-vars -type Transferable = MessagePort | ArrayBuffer; // eslint-disable-line no-unused-vars -export type Exposable = Function | Object; // eslint-disable-line no-unused-vars - -interface InvocationResult { - id?: string; - value: WrappedValue; -} - -type WrappedValue = RawWrappedValue | HandledWrappedValue; - -interface PropertyIteratorEntry { - value: {}; - path: string[]; -} - -interface WrappedChildValue { - path: string[]; - wrappedValue: HandledWrappedValue; -} - -interface RawWrappedValue { - type: "RAW"; - value: {}; - wrappedChildren?: WrappedChildValue[]; -} - -interface HandledWrappedValue { - type: string; - value: {}; -} - -type CBProxyCallbackDescriptor = - | CBPCDGet - | CBPCDApply - | CBPCDConstruct - | CBPCDSet; // eslint-disable-line no-unused-vars - -interface CBPCDGet { - type: "GET"; - callPath: PropertyKey[]; -} - -interface CBPCDApply { - type: "APPLY"; - callPath: PropertyKey[]; - argumentsList: {}[]; -} - -interface CBPCDConstruct { - type: "CONSTRUCT"; - callPath: PropertyKey[]; - argumentsList: {}[]; -} - -interface CBPCDSet { - type: "SET"; - callPath: PropertyKey[]; - property: PropertyKey; - value: {}; -} - -type InvocationRequest = - | GetInvocationRequest - | ApplyInvocationRequest - | ConstructInvocationRequest - | SetInvocationRequest; - -interface GetInvocationRequest { - id?: string; - type: "GET"; - callPath: PropertyKey[]; -} - -interface ApplyInvocationRequest { - id?: string; - type: "APPLY"; - callPath: PropertyKey[]; - argumentsList: WrappedValue[]; -} - -interface ConstructInvocationRequest { - id?: string; - type: "CONSTRUCT"; - callPath: PropertyKey[]; - argumentsList: WrappedValue[]; -} - -interface SetInvocationRequest { - id?: string; - type: "SET"; - callPath: PropertyKey[]; - property: PropertyKey; - value: WrappedValue; -} - -export interface TransferHandler { - canHandle: (obj: {}) => Boolean; - serialize: (obj: {}) => {}; - deserialize: (obj: {}) => {}; -} - -const TRANSFERABLE_TYPES = ["ArrayBuffer", "MessagePort", "OffscreenCanvas"] - .filter(f => f in self) - .map(f => (self as any)[f]); -const uid: number = Math.floor(Math.random() * Number.MAX_SAFE_INTEGER); - -const proxyValueSymbol = Symbol("proxyValue"); -const throwSymbol = Symbol("throw"); -const proxyTransferHandler: TransferHandler = { - canHandle: (obj: {}): Boolean => obj && (obj as any)[proxyValueSymbol], - serialize: (obj: {}): {} => { - const { port1, port2 } = new MessageChannel(); - expose(obj, port1); - return port2; - }, - deserialize: (obj: {}): {} => { - return proxy(obj as MessagePort); - } -}; - -const throwTransferHandler = { - canHandle: (obj: {}): Boolean => obj && (obj as any)[throwSymbol], - serialize: (obj: any): {} => { - const message = obj && obj.message; - const stack = obj && obj.stack; - return Object.assign({}, obj, { message, stack }); - }, - deserialize: (obj: {}): {} => { - throw Object.assign(Error(), obj); - } -}; - -export const transferHandlers: Map = new Map([ - ["PROXY", proxyTransferHandler], - ["THROW", throwTransferHandler] -]); - -let pingPongMessageCounter: number = 0; - -export function proxy( - endpoint: Endpoint | Window, - target?: any -): ProxyResult { - if (isWindow(endpoint)) endpoint = windowEndpoint(endpoint); - if (!isEndpoint(endpoint)) - throw Error( - "endpoint does not have all of addEventListener, removeEventListener and postMessage defined" - ); - - activateEndpoint(endpoint); - return cbProxy( - async irequest => { - let args: WrappedValue[] = []; - if (irequest.type === "APPLY" || irequest.type === "CONSTRUCT") - args = irequest.argumentsList.map(wrapValue); - const response = await pingPongMessage( - endpoint as Endpoint, - Object.assign({}, irequest, { argumentsList: args }), - transferableProperties(args) - ); - const result = response.data as InvocationResult; - return unwrapValue(result.value); - }, - [], - target - ) as ProxyResult; -} - -export function proxyValue(obj: T): T { - (obj as any)[proxyValueSymbol] = true; - return obj; -} - -export function expose(rootObj: Exposable, endpoint: Endpoint | Window): void { - if (isWindow(endpoint)) endpoint = windowEndpoint(endpoint); - if (!isEndpoint(endpoint)) - throw Error( - "endpoint does not have all of addEventListener, removeEventListener and postMessage defined" - ); - - activateEndpoint(endpoint); - attachMessageHandler(endpoint, async function(event: MessageEvent) { - if (!event.data.id || !event.data.callPath) return; - const irequest = event.data as InvocationRequest; - let that = await irequest.callPath - .slice(0, -1) - .reduce((obj, propName) => obj[propName], rootObj as any); - let obj = await irequest.callPath.reduce( - (obj, propName) => obj[propName], - rootObj as any - ); - let iresult = obj; - let args: {}[] = []; - - if (irequest.type === "APPLY" || irequest.type === "CONSTRUCT") - args = irequest.argumentsList.map(unwrapValue); - if (irequest.type === "APPLY") { - try { - iresult = await obj.apply(that, args); - } catch (e) { - iresult = e; - iresult[throwSymbol] = true; - } - } - if (irequest.type === "CONSTRUCT") { - try { - iresult = new obj(...args); // eslint-disable-line new-cap - iresult = proxyValue(iresult); - } catch (e) { - iresult = e; - iresult[throwSymbol] = true; - } - } - if (irequest.type === "SET") { - obj[irequest.property] = irequest.value; - // FIXME: ES6 Proxy Handler `set` methods are supposed to return a - // boolean. To show good will, we return true asynchronously ¯\_(ツ)_/¯ - iresult = true; - } - - iresult = makeInvocationResult(iresult); - iresult.id = irequest.id; - return (endpoint as Endpoint).postMessage( - iresult, - transferableProperties([iresult]) - ); - }); -} - -function wrapValue(arg: {}): WrappedValue { - // Is arg itself handled by a TransferHandler? - for (const [key, transferHandler] of transferHandlers) { - if (transferHandler.canHandle(arg)) { - return { - type: key, - - value: transferHandler.serialize(arg) - }; - } - } - - // If not, traverse the entire object and find handled values. - let wrappedChildren: WrappedChildValue[] = []; - for (const item of iterateAllProperties(arg)) { - for (const [key, transferHandler] of transferHandlers) { - if (transferHandler.canHandle(item.value)) { - wrappedChildren.push({ - path: item.path, - wrappedValue: { - type: key, - value: transferHandler.serialize(item.value) - } - }); - } - } - } - for (const wrappedChild of wrappedChildren) { - const container = wrappedChild.path - .slice(0, -1) - .reduce((obj, key) => obj[key], arg as any); - container[wrappedChild.path[wrappedChild.path.length - 1]] = null; - } - return { - type: "RAW", - value: arg, - wrappedChildren - }; -} - -function unwrapValue(arg: WrappedValue): {} { - if (transferHandlers.has(arg.type)) { - const transferHandler = transferHandlers.get(arg.type)!; - return transferHandler.deserialize(arg.value); - } else if (isRawWrappedValue(arg)) { - for (const wrappedChildValue of arg.wrappedChildren || []) { - if (!transferHandlers.has(wrappedChildValue.wrappedValue.type)) - throw Error( - `Unknown value type "${arg.type}" at ${wrappedChildValue.path.join( - "." - )}` - ); - const transferHandler = transferHandlers.get( - wrappedChildValue.wrappedValue.type - )!; - const newValue = transferHandler.deserialize( - wrappedChildValue.wrappedValue.value - ); - replaceValueInObjectAtPath(arg.value, wrappedChildValue.path, newValue); - } - return arg.value; - } else { - throw Error(`Unknown value type "${arg.type}"`); - } -} - -function replaceValueInObjectAtPath(obj: {}, path: string[], newVal: {}) { - const lastKey = path.slice(-1)[0]; - const lastObj = path - .slice(0, -1) - .reduce((obj: any, key: string) => obj[key], obj); - lastObj[lastKey] = newVal; -} - -function isRawWrappedValue(arg: WrappedValue): arg is RawWrappedValue { - return arg.type === "RAW"; -} - -function windowEndpoint(w: Window): Endpoint { - if (self.constructor.name !== "Window") throw Error("self is not a window"); - return { - addEventListener: self.addEventListener.bind(self), - removeEventListener: self.removeEventListener.bind(self), - postMessage: (msg, transfer) => w.postMessage(msg, "*", transfer) - }; -} - -function isEndpoint(endpoint: any): endpoint is Endpoint { - return ( - "addEventListener" in endpoint && - "removeEventListener" in endpoint && - "postMessage" in endpoint - ); -} - -function activateEndpoint(endpoint: Endpoint): void { - if (isMessagePort(endpoint)) endpoint.start(); -} - -function attachMessageHandler( - endpoint: Endpoint, - f: (e: MessageEvent) => void -): void { - // Checking all possible types of `endpoint` manually satisfies TypeScript’s - // type checker. Not sure why the inference is failing here. Since it’s - // unnecessary code I’m going to resort to `any` for now. - // if(isWorker(endpoint)) - // endpoint.addEventListener('message', f); - // if(isMessagePort(endpoint)) - // endpoint.addEventListener('message', f); - // if(isOtherWindow(endpoint)) - // endpoint.addEventListener('message', f); - (endpoint as any).addEventListener("message", f); -} - -function detachMessageHandler( - endpoint: Endpoint, - f: (e: MessageEvent) => void -): void { - // Same as above. - (endpoint).removeEventListener("message", f); -} - -function isMessagePort(endpoint: Endpoint): endpoint is MessagePort { - return endpoint.constructor.name === "MessagePort"; -} - -function isWindow(endpoint: Endpoint | Window): endpoint is Window { - // TODO: This doesn’t work on cross-origin iframes. - // return endpoint.constructor.name === 'Window'; - return ["window", "length", "location", "parent", "opener"].every( - prop => prop in endpoint - ); -} - -/** - * `pingPongMessage` sends a `postMessage` and waits for a reply. Replies are - * identified by a unique id that is attached to the payload. - */ -function pingPongMessage( - endpoint: Endpoint, - msg: Object, - transferables: Transferable[] -): Promise { - const id = `${uid}-${pingPongMessageCounter++}`; - - return new Promise(resolve => { - attachMessageHandler(endpoint, function handler(event: MessageEvent) { - if (event.data.id !== id) return; - detachMessageHandler(endpoint, handler); - resolve(event); - }); - - // Copy msg and add `id` property - msg = Object.assign({}, msg, { id }); - endpoint.postMessage(msg, transferables); - }); -} - -function cbProxy( - cb: CBProxyCallback, - callPath: PropertyKey[] = [], - target = function() {} -): Proxy { - return new Proxy(target, { - construct(_target, argumentsList, proxy) { - return cb({ - type: "CONSTRUCT", - callPath, - argumentsList - }); - }, - apply(_target, _thisArg, argumentsList) { - // We use `bind` as an indicator to have a remote function bound locally. - // The actual target for `bind()` is currently ignored. - if (callPath[callPath.length - 1] === "bind") - return cbProxy(cb, callPath.slice(0, -1)); - return cb({ - type: "APPLY", - callPath, - argumentsList - }); - }, - get(_target, property, proxy) { - if (property === "then" && callPath.length === 0) { - return { then: () => proxy }; - } else if (property === "then") { - const r = cb({ - type: "GET", - callPath - }); - return Promise.resolve(r).then.bind(r); - } else { - return cbProxy(cb, callPath.concat(property), (_target)[property]); - } - }, - set(_target, property, value, _proxy): boolean { - return cb({ - type: "SET", - callPath, - property, - value - }) as boolean; - } - }); -} - -function isTransferable(thing: {}): thing is Transferable { - return TRANSFERABLE_TYPES.some(type => thing instanceof type); -} - -function* iterateAllProperties( - value: {} | undefined, - path: string[] = [], - visited: WeakSet<{}> | null = null -): Iterable { - if (!value) return; - if (!visited) visited = new WeakSet<{}>(); - if (visited.has(value)) return; - if (typeof value === "string") return; - if (typeof value === "object") visited.add(value); - if (ArrayBuffer.isView(value)) return; - yield { value, path }; - - const keys = Object.keys(value); - for (const key of keys) - yield* iterateAllProperties((value as any)[key], [...path, key], visited); -} - -function transferableProperties(obj: {}[] | undefined): Transferable[] { - const r: Transferable[] = []; - for (const prop of iterateAllProperties(obj)) { - if (isTransferable(prop.value)) r.push(prop.value); - } - return r; -} - -function makeInvocationResult(obj: {}): InvocationResult { - for (const [type, transferHandler] of transferHandlers) { - if (transferHandler.canHandle(obj)) { - const value = transferHandler.serialize(obj); - return { - value: { type, value } - }; - } - } - - return { - value: { - type: "RAW", - value: obj - } - }; -} diff --git a/dist/comlink.d.ts b/dist/comlink.d.ts deleted file mode 100644 index 400b1d4a..00000000 --- a/dist/comlink.d.ts +++ /dev/null @@ -1,59 +0,0 @@ -/** - * Copyright 2017 Google Inc. All Rights Reserved. - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0 - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -export interface Endpoint { - postMessage(message: any, transfer?: any[]): void; - addEventListener( - type: string, - listener: EventListenerOrEventListenerObject, - options?: {} - ): void; - removeEventListener( - type: string, - listener: EventListenerOrEventListenerObject, - options?: {} - ): void; -} -declare type ProxiedObject = { - [P in keyof T]: T[P] extends (...args: infer Arguments) => infer R - ? (...args: Arguments) => Promise - : Promise -}; -export declare type ProxyResult = ProxiedObject & - (T extends (...args: infer Arguments) => infer R - ? (...args: Arguments) => Promise - : unknown) & - (T extends { - new (...args: infer ArgumentsType): infer InstanceType; - } - ? { - new (...args: ArgumentsType): Promise>; - } - : unknown); -export declare type Proxy = Function; -export declare type Exposable = Function | Object; -export interface TransferHandler { - canHandle: (obj: {}) => Boolean; - serialize: (obj: {}) => {}; - deserialize: (obj: {}) => {}; -} -export declare const transferHandlers: Map; -export declare function proxy( - endpoint: Endpoint | Window, - target?: any -): ProxyResult; -export declare function proxyValue(obj: T): T; -export declare function expose( - rootObj: Exposable, - endpoint: Endpoint | Window -): void; -export {}; diff --git a/dist/comlink.js b/dist/comlink.js deleted file mode 100644 index 10e668e1..00000000 --- a/dist/comlink.js +++ /dev/null @@ -1,328 +0,0 @@ -/** - * Copyright 2017 Google Inc. All Rights Reserved. - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0 - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -const TRANSFERABLE_TYPES = ["ArrayBuffer", "MessagePort", "OffscreenCanvas"] - .filter(f => f in self) - .map(f => self[f]); -const uid = Math.floor(Math.random() * Number.MAX_SAFE_INTEGER); -const proxyValueSymbol = Symbol("proxyValue"); -const throwSymbol = Symbol("throw"); -const proxyTransferHandler = { - canHandle: (obj) => obj && obj[proxyValueSymbol], - serialize: (obj) => { - const { port1, port2 } = new MessageChannel(); - expose(obj, port1); - return port2; - }, - deserialize: (obj) => { - return proxy(obj); - } -}; -const throwTransferHandler = { - canHandle: (obj) => obj && obj[throwSymbol], - serialize: (obj) => { - const message = obj && obj.message; - const stack = obj && obj.stack; - return Object.assign({}, obj, { message, stack }); - }, - deserialize: (obj) => { - throw Object.assign(Error(), obj); - } -}; -export const transferHandlers = new Map([ - ["PROXY", proxyTransferHandler], - ["THROW", throwTransferHandler] -]); -let pingPongMessageCounter = 0; -export function proxy(endpoint, target) { - if (isWindow(endpoint)) - endpoint = windowEndpoint(endpoint); - if (!isEndpoint(endpoint)) - throw Error("endpoint does not have all of addEventListener, removeEventListener and postMessage defined"); - activateEndpoint(endpoint); - return cbProxy(async (irequest) => { - let args = []; - if (irequest.type === "APPLY" || irequest.type === "CONSTRUCT") - args = irequest.argumentsList.map(wrapValue); - const response = await pingPongMessage(endpoint, Object.assign({}, irequest, { argumentsList: args }), transferableProperties(args)); - const result = response.data; - return unwrapValue(result.value); - }, [], target); -} -export function proxyValue(obj) { - obj[proxyValueSymbol] = true; - return obj; -} -export function expose(rootObj, endpoint) { - if (isWindow(endpoint)) - endpoint = windowEndpoint(endpoint); - if (!isEndpoint(endpoint)) - throw Error("endpoint does not have all of addEventListener, removeEventListener and postMessage defined"); - activateEndpoint(endpoint); - attachMessageHandler(endpoint, async function (event) { - if (!event.data.id || !event.data.callPath) - return; - const irequest = event.data; - let that = await irequest.callPath - .slice(0, -1) - .reduce((obj, propName) => obj[propName], rootObj); - let obj = await irequest.callPath.reduce((obj, propName) => obj[propName], rootObj); - let iresult = obj; - let args = []; - if (irequest.type === "APPLY" || irequest.type === "CONSTRUCT") - args = irequest.argumentsList.map(unwrapValue); - if (irequest.type === "APPLY") { - try { - iresult = await obj.apply(that, args); - } - catch (e) { - iresult = e; - iresult[throwSymbol] = true; - } - } - if (irequest.type === "CONSTRUCT") { - try { - iresult = new obj(...args); // eslint-disable-line new-cap - iresult = proxyValue(iresult); - } - catch (e) { - iresult = e; - iresult[throwSymbol] = true; - } - } - if (irequest.type === "SET") { - obj[irequest.property] = irequest.value; - // FIXME: ES6 Proxy Handler `set` methods are supposed to return a - // boolean. To show good will, we return true asynchronously ¯\_(ツ)_/¯ - iresult = true; - } - iresult = makeInvocationResult(iresult); - iresult.id = irequest.id; - return endpoint.postMessage(iresult, transferableProperties([iresult])); - }); -} -function wrapValue(arg) { - // Is arg itself handled by a TransferHandler? - for (const [key, transferHandler] of transferHandlers) { - if (transferHandler.canHandle(arg)) { - return { - type: key, - value: transferHandler.serialize(arg) - }; - } - } - // If not, traverse the entire object and find handled values. - let wrappedChildren = []; - for (const item of iterateAllProperties(arg)) { - for (const [key, transferHandler] of transferHandlers) { - if (transferHandler.canHandle(item.value)) { - wrappedChildren.push({ - path: item.path, - wrappedValue: { - type: key, - value: transferHandler.serialize(item.value) - } - }); - } - } - } - for (const wrappedChild of wrappedChildren) { - const container = wrappedChild.path - .slice(0, -1) - .reduce((obj, key) => obj[key], arg); - container[wrappedChild.path[wrappedChild.path.length - 1]] = null; - } - return { - type: "RAW", - value: arg, - wrappedChildren - }; -} -function unwrapValue(arg) { - if (transferHandlers.has(arg.type)) { - const transferHandler = transferHandlers.get(arg.type); - return transferHandler.deserialize(arg.value); - } - else if (isRawWrappedValue(arg)) { - for (const wrappedChildValue of arg.wrappedChildren || []) { - if (!transferHandlers.has(wrappedChildValue.wrappedValue.type)) - throw Error(`Unknown value type "${arg.type}" at ${wrappedChildValue.path.join(".")}`); - const transferHandler = transferHandlers.get(wrappedChildValue.wrappedValue.type); - const newValue = transferHandler.deserialize(wrappedChildValue.wrappedValue.value); - replaceValueInObjectAtPath(arg.value, wrappedChildValue.path, newValue); - } - return arg.value; - } - else { - throw Error(`Unknown value type "${arg.type}"`); - } -} -function replaceValueInObjectAtPath(obj, path, newVal) { - const lastKey = path.slice(-1)[0]; - const lastObj = path - .slice(0, -1) - .reduce((obj, key) => obj[key], obj); - lastObj[lastKey] = newVal; -} -function isRawWrappedValue(arg) { - return arg.type === "RAW"; -} -function windowEndpoint(w) { - if (self.constructor.name !== "Window") - throw Error("self is not a window"); - return { - addEventListener: self.addEventListener.bind(self), - removeEventListener: self.removeEventListener.bind(self), - postMessage: (msg, transfer) => w.postMessage(msg, "*", transfer) - }; -} -function isEndpoint(endpoint) { - return ("addEventListener" in endpoint && - "removeEventListener" in endpoint && - "postMessage" in endpoint); -} -function activateEndpoint(endpoint) { - if (isMessagePort(endpoint)) - endpoint.start(); -} -function attachMessageHandler(endpoint, f) { - // Checking all possible types of `endpoint` manually satisfies TypeScript’s - // type checker. Not sure why the inference is failing here. Since it’s - // unnecessary code I’m going to resort to `any` for now. - // if(isWorker(endpoint)) - // endpoint.addEventListener('message', f); - // if(isMessagePort(endpoint)) - // endpoint.addEventListener('message', f); - // if(isOtherWindow(endpoint)) - // endpoint.addEventListener('message', f); - endpoint.addEventListener("message", f); -} -function detachMessageHandler(endpoint, f) { - // Same as above. - endpoint.removeEventListener("message", f); -} -function isMessagePort(endpoint) { - return endpoint.constructor.name === "MessagePort"; -} -function isWindow(endpoint) { - // TODO: This doesn’t work on cross-origin iframes. - // return endpoint.constructor.name === 'Window'; - return ["window", "length", "location", "parent", "opener"].every(prop => prop in endpoint); -} -/** - * `pingPongMessage` sends a `postMessage` and waits for a reply. Replies are - * identified by a unique id that is attached to the payload. - */ -function pingPongMessage(endpoint, msg, transferables) { - const id = `${uid}-${pingPongMessageCounter++}`; - return new Promise(resolve => { - attachMessageHandler(endpoint, function handler(event) { - if (event.data.id !== id) - return; - detachMessageHandler(endpoint, handler); - resolve(event); - }); - // Copy msg and add `id` property - msg = Object.assign({}, msg, { id }); - endpoint.postMessage(msg, transferables); - }); -} -function cbProxy(cb, callPath = [], target = function () { }) { - return new Proxy(target, { - construct(_target, argumentsList, proxy) { - return cb({ - type: "CONSTRUCT", - callPath, - argumentsList - }); - }, - apply(_target, _thisArg, argumentsList) { - // We use `bind` as an indicator to have a remote function bound locally. - // The actual target for `bind()` is currently ignored. - if (callPath[callPath.length - 1] === "bind") - return cbProxy(cb, callPath.slice(0, -1)); - return cb({ - type: "APPLY", - callPath, - argumentsList - }); - }, - get(_target, property, proxy) { - if (property === "then" && callPath.length === 0) { - return { then: () => proxy }; - } - else if (property === "then") { - const r = cb({ - type: "GET", - callPath - }); - return Promise.resolve(r).then.bind(r); - } - else { - return cbProxy(cb, callPath.concat(property), _target[property]); - } - }, - set(_target, property, value, _proxy) { - return cb({ - type: "SET", - callPath, - property, - value - }); - } - }); -} -function isTransferable(thing) { - return TRANSFERABLE_TYPES.some(type => thing instanceof type); -} -function* iterateAllProperties(value, path = [], visited = null) { - if (!value) - return; - if (!visited) - visited = new WeakSet(); - if (visited.has(value)) - return; - if (typeof value === "string") - return; - if (typeof value === "object") - visited.add(value); - if (ArrayBuffer.isView(value)) - return; - yield { value, path }; - const keys = Object.keys(value); - for (const key of keys) - yield* iterateAllProperties(value[key], [...path, key], visited); -} -function transferableProperties(obj) { - const r = []; - for (const prop of iterateAllProperties(obj)) { - if (isTransferable(prop.value)) - r.push(prop.value); - } - return r; -} -function makeInvocationResult(obj) { - for (const [type, transferHandler] of transferHandlers) { - if (transferHandler.canHandle(obj)) { - const value = transferHandler.serialize(obj); - return { - value: { type, value } - }; - } - } - return { - value: { - type: "RAW", - value: obj - } - }; -} diff --git a/dist/messagechanneladapter.d.ts b/dist/messagechanneladapter.d.ts deleted file mode 100644 index 7c6ee3e4..00000000 --- a/dist/messagechanneladapter.d.ts +++ /dev/null @@ -1,19 +0,0 @@ -/** - * Copyright 2017 Google Inc. All Rights Reserved. - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0 - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -export interface StringMessageChannel extends EventTarget { - send(data: string): void; -} -export declare function wrap( - smc: StringMessageChannel, - id?: string | null -): MessagePort; diff --git a/dist/messagechanneladapter.js b/dist/messagechanneladapter.js deleted file mode 100644 index 3af0bff0..00000000 --- a/dist/messagechanneladapter.js +++ /dev/null @@ -1,85 +0,0 @@ -/** - * Copyright 2017 Google Inc. All Rights Reserved. - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0 - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -export function wrap(smc, id = null) { - const { port1, port2 } = new MessageChannel(); - hookup(port2, smc, id); - return port1; -} -function hookup(internalPort, smc, id = null) { - internalPort.onmessage = (event) => { - if (!id) - id = generateUID(); - const msg = event.data; - const messageChannels = Array.from(findMessageChannels(event.data)); - for (const messageChannel of messageChannels) { - const id = generateUID(); - const channel = replaceProperty(msg, messageChannel, id); - hookup(channel, smc, id); - } - const payload = JSON.stringify({ id, msg, messageChannels }); - smc.send(payload); - }; - smc.addEventListener("message", (event) => { - let data = {}; - try { - data = JSON.parse(event.data); - } - catch (e) { - return; - } - if (id && id !== data.id) - return; - const mcs = data.messageChannels.map(messageChannel => { - const id = messageChannel.reduce((obj, key) => obj[key], data.msg); - const port = wrap(smc, id); - replaceProperty(data.msg, messageChannel, port); - return port; - }); - internalPort.postMessage(data.msg, mcs); - }); -} -function replaceProperty(obj, path, newVal) { - for (const key of path.slice(0, -1)) - obj = obj[key]; - const key = path[path.length - 1]; - const orig = obj[key]; - obj[key] = newVal; - return orig; -} -function* findMessageChannels(obj, path = []) { - if (!obj) - return; - if (typeof obj === "string") - return; - if (obj instanceof MessagePort) { - yield path.slice(); - return; - } - for (const key of Object.keys(obj)) { - path.push(key); - yield* findMessageChannels(obj[key], path); - path.pop(); - } -} -function hex4() { - return Math.floor((1 + Math.random()) * 0x10000) - .toString(16) - .substring(1); -} -const bits = 128; -function generateUID() { - return new Array(bits / 16) - .fill(0) - .map(_ => hex4()) - .join(""); -} diff --git a/dist/umd/comlink.d.ts b/dist/umd/comlink.d.ts deleted file mode 100644 index 126934aa..00000000 --- a/dist/umd/comlink.d.ts +++ /dev/null @@ -1,37 +0,0 @@ -/** - * Copyright 2017 Google Inc. All Rights Reserved. - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0 - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -export interface Endpoint { - postMessage(message: any, transfer?: any[]): void; - addEventListener(type: string, listener: EventListenerOrEventListenerObject, options?: {}): void; - removeEventListener(type: string, listener: EventListenerOrEventListenerObject, options?: {}): void; -} -declare type ProxiedObject = { - [P in keyof T]: T[P] extends (...args: infer Arguments) => infer R ? (...args: Arguments) => Promise : Promise; -}; -export declare type ProxyResult = ProxiedObject & (T extends (...args: infer Arguments) => infer R ? (...args: Arguments) => Promise : unknown) & (T extends { - new (...args: infer ArgumentsType): infer InstanceType; -} ? { - new (...args: ArgumentsType): Promise>; -} : unknown); -export declare type Proxy = Function; -export declare type Exposable = Function | Object; -export interface TransferHandler { - canHandle: (obj: {}) => Boolean; - serialize: (obj: {}) => {}; - deserialize: (obj: {}) => {}; -} -export declare const transferHandlers: Map; -export declare function proxy(endpoint: Endpoint | Window, target?: any): ProxyResult; -export declare function proxyValue(obj: T): T; -export declare function expose(rootObj: Exposable, endpoint: Endpoint | Window): void; -export {}; diff --git a/dist/umd/comlink.js b/dist/umd/comlink.js deleted file mode 100644 index aea35f83..00000000 --- a/dist/umd/comlink.js +++ /dev/null @@ -1,344 +0,0 @@ -/** - * Copyright 2017 Google Inc. All Rights Reserved. - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0 - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -(function (factory) { - if (typeof module === "object" && typeof module.exports === "object") { - var v = factory(require, exports); - if (v !== undefined) module.exports = v; - } - else if (typeof define === "function" && define.amd) { - define(["require", "exports"], factory); - } -else {factory([], self.Comlink={});} -})(function (require, exports) { - "use strict"; - Object.defineProperty(exports, "__esModule", { value: true }); - const TRANSFERABLE_TYPES = ["ArrayBuffer", "MessagePort", "OffscreenCanvas"] - .filter(f => f in self) - .map(f => self[f]); - const uid = Math.floor(Math.random() * Number.MAX_SAFE_INTEGER); - const proxyValueSymbol = Symbol("proxyValue"); - const throwSymbol = Symbol("throw"); - const proxyTransferHandler = { - canHandle: (obj) => obj && obj[proxyValueSymbol], - serialize: (obj) => { - const { port1, port2 } = new MessageChannel(); - expose(obj, port1); - return port2; - }, - deserialize: (obj) => { - return proxy(obj); - } - }; - const throwTransferHandler = { - canHandle: (obj) => obj && obj[throwSymbol], - serialize: (obj) => { - const message = obj && obj.message; - const stack = obj && obj.stack; - return Object.assign({}, obj, { message, stack }); - }, - deserialize: (obj) => { - throw Object.assign(Error(), obj); - } - }; - exports.transferHandlers = new Map([ - ["PROXY", proxyTransferHandler], - ["THROW", throwTransferHandler] - ]); - let pingPongMessageCounter = 0; - function proxy(endpoint, target) { - if (isWindow(endpoint)) - endpoint = windowEndpoint(endpoint); - if (!isEndpoint(endpoint)) - throw Error("endpoint does not have all of addEventListener, removeEventListener and postMessage defined"); - activateEndpoint(endpoint); - return cbProxy(async (irequest) => { - let args = []; - if (irequest.type === "APPLY" || irequest.type === "CONSTRUCT") - args = irequest.argumentsList.map(wrapValue); - const response = await pingPongMessage(endpoint, Object.assign({}, irequest, { argumentsList: args }), transferableProperties(args)); - const result = response.data; - return unwrapValue(result.value); - }, [], target); - } - exports.proxy = proxy; - function proxyValue(obj) { - obj[proxyValueSymbol] = true; - return obj; - } - exports.proxyValue = proxyValue; - function expose(rootObj, endpoint) { - if (isWindow(endpoint)) - endpoint = windowEndpoint(endpoint); - if (!isEndpoint(endpoint)) - throw Error("endpoint does not have all of addEventListener, removeEventListener and postMessage defined"); - activateEndpoint(endpoint); - attachMessageHandler(endpoint, async function (event) { - if (!event.data.id || !event.data.callPath) - return; - const irequest = event.data; - let that = await irequest.callPath - .slice(0, -1) - .reduce((obj, propName) => obj[propName], rootObj); - let obj = await irequest.callPath.reduce((obj, propName) => obj[propName], rootObj); - let iresult = obj; - let args = []; - if (irequest.type === "APPLY" || irequest.type === "CONSTRUCT") - args = irequest.argumentsList.map(unwrapValue); - if (irequest.type === "APPLY") { - try { - iresult = await obj.apply(that, args); - } - catch (e) { - iresult = e; - iresult[throwSymbol] = true; - } - } - if (irequest.type === "CONSTRUCT") { - try { - iresult = new obj(...args); // eslint-disable-line new-cap - iresult = proxyValue(iresult); - } - catch (e) { - iresult = e; - iresult[throwSymbol] = true; - } - } - if (irequest.type === "SET") { - obj[irequest.property] = irequest.value; - // FIXME: ES6 Proxy Handler `set` methods are supposed to return a - // boolean. To show good will, we return true asynchronously ¯\_(ツ)_/¯ - iresult = true; - } - iresult = makeInvocationResult(iresult); - iresult.id = irequest.id; - return endpoint.postMessage(iresult, transferableProperties([iresult])); - }); - } - exports.expose = expose; - function wrapValue(arg) { - // Is arg itself handled by a TransferHandler? - for (const [key, transferHandler] of exports.transferHandlers) { - if (transferHandler.canHandle(arg)) { - return { - type: key, - value: transferHandler.serialize(arg) - }; - } - } - // If not, traverse the entire object and find handled values. - let wrappedChildren = []; - for (const item of iterateAllProperties(arg)) { - for (const [key, transferHandler] of exports.transferHandlers) { - if (transferHandler.canHandle(item.value)) { - wrappedChildren.push({ - path: item.path, - wrappedValue: { - type: key, - value: transferHandler.serialize(item.value) - } - }); - } - } - } - for (const wrappedChild of wrappedChildren) { - const container = wrappedChild.path - .slice(0, -1) - .reduce((obj, key) => obj[key], arg); - container[wrappedChild.path[wrappedChild.path.length - 1]] = null; - } - return { - type: "RAW", - value: arg, - wrappedChildren - }; - } - function unwrapValue(arg) { - if (exports.transferHandlers.has(arg.type)) { - const transferHandler = exports.transferHandlers.get(arg.type); - return transferHandler.deserialize(arg.value); - } - else if (isRawWrappedValue(arg)) { - for (const wrappedChildValue of arg.wrappedChildren || []) { - if (!exports.transferHandlers.has(wrappedChildValue.wrappedValue.type)) - throw Error(`Unknown value type "${arg.type}" at ${wrappedChildValue.path.join(".")}`); - const transferHandler = exports.transferHandlers.get(wrappedChildValue.wrappedValue.type); - const newValue = transferHandler.deserialize(wrappedChildValue.wrappedValue.value); - replaceValueInObjectAtPath(arg.value, wrappedChildValue.path, newValue); - } - return arg.value; - } - else { - throw Error(`Unknown value type "${arg.type}"`); - } - } - function replaceValueInObjectAtPath(obj, path, newVal) { - const lastKey = path.slice(-1)[0]; - const lastObj = path - .slice(0, -1) - .reduce((obj, key) => obj[key], obj); - lastObj[lastKey] = newVal; - } - function isRawWrappedValue(arg) { - return arg.type === "RAW"; - } - function windowEndpoint(w) { - if (self.constructor.name !== "Window") - throw Error("self is not a window"); - return { - addEventListener: self.addEventListener.bind(self), - removeEventListener: self.removeEventListener.bind(self), - postMessage: (msg, transfer) => w.postMessage(msg, "*", transfer) - }; - } - function isEndpoint(endpoint) { - return ("addEventListener" in endpoint && - "removeEventListener" in endpoint && - "postMessage" in endpoint); - } - function activateEndpoint(endpoint) { - if (isMessagePort(endpoint)) - endpoint.start(); - } - function attachMessageHandler(endpoint, f) { - // Checking all possible types of `endpoint` manually satisfies TypeScript’s - // type checker. Not sure why the inference is failing here. Since it’s - // unnecessary code I’m going to resort to `any` for now. - // if(isWorker(endpoint)) - // endpoint.addEventListener('message', f); - // if(isMessagePort(endpoint)) - // endpoint.addEventListener('message', f); - // if(isOtherWindow(endpoint)) - // endpoint.addEventListener('message', f); - endpoint.addEventListener("message", f); - } - function detachMessageHandler(endpoint, f) { - // Same as above. - endpoint.removeEventListener("message", f); - } - function isMessagePort(endpoint) { - return endpoint.constructor.name === "MessagePort"; - } - function isWindow(endpoint) { - // TODO: This doesn’t work on cross-origin iframes. - // return endpoint.constructor.name === 'Window'; - return ["window", "length", "location", "parent", "opener"].every(prop => prop in endpoint); - } - /** - * `pingPongMessage` sends a `postMessage` and waits for a reply. Replies are - * identified by a unique id that is attached to the payload. - */ - function pingPongMessage(endpoint, msg, transferables) { - const id = `${uid}-${pingPongMessageCounter++}`; - return new Promise(resolve => { - attachMessageHandler(endpoint, function handler(event) { - if (event.data.id !== id) - return; - detachMessageHandler(endpoint, handler); - resolve(event); - }); - // Copy msg and add `id` property - msg = Object.assign({}, msg, { id }); - endpoint.postMessage(msg, transferables); - }); - } - function cbProxy(cb, callPath = [], target = function () { }) { - return new Proxy(target, { - construct(_target, argumentsList, proxy) { - return cb({ - type: "CONSTRUCT", - callPath, - argumentsList - }); - }, - apply(_target, _thisArg, argumentsList) { - // We use `bind` as an indicator to have a remote function bound locally. - // The actual target for `bind()` is currently ignored. - if (callPath[callPath.length - 1] === "bind") - return cbProxy(cb, callPath.slice(0, -1)); - return cb({ - type: "APPLY", - callPath, - argumentsList - }); - }, - get(_target, property, proxy) { - if (property === "then" && callPath.length === 0) { - return { then: () => proxy }; - } - else if (property === "then") { - const r = cb({ - type: "GET", - callPath - }); - return Promise.resolve(r).then.bind(r); - } - else { - return cbProxy(cb, callPath.concat(property), _target[property]); - } - }, - set(_target, property, value, _proxy) { - return cb({ - type: "SET", - callPath, - property, - value - }); - } - }); - } - function isTransferable(thing) { - return TRANSFERABLE_TYPES.some(type => thing instanceof type); - } - function* iterateAllProperties(value, path = [], visited = null) { - if (!value) - return; - if (!visited) - visited = new WeakSet(); - if (visited.has(value)) - return; - if (typeof value === "string") - return; - if (typeof value === "object") - visited.add(value); - if (ArrayBuffer.isView(value)) - return; - yield { value, path }; - const keys = Object.keys(value); - for (const key of keys) - yield* iterateAllProperties(value[key], [...path, key], visited); - } - function transferableProperties(obj) { - const r = []; - for (const prop of iterateAllProperties(obj)) { - if (isTransferable(prop.value)) - r.push(prop.value); - } - return r; - } - function makeInvocationResult(obj) { - for (const [type, transferHandler] of exports.transferHandlers) { - if (transferHandler.canHandle(obj)) { - const value = transferHandler.serialize(obj); - return { - value: { type, value } - }; - } - } - return { - value: { - type: "RAW", - value: obj - } - }; - } -}); diff --git a/dist/umd/messagechanneladapter.d.ts b/dist/umd/messagechanneladapter.d.ts deleted file mode 100644 index f6d700bb..00000000 --- a/dist/umd/messagechanneladapter.d.ts +++ /dev/null @@ -1,16 +0,0 @@ -/** - * Copyright 2017 Google Inc. All Rights Reserved. - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0 - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -export interface StringMessageChannel extends EventTarget { - send(data: string): void; -} -export declare function wrap(smc: StringMessageChannel, id?: string | null): MessagePort; diff --git a/dist/umd/messagechanneladapter.js b/dist/umd/messagechanneladapter.js deleted file mode 100644 index d45d930a..00000000 --- a/dist/umd/messagechanneladapter.js +++ /dev/null @@ -1,99 +0,0 @@ -/** - * Copyright 2017 Google Inc. All Rights Reserved. - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0 - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -(function (factory) { - if (typeof module === "object" && typeof module.exports === "object") { - var v = factory(require, exports); - if (v !== undefined) module.exports = v; - } - else if (typeof define === "function" && define.amd) { - define(["require", "exports"], factory); - } -else {factory([], self.Comlink={});} -})(function (require, exports) { - "use strict"; - Object.defineProperty(exports, "__esModule", { value: true }); - function wrap(smc, id = null) { - const { port1, port2 } = new MessageChannel(); - hookup(port2, smc, id); - return port1; - } - exports.wrap = wrap; - function hookup(internalPort, smc, id = null) { - internalPort.onmessage = (event) => { - if (!id) - id = generateUID(); - const msg = event.data; - const messageChannels = Array.from(findMessageChannels(event.data)); - for (const messageChannel of messageChannels) { - const id = generateUID(); - const channel = replaceProperty(msg, messageChannel, id); - hookup(channel, smc, id); - } - const payload = JSON.stringify({ id, msg, messageChannels }); - smc.send(payload); - }; - smc.addEventListener("message", (event) => { - let data = {}; - try { - data = JSON.parse(event.data); - } - catch (e) { - return; - } - if (id && id !== data.id) - return; - const mcs = data.messageChannels.map(messageChannel => { - const id = messageChannel.reduce((obj, key) => obj[key], data.msg); - const port = wrap(smc, id); - replaceProperty(data.msg, messageChannel, port); - return port; - }); - internalPort.postMessage(data.msg, mcs); - }); - } - function replaceProperty(obj, path, newVal) { - for (const key of path.slice(0, -1)) - obj = obj[key]; - const key = path[path.length - 1]; - const orig = obj[key]; - obj[key] = newVal; - return orig; - } - function* findMessageChannels(obj, path = []) { - if (!obj) - return; - if (typeof obj === "string") - return; - if (obj instanceof MessagePort) { - yield path.slice(); - return; - } - for (const key of Object.keys(obj)) { - path.push(key); - yield* findMessageChannels(obj[key], path); - path.pop(); - } - } - function hex4() { - return Math.floor((1 + Math.random()) * 0x10000) - .toString(16) - .substring(1); - } - const bits = 128; - function generateUID() { - return new Array(bits / 16) - .fill(0) - .map(_ => hex4()) - .join(""); - } -}); diff --git a/docs/examples/01-simple-example/index.html b/docs/examples/01-simple-example/index.html index 7a236cab..f72f86a1 100644 --- a/docs/examples/01-simple-example/index.html +++ b/docs/examples/01-simple-example/index.html @@ -1,17 +1,18 @@ - - + diff --git a/docs/examples/01-simple-example/worker.js b/docs/examples/01-simple-example/worker.js index 672580c1..5f26e1e0 100644 --- a/docs/examples/01-simple-example/worker.js +++ b/docs/examples/01-simple-example/worker.js @@ -11,7 +11,8 @@ * limitations under the License. */ -importScripts("https://cdn.jsdelivr.net/npm/comlinkjs@3/umd/comlink.js"); +importScripts("https://unpkg.com/comlink@alpha/dist/umd/comlink.js"); +// importScripts("../../../dist/umd/comlink.js"); const obj = { counter: 0, @@ -20,4 +21,4 @@ const obj = { } }; -Comlink.expose(obj, self); +Comlink.expose(obj); diff --git a/docs/examples/02-callback-example/index.html b/docs/examples/02-callback-example/index.html index 25c63468..d697b371 100644 --- a/docs/examples/02-callback-example/index.html +++ b/docs/examples/02-callback-example/index.html @@ -1,15 +1,17 @@ - + diff --git a/docs/examples/02-callback-example/worker.js b/docs/examples/02-callback-example/worker.js index f4e4c4d7..6a7ca4fa 100644 --- a/docs/examples/02-callback-example/worker.js +++ b/docs/examples/02-callback-example/worker.js @@ -11,10 +11,11 @@ * limitations under the License. */ -importScripts("https://cdn.jsdelivr.net/npm/comlinkjs@3/umd/comlink.js"); +importScripts("https://unpkg.com/comlink@alpha/dist/umd/comlink.js"); +// importScripts("../../../dist/umd/comlink.js"); async function remoteFunction(cb) { await cb("A string from a worker"); } -Comlink.expose(remoteFunction, self); +Comlink.expose(remoteFunction); diff --git a/docs/examples/03-classes-example/index.html b/docs/examples/03-classes-example/index.html index dc5c26fb..5c6e6fdc 100644 --- a/docs/examples/03-classes-example/index.html +++ b/docs/examples/03-classes-example/index.html @@ -1,7 +1,9 @@ - + diff --git a/docs/examples/03-classes-example/worker.js b/docs/examples/03-classes-example/worker.js index 169f4764..25cb0c0f 100644 --- a/docs/examples/03-classes-example/worker.js +++ b/docs/examples/03-classes-example/worker.js @@ -11,10 +11,12 @@ * limitations under the License. */ -importScripts("https://cdn.jsdelivr.net/npm/comlinkjs@3/umd/comlink.js"); +importScripts("https://unpkg.com/comlink@alpha/dist/umd/comlink.js"); +// importScripts("../../../dist/umd/comlink.js"); class MyClass { constructor(init = 0) { + console.log(init); this._counter = init; } @@ -27,4 +29,4 @@ class MyClass { } } -Comlink.expose(MyClass, self); +Comlink.expose(MyClass); diff --git a/docs/examples/04-eventlistener-example/event.transferhandler.js b/docs/examples/04-eventlistener-example/event.transferhandler.js index ba8f54cd..c154cdfa 100644 --- a/docs/examples/04-eventlistener-example/event.transferhandler.js +++ b/docs/examples/04-eventlistener-example/event.transferhandler.js @@ -1,15 +1,18 @@ -Comlink.transferHandlers.set("EVENT", { +Comlink.transferHandlers.set("event", { canHandle(obj) { return obj instanceof Event; }, serialize(obj) { - return { - targetId: obj && obj.target && obj.target.id, - targetClassList: obj && - obj.target && - obj.target.classList && [...obj.target.classList], - detail: obj && obj.detail - }; + return [ + { + targetId: obj && obj.target && obj.target.id, + targetClassList: obj && + obj.target && + obj.target.classList && [...obj.target.classList], + detail: obj && obj.detail + }, + [] + ]; }, deserialize(obj) { return obj; diff --git a/docs/examples/04-eventlistener-example/index.html b/docs/examples/04-eventlistener-example/index.html index 0b05956a..4e748de8 100644 --- a/docs/examples/04-eventlistener-example/index.html +++ b/docs/examples/04-eventlistener-example/index.html @@ -1,11 +1,14 @@ - + Open DevTools and click the button. - - + + + diff --git a/docs/examples/04-eventlistener-example/worker.js b/docs/examples/04-eventlistener-example/worker.js index 3f408c93..cbd6f1c3 100644 --- a/docs/examples/04-eventlistener-example/worker.js +++ b/docs/examples/04-eventlistener-example/worker.js @@ -11,18 +11,16 @@ * limitations under the License. */ -importScripts("https://cdn.jsdelivr.net/npm/comlinkjs@3/umd/comlink.js"); +importScripts("https://unpkg.com/comlink@alpha/dist/umd/comlink.js"); +// importScripts("../../../dist/umd/comlink.js"); importScripts("event.transferhandler.js"); -Comlink.expose( - { - onclick(ev) { - console.log( - `Click! Button id: ${ev.targetId}, Button classes: ${JSON.stringify( - ev.targetClassList - )}` - ); - } - }, - self -); +Comlink.expose({ + onclick(ev) { + console.log( + `Click! Button id: ${ev.targetId}, Button classes: ${JSON.stringify( + ev.targetClassList + )}` + ); + } +}); diff --git a/docs/examples/05-service-worker/README.md b/docs/examples/05-serviceworker-example/README.md similarity index 100% rename from docs/examples/05-service-worker/README.md rename to docs/examples/05-serviceworker-example/README.md diff --git a/docs/examples/05-service-worker/index.html b/docs/examples/05-serviceworker-example/index.html similarity index 58% rename from docs/examples/05-service-worker/index.html rename to docs/examples/05-serviceworker-example/index.html index 8ff5f38a..a71df692 100644 --- a/docs/examples/05-service-worker/index.html +++ b/docs/examples/05-serviceworker-example/index.html @@ -1,17 +1,18 @@ - - + diff --git a/docs/examples/05-service-worker/worker.js b/docs/examples/05-serviceworker-example/worker.js similarity index 88% rename from docs/examples/05-service-worker/worker.js rename to docs/examples/05-serviceworker-example/worker.js index ee8e179d..e455d6b0 100644 --- a/docs/examples/05-service-worker/worker.js +++ b/docs/examples/05-serviceworker-example/worker.js @@ -11,7 +11,8 @@ * limitations under the License. */ -importScripts("https://cdn.jsdelivr.net/npm/comlinkjs@3/umd/comlink.js"); +importScripts("https://unpkg.com/comlink@alpha/dist/umd/comlink.js"); +// importScripts("../../../dist/umd/comlink.js"); addEventListener("install", () => skipWaiting()); addEventListener("activate", () => clients.claim()); diff --git a/docs/examples/06-node-example/main.mjs b/docs/examples/06-node-example/main.mjs new file mode 100644 index 00000000..7597519c --- /dev/null +++ b/docs/examples/06-node-example/main.mjs @@ -0,0 +1,11 @@ +import { Worker } from "worker_threads"; +import * as Comlink from "../../../dist/esm/comlink.mjs"; +import nodeEndpoint from "../../../dist/esm/node-adapter.mjs"; + +async function init() { + const worker = new Worker("./worker.mjs"); + + const api = Comlink.wrap(nodeEndpoint(worker)); + console.log(await api.doMath()); +} +init(); diff --git a/docs/examples/06-node-example/worker.mjs b/docs/examples/06-node-example/worker.mjs new file mode 100644 index 00000000..a402a934 --- /dev/null +++ b/docs/examples/06-node-example/worker.mjs @@ -0,0 +1,10 @@ +import { parentPort } from "worker_threads"; +import * as Comlink from "../../../dist/esm/comlink.mjs"; +import nodeEndpoint from "../../../dist/esm/node-adapter.mjs"; + +const api = { + doMath() { + return 4; + } +}; +Comlink.expose(api, nodeEndpoint(parentPort)); diff --git a/docs/examples/99-nonworker-examples/iframes/README.md b/docs/examples/99-nonworker-examples/iframes/README.md index 6b4ea793..ae6ec495 100644 --- a/docs/examples/99-nonworker-examples/iframes/README.md +++ b/docs/examples/99-nonworker-examples/iframes/README.md @@ -1 +1 @@ -This example shows how to set up Comlink between a window and an embedded iframe. +This example shows how to set up Comlink between a window and an embedded iframe using `Comlink.windowEndpoint()`. diff --git a/docs/examples/99-nonworker-examples/iframes/iframe.html b/docs/examples/99-nonworker-examples/iframes/iframe.html index a01c19e0..ac4cd924 100644 --- a/docs/examples/99-nonworker-examples/iframes/iframe.html +++ b/docs/examples/99-nonworker-examples/iframes/iframe.html @@ -1,9 +1,10 @@ - + diff --git a/docs/examples/99-nonworker-examples/iframes/index.html b/docs/examples/99-nonworker-examples/iframes/index.html index ec7e44e5..f9127e8e 100644 --- a/docs/examples/99-nonworker-examples/iframes/index.html +++ b/docs/examples/99-nonworker-examples/iframes/index.html @@ -1,12 +1,13 @@ - + - - diff --git a/docs/examples/99-nonworker-examples/presentation-api/presenter.html b/docs/examples/99-nonworker-examples/presentation-api/presenter.html deleted file mode 100644 index f4de45dd..00000000 --- a/docs/examples/99-nonworker-examples/presentation-api/presenter.html +++ /dev/null @@ -1,35 +0,0 @@ - - - - - - - diff --git a/docs/examples/99-nonworker-examples/webrtc/README.md b/docs/examples/99-nonworker-examples/webrtc/README.md deleted file mode 100644 index c1d51e7f..00000000 --- a/docs/examples/99-nonworker-examples/webrtc/README.md +++ /dev/null @@ -1,5 +0,0 @@ -# This is experimental - -This example sets up Comlink over a WebRTC `RTCPeerConnection`. - -Here’s a [video](https://www.youtube.com/watch?v=xPPGgyGoAS0). diff --git a/docs/examples/99-nonworker-examples/webrtc/index.html b/docs/examples/99-nonworker-examples/webrtc/index.html deleted file mode 100644 index 7fe9364d..00000000 --- a/docs/examples/99-nonworker-examples/webrtc/index.html +++ /dev/null @@ -1,77 +0,0 @@ - -

Instructions

-
    -
  • Open the second window (can also be another device on the same network)
  • -
  • Generate the handshake
  • -
  • Copy the text from this textarea to the 2nd window’s textarea
  • -
  • Generate the answer
  • -
  • Copy the text from the 2nd window’s textarea to this textarea
  • -
  • Consume answer
  • -
  • Increase counter as often as you like
  • -
- -

Handshake

- - - - -

-
-
-
diff --git a/docs/examples/99-nonworker-examples/webrtc/secondwindow.html b/docs/examples/99-nonworker-examples/webrtc/secondwindow.html
deleted file mode 100644
index de919658..00000000
--- a/docs/examples/99-nonworker-examples/webrtc/secondwindow.html
+++ /dev/null
@@ -1,59 +0,0 @@
-
-

Handshake

- - -

-
-
-
diff --git a/docs/index.html b/docs/index.html
index db3adfb9..22567031 100644
--- a/docs/index.html
+++ b/docs/index.html
@@ -1,2 +1,5 @@
-
-
+
+
diff --git a/karma.conf.js b/karma.conf.js
index 6b2c3183..c6bf59ce 100644
--- a/karma.conf.js
+++ b/karma.conf.js
@@ -14,14 +14,14 @@
 module.exports = function(config) {
   const configuration = {
     basePath: "",
-    frameworks: ["mocha", "chai"],
+    frameworks: ["mocha", "chai", "detectBrowsers"],
     files: [
       {
         pattern: "tests/fixtures/*",
         included: false
       },
       {
-        pattern: "dist/**/*.js",
+        pattern: "dist/**/*.@(mjs|js)",
         included: false
       },
       {
@@ -36,35 +36,27 @@ module.exports = function(config) {
     autoWatch: true,
     singleRun: true,
     concurrency: Infinity,
-    browsers: [
-      "Chrome",
-      "ChromeCanaryHarmony",
-      "Firefox",
-      "FirefoxNightly",
-      "Safari"
-      // "SafariTechPreview"
-    ],
+    detectBrowsers: {
+      enabled: true,
+      usePhantomJS: false,
+      preferHeadless: true,
+      postDetection: availableBrowsers => {
+        if (process.env.INSIDE_DOCKER) {
+          return ["DockerChrome"];
+        } else if (process.env.CHROME_ONLY) {
+          return ["ChromeHeadless"];
+        } else {
+          return availableBrowsers;
+        }
+      }
+    },
     customLaunchers: {
-      ChromeCanaryHarmony: {
-        base: "ChromeCanary",
-        flags: ["--js-flags=--harmony"]
-      },
-      ChromeCanaryHeadlessHarmony: {
-        base: "ChromeCanary",
-        flags: ["--js-flags=--harmony", /* '--headless', */ "--disable-gpu"]
-      },
       DockerChrome: {
         base: "ChromeHeadless",
         flags: ["--no-sandbox"]
       }
-    },
-    // Remove these 2 lines once this PR lands
-    // https://github.com/karma-runner/karma/pull/2834
-    customContextFile: "tests/context.html",
-    customDebugFile: "tests/debug.html"
+    }
   };
 
-  if (process.env.INSIDE_DOCKER) configuration.browsers = ["DockerChrome"];
-
   config.set(configuration);
 };
diff --git a/mangle_umd.js b/mangle_umd.js
deleted file mode 100644
index 739be527..00000000
--- a/mangle_umd.js
+++ /dev/null
@@ -1,13 +0,0 @@
-const fs = require("fs");
-
-function mangleUMD(contents) {
-  const lines = contents.split("\n");
-  lines.splice(20, 0, "else {factory([], self.Comlink={});}");
-  return lines.join("\n");
-}
-
-["dist/umd/comlink.js", "dist/umd/messagechanneladapter.js"].forEach(file => {
-  const contents = fs.readFileSync(file).toString();
-  const mangled = mangleUMD(contents);
-  fs.writeFileSync(file, mangled);
-});
diff --git a/messagechanneladapter.md b/messagechanneladapter.md
deleted file mode 100644
index 78916540..00000000
--- a/messagechanneladapter.md
+++ /dev/null
@@ -1,23 +0,0 @@
-# MessageChannelAdapter
-
-`MessageChannelAdapter` is a small utility function that turns string-based
-communication channels – like a [WebSocket], [RTCDataChannel] or
-[PresentationConnection] – into a Comlink-compatible `postMessage`-based API
-that can transfer `MessagePorts`.
-
-## Usage
-
-See the [examples], specifically WebRTC and Presentation API.
-
-## API
-
-### `MessageChannelAdapter.wrap(endpoint)`
-
-`wrap` returns a `MessagePort` that serializes messages using `JSON.stringify`
-and handles transferred `MessagePort`s automatically. `endpoint` is expected to
-have `send` and `addEventListener`.
-
-[examples]: https://github.com/GoogleChromeLabs/comlink/tree/master/docs/examples
-[websocket]: https://developer.mozilla.org/en-US/docs/Web/API/WebSocket
-[rtcdatachannel]: https://developer.mozilla.org/en-US/docs/Web/API/RTCDataChannel
-[presentationconnection]: https://developer.mozilla.org/en-US/docs/Web/API/PresentationConnection
diff --git a/messagechanneladapter.ts b/messagechanneladapter.ts
deleted file mode 100644
index 1a1abfda..00000000
--- a/messagechanneladapter.ts
+++ /dev/null
@@ -1,107 +0,0 @@
-/**
- * Copyright 2017 Google Inc. All Rights Reserved.
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *     http://www.apache.org/licenses/LICENSE-2.0
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-export interface StringMessageChannel extends EventTarget {
-  send(data: string): void;
-}
-
-interface Message {
-  id: string;
-  msg: any;
-  messageChannels: string[][];
-}
-
-export function wrap(
-  smc: StringMessageChannel,
-  id: string | null = null
-): MessagePort {
-  const { port1, port2 } = new MessageChannel();
-  hookup(port2, smc, id);
-  return port1;
-}
-
-function hookup(
-  internalPort: MessagePort,
-  smc: StringMessageChannel,
-  id: string | null = null
-): void {
-  internalPort.onmessage = (event: MessageEvent) => {
-    if (!id) id = generateUID();
-    const msg = event.data;
-    const messageChannels = Array.from(findMessageChannels(event.data));
-    for (const messageChannel of messageChannels) {
-      const id = generateUID();
-      const channel = replaceProperty(msg, messageChannel, id);
-      hookup(channel, smc, id);
-    }
-    const payload = JSON.stringify({ id, msg, messageChannels });
-    smc.send(payload);
-  };
-
-  smc.addEventListener("message", (event: Event): void => {
-    let data = {} as Message;
-    try {
-      data = JSON.parse((event as MessageEvent).data) as Message;
-    } catch (e) {
-      return;
-    }
-    if (id && id !== data.id) return;
-    const mcs = data.messageChannels.map(messageChannel => {
-      const id = messageChannel.reduce((obj, key) => obj[key], data.msg);
-      const port = wrap(smc, id);
-      replaceProperty(data.msg, messageChannel, port);
-      return port;
-    });
-    internalPort.postMessage(data.msg, mcs);
-  });
-}
-
-function replaceProperty(obj: any, path: string[], newVal: any): any {
-  for (const key of path.slice(0, -1)) obj = obj[key];
-  const key = path[path.length - 1];
-  const orig = obj[key];
-  obj[key] = newVal;
-  return orig;
-}
-
-function* findMessageChannels(
-  obj: any,
-  path: string[] = []
-): Iterable {
-  if (!obj) return;
-  if (typeof obj === "string") return;
-  if (obj instanceof MessagePort) {
-    yield path.slice();
-    return;
-  }
-  for (const key of Object.keys(obj)) {
-    path.push(key);
-    yield* findMessageChannels(obj[key], path);
-    path.pop();
-  }
-}
-
-function hex4(): string {
-  return Math.floor((1 + Math.random()) * 0x10000)
-    .toString(16)
-    .substring(1);
-}
-
-const bits = 128;
-function generateUID(): string {
-  return new Array(bits / 16)
-    .fill(0)
-    .map(_ => hex4())
-    .join("");
-}
-
diff --git a/package-lock.json b/package-lock.json
index dbdadd8e..4eec5ce8 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,9 +1,41 @@
 {
-  "name": "comlinkjs",
-  "version": "3.1.1",
+  "name": "comlink",
+  "version": "4.0.0-alpha.10",
   "lockfileVersion": 1,
   "requires": true,
   "dependencies": {
+    "@babel/code-frame": {
+      "version": "7.0.0",
+      "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.0.0.tgz",
+      "integrity": "sha512-OfC2uemaknXr87bdLUkWog7nYuliM9Ij5HUcajsVcMCpQrcLmtxRbVFTIqmcSkSeYRBFBRxs2FiUqFJDLdiebA==",
+      "dev": true,
+      "requires": {
+        "@babel/highlight": "^7.0.0"
+      }
+    },
+    "@babel/highlight": {
+      "version": "7.0.0",
+      "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.0.0.tgz",
+      "integrity": "sha512-UFMC4ZeFC48Tpvj7C8UgLvtkaUuovQX+5xNWrsIoMG8o2z+XFKjKaN9iVmS84dPwVN00W4wPmqvYoZF3EGAsfw==",
+      "dev": true,
+      "requires": {
+        "chalk": "^2.0.0",
+        "esutils": "^2.0.2",
+        "js-tokens": "^4.0.0"
+      }
+    },
+    "@types/estree": {
+      "version": "0.0.39",
+      "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.39.tgz",
+      "integrity": "sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw==",
+      "dev": true
+    },
+    "@types/node": {
+      "version": "11.11.0",
+      "resolved": "https://registry.npmjs.org/@types/node/-/node-11.11.0.tgz",
+      "integrity": "sha512-D5Rt+HXgEywr3RQJcGlZUCTCx1qVbCZpVk3/tOOA6spLNZdGm8BU+zRgdRYDoF1pO3RuXLxADzMrF903JlQXqg==",
+      "dev": true
+    },
     "accepts": {
       "version": "1.3.5",
       "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.5.tgz",
@@ -14,12 +46,27 @@
         "negotiator": "0.6.1"
       }
     },
+    "acorn": {
+      "version": "6.1.1",
+      "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.1.1.tgz",
+      "integrity": "sha512-jPTiwtOxaHNaAPg/dmrJ/beuzLRnXtB0kQPQ8JpotKJgTB6rX6c8mlf315941pyjBSaPg8NHXS9fhP4u17DpGA==",
+      "dev": true
+    },
     "after": {
       "version": "0.8.2",
       "resolved": "https://registry.npmjs.org/after/-/after-0.8.2.tgz",
       "integrity": "sha1-/ts5T58OAqqXaOcCvaI7UF+ufh8=",
       "dev": true
     },
+    "ansi-styles": {
+      "version": "3.2.1",
+      "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
+      "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
+      "dev": true,
+      "requires": {
+        "color-convert": "^1.9.0"
+      }
+    },
     "anymatch": {
       "version": "2.0.0",
       "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-2.0.0.tgz",
@@ -30,6 +77,15 @@
         "normalize-path": "^2.1.1"
       }
     },
+    "argparse": {
+      "version": "1.0.10",
+      "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz",
+      "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==",
+      "dev": true,
+      "requires": {
+        "sprintf-js": "~1.0.2"
+      }
+    },
     "arr-diff": {
       "version": "4.0.0",
       "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz",
@@ -48,12 +104,6 @@
       "integrity": "sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ=",
       "dev": true
     },
-    "array-slice": {
-      "version": "0.2.3",
-      "resolved": "https://registry.npmjs.org/array-slice/-/array-slice-0.2.3.tgz",
-      "integrity": "sha1-3Tz7gO15c6dRF82sabC5nshhhvU=",
-      "dev": true
-    },
     "array-unique": {
       "version": "0.3.2",
       "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz",
@@ -78,6 +128,15 @@
       "integrity": "sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=",
       "dev": true
     },
+    "async": {
+      "version": "2.6.2",
+      "resolved": "https://registry.npmjs.org/async/-/async-2.6.2.tgz",
+      "integrity": "sha512-H1qVYh1MYhEEFLsP97cVKqCGo7KfCyTt6uEWqsTBr9SO84oK9Uwbyd/yCW+6rKJLHksBNUVWZDAjfS+Ccx0Bbg==",
+      "dev": true,
+      "requires": {
+        "lodash": "^4.17.11"
+      }
+    },
     "async-each": {
       "version": "1.0.1",
       "resolved": "https://registry.npmjs.org/async-each/-/async-each-1.0.1.tgz",
@@ -185,9 +244,9 @@
       }
     },
     "binary-extensions": {
-      "version": "1.12.0",
-      "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-1.12.0.tgz",
-      "integrity": "sha512-DYWGk01lDcxeS/K9IHPGWfT8PsJmbXRtRd2Sx72Tnb8pcYZQFF1oSDb8hJtS1vhp212q1Rzi5dUf9+nq0o9UIg==",
+      "version": "1.13.0",
+      "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-1.13.0.tgz",
+      "integrity": "sha512-EgmjVLMn22z7eGGv3kcnHwSnJXmFHjISTY9E/S5lIcTD3Oxw05QTcBLNkJFzcb3cNueUdF/IN4U+d78V0zO8Hw==",
       "dev": true
     },
     "blob": {
@@ -246,17 +305,6 @@
         "snapdragon-node": "^2.0.1",
         "split-string": "^3.0.2",
         "to-regex": "^3.0.1"
-      },
-      "dependencies": {
-        "extend-shallow": {
-          "version": "2.0.1",
-          "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
-          "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=",
-          "dev": true,
-          "requires": {
-            "is-extendable": "^0.1.0"
-          }
-        }
       }
     },
     "browser-stdout": {
@@ -287,6 +335,12 @@
       "integrity": "sha1-+PeLdniYiO858gXNY39o5wISKyw=",
       "dev": true
     },
+    "buffer-from": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz",
+      "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==",
+      "dev": true
+    },
     "bytes": {
       "version": "3.0.0",
       "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz",
@@ -310,12 +364,36 @@
         "unset-value": "^1.0.0"
       }
     },
+    "caller-callsite": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/caller-callsite/-/caller-callsite-2.0.0.tgz",
+      "integrity": "sha1-hH4PzgoiN1CpoCfFSzNzGtMVQTQ=",
+      "dev": true,
+      "requires": {
+        "callsites": "^2.0.0"
+      }
+    },
+    "caller-path": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/caller-path/-/caller-path-2.0.0.tgz",
+      "integrity": "sha1-Ro+DBE42mrIBD6xfBs7uFbsssfQ=",
+      "dev": true,
+      "requires": {
+        "caller-callsite": "^2.0.0"
+      }
+    },
     "callsite": {
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/callsite/-/callsite-1.0.0.tgz",
       "integrity": "sha1-KAOY5dZkvXQDi28JBRU+borxvCA=",
       "dev": true
     },
+    "callsites": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/callsites/-/callsites-2.0.0.tgz",
+      "integrity": "sha1-BuuE8A7qQT2oav/vrL/7Ngk7PFA=",
+      "dev": true
+    },
     "chai": {
       "version": "4.2.0",
       "resolved": "https://registry.npmjs.org/chai/-/chai-4.2.0.tgz",
@@ -330,6 +408,17 @@
         "type-detect": "^4.0.5"
       }
     },
+    "chalk": {
+      "version": "2.4.2",
+      "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
+      "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
+      "dev": true,
+      "requires": {
+        "ansi-styles": "^3.2.1",
+        "escape-string-regexp": "^1.0.5",
+        "supports-color": "^5.3.0"
+      }
+    },
     "check-error": {
       "version": "1.0.2",
       "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.2.tgz",
@@ -337,30 +426,37 @@
       "dev": true
     },
     "chokidar": {
-      "version": "2.0.4",
-      "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.0.4.tgz",
-      "integrity": "sha512-z9n7yt9rOvIJrMhvDtDictKrkFHeihkNl6uWMmZlmL6tJtX9Cs+87oK+teBx+JIgzvbX3yZHT3eF8vpbDxHJXQ==",
+      "version": "2.1.2",
+      "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.1.2.tgz",
+      "integrity": "sha512-IwXUx0FXc5ibYmPC2XeEj5mpXoV66sR+t3jqu2NS2GYwCktt3KF1/Qqjws/NkegajBA4RbZ5+DDwlOiJsxDHEg==",
       "dev": true,
       "requires": {
         "anymatch": "^2.0.0",
-        "async-each": "^1.0.0",
-        "braces": "^2.3.0",
-        "fsevents": "^1.2.2",
+        "async-each": "^1.0.1",
+        "braces": "^2.3.2",
+        "fsevents": "^1.2.7",
         "glob-parent": "^3.1.0",
-        "inherits": "^2.0.1",
+        "inherits": "^2.0.3",
         "is-binary-path": "^1.0.0",
         "is-glob": "^4.0.0",
-        "lodash.debounce": "^4.0.8",
-        "normalize-path": "^2.1.1",
+        "normalize-path": "^3.0.0",
         "path-is-absolute": "^1.0.0",
-        "readdirp": "^2.0.0",
-        "upath": "^1.0.5"
+        "readdirp": "^2.2.1",
+        "upath": "^1.1.0"
+      },
+      "dependencies": {
+        "normalize-path": {
+          "version": "3.0.0",
+          "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
+          "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
+          "dev": true
+        }
       }
     },
-    "circular-json": {
-      "version": "0.5.9",
-      "resolved": "https://registry.npmjs.org/circular-json/-/circular-json-0.5.9.tgz",
-      "integrity": "sha512-4ivwqHpIFJZBuhN3g/pEcdbnGUywkBblloGbkglyloVjjR3uT6tieI89MVOfbP2tHX5sgb01FuLgAOzebNlJNQ==",
+    "ci-info": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz",
+      "integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==",
       "dev": true
     },
     "class-utils": {
@@ -373,17 +469,6 @@
         "define-property": "^0.2.5",
         "isobject": "^3.0.0",
         "static-extend": "^0.1.1"
-      },
-      "dependencies": {
-        "define-property": {
-          "version": "0.2.5",
-          "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz",
-          "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=",
-          "dev": true,
-          "requires": {
-            "is-descriptor": "^0.1.0"
-          }
-        }
       }
     },
     "collection-visit": {
@@ -396,21 +481,27 @@
         "object-visit": "^1.0.0"
       }
     },
+    "color-convert": {
+      "version": "1.9.3",
+      "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
+      "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
+      "dev": true,
+      "requires": {
+        "color-name": "1.1.3"
+      }
+    },
+    "color-name": {
+      "version": "1.1.3",
+      "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
+      "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=",
+      "dev": true
+    },
     "colors": {
       "version": "1.3.3",
       "resolved": "https://registry.npmjs.org/colors/-/colors-1.3.3.tgz",
       "integrity": "sha512-mmGt/1pZqYRjMxB1axhTo16/snVZ5krrKkcmMeVKxzECMMXoCgnvTPp10QgHfcbQZw8Dq2jMNG6je4JlWU0gWg==",
       "dev": true
     },
-    "combine-lists": {
-      "version": "1.0.1",
-      "resolved": "https://registry.npmjs.org/combine-lists/-/combine-lists-1.0.1.tgz",
-      "integrity": "sha1-RYwH4J4NkA/Ci3Cj/sLazR0st/Y=",
-      "dev": true,
-      "requires": {
-        "lodash": "^4.5.0"
-      }
-    },
     "commander": {
       "version": "2.15.1",
       "resolved": "https://registry.npmjs.org/commander/-/commander-2.15.1.tgz",
@@ -441,6 +532,12 @@
       "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=",
       "dev": true
     },
+    "conditional-type-checks": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/conditional-type-checks/-/conditional-type-checks-1.0.0.tgz",
+      "integrity": "sha512-4fvSNAF1LZs6TPiXNO1H6vCjUcSp/IQbhzb7Vr8jhFj0YBn9wagReuBvMGG406lheiFlyxgfcF23RYMZyGvbcQ==",
+      "dev": true
+    },
     "connect": {
       "version": "3.6.6",
       "resolved": "https://registry.npmjs.org/connect/-/connect-3.6.6.tgz",
@@ -472,9 +569,9 @@
       "dev": true
     },
     "core-js": {
-      "version": "2.6.0",
-      "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.0.tgz",
-      "integrity": "sha512-kLRC6ncVpuEW/1kwrOXYX6KQASCVtrh1gQr/UiaVgFlf9WE5Vp+lNe5+h3LuMr5PAucWnnEXwH0nQHRH/gpGtw==",
+      "version": "2.6.5",
+      "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.5.tgz",
+      "integrity": "sha512-klh/kDpwX8hryYL14M9w/xei6vrv6sE8gTHDG7/T/+SEovB/G4ejwcfE/CBzO6Edsu+OETZMZ3wcX/EjUkrl5A==",
       "dev": true
     },
     "core-util-is": {
@@ -483,6 +580,32 @@
       "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=",
       "dev": true
     },
+    "cosmiconfig": {
+      "version": "5.1.0",
+      "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-5.1.0.tgz",
+      "integrity": "sha512-kCNPvthka8gvLtzAxQXvWo4FxqRB+ftRZyPZNuab5ngvM9Y7yw7hbEysglptLgpkGX9nAOKTBVkHUAe8xtYR6Q==",
+      "dev": true,
+      "requires": {
+        "import-fresh": "^2.0.0",
+        "is-directory": "^0.3.1",
+        "js-yaml": "^3.9.0",
+        "lodash.get": "^4.4.2",
+        "parse-json": "^4.0.0"
+      }
+    },
+    "cross-spawn": {
+      "version": "6.0.5",
+      "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz",
+      "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==",
+      "dev": true,
+      "requires": {
+        "nice-try": "^1.0.4",
+        "path-key": "^2.0.1",
+        "semver": "^5.5.0",
+        "shebang-command": "^1.2.0",
+        "which": "^1.2.9"
+      }
+    },
     "custom-event": {
       "version": "1.0.1",
       "resolved": "https://registry.npmjs.org/custom-event/-/custom-event-1.0.1.tgz",
@@ -490,9 +613,9 @@
       "dev": true
     },
     "date-format": {
-      "version": "1.2.0",
-      "resolved": "https://registry.npmjs.org/date-format/-/date-format-1.2.0.tgz",
-      "integrity": "sha1-YV6CjiM90aubua4JUODOzPpuytg=",
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/date-format/-/date-format-2.0.0.tgz",
+      "integrity": "sha512-M6UqVvZVgFYqZL1SfHsRGIQSz3ZL+qgbsV5Lp1Vj61LZVYuEwcMXYay7DRDtYs2HQQBK5hQtQ0fD9aEJ89V0LA==",
       "dev": true
     },
     "debug": {
@@ -520,44 +643,12 @@
       }
     },
     "define-property": {
-      "version": "2.0.2",
-      "resolved": "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz",
-      "integrity": "sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==",
+      "version": "0.2.5",
+      "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz",
+      "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=",
       "dev": true,
       "requires": {
-        "is-descriptor": "^1.0.2",
-        "isobject": "^3.0.1"
-      },
-      "dependencies": {
-        "is-accessor-descriptor": {
-          "version": "1.0.0",
-          "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz",
-          "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==",
-          "dev": true,
-          "requires": {
-            "kind-of": "^6.0.0"
-          }
-        },
-        "is-data-descriptor": {
-          "version": "1.0.0",
-          "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz",
-          "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==",
-          "dev": true,
-          "requires": {
-            "kind-of": "^6.0.0"
-          }
-        },
-        "is-descriptor": {
-          "version": "1.0.2",
-          "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz",
-          "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==",
-          "dev": true,
-          "requires": {
-            "is-accessor-descriptor": "^1.0.0",
-            "is-data-descriptor": "^1.0.0",
-            "kind-of": "^6.0.2"
-          }
-        }
+        "is-descriptor": "^0.1.0"
       }
     },
     "depd": {
@@ -596,6 +687,15 @@
       "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=",
       "dev": true
     },
+    "end-of-stream": {
+      "version": "1.4.1",
+      "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.1.tgz",
+      "integrity": "sha512-1MkrZNvWTKCaigbn+W15elq2BB/L22nqrSY5DKlo3X6+vclJm8Bb5djXJBmEX6fS3+zCh/F4VBK5Z2KxJt4s2Q==",
+      "dev": true,
+      "requires": {
+        "once": "^1.4.0"
+      }
+    },
     "engine.io": {
       "version": "3.2.1",
       "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-3.2.1.tgz",
@@ -670,6 +770,15 @@
       "integrity": "sha1-6WQhkyWiHQX0RGai9obtbOX13R0=",
       "dev": true
     },
+    "error-ex": {
+      "version": "1.3.2",
+      "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz",
+      "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==",
+      "dev": true,
+      "requires": {
+        "is-arrayish": "^0.2.1"
+      }
+    },
     "escape-html": {
       "version": "1.0.3",
       "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
@@ -682,38 +791,43 @@
       "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=",
       "dev": true
     },
+    "esprima": {
+      "version": "4.0.1",
+      "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz",
+      "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==",
+      "dev": true
+    },
+    "estree-walker": {
+      "version": "0.5.2",
+      "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-0.5.2.tgz",
+      "integrity": "sha512-XpCnW/AE10ws/kDAs37cngSkvgIR8aN3G0MS85m7dUpuK2EREo9VJ00uvw6Dg/hXEpfsE1I1TvJOJr+Z+TL+ig==",
+      "dev": true
+    },
+    "esutils": {
+      "version": "2.0.2",
+      "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.2.tgz",
+      "integrity": "sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs=",
+      "dev": true
+    },
     "eventemitter3": {
       "version": "3.1.0",
       "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-3.1.0.tgz",
       "integrity": "sha512-ivIvhpq/Y0uSjcHDcOIccjmYjGLcP09MFGE7ysAwkAvkXfpZlC985pH2/ui64DKazbTW/4kN3yqozUxlXzI6cA==",
       "dev": true
     },
-    "expand-braces": {
-      "version": "0.1.2",
-      "resolved": "https://registry.npmjs.org/expand-braces/-/expand-braces-0.1.2.tgz",
-      "integrity": "sha1-SIsdHSRRyz06axks/AMPRMWFX+o=",
+    "execa": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz",
+      "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==",
       "dev": true,
       "requires": {
-        "array-slice": "^0.2.3",
-        "array-unique": "^0.2.1",
-        "braces": "^0.1.2"
-      },
-      "dependencies": {
-        "array-unique": {
-          "version": "0.2.1",
-          "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.2.1.tgz",
-          "integrity": "sha1-odl8yvy8JiXMcPrc6zalDFiwGlM=",
-          "dev": true
-        },
-        "braces": {
-          "version": "0.1.5",
-          "resolved": "https://registry.npmjs.org/braces/-/braces-0.1.5.tgz",
-          "integrity": "sha1-wIVxEIUpHYt1/ddOqw+FlygHEeY=",
-          "dev": true,
-          "requires": {
-            "expand-range": "^0.1.0"
-          }
-        }
+        "cross-spawn": "^6.0.0",
+        "get-stream": "^4.0.0",
+        "is-stream": "^1.1.0",
+        "npm-run-path": "^2.0.0",
+        "p-finally": "^1.0.0",
+        "signal-exit": "^3.0.0",
+        "strip-eof": "^1.0.0"
       }
     },
     "expand-brackets": {
@@ -729,50 +843,6 @@
         "regex-not": "^1.0.0",
         "snapdragon": "^0.8.1",
         "to-regex": "^3.0.1"
-      },
-      "dependencies": {
-        "define-property": {
-          "version": "0.2.5",
-          "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz",
-          "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=",
-          "dev": true,
-          "requires": {
-            "is-descriptor": "^0.1.0"
-          }
-        },
-        "extend-shallow": {
-          "version": "2.0.1",
-          "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
-          "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=",
-          "dev": true,
-          "requires": {
-            "is-extendable": "^0.1.0"
-          }
-        }
-      }
-    },
-    "expand-range": {
-      "version": "0.1.1",
-      "resolved": "http://registry.npmjs.org/expand-range/-/expand-range-0.1.1.tgz",
-      "integrity": "sha1-TLjtoJk8pW+k9B/ELzy7TMrf8EQ=",
-      "dev": true,
-      "requires": {
-        "is-number": "^0.1.1",
-        "repeat-string": "^0.2.2"
-      },
-      "dependencies": {
-        "is-number": {
-          "version": "0.1.1",
-          "resolved": "https://registry.npmjs.org/is-number/-/is-number-0.1.1.tgz",
-          "integrity": "sha1-aaevEWlj1HIG7JvZtIoUIW8eOAY=",
-          "dev": true
-        },
-        "repeat-string": {
-          "version": "0.2.2",
-          "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-0.2.2.tgz",
-          "integrity": "sha1-x6jTI2BoNiBZp+RlH8aITosftK4=",
-          "dev": true
-        }
       }
     },
     "extend": {
@@ -782,24 +852,12 @@
       "dev": true
     },
     "extend-shallow": {
-      "version": "3.0.2",
-      "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz",
-      "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=",
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
+      "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=",
       "dev": true,
       "requires": {
-        "assign-symbols": "^1.0.0",
-        "is-extendable": "^1.0.1"
-      },
-      "dependencies": {
-        "is-extendable": {
-          "version": "1.0.1",
-          "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz",
-          "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==",
-          "dev": true,
-          "requires": {
-            "is-plain-object": "^2.0.4"
-          }
-        }
+        "is-extendable": "^0.1.0"
       }
     },
     "extglob": {
@@ -827,15 +885,6 @@
             "is-descriptor": "^1.0.0"
           }
         },
-        "extend-shallow": {
-          "version": "2.0.1",
-          "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
-          "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=",
-          "dev": true,
-          "requires": {
-            "is-extendable": "^0.1.0"
-          }
-        },
         "is-accessor-descriptor": {
           "version": "1.0.0",
           "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz",
@@ -867,6 +916,12 @@
         }
       }
     },
+    "filename-regex": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/filename-regex/-/filename-regex-2.0.1.tgz",
+      "integrity": "sha1-wcS5vuPglyXdsQa3XB4wH+LxiyY=",
+      "dev": true
+    },
     "fill-range": {
       "version": "4.0.0",
       "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz",
@@ -877,17 +932,6 @@
         "is-number": "^3.0.0",
         "repeat-string": "^1.6.1",
         "to-regex-range": "^2.1.0"
-      },
-      "dependencies": {
-        "extend-shallow": {
-          "version": "2.0.1",
-          "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
-          "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=",
-          "dev": true,
-          "requires": {
-            "is-extendable": "^0.1.0"
-          }
-        }
       }
     },
     "finalhandler": {
@@ -913,6 +957,15 @@
         }
       }
     },
+    "find-up": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz",
+      "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==",
+      "dev": true,
+      "requires": {
+        "locate-path": "^3.0.0"
+      }
+    },
     "flatted": {
       "version": "2.0.0",
       "resolved": "https://registry.npmjs.org/flatted/-/flatted-2.0.0.tgz",
@@ -920,22 +973,28 @@
       "dev": true
     },
     "follow-redirects": {
-      "version": "1.5.10",
-      "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.5.10.tgz",
-      "integrity": "sha512-0V5l4Cizzvqt5D44aTXbFZz+FtyXV1vrDN6qrelxtfYQKW0KO0W2T/hkE8xvGa/540LkZlkaUjO4ailYTFtHVQ==",
+      "version": "1.7.0",
+      "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.7.0.tgz",
+      "integrity": "sha512-m/pZQy4Gj287eNy94nivy5wchN3Kp+Q5WgUPNy5lJSZ3sgkVKSYV/ZChMAQVIgx1SqfZ2zBZtPA2YlXIWxxJOQ==",
       "dev": true,
       "requires": {
-        "debug": "=3.1.0"
+        "debug": "^3.2.6"
       },
       "dependencies": {
         "debug": {
-          "version": "3.1.0",
-          "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz",
-          "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==",
+          "version": "3.2.6",
+          "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz",
+          "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==",
           "dev": true,
           "requires": {
-            "ms": "2.0.0"
+            "ms": "^2.1.1"
           }
+        },
+        "ms": {
+          "version": "2.1.1",
+          "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz",
+          "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==",
+          "dev": true
         }
       }
     },
@@ -945,6 +1004,15 @@
       "integrity": "sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=",
       "dev": true
     },
+    "for-own": {
+      "version": "0.1.5",
+      "resolved": "https://registry.npmjs.org/for-own/-/for-own-0.1.5.tgz",
+      "integrity": "sha1-UmXGgaTylNq78XyVCbZ2OqhFEM4=",
+      "dev": true,
+      "requires": {
+        "for-in": "^1.0.1"
+      }
+    },
     "fragment-cache": {
       "version": "0.2.1",
       "resolved": "https://registry.npmjs.org/fragment-cache/-/fragment-cache-0.2.1.tgz",
@@ -963,6 +1031,17 @@
         "null-check": "^1.0.0"
       }
     },
+    "fs-extra": {
+      "version": "7.0.1",
+      "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-7.0.1.tgz",
+      "integrity": "sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==",
+      "dev": true,
+      "requires": {
+        "graceful-fs": "^4.1.2",
+        "jsonfile": "^4.0.0",
+        "universalify": "^0.1.0"
+      }
+    },
     "fs.realpath": {
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
@@ -970,9 +1049,9 @@
       "dev": true
     },
     "fsevents": {
-      "version": "1.2.4",
-      "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.4.tgz",
-      "integrity": "sha512-z8H8/diyk76B7q5wg+Ud0+CqzcAF3mBBI/bA5ne5zrRUUIvNkJY//D3BqyH571KuAC4Nr7Rw7CjWX4r0y9DvNg==",
+      "version": "1.2.7",
+      "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.7.tgz",
+      "integrity": "sha512-Pxm6sI2MeBD7RdD12RYsqaP0nMiwx8eZBXCa6z2L+mRHm2DYrOYwihmhjpkdjUHwQhslWQjRpEgNq4XvBmaAuw==",
       "dev": true,
       "optional": true,
       "requires": {
@@ -999,7 +1078,7 @@
           "optional": true
         },
         "are-we-there-yet": {
-          "version": "1.1.4",
+          "version": "1.1.5",
           "bundled": true,
           "dev": true,
           "optional": true,
@@ -1025,7 +1104,7 @@
           }
         },
         "chownr": {
-          "version": "1.0.1",
+          "version": "1.1.1",
           "bundled": true,
           "dev": true,
           "optional": true
@@ -1064,7 +1143,7 @@
           }
         },
         "deep-extend": {
-          "version": "0.5.1",
+          "version": "0.6.0",
           "bundled": true,
           "dev": true,
           "optional": true
@@ -1113,7 +1192,7 @@
           }
         },
         "glob": {
-          "version": "7.1.2",
+          "version": "7.1.3",
           "bundled": true,
           "dev": true,
           "optional": true,
@@ -1133,12 +1212,12 @@
           "optional": true
         },
         "iconv-lite": {
-          "version": "0.4.21",
+          "version": "0.4.24",
           "bundled": true,
           "dev": true,
           "optional": true,
           "requires": {
-            "safer-buffer": "^2.1.0"
+            "safer-buffer": ">= 2.1.2 < 3"
           }
         },
         "ignore-walk": {
@@ -1203,17 +1282,17 @@
           "optional": true
         },
         "minipass": {
-          "version": "2.2.4",
+          "version": "2.3.5",
           "bundled": true,
           "dev": true,
           "optional": true,
           "requires": {
-            "safe-buffer": "^5.1.1",
+            "safe-buffer": "^5.1.2",
             "yallist": "^3.0.0"
           }
         },
         "minizlib": {
-          "version": "1.1.0",
+          "version": "1.2.1",
           "bundled": true,
           "dev": true,
           "optional": true,
@@ -1237,7 +1316,7 @@
           "optional": true
         },
         "needle": {
-          "version": "2.2.0",
+          "version": "2.2.4",
           "bundled": true,
           "dev": true,
           "optional": true,
@@ -1248,18 +1327,18 @@
           }
         },
         "node-pre-gyp": {
-          "version": "0.10.0",
+          "version": "0.10.3",
           "bundled": true,
           "dev": true,
           "optional": true,
           "requires": {
             "detect-libc": "^1.0.2",
             "mkdirp": "^0.5.1",
-            "needle": "^2.2.0",
+            "needle": "^2.2.1",
             "nopt": "^4.0.1",
             "npm-packlist": "^1.1.6",
             "npmlog": "^4.0.2",
-            "rc": "^1.1.7",
+            "rc": "^1.2.7",
             "rimraf": "^2.6.1",
             "semver": "^5.3.0",
             "tar": "^4"
@@ -1276,13 +1355,13 @@
           }
         },
         "npm-bundled": {
-          "version": "1.0.3",
+          "version": "1.0.5",
           "bundled": true,
           "dev": true,
           "optional": true
         },
         "npm-packlist": {
-          "version": "1.1.10",
+          "version": "1.2.0",
           "bundled": true,
           "dev": true,
           "optional": true,
@@ -1359,12 +1438,12 @@
           "optional": true
         },
         "rc": {
-          "version": "1.2.7",
+          "version": "1.2.8",
           "bundled": true,
           "dev": true,
           "optional": true,
           "requires": {
-            "deep-extend": "^0.5.1",
+            "deep-extend": "^0.6.0",
             "ini": "~1.3.0",
             "minimist": "^1.2.0",
             "strip-json-comments": "~2.0.1"
@@ -1394,16 +1473,16 @@
           }
         },
         "rimraf": {
-          "version": "2.6.2",
+          "version": "2.6.3",
           "bundled": true,
           "dev": true,
           "optional": true,
           "requires": {
-            "glob": "^7.0.5"
+            "glob": "^7.1.3"
           }
         },
         "safe-buffer": {
-          "version": "5.1.1",
+          "version": "5.1.2",
           "bundled": true,
           "dev": true,
           "optional": true
@@ -1421,7 +1500,7 @@
           "optional": true
         },
         "semver": {
-          "version": "5.5.0",
+          "version": "5.6.0",
           "bundled": true,
           "dev": true,
           "optional": true
@@ -1474,17 +1553,17 @@
           "optional": true
         },
         "tar": {
-          "version": "4.4.1",
+          "version": "4.4.8",
           "bundled": true,
           "dev": true,
           "optional": true,
           "requires": {
-            "chownr": "^1.0.1",
+            "chownr": "^1.1.1",
             "fs-minipass": "^1.2.5",
-            "minipass": "^2.2.4",
-            "minizlib": "^1.1.0",
+            "minipass": "^2.3.4",
+            "minizlib": "^1.1.1",
             "mkdirp": "^0.5.0",
-            "safe-buffer": "^5.1.1",
+            "safe-buffer": "^5.1.2",
             "yallist": "^3.0.2"
           }
         },
@@ -1495,12 +1574,12 @@
           "optional": true
         },
         "wide-align": {
-          "version": "1.1.2",
+          "version": "1.1.3",
           "bundled": true,
           "dev": true,
           "optional": true,
           "requires": {
-            "string-width": "^1.0.2"
+            "string-width": "^1.0.2 || 2"
           }
         },
         "wrappy": {
@@ -1510,7 +1589,7 @@
           "optional": true
         },
         "yallist": {
-          "version": "3.0.2",
+          "version": "3.0.3",
           "bundled": true,
           "dev": true,
           "optional": true
@@ -1523,6 +1602,21 @@
       "integrity": "sha1-6td0q+5y4gQJQzoGY2YCPdaIekE=",
       "dev": true
     },
+    "get-stdin": {
+      "version": "6.0.0",
+      "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-6.0.0.tgz",
+      "integrity": "sha512-jp4tHawyV7+fkkSKyvjuLZswblUtz+SQKzSWnBbii16BuZksJlU1wuBYXY75r+duh/llF1ur6oNwi+2ZzjKZ7g==",
+      "dev": true
+    },
+    "get-stream": {
+      "version": "4.1.0",
+      "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz",
+      "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==",
+      "dev": true,
+      "requires": {
+        "pump": "^3.0.0"
+      }
+    },
     "get-value": {
       "version": "2.0.6",
       "resolved": "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz",
@@ -1543,6 +1637,42 @@
         "path-is-absolute": "^1.0.0"
       }
     },
+    "glob-base": {
+      "version": "0.3.0",
+      "resolved": "https://registry.npmjs.org/glob-base/-/glob-base-0.3.0.tgz",
+      "integrity": "sha1-27Fk9iIbHAscz4Kuoyi0l98Oo8Q=",
+      "dev": true,
+      "requires": {
+        "glob-parent": "^2.0.0",
+        "is-glob": "^2.0.0"
+      },
+      "dependencies": {
+        "glob-parent": {
+          "version": "2.0.0",
+          "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-2.0.0.tgz",
+          "integrity": "sha1-gTg9ctsFT8zPUzbaqQLxgvbtuyg=",
+          "dev": true,
+          "requires": {
+            "is-glob": "^2.0.0"
+          }
+        },
+        "is-extglob": {
+          "version": "1.0.0",
+          "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-1.0.0.tgz",
+          "integrity": "sha1-rEaBd8SUNAWgkvyPKXYMb/xiBsA=",
+          "dev": true
+        },
+        "is-glob": {
+          "version": "2.0.1",
+          "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-2.0.1.tgz",
+          "integrity": "sha1-0Jb5JqPe1WAPP9/ZEZjLCIjC2GM=",
+          "dev": true,
+          "requires": {
+            "is-extglob": "^1.0.0"
+          }
+        }
+      }
+    },
     "glob-parent": {
       "version": "3.1.0",
       "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz",
@@ -1643,9 +1773,15 @@
       "integrity": "sha1-k0EP0hsAlzUVH4howvJx80J+I/0=",
       "dev": true
     },
+    "hosted-git-info": {
+      "version": "2.7.1",
+      "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.7.1.tgz",
+      "integrity": "sha512-7T/BxH19zbcCTa8XkMlbK5lTo1WtgkFi3GvdWEyNuc4Vex7/9Dqbnpsf4JMydcfj9HCg4zUWFTL3Za6lapg5/w==",
+      "dev": true
+    },
     "http-errors": {
       "version": "1.6.3",
-      "resolved": "http://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz",
+      "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz",
       "integrity": "sha1-i1VoC7S+KDoLW/TqLjhYC+HZMg0=",
       "dev": true,
       "requires": {
@@ -1666,6 +1802,24 @@
         "requires-port": "^1.0.0"
       }
     },
+    "husky": {
+      "version": "1.3.1",
+      "resolved": "https://registry.npmjs.org/husky/-/husky-1.3.1.tgz",
+      "integrity": "sha512-86U6sVVVf4b5NYSZ0yvv88dRgBSSXXmHaiq5pP4KDj5JVzdwKgBjEtUPOm8hcoytezFwbU+7gotXNhpHdystlg==",
+      "dev": true,
+      "requires": {
+        "cosmiconfig": "^5.0.7",
+        "execa": "^1.0.0",
+        "find-up": "^3.0.0",
+        "get-stdin": "^6.0.0",
+        "is-ci": "^2.0.0",
+        "pkg-dir": "^3.0.0",
+        "please-upgrade-node": "^3.1.1",
+        "read-pkg": "^4.0.1",
+        "run-node": "^1.0.0",
+        "slash": "^2.0.0"
+      }
+    },
     "iconv-lite": {
       "version": "0.4.23",
       "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.23.tgz",
@@ -1675,6 +1829,16 @@
         "safer-buffer": ">= 2.1.2 < 3"
       }
     },
+    "import-fresh": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-2.0.0.tgz",
+      "integrity": "sha1-2BNVwVYS04bGH53dOSLUMEgipUY=",
+      "dev": true,
+      "requires": {
+        "caller-path": "^2.0.0",
+        "resolve-from": "^3.0.0"
+      }
+    },
     "indexof": {
       "version": "0.0.1",
       "resolved": "https://registry.npmjs.org/indexof/-/indexof-0.0.1.tgz",
@@ -1699,7 +1863,7 @@
     },
     "is-accessor-descriptor": {
       "version": "0.1.6",
-      "resolved": "http://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz",
+      "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz",
       "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=",
       "dev": true,
       "requires": {
@@ -1717,6 +1881,12 @@
         }
       }
     },
+    "is-arrayish": {
+      "version": "0.2.1",
+      "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz",
+      "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=",
+      "dev": true
+    },
     "is-binary-path": {
       "version": "1.0.1",
       "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-1.0.1.tgz",
@@ -1732,9 +1902,18 @@
       "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==",
       "dev": true
     },
+    "is-ci": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-2.0.0.tgz",
+      "integrity": "sha512-YfJT7rkpQB0updsdHLGWrvhBJfcfzNNawYDNIyQXJz0IViGf75O8EBPKSdvw2rF+LGCsX4FZ8tcr3b19LcZq4w==",
+      "dev": true,
+      "requires": {
+        "ci-info": "^2.0.0"
+      }
+    },
     "is-data-descriptor": {
       "version": "0.1.4",
-      "resolved": "http://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz",
+      "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz",
       "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=",
       "dev": true,
       "requires": {
@@ -1771,6 +1950,27 @@
         }
       }
     },
+    "is-directory": {
+      "version": "0.3.1",
+      "resolved": "https://registry.npmjs.org/is-directory/-/is-directory-0.3.1.tgz",
+      "integrity": "sha1-YTObbyR1/Hcv2cnYP1yFddwVSuE=",
+      "dev": true
+    },
+    "is-dotfile": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmjs.org/is-dotfile/-/is-dotfile-1.0.3.tgz",
+      "integrity": "sha1-pqLzL/0t+wT1yiXs0Pa4PPeYoeE=",
+      "dev": true
+    },
+    "is-equal-shallow": {
+      "version": "0.1.3",
+      "resolved": "https://registry.npmjs.org/is-equal-shallow/-/is-equal-shallow-0.1.3.tgz",
+      "integrity": "sha1-IjgJj8Ih3gvPpdnqxMRdY4qhxTQ=",
+      "dev": true,
+      "requires": {
+        "is-primitive": "^2.0.0"
+      }
+    },
     "is-extendable": {
       "version": "0.1.1",
       "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz",
@@ -1821,6 +2021,24 @@
         "isobject": "^3.0.1"
       }
     },
+    "is-posix-bracket": {
+      "version": "0.1.1",
+      "resolved": "https://registry.npmjs.org/is-posix-bracket/-/is-posix-bracket-0.1.1.tgz",
+      "integrity": "sha1-MzTceXdDaOkvAW5vvAqI9c1ua8Q=",
+      "dev": true
+    },
+    "is-primitive": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/is-primitive/-/is-primitive-2.0.0.tgz",
+      "integrity": "sha1-IHurkWOEmcB7Kt8kCkGochADRXU=",
+      "dev": true
+    },
+    "is-stream": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz",
+      "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=",
+      "dev": true
+    },
     "is-windows": {
       "version": "1.0.2",
       "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz",
@@ -1854,29 +2072,81 @@
       "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=",
       "dev": true
     },
+    "jest-worker": {
+      "version": "24.3.1",
+      "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-24.3.1.tgz",
+      "integrity": "sha512-ZCoAe/iGLzTJvWHrO8fyx3bmEQhpL16SILJmWHKe8joHhyF3z00psF1sCRT54DoHw5GJG0ZpUtGy+ylvwA4haA==",
+      "dev": true,
+      "requires": {
+        "@types/node": "*",
+        "merge-stream": "^1.0.1",
+        "supports-color": "^6.1.0"
+      },
+      "dependencies": {
+        "supports-color": {
+          "version": "6.1.0",
+          "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz",
+          "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==",
+          "dev": true,
+          "requires": {
+            "has-flag": "^3.0.0"
+          }
+        }
+      }
+    },
+    "js-tokens": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
+      "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
+      "dev": true
+    },
+    "js-yaml": {
+      "version": "3.12.2",
+      "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.12.2.tgz",
+      "integrity": "sha512-QHn/Lh/7HhZ/Twc7vJYQTkjuCa0kaCcDcjK5Zlk2rvnUpy7DxMJ23+Jc2dcyvltwQVg1nygAVlB2oRDFHoRS5Q==",
+      "dev": true,
+      "requires": {
+        "argparse": "^1.0.7",
+        "esprima": "^4.0.0"
+      }
+    },
+    "json-parse-better-errors": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz",
+      "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==",
+      "dev": true
+    },
+    "jsonfile": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz",
+      "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=",
+      "dev": true,
+      "requires": {
+        "graceful-fs": "^4.1.6"
+      }
+    },
     "karma": {
-      "version": "3.1.4",
-      "resolved": "https://registry.npmjs.org/karma/-/karma-3.1.4.tgz",
-      "integrity": "sha512-31Vo8Qr5glN+dZEVIpnPCxEGleqE0EY6CtC2X9TagRV3rRQ3SNrvfhddICkJgUK3AgqpeKSZau03QumTGhGoSw==",
+      "version": "4.0.1",
+      "resolved": "https://registry.npmjs.org/karma/-/karma-4.0.1.tgz",
+      "integrity": "sha512-ind+4s03BqIXas7ZmraV3/kc5+mnqwCd+VDX1FndS6jxbt03kQKX2vXrWxNLuCjVYmhMwOZosAEKMM0a2q7w7A==",
       "dev": true,
       "requires": {
         "bluebird": "^3.3.0",
         "body-parser": "^1.16.1",
+        "braces": "^2.3.2",
         "chokidar": "^2.0.3",
         "colors": "^1.1.0",
-        "combine-lists": "^1.0.0",
         "connect": "^3.6.0",
         "core-js": "^2.2.0",
         "di": "^0.0.1",
         "dom-serialize": "^2.2.0",
-        "expand-braces": "^0.1.1",
         "flatted": "^2.0.0",
         "glob": "^7.1.1",
         "graceful-fs": "^4.1.2",
         "http-proxy": "^1.13.0",
         "isbinaryfile": "^3.0.0",
-        "lodash": "^4.17.5",
-        "log4js": "^3.0.0",
+        "lodash": "^4.17.11",
+        "log4js": "^4.0.0",
         "mime": "^2.3.1",
         "minimatch": "^3.0.2",
         "optimist": "^0.6.1",
@@ -1906,6 +2176,15 @@
         "which": "^1.2.1"
       }
     },
+    "karma-detect-browsers": {
+      "version": "2.3.3",
+      "resolved": "https://registry.npmjs.org/karma-detect-browsers/-/karma-detect-browsers-2.3.3.tgz",
+      "integrity": "sha512-ltFVyA3ijThv9l9TQ+TKnccoMk6YAWn8OMaccL+n8pO2LGwMOcy6tUWy3Mnv9If29jqvVHDCWntj7wBQpPtv7Q==",
+      "dev": true,
+      "requires": {
+        "which": "^1.2.4"
+      }
+    },
     "karma-firefox-launcher": {
       "version": "1.1.0",
       "resolved": "https://registry.npmjs.org/karma-firefox-launcher/-/karma-firefox-launcher-1.1.0.tgz",
@@ -1947,29 +2226,39 @@
       "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==",
       "dev": true
     },
+    "locate-path": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz",
+      "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==",
+      "dev": true,
+      "requires": {
+        "p-locate": "^3.0.0",
+        "path-exists": "^3.0.0"
+      }
+    },
     "lodash": {
       "version": "4.17.11",
       "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz",
       "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==",
       "dev": true
     },
-    "lodash.debounce": {
-      "version": "4.0.8",
-      "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz",
-      "integrity": "sha1-gteb/zCmfEAF/9XiUVMArZyk168=",
+    "lodash.get": {
+      "version": "4.4.2",
+      "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz",
+      "integrity": "sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk=",
       "dev": true
     },
     "log4js": {
-      "version": "3.0.6",
-      "resolved": "https://registry.npmjs.org/log4js/-/log4js-3.0.6.tgz",
-      "integrity": "sha512-ezXZk6oPJCWL483zj64pNkMuY/NcRX5MPiB0zE6tjZM137aeusrOnW1ecxgF9cmwMWkBMhjteQxBPoZBh9FDxQ==",
+      "version": "4.0.2",
+      "resolved": "https://registry.npmjs.org/log4js/-/log4js-4.0.2.tgz",
+      "integrity": "sha512-KE7HjiieVDPPdveA3bJZSuu0n8chMkFl8mIoisBFxwEJ9FmXe4YzNuiqSwYUiR1K8q8/5/8Yd6AClENY1RA9ww==",
       "dev": true,
       "requires": {
-        "circular-json": "^0.5.5",
-        "date-format": "^1.2.0",
+        "date-format": "^2.0.0",
         "debug": "^3.1.0",
+        "flatted": "^2.0.0",
         "rfdc": "^1.1.2",
-        "streamroller": "0.7.0"
+        "streamroller": "^1.0.1"
       },
       "dependencies": {
         "debug": {
@@ -2014,12 +2303,27 @@
         "object-visit": "^1.0.0"
       }
     },
+    "math-random": {
+      "version": "1.0.4",
+      "resolved": "https://registry.npmjs.org/math-random/-/math-random-1.0.4.tgz",
+      "integrity": "sha512-rUxjysqif/BZQH2yhd5Aaq7vXMSx9NdEsQcyA07uEzIvxgI7zIr33gGsh+RU0/XjmQpCW7RsVof1vlkvQVCK5A==",
+      "dev": true
+    },
     "media-typer": {
       "version": "0.3.0",
-      "resolved": "http://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
+      "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
       "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=",
       "dev": true
     },
+    "merge-stream": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-1.0.1.tgz",
+      "integrity": "sha1-QEEgLVCKNCugAXQAjfDCUbjBNeE=",
+      "dev": true,
+      "requires": {
+        "readable-stream": "^2.0.1"
+      }
+    },
     "micromatch": {
       "version": "3.1.10",
       "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz",
@@ -2039,6 +2343,66 @@
         "regex-not": "^1.0.0",
         "snapdragon": "^0.8.1",
         "to-regex": "^3.0.2"
+      },
+      "dependencies": {
+        "define-property": {
+          "version": "2.0.2",
+          "resolved": "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz",
+          "integrity": "sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==",
+          "dev": true,
+          "requires": {
+            "is-descriptor": "^1.0.2",
+            "isobject": "^3.0.1"
+          }
+        },
+        "extend-shallow": {
+          "version": "3.0.2",
+          "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz",
+          "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=",
+          "dev": true,
+          "requires": {
+            "assign-symbols": "^1.0.0",
+            "is-extendable": "^1.0.1"
+          }
+        },
+        "is-accessor-descriptor": {
+          "version": "1.0.0",
+          "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz",
+          "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==",
+          "dev": true,
+          "requires": {
+            "kind-of": "^6.0.0"
+          }
+        },
+        "is-data-descriptor": {
+          "version": "1.0.0",
+          "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz",
+          "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==",
+          "dev": true,
+          "requires": {
+            "kind-of": "^6.0.0"
+          }
+        },
+        "is-descriptor": {
+          "version": "1.0.2",
+          "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz",
+          "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==",
+          "dev": true,
+          "requires": {
+            "is-accessor-descriptor": "^1.0.0",
+            "is-data-descriptor": "^1.0.0",
+            "kind-of": "^6.0.2"
+          }
+        },
+        "is-extendable": {
+          "version": "1.0.1",
+          "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz",
+          "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==",
+          "dev": true,
+          "requires": {
+            "is-plain-object": "^2.0.4"
+          }
+        }
       }
     },
     "mime": {
@@ -2048,18 +2412,18 @@
       "dev": true
     },
     "mime-db": {
-      "version": "1.37.0",
-      "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.37.0.tgz",
-      "integrity": "sha512-R3C4db6bgQhlIhPU48fUtdVmKnflq+hRdad7IyKhtFj06VPNVdk2RhiYL3UjQIlso8L+YxAtFkobT0VK+S/ybg==",
+      "version": "1.38.0",
+      "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.38.0.tgz",
+      "integrity": "sha512-bqVioMFFzc2awcdJZIzR3HjZFX20QhilVS7hytkKrv7xFAn8bM1gzc/FOX2awLISvWe0PV8ptFKcon+wZ5qYkg==",
       "dev": true
     },
     "mime-types": {
-      "version": "2.1.21",
-      "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.21.tgz",
-      "integrity": "sha512-3iL6DbwpyLzjR3xHSFNFeb9Nz/M8WDkX33t1GFQnFOllWk8pOrh/LSrB5OXlnlW5P9LH73X6loW/eogc+F5lJg==",
+      "version": "2.1.22",
+      "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.22.tgz",
+      "integrity": "sha512-aGl6TZGnhm/li6F7yx82bJiBZwgiEa4Hf6CNr8YO+r5UHr53tSTYZb102zyU50DOWWKeOv0uQLRL0/9EiKWCog==",
       "dev": true,
       "requires": {
-        "mime-db": "~1.37.0"
+        "mime-db": "~1.38.0"
       }
     },
     "minimatch": {
@@ -2159,9 +2523,9 @@
       "dev": true
     },
     "nan": {
-      "version": "2.12.0",
-      "resolved": "https://registry.npmjs.org/nan/-/nan-2.12.0.tgz",
-      "integrity": "sha512-zT5nC0JhbljmyEf+Z456nvm7iO7XgRV2hYxoBtPpnyp+0Q4aCoP6uWNn76v/I6k2kCYNLWqWbwBWQcjsNI/bjw==",
+      "version": "2.12.1",
+      "resolved": "https://registry.npmjs.org/nan/-/nan-2.12.1.tgz",
+      "integrity": "sha512-JY7V6lRkStKcKTvHO5NVSQRv+RV+FIL5pvDoLiAtSL9pKlC5x9PKQcZDsq7m4FO4d57mkhC6Z+QhAh3Jdk5JFw==",
       "dev": true,
       "optional": true
     },
@@ -2182,6 +2546,66 @@
         "regex-not": "^1.0.0",
         "snapdragon": "^0.8.1",
         "to-regex": "^3.0.1"
+      },
+      "dependencies": {
+        "define-property": {
+          "version": "2.0.2",
+          "resolved": "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz",
+          "integrity": "sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==",
+          "dev": true,
+          "requires": {
+            "is-descriptor": "^1.0.2",
+            "isobject": "^3.0.1"
+          }
+        },
+        "extend-shallow": {
+          "version": "3.0.2",
+          "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz",
+          "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=",
+          "dev": true,
+          "requires": {
+            "assign-symbols": "^1.0.0",
+            "is-extendable": "^1.0.1"
+          }
+        },
+        "is-accessor-descriptor": {
+          "version": "1.0.0",
+          "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz",
+          "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==",
+          "dev": true,
+          "requires": {
+            "kind-of": "^6.0.0"
+          }
+        },
+        "is-data-descriptor": {
+          "version": "1.0.0",
+          "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz",
+          "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==",
+          "dev": true,
+          "requires": {
+            "kind-of": "^6.0.0"
+          }
+        },
+        "is-descriptor": {
+          "version": "1.0.2",
+          "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz",
+          "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==",
+          "dev": true,
+          "requires": {
+            "is-accessor-descriptor": "^1.0.0",
+            "is-data-descriptor": "^1.0.0",
+            "kind-of": "^6.0.2"
+          }
+        },
+        "is-extendable": {
+          "version": "1.0.1",
+          "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz",
+          "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==",
+          "dev": true,
+          "requires": {
+            "is-plain-object": "^2.0.4"
+          }
+        }
       }
     },
     "negotiator": {
@@ -2190,6 +2614,35 @@
       "integrity": "sha1-KzJxhOiZIQEXeyhWP7XnECrNDKk=",
       "dev": true
     },
+    "nice-try": {
+      "version": "1.0.5",
+      "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz",
+      "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==",
+      "dev": true
+    },
+    "normalize-package-data": {
+      "version": "2.5.0",
+      "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz",
+      "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==",
+      "dev": true,
+      "requires": {
+        "hosted-git-info": "^2.1.4",
+        "resolve": "^1.10.0",
+        "semver": "2 || 3 || 4 || 5",
+        "validate-npm-package-license": "^3.0.1"
+      },
+      "dependencies": {
+        "resolve": {
+          "version": "1.10.0",
+          "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.10.0.tgz",
+          "integrity": "sha512-3sUr9aq5OfSg2S9pNtPA9hL1FVEAjvfOC4leW0SNf/mpnaakz2a9femSd6LqAww2RaFctwyf1lCqnTHuF1rxDg==",
+          "dev": true,
+          "requires": {
+            "path-parse": "^1.0.6"
+          }
+        }
+      }
+    },
     "normalize-path": {
       "version": "2.1.1",
       "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz",
@@ -2199,6 +2652,15 @@
         "remove-trailing-separator": "^1.0.1"
       }
     },
+    "npm-run-path": {
+      "version": "2.0.2",
+      "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz",
+      "integrity": "sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8=",
+      "dev": true,
+      "requires": {
+        "path-key": "^2.0.0"
+      }
+    },
     "null-check": {
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/null-check/-/null-check-1.0.0.tgz",
@@ -2222,15 +2684,6 @@
         "kind-of": "^3.0.3"
       },
       "dependencies": {
-        "define-property": {
-          "version": "0.2.5",
-          "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz",
-          "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=",
-          "dev": true,
-          "requires": {
-            "is-descriptor": "^0.1.0"
-          }
-        },
         "kind-of": {
           "version": "3.2.2",
           "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
@@ -2251,6 +2704,16 @@
         "isobject": "^3.0.0"
       }
     },
+    "object.omit": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/object.omit/-/object.omit-2.0.1.tgz",
+      "integrity": "sha1-Gpx0SCnznbuFjHbKNXmuKlTr0fo=",
+      "dev": true,
+      "requires": {
+        "for-own": "^0.1.4",
+        "is-extendable": "^0.1.1"
+      }
+    },
     "object.pick": {
       "version": "1.3.0",
       "resolved": "https://registry.npmjs.org/object.pick/-/object.pick-1.3.0.tgz",
@@ -2290,22 +2753,91 @@
     },
     "os-tmpdir": {
       "version": "1.0.2",
-      "resolved": "http://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz",
+      "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz",
       "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=",
       "dev": true
     },
-    "parseqs": {
-      "version": "0.0.5",
-      "resolved": "https://registry.npmjs.org/parseqs/-/parseqs-0.0.5.tgz",
-      "integrity": "sha1-1SCKNzjkZ2bikbouoXNoSSGouJ0=",
+    "p-finally": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz",
+      "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=",
+      "dev": true
+    },
+    "p-limit": {
+      "version": "2.2.0",
+      "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.2.0.tgz",
+      "integrity": "sha512-pZbTJpoUsCzV48Mc9Nh51VbwO0X9cuPFE8gYwx9BTCt9SF8/b7Zljd2fVgOxhIF/HDTKgpVzs+GPhyKfjLLFRQ==",
       "dev": true,
       "requires": {
-        "better-assert": "~1.0.0"
+        "p-try": "^2.0.0"
       }
     },
-    "parseuri": {
-      "version": "0.0.5",
-      "resolved": "https://registry.npmjs.org/parseuri/-/parseuri-0.0.5.tgz",
+    "p-locate": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz",
+      "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==",
+      "dev": true,
+      "requires": {
+        "p-limit": "^2.0.0"
+      }
+    },
+    "p-try": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.0.0.tgz",
+      "integrity": "sha512-hMp0onDKIajHfIkdRk3P4CdCmErkYAxxDtP3Wx/4nZ3aGlau2VKh3mZpcuFkH27WQkL/3WBCPOktzA9ZOAnMQQ==",
+      "dev": true
+    },
+    "parse-glob": {
+      "version": "3.0.4",
+      "resolved": "https://registry.npmjs.org/parse-glob/-/parse-glob-3.0.4.tgz",
+      "integrity": "sha1-ssN2z7EfNVE7rdFz7wu246OIORw=",
+      "dev": true,
+      "requires": {
+        "glob-base": "^0.3.0",
+        "is-dotfile": "^1.0.0",
+        "is-extglob": "^1.0.0",
+        "is-glob": "^2.0.0"
+      },
+      "dependencies": {
+        "is-extglob": {
+          "version": "1.0.0",
+          "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-1.0.0.tgz",
+          "integrity": "sha1-rEaBd8SUNAWgkvyPKXYMb/xiBsA=",
+          "dev": true
+        },
+        "is-glob": {
+          "version": "2.0.1",
+          "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-2.0.1.tgz",
+          "integrity": "sha1-0Jb5JqPe1WAPP9/ZEZjLCIjC2GM=",
+          "dev": true,
+          "requires": {
+            "is-extglob": "^1.0.0"
+          }
+        }
+      }
+    },
+    "parse-json": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz",
+      "integrity": "sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=",
+      "dev": true,
+      "requires": {
+        "error-ex": "^1.3.1",
+        "json-parse-better-errors": "^1.0.1"
+      }
+    },
+    "parseqs": {
+      "version": "0.0.5",
+      "resolved": "https://registry.npmjs.org/parseqs/-/parseqs-0.0.5.tgz",
+      "integrity": "sha1-1SCKNzjkZ2bikbouoXNoSSGouJ0=",
+      "dev": true,
+      "requires": {
+        "better-assert": "~1.0.0"
+      }
+    },
+    "parseuri": {
+      "version": "0.0.5",
+      "resolved": "https://registry.npmjs.org/parseuri/-/parseuri-0.0.5.tgz",
       "integrity": "sha1-gCBKUNTbt3m/3G6+J3jZDkvOMgo=",
       "dev": true,
       "requires": {
@@ -2330,24 +2862,72 @@
       "integrity": "sha1-zDPSTVJeCZpTiMAzbG4yuRYGCeA=",
       "dev": true
     },
+    "path-exists": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz",
+      "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=",
+      "dev": true
+    },
     "path-is-absolute": {
       "version": "1.0.1",
       "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
       "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=",
       "dev": true
     },
+    "path-key": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz",
+      "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=",
+      "dev": true
+    },
+    "path-parse": {
+      "version": "1.0.6",
+      "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz",
+      "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==",
+      "dev": true
+    },
     "pathval": {
       "version": "1.1.0",
       "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.0.tgz",
       "integrity": "sha1-uULm1L3mUwBe9rcTYd74cn0GReA=",
       "dev": true
     },
+    "pify": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz",
+      "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=",
+      "dev": true
+    },
+    "pkg-dir": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-3.0.0.tgz",
+      "integrity": "sha512-/E57AYkoeQ25qkxMj5PBOVgF8Kiu/h7cYS30Z5+R7WaiCCBfLq58ZI/dSeaEKb9WVJV5n/03QwrN3IeWIFllvw==",
+      "dev": true,
+      "requires": {
+        "find-up": "^3.0.0"
+      }
+    },
+    "please-upgrade-node": {
+      "version": "3.1.1",
+      "resolved": "https://registry.npmjs.org/please-upgrade-node/-/please-upgrade-node-3.1.1.tgz",
+      "integrity": "sha512-KY1uHnQ2NlQHqIJQpnh/i54rKkuxCEBx+voJIS/Mvb+L2iYd2NMotwduhKTMjfC1uKoX3VXOxLjIYG66dfJTVQ==",
+      "dev": true,
+      "requires": {
+        "semver-compare": "^1.0.0"
+      }
+    },
     "posix-character-classes": {
       "version": "0.1.1",
       "resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz",
       "integrity": "sha1-AerA/jta9xoqbAL+q7jB/vfgDqs=",
       "dev": true
     },
+    "preserve": {
+      "version": "0.2.0",
+      "resolved": "https://registry.npmjs.org/preserve/-/preserve-0.2.0.tgz",
+      "integrity": "sha1-gV7R9uvGWSb4ZbMQwHE7yzMVzks=",
+      "dev": true
+    },
     "prettier": {
       "version": "1.16.1",
       "resolved": "https://registry.npmjs.org/prettier/-/prettier-1.16.1.tgz",
@@ -2366,6 +2946,16 @@
       "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=",
       "dev": true
     },
+    "pump": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz",
+      "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==",
+      "dev": true,
+      "requires": {
+        "end-of-stream": "^1.1.0",
+        "once": "^1.3.1"
+      }
+    },
     "qjobs": {
       "version": "1.2.0",
       "resolved": "https://registry.npmjs.org/qjobs/-/qjobs-1.2.0.tgz",
@@ -2378,6 +2968,25 @@
       "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==",
       "dev": true
     },
+    "randomatic": {
+      "version": "3.1.1",
+      "resolved": "https://registry.npmjs.org/randomatic/-/randomatic-3.1.1.tgz",
+      "integrity": "sha512-TuDE5KxZ0J461RVjrJZCJc+J+zCkTb1MbH9AQUq68sMhOMcy9jLcb3BrZKgp9q9Ncltdg4QVqWrH02W2EFFVYw==",
+      "dev": true,
+      "requires": {
+        "is-number": "^4.0.0",
+        "kind-of": "^6.0.0",
+        "math-random": "^1.0.1"
+      },
+      "dependencies": {
+        "is-number": {
+          "version": "4.0.0",
+          "resolved": "https://registry.npmjs.org/is-number/-/is-number-4.0.0.tgz",
+          "integrity": "sha512-rSklcAIlf1OmFdyAqbnWTLVelsQ58uvZ66S/ZyawjWqIviTWCjg2PzVGw8WUA+nNuPTqb4wgA+NszrJ+08LlgQ==",
+          "dev": true
+        }
+      }
+    },
     "range-parser": {
       "version": "1.2.0",
       "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.0.tgz",
@@ -2396,6 +3005,17 @@
         "unpipe": "1.0.0"
       }
     },
+    "read-pkg": {
+      "version": "4.0.1",
+      "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-4.0.1.tgz",
+      "integrity": "sha1-ljYlN48+HE1IyFhytabsfV0JMjc=",
+      "dev": true,
+      "requires": {
+        "normalize-package-data": "^2.3.2",
+        "parse-json": "^4.0.0",
+        "pify": "^3.0.0"
+      }
+    },
     "readable-stream": {
       "version": "2.3.6",
       "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz",
@@ -2422,6 +3042,15 @@
         "readable-stream": "^2.0.2"
       }
     },
+    "regex-cache": {
+      "version": "0.4.4",
+      "resolved": "https://registry.npmjs.org/regex-cache/-/regex-cache-0.4.4.tgz",
+      "integrity": "sha512-nVIZwtCjkC9YgvWkpM55B5rBhBYRZhAaJbgcFYXXsHnbZ9UZI9nnVWYZpBlCqv9ho2eZryPnWrZGsOdPwVWXWQ==",
+      "dev": true,
+      "requires": {
+        "is-equal-shallow": "^0.1.3"
+      }
+    },
     "regex-not": {
       "version": "1.0.2",
       "resolved": "https://registry.npmjs.org/regex-not/-/regex-not-1.0.2.tgz",
@@ -2430,6 +3059,27 @@
       "requires": {
         "extend-shallow": "^3.0.2",
         "safe-regex": "^1.1.0"
+      },
+      "dependencies": {
+        "extend-shallow": {
+          "version": "3.0.2",
+          "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz",
+          "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=",
+          "dev": true,
+          "requires": {
+            "assign-symbols": "^1.0.0",
+            "is-extendable": "^1.0.1"
+          }
+        },
+        "is-extendable": {
+          "version": "1.0.1",
+          "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz",
+          "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==",
+          "dev": true,
+          "requires": {
+            "is-plain-object": "^2.0.4"
+          }
+        }
       }
     },
     "remove-trailing-separator": {
@@ -2456,6 +3106,21 @@
       "integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=",
       "dev": true
     },
+    "resolve": {
+      "version": "1.8.1",
+      "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.8.1.tgz",
+      "integrity": "sha512-AicPrAC7Qu1JxPCZ9ZgCZlY35QgFnNqc+0LtbRNxnVw4TXvjQ72wnuL9JQcEBgXkI9JM8MsT9kaQoHcpCRJOYA==",
+      "dev": true,
+      "requires": {
+        "path-parse": "^1.0.5"
+      }
+    },
+    "resolve-from": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-3.0.0.tgz",
+      "integrity": "sha1-six699nWiBvItuZTM17rywoYh0g=",
+      "dev": true
+    },
     "resolve-url": {
       "version": "0.2.1",
       "resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz",
@@ -2475,14 +3140,212 @@
       "dev": true
     },
     "rimraf": {
-      "version": "2.6.2",
-      "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.2.tgz",
-      "integrity": "sha512-lreewLK/BlghmxtfH36YYVg1i8IAce4TI7oao75I1g245+6BctqTVQiBP3YUJ9C6DQOXJmkYR9X9fCLtCOJc5w==",
+      "version": "2.6.3",
+      "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz",
+      "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==",
       "dev": true,
       "requires": {
-        "glob": "^7.0.5"
+        "glob": "^7.1.3"
+      },
+      "dependencies": {
+        "glob": {
+          "version": "7.1.3",
+          "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz",
+          "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==",
+          "dev": true,
+          "requires": {
+            "fs.realpath": "^1.0.0",
+            "inflight": "^1.0.4",
+            "inherits": "2",
+            "minimatch": "^3.0.4",
+            "once": "^1.3.0",
+            "path-is-absolute": "^1.0.0"
+          }
+        }
+      }
+    },
+    "rollup": {
+      "version": "1.6.0",
+      "resolved": "https://registry.npmjs.org/rollup/-/rollup-1.6.0.tgz",
+      "integrity": "sha512-qu9iWyuiOxAuBM8cAwLuqPclYdarIpayrkfQB7aTGTiyYPbvx+qVF33sIznfq4bxZCiytQux/FvZieUBAXivCw==",
+      "dev": true,
+      "requires": {
+        "@types/estree": "0.0.39",
+        "@types/node": "^11.9.5",
+        "acorn": "^6.1.1"
+      }
+    },
+    "rollup-plugin-terser": {
+      "version": "4.0.4",
+      "resolved": "https://registry.npmjs.org/rollup-plugin-terser/-/rollup-plugin-terser-4.0.4.tgz",
+      "integrity": "sha512-wPANT5XKVJJ8RDUN0+wIr7UPd0lIXBo4UdJ59VmlPCtlFsE20AM+14pe+tk7YunCsWEiuzkDBY3QIkSCjtrPXg==",
+      "dev": true,
+      "requires": {
+        "@babel/code-frame": "^7.0.0",
+        "jest-worker": "^24.0.0",
+        "serialize-javascript": "^1.6.1",
+        "terser": "^3.14.1"
+      }
+    },
+    "rollup-plugin-typescript2": {
+      "version": "0.19.3",
+      "resolved": "https://registry.npmjs.org/rollup-plugin-typescript2/-/rollup-plugin-typescript2-0.19.3.tgz",
+      "integrity": "sha512-lsRqfBCZhMl/tq9AT5YnQvzQWzXtnx3EQYFcHD72gul7nyyoOrzx5yCEH20smpw58v6UkHHZz03FbdLEPoHWjA==",
+      "dev": true,
+      "requires": {
+        "fs-extra": "7.0.1",
+        "resolve": "1.8.1",
+        "rollup-pluginutils": "2.3.3",
+        "tslib": "1.9.3"
+      }
+    },
+    "rollup-pluginutils": {
+      "version": "2.3.3",
+      "resolved": "https://registry.npmjs.org/rollup-pluginutils/-/rollup-pluginutils-2.3.3.tgz",
+      "integrity": "sha512-2XZwja7b6P5q4RZ5FhyX1+f46xi1Z3qBKigLRZ6VTZjwbN0K1IFGMlwm06Uu0Emcre2Z63l77nq/pzn+KxIEoA==",
+      "dev": true,
+      "requires": {
+        "estree-walker": "^0.5.2",
+        "micromatch": "^2.3.11"
+      },
+      "dependencies": {
+        "arr-diff": {
+          "version": "2.0.0",
+          "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-2.0.0.tgz",
+          "integrity": "sha1-jzuCf5Vai9ZpaX5KQlasPOrjVs8=",
+          "dev": true,
+          "requires": {
+            "arr-flatten": "^1.0.1"
+          }
+        },
+        "array-unique": {
+          "version": "0.2.1",
+          "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.2.1.tgz",
+          "integrity": "sha1-odl8yvy8JiXMcPrc6zalDFiwGlM=",
+          "dev": true
+        },
+        "braces": {
+          "version": "1.8.5",
+          "resolved": "https://registry.npmjs.org/braces/-/braces-1.8.5.tgz",
+          "integrity": "sha1-uneWLhLf+WnWt2cR6RS3N4V79qc=",
+          "dev": true,
+          "requires": {
+            "expand-range": "^1.8.1",
+            "preserve": "^0.2.0",
+            "repeat-element": "^1.1.2"
+          }
+        },
+        "expand-brackets": {
+          "version": "0.1.5",
+          "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-0.1.5.tgz",
+          "integrity": "sha1-3wcoTjQqgHzXM6xa9yQR5YHRF3s=",
+          "dev": true,
+          "requires": {
+            "is-posix-bracket": "^0.1.0"
+          }
+        },
+        "expand-range": {
+          "version": "1.8.2",
+          "resolved": "https://registry.npmjs.org/expand-range/-/expand-range-1.8.2.tgz",
+          "integrity": "sha1-opnv/TNf4nIeuujiV+x5ZE/IUzc=",
+          "dev": true,
+          "requires": {
+            "fill-range": "^2.1.0"
+          }
+        },
+        "extglob": {
+          "version": "0.3.2",
+          "resolved": "https://registry.npmjs.org/extglob/-/extglob-0.3.2.tgz",
+          "integrity": "sha1-Lhj/PS9JqydlzskCPwEdqo2DSaE=",
+          "dev": true,
+          "requires": {
+            "is-extglob": "^1.0.0"
+          }
+        },
+        "fill-range": {
+          "version": "2.2.4",
+          "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-2.2.4.tgz",
+          "integrity": "sha512-cnrcCbj01+j2gTG921VZPnHbjmdAf8oQV/iGeV2kZxGSyfYjjTyY79ErsK1WJWMpw6DaApEX72binqJE+/d+5Q==",
+          "dev": true,
+          "requires": {
+            "is-number": "^2.1.0",
+            "isobject": "^2.0.0",
+            "randomatic": "^3.0.0",
+            "repeat-element": "^1.1.2",
+            "repeat-string": "^1.5.2"
+          }
+        },
+        "is-extglob": {
+          "version": "1.0.0",
+          "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-1.0.0.tgz",
+          "integrity": "sha1-rEaBd8SUNAWgkvyPKXYMb/xiBsA=",
+          "dev": true
+        },
+        "is-glob": {
+          "version": "2.0.1",
+          "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-2.0.1.tgz",
+          "integrity": "sha1-0Jb5JqPe1WAPP9/ZEZjLCIjC2GM=",
+          "dev": true,
+          "requires": {
+            "is-extglob": "^1.0.0"
+          }
+        },
+        "is-number": {
+          "version": "2.1.0",
+          "resolved": "https://registry.npmjs.org/is-number/-/is-number-2.1.0.tgz",
+          "integrity": "sha1-Afy7s5NGOlSPL0ZszhbezknbkI8=",
+          "dev": true,
+          "requires": {
+            "kind-of": "^3.0.2"
+          }
+        },
+        "isobject": {
+          "version": "2.1.0",
+          "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz",
+          "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=",
+          "dev": true,
+          "requires": {
+            "isarray": "1.0.0"
+          }
+        },
+        "kind-of": {
+          "version": "3.2.2",
+          "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
+          "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=",
+          "dev": true,
+          "requires": {
+            "is-buffer": "^1.1.5"
+          }
+        },
+        "micromatch": {
+          "version": "2.3.11",
+          "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-2.3.11.tgz",
+          "integrity": "sha1-hmd8l9FyCzY0MdBNDRUpO9OMFWU=",
+          "dev": true,
+          "requires": {
+            "arr-diff": "^2.0.0",
+            "array-unique": "^0.2.1",
+            "braces": "^1.8.2",
+            "expand-brackets": "^0.1.4",
+            "extglob": "^0.3.1",
+            "filename-regex": "^2.0.0",
+            "is-extglob": "^1.0.0",
+            "is-glob": "^2.0.1",
+            "kind-of": "^3.0.2",
+            "normalize-path": "^2.0.1",
+            "object.omit": "^2.0.0",
+            "parse-glob": "^3.0.4",
+            "regex-cache": "^0.4.2"
+          }
+        }
       }
     },
+    "run-node": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/run-node/-/run-node-1.0.0.tgz",
+      "integrity": "sha512-kc120TBlQ3mih1LSzdAJXo4xn/GWS2ec0l3S+syHDXP9uRr0JAT8Qd3mdMuyjqCzeZktgP3try92cEgf9Nks8A==",
+      "dev": true
+    },
     "safe-buffer": {
       "version": "5.1.2",
       "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
@@ -2504,6 +3367,24 @@
       "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
       "dev": true
     },
+    "semver": {
+      "version": "5.6.0",
+      "resolved": "https://registry.npmjs.org/semver/-/semver-5.6.0.tgz",
+      "integrity": "sha512-RS9R6R35NYgQn++fkDWaOmqGoj4Ek9gGs+DPxNUZKuwE183xjJroKvyo1IzVFeXvUrvmALy6FWD5xrdJT25gMg==",
+      "dev": true
+    },
+    "semver-compare": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/semver-compare/-/semver-compare-1.0.0.tgz",
+      "integrity": "sha1-De4hahyUGrN+nvsXiPavxf9VN/w=",
+      "dev": true
+    },
+    "serialize-javascript": {
+      "version": "1.6.1",
+      "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-1.6.1.tgz",
+      "integrity": "sha512-A5MOagrPFga4YaKQSWHryl7AXvbQkEqpw4NNYMTNYUNV51bA8ABHgYFpqKx+YFFrw59xMV1qGH1R4AgoNIVgCw==",
+      "dev": true
+    },
     "set-value": {
       "version": "2.0.0",
       "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.0.tgz",
@@ -2514,17 +3395,6 @@
         "is-extendable": "^0.1.1",
         "is-plain-object": "^2.0.3",
         "split-string": "^3.0.1"
-      },
-      "dependencies": {
-        "extend-shallow": {
-          "version": "2.0.1",
-          "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
-          "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=",
-          "dev": true,
-          "requires": {
-            "is-extendable": "^0.1.0"
-          }
-        }
       }
     },
     "setprototypeof": {
@@ -2533,6 +3403,33 @@
       "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==",
       "dev": true
     },
+    "shebang-command": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz",
+      "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=",
+      "dev": true,
+      "requires": {
+        "shebang-regex": "^1.0.0"
+      }
+    },
+    "shebang-regex": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz",
+      "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=",
+      "dev": true
+    },
+    "signal-exit": {
+      "version": "3.0.2",
+      "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz",
+      "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=",
+      "dev": true
+    },
+    "slash": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/slash/-/slash-2.0.0.tgz",
+      "integrity": "sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==",
+      "dev": true
+    },
     "snapdragon": {
       "version": "0.8.2",
       "resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz",
@@ -2549,24 +3446,6 @@
         "use": "^3.1.0"
       },
       "dependencies": {
-        "define-property": {
-          "version": "0.2.5",
-          "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz",
-          "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=",
-          "dev": true,
-          "requires": {
-            "is-descriptor": "^0.1.0"
-          }
-        },
-        "extend-shallow": {
-          "version": "2.0.1",
-          "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
-          "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=",
-          "dev": true,
-          "requires": {
-            "is-extendable": "^0.1.0"
-          }
-        },
         "source-map": {
           "version": "0.5.7",
           "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz",
@@ -2757,12 +3636,54 @@
         "urix": "^0.1.0"
       }
     },
+    "source-map-support": {
+      "version": "0.5.10",
+      "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.10.tgz",
+      "integrity": "sha512-YfQ3tQFTK/yzlGJuX8pTwa4tifQj4QS2Mj7UegOu8jAz59MqIiMGPXxQhVQiIMNzayuUSF/jEuVnfFF5JqybmQ==",
+      "dev": true,
+      "requires": {
+        "buffer-from": "^1.0.0",
+        "source-map": "^0.6.0"
+      }
+    },
     "source-map-url": {
       "version": "0.4.0",
       "resolved": "https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.0.tgz",
       "integrity": "sha1-PpNdfd1zYxuXZZlW1VEo6HtQhKM=",
       "dev": true
     },
+    "spdx-correct": {
+      "version": "3.1.0",
+      "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.0.tgz",
+      "integrity": "sha512-lr2EZCctC2BNR7j7WzJ2FpDznxky1sjfxvvYEyzxNyb6lZXHODmEoJeFu4JupYlkfha1KZpJyoqiJ7pgA1qq8Q==",
+      "dev": true,
+      "requires": {
+        "spdx-expression-parse": "^3.0.0",
+        "spdx-license-ids": "^3.0.0"
+      }
+    },
+    "spdx-exceptions": {
+      "version": "2.2.0",
+      "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.2.0.tgz",
+      "integrity": "sha512-2XQACfElKi9SlVb1CYadKDXvoajPgBVPn/gOQLrTvHdElaVhr7ZEbqJaRnJLVNeaI4cMEAgVCeBMKF6MWRDCRA==",
+      "dev": true
+    },
+    "spdx-expression-parse": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.0.tgz",
+      "integrity": "sha512-Yg6D3XpRD4kkOmTpdgbUiEJFKghJH03fiC1OPll5h/0sO6neh2jqRDVHOQ4o/LMea0tgCkbMgea5ip/e+MkWyg==",
+      "dev": true,
+      "requires": {
+        "spdx-exceptions": "^2.1.0",
+        "spdx-license-ids": "^3.0.0"
+      }
+    },
+    "spdx-license-ids": {
+      "version": "3.0.3",
+      "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.3.tgz",
+      "integrity": "sha512-uBIcIl3Ih6Phe3XHK1NqboJLdGfwr1UN3k6wSD1dZpmPsIkb8AGNbZYJ1fOBk834+Gxy8rpfDxrS6XLEMZMY2g==",
+      "dev": true
+    },
     "split-string": {
       "version": "3.1.0",
       "resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz",
@@ -2770,8 +3691,35 @@
       "dev": true,
       "requires": {
         "extend-shallow": "^3.0.0"
+      },
+      "dependencies": {
+        "extend-shallow": {
+          "version": "3.0.2",
+          "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz",
+          "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=",
+          "dev": true,
+          "requires": {
+            "assign-symbols": "^1.0.0",
+            "is-extendable": "^1.0.1"
+          }
+        },
+        "is-extendable": {
+          "version": "1.0.1",
+          "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz",
+          "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==",
+          "dev": true,
+          "requires": {
+            "is-plain-object": "^2.0.4"
+          }
+        }
       }
     },
+    "sprintf-js": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
+      "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=",
+      "dev": true
+    },
     "static-extend": {
       "version": "0.1.2",
       "resolved": "https://registry.npmjs.org/static-extend/-/static-extend-0.1.2.tgz",
@@ -2780,17 +3728,6 @@
       "requires": {
         "define-property": "^0.2.5",
         "object-copy": "^0.1.0"
-      },
-      "dependencies": {
-        "define-property": {
-          "version": "0.2.5",
-          "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz",
-          "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=",
-          "dev": true,
-          "requires": {
-            "is-descriptor": "^0.1.0"
-          }
-        }
       }
     },
     "statuses": {
@@ -2800,15 +3737,16 @@
       "dev": true
     },
     "streamroller": {
-      "version": "0.7.0",
-      "resolved": "https://registry.npmjs.org/streamroller/-/streamroller-0.7.0.tgz",
-      "integrity": "sha512-WREzfy0r0zUqp3lGO096wRuUp7ho1X6uo/7DJfTlEi0Iv/4gT7YHqXDjKC2ioVGBZtE8QzsQD9nx1nIuoZ57jQ==",
+      "version": "1.0.3",
+      "resolved": "https://registry.npmjs.org/streamroller/-/streamroller-1.0.3.tgz",
+      "integrity": "sha512-P7z9NwP51EltdZ81otaGAN3ob+/F88USJE546joNq7bqRNTe6jc74fTBDyynxP4qpIfKlt/CesEYicuMzI0yJg==",
       "dev": true,
       "requires": {
-        "date-format": "^1.2.0",
+        "async": "^2.6.1",
+        "date-format": "^2.0.0",
         "debug": "^3.1.0",
-        "mkdirp": "^0.5.1",
-        "readable-stream": "^2.3.0"
+        "fs-extra": "^7.0.0",
+        "lodash": "^4.17.10"
       },
       "dependencies": {
         "debug": {
@@ -2837,6 +3775,40 @@
         "safe-buffer": "~5.1.0"
       }
     },
+    "strip-eof": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz",
+      "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=",
+      "dev": true
+    },
+    "supports-color": {
+      "version": "5.5.0",
+      "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
+      "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
+      "dev": true,
+      "requires": {
+        "has-flag": "^3.0.0"
+      }
+    },
+    "terser": {
+      "version": "3.16.1",
+      "resolved": "https://registry.npmjs.org/terser/-/terser-3.16.1.tgz",
+      "integrity": "sha512-JDJjgleBROeek2iBcSNzOHLKsB/MdDf+E/BOAJ0Tk9r7p9/fVobfv7LMJ/g/k3v9SXdmjZnIlFd5nfn/Rt0Xow==",
+      "dev": true,
+      "requires": {
+        "commander": "~2.17.1",
+        "source-map": "~0.6.1",
+        "source-map-support": "~0.5.9"
+      },
+      "dependencies": {
+        "commander": {
+          "version": "2.17.1",
+          "resolved": "https://registry.npmjs.org/commander/-/commander-2.17.1.tgz",
+          "integrity": "sha512-wPMUt6FnH2yzG95SA6mzjQOEKUU3aLaDEmzs1ti+1E9h+CsrZghRlqEM/EJ4KscsQVG8uNN4uVreUeT8+drlgg==",
+          "dev": true
+        }
+      }
+    },
     "tmp": {
       "version": "0.0.33",
       "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz",
@@ -2882,6 +3854,66 @@
         "extend-shallow": "^3.0.2",
         "regex-not": "^1.0.2",
         "safe-regex": "^1.1.0"
+      },
+      "dependencies": {
+        "define-property": {
+          "version": "2.0.2",
+          "resolved": "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz",
+          "integrity": "sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==",
+          "dev": true,
+          "requires": {
+            "is-descriptor": "^1.0.2",
+            "isobject": "^3.0.1"
+          }
+        },
+        "extend-shallow": {
+          "version": "3.0.2",
+          "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz",
+          "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=",
+          "dev": true,
+          "requires": {
+            "assign-symbols": "^1.0.0",
+            "is-extendable": "^1.0.1"
+          }
+        },
+        "is-accessor-descriptor": {
+          "version": "1.0.0",
+          "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz",
+          "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==",
+          "dev": true,
+          "requires": {
+            "kind-of": "^6.0.0"
+          }
+        },
+        "is-data-descriptor": {
+          "version": "1.0.0",
+          "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz",
+          "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==",
+          "dev": true,
+          "requires": {
+            "kind-of": "^6.0.0"
+          }
+        },
+        "is-descriptor": {
+          "version": "1.0.2",
+          "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz",
+          "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==",
+          "dev": true,
+          "requires": {
+            "is-accessor-descriptor": "^1.0.0",
+            "is-data-descriptor": "^1.0.0",
+            "kind-of": "^6.0.2"
+          }
+        },
+        "is-extendable": {
+          "version": "1.0.1",
+          "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz",
+          "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==",
+          "dev": true,
+          "requires": {
+            "is-plain-object": "^2.0.4"
+          }
+        }
       }
     },
     "to-regex-range": {
@@ -2894,6 +3926,12 @@
         "repeat-string": "^1.6.1"
       }
     },
+    "tslib": {
+      "version": "1.9.3",
+      "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.9.3.tgz",
+      "integrity": "sha512-4krF8scpejhaOgqzBEcGM7yDIEfi0/8+8zDRZhNZZ2kjmHJ4hv3zCbQWxoJGz1iw5U0Jl0nma13xzHXcncMavQ==",
+      "dev": true
+    },
     "type-detect": {
       "version": "4.0.8",
       "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz",
@@ -2911,9 +3949,9 @@
       }
     },
     "typescript": {
-      "version": "3.2.4",
-      "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.2.4.tgz",
-      "integrity": "sha512-0RNDbSdEokBeEAkgNbxJ+BLwSManFy9TeXz8uW+48j/xhEXv1ePME60olyzw2XzUqUBNAYFeJadIqAgNqIACwg==",
+      "version": "3.3.3333",
+      "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.3.3333.tgz",
+      "integrity": "sha512-JjSKsAfuHBE/fB2oZ8NxtRTk5iGcg6hkYXMnZ3Wc+b2RSqejEqTaem11mHASMnFilHrax3sLK0GDzcJrekZYLw==",
       "dev": true
     },
     "ultron": {
@@ -2934,15 +3972,6 @@
         "set-value": "^0.4.3"
       },
       "dependencies": {
-        "extend-shallow": {
-          "version": "2.0.1",
-          "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
-          "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=",
-          "dev": true,
-          "requires": {
-            "is-extendable": "^0.1.0"
-          }
-        },
         "set-value": {
           "version": "0.4.3",
           "resolved": "https://registry.npmjs.org/set-value/-/set-value-0.4.3.tgz",
@@ -2957,6 +3986,12 @@
         }
       }
     },
+    "universalify": {
+      "version": "0.1.2",
+      "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz",
+      "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==",
+      "dev": true
+    },
     "unpipe": {
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
@@ -3004,9 +4039,9 @@
       }
     },
     "upath": {
-      "version": "1.1.0",
-      "resolved": "https://registry.npmjs.org/upath/-/upath-1.1.0.tgz",
-      "integrity": "sha512-bzpH/oBhoS/QI/YtbkqCg6VEiPYjSZtrHQM6/QnJS6OL9pKUFLqb3aFh4Scvwm45+7iAgiMkLhSbaZxUqmrprw==",
+      "version": "1.1.2",
+      "resolved": "https://registry.npmjs.org/upath/-/upath-1.1.2.tgz",
+      "integrity": "sha512-kXpym8nmDmlCBr7nKdIx8P2jNBa+pBpIUFRnKJ4dr8htyYGJFokkr2ZvERRtUN+9SY+JqXouNgUPtv6JQva/2Q==",
       "dev": true
     },
     "urix": {
@@ -3043,6 +4078,16 @@
       "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=",
       "dev": true
     },
+    "validate-npm-package-license": {
+      "version": "3.0.4",
+      "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz",
+      "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==",
+      "dev": true,
+      "requires": {
+        "spdx-correct": "^3.0.0",
+        "spdx-expression-parse": "^3.0.0"
+      }
+    },
     "void-elements": {
       "version": "2.0.1",
       "resolved": "https://registry.npmjs.org/void-elements/-/void-elements-2.0.1.tgz",
diff --git a/package.json b/package.json
index 84886334..98abe62d 100644
--- a/package.json
+++ b/package.json
@@ -1,36 +1,29 @@
 {
-  "name": "comlinkjs",
-  "version": "3.1.1",
-  "description": "",
-  "main": "comlink.js",
-  "module": "comlink.js",
-  "types": "comlink.d.ts",
+  "name": "comlink",
+  "version": "4.0.0-alpha.10",
+  "description": "Comlink makes WebWorkers enjoyable",
+  "main": "dist/umd/comlink.js",
+  "module": "dist/esm/comlink.mjs",
+  "types": "dist/umd/comlink.d.ts",
   "scripts": {
-    "test": "npm run linter && npm run unittest && npm run build",
-    "unittest": "karma start",
-    "linter": "prettier --write ./*.js ./docs/**/*.js ./tests/**/*.js ./**/*.ts ./**/*.md ./**/*.json",
-    "watchtest": "karma start --no-single-run --browsers ChromeHeadless",
-    "watchtestharmony": "karma start --no-single-run --browsers ChromeCanaryHeadlessHarmony",
-    "version": "sed -i.bak -e 's!comlinkjs@[0-9.]*!comlinkjs@'${npm_package_version}'!' README.md && git add README.md",
-    "mypublish": "npm run build && npm run test && cp README.md package.json dist && npm publish dist",
-    "build": "rm -rf dist && mkdir dist && npm run compile",
-    "compile": "tsc --outDir dist && tsc -m umd --outDir dist/umd && node ./mangle_umd.js"
+    "build": "rollup -c",
+    "test:unit": "karma start",
+    "test:types": "tsc -t esnext -m esnext --lib esnext,dom --moduleResolution node --noEmit tests/type-checks.ts",
+    "test": "npm run fmt_test && npm run build && npm run test:types && npm run test:unit",
+    "fmt": "prettier --write ./*.{mjs,js,ts,md,json,html} ./{src,docs,tests}/**/*.{mjs,js,ts,md,json,html}",
+    "fmt_test": "test $(prettier -l ./*.{mjs,js,ts,md,json,html} ./{src,docs,tests}/**/*.{mjs,js,ts,md,json,html} | wc -l) -eq 0",
+    "watchtest": "CHROME_ONLY=1 karma start --no-single-run"
+  },
+  "husky": {
+    "hooks": {
+      "pre-commit": "npm test"
+    }
   },
   "keywords": [],
   "author": {
     "name": "Surma",
     "email": "surma@google.com"
   },
-  "contributors": [
-    {
-      "name": "Surma",
-      "email": "surma@google.com"
-    },
-    {
-      "name": "Ian Kilpatrick",
-      "email": "ikilpatrick@google.com"
-    }
-  ],
   "repository": {
     "type": "git",
     "url": "https://github.com/GoogleChromeLabs/comlink.git"
@@ -38,16 +31,23 @@
   "license": "Apache-2.0",
   "devDependencies": {
     "chai": "4.2.0",
-    "karma": "3.1.4",
+    "conditional-type-checks": "^1.0.0",
+    "husky": "^1.3.1",
+    "karma": "^4.0.1",
     "karma-chai": "0.1.0",
     "karma-chrome-launcher": "2.2.0",
+    "karma-detect-browsers": "^2.3.3",
     "karma-firefox-launcher": "1.1.0",
     "karma-mocha": "1.3.0",
-    "karma-safari-launcher": "1.0.0",
+    "karma-safari-launcher": "^1.0.0",
     "karma-safaritechpreview-launcher": "2.0.2",
     "mocha": "5.2.0",
     "prettier": "1.16.1",
-    "typescript": "3.2.4"
+    "rimraf": "^2.6.3",
+    "rollup": "^1.6.0",
+    "rollup-plugin-terser": "^4.0.4",
+    "rollup-plugin-typescript2": "^0.19.3",
+    "typescript": "^3.3.3333"
   },
   "dependencies": {}
 }
diff --git a/rollup.config.js b/rollup.config.js
new file mode 100644
index 00000000..08d149b0
--- /dev/null
+++ b/rollup.config.js
@@ -0,0 +1,51 @@
+import typescript from "rollup-plugin-typescript2";
+import { terser } from "rollup-plugin-terser";
+
+function config({ format, minify, input }) {
+  const dir = `dist/${format}/`;
+  const minifierSuffix = minify ? ".min" : "";
+  const ext = format === "esm" ? "mjs" : "js";
+  return {
+    input: `./src/${input}.ts`,
+    output: {
+      name: "Comlink",
+      file: `${dir}/${input}${minifierSuffix}.${ext}`,
+      format,
+      sourcemap: true
+    },
+    plugins: [
+      typescript({
+        clean: true,
+        typescript: require("typescript"),
+        tsconfigOverride: {
+          compilerOptions: {
+            sourceMap: true
+          },
+          // Don’t ask. Without this, the typescript plugin is convinced
+          // to create subfolders and misplace the .d.ts files.
+          files: ["./src/comlink.ts", "./src/protocol.ts"]
+        }
+      }),
+      minify
+        ? terser({
+            sourcemap: true,
+            compress: true,
+            mangle: true
+          })
+        : []
+    ].flat()
+  };
+}
+
+require("rimraf").sync("dist");
+
+export default [
+  { input: "comlink", format: "esm", minify: false },
+  { input: "comlink", format: "esm", minify: true },
+  { input: "comlink", format: "umd", minify: false },
+  { input: "comlink", format: "umd", minify: true },
+  { input: "node-adapter", format: "esm", minify: false },
+  { input: "node-adapter", format: "esm", minify: true },
+  { input: "node-adapter", format: "umd", minify: false },
+  { input: "node-adapter", format: "umd", minify: true }
+].map(config);
diff --git a/src/comlink.ts b/src/comlink.ts
new file mode 100644
index 00000000..e1804521
--- /dev/null
+++ b/src/comlink.ts
@@ -0,0 +1,315 @@
+/**
+ * Copyright 2019 Google Inc. All Rights Reserved.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import {
+  Endpoint,
+  Message,
+  MessageType,
+  WireValue,
+  WireValueType
+} from "./protocol.js";
+export { Endpoint };
+
+export const proxyMarker = Symbol("Comlink.proxy");
+export const createEndpoint = Symbol("Comlink.endpoint");
+const throwSet = new WeakSet();
+
+// prettier-ignore
+type Promisify = T extends { [proxyMarker]: boolean }
+  ? Promise>
+  : T extends Promise
+    ? T
+    : T extends (...args: infer R1) => infer R2
+      ? (...args: R1) => Promisify
+      : Promise;
+
+// prettier-ignore
+export type Remote =
+  (
+    T extends (...args: infer R1) => infer R2
+      ? (...args: R1) => Promisify
+      : { [K in keyof T]: Promisify }
+  ) & (
+    T extends { new (...args: infer R1): infer R2 }
+      ? { new (...args: R1): Promise> }
+      : unknown
+  );
+
+export interface TransferHandler {
+  canHandle(obj: any): boolean;
+  serialize(obj: any): [any, any[]];
+  deserialize(obj: any): any;
+}
+
+export const transferHandlers = new Map([
+  [
+    "proxy",
+    {
+      canHandle: obj => obj && obj[proxyMarker],
+      serialize(obj) {
+        const { port1, port2 } = new MessageChannel();
+        expose(obj, port1);
+        return [port2, [port2]];
+      },
+      deserialize: (port: MessagePort) => {
+        port.start();
+        return wrap(port);
+      }
+    }
+  ],
+  [
+    "throw",
+    {
+      canHandle: obj => throwSet.has(obj),
+      serialize(obj) {
+        const isError = obj instanceof Error;
+        let serialized = obj;
+        if (isError) {
+          serialized = {
+            isError,
+            message: obj.message,
+            stack: obj.stack
+          };
+        }
+        return [serialized, []];
+      },
+      deserialize(obj) {
+        if ((obj as any).isError) {
+          throw Object.assign(new Error(), obj);
+        }
+        throw obj;
+      }
+    }
+  ]
+]);
+
+export function expose(obj: any, ep: Endpoint = self as any) {
+  ep.addEventListener("message", (async (ev: MessageEvent) => {
+    if (!ev || !ev.data) {
+      return;
+    }
+    const { id, type, path } = {
+      path: [] as string[],
+      ...(ev.data as Message)
+    };
+    const argumentList = (ev.data.argumentList || []).map(fromWireValue);
+    let returnValue;
+    try {
+      const parent = path.slice(0, -1).reduce((obj, prop) => obj[prop], obj);
+      const rawValue = path.reduce((obj, prop) => obj[prop], obj);
+      switch (type) {
+        case MessageType.GET:
+          {
+            returnValue = await rawValue;
+          }
+          break;
+        case MessageType.SET:
+          {
+            parent[path.slice(-1)[0]] = fromWireValue(ev.data.value);
+            returnValue = true;
+          }
+          break;
+        case MessageType.APPLY:
+          {
+            returnValue = await rawValue.apply(parent, argumentList);
+          }
+          break;
+        case MessageType.CONSTRUCT:
+          {
+            const value = await new rawValue(...argumentList);
+            returnValue = proxy(value);
+          }
+          break;
+        case MessageType.ENDPOINT:
+          {
+            const { port1, port2 } = new MessageChannel();
+            expose(obj, port2);
+            returnValue = transfer(port1, [port1]);
+          }
+          break;
+        default:
+          console.warn("Unrecognized message", ev.data);
+      }
+    } catch (e) {
+      returnValue = e;
+      throwSet.add(e);
+    }
+    const [wireValue, transferables] = toWireValue(returnValue);
+    ep.postMessage({ ...wireValue, id }, transferables);
+  }) as any);
+  if (ep.start) {
+    ep.start();
+  }
+}
+
+export function wrap(ep: Endpoint): Remote {
+  return createProxy(ep) as any;
+}
+
+function createProxy(
+  ep: Endpoint,
+  path: (string | number | symbol)[] = []
+): Remote {
+  const proxy: Function = new Proxy(new Function(), {
+    get(_target, prop) {
+      if (prop === "then") {
+        if (path.length === 0) {
+          return { then: () => proxy };
+        }
+        const r = requestResponseMessage(ep, {
+          type: MessageType.GET,
+          path: path.map(p => p.toString())
+        }).then(fromWireValue);
+        return r.then.bind(r);
+      }
+      return createProxy(ep, [...path, prop]);
+    },
+    set(_target, prop, rawValue) {
+      // FIXME: ES6 Proxy Handler `set` methods are supposed to return a
+      // boolean. To show good will, we return true asynchronously ¯\_(ツ)_/¯
+      const [value, transferables] = toWireValue(rawValue);
+      return requestResponseMessage(
+        ep,
+        {
+          type: MessageType.SET,
+          path: [...path, prop].map(p => p.toString()),
+          value
+        },
+        transferables
+      ).then(fromWireValue) as any;
+    },
+    apply(_target, _thisArg, rawArgumentList) {
+      const last = path[path.length - 1];
+      if ((last as any) === createEndpoint) {
+        return requestResponseMessage(ep, {
+          type: MessageType.ENDPOINT
+        }).then(fromWireValue);
+      }
+      // We just pretend that `bind()` didn’t happen.
+      if (last === "bind") {
+        return createProxy(ep, path.slice(0, -1));
+      }
+      const [argumentList, transferables] = processArguments(rawArgumentList);
+      return requestResponseMessage(
+        ep,
+        {
+          type: MessageType.APPLY,
+          path: path.map(p => p.toString()),
+          argumentList
+        },
+        transferables
+      ).then(fromWireValue);
+    },
+    construct(_target, rawArgumentList) {
+      const [argumentList, transferables] = processArguments(rawArgumentList);
+      return requestResponseMessage(
+        ep,
+        {
+          type: MessageType.CONSTRUCT,
+          path: path.map(p => p.toString()),
+          argumentList
+        },
+        transferables
+      ).then(fromWireValue);
+    }
+  });
+  return proxy as any;
+}
+
+function myFlat(arr: (T | T[])[]): T[] {
+  return Array.prototype.concat.apply([], arr);
+}
+
+function processArguments(argumentList: any[]): [WireValue[], any[]] {
+  const processed = argumentList.map(toWireValue);
+  return [processed.map(v => v[0]), myFlat(processed.map(v => v[1]))];
+}
+
+const transferCache = new WeakMap();
+export function transfer(obj: any, transfers: any[]) {
+  transferCache.set(obj, transfers);
+  return obj;
+}
+
+export function proxy(obj: T): T & { [proxyMarker]: true } {
+  return Object.assign(obj, { [proxyMarker]: true }) as any;
+}
+
+export function windowEndpoint(w: Window, context = self): Endpoint {
+  return {
+    postMessage: (msg: any, transferables: any[]) =>
+      w.postMessage(msg, "*", transferables),
+    addEventListener: context.addEventListener.bind(context),
+    removeEventListener: context.removeEventListener.bind(context)
+  };
+}
+
+function toWireValue(value: any): [WireValue, any[]] {
+  for (const [name, handler] of transferHandlers) {
+    if (handler.canHandle(value)) {
+      const [serializedValue, transferables] = handler.serialize(value);
+      return [
+        {
+          type: WireValueType.HANDLER,
+          name,
+          value: serializedValue
+        },
+        transferables
+      ];
+    }
+  }
+  return [
+    {
+      type: WireValueType.RAW,
+      value
+    },
+    transferCache.get(value) || []
+  ];
+}
+
+function fromWireValue(value: WireValue): any {
+  switch (value.type) {
+    case WireValueType.HANDLER:
+      return transferHandlers.get(value.name)!.deserialize(value.value);
+    case WireValueType.RAW:
+      return value.value;
+  }
+}
+
+function requestResponseMessage(
+  ep: Endpoint,
+  msg: Message,
+  transfers?: any[]
+): Promise {
+  return new Promise(resolve => {
+    const id = generateUUID();
+    ep.addEventListener("message", function l(ev: MessageEvent) {
+      if (!ev.data || !ev.data.id || ev.data.id !== id) {
+        return;
+      }
+      ep.removeEventListener("message", l as any);
+      resolve(ev.data);
+    } as any);
+    if (ep.start) {
+      ep.start();
+    }
+    ep.postMessage({ id, ...msg }, transfers);
+  });
+}
+
+function generateUUID(): string {
+  return new Array(4)
+    .fill(0)
+    .map(() => Math.floor(Math.random() * Number.MAX_SAFE_INTEGER).toString(16))
+    .join("-");
+}
diff --git a/src/node-adapter.ts b/src/node-adapter.ts
new file mode 100644
index 00000000..326f55e2
--- /dev/null
+++ b/src/node-adapter.ts
@@ -0,0 +1,56 @@
+/**
+ * Copyright 2019 Google Inc. All Rights Reserved.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { Endpoint } from "./protocol.js";
+
+export interface NodeEndpoint {
+  postMessage(message: any, transfer?: any[]): void;
+  on(
+    type: string,
+    listener: EventListenerOrEventListenerObject,
+    options?: {}
+  ): void;
+  off(
+    type: string,
+    listener: EventListenerOrEventListenerObject,
+    options?: {}
+  ): void;
+  start?: () => void;
+}
+
+export default function nodeEndpoint(nep: NodeEndpoint): Endpoint {
+  const listeners = new WeakMap();
+  return {
+    postMessage: nep.postMessage.bind(nep),
+    addEventListener: (_, eh) => {
+      const l = (data: any) => {
+        if ("handleEvent" in eh) {
+          eh.handleEvent({ data } as MessageEvent);
+        } else {
+          eh({ data } as MessageEvent);
+        }
+      };
+      nep.on("message", l);
+      listeners.set(eh, l);
+    },
+    removeEventListener: (_, eh) => {
+      const l = listeners.get(eh);
+      if (!l) {
+        return;
+      }
+      nep.off("message", l);
+      listeners.delete(eh);
+    },
+    start: nep.start && nep.start.bind(nep)
+  };
+}
diff --git a/src/protocol.ts b/src/protocol.ts
new file mode 100644
index 00000000..616cd3c9
--- /dev/null
+++ b/src/protocol.ts
@@ -0,0 +1,98 @@
+/**
+ * Copyright 2019 Google Inc. All Rights Reserved.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+export interface Endpoint {
+  postMessage(message: any, transfer?: any[]): void;
+  addEventListener(
+    type: string,
+    listener: EventListenerOrEventListenerObject,
+    options?: {}
+  ): void;
+  removeEventListener(
+    type: string,
+    listener: EventListenerOrEventListenerObject,
+    options?: {}
+  ): void;
+  start?: () => void;
+}
+
+export const enum WireValueType {
+  RAW,
+  PROXY,
+  THROW,
+  HANDLER
+}
+
+export interface RawWireValue {
+  id?: string;
+  type: WireValueType.RAW;
+  value: {};
+}
+
+export interface HandlerWireValue {
+  id?: string;
+  type: WireValueType.HANDLER;
+  name: string;
+  value: {};
+}
+
+export type WireValue = RawWireValue | HandlerWireValue;
+
+export type MessageID = string;
+
+export const enum MessageType {
+  GET,
+  SET,
+  APPLY,
+  CONSTRUCT,
+  ENDPOINT
+}
+
+export interface GetMessage {
+  id?: MessageID;
+  type: MessageType.GET;
+  path: string[];
+}
+
+export interface SetMessage {
+  id?: MessageID;
+  type: MessageType.SET;
+  path: string[];
+  value: WireValue;
+}
+
+export interface ApplyMessage {
+  id?: MessageID;
+  type: MessageType.APPLY;
+  path: string[];
+  argumentList: WireValue[];
+}
+
+export interface ConstructMessage {
+  id?: MessageID;
+  type: MessageType.CONSTRUCT;
+  path: string[];
+  argumentList: WireValue[];
+}
+
+export interface EndpointMessage {
+  id?: MessageID;
+  type: MessageType.ENDPOINT;
+}
+
+export type Message =
+  | GetMessage
+  | SetMessage
+  | ApplyMessage
+  | ConstructMessage
+  | EndpointMessage;
diff --git a/structured-clone-table.md b/structured-clone-table.md
new file mode 100644
index 00000000..f7fe3f25
--- /dev/null
+++ b/structured-clone-table.md
@@ -0,0 +1,27 @@
+# Behavior of [Structured Clone]
+
+[Structured clone] is JavaScript’s algorithm to create “deep copies” of values. It is used for `postMessage()` and therefore is used extensively under the hood with Comlink. By default, every function parameter and function return value is structured cloned. Here is a table of how the structured clone algorithm handles different kinds of values. Or to phrase it differently: If you pass a value from the left side as a parameter into a proxy’d function, the actual function code will get what is listed on the right side.
+
+| Input                      |     Output     | Notes                                                                                        |
+| -------------------------- | :------------: | -------------------------------------------------------------------------------------------- |
+| `[1,2,3]`                  |   `[1,2,3]`    | Full copy                                                                                    |
+| `{a: 1, b: 2}`             | `{a: 1, b: 2}` | Full copy                                                                                    |
+| `{a: 1, b() { return 2; }` |    `{a: 1}`    | Full copy, functions omitted                                                                 |
+| `new MyClass()`            |    `{...}`     | Just the properties                                                                          |
+| `Map`                      |     `Map`      | [`Map`][map] is structured cloneable                                                         |
+| `Set`                      |     `Set`      | [`Set`][set] is structured cloneable                                                         |
+| `ArrayBuffer`              | `ArrayBuffer`  | [`ArrayBuffer`][arraybuffer] is structured cloneable                                         |
+| `Uint32Array`              | `Uint32Array`  | [`Uint32Array`][uint32array] and all the other typed arrays are structured cloneable         |
+| `Event`                    |       ❌       |                                                                                              |
+| Any DOM element            |       ❌       |                                                                                              |
+| `MessagePort`              |       ❌       | Only transferable, not structured cloneable                                                  |
+| `Request`                  |       ❌       |                                                                                              |
+| `Response`                 |       ❌       |                                                                                              |
+| `ReadableStream`           |       ❌       | [Streams are planned to be transferable][transferable streams], but not structured cloneable |
+
+[structured clone]: https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Structured_clone_algorithm
+[map]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map
+[set]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set
+[arraybuffer]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/ArrayBuffer
+[uint32array]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Uint32Array
+[transferable streams]: https://github.com/whatwg/streams/blob/master/transferable-streams-explainer.md
diff --git a/tests/context.html b/tests/context.html
deleted file mode 100644
index 91ad7fed..00000000
--- a/tests/context.html
+++ /dev/null
@@ -1,39 +0,0 @@
-
-
-
-
-
-  
-  
-  
-
-
-  
-  
-  
-  
-  %SCRIPTS%
-  
-  
-  
-
-
diff --git a/tests/debug.html b/tests/debug.html
deleted file mode 100644
index 6ebd9b8f..00000000
--- a/tests/debug.html
+++ /dev/null
@@ -1,41 +0,0 @@
-
-
-
-
-
-%X_UA_COMPATIBLE%
-  Karma DEBUG RUNNER
-  
-  
-  
-
-
-  
-  
-  
-  
-  
-  %SCRIPTS%
-  
-  
-  
-
-
diff --git a/tests/fixtures/iframe.html b/tests/fixtures/iframe.html
index 1c0ffea1..8f7c38f4 100644
--- a/tests/fixtures/iframe.html
+++ b/tests/fixtures/iframe.html
@@ -1,5 +1,5 @@
 
diff --git a/tests/fixtures/worker.js b/tests/fixtures/worker.js
index 11efd337..64f65de1 100644
--- a/tests/fixtures/worker.js
+++ b/tests/fixtures/worker.js
@@ -13,4 +13,4 @@
 
 importScripts("/base/dist/umd/comlink.js");
 
-Comlink.expose((a, b) => a + b, self);
+Comlink.expose((a, b) => a + b);
diff --git a/tests/iframe.comlink.test.js b/tests/iframe.comlink.test.js
index df311f51..97f1f5f2 100644
--- a/tests/iframe.comlink.test.js
+++ b/tests/iframe.comlink.test.js
@@ -11,7 +11,7 @@
  * limitations under the License.
  */
 
-import * as Comlink from "/base/dist/comlink.js";
+import * as Comlink from "/base/dist/esm/comlink.mjs";
 
 describe("Comlink across iframes", function() {
   beforeEach(function() {
@@ -27,7 +27,7 @@ describe("Comlink across iframes", function() {
   });
 
   it("can communicate", async function() {
-    const proxy = Comlink.proxy(this.ifr.contentWindow);
+    const proxy = Comlink.wrap(Comlink.windowEndpoint(this.ifr.contentWindow));
     expect(await proxy(1, 3)).to.equal(4);
   });
 });
diff --git a/tests/messagechanneladapter.test.js b/tests/messagechanneladapter.test.js
deleted file mode 100644
index 2c31a3b6..00000000
--- a/tests/messagechanneladapter.test.js
+++ /dev/null
@@ -1,116 +0,0 @@
-/**
- * Copyright 2017 Google Inc. All Rights Reserved.
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *     http://www.apache.org/licenses/LICENSE-2.0
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-import * as MessageChannelAdapter from "/base/dist/messagechanneladapter.js";
-
-describe("MessageChannelAdapter", function() {
-  beforeEach(function() {
-    let port1, port2;
-    port1 = port2 = {
-      get other() {
-        if (this === port1) return port2;
-        else return port1;
-      },
-      send(msg) {
-        for (const callback of this.other.callbacks) callback({ data: msg });
-      },
-      addEventListener(type, callback) {
-        if (type.toLowerCase() !== "message") return;
-        this.callbacks.push(callback);
-      }
-    };
-    port1.callbacks = [];
-    port2.callbacks = [];
-    this.port1 = port1;
-    this.port2 = port2;
-    this.wrappedPort1 = MessageChannelAdapter.wrap(this.port1);
-    this.wrappedPort2 = MessageChannelAdapter.wrap(this.port2);
-  });
-
-  it("can send messages", function(done) {
-    this.wrappedPort2.addEventListener("message", event => {
-      expect(event.data).to.equal("ohai");
-      done();
-    });
-    this.wrappedPort2.start();
-    this.wrappedPort1.postMessage("ohai");
-  });
-
-  it("can send structurally cloneable objects", function(done) {
-    const obj = {
-      a: {
-        b: {
-          c: "hai"
-        },
-        d: true
-      },
-      f: 0.2
-    };
-
-    this.wrappedPort2.addEventListener("message", event => {
-      expect(event.data).to.deep.equal(obj);
-      done();
-    });
-    this.wrappedPort2.start();
-    this.wrappedPort1.postMessage(obj);
-  });
-
-  it("can transfer MessagePorts", function(done) {
-    let count = 0;
-    function inc() {
-      count++;
-      if (count == 2) done();
-    }
-    const { port1, port2 } = new MessageChannel();
-    this.wrappedPort2.addEventListener("message", event => {
-      if (event.data === "outer") inc();
-      else if (event.data.port)
-        event.data.port.onmessage = event => {
-          if (event.data === "inner") inc();
-        };
-    });
-    this.wrappedPort2.start();
-    this.wrappedPort1.postMessage({ port: port2 }, [port2]);
-    port1.postMessage("inner");
-    this.wrappedPort1.postMessage("outer");
-  });
-
-  const hasBroadcastChannel = _ => 'BroadcastChannel' in self;
-  guardedIt(hasBroadcastChannel)("works with BroadcastChannel", function(done) {
-    function augmentPort(name) {
-      const bc = new BroadcastChannel(name);
-      bc.send = msg => bc.postMessage(msg);
-      return MessageChannelAdapter.wrap(bc);
-    }
-    const b1 = augmentPort("topic");
-    const b2 = augmentPort("topic");
-    const b3 = augmentPort("topic");
-    [b1, b2, b3].forEach(b => b.start());
-
-    let count = 1;
-    b3.addEventListener('message', ev => {
-      expect(ev.data).to.equal(`hai${count}`);
-      count++;
-      if(count === 3) {
-        done();
-      }
-    });
-    b1.postMessage('hai1');
-    b2.postMessage('hai2');
-  });
-});
-
-function guardedIt(f) {
-  return f() ? it : xit;
-}
-
diff --git a/tests/same_window.comlink.test.js b/tests/same_window.comlink.test.js
index 2d9aa3a1..94d3bd44 100644
--- a/tests/same_window.comlink.test.js
+++ b/tests/same_window.comlink.test.js
@@ -11,11 +11,11 @@
  * limitations under the License.
  */
 
-import * as Comlink from "/base/dist/comlink.js";
+import * as Comlink from "/base/dist/esm/comlink.mjs";
 
 class SampleClass {
-  constructor() {
-    this._counter = 1;
+  constructor(counterInit = 1) {
+    this._counter = counterInit;
     this._promise = Promise.resolve(4);
   }
 
@@ -52,7 +52,7 @@ class SampleClass {
   }
 
   proxyFunc() {
-    return Comlink.proxyValue({
+    return Comlink.proxy({
       counter: 0,
       inc() {
         this.counter++;
@@ -68,127 +68,133 @@ class SampleClass {
 describe("Comlink in the same realm", function() {
   beforeEach(function() {
     const { port1, port2 } = new MessageChannel();
+    port1.start();
+    port2.start();
     this.port1 = port1;
     this.port2 = port2;
   });
 
-  it("catched invalid endpoints", async function() {
-    expect(_ => Comlink.proxy({})).to.throw();
-    expect(_ => Comlink.expose({}, {})).to.throw();
-  });
-
   it("can work with objects", async function() {
-    const proxy = Comlink.proxy(this.port1);
+    const thing = Comlink.wrap(this.port1);
     Comlink.expose({ value: 4 }, this.port2);
-    expect(await proxy.value).to.equal(4);
+    expect(await thing.value).to.equal(4);
   });
 
   it("can work functions on an object", async function() {
-    const proxy = Comlink.proxy(this.port1);
+    const thing = Comlink.wrap(this.port1);
     Comlink.expose({ f: _ => 4 }, this.port2);
-    expect(await proxy.f()).to.equal(4);
+    expect(await thing.f()).to.equal(4);
   });
 
   it("can work with functions", async function() {
-    const proxy = Comlink.proxy(this.port1);
+    const thing = Comlink.wrap(this.port1);
     Comlink.expose(_ => 4, this.port2);
-    expect(await proxy()).to.equal(4);
+    expect(await thing()).to.equal(4);
   });
 
   it("can work with objects that have undefined properties", async function() {
-    const proxy = Comlink.proxy(this.port1);
+    const thing = Comlink.wrap(this.port1);
     Comlink.expose({ x: undefined }, this.port2);
-    expect(await proxy.x).to.be.undefined;
+    expect(await thing.x).to.be.undefined;
   });
 
   it("can keep the stack and message of thrown errors", async function() {
     let stack;
-    const proxy = Comlink.proxy(this.port1);
+    const thing = Comlink.wrap(this.port1);
     Comlink.expose(_ => {
       const error = Error("OMG");
       stack = error.stack;
       throw error;
     }, this.port2);
     try {
-      await proxy();
-      fail("Should have thrown");
+      await thing();
+      throw "Should have thrown";;
     } catch (err) {
+      expect(err).to.not.eq("Should have thrown");
       expect(err.message).to.equal("OMG");
       expect(err.stack).to.equal(stack);
     }
   });
 
   it("can rethrow non-error objects", async function() {
-    const proxy = Comlink.proxy(this.port1);
+    const thing = Comlink.wrap(this.port1);
     Comlink.expose(_ => {
-      throw {test: true};
+      throw { test: true };
     }, this.port2);
     try {
-      await proxy();
-      fail("Should have thrown");
+      await thing();
+      throw "Should have thrown";
     } catch (err) {
+      expect(err).to.not.eq("Should have thrown");
       expect(err.test).to.equal(true);
     }
   });
 
   it("can work with parameterized functions", async function() {
-    const proxy = Comlink.proxy(this.port1);
+    const thing = Comlink.wrap(this.port1);
     Comlink.expose((a, b) => a + b, this.port2);
-    expect(await proxy(1, 3)).to.equal(4);
+    expect(await thing(1, 3)).to.equal(4);
   });
 
   it("can work with functions that return promises", async function() {
-    const proxy = Comlink.proxy(this.port1);
+    const thing = Comlink.wrap(this.port1);
     Comlink.expose(
       _ => new Promise(resolve => setTimeout(_ => resolve(4), 100)),
       this.port2
     );
-    expect(await proxy()).to.equal(4);
+    expect(await thing()).to.equal(4);
   });
 
   it("can work with classes", async function() {
-    const proxy = Comlink.proxy(this.port1);
+    const thing = Comlink.wrap(this.port1);
     Comlink.expose(SampleClass, this.port2);
-    const instance = await new proxy();
+    const instance = await new thing();
     expect(await instance.method()).to.equal(4);
   });
 
+  it("can pass parameters to class constructor", async function() {
+    const thing = Comlink.wrap(this.port1);
+    Comlink.expose(SampleClass, this.port2);
+    const instance = await new thing(23);
+    expect(await instance.counter).to.equal(23);
+  });
+
   it("can access a class in an object", async function() {
-    const proxy = Comlink.proxy(this.port1);
+    const thing = Comlink.wrap(this.port1);
     Comlink.expose({ SampleClass }, this.port2);
-    const instance = await new proxy.SampleClass();
+    const instance = await new thing.SampleClass();
     expect(await instance.method()).to.equal(4);
   });
 
   it("can work with class instance properties", async function() {
-    const proxy = Comlink.proxy(this.port1);
+    const thing = Comlink.wrap(this.port1);
     Comlink.expose(SampleClass, this.port2);
-    const instance = await new proxy();
+    const instance = await new thing();
     expect(await instance._counter).to.equal(1);
   });
 
   it("can set class instance properties", async function() {
-    const proxy = Comlink.proxy(this.port1);
+    const thing = Comlink.wrap(this.port1);
     Comlink.expose(SampleClass, this.port2);
-    const instance = await new proxy();
+    const instance = await new thing();
     expect(await instance._counter).to.equal(1);
     await (instance._counter = 4);
     expect(await instance._counter).to.equal(4);
   });
 
   it("can work with class instance methods", async function() {
-    const proxy = Comlink.proxy(this.port1);
+    const thing = Comlink.wrap(this.port1);
     Comlink.expose(SampleClass, this.port2);
-    const instance = await new proxy();
+    const instance = await new thing();
     expect(await instance.counter).to.equal(1);
     await instance.increaseCounter();
     expect(await instance.counter).to.equal(2);
   });
 
   it("can handle throwing class instance methods", async function() {
-    const proxy = Comlink.proxy(this.port1);
+    const thing = Comlink.wrap(this.port1);
     Comlink.expose(SampleClass, this.port2);
-    const instance = await new proxy();
+    const instance = await new thing();
     return instance
       .throwsAnError()
       .then(_ => Promise.reject())
@@ -196,9 +202,9 @@ describe("Comlink in the same realm", function() {
   });
 
   it("can work with class instance methods multiple times", async function() {
-    const proxy = Comlink.proxy(this.port1);
+    const thing = Comlink.wrap(this.port1);
     Comlink.expose(SampleClass, this.port2);
-    const instance = await new proxy();
+    const instance = await new thing();
     expect(await instance.counter).to.equal(1);
     await instance.increaseCounter();
     await instance.increaseCounter(5);
@@ -206,42 +212,42 @@ describe("Comlink in the same realm", function() {
   });
 
   it("can work with class instance methods that return promises", async function() {
-    const proxy = Comlink.proxy(this.port1);
+    const thing = Comlink.wrap(this.port1);
     Comlink.expose(SampleClass, this.port2);
-    const instance = await new proxy();
+    const instance = await new thing();
     expect(await instance.promiseFunc()).to.equal(4);
   });
 
   it("can work with class instance properties that are promises", async function() {
-    const proxy = Comlink.proxy(this.port1);
+    const thing = Comlink.wrap(this.port1);
     Comlink.expose(SampleClass, this.port2);
-    const instance = await new proxy();
+    const instance = await new thing();
     expect(await instance._promise).to.equal(4);
   });
 
   it("can work with class instance getters that are promises", async function() {
-    const proxy = Comlink.proxy(this.port1);
+    const thing = Comlink.wrap(this.port1);
     Comlink.expose(SampleClass, this.port2);
-    const instance = await new proxy();
+    const instance = await new thing();
     expect(await instance.promise).to.equal(4);
   });
 
   it("can work with static class properties", async function() {
-    const proxy = Comlink.proxy(this.port1);
+    const thing = Comlink.wrap(this.port1);
     Comlink.expose(SampleClass, this.port2);
-    expect(await proxy.SOME_NUMBER).to.equal(4);
+    expect(await thing.SOME_NUMBER).to.equal(4);
   });
 
   it("can work with static class methods", async function() {
-    const proxy = Comlink.proxy(this.port1);
+    const thing = Comlink.wrap(this.port1);
     Comlink.expose(SampleClass, this.port2);
-    expect(await proxy.ADD(1, 3)).to.equal(4);
+    expect(await thing.ADD(1, 3)).to.equal(4);
   });
 
   it("can work with bound class instance methods", async function() {
-    const proxy = Comlink.proxy(this.port1);
+    const thing = Comlink.wrap(this.port1);
     Comlink.expose(SampleClass, this.port2);
-    const instance = await new proxy();
+    const instance = await new thing();
     expect(await instance.counter).to.equal(1);
     const method = instance.increaseCounter.bind(instance);
     await method();
@@ -249,55 +255,64 @@ describe("Comlink in the same realm", function() {
   });
 
   it("can work with class instance getters", async function() {
-    const proxy = Comlink.proxy(this.port1);
+    const thing = Comlink.wrap(this.port1);
     Comlink.expose(SampleClass, this.port2);
-    const instance = await new proxy();
+    const instance = await new thing();
     expect(await instance.counter).to.equal(1);
     await instance.increaseCounter();
     expect(await instance.counter).to.equal(2);
   });
 
   it("can work with class instance setters", async function() {
-    const proxy = Comlink.proxy(this.port1);
+    const thing = Comlink.wrap(this.port1);
     Comlink.expose(SampleClass, this.port2);
-    const instance = await new proxy();
+    const instance = await new thing();
     expect(await instance._counter).to.equal(1);
     await (instance.counter = 4);
     expect(await instance._counter).to.equal(4);
   });
 
-  const hasBroadcastChannel = _ => ('BroadcastChannel' in self);
-  guardedIt(hasBroadcastChannel)("will work with BroadcastChannel", async function() {
-    const b1 = new BroadcastChannel("comlink_bc_test");
-    const b2 = new BroadcastChannel("comlink_bc_test");
-    const proxy = Comlink.proxy(b1);
-    Comlink.expose(b => 40 + b, b2);
-    expect(await proxy(2)).to.equal(42);
-  });
+  const hasBroadcastChannel = _ => "BroadcastChannel" in self;
+  guardedIt(hasBroadcastChannel)(
+    "will work with BroadcastChannel",
+    async function() {
+      const b1 = new BroadcastChannel("comlink_bc_test");
+      const b2 = new BroadcastChannel("comlink_bc_test");
+      const thing = Comlink.wrap(b1);
+      Comlink.expose(b => 40 + b, b2);
+      expect(await thing(2)).to.equal(42);
+    }
+  );
 
   // Buffer transfers seem to have regressed in Safari 11.1, it’s fixed in 11.2.
-  const isNotSafari11_1 = _ => !/11\.1(\.[0-9]+)? Safari/.test(navigator.userAgent);
+  const isNotSafari11_1 = _ =>
+    !/11\.1(\.[0-9]+)? Safari/.test(navigator.userAgent);
   guardedIt(isNotSafari11_1)("will transfer buffers", async function() {
-    const proxy = Comlink.proxy(this.port1);
+    const thing = Comlink.wrap(this.port1);
     Comlink.expose(b => b.byteLength, this.port2);
     const buffer = new Uint8Array([1, 2, 3]).buffer;
-    expect(await proxy(buffer)).to.equal(3);
+    expect(await thing(Comlink.transfer(buffer, [buffer]))).to.equal(3);
     expect(buffer.byteLength).to.equal(0);
   });
 
-  guardedIt(isNotSafari11_1)("will transfer deeply nested buffers", async function() {
-    const proxy = Comlink.proxy(this.port1);
-    Comlink.expose(a => a.b.c.d.byteLength, this.port2);
-    const buffer = new Uint8Array([1, 2, 3]).buffer;
-    expect(await proxy({ b: { c: { d: buffer } } })).to.equal(3);
-    expect(buffer.byteLength).to.equal(0);
-  });
+  guardedIt(isNotSafari11_1)(
+    "will transfer deeply nested buffers",
+    async function() {
+      const thing = Comlink.wrap(this.port1);
+      Comlink.expose(a => a.b.c.d.byteLength, this.port2);
+      const buffer = new Uint8Array([1, 2, 3]).buffer;
+      expect(
+        await thing(Comlink.transfer({ b: { c: { d: buffer } } }, [buffer]))
+      ).to.equal(3);
+      expect(buffer.byteLength).to.equal(0);
+    }
+  );
 
   it("will transfer a message port", async function() {
-    const proxy = Comlink.proxy(this.port1);
+    const thing = Comlink.wrap(this.port1);
     Comlink.expose(a => a.postMessage("ohai"), this.port2);
     const { port1, port2 } = new MessageChannel();
-    await proxy(port2);
+    await thing(Comlink.transfer(port2, [port2]));
     return new Promise(resolve => {
       port1.onmessage = event => {
         expect(event.data).to.equal("ohai");
@@ -306,11 +321,11 @@ describe("Comlink in the same realm", function() {
     });
   });
 
-  it("will proxy marked return values", async function() {
-    const proxy = Comlink.proxy(this.port1);
+  it("will wrap marked return values", async function() {
+    const thing = Comlink.wrap(this.port1);
     Comlink.expose(
       _ =>
-        Comlink.proxyValue({
+        Comlink.proxy({
           counter: 0,
           inc() {
             this.counter += 1;
@@ -318,24 +333,24 @@ describe("Comlink in the same realm", function() {
         }),
       this.port2
     );
-    const obj = await proxy();
+    const obj = await thing();
     expect(await obj.counter).to.equal(0);
     await obj.inc();
     expect(await obj.counter).to.equal(1);
   });
 
-  it("will proxy marked return values from class instance methods", async function() {
-    const proxy = Comlink.proxy(this.port1);
+  it("will wrap marked return values from class instance methods", async function() {
+    const thing = Comlink.wrap(this.port1);
     Comlink.expose(SampleClass, this.port2);
-    const instance = await new proxy();
+    const instance = await new thing();
     const obj = await instance.proxyFunc();
     expect(await obj.counter).to.equal(0);
     await obj.inc();
     expect(await obj.counter).to.equal(1);
   });
 
-  it("will proxy marked parameter values", async function() {
-    const proxy = Comlink.proxy(this.port1);
+  it("will wrap marked parameter values", async function() {
+    const thing = Comlink.wrap(this.port1);
     const local = {
       counter: 0,
       inc() {
@@ -346,62 +361,79 @@ describe("Comlink in the same realm", function() {
       await f.inc();
     }, this.port2);
     expect(local.counter).to.equal(0);
-    await proxy(Comlink.proxyValue(local));
+    await thing(Comlink.proxy(local));
     expect(await local.counter).to.equal(1);
   });
 
-  it("will proxy marked parameter values, simple function", async function() {
-    const proxy = Comlink.proxy(this.port1);
+  it("will wrap marked assignments", function(done) {
+    const thing = Comlink.wrap(this.port1);
+    const obj = {
+      onready: null,
+      call() {
+        this.onready();
+      }
+    };
+    Comlink.expose(obj, this.port2);
+
+    thing.onready = Comlink.proxy(() => done());
+    thing.call();
+  });
+
+  it("will wrap marked parameter values, simple function", async function() {
+    const thing = Comlink.wrap(this.port1);
     Comlink.expose(async function(f) {
       await f();
     }, this.port2);
     // Weird code because Mocha
     await new Promise(async resolve => {
-      proxy(Comlink.proxyValue(_ => resolve()));
+      thing(Comlink.proxy(_ => resolve()));
     });
   });
 
-  it("will proxy deeply nester values", async function() {
-    const proxy = Comlink.proxy(this.port1);
+  it("will wrap multiple marked parameter values, simple function", async function() {
+    const thing = Comlink.wrap(this.port1);
+    Comlink.expose(async function(f1, f2, f3) {
+      return await f1() + await f2() + await f3();
+    }, this.port2);
+    // Weird code because Mocha
+    expect(
+      await thing(
+        Comlink.proxy(_ => 1),
+        Comlink.proxy(_ => 2),
+        Comlink.proxy(_ => 3)
+      )
+    ).to.equal(6);
+  });
+
+  it("will proxy deeply nested values", async function() {
+    const thing = Comlink.wrap(this.port1);
     const obj = {
       a: {
         v: 4
       },
-      b: Comlink.proxyValue({
-        v: 4
+      b: Comlink.proxy({
+        v: 5
       })
     };
     Comlink.expose(obj, this.port2);
 
-    const a = await proxy.a;
-    const b = await proxy.b;
-    expect(await a.v).to.equal(4);
-    expect(await b.v).to.equal(4);
-    obj.a.v = 9;
-    obj.b.v = 9;
+    const a = await thing.a;
+    const b = await thing.b;
     expect(await a.v).to.equal(4);
-    expect(await b.v).to.equal(9);
-  });
-
-  it("will proxy values in an array", function(done) {
-    const proxy = Comlink.proxy(this.port1);
-    const obj = {
-      async someFunc(v) {
-        await v[0]();
-      }
-    };
-    Comlink.expose(obj, this.port2);
-
-    proxy.someFunc([Comlink.proxyValue(_ => done())]);
+    expect(await b.v).to.equal(5);
+    await (a.v = 8);
+    await (b.v = 9);
+    expect(await thing.a.v).to.equal(4);
+    expect(await thing.b.v).to.equal(9);
   });
 
   it("will handle undefined parameters", async function() {
-    const proxy = Comlink.proxy(this.port1);
+    const thing = Comlink.wrap(this.port1);
     Comlink.expose({ f: _ => 4 }, this.port2);
-    expect(await proxy.f(undefined)).to.equal(4);
+    expect(await thing.f(undefined)).to.equal(4);
   });
 
-  it("can handle destructuring on the proxy side", async function() {
+  it("can handle destructuring", async function() {
     Comlink.expose(
       {
         a: 4,
@@ -414,16 +446,54 @@ describe("Comlink in the same realm", function() {
       },
       this.port2
     );
-    const { a, b, c } = Comlink.proxy(this.port1);
+    const { a, b, c } = Comlink.wrap(this.port1);
     expect(await a).to.equal(4);
     expect(await b).to.equal(5);
     expect(await c()).to.equal(6);
   });
 
-  it("can proxy with a given target", async function() {
-    const proxy = Comlink.proxy(this.port1, { value: {} });
-    Comlink.expose({ value: 4 }, this.port2);
-    expect(await proxy.value).to.equal(4);
+  it("lets users define transfer handlers", function(done) {
+    Comlink.transferHandlers.set("event", {
+      canHandle(obj) {
+        return obj instanceof Event
+      },
+      serialize(obj) {
+        return [obj.data, []];
+      },
+      deserialize(data) {
+        return new MessageEvent("message", {data});
+      }
+    });
+
+    Comlink.expose((ev) => {
+      expect(ev).to.be.an.instanceOf(Event);
+      expect(ev.data).to.deep.equal({a: 1});
+      done();
+    }, this.port1);
+    const thing = Comlink.wrap(this.port2);
+
+    const {port1, port2} = new MessageChannel();
+    port1.addEventListener("message", thing.bind(this));
+    port1.start();
+    port2.postMessage({a: 1});
+  });
+
+  it("can tunnels a new endpoint with createEndpoint", async function() {
+    Comlink.expose({
+        a: 4,
+        c() {
+          return 5;
+        }
+      },
+      this.port2
+    );
+    const proxy = Comlink.wrap(this.port1);
+    const otherEp = await proxy[Comlink.createEndpoint]();
+    const otherProxy = Comlink.wrap(otherEp);
+    expect(await otherProxy.a).to.equal(4);
+    expect(await proxy.a).to.equal(4);
+    expect(await otherProxy.c()).to.equal(5);
+    expect(await proxy.c()).to.equal(5);
   });
 });
 
diff --git a/tests/type-checks.ts b/tests/type-checks.ts
new file mode 100644
index 00000000..74c82505
--- /dev/null
+++ b/tests/type-checks.ts
@@ -0,0 +1,54 @@
+import { assert, Has, NotHas, IsExact } from "conditional-type-checks";
+
+import * as Comlink from "../src/comlink.js";
+
+async function test1() {
+  function simpleNumberFunction() {
+    return 4;
+  }
+
+  const proxy = Comlink.wrap(0 as any);
+  const v = proxy();
+  assert>>(true);
+}
+
+async function test2() {
+  function simpleObjectFunction() {
+    return { a: 3 };
+  }
+
+  const proxy = Comlink.wrap(0 as any);
+  const v = await proxy();
+  assert>(true);
+}
+
+async function test3() {
+  function functionWithProxy() {
+    return Comlink.proxy({ a: 3 });
+  }
+
+  const proxy = Comlink.wrap(0 as any);
+  const subproxy = await proxy();
+  const prop = subproxy.a;
+  assert>>(true);
+}
+
+async function test4() {
+  class X {
+    static staticFunc() {
+      return 4;
+    }
+    private f = 4;
+    public g = 9;
+    sayHi() {
+      return "hi";
+    }
+  }
+
+  const proxy = Comlink.wrap(0 as any);
+  assert Promise }>>(true);
+  const instance = await new proxy();
+  assert Promise }>>(true);
+  assert }>>(true);
+  assert }>>(true);
+}
diff --git a/tests/worker.comlink.test.js b/tests/worker.comlink.test.js
index 2cd72dce..23deb17b 100644
--- a/tests/worker.comlink.test.js
+++ b/tests/worker.comlink.test.js
@@ -11,7 +11,7 @@
  * limitations under the License.
  */
 
-import * as Comlink from "/base/dist/comlink.js";
+import * as Comlink from "/base/dist/esm/comlink.mjs";
 
 describe("Comlink across workers", function() {
   beforeEach(function() {
@@ -23,7 +23,14 @@ describe("Comlink across workers", function() {
   });
 
   it("can communicate", async function() {
-    const proxy = Comlink.proxy(this.worker);
+    const proxy = Comlink.wrap(this.worker);
     expect(await proxy(1, 3)).to.equal(4);
   });
+
+  it("can tunnels a new endpoint with createEndpoint", async function() {
+    const proxy = Comlink.wrap(this.worker);
+    const otherEp = await proxy[Comlink.createEndpoint]();
+    const otherProxy = Comlink.wrap(otherEp);
+    expect(await otherProxy(20, 1)).to.equal(21);
+  });
 });
diff --git a/transferhandler.md b/transferhandler.md
deleted file mode 100644
index 508007ad..00000000
--- a/transferhandler.md
+++ /dev/null
@@ -1,40 +0,0 @@
-# TransferHandler
-
-Some types are neither transferable not structurally cloneable and can therefore not be `postMessage`’d. To remedy this, a `TransferHandler` offers a hook into the serialization and deserialization process to allow these types to be used with Comlink. `TransferHandler`s must fulfill the following interface:
-
-- `canHandle(obj)`: Should `true` if this `TransferHandler` is capable of (de)serializing the given object.
-- `serialize(obj)`: Serializes `obj` to something structurally cloneable.
-- `deserialize(obj)`: The inverse of `serialize`.
-
-## Example
-
-One example would be that using an instance of a class as a parameter to a remote function will invoke the function with a simple JSON object. The prototype gets lost when the instance gets structurally cloned. Let’s say the class `ComplexNumber` is used for some calculations. To make sure instances of `ComplexNumber` are handled correctly, the following `TransferHandler` can be used:
-
-```js
-const complexNumberTransferHandler = {
-  canHandle(obj) {
-    return obj instanceof ComplexNumber;
-  },
-  serialize(obj) {
-    return {re: obj.re, im: obj.im};
-  }
-  deserialize(obj) {
-    return new ComplexNumber(obj.re, obj.im);
-  }
-};
-```
-
-This new `TransferHandler` can be registered with Comlink like this:
-
-```js
-Comlink.transferHandlers.set("COMPLEX", complexNumberTransferHandler);
-```
-
-The string can be arbitrary but must be unique across all `TransferHandler`s.
-
-**Note:** The `TransferHandler` must be registered on _both_ sides of the Comlink channel.
-
-To see a more generic example see the [EventListener example] or the [Classes example].
-
-[eventlistener example]: https://github.com/GoogleChromeLabs/comlink/tree/master/docs/examples/eventlistener
-[classes example]: https://github.com/GoogleChromeLabs/comlink/tree/master/docs/examples/classes
diff --git a/tsconfig.json b/tsconfig.json
index 148bb398..9abfbd08 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -1,13 +1,10 @@
 {
   "compilerOptions": {
     /* Basic Options */
-    "target": "es2017" /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', or 'ESNEXT'. */,
-    "module": "es2015" /* Specify module code generation: 'none', commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */,
+    "target": "esnext" /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', or 'ESNEXT'. */,
+    "module": "esnext" /* Specify module code generation: 'none', commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */,
     "lib": [
-      "es2015",
-      "es2017",
-      "es2015.proxy",
-      "esnext.asynciterable",
+      "esnext",
       "dom"
     ] /* Specify library files to be included in the compilation:  */,
     // "allowJs": true,                       /* Allow javascript files to be compiled. */
@@ -25,7 +22,7 @@
     // "isolatedModules": true,               /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */
 
     /* Strict Type-Checking Options */
-    "strict": true /* Enable all strict type-checking options. */
+    "strict": true /* Enable all strict type-checking options. */,
     // "noImplicitAny": true,                 /* Raise error on expressions and declarations with an implied 'any' type. */
     // "strictNullChecks": true,              /* Enable strict null checks. */
     // "noImplicitThis": true,                /* Raise error on 'this' expressions with an implied 'any' type. */
@@ -38,7 +35,7 @@
     // "noFallthroughCasesInSwitch": true,    /* Report errors for fallthrough cases in switch statement. */
 
     /* Module Resolution Options */
-    // "moduleResolution": "node",            /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */
+    "moduleResolution": "node" /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */
     // "baseUrl": "./",                       /* Base directory to resolve non-absolute module names. */
     // "paths": {},                           /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */
     // "rootDirs": [],                        /* List of root folders whose combined content represents the structure of the project at runtime. */