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

@venturekit-pro/tenancy

v0.0.47

Published

Multi-tenant utilities for VentureKit

Readme

@venturekit-pro/tenancy

Warning: This package is in active development and not production-ready. APIs may change without notice.

Multi-tenant utilities for VentureKit — tenant resolution, context management, data isolation, and quota enforcement.

Installation

npm install @venturekit-pro/tenancy@dev

Overview

@venturekit-pro/tenancy provides:

  • Tenant resolution — resolve tenants from subdomain, custom domain, path, header, or JWT
  • Tenant context — createTenantContext(), getCurrentTenant(), resolveTenant()
  • Tenant middleware — createTenantMiddleware() for automatic tenant resolution per request
  • Quota enforcement — createQuotaMiddleware(), checkQuotas() for usage limits
  • Error types — TenantNotFoundError, TenantSuspendedError, TenantInactiveError, QuotaExceededError

Tenant Resolution

VentureKit supports multiple strategies for identifying the current tenant:

| Strategy | Example | |----------|---------| | Subdomain | acme.app.example.com → tenant acme | | Custom domain | app.acme.com → lookup in domain table | | Path prefix | /t/acme/api/tasks → tenant acme | | Header | X-Tenant-ID: acme | | JWT claim | tenant_id claim in access token |

Middleware

Add tenant resolution to your handlers:

import { createTenantMiddleware, createQuotaMiddleware } from '@venturekit-pro/tenancy';
import { handler } from '@venturekit/runtime';

export const main = handler(async (_body, ctx, logger) => {
  logger.info('Current tenant', { tenantId: ctx.tenant?.id });
  return { tenantId: ctx.tenant?.id };
}, {
  scopes: ['api.read'],
  middleware: [
    createTenantMiddleware({ strategy: 'subdomain' }),
    createQuotaMiddleware(),
  ],
});

Quota Enforcement

Define per-tenant quotas and enforce them automatically:

import { checkQuotas } from '@venturekit-pro/tenancy';

// Throws QuotaExceededError if over limit
await checkQuotas(tenantId, {
  apiRequests: { limit: 10000, period: 'month' },
  storage: { limit: 5_000_000_000 }, // 5 GB
});

Context

Access the current tenant anywhere in your handler:

import { getCurrentTenant } from '@venturekit-pro/tenancy';

const tenant = getCurrentTenant(ctx);
// { id: 'acme', slug: 'acme', metadata: { ... } }

Hierarchy

Tenants form trees — a group and its schools, a franchise and its stores. The tree is vk_tenants.parent_id plus vk_tenant_closure (every ancestor→descendant path with its depth, migration 0000_vk_tenancy_tree), maintained in code and read in one statement:

import { createTenantTree, unpackTenantRoles } from '@venturekit-pro/tenancy';
import { query } from '@venturekit/data';

const tree = createTenantTree(); // or { tenants: 'platform.tenant_ref', closure: 'platform.tenant_closure' } on a projection
await tree.setParent(query, schoolId, groupId); // rewrites the subtree's closure; refuses a cycle
await tree.descendantsOf(query, groupId);         // [{ tenantId, depth }], shallowest first
await tree.ancestorsOf(query, schoolId);          // nearest first

A role held in a parent counts in its descendants — a group's owner is the owner of each of its schools without a membership row in any. The rules are pure (inheritedTenantRole, reachableTenants), and the tree feeds them:

const pack = unpackTenantRoles(ctx.user.claims['custom:tenantRoles']);
const isSystemRole = (role: string) => role in SYSTEM_ROLES; // custom roles are rows of the tenant that defined them

// a request naming another tenant: may this session act as it, and as what?
const held = await tree.roleIn(query, pack, requestedTenantId, { inherits: isSystemRole });
if (!held) throw new ForbiddenError('This session may not act as that tenant');

// a session description / tenant switcher: every tenant this user may act as
const sites = await tree.reach(query, pack, { inherits: isSystemRole });

An explicit membership in a child always wins over an inherited one, and a nearer ancestor over a farther one. The store is read only when the memberships alone do not settle the question.

Erasure and export

The cascade walker that backs hardDeleteTenant also serves the two per-user obligations (GDPR art. 17 / art. 20). Both key on the column that marks a row as the user's — user_id by default — and introspect the schema, so a new table is covered the day it is added.

import { exportUserData, executeUserErasure, planUserErasure } from '@venturekit-pro/tenancy';

// Portability: every row of the user, per table, ready to redact and hand over
const dump = await exportUserData(query, { userId, skipTables: ['audit_events'] });

// Erasure: dry-run first, then delete FK children before parents
const plan = await planUserErasure(query, { column: 'member_id' });
await withTransaction((tx) => executeUserErasure(tx.query, { userId, column: 'member_id', skipTables: ['audit_events'] }));
// …then adminDeleteUser() from @venturekit/auth/server for the Cognito account.

Rows keyed by another column (author_id, created_by) need one call per column; tables in skipTables (an append-only ledger you must keep) are the ones to anonymise instead.

API Reference

See the API reference for full documentation.

License

Apache-2.0 — see LICENSE for details.