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

@makoai/app-sdk

v2.7.2

Published

Mako app SDK: data bindings (useQuery/useDuckDB), URL state, theme, and the house dashboard kit (/ui).

Readme

@makoai/app-sdk

The runtime SDK for Mako data apps — React hooks over an app's data bindings, a Vite plugin that serves those bindings during a local vite dev, and Mako's house dashboard kit (@makoai/app-sdk/ui).

Apps depend on the published package with a caret range ("@makoai/app-sdk": "^2.7.0"), and Mako's deploys move every app to the newest release within that range — fixes and design updates reach apps without anyone copying files. Do not vendor it into a workspace repo.

Install

Apps use pnpm (new apps pin it in packageManager; Mako installs each app with the package manager its lockfile names):

pnpm add @makoai/app-sdk

Do not run npm install in a pnpm app — it ignores pnpm-lock.yaml and writes a competing package-lock.json.

In the app

import { useQuery, useDuckDB, useSearchParams, useTheme } from "@makoai/app-sdk";

// Rows of bindings/<name>.sql, materialized to parquet by Mako.
const { data, loading, error } = useQuery("latest_sales");

// Analytical SQL over every binding (DuckDB-WASM in the browser; table
// names are binding names).
const totals = useDuckDB("select country, sum(revenue) r from latest_sales group by 1");

// Rematerialize on demand: re-runs the binding's query on the warehouse,
// then every hook reading it re-renders with the new rows. The old rows
// stay on screen while `refreshing` is true.
const { refresh, refreshing } = useQuery("latest_sales");
<button onClick={() => refresh().catch(e => alert(e.message))} disabled={refreshing}>
  Refresh
</button>

useDuckDB(...).refresh() refreshes every binding; refreshBinding(name) / refreshBindings() do the same outside a component. A refresh POSTs to __data/<name>/refresh, the data URL's sibling, so whoever serves the app's data (Mako, the sandbox dev server, the Vite plugin below) rebuilds it with its own authorization: a signed-in member can always refresh; a public share only when its owner enabled live queries, and at most once every few minutes per binding. A refused refresh rejects with status (403, 429 + retryAfterMs) or 502 with the query's error.

useLocation / useSearchParams / navigate keep filter state in the URL; useTheme follows the OS preference. Theme tokens (--background, --chart-1, …) match the ones the scaffold's styles.css declares.

Data arrives from __data/<name>.parquet, relative to the page — the same path in Mako's sandbox, in a published app, and on a laptop.

Who is looking: useViewer()

import { useViewer } from "@makoai/app-sdk";

const { viewer, loading } = useViewer();
// viewer === null       → anonymous share link (or still loading)
// viewer.email          → "[email protected]"
// viewer.workspace.role → "owner" | "admin" | "member" | "viewer" | null
// viewer.app.role       → "owner" | "editor" | "viewer" | null

Mako resolves the viewer server-side from the session or the signed view token — the page cannot forge it — and reports only what the platform knows: identity, the workspace and the person's access role in it, and their role on this app. There is no job title, team or country in the platform, on purpose: those are your data. Put a roster in a binding and join on the email:

-- bindings/viewers.sql
SELECT lower(email) AS email, team, country, is_lead FROM hr.people
const { viewer } = useViewer();
const me = useDuckDB(
  viewer ? `select * from viewers where email = '${viewer.email.replace(/'/g, "''")}'` : "select 1 where false",
);
const board = useDuckDB(
  me.data?.[0]?.is_lead
    ? "select * from pipeline"
    : `select * from pipeline where team = '${me.data?.[0]?.team ?? ""}'`,
);

Keep the roster binding to the columns the app needs for its logic: every binding the app can read is downloaded whole into the viewer's browser, so this shapes the UI rather than enforcing access. Who may open the app is the app's access setting in Mako; server-side row filtering is a follow-up on the same identity (apps.md §28).

The house style: @makoai/app-sdk/ui

Importing the SDK injects Mako's theme tokens (stone, warm canvas, #527df2 brand; light and dark): --background, --foreground, --card, --border, --muted-foreground, --brand, --canvas, --positive / --warning / --negative, --chart-1…--chart-5, … Style with these names and the app follows the house palette and dark mode for free. Override a token in your own stylesheet to re-theme; do not paste the whole block.

The kit is the dashboard furniture every app was re-forking:

import "@makoai/app-sdk/ui.css"; // before ./styles.css, so the app wins
import {
  PageHeader, Card, KpiRow, KpiTile, Button, StatusDot,
  MultiSelect, FreshnessBadge, RefreshAllButton,
} from "@makoai/app-sdk/ui";

