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

@tosiiko/cup

v0.3.4

Published

CUP universal UI runtime for protocol-driven browser rendering

Readme

CUP

A protocol-driven UI runtime for backend-first browser apps.

Your backend returns JSON views that match schema/uiview.v1.json. The browser runtime validates, renders, and remounts them on each interaction. Auth, permissions, sessions, routing, and mutations stay on the server. The browser is a thin view layer.

┌───────────────┐    JSON view      ┌──────────────────────┐
│               │  ───────────────▶ │                      │
│   Backend     │                   │    Browser runtime   │
│ (owns state)  │   action payload  │  (validate + mount)  │
│               │  ◀─────────────── │                      │
└───────────────┘                   └──────────────────────┘

Table of Contents

When To Use CUP

Best fit:

  • dashboards, admin panels, CRMs, portals, internal tools
  • authenticated workflows where authorization, sessions, and mutations belong on the server
  • multi-language backends that want the same browser runtime
  • apps where view structure is data, not code — so it can be validated, generated, tested, and audited

Less ideal:

  • animation-heavy consumer SPAs
  • offline-first client apps with large local state
  • apps that want React/Vue/Svelte component trees to own most business logic

Install

npm install @tosiiko/cup

Initialize A Project

Scaffold a runnable CUP app into the current directory or a new folder.

npx @tosiiko/cup init --adapter py-cup
npx @tosiiko/cup init my-login --adapter ts-cup

Runnable init adapters: py-cup, go-cup, node-cup, ts-cup.

py-cup defaults to the standard structured Python layout:

my-cup-app/
  app/
  templates/
  static/
  cup/
  server.py
  README.md

For the minimal Python login demo:

npx @tosiiko/cup init --adapter py-cup --template login

To refresh a generated app to the latest packaged browser runtime:

npx @tosiiko/cup upgrade

That updates the vendored cup/index.js snapshot.

Generate a new page scaffold inside an existing project:

npx @tosiiko/cup generate page billing
npx @tosiiko/cup generate page account-health --route /account/health

cup generate page auto-detects py-cup, go-cup, node-cup, and ts-cup projects from the current folder, or you can force a target with --adapter. The generated files follow the detected project layout, write the view/template files, and print the exact route wiring snippet for the chosen adapter and route.

The Protocol In One View

Every CUP view is one JSON document. Minimum required fields: template, meta.version.

{
  "template": "<section><h1>{{ title }}</h1><button data-action=\"refresh\">Refresh</button></section>",
  "state": {
    "title": "Accounts"
  },
  "actions": {
    "refresh": {
      "type": "fetch",
      "url": "/api/accounts",
      "method": "GET"
    }
  },
  "meta": {
    "version": "1",
    "title": "Accounts",
    "route": "/accounts"
  }
}

| Field | Purpose | |--------------|----------------------------------------------------------------------| | template | Micro-template string (see Templates) | | state | JSON data bound into the template | | actions | Keyed map of fetch / emit / navigate descriptors | | regions | Named sub-views with their own template, state, and actions | | pagination | Pagination hints (page, pageSize, totalItems, hasNext, …) | | forms | Form field metadata (labels, validation, options) | | meta | version, title, route, lang, generator, provenance, extensions |

Adapters may attach meta.provenance (where the view came from — human, AI, adapter, hybrid) and meta.extensions (additive protocol features the view relies on).

Quick Start (Browser)

import {
  STARTER_VIEW_POLICY,
  mountRemoteView,
  validateProtocolView,
  validateViewPolicy,
} from '@tosiiko/cup';

async function loadView(url: string, root: HTMLElement) {
  const response = await fetch(url, {
    credentials: 'same-origin',
    headers: { Accept: 'application/json' },
  });

  const payload = await response.json();
  const view = validateProtocolView(payload);
  validateViewPolicy(view, STARTER_VIEW_POLICY);
  mountRemoteView(view, root);
}

loadView('/api/view', document.getElementById('app')!);

The thin browser shell:

  1. load the current route
  2. validate the returned view
  3. mount it
  4. submit forms or action payloads back to the server
  5. remount the next server-approved view

Backend Example (Python)

from cup import UIView, FetchAction, EmitAction

