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

@super-communications/sdk

v0.10.0

Published

Typed client for the Super platform API and integration gateway — connections, not HTTP calls

Readme

@super-communications/sdk

Typed client for the Super platform API and integration gateway. One client, one connection per integration — callers never build URLs, hold provider credentials, or hand-roll retries.

Runtime-agnostic by design: the only platform requirement is a web-standard fetch. No Node-specific APIs, no framework attachments. Framework sugar (e.g. a future @super-communications/sdk-nestjs) layers on top of this core.

What you build with this SDK is the app around the connections — not the connections themselves. The Buildium and HubSpot connections ship complete: 38 Buildium namespaces (leases, workorders, rentalUnits, …) with 468+ methods generated from Buildium's OpenAPI spec, plus hand-written HubSpot methods. You don't build or register a connection; you use one. The premise "let me build a Buildium connection" is a category error the docs sometimes invite by using "connection" as a central noun — it means "a surface the SDK provides."

The SDK is the intended path — end to end. Configure an agent, create a trigger, bind them together, register the webhook, publish events, read invocations, all through the SDK. The only touchpoints that require a browser: vendor OAuth linking (Settings → Integrations) and the one-shot display of a new sk_super_… API key (Settings → API Keys). Webhook signing secrets (whsec_…) come back in the createWebhookSubscription response body — single-shot, API path. See BOOTSTRAP.md for the setup sequence.

Install. pnpm add @super-communications/sdk — resolves to the current release. Shape may still change between minor versions before 1.0.0; pin the exact version if you need stability across a deploy.

First time? BOOTSTRAP.md walks the full zero-to-a-working-integration setup (link vendor → mint key → create trigger → register webhook → runtime code). GETTING_STARTED.md is the shorter zero-to-first-successful-call check for someone who already has the pieces.

Usage

import { SuperClient } from '@super-communications/sdk'

const superClient = new SuperClient({
  apiKey: process.env.SUPER_API_KEY!, // sk_super_...
  baseUrl: 'https://api.backend.hiresuper.com',
})

// Super's own platform API — no proxy
await superClient.core.publishEvent({
  event: 'lead.received',
  source: 'my-bridge',
  data: { contactId: '12345', phone: '+15555550100' },
})

// The company's connected HubSpot portal, via Super's integration gateway —
// Super injects the credentials, callers never see a HubSpot token
await superClient.hubspot.updateContact({
  contactId: '12345',
  properties: { hubspot_owner_id: '99', hs_lead_status: 'CONNECTED' },
})

// The company's connected Buildium account, via the integration gateway.
// Namespaces mirror Buildium's own OpenAPI tags — see
// https://developer.buildium.com/ for the reference docs. Generated from
// the spec, so every Buildium public operation (except Administration,
// Custom Fields, Resident Center, Client Leads) has a method.
const unit = await superClient.buildium.rentalUnits.getRentalUnitById({
  unitId: 12345,
})

// Create a work order (non-idempotent — the SDK will not retry on 5xx).
// EntryAllowed + VendorId are required by Buildium; hover
// `WorkOrderCreate` in your editor for the full field list.
await superClient.buildium.workOrders.createWorkOrder({
  body: {
    EntryAllowed: 'Yes',
    VendorId: 100,
    Title: 'Fix leak in kitchen',
  },
})

// Every route also has a named constant — greppable + refactorable
import { ROUTES as WorkOrderRoutes } from '@super-communications/sdk/connections/buildium/work-orders'
console.log(WorkOrderRoutes.GET_WORK_ORDER_BY_ID)

Types

Type names follow a convention so lookup is predictable:

| Kind | Pattern | Example | Import from | | --------------------- | ---------------------- | ------------------------------ | --------------------------------------- | | HubSpot / core entity | <Vendor><Entity> | HubspotContact | root — import type { HubspotContact } | | Buildium entity | Buildium.<Entity> | Buildium.Lease | namespace — import type { Buildium } | | Method input | <Verb><Object>Input | UpdateContactInput | root | | Method result summary | <Verb><Object>Result | PublishEventResult | root | | Errors | <Reason>Error | IntegrationNotConnectedError | root |

