开发者
What a Language Needs Before It Can Compile Itself
Code: Megapixel99/lambda-language lm is a small low-level language I wrote: static types, explicit memory, no closures, no garbage collector, and four independent backends that emit C, WebAssembly, ARM64 and bytecode for a VM. Its compiler is about 4,400 lines of JavaScript. The obvious next question is whether the language can compile itself, and the obvious first step is the lexer, which is 129 lines. In lm the same lexer is 355 lines. That ratio is the finding, because almost none of it is lm being a verbose language. Six specific absences account for nearly all of it, and writing them down was a planned milestone rather than an afterthought: the point of porting the lexer first was to find out what the language could not do while the port was still small enough to abandon. The one that cost the most src/lexer.js has a single advance(n) that moves pos , line and col together, called from 14 places. lm had no way to take the address of a scalar local, so a function could not mutate a caller's variable, and a function returning three values would need a struct allocated on every call. So advance does not exist. All 14 sites write pos += 1; col += 1; inline, and the newline case writes the three-line variant. That is the single largest source of the size difference, and it also caused the only correctness bug in the port. Column counting inside a string literal has to skip UTF-8 continuation bytes, and because the logic is inlined rather than centralised there is no one place to fix it. The two comment scanners over-count a column in exactly the same way. They get away with it only because a comment always ends at a newline, which resets the column before anything reads it. That is worth sitting with. A centralised advance would have been fixed once and been right in all three places. Instead the code is right in one place by correction and in two others by luck, and the luck is load-bearing: change what terminates a comment and two latent bugs become live ones. Dup
AI 资讯
OpenAI Says GPT-6 Astra Runs 40 Minutes on One Task. Your Agent Loop Probably Can't.
Book: AI That Plans The series: AI in TypeScript — 5 books, from your first LLM call to agents in production — all five here My project: Hermes IDE | GitHub — an IDE for developers who ship with Claude Code and other AI coding tools Me: xgabriel.com | GitHub Your agent starts a task at 14:02. It reads the ticket, opens the repo, edits four files, runs the test suite, reads the failures, edits two more files. At 14:33 someone merges to main and the deploy rolls your pods. The process disappears mid-tool-call. At 14:34 the user hits retry. The agent reads the ticket. It opens the repo. It edits four files. Thirty-one minutes of tokens, gone, and you are paying for the second attempt at the same work. Nothing crashed in a way you would see in Sentry. The pod exited 0. Kubernetes did what you told it to do. This failure mode has been survivable for two years because runs were short. A 20-second agent run that dies gets retried and nobody notices. That is the part that just changed. The interesting number in the Astra launch is a duration OpenAI announced GPT-6 Astra on 3 September 2026. The launch coverage led with percentages, and the percentages are high. But the number that should change your architecture is on the OSWorld 2.0 line, and it is not a percentage. Here are the scores OpenAI reported at launch. All of these are vendor-reported and not independently verified at the time of writing: Benchmark OpenAI-reported score ARC-AGI-3 98.6% FrontierMath Tier 4 v2 97.6% GPQA Diamond 96% BenchCAD 95.9% DeepSWE v1.1 74.1% OSWorld 2.0 (offline subset) 72.6% On that OSWorld 2.0 subset, OpenAI reports the model spending roughly 40 minutes per task . OpenAI calls Astra a new high-water mark for autonomously controlling computer systems: filling out spreadsheets, building websites from scratch. It also says the model stays oriented better and carries multi-step workflows to the end. VentureBeat's launch writeup has the full set. Forty minutes is longer than most HTTP timeouts
AI 资讯
pnpm 12 Rewrites Package Manager in Rust, Accelerating Installs While Preserving pnpm 11 Workflows
pnpm 12 has transitioned to a native Rust implementation, maintaining compatibility with pnpm 11 commands, flags, and formats. This update enhances startup and filesystem performance, particularly when using existing caches. Community feedback highlights the performance gains while noting some trade-offs in larger artifact sizes. By Daniel Curtis
AI 资讯
How to Scale Realtime Duplicate Event Delivery: Node.js Chat Reconnects
Short answer: make the event identity durable, deduplicate at the consumer boundary, and resume from a server-issued cursor; a client-side set alone cannot make a marketplace chat room survive reconnects or an incident-response burst. The constraint is trust. A browser reconnects after a laptop sleeps, a mobile radio changes networks, or a tab is restored from the back-forward cache. It may replay its last request, lose an acknowledgement, or present an event twice. In an incident response dashboard, the same mechanics become dangerous at scale: an alert that appears twice can page two people, while a missing alert can hide the incident. I design the storage boundary first, because a pretty WebSocket demo does not answer either question. Start with an event identity that can outlive a connection Every published event needs an immutable identity scoped to the stream, not to a socket. For a marketplace chat room, I use (room_id, sequence) as the primary key and keep a globally unique event_id for tracing. The sequence is allocated by the room writer, so two reconnecting clients can compare progress without trusting wall-clock timestamps. The payload is deliberately boring. It includes the room, sequence, event ID, type, and data. A client can verify that an event belongs to the room it requested; it cannot mint a higher sequence or widen its token scope. That last rule matters more than transport choice. from dataclasses import dataclass from typing import Any @dataclass ( frozen = True ) class ChatEvent : room_id : str sequence : int event_id : str event_type : str data : dict [ str , Any ] def identity ( event : ChatEvent ) -> tuple [ str , int ]: """ The room sequence is the replay-safe identity. """ return event . room_id , event . sequence Do not use a payload hash as the only key. Two legitimate messages can have identical text, and a producer retry can produce different JSON ordering. Persist the identity and the payload together, with a uniqueness constraint,
AI 资讯
A Node.js SaaS App Field Guide to Lean KPI Telemetry and Hosted Dashboards
Short answer: For a Node.js SaaS app, start with a hosted metrics path only when the dashboard needs aggregate trends, bounded dimensions, and operational alerts. Use a detailed event store when individual customer actions must remain searchable or auditable. Define the KPI before comparing APIs. Pick this path Pick it when Main limitation Direct hosted metrics API The service is small, dimensions are controlled, and the team wants minimal infrastructure Application code owns credential, retry, buffering, and delivery decisions Collector in front of hosted metrics Several workloads need one controlled telemetry exit The collector becomes production infrastructure with its own deployment and telemetry Scraped application metrics Long-running services expose stable targets Short-lived jobs and some autoscaled runtimes need extra lifecycle planning Detailed business events Per-tenant investigation, audit, or record reconstruction matters Event search is a different job from low-friction aggregate KPI queries The least complex option is the one whose whole data path the team can test and explain. A clean dashboard is not evidence that the underlying business definition, delivery behavior, or missing-data policy is correct. How should a Node.js SaaS app choose a simple hosted metrics dashboard API? Start with the decision the metric must support. “Are completed trials falling?” is useful. “Can we put trials on a chart?” isn't. Product, engineering, and incident owners need one definition for the event, unit, time window, and exclusions. If a trial completion is retried, should it count once or twice? Settle that before sending a sample. Draw the system in words: business action -> typed measurement -> delivery path -> time-series store -> query -> dashboard -> owner . Every arrow can lose either data or meaning. This tiny diagram changes the evaluation from a screenshot contest into an engineering review of ingestion behavior, query portability, dimension limits, retenti
AI 资讯
I Built an API That AI Agents Pay in USDC — Full x402 Walkthrough (27 Endpoints, Real Transactions)
I built an Express API that AI agents (or humans, or anything with fetch ) can pay per call, in USDC, with no signup and no API key. It's live on Base mainnet with 27 paid endpoints, and I've run real settled transactions against it. This is the technical walkthrough — the code, the protocol, and the things that actually broke — not an "agentic economy" pitch. What x402 is, in 5 lines x402 resurrects the dormant HTTP 402 Payment Required status code as a real payment handshake. A client calls a paid route → the server replies 402 with payment requirements (amount, asset, network) instead of the resource → the client signs a USDC transfer on Base and replays the request with a PAYMENT header → a facilitator (a third party, or Coinbase's CDP service in production) verifies and settles the transfer on-chain → the server serves the response. No account creation, no API key issuance, no OAuth dance — the wallet address is the identity, and payment is the auth. The seller side The server is plain Express. Each endpoint is a file in endpoints/ exporting { path, method, price, handler } ; server.js loads them all, builds the x402 route table, and mounts one middleware: import { paymentMiddleware , x402ResourceServer } from " @x402/express " ; import { ExactEvmScheme } from " @x402/evm/exact/server " ; import { HTTPFacilitatorClient } from " @x402/core/server " ; import { createFacilitatorConfig } from " @coinbase/x402 " ; const facilitatorConfig = config . isMainnet ? createFacilitatorConfig ( config . cdpApiKeyId , config . cdpApiKeySecret ) : { url : config . testnetFacilitatorUrl }; // https://x402.org/facilitator, no key const facilitatorClient = new HTTPFacilitatorClient ( facilitatorConfig ); const resourceServer = new x402ResourceServer ( facilitatorClient ). register ( config . caip2Network , // "eip155:8453" on mainnet new ExactEvmScheme () ); const paidRoutes = {}; for ( const ep of endpoints ) { if ( ep . price == null ) continue ; paidRoutes [ ` ${ ep . method }
AI 资讯
n8n 'No testing function found for this credential' Fix
What actually changed You built a custom n8n node with its own credential type. The node works in a workflow. You open the credential in the n8n UI, click Test , and instead of a green checkmark you get: No testing function found for this credential. You double-check the node — credentialTest is defined in methods , testedBy is set on the credential declaration, everything compiles. n8n just refuses to see it. This is one of the most-reported custom-node issues in n8n's history — the original report is Stack Overflow q/75109822 and the underlying bug is tracked in n8n-io/n8n#8188 , with users still reproducing it on 1.58+ and 1.94 well after the original fix landed. The fix The root cause is not a missing function. It is in LoadNodesAndCredentials.ts : n8n generates nodesToTestWith in dist/known/credentials.json but only reads supportedNodes when linking a credential to its test function. For custom and community nodes the two keys never match, so the linkage is dropped and the UI shows the "no testing function" message. The fix that survives across n8n versions is to stop relying on credentialTest on the node and instead define the test directly on the credential class as an ICredentialTestRequest . Before — the linkage that breaks // credentials/MyApi.credentials.ts import { ICredentialType , INodeProperties } from ' n8n-workflow ' ; export class MyApi implements ICredentialType { name = ' myApi ' ; displayName = ' My API ' ; // ❌ testedBy points at the node's credentialTest, which the loader // never resolves for custom/community nodes. testedBy = ' MyApiNode ' ; properties : INodeProperties [] = [ { displayName : ' API Key ' , name : ' apiKey ' , type : ' string ' , typeOptions : { password : true }, default : '' , }, ]; } // nodes/MyApi.node.ts export class MyApiNode implements INodeType { methods : INodeTypeMethods = { credentialTest : async ( credentials ) => { // n8n never calls this for a custom node. const res = await fetch ( ' https://api.example.com/me '
AI 资讯
CodeHub Classroom
GitHub Classroom launched in July 2013. In August 2026, it was sunset — and a lot of instructors were left scrambling for an alternative. The official recommendations point to "yet-another-online-service" - and your school might not like you sending student information (names, emails, etc.) through unknown online solutions. That was one of my pain points (among others). So I built CodeHub Classroom . 🎉 I teach computer programming at the post-secondary level. My colleagues and I needed to adjust fast. You might be scrambling for alternatives. So I'm throwing my hat in the ring with CodeHub Classroom . What it is CodeHub Classroom is a free desktop app that follows a two step Provision and Release approach to setting up student assignments as GitHub repositories. I'm baking in a lot of the features I hoped GitHub Classroom would eventually build, but never did. The core workflow is simple and deliberately opinionated, based on over a decade of actually using GitHub Classroom day-to-day: Provision — generate each student's private repository from your assignment template. Nobody has access yet. Release — invite each student as a collaborator on their own repository, exactly when you're ready. Why it's different It's built around your whole school term , not one classroom at a time. GitHub Classroom made you manage a separate "classroom" per course section. If you taught three sections of the same course, that meant duplicate setup (and possible copy/paste errors) for shared assignments. CodeHub Classroom shows every course and section you're teaching this term in one dashboard . It runs on your computer — there's no backend. No GitHub App, no shared service account, nothing for GitHub to rate-limit. It just drives the git and GitHub CLI ( gh ) tools you probably already have installed. If you've got the GitHub CLI, you already had everything CodeHub needs. On privacy This one matters to me, so I'll say it plainly: I built CodeHub Classroom so that ALL your classroom d
AI 资讯
Why your transactional email needs a queue, not a try/catch
Almost every codebase I've inherited sends email the same way: somewhere inside a POST handler, between the database write and the response, there's an await on the mail provider's SDK. It works, for months. Then one afternoon your signup endpoint starts timing out, and it takes an hour to work out that the cause is your email provider having a bad day three thousand kilometres away. I build Pulsenote , a transactional email API, so I've spent an unreasonable amount of time in the space between "your API call returned 200" and "the message is in the inbox". This post is what lives in that gap, why a try/catch doesn't cover it, and where the line sits between "you need a pipeline" and "you're overengineering a side project". The naive version Here's the code. You've written this. // users.controller.ts @ Post ( ' signup ' ) async signup (@ Body () dto : SignupDto ) { const user = await this . users . create ( dto ); await this . mailer . send ({ to : user . email , subject : ' Confirm your email ' , html : renderConfirmation ( user ), }); return { id : user . id }; } Nine lines, obvious intent, no infrastructure. For a lot of applications this is genuinely the right answer, and I'll come back to that at the end. But let's be precise about what it costs, because "it's fine" and "I haven't measured it" are different statements. It puts a third party in your request path. Your p99 for POST /signup is now your p99 plus the provider's p99. Not their median — their tail. A slow provider becomes a slow endpoint, then no endpoint. This is the failure mode that actually takes services down. If the provider degrades to five seconds per call, every signup request holds a connection and an event-loop continuation for five seconds. Your connection pool fills, your load balancer queues, health checks fail, the pod gets restarted, and now you're down — because of email. The blast radius of a non-critical dependency became the whole endpoint. A provider 5xx loses the mail entirely.
AI 资讯
Playwright Email Testing: A Real End-to-End Tutorial (No Mocks)
Most "email testing" advice ends at stubbing the send call. You assert that your app tried to send a message, and the test goes green. That leaves the interesting half untested: whether the message actually left your infrastructure, whether the template rendered, and whether the six-digit code inside it matches the one your backend is willing to accept. This walks through the other approach — driving a real signup flow in Playwright , letting a real email get delivered to a real inbox, then reading it back over an API and typing the code into the page. No mail server to run, no shared QA mailbox to clean up. The shape of the problem A verification-email test has four moving parts: an address that is unique to this test run, the browser flow that triggers the send, a way to read the message that arrives, code extraction and the assertion. Steps 1 and 3 are the ones people get wrong, and they get them wrong in the same way: by sharing one mailbox across the suite. The moment two tests run in parallel, one of them reads the other's email. So the rule is one inbox per test , provisioned on the fly and thrown away afterwards. The inbox helper Any disposable-inbox API with a REST interface works here. I'll use MoeMail 's because it's open source and the free tier is enough for a CI suite — the shape is the same anywhere, so swap the base URL and the auth header if you use something else. // inbox.ts const API = ' https://moemail.app/api ' const KEY = process . env . MAIL_KEY ! export type Inbox = { id : string ; email : string } export async function createInbox ( ttlMs = 3 _600_000 ): Promise < Inbox > { const res = await fetch ( ` ${ API } /emails/generate` , { method : ' POST ' , headers : { ' X-API-Key ' : KEY , ' Content-Type ' : ' application/json ' }, // Omit `name` and a random local part is generated for you — which is // exactly what you want, so parallel tests can never collide. body : JSON . stringify ({ expiryTime : ttlMs , domain : ' moemail.app ' }), }) if
AI 资讯
NestJS Request Lifecycle Explained (with Cheat Sheet)
A complete guide to the NestJS request lifecycle: the exact order middleware, guards, interceptors, pipes, and filters run, and why it matters.
AI 资讯
A test said the server started. I deleted the server. It still passed.
Here is a test from a real, well run Node project: test ( ' server starts ' , async ( t ) => { const app = build () await app . listen ({ port : 0 }) t . assert . ok ( true , ' server started ' ) }) It reads fine in review. It runs green. Now delete the body of build() so the server never comes up. The test is still green, because the only thing it asserts is true . In the same file two more of these caught the error in a catch and asserted true there too, so even the failure path was green. That is not a made up example. I found it in fastify at a pinned commit and opened a PR to fix it. More on that at the end. A whole class of tests cannot fail Once you start looking, the pattern turns up in a few shapes: A literal: assert.ok(true) , expect(1).toBe(1) , a snapshot of a constant. An assertion parked in a catch the happy path never reaches, so nothing is checked when the code works and nothing is checked when it breaks. A status list that accepts both outcomes: assert.ok([200, 500].includes(res.status)) . Each one runs, counts toward coverage and guards nothing. Coverage is the trap. The line executed, so the tool that counts executed lines is happy. Whether the line would go red on a regression is a different question. It is the one that matters. Why review misses it A reviewer reading the diff sees a test called server starts , an await listen and a green tick. The name states intent. The assertion is what actually runs, yet ok(true) does not look like a problem until you stop and ask what would ever turn this test red. A missing check does not show up in a diff the way a wrong line does. Finding them I wrote a small scanner for this. No account, no config file, no network call: npx margyn-scan /path/to/repo One of its checks is cannot-fail : tests whose assertions hold whatever the code does. It also flags tests that assert nothing at all, files the build reads that git never committed, gates declared in package.json that no workflow invokes and linter exclusion
AI 资讯
Simple Hosted Metrics Dashboard API Explained (for Small Node.js SaaS with Postgres)
Choice Setup burden Incident evidence Best fit Hosted metrics API Low Good if event context is preserved Small teams with an on-call rotation Postgres plus a custom dashboard Medium Excellent for joining metrics to business records Low-volume systems with strong SQL skills Self-hosted metrics stack High Configurable, but operationally demanding Teams that already run observability infrastructure Short answer: start with a hosted metrics dashboard API, send a small set of custom application metrics from Node.js, and retain reconstruction fields in Postgres. Choose the custom Postgres path when joins are the investigation, or self-hosting when data control outweighs maintenance. That recommendation has a catch. A chart can show when enrollment failures rose, but it cannot explain which course, release, region, or feature state produced them unless those dimensions were recorded at write time. For an edtech SaaS, the real deliverable isn't a pretty dashboard. It is enough evidence to replay the story of a customer incident without guessing. How can Node.js send custom app metrics to a hosted dashboard API? Capture the dimensions an investigator can act on: metric name, timestamp, deployment identifier, region, tenant or school identifier, operation, outcome, and a bounded error class. Keep direct student data out of labels. A useful event might say that lesson_publish failed validation in the EU region on deployment 7f3c2a1 ; it should not contain a learner's name, email, answer, or free-form support message. Small is good. Stop there. Start with service-level signals tied to customer work: request count, failure count, latency distribution, queue depth, and the age of the oldest queued job. Add business-flow counters such as course publication attempts only when they answer a concrete incident question. Don't export every database column as a label. High-cardinality dimensions make charts harder to read, alerts harder to tune, and the ingestion boundary harder to reas
AI 资讯
Prompt Caching at the Edge: Using CloudFront Functions and Lambda to Speed Up Claude Calls
LLM APIs like Claude feel snappy—until latency spikes hit your users. By caching prompt‑response pairs right at the edge, you can cut round‑trip time to milliseconds. This post shows you how to make that happen with CloudFront Functions and a Lambda origin. Why Prompt Caching Matters for LLM‑Powered Apps When a user types a question, your front‑end sends the text to an LLM (large language model) API, waits for the model to generate a reply, and then shows the answer. The user experience is dominated by two things: Network latency – the time it takes for the request to travel from the user’s browser to the API endpoint and back. Model compute time – how long the LLM needs to think. Even if the model itself is fast, the network hop to the provider’s data center can add 100 ms – 300 ms, and sometimes more during traffic spikes. For a chat UI that refreshes every few seconds, those extra milliseconds feel like a noticeable lag. Prompt caching means storing the exact prompt (the user’s message) together with the response (the model’s answer) in a fast lookup table. If the same prompt arrives again within a short window, you can return the cached answer instantly, without touching the LLM provider at all. In plain English: Think of the cache as a “sticky note” on the receptionist’s desk. If someone asks the same question twice, the receptionist can hand them the note instead of calling the manager again. Freshness vs. Speed LLM responses are not immutable—new data, temperature settings, or model updates can change the answer. A short time‑to‑live (TTL) of a few minutes gives you a good trade‑off: most users repeat recent prompts, but you still get new answers after a reasonable window. Setting Up a CloudFront Distribution with an Edge Key‑Value Store The big picture Edge KV store – a tiny key‑value database that lives on every CloudFront edge node. CloudFront Function – a lightweight JavaScript snippet (max 2 MB) that runs on every request before it reaches the origin. It
AI 资讯
Frame-accurate FFmpeg trimming without re-encoding the whole file
TL;DR -c copy can only cut on keyframes, so your 12.4s trim starts wherever the last keyframe was. We'll build a smart-trim script that probes keyframe positions with ffprobe , re-encodes only the head and tail fragments, stream copies everything between them, and concatenates the three. Frame accurate output, encoding cost proportional to two GOPs instead of the whole file. Tested with FFmpeg 9.0 "Lei" (released 2026-08-04) and Node 22.x. The JS is ESM, so put "type": "module" in your package.json before running any of it. Everything here also works on FFmpeg 7.x and 8.x; nothing we use is new. The problem, in two commands 🎬 # fast, and wrong ffmpeg -ss 12.4 -i input.mp4 -t 20 -c copy fast.mp4 ffprobe -v error -show_entries format = start_time,duration -of default = nw = 1 fast.mp4 # start_time=0.000000 # duration=20.388000 <- we asked for 20, starting at 12.4 The clip is long by the distance from our requested start back to the previous keyframe, and every frame in it is shifted earlier than the user asked for. Stream copy moves compressed packets without decoding them. Most frames in a compressed stream only describe the difference from their neighbors, so the only place you can start is a keyframe. FFmpeg snaps back to the nearest preceding one, and your clip starts early. # accurate, and slow on a long source ffmpeg -ss 12.4 -i input.mp4 -t 20 -c :v libx264 -crf 20 -c :a aac slow.mp4 We want the accuracy of the second and roughly the cost of the first. 1. Look at your keyframes first Before writing any code, find out how bad the problem is for your content: ffprobe -v error -select_streams v:0 \ -show_entries packet = pts_time,flags \ -of csv = print_section = 0 input.mp4 | grep 'K' | head -20 0.000000,K__ 2.002000,K__ 4.004000,K__ 6.006000,K__ Two second GOPs here, so worst-case error is about two seconds. Screen recorders and some camera output emit keyframes on scene change only, and there the gaps can be 30 seconds or more. That distribution is the real spe
AI 资讯
Past the README Demo: Conversations, Healthcare Data, Agents, and CI Checks
"Extract a name and email from this sentence" is the easy 10% of structured output. The other 90% is everything that doesn't fit in one prompt, one turn, or one model call. Here are five things shapecraft handles once you're past the basics. 1. Collecting data across a whole conversation A single message rarely has everything you need. Someone books an appointment over three or four back-and-forth messages, not one. turnaround mode lets the conversation run naturally and validates the whole transcript once, at the end, against one schema: import { generate , openai } from " @aviasole/shapecraft " ; const result = await generate ( model , BookingSchema , conversationHistory , { turnaround : true , }); No manual "do I have everything yet?" tracking, no partial-state bugs, just one validated object once the conversation is actually complete. 2. Extracting from clinical notes into real FHIR shapes Healthcare data has a standard (FHIR R4) and it's not optional if you're integrating with anything real. Built-in presets mean you're not hand-writing a Patient or Observation schema from scratch: import { generate , openai } from " @aviasole/shapecraft/fhir " ; import { PatientSchema } from " @aviasole/shapecraft/fhir " ; const patient = await generate ( openai ({ model : " gpt-4o-mini " }), PatientSchema , clinicalNote ); Same retry/validation guarantees as any other schema, just pre-built to match a spec you'd otherwise have to implement yourself. 3. An agent that checks real data before answering "Is this order still on hold?" isn't answerable from the prompt alone, it needs an actual lookup. generateWithTools() lets the model call your functions, see the results, and then produce a validated final answer: import { generateWithTools } from " @aviasole/shapecraft " ; const result = await generateWithTools ( model , [ lookupOrder ], AnswerSchema , userQuestion ); The tool call's arguments are validated before your function ever runs, and the final answer goes through the sam
AI 资讯
Fintech Shipment Fan-Out: SaaS Retention Cleanup and the Node.js Cron-Queue Boundary
Short answer: use a scheduled cleanup endpoint when one indexed, bounded pass can finish predictably; use a queue when cleanup must be divided into independently retriable batches. For a fintech SaaS that fans out shipment updates to many subscribers, latency and cost should be judged at the system boundary: a cheap cleanup run is not a good bargain if it contends with delivery or leaves retention evidence incomplete. The first design decision is to keep shipment fan-out separate from retention work. A shipment update has a latency-sensitive path. Expired subscriptions, old delivery attempts, and temporary fan-out records usually have a policy-driven path. They may share a database, but they should not share an unbounded transaction or an execution budget. This distinction matters more than the spelling of a cron expression. It also gives the team a useful test: can the cleanup be repeated safely while the shipment update path continues to make progress? How should a Node.js SaaS choose a cron or queue for scheduled cleanup? Measure the worst case first. Count eligible records by tenant, check the relevant index, estimate lock pressure, and measure a bounded pass while the database is serving normal shipment traffic. The median duration is not the decision variable; the tail is. A scheduled data cleanup is a good fit for one HTTP-triggered run when its cutoff, tenant scope, batch size, and completion state can be recorded and the run has room to finish before its execution limit. The cutoff should be computed by the application and persisted with the run. A schedule has jitter, and a paused schedule may not replay every missed invocation. “Delete records older than the cutoff captured at run start” is therefore more auditable than silently recalculating the boundary for every page. The query should also exclude legal holds, active disputes, and any retention exception required by the business policy. Keep it bounded. The boundary is operational. When a tenant can mo
AI 资讯
RFLCT: Bringing Runtime Type Metadata to TypeScript 7
If you've built large-scale applications in TypeScript, chances are you've used a Dependency Injection (DI) container. As the creator of InversifyJS, I've spent years thinking deeply about inversion of control, decoupling, and how to make enterprise patterns feel natural in TypeScript. But for all those years, there has been a glaring elephant in the room: our heavy reliance on experimentalDecorators and emitDecoratorMetadata . These compiler flags have served us well, but they are exactly that— experimental . They tie us to legacy decorator implementations, require specific compiler configurations, and often feel like a magic black box that doesn't perfectly align with modern build pipelines. I've spent a lot of time recently thinking about how we could finally drop these flags entirely while keeping the developer experience pristine. With the release of TypeScript 7, I'm thrilled to introduce the solution: 🪞 RFLCT . What is RFLCT? RFLCT is an ahead-of-time (AOT) reflect metadata injector for TypeScript 7. It injects design:symbols and design:arguments directly at build time. Zero decorators. Zero emitDecoratorMetadata . It integrates seamlessly with virtually any build tool (Vite, Rollup, webpack, esbuild) via unplugin , or you can use the built-in CLI using the TypeScript 7 API for standalone tsgo projects. Let's look at how it actually feels to write code with RFLCT. The Magic: Before and After With RFLCT, you annotate the types you want to expose to your runtime metadata using a special Reflect<T> wrapper type. What you write: import { Reflect , resolve } from " rflct " ; interface Shape { sides : number ; } class Polygon { constructor ( public shape : Reflect < Shape > , public label : Reflect < string , { optional : true } > ) {} } // resolve<T>() → the runtime identity of T (Symbol for interfaces, class for classes) container . bind ( resolve < Shape > ()). to ( Polygon ); What RFLCT compiles it to: Notice how the interfaces are safely converted into global
AI 资讯
From SOLID to Composition, Dependency Injection, and IoC: How Angular, Spring, and Node.js Differ
When learning Angular, Spring, and Node.js, I often came across terms like SOLID, Dependency Injection (DI), Inversion of Control (IoC), IoC Container, and Composition . At first, these concepts can feel like they are all the same thing. They are not. The key realization is: SOLID is about how we design software. Composition is about how we build larger systems from smaller pieces. Dependency Injection is a technique for providing those pieces. IoC containers automate that process. Understanding this relationship makes Angular, Spring, and Node.js architectures much easier to reason about. 1. SOLID Is a Design Principle, Not a Framework Feature SOLID is a collection of software design principles. For example, Single Responsibility Principle (SRP) says that a component should have a focused responsibility. Instead of having one class responsible for HTTP handling, database access, validation, email, and payment processing, we can separate those responsibilities: Controller ↓ Service ↓ Repository ↓ Database Each part has a focused job. Similarly, the Open/Closed Principle (OCP) encourages us to design components that can be extended without constantly modifying their existing implementation. These principles don't require Angular, Spring, or an IoC container. You can follow SOLID in plain JavaScript. 2. Composition Is the Bigger Idea Composition means: Build a larger behavior by combining smaller, focused pieces. This works in both functional and object-oriented programming. In functional programming: function A ↓ function B ↓ function C A larger function can be created by composing smaller functions. In object-oriented programming: OrderService │ ├── PaymentService └── EmailService OrderService is composed using other objects. The important relationship is often: HAS-A rather than IS-A For example: OrderService HAS-A PaymentService rather than: OrderService IS-A PaymentService This is one reason composition is often preferred over deep inheritance hierarchies. 3. Dep
AI 资讯
I built an RPG that teaches Claude Code by making you actually use it
Most tool documentation teaches by just telling you things. You read a page about /model , or hooks, or subagents, nod along, and forget it by the time you'd actually need it. I wanted something closer to how people actually learn a CLI, by using it, with something checking whether you did the thing right or not. So I built claude-quest , a text RPG that runs entirely inside a real claude session and teaches the Claude Code CLI zero to hero. the idea isn't new, I borrowed it This is basically GameShell 's philosophy applied to Claude Code. GameShell teaches Unix shell commands by dropping you into a real shell wrapped in a themed fake filesystem, and grading your progress by checking real shell/filesystem state instead of asking you to self report or answer a quiz question. claude-quest does the same thing, except the "filesystem" is a real Claude Code environment. missions live in real sandbox directories, and progress is checked by inspecting what you actually did, config files you wrote, hooks that fired, tool calls that happened. what it actually looks like There's no separate app, no fancy terminal UI, none of that. You say "let's play claude quest" inside a normal claude session, and Claude itself becomes the game master. It reads real output from the engine and narrates on top of it, something like this: > lets play claude quest **The Gatehall** You've just been let through the outer gate. The tower keeps no secrets from those who bother to read the walls... **Mission: First Contact** (Tier 1, Artifact) Create a CLAUDE.md file in this sandbox recording how to run the project's tests. Let me know when you're done and I'll check it. > done CLAUDE.md records how to run the tests. Claude Code will know next time. MISSION_STATUS: complete **What you actually learned:** Claude Code reads a file named CLAUDE.md in your project root automatically, at the start of every session... that's it, that's the whole interface. it's just a chat, with real commands running unde