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

@tenantscale/koa

v0.1.1

Published

Koa middleware adapter for TenantScale — API key auth, plan enforcement, rate limiting, audit logging

Downloads

207

Readme

@tenantscale/koa

Koa middleware for TenantScale — multi-tenant API key authentication, portal session validation, plan enforcement, rate limiting, and audit logging for Koa apps.

Install

npm install @tenantscale/koa koa
# or
pnpm add @tenantscale/koa koa

Requires @tenantscale/sdk as a peer dependency.

Quick Start

import Koa from 'koa'
import { TenantScale } from '@tenantscale/sdk'
import { authenticateApiKey, errorHandler } from '@tenantscale/koa'

const ts = new TenantScale({ /* SDK config */ })
const app = new Koa()

// Register first: wraps downstream middleware and maps
// TenantScale errors to structured JSON responses
app.use(errorHandler())

// Authenticate every request via the x-api-key header
app.use(authenticateApiKey({ ts }))

app.use(async (ctx) => {
  ctx.body = { tenantId: ctx.tenantId }
})

app.listen(3000)

Options

Every middleware takes a KoaAdapterOptions object as its first argument:

| Option | Type | Default | Description | |--------|------|---------|-------------| | ts | TenantScale | — (required) | TenantScale SDK instance | | audit | boolean | true | Log an audit event on successful API key authentication | | apiKeyHeader | string | 'x-api-key' | Header name for API key authentication | | authHeader | string | 'authorization' | Header name for portal session authentication |

API Reference

authenticateApiKey(options)

Validates the API key from the configured header, sets ctx.tenantKey (an ApiKeyInfo) and ctx.tenantId, and logs an api_key.authenticated audit event unless audit: false. Responds 401 / AUTH_FAILED when the key is missing or invalid.

app.use(authenticateApiKey({ ts }))

requireScope(options, ...scopes)

Requires the authenticated API key to hold the given scopes. Run after authenticateApiKey. Responds 403 / MISSING_SCOPE.

router.delete('/tenants/:id', authenticateApiKey({ ts }), requireScope({ ts }, 'admin'), handler)

requirePortalSession(options)

Validates a Bearer <token> portal session from the Authorization header. Sets ctx.portalSession (a PortalSessionInfo) and ctx.tenantId when tenant-bound. Responds 401 / SESSION_INVALID.

router.get('/portal/dashboard', requirePortalSession({ ts }), handler)

requirePortalRole(options, ...roles)

Requires the portal session to hold one of the given roles. Run after requirePortalSession. Responds 403 / MISSING_ROLE.

requireSuperAdmin(options)

Requires a super admin portal session. Run after requirePortalSession. Responds 403 / NOT_SUPER_ADMIN.

requirePlanLimit(options, feature, currentCount)

Checks the tenant's usage of feature against their plan limit. currentCount is a number or a (possibly async) (ctx) => number function. A null plan limit means unlimited. Requires ctx.tenantId, so run after authenticateApiKey or requirePortalSession. Responds 403 / PLAN_LIMIT_REACHED.

router.post(
  '/projects',
  authenticateApiKey({ ts }),
  requirePlanLimit({ ts }, 'projects', async (ctx) => countProjects(ctx.tenantId)),
  handler,
)

rateLimitByApiKey(options)

Enforces the tenant's daily request limit for the authenticated API key. Run after authenticateApiKey. Responds 429 / DAILY_LIMIT_EXCEEDED.

rateLimitByIp(options)

Rate limits by client IP (resolved from x-forwarded-for, then x-real-ip). Useful for unauthenticated routes such as signup. Sets Retry-After and responds 429 / IP_RATE_LIMITED when blocked.

auditLog(options, config)

Writes an audit event for the route. Fire-and-forget: failures are logged via ts.logger and never block the request. No-op when ctx.tenantId is not resolved.

auditLog({ ts }, {
  action: 'ticket.created',
  resource: '/api/tickets',
  actorType: 'user', // 'user' | 'system' | 'admin_api' | 'admin_impersonation'
  getDetails: (ctx) => ({ body: ctx.request.body }),
})

errorHandler(options?)

Returns a Koa middleware that catches downstream errors and maps TenantScaleError subclasses to structured JSON. Adds details ({ limit, current } for plan limit errors, { planLimit } for rate limit errors) and a Retry-After header when applicable. Non-TenantScale errors become 500 / INTERNAL_ERROR (message hidden when NODE_ENV=production). Register it before other middleware.

Error response shape:

interface ErrorResponse {
  error: string
  code: string
  statusCode: number
  details?: Record<string, unknown>
}

Context State

| Property | Set by | Type | |----------|--------|------| | ctx.tenantKey | authenticateApiKey | ApiKeyInfo | | ctx.tenantId | authenticateApiKey / requirePortalSession | string | | ctx.portalSession | requirePortalSession | PortalSessionInfo |

ApiKeyInfo and PortalSessionInfo are re-exported from this package.

Error Codes

| Middleware | HTTP Status | Code | |------------|-------------|------| | authenticateApiKey | 401 | AUTH_FAILED | | requirePortalSession | 401 | SESSION_INVALID | | requireScope | 403 | MISSING_SCOPE | | requirePortalRole | 403 | MISSING_ROLE | | requireSuperAdmin | 403 | NOT_SUPER_ADMIN | | requirePlanLimit | 403 | PLAN_LIMIT_REACHED | | rateLimitByApiKey | 429 | DAILY_LIMIT_EXCEEDED | | rateLimitByIp | 429 | IP_RATE_LIMITED | | errorHandler (fallback) | 500 | INTERNAL_ERROR |

Documentation