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

@vxil/react

v0.2.3

Published

The Vxil React component library — drop-in, themeable widgets over the stable Vxil APIs, one package with five subpath entries: @vxil/react/copilot (<CopilotProvider>/<CopilotThread>/<Composer>/<ProposalCard> + useCopilot, propose→confirm AI actions), @vx

Readme

@vxil/react

The Vxil React component library — drop-in, themeable widgets over the stable Vxil REST + realtime APIs, in one package with five subpath entries (each tree-shakes independently; importing one pulls in nothing from the others):

| Subpath | What you get | | --- | --- | | @vxil/react/copilot | An in-app AI assistant — <CopilotProvider> / <CopilotThread> / <Composer> / <ProposalCard> + useCopilot(), with propose→confirm write actions. | | @vxil/react/feed | Activity streams + in-app notifications — <FeedProvider> / <FeedTimeline> / <NotificationBell> + useActivityFeed() / useNotifications(), live. | | @vxil/react/comments | Threaded comments + file uploads — <CommentThread> + useComments(), and <UploadWidget> / <Dropzone> + useUpload(). | | @vxil/react/entitlements | Feature gating over the payments integration — useEntitlement() (a three-state answer that never downgrades on a failed read) + <Paywall> / <ManageSubscription>. | | @vxil/react/auth | Passwordless sign-in state machines — useOtpFlow() / useMagicLink() / useGuestSession() + <OtpFlow>, with injected token storage, refresh-before-expiry, and sign-out. |

npm install @vxil/react @vxil/sdk
# react / react-dom are peers you already have in your app (React 18 or 19)

react / react-dom are peer dependencies (>=18; works on React 19). @vxil/sdk is an optional peer — the components take a client port (a structural slice of the SDK surface), so you'll normally construct a Vxil client and pass its namespaces in, but nothing here hard-requires the SDK at runtime. The /feed subpath brings @vxil/realtime (the reconnecting WebSocket client) with it for live updates.

This is an npm package for a bundled app (Vite/Next/etc.) — install it and import from the subpaths. Unlike the base @vxil/sdk / @vxil/realtime, there is no env-baked served .mjs build of the React components; they ship only via npm.

Shared design, all five families:

  • Pure, node-testable cores + thin React bindings. All state + logic lives in framework-free stores/state machines (CopilotStore, FeedStore, NotificationStore, CommentStore, UploadMachine, EntitlementStore, SessionManager, OtpFlowStore, MagicLinkFlow, GuestSession) — exhaustively unit-tested; the React layer is a thin projection via useSyncExternalStore. The cores are exported too, so you can drive everything without React.
  • XSS floor. Model and user content is always rendered as text nodes — never dangerouslySetInnerHTML.
  • Bring your own CSS. Minimal/class-based styling with stable data-vxil-* / class hooks, unopinionated by design.

@vxil/react/copilot

