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

@ophelio/sdk

v0.2.0

Published

Official JavaScript / TypeScript SDK for the Ophel.io membership & entitlement API.

Readme

@ophelio/sdk

Official JavaScript / TypeScript SDK for the Ophel.io membership & entitlement API. Works on Node 20+, Cloudflare Workers, Deno and Bun — zero runtime dependencies, ESM + CJS, fully typed from the platform's OpenAPI spec.

Status: pre-1.0. Minor versions may contain breaking changes.

Install

npm install @ophelio/sdk

Quickstart

import { Ophelio } from '@ophelio/sdk'

const ophelio = new Ophelio({ apiKey: process.env.OPHELIO_API_KEY })

// Gate admission check
const result = await ophelio.admit.check({ card: 'M-123456' })
if (result.admitted) {
  console.log(result.pricing_group) // e.g. 'member_adult'
}

// Offline gate cache
const { members, generated_at } = await ophelio.members.sync()
// later, incremental:
await ophelio.members.sync({ since: generated_at })

Authentication

Pass your project API key (o_live_…); the project is implied by the key:

new Ophelio({ apiKey: 'o_live_…' })

Errors

Non-2xx responses throw a typed subclass of OphelioError mapped from the API's AIP-193 envelope:

import { NotFoundError, OphelioError } from '@ophelio/sdk'

try {
  await ophelio.memberships.get(id)
} catch (error) {
  if (error instanceof NotFoundError) {
    // error.status === 404, error.code === 'NOT_FOUND', error.details — raw ErrorInfo
  } else if (error instanceof OphelioError) {
    // UnauthenticatedError, PermissionDeniedError, AlreadyExistsError,
    // ResourceExhaustedError, OphelioTimeoutError, OphelioConnectionError, …
  }
}

Retries & idempotency

GETs are retried automatically on network errors / 429 / 502 / 503 / 504 (exponential backoff with jitter, Retry-After honoured; configure with maxRetries, default 2). Plain mutations are never retried.

entitlements.redeem and entitlements.recordUsage are idempotency-keyed: the SDK generates a key per call and reuses it across its own retries, so they're safely retryable. If you retry at the application level (offline gate queues), pass your own key:

await ophelio.entitlements.redeem(
  { member_id, entitlement_code: 'guest_pass' },
  { idempotencyKey: `gate-7-${visitId}` },
)

Webhooks

Verify deliveries with the X-Ophelio-Signature header and your subscription secret. Always pass the raw request body:

import { Ophelio, WebhookSignatureVerificationError } from '@ophelio/sdk'

const event = await Ophelio.webhooks.constructEvent(
  rawBody,
  request.headers.get('X-Ophelio-Signature'),
  process.env.OPHELIO_WEBHOOK_SECRET,
) // throws WebhookSignatureVerificationError on mismatch

switch (event.type) {
  case 'membership.payment_failed':
    // event.data — see the event reference at https://docs.ophel.io
    break
}

Unknown event types still verify and parse, so new platform events don't break older SDK versions.

Pagination

The larger list endpoints are paginated. Their list* methods accept an optional { page_size?, page_token? } and resolve to a Page<T>:

interface Page<T> {
  items: T[]
  next_page_token: string // '' on the last page
  total_size: number // exact count across all pages
}

Most of the time, let paginate walk the pages for you — pass a closure that forwards the pagination params, capturing any ids, filters and options in it:

import { collect, paginate } from '@ophelio/sdk'

for await (const membership of paginate((page) =>
  ophelio.memberships.list(page),
)) {
  // …
}

// nested + filtered:
for await (const change of paginate((page) =>
  ophelio.memberships.listScheduledChanges(id, { ...page, status: 'all' }),
)) {
  // …
}

// or drain everything into an array (loads the whole collection into memory):
const customers = await collect((page) => ophelio.customers.list(page))

To page manually — e.g. to surface total_size or drive numbered pages — feed next_page_token back in as page_token until it comes back empty.

page_size defaults to 50 and is capped at 250. Because pagination is offset-based, a row can be seen twice or skipped across a page boundary if the collection is written to while you iterate. Paginated methods are marked (params?) in the table below; the bounded lists (config resources, listMembers) return a plain array.

API surface

Everything reachable with an API key is covered:

| Namespace | Methods | | ---------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | admit | check({ card }) | | members | sync({ since? }), get(id), listEntitlements(id, { type? }), reissueCard(id, { note? }) | | memberships | list(params?), get(id), create(...), update(id, ...), cancel(id, ...), uncancel(id), renew(id, ...), upgrade(id, ...), downgrade(id, ...), listMembers(id), listScheduledChanges(id, params?), cancelScheduledChange(id, changeId), listTransactions(id, params?), listTermTransactions(id, termId, params?), createTransaction(id, ...) | | customers | list(params?), get(id), create(...), update(id, ...), delete(id), listMemberships(id, params?) | | entitlements | redeem(...), recordUsage(...), list(), get(id), create(...), update(id, ...), delete(id) | | plans | list(), get(id), create(...), update(id, ...), delete(id), listBillingOptions(id), createBillingOption(id, ...), listEntitlements(id), createEntitlement(id, ...), listPlanLinks(id), createPlanLink(id, ...), listPricingGroupMappings(id), createPricingGroupMapping(id, ...) | | billingOptions | list(), get(id), update(id, ...), delete(id), listSalesChannels(id), createSalesChannelLink(id, ...) | | memberRoles | list(), get(id), create(...), update(id, ...), delete(id) | | pricingGroups | list(), get(id), create(...), update(id, ...), delete(id) | | salesChannels | list(), get(id), create(...), update(id, ...), delete(id) | | planEntitlements | get(id), update(id, ...), delete(id) | | planLinks | delete(id) | | planPricingGroupMappings | delete(id) | | billingOptionSalesChannels | delete(id) | | webhookSubscriptions | list(), get(id), create(...), update(id, ...), delete(id), listDeliveries(id, params?) |

Every method accepts a trailing options argument: { signal?, headers? } (plus idempotencyKey? on redeem / recordUsage).

Endpoints requiring a user session (API key management, organisation and project admin) are intentionally not in the SDK — API keys cannot call them.