<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
<channel>
<title>mal</title>
<link>https://mal.haza.website/</link>
<description>mal</description>
<atom:link href="https://mal.haza.website/feed.xml" rel="self" type="application/rss+xml"/>
<lastBuildDate>Fri, 13 Nov 2026 10:30:23 GMT</lastBuildDate>
<item><title>Self Hosting</title><link>https://mal.haza.website/2026/11/self-hosting.html</link><guid isPermaLink="true">https://mal.haza.website/2026/11/self-hosting.html</guid><pubDate>Fri, 13 Nov 2026 10:17:00 GMT</pubDate><description>&lt;h1 id=&quot;self-hosting&quot;&gt;Self Hosting&lt;/h1&gt;&lt;p&gt;&lt;/p&gt;&lt;p&gt;Hello! This is like a First Post all over again, this time publishing from &lt;a href=&quot;https://i.haza.website&quot;&gt;i.haza.website&lt;/a&gt;! &lt;span data-name=&quot;tada&quot; data-type=&quot;emoji&quot;&gt;🎉️&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;p&gt;I&apos;ve held off posting a few updates so that I could finish the changes required to bring this blog over, and that day has finally come. Like with the &lt;a href=&quot;https://docs.haza.website&quot;&gt;docs&lt;/a&gt; and &lt;a href=&quot;https://holster.haza.website&quot;&gt;holster&lt;/a&gt; sites, there were a lot of incremental changes to make it happen. I will talk more about them in future posts, for now I&apos;m happy to be writing in a nice editor, that auto saves updates, allows me to create drafts, and has some nice templating features built in.&lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;p&gt;There&apos;s quite a few features you need for a static site to publish a blog, one I really wanted from the beginning is multiple posts on the home page. This was added as Front Matter at the start of a template, which makes it capable of publishing pages that don&apos;t otherwise exist in your folder structure:&lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;p&gt;&lt;img src=&quot;https://mal.haza.website/_media/mal/screenshot-from-2026-11-13.png&quot; data-media-id=&quot;ae0397cf-84b7-428f-b121-5d256d4f94f4&quot;&gt;&lt;/p&gt;</description></item>
<item><title>holster.haza.website</title><link>https://mal.haza.website/2026/11/holsterhazawebsite.html</link><guid isPermaLink="true">https://mal.haza.website/2026/11/holsterhazawebsite.html</guid><pubDate>Mon, 02 Nov 2026 08:28:03 GMT</pubDate><description>&lt;h1 id=&quot;holster-haza-website&quot;&gt;holster.haza.website&lt;/h1&gt;&lt;p&gt;The Holster website has been gone since rebuilding &lt;a href=&quot;https://i.haza.website&quot;&gt;i.haza.website&lt;/a&gt;, it&apos;s now back as a replacement for the github wiki docs with updated information for all API&apos;s and better examples&amp;nbsp;🎉&lt;/p&gt;&lt;p&gt;I Haza Website also got some updates as I was working on this, so it now does nice preview navigation while you&apos;re editing pages. You can start from one page in preview mode, navigate to another page you&apos;re working on and continue editing from there.&lt;/p&gt;&lt;p&gt;There&apos;s also a lot of code examples on the site so I added a Copy Code plugin that allows you to copy code blocks if you want to run them interactively in Node. There&apos;s also a theme switcher plugin for light and dark mode:&lt;br&gt;&lt;video data-media-id=&quot;41ecfe23-e2ef-4a93-a240-a49a5540d7c8&quot; src=&quot;https://mal.haza.website/_media/mal/media/preview-nav.mp4&quot; controls=&quot;true&quot;&gt;&lt;/video&gt;&lt;/p&gt;</description></item>
<item><title>Holster 2.3.0</title><link>https://mal.haza.website/2026/10/holster-230.html</link><guid isPermaLink="true">https://mal.haza.website/2026/10/holster-230.html</guid><pubDate>Mon, 19 Oct 2026 08:43:34 GMT</pubDate><description>&lt;h1 id=&quot;holster-2-3-0&quot;&gt;Holster 2.3.0&lt;/h1&gt;&lt;p&gt;A &lt;a href=&quot;https://www.npmjs.com/package/@mblaney/holster&quot;&gt;new Holster release&lt;/a&gt; is available that provides &lt;code&gt;await&lt;/code&gt; support.&lt;/p&gt;&lt;p&gt;Using Holster for building &lt;a href=&quot;https://i.haza.website&quot;&gt;i.haza.website&lt;/a&gt;&amp;nbsp;was very different than working on &lt;a href=&quot;https://rsstream.app&quot;&gt;RSStream&lt;/a&gt; as it needed to be much more responsive to Holster data changes. Looking back at the code up until this point, it was full of &lt;code&gt;await new Promise(resolve =&amp;gt; { })&lt;/code&gt; wrappers so that state could update in the correct order.&lt;/p&gt;&lt;p&gt;The latest Holster release removes all of this from application code by providing a &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise#thenables&quot;&gt;Thenables&lt;/a&gt; interface onto terminal API calls. That sounds weird but it does exactly what you need, which is when you would otherwise provide a callback to the Holster API you can just &lt;code&gt;await&lt;/code&gt; the call instead. Internally Holster knows when a callback isn&apos;t provided and can extend the response to include a &lt;code&gt;then()&lt;/code&gt; function, if you were chaining the request then the extra function is ignored, otherwise it provides your data.&lt;/p&gt;&lt;p&gt;This works for &lt;code&gt;get&lt;/code&gt; and &lt;code&gt;next&lt;/code&gt; in the API, &lt;code&gt;put&lt;/code&gt; requests can also be awaited but they now internally return a promise as they only ever returned an error or &lt;code&gt;null&lt;/code&gt; anyway. It allows for simplifying get requests like:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-js hljs&quot;&gt;&lt;span class=&quot;hljs-comment&quot;&gt;// From wrapped calls:&lt;/span&gt;
&lt;span class=&quot;hljs-keyword&quot;&gt;const&lt;/span&gt; existing = &lt;span class=&quot;hljs-keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;hljs-keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;hljs-title class_&quot;&gt;Promise&lt;/span&gt;(&lt;span class=&quot;hljs-function&quot;&gt;&lt;span class=&quot;hljs-params&quot;&gt;resolve&lt;/span&gt; =&amp;gt;&lt;/span&gt;
  user.&lt;span class=&quot;hljs-title function_&quot;&gt;get&lt;/span&gt;(&lt;span class=&quot;hljs-string&quot;&gt;&quot;mediaRootId&quot;&lt;/span&gt;, resolve),
)

