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

@shubh90/app-runtime

v0.10.5

Published

The platform contract every mii org app depends on — sign-in, chat with the org's agents, and the config an app must not diverge from. A versioned package, not files copied into each repo.

Readme

@shubh90/app-runtime

The platform contract every org app depends on — sign-in and the Next.js config an app must not diverge from — as a versioned package instead of files copied into each app repo on every agent run.

This is Phase 0 + Phase 1 of docs/handoffs/2026-08-24-platform-runtime-plan.md: replace the kit-sync "vendoring" model (force-overwrite platform files in each repo, which caused the fleet-wide TS2307, the stuck rebases, and the framing white-screens) with a dependency an app installs and pins.

What's in it

  • @shubh90/app-runtime/auth — createMiiAuth(), the whole sign-in flow (the platform client, session, the login/logout route handlers, SIGN_IN_PROBLEMS, LOGIN_ACTION, safeNext). Self-contained: it depends only on postgres and next, never on the app's own code. The app still renders its own login page look.
  • @shubh90/app-runtime/next — withMiiPlatform(nextConfig), which force-merges the framing headers the platform's App pane needs so an app cannot forbid its own embedding (the Newmark white-screen bug becomes impossible).

Why a package (not the kit sync)

A published package physically cannot import "@/components/...", so the class of bug where a synced login page imported an app UI component it lacked is gone. Nothing is written into the app's tree each run, so the tree stays clean and autosave never fights a kit-dirtied tree. Apps pin a version; a breaking platform contract change is a semver major with a deprecation window, not a flag day.

Publishing (npmjs, public)

Every released version (0.1.0 →) lives on the PUBLIC npm registry — that is where app installs resolve, with no auth to provision in any build environment, which is the point. Publish from a machine logged in as shubh90:

# from packages/app-runtime:
npm run check
npm version patch          # or minor / major — updates the lockfile with it
npm publish --registry=https://registry.npmjs.org

prepack builds and checks what is about to be sent, so the tarball is this build of src whether or not anyone remembered to build first. Publishing is by hand and on purpose: there is no CI release for this package.

Then bump whoever should take it. ^0.5.0 does not accept 0.6.0 — npm reads a caret on a 0.x version as the minor being fixed — so a release reaches apps only when their own dependency moves.

Installing in an org app

Nothing to configure. The package is public, which is the point: an app's build environment needs no token to install the platform's half of itself.

pnpm add @shubh90/app-runtime

The chat and invoke server handlers import zod/v4, so the app needs zod >= 3.25.

Wiring it up (what each app file becomes)

One binding (src/lib/mii-auth.ts) — this is where the old tables.ts config goes now:

import { createMiiAuth } from "@shubh90/app-runtime/auth";
import * as Sentry from "@sentry/nextjs"; // optional

export const auth = createMiiAuth({
  // Only if the app kept a pre-kit user table (CBRE, Newmark, WBP):
  // users: { table: "user", idFrom: "kit", emailRequired: true, adoptByEmail: true, ensureSchema: false, insertDefaults: { emailVerified: false } },
  reportError: (error, tags) => Sentry.captureException(error, { tags })
});

The route handlers become one line each:

// src/app/api/mii-auth/login/route.ts
import { auth } from "@/lib/mii-auth";
export const dynamic = "force-dynamic";
export const POST = (request: Request) => auth.loginRoute(request);

// src/app/api/mii-auth/logout/route.ts
import { auth } from "@/lib/mii-auth";
export const dynamic = "force-dynamic";
export const POST = () => auth.logoutRoute();

Guarding pages (getUser / requireUser as before):

import { auth } from "@/lib/mii-auth";
const user = await auth.requireUser();   // in the authenticated layout
const user = await auth.getUser();       // in API routes: MiiUser | null

The login page stays the app's own look. It reads the pieces from the binding rather than a copied problems.ts / route:

import { auth } from "@/lib/mii-auth";
const problem = params.error ? auth.SIGN_IN_PROBLEMS[params.error] : null;
// <form method="post" action={auth.LOGIN_ACTION}> ... </form>

next.config wraps the app's own config:

import { withMiiPlatform } from "@shubh90/app-runtime/next";
export default withMiiPlatform({ /* the app's own next config */ });

Contracts: a button that asks the org's agents

Needs zod >= 3.25 in the app (the handler imports zod/v4). The app declares each contract once; the id is what mii contract create printed.

// src/contracts.ts
import { defineContracts } from "@shubh90/app-runtime/invoke/server";
import { z } from "zod/v4";

export const contracts = defineContracts({
  enrichCompany: {
    id: "k7m2x9pq",
    input: z.object({ domain: z.string() }),
    output: z.object({ website: z.url() })
  }
});