Buildium entities live under a namespace, not at the root. There is no bare Lease export; use Buildium.Lease instead:

import type { Buildium } from '@super-communications/sdk'

const lease: Buildium.Lease = await client.buildium.leases.getLeaseById({
  leaseId: 1,
})
const create: Buildium.LeaseCreate = {
  LeaseFromDate: '2026-01-01',
  LeaseType: 'Fixed',
  UnitId: 100,
  SendWelcomeEmail: false,
}
const query: Buildium.GetLeasesQuery = { leasestatuses: ['Active'] }

Why the split: Buildium's surface has ~500 entity types and folding all of them into the root autocomplete would drown out cross-vendor primitives. The namespace keeps vendor scope explicit. HubSpot and core types don't have this issue (small hand-written surface) so they live at the root.

Buildium namespace classes are accessed via superClient.buildium.<namespace> — type via SuperClient['buildium']['rentalUnits'] if you need to name it. Every non-primitive Buildium interface carries [key: string]: unknown so a new upstream field never breaks a strict-typed caller.

Types are not exhaustive. The [key: string]: unknown overflow means an unexpected field passes through instead of crashing — but it also means the SDK will not catch a newly-required Buildium field at compile time. That kind of change surfaces at runtime as a 4xx from the proxy carrying Buildium's validation error. Treat compile-time typing as a strong default, not full API contract validation.

The overflow bites reads too. Because every entity carries [key: string]: unknown, a field-name typo (balance.TotalBaance instead of TotalBalance) compiles clean and resolves to unknown — even under strict + noUncheckedIndexedAccess. There's no compile-time signal. Rely on autocomplete for field access, not memory; if you must hand-type a field name, hover the entity type to verify the spelling.

Webhooks

Super delivers events over signed HTTPS webhooks. Register a subscription (returns a whsec_… secret once), then verify every incoming request against that secret with the SDK's verifySuperSignature helper:

import express from 'express'
import { verifySuperSignature } from '@super-communications/sdk/webhooks'

const app = express()

app.post(
  '/webhooks/super',
  express.raw({ type: 'application/json' }), // ← raw bytes, not json — see below
  (req, res) => {
    const ok = verifySuperSignature(
      req.body as Buffer,
      req.header('X-Super-Signature'),
      process.env.SUPER_WEBHOOK_SECRET!,
    )
    if (!ok) return res.status(401).send('bad signature')

    // Signature good — parse and handle. Ack fast; enqueue heavy work.
    res.status(200).send('ok')
  },
)

verifySuperSignature checks two things: (a) the request was signed with your secret and (b) the timestamp is fresh (default 5-minute window, bounds replay). Requires Node's crypto — the helper runs on Node runtimes; browser consumers should not receive raw webhook traffic. It ships under the /webhooks subpath so consumers of the SDK root (browser or edge bundlers) don't inherit Node types they'll never use.

Critical: verify before your framework parses the body. Call verifySuperSignature against the raw request bytes. Frameworks that parse first lose whitespace and key ordering, and the signature check will fail on legitimate requests. Every consumer of this helper has to route raw bytes to it — the SDK cannot do this for you. See DEPLOYMENT.md for framework-specific raw-body setup (Express, Fastify, Next.js App Router).

Freshness bounds replay, but does not deduplicate. A valid signature will verify on every retry inside the 5-minute window. Dedupe on eventId at the application layer if you need exactly-once processing.

Silent-fail is deliberate. The helper returns false for every failure mode — bad signature, malformed header, stale timestamp, missing header — and never throws for attacker-controlled input. Don't distinguish these in your response (same status code, same body) — variance leaks information to attackers.

Building with an AI assistant