&lt;span class=&quot;hljs-comment&quot;&gt;// To just:&lt;/span&gt;
&lt;span class=&quot;hljs-keyword&quot;&gt;const&lt;/span&gt; existing = &lt;span class=&quot;hljs-keyword&quot;&gt;await&lt;/span&gt; user.&lt;span class=&quot;hljs-title function_&quot;&gt;get&lt;/span&gt;(&lt;span class=&quot;hljs-string&quot;&gt;&quot;mediaRootId&quot;&lt;/span&gt;)&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;And previously put requests where you waited for the result were:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-js hljs&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;hljs-keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;hljs-title function_&quot;&gt;bumpProjectsVersion&lt;/span&gt;(&lt;span class=&quot;hljs-params&quot;&gt;user&lt;/span&gt;) {
  &lt;span class=&quot;hljs-keyword&quot;&gt;const&lt;/span&gt; now = &lt;span class=&quot;hljs-title class_&quot;&gt;Date&lt;/span&gt;.&lt;span class=&quot;hljs-title function_&quot;&gt;now&lt;/span&gt;()
  &lt;span class=&quot;hljs-keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;hljs-keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;hljs-title class_&quot;&gt;Promise&lt;/span&gt;(&lt;span class=&quot;hljs-function&quot;&gt;(&lt;span class=&quot;hljs-params&quot;&gt;resolve, reject&lt;/span&gt;) =&amp;gt;&lt;/span&gt;
    user
      .&lt;span class=&quot;hljs-title function_&quot;&gt;get&lt;/span&gt;(&lt;span class=&quot;hljs-string&quot;&gt;&quot;projectsVersion&quot;&lt;/span&gt;)
      .&lt;span class=&quot;hljs-title function_&quot;&gt;put&lt;/span&gt;(now, &lt;span class=&quot;hljs-function&quot;&gt;&lt;span class=&quot;hljs-params&quot;&gt;err&lt;/span&gt; =&amp;gt;&lt;/span&gt; (err ? &lt;span class=&quot;hljs-title function_&quot;&gt;reject&lt;/span&gt;(&lt;span class=&quot;hljs-keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;hljs-title class_&quot;&gt;Error&lt;/span&gt;(err)) : &lt;span class=&quot;hljs-title function_&quot;&gt;resolve&lt;/span&gt;(now))),
  )
}&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Are now:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-js hljs&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;hljs-keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;hljs-title function_&quot;&gt;bumpProjectsVersion&lt;/span&gt;(&lt;span class=&quot;hljs-params&quot;&gt;user&lt;/span&gt;) {
  &lt;span class=&quot;hljs-keyword&quot;&gt;const&lt;/span&gt; now = &lt;span class=&quot;hljs-title class_&quot;&gt;Date&lt;/span&gt;.&lt;span class=&quot;hljs-title function_&quot;&gt;now&lt;/span&gt;()
  &lt;span class=&quot;hljs-keyword&quot;&gt;await&lt;/span&gt; user.&lt;span class=&quot;hljs-title function_&quot;&gt;get&lt;/span&gt;(&lt;span class=&quot;hljs-string&quot;&gt;&quot;projectsVersion&quot;&lt;/span&gt;).&lt;span class=&quot;hljs-title function_&quot;&gt;put&lt;/span&gt;(now)
  &lt;span class=&quot;hljs-keyword&quot;&gt;return&lt;/span&gt; now
}&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;It also makes it easier to work with try and catch blocks, but it&apos;s also nice that the old callback style isn&apos;t effected by these changes when you want to use it.&lt;/p&gt;</description></item>
<item><title>I Haza Website</title><link>https://mal.haza.website/2026/10/i-haza-website.html</link><guid isPermaLink="true">https://mal.haza.website/2026/10/i-haza-website.html</guid><pubDate>Sun, 18 Oct 2026 05:14:28 GMT</pubDate><description>&lt;h1 id=&quot;i-haza-website&quot;&gt;I Haza Website&lt;/h1&gt;&lt;p&gt;Happy to share that &lt;a href=&quot;https://i.haza.website&quot;&gt;i.haza.website&lt;/a&gt;&amp;nbsp;is now available again as a new local-first app!&lt;/p&gt;&lt;p&gt;It was previously a PHP app with a similar goal to what the new version is doing, which is to make it easy to publish web pages. The new version is a web app backed by Holster, and besides publishing websites is great for taking notes and transferring media between devices. Holster provides automatic syncing between devices that you&apos;re logged in to, as well as encryption and offline support.&lt;/p&gt;&lt;p&gt;This has been another app where the complimentary goal has been Holster improvements alongside having useful tools. This has been true again because I Haza Website is much more interactive with the Holster API then other apps, so it&apos;s caught a few things that have needed fixing.&lt;/p&gt;&lt;p&gt;This blog hasn&apos;t been replaced yet because I Haza Website doesn&apos;t have some of the blogging features of the old app. Expect that to change soon though, I&apos;m looking forward to moving to the new interface it&apos;s much nicer to write in.&amp;nbsp;😊&lt;/p&gt;&lt;p&gt;&lt;img src=&quot;https://mal.haza.website/_media/mal/media/title2.png&quot; alt=&quot;&quot; data-media-id=&quot;ee0ab387-5a93-4540-8188-2e2875bc9f80&quot;&gt;&lt;/p&gt;</description></item>
<item><title>Nested listeners</title><link>https://mal.haza.website/2026/09/nested-listeners.html</link><guid isPermaLink="true">https://mal.haza.website/2026/09/nested-listeners.html</guid><pubDate>Thu, 03 Sep 2026 09:15:31 GMT</pubDate><description>&lt;h1 id=&quot;nested-listeners&quot;&gt;Nested listeners&lt;/h1&gt;&lt;p&gt;Another &lt;a href=&quot;https://github.com/mblaney/holster/releases/tag/2.2.0&quot;&gt;Holster release&lt;/a&gt; to add a new nested listener feature that I&apos;ve needed for multiple projects now. In &lt;a href=&quot;https://rsstream.app&quot;&gt;RSStream&lt;/a&gt; I added it outside of Holster itself, since I wasn&apos;t sure how often I would need it.&lt;/p&gt;&lt;p&gt;The idea is that you add a listener to a query in Holster, but that query always refers to a particular node. That node can reference other nodes, but those references don&apos;t change. So even though the API chains nodes together you need to explicitly list which ones you want to listen to changes on. The external way to do this is to create &lt;code&gt;on()&lt;/code&gt; callbacks for each of the child nodes in the parent&apos;s &lt;code&gt;on()&lt;/code&gt; callback. This works fine, but the code is really the same no matter what the project. So it&apos;s now included with Holster and just needs the extra parameter to the parent query to set up all listeners below that point. They&apos;re also automatically removed when the parent &lt;code&gt;off()&lt;/code&gt; is called.&lt;/p&gt;&lt;p&gt;I asked Claude for a simple example and it of course gave a stand alone example that works in Node 🤖:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-js hljs&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;hljs-title class_&quot;&gt;Holster&lt;/span&gt; &lt;span class=&quot;hljs-keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&quot;@mblaney/holster/src/holster.js&quot;&lt;/span&gt;

&lt;span class=&quot;hljs-keyword&quot;&gt;const&lt;/span&gt; holster = &lt;span class=&quot;hljs-title class_&quot;&gt;Holster&lt;/span&gt;()
&lt;span class=&quot;hljs-keyword&quot;&gt;const&lt;/span&gt; user = holster.&lt;span class=&quot;hljs-title function_&quot;&gt;user&lt;/span&gt;()
  
