Notebook
Whatever is holding my attention.
Notes from building with AI agents, plus whatever else teaches me something.
Series
- pgvector for TypeScript
Eight posts that take one TypeScript project, a multimodal docs assistant searching text and screenshots, from a first similarity query to vector search you can tune, measure, and run in production. No second database.
All posts
- Tuning Postgres and pgvector: the three knobs that matter08-18-2026 · Most pgvector performance problems come down to three settings. This post shows how to read an ANN query plan and tune ef_search, shared_buffers, and work_mem in the right order.
- AI text watermarking: how it works and what it can't do08-16-2026 · Claude now watermarks its text. The watermark changes where the randomness in word choice comes from, not what the model can say. Here is the whole pipeline, with simulations you can poke at.
- HNSW vs IVFFlat: choosing and building your pgvector index08-14-2026 · Past a few hundred thousand rows, an exact scan stops being fast enough. Here is how to pick between HNSW and IVFFlat and build the index without locking the table.
- Vector search relevance: chunking, metadata, and the 0.81 problem08-11-2026 · Most bad vector search results come from one of three failure modes: chunking, modality mismatch, or a confused model. Each one has a specific diagnostic and a specific fix.
- pgvector setup: your first multimodal query in TypeScript08-03-2026 · One Postgres table can hold text and screenshot embeddings in the same vector column. This post sets up the schema, the Voyage embedding call, and the first query that returns both.
- Vector search in Postgres: the mental model behind pgvector08-02-2026 · A first similarity query that returns results is not a finished search feature. This post explains what an embedding is, why one Postgres column can hold text and images, and where an untuned index starts returning wrong results.
- AI agent architecture patterns that survive production05-18-2026 · Most agent demos are a model in a loop. Production agents need a run store, typed memory layers, idempotent tools, a trace log, and approval gates. Here's the full architecture with Postgres schema and TypeScript patterns.
- Stop adding prompts. Your agent needs control flow.05-17-2026 · A while-loop calling an LLM keeps doing the wrong thing, and more prompt engineering isn't the fix. Here's the control-flow ladder for AI agent orchestration: bounded loop, workflow, state machine, with TypeScript examples at each level.
- pgvector vs Pinecone for RAG05-16-2026 · If you already run Postgres, pgvector covers most RAG workloads without Pinecone. A workload-by-workload comparison: HNSW and IVFFlat index types, filtered search, cost at 1M and 10M vectors, and the signals that mean it's time to switch.
- RAG with Postgres, Drizzle, and pgvector05-15-2026 · You don't need Pinecone. If you already run Postgres, pgvector is a one-extension install away from a full RAG pipeline. Here's the whole thing in TypeScript with Drizzle: schema, ingestion, retrieval, hybrid search, and eval.
- Building an MCP Server in TypeScript with NestJS05-14-2026 · How to build a NestJS MCP server in TypeScript on top of a real backend: dependency injection, Zod-typed tools, stdio and Streamable HTTP transports, bearer auth, Pino logging, and handlers you can unit test.
- Build an MCP Server from Scratch05-13-2026 · MCP is how your TypeScript code becomes a tool Claude can call. This is how to build an MCP server from scratch: the protocol in one paragraph, the three primitives explained, and a working server wired into Claude Desktop.
- Self-hosting an LLM as a TypeScript Developer05-12-2026 · A self-hosted LLM is just an OpenAI-compatible endpoint you run yourself. The path from a local Ollama instance to a production vLLM server with auth, all in TypeScript from a Node app.
- Running Homebrew on Linux with Multiple Users05-11-2026 · Share a Linuxbrew installation between multiple users on one Linux machine by fixing group permissions on the Cellar and adding Git safe directory entries.
- Drizzle ORM vs Prisma05-10-2026 · Drizzle ORM vs Prisma comes down to one architectural difference: where the schema lives and where the types come from. This comparison covers schema, queries, migrations, bundle size, and type-checking performance, with a clear answer for each situation.
- Running cron jobs in NestJS05-09-2026 · @nestjs/schedule is the official way to run a cron job in NestJS. This covers the @Cron decorator, CronExpression values like EVERY_MINUTE and EVERY_5_MINUTES, dynamic jobs with SchedulerRegistry, and the silent error failure.
- Kafka vs RabbitMQ05-08-2026 · The Kafka vs RabbitMQ comparison comes down to one architectural fact. RabbitMQ deletes a message once it is consumed, Apache Kafka keeps it. That fact decides replay, fan-out, routing, throughput, and when BullMQ is the better answer.
- Zod vs Yup05-07-2026 · Yup came to most of us through Formik and it is fine for form validation. For validating API payloads in TypeScript, Zod is the better fit because the schema is the type. Here is the difference, and when the switch is worth it.
- Unit and e2e testing in NestJS with Jest and Supertest05-06-2026 · NestJS routes testing through its DI system, so getting a service instance, mocking an injected dependency, and booting the full app without hitting a real database all work differently than in plain Jest. Here is the unit and e2e setup with Jest and Supertest that works.
- NestJS microservices with a real two-service example05-05-2026 · ClientProxy, @MessagePattern, and transport config make sense once you see a working setup. Two NestJS microservices talking over TCP, with send() vs emit() explained, error propagation, and a Docker Compose for local dev.
- TypeScript conditional types and how infer works05-04-2026 · TypeScript conditional types pick one type or another based on assignability. This covers the T extends X ? Y : Z syntax, the infer keyword, and distributive conditional types over unions.
- Adding prettier to eslint 04-10-2025 · ESLint and Prettier fight over formatting until you install eslint-config-prettier and extend from it. Includes the fix for the ESLint couldn't find the config prettier to extend from error.
- A try at type-safe groupBy function in TypeScript04-10-2025 · Build a type-safe TypeScript groupBy function for an array of objects, handle object keys and nested values, and compare it with Object.groupBy and lodash's groupBy.
- Upgrading my blog to Next 1504-05-2025 · I upgraded my blog to Next 15 after a middleware security flaw, using @next/codemod and the Tailwind CLI, and replaced the deprecated @vercel/style-guide with eslint-config-next.
- tsx doesn’t support decorators03-26-2025 · tsx runs on esbuild, which does not support TypeScript decorators. Why a NestJS project still breaks with experimentalDecorators and emitDecoratorMetadata set in tsconfig, and how switching to ts-node solved it.
- Extending Window: types vs interfaces03-21-2025 · To extend the Window type in TypeScript, use an interface, not a type alias. Only interfaces support declaration merging, so only they can augment the Window object in a React app.
- Validating NestJS env vars with Zod02-06-2025 · NestJS env validation with Zod instead of Joi: wire up ConfigModule with a Zod schema, get full type safety from ConfigService, and use getOrThrow without casting.
- Using node API for delay02-06-2025 · Node.js has a built-in delay API. setTimeout from node:timers/promises returns a promise, so a node delay or sleep needs no callback wrapper.
- Loading env file into Node process02-06-2025 · Node.js 20 can load a .env file with the built-in --env-file flag, and --env-file-if-exists keeps the process from crashing when the file is missing.
- React Component Lifecycle11-28-2024 · The React component lifecycle has three phases: mount, update, and unmount. Here is how useEffect and useLayoutEffect hook into each phase in a functional component, and what the dependency array changes.
- Email special headers11-20-2024 · Email headers like List-Unsubscribe and List-ID improve deliverability and let inbox providers group your messages. Here is how to set them in Node.
- How CQRS is different than Event Sourcing08-18-2024 · The difference between CQRS and Event Sourcing, two distinct architectural patterns: CQRS splits reads from writes, Event Sourcing stores every state change as an event.
- RabbitMQ exchange vs queue, explained08-14-2024 · RabbitMQ separates routing from storage: exchanges route messages and queues store them. This breaks down what each one does, how bindings connect them, and when topic exchanges make routing much simpler.
- What the pgvector operators <->, <=>, and <#> actually mean08-13-2024 · The pgvector distance operators pick different metrics: <-> is L2 distance, <=> is cosine, <#> is inner product. Here's when each one makes sense, and why cosine needs normalized vectors.
- PgVector indexing options for vector similarity search07-31-2024 · pgvector gives PostgreSQL two index types for vector similarity search: HNSW and IVFFlat. Compare build time, query speed, and recall, with create index examples and tuning parameters for each.
- Counting GPT tokens06-30-2024 · Tokens are the units LLMs read and bill for. How to get a GPT token count in Node with tiktoken, and what that count means for cost and performance.
- Logging route handler responses in Next.js 1406-19-2024 · Next.js 14 middleware cannot read response bodies. Build a custom Next.js logger that wraps route handlers for request logging, including the NextResponse.json body.
- Redirect www subdomain with Cloudflare06-17-2024 · Redirect your www subdomain to your main domain with Cloudflare: add a proxied DNS record for www, then forward www to non-www with a page rule.
- Logging requests in Express app06-16-2024 · express-requests-logger is a maintained Express logger that replaces morgan. It logs all requests and responses in your Express.js app, skips health check URLs, and masks sensitive fields.
- Move Docker volume to bind mount 06-12-2024 · How to convert a Docker named volume to a bind mount and migrate an existing PostgreSQL database into it, including the Docker Compose change and the directory ownership that has to match.
- Docker Compose won't pull the latest image unless you tell it to06-11-2024 · Use Docker Compose pull policies when your image is built elsewhere and `docker compose up` keeps using a stale local copy.
- Using puppeteer executable for GSTS06-08-2024 · GSTS uses Google Workspace authentication as a credential provider for the AWS CLI. Point it at a Chromium executable path you already have from Puppeteer to manage multiple AWS accounts without installing Playwright.
- Next.js Hydration Window Issue05-29-2024 · The Next.js hydration window issue throws "window is not defined" during server rendering, even inside a client component. Here is why hydration causes it and how to fix the component with usePathname.
- Using Git rebase without creating chaos in your repo05-16-2024 · Git rebase rewrites commit hashes, so it needs rules. Best practices for rebase vs merge, handling conflicts, undoing a bad rebase, and pushing with --force-with-lease instead of a plain force push.
- Why EQ is Your Next Career Upgrade05-13-2024 · Emotional intelligence (EQ) drives career success in tech as much as technical skill does. How to read workplace emotions and use them for better collaboration and growth.
- Finding Your Raspberry Pi Address on a Mac03-24-2024 · Use arp -a on your Mac to find the local IP address and the MAC address of a Raspberry Pi, so you can SSH into it without guessing.
- How to store vector embeddings in Postgres with Drizzle ORM03-21-2024 · The full setup for importing vector from drizzle-orm/pg-core. Enable the pgvector extension, define the vector embedding column in your Drizzle schema, and add an index. Includes the Payload CMS case.
- RabbitMQ RPC pattern in TypeScript03-16-2024 · The RabbitMQ RPC pattern gives services request-response communication over a queue. Full implementation in TypeScript with amqplib: correlation IDs, reply queues, and both sides of the call.
- Optimize webpage load with special tags03-15-2024 · Four resource hints (preload, preconnect, prefetch, and DNS prefetch) that improve your website loading speed and enhance user experience.
- What the hell is Open Graph?03-13-2024 · Open Graph (OG) tags are meta tags that control your social media sharing preview: the title, description, and image a platform shows for your content.
- List of useful Chrome args03-10-2024 · The Chrome arguments worth passing through Puppeteer launch options, what each flag does in headless and Docker environments, and how they affect performance when you automate or test SPAs.
- My go-to Next.js ESlint config03-10-2024 · The ESLint config I use in every Next.js project: eslint-config-next, next/core-web-vitals, and @vercel/style-guide in one file.
- The trap of making everything dynamic03-01-2024 · Overly dynamic TypeScript configs break computed keys because the values widen to string. const, as const, and unique symbols keep types literal at compile time.
- PostgreSQL doesn't have ON UPDATE CURRENT_TIMESTAMP. Here's the fix02-27-2024 · PostgreSQL has no ON UPDATE CURRENT_TIMESTAMP clause, so an updated_at column with DEFAULT CURRENT_TIMESTAMP does not auto update after the insert. Here's how to auto update the timestamp with a trigger, and the Drizzle ORM equivalent.
- Cannot find module '../build/Release/canvas.node' on macOS02-20-2024 · The error Cannot find module '../build/Release/canvas.node' on macOS means the Node canvas package never built its native binary. Install the missing libraries with Homebrew, clear node_modules, and reinstall.
- Combining RxJS observables - Part 102-20-2024 · `combineLatest` and `withLatestFrom` both merge RxJS observables, but `combineLatest` emits on every source emission while `withLatestFrom` emits only when its trigger observable fires. Two worked TypeScript examples show which operator to pick.