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

@nsxbet/admin-sdk

v0.2.0

Published

SDK for building NSX Admin modules with integrated shell

Readme

@nsxbet/admin-sdk

SDK for building admin modules for the NSX Admin platform.

Installation

bun add @nsxbet/admin-sdk @nsxbet/admin-ui

Peer dependencies (install these too):

bun add react react-dom react-router-dom i18next react-i18next
bun add -D @vitejs/plugin-react vite tailwindcss postcss autoprefixer typescript

Quick Start

This SDK enables you to build admin modules that integrate with the NSX Admin shell. Modules are loaded dynamically via React.lazy and share the shell's Router context.

Key concepts:

  • Modules export a default React component from spa.tsx
  • Modules share the shell's BrowserRouter - use useNavigate() from react-router-dom directly
  • Use @nsxbet/admin-ui for consistent UI components
  • Define module metadata in admin.module.json

Architecture Overview

┌─────────────────────────────────────────────────────────────────┐
│  Shell (BrowserRouter)                                          │
│  ├── TopBar, LeftNav, CommandPalette                           │
│  └── <Route path="/your-module/*">                             │
│       └── React.lazy(() => import(baseUrl/spa.js))             │
│            └── Your App component (shares Router context)       │
└─────────────────────────────────────────────────────────────────┘

Two entry points pattern:

| File | Purpose | When used | |------|---------|-----------| | src/spa.tsx | Default export of App | Shell loads this via React.lazy | | src/standalone.tsx | Full app with AdminShell wrapper | Local development (npm run dev) |

Complete Module Example

Below are all the files needed for a working module. Copy these and modify for your needs.

File: vite.config.ts

import { defineModuleConfig } from "@nsxbet/admin-sdk/vite";
import react from "@vitejs/plugin-react";

export default defineModuleConfig({
  port: 3003,
  plugins: [react()],
});

File: admin.module.json

{
  "id": "@admin/my-module",
  "title": "My Module",
  "description": "Description of what this module does",
  "version": "1.0.0",
  "category": "Tools",
  "icon": "clipboard-list",
  "routeBase": "/my-module",
  "keywords": ["my", "module", "example"],
  "commands": [
    {
      "id": "list",
      "title": "List Items",
      "route": "/my-module/list",
      "icon": "file-text"
    },
    {
      "id": "new",
      "title": "New Item",
      "route": "/my-module/new",
      "icon": "plus"
    }
  ],
  "permissions": {
    "view": ["admin.mymodule.view"],
    "edit": ["admin.mymodule.edit"],
    "delete": ["admin.mymodule.delete"]
  },
  "owners": {
    "team": "Platform",
    "supportChannel": "#platform-support"
  }
}

File: src/spa.tsx

/**
 * Module entry point for shell mode.
 * The module shares the shell's Router context.
 */
import { App } from "./App";

export default App;

File: src/standalone.tsx

import React from "react";
import ReactDOM from "react-dom/client";
import { AdminShell, initI18n, i18n } from "@nsxbet/admin-sdk";
import type { AdminModuleManifest } from "@nsxbet/admin-sdk";
import { App } from "./App";
import manifest from "../admin.module.json";

import "./index.css";

// Import module translations (optional - for i18n support)
import enUS from "./i18n/locales/en-US.json";
import ptBR from "./i18n/locales/pt-BR.json";

// Initialize i18n BEFORE shell renders
initI18n();

// Register module translations with namespace matching your module
const NAMESPACE = "mymodule";
i18n.addResourceBundle("en-US", NAMESPACE, enUS, true, true);
i18n.addResourceBundle("pt-BR", NAMESPACE, ptBR, true, true);

ReactDOM.createRoot(document.getElementById("root")!).render(
  <React.StrictMode>
    <AdminShell modules={[manifest as AdminModuleManifest]}>
      <App />
    </AdminShell>
  </React.StrictMode>
);

File: src/App.tsx

import { Routes, Route, Navigate } from "react-router-dom";
import { ItemList } from "./ItemList";
import { NewItem } from "./NewItem";

export function App() {
  return (
    <Routes>
      {/* Redirect root to list */}
      <Route path="/" element={<Navigate to="list" replace />} />
      <Route path="list" element={<ItemList />} />
      <Route path="new" element={<NewItem />} />
    </Routes>
  );
}

File: src/ItemList.tsx