def accounts_page(session):
    accounts = load_accounts(session.user_id)

    return (
        UIView("""
            <section>
              <h1>{{ title }}</h1>
              <button data-action="refresh">Refresh</button>
              <ul>
                {% for a in accounts %}
                  <li>
                    {{ a.name }} — ${{ a.balance }}
                    <button data-action="open" data-id="{{ a.id }}">Open</button>
                  </li>
                {% endfor %}
              </ul>
            </section>
        """)
        .state(title="Accounts", accounts=accounts)
        .action("refresh", FetchAction("/api/accounts", "GET"))
        .action("open", EmitAction("sheet:open", detail={"id": "{{ id }}"}))
        .title("Accounts")
        .route("/accounts")
    )

See adapters/python for the full API. A Go adapter with the same shape lives at adapters/go.

Actions

Actions are how the browser talks back to the server. Each action is a keyed descriptor on the view's actions map and is triggered by a data-action attribute in the template.

fetch — HTTP request:

{ "type": "fetch", "url": "/api/accounts/{{ id }}", "method": "DELETE" }

Supports GET, POST, PUT, PATCH, DELETE. Form data and explicit payloads are both supported.

navigate — browser navigation:

{ "type": "navigate", "url": "/accounts" }

emit — client-side custom event for app-specific behavior:

{ "type": "emit", "event": "sheet:open", "detail": { "id": "{{ id }}" } }

Trigger from the template:

<button data-action="refresh">Refresh</button>
<button data-action="delete" data-id="{{ item.id }}">Delete</button>
<form data-action="save">…</form>

Each action may also carry target (which view/region to refresh), semantics (read/mutate), priority, and a confirm prompt.

Templates

CUP templates are small and predictable — not a general-purpose template language.

  • {{ value }} — escapes HTML by default
  • {{ value|safe }} — renders trusted HTML (only for sanitized content)
  • chained filters are supported: {{ name|trim|upper }}, {{ total|currency:"USD" }}, {{ count|pluralize:"item,items" }}
  • {% if %}, {% elif %}, {% else %}, {% endif %} — with not / ! and comparisons ==, !=, >, <, >=, <=
  • {% for item in items %}{% endfor %} — exposes loop.index, loop.index1, loop.first, loop.last
  • unsupported tags like {% include %} fail with parser errors

Built-in filters cover common string, number, date, JSON, join, replace, currency, percent, truncate, default, and pluralization cases. Custom filters can be added globally with registerFilter(name, fn), and listFilters() returns the currently available filter names for debugging or inspection tooling.

Good practice:

  • keep templates focused on rendering
  • keep permission logic out of templates
  • prefer fixed class names over dynamic class generation
  • treat |safe as exceptional

Regions & Patches

Regions are named sub-views with their own template, state, and actions. They allow granular updates without replacing the whole view.

{
  "template": "<section>…<div id=\"sidebar\"></div></section>",
  "regions": {
    "sidebar": {
      "template": "<ul>{% for n in notifications %}<li>{{ n.text }}</li>{% endfor %}</ul>",
      "state": { "notifications": [] }
    }
  }
}

A ProtocolPatch targets a region by name and specifies a mode — replace, append, or prepend — so the backend can push incremental updates (e.g. new list items) without re-rendering the page.

import { applyProtocolPatch, isProtocolPatch } from '@tosiiko/cup';

if (isProtocolPatch(frame)) {
  applyProtocolPatch(root, frame);
}

Streaming

Two streaming modes are built in.

NDJSON fetch stream with automatic reconnect:

import { streamView } from '@tosiiko/cup';

streamView('/api/feed', document.getElementById('app')!, {
  onFrame: (frame) => console.log('frame', frame.type),
  onError: (err) => console.error(err),
});

Server-Sent Events:

import { streamSSE } from '@tosiiko/cup';

streamSSE('/api/live', document.getElementById('app')!);

Frame types: view (full replace), patch (region update), heartbeat, done, error. Both helpers validate frames before mounting and expose onFrame, onError, and onClose callbacks plus a StreamHandle to close the stream.

Router

Client-side navigation without full page reloads:

import { createRouter } from '@tosiiko/cup';

const router = createRouter({
  routes: [
    { path: '/accounts',     handler: () => loadView('/api/accounts') },
    { path: '/accounts/:id', handler: ({ id }) => loadView(`/api/accounts/${id}`) },
  ],
});

router.start();

Supports named path params, query strings, CSS view transitions, and programmatic navigation.

Dispatcher & Middleware

Centralize action handling with a composable pipeline:

import {
  createDispatcher,
  loggerMiddleware,
  loadingMiddleware,
  errorMiddleware,
  delayMiddleware,
} from '@tosiiko/cup';

