今日已更新 204 条资讯 | 累计 40649 条内容
关于我们

标签:#node

找到 235 篇相关文章

AI 资讯

Your ORM is hiding the line that caused the slow query

I was building a runtime N+1 query detector for Node. The detection part worked on the first afternoon. Getting it to tell you which line of your code caused the problem took considerably longer, and taught me something about how ORMs execute queries that I had not thought about before. This is that story, and the fix. The symptom The detector instruments your database driver. When the same query shape runs many times inside one request, it reports it — along with the file and line that issued it, which is the part that actually saves you time: nplusone 1 finding in GET /orders — 51 queries, 840ms N+1 query 50× SELECT * FROM items WHERE order_id = ? at src/routes/orders.ts:47:38 (loadOrdersPage) 612ms spent here That worked. Then I pointed it at an app using Drizzle and got this instead: N + 1 query 12 × select "id" , "order_id" from "items" where "items" . "order_id" = $ 1 < unknown call site > Detected, counted, and attributed to nothing. Do not theorise. Dump the stack My first instinct was that my frame filter was too aggressive — it skips node_modules , node:internal , and the library's own frames, so maybe it was eating something it should not have. Rather than guess, I printed the whole stack at the exact moment the driver was called: const originalQuery = pg . Client . prototype . query ; pg . Client . prototype . query = function (... args ) { const previous = Error . stackTraceLimit ; Error . stackTraceLimit = 100 ; const stack = new Error (). stack . split ( " \n " ). slice ( 1 ); Error . stackTraceLimit = previous ; console . log ( " FRAMES: " , stack . length ); stack . forEach (( line , i ) => { const mine = ! /node_modules|node:internal/ . test ( line ); console . log ( ` ${ String ( i ). padStart ( 3 )} ${ mine ? " >>> " : " " } ${ line . trim ()} ` ); }); return originalQuery . apply ( this , args ); }; Here is what came back for a single await db.select().from(items).where(...) : FRAMES: 12 0 at Proxy.<anonymous> (.../nplusone/dist/adapters/postgre

2026-08-11 原文 →
AI 资讯

How I Protected My Express API from Spam and High AI Costs Using Redis

When I was building my backend API, I realized a big problem: anyone could spam my endpoints. If a user repeatedly reloads a page or hits an endpoint calling an external AI service, it can crash the server or run up high API costs. To fix this, I added Rate Limiting . Here is why I used Redis for it and how I set it up. The Problem with Simple In-Memory Limiters At first, I thought about saving request counts in a simple JavaScript object: // ❌ Simple in-memory check (Not good for production) const requestCounts = {}; app . use (( req , res , next ) => { const ip = req . ip ; requestCounts [ ip ] = ( requestCounts [ ip ] || 0 ) + 1 ; if ( requestCounts [ ip ] > 100 ) { return res . status ( 429 ). json ({ error : " Too many requests " }); } next (); }); This works locally, but has two big flaws: 1)Memory Leaks: The requestCounts object keeps growing in memory forever. 2)Breaks when Scaling: If you deploy multiple instances of your app behind a load balancer, each server keeps its own count. A user can easily bypass the limit by hitting different servers. The Solution: Centralized Redis Store Redis stores data in RAM outside our Node.js app. Because it is centralized, all server instances share the exact same count. [ Incoming Client Requests ] │ ▼ [ Cloud Load Balancer ] │ ┌───────────────┼───────────────┐ ▼ ▼ ▼ [ Express Node 1 ] [ Express Node 2 ] [ Express Node 3 ] │ │ │ └───────────────┼───────────────┘ ▼ [ Central Redis Store ] (Checks Request Limits) How I Configured It in My Project In my app, I use two levels of protection: Global Limit: 100 requests per 15 minutes for normal routes. Strict Limit: 5 requests per 10 minutes for heavy routes (like AI generation or OTP emails). 1 . Redis Connection ( config / redis . js ) import { createClient } from ' redis ' ; const redisClient = createClient ({ url : process . env . REDIS_URL || ' redis://localhost:6379 ' }); redisClient . on ( ' error ' , ( err ) => console . error ( ' Redis Error: ' , err )); redisClient .

2026-08-10 原文 →
AI 资讯

CPU utilization lies: autoscaling a single-threaded service

