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

@tenence/sdk

v0.2.22

Published

Row-level access control for PostgreSQL. Multi-tenant isolation, built-in auth, and embeddable UI components.

Readme

@tenence/sdk

Build secure, multi-tenant applications without worrying about data isolation. Tenence handles tenant separation at the database level using PostgreSQL Row Level Security — so you can focus on building your product.

For full documentation, guides, and tutorials, visit tenence.io/docs.

🚧 Under Construction — Early Access

Tenence is currently under construction and not yet publicly available. If you're interested in early access, join the waitlist on tenence.io to be notified when we launch.

Install

npm install @tenence/sdk

Peer dependencies (install if not already present):

npm install pg
npm install @openfeature/server-sdk  # only if using feature flags

Neon serverless alternative: If your database is on Neon and you're deploying to a serverless or edge environment, you can use @neondatabase/serverless instead of pg. There are two approaches:

  1. WebSocket Pool — The Neon driver provides a WebSocket-based Pool class that is API-compatible with pg.Pool. Use this with createClient when you need SET LOCAL-based tenant isolation (the standard approach).

  2. Neon HTTP driver with JWT auth — Use the stateless neon() HTTP driver with createNeonClient for fully serverless/edge deployments. Tenant isolation is enforced via JWT claims embedded in the auth token, with no persistent connection required.

npm install @neondatabase/serverless

Quick Start

The fastest way to get started is with the guided setup wizard:

npx tenence setup

This walks you through authenticating, selecting a project, connecting your database, installing the RLS stored procedures, and generating a config file — all in one step.

Or, if you prefer to do it step by step:

1. Authenticate

Log in to your Tenence account from the terminal (similar to gh auth login):

npx tenence auth login

A browser window will open asking you to approve the CLI. Once approved, your credentials are stored locally at ~/.config/tenence/auth.json.

2. Set up your database

Install the required PostgreSQL stored procedures:

npx tenence init

3. Connect your project

Register your database with the Tenence console:

npx tenence register

4. Create a client

import { createClient } from "@tenence/sdk";

const tenence = createClient({
  databaseUrl: process.env.DATABASE_URL,
});

5. Add the middleware

The middleware automatically connects each incoming request to the right tenant:

import { tenenceMiddleware } from "@tenence/sdk/middleware";

app.use(tenenceMiddleware({
  client: tenence,
  resolveContext: (req) => ({
    tenantId: req.user.orgId,
    userId: req.user.id,
    role: req.user.role,
  }),
}));

6. Query with automatic tenant isolation

Every database query is automatically scoped to the current tenant — no manual filtering needed:

app.get("/api/tasks", async (req, res) => {
  const result = await req.tenence.query("SELECT * FROM tasks");
  res.json(result.rows);
});

For lower-level control, use withConnection to get a raw pg client with tenant context already set:

app.get("/api/tasks", async (req, res) => {
  const rows = await req.tenence.withConnection(async (client) => {
    const result = await client.query("SELECT * FROM tasks WHERE status = $1", ["active"]);
    return result.rows;
  });
  res.json(rows);
});

Neon Serverless with JWT Auth

For fully stateless serverless or edge deployments on Neon, use createNeonClient with the Neon HTTP driver. Instead of SET LOCAL session variables, tenant isolation is enforced through JWT claims embedded in the auth token that Neon validates at the database level.

1. Install the Neon driver

npm install @neondatabase/serverless

2. Create a Neon client

import { createNeonClient } from "@tenence/sdk";

const tenence = createNeonClient({
  databaseUrl: process.env.DATABASE_URL!,
  authToken: () => getJwtTokenForCurrentUser(),
});

The authToken can be a static string or an async function that returns a fresh JWT per request.

3. Define RLS policies with JWT claims

Use jwtClaim instead of sessionVar when writing RLS expressions for the Neon HTTP driver:

import { eq, and, jwtClaim, templates } from "@tenence/sdk";

const isolation = templates.neonTenantIsolation();

const customRule = and(
  eq("tenant_id", jwtClaim("org_id", "uuid")),
  eq("user_id", jwtClaim("sub", "uuid")),
);

JWT-aware templates:

| Template | What it does | |----------|-------------| | templates.neonTenantIsolation() | Restricts rows by org_id JWT claim | | templates.neonOwnerOnly() | Restricts rows by sub JWT claim | | templates.neonCompoundIsolation() | Tenant + user compound isolation via JWT claims |

4. Use neonMiddleware in Express

The neonMiddleware attaches a per-request Neon HTTP client to req.tenence, using the JWT from each request:

import { neonMiddleware } from "@tenence/sdk/middleware";

app.use(neonMiddleware({
  databaseUrl: process.env.DATABASE_URL!,
  resolveAuthToken: (req) => req.headers.authorization?.replace("Bearer ", "") ?? "",
}));

app.get("/api/tasks", async (req, res) => {
  const result = await req.tenence.query("SELECT * FROM tasks");
  res.json(result.rows);
});

When to use each approach

