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

@jerrick/workspaces

v0.9.22

Published

Auth, multi-tenancy, authorization, resources, billing, and lifecycle infrastructure for React and Next.js applications.

Downloads

285

Readme

@jerrick/workspaces

The multi-tenant application kernel for React and Next.js.

@jerrick/workspaces gives a product one coherent foundation for identity, tenancy, authorization, billing, and the UI around them. Define the tenant model once; the server, browser client, and generated Firestore rules all use that same definition.

definition ──→ server authorization
           ├─→ browser hooks and UI
           ├─→ billing and entitlements
           └─→ Firestore rules and indexes

It is framework-thin at its core: request handlers use standard Request and Response objects. Next.js helpers are available where they make server components and route mounting easier.

What it owns

| Area | Included | | --- | --- | | Identity | Firebase session cookies, email/password and OAuth sign-in, profile management, password resets, verification | | Tenancy | Multiple workspace kinds, members, roles, capabilities, invites, API keys, lifecycle, account deletion | | Authorization | Server require(...), a shared client evaluator, and generated Firestore rules | | Billing | Plans, entitlements, usage, seats, Stripe Checkout/portal, Stripe and RevenueCat providers | | Resources | Typed, workspace-scoped CRUD backed by Firestore or Postgres | | Workspace presentation | A common branding value, form metadata, upload slots, and safe fallback resolution | | Product surfaces | Realtime React hooks and brand-neutral UI for auth, teams, account settings, and workspace management |

Your application still owns its domain: product data, business workflows, media pipelines, analytics, and how workspace branding is applied to its public surfaces. The SDK only standardizes the portable value and editing mechanics. When an app needs to react to a kernel event, use a lifecycle event or configuration seam rather than placing app-specific code in this package.

The mental model

A workspace is the tenant: an organization, team, project, or any shared boundary in your product.

  • Kind — a workspace type and its collection. Most apps have one; marketplaces may have several.
  • Role — an ordered bundle of default capabilities. The first role is the owner role.
  • Capability — an action such as flows.manage. A bare string in an access check is always a capability.
  • Feature — a paid or plan-controlled product capability, such as seats, metered usage, or an entitlement.

Membership is stored on the workspace document as members: { uid: role }. That is deliberately the one shape the server, client, and Firestore rules can all evaluate.

Start in five files

Install the package and only the peers you use:

npm install @jerrick/workspaces firebase firebase-admin
# Add react for hooks/UI, stripe for Stripe billing, and postgres for Postgres resources.

The npm package ships TypeScript source—the same inspectable source used by the fleet's vendored tarballs. A Next.js application should transpile each shared package it imports:

// next.config.ts
const nextConfig = {
  transpilePackages: ["@jerrick/workspaces", "@jerrick/ui"],
};

export default nextConfig;

Tailwind v4 must scan the whole package because definition-driven form classes live under src/resources as well as src/ui:

/* app/globals.css — adjust the relative path for your app */
@source "../node_modules/@jerrick/workspaces/src";

Do not narrow that path to src/ui; doing so can make the same SDK form lose layout, color-picker, or upload styles in one consumer while looking correct in another.

1. Define the tenant model

The definition must be pure and safe to import in the browser: no secrets, Firebase Admin, Stripe client, environment variables, or server-only imports.

// lib/workspaces/organizations.ts
import { defineWorkspaces } from "@jerrick/workspaces";

export const definition = defineWorkspaces({
  version: 1,
  kinds: {
    organization: {
      collection: "organizations",
      roles: {
        owner: { label: "Owner", capabilities: ["*"] },
        admin: {
          label: "Admin",
          capabilities: ["members.view", "members.invite", "members.manage", "flows.manage"],
        },
        member: { label: "Member", capabilities: ["members.view"] },
      },
      defaultRole: "member",
      features: { invites: true, apiKeys: true },
    },
  },
} as const);

Role order matters: declare the highest role first. Use the built-in operation names for kernel behavior (workspace.view, workspace.update, workspace.delete, members.manage, members.invite, apiKeys.manage, domains.manage, and connect.manage); invent names only for your own product gates.

2. Create one server

// lib/workspaces/server.ts
import "server-only";
import { createNextWorkspaceManager } from "@jerrick/workspaces/next";
import { createWorkspaceServer, type WorkspaceServer } from "@jerrick/workspaces/server";
import { definition } from "./organizations";

let cached: WorkspaceServer<typeof definition> | undefined;

export function workspaceServer() {
  return (cached ??= createWorkspaceServer({
    definition,
    tokenPepper: process.env.WORKSPACES_TOKEN_PEPPER!,
  }));
}

// Verified identity for Server Components, server actions, and layouts.
export const currentWorkspaces = createNextWorkspaceManager({ server: workspaceServer });

