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

@may-db/core

v0.1.10

Published

Build collaborative apps, not backends.

Readme

@may-db/core

Build collaborative apps, not backends.

MayDB is a client-side "Bring your own server" database for collaborative, realtime applications.

  • Developers are not responsible for a backend server
  • Users select a Matrix provider to store their data

Install

pnpm add @may-db/core @may-db/react

Compatibility policy

It is early days.

@may-db/[email protected] is validated against this dependency pair:

Support policy:

  • Treat matrix-crdt and matrix-js-sdk as a tested pair.
  • Avoid overriding either package version unless you are validating the combo yourself.

Yjs import rule

Use the Yjs instance re-exported by may-db:

import { Y } from "@may-db/core";

Do not import from yjs directly in app code. Using different Yjs module instances can break constructor checks and CRDT behavior.

Key concepts

Room: The building block of MayDB. Each room has:

  • Users who can edit it
  • Users who can view it
  • A Yjs CRDT containing data
  • Delegate rooms, whose members can act as members here
  • A room in Matrix with corresponding users, containing the data

Walkthrough

This example builds a collaborative note app: create notes, edit them live with others, and share them by Matrix ID.

Schema

Start by defining your room types and indexes, so you can list and query them.

import type { MayDbSchema } from "@may-db/core";

const SCHEMA: MayDbSchema = {
  roomTypes: { note: {} },
  indexes: {
    notes: {
      scope: "private",
      roomTypes: ["note"],
      columns: {
        roomName: { source: { kind: "system", field: "roomName" } },
      },
      defaultOrderBy: { column: "roomName", direction: "asc" },
      maxEntries: 5000,
    },
  },
};

The notes index is private, so it automatically tracks rooms this user has created or been shared into.

Authentication

useMayDb handles Matrix authentication and wires up the session.

import { useEffect, useMemo, useState } from "react";
import type { MayDb } from "@may-db/core";
import { useMayDb, useRoom, useMayDbIndex, useMayDbQuery } from "@may-db/react";

export function TinyNoteApp() {
  const { status, db, login } = useMayDb({
    namespace: "com.may-db.tiny-note",
    schema: SCHEMA,
  });
  const [roomId, setRoomId] = useState<string | null>(null);

  if (status === "signed_out") {
    return <button onClick={login}>Log in with Matrix</button>;
  }

  if (status !== "ready" || !db) {
    return <p>Loading...</p>;
  }

  if (roomId) {
    return <TinyNoteRoom db={db} roomId={roomId} onBack={() => setRoomId(null)} />;
  }

  return <NoteList db={db} onOpen={setRoomId} />;
}

namespace scopes all rooms and indexes to your app. Rooms created by other apps never appear here, even on the same homeserver.

Querying for Rooms

useMayDbIndex opens an index, and should be inserted at the top level of your application. useMayDbQuery queries it reactively.

function NoteList({ db, onOpen }: { db: MayDb; onOpen: (id: string) => void }) {
  const { index } = useMayDbIndex({ db, indexName: "notes" });
  const { items } = useMayDbQuery({
    index,
    after: null,
  });

  return (
    <main>
      <button
        onClick={async () => {
          const room = await db.rooms.create({
            name: "Untitled Note",
            roomType: "note",
          });
          onOpen(room.id);
        }}
      >
        New note
      </button>
      <ul>
        {items.map((item) => (
          <li key={item.roomId}>
            <button onClick={() => onOpen(item.roomId)}>
              {String(item.values.roomName)}
            </button>
          </li>
        ))}
      </ul>
    </main>
  );
}

By default, useMayDbQuery matches all rows (where: {}), uses the index defaultOrderBy, and returns up to the index maxEntries. Rooms you create appear here immediately. When another user shares a room with you, it appears in your list after a few seconds — no extra step needed in app code.

useMayDbQuery supports exact-match filtering per column and cursor pagination via after.

Collaboration

useRoom provides a Yjs doc and syncs it in real time with other users in the room. Call room.members.invite to share the room with someone by their Matrix ID.

function TinyNoteRoom({
  db,
  roomId,
  onBack,
}: {
  db: MayDb;
  roomId: string;
  onBack: () => void;
}) {
  const { room, doc, ready, canEdit } = useRoom({ db, roomId });
  const note = useMemo(() => doc.getText("note"), [doc]);
  const [, invalidate] = useState(0);
  const [invitee, setInvitee] = useState("");

  // Minimal Yjs -> React subscription to illustrate. Libraries exist to handle this.
  useEffect(() => {
    const onChange = () => invalidate((n) => n + 1);
    note.observe(onChange);
    return () => note.unobserve(onChange);
  }, [note]);

  if (!ready) {
    return <p>Syncing room...</p>;
  }

  return (
    <main>
      <button onClick={onBack}>← Notes</button>
      <textarea
        value={note.toString()}
        onChange={(e) => {
          const next = e.target.value;
          note.doc?.transact(() => {
            note.delete(0, note.length);
            note.insert(0, next);
          });
        }}
        disabled={!canEdit}
      />
      <form
        onSubmit={async (e) => {
          e.preventDefault();
          await room.members.invite(invitee.trim(), "editor");
          setInvitee("");
        }}
      >
        <input
          value={invitee}
          onChange={(e) => setInvitee(e.target.value)}
          placeholder="@user:matrix.org"
        />
        <button type="submit">Share note</button>
      </form>
    </main>
  );
}

The Yjs doc is a CRDT. It supports long text, as well as hierarchical JSON-like structures, both allowing safe concurrent edits to different parts of the doc.

API surface (app-facing)

  • useMayDb({ namespace, schema }) -> Matrix auth/session + db
  • db.rooms.create/open/getOrCreateSingleton(...)
  • useRoom({ db, roomId }) -> room, doc, ready, canEdit, name
  • room.members.* to share a room with users
  • room.delegates.* to share a room with the members of other rooms
  • useMayDbIndex({ db, indexName }) -> reactive index handle
  • useMayDbQuery({ index, after, where?, orderBy?, limit? }) -> items, hasMore, loading
  • useRoomPresence(...) to exchange presence/cursor with other users