AI 资讯
I Wrote 238 Tests Against My Own Auth Package and Found 4 Real Bugs
I'd already done a lot right by the time I started writing tests for Beaver-Auth . Every module had gone through multiple rounds of deliberate review. Enumeration protection, hashed tokens, refresh rotation, TOTP replay defense — the design was solid, and I knew it was solid, because I'd thought hard about every piece of it. Then I wrote 238 tests against the actual code, and found 8 real bugs. Some of them were the kind that would have silently broken production on day one. This post isn't about the bugs specifically — it's about the gap between "I reviewed this carefully" and "this is shippable," and why that gap is bigger than most of us assume, even when the reviewing was genuinely careful. "Passing tests" and "shippable" are different claims Here's the trap I nearly walked into: I'd built a solid test suite covering the core auth flows — registration, login, verification — and every test passed. It felt done. But passing tests only tell you the code does what the tests expect. If the tests were written from the same mental model as the code, they'll happily confirm a bug is correct behavior, because both the code and the test agree on the same wrong assumption. The fix wasn't "write more tests." It was testing against the real, integrated system — not a hand-built mock of my own logic, and not testing modules in isolation from what actually calls them. A few of the bugs below only surfaced because a test exercised the real dependency chain instead of assuming it worked. Bug 1: TypeScript let an argument-shift bug compile clean This is the one that scared me most. Beaver-Auth dispatches background work (like sending a verification email) through a TaskDispatcher interface: interface TaskDispatcher { dispatch ( taskName : string , payload : unknown , handler : () => Promise < void > , onFailure ?: ( error : unknown ) => Promise < void > | void , ): Promise < void > } The default implementation had drifted to a different signature — missing the payload parameter e
AI 资讯
How We Built a Safe GitHub Bounty Lifecycle for MyZubster
How We Built a Safe GitHub Bounty Lifecycle for MyZubster MyZubster is evolving into a distributed ecosystem of repositories, services, automation, hardware projects, AI components, and contributor workflows. As the number of repositories and contributors increased, one problem became increasingly important: How do we automate bounty workflows without accidentally treating a GitHub event as proof of payment, verification, or settlement? We recently completed an important part of that architecture: a real-time GitHub bounty lifecycle system . And we tested it end-to-end. The lifecycle We use an explicit bounty lifecycle instead of assuming that an issue, pull request, or merge means a bounty has been completed. The lifecycle is roughly: text PROPOSED ↓ VALIDATED ↓ APPROVED ↓ FUNDED ↓ ACTIVE ↓ SUBMITTED ↓ UNDER_REVIEW ↓ VERIFIED ↓ REWARD_RECORDED ↓ SETTLEMENT_PENDING ↓ SETTLED The important part is that GitHub automation only controls a limited part of this flow. Today, GitHub can automatically move a bounty through: APPROVED ↓ assignment ACTIVE ↓ linked PR SUBMITTED ↓ review UNDER_REVIEW And then automation stops. GitHub Webhooks Across the Ecosystem We configured repository webhooks across 17 first-party MyZubster repositories. The subscribed events are: issues pull_request pull_request_review The central endpoint is: POST /api/github-bounties/webhook The backend is Node.js / Express and validates GitHub webhook signatures using: X-Hub-Signature-256 with an HMAC-SHA256 secret. Unsigned requests are rejected. For example: POST /api/github-bounties/webhook → HTTP 401 while valid GitHub webhook deliveries receive a normal application response. A Useful Production Bug: PM2 Had a Stale Secret One of the most interesting parts of the deployment was a real production debugging problem. GitHub was delivering webhook events correctly, but every delivery returned: 401 Unauthorized Cloudflare was healthy. The public API was healthy. The webhook route was healthy. GitHub delive
AI 资讯
Transactions in NestJS and TypeORM without passing the EntityManager around
Transactions promise a simple guarantee: either everything commits, or nothing does. And yet, in a NestJS application with a repository layer, it is perfectly possible to run a rollback with no errors and then find a row still sitting in the database that should have disappeared with it. This is not a TypeORM or PostgreSQL bug. One of the repositories involved was never inside the transaction, because the EntityManager stopped being passed down three layers up. There was no exception, no warning, and the tests passed because that repository was mocked. This article describes how to make that class of failure impossible: the transaction opens at a single point — the controller handling the request — and repositories enlist themselves in the transaction in progress, without receiving anything as a parameter. It comes to about sixty lines built on AsyncLocalStorage . The second part is the one rarely told: three consequences of the transaction boundary, each with its fix. A network call inside the transaction holds a pooled connection and its locks for the entire wait. A failure record written in the catch is rolled back along with the very failure it was meant to document. And nesting two execute calls does not open a nested transaction but two independent ones, with the self-deadlock that allows. The problem: passing the EntityManager by hand TypeORM offers a transaction like this: await dataSource . transaction ( async ( manager ) => { await manager . getRepository ( UserModel ). save ( user ); await manager . getRepository ( UserSettingModel ). save ( settings ); }); For a small project this is the correct answer and nothing more is needed. The problem shows up once a repository layer exists. The manager is the transaction: if a repository does not use that manager, its queries run on a different connection and end up outside the transaction. Silently, with no error and no warning. The rollback simply does not revert them. So the manager has to reach the repository
AI 资讯
Your backup is not a backup until you have restored it
This is an English write-up of a post from my Japanese dev diary. Original: https://saas-diary.com/tech-log/backup-restore-drill-automation/ For over a year, my backup job has reported success every single night. Green check, every day, no exceptions. Then I asked myself one question and went cold: "How many times have I actually restored from it?" Zero. Not once. "It was backed up" and "it can be restored" are different states My setup has two paths. One mirrors all source to a private repo. The other packs the things I can never recreate — notes, config, and Android signing keys — into an encrypted bundle and ships it to a private channel every night. Both were green every day. But green only proved the upload finished . It never proved the contents were right, or that the archive could even be opened. Within one month, I had two failures that stayed green the whole time. Failure 1. The collector for signing keys used three hardcoded paths. I kept shipping new apps, so the number of keys kept growing — but the collector didn't. By the time I noticed, 7 of 10 keys were missing from the backup . Five of those apps were live on the store. If my machine had died, I could never have shipped an update for them again. The backup reported success every night through all of it. Failure 2. The mirror push failed 7 days in a row (a large binary hit the host's file-size limit). But the script printed "✅ done" and returned exit code 0 even when one half failed. A failure that isn't visible isn't a failure — it's a time bomb. So I automated a restore drill Once a month, a job now does this: Rebuild the encrypted bundle (without shipping it) Actually decrypt it with the stored passphrase Extract it and count what's inside Check the mirror is not stalled (latest commit timestamp via API) Delete the scratch folder and the generated bundle The encryption is openssl-compatible AES-256-CBC with PBKDF2 (SHA-256, 100k iterations). I deliberately avoided depending on the openssl binary,
AI 资讯
Authentication done right: JWT, sessions, and OAuth explained — Like a Marvel superhero assembling the team
The Quest Begins (The "Why") I still remember the first time I tried to add login to a side‑project. I’d read a tutorial that said “just store a token in localStorage and you’re good,” slapped together a few fetch calls, and called it a day. A week later I got an email from a user: “Hey, I can’t log out, and someone else seems to be using my account.” My heart sank. I realized I’d bolted a flashy lock onto a screen door — it looked secure, but anyone with a screwdriver could walk right in. That moment kicked off a deep dive. I wanted to understand the trade‑offs between sessions , JSON Web Tokens (JWT) , and OAuth so I could pick the right tool for each job, not just the shiniest one. What followed felt like assembling a superhero squad: each member has a unique power, and knowing when to call on them makes the difference between saving the day and causing collateral damage. The Revelation (The Insight) Sessions – The Trusty Sidekick Sessions are the classic, server‑side approach. When a user logs in, the server creates a random identifier (the session ID), stores it in a database or cache (Redis, Memcached, etc.), and sends it back to the browser as an HttpOnly cookie. On every request, the browser automatically includes that cookie, the server looks up the ID, and pulls the associated user data. Why I love it: The secret never leaves the server, so stealing a cookie only gives an attacker a session ID that’s useless without the server’s store. Revoking a session is trivial — just delete the row from the store. Works great for traditional web apps where you control both front‑ and back‑end. Where it stumbles: Horizontal scaling requires a shared session store; otherwise each instance forgets who the user is. Every request does a database/lookup, which can add latency if the store isn’t fast enough. JWT – The Lone Wolf with a Signed Badge A JWT is a compact, URL‑safe string that contains claims (like sub , exp , roles ) and is cryptographically signed (HMAC or RSA).
AI 资讯
Why I Built Unlockt: A Local-First Instagram Saved Archiver, Canvas Collage Studio & 9:16 Video Vault
Like many developers, designers, and digital marketers, my Instagram "Saved" collection had turned into a digital graveyard with over 5,000 bookmarked posts, reels, and carousels. The native Instagram web app offers virtually zero productivity tools: ❌ No full-text search across captions or hashtags ❌ No way to extract individual slides from carousel photo dumps ❌ No offline preservation (if a creator archives a post, it disappears forever) ❌ Existing web downloaders ask for account passwords, inject trackers, or bombard you with ads. So I spent the last few months developing Unlockt — a 100% free, MIT open-source, local-first Chromium extension and Node.js Express dashboard. --- ## 🏗️ Architecture & Engineering Highlights Here is how Unlockt is designed under the hood: ┌─────────────────────────────────┐ │ Chromium Extension (MV3) │ ──► Reads Instagram GraphQL via active session └────────────────┬────────────────┘ │ Local REST Sync ▼ ┌─────────────────────────────────┐ │ Express Backend (Port 3000) │ ──► SSRF-Hardened Proxy & HTTP 206 Video Streamer └────────────────┬────────────────┘ │ ┌────────┴────────┐ ▼ ▼ ┌──────────────┐ ┌───────────────────────────┐ │ data/saved. │ │ /thumbnails /videos │ │ json (DB) │ │ (Local High-DPI Storage) │ └──────────────┘ └───────────────────────────┘ 1. Zero-Password Session Scraping Rather than asking users for their credentials or running headless browser instances that trigger Meta account checkpoints, Unlockt operates as a Manifest V3 Chromium extension. It uses the cookies and CSRF tokens already present in your authenticated browser tab with randomized jitter delays (800ms - 2200ms) to respect rate limits. 2. 1-Click HTML5 Canvas Collage Studio One of my favorite features is the Carousel Studio . When you open a 10-slide photo dump, Unlockt extracts every slide and can render them onto an off-screen HTML5 <canvas> element to produce high-resolution moodboards ( 2x1 , 2x2 , 3x2 , 3x3 , and 5x2 ) with crisp 4px white margin div
AI 资讯
Null Is Not Zero: Building a JavaScript SEO Audit That Admits Its Limits
We moved a server-side SEO engine into a Chrome extension. Measuring the page was the easy half. Saying what we could not measure was the hard half. We had been running an on-page analysis engine on our own servers for years. You give it a URL, it fetches the page, it reports. Ordinary. Then we moved that engine into the browser, because a server cannot reach localhost , a staging box, an intranet, or anything behind a login. The browser can. Porting the analysis was mechanical work. What took the real time was a category of problem that barely exists on the server: in a live tab, half the things you want to measure are sometimes unavailable, and the honest answer is not a number. This post is about the decisions that came out of that, with the code that implements them. The One Rule: Null Is Not Zero Every derivation in the engine returns number | null , and the two mean different things. 0 means we measured it and it is zero. A page with no layout shift really does score zero. null means we could not measure it. No interaction happened yet, the browser does not support that entry type, or the document came from another origin and the size fields were zeroed out. A zero printed where a null belongs is a made-up number. It is worse than an empty cell, because the reader has no way to tell it apart from a real measurement. So the two never collapse: the derivation keeps them separate and the UI renders them differently. That sounds obvious written down. It is surprisingly easy to violate, and the next section is the most common way. PerformanceObserver Fails Silently, So Ask It First Here is the trap. Calling observe() with an entry type the browser does not support does not throw . It does not warn. It quietly does nothing, and your handler is simply never called. Which means an unsupported metric produces exactly the same result as a measured zero. The one thing the rule above forbids. The fix is to ask before you observe, and to record the refusal: js const SUPPOR
AI 资讯
Build a POS receipt printer in Node.js
Disclosure: I build Receiptful, the printing API used in this tutorial. The Node and Express parts apply whatever you print with. You have orders coming into your point of sale, and you want each one to print on the thermal printer at the counter. This is a complete walkthrough of a small Node service that does exactly that. By the end you will have an endpoint you can POST an order to and watch paper come out. There is nothing to install next to the printer for this tutorial to work, and no ESC/POS to write by hand. You send HTML, Receiptful prints it. Before you start You need two things from the console : A paired printer, which gives you a printer ID . If you have not done this yet, the getting started guide walks through it in a couple of minutes. An API key (the rf_live_… value), created under API keys and shown only once. On the code side you need Node 18 or newer, so that fetch is available globally with no extra dependency. We will use TypeScript, but the same code works in plain JavaScript if you drop the types. Put your credentials in the environment rather than in the source: export RECEIPTFUL_API_KEY = "rf_live_3f9c…" export RECEIPTFUL_PRINTER_ID = "42" Step 1: model the order Start with the shape of an order. Yours will have more fields, but this is enough to print a useful receipt: interface LineItem { name : string ; quantity : number ; unitPrice : number ; // in cents, to avoid float rounding } interface Order { id : number ; items : LineItem []; placedAt : Date ; } Keeping money in cents and formatting only at the edges saves you from the classic floating point rounding bugs that show up as a receipt total that is one cent off. Step 2: render the order as HTML This is the part that decides how the receipt looks. Receiptful converts the HTML you send into ESC/POS for your specific printer, so you get to lay a receipt out with tags you already know instead of byte codes. function money ( cents : number ): string { return " $ " + ( cents / 100 ). toFi
AI 资讯
Tenant-Aware Speech-to-Text Explained — MP3/WAV File Uploads Across US/EU in 2026
Short answer: for a small fintech product that turns reviewer voice notes into structured code findings, start with one synchronous speech-to-text file-upload adapter for MP3 and WAV, but write every upload to a tenant ledger before making the transcription request. That is usually the fastest integration because it keeps the first release small while preserving per-tenant cost visibility and a clean path to regional routing. Choice Shipping effort Tenant attribution Best fit Main constraint Direct file upload Lowest Clear with an internal ledger Short reviewer notes Bound by the selected API's request and duration limits Object storage plus async worker Medium Clear with job records Long or bursty recordings More states to operate Self-hosted transcription Highest Fully internal Strict control requirements or sustained workloads Model serving becomes your job My recommendation is the first row for the initial release. Keep the adapter replaceable, measure billed units rather than guessing from file size, and promote work to a queue only after real upload patterns justify it. The point isn't to find a universally fastest model. It is to ship weekly without losing the tenant-level evidence needed to understand margin. How should a simple speech-to-text API handle MP3 and WAV file uploads? Treat the upload as a business event, not as an anonymous call to an AI endpoint. Before sending any audio, create an internal record with tenantId , changeId , uploadId , media type, byte count, selected processing region, and a start timestamp. After transcription, add the external request identifier when one exists, the terminal status, and the billable unit reported by the selected service. A byte count is useful for capacity planning; it is not a substitute for actual billing data. That distinction matters in a multi-tenant SaaS. One tenant may submit many short WAV notes, while another submits compressed MP3 files with longer conversations. Charging, margin analysis, and abuse
AI 资讯
JWT Authentication in Express That You Can Actually Revoke
Access tokens, refresh token rotation, and theft detection: the parts most Node.js tutorials leave out. A friend messaged me about his side project a few months ago: "Someone else is logged into my account. I changed my password. They're still in." He had followed the tutorials to the letter. Sign a JWT on login, send it to the frontend, keep it in localStorage , attach it to every request. Done. What none of those tutorials mentioned is that this setup has no way to un -log anyone in. A JWT is a signed piece of paper. Once you hand it over, it stays valid until it expires, and his expired in 30 days. Changing the password accomplished nothing, because the token had already been signed and nothing about it depended on the password. There was no list of active sessions to delete from. There was nothing to revoke. His only remaining move was rotating the signing secret, which logged out every user on the platform at once. That was his entire kill switch: burn it all down. This is the walkthrough I wish someone had handed me the first time I built auth. Token design, storage, refresh rotation, theft detection, the Express code, the Axios interceptor on the frontend, and the specific mistakes that turn a working login into an incident. It's long. Auth is one of those areas where the missing ten percent is the part that gets you. What the standard tutorial leaves out Nearly every "JWT authentication in Node.js" post ends in the same place: sign a token, put it in localStorage , send a Bearer header. That gets you a demo. Four things stand between that and production. localStorage is readable by any JavaScript on the page. That includes the analytics snippet you added last week, the npm package that got compromised upstream, and any XSS hole in your own code. One call to localStorage.getItem('token') and an attacker holds a working credential they can replay from their own machine. You can't detect it and you can't stop it. There is no revocation. The appeal of JWTs is st
AI 资讯
Your `if` statements are a database nobody can query
Somewhere in your codebase there is a line that looks like this: if ( user . plan === ' enterprise ' || user . tenantId === ' acme-corp ' ) { // ... } Nobody remembers who wrote the second half of that condition. It has been there for two years. It is almost certainly still load-bearing. Here is the thing I want to convince you of: that line isn't code. It's data, and it's stored in the worst possible place. Every conditional that encodes a business decision is really a row. It has a condition, an outcome, and a bunch of implicit context about when it applies. You have hundreds of these rows. They're spread across a dozen services, written in four different styles, and there is no way to list them. You have a database. You just can't query it. Five things a database gives you that your code doesn't Once you look at it this way, the problems stop feeling like sloppiness and start feeling structural. There's no schema. One service decides a customer is premium by checking plan === 'premium' . Another checks subscription.tier > 2 . A third checks a flag that was set during a migration in 2023. All three are "the same rule" until the day they aren't, and there's nothing in the system that would notice the drift. There's no way to query it. Try to answer a simple question: what rules are live in production right now? You can't. Someone has to read the source. And grep won't save you, because the interesting conditions are compound, spread across guard clauses, and half of them are expressed as an early return rather than an if . There are no migrations. Changing a rate limit from 100 to 200 requires a pull request, a review, a CI run, and a deploy window. You're pushing a code change through the full pipeline to change a number. It's a schema migration with none of the tooling that makes schema migrations tolerable. There's no audit log. Git tells you who edited the line. It doesn't tell you who decided the rule, when it was supposed to expire, or whether the customer it
AI 资讯
I Built a RAG Pipeline in TypeScript Without LangChain — The Whole Thing in 200 Lines
Every RAG tutorial I found looked like this: const chain = RetrievalQAChain . fromLLM ( model , vectorStore . asRetriever ()); const res = await chain . call ({ query : " what is this document about? " }); Twelve lines, a Pinecone key, a screenshot of it answering one question about one PDF, and a confident closing paragraph about "production readiness." I read four of them and still couldn't have told you what an embedding actually was, why cosine similarity was the metric everyone used, or what would happen if my documents were 800 pages instead of 8. I could copy the code. I couldn't debug it. So I deleted the frameworks and wrote the whole thing by hand. No LangChain, no LlamaIndex, no hosted vector database, and no cloud LLM — the model runs on my laptop. Six files, a bit over 200 lines of TypeScript, and nothing imported that I can't explain. This post is the whole pipeline, the data structures behind each stage and why they were chosen, the four bugs that cost me the most time, and a debugging method that will save you an afternoon. Who this is for I'm assuming you write JavaScript or TypeScript, you're comfortable with async / await , arrays, and classes, and you've installed an npm package before. That's it. I am not assuming you know anything about machine learning, vectors, embeddings, or information retrieval. Every one of those is explained from zero as it comes up, and if a line of code does something non-obvious, I explain the line. If you already know what a vector store is, skip to the bug list at the bottom. What RAG actually is Strip the acronym away and RAG is one idea: Language models can't read your files. So find the relevant paragraphs yourself, paste them into the prompt, and ask the question. The rest of the pipeline exists to make that sentence practical. Finding the right paragraphs is the hard part. You can't keyword-search your way there, because a user asking "how do I stop duplicate rows" won't use the word "DISTINCT" that appears in
AI 资讯
Building a Restaurant Reservation System with Node.js, Express & MongoDB - A Beginner's Guide
Building a Restaurant Reservation System with Node.js, Express & MongoDB - A Beginner's Guide Tags: #nodejs #express #mongodb #webdevelopment #tutorial #beginner Introduction Hey everyone! 👋 This is my first Dev.to post, and I'm excited to share what I've been learning. As a 5th-semester CS student, I've been diving deep into full-stack web development, and today I want to walk you through building a Restaurant Reservation System – a real project I built that taught me so much about backend architecture and database design. If you're just starting with Node.js, Express, and MongoDB, this post is for you! What We'll Build A simple but functional restaurant reservation system where: Users can browse available time slots Users can book a table for a specific date and time Admin can manage reservations Weekly scheduling (Monday-Sunday) 2-hour time slots Tech Stack: Backend: Node.js + Express Database: MongoDB Frontend: React + Tailwind CSS (we'll focus on backend in this post) Prerequisites Before we start, make sure you have: Node.js installed MongoDB running locally or MongoDB Atlas account Basic JavaScript knowledge VS Code or any code editor Project Setup 1. Initialize the Project mkdir restaurant-reservation-system cd restaurant-reservation-system npm init -y 2. Install Dependencies npm install express mongoose cors dotenv npm install nodemon --save-dev 3. Create Project Structure restaurant-reservation-system/ ├── models/ │ └── Reservation.js ├── routes/ │ └── reservations.js ├── config/ │ └── db.js ├── .env ├── server.js └── package.json Step 1: Set Up MongoDB Connection config/db.js const mongoose = require ( ' mongoose ' ); const connectDB = async () => { try { await mongoose . connect ( process . env . MONGODB_URI ); console . log ( ' MongoDB connected successfully ' ); } catch ( error ) { console . log ( ' MongoDB connection failed: ' , error ); process . exit ( 1 ); } }; module . exports = connectDB ; Step 2: Create Reservation Model models/Reservation.js co
AI 资讯
Environment Variables the Safe Way
Why Environment Variables Matter Every app has secrets: API keys, database URLs, admin passwords. Hardcoding them in source code is a one-way ticket to leaks. Even if your repo is private, you never know who forks it or what CI logs expose. Environment variables are the standard way to keep configuration out of code. But using them safely requires a few habits that go beyond just process.env . The Basics: Loading and Accessing In Node.js, you read env vars with process.env . But you should not access them raw everywhere. Create a central config module that validates and exposes them. // config.js const required = [ ' DB_URL ' , ' API_KEY ' , ' PORT ' ]; for ( const key of required ) { if ( ! process . env [ key ]) { throw new Error ( `Missing required env var: ${ key } ` ); } } module . exports = { dbUrl : process . env . DB_URL , apiKey : process . env . API_KEY , port : parseInt ( process . env . PORT , 10 ), }; Fail fast at startup. If a required variable is missing, crash immediately rather than failing later in a confusing way. Never Commit .env Files Tools like dotenv load variables from a .env file for local development. That file must stay out of version control. Add .env to your .gitignore immediately. Also add .env.local , .env.production , etc. if you use them. Instead of committing the actual values, commit a .env.example with placeholder or fake values. This documents what is needed without exposing anything. # .env.example DB_URL = postgres :// user : password @ localhost : 5432 / mydb API_KEY = your - api - key - here PORT = 3000 Use a Validation Library Manual checks are fine for small projects, but for anything serious use a schema validator like envalid or joi . They give you type coercion, defaults, and clear error messages. // with envalid const { cleanEnv , str , num } = require ( ' envalid ' ); const env = cleanEnv ( process . env , { DB_URL : str (), API_KEY : str (), PORT : num ({ default : 3000 }), }); module . exports = env ; This catches m
AI 资讯
npm 12 Released: Install Scripts Off by Default as Registry Moves to Explicit Trust
npm 12 introduces significant security-related changes, making certain installation behaviors opt-in. Notably, script allowances are now off by default, which requires explicit approval for running scripts, including implicit builds. The update also restricts non-registry sources and addresses community concerns about security risks from automatic script execution. By Daniel Curtis
AI 资讯
Add Model Fallback to an OpenAI-Compatible Node.js App
A single model can be unavailable, rate-limited, or temporarily slow. If your application already uses an OpenAI-compatible API, a simple fallback can make testing more resilient without introducing another SDK. This tutorial uses Node.js and the official OpenAI JavaScript package. It tries one model first and switches to a second model only when the first request fails. 1. Install the SDK npm install openai 2. Store the API key outside your code On macOS or Linux: export JINZEAI_API_KEY = "your_api_key_here" On PowerShell: $ env : JINZEAI_API_KEY = "your_api_key_here" Never commit a real API key. Rotate it immediately if it appears in a public repository, screenshot, or support message. 3. Create an OpenAI-compatible client import OpenAI from " openai " ; const client = new OpenAI ({ baseURL : " https://jinzeai.cc/v1 " , apiKey : process . env . JINZEAI_API_KEY , }); 4. Add a small fallback function const models = [ " deepseek-chat " , " qwen-flash " ]; async function completeWithFallback ( messages ) { let lastError ; for ( const model of models ) { try { const response = await client . chat . completions . create ({ model , messages , }); return { model , text : response . choices [ 0 ]. message . content , }; } catch ( error ) { lastError = error ; console . warn ( ` ${ model } failed: ${ error . status ?? " unknown status " } ` ); } } throw lastError ; } const result = await completeWithFallback ([ { role : " user " , content : " Explain model fallback in one sentence. " , }, ]); console . log ( `Model: ${ result . model } ` ); console . log ( result . text ); 5. Decide which errors should trigger fallback The minimal example retries on every error so the control flow is easy to see. A production application should be more selective. Fallback may be reasonable for: rate limits; upstream server errors; temporary timeouts; a model that is unavailable to the current account. Do not silently retry authentication errors. An HTTP 401 usually means the key is missing,
AI 资讯
Support Catalog Backfill: Moderate Existing Posts and Comments in a Node.js Bulk Job
Per-tenant cost visibility changes the design: don't begin with parallel API calls; begin with a durable ledger that ties every classification result and usage record to a tenant, policy version, and source item. For a customer-support catalog backfill, the practical choice is a bounded Node.js worker that reads existing posts and comments, classifies them through a replaceable adapter, checkpoints each result, and exports tenant-scoped JSONL. Short answer: make the ledger the product of the job and the LLM call one restartable step inside it. That ordering matters when support conversations contain messy product descriptions such as “the small blue charger for the old tablet.” The moderation label decides whether the text is safe to reuse; the enrichment labels connect it to a catalog candidate. Operations still need to answer a less glamorous question: which tenant consumed the tokens? Make tenant cost visible before optimizing it Token totals belong beside decisions, not in an unrelated monthly dashboard. Record normalized input and output token counts on every completed row, then aggregate by tenantId , policyVersion , and time window. If the API reports different usage units, preserve the raw usage payload in restricted telemetry and map it explicitly; don't pretend unlike units are interchangeable. Start there. Three signals are enough for the first useful view: Signal Group by Operational question Completed items tenant, policy version Is the backfill moving? Input and output tokens tenant, model Where is consumption occurring? Review and block counts tenant, content kind Did the decision mix shift? Cost in currency should be derived from a versioned rate configuration, not baked into historical rows. Store usage and the model identifier, then apply the applicable rate when producing a report. This keeps a rate change from rewriting what the runtime actually observed. It also lets finance reproduce an invoice-period view while engineering inspects tokens per
AI 资讯
Upload Moderation: Node.js NSFW, Violence, Hate-Symbol Classification + JSON Fallback
Short answer: for media support tickets that include an image, keep classification, policy enforcement, and tenant cost accounting as three separate steps. Send the image to a multimodal chat model with a strict JSON Schema, validate the returned object locally, and send invalid or uncertain cases to review. The fallback is a queue, not a guess. That design matters because a support agent is usually triaging a complaint, not publishing a photo. The same upload might be evidence of a violent broadcast, a screenshot containing a hate symbol, or an ordinary account avatar. A boolean called safe throws away the context that the agent needs. Keep it boring. How can a Node.js image moderation flow classify risky uploads without trusting JSON? Start with a versioned taxonomy. For this media workflow, I would keep nsfw , violence , and hate_symbols as separate observations, add uncertain , and retain a short evidence string. The model describes what it can see; application code decides whether a ticket is visible, blocked, or waiting for a human. This boundary also makes an eval harness useful: a prompt change can be tested independently from the enforcement policy. The tempting shortcut is to ask for a sentence and search it for words. It feels flexible in a notebook, then becomes difficult to replay: punctuation changes the parser, a missing category looks like a negative result, and a tenant's policy cannot be reconstructed from a free-form answer. Typed output is not a safety decision, but it gives the rest of the pipeline a stable input. Here is a deliberately small adapter. The surrounding Node.js upload service can call the same contract from any language; the example keeps the model call behind an OpenAI-compatible chat client and uses environment variables for the concrete base URL and model. It does not publish an upload merely because the response parses. import json import os from openai import OpenAI MODERATION_SCHEMA = { " name " : " media_upload_labels " , "
AI 资讯
5 Advanced CLI Engineering Patterns in Node.js & Go (Building Production Tools)
5 Advanced CLI Engineering Patterns in Node.js & Go (Building Production Tools) Command line utilities (CLIs) are the backbone of modern developer workflows. From package managers to security scanners, a well-engineered CLI tool can boost developer velocity tenfold. Drawing from production patterns behind open-source CLI tools like node-reaper and port-sniper , here are 5 essential engineering patterns for building high-performance CLI utilities. 1. Graceful Process Signal Handling (SIGINT / SIGTERM) Always handle Ctrl+C cleanly to release ports, clean up temporary files, and restore cursor states. 🔴 Node.js Signal Handler Pattern: import process from ' node:process ' ; function setupGracefulShutdown ( cleanupFn : () => Promise < void > ) { const shutdown = async ( signal : string ) => { console . log ( `\n\n[INFO] Received ${ signal } . Cleaning up resources...` ); try { await cleanupFn (); console . log ( " [SUCCESS] Cleanup complete. Exiting. " ); process . exit ( 0 ); } catch ( err ) { console . error ( " [ERROR] Cleanup failed: " , err ); process . exit ( 1 ); } }; process . on ( ' SIGINT ' , () => shutdown ( ' SIGINT ' )); process . on ( ' SIGTERM ' , () => shutdown ( ' SIGTERM ' )); } 2. Interactive Terminal Prompts & Selection Instead of forcing users to memorize complex flags, provide interactive dropdown menus when flags are omitted. 🔴 Interactive Dropdown Selection: import { select } from ' @inquirer/prompts ' ; export async function promptTargetSelection ( processList : { pid : number ; port : number ; name : string }[]) { const selectedPid = await select ({ message : ' Select zombie process to kill: ' , choices : processList . map ( proc => ({ name : `Port ${ proc . port } ──► PID ${ proc . pid } ( ${ proc . name } )` , value : proc . pid , })), }); return selectedPid ; } 3. High-Speed Concurrent Task Execution in Go When scanning filesystem directories (e.g. cleaning node_modules ), use Go goroutines with worker pools for maximum IOPS efficiency. packa
开发者
Why I Chose NestJS and Never Looked Back
A few years ago, I was just another developer trying to figure out how to build things that actually work, not just things that run once and fall apart the moment real users touch them. I tried a few paths. I read a lot. I broke a lot of things. And somewhere along that road, I found NestJS. At first, it looked like just another tool. Another framework to learn, another thing to add to my resume. But the more I used it, the more I realized something. NestJS wasn't just teaching me how to build backend systems. It was teaching me how to think like someone who builds things meant to last. I did not choose NestJS because it was trendy. I chose it because it made me feel organized in a way nothing else had. It gave structure to ideas that used to feel messy in my head. It made me feel like a professional, not just someone typing code and hoping it works. Here is the lesson I want you to take from this, even if you never write a single line of NestJS code. Anything you build, whether it is software, a business, or even your own life, lasts longer when it has structure. Not rules for the sake of rules, but structure that makes room for growth without everything falling apart. That is what NestJS taught me first, before it taught me anything technical. Organize your thinking, and the work becomes easier to carry. Today, when people ask me why I still use NestJS after all this time, my answer is simple. It is not just a tool I use. It is the reason I became confident in what I do. And once you experience that kind of confidence in your work, it is very hard to walk away from it. I write these thoughts as Peace Melodi, a backend software engineer who cares deeply about building things that hold up under real pressure, real users, and real growth. If any of this resonated with you, I would love to connect. LinkedIn: https://www.linkedin.com/in/melodi-peace-406494368 GitHub: https://github.com/PeaceMelodi