[{"data":1,"prerenderedAt":675},["ShallowReactive",2],{"navigation":3,"-guide-pubsub":88,"-guide-pubsub-surround":672},[4,51],{"title":5,"path":6,"stem":7,"children":8,"icon":10},"Guide","\u002Fguide","1.guide\u002F1.index",[9,11,16,21,26,31,36,41,46],{"title":5,"path":6,"stem":7,"icon":10},"ph:book-open-duotone",{"title":12,"path":13,"stem":14,"icon":15},"Hooks","\u002Fguide\u002Fhooks","1.guide\u002F2.hooks","material-symbols-light:data-object",{"title":17,"path":18,"stem":19,"icon":20},"Peer","\u002Fguide\u002Fpeer","1.guide\u002F3.peer","mynaui:api",{"title":22,"path":23,"stem":24,"icon":25},"Message","\u002Fguide\u002Fmessage","1.guide\u002F4.message","solar:letter-line-duotone",{"title":27,"path":28,"stem":29,"icon":30},"Pub \u002F Sub","\u002Fguide\u002Fpubsub","1.guide\u002F5.pubsub","simple-icons:googlepubsub",{"title":32,"path":33,"stem":34,"icon":35},"Sync Backplane","\u002Fguide\u002Fsync","1.guide\u002F6.sync","tabler:refresh",{"title":37,"path":38,"stem":39,"icon":40},"Resolver API","\u002Fguide\u002Fresolver","1.guide\u002F7.resolver","tabler:route",{"title":42,"path":43,"stem":44,"icon":45},"WebSocket Proxy","\u002Fguide\u002Fproxy","1.guide\u002F8.proxy","tabler:arrows-exchange",{"title":47,"path":48,"stem":49,"icon":50},"WebSocket Client","\u002Fguide\u002Fclient","1.guide\u002F9.client","tabler:plug-connected",{"title":52,"path":53,"stem":54,"children":55,"icon":57},"Adapters","\u002Fadapters","2.adapters\u002F1.index",[56,58,63,68,73,78,83],{"title":52,"path":53,"stem":54,"icon":57},"emojione-monotone:electric-plug",{"title":59,"path":60,"stem":61,"icon":62},"Bun","\u002Fadapters\u002Fbun","2.adapters\u002Fbun","simple-icons:bun",{"title":64,"path":65,"stem":66,"icon":67},"Bunny","\u002Fadapters\u002Fbunny","2.adapters\u002Fbunny","mdi:rabbit",{"title":69,"path":70,"stem":71,"icon":72},"Cloudflare","\u002Fadapters\u002Fcloudflare","2.adapters\u002Fcloudflare","devicon-plain:cloudflareworkers",{"title":74,"path":75,"stem":76,"icon":77},"Deno","\u002Fadapters\u002Fdeno","2.adapters\u002Fdeno","teenyicons:deno-solid",{"title":79,"path":80,"stem":81,"icon":82},"Node.js","\u002Fadapters\u002Fnode","2.adapters\u002Fnode","akar-icons:node-fill",{"title":84,"path":85,"stem":86,"icon":87},"SSE","\u002Fadapters\u002Fsse","2.adapters\u002Fsse","clarity:two-way-arrows-line",{"id":89,"title":27,"body":90,"description":127,"extension":667,"meta":668,"navigation":669,"path":28,"seo":670,"stem":29,"__hash__":671},"content\u002F1.guide\u002F5.pubsub.md",{"type":91,"value":92,"toc":662,"icon":30},"minimark",[93,111,121,451,458,461,466,484,608,615,656,658],[94,95,96,97,101,102,106,107,110],"p",{},"crossws supports native pub-sub API integration. A ",[98,99,100],"a",{"href":18},"peer"," can be subscribed to a set of named topics using ",[103,104,105],"code",{},"peer.subscribe(\u003Cname>)",". Messages can be published to a topic using ",[103,108,109],{},"peer.publish(\u003Cname>, \u003Cmessage>)",". When publishing to a topic, the peer that does the publish is excluded from the broadcast so it won't receive its own message.",[112,113,114],"note",{},[94,115,116,117,120],{},"\nSelf-exclusion is always in effect, on every adapter: the publishing peer never receives its own message back. Adapters implement this in one of two equivalent ways — some (Node, Deno, SSE) explicitly filter the publisher out before sending, while others (Bun, μWebSockets) delegate straight to the runtime's native ",[103,118,119],{},"publish()",", which already excludes the sender by its own documented pub\u002Fsub semantics. The observable behavior is identical either way.",[122,123,128],"pre",{"className":124,"code":125,"language":126,"meta":127,"style":127},"language-js shiki shiki-themes github-light github-dark github-dark","import { defineHooks } from \"crossws\";\n\nconst hooks = defineHooks({\n  upgrade(req) {\n    return {\n      \u002F\u002F namespace: new URL(req.url).pathname\n    };\n  },\n  open(peer) {\n    \u002F\u002F Send welcome to the new client\n    peer.send(\"Welcome to the server!\");\n\n    \u002F\u002F Join new client to the \"chat\" topic\n    peer.subscribe(\"chat\");\n\n    \u002F\u002F Notify every other connected client\n    peer.publish(\"chat\", `[system] ${peer} joined!`);\n  },\n\n  message(peer, message) {\n    \u002F\u002F The server re-broadcasts incoming messages to everyone\n    peer.publish(\"chat\", `[${peer}] ${message}`);\n  },\n\n  close(peer) {\n    peer.publish(\"chat\", `[system] ${peer} has left the chat!`);\n    peer.unsubscribe(\"chat\");\n  },\n});\n","js","",[103,129,130,153,160,180,196,205,212,218,224,236,242,259,264,270,285,290,296,321,326,331,348,354,382,387,392,404,426,440,445],{"__ignoreMap":127},[131,132,135,139,143,146,150],"span",{"class":133,"line":134},"line",1,[131,136,138],{"class":137},"so5gQ","import",[131,140,142],{"class":141},"slsVL"," { defineHooks } ",[131,144,145],{"class":137},"from",[131,147,149],{"class":148},"sfrk1"," \"crossws\"",[131,151,152],{"class":141},";\n",[131,154,156],{"class":133,"line":155},2,[131,157,159],{"emptyLinePlaceholder":158},true,"\n",[131,161,163,166,170,173,177],{"class":133,"line":162},3,[131,164,165],{"class":137},"const",[131,167,169],{"class":168},"suiK_"," hooks",[131,171,172],{"class":137}," =",[131,174,176],{"class":175},"shcOC"," defineHooks",[131,178,179],{"class":141},"({\n",[131,181,183,186,189,193],{"class":133,"line":182},4,[131,184,185],{"class":175},"  upgrade",[131,187,188],{"class":141},"(",[131,190,192],{"class":191},"sQHwn","req",[131,194,195],{"class":141},") {\n",[131,197,199,202],{"class":133,"line":198},5,[131,200,201],{"class":137},"    return",[131,203,204],{"class":141}," {\n",[131,206,208],{"class":133,"line":207},6,[131,209,211],{"class":210},"sCsY4","      \u002F\u002F namespace: new URL(req.url).pathname\n",[131,213,215],{"class":133,"line":214},7,[131,216,217],{"class":141},"    };\n",[131,219,221],{"class":133,"line":220},8,[131,222,223],{"class":141},"  },\n",[131,225,227,230,232,234],{"class":133,"line":226},9,[131,228,229],{"class":175},"  open",[131,231,188],{"class":141},[131,233,100],{"class":191},[131,235,195],{"class":141},[131,237,239],{"class":133,"line":238},10,[131,240,241],{"class":210},"    \u002F\u002F Send welcome to the new client\n",[131,243,245,248,251,253,256],{"class":133,"line":244},11,[131,246,247],{"class":141},"    peer.",[131,249,250],{"class":175},"send",[131,252,188],{"class":141},[131,254,255],{"class":148},"\"Welcome to the server!\"",[131,257,258],{"class":141},");\n",[131,260,262],{"class":133,"line":261},12,[131,263,159],{"emptyLinePlaceholder":158},[131,265,267],{"class":133,"line":266},13,[131,268,269],{"class":210},"    \u002F\u002F Join new client to the \"chat\" topic\n",[131,271,273,275,278,280,283],{"class":133,"line":272},14,[131,274,247],{"class":141},[131,276,277],{"class":175},"subscribe",[131,279,188],{"class":141},[131,281,282],{"class":148},"\"chat\"",[131,284,258],{"class":141},[131,286,288],{"class":133,"line":287},15,[131,289,159],{"emptyLinePlaceholder":158},[131,291,293],{"class":133,"line":292},16,[131,294,295],{"class":210},"    \u002F\u002F Notify every other connected client\n",[131,297,299,301,304,306,308,311,314,316,319],{"class":133,"line":298},17,[131,300,247],{"class":141},[131,302,303],{"class":175},"publish",[131,305,188],{"class":141},[131,307,282],{"class":148},[131,309,310],{"class":141},", ",[131,312,313],{"class":148},"`[system] ${",[131,315,100],{"class":141},[131,317,318],{"class":148},"} joined!`",[131,320,258],{"class":141},[131,322,324],{"class":133,"line":323},18,[131,325,223],{"class":141},[131,327,329],{"class":133,"line":328},19,[131,330,159],{"emptyLinePlaceholder":158},[131,332,334,337,339,341,343,346],{"class":133,"line":333},20,[131,335,336],{"class":175},"  message",[131,338,188],{"class":141},[131,340,100],{"class":191},[131,342,310],{"class":141},[131,344,345],{"class":191},"message",[131,347,195],{"class":141},[131,349,351],{"class":133,"line":350},21,[131,352,353],{"class":210},"    \u002F\u002F The server re-broadcasts incoming messages to everyone\n",[131,355,357,359,361,363,365,367,370,372,375,377,380],{"class":133,"line":356},22,[131,358,247],{"class":141},[131,360,303],{"class":175},[131,362,188],{"class":141},[131,364,282],{"class":148},[131,366,310],{"class":141},[131,368,369],{"class":148},"`[${",[131,371,100],{"class":141},[131,373,374],{"class":148},"}] ${",[131,376,345],{"class":141},[131,378,379],{"class":148},"}`",[131,381,258],{"class":141},[131,383,385],{"class":133,"line":384},23,[131,386,223],{"class":141},[131,388,390],{"class":133,"line":389},24,[131,391,159],{"emptyLinePlaceholder":158},[131,393,395,398,400,402],{"class":133,"line":394},25,[131,396,397],{"class":175},"  close",[131,399,188],{"class":141},[131,401,100],{"class":191},[131,403,195],{"class":141},[131,405,407,409,411,413,415,417,419,421,424],{"class":133,"line":406},26,[131,408,247],{"class":141},[131,410,303],{"class":175},[131,412,188],{"class":141},[131,414,282],{"class":148},[131,416,310],{"class":141},[131,418,313],{"class":148},[131,420,100],{"class":141},[131,422,423],{"class":148},"} has left the chat!`",[131,425,258],{"class":141},[131,427,429,431,434,436,438],{"class":133,"line":428},27,[131,430,247],{"class":141},[131,432,433],{"class":175},"unsubscribe",[131,435,188],{"class":141},[131,437,282],{"class":148},[131,439,258],{"class":141},[131,441,443],{"class":133,"line":442},28,[131,444,223],{"class":141},[131,446,448],{"class":133,"line":447},29,[131,449,450],{"class":141},"});\n",[94,452,453,454,457],{},"By default pub\u002Fsub is in-memory and local to one instance. To span a cluster, relay messages over a shared backplane with a ",[98,455,456],{"href":33},"sync adapter",".",[459,460],"read-more",{"to":33},[462,463,465],"h2",{"id":464},"publishing-from-outside-a-peer","Publishing from outside a peer",[94,467,468,471,472,475,476,479,480,483],{},[103,469,470],{},"peer.publish()"," is convenient inside a hook, but sometimes you need to broadcast from somewhere that isn't tied to any single connection: an HTTP route, a cron job, or a webhook handler. The object returned by an adapter factory (e.g. ",[103,473,474],{},"nodeAdapter(...)",", or the instance backing ",[103,477,478],{},"crossws\u002Fadapters\u002Fcloudflare",") exposes this same capability directly, alongside a read-only ",[103,481,482],{},"peers"," map of every currently connected peer:",[122,485,487],{"className":124,"code":486,"language":126,"meta":127,"style":127},"import nodeAdapter from \"crossws\u002Fadapters\u002Fnode\";\n\nconst ws = nodeAdapter({ hooks: { message() {} } });\n\n\u002F\u002F Broadcast to every peer subscribed to \"chat\", in every namespace\nws.publish(\"chat\", \"Server is restarting for maintenance\");\n\n\u002F\u002F Inspect connected peers (Map\u003Cnamespace, Set\u003CPeer>>)\nfor (const [namespace, peers] of ws.peers) {\n  console.log(namespace, peers.size);\n}\n",[103,488,489,503,507,527,531,536,554,558,563,592,603],{"__ignoreMap":127},[131,490,491,493,496,498,501],{"class":133,"line":134},[131,492,138],{"class":137},[131,494,495],{"class":141}," nodeAdapter ",[131,497,145],{"class":137},[131,499,500],{"class":148}," \"crossws\u002Fadapters\u002Fnode\"",[131,502,152],{"class":141},[131,504,505],{"class":133,"line":155},[131,506,159],{"emptyLinePlaceholder":158},[131,508,509,511,514,516,519,522,524],{"class":133,"line":162},[131,510,165],{"class":137},[131,512,513],{"class":168}," ws",[131,515,172],{"class":137},[131,517,518],{"class":175}," nodeAdapter",[131,520,521],{"class":141},"({ hooks: { ",[131,523,345],{"class":175},[131,525,526],{"class":141},"() {} } });\n",[131,528,529],{"class":133,"line":182},[131,530,159],{"emptyLinePlaceholder":158},[131,532,533],{"class":133,"line":198},[131,534,535],{"class":210},"\u002F\u002F Broadcast to every peer subscribed to \"chat\", in every namespace\n",[131,537,538,541,543,545,547,549,552],{"class":133,"line":207},[131,539,540],{"class":141},"ws.",[131,542,303],{"class":175},[131,544,188],{"class":141},[131,546,282],{"class":148},[131,548,310],{"class":141},[131,550,551],{"class":148},"\"Server is restarting for maintenance\"",[131,553,258],{"class":141},[131,555,556],{"class":133,"line":214},[131,557,159],{"emptyLinePlaceholder":158},[131,559,560],{"class":133,"line":220},[131,561,562],{"class":210},"\u002F\u002F Inspect connected peers (Map\u003Cnamespace, Set\u003CPeer>>)\n",[131,564,565,568,571,573,576,579,581,583,586,589],{"class":133,"line":226},[131,566,567],{"class":137},"for",[131,569,570],{"class":141}," (",[131,572,165],{"class":137},[131,574,575],{"class":141}," [",[131,577,578],{"class":168},"namespace",[131,580,310],{"class":141},[131,582,482],{"class":168},[131,584,585],{"class":141},"] ",[131,587,588],{"class":137},"of",[131,590,591],{"class":141}," ws.peers) {\n",[131,593,594,597,600],{"class":133,"line":238},[131,595,596],{"class":141},"  console.",[131,598,599],{"class":175},"log",[131,601,602],{"class":141},"(namespace, peers.size);\n",[131,604,605],{"class":133,"line":244},[131,606,607],{"class":141},"}\n",[609,610,612],"h3",{"id":611},"publishtopic-message-compress-namespace",[103,613,614],{},"publish(topic, message, { compress?, namespace? })",[616,617,618,631],"ul",{},[619,620,621,626,627,630],"li",{},[622,623,624],"strong",{},[103,625,578],{},": scopes delivery to peers in that namespace only. Omit it for a ",[622,628,629],{},"global publish"," that reaches subscribers across every namespace.",[619,632,633,636,637,640,641,643,644,648,649,652,653,655],{},[622,634,635],{},"Timing",": delivery to local subscribers is fully ",[622,638,639],{},"synchronous"," — ",[103,642,119],{}," sends to every matching local peer before it returns, and it does not return a promise. The only asynchronous, best-effort part is relaying to ",[645,646,647],"em",{},"other"," instances over an optional ",[98,650,651],{"href":33},"sync backplane","; that relay is fire-and-forget and never throws out of ",[103,654,119],{},", so a flaky backplane can't block or crash the caller.",[459,657],{"to":33},[659,660,661],"style",{},"html pre.shiki code .so5gQ, html code.shiki .so5gQ{--shiki-light:#D73A49;--shiki-default:#F97583;--shiki-dark:#F97583}html pre.shiki code .slsVL, html code.shiki .slsVL{--shiki-light:#24292E;--shiki-default:#E1E4E8;--shiki-dark:#E1E4E8}html pre.shiki code .sfrk1, html code.shiki .sfrk1{--shiki-light:#032F62;--shiki-default:#9ECBFF;--shiki-dark:#9ECBFF}html pre.shiki code .suiK_, html code.shiki .suiK_{--shiki-light:#005CC5;--shiki-default:#79B8FF;--shiki-dark:#79B8FF}html pre.shiki code .shcOC, html code.shiki .shcOC{--shiki-light:#6F42C1;--shiki-default:#B392F0;--shiki-dark:#B392F0}html pre.shiki code .sQHwn, html code.shiki .sQHwn{--shiki-light:#E36209;--shiki-default:#FFAB70;--shiki-dark:#FFAB70}html pre.shiki code .sCsY4, html code.shiki .sCsY4{--shiki-light:#6A737D;--shiki-default:#6A737D;--shiki-dark:#6A737D}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"title":127,"searchDepth":155,"depth":155,"links":663},[664],{"id":464,"depth":155,"text":465,"children":665},[666],{"id":611,"depth":162,"text":614},"md",{"icon":30},{"icon":30},{"title":27,"description":127},"11qIDfG_oESC_HfutFaTbQUYbMlqdg2SJL0STnjeXec",[673,674],{"title":22,"path":23,"stem":24,"description":127,"icon":25,"children":-1},{"title":32,"path":33,"stem":34,"description":127,"icon":35,"children":-1},1783073589554]