{"id":15689547,"url":"https://github.com/mathisbullinger/typerpc","last_synced_at":"2025-03-30T12:12:30.884Z","repository":{"id":48280589,"uuid":"334279201","full_name":"MathisBullinger/typerpc","owner":"MathisBullinger","description":"JSON-RPC 2.0 implementation in TypeScript with statically typed introspectable schema.","archived":false,"fork":false,"pushed_at":"2021-08-18T09:12:45.000Z","size":1117,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-02-05T13:48:23.814Z","etag":null,"topics":["json-rpc","rpc","typescript"],"latest_commit_sha":null,"homepage":"https://www.npmjs.com/package/typerpc","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"isc","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/MathisBullinger.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2021-01-29T22:39:28.000Z","updated_at":"2022-05-27T06:09:28.000Z","dependencies_parsed_at":"2022-09-14T00:21:21.245Z","dependency_job_id":null,"html_url":"https://github.com/MathisBullinger/typerpc","commit_stats":null,"previous_names":[],"tags_count":16,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/MathisBullinger%2Ftyperpc","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/MathisBullinger%2Ftyperpc/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/MathisBullinger%2Ftyperpc/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/MathisBullinger%2Ftyperpc/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/MathisBullinger","download_url":"https://codeload.github.com/MathisBullinger/typerpc/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":246314168,"owners_count":20757463,"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":["json-rpc","rpc","typescript"],"created_at":"2024-10-03T18:03:05.107Z","updated_at":"2025-03-30T12:12:30.854Z","avatar_url":"https://github.com/MathisBullinger.png","language":"TypeScript","readme":"# TypeRPC\n\nAt its core, this is an implementation of the [JSON-RPC 2.0\nspecification](https://www.jsonrpc.org/specification) in TypeScript. The library \nprovides transport agnostic interfaces for the RPC Client and the Server.\n\nOn top of that the server takes a description of its available methods, their \nparameters and their result type (if one exists). This description is also used \nto infer static types for all requests, responses, and method resolvers. The \nclient can use the same types to provide TypeScript definitions for its methods.\n\n## Schema Definition\n\nThe server accepts a schema definition of the following structure:\n\n`{ [name of method]: { params?: TYPE, result?: TYPE } }`\n\nwhere `TYPE` can be defined as follows:\n\n- the `String` or `Number` constructors are translated to their\nrespective primitive type\n- `Object` is interpreted as `any`\n- `null` is `null`\n- these options can be combined in tuples (e.g. `[String, Number]`) that \nwill be typed as such (fixed length and type) and in nested objects (e.g. `{ name: String, age: Number }`)\n- *there is a restriction right now that tuples can only contain primitive types\nand can't be further nested*\n\n`params` and `result` can both be omitted. If `result` is omitted, the method\nis interpreted as a [notification](https://www.jsonrpc.org/specification#notification), meaning the server will not send a response for the method. In the client this will result in\nthe method only being available via the `.notify` method.\n\n## Resolvers\n\nThe methods have to be defined via the endpoint's `.on(name, resolver)` method.\nThe `resolver` is typed according to the schema description. If a resolver is\nasynchronous the server will wait for it to resolve. If a resolver throws or\nrejects, the server returns a `-32603 (Internal error)` error.\n\nIf a client calls a method for which no resolver has been registered, the server\nresponds with `-32601 (Method not found)`.\n\nIf a client provides an id for a method for which no result type has been \ndeclared (i.e. expects a response for a notification), the server will execute\nthe method but respond with an `-32001 (Invalid notification id)` error.\n\n## Batch Requests\n\nMultiple requests can be batched to be sent as a single request, as described by the [JSON-RPC spec](https://www.jsonrpc.org/specification#batch).\n\nTo create a batched request, use the `.batch()` method, and call `.notify` and `.call` on the resulting object as you would with a regular request. E.g.:\n\n``` ts\nconst batch = server.batch()\nconst prom3 = batch.call('add', 1, 2)\nconst prom5 = batch.call('add', 2, 3)\nconst prom7 = batch.call('add', 3, 4)\n```\n\nThe request will be sent once either the batch object itself or any of requests\ncreated from it is resolved (either by calling `.then()` on it or `await`ing it).\nAfter the request is sent, trying to add more requests to the batch will result\nin an error.\n\nRequests can also be added to the batch by chaining `.call` or `.notify` on any\nof the batches other requests. So these are functionally equivalent to the above\nexample:\n\n```ts\nserver.batch().call('add', 1, 2).call('add', 2, 3).call('add', 3, 4)\n```\n```ts\nconst batch = server.batch()\nconst prom5 = batch.call('add', 1, 2).call('add', 2, 3)\nconst prom7 = batch.call('add', 3, 4)\n```\n\nIf all requests are successful, the batch promise will resolve to an array of\nall results, otherwise it will reject with the error of the first failed request:\n\n```ts\nconst batch = server.batch()\nawait Promise.all([\n  batch.call('add', 1, 2),\n  batch.notify('hello'),\n  batch.call('add', 2, 3),\n  batch\n]) // resolves to [3, 5, [3, 5]]\n\nconst batch = server.batch()\nconst prom3 = server.add('add', 1, 2)\nconst invalid = server.add('add', '!!')\n\nawait batch   // rejects with { code: -32602, message: \"Invalid params\"}\nawait prom3   // resolves to 3\nawait invalid // rejects with { code: -32602, message: \"Invalid params\"}\n```\n\n## Introspection\n\nThe server provides the `__schema` method to query its schema description \n(including any internal methods that may exist). Constructors (`String`, \n`Number`, `Object`) will be encoded as strings, e.g. `Number` -\u003e `\"Number\"`.\n\nThis can be used to generate types to provide to the client of the API.\n\nSchema introspection can be disabled by setting `introspection: false` in the \nserver options.\n\n## Examples\n\n### Basic calculator without network transport\n\n```ts\nimport Endpoint, { Transport } from '.'\n\nconst calculatorCPU = new Endpoint({\n  add: { params: [Number, Number], result: Number },\n  shutdown: {},\n})\n\ncalculatorCPU.on('add', ([a, b]) =\u003e a + b)\ncalculatorCPU.on('shutdown', () =\u003e {/*...*/})\n\n// In this example the user doesn't provide any API that the calculator\n// could call into.\n// Note however, that in principle, there is no distinction between a \"client\"\n// and the \"server\", and both sides can act as both at the same time.\nconst user = new Endpoint(null)\n\n// For now, let's just directly send all messages from the user to the calculator\n// and vice versa.\n// In the real world, the transports would probably do something more useful, like\n// sending the messages through HTTP requests, accross threads or something along\n// those lines.\n// More complex transports will also want to route messages differently based\n// on the address they were sent to / received from.\n// An example of transports that send \u0026 receive messages through websockets in\n// a browser and AWS Lambda functions with an API gateway can be found in src/transport/ws\nconst calcTransport: Transport\u003cany\u003e = {\n  in(msg, caller) {\n    this.onInput?.(msg, caller)\n  },\n  out(address, msg) {\n    userTransport.in(msg, '/calc')\n  },\n}\nconst userTransport: Transport\u003cany\u003e = {\n  in(msg, caller) {\n    this.onInput?.(msg, caller)\n  },\n  out(address, msg) {\n    if (address !== '/calc') throw Error(\"that's not the calculator\")\n    calcTransport.in(msg, '/user')\n  },\n}\ncalculatorCPU.addTransport(calcTransport, { default: true })\nuser.addTransport(userTransport, { default: true })\n\n// The schema of any endpoint can also be introspected by calling its __schema method\ntype Schema = typeof calculatorCPU extends Endpoint\u003cinfer I\u003e ? I : never\n\n// This is the interface that the user will use to speak to the calculator.\n// You can think of it as the calculators buttons that the user presses.\n// Connections will use the default transport unless specified otherwise.\nconst calculator = user.addConnection\u003cSchema\u003e('/calc')\n\n// Now that the user and calculator can speak to each other, let's do some maths:\nconst sum = await calculator.call('add', 1, 2) // -\u003e 3 🎉\n\n// And turn the calculator off, we don't need to wait for a result for that\ncalculator.notify('shutdown')\n```\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmathisbullinger%2Ftyperpc","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmathisbullinger%2Ftyperpc","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmathisbullinger%2Ftyperpc/lists"}