user.&lt;span class=&quot;hljs-title function_&quot;&gt;create&lt;/span&gt;(&lt;span class=&quot;hljs-string&quot;&gt;&quot;alice&quot;&lt;/span&gt;, &lt;span class=&quot;hljs-string&quot;&gt;&quot;password&quot;&lt;/span&gt;, &lt;span class=&quot;hljs-function&quot;&gt;() =&amp;gt;&lt;/span&gt; {
    user.&lt;span class=&quot;hljs-title function_&quot;&gt;auth&lt;/span&gt;(&lt;span class=&quot;hljs-string&quot;&gt;&quot;alice&quot;&lt;/span&gt;, &lt;span class=&quot;hljs-string&quot;&gt;&quot;password&quot;&lt;/span&gt;, &lt;span class=&quot;hljs-function&quot;&gt;() =&amp;gt;&lt;/span&gt; {
      &lt;span class=&quot;hljs-comment&quot;&gt;// Without {nested: true}, this listener only fires when a NEW key is&lt;/span&gt;
      &lt;span class=&quot;hljs-comment&quot;&gt;// added to &quot;team&quot; - it won&apos;t fire again for a later, value-only&lt;/span&gt;
      &lt;span class=&quot;hljs-comment&quot;&gt;// change to an EXISTING member, because that&apos;s a write to the&lt;/span&gt;
      &lt;span class=&quot;hljs-comment&quot;&gt;// member&apos;s own soul (every object-valued child gets its own), not to&lt;/span&gt;
      &lt;span class=&quot;hljs-comment&quot;&gt;// &quot;team&quot; itself.&lt;/span&gt;
      user.&lt;span class=&quot;hljs-title function_&quot;&gt;get&lt;/span&gt;(&lt;span class=&quot;hljs-string&quot;&gt;&quot;team&quot;&lt;/span&gt;).&lt;span class=&quot;hljs-title function_&quot;&gt;on&lt;/span&gt;(&lt;span class=&quot;hljs-function&quot;&gt;&lt;span class=&quot;hljs-params&quot;&gt;members&lt;/span&gt; =&amp;gt;&lt;/span&gt; {
        &lt;span class=&quot;hljs-variable language_&quot;&gt;console&lt;/span&gt;.&lt;span class=&quot;hljs-title function_&quot;&gt;log&lt;/span&gt;(&lt;span class=&quot;hljs-string&quot;&gt;&quot;team changed:&quot;&lt;/span&gt;, members)
      }, &lt;span class=&quot;hljs-literal&quot;&gt;true&lt;/span&gt;, {&lt;span class=&quot;hljs-attr&quot;&gt;nested&lt;/span&gt;: &lt;span class=&quot;hljs-literal&quot;&gt;true&lt;/span&gt;})

      &lt;span class=&quot;hljs-comment&quot;&gt;// A brand new key - the listener above always sees this.&lt;/span&gt;
      user.&lt;span class=&quot;hljs-title function_&quot;&gt;get&lt;/span&gt;(&lt;span class=&quot;hljs-string&quot;&gt;&quot;team&quot;&lt;/span&gt;).&lt;span class=&quot;hljs-title function_&quot;&gt;next&lt;/span&gt;(&lt;span class=&quot;hljs-string&quot;&gt;&quot;alice&quot;&lt;/span&gt;).&lt;span class=&quot;hljs-title function_&quot;&gt;put&lt;/span&gt;({&lt;span class=&quot;hljs-attr&quot;&gt;role&lt;/span&gt;: &lt;span class=&quot;hljs-string&quot;&gt;&quot;engineer&quot;&lt;/span&gt;})

      &lt;span class=&quot;hljs-comment&quot;&gt;// A value-only change to an ALREADY-KNOWN key - only reaches the&lt;/span&gt;
      &lt;span class=&quot;hljs-comment&quot;&gt;// listener because of {nested: true}. Without it, this write would&lt;/span&gt;
      &lt;span class=&quot;hljs-comment&quot;&gt;// be silently missed.&lt;/span&gt;
      &lt;span class=&quot;hljs-built_in&quot;&gt;setTimeout&lt;/span&gt;(&lt;span class=&quot;hljs-function&quot;&gt;() =&amp;gt;&lt;/span&gt; {
        user.&lt;span class=&quot;hljs-title function_&quot;&gt;get&lt;/span&gt;(&lt;span class=&quot;hljs-string&quot;&gt;&quot;team&quot;&lt;/span&gt;).&lt;span class=&quot;hljs-title function_&quot;&gt;next&lt;/span&gt;(&lt;span class=&quot;hljs-string&quot;&gt;&quot;alice&quot;&lt;/span&gt;).&lt;span class=&quot;hljs-title function_&quot;&gt;put&lt;/span&gt;({&lt;span class=&quot;hljs-attr&quot;&gt;role&lt;/span&gt;: &lt;span class=&quot;hljs-string&quot;&gt;&quot;lead engineer&quot;&lt;/span&gt;})
      }, &lt;span class=&quot;hljs-number&quot;&gt;100&lt;/span&gt;)
    })
})&lt;/code&gt;&lt;/pre&gt;</description></item>
<item><title>Concurrency</title><link>https://mal.haza.website/2026/08/concurrency.html</link><guid isPermaLink="true">https://mal.haza.website/2026/08/concurrency.html</guid><pubDate>Wed, 19 Aug 2026 08:52:52 GMT</pubDate><description>&lt;h1 id=&quot;concurrency&quot;&gt;Concurrency&lt;/h1&gt;&lt;p&gt;New &lt;a href=&quot;https://github.com/mblaney/holster/releases/tag/2.1.4&quot;&gt;Holster release&lt;/a&gt; focused mainly on concurrency fixes. I&apos;ve been working on a new app that runs on Holster which is very front end heavy and running all sorts of async requests, and creating lots of tests for it at the same time. The tests started getting pretty complicated, matching the types of interactions involved, and these all run on top of real holster test instances. The tests started getting a bit flaky as they exhausted their memory limit and swapped out cache files.&lt;/p&gt;&lt;p&gt;I realized Holster has never had any specific concurrency tests, so it was interesting adding them and looking at what sort of race conditions were causing problems. I have to say that Claude is getting really good at methodically working through scenarios without getting lost in the details. I&apos;m impressed with the way it found the issues that have been fixed in this release and the relatively simple changes that were made.&lt;/p&gt;</description></item>
<item><title>Holster browser</title><link>https://mal.haza.website/2026/07/holster-browser.html</link><guid isPermaLink="true">https://mal.haza.website/2026/07/holster-browser.html</guid><pubDate>Sun, 12 Jul 2026 06:25:23 GMT</pubDate><description>&lt;h1 id=&quot;holster-browser&quot;&gt;Holster browser&lt;/h1&gt;&lt;p&gt;I&apos;ve created another npm package that is complimentary to holster-router, this one is called &lt;a href=&quot;https://www.npmjs.com/package/@mblaney/holster-browser&quot;&gt;holster-browser&lt;/a&gt;. It&apos;s another extraction of core utilities and components from RSStream so I can use them in other projects.&lt;/p&gt;&lt;p&gt;So now if you want to use Holster with Express and React, it&apos;s super easy to put them all together and have a working authentication system. Holster is still completely independent, but the other two side projects assume the same endpoints and account structures. I&apos;ll probably add to both as I need extra features and not worry so much about publishing the applications that use them.&lt;/p&gt;</description></item>
<item><title>Holster router</title><link>https://mal.haza.website/2026/07/holster-router.html</link><guid isPermaLink="true">https://mal.haza.website/2026/07/holster-router.html</guid><pubDate>Sun, 05 Jul 2026 02:15:13 GMT</pubDate><description>&lt;h1 id=&quot;holster-router&quot;&gt;Holster router&lt;/h1&gt;&lt;p&gt;Lots of updates this week, I gave a presentation on Holster at the &lt;a href=&quot;https://www.youtube.com/watch?v=QEP1mr2CYxA&amp;amp;list=PLTbD2QA-VMnXFsLbuPGz1H-Najv9MD2-H&amp;amp;index=38&quot;&gt;June LoFi meetup&lt;/a&gt; and published a &lt;a href=&quot;https://www.npmjs.com/package/@mblaney/holster-router&quot;&gt;new npm package&lt;/a&gt;. The meetup was at 1am for me, so not the best time to be trying to explain how Holster works! 😅 but it was great to be invited to speak and hear from others doing similar work. I&apos;ve also been going back and watching previous LoFi episodes which are really interesting and great to see how much work is going on in the local first application space.&lt;/p&gt;&lt;p&gt;The npm package I&apos;ve released is called &lt;strong&gt;holster-router&lt;/strong&gt; and is an extraction of some of the work that went into RSStream. I&apos;ve done that so I can re-use the same routes and user creation process in a new Holster based project that I&apos;m about to start. RSStream uses the concept of invite codes to control accounts, but the new project will be more open than this, so the holster-router functions that are shared between these projects will allow for more than one way to create an account. The recent work that went into user storage limits should mean that sign-ups can be open without worrying about server storage. All the other routes for managing password updates and resets will be shared and the new project will add new routes to holster-router as the sign-up model for that is worked out.&lt;/p&gt;</description></item>
<item><title>Holster 2.1.0</title><link>https://mal.haza.website/2026/06/holster-210.html</link><guid isPermaLink="true">https://mal.haza.website/2026/06/holster-210.html</guid><pubDate>Thu, 18 Jun 2026 08:24:30 GMT</pubDate><description>&lt;h1 id=&quot;holster-2-1-0&quot;&gt;Holster 2.1.0&lt;/h1&gt;&lt;p&gt;A new version of Holster is available that provides a new feature I&apos;ve wanted to add for a while. This is &lt;strong&gt;per-user storage limits&lt;/strong&gt;. It is off by default, but starting Holster with a new &lt;code&gt;userLimit&lt;/code&gt; option set to &lt;code&gt;true&lt;/code&gt; will check how much data is stored under a public key before writing updates to disk. This is a server only feature and allows controlling your disk usage if you have limited resources.&lt;/p&gt;&lt;p&gt;One of the things I often thought about when learning about GunDB was how you were meant to run a relay when all data is distributed. Holster is careful to only store data that browsers request, but if you host a public relay I think you should be able to decide how those resources are used. Holster already had options for memory limits, so now it has options for disk usage too. The default is only 1MB, but can also be set in the options when starting. If you set it to zero then unlisted users are effectively blocked from storing data on your server.&lt;/p&gt;&lt;p&gt;Listed users should be added to a simple &lt;code&gt;.user_limit.json&lt;/code&gt; file in your application directory, it&apos;s just an object containing public keys and their allowed storage in megabytes. Your application can update this file for Holster to read from, which is what &lt;a href=&quot;https://github.com/mblaney/rsstream&quot;&gt;RSStream&lt;/a&gt; is doing now. Holster then checks it periodically while also tracking user storage in a second file called &lt;code&gt;.user_storage.json&lt;/code&gt;. For more information and settings to update when running a relay see the &lt;a href=&quot;https://github.com/mblaney/holster/wiki/Holster-API#holster-options&quot;&gt;Holster API docs&lt;/a&gt;.&lt;/p&gt;&lt;p&gt;Future work for this feature can be done at the application level, for instance you can provide feedback to users about how much storage they&apos;re using and provide an endpoint to increase it. Note that this is only backup storage on your server, all their data is still stored locally if you use IndexedDB.&lt;/p&gt;</description></item>
<item><title>Switched to Vite</title><link>https://mal.haza.website/2026/06/switched-to-vite.html</link><guid isPermaLink="true">https://mal.haza.website/2026/06/switched-to-vite.html</guid><pubDate>Wed, 10 Jun 2026 09:07:17 GMT</pubDate><description>&lt;h1 id=&quot;switched-to-vite&quot;&gt;Switched to Vite&lt;/h1&gt;&lt;p&gt;I just updated &lt;a href=&quot;https://github.com/mblaney/rsstream&quot;&gt;RSStream&lt;/a&gt; to build using &lt;a href=&quot;https://vite.dev/&quot;&gt;Vite&lt;/a&gt; instead of the old CRA tools. I didn&apos;t realize it would be such a small change otherwise I would&apos;ve switched over earlier!&amp;nbsp;🎉 I&apos;ve also removed the build folder from the github repo, which was suggested I do for &lt;a href=&quot;https://github.com/mblaney/holster&quot;&gt;Holster&lt;/a&gt; to avoid pr&apos;s being merged where the build files don&apos;t match the source change. The Holster github workflow runs the build before publishing to npm, for RSStream this means an extra step, but it&apos;s worth it to make sure the build matches your files.&lt;/p&gt;&lt;p&gt;RSStream has replaced my previous (also homegrown) reader and I&apos;m not finding I want to add any other features at this stage. That means it&apos;s time to move on to another project... the next one involves adding new Holster features that I&apos;m looking forward to being able to use, so stay tuned for updates.&lt;/p&gt;</description></item>
<item><title>RSStream walk through</title><link>https://mal.haza.website/2026/05/rsstream-walk-through.html</link><guid isPermaLink="true">https://mal.haza.website/2026/05/rsstream-walk-through.html</guid><pubDate>Wed, 20 May 2026 09:24:19 GMT</pubDate><description>&lt;h1 id=&quot;rsstream-walk-through&quot;&gt;RSStream walk through&lt;/h1&gt;&lt;p&gt;&lt;a href=&quot;https://rsstream.app/&quot;&gt;RSStream&lt;/a&gt; is working well now, check out the walk through video below!&lt;/p&gt;&lt;p&gt;It starts with signing into the app, then demonstrates bookmarking, loading older items on scroll-back and search. The crazy thing that I really wanted to show however is that the whole video is offline!&amp;nbsp;🤯&lt;/p&gt;&lt;p&gt;This is thanks to Holster storing everything in indexedDB and the service worker cache storing images. I&apos;m now also working on storing audio and video for offline playback and allowing cache management too.&lt;/p&gt;&lt;p&gt;&lt;video style=&quot;width: 320px;&quot; data-media-id=&quot;72c56e46-eae0-4e52-9c93-71a681dea3f6&quot; src=&quot;https://mal.haza.website/_media/mal/media/rsstream.mp4&quot; controls=&quot;true&quot;&gt;&lt;/video&gt;&lt;/p&gt;</description></item>
<item><title>Application development</title><link>https://mal.haza.website/2026/05/application-development.html</link><guid isPermaLink="true">https://mal.haza.website/2026/05/application-development.html</guid><pubDate>Thu, 07 May 2026 08:24:41 GMT</pubDate><description>&lt;h1 id=&quot;application-development&quot;&gt;Application development&lt;/h1&gt;&lt;p&gt;The last few weeks have felt like Holster is capable enough to now focus on some application development. Previously trying to work on an app that used Holster meant constantly jumping back into the Holster code trying to fix edge cases or find performance improvements.&lt;/p&gt;&lt;p&gt;But I&apos;ve recently been able to focus on improving &lt;a href=&quot;https://rsstream.app&quot;&gt;RSStream&lt;/a&gt; and haven&apos;t had to context switch too much. I&apos;ve just made a small &lt;a href=&quot;https://github.com/mblaney/holster/releases/tag/2.0.1&quot;&gt;Holster 2.0.1&lt;/a&gt; bug fix release, but besides that it&apos;s feeling solid.&lt;/p&gt;&lt;p&gt;The real improvement I&apos;ve found is not in any Holster change, but in the data model I&apos;m using. A feed reader is possibly the worst starting application to test Holster with, it&apos;s too much data arriving all the time, and it&apos;s always new! What I realised is that I was treating everything as graph data, starting from feeds all the way down to properties on an item. But unless you&apos;re using the ability to query data on the graph, it&apos;s a massive performance hit to create references for every piece of graph data. So at some point it&apos;s good to stop and decide what can just be stored as strings.&lt;/p&gt;&lt;p&gt;When I was looking into this Claude reported stringifying items would reduce reference look ups from O(N) to O(1)... I didn&apos;t believe this, so cleared the session and started again, but same result!&amp;nbsp; So I looked further and realised that as well as saving all the property references on an item, the whole item itself didn&apos;t need a reference created for it as it became a simple property on it&apos;s parent node. This is a big deal for a feed reader since the N mentioned here is the number of items per day per feed, so with this simple change the app feels usable now.&lt;/p&gt;</description></item>
<item><title>Holster 2.0</title><link>https://mal.haza.website/2026/04/holster-20.html</link><guid isPermaLink="true">https://mal.haza.website/2026/04/holster-20.html</guid><pubDate>Mon, 27 Apr 2026 11:37:04 GMT</pubDate><description>&lt;h1 id=&quot;holster-2-0&quot;&gt;Holster 2.0&lt;/h1&gt;&lt;p&gt;A &lt;a href=&quot;https://github.com/mblaney/holster/releases/tag/2.0.0&quot;&gt;2.0 release&lt;/a&gt; sounds like it should be a big update, and that actually has turned out to be true here, but more by coincidence. Holster uses semver for version numbers, so this 2.0 update is really just for a small breaking API change: a server now needs to be started with either an explicit port number or a websocket server in their config, there&apos;s no longer a default port as a fallback.&lt;/p&gt;&lt;p&gt;This was required because some browser testing frameworks were breaking on the &lt;code&gt;const isNode = typeof document === &quot;undefined&quot;&lt;/code&gt; check as they also don&apos;t define &lt;code&gt;document&lt;/code&gt;. The updated check &lt;code&gt;const isNode = typeof process !== &quot;undefined&quot; &amp;amp;&amp;amp; process.versions?.node != null&lt;/code&gt;, is a better test for server environments but again is also true for testing libraries like jsdom. So the fix isn&apos;t to rely on &lt;code&gt;isNode&lt;/code&gt; in the tests, but to explicitly require a port when running a server. The real benefit of this &lt;code&gt;isNode&lt;/code&gt; change is that Web Workers should now be supported, which is enough of a reason to make the change. But the reason this has become a big update is because I made some radisk changes, and then didn&apos;t want to publish those until I was confident it was stable. This meant I just kept adding other patches while watching how the radisk change was performing.&lt;/p&gt;&lt;p&gt;One other API change is that there is no need for a &lt;code&gt;wait&lt;/code&gt; parameter on requests, so there&apos;s no reason for an &lt;code&gt;options&lt;/code&gt; object at all now. This was previously helpful if you wanted to wait longer for certain queries, but this update has dealt with timeout issues internally so there&apos;s no longer a reason to specify it. So requests are either fast if you&apos;ve requested a key previously, or given much more time to check the network if it hasn&apos;t been seen before.&lt;/p&gt;&lt;p&gt;My last update mentioned using private browsing mode for testing, but even better than that is going offline and seeing how Holster handles fetching data. The honest answer was not very well, requests would fall through to the network and get stuck in a queue that couldn&apos;t be processed. But this also made it quite easy to find what needed improving. When a query is made via the API, the first place to check is in-memory, ie &quot;the graph&quot;. There were a few places where it wasn&apos;t capturing everything available, so requests would fall through to disk.&lt;/p&gt;&lt;p&gt;Next there were the radisk improvements I mentioned. The change was quite simple, radisk shouldn&apos;t ever really time out unless there&apos;s an actual problem reading from disk. So timeouts have been mostly removed, file lookup has got faster and file splitting has improved too. It would previously split into the configured size and a smaller file for the overflow, now it aims for half each which is what GunDB tries to do too.&lt;/p&gt;&lt;p&gt;So now if the browser has previously synchronised all available data, no network requests are required. Lastly if something does fall through to a network request when offline then there&apos;s much better recovery there too.&lt;/p&gt;</description></item>
<item><title>I&apos;m back</title><link>https://mal.haza.website/2026/03/im-back.html</link><guid isPermaLink="true">https://mal.haza.website/2026/03/im-back.html</guid><pubDate>Tue, 31 Mar 2026 07:50:01 GMT</pubDate><description>&lt;h1 id=&quot;i-m-back&quot;&gt;I&apos;m back&lt;/h1&gt;&lt;p&gt;Hello! This is one of those &quot;I can&apos;t believe I haven&apos;t written anything in so long&quot; posts where I try and explain why my last update was in November... well it&apos;s enough to say that life can get busy and I&apos;m excited to be able to get back to some Holster development.&lt;/p&gt;&lt;p&gt;My focus at the moment is actually on application development on top of Holster, but that has just found all sorts of things that have needed fixing. They are mostly to do with timing, either timeouts from slow networks or stale cache data arriving before other updates on a callback. The easiest way to test these issues is with a private browsing window that forces a network fetch, so that has become part of my testing routine.&lt;/p&gt;&lt;p&gt;The&lt;a href=&quot;https://github.com/mblaney/holster/releases/tag/1.0.36&quot;&gt; latest release&lt;/a&gt; fixes a lot of bugs, some of which could be considered new features... for instance it was always possible to listen to a path on the graph that doesn&apos;t exist, but the listener now waits for that path to be converted to a node and attaches the original listener to that. That simplifies listeners from an application point of view as they now don&apos;t need to check that condition. There&apos;s also more retrying of underlying wire requests which mean applications don&apos;t need to check if Holster is returning something useful.&lt;/p&gt;&lt;p&gt;Besides that there&apos;s now some nice data consistency fixes as in-memory data was available but not being returned before a lookup was being done, and store was missing some error callbacks that mean better data checking is now happening.&lt;/p&gt;&lt;p&gt;These last few fixes were found by Claude, which I was using for development last year, but as a lot of people are noticing AI is getting better at code changes. It&apos;s only been a few months but it previously wouldn&apos;t find these sorts of problems so it&apos;s definitely getting more useful.&lt;/p&gt;</description></item>
<item><title>Timestamp Signatures</title><link>https://mal.haza.website/2025/11/timestamp-signatures.html</link><guid isPermaLink="true">https://mal.haza.website/2025/11/timestamp-signatures.html</guid><pubDate>Sun, 09 Nov 2025 13:05:21 GMT</pubDate><description>&lt;h1 id=&quot;timestamp-signatures&quot;&gt;Timestamp Signatures&lt;/h1&gt;&lt;p&gt;So long per property signatures, we hardly knew ye! ...I ran the per property signature update on my test server and it blew up my CPU usage with all the extra processing it was doing.&lt;/p&gt;&lt;p&gt;We couldn&apos;t go back to node based signatures because they&apos;re too hard to verify, so this meant coming up with a new solution. I realised we didn&apos;t need to sign content at all, we&apos;re just trying to verify ownership of the update, &lt;em&gt;so signing the timestamp associated with the the update is enough&lt;/em&gt;.&lt;/p&gt;&lt;p&gt;This is great because generally you&apos;re putting a bunch of properties onto a node at once, so they all have the same timestamp, but they only need one signature. If you do update individual properties on a node then it will get it&apos;s own timestamp and an updated signature with it.&lt;/p&gt;&lt;p&gt;This has meant my test server is back to running normally, update is available in &lt;a href=&quot;https://github.com/mblaney/holster/releases/tag/1.0.25&quot;&gt;Holster 1.0.25&lt;/a&gt;.&lt;/p&gt;</description></item>
<item><title>Holster 1.0.24</title><link>https://mal.haza.website/2025/11/holster-1024.html</link><guid isPermaLink="true">https://mal.haza.website/2025/11/holster-1024.html</guid><pubDate>Tue, 04 Nov 2025 10:25:52 GMT</pubDate><description>&lt;h1 id=&quot;holster-1-0-24&quot;&gt;Holster 1.0.24&lt;/h1&gt;&lt;p&gt;I&apos;ve just published &lt;a href=&quot;https://github.com/mblaney/holster/releases/tag/1.0.24&quot;&gt;Holster 1.0.24&lt;/a&gt; which is quite a major change, as signatures are now added per property rather than per node. Before making this change I was trying to debug a signature validation issue on a node, but there was too much data being signed to know what could&apos;ve gone wrong.&lt;/p&gt;&lt;p&gt;This led to simplifying signatures to each property, which actually fixed a whole bunch of other problems in Holster. Signing a node means you always have to fetch the full node to update it even though you might only be updating one property. There were also timing issues around node updates that aren&apos;t a problem any more.&lt;/p&gt;&lt;p&gt;There were a few other recent releases too, including adding Docker support. See the updated README for how to start the server with either Docker or Node.&lt;/p&gt;</description></item>
<item><title>Migrating from Gun to Holster</title><link>https://mal.haza.website/2025/10/migrating-from-gun-to-holster.html</link><guid isPermaLink="true">https://mal.haza.website/2025/10/migrating-from-gun-to-holster.html</guid><pubDate>Mon, 20 Oct 2025 06:20:57 GMT</pubDate><description>&lt;h1 id=&quot;migrating-from-gun-to-holster&quot;&gt;Migrating from Gun to Holster&lt;/h1&gt;&lt;p&gt;The last few weeks have seen a lot of development activity, first in Holster itself then in applications using it. This has been driven by the &lt;a href=&quot;https://github.com/interplaynetary/free-association&quot;&gt;Free Association&lt;/a&gt; project, which is now capable of using Holster instead of Gun. The start of the migration process required some further updates to Holster, which is now at version &lt;a href=&quot;https://github.com/mblaney/holster/releases/tag/1.0.21&quot;&gt;1.0.21&lt;/a&gt; and feeling pretty stable!&lt;/p&gt;&lt;p&gt;The rest of the work was done in switching API&apos;s in the application itself. Holster&apos;s API is not only slightly different to Gun, but also conceptually different. The biggest difference is that Gun streams data whereas Holster resolves all references before returning. This doesn&apos;t mean much from a performance perspective, but it does significantly change your application code. I&apos;ve done a few of these migrations now so I already had the notes from the first one, which I&apos;ve used again successfully since. I&apos;m sharing them here as hopefully they&apos;re general enough for other migrations to benefit from.&lt;/p&gt;&lt;p&gt;On the server I&apos;ve been developing with both Node and Bun, the first step in Node is:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-js hljs&quot;&gt;npm uninstall gun
npm i @mblaney/holster&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;My previous Gun initialization code was:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-js hljs&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;hljs-title class_&quot;&gt;Gun&lt;/span&gt; &lt;span class=&quot;hljs-keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&quot;gun&quot;&lt;/span&gt;
&lt;span class=&quot;hljs-keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&quot;gun/lib/then.js&quot;&lt;/span&gt;
&lt;span class=&quot;hljs-keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&quot;gun/sea.js&quot;&lt;/span&gt;

