npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

better-mastra

v0.4.0

Published

A small layer on Mastra and the AI SDK: model registry, chat turns, resumable streams and chat clients for React and Vue.

Readme

better-mastra

A small layer on Mastra and the AI SDK: model registry, chat turns, resumable streams and chat clients for React and Vue.

Work in progress.

Runtime presets

Storage and memory presets are synchronous; pools connect on their first query, so a process can build its Mastra at import time:

import { Mastra } from '@mastra/core'
import { createMemory, shutdown } from 'better-mastra' // presets come from the package root
import { openRouterRoute } from 'better-mastra/openrouter'
import { pgStorage } from 'better-mastra/pg' // or libsqlStorage from 'better-mastra/libsql'

const storage = pgStorage({ connectionString: process.env.DATABASE_URL })
const models = openRouterRoute({ apiKey: process.env.OPENROUTER_API_KEY })
const memory = createMemory({ storage: storage.store, observer: models.model('google/gemini-3.5-flash') })
export const mastra = new Mastra({ storage: storage.store })

await storage.init() // once, in the process that migrates

pgStorage and libsqlStorage from the package root are the same presets loaded on call (they return promises); they stay for compatibility.

Resumable streams (better-mastra/resumable)

Opt-in. A dropped connection no longer aborts the turn: the client reconnects with AI SDK reconnectToStream (useChat({ resume: true })) and gets a full replay of the current assistant message plus the live tail (resumeStreamIsReplay: true, the DefaultChatTransport default).

import { RedisClient } from 'bun'
import { bunRedis, createResumableStreams } from 'better-mastra/resumable'

const resumable = createResumableStreams({ redis: bunRedis(new RedisClient(process.env.REDIS_URL)) })

// POST /chat/:threadId — do not tie the run to request.signal
const abort = new AbortController()
const stream = await resumable.start({ threadKey: threadId, stream: uiMessageStream, abort })

// GET /chat/:threadId/stream
const resumed = await resumable.reconnect(threadId, request.signal)
if (!resumed) return new Response(null, { status: 204 })

// POST /chat/:threadId/stop — works from any replica
await resumable.stop(threadId)

// shutdown: waits for active turns (drainTimeoutMs)
await resumable.close()

Use ioRedis(client) for ioredis. Both adapters open extra connections with duplicate(): one subscriber per replica, and one per cross-replica reader (for XREAD BLOCK).

Use upstashRedis(client) for @upstash/redis. Upstash has no blocking XREAD, so a cross-replica reader polls every pollMs (200 ms); pass () => Redis.fromEnv() to create the client on first use.

An app that already holds a per-thread lock can keep the locator in the lock's value with locator: { read, write, expire }. The locator then lives exactly as long as the lock. expire renews it while the turn runs and shortens it to graceMs after; leave it out when the lock renews itself:

createResumableStreams({
	redis: upstashRedis(() => Redis.fromEnv()),
	locator: {
		read: (threadId) => readLockValue(threadId), // what follows the lock token
		write: (threadId, locator, ttlMs) => writeLockValue(threadId, locator, ttlMs),
		expire: (threadId, ttlMs) => redis.pexpire(lockKey(threadId), ttlMs)
	}
})

How it works: chunks are buffered in memory (consecutive text-deltas merged) and fanned out to subscribers. A per-thread locator { streamId, owner } in Redis says which replica owns the turn. Reconnecting to the owner is served from memory. Reconnecting to another replica publishes a spill request on the owner's control channel; the owner writes its buffer in one XADD, then mirrors new chunks in 50 ms batches (first one immediate) until a terminal marker, and the reader follows with XREAD BLOCK.

Redis ops for a ~1,500-chunk turn:

| Design | Redis ops | |---|---| | Mastra durable agents (CachingPubSub: cache write + publish per chunk) | ~3,000–4,500 | | Coalesced SETEX + PUBLISH (hellu support-turn) | ~600 | | This, no reconnect | 2 (locator SET, final PEXPIRE) + 1 PEXPIRE per 10 s of turn | | This, same-replica reconnect | +0 for a running turn, +1 GET for a finished one | | This, cross-replica reconnect | +1 PUBLISH, +1 XADD and EXPIRE per 50 ms batch, reader XREADs |

Failure behaviour: a buffer over maxBufferBytes (2 MB) keeps streaming but new reconnects get 204. If the owner crashes, its locator expires and the cross-replica reader ends with an error chunk after idleTimeoutMs; the client reloads the thread from Mastra memory. If Redis is down, turns still run and same-replica reconnects work; cross-replica reconnects return 204. Finished turns stay resumable for graceMs (30 s).

Per-user request limits (better-mastra/usage)

admitUserRequest charges one request against every window of a user's operation at once, or refuses it with the seconds until the fullest window has room. The keys hold an HMAC of the user id, never the id itself. Pass any Redis client's EVAL:

import { admitUserRequest } from 'better-mastra/usage'

const admission = await admitUserRequest({
	runScript: (script, keys, args) => redis.send('EVAL', [script, String(keys.length), ...keys, ...args.map(String)]),
	secret: process.env.AUTH_SECRET,
	keyPrefix: 'app:ai-usage',
	operation: 'receipt',
	userId,
	windows: [
		{ name: 'minute', seconds: 60, limit: 5 },
		{ name: 'day', seconds: 86_400, limit: 50 }
	]
})
// { kind: 'admitted' } | { kind: 'limited', retryAfterSeconds } | { kind: 'unavailable', error }