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 🙏

© 2025 – Pkg Stats / Ryan Hefner

@code-net/multi-tenancy

v0.2.0

Published

A library for managing tenant context lifecycle in Node.js applications.

Downloads

37

Readme

Multi-Tenancy

The multi-tenancy package provides utilities for managing tenant-specific contexts in a Node.js application. It ensures that operations are executed within the correct tenant context and provides hooks for handling tenant lifecycle events.

Installation

Install the package using your preferred package manager:

pnpm install @code-net/multi-tenancy

Features

  • Tenant Context Management: Run operations within a specific tenant context.
  • Lifecycle Event Hooks: Listen to tenant context creation and destruction events.
  • Error Handling: Provides custom errors for missing or mismatched tenant contexts.

Usage

Before you can resolve tenant ID or name, you need to run your code within a tenant context. You can do this using the withTenant function.

If you use express, you can create a middleware to set the tenant context for each request.

import { withTenant } from '@code-net/multi-tenancy';

const app = express();
app.use((req, res, next) => {
  // Resolve the tenant whatever way you want
  // Here we assume the tenant ID and name are passed in headers
  const tenantId = req.headers['x-tenant-id'];
  const tenantName = req.headers['x-tenant-name'];
  if (!tenantId || !tenantName) {
    return res.status(400).send('Missing tenant information');
  }
  withTenant({ id: tenantId as string, name: tenantName as string }, next);
});

After this is done, you can use getTenant() or getTenantId() / getTenantName() anywhere in the request handling code to get the current tenant's ID and name.

Use cases

Database per tenant with Knex

import knex, { Knex } from 'knex';
import { onTenantContext, getTenantId } from '@code-net/multi-tenancy';

const databases: Record<string, Knex> = {};
onTenantContext('created', async (tenant) => {
  if (databases[tenant.id]) {
    // Already created
    return;
  }
  // If you have a tenant catalog, you might want to fetch connection info here
  databases[tenant.id] = knex('postgres://host/' + tenant.name);
});

export function getKnex(): Knex {
  const tenantId = getTenantId();
  const db = databases[tenantId];
  if (!db) {
    throw new Error(`No database connection for tenant ${tenantId}`);
  }
  return db;
}
// Use getKnex() to get the Knex instance for the current tenant

Row Level Security with PostgreSQL and Knex

import knex from 'knex';
import { getTenantId } from '@code-net/multi-tenancy';

const db = new knex('postgres://host/dbname');

await db.transaction(async (trx) => {
  // Whenever you start a transaction, set the tenant_id for the transaction connection
  // This assumes you have a PostgreSQL RLS policy that uses app.tenant_id
  await trx.raw(`SET app.tenant_id = ?`, [getTenantId()]);
  // Now you can use trx for all your queries within this tenant context
  // trx('table').select('*').where(...);
  // trx('table').insert({ ...
});

Row Level Security with PostgreSQL and Knex with DatabaseContext

import { getTenantId } from '@code-net/multi-tenancy';
import { KnexMaster } from '@code-net/database-context-knex';

const master = new KnexMaster('postgres://user:pass@host/dbname');

await master.on('transaction', async (trx) => {
  // Whenever you start a transaction, set the tenant_id for the transaction connection
  // This assumes you have a PostgreSQL RLS policy that uses app.tenant_id
  await trx.raw(`SET app.tenant_id = ?`, [getTenantId()]);
});

// Use the master instance as usual, it will automatically handle RLS for the current tenant

Database per tenant with Sequelize

import { Sequelize } from 'sequelize';
import { onTenantContext, getTenantId } from '@code-net/multi-tenancy';

const databases: Record<string, Sequelize> = {};
onTenantContext('created', async (tenant) => {
  if (databases[tenant.id]) {
    // Already created
    return;
  }
  // If you have a tenant catalog, you might want to fetch connection info here
  databases[tenant.id] = new Sequelize(
    `postgres://host/${tenant.name}`,
  );
  // define your models here or register them with another call to `onTenantContext('created', ...)`
});

export function getConnection() {
  const tenantId = getTenantId();
  const db = databases[tenantId];
  if (!db) {
    throw new Error(`No database connection for tenant ${tenantId}`);
  }
  return db;
}

// Use getConnection() to get the Sequelize instance for the current tenant

API

withTenant(tenant: TenantContext, callback: (tenant: TenantContext) => Promise<T>): Promise<T>

Runs the provided callback within the specified tenant context.

Parameters

  • tenant: An object containing the tenant's id and name.
  • callback: An asynchronous function to execute within the tenant context.

Example

await withTenant({ id: '1', name: 'test' }, async () => {
  console.log('Running within tenant context');
});

getTenant(): TenantContext

Retrieves the current tenant context.

Throws

  • TenantContextNotFound if no tenant context is active.

Example

const tenant = getTenant();
console.log(tenant);

hasTenant(): boolean

Checks if a tenant context is active.

Example

if (hasTenant()) {
  console.log('Tenant context is active');
}

onTenantContext(event: TenantContextEvent, callback: (tenant: TenantContext) => Promise<void>)

Registers a callback for tenant lifecycle events.

Parameters

  • event: The event to listen for ('created', 'entered', 'exited', 'destroyed').
  • callback: An asynchronous function to execute when the event occurs.

Example

onTenantContext('created', async (tenant) => {
  console.log(`Tenant created: ${tenant.name}`);
});

getTenantName(): string

Retrieves the name of the current tenant.

Throws

  • TenantContextNotFound if no tenant context is active.

Example

const tenantName = getTenantName();
console.log(tenantName);

getTenantId(): string

Retrieves the ID of the current tenant.

Throws

  • TenantContextNotFound if no tenant context is active.

Example

const tenantId = getTenantId();
console.log(tenantId);

Errors

TenantContextNotFound

Thrown when attempting to access a tenant context that does not exist.

TenantContextMismatch

Thrown when attempting to run a callback with a different tenant context while another is already active.

Testing

Run the tests to ensure everything is working correctly:

pnpm test

License

This package is licensed under the MIT License. See the LICENSE file for details.