&lt;span class=&quot;hljs-keyword&quot;&gt;const&lt;/span&gt; gun = &lt;span class=&quot;hljs-title class_&quot;&gt;Gun&lt;/span&gt;({
  &lt;span class=&quot;hljs-attr&quot;&gt;web&lt;/span&gt;: &lt;span class=&quot;hljs-title function_&quot;&gt;express&lt;/span&gt;().&lt;span class=&quot;hljs-title function_&quot;&gt;listen&lt;/span&gt;(&lt;span class=&quot;hljs-number&quot;&gt;8765&lt;/span&gt;),
  &lt;span class=&quot;hljs-attr&quot;&gt;multicast&lt;/span&gt;: &lt;span class=&quot;hljs-literal&quot;&gt;false&lt;/span&gt;,
  &lt;span class=&quot;hljs-attr&quot;&gt;axe&lt;/span&gt;: &lt;span class=&quot;hljs-literal&quot;&gt;false&lt;/span&gt;,
})&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This has been replaced by:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-js hljs&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;hljs-title class_&quot;&gt;Holster&lt;/span&gt; &lt;span class=&quot;hljs-keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&quot;@mblaney/holster/src/holster.js&quot;&lt;/span&gt;

&lt;span class=&quot;hljs-keyword&quot;&gt;const&lt;/span&gt; holster = &lt;span class=&quot;hljs-title class_&quot;&gt;Holster&lt;/span&gt;()&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Holster is a module so the path to the file is required. If you&apos;re not using a build tool you can switch &lt;code&gt;src&lt;/code&gt; to &lt;code&gt;build&lt;/code&gt; in the path above. The only option I left out of both examples above was &lt;code&gt;secure: true&lt;/code&gt;, which means only data under user accounts will be saved (no public data).&lt;/p&gt;&lt;p&gt;Here is the browser code I previously used for Gun (also not including secure here):&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-js hljs&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;hljs-title class_&quot;&gt;Gun&lt;/span&gt; &lt;span class=&quot;hljs-keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&quot;gun&quot;&lt;/span&gt;
&lt;span class=&quot;hljs-built_in&quot;&gt;require&lt;/span&gt;(&lt;span class=&quot;hljs-string&quot;&gt;&quot;gun/lib/radix.js&quot;&lt;/span&gt;)
&lt;span class=&quot;hljs-built_in&quot;&gt;require&lt;/span&gt;(&lt;span class=&quot;hljs-string&quot;&gt;&quot;gun/lib/radisk.js&quot;&lt;/span&gt;)
&lt;span class=&quot;hljs-built_in&quot;&gt;require&lt;/span&gt;(&lt;span class=&quot;hljs-string&quot;&gt;&quot;gun/lib/store.js&quot;&lt;/span&gt;)
&lt;span class=&quot;hljs-built_in&quot;&gt;require&lt;/span&gt;(&lt;span class=&quot;hljs-string&quot;&gt;&quot;gun/lib/rindexed.js&quot;&lt;/span&gt;)
&lt;span class=&quot;hljs-built_in&quot;&gt;require&lt;/span&gt;(&lt;span class=&quot;hljs-string&quot;&gt;&quot;gun/lib/then.js&quot;&lt;/span&gt;)
&lt;span class=&quot;hljs-built_in&quot;&gt;require&lt;/span&gt;(&lt;span class=&quot;hljs-string&quot;&gt;&quot;gun/sea&quot;&lt;/span&gt;)

