@tenence/sdk
v0.2.4
Published
Row-level access control for PostgreSQL. Multi-tenant isolation, built-in auth, and embeddable UI components.
Downloads
447
Maintainers
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.
Install
npm install @tenence/sdkPeer dependencies (install if not already present):
npm install pg
npm install @openfeature/server-sdk # only if using feature flagsQuick Start
The fastest way to get started is with the guided setup wizard:
npx tenence setupThis 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 loginA 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 init3. Connect your project
Register your database with the Tenence console:
npx tenence register4. 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);
});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 consoleOptions:
--host=URL— Override the console URL (default:https://console.tenence.io)TENENCE_CONSOLE_URLenv var — Alternative way to set the console URLTENENCE_API_KEYenv 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";
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";
OpenFeature.setProvider(new TenenceProvider({
apiUrl: "https://console.tenence.io",
apiKey: process.env.TENENCE_API_KEY,
projectId: "your-project-id",
}));
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 |
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 colorCSS 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 |
| @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
- Getting Started — Set up your first project
- Concepts — Understand how Tenence works
- Tutorial — Build a complete multi-tenant app
- API Reference — Full SDK reference
- Billing — Entitlements and subscription management
- Feature Flags — Per-tenant feature toggles
- Auth Gateway — Authentication setup
License
MIT