import { useEffect } from "react";
import { useNavigate } from "react-router-dom";
import { useAuth, usePlatformAPI, useTelemetry } from "@nsxbet/admin-sdk";
import { Button, Card, CardContent, Badge } from "@nsxbet/admin-ui";

export function ItemList() {
  const navigate = useNavigate();
  const { hasPermission } = useAuth();
  const { api } = usePlatformAPI();
  const { track } = useTelemetry();

  const canEdit = hasPermission("admin.mymodule.edit");

  useEffect(() => {
    // Set breadcrumbs
    api?.nav.setBreadcrumbs([
      { label: "My Module" },
      { label: "List" }
    ]);
    // Track page view
    track("page.viewed", { page: "item_list" });
  }, [api, track]);

  return (
    <div className="p-6 max-w-5xl mx-auto">
      <div className="mb-6 flex items-center justify-between">
        <h1 className="text-3xl font-bold">Items</h1>
        {canEdit && (
          <Button onClick={() => navigate("/my-module/new")}>
            New Item
          </Button>
        )}
      </div>

      <Card>
        <CardContent className="p-4">
          <p className="text-muted-foreground">No items yet.</p>
        </CardContent>
      </Card>
    </div>
  );
}

File: src/index.css

@import "@nsxbet/admin-ui/styles.css";

@tailwind base;
@tailwind components;
@tailwind utilities;

File: tailwind.config.js

Use withAdminSdk which automatically includes the UI preset and SDK/UI content paths:

import { withAdminSdk } from "@nsxbet/admin-sdk/tailwind";

/** @type {import('tailwindcss').Config} */
export default withAdminSdk({
  content: ["./index.html", "./src/**/*.{ts,tsx}"],
});

File: package.json

{
  "name": "@admin/my-module",
  "version": "1.0.0",
  "type": "module",
  "scripts": {
    "dev": "vite",
    "build": "tsc && vite build && cp admin.module.json dist/",
    "preview": "vite preview"
  },
  "dependencies": {
    "@nsxbet/admin-sdk": "latest",
    "@nsxbet/admin-ui": "latest",
    "react": "^18.2.0",
    "react-dom": "^18.2.0",
    "react-router-dom": "^6.20.0",
    "i18next": "^25.0.0",
    "react-i18next": "^16.0.0"
  },
  "devDependencies": {
    "@types/react": "^18.2.0",
    "@types/react-dom": "^18.2.0",
    "@vitejs/plugin-react": "^4.2.0",
    "autoprefixer": "^10.4.16",
    "postcss": "^8.4.32",
    "tailwindcss": "^3.4.0",
    "typescript": "^5.2.0",
    "vite": "^5.0.0"
  }
}

File: tsconfig.json

{
  "compilerOptions": {
    "target": "ES2020",
    "useDefineForClassFields": true,
    "lib": ["ES2020", "DOM", "DOM.Iterable"],
    "module": "ESNext",
    "skipLibCheck": true,
    "moduleResolution": "bundler",
    "allowImportingTsExtensions": true,
    "resolveJsonModule": true,
    "isolatedModules": true,
    "noEmit": true,
    "jsx": "react-jsx",
    "strict": true,
    "noUnusedLocals": true,
    "noUnusedParameters": true,
    "noFallthroughCasesInSwitch": true
  },
  "include": ["src", "admin.module.json"]
}

File: index.html

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>My Module - Standalone</title>
  </head>
  <body>
    <div id="root"></div>
    <script type="module" src="/src/standalone.tsx"></script>
  </body>
</html>

File: postcss.config.js

export default {
  plugins: {
    tailwindcss: {},
    autoprefixer: {},
  },
};

Manifest Schema (admin.module.json)

| Field | Type | Required | Description | |-------|------|----------|-------------| | id | string | ✅ | Unique identifier (e.g., @admin/tasks) | | title | string | ✅ | Human-readable title | | routeBase | string | ✅ | Base route path (must start with /) | | description | string | | What the module does | | version | string | | Semantic version | | category | string | | Navigation grouping | | icon | string | | Lucide icon name in kebab-case | | keywords | string[] | | Search keywords | | commands | Command[] | | Available actions | | permissions | object | | Permission configuration | | owners | object | | Team ownership info |

Command Schema

| Field | Type | Required | Description | |-------|------|----------|-------------| | id | string | ✅ | Unique command identifier | | title | string | ✅ | Command title | | route | string | ✅ | Full route path | | icon | string | | Lucide icon name | | keywords | string[] | | Search keywords |

