Writing• The 'generic' service circular dependency problem
How to tackle your overburdened ‘notification’ or ‘comment’ services when they start accumulating everyone else’s cruft.
- Note•
Scratching your own itch with AI
Many have written about this already, but regardless of how you feel about AI (in engineering or otherwise), it’s hard to overstate how wonderful it is to be able to scratch your own itch.
I’ve always loved IntelliJ’s ‘history for line selection feature’, it’s great: select a line range and easily navigate through the history:

GitLens and similar VSCode extensions don’t really come close to this. You can say ‘show me the history for this line’, but it doesn’t let you easily navigate through just that specific range.
In just minutes I had exactly what I wanted in VSCode, thanks to Codex. Here it is (unpublished/not in the store):

Then, just the other week, while I was mucking around with Lua, I realized that the Busted VSCode test runner plugin wasn’t great and didn’t support debugging. So I vibe’d one. It works great, at least for my limited use case.
Maintaining this stuff and all the gnarly edge cases I’m sure remain unsolved is a different story, but how awesome is it to just be able to do this kind of stuff, off the cuff, in minutes?
- Link•
Causewalk and error wrapping →
I’m a big fan of error wrapping to provide more context.
In Go, this is standard practice:
// Hopefully something better than oh-no. // Think contextual information that the caller has. result, err := run() if err != nil { return fmt.Errorf("Oh no: %w", err) }GoBut in JS, we’ve had this for a while too, via
Error.cause:try { throw new Error() } catch (cause) { throw new Error("Oh no!", { cause }) }jsGo has a couple of nice utilities for walking the error tree to see if an error matches a particular type or interface (
errors.As,errors.AsType, etc.). This is great when most of the time you don’t care about the specific error, but at the edge you do. For instance, an HTTP server might care if an error is of a certain type when choosing the right response status code.There are a bunch of error libraries out there, but I’ve just published a super tiny one that scratches my personal itch for these helpers: causewalk.
Here’s an example:
import { is } from "causewalk" import { CardDeclinedError } from "./payments" try { await checkout(order) return response.status(204).send() } catch (error) { // Does any error in the chain match `CardDeclinedError`? if (is(error, CardDeclinedError)) { return response.status(402).send({ message: "Card was declined" }) } return response.status(500) }TypeScriptI really do mean small: the surface area today is just three simple functions that share the same helper for walking errors. Copy-paste it, install it or use it as a starting point. I love things like
neverthrow, but often they require too much investment and buy-in. Writing• You might not need… the always-valid entity
One of the classic OOP principles is the ‘always-valid domain entity’. The idea is that entities and value objects don’t need to be validated, as invalid construction is actively guarded against.
Writing• You might not need… a service worker
Once the ‘next big thing’, now largely forgotten. What happened to the service worker?
Writing• Ship the policy, not the code
A rule has to hold on both sides of an API boundary. Do you duplicate the code, share it, or let the backend drive the invariant?
- Note•
Concurrency in transactions
There’s a temptation to make independent database operations concurrent:
await db.transaction(async (tx) => { await Promise.all([ tx .update(accounts) .set({ balance: sql`${accounts.balance} - 100.00` }) .where(eq(accounts.name, 'Dan')), tx .update(accounts) .set({ balance: sql`${accounts.balance} + 100.00` }) .where(eq(accounts.name, 'Andrew')), ]); });TypeScriptInside a transaction, though, there usually isn’t any point. Even if both promises fire at once, the driver still has to send the statements down a single connection.
Anything not bound to the database connection can still run concurrently:
await db.transaction(async (tx) => { await Promise.all([ otherAsyncWork(), tx .update(accounts) .set({ balance: sql`${accounts.balance} + 100.00` }) .where(eq(accounts.name, 'Andrew')), ]); });TypeScriptBut you probably shouldn’t do other I/O in the middle of a transaction anyway if you can avoid it (especially writes). It keeps the transaction open for longer, and any external side effects won’t be rolled back.
- Note•
Avoiding prettified logs
It’s common to pretty-print logs to stdout during development:
[10:42:18] INFO Request started method: GET path: /api/posts requestId: req_123Then switch to structured logs in production:
{"time":"2026-05-24T10:42:18.120Z","level":"info","message":"Request started","method":"GET","path":"/api/posts","requestId":"req_123"}JSONPretty logs are nice when an app is small, but they become painful once things scale. Thousands of lines fly past, terminal search only gets you so far (your scrollback needs to be set absurdly high), and when the terminal closes, the logs are gone.
I’ve started to prefer writing structured logs to disk during development. Send them over to something like Logdy (if you want something lightweight), or VictoriaLogs (if you want something a bit more capable).
Then you get the same filtering, grouping, and JSON-attribute goodness you rely on in production. It’s also very useful to be able to point your agent at the logs of an already running process.
Writing• You might not need… the repository pattern
Most codebases implement a cargo-cult ‘repository pattern’ that Evans and Vernon would shudder at. What actually gets shipped is usually a grab-bag collection of methods that provide no real abstraction, and no clear value.
- Note•
Distributed Zod discriminated unions
Here’s a trick that seems obvious in retrospect. If you want to represent a type like this in Zod:
type Order = { name: string } & ( | { orderType: 'DELIVERY'; details: DeliveryOrderDetails } | { orderType: 'PICKUP'; details: PickupOrderDetails } );TypeScriptYou can just create the union type separately and then intersect it:
const OrderDetails = z.discriminatedUnion('orderType', [ z.object({ orderType: z.literal('DELIVERY'), details: DeliveryDetailsSchema, }), z.object({ orderType: z.literal('PICKUP'), details: PickupDetailsSchema, }), ]); const OrderSchema = z.object({ name: z.string() }).and(OrderDetails); // Or this: // const OrderSchema = z.intersection(z.object({ name: z.string() }), OrderDetails);TypeScriptNeat if you’re forced to keep the type discriminator top-level.
Writing• Intentionally blocking rendering with JavaScript
You nearly always want to put
<script>tags in the<head>and mark them as non-blocking using eitherasyncordefer. However, there’s an interesting use-case for actually wanting to block paint.- Link•
Coldstarts are dead →
I have to admit, this one made me chuckle:
The cold start argument had its day. That day was 2018. If you’re still leading with it, I get it, it used to be real. But the data has moved on, and it’s time we did too.
Followed by:
| Runtime | P50 | P99 | Notes | |------------|-----------|-----------|-----------------------| | Node.js 22 | 200-350ms | 600ms-1s | Solid general choice |Sure doesn’t look dead to me! That’s the entire TTFB performance budget for a lot of sites.
Writing• When React parent components need to know their children
In React, it’s always preferable to maintain a linear data flow. If a parent needs something, lift the state instead of smuggling it back up. All rules are made to be broken, though.
Writing• Module System Dependency Injection in React & Friends
What does ‘use the module system for global dependency injection’ actually mean, and is it a good substitute for real DI?
- Note•
Vitest async fixture destructuring
Vitest’s custom fixture support is great. Originally inspired by Playwright’s fixture setup, it’s now evolved into an auto-typed flexible builder:
const test = baseTest .extend('client', () => new UserClient()) .extend('user', async ({ client }) => client.getUser()) test('Does the thing', async ({ user }) => { // No need to await user, it's already resolved. })TypeScriptI’ve long wondered how they manage to lazily initialise fixtures based on object destructuring — even with a Proxy or similar it’s not possible to hide the async part. Today I finally took a look, and it’s actually pretty cursed:
// https://github.com/vitest-dev/vitest/blob/v4.1/packages/runner/src/fixture.ts#L646 let fnString = filterOutComments(implementation.toString())TypeScriptThe trick is to stringify the function and extract with regex the names of the fixtures requested. Vitest can then build up the fixture dependency tree and resolve these before passing them to your test fixture.
It’s also why this doesn’t work:
test('Does the thing', async (ctx) => { // No proxy magic or anything wild, so this won't work - // only the function arguments are parsed. const { user } = ctx })TypeScriptTurns out Playwright does the extact same thing.