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

@vibemonetize/core

v0.0.7

Published

Domain zod schemas, resolveEntitlements, entitlement JWT issue/verify, error catalog, event schema. Zero deps beyond zod/jose.

Readme

@vibemonetize/core

Shared contracts for the whole platform. Zero dependencies beyond zod and jose. Zod schemas here are the single source of truth for shapes — API and SDKs import them, never redeclare.

Public API

Domain schemas (schemas.ts)

ulidSchema, slugSchema, and zod schemas + inferred types for every domain object: Developer, App, Feature, Meter (period: month | day | lifetime | credit), ObservedOrigin ({ origin, firstSeenAt, lastSeenAt } — passive Origin/Referer capture from browser-facing authenticated traffic, so a CLI-created app can still surface where it's deployed) and AppDetail (appSchema.extend({ observedOrigins }), GET /v1/apps/:appId only — list/create/update stay on plain App; observation only, never auto-promoted into App.domains), Plan (with grants: FeatureGrant[], limits: UsageLimit[]; appId XOR bundleId), Bundle, EndUser, Membership (status/source enums; trials are memberships with expiresAt), Purchase, UsageBalance, PaywallConfig (versioned per app; config is a freeform v1 passthrough object owned by the dashboard editor + react <Paywall>). The agreed keys inside that freeform config live here too: paywallContentV1Schema (headline/body/accentColor — hex-validated — /gatedFeatureSlugs/showPoweredBy) + readPaywallContentV1 (lenient read with defaults, never throws) — one shared definition for the dashboard editor and the SDK renderer, while the api contract stays passthrough. showPoweredBy (RFC 021) toggles the brand-anchored "Powered by VibeMonetize" paywall footer; it is .default(true) (badge on by default), so configs persisted before the key existed keep validating and resolve to true — paid tenants with remove_branding set it false.

Mode (RFC 012): MODES = ["test", "live"] as const, modeSchema (zod enum), DEFAULT_MODE = "live". Developer test/live split, Stripe-style — partitions runtime data (api keys, end-user identities, purchases, usage, ledger, webhooks) per developer; app/plan/feature/meter registry rows stay mode-less. resolveEntitlements stays mode-agnostic and pure (CLAUDE.md ground truth): mode is a data-layer filter applied by the api and a JWT passthrough claim, never resolver logic.

Entitlement resolution (entitlements.ts)

resolveEntitlements({ memberships, plans, usage, now }): EntitlementSet
// EntitlementSet = { features: Set<`${appId}:${featureSlug}`>,
//                    meters: Map<`${appId}:${meterSlug}`, { cap, used, remaining, resetsAt }> }

Pure, deterministic, zero I/O. Rules: feature grants union across active memberships; meter caps take the MAX across non-credit sources plus the SUM of credit sources; expired/canceled/archived memberships and stale usage balances contribute nothing. Helpers: isMembershipActive, entitlementKey, serializeEntitlementSet, deserializeEntitlementSet.

This is the only place entitlement logic lives. (CLAUDE.md ground truth.)

Entitlement JWT (jwt.ts)

ES256, 5-minute TTL, JWKS-friendly. issueEntitlementToken, verifyEntitlementToken (throws VibeMonetizeError("token_expired" | "unauthorized")), claimsForApp (projects an EntitlementSet to one app's claims: { sub, appId, plan, features[], meters{}, mode }, claimsForApp(...) takes an optional mode?: Mode, defaulting to "live"), ENTITLEMENT_JWT_ALG, ENTITLEMENT_TOKEN_TTL_SECONDS, entitlementTokenClaimsSchema.

EntitlementTokenClaims.mode: Mode (RFC 012) is optional on the wire for back-compat: entitlementTokenClaimsSchema defaults a missing mode to "live" on parse, so tokens issued before this claim existed keep verifying unchanged. issueEntitlementToken always signs the resolved mode into the JWT payload.

verifyEntitlementToken takes an optional expectMode?: Mode (RFC 022, Gap 2 — closing the "free test-mode entitlement replayed against a mode-blind live backend" risk): enforces the token's effective mode (claims.mode ?? "live") matches, throwing VibeMonetizeError("mode_mismatch") (403) on any mismatch. Omit expectMode to skip the check entirely (mode-blind verification, the pre-RFC-022 behavior). This is the ONE enforcement point every verifier shares — @vibemonetize/server's createClient and the RFC 007 edge-enforcement recipe both call through this, never re-deriving the check.

Errors (errors.ts)

ERROR_CATALOG (code → HTTP status + default message), VibeMonetizeError, apiErrorSchema ({ code, message } — the wire shape of every API error), toApiError. RFC 022 additions: mode_unavailable (503 — a deployment has no Stripe key configured for a request's mode; apps/api-side only) and mode_mismatch (403 — an entitlement token's mode claim doesn't match what a verifier requires; see jwt.ts above).

API route contracts (routes.ts)

API_ROUTES — OpenAPI-ish table of every v1 endpoint (method, path, auth, request/response zod schemas), plus the request/response schemas themselves (entitlementTokenRequestSchema, usageEventRequestSchema, checkoutSessionRequestSchema, CRUD create/update inputs, eventsIngestRequestSchema). See CONTRACTS.md for the full table.

identifyEndUserRequestSchema/verifyEndUserRequestSchema (RFC 002: publishable-key, platform-verified 6-digit email code) and mergeEndUserRequestSchema/ mergeEndUserResponseSchema (RFC 014: sk_-only — the developer attests email is already verified by their own auth, e.g. Neon Auth/Clerk/Supabase) both fold an anonId's memberships/usage balances/events into the (found-or-created) verified-email end user and return { endUserId }; the merge route is strictly more privileged (secret-key only, never pk_) since the trust for email verification shifts from the platform to the developer.

checkoutSessionRequestSchema (RFC 006) names the plan by exactly one of planId (ULID escape hatch) or planSlug (the pricing-editor slug — preferred, promptable at codegen time). Slugs are unique per app, so a planSlug request also carries the optional appId resolution scope (the react SDK sends <MonetizeProvider>'s automatically; an app-scoped api key can supply it implicitly). Bundle plans have no single app scope and remain planId-only.

billingPortalSessionRequestSchema/billingPortalSessionResponseSchema (RFC 009): POST /v1/billing-portal-sessions — the publishable-key route <AccountButton>'s "Manage billing" calls to mint a hosted Stripe billing-portal session with no app backend. Unlike the other pk_ routes it does NOT take endUserId ⊕ anonId: the request carries the raw entitlement JWT (token) plus a returnUrl, and the api verifies the token's signature and scopes the portal strictly to its sub claim — a portal session exposes payment methods/invoices/cancellation, so identity must be proven, not named. Responds { url }, or a typed not_found (404) when the end user has no Stripe customer yet in the request's mode.

Registry CRUD is API-complete (api-first paywall management): features/meters have update/archive routes (featureUpdateSchema, meterUpdateSchema), and plans have get/update/archive (planUpdateSchemaslug/currency/owner immutable; grants/limits replace wholesale; pricing changes attach a new Stripe price with existing subscribers grandfathered). bundleCreateSchema (bundleSchema.omit(stamped), same idiom as appCreateSchema — DX loop cycle 2) is the create input for POST /v1/bundles: the missing half of bundle support, since planCreateSchema already accepted bundleId but the Bundle row itself had no creation path before this.

appCreateSchema carries one create-only extra beyond the omitted-stamps idiom: listInStore: z.boolean().default(true) (RFC 005 amendment). Store listing at app creation is default-ON with an explicit off-switch — omit the field (older clients) to list by default, pass false to create the app unlisted. It is an INPUT flag only, not an app entity field: the entity's listedAt timestamp (managed by the listing routes) stays the source of truth for listing state, and unlist-immediately semantics are unchanged. Like other .default() keys it is required in the inferred AppCreate type.

listPlansForApp (GET /v1/apps/:appId/plans) nests plans under the app the same way features/meters already do — same response shape as GET /v1/plans?appId= (DX loop cycle 2).

Membership management (subscription surface): membershipWithPlanSchema (+ MembershipWithPlan) — membership rows with the plan hydrated inline, listMembershipsQuerySchema (filters: appId incl. bundle fan-out, endUserId, email, status), membershipCreateSchema (+ MembershipCreate) — comp grants (grantee is exactly one of endUserId, anonId, or email — the email form is RFC 014-style attestation: found-or-created in the key's mode, no verification challenge; it's what vibemonetize grant sends — plus optional expiresAt), and a body-less cancel route (POST /v1/memberships/:membershipId/cancel; Stripe subs cancel at period end).

Setup status (onboarding progress; powers vibemonetize doctor + the dashboard "Getting started" checklist): setupStatusQuerySchema (?appId= optional — omitted, the server targets the developer's only app or returns app: null), setupStatusResponseSchema (+ SetupStatusResponse, SetupStatusApp, SetupStatusMilestones) — the authenticated key's mode, per-mode key inventory, appCount, registry counts for the target app (featureCount/meterCount/planCount/ paidPlanCount), and first-time funnel milestones (firstPaywallImpressionAt, firstCheckoutStartedAt, firstCheckoutCompletedAt, firstUsageEventAt, firstPaidMembershipAt — the event-derived three are cross-mode by design, the last two are scoped to the key's mode per RFC 012).

Events (events.ts)

analyticsEventSchema (stored shape), analyticsEventInputSchema (SDK payload), CANONICAL_EVENTS (funnel names: page_viewusage_limit_reached), eventSourceSchema, installChannelSchema (RFC 020: assistant | cli | docs | unknown). Both event schemas carry an optional installChannel alongside source (RFC 008) — additive, absent means the channel is unknown (never inferred), not a new event type.

Analytics reporting (analytics.ts)

analyticsFunnelQuerySchema/analyticsFunnelResponseSchema (per-canonical-event counts

  • distinct visitors for an app/date-range; optional groupBy=attribution_ref adds signupsByRefsignup counts keyed by properties.attribution.ref, RFC 008) and appRankingEntrySchema/ appsRankingResponseSchema (per-app portfolio triage: visitors30d, signups30d, conversion, mrrCents, recommendation). recommendAppAction() is the pure decision function (push | bundle | kill) over the documented APP_RANKING_THRESHOLDS — the api computes the metrics, this is the only place the push/bundle/kill decision is made.

Store (store.ts, RFC 005)

computeStoreScore — the ONE ranking formula (log-scaled 28d visitors/signups/ checkouts/revenue + clamped conversion; weights in STORE_SCORE_WEIGHTS). storeConversionRate uses checkouts / max(impressions, checkouts) so it stays monotone when checkouts arrive without recorded impressions. storePublicTiers/ storeTierFor bucket raw counts into public 0–4 order-of-magnitude tiers (the privacy boundary — raw numbers never leave the owner's dashboard). Public projections: storeAppSchema, storeCreatorSchema, storeCreatorDetailSchema, storeRankingSchema. Listing inputs live in routes.ts (appListingUpsertSchema, developerProfileUpdateSchema); schemas.ts gained handleSchema, APP_CATEGORIES/appCategorySchema, listing fields on appSchema, and profile fields on developerSchema.

Named sell links (routes.ts, RFC 018)

sellLinkSchema (+ SellLink, schemas.ts) — the sell_links CRUD entity: slug (unique per developer), publishableKeyId (the api_keys row backing the page — changeable via PATCH independently of the link's own slug/URL), planIds (nullable subset; null = every public plan on the app), disabledAt (this entity's only lifecycle marker; no archivedAt, no delete route). sellLinkCreateSchema/ sellLinkUpdateSchema take the actual publishableKey VALUE (pk_...), never an id — the api resolves + validates it into publishableKeyId server-side and persists the plaintext alongside it, since api_keys itself only ever stores a key's hash (see apps/api/src/apiKeys.ts); safe because publishable keys are browser-safe by design. listSellLinksQuerySchema (?appId= optional). publicSellLinkSchema (+ PublicSellLink) is the public resolver's response (GET /v1/sell-links/:handle/:slug, auth none) — {appId, appName, publishableKey, planIds}, revealing only what the page would have shipped to a browser anyway; 404s when disabled or the backing key is revoked/archived. Powers apps/store's indexable /buy/[handle]/[slug] page and vibemonetize app create's named sell-link summary line, both new in this RFC — /p/[appId]?pk= remains the zero-setup, key-in-URL fallback for a developer with no public creator handle yet.

Outbound webhooks (webhooksOut.ts, task #6)

Contract for developer-registered webhook receivers (webhook_endpoints_out): webhookOutEventTypeSchema (subscription.created | subscription.canceled | usage.limit_reached), webhookEndpointSchema (+ WebhookEndpoint) — the CRUD response entity (secret is full only in the create response, redacted to the last 4 chars everywhere else), webhookEndpointCreateSchema (appId: null = all of the developer's apps) / webhookEndpointUpdateSchema (partial, ≥1 field), and the dispatch payload: webhookOutEnvelopeSchema ({id, type, createdAt, data}) with per-event data shapes (subscriptionCreatedDataSchema, subscriptionCanceledDataSchema, usageLimitReachedDataSchema). Route contracts are in API_ROUTES (createWebhookEndpointarchiveWebhookEndpoint, secret_key). Signing and secret generation are api implementation details (Stripe-style x-vibemonetize-signature: t=<ts>,v1=<hmac-sha256>), not part of this package.

Pricing experiments (experiments.ts, Phase 4)

experimentSchema/experimentVariantSchema — variants reference existing Plan rows (2+ price points on one app; unique keys and planIds enforced by experimentVariantsAreValid) rather than inline price overrides, reusing the per-plan Stripe price machinery instead of duplicating it. status (draft | running | stopped) is derived from startedAt/endedAt by deriveExperimentStatus — never stored redundantly. assignVariant({ experimentId, subjectId, variants }) is the pure, deterministic weighted-bucket assignment function (djb2 hash of ${experimentId}:${subjectId}) — the only place variant-bucketing logic lives; the api persists the result lazily on first lookup. experimentAssignmentSchema mirrors the experiment_assignments table. Route contracts: experimentCreateSchema, listExperimentsQuerySchema, experimentSummaryResponseSchema (checkout-starts + conversions per variant), getExperimentAssignmentsQuerySchema/ experimentAssignmentResultSchema (publishable-key, lazy per-subject assignment).

Tests

pnpm test — vitest; resolveEntitlements is covered by fast-check property tests (union semantics, max-cap meters, additive credits, trial expiry, determinism); computeStoreScore/storeTierFor likewise (determinism, per-component monotonicity, tier bounds).