React components + hooks for vxil copilot agents — the CopilotKit / assistant-ui parity surface, built entirely on the stable copilot REST + resume-stream API. It complements the hosted iframe widget with a first-class, themeable, in-app assistant.

  • Grounded, cited answers. Every turn retrieves, grounds, and generates a cited answer; citations are surfaced inline.
  • Propose → confirm write actions. A WRITE action the agent wants to take is proposed, never auto-run. <ProposalCard> shows the tool + args and a Confirm button — only then does the real internal route execute (with the caller's own scopes; nothing widens).
  • Streaming. Answers stream in token-by-token via the durable resume buffer (GET …/stream?since=N) — no socket required, and a dropped connection replays without duplicating text.
import { Vxil } from '@vxil/sdk';
import { CopilotProvider, CopilotThread, Composer } from '@vxil/react/copilot';

// In a thin-client app, scope the client to the signed-in end user; for a
// guest widget, mint a guest bearer and use it as the end-user token.
const vx = new Vxil({ apiKey: THIN_CLIENT_KEY, endUserToken: userSessionToken });

export function Assistant() {
  return (
    <CopilotProvider client={vx.copilot} agentId="support" userId={currentUserId}>
      <CopilotThread />
      <Composer placeholder="Ask the assistant…" />
    </CopilotProvider>
  );
}

The hook

import { useCopilot } from '@vxil/react/copilot';

function Toolbar() {
  const { messages, sending, usage, send, confirm } = useCopilot();
  return (
    <div>
      <span>{usage.totalTokens} tokens · {usage.turns} turns</span>
      <button disabled={sending} onClick={() => send('Summarize this thread')}>
        Summarize
      </button>
    </div>
  );
}

Proposal (write) actions

When an agent proposes a write, <CopilotThread> renders an inline <ProposalCard> automatically. Nothing runs until the user presses Confirm; Decline is a purely local no-op (a proposal is inert on the server too). Pass an idempotency key to make a retried confirm a downstream no-op:

<ProposalCard message={m} idempotencyKey={m.id} />

Components

| Export | What it is | | ------------------ | ---------------------------------------------------------------------- | | <CopilotProvider>| Context: the client + agent id (+ optional userId, conversationId). | | useCopilot() | { messages, sending, usage, send, confirm, reject, … }. | | <CopilotThread> | The transcript — bubbles, citation counts, inline <ProposalCard>. | | <Composer> | Input + send; disabled while a turn is in flight. | | <ProposalCard> | The propose→confirm gate; renders the result after confirm. | | <MessageBubble> | A single message bubble (override via <CopilotThread renderMessage>). |

The pure core

For non-React (or custom-UI) callers, CopilotStore and the reducer are exported directly:

import { CopilotStore, reduceFrames, emptyAccumulator } from '@vxil/react/copilot';

const store = new CopilotStore({ client: vx.copilot, agentId: 'support' });
store.subscribe(() => render(store.getState()));
await store.send('hello');

Styling is class-based (.vxil-copilot-*) and unopinionated — bring your own CSS.


@vxil/react/feed

React activity streams + in-app notification feeds — a GetStream-Feeds-style surface (timelines, follows, aggregation, a notification badge, and live updates) over the shipped activity-feed REST + realtime APIs.

Build a Vxil SDK client for the signed-in end user (it holds a public/thin-client key + the user's session token so every read/write is owner-scoped at the edge), then pass its activityFeed namespace to <FeedProvider>.

import { Vxil } from '@vxil/sdk';
import { FeedProvider, FeedTimeline, NotificationBell, feedRealtimeTokenProvider } from '@vxil/react/feed';

const vx = Vxil.connect({ apiKey: PUBLIC_KEY, endUserToken: sessionToken });

function App() {
  return (
    <FeedProvider
      client={vx.activityFeed}
      userId="u_ada"
      // OPTIONAL: enable live updates. Mints a scoped realtime token per channel
      // via the same end-user client — the browser never needs the tenant key.
      realtimeTokenProvider={feedRealtimeTokenProvider(vx.activityFeed, 'u_ada')}
    >
      <NotificationBell />
      <FeedTimeline group="timeline" feedId="u_ada" aggregate />
    </FeedProvider>
  );
}

Components (content is always rendered as text — the XSS floor; every row is overridable via render props):

  • <FeedProvider> — holds the client, the userId, and a live realtime client. Cleans up all realtime subscriptions on unmount.
  • <FeedTimeline group feedId> — a feed's activities with keyset (infinite) pagination and optional GetStream-style group aggregation.
  • <NotificationBell> — the unread badge (capped, e.g. 99+) + a dropdown of unread groups + mark-all-read.

Hooks:

  • useActivityFeed({ group, feedId }) — { activities, groups, aggregated, hasMore, refresh, paginate, addActivity, removeActivity, follow, unfollow, block, unblock }, live-merged from new-activity frames.
  • useNotifications() — { badge, groups, preferences, markSeen, markRead, markAll, archive, refreshBadge, loadPreferences, setPreferences }, live-merged from the badge channel.

Pure core (no React, exported for custom UIs / testing):

  • FeedStore — keyset pagination + realtime merge (a new activity prepends exactly once; de-duped on id).
  • NotificationStore — the { unseen, unread, total } badge; mark-seen/read/archive adopt the server-authoritative badge; the realtime feeds.unread-count frame merges in.
  • aggregateActivities(activities) — collapse consecutive same verb+object+day activities into "X and N others …" groups.
  • Channel naming helpers — a feed ref is {group}:{feedId}; its live channel is feeds-{group}:{feedId}. Never hand-roll the strip: channelForFeed('timeline:u_ada') → 'feeds-timeline:u_ada', feedRefFromChannel('feeds-timeline:u_ada') → 'timeline:u_ada' (null for a non-feed channel), parseFeedRef(ref) → { group, feedId }, and feedRealtimeTokenProvider(client, userId, { ttlSeconds? }) — the ready-made realtimeTokenProvider shown above (it mints one scoped token per channel through the end-user call).

Realtime is optional: without realtimeTokenProvider the components still read and paginate; they just don't live-update until you refresh().


@vxil/react/comments

Embeddable React widgets over the Vxil comments and files features — a threaded comment thread and a file uploader, drop-in with one provider.

Wrap your app in <CommentsProvider> with an end-user-scoped vxil client (vx.asEndUser(sessionToken)) and the acting user id, then drop in the widgets:

import { Vxil } from '@vxil/sdk';
import { CommentsProvider, CommentThread, UploadWidget, Dropzone } from '@vxil/react/comments';

// A thin/public API key + the signed-in user's auth session token. The edge
// verifies the token and owner-scopes every read/write to that user.
const vx = new Vxil({ apiKey: PUBLIC_KEY }).asEndUser(sessionToken);

function PostPage({ postId, userId }) {
  return (
    <CommentsProvider vx={vx} authorId={userId}>
      <CommentThread topic={`post:${postId}`} />
      <Dropzone onUpload={(file) => console.log('uploaded', file.object_id)} />
    </CommentsProvider>
  );
}

Comments

  • <CommentThread topic="…"> — threaded list (top-level + replies), a composer, per-comment emoji reactions, and an author edit/delete affordance gated to the tenant's edit window. All user content renders as plain, escaped text (no HTML/markdown), the XSS floor.
  • useComments(topic) — the headless hook powering the widget: { comments, threads, post, edit, remove, toggleReaction, loadMore, canEdit, hasReacted, ... }. Mutations are optimistic and reconcile against the server (or roll back on error).
const c = useComments(`post:${postId}`);
await c.post('Nice write-up!');
await c.post('Agreed', someCommentId);   // a reply
await c.toggleReaction(someCommentId, '👍');

Files

  • <UploadWidget onUpload={fn}> — a button + progress bar; onUpload fires with the available FileObject.
  • <Dropzone onUpload={fn}> — a drag-and-drop wrapper (multi-file, sequential) over the same engine.
  • useUpload() — the headless hook: { upload, uploadFile, cancel, reset, phase, progress, file, error, busy }.

Uploads run the vxil files 3-step: createUploadUrl → PUT bytes → complete. The presigned PUT URL is used once inside the upload and never persisted. If the signature window elapses, the error surfaces as kind: 'expired' and the widget offers a one-tap Retry that mints a fresh URL. The browser build uses an XHR transport for granular upload progress.

const up = useUpload();
const file = await up.uploadFile(browserFile); // -> FileObject

Error text is provider-supplied — treat it as untrusted

Some upload error text is provider-supplied: on a presigned-URL expiry (kind: 'expired') the object store's own body may be surfaced on up.error.message and forwarded to any onError you pass. That text comes from the underlying object store, not from vxil — treat it as untrusted text. The widget only ever renders it as an escaped text node (the XSS floor holds), but if your app logs or re-displays onError text, do not inject it as HTML and consider capping/normalizing it. (Non-expiry byte-upload failures use a fixed byte upload failed (status N) message — no provider text is passed through there.)

Styling

The widgets ship minimal inline layout so they render without a stylesheet. Every element carries a stable data-vxil-* / data-comment-id hook and accepts a className — bring your own CSS to theme them.


@vxil/react/entitlements

Feature gating over the Vxil payments integration (your own Stripe / Paddle / PayPal / RevenueCat account behind Vxil's credits & entitlements ledger). One hook answers "is this user entitled?" with three states, and two unstyled components render a paywall and a manage-subscription link.

The rule the subpath exists for: a paying user must never be shown the free experience because a read failed. useEntitlement() returns 'entitled' | 'not_entitled' | 'unknown':

  • entitled / not_entitled come only from a successful read.
  • Any throw, non-2xx, malformed body, or network failure is unknown — never not_entitled.
  • Once a good answer exists it is kept through failures (stale: true), so the UI renders the cached answer rather than free. An optional maxStaleMs degrades a too-old cached answer to unknown — still never not_entitled.
import { Vxil } from '@vxil/sdk';
import { useEntitlement, Paywall, ManageSubscription } from '@vxil/react/entitlements';

// A thin-client key + the signed-in user's session token: the read is
// owner-bound to that user (the edge decides the user, not the query).
const vx = new Vxil({ apiKey: PUBLIC_KEY, endUserToken: sessionToken });

function ExportButton() {
  const ent = useEntitlement({ client: vx.payments, entitlement: 'export', refreshMs: 60_000 });
  // ent.state · ent.tier · ent.until · ent.asOf · ent.raw · ent.stale · ent.refresh()
  if (ent.state === 'unknown') return <button disabled>Export</button>;
  return ent.state === 'entitled' ? <button>Export</button> : <UpgradeNudge />;
}

function ProArea() {
  return (
    <>
      <Paywall client={vx.payments} entitlement="pro" when="not_entitled">
        <UpgradeCard />
      </Paywall>
      <ManageSubscription client={vx.payments} label="Manage billing" fallback={null} />
    </>
  );
}

No SDK? Pass the raw transport instead — useEntitlement({ apiKey, endUserToken, fetch?, baseUrl? }) — or set it once with <EntitlementProvider client|apiKey endUserToken userId? refreshMs?> and omit it from every hook/component below.

The hook

useEntitlement({ client | apiKey+endUserToken, userId?, entitlement?, quota?, creditType?, decide?, refreshMs?, maxStaleMs?, read?, enabled? }) → { state, tier, until, asOf, raw, quotaValue, credits, stale, loading, error, lastGoodAt, failures, refresh() }

| Criterion | Entitled when | | --- | --- | | entitlement: 'pro' | the snapshot's entitlements include it | | quota: 'seats' | quotas.seats > 0 (the value is on quotaValue) | | creditType: 'scan' | the credit balance's available > 0 (on credits) | | decide(ctx) | your predicate, replacing the above | | (none) | the user holds at least one entitlement |

Several criteria are ANDed. userId is optional in end-user mode (the verified session decides the user; nothing is sent) and required when a server-mode client is passed.

Routes it calls

  • GET /v1/payments/entitlements — the snapshot ({ user_id, tier, entitlements, quotas }). Your quota / creditType criteria are sent as ?quota= / ?credit_type=, so the route inlines that quota's value and that credit type's balance and the hook gets its whole answer in one call. since / until / as_of / environment are read from the response when present.
  • GET /v1/payments/credits/balance?credit_type=… — the fallback, used only with creditType and only when the snapshot did not already inline it (e.g. an older server, or an SDK namespace that sends no read params).
  • GET /v1/payments/customer-portal-url — <ManageSubscription> only. Answers { url } today; an optional kind ('portal' | 'store' | 'none') is honored when present and inferred otherwise. A 501 (no hosted portal for this provider) renders the fallback, not an error.

The browser key for this subpath carries payments:read only. Consuming or granting credits is server-only by design and unreachable from a thin client.

Components

| Export | What it is | | --- | --- | | <Paywall when entitlement? quota? creditType? result? fallback? render?> | Renders children when the state matches when (default 'not_entitled'; an array is allowed). unknown matches only when asked for — a failed read never opens a paywall. Pass result to reuse a hook result. | | <ManageSubscription label? fallback? render? newTab?> | Resolves the portal link and renders <a data-vxil-portal-kind>; render({ url, kind, loading, error }) overrides. | | useManageSubscription() | The headless version of the above. | | EntitlementStore, decideEntitlement, normalizeSnapshot, resolvePortalLink, fetchEntitlementClient | The pure core, for non-React callers. |


@vxil/react/auth

Passwordless sign-in for a browser or mobile web app over the Vxil auth feature — release one of two: the OTP flow, the magic-link flow, and guest/anonymous sessions, plus the session pair's storage, rotation and sign-out. Every flow is a pure, node-tested state machine; the hooks are thin.

import { Vxil } from '@vxil/sdk';
import { AuthProvider, OtpFlow, useAuthSession, localStorageStorage } from '@vxil/react/auth';

// The browser's key: a thin-client key carrying `auth:signin` (+ the read
// scopes the UI needs) — see "The browser key" below.
const vx = new Vxil({ apiKey: PUBLIC_KEY });

export function App() {
  return (
    <AuthProvider client={vx.auth} storage={localStorageStorage()}>
      <Gate />
    </AuthProvider>
  );
}

function Gate() {
  const s = useAuthSession(); // { status, session, userId, anonymous, token, signOut, refresh }
  if (s.status === 'restoring') return null;
  if (s.status !== 'signed_in') return <OtpFlow onSignedIn={() => {}} />;
  // Hand the token to the rest of the app: vx.asEndUser(s.token)
  return <button onClick={() => void s.signOut()}>Sign out</button>;
}

No SDK? <AuthProvider apiKey={PUBLIC_KEY} fetch? baseUrl?> builds the raw-fetch client.

Hooks

  • useOtpFlow({ cooldownSec?, onSignedIn? }) → { phase, email, error, attempts, cooldownUntil, cooldownRemainingMs, canRequest, request(email), verify(code), resend(), reset() }. Phases: idle → requesting → code_sent → verifying → signed_in. A wrong code stays in code_sent (retry); an exhausted guessing budget returns to idle (the code is burned — resend()); a 429 mirrors the server's resend cooldown locally (Retry-After when sent).
  • useMagicLink({ consumeOnMount?, onSignedIn? }) → { phase, error, request(email, redirectUrl), consume(token), consumeFromUrl(url?), reset() }. redirectUrl is the https page in your app that receives ?token=; consumeFromUrl() reads it from the location (query or hash router).
  • useGuestSession({ auto? }) → { phase, session, error, ensure() }. Mints an anonymous session on mount when nothing is persisted; an existing session (guest or not) is reused, never shadowed. The session carries anonymous: true.
  • useAuthSession() → the pair + signOut() / refresh() and the underlying manager (call manager.adopt(result) from a custom sign-in).
  • The pure cores are exported for non-React callers: SessionManager, OtpFlowStore, MagicLinkFlow, GuestSession, fetchAuthClient, mapAuthError, magicLinkTokenFromUrl.

Components

  • <OtpFlow labels… signedIn? renderError?> — one unstyled form: email → 6-digit code → done. Stable vxil-otp-* classes and data-vxil-otp-phase / data-vxil-otp-error hooks; bring your own CSS.

Token storage (injected)

<AuthProvider storage={…}> takes a TokenStorage — { get, set, clear } over { user_id, token, refresh_token, expires_at, anonymous }:

  • memoryStorage() — the default; nothing survives a reload.
  • localStorageStorage(key?) — persists across reloads; falls back to memory when storage is unavailable. webStorage(sessionStorage, key?) for any Storage-shaped object.

Persisting the pair in web storage is the usual SPA trade-off; keep the access token TTL short and let the manager rotate.

Refresh + sign-out

The SessionManager rotates via POST /v1/auth/sessions/refresh before expires_at (refreshLeadMs, default 60 s; one in-flight refresh at a time), stores the new pair and discards the old. A transient failure keeps the still-valid session and retries (refreshRetryMs); an invalid_refresh ends the session. signOut() calls POST /v1/auth/sessions/revoke { token } best-effort and clears locally regardless — a sign-out never fails visibly because the network did.

Routes it calls

POST /v1/auth/otp/request · POST /v1/auth/otp/verify · POST /v1/auth/magic-link/request · POST /v1/auth/magic-link/verify · POST /v1/auth/anonymous/sign-in · POST /v1/auth/sessions/refresh · POST /v1/auth/sessions/revoke.

Errors

Every failure is mapped to one AuthFailure — { kind, code, status, message, retryAfterSec } with kind ∈ invalid_code | attempts_exhausted | rate_limited | method_disabled | invalid_token | invalid_refresh | validation | email_dispatch_failed | forbidden | network | unknown — whether it came from the SDK, the raw-fetch client, or a bare fetch TypeError. forbidden is the key being rejected (missing / invalid / revoked, or missing auth:signin) — an app-configuration problem, never something the end user can fix by retyping the code.

The browser key — a public key never carries a write scope

The key you ship to a browser or phone is a sign-in key: it may obtain, renew, verify and end its own session, and read what the signed-in user owns. Mint it as an end_user_required-class key with auth:signin plus the read scopes your UI needs (e.g. payments:read) — and never auth:write (the administrative class: erase users, force-revoke other devices, list sessions), never *:write / *:send / admin:*. assertPublicKeyScopes(scopes) throws on a violation; publicKeyScopeViolations(scopes) lists them. Money moves (consume / grant) are server-only regardless.

Not in this release (release two)

The server side is live: GET /v1/auth/sessions is owner-bound in end-user mode, POST /v1/auth/sessions/{id}/revoke only finds a session the principal owns (a foreign id is a 404, not an error that confirms it exists), POST /v1/auth/sessions/revoke-all is a self operation in end-user mode, and the step-up challenge routes ship. What is missing is the client binding — these are simply not built yet, and the exports do not exist:

  • useDevices() — the signed-in user's device list (GET /v1/auth/sessions; that route is auth:read, so a browser key would need it alongside auth:signin).
  • <TakeOverDialog> / revokeDevice(sessionId) — force-revoke one of your own devices (POST /v1/auth/sessions/{id}/revoke — still the administrative auth:write class, so today it is a call your server makes, not the browser).
  • useStepUp() — the step-up re-auth challenge on the current session (POST /v1/auth/step-up/request · POST /v1/auth/step-up/verify).

Changelog

  • 0.2.0 — 2026-09-11, published to npm. New subpaths @vxil/react/entitlements and @vxil/react/auth (release one); @vxil/react/feed gains channelForFeed / feedRefFromChannel / parseFeedRef / feedRealtimeTokenProvider.
  • 0.1.0 — first publish: ./copilot, ./feed, ./comments.

Docs: vxil.com · Dashboard: vxil.com/dashboard

MIT © techmaker.io