Cursor, Claude Code, Continue, and similar in-editor AI harnesses read the SDK through the TypeScript LSP — every method's @param, @returns, and @throws shows up on hover and in completions. Two extra files ship in the tarball to make the SDK legible without reading the code:

  • skill.md — a Claude Code skill file. When @super-communications/sdk appears in dependencies and the user asks to build a Super integration (or to sync HubSpot ↔ Buildium, or to publish a Super event), the skill loads and gives the harness the positioning, the three-connection model, and the error-narrowing pattern. The tarball at the package root is the source of truth; for harnesses fetching over HTTP, use one of the CDN paths: GitHub raw, unpkg, or jsDelivr.
  • PROMPTS.md — canonical patterns organized by SDK mechanic (construct, read, list with filter, paginate, create/update, compose across connections, correlation IDs, publish an event, narrow errors). Vendor examples inline; the shapes generalize across every connection.

For the human introduction, keep reading below (Usage, Errors, Retries, etc.). For the LLM introduction, the answer is those two files plus the JSDoc on every method.

Errors

Every failed call throws a subclass of SuperSdkError — catch by type or branch on the stable code:

import {
  BuildiumUpstreamError,
  CredentialsUnavailableError,
  HubspotUpstreamError,
  IntegrationNotConnectedError,
  MissingScopeError,
  RateLimitError,
  UpstreamError,
} from '@super-communications/sdk'

try {
  await superClient.hubspot.updateContact({ contactId, properties })
} catch (error) {
  if (error instanceof MissingScopeError) {
    // key doesn't carry the required proxy scope — check Settings → API Keys
    // (error.requiredScope names the missing scope, e.g. 'integrations:hubspot:proxy')
  } else if (error instanceof IntegrationNotConnectedError) {
    // customer hasn't linked the vendor yet — prompt them to connect
  } else if (error instanceof CredentialsUnavailableError) {
    // link exists but the upstream rejected it — surface for reauth
  } else if (error instanceof BuildiumUpstreamError) {
    // error.upstream is a BuildiumErrorBody — narrow with autocomplete
    logger.warn({
      userMessage: error.upstream?.UserMessage,
      code: error.upstream?.ErrorCode,
    })
  } else if (error instanceof HubspotUpstreamError) {
    // error.upstream is a HubspotErrorBody
    logger.warn({
      category: error.upstream?.category,
      correlationId: error.upstream?.correlationId,
    })
  } else if (error instanceof UpstreamError) {
    // any other vendor — error.upstream is `unknown` until its subclass is added
  }
}

Both BuildiumUpstreamError and HubspotUpstreamError extend UpstreamError, so a broad instanceof UpstreamError still catches everything if you don't need per-vendor narrowing. Adding a new typed vendor means adding one row to the codegen's error routing table + a body interface in errors/upstream-body.ts — no changes to caller code that already catches on the base class.

Retries & timeouts

429/5xx/network failures on idempotent methods (GET/HEAD/PUT/DELETE) are retried with exponential backoff + jitter (2 retries by default). POST/PATCH are never retried automatically — a duplicated write (contact, booking, event) can't be undone; handle those failures explicitly until idempotency keys exist. Every attempt has a timeout (30s default):

import { SuperClient } from '@super-communications/sdk'

new SuperClient({
  apiKey: process.env.SUPER_API_KEY!,
  baseUrl: 'https://api.backend.hiresuper.com',
  retry: { maxRetries: 3 },
  timeoutMs: 10_000,
  connectionTimeouts: {
    // Long-running Buildium iterators can legitimately need more headroom
    // than a core event publish.
    buildium: 60_000,
  },
})

When Super's throttler emits Retry-After on a 429 or 503, the SDK honors the header (capped at 60s) instead of its exponential backoff — you can watch this via the onRetryScheduled hook (see below). Per-call overrides via options.timeoutMs are supported on regular methods:

await client.buildium.leases.getLeaseById(
  { leaseId: 42 },
  { timeoutMs: 120_000 },
)

Iterators are different — iterOptions accepts { pageSize, signal }, not timeoutMs. The per-connection connectionTimeouts (above) apply to each underlying page fetch:

for await (const tx of client.buildium.generalLedger.iterateTransactions(
  {
    query: {
      startdate: '2026-01-01',
      enddate: '2026-06-30',
      glaccountids: [1001, 1002],
    },
  },
  { pageSize: 200 },
)) {
  // each page fetch honors client.connectionTimeouts.buildium
}

