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

@mexty/database

v0.1.1

Published

Persistent per-block data storage for Mexty blocks

Downloads

90

Readme

@mexty/database

Persistent data storage for Mexty blocks. Rows survive the session, so a learner's answer is still there tomorrow, and a block later in a learning path can read what an earlier one collected.

If you want live state shared between people in the room right now, that is @mexty/multiplayer — it is gone when the room empties. Use this when the data has to outlive the visit. The two work together.

Before you write any code

Linking a database to a block is Mexty staff only, and it does not happen in code. A staff member creates the database at /dashboard/databases and attaches it to the block from the blueprint editor's Mexty database property. This package reads whatever id ended up in the block's props.

Two things are required, and the first is the one that gets forgotten:

1. Declare the property in your BlockProps interface, verbatim:

/** Mexty database this activity reads and writes. Staff-managed. */
mextyDatabaseId?: string;

Without that line the property cannot be linked from the editor at all — the editor only offers keys it finds in your block's parsed BlockProps schema. Declaring it is what makes the block linkable.

2. Add the dependency. The block scaffold does not ship it:

"dependencies": {
  "@mexty/database": "^0.1.0"
}

A range rather than the exact pin @mexty/multiplayer uses, and deliberately. A block's dependency is resolved when the block is built, and the fixes that matter here are client-side ones a block author cannot apply themselves — a session that stops renewing, say. An exact pin means every block has to be edited to receive one. ^0.1.0 picks up 0.1.x on the next rebuild and never resolves to a version that is not on the registry yet.

⚠️ Everything in a database is readable

Any viewer who can open any block linked to a database can read every row in it, including other learners' rows. This is deliberate — it is what makes a shared leaderboard or a class-wide wall work — but it means:

  • Never store secrets, contact details, or anything you would not print on the page.
  • Store only what the learner would expect other learners to see.
  • Rows carry an opaque ownerId (a user id, or anon:…) and never an email or a display name.

Writing is narrower than reading: a row belongs to whoever wrote it, and only they — or staff — can change it.

Usage

Basic — one row per learner

import { useMextyDatabase, useMextyRecord } from '@mexty/database';

function MyBlock({ mextyDatabaseId }: BlockProps) {
  const db = useMextyDatabase(mextyDatabaseId);
  const { value, save, loading } = useMextyRecord(db, 'profile', 'main', {
    nickname: '',
  });

  if (loading) return <p>Loading…</p>;

  return (
    <input
      value={value.nickname}
      onChange={(e) => save({ ...value, nickname: e.target.value })}
    />
  );
}

save upserts the caller's own row and is debounced, so calling it on every keystroke is the intended usage, not an abuse of it.

Everyone's rows

import { useMextyRecords } from '@mexty/database';

const { records, reload } = useMextyRecords(db, 'answers');
const mine = useMextyRecords(db, 'answers', { mine: true });

records is a snapshot, not a live feed — call reload() after you write, or pair the database with @mexty/multiplayer if viewers need to see each other's writes as they happen.

Appending rows

await db.insert('answers', { choice: 'b', correct: true });

insert appends a new row every call and is not debounced. Never call it in a render or from an unthrottled handler.

Handling the states a block can be in

const db = useMextyDatabase(props.mextyDatabaseId);

if (db.status === 'unauthenticated') return <SignInPrompt />;

db.status is one of:

| status | meaning | |---|---| | ready | linked and reachable — reads and writes work | | no-database | no database is linked to this block | | sandbox | the block is not running with a blockId | | unauthenticated | this database needs a signed-in viewer and there is none |

Render normally on all four. A non-ready client resolves reads empty and makes writes a no-op, so a block whose database was never linked still works — which is its normal state in the marketplace, and after anyone forks it.

API

useMextyDatabase(databaseId?)

The client for this block. Recreated only when the id changes, so it is safe to call straight from props. Re-renders when status or callerId changes.

useMextyRecord(db, collection, key, initial)

{ value, save, loading, error, record } — one row, this viewer's own, upserted at key. value updates immediately; the debounced write follows.

useMextyRecords(db, collection, options?)

{ records, loading, error, reload }. Options: mine, key, courseId, programId, limit (server caps at 200).

The client directly

db.list(collection, options?)   // rows, newest first
db.get(collection, key)         // this viewer's row at key, or null
db.set(collection, key, data)   // upsert, debounced
db.insert(collection, data)     // append
db.update(recordId, data)       // replace a row's data (owner or staff)
db.remove(recordId)             // delete a row (owner or staff)
db.flush()                      // await pending debounced writes
db.callerId                     // which owner the server sees you as
db.context                      // blockId, courseId, programId… from the URL

Semantics & limits

  • Collections are [a-zA-Z0-9_-], 1–64 characters. Keys additionally allow . and :, up to 128.
  • Rows are plain JSON, 64 KB each. No Date, Map, Set, class instances or functions — store an ISO string, not a Date.
  • set is per (collection, key, viewer). Two viewers writing the same key get their own rows; the same viewer writing it twice replaces theirs.
  • update replaces data wholesale. Read, modify, write if you want a merge.
  • Every database has a row ceiling (100,000 by default). Past it, appends fail with a clear error while keyed upserts keep working.
  • Rows written from an editor preview are flagged as test data and excluded from staff exports, so trying your own block out does not pollute results.
  • A 429 is retried with backoff. The budget is 3000 requests per 5 minutes, per viewer, shared with everything else the page does.
  • An expired session is renewed once and the request replayed, so a block does not go quiet ten minutes into a visit. unauthenticated means the renewal failed too, and the viewer really is signed out.
  • Do NOT call configure() on the platform. The server URL is resolved from the page's hostname; overriding it points a block at the wrong environment.

Development

npm install
npm run typecheck
npm test
npm run build