{"_id":"mixed-signals","_rev":"7-5a39c1716881e20351b6d992e2c85d88","name":"mixed-signals","dist-tags":{"latest":"0.4.2"},"versions":{"0.1.0":{"name":"mixed-signals","version":"0.1.0","keywords":["preact","signals","rpc","reflection","reactive","realtime","websocket"],"author":{"url":"https://jasonformat.com","name":"Jason Miller"},"license":"MIT","_id":"mixed-signals@0.1.0","maintainers":[{"name":"developit","email":"jason@developit.ca"}],"homepage":"https://github.com/developit/mixed-signals#readme","bugs":{"url":"https://github.com/developit/mixed-signals/issues"},"dist":{"shasum":"80e863fa13a4f687c2fb68e092433f217cabbf41","tarball":"https://registry.npmjs.org/mixed-signals/-/mixed-signals-0.1.0.tgz","fileCount":22,"integrity":"sha512-eLMEwCNwwSlZWyZQQxVtR9SQoay9kMXWAhzRnv79lGyXBlgqYGbmxc/UzV/et7zT7ca78HWwUunTgGehKiRcRg==","signatures":[{"sig":"MEQCIDucyyRhyNkNPdS8fP6ZTBNngfMaQ64oVAvcnu190TyHAiAPFJXFGNvFgILXjbSF86IMGaYSXI54ovaMmoH314Xv1A==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":94569},"type":"module","exports":{"./client":{"types":"./build/client.d.ts","default":"./build/client.js"},"./server":{"types":"./build/server.d.ts","default":"./build/server.js"},"./package.json":"./package.json","./disposable.d.ts":"./disposable.d.ts"},"gitHead":"cbabd53ae11afbccec28493f01048fd2f044c83b","scripts":{"lint":"biome lint .","test":"vitest","build":"tsdown","check":"biome check --write .","format":"biome format --write .","docs:api":"node scripts/generate-readme.mjs","typecheck":"tsc","prepublishOnly":"tsdown && node scripts/generate-readme.mjs"},"_npmUser":{"name":"developit","email":"jason@developit.ca"},"repository":{"url":"git+https://github.com/developit/mixed-signals.git","type":"git"},"_npmVersion":"11.9.0","description":"RPC Reflection for Preact Models & Signals: use Models/Signals from a somewhere else (server/worker/etc) as if they were here.","directories":{},"_nodeVersion":"24.14.0","_hasShrinkwrap":false,"devDependencies":{"tsdown":"^0.20.0","vitest":"^4.0.0","typedoc":"^0.28.13","typescript":"^5.7.0","@biomejs/biome":"^2.4.2","@preact/signals-core":"^1.14.0"},"peerDependencies":{"@preact/signals-core":">=1.14.0"},"_npmOperationalInternal":{"tmp":"tmp/mixed-signals_0.1.0_1773892687148_0.596247295626662","host":"s3://npm-registry-packages-npm-production"}},"0.2.0":{"name":"mixed-signals","version":"0.2.0","keywords":["preact","signals","rpc","reflection","reactive","realtime","websocket"],"author":{"url":"https://jasonformat.com","name":"Jason Miller"},"license":"MIT","_id":"mixed-signals@0.2.0","maintainers":[{"name":"developit","email":"jason@developit.ca"}],"homepage":"https://github.com/developit/mixed-signals#readme","bugs":{"url":"https://github.com/developit/mixed-signals/issues"},"dist":{"shasum":"00c35b30693701c3328f1fff3d5b096a71967d3f","tarball":"https://registry.npmjs.org/mixed-signals/-/mixed-signals-0.2.0.tgz","fileCount":22,"integrity":"sha512-XpaEJcGbvdREZg6aFW4eNdx8Hckzz7S3DfSthUvt0wiW2NaphhV1appnz0qevQQIEBXu6fF15fyiI8PSorD/gw==","signatures":[{"sig":"MEUCIGa6v/DBllrqpojEM8F+44EucebdcXJSMCxCVeBQC6lHAiEAzt1gFHkOz2EmsPYqDjqqb/Erx/Gmnr6VzN+RPQX9XzI=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":84405},"type":"module","exports":{"./client":{"types":"./build/client.d.ts","default":"./build/client.js"},"./server":{"types":"./build/server.d.ts","default":"./build/server.js"},"./package.json":"./package.json","./disposable.d.ts":"./disposable.d.ts"},"gitHead":"468a378780bba014c00f99fa3e810518773a59c3","scripts":{"lint":"biome lint .","test":"vitest","build":"tsdown","check":"biome check --write .","format":"biome format --write .","docs:api":"node scripts/generate-readme.mjs","typecheck":"tsc","prepublishOnly":"tsdown && node scripts/generate-readme.mjs"},"_npmUser":{"name":"developit","email":"jason@developit.ca"},"repository":{"url":"git+https://github.com/developit/mixed-signals.git","type":"git"},"_npmVersion":"11.9.0","description":"RPC Reflection for Preact Models & Signals: use Models/Signals from a somewhere else (server/worker/etc) as if they were here.","directories":{},"_nodeVersion":"24.14.0","_hasShrinkwrap":false,"devDependencies":{"tsdown":"^0.20.0","vitest":"^4.0.0","typedoc":"^0.28.13","typescript":"^5.7.0","@biomejs/biome":"^2.4.2","@preact/signals-core":"^1.14.0"},"peerDependencies":{"@preact/signals-core":">=1.14.0"},"_npmOperationalInternal":{"tmp":"tmp/mixed-signals_0.2.0_1773897748021_0.8530095467837693","host":"s3://npm-registry-packages-npm-production"}},"0.2.1":{"name":"mixed-signals","version":"0.2.1","keywords":["preact","signals","rpc","reflection","reactive","realtime","websocket"],"author":{"url":"https://jasonformat.com","name":"Jason Miller"},"license":"MIT","_id":"mixed-signals@0.2.1","maintainers":[{"name":"developit","email":"jason@developit.ca"}],"homepage":"https://github.com/developit/mixed-signals#readme","bugs":{"url":"https://github.com/developit/mixed-signals/issues"},"dist":{"shasum":"6aaa63e663f75dc4b2eabf698475405455d90ed3","tarball":"https://registry.npmjs.org/mixed-signals/-/mixed-signals-0.2.1.tgz","fileCount":22,"integrity":"sha512-b6sod9KzU5v20WTghxTp3cffjR9pug3U7mZIMocjbkGaozd/RQ/Dohjj34kbyeWqAS6TPFFzioTnmJrohZ7wdQ==","signatures":[{"sig":"MEQCIBW8KBQga0GFT06J4RZr39Xsyc85WgNUkORPmlFHQ5SAAiBXcWGNyt1udZjyMCGm6000VncyYqt0n0al+1u3927PDA==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/mixed-signals@0.2.1","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":87809},"type":"module","_from":"file:mixed-signals-0.2.1.tgz","exports":{"./client":{"types":"./build/client.d.ts","default":"./build/client.js"},"./server":{"types":"./build/server.d.ts","default":"./build/server.js"},"./package.json":"./package.json","./disposable.d.ts":"./disposable.d.ts"},"scripts":{"lint":"biome lint .","test":"vitest","build":"tsdown","check":"biome check --write .","format":"biome format --write .","release":"changeset publish","docs:api":"node scripts/generate-readme.mjs","changeset":"changeset","typecheck":"tsc","version-packages":"changeset version"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:2f10b887-3b59-455e-9e9f-53f1b762c57d"}},"_resolved":"/tmp/1ece1d7273f85a606beab6454c725dc9/mixed-signals-0.2.1.tgz","_integrity":"sha512-b6sod9KzU5v20WTghxTp3cffjR9pug3U7mZIMocjbkGaozd/RQ/Dohjj34kbyeWqAS6TPFFzioTnmJrohZ7wdQ==","repository":{"url":"git+https://github.com/developit/mixed-signals.git","type":"git"},"_npmVersion":"11.11.0","description":"RPC Reflection for Preact Models & Signals: use Models/Signals from a somewhere else (server/worker/etc) as if they were here.","directories":{},"_nodeVersion":"24.14.1","publishConfig":{"access":"public","provenance":true},"_hasShrinkwrap":false,"devDependencies":{"tsdown":"^0.20.0","vitest":"^4.0.0","typedoc":"^0.28.13","typescript":"^5.7.0","@biomejs/biome":"^2.4.2","@changesets/cli":"^2.29.8","@preact/signals-core":"^1.14.0"},"peerDependencies":{"@preact/signals-core":">=1.14.0"},"_npmOperationalInternal":{"tmp":"tmp/mixed-signals_0.2.1_1775604643967_0.04297292346458925","host":"s3://npm-registry-packages-npm-production"}},"0.3.0":{"name":"mixed-signals","version":"0.3.0","keywords":["preact","signals","rpc","reflection","reactive","realtime","websocket"],"author":{"url":"https://jasonformat.com","name":"Jason Miller"},"license":"MIT","_id":"mixed-signals@0.3.0","maintainers":[{"name":"developit","email":"jason@developit.ca"}],"homepage":"https://github.com/developit/mixed-signals#readme","bugs":{"url":"https://github.com/developit/mixed-signals/issues"},"dist":{"shasum":"f10cec745ecf10a5c0767194e0cb48be35dce74d","tarball":"https://registry.npmjs.org/mixed-signals/-/mixed-signals-0.3.0.tgz","fileCount":22,"integrity":"sha512-ROW8mPmcxwOWP7kaebwyVKTgqK7vSjcu1WJcA03wV2iTm2eu8obwXluR69yxzX9u/2b8p6H08AIn3oSy/EF4oQ==","signatures":[{"sig":"MEUCIQDr7bmN+5MGw6k4w2kS+CEvZ2byxWm3WuEhnebA4RKHtgIgJp3ouIDDS0QIUTt7BhwD7XbpIQs2UzUljxbjsExO0Qw=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/mixed-signals@0.3.0","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":92752},"type":"module","_from":"file:mixed-signals-0.3.0.tgz","exports":{"./client":{"types":"./build/client.d.ts","default":"./build/client.js"},"./server":{"types":"./build/server.d.ts","default":"./build/server.js"},"./package.json":"./package.json","./disposable.d.ts":"./disposable.d.ts"},"scripts":{"lint":"biome lint .","test":"vitest","build":"tsdown","check":"biome check --write .","format":"biome format --write .","release":"changeset publish","docs:api":"node scripts/generate-readme.mjs","changeset":"changeset","typecheck":"tsc","version-packages":"changeset version"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:2f10b887-3b59-455e-9e9f-53f1b762c57d"}},"_resolved":"/tmp/aeb2c7b88f3f0e5e37b699061ad5a9dd/mixed-signals-0.3.0.tgz","_integrity":"sha512-ROW8mPmcxwOWP7kaebwyVKTgqK7vSjcu1WJcA03wV2iTm2eu8obwXluR69yxzX9u/2b8p6H08AIn3oSy/EF4oQ==","repository":{"url":"git+https://github.com/developit/mixed-signals.git","type":"git"},"_npmVersion":"11.11.0","description":"RPC Reflection for Preact Models & Signals: use Models/Signals from a somewhere else (server/worker/etc) as if they were here.","directories":{},"_nodeVersion":"24.14.1","publishConfig":{"access":"public","provenance":true},"_hasShrinkwrap":false,"devDependencies":{"tsdown":"^0.20.0","vitest":"^4.0.0","typedoc":"^0.28.13","typescript":"^5.7.0","@biomejs/biome":"^2.4.2","@changesets/cli":"^2.29.8","@preact/signals-core":"^1.14.0"},"peerDependencies":{"@preact/signals-core":">=1.14.0"},"_npmOperationalInternal":{"tmp":"tmp/mixed-signals_0.3.0_1778598671002_0.7097055074203495","host":"s3://npm-registry-packages-npm-production"}},"0.4.0":{"name":"mixed-signals","version":"0.4.0","keywords":["preact","signals","rpc","reflection","reactive","realtime","websocket"],"author":{"url":"https://jasonformat.com","name":"Jason Miller"},"license":"MIT","_id":"mixed-signals@0.4.0","maintainers":[{"name":"developit","email":"jason@developit.ca"}],"homepage":"https://github.com/developit/mixed-signals#readme","bugs":{"url":"https://github.com/developit/mixed-signals/issues"},"dist":{"shasum":"792caa827eb3a171cfad9416f62f2b75b2637473","tarball":"https://registry.npmjs.org/mixed-signals/-/mixed-signals-0.4.0.tgz","fileCount":22,"integrity":"sha512-/sXeu+gtW1DjfMvT+sl1kyXtQf0TAEwPEz2aTNclOXMQPmVqANR8dUkb3birwcZyOKxo7yPWJdmgACi5nfUzow==","signatures":[{"sig":"MEUCIBmrii+eZIdxD72fLlwIbN0zP+9tWK6YswqUHwcMvImGAiEAo0hrIdcdgXlh/b55BfG+EGg26VMHGts+hZ8gV7v1UWs=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/mixed-signals@0.4.0","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":175222},"type":"module","exports":{"./client":{"types":"./build/client.d.ts","default":"./build/client.js"},"./server":{"types":"./build/server.d.ts","default":"./build/server.js"},"./package.json":"./package.json","./disposable.d.ts":"./disposable.d.ts"},"gitHead":"c3206b6e4cbb9af245c67d2f5848d446cd2bf559","scripts":{"lint":"biome lint .","test":"vitest","build":"tsdown","check":"biome check --write .","format":"biome format --write .","release":"changeset publish","version":"changeset version && pnpm install --lockfile-only","docs:api":"node scripts/generate-readme.mjs","changeset":"changeset","typecheck":"tsc","prepublishOnly":"tsdown && node scripts/generate-readme.mjs"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","approver":{"name":"developit","email":"jason@developit.ca"},"trustedPublisher":{"id":"github","oidcConfigId":"oidc:09970ce5-d282-46e6-b5d7-79b033d9423e"}},"repository":{"url":"git+https://github.com/developit/mixed-signals.git","type":"git"},"_npmVersion":"11.15.0","description":"RPC Reflection for Preact Models & Signals: use Models/Signals from a somewhere else (server/worker/etc) as if they were here.","directories":{},"_nodeVersion":"22.23.1","publishConfig":{"access":"public","provenance":true},"_hasShrinkwrap":false,"devDependencies":{"tsdown":"^0.20.0","vitest":"^4.0.0","typedoc":"^0.28.13","typescript":"^5.7.0","@biomejs/biome":"^2.4.2","@changesets/cli":"^2.29.8","@preact/signals-core":"^1.14.0"},"peerDependencies":{"@preact/signals-core":">=1.14.0"},"_npmOperationalInternal":{"tmp":"tmp/mixed-signals_0.4.0_1785272365254_0.16485742825849625","host":"s3://npm-registry-packages-npm-production"}},"0.4.1":{"name":"mixed-signals","version":"0.4.1","keywords":["preact","signals","rpc","reflection","reactive","realtime","websocket"],"author":{"url":"https://jasonformat.com","name":"Jason Miller"},"license":"MIT","_id":"mixed-signals@0.4.1","maintainers":[{"name":"developit","email":"jason@developit.ca"}],"homepage":"https://github.com/developit/mixed-signals#readme","bugs":{"url":"https://github.com/developit/mixed-signals/issues"},"dist":{"shasum":"79cc87b918cadf84f08cc7ff59928e336ca905a2","tarball":"https://registry.npmjs.org/mixed-signals/-/mixed-signals-0.4.1.tgz","fileCount":22,"integrity":"sha512-gPQTA6O4Z8bKypMIMfV+xpUAwi1Isz6wL/ak+GXAzMte+XTccVszrugprKCrO9Ocwbir7c5kedHkXFayjDz9uA==","signatures":[{"sig":"MEUCIBVTMIHnde0238bxEpkutM2cMWZw8tI8JdMBn/vYTVuyAiEA/+KLVT0DeSpsJ7fQbhhRx8m6HesGPzn1Z4d6DgIwNf8=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/mixed-signals@0.4.1","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":179318},"type":"module","exports":{"./client":{"types":"./build/client.d.ts","default":"./build/client.js"},"./server":{"types":"./build/server.d.ts","default":"./build/server.js"},"./package.json":"./package.json","./disposable.d.ts":"./disposable.d.ts"},"gitHead":"6cc25342643dbabfbb7d7a4acacf4ed22c7f770c","scripts":{"lint":"biome lint .","test":"vitest","build":"tsdown","check":"biome check --write .","format":"biome format --write .","prepare":"tsdown","release":"changeset publish","version":"changeset version && pnpm install --lockfile-only","docs:api":"node scripts/generate-readme.mjs","changeset":"changeset","typecheck":"tsc","prepublishOnly":"tsdown && node scripts/generate-readme.mjs"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","approver":{"name":"developit","email":"jason@developit.ca"},"trustedPublisher":{"id":"github","oidcConfigId":"oidc:09970ce5-d282-46e6-b5d7-79b033d9423e"}},"repository":{"url":"git+https://github.com/developit/mixed-signals.git","type":"git"},"_npmVersion":"11.15.0","description":"RPC Reflection for Preact Models & Signals: use Models/Signals from a somewhere else (server/worker/etc) as if they were here.","directories":{},"_nodeVersion":"22.23.1","publishConfig":{"access":"public","provenance":true},"_hasShrinkwrap":false,"devDependencies":{"tsdown":"^0.20.0","vitest":"^4.0.0","typedoc":"^0.28.13","typescript":"^5.7.0","@biomejs/biome":"^2.4.2","@changesets/cli":"^2.29.8","@preact/signals-core":"^1.14.0"},"peerDependencies":{"@preact/signals-core":">=1.14.0"},"_npmOperationalInternal":{"tmp":"tmp/mixed-signals_0.4.1_1785299186884_0.8762367417813088","host":"s3://npm-registry-packages-npm-production"}},"0.4.2":{"_id":"mixed-signals@0.4.2","bugs":{"url":"https://github.com/developit/mixed-signals/issues"},"dist":{"shasum":"a5a2743dc389b643e08643816e088f13c1d8c2fa","tarball":"https://registry.npmjs.org/mixed-signals/-/mixed-signals-0.4.2.tgz","integrity":"sha512-oKMega/MK6oMC/G3gyc09SUq4iXSxrH7y32hZuSWCy2a3xHZo25iVh/1wYhlKc4mA5dLjXgAmIHu5z0+QhuHqg==","fileCount":22,"unpackedSize":190215,"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/mixed-signals@0.4.2","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIAzkZTq72ikyp/l7iClzFsRMrlAc45gjzrLr073hL8xcAiEA7ahvRk9TRyh7Uu7sfGYU/PUa6i7Fe7P1QmkEnFb2sU8="}]},"name":"mixed-signals","type":"module","author":{"url":"https://jasonformat.com","name":"Jason Miller"},"exports":{"./client":{"types":"./build/client.d.ts","default":"./build/client.js"},"./server":{"types":"./build/server.d.ts","default":"./build/server.js"},"./package.json":"./package.json","./disposable.d.ts":"./disposable.d.ts"},"gitHead":"e7ddeea7d5dd91199bab2ae753bd1bba2ac30087","license":"MIT","scripts":{"lint":"biome lint .","test":"vitest","build":"tsdown","check":"biome check --write .","format":"biome format --write .","prepare":"tsdown","release":"changeset publish","version":"changeset version && pnpm install --lockfile-only","docs:api":"node scripts/generate-readme.mjs","changeset":"changeset","typecheck":"tsc","prepublishOnly":"tsdown && node scripts/generate-readme.mjs"},"version":"0.4.2","_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:09970ce5-d282-46e6-b5d7-79b033d9423e"},"approver":{"name":"developit","email":"jason@developit.ca"}},"homepage":"https://github.com/developit/mixed-signals#readme","keywords":["preact","signals","rpc","reflection","reactive","realtime","websocket"],"repository":{"url":"git+https://github.com/developit/mixed-signals.git","type":"git"},"_npmVersion":"11.15.0","description":"RPC Reflection for Preact Models & Signals: use Models/Signals from a somewhere else (server/worker/etc) as if they were here.","directories":{},"maintainers":[{"name":"developit","email":"jason@developit.ca"}],"_nodeVersion":"22.23.2","publishConfig":{"access":"public","provenance":true},"devDependencies":{"tsdown":"^0.20.0","vitest":"^4.0.0","typedoc":"^0.28.13","typescript":"^5.7.0","@biomejs/biome":"^2.4.2","@changesets/cli":"^2.29.8","@preact/signals-core":"^1.14.0"},"peerDependencies":{"@preact/signals-core":">=1.14.0"},"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/mixed-signals_0.4.2_1786991328352_0.440419897023381"},"_hasShrinkwrap":false}},"time":{"created":"2026-03-19T03:58:07.045Z","modified":"2026-08-17T18:28:48.797Z","0.1.0":"2026-03-19T03:58:07.312Z","0.2.0":"2026-03-19T05:22:28.165Z","0.2.1":"2026-04-07T23:30:44.104Z","0.3.0":"2026-05-12T15:11:11.154Z","0.4.0":"2026-07-28T20:59:25.351Z","0.4.1":"2026-07-29T04:26:26.957Z","0.4.2":"2026-08-17T18:28:48.476Z"},"bugs":{"url":"https://github.com/developit/mixed-signals/issues"},"author":{"url":"https://jasonformat.com","name":"Jason Miller"},"license":"MIT","homepage":"https://github.com/developit/mixed-signals#readme","keywords":["preact","signals","rpc","reflection","reactive","realtime","websocket"],"repository":{"url":"git+https://github.com/developit/mixed-signals.git","type":"git"},"description":"RPC Reflection for Preact Models & Signals: use Models/Signals from a somewhere else (server/worker/etc) as if they were here.","maintainers":[{"name":"developit","email":"jason@developit.ca"}],"readme":"# mixed-signals\n\nRPC + reflection for [Preact Signals and Models](https://github.com/preactjs/signals): access reactive model state and methods from a server (or worker/tab/etc) as if they lived on the client. Type-safe, minimal magic, and an optimized transport-agnostic protocol (WebSocket, SSE, postMessage, etc).\n\n**Installation:**\n\n```sh\nnpm install mixed-signals\n```\n\nThe only dependency is `@preact/signals-core` (>=1.8.0).\n\n## How it works\n\n**mixed-signals** reflects server-side Preact Models and Signals (anything created via `@preact/signals-core`) to connected clients in real-time. Signals on the server are serialized with identity markers, and the client reconstructs them as local signals that stay in sync via a lightweight wire protocol.\n\n- **Server** models use `createModel()` from `mixed-signals/server` _(a thin wrapper around `@preact/signals-core`'s `createModel`)_\n- **Client** models are proxy facades created automatically from the server-sent model definition; no client-side model registry is required\n- An **RPC** layer handles method calls (client → server) and signal updates (server → client)\n- Delta compression for arrays (append), objects (merge), and strings (append) minimizes bandwidth\n\n## Full Example\n\n### `server.ts`\n\n```ts\nimport { WebSocketServer } from \"ws\";\nimport { signal } from \"@preact/signals-core\";\nimport { RPC, createModel } from \"mixed-signals/server\";\n\nconst Todo = createModel((_text = \"\") => {\n  const text = signal(_text);\n  const done = signal(false);\n  const toggle = () => done.value = !done.value;\n  return { text, done, toggle };\n});\ntype Todo = InstanceType<typeof Todo>;\n\nconst Todos = createModel(() => {\n  const all = signal<Todo[]>([]);\n  function add(text: string) {\n    const todo = new Todo(text);\n    all.value = [...all.value, todo];\n    return todo;\n  }\n  return { all, add };\n});\ntype Todos = InstanceType<typeof Todos>;\n\nconst todos = new Todos();\nconst rpc = new RPC({ todos });\nrpc.registerModel(\"Todo\", Todo);\nrpc.registerModel(\"Todos\", Todos);\n\nconst wss = new WebSocketServer();\nwss.on(\"connection\", (ws, request) => {\n  // Feed a previously negotiated id back in here if your WebSocket URL\n  // carries one. The server will report whether this process recognizes it.\n  const connectionId =\n    new URL(request.url, \"http://localhost\").searchParams.get(\"connectionId\") ??\n    undefined;\n  rpc.addClient(\n    {\n      send: ws.send.bind(ws),\n      onMessage: ws.on.bind(ws, \"message\"),\n      onClose: (cb) => ws.on(\"close\", cb),\n    },\n    connectionId,\n  );\n});\n```\n\n### `client.tsx`\n\n```tsx\nimport { useSignal } from \"@preact/signals\";\nimport { RPCClient } from \"mixed-signals/client\";\nimport type { Todos } from \"./server.ts\";\n\ntype Root = { todos: Todos };\n\nconst ws = new WebSocket(\"/rpc\");\nconst rpc = new RPCClient<Root>({\n  send: ws.send.bind(ws),\n  onMessage: ws.addEventListener.bind(ws, \"message\"),\n  onClose: (cb) => ws.addEventListener(\"close\", () => cb(), { once: true }),\n  ready: new Promise((r) => ws.addEventListener(\"open\", r, { once: true })),\n});\n\nfunction Demo({ ctx }: { ctx: RPCClient<Root>[\"root\"] }) {\n  const text = useSignal('');\n\n  function add(e) {\n    e.preventDefault();\n    ctx.todos.add(text.value);\n    text.value = '';\n  }\n\n  return <>\n    <ul>\n      <For each={ctx.todos.all}>\n        {todo => (\n          <li>\n            <input type=\"checkbox\" checked={todo.done} />\n            {todo.text}\n          </li>\n        )}\n      </For>\n    </ul>\n    <form onSubmit={add}>\n      <input value={text} onInput={e => text.value = e.target.value} />\n    </form>\n  </>;\n}\n\nrpc.ready.then(() => {\n  render(<Demo ctx={rpc.root} />, document.body);\n});\n```\n\nIf you do not want to pass the root type at each construction site, augment the client module once:\n\n```ts\nimport type { Todos } from \"./server.ts\";\n\ndeclare module \"mixed-signals/client\" {\n  interface ReflectedRoot {\n    todos: Todos;\n  }\n}\n\nconst rpc = new RPCClient(transport); // rpc.root is typed from ReflectedRoot\n```\n\n`createReflectedModel()` is still exported for older clients that prefer declared reflected facades, and `registerModel()` remains the custom-facade escape hatch. Ordinary reflected models need neither. Unknown model types are safe: the client builds a proxy from the received signal properties, and a missing method call rejects with the server's `Method not found` error.\n\n## Reconnects\n\n`RPCClient.reconnect(newTransport)` swaps in a new transport while keeping the\nexisting root object, signals, and reflected model facades alive. The next `@R`\nroot snapshot refreshes existing signal values, rebinds root signals if the new\nserver process assigned different signal ids, refreshes cached model facades,\nrequests fresh snapshots for active held model facades that were not present in\nthe reconnect root, and replays currently watched signal subscriptions. After a\nprocess change, inactive held facades are marked stale; if one of their signal\nprops becomes watched later, the client lazily refreshes that facade before\nreplaying the new signal id. Only explicit protocol identities are preserved:\n`@S` signals and `@M` model facades. Unbranded nested arrays and plain objects\nare replaced instead of reconciled by index or shape. Watches are replayed once\nimmediately from the ids already known in the root snapshot and again after\nheld-model refreshes bind any additional signal ids.\n\nClient reflection caches use weak references when the runtime supports them, so\ncached signal ids and reflected model markers do not by themselves keep\nunwatched, otherwise-unheld client objects alive. Watched signals remain strongly\ntracked until their `@U` can be sent and so reconnect replay remains\ndeterministic.\n\nServers include an opaque `connectionId` and `processId` with each root\nsnapshot. If the client reconnects with the same `connectionId`, the server's\n`resumed` flag tells you whether this connection replaced active retained state\nfor that id. Same-process reconnects keep matching signal ids; different-process\nreconnects still work when the new root snapshot contains the same logical model\nids. If a root snapshot omits connection metadata, the client treats it as an\nunknown/new process once a root already exists, which keeps legacy servers safe\nby avoiding raw signal-id reuse. Held model facades can also recover when the new\nserver process can resolve their `Type#id` markers from its instance registry.\nFor reconnectable transports (`onOpen` present), `client.ready` remains pending\nif the transport disconnects before the first root snapshot and never opens\nagain. Callers that need a hard failure should wrap `ready` in their own timeout\nor abort signal.\n\n## API\n\n_Generated from TypeScript declarations._\n\n### `mixed-signals/server`\n\n#### `createMemoryTransportPair`\n\n- Kind: **Function**\n- Signatures:\n  - `() => tuple` — Creates two linked Transport instances for in-process communication.\nMessages sent on one end are delivered to the other via queueMicrotask.\n\n#### `createModel`\n\n- Kind: **Function**\n- Signatures:\n  - `(factory: ModelFactory<TModel, TFactoryArgs>) => ModelConstructor<TModel, TFactoryArgs>`\n\n#### `RPC`\n\n- Kind: **Class**\n- Constructor:\n  - `new RPC(root?: any) => RPC`\n- Methods:\n  - `addClient(transport: Transport, clientId?: string) => () => void`\n  - `addUpstream(transport: Transport) => () => void` — Register an upstream mixed-signals connection whose models are forwarded\nto downstream clients. All models from the upstream are automatically\nforwarded — no per-model declaration needed.\n  - `expose(root: any) => void`\n  - `notify(method: string, params: any[], clientId?: string) => void`\n  - `registerModel(name: string, Ctor: ModelConstructor) => void`\n\n### `mixed-signals/client`\n\n#### `createReflectedModel`\n\n- Kind: **Function**\n- Signatures:\n  - `(signalProps?: readonly string[], methods: readonly string[]) => ModelConstructor<T, tuple>`\n\n#### `Reflected`\n\n- Kind: **Type alias**\n- Client-side shape for a server value after mixed-signals reflection.\nSignals become read-only client signals and methods become async RPC calls.\n- Type: `conditional`\n\n#### `ReflectedMethod`\n\n- Kind: **Type alias**\n- Type: `(args: Parameters<T>) => Promise<Reflected<Awaited<ReturnType<T>>>>`\n\n#### `ReflectedModel`\n\n- Kind: **Type alias**\n- Client-side facade shape for a reflected server model instance.\n- Type: `ReflectedObject<T> & { id: ReadonlySignal<string> }`\n\n#### `ReflectedObject`\n\n- Kind: **Type alias**\n- Type: `mapped`\n\n#### `ReflectedRoot`\n\n- Kind: **Interface**\n- Declaration-merging hook for projects that want a typed `RPCClient.root`\nwithout passing a generic at every construction site.\n\n#### `RPCClient`\n\n- Kind: **Class**\n- Constructor:\n  - `new RPCClient(transport: Transport, ctx?: any, options?: RPCClientOptions) => RPCClient<TRoot>`\n- Methods:\n  - `call(method: string, params?: any) => Promise<any>`\n  - `expose(root: any) => void` — Publish an object as the dispatch target for peer-issued method\ncalls. Mirrors the server's `RPC.expose`: an inbound `M{id}:method`\nframe is dispatched against this root using the same dot-notation\nlookup the server uses for nested method calls (e.g. `\"browser.logs\"`\nwalks `root.browser.logs`). Returning a non-promise sends `R{id}`\nwith the value; throwing or rejecting sends `E{id}` with the\n`{code, message}` shape. Calling `expose` again replaces the prior\nroot.\n  - `notify(method: string, params?: any[]) => void`\n  - `onNotification(cb: (method: string, params: any[]) => void) => () => void`\n  - `reconnect(transport: Transport) => void` — Replace the transport for a reconnection. Cached roots, signals and model\nfacades are kept alive so the next `@R` snapshot can refresh/rebind them,\nthen currently watched signals are replayed on the new connection.\n  - `registerModel(typeName: string, ctor?: (ctx: any, data: any) => any) => void` — Optionally register a custom facade constructor for a server model type.\nUnregistered model types are reflected with proxy facades automatically.\n- Properties:\n  - `connectionId: string | undefined` — Opaque server-assigned id that can be sent back on a future reconnect.\n  - `connectionInfo: ConnectionInfo | undefined` — Metadata from the server process that sent the latest root snapshot.\n  - `ready: Promise<void>`\n  - `root: Reflected<TRoot>`\n\n#### `RPCClientOptions`\n\n- Kind: **Interface**\n- Properties:\n  - `staleTimeout: number | false` — How long a call may wait with zero inbound traffic before the transport\nis presumed dead. When the deadline passes, pending calls reject, the\nnormal disconnect lifecycle runs, and `transport.close()` is called if\nthe transport provides it.\n\nThis exists because a connection can go half-open: the network path is\ngone (laptop slept, NAT entry expired, server died without a FIN) but no\nclose event fires, sometimes for minutes. Browsers don't expose\nWebSocket ping/pong to JavaScript, so without a deadline a call issued\non such a socket waits forever.\n\nAny inbound frame resets the clock, so a slow response on a connection\nthat is otherwise delivering traffic is not treated as staleness.\nDefaults to 30 seconds. Set to `false` for transports that can't go\nhalf-open, like a MessagePort to a worker, where blocking past the\ndeadline is legitimate.\n\n### Shared\n\n#### `ConnectionInfo`\n\n- Kind: **Interface**\n- Properties:\n  - `connectionId: string` — Opaque id that can be supplied on a future server addClient() call.\n  - `processId: string` — Opaque id for the server process that accepted this connection.\n  - `resumed: boolean` — True when this connection replaced active state retained for connectionId.\n\n#### `Transport`\n\n- Kind: **Interface**\n- Methods:\n  - `close() => void` — Close the underlying connection. The client calls this when it gives up\non a stale transport, so the dead connection doesn't linger.\n  - `onClose(cb: (error?: unknown) => void) => void`\n  - `onMessage(cb: (data: { toString: unknown }) => void) => void`\n  - `onOpen(cb: () => void) => void`\n  - `send(data: string) => void`\n- Properties:\n  - `ready: Promise<void>`\n\n","readmeFilename":"README.md"}