Icon Names

Use Lucide icon names in kebab-case:

| Category | Icons | |----------|-------| | Navigation | home, settings, menu, search, chevron-right | | Actions | plus, edit, trash, copy, check | | Content | file, file-text, folder, clipboard-list | | Users | user, users | | Status | alert-circle, info, ban, lock |

Vite Configuration

The SDK provides defineModuleConfig to simplify Vite setup:

import { defineModuleConfig } from "@nsxbet/admin-sdk/vite";
import react from "@vitejs/plugin-react";

export default defineModuleConfig({
  port: 3003,
  plugins: [react()], // REQUIRED: you must add the React plugin
});

Options

| Option | Type | Default | Description | |--------|------|---------|-------------| | port | number | required | Dev server port | | entry | string | "./src/spa.tsx" | Entry file path | | outDir | string | "dist" | Output directory | | plugins | Plugin[] | [] | Additional Vite plugins | | additionalExternals | string[] | [] | Extra externals | | overrides | object | {} | Override any Vite config |

Shared Externals

These dependencies are provided by the shell (do not bundle them):

["react", "react-dom", "react-router-dom", "i18next", "react-i18next"]

SDK Hooks

useAuth()

Access authentication and permissions.

import { useAuth } from "@nsxbet/admin-sdk";

function MyComponent() {
  const { hasPermission, getUser, getAccessToken, logout } = useAuth();

  if (!hasPermission("admin.mymodule.view")) {
    return <div>Access denied</div>;
  }

  const user = getUser();
  console.log(user.email, user.displayName);
}

| Method | Returns | Description | |--------|---------|-------------| | hasPermission(perm) | boolean | Check if user has permission | | getUser() | User | Get current user info | | getAccessToken() | Promise<string> | Get JWT token | | logout() | void | Log out user |

usePlatformAPI()

Access the shell's platform API.

import { usePlatformAPI } from "@nsxbet/admin-sdk";

function MyComponent() {
  const { api, isShellMode } = usePlatformAPI();

  useEffect(() => {
    api?.nav.setBreadcrumbs([
      { label: "My Module" },
      { label: "Current Page" }
    ]);
  }, [api]);
}

useFetch()

Make authenticated API requests.

import { useFetch } from "@nsxbet/admin-sdk";

function MyComponent() {
  const fetch = useFetch();

  const loadData = async () => {
    const response = await fetch("/api/data");
    const data = await response.json();
  };
}

useTelemetry()

Track events and errors.

import { useTelemetry } from "@nsxbet/admin-sdk";

function MyComponent() {
  const { track, trackError } = useTelemetry();

  const handleClick = () => {
    track("button.clicked", { buttonId: "save" });
  };

  const handleError = (error: Error) => {
    trackError(error, { context: "save_operation" });
  };
}

useI18n()

Manage translations and locale.

import { useI18n } from "@nsxbet/admin-sdk";

function MyComponent() {
  const { t, locale, setLocale } = useI18n();

  return (
    <div>
      <h1>{t("common.title")}</h1>
      <p>Current locale: {locale}</p>
    </div>
  );
}

Navigation

Use useNavigate from react-router-dom directly:

import { useNavigate } from "react-router-dom";

function MyComponent() {
  const navigate = useNavigate();

  return (
    <Button onClick={() => navigate("/my-module/new")}>
      New Item
    </Button>
  );
}

Custom Mock Users for Development

During standalone development, the SDK uses an in-memory auth client with default mock users. You can customize these users to match your module's specific permission requirements.

Using createMockUsersFromRoles()

The simplest way to create custom mock users is with the createMockUsersFromRoles() factory:

import {
  AdminShell,
  createInMemoryAuthClient,
  createMockUsersFromRoles,
} from "@nsxbet/admin-sdk";

// Define roles for your module
const mockUsers = createMockUsersFromRoles({
  admin: [
    "admin.payments.view",
    "admin.payments.edit",
    "admin.payments.delete",
  ],
  editor: ["admin.payments.view", "admin.payments.edit"],
  viewer: ["admin.payments.view"],
  noAccess: [],
});

// Create auth client with custom users
const authClient = createInMemoryAuthClient({ users: mockUsers });

// Use in standalone.tsx
ReactDOM.createRoot(document.getElementById("root")!).render(
  <AdminShell authClient={authClient} modules={[manifest]}>
    <App />
  </AdminShell>
);

