@codeswayam/access
v1.0.3
Published
Universal entitlements, usage meters, and feature gates for CodeSwayam apps
Maintainers
Readme
@codeswayam/access
Universal entitlements, usage meters, and feature gates for all CodeSwayam apps.
@codeswayam/access is the single source of truth for access control across the CodeSwayam platform. It provides a unified React context, hooks, and components that connect to the entitlements API and expose tier checks, feature gates, and usage metering — all with built-in SWR caching.
Table of Contents
- Overview
- Installation
- Peer Dependencies
- Quick Start
- API Reference
- TypeScript Interfaces
- Code Examples
- Integration Guide (Next.js)
- Caching Behaviour
- Tier System
- Changelog
Overview
Every CodeSwayam application needs to know:
- Who the user is (role, tier)
- What they can access (features, subscription plan)
- How much they've used (usage counters vs limits)
@codeswayam/access answers all three questions through a single call to the entitlements API and exposes the result via composable React primitives. It is designed to be dropped into any CodeSwayam app with minimal configuration.
Key capabilities:
| Capability | Primitive |
|---|---|
| Load full entitlements for an app | useAppAccess(appId) |
| Check if user meets a minimum tier | access.hasTier('pro') |
| Check a specific feature flag | access.hasFeature('ai_chat') |
| Check feature + credits together | access.canUseFeature('ai_chat') |
| Declarative component-level gate | <FeatureGate minTier="pro" /> |
| Render a visual usage progress bar | <UsageMeter counterKey="dms" /> |
| Show the user's current tier | <TierBadge tier="pro" /> |
| Increment a usage counter | useUsageTracker(appId) |
Installation
npm install @codeswayam/access
# or
yarn add @codeswayam/access
# or
pnpm add @codeswayam/accessPeer Dependencies
These packages must be present in your application. They are not bundled with @codeswayam/access.
| Package | Version |
|---|---|
| react | ^18.0.0 |
| react-dom | ^18.0.0 |
| swr | ^2.0.0 |
| @codeswayam/api-client | ^1.3.0 |
npm install react react-dom swr @codeswayam/api-clientQuick Start
1. Wrap your app with AccessProvider
// app/layout.tsx (Next.js App Router)
import { AccessProvider } from '@codeswayam/access';
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="en">
<body>
<AccessProvider>
{children}
</AccessProvider>
</body>
</html>
);
}2. Use useAppAccess in any component
import { useAppAccess } from '@codeswayam/access';
export function Dashboard() {
const access = useAppAccess('auraflow');
if (!access.isLoaded) return <Spinner />;
return (
<div>
<h1>Welcome, {access.role}</h1>
{access.hasTier('pro') && <ProFeaturePanel />}
</div>
);
}3. Gate with a component
import { FeatureGate } from '@codeswayam/access';
export function AiChatPage() {
return (
<FeatureGate appId="auraflow" minTier="pro" fallback={<UpgradePrompt />}>
<AiChatInterface />
</FeatureGate>
);
}API Reference
AccessProvider
The root React context provider. Must wrap any component that uses access hooks or components. Typically placed at the top-most layout.
import { AccessProvider } from '@codeswayam/access';
<AccessProvider>
{children}
</AccessProvider>Props: None. The provider does not accept configuration — all per-app entitlement loading is done lazily on first useAppAccess(appId) call.
useAppAccess
The primary hook for loading and interacting with entitlements. Calls fetchMyEntitlements via SWR and returns a fully-typed AppAccess object.
function useAppAccess(appId: string): AppAccessParameters:
| Parameter | Type | Description |
|---|---|---|
| appId | string | The CodeSwayam app identifier (e.g. "auraflow", "dmflow") |
Returns: AppAccess
AppAccess properties and methods:
| Member | Type | Description |
|---|---|---|
| isLoaded | boolean | true once entitlements have been fetched |
| appId | string | The app identifier passed to the hook |
| role | string | User's role string (e.g. "user", "admin") |
| tier | AppTier | Current tier object: { name, label, aiIncluded } |
| subscription | AppSubscription \| null | Active subscription data or null |
| credits | AppCredits | Credit balance and per-feature costs |
| usage | Record<string, UsageCounter> | Map of usage counters keyed by counter name |
| features | Record<string, boolean \| number> | Feature flags and numeric feature values |
| hasTier(minTier) | (minTier: AppTierName) => boolean | Returns true if user's tier ≥ minTier |
| hasFeature(key) | (key: string) => boolean | Returns true if the feature flag is enabled |
| canUseFeature(key) | (key: string) => boolean | Returns true if feature is enabled and credits are sufficient |
| refresh() | () => void | Manually revalidate the SWR cache |
useFeatureGate
Lightweight hook for a single boolean feature check. Prefer this over useAppAccess when you only need one flag.
function useFeatureGate(appId: string, feature: string): booleanParameters:
| Parameter | Type | Description |
|---|---|---|
| appId | string | The CodeSwayam app identifier |
| feature | string | Feature key to check (e.g. "ai_chat", "export_pdf") |
Returns: boolean — true if the feature is accessible for this user.
useUsageTracker
Returns a trackUsage function that records usage increments against the entitlements API.
function useUsageTracker(appId: string): {
trackUsage: (counterKey: string, increment?: number) => Promise<void>
}Parameters:
| Parameter | Type | Description |
|---|---|---|
| appId | string | The CodeSwayam app identifier |
Returns:
| Property | Type | Description |
|---|---|---|
| trackUsage | (counterKey: string, increment?: number) => Promise<void> | Posts a usage increment. increment defaults to 1. |
FeatureGate Component
Declarative component wrapper that conditionally renders children based on tier, feature flags, or both.
import { FeatureGate } from '@codeswayam/access';
<FeatureGate
appId="auraflow"
minTier="pro"
feature="ai_chat"
fallback={<UpgradePrompt />}
>
<AiChatInterface />
</FeatureGate>Props (FeatureGateProps):
| Prop | Type | Required | Description |
|---|---|---|---|
| appId | string | ✅ | The CodeSwayam app identifier |
| minTier | AppTierName | — | Minimum tier required to render children |
| feature | string | — | Feature key that must be enabled |
| fallback | React.ReactNode | — | Content to render when access is denied |
| children | React.ReactNode | ✅ | Content to render when access is granted |
When both minTier and feature are provided, both conditions must pass.
TierBadge Component
Renders a styled badge showing the given tier name.
import { TierBadge } from '@codeswayam/access';
<TierBadge tier="pro" />Props:
| Prop | Type | Required | Description |
|---|---|---|---|
| tier | AppTierName | ✅ | Tier to display: 'free', 'standard', 'pro', 'enterprise' |
| className | string | — | Additional CSS class names |
UsageMeter Component
Renders a visual progress bar showing used vs limit for a specific usage counter.
import { UsageMeter } from '@codeswayam/access';
<UsageMeter appId="auraflow" counterKey="dms" />Props:
| Prop | Type | Required | Description |
|---|---|---|---|
| appId | string | ✅ | The CodeSwayam app identifier |
| counterKey | string | ✅ | The usage counter to display (e.g. "dms", "ai_requests") |
| label | string | — | Override the display label |
| className | string | — | Additional CSS class names |
TypeScript Interfaces
// Tier names in ascending order
type AppTierName = 'free' | 'standard' | 'pro' | 'enterprise';
// Tier order array — useful for comparisons
const TIER_ORDER: AppTierName[] = ['free', 'standard', 'pro', 'enterprise'];
interface AppTier {
name: AppTierName;
label: string; // Human-readable: "Free", "Pro", etc.
aiIncluded: boolean; // Whether AI features are bundled in this tier
}
interface AppSubscription {
id: string;
status: string; // e.g. "active", "cancelled", "expired"
planType: string; // e.g. "pro_monthly"
expiresAt: string | null; // ISO date string
billingCycle: string; // "monthly" | "annual"
}
interface AppCredits {
balance: number;
featureCosts: Record<string, number>; // { ai_chat: 5, export_pdf: 2 }
}
interface UsageCounter {
used: number;
limit: number;
remaining: number;
percentage: number; // 0–100
}
interface AppAccess {
isLoaded: boolean;
appId: string;
role: string;
tier: AppTier;
subscription: AppSubscription | null;
credits: AppCredits;
usage: Record<string, UsageCounter>;
features: Record<string, boolean | number>;
refresh(): void;
hasTier(minTier: AppTierName): boolean;
hasFeature(key: string): boolean;
canUseFeature(featureKey: string): boolean;
}
interface FeatureGateProps {
appId: string;
minTier?: AppTierName;
feature?: string;
fallback?: React.ReactNode;
children: React.ReactNode;
}
type DenialReason = 'tier' | 'feature' | 'credits' | 'usage_limit' | 'not_loaded';Re-exported types from @codeswayam/api-client:
// These are available directly from @codeswayam/access
import type {
EntitlementResult,
AppEntitlementSummary,
TrackUsagePayload,
TrackUsageResult,
UsageSummary,
} from '@codeswayam/access';Code Examples
1. Installation and AccessProvider Setup
// app/layout.tsx
import { AccessProvider } from '@codeswayam/access';
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="en">
<body>
{/* AccessProvider must be an ancestor of any component using access hooks */}
<AccessProvider>
{children}
</AccessProvider>
</body>
</html>
);
}2. useAppAccess — Full Example with Conditional Rendering
import { useAppAccess } from '@codeswayam/access';
export function AuraflowDashboard() {
const access = useAppAccess('auraflow');
// Show skeleton while entitlements are loading
if (!access.isLoaded) {
return <DashboardSkeleton />;
}
const dmUsage = access.usage['dms'];
return (
<div className="dashboard">
{/* Tier info */}
<p>Plan: {access.tier.label}</p>
<p>Role: {access.role}</p>
{/* Subscription details */}
{access.subscription ? (
<p>Subscription active until: {access.subscription.expiresAt}</p>
) : (
<p>No active subscription</p>
)}
{/* Credit balance */}
<p>Credits: {access.credits.balance}</p>
{/* Usage counter */}
{dmUsage && (
<p>DMs used: {dmUsage.used} / {dmUsage.limit} ({dmUsage.percentage}%)</p>
)}
{/* Feature-gated section */}
{access.hasTier('pro') && <ProAnalyticsPanel />}
{/* Feature flag check */}
{access.hasFeature('ai_suggestions') && <AiSuggestionsWidget />}
{/* Credits + feature check combined */}
{access.canUseFeature('ai_chat') ? (
<AiChatButton />
) : (
<button onClick={() => {/* open buy credits modal */}}>
Buy Credits to Use AI Chat
</button>
)}
{/* Manual refresh */}
<button onClick={access.refresh}>Refresh Entitlements</button>
</div>
);
}3. FeatureGate Component — Declarative Gate
import { FeatureGate } from '@codeswayam/access';
function UpgradePrompt() {
return (
<div className="upgrade-banner">
<p>This feature requires a Pro plan.</p>
<a href="/upgrade">Upgrade Now →</a>
</div>
);
}
export function ExportPage() {
return (
<div>
<h1>Export Your Data</h1>
{/* Tier gate only */}
<FeatureGate appId="auraflow" minTier="standard" fallback={<UpgradePrompt />}>
<CsvExportButton />
</FeatureGate>
{/* Feature flag gate only */}
<FeatureGate appId="auraflow" feature="pdf_export" fallback={<ComingSoonBadge />}>
<PdfExportButton />
</FeatureGate>
{/* Both tier AND feature must pass */}
<FeatureGate
appId="auraflow"
minTier="pro"
feature="bulk_export"
fallback={<UpgradePrompt />}
>
<BulkExportPanel />
</FeatureGate>
</div>
);
}4. Hard Subscription Gate for AI & Premium Features
In applications offering exclusive AI capabilities (such as AuraFlow Smart AI Agents), follow this deterministic entitlement check:
import { useAppAccess } from '@codeswayam/access';
export function AiFeatureWrapper() {
const access = useAppAccess('auraflow');
// Verify Pro subscription, AI bundle, or active credit balance
const canUseAi = access.isLoaded && (
access.isSubscribed ||
access.tier.aiIncluded ||
access.hasTier('pro') ||
access.hasFeature('canUseSmartAi') ||
(access.credits.balance >= (access.credits.featureCosts['ai_reply'] ?? 5) && access.credits.balance > 0)
);
if (!access.isLoaded) return <Skeleton />;
if (!canUseAi) {
return (
<div className="locked-feature-card">
<span className="badge-pro">PRO ONLY</span>
<p>AI Agents require an active Pro subscription or credit balance.</p>
<a href="/subscription">Upgrade to Pro</a>
</div>
);
}
return <AutonomousAgentEngine />;
}5. UsageMeter Component
import { UsageMeter } from '@codeswayam/access';
export function UsageDashboard() {
return (
<div className="usage-panel">
<h2>Your Usage This Month</h2>
<UsageMeter
appId="auraflow"
counterKey="dms"
label="Direct Messages"
/>
<UsageMeter
appId="auraflow"
counterKey="ai_requests"
label="AI Requests"
/>
<UsageMeter
appId="auraflow"
counterKey="storage_gb"
label="Storage"
/>
</div>
);
}5. TierBadge Component
import { TierBadge } from '@codeswayam/access';
import { useAppAccess } from '@codeswayam/access';
export function UserProfileHeader() {
const access = useAppAccess('auraflow');
if (!access.isLoaded) return null;
return (
<div className="profile-header">
<Avatar />
<div>
<span className="username">John Doe</span>
{/* Renders a styled badge — "Free", "Pro", etc. */}
<TierBadge tier={access.tier.name} />
</div>
</div>
);
}
// Or render a static badge anywhere
export function PricingCard() {
return (
<div className="pricing-card">
<TierBadge tier="pro" />
<h3>Pro Plan</h3>
<p>Everything in Standard, plus AI features.</p>
</div>
);
}6. useUsageTracker — Tracking Usage
import { useUsageTracker } from '@codeswayam/access';
export function SendDmButton({ recipientId }: { recipientId: string }) {
const { trackUsage } = useUsageTracker('auraflow');
async function handleSend() {
// Send DM via your API
await sendDm(recipientId);
// Track the usage increment (defaults to +1)
await trackUsage('dms');
}
async function handleBulkSend(count: number) {
await bulkSendDms(recipientId, count);
// Track a custom increment
await trackUsage('dms', count);
}
return <button onClick={handleSend}>Send DM</button>;
}7. Tier-Based Feature Gating Pattern
import { useAppAccess, TIER_ORDER } from '@codeswayam/access';
// Define which features are available per tier
const FEATURE_TIERS: Record<string, 'free' | 'standard' | 'pro' | 'enterprise'> = {
basic_analytics: 'free',
csv_export: 'standard',
ai_suggestions: 'pro',
custom_branding: 'enterprise',
};
export function FeatureList() {
const access = useAppAccess('auraflow');
if (!access.isLoaded) return <Skeleton />;
return (
<ul>
{Object.entries(FEATURE_TIERS).map(([feature, requiredTier]) => {
const isUnlocked = access.hasTier(requiredTier);
return (
<li key={feature} className={isUnlocked ? 'unlocked' : 'locked'}>
<span>{feature}</span>
{isUnlocked ? (
<CheckIcon className="text-green-500" />
) : (
<span className="text-gray-400">Requires {requiredTier}</span>
)}
</li>
);
})}
</ul>
);
}Integration Guide (Next.js)
App Router Setup
src/
├── app/
│ ├── layout.tsx ← Add AccessProvider here
│ ├── auth/
│ │ └── callback/
│ │ └── page.tsx
│ └── dashboard/
│ └── page.tsx ← Use useAppAccess here
└── components/
└── ProFeature.tsx ← Use FeatureGate hereapp/layout.tsx
import { AccessProvider } from '@codeswayam/access';
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="en">
<body>
<AccessProvider>
{children}
</AccessProvider>
</body>
</html>
);
}app/dashboard/page.tsx
'use client';
import { useAppAccess, UsageMeter, TierBadge, FeatureGate } from '@codeswayam/access';
export default function DashboardPage() {
const access = useAppAccess('auraflow');
if (!access.isLoaded) return <p>Loading...</p>;
return (
<main>
<div className="flex items-center gap-2">
<h1>Dashboard</h1>
<TierBadge tier={access.tier.name} />
</div>
<UsageMeter appId="auraflow" counterKey="dms" label="Messages" />
<FeatureGate appId="auraflow" minTier="pro" fallback={<p>Upgrade to Pro</p>}>
<AdvancedAnalytics />
</FeatureGate>
</main>
);
}Pages Router Setup
pages/_app.tsx
import { AccessProvider } from '@codeswayam/access';
import type { AppProps } from 'next/app';
export default function App({ Component, pageProps }: AppProps) {
return (
<AccessProvider>
<Component {...pageProps} />
</AccessProvider>
);
}Caching Behaviour
@codeswayam/access uses SWR under the hood with the following configuration:
| Setting | Value | Reason |
|---|---|---|
| dedupingInterval | 30,000ms | Prevents duplicate requests within 30 seconds |
| revalidateOnFocus | false | Avoids refetching when the tab regains focus |
| Manual revalidation | access.refresh() | Trigger a fresh fetch on demand |
To force a fresh entitlement check (e.g. after a plan upgrade), call:
const access = useAppAccess('auraflow');
// After user upgrades their plan:
await upgradePlan();
access.refresh(); // triggers immediate revalidationTier System
Tiers are ordered from lowest to highest access level:
free → standard → pro → enterpriseThe TIER_ORDER constant reflects this:
import { TIER_ORDER } from '@codeswayam/access';
// ['free', 'standard', 'pro', 'enterprise']hasTier('pro') returns true for users on pro or enterprise. It always checks "at least this tier".
| User Tier | hasTier('free') | hasTier('standard') | hasTier('pro') | hasTier('enterprise') |
|---|---|---|---|---|
| free | ✅ | ❌ | ❌ | ❌ |
| standard | ✅ | ✅ | ❌ | ❌ |
| pro | ✅ | ✅ | ✅ | ❌ |
| enterprise | ✅ | ✅ | ✅ | ✅ |
Changelog
v1.0.3
- Added Cross-App Entitlement Grants support (
crossAppGrants,hasGrantedCategory) - Dual-rail access verification: Subscription Tier + Credit Wallet balance fallback
- Updated dependency to
@codeswayam/api-client@^1.3.4
v1.0.0 — Initial Release
AccessProviderReact context provideruseAppAccess(appId)— Universal entitlements hook backed by SWRuseFeatureGate(appId, feature)— Single feature gate hookuseUsageTracker(appId)— Usage increment trackingFeatureGate— Declarative component gate with tier and feature supportTierBadge— Tier display badge componentUsageMeter— Visual usage progress bar- Full TypeScript support with exported interfaces
- Re-exports from
@codeswayam/api-client:EntitlementResult,AppEntitlementSummary,TrackUsagePayload,TrackUsageResult,UsageSummary
Source Structure
packages/access/src/
├── index.ts — Barrel exports
├── AccessProvider.tsx — React context provider
├── types.ts — All TypeScript types and interfaces
├── hooks/
│ ├── useAppAccess.ts — Primary entitlements hook (SWR)
│ ├── useFeatureGate.ts — Single feature gate hook
│ └── useUsageTracker.ts — Usage tracking hook
└── components/
├── FeatureGate.tsx — Declarative gate component
├── TierBadge.tsx — Tier badge component
└── UsageMeter.tsx — Usage progress bar componentPart of the CodeSwayam platform. For support, open an issue in the monorepo.
