{
    "componentChunkName": "component---src-templates-post-template-js",
    "path": "/en/interactive_rubber_ducking_with_gen_ai/",
    "result": {"data":{"post":{"id":"2d762ec8-06a0-550f-aecd-49179370afed","html":"<p><span\n      class=\"gatsby-resp-image-wrapper\"\n      style=\"position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto\"\n    >\n      <a\n    class=\"gatsby-resp-image-link\"\n    href=\"/static/a08c97f5281d31661ba3946fbc3b1ec0/a331c/2026-03-16-cover.png\"\n    style=\"display: block\"\n    target=\"_blank\"\n    rel=\"noopener\"\n  >\n    <span\n    class=\"gatsby-resp-image-background-image\"\n    style=\"padding-bottom: 62.5%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAANCAYAAACpUE5eAAAACXBIWXMAAC4jAAAuIwF4pT92AAADsUlEQVQ4yxXPjTvUBwDA8V+FUXucl7yelFmu4ygvxcmMc5zXw90KP1xejuQl0U15CV1Uzz29iLNhWiF7ppcn1TzLabLn2fbsCSWzhdmTZ+tpf8Z3zz7/wUcoqyrl81YTtrnnvFpZ5v2/73j2dILZ6fu8fbvGxOQQU9/dYurBCKViDoci5XzZe5GFn6d5Pn0P25Nxnj4aZdDaTV29EeGy1crgxAPMN3rpvHadjMJCYjVq2jqbmZ37nvOXGzGW6zhbmU/OYQVe7o6EKgKoqjpGZlYyp0+V83hiENGQx5nmBoSRu9/y5u93vN74i8XVNQ6mprLNTYIyMY5nP9qwXGrhM20qb2z3udskUpMcSlGsjAORckJCP0KXnchwXxdGo4hGq0EYHLnF/OoaPy0tMTe/QHy2Fn9FEI7uzuiLjnL8ZCVRnyjpMVXwwFRAvxiLJU9J+kEZIWEh5OrU3B6w0N3ZSM91M8L43W9Y/ec9L9c3efHnJod0R9ixJwAHVwmpWWoKS7XIo8LRxEfzqLmMsxlh1KlkxPt7kLn3Y0pUhxm40knbORO3h60I4TExnDA1MvbkK7o6WqkWDcRpczhSkE9tfS2FYhaGIh3lhlzq9GryokM54O2Gyt+fdrWG8ogIdDGRVB83IB4TEbY5OeIf4M3XQ2WkREmZs17AfKGZ1qYaRq1mzlTpuFCbjdXSRIfRQHtyBvVJGlqSUmn8NAl9cDAxPp74eLghONgjSDxc2eLgQH1dBQ2mk4z1t2MbMdNcnceVpgK6TMVYTuv4dbiJYmUEx6KV3CytpFurJztYgVYmI0bqhdTdha3OHyLYuXohODkjFqcxM2Skv6GEYn0SinA5CvkujKpwjkQG0pFzkGSFNyn7ZbSoM6mPSyA9SEb+/v3E7vJF6iZB2O6IEBLkh4/fbvraEtj8wokbR4NIycxEn6ejp72amf7z9BrSuFqoYrgiGVXYHsqUsWQHyQlxdackIppEuRxfz//LdggWQyizvQlsPNSwarVn5lIEy4vzLC8tsrz0isX5F/wy9ZA7F5spiNtHtGI3lpJURhtE+mtELGIuaUGBeLjsQNi6BWHwlJqVkSjWR6X80G7HgMEJ272brG9ssPLbMr+v/MHCy9cszM/TbW7F19eF8D3u1KYfovdENn2VaTRowgj228kH250QbNYqHneqGKt055peQnuaJ5PX65gc62e89xzjVxq4011OcW4CKYnRBPpJ2O3piLfEHk9ne/ZJJSj3ehEg9cBt507+A8OtM7aUdbu2AAAAAElFTkSuQmCC'); background-size: cover; display: block;\"\n  ></span>\n  <img\n        class=\"gatsby-resp-image-image\"\n        alt=\"cover\"\n        title=\"cover\"\n        src=\"/static/a08c97f5281d31661ba3946fbc3b1ec0/a331c/2026-03-16-cover.png\"\n        srcset=\"/static/a08c97f5281d31661ba3946fbc3b1ec0/36ca5/2026-03-16-cover.png 200w,\n/static/a08c97f5281d31661ba3946fbc3b1ec0/a3397/2026-03-16-cover.png 400w,\n/static/a08c97f5281d31661ba3946fbc3b1ec0/a331c/2026-03-16-cover.png 800w\"\n        sizes=\"(max-width: 800px) 100vw, 800px\"\n        style=\"width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;\"\n        loading=\"lazy\"\n        decoding=\"async\"\n      />\n  </a>\n    </span></p>\n<p>You may already know that <a href=\"/en/the_end_of_coding_wrong_question/\">I’m a GenAI sceptic</a>. And a general sceptic.</p>\n<p>Do you know that scepticism comes from the Greek <em>σκέπτομαι</em> (<em>skeptomai</em>), meaning ‘to search, to think about, or look for’? So my intention is not to say no to everything new, but more to think about it first, and understand before I say yes.</p>\n<p>There’s a lot of stuff about GenAI that makes me smile, but I still understand that my way is my way, and I won’t stop the world. I won’t even try. Thus, I want to research and consider how those tools can help me. I already wrote that I don’t feel like <a href=\"https://www.architecture-weekly.com/p/requiem-for-a-10x-engineer-dream\">10x Dev</a>, but I’m finding more ways to get help from it.</p>\n<p><strong>One of the ways that helped me is something I call <em>“Interactive Rubber-Ducking”</em>.</strong></p>\n<p>Initially, I called it just <a href=\"https://www.architecture-weekly.com/p/start-alone-then-together-why-software\">brainstorming</a>, but that wouldn’t be precise, as I’m not using it to brainstorm ideas, more to challenge and clarify them.</p>\n<p>Most of the code I write nowadays is done in <a href=\"https://github.com/oskardudycz/\">my OSS projects</a>. I’m grateful to have a <a href=\"https://discord.gg/fTpqUTMmVa\">great community</a> with people actively contributing in different ways; still, the canonical design and code work is on my side. As I work in an event-driven niche, I’m often alone with my own thoughts. I try to use the <a href=\"https://www.architecture-weekly.com/p/workflow-engine-design-proposal-tell\">RFC process</a> and discuss it with other fellow humans, but they are not always available. Even if they do, to avoid wasting their time, I need to know what to tell or ask them. I need to give some proposals (with alternatives) to have an <a href=\"/en/fifteen_tips_on_how_to_run_meetings_effectively/\">effective discussion</a>. I may seem organised, but that’s not always the thing. Sitting in your own head is not a great place to be in general. If you’re a technical leader or an architect, I’m sure that you know that solitude too well.</p>\n<p>GenAI tools are not great sparing partners. They’re <em>Yes men</em>. If they read this article, they’d for sure confirm it. They’d probably do it even without reading it. Of course, you can ask them not to be <a href=\"https://www.merriam-webster.com/dictionary/sycophant\">sycophant</a>. You can ask numerous MUSTS with capital letters and bolded <strong>NEVER</strong> here and there, and it can help, but it won’t fully beat the way they were trained.</p>\n<p>And talking to yourself is a similar experience: you’ll find numerous ways to justify your own decisions, and looking at the same place for too long will make you miss obvious blind spots.</p>\n<p>Ok, so why would we take those two blind “people” and try to make them help each other?</p>\n<p><strong>That’s kinda what “Interactive Rubber-Ducking” is.</strong> It takes a blind human with an idea, and another blind not-so-human asking questions. It starts with such a prompt:</p>\n<blockquote>\n<p>Ask me one question at a time so we can develop a thorough, step-by-step spec for this idea. Each question should build on my previous answers, and our end goal is to have a detailed specification I can hand off to a developer. Let’s do this iteratively and dig into every relevant detail. Remember, only one question at a time.</p>\n<p>Once we are done, save the spec as spec.md</p>\n<p>Before asking another question, store the previous one with the answer in qa.md. Write literally the question and answer, not just a summary.</p>\n<p>Here’s the idea:</p>\n</blockquote>\n<p>Don’t treat it as <em>“one magical prompt that will change your life”</em>. Most important is why we’re doing it, what happens next, and who’s actually doing the work. Spoiler alert: it’s not LLM.</p>\n<p>I’m using it as a command in Claude Code, and, most importantly, with beefier models like Opus, which can better reason and ask better questions. Doing it with lower-level models always gave me much worse results.</p>\n<p>I’m using it with Claude Code, not Claude Chat, because I want the model to scan my codebase. I can ask it to look in certain areas or to reference my answers. I can even ask to search the web or MCPs like <a href=\"https://context7.com/\">Context 7</a> to check documentation and APIs for popular libraries. Then it’s getting more into brainstorming sometimes, than rubber-ducking, but that’s fine.</p>\n<p>As a result, we’ll get two artefacts:</p>\n<ul>\n<li><strong>qa.md</strong> - with the log of the back-and-forth discussion,</li>\n<li><strong>spec.md</strong> - in theory spec built by LLM, but imho it’s more of a concise summary.</li>\n</ul>\n<p>It may look like Specification-Driven Design, but it’s not.</p>\n<p>My goal for this exercise is not to get an actionable specification.</p>\n<p><strong>The goal is to get our LLM-based Rubber Duck to ask us hard questions and make us think, not to make the LLM think for us. Find blind spots, and challenge our thinking.</strong></p>\n<p>But we’re drivers, we need to know what we want to do, we need to know all the WHYs, and we also need to know HOW. LLM is here to help, but not to do creative work for us. It just pulls it out from our heads.</p>\n<p>It also helps to see how our design may be seen by others, especially such mediocre thinkers as LLMs.</p>\n<p>I don’t expect the Agent to be able to start implementing the spec. I expect it to reflect all considerations and summarise findings. I’m always double-checking to make sure it includes all the important points. If not, I’ll keep doing Q&#x26;A until I’m satisfied.</p>\n<p>Having both of those files will allow us to keep a full discussion without losing important details, and a shorter version. We can feed that to another model for review, or try to work on tasks and develop a more detailed plan. Sometimes I plan on my own; for simpler tasks, I may ask the LLM to do it fully. Usually, I’m driving the LLM step by step, passing just specific asks.</p>\n<p>The example? Why not.</p>\n<p><strong>I recently did such an exercise, trying to narrow down how to introduce the <em>Second-level cache</em> to <a href=\"https://github.com/event-driven-io/pongo\">Pongo</a> and <a href=\"https://github.com/event-driven-io/emmett\">Emmett</a>.</strong> What’s <em>Second-level cache</em>? A Second-level cache is a local store of data managed by the persistence provider to improve application performance.</p>\n<p><strong>Why do I want to introduce it?</strong> Because I got an <a href=\"https://github.com/event-driven-io/emmett/issues/322\">issue from the user</a> that <a href=\"/en/rebuilding_event_driven_read_models/\">rebuilding projections</a> with a lot events can take too long. One of the reasons is that applying an event on a projection takes:</p>\n<ul>\n<li>loading the current state,</li>\n<li>updating it,</li>\n<li>storing the result.</li>\n</ul>\n<p>Those are two operations per event. If we have a batch of 100 events, that’d mean 200 operations; for 1000 events, this would be 2000, so the classical N+1 problem. We could do it differently, and within a batch:</p>\n<ul>\n<li>group events by the target documents,</li>\n<li>load all of them in one operation by finding all documents within an array of ids (taken from events),</li>\n<li>caching them,</li>\n<li>applying events in memory,</li>\n<li>storing updated documents in one operation.</li>\n</ul>\n<p>Then we’ll get at worst 102 operations for 100 events and 1002 for 1000 events, so a linear increase.</p>\n<p>Still, I decided to add batching and introduce 2nd-level cache as a solution. I and my experience were a driving force behind that. I could,  of course, rubber duck it, but I had that already planned, not just detailed. I also did some initial research before doing the session.</p>\n<p>Let me show you the result below. Not too bad if you ask me. Of course, the task wasn’t very innovative; it’s a standard way for mature solutions. Still questions were surprisingly good and helped me.</p>\n<p>I hope this log will show you (besides how bad I’m at typos) some insight into how I think, talk, and consider the design.</p>\n<p>Tell me your thoughts, if that matches your way, if you haven’t done it yet, try it and tell me how it went!</p>\n<hr>\n<h2 id=\"interactive-rubber-ducking-example\" style=\"position:relative;\"><a href=\"#interactive-rubber-ducking-example\" aria-label=\"interactive rubber ducking example permalink\" class=\"anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>Interactive Rubber Ducking example</h2>\n<h3 id=\"my-idea\" style=\"position:relative;\"><a href=\"#my-idea\" aria-label=\"my idea permalink\" class=\"anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>My idea</h3>\n<p>I’d like to provide 2nd-level cache to Pongo. I want to either use keyv (<a href=\"https://www.npmjs.com/package/keyv\">https://www.npmjs.com/package/keyv</a>) or lru-cache (<a href=\"https://www.npmjs.com/package/lru-cache\">https://www.npmjs.com/package/lru-cache</a>) packages.</p>\n<p>For now, I’d like to do it by id.</p>\n<p>I probably need to either extend pongoCollection src/packages/pongo/src/core/collection/pongoCollection.ts with findeOneById and findManyByIds methods, or check if _id is passed in and then try to access the cache.</p>\n<p>Cache should be set up either on the pongo collection, pongo db or pongoClient.</p>\n<p>Optimistic Concurrency errors should invalidate cache (as that means someone else updated the record in db, e.g. through a different node).</p>\n<p>InsertMany and insertOne should put it into the cache.</p>\n<p>I’d also like to expand the handle method to allow passing either an id or an array of IDs to support batch loading of entities and improve performance, and to support batch update/insert/delete.</p>\n<p>Thoughts?</p>\n<p>Which defaults should I set? (Of course, allowing users to override it).</p>\n<p>Check the related code I pasted. I want to keep it aligned and as non-invasive as possible.</p>\n<h3 id=\"q1-cache-library-choice--lru-cache-keyv-or-custom-interface\" style=\"position:relative;\"><a href=\"#q1-cache-library-choice--lru-cache-keyv-or-custom-interface\" aria-label=\"q1 cache library choice  lru cache keyv or custom interface permalink\" class=\"anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>Q1: Cache library choice — lru-cache, keyv, or custom interface?</h3>\n<p><strong>Question:</strong> You mentioned both <code class=\"language-text\">keyv</code> and <code class=\"language-text\">lru-cache</code>. They pull in different directions. Which trade-off do you prefer?</p>\n<ul>\n<li>A) Start with <code class=\"language-text\">lru-cache</code>, optionally let users swap adapters later.</li>\n<li>B) Start with <code class=\"language-text\">keyv</code> so cross-node sharing works out of the box.</li>\n<li>C) Define a thin cache interface (<code class=\"language-text\">get</code>/<code class=\"language-text\">set</code>/<code class=\"language-text\">delete</code>/<code class=\"language-text\">clear</code>) and ship <code class=\"language-text\">lru-cache</code> as the default adapter.</li>\n</ul>\n<p><strong>Answer:</strong> Custom cache interface (option C). No dependency on external APIs — keep it pluggable. Start with an in-memory provider only. Considered writing a custom LRU with me, but wants to ship fast. Key point: don’t stick to any third-party API surface, allow pluggability.</p>\n<h3 id=\"q2-where-should-the-cache-live-in-pongos-hierarchy\" style=\"position:relative;\"><a href=\"#q2-where-should-the-cache-live-in-pongos-hierarchy\" aria-label=\"q2 where should the cache live in pongos hierarchy permalink\" class=\"anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>Q2: Where should the cache live in Pongo’s hierarchy?</h3>\n<p><strong>Question:</strong> Three attachment points: A) PongoClient level (shared across all), B) PongoDb level (per database), C) PongoCollection level (per collection). Or configurable at multiple levels?</p>\n<p><strong>Answer:</strong> Allow defining it at different levels — cascading configuration. Set a default higher up, override per collection if needed.</p>\n<h3 id=\"q3-inheritanceoverride-behavior-for-cascading-cache-config\" style=\"position:relative;\"><a href=\"#q3-inheritanceoverride-behavior-for-cascading-cache-config\" aria-label=\"q3 inheritanceoverride behavior for cascading cache config permalink\" class=\"anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>Q3: Inheritance/override behavior for cascading cache config?</h3>\n<p><strong>Question:</strong> For the cascading config — what should the inheritance/override behavior be?</p>\n<p>Two sub-questions:</p>\n<ol>\n<li>Should a lower level be able to <em>disable</em> caching even if a parent enables it?</li>\n<li>Should cache <em>settings</em> (like max size, TTL) also cascade, or only the cache provider instance itself?</li>\n</ol>\n<p><strong>Answer:</strong></p>\n<ol>\n<li>Yes. Cache should be an object with type, general params and specific for this type, or <code class=\"language-text\">'disabled'</code>. That’s a proper devex.</li>\n<li>Yes, as above. But if one selects a different cache type then general params should be inherited but type-specific should not be inherited and should fallback to defaults.</li>\n</ol>\n<p><strong>Claude’s thoughts:</strong> The <code class=\"language-text\">CacheConfig | 'disabled'</code> pattern is clean — discriminated unions play nicely with TypeScript. The inheritance rule makes sense: general params (TTL, max size) cascade, but type-specific params reset to defaults when you switch providers, avoiding leaking nonsensical config (e.g., a Redis connection string bleeding into an in-memory cache). Also worth distinguishing between “I didn’t specify anything” (inherit from parent via <code class=\"language-text\">undefined</code>) and “I explicitly want defaults” (reset) — <code class=\"language-text\">undefined</code> vs <code class=\"language-text\">'disabled'</code> vs <code class=\"language-text\">CacheConfig</code> gives a clean three-state: inherit / off / explicit.</p>\n<h3 id=\"q4-cache-interface-shape--async-batch-ops-scoping\" style=\"position:relative;\"><a href=\"#q4-cache-interface-shape--async-batch-ops-scoping\" aria-label=\"q4 cache interface shape  async batch ops scoping permalink\" class=\"anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>Q4: Cache interface shape — async, batch ops, scoping?</h3>\n<p><strong>Question:</strong> Settling the cache interface details:</p>\n<ol>\n<li>Async or sync? Making the interface <code class=\"language-text\">Promise</code>-based from the start keeps it pluggable. Sound right?</li>\n<li>Batch methods (<code class=\"language-text\">getMany</code>, <code class=\"language-text\">setMany</code>, <code class=\"language-text\">deleteMany</code>) — should the cache interface support batch ops natively, or loop internally?</li>\n<li>Should <code class=\"language-text\">clear()</code> be scoped per collection, or always clear the entire cache?</li>\n</ol>\n<p><strong>Answer:</strong></p>\n<ol>\n<li>Maybe we could return either promise or no promise, then if we await it, it would support both? Also ensure that settings are close to what lru-cache and keyv are having.</li>\n<li>Having them (even as syntactic sugar) would be a good devex.</li>\n<li>We could pass down not only settings, but also the cache provider instance, then if settings are the same we can reuse cache without recreating it. If set had ttl provided, then we could do it by overriding those TTLs on item level. ClearAll probably would be something we should expose only for advanced, typically user should not be aware of cache.</li>\n</ol>\n<p><strong>Claude’s thoughts:</strong> <code class=\"language-text\">MaybePromise&lt;T></code> (i.e. <code class=\"language-text\">T | PromiseLike&lt;T></code>) works — <code class=\"language-text\">await</code> on a non-Promise just resolves immediately. No overhead for sync providers, no breaking change for async ones. Passing a cache instance is smart: config becomes either settings (Pongo creates the cache) or a pre-built instance (user brings their own). Multiple collections can explicitly share one cache instance. <code class=\"language-text\">clear()</code> should be internal/advanced — scope by collection prefix internally so a collection-level clear doesn’t nuke another collection’s entries when sharing an instance. For aligning with lru-cache/keyv settings: both support <code class=\"language-text\">max</code> (max entries) and <code class=\"language-text\">ttl</code> (TTL in ms). Start with <code class=\"language-text\">max</code> and <code class=\"language-text\">ttl</code> as the general params.</p>\n<h3 id=\"q5-cache-key-strategy\" style=\"position:relative;\"><a href=\"#q5-cache-key-strategy\" aria-label=\"q5 cache key strategy permalink\" class=\"anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>Q5: Cache key strategy</h3>\n<p><strong>Question:</strong> Since cache instances might be shared across collections, how to avoid key collisions?</p>\n<ul>\n<li>A) Pongo manages key prefixing internally (<code class=\"language-text\">{dbName}:{collectionName}:{id}</code>). The cache interface works with plain string keys, namespacing is Pongo’s concern.</li>\n<li>B) Key is just the document <code class=\"language-text\">_id</code>. Simpler but only safe with per-collection instances.</li>\n</ul>\n<p><strong>Answer:</strong> A, definitely.</p>\n<h3 id=\"q6-cache-invalidation-and-consistency-with-optimistic-concurrency\" style=\"position:relative;\"><a href=\"#q6-cache-invalidation-and-consistency-with-optimistic-concurrency\" aria-label=\"q6 cache invalidation and consistency with optimistic concurrency permalink\" class=\"anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>Q6: Cache invalidation and consistency with optimistic concurrency</h3>\n<p><strong>Question:</strong> How should cache interact with different operations?</p>\n<ol>\n<li>Successful writes → update/evict cache. Clear.</li>\n<li>Concurrency errors → evict stale entry. Clear.</li>\n<li>Reads with <code class=\"language-text\">_id</code> filter → cache hit or miss-then-populate. Clear.</li>\n<li><code class=\"language-text\">handle</code> method — should the read inside <code class=\"language-text\">handle</code> use the cache (risking more concurrency errors on stale data) or always go to DB?</li>\n</ol>\n<p><strong>Answer:</strong> Definitely use cache. We could consider disabling cache through handle options.</p>\n<p><strong>Claude’s thoughts:</strong> Use cache by default — optimistic concurrency is the safety net. If cached version is stale, write fails, we evict, caller can retry. Adding <code class=\"language-text\">skipCache?: boolean</code> to <code class=\"language-text\">HandleOptions</code> is a clean escape hatch for high-contention cases.</p>\n<h3 id=\"q7-should-skipcache-be-available-on-read-methods-too\" style=\"position:relative;\"><a href=\"#q7-should-skipcache-be-available-on-read-methods-too\" aria-label=\"q7 should skipcache be available on read methods too permalink\" class=\"anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>Q7: Should <code class=\"language-text\">skipCache</code> be available on read methods too?</h3>\n<p><strong>Question:</strong> Should <code class=\"language-text\">skipCache</code> also be available on <code class=\"language-text\">findOne</code> and other read methods for symmetry, or keep it <code class=\"language-text\">handle</code>-only?</p>\n<p><strong>Answer:</strong> Good point, having <code class=\"language-text\">skipCache</code> on find methods (also <code class=\"language-text\">findMany</code> etc.) would be good.</p>\n<p><strong>Claude’s thoughts:</strong> Consistent API surface. Add <code class=\"language-text\">skipCache?: boolean</code> on the shared options type (e.g. <code class=\"language-text\">CollectionOperationOptions</code>) so it’s available everywhere without repeating yourself.</p>\n<h3 id=\"q8-batch-operations-on-handle--signature-and-handler-shape\" style=\"position:relative;\"><a href=\"#q8-batch-operations-on-handle--signature-and-handler-shape\" aria-label=\"q8 batch operations on handle  signature and handler shape permalink\" class=\"anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>Q8: Batch operations on <code class=\"language-text\">handle</code> — signature and handler shape</h3>\n<p><strong>Question:</strong> You want <code class=\"language-text\">handle</code> to accept an id or array of ids. Two design choices:</p>\n<ol>\n<li>Overloaded signature (<code class=\"language-text\">handle(id: string | string[], ...)</code>) vs separate method (<code class=\"language-text\">handleMany</code>)?</li>\n<li>Handler shape for batch: A) still receives/returns one document at a time (Pongo batches DB ops internally) or B) receives/returns full array?</li>\n</ol>\n<p>Also noted: since PongoDocuments always have <code class=\"language-text\">_version</code>, if someone provides <code class=\"language-text\">expectedVersion</code> we could compare it against the cached version and decide if it’s stale without hitting the DB.</p>\n<p><strong>Answer:</strong> Option A for handler shape. <code class=\"language-text\">handleMany</code> would be consistent with other APIs, but single id or array (<code class=\"language-text\">string | string[]</code>) is clean enough.</p>\n<p><strong>Claude’s thoughts:</strong> Version comparison in cache is a cheap short-circuit — if cached <code class=\"language-text\">_version</code> doesn’t match <code class=\"language-text\">expectedVersion</code>, we know it’s stale without a round trip. On signature: TypeScript overloads can narrow the return type (<code class=\"language-text\">string</code> → single result, <code class=\"language-text\">string[]</code> → array). <code class=\"language-text\">handle</code> is a higher-level orchestration method, not direct CRUD, so a union input feels natural. Go with <code class=\"language-text\">handle(id: string | string[], handler, options?)</code>.</p>\n<h3 id=\"q9-defaults--max-entries-ttl-enabled-by-default\" style=\"position:relative;\"><a href=\"#q9-defaults--max-entries-ttl-enabled-by-default\" aria-label=\"q9 defaults  max entries ttl enabled by default permalink\" class=\"anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>Q9: Defaults — max entries, TTL, enabled by default?</h3>\n<p><strong>Question:</strong> What defaults to set? Proposed: <code class=\"language-text\">max: 1000</code>, <code class=\"language-text\">ttl: 60_000</code> (1 min), and should cache be enabled by default or opt-in?</p>\n<p><strong>Answer:</strong> Stick to recommended lru-cache defaults for now. Enable cache by default.</p>\n<p><strong>Claude’s thoughts:</strong> Enabling by default works because: (1) optimistic concurrency is always the safety net, (2) it’s document-by-id only so invalidation is straightforward, (3) users can set <code class=\"language-text\">'disabled'</code>. <code class=\"language-text\">lru-cache</code> requires <code class=\"language-text\">max</code> to be set (no default) and has no default TTL. Suggested: <code class=\"language-text\">max: 1000</code>, no TTL (rely on LRU eviction + invalidation on writes/concurrency errors) — most lru-cache-aligned.</p>\n<h3 id=\"q10-ttl-or-no-ttl-as-staleness-backstop\" style=\"position:relative;\"><a href=\"#q10-ttl-or-no-ttl-as-staleness-backstop\" aria-label=\"q10 ttl or no ttl as staleness backstop permalink\" class=\"anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>Q10: TTL or no TTL as staleness backstop?</h3>\n<p><strong>Question:</strong> No TTL means cached entries live until LRU-evicted or invalidated by a Pongo write. If something updates the DB outside this Pongo instance (another service, direct SQL), the cache won’t know. Options:</p>\n<ul>\n<li>A) No TTL, accept the trade-off — OC catches it on next write.</li>\n<li>B) Set a conservative default TTL (e.g., 5 minutes) as a staleness bound.</li>\n</ul>\n<p><strong>Answer:</strong> Fine with using TTL and sometimes hitting OC issue. OC assumes rare conflicts.</p>\n<h3 id=\"q11-where-does-cache-integration-hook-into-existing-code\" style=\"position:relative;\"><a href=\"#q11-where-does-cache-integration-hook-into-existing-code\" aria-label=\"q11 where does cache integration hook into existing code permalink\" class=\"anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>Q11: Where does cache integration hook into existing code?</h3>\n<p><strong>Question:</strong> Two approaches for minimal invasiveness:</p>\n<ol>\n<li>Wrap at the <code class=\"language-text\">pongoCollection</code> level — intercept operations inside the factory function, driver/SQL layers untouched.</li>\n<li>Decorator/wrapper pattern — <code class=\"language-text\">cachedPongoCollection</code> wraps <code class=\"language-text\">PongoCollection</code>, adds cache logic around delegation.</li>\n</ol>\n<p><strong>Answer:</strong> Leaning more to option 1. Thinks it’ll be easier to make observability precise eventually.</p>\n<p><strong>Claude’s assessment:</strong> Agreed with option 1. Cache decisions depend on internal state (whether filter targets <code class=\"language-text\">_id</code>, document <code class=\"language-text\">_version</code>, write success vs concurrency error). A wrapper would need to re-inspect all of that from the outside. For observability, cache hit/miss logic next to the actual DB call enables precise metrics in context. Pongo’s factory functions already compose behavior internally (transactions, schema migration) — cache is another concern at the same level. A decorator would duplicate filter-inspection logic and add indirection that doesn’t carry its weight.</p>\n<h3 id=\"q12-cache-interaction-with-transactions\" style=\"position:relative;\"><a href=\"#q12-cache-interaction-with-transactions\" aria-label=\"q12 cache interaction with transactions permalink\" class=\"anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>Q12: Cache interaction with transactions</h3>\n<p><strong>Question:</strong> How should cache interact with transactions?</p>\n<ol>\n<li>Should writes within a transaction update the cache immediately, or only after commit?</li>\n<li>Should reads within a transaction check the cache?</li>\n</ol>\n<p><strong>Answer:</strong></p>\n<ol>\n<li>After commit.</li>\n<li>We should use cache, but allow skipping it on transaction or session options. We can also allow setting cache settings per session.</li>\n</ol>\n<p><strong>Claude’s thoughts:</strong> Reads within a transaction benefiting from cache is more aggressive but logical — most transactions are short-lived. The narrow case: <code class=\"language-text\">insertOne</code> then <code class=\"language-text\">findOne</code> same <code class=\"language-text\">_id</code> within a transaction — cache won’t have the uncommitted insert, falls through to DB, gets the right answer anyway. No correctness issue, just no perf benefit for that case. Per-session cache settings fit the cascading pattern (e.g., <code class=\"language-text\">startSession({ cache: 'disabled' })</code> for batch migrations). Proposed cascade priority: client → db → collection → session → per-operation <code class=\"language-text\">skipCache</code>.</p>\n<h3 id=\"q13-cascade-priority--does-session-override-collection\" style=\"position:relative;\"><a href=\"#q13-cascade-priority--does-session-override-collection\" aria-label=\"q13 cascade priority  does session override collection permalink\" class=\"anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>Q13: Cascade priority — does session override collection?</h3>\n<p><strong>Question:</strong> Does the cascade priority <strong>client → db → collection → session → per-operation <code class=\"language-text\">skipCache</code></strong> feel right? Session overriding collection is the non-obvious part — the argument being that session is the most immediate runtime context, while collection is structural config. Or would you prefer collection always wins over session?</p>\n<p><strong>Answer:</strong> That’s fine. Session should precede collection, as normally you create session to e.g. share connection, and join the number of operations logically, so it’d make sense actually to also do it to e.g. override settings like caching.</p>\n<p><strong>Claude’s thoughts:</strong> Session as logical grouping of operations with shared context (connection, transaction) — overriding cache settings at that level is extending the same concept. Bulk import in a session? Set cache disabled once on the session instead of <code class=\"language-text\">skipCache</code> on every call. Final cascade: client → db → collection → session → per-operation.</p>\n<h3 id=\"q14-cache-provider-error-handling\" style=\"position:relative;\"><a href=\"#q14-cache-provider-error-handling\" aria-label=\"q14 cache provider error handling permalink\" class=\"anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>Q14: Cache provider error handling</h3>\n<p><strong>Question:</strong> If the cache provider throws (e.g., Redis connection drops), should Pongo:</p>\n<ul>\n<li>A) Treat it as a cache miss — fall through to DB silently, maybe log a warning. Cache failures never break the application.</li>\n<li>B) Propagate the error — let the caller deal with it.</li>\n</ul>\n<p><strong>Answer:</strong> A — yes, when we add observability to Pongo, we need to make it also a metric and trace event/attribute/log entry.</p>\n<p><strong>Claude’s thoughts:</strong> Cache is an optimization, not a correctness requirement. Silent degradation now, observable degradation when the observability layer lands.</p>\n<h3 id=\"q15-cache-warming-eventshooks-and-delete-operations\" style=\"position:relative;\"><a href=\"#q15-cache-warming-eventshooks-and-delete-operations\" aria-label=\"q15 cache warming eventshooks and delete operations permalink\" class=\"anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>Q15: Cache warming, events/hooks, and delete operations</h3>\n<p><strong>Question:</strong> Three remaining areas:</p>\n<ol>\n<li>Cache warming — should Pongo support pre-populating the cache on startup?</li>\n<li>Cache events/hooks — should users be able to listen to cache hits/misses/evictions?</li>\n<li>Delete operations — <code class=\"language-text\">deleteOne</code>/<code class=\"language-text\">deleteMany</code> should evict from cache. Anything non-obvious there?</li>\n</ol>\n<p><strong>Answer:</strong></p>\n<ol>\n<li>For now keep it as a future concept, or define pluggable strategy for users.</li>\n<li>You can add the basic hook to the Pongo Cache wrapper. Shouldn’t be that hard to just call some function.</li>\n<li>Yes, they should evict if they can, we can delete (for now) when <code class=\"language-text\">deleteMany</code> deletes by ids.</li>\n</ol>\n<p><strong>Claude’s thoughts:</strong> Cache warming deferred — can add a strategy hook later. Basic event callbacks (onHit, onMiss, onEvict, onError) on the cache wrapper are low-cost and useful for debugging even before full observability. Delete eviction scoped to id-based deletes for now — filter-based <code class=\"language-text\">deleteMany</code> doesn’t evict (no way to know which ids were affected without querying first).</p>\n<hr>\n<h2 id=\"interactive-rubber-ducking-result\" style=\"position:relative;\"><a href=\"#interactive-rubber-ducking-result\" aria-label=\"interactive rubber ducking result permalink\" class=\"anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>Interactive Rubber Ducking result</h2>\n<h3 id=\"pongo-2nd-level-cache--specification\" style=\"position:relative;\"><a href=\"#pongo-2nd-level-cache--specification\" aria-label=\"pongo 2nd level cache  specification permalink\" class=\"anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>Pongo 2nd Level Cache — Specification</h3>\n<h3 id=\"overview\" style=\"position:relative;\"><a href=\"#overview\" aria-label=\"overview permalink\" class=\"anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>Overview</h3>\n<p>Add a document-level (by <code class=\"language-text\">_id</code>) 2nd level cache to Pongo with a pluggable provider interface. Ships with an in-memory provider backed by <code class=\"language-text\">lru-cache</code>. Enabled by default. Designed to be minimally invasive to existing code.</p>\n<h3 id=\"cache-interface\" style=\"position:relative;\"><a href=\"#cache-interface\" aria-label=\"cache interface permalink\" class=\"anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>Cache Interface</h3>\n<h4 id=\"pongocacheprovidert\" style=\"position:relative;\"><a href=\"#pongocacheprovidert\" aria-label=\"pongocacheprovidert permalink\" class=\"anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a><code class=\"language-text\">PongoCacheProvider&lt;T></code></h4>\n<p>A thin, Pongo-owned interface. No coupling to any third-party API.</p>\n<div class=\"gatsby-highlight\" data-language=\"typescript\"><pre class=\"language-typescript\"><code class=\"language-typescript\"><span class=\"token keyword\">type</span> <span class=\"token class-name\">MaybePromise<span class=\"token operator\">&lt;</span><span class=\"token constant\">T</span><span class=\"token operator\">></span></span> <span class=\"token operator\">=</span> <span class=\"token constant\">T</span> <span class=\"token operator\">|</span> PromiseLike<span class=\"token operator\">&lt;</span><span class=\"token constant\">T</span><span class=\"token operator\">></span><span class=\"token punctuation\">;</span>\n\n<span class=\"token keyword\">interface</span> <span class=\"token class-name\">PongoCacheProvider</span> <span class=\"token punctuation\">{</span>\n  <span class=\"token function\">get</span><span class=\"token punctuation\">(</span>key<span class=\"token operator\">:</span> <span class=\"token builtin\">string</span><span class=\"token punctuation\">)</span><span class=\"token operator\">:</span> MaybePromise<span class=\"token operator\">&lt;</span>PongoDocument <span class=\"token operator\">|</span> <span class=\"token keyword\">null</span> <span class=\"token operator\">|</span> <span class=\"token keyword\">undefined</span><span class=\"token operator\">></span><span class=\"token punctuation\">;</span>\n  <span class=\"token function\">set</span><span class=\"token punctuation\">(</span>key<span class=\"token operator\">:</span> <span class=\"token builtin\">string</span><span class=\"token punctuation\">,</span> value<span class=\"token operator\">:</span> PongoDocument<span class=\"token punctuation\">,</span> options<span class=\"token operator\">?</span><span class=\"token operator\">:</span> <span class=\"token punctuation\">{</span> ttl<span class=\"token operator\">?</span><span class=\"token operator\">:</span> <span class=\"token builtin\">number</span> <span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span><span class=\"token operator\">:</span> MaybePromise<span class=\"token operator\">&lt;</span><span class=\"token keyword\">void</span><span class=\"token operator\">></span><span class=\"token punctuation\">;</span>\n  <span class=\"token keyword\">delete</span><span class=\"token punctuation\">(</span>key<span class=\"token operator\">:</span> <span class=\"token builtin\">string</span><span class=\"token punctuation\">)</span><span class=\"token operator\">:</span> MaybePromise<span class=\"token operator\">&lt;</span><span class=\"token keyword\">void</span><span class=\"token operator\">></span><span class=\"token punctuation\">;</span>\n  <span class=\"token function\">getMany</span><span class=\"token punctuation\">(</span>keys<span class=\"token operator\">:</span> <span class=\"token builtin\">string</span><span class=\"token punctuation\">[</span><span class=\"token punctuation\">]</span><span class=\"token punctuation\">)</span><span class=\"token operator\">:</span> MaybePromise<span class=\"token operator\">&lt;</span><span class=\"token punctuation\">(</span>PongoDocument <span class=\"token operator\">|</span> <span class=\"token keyword\">null</span> <span class=\"token operator\">|</span> <span class=\"token keyword\">undefined</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">[</span><span class=\"token punctuation\">]</span><span class=\"token operator\">></span><span class=\"token punctuation\">;</span>\n  <span class=\"token function\">setMany</span><span class=\"token punctuation\">(</span>entries<span class=\"token operator\">:</span> <span class=\"token punctuation\">{</span> key<span class=\"token operator\">:</span> <span class=\"token builtin\">string</span><span class=\"token punctuation\">;</span> value<span class=\"token operator\">:</span> PongoDocument<span class=\"token punctuation\">;</span> ttl<span class=\"token operator\">?</span><span class=\"token operator\">:</span> <span class=\"token builtin\">number</span> <span class=\"token punctuation\">}</span><span class=\"token punctuation\">[</span><span class=\"token punctuation\">]</span><span class=\"token punctuation\">)</span><span class=\"token operator\">:</span> MaybePromise<span class=\"token operator\">&lt;</span><span class=\"token keyword\">void</span><span class=\"token operator\">></span><span class=\"token punctuation\">;</span>\n  <span class=\"token function\">deleteMany</span><span class=\"token punctuation\">(</span>keys<span class=\"token operator\">:</span> <span class=\"token builtin\">string</span><span class=\"token punctuation\">[</span><span class=\"token punctuation\">]</span><span class=\"token punctuation\">)</span><span class=\"token operator\">:</span> MaybePromise<span class=\"token operator\">&lt;</span><span class=\"token keyword\">void</span><span class=\"token operator\">></span><span class=\"token punctuation\">;</span>\n  <span class=\"token function\">clear</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token operator\">:</span> MaybePromise<span class=\"token operator\">&lt;</span><span class=\"token keyword\">void</span><span class=\"token operator\">></span><span class=\"token punctuation\">;</span>\n<span class=\"token punctuation\">}</span></code></pre></div>\n<ul>\n<li><code class=\"language-text\">MaybePromise</code> return types: sync providers (in-memory) return values directly, async providers (Redis) return Promises. <code class=\"language-text\">await</code> handles both transparently.</li>\n<li>Batch methods (<code class=\"language-text\">getMany</code>, <code class=\"language-text\">setMany</code>, <code class=\"language-text\">deleteMany</code>) are first-class. Default in-memory implementation may loop internally, but the interface allows optimized batch ops for external providers.</li>\n<li><code class=\"language-text\">clear()</code> is internal/advanced — not exposed to typical users. When sharing a cache instance across collections, scoping is handled via key prefixing by Pongo, not by the provider.</li>\n</ul>\n<h4 id=\"cache-key-strategy\" style=\"position:relative;\"><a href=\"#cache-key-strategy\" aria-label=\"cache key strategy permalink\" class=\"anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>Cache key strategy</h4>\n<p>Pongo manages key prefixing internally: <code class=\"language-text\">{dbName}:{collectionName}:{documentId}</code>.</p>\n<p>The cache provider works with plain string keys — namespacing is Pongo’s concern, not the provider’s.</p>\n<h4 id=\"event-hooks\" style=\"position:relative;\"><a href=\"#event-hooks\" aria-label=\"event hooks permalink\" class=\"anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>Event hooks</h4>\n<p>The Pongo cache wrapper supports basic callbacks:</p>\n<ul>\n<li><code class=\"language-text\">onHit?(key: string): void</code></li>\n<li><code class=\"language-text\">onMiss?(key: string): void</code></li>\n<li><code class=\"language-text\">onEvict?(key: string): void</code></li>\n<li><code class=\"language-text\">onError?(error: unknown, operation: string): void</code></li>\n</ul>\n<p>These are optional and intended for debugging and future observability integration.</p>\n<h3 id=\"configuration\" style=\"position:relative;\"><a href=\"#configuration\" aria-label=\"configuration permalink\" class=\"anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>Configuration</h3>\n<h4 id=\"cacheconfig\" style=\"position:relative;\"><a href=\"#cacheconfig\" aria-label=\"cacheconfig permalink\" class=\"anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a><code class=\"language-text\">CacheConfig</code></h4>\n<div class=\"gatsby-highlight\" data-language=\"typescript\"><pre class=\"language-typescript\"><code class=\"language-typescript\"><span class=\"token keyword\">type</span> <span class=\"token class-name\">CacheConfig</span> <span class=\"token operator\">=</span> <span class=\"token punctuation\">{</span>\n type<span class=\"token operator\">:</span> <span class=\"token builtin\">string</span><span class=\"token punctuation\">;</span>               <span class=\"token comment\">// e.g., 'in-memory', 'redis', etc.</span>\n max<span class=\"token operator\">?</span><span class=\"token operator\">:</span> <span class=\"token builtin\">number</span><span class=\"token punctuation\">;</span>               <span class=\"token comment\">// max entries (general param, cascades)</span>\n ttl<span class=\"token operator\">?</span><span class=\"token operator\">:</span> <span class=\"token builtin\">number</span><span class=\"token punctuation\">;</span>               <span class=\"token comment\">// TTL in ms (general param, cascades)</span>\n  <span class=\"token comment\">// type-specific options live here too, keyed by type</span>\n <span class=\"token punctuation\">[</span>key<span class=\"token operator\">:</span> <span class=\"token builtin\">string</span><span class=\"token punctuation\">]</span><span class=\"token operator\">:</span> <span class=\"token builtin\">unknown</span><span class=\"token punctuation\">;</span>\n<span class=\"token punctuation\">}</span> <span class=\"token operator\">|</span> <span class=\"token string\">'disabled'</span><span class=\"token punctuation\">;</span></code></pre></div>\n<p>Three states:</p>\n<ul>\n<li><code class=\"language-text\">undefined</code> — inherit from parent level</li>\n<li><code class=\"language-text\">'disabled'</code> — explicitly turn off caching at this level</li>\n<li><code class=\"language-text\">CacheConfig</code> object — explicit configuration</li>\n</ul>\n<h4 id=\"cascading-configuration\" style=\"position:relative;\"><a href=\"#cascading-configuration\" aria-label=\"cascading configuration permalink\" class=\"anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>Cascading configuration</h4>\n<p>Cache config can be set at multiple levels. Each level inherits from its parent unless explicitly overridden:</p>\n<p><strong>client → db → collection → session → per-operation</strong></p>\n<p>Inheritance rules:</p>\n<ul>\n<li>General params (<code class=\"language-text\">max</code>, <code class=\"language-text\">ttl</code>) cascade down.</li>\n<li>If a lower level switches <code class=\"language-text\">type</code>, type-specific params reset to defaults (not inherited from parent).</li>\n<li>Session overrides collection — session is a logical grouping of operations, natural place to override runtime behavior (e.g., disable cache for a bulk import).</li>\n<li>Per-operation <code class=\"language-text\">skipCache?: boolean</code> is the most granular escape hatch.</li>\n</ul>\n<h4 id=\"passing-a-cache-instance\" style=\"position:relative;\"><a href=\"#passing-a-cache-instance\" aria-label=\"passing a cache instance permalink\" class=\"anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>Passing a cache instance</h4>\n<p>Users can provide either:</p>\n<ul>\n<li><strong>Settings</strong> — Pongo creates and manages the cache provider.</li>\n<li><strong>A pre-built cache provider instance</strong> — Pongo uses it directly.</li>\n</ul>\n<p>If settings are the same across multiple collections, Pongo can reuse the same provider instance internally. When a user passes an instance, multiple collections can explicitly share one cache.</p>\n<h4 id=\"defaults\" style=\"position:relative;\"><a href=\"#defaults\" aria-label=\"defaults permalink\" class=\"anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>Defaults</h4>\n<ul>\n<li><strong>Enabled by default</strong></li>\n<li><code class=\"language-text\">max</code>: follow <code class=\"language-text\">lru-cache</code> recommended defaults (1000)</li>\n<li><code class=\"language-text\">ttl</code>: follow <code class=\"language-text\">lru-cache</code> recommended defaults</li>\n<li>Default provider: in-memory (<code class=\"language-text\">lru-cache</code>)</li>\n</ul>\n<h3 id=\"integration-points\" style=\"position:relative;\"><a href=\"#integration-points\" aria-label=\"integration points permalink\" class=\"anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>Integration points</h3>\n<h4 id=\"where-pongocollection-factory-function\" style=\"position:relative;\"><a href=\"#where-pongocollection-factory-function\" aria-label=\"where pongocollection factory function permalink\" class=\"anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>Where: <code class=\"language-text\">pongoCollection</code> factory function</h4>\n<p>Cache logic is added directly inside <code class=\"language-text\">pongoCollection</code>, not as an external decorator/wrapper. This gives cache operations access to internal state (filter inspection, <code class=\"language-text\">_version</code>, write outcomes) and keeps observability precise.</p>\n<h4 id=\"read-operations\" style=\"position:relative;\"><a href=\"#read-operations\" aria-label=\"read operations permalink\" class=\"anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>Read operations</h4>\n<p><strong><code class=\"language-text\">findOne</code>:</strong></p>\n<ul>\n<li>If the filter targets <code class=\"language-text\">_id</code>, check cache first.</li>\n<li>Cache hit → return cached document.</li>\n<li>Cache miss → query DB, populate cache, return.</li>\n<li><code class=\"language-text\">skipCache?: boolean</code> option available.</li>\n</ul>\n<p><strong><code class=\"language-text\">findMany</code> / other query methods:</strong></p>\n<ul>\n<li>If the filter is a list of <code class=\"language-text\">_id</code> values, check cache for each.</li>\n<li>Return cached hits, query DB for misses, populate cache with DB results.</li>\n<li>Non-<code class=\"language-text\">_id</code> filters bypass cache entirely (cache is by-id only).</li>\n</ul>\n<h4 id=\"write-operations\" style=\"position:relative;\"><a href=\"#write-operations\" aria-label=\"write operations permalink\" class=\"anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>Write operations</h4>\n<p><strong><code class=\"language-text\">insertOne</code>:</strong></p>\n<ul>\n<li>After successful insert, put the document into cache.</li>\n</ul>\n<p><strong><code class=\"language-text\">insertMany</code>:</strong></p>\n<ul>\n<li>After successful insert, put all documents into cache.</li>\n</ul>\n<p><strong><code class=\"language-text\">updateOne</code> / <code class=\"language-text\">updateMany</code> / <code class=\"language-text\">replaceOne</code>:</strong></p>\n<ul>\n<li>After successful write, update the cache entry with the new document state.</li>\n</ul>\n<p><strong><code class=\"language-text\">deleteOne</code>:</strong></p>\n<ul>\n<li>After successful delete, evict from cache.</li>\n</ul>\n<p><strong><code class=\"language-text\">deleteMany</code>:</strong></p>\n<ul>\n<li>If deleting by ids, evict those ids from cache.</li>\n<li>Filter-based <code class=\"language-text\">deleteMany</code> does not evict (no way to know affected ids without extra query). Future improvement possible.</li>\n</ul>\n<h3 id=\"optimistic-concurrency\" style=\"position:relative;\"><a href=\"#optimistic-concurrency\" aria-label=\"optimistic concurrency permalink\" class=\"anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>Optimistic concurrency</h3>\n<ul>\n<li>On concurrency error (version mismatch), <strong>evict the stale entry</strong> from cache. This is critical — a concurrency error means someone else updated the record (e.g., from another node), so the cached version is stale.</li>\n<li>If a caller provides <code class=\"language-text\">expectedVersion</code> and the cached document has <code class=\"language-text\">_version</code>, compare them in memory. If they don’t match, we know it’s stale without hitting the DB — cheap short-circuit.</li>\n</ul>\n<h4 id=\"handle-method\" style=\"position:relative;\"><a href=\"#handle-method\" aria-label=\"handle method permalink\" class=\"anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a><code class=\"language-text\">handle</code> method</h4>\n<ul>\n<li>Accepts <code class=\"language-text\">id: string | string[]</code> (overloaded: string returns single result, string[] returns array).</li>\n<li>Read phase uses cache by default.</li>\n<li>Write phase updates/evicts cache based on outcome.</li>\n<li><code class=\"language-text\">skipCache?: boolean</code> available in handle options.</li>\n<li>For batch: loads all ids from cache, fetches misses from DB in one query, processes handler per document, batch writes to DB, updates cache after commit.</li>\n</ul>\n<h4 id=\"transactions\" style=\"position:relative;\"><a href=\"#transactions\" aria-label=\"transactions permalink\" class=\"anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>Transactions</h4>\n<ul>\n<li><strong>Writes update cache only after commit.</strong> Not during the transaction — uncommitted data should not enter the cache.</li>\n<li><strong>Reads within a transaction check cache</strong> by default (most transactions are short-lived, cached data is probably valid). If the transaction modified a document and then reads it, cache miss falls through to DB which returns the correct uncommitted state.</li>\n<li>Cache can be disabled per-session: <code class=\"language-text\">startSession({ cache: 'disabled' })</code>.</li>\n<li>Per-session cache settings follow the cascade (session overrides collection).</li>\n</ul>\n<h3 id=\"error-handling\" style=\"position:relative;\"><a href=\"#error-handling\" aria-label=\"error handling permalink\" class=\"anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>Error handling</h3>\n<p>Cache provider errors are <strong>swallowed</strong> — treated as cache misses. The application never fails because of a cache failure.</p>\n<ul>\n<li>Cache is an optimization, not a correctness requirement.</li>\n<li>Errors trigger <code class=\"language-text\">onError</code> hook for debugging.</li>\n<li>Future: errors will become metrics and trace events when Pongo’s observability layer lands.</li>\n</ul>\n<h3 id=\"future-considerations-out-of-scope-for-v1\" style=\"position:relative;\"><a href=\"#future-considerations-out-of-scope-for-v1\" aria-label=\"future considerations out of scope for v1 permalink\" class=\"anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>Future considerations (out of scope for v1)</h3>\n<ul>\n<li><strong>Cache warming</strong>: pluggable strategy for pre-populating cache on startup. Interface hook defined but no default implementation shipped.</li>\n<li><strong>Query-result caching</strong>: caching results of non-<code class=\"language-text\">_id</code> queries. Complex invalidation — deferred.</li>\n<li><strong>Cross-node cache sharing</strong>: Redis/Memcached providers. The interface supports it; no provider shipped in v1.</li>\n<li><strong>Filter-based <code class=\"language-text\">deleteMany</code> eviction</strong>: would require querying affected ids before delete.</li>\n<li><strong>Observability integration</strong>: metrics (hit ratio, latency saved, eviction counts), trace attributes, log entries for cache operations.</li>\n</ul>\n<h3 id=\"implementation-approach\" style=\"position:relative;\"><a href=\"#implementation-approach\" aria-label=\"implementation approach permalink\" class=\"anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>Implementation approach</h3>\n<ol>\n<li>Define <code class=\"language-text\">PongoCacheProvider</code> interface and <code class=\"language-text\">CacheConfig</code> types.</li>\n<li>Implement in-memory provider using <code class=\"language-text\">lru-cache</code>.</li>\n<li>Build Pongo cache wrapper (key prefixing, event hooks, error swallowing).</li>\n<li>Wire cascading config resolution (client → db → collection → session → per-op).</li>\n<li>Integrate into <code class=\"language-text\">pongoCollection</code>: intercept <code class=\"language-text\">_id</code>-based reads, update cache on writes, evict on deletes and concurrency errors.</li>\n<li>Extend <code class=\"language-text\">handle</code> to accept <code class=\"language-text\">string | string[]</code> with batch support.</li>\n<li>Add <code class=\"language-text\">skipCache</code> to operation options.</li>\n<li>Tests: unit tests for cache provider, integration tests for cache + collection operations, e2e tests for concurrency scenarios.</li>\n</ol>\n<hr>\n<p>Cheers!</p>\n<p>Oskar</p>\n<p><strong>p.s.</strong> and yes <a href=\"https://gemini.google/pl/overview/image-generation/\">Nano Banana</a> also did pretty well generating the cover of this post!</p>\n<p>p.s.2. <strong>Ukraine is still under brutal Russian invasion. A lot of Ukrainian people are hurt, without shelter and need help.</strong> You can help in various ways, for instance, directly helping refugees, spreading awareness, putting pressure on your local government or companies. You can also support Ukraine by donating e.g. to <a href=\"https://www.icrc.org/en/donate/ukraine\">Red Cross</a>, <a href=\"https://savelife.in.ua/en/donate/\">Ukraine humanitarian organisation</a> or <a href=\"https://www.gofundme.com/f/help-to-save-the-lives-of-civilians-in-a-war-zone\">donate Ambulances for Ukraine</a>.</p>","excerpt":"You may already know that I’m a GenAI sceptic. And a general sceptic. Do you know that scepticism comes from the Greek σκέπτομαι (skeptomai…","fields":{"slug":"/interactive_rubber_ducking_with_gen_ai/","prefix":"2026-03-16","langKey":"en"},"frontmatter":{"title":"Interactive Rubber Ducking with GenAI","author":"oskar dudycz","category":"Software Architecture","disqusId":null,"useDefaultLangCanonical":null,"cover":{"childImageSharp":{"resize":{"src":"/static/a08c97f5281d31661ba3946fbc3b1ec0/2a4de/2026-03-16-cover.png"}}}}},"authornote":{"id":"f1fb8a26-256d-5b24-9b06-ad19a0c2961b","html":"<p><span\n      class=\"gatsby-resp-image-wrapper\"\n      style=\"position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto\"\n    >\n      <a\n    class=\"gatsby-resp-image-link\"\n    href=\"/static/f748655e118b2b9d5ce6b7dd6f9f4f85/d2429/2021-10-13-cover.png\"\n    style=\"display: block\"\n    target=\"_blank\"\n    rel=\"noopener\"\n  >\n    <span\n    class=\"gatsby-resp-image-background-image\"\n    style=\"padding-bottom: 55.99999999999999%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAIAAADwazoUAAAACXBIWXMAAA6cAAAOnAEHlFPdAAAChUlEQVQozyXOTU/acBwA4J+FFmgp/5RCX2AlFVgUgxTCYZNA6KZMooPSiAoaZ8bLjTmEbALRePAygyaLBEl0lyWePGji1YsnT8YDBxMTE+9+iiXu+QQPPDw8nJ2dtVqtZrNZr9cbjUYul8tkMvl8vt1un5yc3Nzc3N7eDofDu7u74+Pj+/v7x8fH6+vr8/NzGA6HOzs73W53f3+/0+mUSqVisbi3t3d0dNTv9w8PDy8uLnq93sHBQblcrtfrp6enl5eXrVarUqnA8/OzruvLy8uDwUDTtJmZmWw22+12Nzc3q9VqKpUqFovT09OFQsHr9SaTyZWVlVgspmlaoVCAl5cXRVE4jotGowghiqJIkpybm/N4PAsLCwghRVF0Xff5fHa7HQDi8Xg6nQaASCQCT09PwWAQXrEsa7VaZVlOp9NjY2Nut1uWZYIgWJYVRZEkSYZhLBaL0WgkCCIUCkGv1xMEAcMwo9EIABRFEQQhSVI0GhUEwWAw0DQ9OjqK4zhFUQzD2O12HMc5jhMEAa6urmRZRgglEgmHwzEyMuJyuXZ3d2u12tTUFAAQBOF0Onmep2mae2V6heM4bG1tIYR4nk8kEqIoAkAgEOh0OvF4XJIkADCZTDzPu1wuk8lks9kQQhiGmc1mkiTB65U5zun3+xFCPp+P5znaRquqGg6HcdyIYRiO4+VyeWlp6f+CpmmDwWAxm0VRAFX9kM3mVlfXJidDPv/bN24pHI4kkyrDMCRJjo8HJMnz6dNsq9W2WmmzhWRZB2W1BSaCmqbDxrfK9naj/r2az2fWvxTfvY98La2pakxRJuY/zy4u5jLZtPox/uPnxvp6YTalLurz7WZt8PvX3z/9f7APp/oT02NdAAAAAElFTkSuQmCC'); background-size: cover; display: block;\"\n  ></span>\n  <img\n        class=\"gatsby-resp-image-image\"\n        alt=\"cover\"\n        title=\"cover\"\n        src=\"/static/f748655e118b2b9d5ce6b7dd6f9f4f85/a331c/2021-10-13-cover.png\"\n        srcset=\"/static/f748655e118b2b9d5ce6b7dd6f9f4f85/36ca5/2021-10-13-cover.png 200w,\n/static/f748655e118b2b9d5ce6b7dd6f9f4f85/a3397/2021-10-13-cover.png 400w,\n/static/f748655e118b2b9d5ce6b7dd6f9f4f85/a331c/2021-10-13-cover.png 800w,\n/static/f748655e118b2b9d5ce6b7dd6f9f4f85/d2429/2021-10-13-cover.png 805w\"\n        sizes=\"(max-width: 800px) 100vw, 800px\"\n        style=\"width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;\"\n        loading=\"lazy\"\n        decoding=\"async\"\n      />\n  </a>\n    </span></p>\n<p>Through my window, I see the result of good plans but poor execution. Opposite my flat, there is a partially completed construction place. Buildings were supposed to be eye-catching Mediterranean style apartments.  Delivery date? Two years ago. Actual? More and more unknown.</p>\n<p>Some time ago, I heard that using Event Sourcing makes creating Event-Driven Architecture easier. The arguments were correct, that if we’re already publishing events to trigger business workflows, then at some point, we may want to also store events to not lose information. Agreed. However, I also heard that keeping the state as events will simplify things. We’ll have a source of truth with a record of the system behaviour. This will allow, e.g. to confront the results of the operations with the recorded state. I’d agree with that, with one distinction. It’s easier as long as you already know Event Sourcing.</p>\n<p>Many people in the DDD community claim that the essential is to properly break down the system into autonomous parts called bounded contexts. Once we have it, the rest is secondary and will sort itself out. For sure.</p>\n<p>Many seasoned programmers speak similarly about new technologies. They claim that they can translate past experience into new technologies. That’s true that by analogy, they can catch the big picture quicker. But isn’t it a bold assumption to say that Win.Forms specialist will learn Angular quickly?</p>\n<p>The end result may differ a lot from the initial ideas. I saw the plan of those buildings next to me. Now I can see the effects of the execution. Or actually, the lack.</p>\n<p>I believe that we should carefully acknowledge not only the point of view of our authorities but also their seating point. If we want to find out how to form a wall, do we ask an architect or a foreman? An architect may know the theory, but the practice is what we’re looking for. On the other hand, if you want to know where to put the wall, you prefer the architect to do measurements. At least if you don’t want to have the roof falling to your head.</p>\n<p>After I had torn a ligament in my knee, I went to two qualified orthopedists. One said I should have surgery and do a reconstruction. The second stated that there is no need for that; rehabilitation should be enough. Guess which one had a specialization in surgery and which in rehabilitation?</p>\n<p>People usually give us advice from the point where they’re currently standing. They are entitled to a biased view. An architect who rarely does programming will tend to downplay the value of implementation and tactical patterns. Midlevel developers will focus on technicalities instead of the global system impact. The team manager or consultant will emphasize the importance of soft skills (or esoteric techniques known only to them).</p>\n<p>The truth is that we need all of them. The excellent plan will fall on the bad execution. The best execution for the wrong case will be just a waste of time. We should carefully evaluate the advice considering what we need and what an expert can give us.</p>\n<p>Therefore, when we’re reading an article, watching a talk, let’s also pay attention to the place where the person is standing. The perspective from there may be much different from where we are right now. That can be good, as it may push us in the right direction. But it may also be misleading, as we accidentally take biases of this person without understanding the tradeoffs. Personally, I prefer to follow not only people from pedestal but also those that are closer to my position. A bit further in the journey, but not too far. That helps me to calibrate my view as those people are more relative to my daily struggles.</p>\n<p>Polish historical leader <a href=\"https://en.wikipedia.org/wiki/J%C3%B3zef_Pi%C5%82sudski\">Józef Piłsudzki</a> reportedly used to say: <em>“Right is like an ass, everyone has its own”</em>.</p>\n<p>Cheers!</p>\n<p>Oskar</p>"},"site":{"siteMetadata":{"facebook":{"appId":""}}}},"pageContext":{"slug":"/interactive_rubber_ducking_with_gen_ai/","lang":"en","langKey":"en","prev":{"id":"c97cb0fb-a5d7-5385-aa00-85d9385ec149","fields":{"slug":"/the_end_of_coding_wrong_question/","prefix":"2026-03-09","source":"posts","langKey":"en"},"frontmatter":{"title":"The End of Coding? Wrong Question","category":"Software Architecture"}},"next":{"id":"759c3674-3d52-5530-a074-502fbe9fab4a","fields":{"slug":"/intro_to_example_mapping/","prefix":"2026-03-30","source":"posts","langKey":"en"},"frontmatter":{"title":"The one where Oskar explains Example Mapping","category":"Software Architecture"}},"source":"posts"}},
    "staticQueryHashes": ["2742854296"]}