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

@dreamtree-org/tenancy

v0.3.0

Published

The multi-tenant and RBAC authorization core for every Dreamtree product: company resolution, per-company database pools, sessions, TOTP, password recovery, provisioning, branch guard, unified RBAC policy engine, React client and platform settings, with N

Readme

@dreamtree-org/tenancy

The multi-tenant and RBAC authorization core for every Dreamtree product: combines company resolution, per-company database pools, sessions, TOTP, password recovery, provisioning, the branch guard and platform settings with the unified RBAC policy engine (@dreamtree-org/tenancy/authz) and React permission gating (@dreamtree-org/tenancy/react).

  • One database per company, one control-plane ("platform") database. Single-DB apps can also use the standalone RBAC engine and KORM adapter via @dreamtree-org/tenancy/authz.
  • Fails closed: unknown models, unregistered verbs, explicit deny statements (which override even superadmin), other branches' rows and suspended companies all get refused.
  • Hierarchical scope ranking (all > org > team > own), wildcard pattern matching (invoice.*.all), and automatic row-level KORM WHERE synthesis (accessibleBy, mergeWhere, applyScopeFilter).
  • Ships ESM + CJS across 5 entry points: ., /authz, /react, /next, and /express.

Install

npm install @dreamtree-org/tenancy knex mysql2 @node-rs/argon2 @dreamtree-org/korm-js
# optional: ioredis (shared throttle counters), next (the /next adapter), react (the /react client)

60-second start

// lib/tenancy.js — once per app
const { createTenancy, BASE_SETTING_DEFINITIONS, BASE_SETTING_FAMILIES } = require('@dreamtree-org/tenancy');
const catalog = require('./permissionCatalog');

module.exports = createTenancy({
  appKey: 'acme',                      // tenant_metadata key, Redis prefix
  prefix: 'ac',                        // ac_session, ac_csrf, ac_ constraint names
  platformDbUrl: process.env.PLATFORM_DATABASE_URL,
  tenantSchema: { version: 'ac-1', korm: require('./schema/sync.json') },
  permissions: { catalog: catalog.PERMISSIONS, defaultRoles: catalog.DEFAULT_ROLES, canPerform: catalog.canPerform },
  screens: require('./screenManifest').SCREEN_MANIFEST,
  settings: { definitions: [...BASE_SETTING_DEFINITIONS], families: BASE_SETTING_FAMILIES },
  branchPolicy: { branchOwned: ['Invoice'], sharedMaster: ['Customer'], child: { InvoiceLine: { parent: 'Invoice', fk: 'invoice_id' } } },
});
const tenancy = require('./lib/tenancy');

const company = await tenancy.resolve('acme');          // slug, or a verified hostname
const { tenantDb, korm } = await tenancy.db(company.id); // pooled, identity-verified
const session = await tenancy.sessions.createSession(tenantDb, { userId, activeBranchId, credentialVersion: 1, permissionVersion: 1 });
await tenancy.provision({ companyId: company.id });      // enqueue; a worker runs tenancy.retryProvisioning(jobId)

The secret key ring, peppers and session secret default to the environment (TENANT_SECRET_KEYS_JSON + TENANT_SECRET_ACTIVE_KEY_ID, PASSWORD_PEPPER, MPIN_PEPPER, SESSION_SECRET, REDIS_URL). They're read the first time they're used, never at import.

What you get

| Property | What it is | |---|---| | resolve(locator) | Company row by slug or verified custom domain, or null | | db(companyId) | { knex, korm, tenantDb }: one pool per company, single-flight, verified against tenant_metadata | | withCompany(id, fn) | Run fn with the company's knex bound (jobs, scripts) | | provision(args) / retryProvisioning(jobId) | Leased, checkpointed, idempotent provisioning jobs | | sessions | Opaque sessions as digests, version binding, idle/absolute expiry, throttles (Redis or DB) | | identity | Argon2id + pepper passwords, MPIN, users, memberships, effective permissions | | totp / recovery / mfa | TOTP with recovery codes and lock order; password reset tokens; the login MFA ticket | | secrets | AES-256-GCM envelopes bound by AAD, key rotation, streaming envelope | | connections | Pool manager: eviction, serverless release, invalidateCompany | | tenantSchema / appSchema / seed / syncLock / migrator | Company DDL, additive upgrades with preflight, catalogue seed, advisory sync lock | | branchPolicy / guard / authz | Branch classification reader, the KORM request guard, KORM rules and evaluate() | | platform.{db, schema, settings, secrets, admins, companyAdmins} | Control-plane pool, platform DDL, settings service, superadmins | | binding | openTenantDbScope, runWithTenantDb, currentKnex (AsyncLocalStorage, shared by every copy) |

Every service is also exported as a factory (createSessionService, createBranchGuard, …) with the same function signatures the products used. That's how a product can switch one module at a time.

Config reference