const container = document.getElementById('app')!;
const dispatcher = createDispatcher(container, {
  template: '<button data-action="delete">Delete</button>',
  state: {},
});

dispatcher.use(loggerMiddleware());
dispatcher.use(loadingMiddleware(dispatcher));
dispatcher.use(errorMiddleware(dispatcher));
dispatcher.use(delayMiddleware(150));

dispatcher.register('delete', async (ctx) => {
  await fetch('/api/delete', { method: 'DELETE', body: JSON.stringify(ctx.payload) });
});

dispatcher.mount();
dispatcher.dispatch('delete', { id: '123' });

Built-in middleware: loggerMiddleware, loadingMiddleware, errorMiddleware, delayMiddleware. Add your own for auth headers, analytics, or optimistic updates.

loadingMiddleware(dispatcher) now writes DOM-visible loading state in addition to the dispatcher signals:

  • the container gets data-cup-loading="<action names>" while actions are active
  • the container and triggering element get a .cup-loading class for CSS hooks

Mounted views also annotate the DOM with CUP debugging metadata such as data-cup-mount, data-cup-source, data-cup-view-route, data-cup-view-title, data-cup-view-version, and per-action data-cup-action-* attributes. If you need to preserve state across a remount, mark nodes with data-cup-preserve, data-cup-preserve-value, data-cup-preserve-scroll, or data-cup-focus-key.

Deployment Profiles

Profiles bundle a security policy, eval thresholds, and required capabilities into a named trust level.

import {
  STARTER_PROFILE,
  REGULATED_PROFILE,
  ENTERPRISE_PROFILE,
  applyProfile,
} from '@tosiiko/cup';

const result = applyProfile(view, STARTER_PROFILE);
if (!result.ok) throw new Error(result.reason);

| Profile | Intended Use | |----------------------|----------------------------------------------------| | STARTER_PROFILE | Safe defaults — relative URLs only, no unsafe HTML | | REGULATED_PROFILE | Healthcare, finance, legal — stricter checks | | ENTERPRISE_PROFILE | Large internal tools — balanced |

AI Eval Engine

Score AI-generated or adapter-generated views before mounting or serving them.

import { evalView, evalBatch, repairAndEval } from '@tosiiko/cup';

const result = evalView(view);
// result.score          — 0–1 weighted aggregate
// result.validity       — schema compliance
// result.security       — policy compliance
// result.accessibility  — a11y checks
// result.completeness   — required fields, state/template sync

Default dimension weights:

| Dimension | Weight | |---------------|--------| | Validity | 35% | | Security | 35% | | Accessibility | 15% | | Completeness | 15% |

repairAndEval(candidate) auto-repairs a malformed view then re-evaluates it. evalBatch(views) evaluates many views and surfaces common failure patterns.

Capability Negotiation

Clients and servers negotiate protocol version and extension support before mounting.

import {
  createCapabilityHeaders,
  negotiateCapabilities,
  parseCapabilityHeaders,
  DEFAULT_RUNTIME_CAPABILITIES,
} from '@tosiiko/cup';

const headers = createCapabilityHeaders(DEFAULT_RUNTIME_CAPABILITIES);

const response = await fetch('/api/view', { headers });
const support = parseCapabilityHeaders(response.headers);
const result  = negotiateCapabilities(DEFAULT_RUNTIME_CAPABILITIES, support);

if (!result.ok) {
  // server required an extension the client does not support
}

Views that declare a required extension in meta.extensions are rejected before mount if the client cannot honor it.

Offline Drafts

Server-authoritative patterns with offline tolerance:

import { createDraftStore, createRetryQueue } from '@tosiiko/cup';

const drafts = createDraftStore({ key: 'account-edits' });
drafts.save('account-42', { name: 'Pending change' });

const queue = createRetryQueue();
queue.enqueue({ url: '/api/accounts/42', method: 'PUT', body: { … } });
queue.flush();

Drafts persist in browser storage. The retry queue handles transient failures and replays when the client comes back online.

Inspection & Tracing

Debug a mounted view or the whole runtime:

import {
  inspectView,
  createInspector,
  createTraceObserver,
  inspectTraces,
} from '@tosiiko/cup';

// Snapshot a single view
const snapshot = inspectView(view);

// Long-lived inspector
const inspector = createInspector(root);
console.log(inspector.snapshot());

// Trace renders, actions, and validations
const observer = createTraceObserver();
observer.on('action', (trace) => console.log(trace));