| | WebSocket Pool (createClient) | Neon HTTP (createNeonClient) | |---|---|---| | Connection model | Persistent pool via WebSocket | Stateless HTTP per query | | Isolation mechanism | SET LOCAL session variables | JWT claims verified by Neon | | Best for | Long-running servers, traditional hosting | Edge functions, serverless (Vercel, Cloudflare Workers) | | Cold start | Pool initialization overhead | No pool, instant cold start | | Driver | @neondatabase/serverless Pool or pg.Pool | @neondatabase/serverless neon() |

CLI

The CLI provides GitHub-style browser-based authentication and tools for managing your Tenence setup.

npx tenence setup          # Guided setup wizard (recommended for new projects)

npx tenence auth login     # Authenticate via browser
npx tenence auth logout    # Clear stored credentials
npx tenence auth status    # Show current auth status

npx tenence init           # Install RLS stored procedures into your database
npx tenence status         # Check if stored procedures are installed
npx tenence register       # Connect your database to the Tenence console

Organization Management

npx tenence orgs list                        # List all organizations
npx tenence orgs create --name="Acme" --slug=acme   # Create an organization
npx tenence orgs get <orgId>                 # Get organization details
npx tenence orgs update <orgId> --name="New Name"    # Update an organization
npx tenence orgs suspend <orgId>             # Suspend an organization

npx tenence orgs members list <orgId>        # List members
npx tenence orgs members add <orgId> [email protected] --role=member
npx tenence orgs members update <orgId> --user=<userId> --role=admin
npx tenence orgs members remove <orgId> --user=<userId>
npx tenence orgs members import <orgId> [email protected],[email protected] --role=member

npx tenence orgs keys list <orgId>           # List API keys
npx tenence orgs keys create <orgId> --name="My Key"
npx tenence orgs keys revoke <orgId> --key=<keyId>

All orgs commands support --project=ID, --json for machine-readable output, and --help.

