Product Update

Introducing Node.js 24 Support

Ampt now supports Node.js 24 as the default runtime, bringing Web Streams, URLPattern, iterator helpers, and a pile of features that used to require an npm package.

We've been testing Node.js 24 in preview for a while, and it's now the default runtime for all new Ampt apps. Existing apps can move over by setting "runtime": "nodejs24" in the ampt section of their package.json.

A lot of what's interesting in Node 24 is "previously needed an npm package, now it's built in." That kind of feature doesn't always get a lot of attention, but it makes your node_modules smaller and your code less dependent on third-party packages.

Web Streams that actually stream

ReadableStream, WritableStream, and TransformStream are globals now. Same APIs you'd use in the browser, no imports required.

javascript
import { http } from '@ampt/sdk'; import express from 'express'; const app = express(); app.get('/ticks', async (_req, res) => { const stream = new ReadableStream({ async start(controller) { for (let i = 1; i <= 5; i++) { controller.enqueue(JSON.stringify({ tick: i, at: new Date() }) + '\n'); await new Promise((r) => setTimeout(r, 1000)); } controller.close(); }, }); res.set('Content-Type', 'text/plain'); res.set('Content-Encoding', 'identity'); const reader = stream.getReader(); for (;;) { const { value, done } = await reader.read(); if (done) break; res.write(value); } res.end(); }); http.node.use(app);

That actually streams end to end. From your function to the browser, one line at a time.

There's a small footnote. CloudFront sits in front of every Ampt app, and it buffers small responses while it decides whether to compress them. The Content-Encoding: identity header above tells it to skip the compress step. We're folding this into the runtime so future apps won't need to set the header by hand.

URL matching without a library

URLPattern is a global. You can pull route parameters out of any URL without path-to-regexp or whatever you've been using:

javascript
const pattern = new URLPattern({ pathname: '/users/:id' }); const match = pattern.exec('https://example.com/users/42'); console.log(match.pathname.groups.id); // '42'

Useful for webhook routers, lightweight URL validation, and anywhere you need something more capable than String.includes but less heavyweight than a real router.

A safer way to build regex from user input

RegExp.escape() is now available. If you've ever written a search endpoint and had to copy-paste or write your own escapeRegExp helper, this should eliminate all the edge cases:

javascript
const term = req.query.q ?? ''; const re = new RegExp(RegExp.escape(term), 'i'); const matches = corpus.filter((s) => re.test(s));

One less foot-gun in the search-endpoint pipeline.

Iterator helpers, finally lazy

You can chain .filter, .map, .take, and friends directly on any iterator now. They're lazy, which is the whole point:

javascript
function* naturals() { let i = 1; while (true) yield i++; } const firstFiveEvenSquares = naturals() .filter((n) => n % 2 === 0) .map((n) => n * n) .take(5) .toArray(); // [4, 16, 36, 64, 100]

No intermediate arrays. The chain pulls one value at a time, applies the transformations, stops when take(5) is satisfied. If you've been reaching for libraries like iter-tools to get this, you can drop the dependency.

Outbound WebSocket without the ws package

new WebSocket(url) works in any Ampt function. No imports.

javascript
const ws = new WebSocket('wss://api.example.com/stream'); ws.addEventListener('open', () => ws.send('hello')); ws.addEventListener('message', (e) => console.log(e.data));

Useful if you're talking to realtime APIs from a serverless function. LLM providers with WebSocket transports, market data feeds, anything that pushes data at you. The API is browser-compatible too, so the same code runs in the browser unchanged.

AsyncLocalStorage stops being a foot-gun

This one needs context. AsyncLocalStorage is what most frameworks use for request-scoped state. Trace IDs, tenant context, per-request loggers. The well-known warning has always been: don't use enterWith() on Lambda, because the warm-start invocation model can leak state from one request into the next.

We pressure tested this. A Node 24 Ampt deployment took alternating requests on the same warm container, half deliberately calling enterWith() to try to leak a value and half checking whether anything got through. Zero leaks. Then we forced Node back onto the legacy AsyncLocalStorage implementation (the one the warning was originally written about) and ran the same test. Still zero leaks.

The reason is the runtime architecture. Every Ampt invocation runs inside its own AsyncLocalStorage.run() bubble at the platform level. When the bubble closes, anything you attached to the async chain inside it goes with it. Combined with Node 24's improved context propagation and AWS Lambda's own invocation isolation, the classic foot-gun isn't really a foot-gun on this stack anymore.

Use enterWith() if it makes your code cleaner.

Smaller embeddings with Float16Array

If your app does anything with vector embeddings (RAG, semantic search, classification), you've probably been wondering whether you really need 32 bits per dimension. Float16Array is half the memory at 16 bits, which adds up fast when you're holding thousands of vectors:

javascript
const embedding = new Float16Array(1536); // OpenAI ada-002 dimension console.log(embedding.byteLength); // 3072 bytes (vs 6144 for Float32Array)

Most embedding workloads don't need the extra precision. Cuts your memory roughly in half.

ES modules from CommonJS code

Node 24 made require() work for ESM modules. Mixed dependency trees stop being a battle.

If you've ever installed a fresh package and gotten Error: require() of ES Module in your CommonJS code, that's gone. The library is ESM-only? Fine. require() it.

A few quality-of-life things

  • fs.globSync('*.{ts,json}') gives you built-in glob matching, drop your glob dependency for simple cases
  • Array.fromAsync(asyncIterable) drains a paginated async generator into an array in one call
  • The regex /v flag adds set notation, \p{RGI_Emoji}, and intersection and subtraction in character classes
  • dirent.parentPath is the new name for the deprecated dirent.path on readdir({ withFileTypes: true })
  • OpenSSL 3.x and Undici 7 are the floor for crypto and outbound HTTP

Heads-up: a few APIs are gone

Node 24 finally retired some long-deprecated helpers. Most importantly: util.isBoolean, util.isString, util.isNumber, and util.isObject. If your code (or a dependency) still calls them, you'll see a runtime error.

The fix is almost always one line. typeof x === 'boolean' or Array.isArray(x). Worth a quick grep before you flip the runtime.

tls.createSecurePair is also gone. That one's been deprecated since Node 6 and almost nobody uses it directly, but if you do, you'll need tls.TLSSocket.

Migrating your Ampt apps to Node.js 24

To migrate existing Ampt apps to Node.js 24, update the runtime in the ampt section of your package.json to nodejs24:

json
{ "ampt": { "runtime": "nodejs24" } }

Redeploy and you're on it.

note

Node.js 24 is now the default runtime for all new Ampt apps.

For more information about Node.js 24, see the official release announcement and documentation.

The fastest way to get things done in the cloud!