{"id":18245015,"url":"https://github.com/edgeapp/yaob","last_synced_at":"2025-06-15T22:06:03.346Z","repository":{"id":32863344,"uuid":"139633536","full_name":"EdgeApp/yaob","owner":"EdgeApp","description":"Yet Another Object Bridge","archived":false,"fork":false,"pushed_at":"2023-12-11T18:25:12.000Z","size":506,"stargazers_count":8,"open_issues_count":2,"forks_count":0,"subscribers_count":12,"default_branch":"master","last_synced_at":"2025-03-18T17:36:55.953Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/EdgeApp.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2018-07-03T20:21:56.000Z","updated_at":"2022-10-14T02:39:13.000Z","dependencies_parsed_at":"2024-06-19T19:08:10.914Z","dependency_job_id":"20448725-2fef-4d0b-8028-f763dd742bf0","html_url":"https://github.com/EdgeApp/yaob","commit_stats":null,"previous_names":["airbitz/yaob"],"tags_count":17,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/EdgeApp%2Fyaob","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/EdgeApp%2Fyaob/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/EdgeApp%2Fyaob/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/EdgeApp%2Fyaob/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/EdgeApp","download_url":"https://codeload.github.com/EdgeApp/yaob/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247185116,"owners_count":20897905,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-11-05T09:18:36.107Z","updated_at":"2025-04-04T13:31:58.276Z","avatar_url":"https://github.com/EdgeApp.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# yaob\n\n\u003e Yet Another Object Bridge\n\nNormally Web Workers, Node.js child processes, cross-domain windows, and similar things can only communicate by sending messages back and forth. This is really inconvenient compared to using a normal object-oriented API.\n\nThis library allows software to expose a nice object-oriented API, even if it's trapped behind a messaging interface. It does this by serializing method calls, property changes, and events into a stream of messages that can pass over the interface.\n\nSimilar libraries include:\n\n- [post-robot](https://github.com/krakenjs/post-robot) - Only works with iframes and popup windows.\n- [remote-lib](http://www.remotelib.com/) - Rich functionality, but requires ES6 proxy support.\n\nYaob is small (under 4K minified and gzipped) and doesn't require any ES6 features besides a `Promise` implementation.\n\n[![Standard.js Style Guide](https://img.shields.io/badge/code_style-standard-brightgreen.svg)](https://standardjs.com)\n\n## Using\n\nIn a typical use-case, a client process, such as a web app, needs some functionality from a server process, such as a Web Worker. The client launches the server process, and the server process sends back an object that contains its API. The client interacts with the server by calling methods on this object.\n\nThe initial object the server sends across is called the \"root\" object. This object can have methods that return other objects, allowing the server to expose a rich, multi-object API.\n\nThese API objects can also have dynamic properties. If the properties change on the server-side object, `yaob` will update the client-side object to match. It will also generate an event that the client can subscribe to. This way, the client can react to changing server-side values. This feature makes `yaob` unique compared to similar libraries.\n\n### Bridgeable Objects\n\nTo pass over the bridge, objects should inherit from the `Bridgeable` base class. Here is an example:\n\n```js\nimport { Bridgeable } from 'yaob'\n\nclass WorkerApi extends Bridgeable {\n  constructor () {\n    this._multiplier = 2\n  }\n\n  async double (x) {\n    return x * this._multiplier\n  }\n\n  get version () {\n    return '1.0.0'\n  }\n}\n```\n\nThese Bridgeable objects can contain properties, getter functions, and async methods, which the `yaob` library will bridge across the messaging interface. This is the only place functions are allowed. If the API tries to pass function objects directly, `yaob` won't be able to handle them and will throw an error.\n\nThe `yaob` library will not bridge property or method names that begin with an underscore. This means that `this._multiplier` will not be visible to the client. The client will only see the `double` method and the `version` getter. This provides a simple way to make things private.\n\n### Server Side\n\nThe `yaob` library provides a `Bridge` object, which can send objects back and forth. The `Bridge` needs to know how to send and receive messages. For Web Workers, `postMessage` and `onmessage` are the way to do this. Other interfaces, such as Web Sockets, TCP/IP, or Node.js child processes each have their own different ways:\n\n```js\n// worker.js\nimport { Bridge } from 'yaob'\n\n// Create a bridge server, telling it how to send messages out:\nconst server = new Bridge({\n  sendMessage: message =\u003e postMessage(message)\n})\n\n// If the worker gets a message, give it to the bridge server:\nonmessage = event =\u003e server.handleMessage(event.data)\n```\n\nOnce this send \u0026 receive functionality is set up, we can transmit our initial API object:\n\n```js\n// worker.js\nserver.sendRoot(new WorkerApi());\n```\n\nThis is all the server side needs to do.\n\n### Client Side\n\nOn the client side, spin up your worker and connect a `Bridge` to its messaging interface:\n\n```js\nimport { Bridge } from 'yaob'\n\nconst worker = new WebWorker('./worker.js')\n\n// Create the bridge client, telling it how to send messages out:\nconst client = new Bridge({\n  sendMessage: message =\u003e worker.postMessage(message)\n})\n\n// If the worker sends us a message, forward it to the bridge client:\nworker.onmessage = event =\u003e client.handleMessage(event.data)\n```\n\nNow we can wait for the server side to send us the root object, which we can then use:\n\n```js\n// Wait for the server to send us the root object...\nconst root = await client.getRoot()\n\n// Method calls are async:\nexpect(await root.double(1.5)).equals(3)\n\n// Property access is synchronous!\nexpect(root.version).equals('1.0.0')\n\n// Private properties are not visible:\nexpect(root).to.not.have.property('_multiplier')\n```\n\n### Updating properties\n\nAny time a property changes, the server-side object should call `this._update()`. This method is part of the `Bridgeable` base class. It tells the bridge to diff the object's properties and send over the changed ones. The `_update` method is only available on the server side, since its name begins with an underscore.\n\nThe bridge compares the object's properties shallowly (`===`). This means `yaob` won't notice if you modify the contents of an array, for example, since the property's identity doesn't change (it's still the same array). To force the bridge to send a property like this, simply pass the property's name to the `_update` method:\n\n```js\nclass ListExample extends Bridgeable {\n  constructor () {\n    this.list = []\n  }\n\n  async addItem (item) {\n    this.list.push(item)\n\n    // Explicitly send the `list` property over the bridge:\n    this._update('list')\n  }\n}\n```\n\n### Watching properties\n\nTo receive a callback any time a property changes, use the `watch` method, which is part of the `Bridgeable` base class:\n\n```js\nsomeObject.watch('list', newValue =\u003e console.log(newValue))\n```\n\nThe first parameter is the property name, and the second parameter is the callback. The callback will fire any time the property changes. The object must use `this._update()` to trigger the changes, as described above.\n\nThe `watch` method returns an `unsubscribe` function. You can use this to unsubscribe at any time.\n\n### Events\n\n`Bridgeable` objects can also emit events. To subscribe to events, use the `on` method, which is part of the `Bridgeable` base class:\n\n```js\nsomeObject.on('login', username =\u003e {\n  console.log('got new user:', username)\n})\n```\n\nThe bridge will emit an `error` event any time an event callback throws an exception, and will emit a `close` event when objects are closed.\n\nUse the `_emit` method to send events, which is part of the `Bridgeable` base class:\n\n```js\nsomeObject._emit('eventName', somePayload)\n```\n\nThe `on` method is available on both the client and server side objects, but the `_emit` method is only available on the server side, since its name begins with an underscore.\n\nThe payload must be a single value. If you need a more complicated payload, simply pack everything into an object:\n\n```js\nsomeObject._emit('logout', { username: 'yaob', reason: 'timeout' })\n```\n\nThe `on` method returns an unsubscribe function. You can use this to unsubscribe at any time. You can also use it to set up a one-shot event listener:\n\n```js\nconst unsubscribe = someObject.on('logout', payload =\u003e {\n  unsubscribe()\n  shutDownApp(payload)\n})\n```\n\n### Closing\n\nOnce the server sends an object over the bridge, the object will stick around for the lifetime of the bridge. This is because there is no way of knowing when the client will access the object again. This can leak memory.\n\nIf this sort of thing becomes a problem, you can explicitly free objects by calling `this._close()`, which is part of the `Bridgeable` base class. Closing a server-side object will make it un-bridgeable and will destroy the client-side object. Calling any method on the client side will then throw an exception.\n\nThis can also be a useful way to represent logging out of accounts, closing files, or other situations where an API object needs to become unusable.\n\n### Unit Testing\n\nTo help test your API in a realistic setting (but without starting an entirely new process), the `yaob` library provides a `makeLocalBridge` function, which returns a locally-connected bridge for any bridgeable object:\n\n```js\nclass MyApi extends Bridgeable { ... }\n\nconst testApi = makeLocalBridge(new MyApi())\n```\n\nThis example creates a `testApi` which looks and feels just like a `MyApi` instance, but is actually a bridge. Every property change and method call turns into a message, just as it would if the `MyApi` instance were in another process.\n\nThe `makeLocalBridge` function also accepts an optional `cloneMessage` function:\n\n```js\nconst testApi = makeLocalBridge(new MyApi(), {\n  cloneMessage: m =\u003e JSON.parse(JSON.stringify(m))\n})\n```\n\nThis makes it possible to incorporate realistic message serialization and deserialization into the test.\n\n### Shared Methods\n\nBridges normally forward method calls to the original object. Sometimes, though, it's useful to have synchronous methods that run directly on the client side without bridging. The `shareData` function makes this possible:\n\n```js\nimport { Bridgeable, shareData } from 'yaob'\n\nclass SomeApi extends Bridgeable {\n  syncMethod (x) {\n    return 2 * x\n  }\n}\n\n// Share the method with the client:\nshareData({\n  'SomeApi.syncMethod': SomeApi.prototype.syncMethod\n})\n\n// Send the object over a bridge:\nconst local = makeLocalBridge(new SomeApi())\n\n// No `await` needed!\nexpect(local.double(3)).equals(6)\n```\n\nSince shared methods run on the client side, they can only access whatever public API the client side could access anyhow. In particular, this means they cannot access private class members that begin with underscores, since those aren't bridged.\n\nBoth the client and the server keep matching tables of shared data. When the server encounters a shared value, it sends value's name to the client, who looks up the equivalent value in its table. This means that every shared value must have a unique name, such as the `'SomeApi.syncMethod'` name given in the example above.\n\nAdding items to the shared table is only effective at library load time. Otherwise, bundling tools like rollup.js will not copy the values into the client-side code bundle.\n\n### Throttling\n\nBoth the `Bridge` constructor and `makeLocalBridge` function accept an optional `throttleMs` option. When this option is set, the bridge will wait this long between sending messages. It will batch up any events, method calls, or property changes that occur in the mean time. This may improve performance if properties change often, but could also hurt performance by increasing latency.\n\n```js\nmakeLocalBridge(new RootApi(), { throttleMs: 10 })\n```\n\n```js\nconst server = new Bridge({\n  throttleMs: 10,\n  sendMessage () {}\n})\n```\n\n### Avoiding `Bridgeable`\n\nThe easiest way to make your object bridgeable is to extend the `Bridgeable` base class. If you need more control though, `yaob` provides other options:\n\n* Call `bridgifyClass` on a class constructor function. Any instances of this class will be bridgeable.\n* Call `bridgifyObject` directly on an object.\n\nYou might use one of these other options if you don't control your class hierarchy, for instance. All the `Bridgeable` methods have standalone versions, so their functionality is available even if your class doesn't extend `Bridgeable`:\n\n```js\nimport { close, emit, update } from 'yaob'\n\n// Instead of this._emit(...):\nemit(this, 'event', payload)\n\n// Instead of this._update():\nupdate(this)\n\n// Instead of this._close():\nclose(this)\n```\n\nIf you would like to give your users nice `on` or `watch` methods like the one `Bridgeable` provides, you can do this:\n\n```js\nimport { bridgifyClass, onMethod, watchMethod } from 'yaob'\n\nclass SomeApi { ... }\nSomeApi.prototype.on = onMethod\nSomeApi.prototype.watch = watchMethod\n\nbridgifyClass(SomeApi)\n```\n\nThe `onMethod` and `watchMethod` values are shared, so the bridge knows to replace them with a proper client-side methods instead of bridging them.\n\n### Hiding Properties\n\nBy default, yaob uses the JavaScript `enumerable` flag to hide bridged methods. This way, if you pass a bridged object to `console.log` or `JSON.stringify`, you will just see the values, not the methods.\n\nIf you want to hide additional properties, you can pass their names in a `hideProperites` bridge option. Yaob will hide any bridgeable object properties with names that match the list.\n\nIn this example, yaob automatically hides `someMethod`, since it's a method, and also hides the `hideMe` property because it's name is on the list:\n\n```js\nconst example = bridgifyObject({\n  visible: 1,\n  hideMe: 2,\n  someMethod () {}\n})\n\nconst local = makeLocalBridge(example, { hideProperties: [\"hideMe\"] })\n\nJSON.stringify(local) // Returns {\"visible\":1}\n```\n\n### Flow Types\n\nThis library ships with Flow types. For information on using them, please see the [Flow tutorial](./docs/flow.md).\n\n### Bundling\n\nConsider using a tool like [rollup.js](https://rollupjs.org/guide/en) to bundle your library. This bundler supports tree shaking, so it will eliminate unused code from the bundles it produces. This way, you can put all your code in one source tree, and let the bundler separate your server-side code from your client-side code.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fedgeapp%2Fyaob","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fedgeapp%2Fyaob","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fedgeapp%2Fyaob/lists"}