{"title":"A Lazy Sequence","version":"https://jsonfeed.org/version/1.1","home_page_url":"https://brehaut.net/","feed_url":"https://brehaut.net/feeds/everything.json","language":"en","favicon":"https://brehaut.net/static/favicon.ico","icon":"https://brehaut.net/static/apple-touch-icon.png","authors":{"0":{"name":"Andrew Brehaut","email":"andrew@brehaut.net"}},"items":[{"id":"https://brehaut.net/blog/2026/sveltekit_query_live","title":"Svelte Kit <code>query.live</code> first thoughts","summary":"Initial thoughts on one of the new remote function APIs in Svelte Kit 3/Svelte 6","content_html":"<p>With Svelte Kit 3 entering <a href=\"https://svelte.dev/blog/sveltekit-3-release-candidate\">Release Candidate</a> a couple of weeks ago, it seemed like a good time to finally start exploring the <a href=\"https://svelte.dev/docs/kit/remote-functions\">Remote Functions</a> support that the Svelte team have been previewing since last year. I’ve got a hobby project in mind that I’d like to use it for, a dice roller for the <a href=\"https://www.burningwheel.com\">Burning Wheel RPG</a>, and <a href=\"https://svelte.dev/docs/kit/remote-functions#query.live\"><code>query.live</code></a> looks like a good place to start. Until now doing something like this with Svelte has been a bit of a pain in the ass as there was no first class support for <a href=\"https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API\">WebSockets</a> or <a href=\"https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events\">Server-Sent Events</a>.</p>\n<p>A dice roller is really just a broadcast chat app that happens to do some random number stuff on the server, so a minimal in-memory chat program is a good place to start.</p>\n<p>Here’s the core of this prototype lives in one file, <code>chat.remote.ts</code>:</p>\n<pre class=\"astro-code astro-code-themes github-light everforest-dark\" style=\"background-color:#fff;--shiki-dark-bg:#2d353b;color:#24292e;--shiki-dark:#d3c6aa; overflow-x: auto;\" tabindex=\"0\" data-language=\"typescript\"><code><span class=\"line\"><span style=\"color:#D73A49;--shiki-dark:#D699B6\">import</span><span style=\"color:#005CC5;--shiki-dark:#D699B6\"> *</span><span style=\"color:#D73A49;--shiki-dark:#E67E80\"> as</span><span style=\"color:#24292E;--shiki-dark:#D3C6AA\"> v </span><span style=\"color:#D73A49;--shiki-dark:#E67E80\">from</span><span style=\"color:#032F62;--shiki-dark:#DBBC7F\"> 'valibot'</span><span style=\"color:#24292E;--shiki-dark:#D3C6AA\">;</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#D73A49;--shiki-dark:#D699B6\">import</span><span style=\"color:#24292E;--shiki-dark:#D3C6AA\"> { query, command } </span><span style=\"color:#D73A49;--shiki-dark:#E67E80\">from</span><span style=\"color:#032F62;--shiki-dark:#DBBC7F\"> '$app/server'</span><span style=\"color:#24292E;--shiki-dark:#D3C6AA\">;</span></span>\n<span class=\"line\"><span style=\"color:#D73A49;--shiki-dark:#D699B6\">import</span><span style=\"color:#24292E;--shiki-dark:#D3C6AA\"> { PubSubHub } </span><span style=\"color:#D73A49;--shiki-dark:#E67E80\">from</span><span style=\"color:#032F62;--shiki-dark:#DBBC7F\"> '../lib/pubsubhub.ts'</span><span style=\"color:#24292E;--shiki-dark:#D3C6AA\">;</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#D73A49;--shiki-dark:#E69875\">const</span><span style=\"color:#005CC5;--shiki-dark:#D3C6AA\"> history</span><span style=\"color:#D73A49;--shiki-dark:#859289\">:</span><span style=\"color:#005CC5;--shiki-dark:#7FBBB3\">string</span><span style=\"color:#24292E;--shiki-dark:#D3C6AA\">[] </span><span style=\"color:#D73A49;--shiki-dark:#E69875\">=</span><span style=\"color:#24292E;--shiki-dark:#D3C6AA\"> [];</span></span>\n<span class=\"line\"><span style=\"color:#D73A49;--shiki-dark:#E69875\">const</span><span style=\"color:#005CC5;--shiki-dark:#D3C6AA\"> room</span><span style=\"color:#D73A49;--shiki-dark:#E69875\"> =</span><span style=\"color:#D73A49;--shiki-dark:#E67E80\"> new</span><span style=\"color:#6F42C1;--shiki-dark:#A7C080\"> PubSubHub</span><span style=\"color:#24292E;--shiki-dark:#D3C6AA\">&#x3C;</span><span style=\"color:#005CC5;--shiki-dark:#7FBBB3\">string</span><span style=\"color:#24292E;--shiki-dark:#D3C6AA\">[]>();</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#D73A49;--shiki-dark:#D699B6\">export</span><span style=\"color:#D73A49;--shiki-dark:#E69875\"> const</span><span style=\"color:#005CC5;--shiki-dark:#D3C6AA\"> addChat</span><span style=\"color:#D73A49;--shiki-dark:#E69875\"> =</span><span style=\"color:#6F42C1;--shiki-dark:#A7C080\"> command</span><span style=\"color:#24292E;--shiki-dark:#D3C6AA\">(v</span><span style=\"color:#24292E;--shiki-dark:#859289\">.</span><span style=\"color:#6F42C1;--shiki-dark:#A7C080\">string</span><span style=\"color:#24292E;--shiki-dark:#D3C6AA\">(), (</span><span style=\"color:#E36209;--shiki-dark:#D3C6AA\">text</span><span style=\"color:#D73A49;--shiki-dark:#859289\">:</span><span style=\"color:#005CC5;--shiki-dark:#7FBBB3\"> string</span><span style=\"color:#24292E;--shiki-dark:#D3C6AA\">) </span><span style=\"color:#D73A49;--shiki-dark:#E69875\">=></span><span style=\"color:#24292E;--shiki-dark:#D3C6AA\"> {    </span></span>\n<span class=\"line\"><span style=\"color:#24292E;--shiki-dark:#D3C6AA\">    history</span><span style=\"color:#24292E;--shiki-dark:#859289\">.</span><span style=\"color:#6F42C1;--shiki-dark:#A7C080\">push</span><span style=\"color:#24292E;--shiki-dark:#D3C6AA\">(text);</span></span>\n<span class=\"line\"><span style=\"color:#24292E;--shiki-dark:#D3C6AA\">    room</span><span style=\"color:#24292E;--shiki-dark:#859289\">.</span><span style=\"color:#6F42C1;--shiki-dark:#A7C080\">publish</span><span style=\"color:#24292E;--shiki-dark:#D3C6AA\">(history);</span></span>\n<span class=\"line\"><span style=\"color:#24292E;--shiki-dark:#D3C6AA\">});</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#D73A49;--shiki-dark:#D699B6\">export</span><span style=\"color:#D73A49;--shiki-dark:#E69875\"> const</span><span style=\"color:#005CC5;--shiki-dark:#D3C6AA\"> getChats</span><span style=\"color:#D73A49;--shiki-dark:#E69875\"> =</span><span style=\"color:#24292E;--shiki-dark:#D3C6AA\"> query</span><span style=\"color:#24292E;--shiki-dark:#859289\">.</span><span style=\"color:#6F42C1;--shiki-dark:#A7C080\">live</span><span style=\"color:#24292E;--shiki-dark:#D3C6AA\">(room</span><span style=\"color:#24292E;--shiki-dark:#859289\">.</span><span style=\"color:#24292E;--shiki-dark:#D3C6AA\">subscribe</span><span style=\"color:#24292E;--shiki-dark:#859289\">.</span><span style=\"color:#6F42C1;--shiki-dark:#A7C080\">bind</span><span style=\"color:#24292E;--shiki-dark:#D3C6AA\">(room));</span></span></code></pre>\n<p>Probably the most notable thing is the presence of <code>PubSubHub</code> (code below). I immediately ran into an issue where the new <code>query.live</code> consumes a <a href=\"https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function*\">Async Generator function</a>. The example in the documentation, <code>getTime</code>, makes this appear very simple: the only source of events is <code>setTimeout</code> so it’s trivial to setup. Of course, in a real application you probably want the remote function to update in response to some external events, such as the user posting a chat message.</p>\n<p>If you’ve ever looked at async generators before, you’ll know that this pretty quickly becomes a bunch of bookkeepping to manage a cavalcade of <a href=\"https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise\">Promises</a>. These <code>Promise</code> objects are subverted for use as signalling rather than as delayed values, resulting in the <code>resolve</code> functions being exfiltrated as one time event triggers. Because a Promise only delivers at most once, you need to keep creating new Promises after each <code>yield</code>.</p>\n<p>To that end, I create this minimal publish subscribe object that returns an <code>AsyncGenerator</code> when you <code>subscribe</code>.</p>\n<p><code>pubsubhub.ts</code>:</p>\n<pre class=\"astro-code astro-code-themes github-light everforest-dark\" style=\"background-color:#fff;--shiki-dark-bg:#2d353b;color:#24292e;--shiki-dark:#d3c6aa; overflow-x: auto;\" tabindex=\"0\" data-language=\"typescript\"><code><span class=\"line\"><span style=\"color:#D73A49;--shiki-dark:#E67E80\">interface</span><span style=\"color:#6F42C1;--shiki-dark:#83C092\"> SubscriberHandle</span><span style=\"color:#24292E;--shiki-dark:#D3C6AA\">&#x3C;</span><span style=\"color:#6F42C1;--shiki-dark:#83C092\">T</span><span style=\"color:#24292E;--shiki-dark:#D3C6AA\">> {</span></span>\n<span class=\"line\"><span style=\"color:#6F42C1;--shiki-dark:#A7C080\">    handle</span><span style=\"color:#D73A49;--shiki-dark:#859289\">:</span><span style=\"color:#24292E;--shiki-dark:#D3C6AA\"> (</span><span style=\"color:#E36209;--shiki-dark:#D3C6AA\">msg</span><span style=\"color:#D73A49;--shiki-dark:#859289\">:</span><span style=\"color:#6F42C1;--shiki-dark:#83C092\">T</span><span style=\"color:#24292E;--shiki-dark:#D3C6AA\">) </span><span style=\"color:#D73A49;--shiki-dark:#E69875\">=></span><span style=\"color:#005CC5;--shiki-dark:#7FBBB3\"> void</span><span style=\"color:#24292E;--shiki-dark:#D3C6AA\">;</span></span>\n<span class=\"line\"><span style=\"color:#24292E;--shiki-dark:#D3C6AA\">}</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#6A737D;--shiki-light-font-style:inherit;--shiki-dark:#859289;--shiki-dark-font-style:italic\">/**</span></span>\n<span class=\"line\"><span style=\"color:#6A737D;--shiki-light-font-style:inherit;--shiki-dark:#859289;--shiki-dark-font-style:italic\"> * A Publish Subscribe hub that is consumed via async generators.</span></span>\n<span class=\"line\"><span style=\"color:#6A737D;--shiki-light-font-style:inherit;--shiki-dark:#859289;--shiki-dark-font-style:italic\"> * </span></span>\n<span class=\"line\"><span style=\"color:#6A737D;--shiki-light-font-style:inherit;--shiki-dark:#859289;--shiki-dark-font-style:italic\"> * Subscribe uses an async generator because that’s what Svelte’s </span></span>\n<span class=\"line\"><span style=\"color:#6A737D;--shiki-light-font-style:inherit;--shiki-dark:#859289;--shiki-dark-font-style:italic\"> * `query.live` consumes.</span></span>\n<span class=\"line\"><span style=\"color:#6A737D;--shiki-light-font-style:inherit;--shiki-dark:#859289;--shiki-dark-font-style:italic\"> * </span></span>\n<span class=\"line\"><span style=\"color:#6A737D;--shiki-light-font-style:inherit;--shiki-dark:#859289;--shiki-dark-font-style:italic\"> * ```typescript</span></span>\n<span class=\"line\"><span style=\"color:#6A737D;--shiki-light-font-style:inherit;--shiki-dark:#859289;--shiki-dark-font-style:italic\"> * const hub = new PubSubHub&#x3C;string>();</span></span>\n<span class=\"line\"><span style=\"color:#6A737D;--shiki-light-font-style:inherit;--shiki-dark:#859289;--shiki-dark-font-style:italic\"> * </span></span>\n<span class=\"line\"><span style=\"color:#6A737D;--shiki-light-font-style:inherit;--shiki-dark:#859289;--shiki-dark-font-style:italic\"> * for await (const message of hub.subscribe()) {</span></span>\n<span class=\"line\"><span style=\"color:#6A737D;--shiki-light-font-style:inherit;--shiki-dark:#859289;--shiki-dark-font-style:italic\"> *     // Process message</span></span>\n<span class=\"line\"><span style=\"color:#6A737D;--shiki-light-font-style:inherit;--shiki-dark:#859289;--shiki-dark-font-style:italic\"> * } </span></span>\n<span class=\"line\"><span style=\"color:#6A737D;--shiki-light-font-style:inherit;--shiki-dark:#859289;--shiki-dark-font-style:italic\"> * </span></span>\n<span class=\"line\"><span style=\"color:#6A737D;--shiki-light-font-style:inherit;--shiki-dark:#859289;--shiki-dark-font-style:italic\"> * …</span></span>\n<span class=\"line\"><span style=\"color:#6A737D;--shiki-light-font-style:inherit;--shiki-dark:#859289;--shiki-dark-font-style:italic\"> * </span></span>\n<span class=\"line\"><span style=\"color:#6A737D;--shiki-light-font-style:inherit;--shiki-dark:#859289;--shiki-dark-font-style:italic\"> * function onSend(text:string) {</span></span>\n<span class=\"line\"><span style=\"color:#6A737D;--shiki-light-font-style:inherit;--shiki-dark:#859289;--shiki-dark-font-style:italic\"> *     hub.publish(text);</span></span>\n<span class=\"line\"><span style=\"color:#6A737D;--shiki-light-font-style:inherit;--shiki-dark:#859289;--shiki-dark-font-style:italic\"> * }</span></span>\n<span class=\"line\"><span style=\"color:#6A737D;--shiki-light-font-style:inherit;--shiki-dark:#859289;--shiki-dark-font-style:italic\"> * ```</span></span>\n<span class=\"line\"><span style=\"color:#6A737D;--shiki-light-font-style:inherit;--shiki-dark:#859289;--shiki-dark-font-style:italic\"> */</span></span>\n<span class=\"line\"><span style=\"color:#D73A49;--shiki-dark:#D699B6\">export</span><span style=\"color:#D73A49;--shiki-dark:#E67E80\"> class</span><span style=\"color:#6F42C1;--shiki-dark:#83C092\"> PubSubHub</span><span style=\"color:#24292E;--shiki-dark:#D3C6AA\">&#x3C;</span><span style=\"color:#6F42C1;--shiki-dark:#83C092\">T</span><span style=\"color:#24292E;--shiki-dark:#D3C6AA\">> {</span></span>\n<span class=\"line\"><span style=\"color:#D73A49;--shiki-dark:#E69875\">    private</span><span style=\"color:#E36209;--shiki-dark:#D3C6AA\"> subscribers</span><span style=\"color:#D73A49;--shiki-dark:#859289\">:</span><span style=\"color:#6F42C1;--shiki-dark:#83C092\"> Set</span><span style=\"color:#24292E;--shiki-dark:#D3C6AA\">&#x3C;</span><span style=\"color:#6F42C1;--shiki-dark:#83C092\">SubscriberHandle</span><span style=\"color:#24292E;--shiki-dark:#D3C6AA\">&#x3C;</span><span style=\"color:#6F42C1;--shiki-dark:#83C092\">T</span><span style=\"color:#24292E;--shiki-dark:#D3C6AA\">>> </span><span style=\"color:#D73A49;--shiki-dark:#E69875\">=</span><span style=\"color:#D73A49;--shiki-dark:#E67E80\"> new</span><span style=\"color:#6F42C1;--shiki-dark:#A7C080\"> Set</span><span style=\"color:#24292E;--shiki-dark:#D3C6AA\">();</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#6F42C1;--shiki-dark:#A7C080\">    publish</span><span style=\"color:#24292E;--shiki-dark:#D3C6AA\">(</span><span style=\"color:#E36209;--shiki-dark:#D3C6AA\">msg</span><span style=\"color:#D73A49;--shiki-dark:#859289\">:</span><span style=\"color:#6F42C1;--shiki-dark:#83C092\">T</span><span style=\"color:#24292E;--shiki-dark:#D3C6AA\">) {</span></span>\n<span class=\"line\"><span style=\"color:#D73A49;--shiki-dark:#E67E80\">        for</span><span style=\"color:#24292E;--shiki-dark:#D3C6AA\"> (</span><span style=\"color:#D73A49;--shiki-dark:#E69875\">const</span><span style=\"color:#005CC5;--shiki-dark:#D3C6AA\"> sub</span><span style=\"color:#D73A49;--shiki-dark:#E67E80\"> of</span><span style=\"color:#005CC5;--shiki-dark:#D699B6\"> this</span><span style=\"color:#24292E;--shiki-dark:#859289\">.</span><span style=\"color:#24292E;--shiki-dark:#D3C6AA\">subscribers) {</span></span>\n<span class=\"line\"><span style=\"color:#24292E;--shiki-dark:#D3C6AA\">            sub</span><span style=\"color:#24292E;--shiki-dark:#859289\">.</span><span style=\"color:#6F42C1;--shiki-dark:#A7C080\">handle</span><span style=\"color:#24292E;--shiki-dark:#D3C6AA\">(msg);</span></span>\n<span class=\"line\"><span style=\"color:#24292E;--shiki-dark:#D3C6AA\">        }</span></span>\n<span class=\"line\"><span style=\"color:#24292E;--shiki-dark:#D3C6AA\">    }</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#D73A49;--shiki-dark:#E69875\">    async</span><span style=\"color:#D73A49;--shiki-dark:#E67E80\"> *</span><span style=\"color:#6F42C1;--shiki-dark:#A7C080\"> subscribe</span><span style=\"color:#24292E;--shiki-dark:#D3C6AA\">()</span><span style=\"color:#D73A49;--shiki-dark:#859289\">:</span><span style=\"color:#6F42C1;--shiki-dark:#83C092\"> AsyncGenerator</span><span style=\"color:#24292E;--shiki-dark:#D3C6AA\">&#x3C;</span><span style=\"color:#6F42C1;--shiki-dark:#83C092\">T</span><span style=\"color:#24292E;--shiki-dark:#D3C6AA\">> {</span></span>\n<span class=\"line\"><span style=\"color:#6A737D;--shiki-light-font-style:inherit;--shiki-dark:#859289;--shiki-dark-font-style:italic\">        // To set up the generator's body, we need an object (the subscriber)</span></span>\n<span class=\"line\"><span style=\"color:#6A737D;--shiki-light-font-style:inherit;--shiki-dark:#859289;--shiki-dark-font-style:italic\">        // to be registered with the `subscribers` set, that can hold the </span></span>\n<span class=\"line\"><span style=\"color:#6A737D;--shiki-light-font-style:inherit;--shiki-dark:#859289;--shiki-dark-font-style:italic\">        // resolve functions generated by the `signal` sentinel Promises.</span></span>\n<span class=\"line\"><span style=\"color:#D73A49;--shiki-dark:#E69875\">        const</span><span style=\"color:#005CC5;--shiki-dark:#D3C6AA\"> subscriber</span><span style=\"color:#D73A49;--shiki-dark:#859289\">:</span><span style=\"color:#6F42C1;--shiki-dark:#83C092\">SubscriberHandle</span><span style=\"color:#24292E;--shiki-dark:#D3C6AA\">&#x3C;</span><span style=\"color:#6F42C1;--shiki-dark:#83C092\">T</span><span style=\"color:#24292E;--shiki-dark:#D3C6AA\">> </span><span style=\"color:#D73A49;--shiki-dark:#E69875\">=</span><span style=\"color:#24292E;--shiki-dark:#D3C6AA\"> {</span><span style=\"color:#6F42C1;--shiki-dark:#A7C080\">handle</span><span style=\"color:#24292E;--shiki-dark:#859289\">:</span><span style=\"color:#24292E;--shiki-dark:#D3C6AA\">() </span><span style=\"color:#D73A49;--shiki-dark:#E69875\">=></span><span style=\"color:#24292E;--shiki-dark:#D3C6AA\"> {}};</span></span>\n<span class=\"line\"><span style=\"color:#005CC5;--shiki-dark:#D699B6\">        this</span><span style=\"color:#24292E;--shiki-dark:#859289\">.</span><span style=\"color:#24292E;--shiki-dark:#D3C6AA\">subscribers</span><span style=\"color:#24292E;--shiki-dark:#859289\">.</span><span style=\"color:#6F42C1;--shiki-dark:#A7C080\">add</span><span style=\"color:#24292E;--shiki-dark:#D3C6AA\">(subscriber);</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#D73A49;--shiki-dark:#E67E80\">        try</span><span style=\"color:#24292E;--shiki-dark:#D3C6AA\"> {</span></span>\n<span class=\"line\"><span style=\"color:#6A737D;--shiki-light-font-style:inherit;--shiki-dark:#859289;--shiki-dark-font-style:italic\">            // Callers can break out of the loop by calling `return()` on the</span></span>\n<span class=\"line\"><span style=\"color:#6A737D;--shiki-light-font-style:inherit;--shiki-dark:#859289;--shiki-dark-font-style:italic\">            // resulting generator, cleanly exiting.</span></span>\n<span class=\"line\"><span style=\"color:#6A737D;--shiki-light-font-style:inherit;--shiki-dark:#859289;--shiki-dark-font-style:italic\">            // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Generator/return</span></span>\n<span class=\"line\"><span style=\"color:#D73A49;--shiki-dark:#E67E80\">            while</span><span style=\"color:#24292E;--shiki-dark:#D3C6AA\"> (</span><span style=\"color:#005CC5;--shiki-dark:#D699B6\">true</span><span style=\"color:#24292E;--shiki-dark:#D3C6AA\">) {</span></span>\n<span class=\"line\"><span style=\"color:#D73A49;--shiki-dark:#E69875\">                const</span><span style=\"color:#005CC5;--shiki-dark:#D3C6AA\"> signal</span><span style=\"color:#D73A49;--shiki-dark:#E69875\"> =</span><span style=\"color:#D73A49;--shiki-dark:#E67E80\"> new</span><span style=\"color:#005CC5;--shiki-dark:#7FBBB3\"> Promise</span><span style=\"color:#24292E;--shiki-dark:#D3C6AA\">&#x3C;</span><span style=\"color:#6F42C1;--shiki-dark:#83C092\">T</span><span style=\"color:#24292E;--shiki-dark:#D3C6AA\">>((</span><span style=\"color:#E36209;--shiki-dark:#D3C6AA\">resolve</span><span style=\"color:#24292E;--shiki-dark:#D3C6AA\">) </span><span style=\"color:#D73A49;--shiki-dark:#E69875\">=></span><span style=\"color:#D73A49;--shiki-dark:#E67E80\"> void</span><span style=\"color:#24292E;--shiki-dark:#D3C6AA\"> (subscriber</span><span style=\"color:#24292E;--shiki-dark:#859289\">.</span><span style=\"color:#24292E;--shiki-dark:#D3C6AA\">handle </span><span style=\"color:#D73A49;--shiki-dark:#E69875\">=</span><span style=\"color:#24292E;--shiki-dark:#D3C6AA\"> resolve));</span></span>\n<span class=\"line\"><span style=\"color:#D73A49;--shiki-dark:#E67E80\">                yield</span><span style=\"color:#D73A49;--shiki-dark:#E67E80\"> await</span><span style=\"color:#24292E;--shiki-dark:#D3C6AA\"> signal;</span></span>\n<span class=\"line\"><span style=\"color:#24292E;--shiki-dark:#D3C6AA\">            }   </span></span>\n<span class=\"line\"><span style=\"color:#24292E;--shiki-dark:#D3C6AA\">        }</span></span>\n<span class=\"line\"><span style=\"color:#D73A49;--shiki-dark:#E67E80\">        finally</span><span style=\"color:#24292E;--shiki-dark:#D3C6AA\"> {</span></span>\n<span class=\"line\"><span style=\"color:#6A737D;--shiki-light-font-style:inherit;--shiki-dark:#859289;--shiki-dark-font-style:italic\">            // if the caller calls `return()` on the generator we need to </span></span>\n<span class=\"line\"><span style=\"color:#6A737D;--shiki-light-font-style:inherit;--shiki-dark:#859289;--shiki-dark-font-style:italic\">            // remove the subscriber so that they no longer receive messages.</span></span>\n<span class=\"line\"><span style=\"color:#005CC5;--shiki-dark:#D699B6\">            this</span><span style=\"color:#24292E;--shiki-dark:#859289\">.</span><span style=\"color:#24292E;--shiki-dark:#D3C6AA\">subscribers</span><span style=\"color:#24292E;--shiki-dark:#859289\">.</span><span style=\"color:#6F42C1;--shiki-dark:#A7C080\">delete</span><span style=\"color:#24292E;--shiki-dark:#D3C6AA\">(subscriber);</span></span>\n<span class=\"line\"><span style=\"color:#24292E;--shiki-dark:#D3C6AA\">        }</span></span>\n<span class=\"line\"><span style=\"color:#24292E;--shiki-dark:#D3C6AA\">    }</span></span>\n<span class=\"line\"><span style=\"color:#24292E;--shiki-dark:#D3C6AA\">}</span></span></code></pre>\n<p>Note that you need to call <code>bind</code> on the <code>subscribe</code> method if you want to pass it as-is to <code>query.live</code>, otherwise it will become detached from the <code>PubSubHub</code> and not be able to find the list of subscribers.</p>\n<p>In a real application you’ll probably want something else, connected to an external service such as a message bus, <a href=\"https://www.postgresql.org/docs/current/sql-notify.html\">Postgres’s <code>LISTEN</code> / <code>NOTIFY</code></a>, etc. But the basic pattern of converting events in a generator stream remains.</p>\n<hr>\n<p><code>query.command</code> is trivial to use. I probably should have used a <code>query.form</code>, but for this toy example it was easier to just skip that for now. That interface seems solid, and <code>query.form</code> is an obvious expansion on the concept.</p>\n<p><code>query.live</code> consuming an async generator I’m less sure on. It works, but the busywork associated with setting it up feels more onerous than I would have liked. Not having an obviously documented example of it interacting with svelte’s observable values or stores seems like an oversight.</p>\n<p>Likewise, the lack of an equivalent of <code>PubSubHub</code>, or other utilities for adapting external data to a generater, in the core library is puzzling. It’s still a release candidate so maybe that is still to come. The store API is very close but doesn’t, as best as I can tell, expose an async generator. Something that can consume any observable svelte object, or a store, and produce an async stream seems like an obvious addition.</p>\n<p>From the consumer side, having the <code>query.live</code> function (<code>getChats</code> in my example) produce a single value that changes over time, and not a sequence is a little surprising. Not a deal breaker, but I dislike the asymmetry between producer and consumer; it means that – as in this example – the client or server has to do extra work to keep track of the history if that is required. I opted to do it on the server for convenience in the example, even though it’s wasteful. Again, some standard convenience functions in the library would resolve this.</p>\n<p>Minor misgivings aside, this is new set of features is promising. Hopefully it will get a finalised release in the not too distant future.</p>\n","url":"https://brehaut.net/blog/2026/sveltekit_query_live","date_published":"2026-08-23T00:00:00Z"},{"id":"https://brehaut.net/blog/2026/jj_user_aliases","title":"User Aliases in <code>jj</code> logs","summary":"<code>jj</code> config for using short aliases for users in <code>log</code> lines","content_html":"<p>Here’s a quick bit of <code>jj</code> config that streamlines the log lines. I use this for my immediate team, and for dependabot, to make my logs more glancable, while leaving commits from irregular contributors louder:</p>\n<pre class=\"astro-code astro-code-themes github-light everforest-dark\" style=\"background-color:#fff;--shiki-dark-bg:#2d353b;color:#24292e;--shiki-dark:#d3c6aa; overflow-x: auto;\" tabindex=\"0\" data-language=\"toml\"><code><span class=\"line\"><span style=\"color:#24292E;--shiki-dark:#D3C6AA\">[</span><span style=\"color:#6F42C1;--shiki-dark:#D3C6AA\">template-aliases</span><span style=\"color:#24292E;--shiki-dark:#D3C6AA\">]</span></span>\n<span class=\"line\"><span style=\"color:#24292E;--shiki-dark:#D3C6AA\">'user_alias(signature)' = </span><span style=\"color:#032F62;--shiki-dark:#DBBC7F\">'''config('user-aliases.\"' ++ signature.email() ++ '\"')'''</span></span>\n<span class=\"line\"><span style=\"color:#24292E;--shiki-dark:#D3C6AA\">'format_short_signature(signature)' = </span><span style=\"color:#032F62;--shiki-dark:#DBBC7F\">'''label(</span></span>\n<span class=\"line\"><span style=\"color:#032F62;--shiki-dark:#DBBC7F\">    'author', </span></span>\n<span class=\"line\"><span style=\"color:#032F62;--shiki-dark:#DBBC7F\">    if (signature.email() == config('user.email').as_string(),</span></span>\n<span class=\"line\"><span style=\"color:#032F62;--shiki-dark:#DBBC7F\">        config('user-aliases.me').as_string(),</span></span>\n<span class=\"line\"><span style=\"color:#032F62;--shiki-dark:#DBBC7F\">        if (user_alias(signature), </span></span>\n<span class=\"line\"><span style=\"color:#032F62;--shiki-dark:#DBBC7F\">            user_alias(signature).as_string(), </span></span>\n<span class=\"line\"><span style=\"color:#032F62;--shiki-dark:#DBBC7F\">            signature</span></span>\n<span class=\"line\"><span style=\"color:#032F62;--shiki-dark:#DBBC7F\">        )</span></span>\n<span class=\"line\"><span style=\"color:#032F62;--shiki-dark:#DBBC7F\">    )</span></span>\n<span class=\"line\"><span style=\"color:#032F62;--shiki-dark:#DBBC7F\">)</span></span>\n<span class=\"line\"><span style=\"color:#032F62;--shiki-dark:#DBBC7F\">'''</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#24292E;--shiki-dark:#D3C6AA\">[</span><span style=\"color:#6F42C1;--shiki-dark:#D3C6AA\">user-aliases</span><span style=\"color:#24292E;--shiki-dark:#D3C6AA\">]</span></span>\n<span class=\"line\"><span style=\"color:#24292E;--shiki-dark:#D3C6AA\">\"me\" = </span><span style=\"color:#032F62;--shiki-dark:#DBBC7F\">\"⏺ Andrew\"</span></span>\n<span class=\"line\"><span style=\"color:#24292E;--shiki-dark:#D3C6AA\">'foo.bar@example.invalid' = </span><span style=\"color:#032F62;--shiki-dark:#DBBC7F\">'Foo'</span></span>\n<span class=\"line\"><span style=\"color:#24292E;--shiki-dark:#D3C6AA\">'…dependabot[bot]@users.noreply.github.com' = </span><span style=\"color:#032F62;--shiki-dark:#DBBC7F\">'🤖 dependabot'</span></span></code></pre>\n<p>This overrides <a href=\"https://docs.jj-vcs.dev/v0.15.1/config/#author-format\"><code>format_short_signature</code></a> which is used by the default log line template. Better factoring of the templates is left as an exercise for the reader.</p>\n<p>The <code>me</code> alias is a special case that uses the already configured <code>user.email</code> config setting. This also means that we could commit a repo level config file with common aliases if we wanted to, and my own commits would still be annotated with the bullet.</p>\n","url":"https://brehaut.net/blog/2026/jj_user_aliases","date_published":"2026-08-07T00:00:00Z"},{"id":"https://brehaut.net/blog/2026/prolog_list_rotation","title":"List rotation in Prolog","content_html":"<p>I’m still dipping into logic programming intermittently. I’m still a tourist. Today I had reason to want to rotate a list – that is, take some number of items from the front, and append them to the back – and I couldn’t find a suitable predicate in the standard libray. My initial attempts to write the predicate myself were bad (leaning on functional and procedural definitions), and in googling I could only find other unsatisfying solutions.</p>\n<pre class=\"astro-code astro-code-themes github-light everforest-dark\" style=\"background-color:#fff;--shiki-dark-bg:#2d353b;color:#24292e;--shiki-dark:#d3c6aa; overflow-x: auto;\" tabindex=\"0\" data-language=\"prolog\"><code><span class=\"line\"><span style=\"color:#6F42C1;--shiki-dark:#A7C080\">list_rotation_list</span><span style=\"color:#24292E;--shiki-dark:#D3C6AA\">(</span><span style=\"color:#E36209;--shiki-dark:#D3C6AA\">List</span><span style=\"color:#24292E;--shiki-dark:#D3C6AA\">, </span><span style=\"color:#E36209;--shiki-dark:#D3C6AA\">N</span><span style=\"color:#24292E;--shiki-dark:#D3C6AA\">, </span><span style=\"color:#E36209;--shiki-dark:#D3C6AA\">Rot</span><span style=\"color:#24292E;--shiki-dark:#D3C6AA\">) </span><span style=\"color:#D73A49;--shiki-dark:#E67E80\">:-</span></span>\n<span class=\"line\"><span style=\"color:#005CC5;--shiki-dark:#A7C080\">    length</span><span style=\"color:#24292E;--shiki-dark:#D3C6AA\">(</span><span style=\"color:#E36209;--shiki-dark:#D3C6AA\">Front</span><span style=\"color:#24292E;--shiki-dark:#D3C6AA\">, </span><span style=\"color:#E36209;--shiki-dark:#D3C6AA\">N</span><span style=\"color:#24292E;--shiki-dark:#D3C6AA\">),</span></span>\n<span class=\"line\"><span style=\"color:#005CC5;--shiki-dark:#A7C080\">    append</span><span style=\"color:#24292E;--shiki-dark:#D3C6AA\">(</span><span style=\"color:#E36209;--shiki-dark:#D3C6AA\">Front</span><span style=\"color:#24292E;--shiki-dark:#D3C6AA\">, </span><span style=\"color:#E36209;--shiki-dark:#D3C6AA\">Back</span><span style=\"color:#24292E;--shiki-dark:#D3C6AA\">, </span><span style=\"color:#E36209;--shiki-dark:#D3C6AA\">List</span><span style=\"color:#24292E;--shiki-dark:#D3C6AA\">),</span></span>\n<span class=\"line\"><span style=\"color:#005CC5;--shiki-dark:#A7C080\">    append</span><span style=\"color:#24292E;--shiki-dark:#D3C6AA\">(</span><span style=\"color:#E36209;--shiki-dark:#D3C6AA\">Back</span><span style=\"color:#24292E;--shiki-dark:#D3C6AA\">, </span><span style=\"color:#E36209;--shiki-dark:#D3C6AA\">Front</span><span style=\"color:#24292E;--shiki-dark:#D3C6AA\">, </span><span style=\"color:#E36209;--shiki-dark:#D3C6AA\">Rot</span><span style=\"color:#24292E;--shiki-dark:#D3C6AA\">)</span><span style=\"color:#D73A49;--shiki-dark:#E67E80\">.</span></span></code></pre>\n<p>Here, <code>length</code> is used to generate a “dynamic” pattern for pattern matching unification against the front of the list: it’s just a list of free variables of a specific length to pattern match the front of the input list. Then the relational behaviour of <code>append</code> unifies the actual values into <code>Front</code> and the remainder into <code>Back</code>, finally just running <code>append</code> again with the order flipped to generate <code>Rot</code>.</p>\n<hr>\n<p>One of my friends described Prolog as a “puzzle box language”, and I think thats a great description; there’s something satisfacting in describing a predicate in a way that declaritively expresses the problem. Initially I didn’t think to use <code>length</code>, and wrote a bespoke – and terrible – predicate called <code>free_list_</code> that did the job badly. This sort of turned-around way of thinking about problems is what I find makes Prolog so satisfying, and mind boggling, to learn and use.</p>\n","url":"https://brehaut.net/blog/2026/prolog_list_rotation","date_published":"2026-08-07T00:00:00Z"},{"id":"https://brehaut.net/blog/2025/picat","title":"Initial impressions of Picat","summary":"A grab bag of thoughts on experimenting with Picat language.","content_html":"<aside>\n    Did you know that initial versions of <a href=\"https://en.wikipedia.org/wiki/C_(programming_language)\">C</a> and <a href=\"https://en.wikipedia.org/wiki/Prolog\">Prolog</a> were both released in 1972? I always think of that era of computing as being more concerned with eeking out performance on the hardware of the era, and yet experimentation with what is still one of the most abstract branches of computation was happening at the same time. \n</aside>\n<p>As a tourist into the world of <a href=\"https://github.com/brehaut/lifepaths\">logic programming</a>, I was interested in reading <a href=\"https://www.hillelwayne.com/post/toolbox-languages/\">Hillel</a> <a href=\"https://www.hillelwayne.com/post/picat/\">Wayne</a> talk about <a href=\"https://picat-lang.org\">Picat</a>, a new (relatively speaking) logical programming language derived from <a href=\"http://en.wikipedia.org/wiki/Prolog\">Prolog</a>, but with affordances for functional and imperative programming<sup class=\"footnote\">1</sup>. Of particular note, Picat predicates default to deterministic and you have to explicitly opt into nondetermnism, which is a big change from Prolog, which – as a Prolog beginner – is maybe the thing I find most difficult about writing real programs.</p>\n<p>I’ve been looking for something to trial Picat with, and this week I gave it a crack on some <em>Advent of Code</em> puzzles (more about that in a separate post maybe). This experiment resulted in around 400 lines of Picat code (including whitespace and some comments), covering some basic IO processing, algorithms, and building my own little test runner over about 5 days. I have not yet experimented in any real way with the planner or constraint programming libraries.</p>\n<hr>\n<p>Picat is clearly a research language. For one thing Github doesn’t have syntax highlighting for it (and there are many hundreds of syntaxes supported there). There is a basic VSCode mode that I suspect is a fork(?) of the Prolog mode: it exhibits the same extremely frustrating bug that occasionaly causes random lines of code from elsewhere in the file to be randomly inserted instead of what you typed when some syntax errors occur.</p>\n<p>There are very little in the way of developer ergonomics. Error messages report almost zero context, no line numbers, or stack traces. There’s a basic built in debugger but I leaned on <code>println</code> debugging more. There is a basic REPL, although it doesn’t have the readline style ergonomics you might be used to.</p>\n<p>The documentation is comprehensive but terse, and I hope you area okay with Times New Roman for everything (<em>everything</em> even inline syntax). Good news, because the language is fundamentally ungoogleable<sup class=\"footnote\">2</sup>.</p>\n<hr>\n<p>Picat encourages using a functional via it’s API design, and language features. It’s functional programming heavily flavoured by the Prolog origins, so powerful pattern matching and logical variables everywhere. This certainly got me productive quickly: I was writing productive code almost immediately. But it comes at a cost: Picat is not a great functional language, the syntax is, somewhat necessarily, clumsier than an ML or a lisp, or even a modern braces language like Javascript. And the inclusion of functions means that structures are just a little clumsier to work with in Picat than in Prolog: it’s not much but that friction adds up.</p>\n<p>Similarly, the imperative facilities are useful (it certainly simplified the IO code I needed to write for <em>Advent of Code</em>), but an expressive imperative language this is not. It reminds me of basic in the 90s. Speaking of which, the IO library exists but is very bare bones.</p>\n<p>A consequence of these feature’s is that I found myself writing more functional code than logical code, which was a disappointment. I didn’t really feel like I was getting the advantage of using the language.</p>\n<hr>\n<p>As mentioned at the top, I ended up writing a minimal test case runner that would allow you to provide a predicate goal (predicates only, no functions), the name of the variable under test, and the expected value, and it would list successes, failures (predicates with no solutions), and incorrect results. Why did I do this? Because I couldn’t find a testing library in the standard library or elsewhere online.</p>\n<p>This was a fun challenge. Prolog, and Picat as a result, have an interesting approach to metaprogramming that feels a bit like lisp macros on rocket fuel. I ended up learning how to make a function that would walk a structure replacing occurances of the named variable under test with a fresh variable, and the executing that goal with the <code>call</code> predicate.</p>\n<p>Of course the very first Advent of Code puzzle I tried this on ended up using bigints which immediately blew up the test running (I suspect somewhere deep in the structure rewriting code). Oh well.</p>\n<hr>\n<p>I’m not ready to say that I’m finished experienting with this language. But in the 5 days I spent with it, I certainly experienced enough friction to need a break, and ultimately I think Prolog is still probably more interesting to me at this point.</p>\n<hr>\n<p>My next logic programming excursion is probably returning to Prolog. <a href=\"https://www.swi-prolog.org\">SWI Prolog</a> recently released version 10, with improved support for their WASM runtime to let you <a href=\"https://www.swi-prolog.org/pldoc/man?section=wasm-version\">run it in the browser</a>. I believe <a href=\"https://www.scryer.pl\">Scryer Prolog</a> also has a WASM build. Perhaps I’ll make a <a href=\"https://svelte.dev\">Svelte</a> app that wraps up SWI Prolog; I’ve wanted to deploy logical programs for some RPG programs in the browser in the past (<a href=\"https://manticore.brehaut.net/\">Manticore</a> started life as a <a href=\"https://minikanren.org\">miniKanren</a> program in Clojure that was rewritten into Typescript).</p>\n<ol class=\"footnotes\">\n\t<li>These are basically syntactic sugar: Picat compiles these down to canonical logical forms.</li>\n\t<li>Googleʻs search AI will insist on generating answers to your queries that are pure fabrication: some sort of bullshit hybrid of Python and Prolog. It’s almost funny given the confidence it supplies this nonsense with.</li>\n</ol>\n","url":"https://brehaut.net/blog/2025/picat","date_published":"2025-12-06T00:00:00Z"},{"id":"https://brehaut.net/blog/2024/vite_node","title":"Run scripts in a vite project with <code>vite-node</code>","summary":"Maybe the easiest way to run a TypeScript program with Node.js","content_html":"<p>While building the <a href=\"/burning_wheel_index\">Burning Wheel index</a> on this site, I’ve written a small command line tool to manage the index database. This tool shares some code with the site proper. As anyone who has ever used Typescript directly will know, managing different <code>tsconfig.json</code> files, and the various targets, modules, output files, etc can quickly become an absolute quagmire. I wasn’t surprised to have trouble getting it to build.</p>\n<p>It turns out that for projects using <a href=\"https://vite.dev\">Vite</a> (such as with <a href=\"https://astro.build\">Astro</a> or <a href=\"https://svelte.dev\">Svelte</a>) the <a href=\"https://www.npmjs.com/package/vite-node\"><code>vite-node</code></a> package lets you trivially run any TypeScript file in your project as a script, using the existing configuration. Honestly, it’s a bit like magic.</p>\n","url":"https://brehaut.net/blog/2024/vite_node","date_published":"2024-12-30T00:00:00Z"}]}