&lt;span class=&quot;hljs-keyword&quot;&gt;const&lt;/span&gt; gun = &lt;span class=&quot;hljs-title class_&quot;&gt;Gun&lt;/span&gt;({
  &lt;span class=&quot;hljs-comment&quot;&gt;// Assume Gun is directly available on localhost, otherwise reverse proxy.&lt;/span&gt;
  &lt;span class=&quot;hljs-attr&quot;&gt;peers&lt;/span&gt;: [
    &lt;span class=&quot;hljs-variable language_&quot;&gt;window&lt;/span&gt;.&lt;span class=&quot;hljs-property&quot;&gt;location&lt;/span&gt;.&lt;span class=&quot;hljs-property&quot;&gt;hostname&lt;/span&gt; === &lt;span class=&quot;hljs-string&quot;&gt;&quot;localhost&quot;&lt;/span&gt;
      ? &lt;span class=&quot;hljs-string&quot;&gt;&quot;http://localhost:8765/gun&quot;&lt;/span&gt;
      : &lt;span class=&quot;hljs-variable language_&quot;&gt;window&lt;/span&gt;.&lt;span class=&quot;hljs-property&quot;&gt;location&lt;/span&gt;.&lt;span class=&quot;hljs-property&quot;&gt;origin&lt;/span&gt; + &lt;span class=&quot;hljs-string&quot;&gt;&quot;/gun&quot;&lt;/span&gt;,
  ],
  &lt;span class=&quot;hljs-attr&quot;&gt;axe&lt;/span&gt;: &lt;span class=&quot;hljs-literal&quot;&gt;false&lt;/span&gt;,
  &lt;span class=&quot;hljs-attr&quot;&gt;localStorage&lt;/span&gt;: &lt;span class=&quot;hljs-literal&quot;&gt;false&lt;/span&gt;,
  &lt;span class=&quot;hljs-attr&quot;&gt;store&lt;/span&gt;: &lt;span class=&quot;hljs-variable language_&quot;&gt;window&lt;/span&gt;.&lt;span class=&quot;hljs-title class_&quot;&gt;RindexedDB&lt;/span&gt;(),
})&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;And here is the browser code for Holster. Note that it doesn&apos;t rewrite the scheme for WebSockets like Gun, so you need to specify it or use the default:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-js hljs&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;hljs-title class_&quot;&gt;Holster&lt;/span&gt; &lt;span class=&quot;hljs-keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&quot;@mblaney/holster/src/holster.js&quot;&lt;/span&gt;