The service was slow. Not down, just slow: p95 latency climbing well past where users notice, requests piling up, the kind of degradation that generates support tickets instead of alerts. And the autoscaler, the whole point of which is to add capacity when a service is under strain, sat there doing nothing. The metric it was watching said everything was fine. Average CPU utilization on the tasks was hovering around 30 percent, nowhere near the scale-out threshold. The dashboard was calm. The users were not. Both were right, and the gap between them is one of the most common autoscaling traps on a container platform. This is the first article in a series on running a multi-tenant SaaS on AWS at team scale. It is about a metric that lies, quietly, by design. Why 30 percent CPU meant 100 percent busy The service was a single-threaded application. A Node.js API, in this case, but the same is true of any process that does its real work on one thread: a classic Python or Ruby worker, most single-process runtimes. A single-threaded process can, by definition, saturate exactly one CPU core. The task it was running on had four vCPUs. So the arithmetic that matters is brutally simple: one core fully pegged / four vCPUs on the task = ~25% task-average CPU At full saturation, the busiest that process can ever make the task look is about 25 percent. Add a little async I/O overhead spread across the runtime and you land around 30 percent. That is not a service with headroom. That is a service redlining on the only core it can use, while three cores sit idle and drag the average down to a number that reads as "barely working." The autoscaling policy was tracking average CPU across the task's cores. For a workload that can only ever use one of them, that average is not a measure of load. It is a measure of load divided by four. The metric was answering a different question This is the real lesson, and it is not specific to AWS or ECS. Average CPU utilization answers "how much of th

2026-08-09 原文 →
AI 资讯

Running AI-Generated Code Safely: Field Notes on Vercel Sandbox

Headline: Vercel Sandbox runs untrusted code — including code a model just wrote — inside an isolated, ephemeral microVM instead of inside my application's own process. I moved every "let the model write and execute a snippet" feature off ad-hoc child_process calls and onto Sandbox: one sandbox per execution, a hard timeout, an isolated filesystem, and no path back into my app's environment variables. Key takeaways Vercel Sandbox ( @vercel/sandbox ) runs code inside an isolated Firecracker microVM, not a container in your app's own process — a compromised sandbox can't read your Vercel Function's memory or environment variables. A sandbox is ephemeral: you create one, run commands, read the output, then stop it. There is no persistent state between runs unless you explicitly persist it yourself. sandbox.runCommand() executes a process inside the sandbox and returns stdout, stderr, and an exit code; sandbox.domain(port) exposes a running server on a public URL for a live preview. The two cases I actually reach for it: an LLM-authored script that needs to run and return a result, and a user-facing "run this code" feature like an AI-generated component preview. Sandbox is not the tool for trusted, first-party build or CI logic — that belongs in the deploy pipeline. Sandbox is for code you did not write and do not trust. Why can't I just run AI-generated code inside my own Vercel Function? A Vercel Function shares its process, filesystem, and environment with the rest of my app. Running an untrusted string as code in that same process — through child_process.exec or, worse, eval — puts every secret the function can see, API keys and database URLs included, inside the blast radius of whatever the model wrote. A generated snippet can read environment variables, open an outbound connection to exfiltrate them, or just spin the CPU and starve every other request the function is serving at the same time. I treat any code I did not author myself as untrusted by default, and th

2026-08-09 原文 →
AI 资讯

Building a Bulletproof Comment Reply System in Node.js & MongoDB 🚀

When building a nested reply system, most developers worry about deep tree complexity or messy data structures. For Vlox , I took a different approach: keeping things flat, fast, and secure by reusing a single Mongoose schema with smart atomic limits. Here is a deep dive into how I engineered a production-ready, race-condition-safe reply mechanism using MongoDB transactions, strict type sanitization, and automated limits. How It Works 🛠️ User Action: A user clicks the reply icon and submits their reply. The Payload: Vlox's system sends 3 fields via the endpoint /api/v1/reply/comment/post/:id : id : The post ID (passed as a URL parameter). rootCommentId : The ID of the root comment being replied to. reply : The raw text entered by the user. Sanitization & Validation: The incoming reply is instantly converted to a trimmed string. It then passes through two critical validation checks: Existence Check: The reply must exist. (If a malicious actor sends a payload without a body, the string literally evaluates to "undefined" and gets blocked). Length Limit: The reply must be under 201 characters, enforcing the standard comment limit. Atomic Transactions: If the validation checks pass, the system initiates a Mongoose transaction to execute the following steps safely: Permission Check: It verifies if the user has permission to reply by checking the post's status via await schemas.Posts.findOne(hotQueries.find_user_post(id, req.session.userId)); . Creation: If permissions are valid, it creates a new reply. (Fun fact: It reuses the exact same schema as standard comments!) The Reply Schema Structure: The reply object functions just like a normal comment, with two distinct exceptions: It does not contain a repliesCount field. It includes an extra rootId field, which explicitly points to the ID of the root comment being replied to. Concurrency & Caps: To guarantee that a single comment never receives more than 10 replies while simultaneously incrementing the counter, the system r