Traces cover renders, actions, validations, and stream frames. They're the backbone for production observability and AI debugging.

Security Defaults

Runtime:

  • {{ value }} escapes HTML by default
  • mountRemoteView() validates protocol views by default
  • the TypeScript adapter's remote helpers require an explicit fetchImpl from the host app
  • core bundle has no transport markers — no baked-in fetch, http://, or https://
  • targets modern evergreen browsers

Starter-grade backend defaults:

  • signed cookie sessions
  • CSRF protection on every state-changing POST
  • no-store headers on HTML and JSON
  • server-owned authorization
  • policy validation before JSON leaves the server
  • relative action URLs by default

Policy example:

import { STARTER_VIEW_POLICY, validateViewPolicy } from '@tosiiko/cup';

validateViewPolicy(view, STARTER_VIEW_POLICY);

See docs/security.md.

Recommended App Structure

my-cup-app/
  app/
    server.py
    routes.py
    actions.py
    sessions.py
    security.py
    data.py
    views/
      auth.py
      overview.py
      accounts.py
      pipeline.py
  templates/
    login.html
    shell.html
    pages/
      overview.html
      accounts.html
      pipeline.html
  static/
    app.js
    app.css
  README.md

Why:

  • server.py stays thin
  • routes.py decides which view to return
  • actions.py owns mutations
  • security.py and sessions.py isolate security-sensitive code
  • views/ assembles template state
  • templates/ keeps markup editable without bloating backend files

Public API

Everything exported from @tosiiko/cup:

Validation validateProtocolView, validateProtocolPatch, ValidationError

Policy validateViewPolicy, STARTER_VIEW_POLICY, PolicyError

Mounting mount, createMountUpdater, mountRemoteView

Templates parseTemplate, render, registerFilter, listFilters, TemplateError

Actions & Router createDispatcher, loggerMiddleware, loadingMiddleware, errorMiddleware, delayMiddleware, createRouter

Streaming streamView, streamSSE

Patches & Composition applyProtocolPatch, isProtocolPatch, renderProtocolRegions, applyProtocolForms, applyProtocolPagination, collectProtocolActionBindings, serializeProtocolForm, findProtocolRegionName, getProtocolActionState, resolveProtocolActionTarget

Eval & Repair evalView, evalBatch, repairAndEval, repairProtocolViewCandidate, repairProtocolPatchCandidate

Profiles STARTER_PROFILE, REGULATED_PROFILE, ENTERPRISE_PROFILE, PROFILES, applyProfile, getProfile

Capability Negotiation createCapabilityHeaders, parseCapabilityHeaders, negotiateCapabilities, DEFAULT_RUNTIME_CAPABILITIES, CUP_CAPABILITY_NEGOTIATION_EXTENSION, CUP_PROVENANCE_EXTENSION

Offline createDraftStore, createRetryQueue

Inspection & Tracing createInspector, inspectView, createTraceObserver, inspectTraces

Binding & CSS bind, unbind, cssState, animate, waitTransition, waitAnimation, waitForVisualCompletion, theme, createSignal

Schema & Styles

  • ./schema/uiview.v1.json
  • ./styles/reference.css

Adapters

Remote loading helpers (fetchView, fetchViewStream) live on the TypeScript adapter path so @tosiiko/cup core stays transport-free.

Every official adapter must emit v1-compatible views, preserve meta.version, meta.lang, and meta.generator, and pass the shared contract tests in tests/runtime/contract.test.ts.

Starters & Demos

Starters (begin real projects):

Demos (study patterns):

Fast bootstrap instead of cloning:

npx @tosiiko/cup init --adapter py-cup

Docs

Development

npm install
npm run build
npm run check          # build + test + pack:check
npm run test           # TS + Python + Go adapter tests
npm run demo:smoke
npm run starter:smoke

Run demos and starters locally:

python3 demo/login/server.py
python3 demo/dashboard/server.py
python3 demo/dashboard2/server.py
python3 starters/python-minimal/server.py
python3 starters/python-portal/server.py
python3 starters/python-crm/server.py
node    starters/node-dashboard/server.mjs

Status & License

  • protocol version: 1
  • package version: 0.3.4
  • browser target: modern evergreen browsers
  • focus: stable backend-first runtime, adapters, starters, and release tooling

Licensed under Apache License 2.0.

  • npm releases through 0.2.3 were published under MIT
  • releases from this repository follow Apache-2.0 unless explicitly noted otherwise