{"_id":"@connectrpc-tauri/transport","_rev":"4-361523f30987c87d199d97e74976c31f","name":"@connectrpc-tauri/transport","dist-tags":{"latest":"0.1.3"},"versions":{"0.1.0":{"name":"@connectrpc-tauri/transport","version":"0.1.0","keywords":["tauri","connectrpc","grpc","ipc","rpc"],"license":"MIT OR Apache-2.0","_id":"@connectrpc-tauri/transport@0.1.0","maintainers":[{"name":"junsheaf","email":"me@jrandolf.com"}],"homepage":"https://github.com/mathematic-inc/connectrpc-tauri#readme","bugs":{"url":"https://github.com/mathematic-inc/connectrpc-tauri/issues"},"dist":{"shasum":"6fed608309d2e78f380bce1e21f6a6a8e758e5fb","tarball":"https://registry.npmjs.org/@connectrpc-tauri/transport/-/transport-0.1.0.tgz","fileCount":10,"integrity":"sha512-xx1/wcYNhfoK7s5n3B1TZ8jeV6YSsTGpYC3C2Yr3hlbmKMjyoIMafDhlP//5vBSXuMS+ffqumYozKXjeHwYELg==","signatures":[{"sig":"MEUCIQDzQOsOETP4YUpyq1z6tHlzTSvWbr6TMUry1m5DGrzjCAIgZW8aMrscU4y3HrpGSGa8INacjwGUrFnnSApktYGWyIE=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":51368},"main":"./dist/index.js","type":"module","types":"./dist/index.d.ts","exports":{".":{"types":"./dist/index.d.ts","default":"./dist/index.js"}},"gitHead":"6b6c25da39a99ae3f455d314fff638d8de89e958","scripts":{"build":"tsc -p tsconfig.build.json","prepack":"cp ../../README.md ../../LICENSE-MIT ../../LICENSE-APACHE ."},"_npmUser":{"name":"junsheaf","email":"me@jrandolf.com"},"repository":{"url":"git+https://github.com/mathematic-inc/connectrpc-tauri.git","type":"git","directory":"packages/transport"},"_npmVersion":"11.17.0","description":"ConnectRPC transport over Tauri IPC (commands + channels)","directories":{},"_nodeVersion":"26.5.1","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"@tauri-apps/api":"^2.11.1","@bufbuild/protobuf":"^2.14.0","@connectrpc/connect":"^2.1.2"},"peerDependencies":{"@tauri-apps/api":"^2.11.0","@bufbuild/protobuf":"^2.14.0","@connectrpc/connect":"^2.1.0"},"_npmOperationalInternal":{"tmp":"tmp/transport_0.1.0_1787021403874_0.7526844142445304","host":"s3://npm-registry-packages-npm-production"}},"0.1.1":{"name":"@connectrpc-tauri/transport","version":"0.1.1","keywords":["tauri","connectrpc","grpc","ipc","rpc"],"license":"MIT OR Apache-2.0","_id":"@connectrpc-tauri/transport@0.1.1","maintainers":[{"name":"junsheaf","email":"me@jrandolf.com"}],"homepage":"https://github.com/mathematic-inc/connectrpc-tauri#readme","bugs":{"url":"https://github.com/mathematic-inc/connectrpc-tauri/issues"},"dist":{"shasum":"0ee16e9fcce4061a2ac444bfe98163f80d1c09b2","tarball":"https://registry.npmjs.org/@connectrpc-tauri/transport/-/transport-0.1.1.tgz","fileCount":10,"integrity":"sha512-kcPHsHxL8K9ry4OS1VaE9Al5m2o67c3SLARmz0SiGfIGURdO4XGN7rByJGRNgoH6GvCBCYw/AYfvr0l7UQk80w==","signatures":[{"sig":"MEUCIQC+Yu8EFxT09PQA9fyz75cijtT9H8KVDP1WY6T5QQMeHwIgbMouFB2UThPDBfNd509Y4+Ec9VABIPIL8tJpevTpK3g=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@connectrpc-tauri%2ftransport@0.1.1","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":56714},"main":"./dist/index.js","type":"module","types":"./dist/index.d.ts","exports":{".":{"types":"./dist/index.d.ts","default":"./dist/index.js"}},"gitHead":"c6232667e32116cd8571571d688318f7f84bbe9b","scripts":{"build":"tsc -p tsconfig.build.json","prepack":"cp ../../README.md ../../LICENSE-MIT ../../LICENSE-APACHE ."},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:ba2eadca-cc32-47a7-b7e9-d7ae14dd5d4d"}},"repository":{"url":"git+https://github.com/mathematic-inc/connectrpc-tauri.git","type":"git","directory":"packages/transport"},"_npmVersion":"11.17.0","description":"ConnectRPC transport over Tauri IPC (commands + channels)","directories":{},"_nodeVersion":"24.19.0","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"@tauri-apps/api":"^2.11.1","@bufbuild/protobuf":"^2.14.0","@connectrpc/connect":"^2.1.2"},"peerDependencies":{"@tauri-apps/api":"^2.11.0","@bufbuild/protobuf":"^2.14.0","@connectrpc/connect":"^2.1.0"},"_npmOperationalInternal":{"tmp":"tmp/transport_0.1.1_1787024849244_0.7980583951631732","host":"s3://npm-registry-packages-npm-production"}},"0.1.2":{"name":"@connectrpc-tauri/transport","version":"0.1.2","keywords":["tauri","connectrpc","grpc","ipc","rpc"],"license":"MIT OR Apache-2.0","_id":"@connectrpc-tauri/transport@0.1.2","maintainers":[{"name":"junsheaf","email":"me@jrandolf.com"}],"homepage":"https://github.com/mathematic-inc/connectrpc-tauri#readme","bugs":{"url":"https://github.com/mathematic-inc/connectrpc-tauri/issues"},"dist":{"shasum":"4b41e18cdecaf1354cdbc6204c3dccb89d791b73","tarball":"https://registry.npmjs.org/@connectrpc-tauri/transport/-/transport-0.1.2.tgz","fileCount":10,"integrity":"sha512-E9C+2M3OeqUQ/GUJ3JNnKc/060owxqcp9TfII3IUdaoiP1uha7/Qf/NAugIobFtrqWJNPuk/7Ca5B/XyZdNzfQ==","signatures":[{"sig":"MEUCIEiQJVHdEI0MlVIgAUcCYe0l+xE02uv4jYrml76Ja6CSAiEAkWMpYoKoaX+20Ro2DZrE7K4ceNHmZehD1Qn/h9Xt3vc=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@connectrpc-tauri%2ftransport@0.1.2","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":57469},"main":"./dist/index.js","type":"module","types":"./dist/index.d.ts","exports":{".":{"types":"./dist/index.d.ts","default":"./dist/index.js"}},"gitHead":"675423ea9fa4bf79a5f4a4f68180f09fbbf8ba4c","scripts":{"build":"tsc -p tsconfig.build.json","prepack":"cp ../../README.md ../../LICENSE-MIT ../../LICENSE-APACHE ."},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:ba2eadca-cc32-47a7-b7e9-d7ae14dd5d4d"}},"repository":{"url":"git+https://github.com/mathematic-inc/connectrpc-tauri.git","type":"git","directory":"packages/transport"},"_npmVersion":"11.17.0","description":"ConnectRPC transport over Tauri IPC (commands + channels)","directories":{},"_nodeVersion":"24.19.0","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"@tauri-apps/api":"^2.11.1","@bufbuild/protobuf":"^2.14.0","@connectrpc/connect":"^2.1.2"},"peerDependencies":{"@tauri-apps/api":"^2.11.0","@bufbuild/protobuf":"^2.14.0","@connectrpc/connect":"^2.1.0"},"_npmOperationalInternal":{"tmp":"tmp/transport_0.1.2_1787041011572_0.10847767910485451","host":"s3://npm-registry-packages-npm-production"}},"0.1.3":{"name":"@connectrpc-tauri/transport","version":"0.1.3","description":"ConnectRPC transport over Tauri IPC (commands + channels)","keywords":["tauri","connectrpc","grpc","ipc","rpc"],"repository":{"type":"git","url":"git+https://github.com/mathematic-inc/connectrpc-tauri.git","directory":"packages/transport"},"license":"MIT OR Apache-2.0","type":"module","exports":{".":{"types":"./dist/index.d.ts","default":"./dist/index.js"}},"main":"./dist/index.js","types":"./dist/index.d.ts","scripts":{"build":"tsc -p tsconfig.build.json","prepack":"cp ../../README.md ../../LICENSE-MIT ../../LICENSE-APACHE ."},"devDependencies":{"@bufbuild/protobuf":"^2.14.1","@connectrpc/connect":"^2.1.2","@tauri-apps/api":"^2.11.1"},"peerDependencies":{"@bufbuild/protobuf":"^2.14.1","@connectrpc/connect":"^2.1.0","@tauri-apps/api":"^2.11.0"},"publishConfig":{"access":"public"},"gitHead":"6687f6db32e0ab96a65b4b6f77b84fe09909c1e5","_id":"@connectrpc-tauri/transport@0.1.3","bugs":{"url":"https://github.com/mathematic-inc/connectrpc-tauri/issues"},"homepage":"https://github.com/mathematic-inc/connectrpc-tauri#readme","_nodeVersion":"26.8.1","_npmVersion":"11.19.0","dist":{"integrity":"sha512-+UJFhzibndB3PQA48yvkv6+w/01LLGV5T8XnyvmstPSP2JTDJmiV0ESSEaXtxBOu8dyRymKfOuFfB9svNTStqg==","shasum":"baa0f7f2b8957695c0a9f4cb36e3d2b99cc9fa5e","tarball":"https://registry.npmjs.org/@connectrpc-tauri/transport/-/transport-0.1.3.tgz","fileCount":10,"unpackedSize":58749,"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@connectrpc-tauri%2ftransport@0.1.3","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIG3XW2xexHIgK7MLX7Qy8Rpde2J/z1ktYMvx4dfOU7GbAiEA4ei7WLugVo62ALPinvHA/Kaa3XUUT5H/BAGKf9pJATE="}]},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:ba2eadca-cc32-47a7-b7e9-d7ae14dd5d4d"}},"directories":{},"maintainers":[{"name":"junsheaf","email":"me@jrandolf.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/transport_0.1.3_1788279171200_0.6264926542361473"},"_hasShrinkwrap":false}},"time":{"created":"2026-08-18T02:50:03.701Z","modified":"2026-09-01T16:12:51.708Z","0.1.0":"2026-08-18T02:50:04.011Z","0.1.1":"2026-08-18T03:47:29.396Z","0.1.2":"2026-08-18T08:16:51.745Z","0.1.3":"2026-09-01T16:12:51.362Z"},"bugs":{"url":"https://github.com/mathematic-inc/connectrpc-tauri/issues"},"license":"MIT OR Apache-2.0","homepage":"https://github.com/mathematic-inc/connectrpc-tauri#readme","keywords":["tauri","connectrpc","grpc","ipc","rpc"],"repository":{"type":"git","url":"git+https://github.com/mathematic-inc/connectrpc-tauri.git","directory":"packages/transport"},"description":"ConnectRPC transport over Tauri IPC (commands + channels)","maintainers":[{"name":"junsheaf","email":"me@jrandolf.com"}],"readme":"# ConnectRPC over Tauri IPC\n\nA [ConnectRPC](https://connectrpc.com) transport that carries the Connect\nprotocol over Tauri's IPC instead of HTTP. The webview is the client, Rust\nhosts the services, and no HTTP server or port is involved.\n\nAll four method kinds work: unary, server streaming, client streaming, and\nbidi.\n\n## Why this works without reimplementing Connect\n\nBoth sides already expose a byte-level seam, so this crate is only a shuttle\nbetween them:\n\n- Rust's `ConnectRpcService` is a `tower::Service<http::Request>` returning\n  `http::Response`, so it accepts a synthetic request that never touched a\n  socket.\n- TypeScript's `@connectrpc/connect/protocol-connect` exports `createTransport`,\n  which takes an `httpClient: UniversalClientFn` — a plain\n  `(request) => Promise<response>` over async-iterable bodies.\n\nMessage serialization, envelope framing, compression negotiation, trailers,\nerror mapping, and Connect's idempotent-GET support all come from the existing\nruntimes.\n\nThis wraps the _protocol-level_ `createTransport`, not `connect-web`'s\n`createConnectTransport`. That distinction is what makes client-streaming and\nbidi work: the web transport rejects them because the fetch API cannot stream a\nrequest body, and there is no fetch here to be limited by.\n\n## Usage\n\nRust:\n\n```rust\nlet router = Arc::new(Greeter).register(connectrpc::Router::new());\n\ntauri::Builder::default()\n    .plugin(connectrpc_tauri::serve(ConnectRpcService::new(router)))\n    .run(tauri::generate_context!())\n    .expect(\"failed to run app\");\n```\n\nIf the router depends on state created during Tauri setup, register a deferred\ndispatcher first and initialize it in the setup hook:\n\n```rust\nlet deferred = connectrpc_tauri::DeferredDispatcher::new();\n\ntauri::Builder::default()\n    .plugin(connectrpc_tauri::serve(ConnectRpcService::new(deferred.clone())))\n    .setup(move |app| {\n        let router = router_from_app(app.handle());\n        assert!(deferred.set(router).is_ok(), \"ConnectRPC router initialized twice\");\n        Ok(())\n    })\n    .run(tauri::generate_context!())\n    .expect(\"failed to run app\");\n```\n\nTypeScript:\n\n```ts\nimport { createClient } from \"@connectrpc/connect\";\nimport { createTauriTransport } from \"@connectrpc-tauri/transport\";\n\nconst client = createClient(GreetService, createTauriTransport());\nconst { greeting } = await client.greet({ name: \"World\" });\n```\n\nThe capability file must grant the plugin's permission, or every call fails\nwith `Plugin not found`:\n\n```json\n{\n  \"identifier\": \"default\",\n  \"windows\": [\"main\"],\n  \"permissions\": [\"core:default\", \"connectrpc-tauri:default\"]\n}\n```\n\nThe permission prefix is the Rust crate name. Tauri's ACL keys a plugin's\npermissions on `CARGO_PKG_NAME`, so `PLUGIN_NAME` must equal the crate name;\na mismatch is rejected at the ACL layer before any command runs.\n\n## How it maps onto IPC\n\nUnary calls bypass Tauri's IPC entirely. Everything else uses three commands\nand one channel per call:\n\n| Connect concept                     | Tauri mechanism                                          |\n| ----------------------------------- | -------------------------------------------------------- |\n| Unary request and response          | `fetch(\"ipc-connect://…\")`, a custom URI scheme          |\n| Request head + first body chunk     | `invoke(\"connect_rpc\", …)` with a protobuf `ArrayBuffer` |\n| Response head (status + headers)    | Resolved value of that `invoke`                          |\n| Streaming response body frames      | `Channel<InvokeResponseBody>` carrying raw protobuf      |\n| Client-stream / bidi request frames | `invoke(\"connect_rpc_send\", …)`                          |\n| Cancellation                        | `invoke(\"connect_rpc_cancel\", …)`, from `AbortSignal`    |\n| Trailers                            | In-band, via Connect's own EndStreamResponse envelope    |\n\nThe envelopes are protobuf because Tauri's IPC payload is _either_ raw bytes or\nJSON, never both, and a JSON payload encodes bytes as one number per byte.\nProtobuf keeps every frame a single raw buffer, so Connect's bytes stay binary\nend to end. For the same reason the response channel id travels inside\n`StartRequest` rather than as a JSON command argument.\n\nChannels are used rather than events: Tauri's event system is broadcast and\nunordered relative to a call, so it would need correlation ids and dedup that a\nchannel provides for free.\n\n## Streaming and backpressure\n\nStreaming is incremental in both directions, not buffered. The request body is\nan mpsc-backed `http_body::Body` that `connect_rpc_send` feeds, so a webview\nwriting faster than the handler reads is held at `CHANNEL_DEPTH` chunks — the\npending `invoke` is the backpressure signal.\n\nOn the webview side that signal is a send in flight. Messages produced while\none is outstanding are coalesced into the next send rather than each taking a\nround trip, and a producer that outruns the hop by more than\n`MAX_PENDING_BYTES` waits for it instead of buffering the stream.\n\nEverything on the IPC path is async: all three commands are `async fn`, the\nservice is driven on its own task so the response head can return before the\nbody completes, and the response pump yields via the cooperative-scheduling\nbudget so an in-memory stream cannot monopolise a worker or outrun a cancel.\nThe one synchronous lock guards a hash map and is never held across an await,\nwhich `#![deny(clippy::await_holding_lock)]` enforces.\n\n## Layout\n\n```text\ncrates/connectrpc-tauri      Rust plugin\npackages/transport           @connectrpc-tauri/transport\nexamples/greet-app           demo app exercising all four kinds\nproto/                       transport envelopes + demo service\n```\n\n## Verifying\n\n```text\ncargo test -p connectrpc-tauri     # 12 tests over the real Connect runtime\nnpx vitest run                     # transport unit tests\n\n# End-to-end, in a real webview. Exits nonzero on failure.\nnpm run build --workspace @connectrpc-tauri/transport\nnpm run build --workspace greet-app\nGREET_APP_SELFTEST=1 cargo run --release -p greet-app --features custom-protocol\n```\n\nThe first two stub the IPC bridge on their own side. The third is the one that\nproves Tauri's real commands and channels carry the protocol: it runs all four\nmethod kinds plus a mid-stream cancel in a real webview, asserts the greetings\neach RPC returned, prints a transcript, and exits with a status.\n\n## Benchmarking\n\n```text\nnpm run build --workspace @connectrpc-tauri/transport\nnpm run build --workspace greet-app\nGREET_APP_BENCH=1 cargo run --release -p greet-app --features custom-protocol\n```\n\nThis times the transport against the floor of what Tauri IPC can do for the\nsame bytes: a raw `invoke` carrying protobuf, a raw `invoke` with Tauri's\ndefault JSON arguments, a bare `Channel` for streamed responses, and one\n`invoke` per message for streamed requests. It runs inside the webview because\nthat is where the cost is — the expensive part of Tauri IPC is crossing the\nwebview boundary, which a Rust-only benchmark never pays.\n\nTimes are batched rather than measured per call: WebKit clamps\n`performance.now()` to 1ms, which is coarser than an entire RPC.\n\nOn an M-series mac, release build:\n\n| Case                          | This transport | Best raw baseline |\n| ----------------------------- | -------------- | ----------------- |\n| unary, 16-byte request        | 0.203ms        | 0.195ms (1.04x)   |\n| unary, 4KiB request           | 0.227ms        | 0.223ms (1.02x)   |\n| unary, 64KiB request          | 0.363ms        | 0.340ms (1.07x)   |\n| server stream, 100 x 16 bytes | 0.797ms        | 1.453ms (0.55x)   |\n| server stream, 100 x 4KiB     | 2.938ms        | 7.875ms (0.37x)   |\n| client stream, 100 x 16 bytes | 1.156ms        | 19.250ms (0.06x)  |\n| client stream, 100 x 4KiB     | 2.250ms        | 21.500ms (0.10x)  |\n\nThe raw baseline above is a protobuf `invoke`, the fastest way to move the\nsame bytes by hand — one per message for the client-streaming rows, which is\nwhat a hand-written client sending a sequence would do.\n\nA unary call is within a few percent of a hand-written `invoke` — it no longer\ngoes through Tauri's IPC at all. Unary requests are a plain `fetch` against the\n`ipc-connect://` scheme the plugin registers, which skips the command router,\nthe ACL check, and the protobuf envelope. See\n[Unary skips IPC entirely](#unary-skips-ipc-entirely).\n\nBoth streaming directions come out ahead of hand-written IPC because neither\npays a webview crossing per message: responses arrive as `http_body` chunks\ncarrying many envelopes, and requests are coalesced on the way out. See\n[Streaming costs less than a crossing per message](#streaming-costs-less-than-a-crossing-per-message).\n\n### Why the IPC payload stays binary\n\nTauri's IPC payload is _either_ raw bytes or JSON, never both, and a JSON\npayload encodes bytes as one JSON number per byte. Sending Connect's bytes as\nJSON command arguments costs, at 64KiB:\n\n| Payload carried as     | 64KiB request |\n| ---------------------- | ------------- |\n| raw bytes (what we do) | 0.375ms       |\n| bytes inside JSON args | 5.063ms       |\n\nThat is 13x, and it grows with size: the same comparison is 1.04x at 16 bytes\nand 2.4x at 4KiB, because the blow-up is in the encoding, not a fixed cost.\nA JSON `invoke` moving a plain _string_ looks fast (0.375ms at 64KiB) — but a\nConnect message is bytes, and that is the row that applies.\n\n### Binary vs JSON Connect codec\n\nA separate question from the one above: `useBinaryFormat` picks how a _message_\nis encoded, while the IPC payload stays raw bytes either way. It is close to\nfree at these sizes, and binary stays the default:\n\n| Codec  | 16B     | 4KiB    | 64KiB   |\n| ------ | ------- | ------- | ------- |\n| binary | 0.258ms | 0.313ms | 0.430ms |\n| JSON   | 0.258ms | 0.309ms | 0.461ms |\n\nJSON is worth it only for readable traffic in a devtools or proxy view; on an\nin-process hop there is no such view to gain, and it costs re-encoding on\nlarger messages.\n\n### Streaming costs less than a crossing per message\n\nA webview crossing costs far more than the bytes it carries, so both streaming\ndirections are built to send fewer of them than there are messages.\n\nResponses come free from the protocol: Connect's envelopes arrive as\n`http_body` chunks, so many messages ride in one frame. Measured, 100 streamed\nmessages cross the IPC boundary as 2 frames, not 100 — where a naive `Channel`\nloop pays one crossing each.\n\nRequests need help, because Connect hands the client one message at a time.\nAwaiting an `invoke` per message made a client-streaming call cost a full round\ntrip per message — 100 messages took ~19ms, exactly tracking a hand-written\nloop of serial invokes. The pump now coalesces whatever the producer has\n_already_ coalesced into one send, which takes the same 100 messages to\n~1.2ms.\n\n\"Already produced\" is the entire subtlety. The pump never waits for a chunk\nthat has not been generated, so a message whose reply the client is waiting on\nstill leaves immediately and alone. That is what keeps bidi interactive: a\nbatching window measured in time would deadlock a conversation where the next\nrequest depends on the last response.\n\n### Unary skips IPC entirely\n\nTauri's IPC is a command router: a request crosses the webview boundary as an\n`InvokeBody`, is matched against a command name, checked against the ACL, then\nanswered. A unary Connect call needs none of that. It is already an HTTP\nrequest against a `tower::Service`, and the webview already speaks HTTP.\n\nSo the plugin registers its own URI scheme, `ipc-connect://`, and unary calls\ngo out as a plain `fetch`. Tauri's webview manager checks whether a scheme is\nalready registered before installing its own handler, so this coexists with\n`ipc://` rather than replacing it — every stock plugin keeps working.\n\nWhat that removes from a unary call: the protobuf envelope and its\nencode/decode, the command-name dispatch, the ACL permission lookup, the call\nregistry, and the channel. What is left is close to an identity mapping —\n`http::Request` in, service, `http::Response` out — which is why unary now\nlands within a few percent of a hand-written `invoke` rather than 15-20% above\nit.\n\nStreaming keeps the command-and-channel path. A scheme responder is one-shot on\nevery platform: it takes a complete response by value, and underneath it\nWKWebView does `didReceiveResponse` then a single `didReceiveData`, while\nWebKitGTK builds its stream from a finished buffer. Nothing can push a second\nframe, so routing a server-streaming response through the scheme would only\ndeliver it once buffered whole — which is exactly what streaming exists to\navoid.\n\nThe scheme is named `ipc-connect` rather than something like `ipc+connect`\nbecause on Windows and Android wry rewrites `scheme://localhost` to\n`http://scheme.localhost`, making the scheme name a hostname label. `+` is\nlegal in a URI scheme but not in a hostname, so it would work on macOS and\nLinux and break on Windows.\n\n`custom-protocol` is what makes Tauri serve the bundled assets. Without it,\nevery build — release included — loads `devUrl` and needs `npm run dev`\nrunning, since Tauri picks dev-vs-bundled from that feature rather than from\nthe cargo profile.\n\n## Limitations\n\n- Webview → Rust only. Rust-initiated calls into webview-hosted services are\n  out of scope.\n- Compression is off by default. It spends CPU to shrink bytes on an in-process\n  hop that never touches a network; the knob is exposed on both sides.\n- Android's IPC lacks `InvokeBody::Raw` and falls back to JSON number arrays. A\n  base64 fallback is not implemented. This affects streaming only; unary goes\n  over the `ipc-connect://` scheme and is unaffected.\n- The unary path has been verified on macOS. The scheme registration and the\n  Windows/Android hostname rewrite are handled, but are not yet exercised in\n  CI on those platforms.\n\n## Contributing\n\nStart with a\n[Discussion](https://github.com/mathematic-inc/connectrpc-tauri/discussions/new),\nnot a pull request. A Mathematic maintainer will review the proposal. If we\ndecide to implement it, a maintainer or one of our AI agents will open the pull\nrequest. GitHub restricts pull request creation to Mathematic maintainers and\nrepository collaborators with write, maintain, or admin access, plus authorized\nmaintenance agents.\n\nWhen Mathematic implements a proposal, the implementation pull request will\nlink to the Discussion and credit the proposal's original author.\n\nSee [CONTRIBUTING.md](https://github.com/mathematic-inc/connectrpc-tauri/blob/main/CONTRIBUTING.md) for the full policy.\n","readmeFilename":"README.md"}