{"id":20156273,"url":"https://github.com/web-infra-dev/unport","last_synced_at":"2025-04-09T22:23:25.355Z","repository":{"id":207688105,"uuid":"719483248","full_name":"web-infra-dev/unport","owner":"web-infra-dev","description":"A Universal Port with strict type inference capability for cross-JSContext communication.","archived":false,"fork":false,"pushed_at":"2025-03-09T09:41:07.000Z","size":627,"stargazers_count":33,"open_issues_count":1,"forks_count":0,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-03-23T13:02:23.063Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/web-infra-dev.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","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":"2023-11-16T09:09:08.000Z","updated_at":"2025-03-10T23:25:27.000Z","dependencies_parsed_at":"2024-06-04T10:59:03.695Z","dependency_job_id":"55b6b41b-dd60-4917-8d49-9af2333d2c2f","html_url":"https://github.com/web-infra-dev/unport","commit_stats":null,"previous_names":["ulivz/unport"],"tags_count":5,"template":false,"template_full_name":"ulivz/ts-lib-template","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/web-infra-dev%2Funport","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/web-infra-dev%2Funport/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/web-infra-dev%2Funport/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/web-infra-dev%2Funport/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/web-infra-dev","download_url":"https://codeload.github.com/web-infra-dev/unport/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248120896,"owners_count":21051043,"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-13T23:38:15.197Z","updated_at":"2025-04-09T22:23:25.331Z","avatar_url":"https://github.com/web-infra-dev.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003cp align=\"center\"\u003e\n  \u003cimg alt=\"Unport Logo\" src=\"https://github.com/ulivz/unport/blob/main/.media/type-infer.png?raw=true\"\u003e\u003cbr\u003e\n  \u003cimg alt=\"Unport Logo\" src=\"https://github.com/ulivz/unport/blob/main/.media/logo.png?raw=true\" width=\"200\"\u003e\n\u003c/p\u003e\n\n\u003cdiv align=\"center\"\u003e\n\n[![NPM version][npm-badge]][npm-url]\n\n\u003c/div\u003e\n\n## 🛰️ What's Unport?\n\n**Unport is a fully type-inferred IPC (Inter-Process Communication) library**. It ensures robust and reliable cross-context communication with strict type checking, enhancing the predictability and stability of your application.\n\n```math\nPort = f(types, channel)\n```\n\nUnport is designed to simplify the complexity revolving around various JSContext environments. These environments encompass a wide range of technologies, including [Node.js](https://nodejs.org/), [ChildProcess](https://nodejs.org/api/child_process.html), [Webview](https://en.wikipedia.org/wiki/WebView), [Web Worker](https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Using_web_workers), [worker_threads](https://nodejs.org/api/worker_threads.html), [WebSocket](https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API), [iframe](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/iframe), [MessageChannel](https://developer.mozilla.org/en-US/docs/Web/API/MessageChannel), [ServiceWorker](https://developer.mozilla.org/en-US/docs/Web/API/Service_Worker_API), and much more.\n\nEach of these JSContexts exhibits distinct methods of communicating with the external world. Still, the lack of defined types can make handling the code for complex projects an arduous task. In the context of intricate and large-scale projects, it's often challenging to track the message's trajectory and comprehend the fields that the recipient necessitates.\n\n- [🛰️ What's Unport?](#️-whats-unport)\n- [💡 Features](#-features)\n- [🛠️ Install](#️-install)\n- [⚡️ Quick Start](#️-quick-start)\n- [📖 Basic Concepts](#-basic-concepts)\n  - [MessageDefinition](#messagedefinition)\n  - [Channel](#channel)\n- [📚 API Reference](#-api-reference)\n  - [Unport](#unport)\n    - [.implementChannel()](#implementchannel)\n    - [.postMessage()](#postmessage)\n    - [.onMessage()](#onmessage)\n  - [Channel](#channel-1)\n    - [.pipe()](#pipe)\n  - [ChannelMessage](#channelmessage)\n  - [Unrpc (Experimental)](#unrpc-experimental)\n- [🤝 Contributing](#-contributing)\n- [🤝 Credits](#-credits)\n- [LICENSE](#license)\n\n## 💡 Features\n\n1. Provides a unified Port paradigm. You only need to define the message types ([MessageDefinition](#messagedefinition)) and Intermediate communication channel ([Channel](#channel)) that different JSContexts need to pass, and you will get a unified type of Port:\n2. 100% type inference. Users only need to maintain the message types between JSContexts, and leave the rest to unport.\n3. Lightweight size and succinct API.\n\n![IPC](https://github.com/ulivz/unport/blob/main/.media/ipc.png?raw=true)\n\n## 🛠️ Install\n\n```bash\nnpm i unport -S\n```\n\n## ⚡️ Quick Start\n\nLet's take ChildProcess as an example to implement a process of sending messages after a parent-child process is connected:\n\n1. Define Message Definition:\n\n```ts\nimport { Unport } from \"unport\";\n\nexport type Definition = {\n  parent2child: {\n    syn: {\n      pid: string;\n    };\n    body: {\n      name: string;\n      path: string;\n    };\n  };\n  child2parent: {\n    ack: {\n      pid: string;\n    };\n  };\n};\n\nexport type ChildPort = Unport\u003cDefinition, \"child\"\u003e;\nexport type ParentPort = Unport\u003cDefinition, \"parent\"\u003e;\n```\n\n2. Parent process implementation:\n\n```ts\n// parent.ts\nimport { join } from \"path\";\nimport { fork } from \"child_process\";\nimport { Unport, ChannelMessage } from \"unport\";\nimport { ParentPort } from \"./port\";\n\n// 1. Initialize a port\nconst parentPort: ParentPort = new Unport();\n\n// 2. Implement a Channel based on underlying IPC capabilities\nconst childProcess = fork(join(__dirname, \"./child.js\"));\nparentPort.implementChannel({\n  send(message) {\n    childProcess.send(message);\n  },\n  accept(pipe) {\n    childProcess.on(\"message\", (message: ChannelMessage) =\u003e {\n      pipe(message);\n    });\n  },\n});\n\n// 3. You get a complete typed Port with a unified interface 🤩\nparentPort.postMessage(\"syn\", { pid: \"parent\" });\nparentPort.onMessage(\"ack\", (payload) =\u003e {\n  console.log(\"[parent] [ack]\", payload.pid);\n  parentPort.postMessage(\"body\", {\n    name: \"index\",\n    path: \" /\",\n  });\n});\n\n// 4. If you want to remove some listeners\nconst handleAck = (payload) =\u003e {\n  console.log(\"[parent] [syn]\");\n};\nparentPort.onMessage(\"ack\", handleAck);\nparentPort.removeMessageListener(\"ack\", handleAck);\n// Note: if the second param of `removeMessageListener` is omitted, all listeners will be removed.\nparentPort.removeMessageList(\"ack\");\n```\n\n3. Child process implementation:\n\n```ts\n// child.ts\nimport { Unport, ChannelMessage } from \"unport\";\nimport { ChildPort } from \"./port\";\n\n// 1. Initialize a port\nconst childPort: ChildPort = new Unport();\n\n// 2. Implement a Channel based on underlying IPC capabilities\nchildPort.implementChannel({\n  send(message) {\n    process.send \u0026\u0026 process.send(message);\n  },\n  accept(pipe) {\n    process.on(\"message\", (message: ChannelMessage) =\u003e {\n      pipe(message);\n    });\n  },\n});\n\n// 3. You get a complete typed Port with a unified interface 🤩\nchildPort.onMessage(\"syn\", (payload) =\u003e {\n  console.log(\"[child] [syn]\", payload.pid);\n  childPort.postMessage(\"ack\", { pid: \"child\" });\n});\n\nchildPort.onMessage(\"body\", (payload) =\u003e {\n  console.log(\"[child] [body]\", JSON.stringify(payload));\n});\n\n// 4. If you want to remove some listeners by `removeMessageList`\nconst handleSyn = (payload) =\u003e {\n  console.log(\"[child] [syn]\");\n};\nchildPort.onMessage(\"syn\", handleSyn);\nchildPort.removeMessageListener(\"syn\", handleSyn);\n// Note: if the second param of `removeMessageListener` is omitted, all listeners will be removed.\nchildPort.removeMessageList(\"syn\");\n```\n\n## 📖 Basic Concepts\n\n### MessageDefinition\n\nIn Unport, a `MessageDefinition` is a crucial concept that defines the structure of the messages that can be sent and received through a `Channel`. It provides a clear and consistent way to specify the data that can be communicated between different JSContexts\n\nA `MessageDefinition` is an object where each key represents a type of message that can be sent or received, and the value is an object that defines the structure of the message.\n\nHere is an example of a `MessageDefinition`:\n\n```ts\nexport type Definition = {\n  parent2child: {\n    syn: {\n      pid: string;\n    };\n    body: {\n      name: string;\n      path: string;\n    };\n  };\n  child2parent: {\n    ack: {\n      pid: string;\n    };\n  };\n};\n```\n\nIn this example, the `MessageDefinition` defines two types of messages that can be sent from the parent to the child (`syn` and `body`), and one type of message that can be sent from the child to the parent (`ack`). Each message type has its own structure, defined by an object with keys representing message types and values representing their message types.\n\nBy using a `MessageDefinition`, you can ensure that the messages sent and received through a `Channel` are consistent and predictable, making your code easier to understand and maintain.\n\n### Channel\n\nIn Unport, a `Channel` is a fundamental concept that represents a Intermediate communication pathway between different JavaScript contexts. It provides a unified interface for sending and receiving messages across different environments.\n\nA `Channel` is implemented using two primary methods:\n\n- `send(message)`: This method is used to send a message through the channel. The `message` parameter is the data you want to send.\n\n- `accept(pipe)`: This method is used to accept incoming messages from the channel. The `pipe` parameter is a function that takes a message as its argument.\n\nHere is an example of how to implement a `Channel`:\n\n```ts\nparentPort.implementChannel({\n  send(message) {\n    childProcess.send(message);\n  },\n  accept(pipe) {\n    childProcess.on(\"message\", (message: ChannelMessage) =\u003e {\n      pipe(message);\n    });\n  },\n});\n```\n\nIn this example, the `send` method is implemented using the `send` method of a child process, and the `accept` method is implemented using the `on` method of the child process to listen for 'message' events.\n\nBy abstracting the details of the underlying communication mechanism, Unport allows you to focus on the logic of your application, rather than the specifics of inter-context communication.\n\n## 📚 API Reference\n\n### Unport\n\nThe `Unport` class is used to create a new port.\n\n```ts\nimport { Unport } from \"unport\";\n```\n\n#### .implementChannel()\n\nThis method is used to implement a universal port based on underlying IPC capabilities.\n\n```ts\nparentPort.implementChannel({\n  send(message) {\n    childProcess.send(message);\n  },\n  accept(pipe) {\n    childProcess.on(\"message\", (message: ChannelMessage) =\u003e {\n      pipe(message);\n    });\n  },\n});\n```\n\n#### .postMessage()\n\nThis method is used to post a message.\n\n```ts\nparentPort.postMessage(\"syn\", { pid: \"parent\" });\n```\n\n#### .onMessage()\n\nThis method is used to listen for a message.\n\n```ts\nparentPort.onMessage(\"ack\", (payload) =\u003e {\n  console.log(\"[parent] [ack]\", payload.pid);\n  parentPort.postMessage(\"body\", {\n    name: \"index\",\n    path: \" /\",\n  });\n});\n```\n\n### Channel\n\nWhen you invoke the [.implementChannel()](#implementchannel) to implement an intermediary pipeline, you will receive a `Channel` instance. This instance comes with several useful methods that enhance the functionality and usability of the pipeline.\n\n#### .pipe()\n\n- Type: `(message: ChannelMessage) =\u003e void`\n\nThe `pipe` method is used to manually handle incoming messages. It's often used in Server with `one-to-many` connections, e.g. Web Socket.\n\nExample:\n\n```ts\nconst channel = port.implementChannel({\n  send: (message) =\u003e {\n    // send message to the other end of the channel\n  },\n});\n\n// when a message is received\nchannel.pipe(message);\n```\n\nSee our [Web Socket](./examples/web-socket/) example to check more details.\n\n### ChannelMessage\n\nThe `ChannelMessage` type is used for the message in the `onMessage` method.\n\n```ts\nimport { ChannelMessage } from \"unport\";\n```\n\n### Unrpc (Experimental)\n\nStarting with the 0.6.0 release, we are experimentally introducing support for Typed [RPC (Remote Procedure Call)](https://en.wikipedia.org/wiki/Remote_procedure_call).\n\nWhen dealing with a single Port that requires RPC definition, we encounter a problem related to the programming paradigm. It's necessary to define `Request` and `Response` messages such as:\n\n```ts\nexport type IpcDefinition = {\n  a2b: {\n    callFoo: {\n      input: string;\n    };\n  };\n  b2a: {\n    callFooCallback: {\n      result: string;\n    };\n  };\n};\n```\n\nIn the case where an RPC call needs to be encapsulated, the API might look like this:\n\n```ts\nfunction rpcCall(request: { input: string }): Promise\u003c{ result: string }\u003e;\n```\n\nConsequently, to associate a callback function, it becomes a requirement to include a `CallbackId` at the **application layer** for every RPC method:\n\n```diff\n export type IpcDefinition = {\n   a2b: {\n     callFoo: {\n       input: string;\n+      callbackId: string;\n     };\n   };\n   b2a: {\n     callFooCallback: {\n       result: string;\n+      callbackId: string;\n     };\n   };\n };\n```\n\n`Unrpc` is provided to address this issue, enabling support for Typed RPC starting from the **protocol layer**:\n\n```ts\n// \"parentPort\" is a Port defined based on Unport in the previous example.\nconst parent = new Unrpc(parentPort);\n\n// Implementing an RPC method.\nparent.implement(\"getParentInfo\", (request) =\u003e ({\n  id: \"parent\",\n  from: request.user,\n}));\n```\n\nThe implementation on the `child` side is as follows:\n\n```ts\n// \"parentPort\" is a Port also defined based on Unport.\nconst child = new Unrpc(childPort);\nconst response = await child.call(\"getParentInfo\", { user: \"child\" }); // =\u003e { id: \"parent\", from: \"child\" }\n```\n\nThe types are defined as such:\n\n```ts\nimport { Unport } from \"unport\";\n\nexport type Definition = {\n  parent2child: {\n    getParentInfo__callback: {\n      content: string;\n    };\n  };\n  child2parent: {\n    getParentInfo: {\n      user: string;\n    };\n  };\n};\n\nexport type ChildPort = Unport\u003cDefinition, \"child\"\u003e;\nexport type ParentPort = Unport\u003cDefinition, \"parent\"\u003e;\n```\n\nIn comparison to Unport, the only new concept to grasp is that the RPC response message key must end with `__callback`. Other than that, no additional changes are necessary! `Unrpc` also offers comprehensive type inference based on this convention; for instance, you won't be able to implement an RPC method that is meant to serve as a response.\n\n\u003e [!NOTE]  \n\u003e You can find the full code example here: [child-process-rpc](https://github.com/web-infra-dev/unport/tree/main/examples/child-process-rpc).\n\n## 🤝 Contributing\n\nContributions, issues and feature requests are welcome!\n\nHere are some ways you can contribute:\n\n1. 🐛 Submit a [Bug Report](https://github.com/ulivz/unport/issues) if you found something isn't working correctly.\n2. 🆕 Suggest a new [Feature Request](https://github.com/ulivz/unport/issues) if you'd like to see new functionality added.\n3. 📖 Improve documentation or write tutorials to help other users.\n4. 🌐 Translate the documentation to other languages.\n5. 💻 Contribute code changes by [Forking the Repository](https://github.com/ulivz/unport/fork), making changes, and submitting a Pull Request.\n\n## 🤝 Credits\n\nThe birth of this project is inseparable from the complex IPC problems we encountered when working in large companies. The previous name of this project was `Multidirectional Typed Port`, and we would like to thank [ahaoboy](https://github.com/ahaoboy) for his previous ideas on this matter.\n\n## LICENSE\n\nMIT License © [ULIVZ](https://github.com/ulivz)\n\n[npm-badge]: https://img.shields.io/npm/v/unport.svg?style=flat\n[npm-url]: https://www.npmjs.com/package/unport\n[ci-badge]: https://github.com/ulivz/unport/actions/workflows/ci.yml/badge.svg?event=push\u0026branch=main\n[ci-url]: https://github.com/ulivz/unport/actions/workflows/ci.yml?query=event%3Apush+branch%3Amain\n[code-coverage-badge]: https://codecov.io/github/ulivz/unport/branch/main/graph/badge.svg\n[code-coverage-url]: https://codecov.io/gh/ulivz/unport\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fweb-infra-dev%2Funport","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fweb-infra-dev%2Funport","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fweb-infra-dev%2Funport/lists"}