<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
	<channel>
		<title>Documentation on WebSub</title>
		<link>https://websub.jazzmoon.ca/</link>
		<description>Recent content in Documentation on WebSub</description>
		<generator>Hugo</generator>
		<language>en-US</language>
		
		
		
		
			<atom:link href="https://websub.jazzmoon.ca/index.xml" rel="self" type="application/rss+xml" />
			<item>
				<title>Architecture</title>
				<link>https://websub.jazzmoon.ca/architecture/</link>
				<pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
				<guid>https://websub.jazzmoon.ca/architecture/</guid>
				<description>&lt;h1 id=&#34;architecture&#34;&gt;Architecture&lt;a class=&#34;anchor&#34; href=&#34;#architecture&#34;&gt;#&lt;/a&gt;&lt;/h1&gt;&#xA;&lt;h2 id=&#34;modules&#34;&gt;Modules&lt;a class=&#34;anchor&#34; href=&#34;#modules&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;p&gt;This is a multi-module repository. Each module is versioned and released on&#xA;its own, so nothing you do not use ends up in your build.&lt;/p&gt;&#xA;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;(repo root)      github.com/Jazzmoon/websub             the protocol, no third party imports&#xA;  tests/                                                black box test suite&#xA;  tests/conformance/                                    the spec cases, importable by anyone&#xA;adapter/gin/     github.com/Jazzmoon/websub/adapter/gin&#xA;adapter/echo/    github.com/Jazzmoon/websub/adapter/echo&#xA;adapter/fiber/   github.com/Jazzmoon/websub/adapter/fiber&#xA;storage/redis/   github.com/Jazzmoon/websub/storage/redis&#xA;storage/postgres/&#xA;storage/sqlite/&#xA;storage/storetest/                                      the shared backend suite&#xA;websubtest/      github.com/Jazzmoon/websub/websubtest  in-process hub for your own tests&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The protocol implementation lives at the repository root rather than in a&#xA;named subdirectory, because Go requires a module&amp;rsquo;s &lt;code&gt;go.mod&lt;/code&gt; to physically&#xA;sit wherever its declared path implies; &lt;code&gt;github.com/Jazzmoon/websub&lt;/code&gt; is the&#xA;bare repo path, so its &lt;code&gt;go.mod&lt;/code&gt; has to be the repo root&amp;rsquo;s &lt;code&gt;go.mod&lt;/code&gt;. Every&#xA;other module here follows the more familiar pattern, where the subdirectory&#xA;name is also the path suffix.&lt;/p&gt;</description>
			</item>
			<item>
				<title>Design decisions</title>
				<link>https://websub.jazzmoon.ca/design/</link>
				<pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
				<guid>https://websub.jazzmoon.ca/design/</guid>
				<description>&lt;h1 id=&#34;design-decisions&#34;&gt;Design decisions&lt;a class=&#34;anchor&#34; href=&#34;#design-decisions&#34;&gt;#&lt;/a&gt;&lt;/h1&gt;&#xA;&lt;p&gt;Decisions that could reasonably have gone the other way, and why they went&#xA;this way. If you are about to propose a change to one of these, this is the&#xA;argument you are arguing against.&lt;/p&gt;&#xA;&lt;h2 id=&#34;principles&#34;&gt;Principles&lt;a class=&#34;anchor&#34; href=&#34;#principles&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;ol&gt;&#xA;&lt;li&gt;&lt;strong&gt;Core has zero third party dependencies.&lt;/strong&gt; Only the standard library.&#xA;Anything pulling in an external module goes in its own module.&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;Every HTTP facing type implements &lt;code&gt;http.Handler&lt;/code&gt;.&lt;/strong&gt; This is what makes&#xA;every router adapter possible.&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;WebSub is the default, PubSubHubbub 0.4 is opt in.&lt;/strong&gt; The differences&#xA;live in one table in &lt;code&gt;mode.go&lt;/code&gt;, never forked into parallel code paths.&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;Every exported symbol has a doc comment.&lt;/strong&gt; Comments explain what is not&#xA;obvious. A comment restating the line below it is noise; one explaining&#xA;why the spec demands something is not.&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;h2 id=&#34;one-package-not-three&#34;&gt;One package, not three&lt;a class=&#34;anchor&#34; href=&#34;#one-package-not-three&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;p&gt;The three roles share &lt;code&gt;Subscription&lt;/code&gt;, &lt;code&gt;Mode&lt;/code&gt;, &lt;code&gt;Discovery&lt;/code&gt;, &lt;code&gt;Sign&lt;/code&gt;, &lt;code&gt;Verify&lt;/code&gt;,&#xA;&lt;code&gt;SubscriptionStore&lt;/code&gt;, and every option. Splitting into &lt;code&gt;websub/subscriber&lt;/code&gt;,&#xA;&lt;code&gt;websub/hub&lt;/code&gt;, and &lt;code&gt;websub/publisher&lt;/code&gt; would mean either a base package that&#xA;all three import anyway, which is indirection rather than separation, or&#xA;duplicating &lt;code&gt;WithMode&lt;/code&gt;, &lt;code&gt;WithLogger&lt;/code&gt;, and &lt;code&gt;WithHTTPClient&lt;/code&gt; three times.&lt;/p&gt;</description>
			</item>
			<item>
				<title>Specification notes</title>
				<link>https://websub.jazzmoon.ca/specs/</link>
				<pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
				<guid>https://websub.jazzmoon.ca/specs/</guid>
				<description>&lt;h1 id=&#34;specification-notes&#34;&gt;Specification notes&lt;a class=&#34;anchor&#34; href=&#34;#specification-notes&#34;&gt;#&lt;/a&gt;&lt;/h1&gt;&#xA;&lt;p&gt;This library implements two specifications that mostly agree:&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://www.w3.org/TR/websub/&#34;&gt;WebSub&lt;/a&gt;, a W3C Recommendation, the default&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://pubsubhubbub.github.io/PubSubHubbub/pubsubhubbub-core-0.4.html&#34;&gt;PubSubHubbub 0.4&lt;/a&gt;,&#xA;its predecessor, behind &lt;code&gt;WithMode(websub.ModePuSH04)&lt;/code&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;The public API is identical either way. Every difference is a field in the&#xA;table in &lt;code&gt;mode.go&lt;/code&gt; and is looked up rather than branched on at a call site,&#xA;so the deltas stay auditable against the spec text in one place.&lt;/p&gt;&#xA;&lt;h2 id=&#34;where-they-differ&#34;&gt;Where they differ&lt;a class=&#34;anchor&#34; href=&#34;#where-they-differ&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;table&gt;&#xA;&#x9;&lt;thead&gt;&#xA;&#x9;&#x9;&#x9;&lt;tr&gt;&#xA;&#x9;&#x9;&#x9;&#x9;&#x9;&lt;th&gt;Behavior&lt;/th&gt;&#xA;&#x9;&#x9;&#x9;&#x9;&#x9;&lt;th&gt;WebSub&lt;/th&gt;&#xA;&#x9;&#x9;&#x9;&#x9;&#x9;&lt;th&gt;PubSubHubbub 0.4&lt;/th&gt;&#xA;&#x9;&#x9;&#x9;&lt;/tr&gt;&#xA;&#x9;&lt;/thead&gt;&#xA;&#x9;&lt;tbody&gt;&#xA;&#x9;&#x9;&#x9;&lt;tr&gt;&#xA;&#x9;&#x9;&#x9;&#x9;&#x9;&lt;td&gt;Lease duration&lt;/td&gt;&#xA;&#x9;&#x9;&#x9;&#x9;&#x9;&lt;td&gt;Required. Hubs must enforce expiration and must not issue perpetual leases&lt;/td&gt;&#xA;&#x9;&#x9;&#x9;&#x9;&#x9;&lt;td&gt;Optional. An omitted lease means the subscription never expires&lt;/td&gt;&#xA;&#x9;&#x9;&#x9;&lt;/tr&gt;&#xA;&#x9;&#x9;&#x9;&lt;tr&gt;&#xA;&#x9;&#x9;&#x9;&#x9;&#x9;&lt;td&gt;Discovery&lt;/td&gt;&#xA;&#x9;&#x9;&#x9;&#x9;&#x9;&lt;td&gt;&lt;code&gt;Link&lt;/code&gt; header, then HTML &lt;code&gt;&amp;lt;link&amp;gt;&lt;/code&gt;, then Atom, then RSS, as a required ordered chain&lt;/td&gt;&#xA;&#x9;&#x9;&#x9;&#x9;&#x9;&lt;td&gt;&lt;code&gt;Link&lt;/code&gt; header primarily, the rest best effort&lt;/td&gt;&#xA;&#x9;&#x9;&#x9;&lt;/tr&gt;&#xA;&#x9;&#x9;&#x9;&lt;tr&gt;&#xA;&#x9;&#x9;&#x9;&#x9;&#x9;&lt;td&gt;Verification&lt;/td&gt;&#xA;&#x9;&#x9;&#x9;&#x9;&#x9;&lt;td&gt;Asynchronous GET challenge, always&lt;/td&gt;&#xA;&#x9;&#x9;&#x9;&#x9;&#x9;&lt;td&gt;Same mechanism, but synchronous verification is tolerated in some deployments&lt;/td&gt;&#xA;&#x9;&#x9;&#x9;&lt;/tr&gt;&#xA;&#x9;&lt;/tbody&gt;&#xA;&lt;/table&gt;&#xA;&lt;p&gt;Two of those have visible consequences:&lt;/p&gt;</description>
			</item>
			<item>
				<title>Conformance suite</title>
				<link>https://websub.jazzmoon.ca/conformance/</link>
				<pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
				<guid>https://websub.jazzmoon.ca/conformance/</guid>
				<description>&lt;h1 id=&#34;conformance-suite&#34;&gt;Conformance suite&lt;a class=&#34;anchor&#34; href=&#34;#conformance-suite&#34;&gt;#&lt;/a&gt;&lt;/h1&gt;&#xA;&lt;p&gt;&lt;code&gt;tests/conformance&lt;/code&gt; holds the spec cases as an importable package&#xA;rather than as test files, so anything can replay them.&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;The core module runs them with no router in between.&lt;/li&gt;&#xA;&lt;li&gt;Each adapter module runs the same cases through its own wrapper.&lt;/li&gt;&#xA;&lt;li&gt;An adapter this repo does not ship can import the package and do the same.&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;h2 id=&#34;running-it&#34;&gt;Running it&lt;a class=&#34;anchor&#34; href=&#34;#running-it&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;go test ./tests -run TestConformance -v&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Through a specific adapter:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;cd adapter/fiber &lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&amp;amp;&lt;/span&gt; go test ./... -race -v&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id=&#34;how-it-is-put-together&#34;&gt;How it is put together&lt;a class=&#34;anchor&#34; href=&#34;#how-it-is-put-together&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;p&gt;Cases are values, not functions scattered across files:&lt;/p&gt;</description>
			</item>
			<item>
				<title>Writing an adapter</title>
				<link>https://websub.jazzmoon.ca/adapters/</link>
				<pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
				<guid>https://websub.jazzmoon.ca/adapters/</guid>
				<description>&lt;h1 id=&#34;writing-an-adapter&#34;&gt;Writing an adapter&lt;a class=&#34;anchor&#34; href=&#34;#writing-an-adapter&#34;&gt;#&lt;/a&gt;&lt;/h1&gt;&#xA;&lt;p&gt;An adapter&amp;rsquo;s whole job is turning an &lt;code&gt;http.Handler&lt;/code&gt; into whatever handler&#xA;type a framework wants. It contains no protocol logic, and it never should.&#xA;If you find yourself parsing a &lt;code&gt;hub.&lt;/code&gt; parameter in an adapter, the fix&#xA;belongs in core.&lt;/p&gt;&#xA;&lt;h2 id=&#34;do-you-need-one-at-all&#34;&gt;Do you need one at all&lt;a class=&#34;anchor&#34; href=&#34;#do-you-need-one-at-all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;p&gt;If your router accepts an &lt;code&gt;http.Handler&lt;/code&gt;, no. chi, gorilla/mux, and&#xA;&lt;code&gt;net/http&lt;/code&gt;&amp;rsquo;s own &lt;code&gt;ServeMux&lt;/code&gt; all do:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-go&#34; data-lang=&#34;go&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;r&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;Handle&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;/websub/*&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;subscriber&lt;/span&gt;)                  &lt;span style=&#34;color:#75715e&#34;&gt;// chi&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;r&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;PathPrefix&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;/websub/&amp;#34;&lt;/span&gt;).&lt;span style=&#34;color:#a6e22e&#34;&gt;Handler&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;subscriber&lt;/span&gt;)       &lt;span style=&#34;color:#75715e&#34;&gt;// gorilla/mux&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;mux&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;Handle&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;/websub/&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;subscriber&lt;/span&gt;)                 &lt;span style=&#34;color:#75715e&#34;&gt;// net/http&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;No adapter package exists for these, and none will. Publishing empty modules&#xA;would imply they were needed.&lt;/p&gt;</description>
			</item>
			<item>
				<title>Writing a storage backend</title>
				<link>https://websub.jazzmoon.ca/storage/</link>
				<pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
				<guid>https://websub.jazzmoon.ca/storage/</guid>
				<description>&lt;h1 id=&#34;writing-a-storage-backend&#34;&gt;Writing a storage backend&lt;a class=&#34;anchor&#34; href=&#34;#writing-a-storage-backend&#34;&gt;#&lt;/a&gt;&lt;/h1&gt;&#xA;&lt;p&gt;A hub keeps subscription state behind &lt;code&gt;SubscriptionStore&lt;/code&gt;. The default is in&#xA;memory, which is right for development and single process hubs and wrong for&#xA;anything that has to survive a restart.&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-go&#34; data-lang=&#34;go&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;type&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;SubscriptionStore&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;interface&lt;/span&gt; {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#x9;&lt;span style=&#34;color:#a6e22e&#34;&gt;Save&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;ctx&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;context&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;Context&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;sub&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Subscription&lt;/span&gt;) &lt;span style=&#34;color:#66d9ef&#34;&gt;error&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#x9;&lt;span style=&#34;color:#a6e22e&#34;&gt;Get&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;ctx&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;context&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;Context&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;topic&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;callback&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;string&lt;/span&gt;) (&lt;span style=&#34;color:#a6e22e&#34;&gt;Subscription&lt;/span&gt;, &lt;span style=&#34;color:#66d9ef&#34;&gt;error&lt;/span&gt;)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#x9;&lt;span style=&#34;color:#a6e22e&#34;&gt;Delete&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;ctx&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;context&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;Context&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;topic&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;callback&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;string&lt;/span&gt;) &lt;span style=&#34;color:#66d9ef&#34;&gt;error&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#x9;&lt;span style=&#34;color:#a6e22e&#34;&gt;ListByTopic&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;ctx&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;context&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;Context&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;topic&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;string&lt;/span&gt;) ([]&lt;span style=&#34;color:#a6e22e&#34;&gt;Subscription&lt;/span&gt;, &lt;span style=&#34;color:#66d9ef&#34;&gt;error&lt;/span&gt;)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#x9;&lt;span style=&#34;color:#a6e22e&#34;&gt;Topics&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;ctx&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;context&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;Context&lt;/span&gt;) ([]&lt;span style=&#34;color:#66d9ef&#34;&gt;string&lt;/span&gt;, &lt;span style=&#34;color:#66d9ef&#34;&gt;error&lt;/span&gt;)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#x9;&lt;span style=&#34;color:#a6e22e&#34;&gt;ListExpiring&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;ctx&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;context&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;Context&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;before&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;time&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;Time&lt;/span&gt;) ([]&lt;span style=&#34;color:#a6e22e&#34;&gt;Subscription&lt;/span&gt;, &lt;span style=&#34;color:#66d9ef&#34;&gt;error&lt;/span&gt;)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Switching is one line:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-go&#34; data-lang=&#34;go&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;hub&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;err&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;:=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;websub&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;NewHub&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;https://example.com/hub&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;websub&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;WithStore&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;store&lt;/span&gt;))&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id=&#34;the-contract&#34;&gt;The contract&lt;a class=&#34;anchor&#34; href=&#34;#the-contract&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;p&gt;A subscription is identified by the pair &lt;code&gt;(topic, callback)&lt;/code&gt;. Nothing else&#xA;is a key, and the pair is not unique on either half alone: one subscriber&#xA;may hold subscriptions to many topics, and one topic may have many&#xA;subscribers.&lt;/p&gt;</description>
			</item>
			<item>
				<title>Releasing</title>
				<link>https://websub.jazzmoon.ca/releasing/</link>
				<pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
				<guid>https://websub.jazzmoon.ca/releasing/</guid>
				<description>&lt;h1 id=&#34;releasing&#34;&gt;Releasing&lt;a class=&#34;anchor&#34; href=&#34;#releasing&#34;&gt;#&lt;/a&gt;&lt;/h1&gt;&#xA;&lt;p&gt;This repository holds several Go modules. Each is tagged and released on its&#xA;own, which is what lets a project depend on &lt;code&gt;adapter/gin&lt;/code&gt; without pulling in&#xA;fasthttp, and on &lt;code&gt;core&lt;/code&gt; without pulling in anything at all.&lt;/p&gt;&#xA;&lt;h2 id=&#34;tags&#34;&gt;Tags&lt;a class=&#34;anchor&#34; href=&#34;#tags&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;p&gt;Go derives a module&amp;rsquo;s version from a tag whose prefix is the module&amp;rsquo;s path&#xA;relative to the repository root:&lt;/p&gt;&#xA;&lt;table&gt;&#xA;&#x9;&lt;thead&gt;&#xA;&#x9;&#x9;&#x9;&lt;tr&gt;&#xA;&#x9;&#x9;&#x9;&#x9;&#x9;&lt;th&gt;Module&lt;/th&gt;&#xA;&#x9;&#x9;&#x9;&#x9;&#x9;&lt;th&gt;Tag&lt;/th&gt;&#xA;&#x9;&#x9;&#x9;&lt;/tr&gt;&#xA;&#x9;&lt;/thead&gt;&#xA;&#x9;&lt;tbody&gt;&#xA;&#x9;&#x9;&#x9;&lt;tr&gt;&#xA;&#x9;&#x9;&#x9;&#x9;&#x9;&lt;td&gt;&lt;code&gt;core&lt;/code&gt;&lt;/td&gt;&#xA;&#x9;&#x9;&#x9;&#x9;&#x9;&lt;td&gt;&lt;code&gt;v1.2.3&lt;/code&gt;&lt;/td&gt;&#xA;&#x9;&#x9;&#x9;&lt;/tr&gt;&#xA;&#x9;&#x9;&#x9;&lt;tr&gt;&#xA;&#x9;&#x9;&#x9;&#x9;&#x9;&lt;td&gt;&lt;code&gt;adapter/gin&lt;/code&gt;&lt;/td&gt;&#xA;&#x9;&#x9;&#x9;&#x9;&#x9;&lt;td&gt;&lt;code&gt;adapter/gin/v1.2.3&lt;/code&gt;&lt;/td&gt;&#xA;&#x9;&#x9;&#x9;&lt;/tr&gt;&#xA;&#x9;&#x9;&#x9;&lt;tr&gt;&#xA;&#x9;&#x9;&#x9;&#x9;&#x9;&lt;td&gt;&lt;code&gt;storage/redis&lt;/code&gt;&lt;/td&gt;&#xA;&#x9;&#x9;&#x9;&#x9;&#x9;&lt;td&gt;&lt;code&gt;storage/redis/v1.2.3&lt;/code&gt;&lt;/td&gt;&#xA;&#x9;&#x9;&#x9;&lt;/tr&gt;&#xA;&#x9;&#x9;&#x9;&lt;tr&gt;&#xA;&#x9;&#x9;&#x9;&#x9;&#x9;&lt;td&gt;&lt;code&gt;websubtest&lt;/code&gt;&lt;/td&gt;&#xA;&#x9;&#x9;&#x9;&#x9;&#x9;&lt;td&gt;&lt;code&gt;websubtest/v1.2.3&lt;/code&gt;&lt;/td&gt;&#xA;&#x9;&#x9;&#x9;&lt;/tr&gt;&#xA;&#x9;&lt;/tbody&gt;&#xA;&lt;/table&gt;&#xA;&lt;p&gt;The &lt;code&gt;core&lt;/code&gt; module &lt;em&gt;is&lt;/em&gt; the repository root: its &lt;code&gt;go.mod&lt;/code&gt; declares&#xA;&lt;code&gt;github.com/Jazzmoon/websub&lt;/code&gt;, the bare repo import path, so its files live&#xA;directly at the repo root rather than in a named subdirectory (Go requires&#xA;the two to match), and its tags carry no prefix as a result. Every other&#xA;module&amp;rsquo;s &lt;code&gt;go.mod&lt;/code&gt; lives in a subdirectory matching its own path suffix.&lt;/p&gt;</description>
			</item>
	</channel>
</rss>
