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

tenancyjs-core

v0.1.2

Published

Framework-neutral tenant context and lifecycle primitives for TenancyJS.

Readme

tenancyjs-core

The foundation of TenancyJS — a fail-closed, TypeScript-first multi-tenancy toolkit for Node.js. Tenant identity follows your async scope, and any tenant-aware access without a valid context throws instead of returning another tenant's data.

📚 Full documentation: tenancyjs.pages.dev — start here.

tenancyjs-core gives you the framework-neutral tenant context, lifecycle, and adapter contract. You compose it with an adapter (your ORM) and an integration (your framework):

tenancyjs-core  ×  an adapter (Prisma/Knex/Lucid/TypeORM/Sequelize/Drizzle/Mongoose)  ×  an integration (Express/Next.js/NestJS/AdonisJS)

Install

npm install tenancyjs-core
# …then add the adapter for your ORM and the integration for your framework, e.g.:
npm install tenancyjs-adapter-prisma tenancyjs-integration-express

TenancyJS is 0.x: safe for real use, with the API still settling before 1.0.

The CLI — tenancyjs-cli

One operational CLI for your whole tenant lifecycle. Run any command with npx tenancyjs-cli … (no install), or npm install -g tenancyjs-cli for a global tenancy. Every command takes --json for machine-readable, secret-redacted output.

Scaffold & inspect

npx tenancyjs-cli init            # detect your framework + ORM and scaffold the wiring
npx tenancyjs-cli doctor          # inspect a project's static setup + migration effort
npx tenancyjs-cli tenant check    # health-probe the runtime and warn on untested combinations
npx tenancyjs-cli test:leak --test-file ./leak.mjs   # run a cross-tenant isolation leak test

Manage tenants (backed by your own store — see Configuration)

npx tenancyjs-cli tenant list                 # list tenants from your store
npx tenancyjs-cli tenant show acme            # show one tenant
npx tenancyjs-cli tenant create acme --set plan=pro --set region=eu
npx tenancyjs-cli tenant suspend acme         # / tenant activate acme

Provision, migrate & run (per-tenant placement + scripts)

npx tenancyjs-cli tenant provision acme       # create the tenant's schema/database (your hook)
npx tenancyjs-cli tenant migrate --all        # migrate every tenant, reporting each outcome
npx tenancyjs-cli tenant deprovision acme     # drop it (explicit id only — never --all)
npx tenancyjs-cli run ./backfill.ts --tenant acme     # run a script inside a tenant scope
npx tenancyjs-cli run ./rollup.ts --central           # …or in the central (cross-tenant) scope

Full reference: CLI docs →.

Or let an AI do it: copy a prompt from Build with AI and paste it into your assistant.

How it works (30 seconds)

  1. An integration resolves the tenant per request and opens a scope.
  2. Your code queries through the adapter's scoped client — no manual WHERE tenant_id.
  3. Outside a valid scope, tenant-aware access throws instead of leaking.
import { TenancyManager, type TenantRecord } from "tenancyjs-core";

interface Tenant extends TenantRecord {
  readonly id: string;
}

const manager = new TenancyManager<Tenant>();

await manager.runWithTenant({ id: "acme" }, async () => {
  // Every tenant query inside here is scoped to "acme".
  const tenant = manager.getTenantOrFail();
});

// Outside a scope? It fails closed.
manager.getTenantOrFail(); // ✗ throws TenantContextError — never an unscoped read

Full end-to-end wiring is in the Quickstart.

Where to next

| Guide | | | -------------------------------------------------------------------------------- | ---------------------------------------------------------------- | | Getting started | Install + scaffold for your stack | | Adapters | Prisma · Knex · Lucid · TypeORM · Sequelize · Drizzle · Mongoose | | Integrations | Express · Next.js · NestJS · AdonisJS | | Strategies | row-level · schema-per-tenant · database-per-tenant | | The rules & limitations | What's rejected, and why — read before you build |


Core API reference

The primitives this package provides directly.

Guarantees

  • Tenant identity is scoped with Node.js AsyncLocalStorage, never process-global mutable state.
  • Missing tenant context fails with a typed TenantContextError.
  • Central execution is explicit and lexical.
  • Nested and concurrent scopes restore their parent automatically.
  • Bootstrappers set up in registration order and revert in reverse order.
  • Cleanup continues after failures and reports every cleanup error.

Explicit central context

await manager.runInCentralContext(async () => {
  // Central (cross-tenant) work is explicit. getTenantOrFail() throws with reason "central" here.
});

Resolver failure never enters central mode — framework integrations decide whether a route is central before calling this API.

Bootstrappers and lifecycle events

Bootstrappers are fixed when the manager is created; each is { id, bootstrap, revert }:

const manager = new TenancyManager({
  bootstrappers: [
    {
      id: "database",
      async bootstrap(context) {
        /* prime context-local resources for context.tenant */
      },
      async revert(context) {
        /* release only resources owned by this scope — always runs on teardown */
      },
    },
  ],
});

Lifecycle order: tenancy.initializing → bootstrap (registration order) → tenancy.initialized → callback → tenancy.ending → revert (reverse order) → tenancy.ended. Subscribe with manager.on(event, listener); the returned function unsubscribes idempotently.

Isolation strategy intent

import { defineConfig } from "tenancyjs-core";

export default defineConfig({ strategy: "rowLevel" });

Core carries strategy intent; adapters implement the actual database isolation.

Errors

  • TenantContextError — tenant access with no scope, or in central mode.
  • InvalidTenantError — a tenant lacks a non-empty string id.
  • InvalidBootstrapperError / DuplicateBootstrapperError — invalid manager configuration.
  • TenancyLifecycleError — cleanup failed; inspect primaryError, hasPrimaryError, cleanupErrors.

License

MIT