<PageHeader
  title="Spain renewals"
  subtitle="Contracts up for renewal in the next 90 days"
  actions={<>
    <FreshnessBadge tone="ok" headline="Synced 12m ago"
      title="Data as of 09:05" schedule="Refreshes 06:30 / 18:30 Europe/Zurich"
      sources={[{ label: "Stripe", value: "09:05", tone: "ok" }]} />
    <RefreshAllButton bindings={__APP_BINDING_NAMES__}
      labels={{ renewals: "Renewals" }} last={["data_freshness*"]} />
  </>}
/>
<KpiRow>
  <KpiTile label="Up for renewal" value="412" delta="+8%" tone="ok" hint="vs last quarter" />
</KpiRow>
<MultiSelect options={["CH", "ES", "IT"]} value={countries} onChange={setCountries} allLabel="All countries" />
  • MultiSelect — controlled (value / onChange). An empty selection means "all" unless emptyMeansAll={false} (then "all" = every value selected). Options are strings or { value, label, color }; showOnly adds an "Only" button per row. On phones the menu is a bottom sheet.

  • FreshnessBadge — presentational: the app decides what fresh means and passes tone (ok / warn / bad, or green / amber / red), a headline, and per-source rows; action is a callout slot (e.g. a "Load latest" button).

  • RefreshAllButton — rebuilds every binding (refreshBinding), concurrency at a time (default 3 — keep it low), first / last / skip patterns (* suffix = prefix), with a progress modal. Give it the binding names with a build-time define in vite.config.ts:

    import { readdirSync } from "node:fs";
    // inside defineConfig({ … }):
    define: {
      __APP_BINDING_NAMES__: JSON.stringify(
        readdirSync(new URL("./bindings", import.meta.url))
          .filter(f => f.endsWith(".sql")).map(f => f.slice(0, -4)),
      ),
    },

    and declare it once (src/vite-env.d.ts): declare const __APP_BINDING_NAMES__: string[];

ui.css is plain CSS (no Tailwind needed; it coexists with Tailwind) and every kit class is prefixed mk-. It also sets the house base: Inter 14px on the warm --canvas.

In vite.config.ts

import { makoData } from "@makoai/app-sdk/vite";

export default defineConfig({ plugins: [react(), makoData()] });

makoData() answers __data/index.json (the app's bindings/*.sql) and __data/<name>.parquet during vite dev from the Mako API, and what it asks for is your local binding file: it sends the text of bindings/<name>.sql and gets back the parquet of exactly that query, run read-only through the workspace connection its front matter names. When the text is the committed binding, that is the app's stored artifact (built on first request if it never was); when you have edited it, Mako builds a draft from your text and hands it back without storing it — nobody else, and no published viewer, ever sees uncommitted SQL. Building needs edit access to the app; read-only members get committed artifacts. POST __data/<name>/refresh (the SDK's refresh()) rebuilds from the local text on demand.

Builds run as jobs: the plugin asks for one (async), gets a job id back at once, and polls it until the parquet is ready — so a query that runs for minutes is waited for instead of failing when a proxy cuts the request at 100 s. pollIntervalMs / buildTimeoutMs tune the wait (1 s, 30 min).

Results are cached under node_modules/.mako-data/ for five minutes (revalidateMs; ?refresh bypasses), next to a fingerprint of the text they were built from: after an edit the cache is never served, however long revalidateMs is, and while the API is unreachable a stale copy is served only if it was built from the same text. It is apply: "serve" only — production builds never load it.

dbt models you are still building

A binding linked to dbt (-- dbt_project: <id>) writes {{ dbt_schema }}, which renders to the production schema. To preview models you built into your own dbt environment, point the dev server at it:

makoData({ dbtEnvironment: "joan" }) // or MAKO_DBT_ENV=joan in the repo's .env

The environment must exist in the linked dbt project (dbt/environments.yml), and a personal environment (owner_user_id) renders only for its owner. These builds are drafts too: never stored, never what a published app reads.

It also answers __data/viewer.json (you, as Mako sees you), and MAKO_VIEWER_AS=<email> — in the environment or the repo's .env — previews the app as that member instead (editors of the app only).

Credentials, in order: MAKO_API_URL / MAKO_API_KEY in the environment, then in the repo-root .env. The workspace id comes from .mako/workspace.json (or MAKO_WORKSPACE_ID). Without a key the app runs and every binding answers 503 with a hint.

Dependency-free: the DuckDB engine loads from jsDelivr at runtime; the plugin uses only Node built-ins.