Mount the handler on a catch-all route under /api/mii/invoke, the way chat is mounted, for GET and POST:

const invoke = createMiiInvokeHandler(auth, contracts);

In the browser, type the client from the registry without bundling it:

import { createInvoker } from "@shubh90/app-runtime/invoke";
import type { contracts } from "~/contracts";

const { invoke, useInvocation } = createInvoker<typeof contracts>();
const { status, run, reset } = useInvocation("enrichCompany");
// run({ domain }); status.state is idle | pending | succeeded | failed, with
// elapsedMs and the invocation id. While pending, status.pollError says why the
// latest read failed (or null): the wait never gives up on its own.

A press is one request that returns when the agent answers. The platform holds the read for up to 60 seconds; if the answer takes longer than that, the client quietly asks again, at once, for as long as it takes. The app's server holds its own request just as long, so the route this handler is mounted on needs a function limit of about 90 seconds: export const maxDuration = 90 on a Next route, the function maxDuration in the Vercel config of a TanStack Start app. A limit below the hold does not break a press, but each read then fails at the limit and the client asks again after 1.5 seconds, so the status's pollError flickers on with each retry while the press still completes.

Coming back to a result. A run carries on in the platform when the page that started it goes away; only the page forgets it. Name what the press is for and the hook finds it again:

const { status, run, reset } = useInvocation("fillColumn", { remember: `fill:${column}` });

The run's id is kept in this browser's localStorage, so a reload or a reopened tab picks it up: still going, it keeps waiting (with elapsedMs counted from the start); finished, the result is there at once. status.input is what the run was asked, so a page that picked it up can show what it is for (which column, which text) without keeping anything itself. reset() is the person being done with it; otherwise it is picked up until INVOCATION_REMEMBER_MS after it started. A run the platform doesn't have for the signed-in person (someone else's, in a shared browser) is let go of. Without remember a run belongs to the mounted component, as before. resume(id) follows a run by its id, such as one a server function started.

Starting from the app's server. When the start must be recorded with something else (a job row that names its run), start it in a POST server function (not a loader: a page load must not start agent work) as the person whose request it answers:

// src/lib/invoke.server.ts: the contracts served once, for the route and server functions
import { createMiiInvoke } from "@shubh90/app-runtime/invoke/server";
export const invoke = createMiiInvoke(auth, contracts);

// in a POST server function
const { id } = await invoke.start({
  request: getRequest(), // from @tanstack/react-start/server: its cookie and origin are checked
  key: "researchRecord",
  input: { job: jobId, record }
});
// refusals reject with InvocationError (the codes a press gets).

Reading a result on the server. A server function that needs a finished run's result (to act on it, or to check what a page sends back) reads it as the same person, typed and parsed by the app's own output schema:

const { company } = await invoke.result("enrichCompany", id, getRequest());

It is one read, with no waiting: a run still going rejects with code pending, one the agents failed with failed, and a run of another contract with not_found.

createMiiInvoke(auth, contracts).handler is the invoke route; createMiiInvokeHandler is the same route alone.

Ship order: 0.9.0 reads input on every invocation, so it needs a platform that returns it; publish it only after that platform release is live.

Long jobs. For work that takes minutes or days, make the contract's output a small acknowledgement, such as { accepted: true }, and have the prompt tell the agent to call submit_result as soon as it has accepted the job, then keep working and write its results and status into the app's own tables, which the UI reads. After the acknowledgement the invocation no longer tracks the job.

The platform validates a result with ajv (JSON Schema draft 2020-12, strict) against the JSON Schema form of output, and this handler then parses it with output itself, which is where refinements and transforms apply: the app's own Zod parse is the final check. createMiiInvokeHandler converts every output once, when it is made, so an output with no JSON Schema form (z.date(), z.map()) stops the server starting. Every zod string format works; z.jwt() emits no pattern, so only the app's own parse checks it.

Migration order (rollout)

See the plan doc. In short: publish @shubh90/app-runtime, convert one app on a test org and prove sign-in in the pane, then a version-bump bot adopts it across the fleet, then delete syncPlatformKit / PLATFORM_OWNED_PREFIXES and the backfill-org-app-* scripts.

Parity with the kit it replaces

The auth logic is ported verbatim from the Next.js template's src/lib/mii-auth (the template is gone; the kit the runner still syncs into those apps lives in infra/provisioning/next-fleet); only the module-level USERS/authSql singletons became a createMiiAuth argument, and the login route's hard @sentry/nextjs import became the injected reportError. The existing next-path and plaza unit tests are ported (test/), plus new coverage for withMiiPlatform and the users config.