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

@kannan19302/framework

v0.1.4

Published

UniERP Frontend Framework — unified schema-driven application layer for all apps and modules

Downloads

169

Readme

@kannan19302/framework

Part of UniERP — an open-source, self-hostable multi-tenant application platform. Repository map · Architecture · Contributing · Security

UniERP's unified frontend framework. Provides a declarative metadata layer: modules describe their resources declaratively, and the framework generates data fetching, validation, navigation, and full list/detail/form views — the same way for every app and module.

Sits above @kannan19302/ui (design system) and below each host app (apps/web, future apps). It is also the shared runtime that Studio-rendered pages should consume — do not fork a second rendering engine.

Layers

| Layer | Import | What it does | |---|---|---| | Metadata | ResourceSchema, FieldDef, ModuleDefinition | Declarative description of a module and its entities | | Registry | defineModule, createRegistry | Modules self-register; hosts derive routes/nav from it | | Client | ApiClient | One configured HTTP gateway (auth, CSRF, tenant, errors) | | Data | useResourceList/Doc, useCreate/Update/DeleteResource | Tenant-scoped TanStack Query hooks with cache invalidation | | Schema | buildZodSchema, validateValues | Zod validation generated from FieldDefs + custom validators | | Permissions | Guarded, RouteGuard, usePermission | RBAC (module.resource.action) gating for nav, routes, actions, fields | | Views | ListView, FormView, DetailView | Schema-driven pages with escape hatches (custom cells, sections, children) |

Quick start

1. Define a module (one file per module, e.g. modules/crm.ts):

import { defineModule, defineResource } from '@kannan19302/framework';

const customer = defineResource({
  name: 'customer',
  labelSingular: 'Customer',
  labelPlural: 'Customers',
  endpoint: '/crm/customers',
  titleField: 'name',
  permissions: { read: 'crm.customer.read', create: 'crm.customer.create', update: 'crm.customer.update' },
  status: { field: 'status', tones: { ACTIVE: 'success', CHURNED: 'danger' } },
  fields: [
    { name: 'name', label: 'Name', type: 'text', required: true },
    { name: 'email', label: 'Email', type: 'email', required: true },
    { name: 'type', label: 'Type', type: 'select', options: [
      { value: 'COMPANY', label: 'Company' }, { value: 'INDIVIDUAL', label: 'Individual' },
    ]},
    { name: 'creditLimit', label: 'Credit Limit', type: 'currency', min: 0,
      visibleIf: (v) => v.type === 'COMPANY' },
  ],
  list: { columns: ['name', 'email', 'type', 'status'], searchable: true, pageSize: 25 },
  form: { sections: [{ title: 'General', fields: ['name', 'email', 'type', 'creditLimit'] }] },
});

export const crmModule = defineModule({
  id: 'crm', title: 'CRM', basePath: '/crm',
  resources: [customer],
});

2. Mount the provider once in the host app:

<FrameworkProvider
  api={{
    baseUrl: process.env.NEXT_PUBLIC_API_URL || '/api/v1',
    getToken: () => localStorage.getItem('token'),
    getCsrfToken: () => readCookie('csrf_token'),
    getTenantId: () => activeTenantId,
  }}
  modules={[crmModule]}
  queryClient={existingQueryClient} // or createQueryClient
>
  {children}
</FrameworkProvider>

3. Pages become one-liners:

// /crm/customer — list
<RouteGuard permission="crm.customer.read">
  <ListView resource={customer} onRowClick={(row) => router.push(`/crm/customer/${row.id}`)}
            onCreate={() => router.push('/crm/customer/new')} />
</RouteGuard>

// /crm/customer/[id] — detail (children slot for tabs, history, related lists)
<DetailView resource={customer} id={id} onEdit={() => router.push(`/crm/customer/${id}/edit`)} />

// create / edit
<FormView resource={customer} id={id} onSuccess={(rec) => router.push(`/crm/customer/${rec.id}`)} />

4. Navigation is derived, not hand-written:

const nav = buildAppNav(registry.getModules(), hasPermission);

Conventions

  • Tenant safety: every cache key is prefixed with the tenant id from getTenantId(); switching tenants can never surface another tenant's cached rows.
  • Permissions: RBAC codes follow module.resource.action. Missing read hides the nav item; RouteGuard blocks the route with a 403 view; Guarded hides buttons/fields.
  • Server contract: list endpoints accept page, pageSize, search, sortField, sortDirection plus arbitrary filter params, and return { data, total } (bare arrays and { items, total } also accepted).
  • Escape hatches: list.render for custom cells, toolbar/actions/children/footer slots on every view, and all data hooks are usable standalone for fully custom pages.

Out of scope (v1)

i18n wiring, saved views, offline, dashboards/reports, Studio editor changes. See .ai/MODULE_REGISTRY.md and the enterprise hardening plan for the roadmap.