Options:

  • --host=URL — Override the console URL (default: https://console.tenence.io)
  • TENENCE_CONSOLE_URL env var — Alternative way to set the console URL
  • TENENCE_API_KEY env var — Use an API key instead of browser auth

Credentials are stored in ~/.config/tenence/auth.json. Tokens expire after 90 days.

What's Included

Data Isolation

Tenence compiles your access rules into native PostgreSQL RLS policies. Each tenant only sees their own data, enforced at the database level. Learn more in the Concepts Guide.

Authentication

Connect your preferred auth provider — or use the built-in Tenence Auth Gateway. Out-of-the-box adapters for:

  • Tenence Auth Gateway — JWT-based with automatic JWKS verification
  • Clerk — Drop-in adapter
  • Custom providers — Bring your own auth logic
  • Built-in sessions — Password-based auth with Express sessions

Gateway adapter:

import { createGatewayAdapter } from "@tenence/sdk/auth";

// API-key-only — jwksUrl is auto-resolved
const auth = createGatewayAdapter({
  apiKey: process.env.TENENCE_API_KEY,
});

// Or provide jwksUrl explicitly:
// const auth = createGatewayAdapter({
//   jwksUrl: "https://console.tenence.io/api/gateway/your-project/.well-known/jwks.json",
// });

Clerk adapter:

import { createClerkAdapter } from "@tenence/sdk/auth";

const auth = createClerkAdapter();

Custom adapter:

import { createCustomAdapter } from "@tenence/sdk/auth";

const auth = createCustomAdapter({
  resolveUser: async (req) => ({
    userId: req.user.id,
    email: req.user.email,
    tenantId: req.user.orgId,
    role: req.user.role,
  }),
  resolveContext: async (req) => ({
    tenantId: req.user.orgId,
    userId: req.user.id,
    role: req.user.role,
  }),
});

See the Auth Gateway Docs for more details.

Entitlements & Billing

Control access to features based on each tenant's subscription plan. Gate premium features, enforce usage limits, and manage trials — all from the Tenence Console.

import { entitlementMiddleware } from "@tenence/sdk";

app.use("/api/premium", entitlementMiddleware({
  feature: "advanced_analytics",
  mode: "hard_block",
}));

You can also check entitlements programmatically:

import { checkEntitlement, parseEntitlementClaims } from "@tenence/sdk";

const claims = parseEntitlementClaims(req.user.metadata.entitlements);
const result = checkEntitlement(claims, { feature: "advanced_analytics" });

if (!result.allowed) {
  return res.status(403).json({ message: result.reason });
}

Learn more in the Billing Docs.

Feature Flags

Toggle features per tenant using the OpenFeature standard:

import { OpenFeature } from "@openfeature/server-sdk";
import { TenenceProvider } from "@tenence/sdk/feature-flags";

// API-key-only — apiUrl and projectId are auto-resolved
OpenFeature.setProvider(new TenenceProvider({
  apiKey: process.env.TENENCE_API_KEY,
}));

const client = OpenFeature.getClient();
const showBeta = await client.getBooleanValue("beta_feature", false, {
  targetingKey: orgId,
});

See the Feature Flags Docs for configuration details.

UI Components

Drop-in Web Components for common multi-tenant UI patterns:

import "@tenence/sdk/components";

| Component | What it does | |-----------|-------------| | <tenence-tenant-switcher> | Lets users switch between organizations | | <tenence-user-badge> | Shows the current user's info | | <tenence-role-indicator> | Displays the user's role | | <tenence-paywall> | Shows or hides content based on subscription | | <tenence-feature-gate> | Shows or hides content based on feature flags | | <tenence-impersonation-bar> | Banner when an admin is impersonating a user | | <tenence-theme-provider> | Applies per-organization branding |

Organization Management

Programmatically manage the full tenant lifecycle — create organizations, add and remove members, manage subscriptions, and more. The OrgClient supports two modes:

  • Admin mode (tenenceKey) — Full CRUD access for backend services and scripts
  • Portal mode (bearerToken) — Self-service access for end-user-facing features
import { createOrgClient } from "@tenence/sdk";

// API-key-only — apiUrl and projectId are auto-resolved
const orgs = createOrgClient({
  tenenceKey: process.env.TENENCE_API_KEY,
});

const org = await orgs.create({ name: "Acme Corp", slug: "acme" });
await orgs.addMember(org.id, { email: "[email protected]", role: "admin" });

const { data, total } = await orgs.list({ search: "acme" });

See the API Reference for the full OrgClient method list.

Theming

Load and apply per-organization branding dynamically — logos, colors, favicon, and app name:

import { createTheme } from "@tenence/sdk";

const theme = createTheme({
  baseUrl: "https://console.tenence.io",
  projectId: "your-project-id",
  orgId: "org-id",
  autoApply: true, // sets CSS variables and favicon automatically
});

await theme.load();

// Access theme values
theme.getLogoUrl();      // organization logo URL
theme.getAppName();      // branded app name
theme.getPrimaryColor(); // primary brand color
theme.getAccentColor();  // accent color

CSS custom properties set by the theme:

  • --tenence-primary — Primary color
  • --tenence-accent — Accent color
  • --tenence-bg — Background color

autoApply defaults to true in browser environments and false in SSR/Node.js.

Rule Builder

Define access rules with a type-safe builder instead of writing raw SQL. Common patterns are available as one-line templates:

import { eq, and, sessionVar, templates } from "@tenence/sdk";

// Tenant isolation in one line
const isolation = templates.tenantIsolation();

// Or build custom rules
const rule = and(
  eq("project_id", sessionVar("app.project_id", "uuid")),
  eq("tenant_id", sessionVar("app.tenant_id", "uuid")),
);

Built-in templates:

| Template | What it does | |----------|-------------| | templates.tenantIsolation() | Restricts rows to the current tenant | | templates.projectIsolation() | Restricts rows to the current project | | templates.compoundIsolation() | Project + tenant compound isolation | | templates.ownerOnly() | Only the row creator can access it | | templates.adminBypass() | Admins can access all rows | | templates.softDeleteFilter() | Hides soft-deleted rows |

Additional expression functions: col, neq, gt, gte, lt, lte, isNull, isNotNull, ilike, inList, contains, not, or, defineRule

For the full expression builder reference, see the API Reference.

Session Variables

The client maps context keys to PostgreSQL session variables using SET LOCAL. The defaults are:

| Context Key | PostgreSQL Variable | Description | |-------------|-------------------|-------------| | tenantId | app.tenant_id | Current tenant/organization ID | | userId | auth.uid | Authenticated user ID | | role | auth.role | User's role | | projectId | app.project_id | Current project ID |

You can customize these mappings:

const tenence = createClient({
  databaseUrl: process.env.DATABASE_URL,
  poolSize: 10,
  sessionVars: {
    tenantId: "app.tenant_id",
    userId: "auth.uid",
    role: "auth.role",
    projectId: "app.project_id",
  },
});

Client API

The TenenceClient provides these methods:

| Method | Description | |--------|-------------| | query(sql, params?, context?) | Run a SQL query, optionally with tenant context | | withContext(context, fn) | Execute a function with a tenant-scoped connection | | compileExpression(expression) | Compile a rule expression to SQL | | applyRule(config) | Apply an RLS policy to a table | | dropRule(table, name) | Remove an RLS policy | | enableRls(table) | Enable RLS on a table | | disableRls(table) | Disable RLS on a table | | testRule(table, expression, context?) | Simulate a policy and preview matched rows | | close() | Close the connection pool |

AI-Powered Development

The Tenence Console includes an MCP (Model Context Protocol) server that lets AI coding assistants manage your data access rules through natural language. Works with Replit Agent, Cursor, Windsurf, and Claude Desktop.

See the Replit Extension Docs for setup instructions.

Import Paths

| Import | What's inside | |--------|--------------| | @tenence/sdk | Client, expressions, templates, middleware, entitlements, theming, OrgClient | | @tenence/sdk/organizations | Organization management (OrgClient, createOrgClient) | | @tenence/sdk/middleware | Express middleware (tenenceMiddleware) | | @tenence/sdk/auth | Auth adapters (Gateway, Clerk, Custom) and password utilities | | @tenence/sdk/components | Web Components (tenant switcher, paywall, etc.) | | @tenence/sdk/feature-flags | OpenFeature provider (TenenceProvider) |

Learn More

License

MIT