This creates 4 standard user types with your custom roles:

  • Admin User - Full access (all roles in admin array)
  • Editor User - View/edit access (roles in editor array)
  • Viewer User - View-only access (roles in viewer array)
  • No Access User - No permissions (roles in noAccess array)

Using Custom Users Directly

For more control, pass custom MockUser objects directly:

import { createInMemoryAuthClient, type MockUser } from "@nsxbet/admin-sdk";

const customUsers: MockUser[] = [
  {
    id: "super-admin",
    email: "[email protected]",
    displayName: "Super Admin",
    roles: ["*"], // Wildcard: all permissions
  },
  {
    id: "payments-admin",
    email: "[email protected]",
    displayName: "Payments Admin",
    roles: ["admin.payments.view", "admin.payments.edit"],
  },
];

const authClient = createInMemoryAuthClient({ users: customUsers });

Options

| Option | Type | Default | Description | |--------|------|---------|-------------| | users | MockUser[] | required | Mock users available for selection | | storageKey | string | "@nsxbet/auth" | localStorage key for persistence |

DO NOT (Common Mistakes)

❌ DO NOT use MemoryRouter

// WRONG - don't create your own router
import { MemoryRouter, Routes, Route } from "react-router-dom";

function App() {
  return (
    <MemoryRouter> {/* ❌ WRONG */}
      <Routes>
        <Route path="/" element={<List />} />
      </Routes>
    </MemoryRouter>
  );
}
// CORRECT - use Routes directly, shell provides BrowserRouter
import { Routes, Route } from "react-router-dom";

function App() {
  return (
    <Routes>
      <Route path="/" element={<List />} />
    </Routes>
  );
}

❌ DO NOT create BrowserRouter in spa.tsx

// WRONG - shell already provides BrowserRouter
import { BrowserRouter } from "react-router-dom";

export default function App() {
  return (
    <BrowserRouter> {/* ❌ WRONG */}
      <MyRoutes />
    </BrowserRouter>
  );
}
// CORRECT - just export the component
import { App } from "./App";
export default App;

❌ DO NOT forget the React plugin

// WRONG - missing React plugin
export default defineModuleConfig({
  port: 3003,
  // ❌ No plugins!
});
// CORRECT - include React plugin
import react from "@vitejs/plugin-react";

export default defineModuleConfig({
  port: 3003,
  plugins: [react()], // ✅
});

❌ DO NOT use external databases

// WRONG - no Supabase, Firebase, or external BaaS
import { createClient } from "@supabase/supabase-js"; // ❌
import { initializeApp } from "firebase/app"; // ❌
// CORRECT - use authenticated fetch for internal APIs
import { useFetch } from "@nsxbet/admin-sdk";

const fetch = useFetch();
const data = await fetch("/api/internal-endpoint");

❌ DO NOT import useNavigate from SDK

// WRONG - useNavigate is not exported from SDK
import { useNavigate } from "@nsxbet/admin-sdk"; // ❌
// CORRECT - import from react-router-dom
import { useNavigate } from "react-router-dom"; // ✅

Troubleshooting

"Failed to resolve module specifier 'react'"

Cause: Module is bundling React instead of using shell's version.

Solution: Ensure vite.config.ts uses defineModuleConfig or has external: ["react", "react-dom", "react-router-dom"].

"process is not defined"

Cause: Module code references Node.js globals.

Solution: defineModuleConfig handles this automatically. If using custom config, add:

define: {
  "process.env": {},
  "process.env.NODE_ENV": JSON.stringify("production"),
}

Module not loading in shell

Checklist:

  1. spa.tsx exports default component
  2. admin.module.json has valid id, title, routeBase
  3. ✅ Build outputs to dist/ with spa-[hash].js
  4. ✅ Module is registered in shell's catalog

Styles not working

Checklist:

  1. index.css imports @nsxbet/admin-ui/styles.css
  2. tailwind.config.js uses withAdminSdk from @nsxbet/admin-sdk/tailwind
  3. postcss.config.js exists with tailwindcss plugin

Running Your Module

# Development (standalone with shell UI)
bun run dev
# Open http://localhost:3003

# Build for production
bun run build
# Output: dist/assets/spa-[hash].js

# Preview built module
bun run preview

Types

import type {
  AdminModuleManifest,
  ModuleCommand,
  PlatformAPI,
  User,
  Breadcrumb,
} from "@nsxbet/admin-sdk";

License

UNLICENSED - Internal use only