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-adapter-lucid

v0.2.1

Published

Fail-closed PostgreSQL row-level and schema-per-tenant isolation for AdonisJS Lucid.

Readme

tenancyjs-adapter-lucid

Fail-closed Lucid 22 row-level, schema-per-tenant, and database-per-tenant isolation for AdonisJS 7 and PostgreSQL 17.

New to TenancyJS? Start with the docs → — install, the tenancyjs-cli CLI, and how this package fits with an adapter + integration.

Node AdonisJS Lucid PostgreSQL Isolation

This package gives normal Lucid model operations tenant-aware hooks while forced PostgreSQL row-level security (RLS) remains the final isolation boundary. It is separate from the Knex adapter because Lucid models, hooks, relationships, transactions, and AdonisJS lifecycle need their own contract and evidence.

Install

pnpm add tenancyjs-core tenancyjs-adapter-lucid \
  @adonisjs/core@^7.3 @adonisjs/lucid@^22.4 luxon@^3.7 pg@^8.20

Node.js 24 or newer is required by AdonisJS 7 and Lucid 22.

Configure

Create the adapter from the application-owned TenancyManager, Lucid database service, and an explicit list of tenant models.

import { createLucidTenancy } from "tenancyjs-adapter-lucid";
import db from "@adonisjs/lucid/services/db";

const lucidTenancy = createLucidTenancy({
  manager,
  database: db,
  tenantModels: [
    {
      model: Post,
      tenantAttribute: "tenantId",
      tenantColumn: "tenant_id",
      policyName: "posts_tenant_isolation",
    },
  ],
});

const validation = await lucidTenancy.validate();
if (!validation.valid) throw new Error("Invalid tenancy database policy");

Run application work inside both the core tenant context and the Lucid managed transaction:

await manager.runWithTenant(tenant, () =>
  lucidTenancy.run(async () => {
    const posts = await Post.query().preload("comments");
    return posts;
  }),
);

The AdonisJS integration will own this composition for HTTP middleware. Jobs and tests use the same lexical callback contract.

Security boundary

  • Every tenant model is classified explicitly; unknown application models are outside this adapter's guarantee.
  • Normal find/fetch/paginate/save/delete operations attach to the managed transaction. Tenant reads add the configured discriminator; creates inject it; updates cannot change it.
  • .pojo(), quiet persistence, bulk mutation, direct database builders, and unsupported relationship writes do not receive a protected transaction. Forced RLS must deny them when tenant context is absent; they are not silently treated as supported.
  • The runtime role must not be superuser, BYPASSRLS, or table owner. A separate migration role owns schema and policy changes.
  • The retained Lucid database service and privileged clients bypass TenancyJS and remain outside the guarantee.

Required PostgreSQL policy

Enable and force RLS on every tenant table. Both USING and WITH CHECK must use the transaction- local settings tenancyjs.tenant_id and tenancyjs.is_central. Runtime code validates this contract but never installs or alters policies.

alter table posts enable row level security;
alter table posts force row level security;

create policy posts_tenant_isolation on posts
using (
  current_setting('tenancyjs.is_central', true) = 'true'
  or tenant_id = nullif(current_setting('tenancyjs.tenant_id', true), '')
)
with check (
  current_setting('tenancyjs.is_central', true) = 'true'
  or tenant_id = nullif(current_setting('tenancyjs.tenant_id', true), '')
);

See the repository security model and ADR-0010/ADR-0013 for the complete operational contract.

Schema per tenant

Schema mode is adapter-enforced. Registered Lucid models use unqualified table names and the shared PostgreSQL engine validates and applies a transaction-local search_path.

const lucidTenancy = createLucidTenancy({
  manager,
  database: db,
  strategy: "schemaPerTenant",
  schema: (tenant) => tenant.schema,
  centralSchema: "public",
  tenantModels: [{ model: Post }, { model: Comment }],
});

Every configured model's actual table must be the same unqualified name. The central schema and every schema on the runtime role's effective default search_path must not contain those tenant-table names: .pojo(), quiet, bulk, and direct builder paths skip Lucid hooks and therefore receive no managed search_path; keeping the names absent makes those paths fail closed. Retained or qualified base-database access remains outside the guarantee. Per-tenant roles and schema provisioning are application-owned. The shared engine rejects a tenant that changes schemas and two tenant identities that resolve to the same schema. Configure role: (tenant) => tenant.role for the optional database-enforced tier.

Database per tenant

Database mode resolves an opaque key and a tenant-specific Lucid transaction provider. The shared bounded cache enforces a one-to-one tenant/key mapping and disposes idle connections on eviction. validate() returns a warning because tenant factories and connectivity are exercised lazily, when each tenant is first used.

const lucidTenancy = createLucidTenancy({
  manager,
  database: landlordDatabase,
  strategy: "databasePerTenant",
  connection: (tenant) => ({
    key: tenant.connectionName,
    create: () => tenantConnection(tenant),
  }),
  tenantModels: [{ model: Post }],
});

The host resolver/factory must map each key to the intended separate database. Keys must never contain URLs or credentials, and close() must run during application shutdown.