Requires Node ≥ 20.3 (for AbortSignal.any) and Node ≥ 19 for crypto.randomUUID — enforced via the SDK's engines.node field.

Configuring observability

The SDK ships no built-in logger. It exposes four callbacks so you wire whatever you already use (pino, winston, console, an OTel span, an alerting webhook — the SDK stays out of it):

import { SuperClient } from '@super-communications/sdk'

new SuperClient({
  apiKey: process.env.SUPER_API_KEY!,
  baseUrl: 'https://api.backend.hiresuper.com',
  observability: {
    onRequest: ctx => log.debug({ ctx }, 'super.sdk.request'),
    onResponse: ctx => log.info({ ctx }, 'super.sdk.response'),
    onError: ctx => log.error({ ctx }, 'super.sdk.error'),
    onRetryScheduled: ctx =>
      log.warn(
        {
          reason: ctx.reason, // 'rate_limit' | 'service_unavailable'
          attempt: ctx.attempt,
          retryAfterMs: ctx.retryAfterMs,
        },
        'super.sdk.retry_scheduled',
      ),
  },
})
  • onRequest(ctx) fires before every attempt — including retries. ctx = { method, path, attempt, requestId }.
  • onResponse(ctx) fires on any 2xx. Adds { status, durationMs }.
  • onError(ctx) fires once per terminal failure (retries exhausted or non-retryable). Adds { error, durationMs } — error is a typed SuperSdkError subclass. Not fired for caller-initiated AbortError — cancellation is a normal termination, not a failure.
  • onRetryScheduled(ctx) fires before every retry triggered by a 429 or 503, so you can log or self-throttle before the SDK sleeps. ctx = { attempt, reason, retryAfterMs, retryAfterRaw, requestId }.
    • reason is 'rate_limit' (429) or 'service_unavailable' (503) — wire your rate-limit alert to filter on 'rate_limit' only.
    • retryAfterMs is undefined when the server sent Retry-After as an HTTP-date (falls back to exponential backoff); retryAfterRaw always carries the raw header if present.

Attempt semantics: attempts are 0-indexed. onRequest fires with the attempt about to run; onRetryScheduled fires with the attempt that just came back 429/503. A typical rate-limited retry sequence:

onRequest(attempt: 0) → onRetryScheduled(attempt: 0, retryAfterMs: 5000) → onRequest(attempt: 1) → onResponse(attempt: 1)

Hooks are advisory. A throwing hook is caught so a bug in your logging doesn't crash the SDK — the SDK logs the failure to console.warn('super-sdk: observability hook threw', err) so you can still see it. Wrap in your own try/catch if you need must-fire semantics.

Request IDs

Every outbound request carries a fresh X-Super-Request-Id (UUID). Super's API may echo it back on the response; either way, the SDK surfaces the id on every thrown error as error.requestId — one token to hand to support:

import { SuperSdkError } from '@super-communications/sdk'

try {
  await client.hubspot.getContact({ contactId: '123' })
} catch (error) {
  if (error instanceof SuperSdkError) {
    console.error({ requestId: error.requestId, code: error.code })
  }
}

Cancellation

Every regular method accepts an AbortSignal via the second (options) argument, composed with the internal timeout. Aborted requests throw AbortError (an instanceof SuperSdkError subclass) and are never retried:

const controller = new AbortController()

await client.buildium.leases.getLeaseById(
  { leaseId: 42 },
  { signal: controller.signal },
)

Iterators accept the signal in their second (iter-options) argument — same shape, different position:

const controller = new AbortController()

for await (const tx of client.buildium.generalLedger.iterateTransactions(
  {
    query: {
      startdate: '2026-01-01',
      enddate: '2026-06-30',
      glaccountids: [1001, 1002],
    },
  },
  { signal: controller.signal },
)) {
  if (someCondition(tx)) controller.abort()
}

API versioning

The SDK sends X-Super-Api-Version: 2026-08-01 on every request. When Super evolves the API in a breaking way we'll date-anchor a new version; upgrading the SDK opts you into the new shape. No action needed today.