WORKSPACES_TOKEN_PEPPER is a long, server-only secret used to digest invite and API-key tokens. Rotating it invalidates all outstanding tokens. Firebase Admin uses the SDK credential ladder by default, or you can pass explicit Firebase services to the server factory.

3. Mount the SDK routes

// app/api/workspaces/[...segments]/route.ts
import { createWebHandler } from "@jerrick/workspaces/server";
import { workspaceServer } from "@/lib/workspaces/server";

export const { GET, POST, PATCH, DELETE } = createWebHandler(workspaceServer);
// app/api/auth/[...auth]/route.ts
import { createAuthRoutes } from "@jerrick/workspaces/auth/server";

export const { GET, POST, PATCH } = createAuthRoutes();

The SDK authenticates and authorizes these routes itself. Do not add wrapper routes for built-in workspace, member, invite, API-key, or billing operations. When you need an app-specific policy, add it to the handler's guarded() seam.

4. Bind the browser client

// lib/workspaces/client.ts
import { createWorkspaceClient } from "@jerrick/workspaces/react";
import { definition } from "./organizations";

export const {
  WorkspacesProvider,
  WorkspaceProvider,
  useWorkspaceAccess,
  useWorkspaces,
  useWorkspaceMutations,
  useMemberMutations,
} = createWorkspaceClient({ definition });
// components/providers.tsx
"use client";
import { WorkspacesProvider } from "@/lib/workspaces/client";

export function Providers({ children }: { children: React.ReactNode }) {
  return <WorkspacesProvider>{children}</WorkspacesProvider>;
}
// A workspace route layout
import { WorkspaceProvider } from "@/lib/workspaces/client";

<WorkspaceProvider workspace={{ kind: "organization", id: organizationId }}>
  {children}
</WorkspaceProvider>;

5. Enforce on the server; project in the client

// Server Component, action, or route handler
const manager = await currentWorkspaces();
if (!manager) redirect("/auth/login");

const workspace = manager.workspace({ kind: "organization", id: organizationId });
await workspace.require("flows.manage");
// Client component: realtime state drives the experience.
const access = useWorkspaceAccess();

access.can("flows.manage");
access.hasMinimumRole("admin");
access.hasFeature("reports");

Client checks make the UI responsive; they are not the security boundary. Always guard server work with require(...) or requireFeature(...). Never treat a cookie's presence as proof of identity: use currentWorkspaces() in server components or workspaceServer().forRequest(request) in route handlers.

6. Generate and deploy rules

Generate Firestore rules and indexes from the same definition, commit them, and deploy both. Read Firestore rules before deploying over a legacy data shape.

Add capabilities when you need them

| Need | Start here | | --- | --- | | Auth pages, session behavior, native auth | Authentication | | Members, roles, invites, API keys, server authorization | Authorization and access | | Workspace data, lifecycle events, deletion | Workspaces | | Plans, seats, usage, Checkout, webhooks | Billing | | Typed Firestore/Postgres workspace CRUD | Resources | | Hooks, providers, feature gates, reusable UI | React and UI | | Emails, account avatars, and workspace assets | Email and Storage | | Shared workspace logos, colors, fonts, and fallbacks | Workspace branding | | Workspace-owned credentials | Credential encryption | | Tenant domains and marketplace payments | Custom domains and Stripe Connect | | Every import, environment variable, and route | Reference |

For a guided product shell, copy apps/workspace-starter. It is the minimal complete application with auth, workspace settings, invites, billing, generated rules, and a secure route layout already wired.

Package map

| Import | Purpose | | --- | --- | | @jerrick/workspaces | Definition, types, authorization evaluator, errors, and paths | | /server · /next | Server engine and Next.js server-component helpers | | /auth/server · /auth/client · /auth/react · /auth/native | Cookie-first auth across web and Expo | | /react · /ui · /ui/native | Realtime client state, gates, and reusable UI | | /billing/* | Billing configuration, engine, and Stripe/RevenueCat providers | | /resources/* | Typed resource definitions, server engine, clients, and Postgres store | | /rules | Firestore rules and index generators | | /email · /storage · /waitlist | Transactional email, workspace asset upload, and waitlist capture | | /crypto | One-way token digests and workspace/field-bound credential encryption | | /domains/* · /connect/* | Custom domains and Stripe Connect |

Operating principles

  • One definition, everywhere. Do not duplicate roles or feature rules in routes, UI, or Firestore configuration.
  • Server-first sessions. Credentials go to the server; the browser receives an httpOnly session cookie. Browser Firebase auth is derived from that session.
  • Transactions protect membership. Never write the members map or member documents outside SDK operations.
  • Gate features, not plan names. requireFeature("reports") remains correct when pricing tiers change; plan === "pro" does not.
  • Deletion is deliberate. Workspace deletion cancels billing first, then emits lifecycle events so the product can remove its own domain data.

Further reading

Documentation index · canonical design contract · changelog