&lt;span class=&quot;hljs-comment&quot;&gt;// If on localhost assume Holster is directly available and use the default&lt;/span&gt;
&lt;span class=&quot;hljs-comment&quot;&gt;// settings, otherwise assume a secure connection is required.&lt;/span&gt;
&lt;span class=&quot;hljs-keyword&quot;&gt;let&lt;/span&gt; peers
&lt;span class=&quot;hljs-keyword&quot;&gt;if&lt;/span&gt; (&lt;span class=&quot;hljs-variable language_&quot;&gt;window&lt;/span&gt;.&lt;span class=&quot;hljs-property&quot;&gt;location&lt;/span&gt;.&lt;span class=&quot;hljs-property&quot;&gt;hostname&lt;/span&gt; !== &lt;span class=&quot;hljs-string&quot;&gt;&quot;localhost&quot;&lt;/span&gt;) {
  peers = [&lt;span class=&quot;hljs-string&quot;&gt;&quot;wss://&quot;&lt;/span&gt; + &lt;span class=&quot;hljs-variable language_&quot;&gt;window&lt;/span&gt;.&lt;span class=&quot;hljs-property&quot;&gt;location&lt;/span&gt;.&lt;span class=&quot;hljs-property&quot;&gt;hostname&lt;/span&gt;]
}

&lt;span class=&quot;hljs-keyword&quot;&gt;const&lt;/span&gt; holster = &lt;span class=&quot;hljs-title class_&quot;&gt;Holster&lt;/span&gt;({&lt;span class=&quot;hljs-attr&quot;&gt;peers&lt;/span&gt;: peers, &lt;span class=&quot;hljs-attr&quot;&gt;indexedDB&lt;/span&gt;: &lt;span class=&quot;hljs-literal&quot;&gt;true&lt;/span&gt;})&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The rest of these notes are for the API&apos;s in general, so apply to both browser and server.&amp;nbsp; First is error handling, where Gun returns an object with an &lt;code&gt;err&lt;/code&gt; property Holster returns just an error. This means a lot of code changes from &lt;code&gt;ack.err&lt;/code&gt; to just &lt;code&gt;err&lt;/code&gt;.&lt;/p&gt;&lt;p&gt;The &lt;code&gt;SEA&lt;/code&gt; functions work pretty much the same, except Gun uses encryption keys under &lt;code&gt;user._.sea&lt;/code&gt;, Holster simplifies this to rely on the &lt;code&gt;user.is&lt;/code&gt; object. They can also be used on their own with &lt;code&gt;import SEA from &quot;@mblaney/holster/src/sea.js&quot;&lt;/code&gt;&lt;/p&gt;&lt;p&gt;I used &lt;code&gt;user.get(key).once(callback)&lt;/code&gt; a lot when working with Gun. In Holster, that pattern is now just &lt;code&gt;user.get(key, callback)&lt;/code&gt;. I would also previously use &lt;code&gt;user.get(key)&lt;/code&gt;, and pass that around as a reference to use later. Holster removes the context that gets created once a callback is provided, so that isn&apos;t possible and you instead need to write the full query each time.&lt;/p&gt;&lt;p&gt;As mentioned above, the big difference is between streaming data and resolving nodes. So when using Gun you could do: &lt;code&gt;get(&quot;items&quot;).map().once((item, key) =&amp;gt; callback)&lt;/code&gt; to get a stream of updates. In Holster it&apos;s just &lt;code&gt;get(&quot;items&quot;, items =&amp;gt; callback)&lt;/code&gt;. So to get the equivalent of Gun&apos;s &lt;code&gt;map()&lt;/code&gt; you would do: &lt;code&gt;Object.entries(items).forEach(([key, item]) =&amp;gt; {callback(item, key)})&lt;/code&gt; on the returned data if you wanted to use the same callback.&lt;/p&gt;&lt;p&gt;Also &lt;code&gt;get(key).map().once(callback)&lt;/code&gt; subscribes to updates in Gun, you would need to use &lt;code&gt;get(key).on(callback)&lt;/code&gt; instead in Holster for updates.&lt;/p&gt;&lt;p&gt;Gun has &lt;code&gt;get(key).then()&lt;/code&gt; which is nice for &lt;code&gt;await&lt;/code&gt;ing data, but it&apos;s just a convenience function which can be done in Holster with:&amp;nbsp; &lt;code&gt;await new Promise(res =&amp;gt; {get(key, res)})&lt;/code&gt;&lt;/p&gt;&lt;p&gt;The biggest change in terms of lines of code has generally been modifying &lt;code&gt;get().get()&lt;/code&gt; chains from Gun, to &lt;code&gt;get().next()&lt;/code&gt; in Holster. One more thing to watch out for is calling &lt;code&gt;put()&lt;/code&gt; in a loop, it&apos;s better to use an actual &lt;code&gt;for&lt;/code&gt; loop rather than &lt;code&gt;forEach&lt;/code&gt; and &lt;code&gt;await&lt;/code&gt;ing each &lt;code&gt;put()&lt;/code&gt; so they don&apos;t make updates at the same time, especially to the same node.&lt;/p&gt;</description></item>
<item><title>Holster 1.0.19</title><link>https://mal.haza.website/2025/10/holster-1019.html</link><guid isPermaLink="true">https://mal.haza.website/2025/10/holster-1019.html</guid><pubDate>Wed, 08 Oct 2025 13:52:33 GMT</pubDate><description>&lt;h1 id=&quot;holster-1-0-19&quot;&gt;Holster 1.0.19&lt;/h1&gt;&lt;p&gt;This is another &lt;a href=&quot;https://github.com/mblaney/holster/releases/tag/1.0.19&quot;&gt;bug fix and performance update&lt;/a&gt;, message queuing felt a little slow so I&apos;ve bumped up queue processing from 4 messages per second to 10, we will see how that goes.&lt;/p&gt;&lt;p&gt;The other fixes are more data sync related than timing, the first is that listeners can subscribe to nodes, but the underlying data can change from a property to a reference. A bit of an edge case but this led to listeners not resolving full objects, which is now fixed.&lt;/p&gt;&lt;p&gt;Next was that updates that didn&apos;t change the in-memory graph weren&apos;t going out on the wire, so other devices on the network weren&apos;t alerted. There&apos;s also some better checks for when a websocket isn&apos;t available messages won&apos;t be dequeued until they are actually sent.&lt;/p&gt;</description></item>
<item><title>Post-performance improvement updates</title><link>https://mal.haza.website/2025/10/post-performance-improvement-updates.html</link><guid isPermaLink="true">https://mal.haza.website/2025/10/post-performance-improvement-updates.html</guid><pubDate>Sun, 05 Oct 2025 06:08:59 GMT</pubDate><description>&lt;h1 id=&quot;post-performance-improvement-updates&quot;&gt;Post-performance improvement updates&lt;/h1&gt;&lt;p&gt;The last update was all about improvements that made Holster more efficient on the server. I&apos;m glad to say those changes, plus a couple of recent updates, mean server processing is looking really good now. There are no more CPU spikes for the same processing load, which means Holster is happy running on small severs when given a reasonable amount of work to do.&lt;/p&gt;&lt;p&gt;The big change that allows this to work efficiently is that Holster now stores all radix trees in memory. This allows for much faster gets and puts less pressure on the radisk implementation by no longer requiring parsing from disk as often. This does mean you need to configure your application to provide Holster with enough memory. There is a new &lt;code&gt;memoryLimit&lt;/code&gt; option which has a default of 500MB, which is fine for small amounts of data. If you plan to store more data you will need to increase this, but Holster also logs how much memory it&apos;s currently using so you will know when it&apos;s an issue. When it hits the limit it resets the cache and goes back to files on disk, so it does continue working but this process will probably repeat itself.&lt;/p&gt;&lt;p&gt;The other change required to keep servers running well was that clients needed to throttle their message sending. This also helps browsers not spike a user&apos;s CPU and memory because it&apos;s making requests that the server couldn&apos;t keep up with anyway. This led to some changes that resulted in orderly message queuing and processing, but introduced some timing issues now that have now been fixed in &lt;a href=&quot;https://github.com/mblaney/holster/releases/tag/1.0.17&quot;&gt;1.0.17&lt;/a&gt;.&lt;/p&gt;&lt;p&gt;To check for these timing issues there&apos;s now a whole set of new system tests. I&apos;ve continued using Claude to write these tests and help with debugging the timing issues which is working really well.&lt;/p&gt;</description></item>
<item><title>Performance Improvements</title><link>https://mal.haza.website/2025/09/performance-improvements.html</link><guid isPermaLink="true">https://mal.haza.website/2025/09/performance-improvements.html</guid><pubDate>Wed, 10 Sep 2025 09:51:43 GMT</pubDate><description>&lt;h1 id=&quot;performance-improvements&quot;&gt;Performance Improvements&lt;/h1&gt;&lt;p&gt;My project to test Holster started hitting 100% CPU the other day during peak processing times, so have been doing some investigation. It&apos;s not a big server, but Holster shouldn&apos;t need the resources it was using and this was a great opportunity to identify what needed improving.&lt;/p&gt;&lt;p&gt;This has led to a few performance improvement releases this week. The first was that the caching radisk was doing was only for the most recent file. This doesn&apos;t do very much when keys come in randomly for different files, and each key caches a new file. Holster is currently not designed to hold heaps of data, so it now just caches all files, which was a big improvement as reading from disk is expensive.&lt;/p&gt;&lt;p&gt;Next was improving the client throttling to use an actual processing queue. It previously tried to throttle with timeouts but this just caused messages to build up later. The advantage of a processing queue is that you can also return an error if the queue gets too long, because there is probably something wrong with your Holster queries if this happens (ie requesting too much data.)&lt;/p&gt;&lt;p&gt;Lastly the &lt;a href=&quot;https://holster.haza.website&quot;&gt;Holster website&lt;/a&gt; got an update! It now has a nice little visualization of connected clients.&lt;/p&gt;&lt;p&gt;&lt;img src=&quot;https://mal.haza.website/_media/mal/media/screenshot-from-2025-09-10-19-04-32.png&quot; alt=&quot;&quot; data-media-id=&quot;d85ec75f-2b7b-41b6-b771-57ae0064eccf&quot;&gt;&lt;/p&gt;</description></item>
</channel>
</rss>