2026-08-09 原文 →
AI 资讯

Why Nodemailer Doesn't Work on Cloudflare Workers (And What To Do Instead)

A short explanation of a wall a lot of developers hit, why it isn't going away, and the five lines that replace it. You wrote a contact form. It worked locally. You deployed it to a Cloudflare Worker, or a Vercel Edge Function, or Deno Deploy, and got something like this: TypeError: Class extends value #<Object> is not a constructor or null Or, if you were luckier and got a useful error: Module not found: Can't resolve 'net' Then you spent an hour trying compatibility flags, polyfills, and bundler aliases. I want to save you the rest of that hour. This isn't a bug, and no amount of configuration will fix it. The actual reason Nodemailer's default transport is SMTP. SMTP is a protocol that runs over a raw TCP connection. To open one in Node.js, you call net.createConnection() . Cloudflare Workers don't run on Node.js. They run on V8 isolates — the same engine as Chrome, without the Node runtime around it. Vercel's Edge Runtime and Deno Deploy are built on similar principles. In that environment, there is no net module, because there are no raw TCP sockets. All networking is handled by managed infrastructure outside the runtime — Cloudflare's own writeup on bringing node:http to Workers is explicit about this: connection pooling, TLS negotiation, and egress IP management are handled at the system level, which is precisely why a subset of Node APIs can never be supported. So the chain is: No raw TCP → no net.createConnection() → no SMTP client → no Nodemailer. There's a second, smaller issue that often gets conflated with this one. Nodemailer issue #1621 points out that Nodemailer imports built-in modules without the node: prefix, which breaks the Workers build step. That one is fixable. But fixing it wouldn't help — you'd just move the failure from build time to runtime, where net still doesn't exist. Issue #1623 covers the broader edge-function problem. It's worth being clear that none of this is a knock on Nodemailer. It's an excellent library, actively maintained,

2026-08-08 原文 →
AI 资讯

I stopped letting GPT-5 babysit my inbox and the whole workflow got cheaper and better

I used to think email was a terrible place for AI. Too messy. Too human. Too full of forwarded chains from 2017 and HTML generated by software nobody at the company can name. Then I spent some time reading inbox automation threads, especially a good one on r/openclaw about email flows, and the pattern finally clicked: Email is a great surface for AI if you stop making the model act like your mail server. That sounds obvious. But a lot of inbox automations still do this: new message arrives ask GPT-5 if it is support ask Claude if it is sales ask another model if it is spammy ask again which alias it belongs to ask again whether to reply now or later That is not intelligence. That is expensive amnesia. The better pattern is simple: code owns state, retries, scheduling, sync, and verification the LLM only handles decisions that actually require judgment That split made my inbox workflows cheaper, easier to debug, and way less fragile. The rule I keep coming back to A comment from an OpenClaw workflow discussion said it better than most docs do: If your workflow stops working when you hit your LLM usage limit, the LLM is probably doing too much. That was about coding agents, but it applies perfectly to inbox automation. If your email pipeline depends on a model to remember mailbox state, dedupe events, handle retries, or re-check routing rules every run, you built the wrong system. Models are good at judgment. They are bad at being custodians. Email feels chaotic, but the transport is already structured Humans experience email as chaos. Machines do not. Every message already arrives with useful structure: From To Reply-To Subject thread identifiers message IDs headers timestamps raw MIME attachment boundaries alias addresses That matters because a lot of routing decisions should never hit an LLM in the first place. If invoices always go to ap@company.com , GPT-5 should not be rediscovering that rule every morning. If support mail always lands on a specific alias, code

2026-08-08 原文 →
AI 资讯

Why Your ZATCA Phase 2 Invoice Passes Compliance and Fails Reporting

