It's not oriented specifically at state-sync'ing, but Comlink[1] comes to mind.<p>> Comlink.wrap(endpoint) and Comlink.expose(value, endpoint?) <i>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. '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 inherently asynchronous. This means that a function that returns a number will now return a promise for a number.</i><p>Creating & exposing an EventEmitter in the main thread then proxy'ing it on each other thread ought create a reasonably similar experience to this Tangle library. The docs themselves discuss some of the extra difficulties if one is trying to proxy DOM Events[2], talking about this as a common usage pattern, but which is complicated by DOM Events not being cloneable.<p>For another example of Comlink in action, there's this blog post on using Redux across threads[3].<p>[1] <a href="https://www.npmjs.com/package/comlink" rel="nofollow">https://www.npmjs.com/package/comlink</a><p>[2] <a href="https://github.com/GoogleChromeLabs/comlink#transfer-handlers-and-event-listeners" rel="nofollow">https://github.com/GoogleChromeLabs/comlink#transfer-handler...</a><p>[3] <a href="https://dassur.ma/things/react-redux-comlink/" rel="nofollow">https://dassur.ma/things/react-redux-comlink/</a>