| Key | Type | Default | Meaning | |---|---|---|---| | appKey | string | "app" | Product key: tenant_metadata.application_key, Redis prefix, password blocklist word | | prefix | string | "dt" | Short prefix: ${prefix}_session, ${prefix}_csrf, ${prefix}-mfa:v1:, constraint and lock names, globals | | platformDbUrl / platformDb | string | fn / db | PLATFORM_DATABASE_URL | Control-plane connection, or your own { all, get, run, exec, tx } | | secretKey / secretKeys | string / { activeKeyId, keys } | env key ring | Encryption keys (32 bytes, base64) | | environment | string | DEPLOYMENT_ENV or "development" | AAD environment label (never NODE_ENV) | | passwordPepper, mpinPepper, sessionSecret | string | fn | env | Server-held secrets | | redisUrl, redisPrefix, redis | string / string / client | REDIS_URL, ${appKey}: | Optional shared throttle store | | tenantSchema | { version, korm, appRoot?, extraDdl?, extraUpgrades?, app? } | required for pools/schema | KORM schema, version stamp, product tables and registries | | permissions | { catalog, keys?, defaultRoles, canPerform? } | required for authz/seed | Permission catalogue. Without canPerform, pass isKnownModel + resolvePermission | | screens | array | [] | Screen manifest seeded into each company | | settings | { definitions, families?, defaults?, rules?, adapterKeyPattern? } | required for settings | Registry. Spread BASE_SETTING_* for the shared keys | | branchPolicy | classification or a built policy | empty | branchOwned, sharedMaster, companyConfig, child, childRefs, readOnly, fkModel, frozenFlags, publicFigures, maxPageSizeEnv | | database | hooks | env-driven | connectionExtras, sslOptions, pool(role), isServerless, onConnectionError, registerReleaser, knexLog, sessionHook, withSessionTimeout, beforeKorm | | provisioning | { steps: { managed, external }, transitionCompany? } | lifecycle default | Step tables (the product's database layout) | | platform | { extraDdl, extraColumns, seed } | none | Product platform tables and seeds | | passwordPolicy | { blockedWords } | fn | [appKey] | Extra blocked words, or your own (pw, ctx) => violations | | logger, scrub | fn | console + DSN scrub | (name) => ({ debug, info, warn, error }) and (text) => text | | sessions | { cookieName?, limits? } | ${prefix}_session, product defaults | Cookie name and idle/absolute limits | | syncLockPrefix, streamMagic, adminRoleKey | string | ${prefix}_sync:, "DTS1", "tenantadmin" | Names kept for compatibility with existing data |

Sections that hold product data (tenantSchema, permissions, screens, settings, branchPolicy, database, provisioning, platform) may be functions. That lets a product wire lazily and avoid require cycles.

Recipes

Next.js (App Router). Add the package to serverExternalPackages, then:

import { createNextTenancy } from '@dreamtree-org/tenancy/next';
import tenancy from '@/lib/tenancy';

export const { tenantRoute, publicRoute, getTenantContext, issueLoginSession, csrf } = createNextTenancy(tenancy, {
  errorMappers: [dbBusyResponse],          // optional extra error → response mapping
  formatIssues: (e) => z.treeifyError(e),  // optional 400 details shape
  // optional: your error envelope for every 401 / 403 / 409 / 429 / 500 the glue answers
  respond: ({ status, code, message, details, headers }) =>
    Response.json({ error: { code, message, details } }, { status, headers }),
});

export const POST = tenantRoute(async ({ ctx, body }) => ({ ok: true }), { body: schema, permission: 'floor_plan.read.own' });

requirePermission (and tenantRoute's permission) ranks scopes: a holder of floor_plan.read.branch passes a floor_plan.read.own check (own < branch < company < all, plus the core own < team < org < all ladder and wildcards). Pass permissionSatisfies(held, required) to replace the rule; holdsPermission is the default, exported from /next.

Each piece is also a separate factory (createCsrf, createRequestContext, createLoginSessions, createIpRateLimit, createRouteKit) that takes its dependencies as functions. A product can pass its own modules that way, and its tests can keep mocking them by path.

Express.

const { tenancyMiddleware, requirePermission } = require('@dreamtree-org/tenancy/express');
app.use(tenancyMiddleware(tenancy));                 // req.tenant, company knex bound
app.post('/lists', requirePermission('lists.edit'), handler);
app.get('/plans', requirePermission('floor_plan.read.own'), handler); // .branch / .company holders pass too

Policy defaults (fail closed). tenancy.authz.registerKormRules refuses every BRANCH_OWNED model when the context has no activeBranchId. tenancy.authz.evaluate does not let ctx.isAdmin hold permissions unless the app opts in, per call (evaluate({ ..., isAdminBypass: true })) or for the policy (createTenancy({ permissions: { isAdminBypass: true } })).

Standalone RBAC & KORM Policy Engine (@dreamtree-org/tenancy/authz).

import {
  rbac,
  evaluate,
  permissionSatisfies,
  accessibleBy,
  applyScopeFilter,
  composeWithDenies,
} from '@dreamtree-org/tenancy/authz';

// Embedded KORM RBAC facade (single-DB or per-tenant DB):
const authz = await rbac({ db: knexInstance, dbClient: 'mysql', roles: SEED_ROLES });
await authz.can({ id: userId }, 'invoice.read.own', { resourceOwnerId: row.created_by });

// Row-level KORM WHERE narrowing:
const { where } = accessibleBy(statements, 'read', 'project', { userId, branch_id: branchId });
request.body.where = applyScopeFilter(request.body.where, where);

React Client (@dreamtree-org/tenancy/react).

import { RbacProvider, Can, useCan, hasPermission, colonFormat } from '@dreamtree-org/tenancy/react';

// Wrap app (controlled mode or store mode):
<RbacProvider permissions={user.permissions} isSuperAdmin={user.isAdmin}>
  <Can permission="invoice.create.own">
    <NewInvoiceButton />
  </Can>
</RbacProvider>

What it deliberately does NOT do

  • Own your domain. Permission catalogues, screen manifests, settings keys, branch classification and KORM models stay in the product.
  • Create databases. The managed-database layout (provisioner principal, migrator/runtime users, append-only grants and triggers) is product infrastructure. You pass the step tables in, and the package runs them as a leased, resumable job.
  • Send mail or SMS. Recovery returns the raw token; delivering it is the caller's job.
  • Run migrations that drop or rename. Every schema pass is additive. Changing an existing CHECK or FK needs a versioned migration.
  • Pick a fallback company. No request ever lands in "the default database".