If you are integrating ZATCA Phase 2 (Saudi Arabia's Fatoora e-invoicing) and you have seen this: { "type" : "ERROR" , "code" : "signed-properties-hashing" , "category" : "CERTIFICATE_ERRORS" , "message" : "Invalid signed properties hashing, SignedProperties with id='xadesSignedProperties'" } ...after your invoice sailed through /compliance/invoices , this post is for you. It is the single most confusing failure mode in the whole integration, and the fix is not what the error suggests. The trap: SignedProperties exists in two byte-shapes The XAdES SignedProperties block is referenced twice in your signed document: ds:Reference URI="#xadesSignedProperties" carries a digest of the block. The block itself is embedded inside ds:Object > xades:QualifyingProperties . The natural assumption is that both refer to the same bytes. They do not. The hashed shape carries namespace declarations and starts at column 0: <xades:SignedProperties xmlns:xades= "http://uri.etsi.org/01903/v1.3.2#" Id= "xadesSignedProperties" > <xades:SignedSignatureProperties> <xades:SigningTime> 2026-08-07T02:14:33 </xades:SigningTime> <xades:SigningCertificate> <xades:Cert> <xades:CertDigest> <ds:DigestMethod xmlns:ds= "http://www.w3.org/2000/09/xmldsig#" Algorithm= "http://www.w3.org/2001/04/xmlenc#sha256" /> The embedded shape carries no namespace declarations (they are inherited from ancestors) and its root element is indented to column 32 : <xades:SignedProperties Id= "xadesSignedProperties" > <xades:SignedSignatureProperties> Embed the hashed shape verbatim - the intuitive thing to do - and the gateway rejects with signed-properties-hashing , even though your indentation "looks right". The second half of the trap: the digest encoding The digest is not the raw SHA-256 bytes in base64. It is base64 of the hex string : const crypto = require ( ' crypto ' ); // hashedShape = the namespaced, column-0 variant above const propsDigest = Buffer . from ( crypto . createHash ( ' sha256 ' ). update ( Buffer .

2026-08-07 原文 →
AI 资讯

Preventing Overselling: Inventory Locks Under Concurrent Checkouts

Two customers are looking at the same product. One unit left. Within the same second, both click Pay. If your checkout reads the stock count, decides there's enough, and then writes the decrement, both requests pass the check and both succeed. You've now sold two units of something you had one of. That's overselling, and it's not a rare edge case — it's the default behaviour of any checkout that treats "check stock" and "reduce stock" as two separate steps. The window is small, but on a product that's nearly sold out, or during a launch when everyone hits the same SKU at once, small windows fire constantly. I've built the order pipeline for two production e-commerce platforms — pikkuna.fi and pi-pi.ee — where concurrent webhooks and concurrent checkouts hit the same order and product rows. This is the layer I reach for when a store sells finite stock. I covered the bare SELECT ... FOR UPDATE primitive briefly in PostgreSQL Production Patterns ; this article is the whole system built on top of it — reservations, multi-line carts, the payment window, and the parts that actually bite you in production. When You Don't Need Any of This Start with the honest disclaimer, because it decides everything downstream. Both pikkuna.fi and pi-pi.ee are made-to-order . A vinyl curtain is cut to the customer's dimensions; a waterless urinal system ships from a supply chain, not a shelf with a hard unit count. When there's no fixed quantity to run out of, overselling isn't a failure mode — you can't sell the tenth unit of something you manufacture on demand. So neither of those platforms needs a row lock on a stock column, and I didn't build one there. You need this article when you sell discrete, finite stock : limited runs, event tickets, one-off items, anything where "5 left" is a real number and selling the sixth is a promise you can't keep. If your catalogue is print-on-demand, made-to-order, or backed by effectively unlimited supply, stop here — the locking below is complexity

2026-08-07 原文 →
AI 资讯

« J'ai fini le tuto Node, et là je suis bloqué » — le mur dont personne ne parle

Tu as fini le tuto. Le vrai, le gros, celui de douze heures. Tu as tout suivi, tout tapé, tout fait tourner. À la fin, l'application marchait. Tu t'es senti capable. Tu t'es dit : « ça y est, je sais faire une API ». Et puis tu as ouvert un dossier vide pour faire la tienne. Curseur qui clignote. index.js . Rien. Pas parce que tu as oublié la syntaxe. Tu la connais. Mais là, tout seul, sans quelqu'un qui te dit quoi taper à la ligne suivante, tu ne sais pas par où commencer. Et cette sensation-là, ce vide entre « j'ai fini le tuto » et « je sais faire », personne ne t'avait prévenu qu'elle existait. C'est de ce mur que je veux parler. Parce que ce n'est pas un défaut chez toi. C'est une étape. 1. Le piège n'est pas le tuto, c'est ce qu'il te cache Un tuto, c'est une suite de bonnes décisions déjà prises pour toi. Quel dossier créer. Quel package installer. Où mettre le fichier de config. Quand extraire une fonction. À chaque embranchement, le formateur a choisi le bon chemin, et toi tu l'as suivi. Tu as tapé du code, oui. Mais tu n'as pris aucune décision. Or coder, le vrai coder, c'est presque que ça : décider. Choisir entre deux structures. Trancher un nom de variable. Décider si ce bout de logique mérite sa propre fonction. Un développeur qui bosse, ce n'est pas quelqu'un qui connaît toutes les réponses — c'est quelqu'un qui sait avancer quand il n'y en a pas. Le tuto t'a entraîné à taper. Il ne t'a pas entraîné à décider. Et c'est exactement la compétence qui te manque devant ton dossier vide. Ce n'est pas un trou dans ton savoir. C'est un muscle que tu n'as jamais sollicité, parce qu'on ne te l'a jamais laissé faire. 2. Pourquoi « un tuto de plus » ne réglera rien Ta réaction instinctive face au blocage, c'est de retourner là où tu te sens compétent. Un autre tuto. Un cours de plus. Une nouvelle techno à cocher. Je comprends le réflexe. Le tuto, c'est confortable : il y a une barre de progression, une fin, une petite dose de « j'ai réussi » à chaque étape. Le d

2026-08-07 原文 →
AI 资讯

Project Explanation for my Chesso application

Project High-Level Summary Chesso is a full-stack, real-time multiplayer chess platform engineered to provide low-latency online gameplay. It features real-time move synchronization, authoritative backend match clocks, secure authentication using JWT and Google OAuth, and full chess rule validation. I built it using the MERN stack (MongoDB, Express, React, Node.js) combined with Socket.IO for bi-directional WebSocket communication and Chess.js for move validation and FEN (Forsyth–Edwards Notation) state management. One of the main challenges I solved was building a server-authoritative state and clock synchronization mechanism to prevent client tampering and handle mid-game reconnections gracefully. Tech Stack & Architectural Overview Frontend : React, Vite for fast builds, React for declarative UI updates upon WebSocket events. Backend API : Node.js, Express.js Event-driven, non-blocking I/O ideal for handling multiple concurrent WebSocket connections. Socket.IO : Provides bi-directional socket events, auto-reconnection fallback, and socket room abstraction. Database : MongoDB for flexible JSON-like document model ideal for storing FEN strings, match logs, and user metadata. Auth & Security : Google OAuth 2.0, Passport.js, JWT, bcryptusing standard authentication flow providing password hashing (bcrypt) and session security via JWT tokens. Implementation of matchmaking & Queueing System When a player clicks "Play", the client emits StartGame with their playerID. The server verifies turn ownership (game.currentP === playerID). The move is executed in an isolated server-side Chess() instance (gameSockets.js). If empty, the player is queued and notified via waitingForOpponent. If another player is waiting, waitingQ.shift() pairs them instantly, creates a new game record in MongoDB (GameModel.js), assigns piece colors (white/black), and joins both sockets into a dedicated Socket.IO room named after the gameID. Game Recovery & Reconnection Resilience The server exposes

2026-08-07 原文 →
AI 资讯

Google's Custom Search image API dies in 2027. Two traps in replacing it.

Google's Custom Search JSON API is closed to new customers, and existing customers have until 2027-01-01 to move off it. That deadline takes searchType=image with it. I maintain cse-bridge , a small self-hosted service that speaks Google's customsearch/v1 wire format on top of your own SearXNG instance, so migrating is a base-URL change rather than a rewrite. Web search shipped first. This week I added image search — and it turned out to be much less mechanical than "map some more fields", because two of the assumptions that hold for web results are actively wrong for image results. Both are worth knowing whether or not you ever use my code. If you are writing anything that normalises image search results, you will hit them. Trap 1: link is not the page For a web result, Google's link is the URL of the page. Easy. For an image result, link is the image file itself , and the page it was found on lives in image.contextLink : { "link" : "https://facts.net/wp-content/uploads/2020/08/AdobeStock_209028852.jpeg" , "displayLink" : "facts.net" , "image" : { "contextLink" : "https://facts.net/nature/animals/red-panda-facts" , "thumbnailLink" : "https://ts1.mm.bing.net/th?id=OIP.I_aIcVvl98DbktQmP297ugHaE7&pid=15.1" , "width" : 4000 , "height" : 2666 } } SearXNG has it the other way round: the result's url is the page, and the image is in a separate img_src field ( documented here ). So the naive mapping — reuse the web mapper, add an image object — produces items whose link points at an HTML document. That fails silently , which is what makes it nasty. Your JSON still validates. Your item count is right. Every field is a well-formed URL. But every client that does <img src={item.link}> — which is the entire point of image search — renders nothing, and it looks like the images are broken rather than like your mapper is wrong. The fix is a rule, not a patch: if a result has no image URL, drop the whole result . Never fall back to the page URL to keep the count up. export functio

2026-08-06 原文 →
AI 资讯

Express 5 on µWebSockets: same middleware, 2x to 7x

I maintain Fulmine , a drop-in replacement for Express 5 that runs on µWebSockets.js instead of node:http . One line changes: const express = require ( " fulmine.js " ); // instead of require("express") Your middleware keeps working: helmet , cors , passport , morgan , multer , express-session and the rest. The numbers are not mine Benchmarks published by a project about itself deserve suspicion, so let me use somebody else's. HttpArena runs every framework on the same 64-core machine, in containers, under the same rules, and publishes the results. Express and Fastify are on that board too. Requests per second, from their published runs: Profile Fulmine Express Fastify Baseline (query parsing) 1,220,308 607,777 711,263 JSON (dataset + serialization) 1,111,187 395,361 522,201 Short-lived connections 1,026,789 278,163 298,779 Pipelined 7,259,814 1,009,543 1,671,338 Mixed API workload, 16 CPUs 126,282 67,724 75,633 Async Postgres 222,701 169,687 179,169 Upload (20 MB body) 2,154 2,104 1,902 That is 2.0x Express on the baseline, 2.8x on JSON, 3.7x on short-lived connections, 7.2x pipelined , and 1.9x on the mixed API profile. Against Fastify, on the same board, it is 1.7x on the baseline and 2.1x on JSON. Now the honest parts, which matter as much as the table. Look at the upload row: 1.02x. A 20 MB body is memory bandwidth and syscalls, not framework code. Everywhere the cost belongs to a library both servers call, the difference disappears: JSON.parse , zlib, OpenSSL. Speed comes from the framework only where the framework is doing the work. My entry runs in the arena's "tuned" mode, Express's and Fastify's run in "standard". On two profiles I left out of the table, static files and compressed JSON, that difference is decisive, because tuned mode allows hand-written compression and negotiation. Those rows would show 23x and 8x, and they would be measuring my entry's tuning, not the framework. I would rather not quote them. Where the speed comes from Not from one trick

2026-08-06 原文 →
AI 资讯

Debugging Node.js Like a Pro

Start with the Built-in Inspector Before reaching for external tools, remember Node.js has a built-in debugger. Run your script with --inspect and open chrome://inspect in Chrome to get a full DevTools experience: breakpoints, step-through, console, and even memory profiling. node --inspect app.js For a quick breakpoint without touching the browser, use --inspect-brk to pause on the first line. This is great for debugging startup issues. Use debugger Statements and Conditional Breakpoints Sometimes you need a breakpoint only when a condition is true. Instead of littering your code with if blocks, set a conditional breakpoint in DevTools. Right-click the line number, choose "Add conditional breakpoint," and enter an expression like user.id === 42 . For quick inline debugging, debugger; works but remember to remove it before committing. I often use it temporarily when I'm too lazy to open the DevTools UI. Log Like a Pro with util.inspect console.log of an object prints [object Object] which is useless. Use util.inspect with depth and colors to see nested structures clearly. const util = require ( ' util ' ); console . log ( util . inspect ( myObject , { showHidden : false , depth : null , colors : true })); Or in modern Node, you can use console.dir with { depth: null } for the same effect. Async Stack Traces: Don't Lose the Context Async errors are painful because stack traces often end at the event loop. Node 12+ gives you better async stack traces by default, but you can improve them further by using Error.captureStackTrace in your own error classes. class MyError extends Error { constructor ( message ) { super ( message ); Error . captureStackTrace ( this , MyError ); } } This makes the stack trace point to the caller, not the constructor. Handle Unhandled Rejections and Exceptions Silent failures are the worst. Set up global handlers to log errors properly and exit gracefully. process . on ( ' unhandledRejection ' , ( reason , promise ) => { console . error ( ' U

2026-08-06 原文 →
AI 资讯

My Tool-Calling Loop Worked Fine, Until Compliance Wanted a Second Model to Check It

Small ask, on paper. A clinician types something like "any allergy conflicts for this patient's current meds?", and before the model answers it needs to actually go get the medication list and the allergy list rather than guess at what's plausible. Two functions, both of which already existed. The interesting part was never the lookups, it was getting a model to decide when to call them and hand back arguments I could trust. Then compliance sat in on the review and asked the question I should have seen coming: "what checks this model's answer?" Fair question, this is going in front of a clinician. Their answer was a second model, from a different provider, running the same lookups independently and flagging if it disagreed. Reasonable. Also, as it turned out, the thing that broke my code. The first version worked. That was the problem. I had the OpenAI SDK already wired into this project, so version one was straightforward: define tools , send the request, read message.tool_calls , run whichever function it asked for, push a role: "tool" message back with the result, loop until it stops asking. Twenty minutes, maybe. It worked on the first real test and I remember thinking this was going to be a short ticket. It was a short ticket, right up until "second model, different provider" landed in the same sprint. I went to point the exact same loop at Claude and it just doesn't speak that dialect - Anthropic sends tool requests back as tool_use blocks sitting inside the message content, not a separate tool_calls array, and the result has to go back as a tool_result block inside a user message. There's no tool role at all on their side. Same idea, completely different shape, and I was about two minutes from just writing a second version of the loop and calling it a day. I'd already installed the thing that fixes this I only stopped because I already had @aviasole/shapecraft in this project for the FHIR schema work, and figured it was worth thirty seconds to check whether g

2026-08-05 原文 →
AI 资讯

Programmatic SEO with hreflang: One Joke, 17 Languages, Server-Rendered

People type 2+2 into Google. They type 9+10 . They type 7*8 when they can't remember whether it's 54 or 56. Each of those is a real, high-volume search query — and most of the results are identical calculator widgets. So when I built Wrongulator , a calculator that returns a confidently wrong answer on purpose, I had a question worth asking: what if every expression were its own page, ranking for the exact arithmetic people already search? That is programmatic SEO — generating a page per parameter instead of writing pages by hand. And doing it across 17 languages means programmatic SEO with hreflang, where each generated page also declares its 16 translated siblings. The trap is that most programmatic surfaces are thin, duplicative, and get buried by Google. This one isn't, for a specific reason: every page has a real, unique answer baked into the HTML before any JavaScript runs. This post is about how — and the honest costs nobody mentions. Why a Permalink Per Expression Is Even Possible A page per expression only works if /2+2 reproduces the same result for everyone, forever, with no database behind it. That property isn't free — it's the result of one design decision I cover in detail in why a viral toy must be wrong the same way every time : the wrong answer is a pure function of the expression, seeded by a stable hash, with no per-user state. The relevant consequence here is what that property unlocks for SEO. Because f("2+2") always returns the same wrong answer, the server can compute that answer on demand for any expression in the URL, with zero storage. There's no pages table, no CMS, no pre-generation job. A request for /64+5 runs the engine, gets 67 ("the only correct number"), and renders a complete page around it. The programmatic surface is, in effect, infinite — but it costs nothing to hold, because nothing is stored. The pure function is what makes thousands of unique pages possible without a database. That's the foundation. Everything below is about

2026-08-05 原文 →
AI 资讯

Can IP Geolocation Personalise Content with Node.js?

A visitor lands on a website and immediately sees prices in the wrong currency, content written for another region, and shipping information that does not apply to them. Nothing is technically broken, yet the experience feels poorly designed. For international websites, location can be a useful personalization signal. Instead of asking every visitor to manually select a country before displaying relevant information, developers can use IP based geographic data as an initial indication of where a request originates. That is where ip geolocation for content personalisation can become useful. The objective is not to identify a person. It is to make an otherwise anonymous visit more contextually relevant. How can location improve content personalisation? Location can influence many small decisions that collectively affect the user experience. An ecommerce website may display a local currency. A news publisher may surface regional stories. A software company may show country specific documentation or availability information. The process is relatively simple. A visitor sends a request to a website. The server obtains the request's public IP address. That IP is sent to a geolocation service. The response provides geographic information. The application then selects content according to predefined rules. The crucial part is the final step. Geolocation provides data, but business logic determines what the visitor actually sees. Which approaches can websites use? One approach is manual location selection. The user chooses their country or region from a menu. This is transparent and usually accurate because the user explicitly provides the information. However, it adds friction and may be forgotten during future visits. Browser based location is another option. It can provide more precise positioning, but it normally requires permission and is not always appropriate for simple content personalization. IP based geolocation sits between these approaches. It requires no location

2026-08-05 原文 →
AI 资讯

I built a short-code marketplace with zero npm dependencies (Node.js 22, no framework)

I've been going back and forth on whether to share this — it's a pretty niche idea, and I wasn't sure if it's clever or just weird. But here's the technical side of it, which I figure this crowd might actually appreciate regardless. What I built: claimo.me — you claim a short code (2-4 letters, or a custom name) for a one-time fee, no subscription, permanently yours. Each code is configurable as a redirect link, a QR code, or a small profile card. There's also a "Claimo Map" — every possible code is a clickable pixel you can browse, inspired by the old Million Dollar Homepage. The part I actually want to talk about here: it's zero-dependency. No Express, no ORM, no build step — just Node.js 22+'s built-in http module and the new built-in node:sqlite. I wanted to see how far "just the standard library" actually gets you for something real — payments (Stripe), admin moderation, rate limiting, a live interactive map UI, the works. Some things that surprised me building it this way: node:sqlite's DatabaseSync is genuinely pleasant to use, but it's missing conveniences like better-sqlite3's .transaction() helper — I ended up writing a small manual BEGIN/COMMIT/ROLLBACK wrapper. Routing without a framework is maybe 40 lines of code and I stopped missing Express within a day. The real cost isn't runtime performance, it's losing the ecosystem — anything I'd normally npm install for free (input validation, rate limiting, even basic templating) I had to hand-roll. Some of that was genuinely good for me, some of it I'd reconsider on a bigger project. Business side, since half of you will ask: it's a real registered business, payments go through Stripe only (I never touch card data), no crypto, nothing weird. The paid tiers fund keeping a free short-link tier alive too. Honestly — is the zero-dependency thing a genuinely good call for a real production app, or am I just going to regret it in a year? And separately: does "own a short code" as a product idea make any sense to you

2026-08-05 原文 →
开发者

I got tired of mocking Date, so I built a TimeProvider for TypeScript

Every (or at least a lot of) project seems to have code like this somewhere: if (user.subscriptionEndsAt < new Date()) { // ... } There's nothing wrong with it... until you have to test it. Then you end up freezing time, mocking Date, enabling fake timers, remembering to restore them afterwards, and hoping another test didn't leave the clock in a weird state. While Jest's and Vitest's fake timers are great tools, they always felt like they were solving the problem from the outside by patching global APIs. I wanted to try something different. Time is a dependency When you think about it, the current time isn't much different from a database or an HTTP client. Your business logic depends on it, but it doesn't have to know where it comes from. Instead of writing this: const now = new Date(); what if we wrote this? const now = timeProvider.now(); Suddenly, testing becomes boring—in the best possible way. You don't need global fake timers anymore. You just pass a different implementation. .NET had the same idea While looking into this, I discovered that .NET 8 introduced a TimeProvider abstraction. Seeing that was reassuring. It suggested I wasn't the only one who felt that "current time" deserved to be treated as a real dependency. I didn't want to copy the .NET API, but I did like the underlying idea. So I started building a version that felt natural in the TypeScript ecosystem. It grew beyond a clock At first I only wanted to replace new Date(). Then I realized the same issue exists with setTimeout, setInterval, performance measurements, and a few other APIs. They all depend on the environment's notion of time. So the library slowly became an abstraction around all of those instead of just "what time is it?". Is this actually useful? That's the part I'm still curious about. In the projects I've worked on, I prefer injecting time over patching globals during tests. Maybe other teams have reached the same conclusion. Maybe everyone is perfectly happy with fake timers an

2026-08-05 原文 →
AI 资讯

Browser vs Node — Where the Event Loop Actually Diverges (Part 2/3)

In part 1, we built the shared mental model: call stack, microtask queue, macrotask queue, and the rule that microtasks fully drain before the next macrotask runs. That model is spec-level JavaScript behavior — but it's not the whole story once you actually run code. The event loop isn't part of the JS language spec. It's part of the host environment — the browser or Node — and each one implements it differently around that shared core. This is the post most "event loop" explainers skip, because it means going past the diagram and into how each runtime is actually built. The browser: event loop meets rendering In a browser, the event loop isn't just juggling callbacks — it's also responsible for keeping the page visually responsive. That means rendering has to get a turn too, and the browser has to decide when . Here's the roughly accurate sequence per loop iteration: Execute one macrotask (a click handler, a setTimeout callback, a network event, whatever's next in the queue) Drain the entire microtask queue Maybe render a frame — the browser doesn't render after every single task; it tries to hit ~60fps and will batch work between paints Go back to step 1 The "maybe render" part is where two APIs come in that don't exist in Node at all: requestAnimationFrame(callback) — schedules a callback to run right before the next repaint. It's not a macrotask or microtask in the queue sense — it's tied directly to the rendering pipeline. Use it for anything visual (animations, DOM measurements) instead of setTimeout , because it's synced to when the browser is actually about to paint, not an arbitrary delay. requestIdleCallback(callback) — schedules a callback to run when the browser is idle, after layout and paint, with a deadline. Meant for low-priority work you don't want competing with rendering — analytics, prefetching, non-urgent DOM updates. Here's the key interaction that's easy to miss: microtasks can starve rendering. If a promise chain keeps queueing more microtask

2026-08-05 原文 →