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

@eintrek/erp-shell

v0.1.32

Published

Shared frontend platform for ERP apps — HTTP client, providers, navigation, hooks.

Readme

@eintrek/erp-shell

Shared frontend platform for ERP apps (erp-accounting, erp-hr, erp-platform, …).

Holds the cross-app glue that was previously duplicated in every consumer: HTTP client, auth/token plumbing, organization-id context, app chrome (sidebar/header), context providers, common hooks.

Sibling package to @eintrek/erp-theme (UI primitives — Button, Form, Dialog, etc.). erp-shell depends on erp-theme for visual components.

Status — what's in here today

Phase 1 ✅ — HTTP client + organization-id

  • axiosInstance — preconfigured Axios with auth token + X-Organization-ID injection, single-flight 401 refresh, GR precheck validator
  • ApiError, toApiError, isAxiosError, getAxiosErrorMessage
  • getAccessToken, clearAccessTokenCache
  • unwrapApiResponse
  • getOrganizationId, setOrganizationId, clearOrganizationId, updateOrganizationId

Phase 2a ✅ — Lightweight context providers (zero / low coupling)

  • ThemeProvider — wraps next-themes
  • FeatureFlagsProvider + useFeatureFlags + useFeatureFlag + FeatureFlags type
  • ReactQueryProvider — preconfigured with shared getQueryClientConfig (5 min staleTime, 2 retries, swallows "no org context" noise)
  • getQueryClientConfig exported standalone for callers that want to bring their own QueryClient

Consumer apps' @/context/{theme,feature-flags,react-query}-provider shims now re-export from this package.

Phase 2b (partial) ✅ — Smaller heavy providers (DI design)

  • PermissionRulesProvider (generic) + usePermissionRules — consumer passes in data (from its useUiRoutePermissions() hook), isLoading, isError, and fallbackPolicies (its static JSON).
  • SessionBackedProfileCacheSeeder<T> (generic) — consumer passes queryKey + mapper(session) to seed React Query cache from NextAuth session.
  • App-side wrappers in context/{permission-rules-context,session-backed-profile-cache-seeder}.tsx now inject local hooks into the shell generics.

Phase 2b (cont'd) ✅ — erp-provider state machine + side-effects extracted

Rather than move the whole 471-line provider (tightly coupled to org / employee / department / position data fetchers), the stable bits moved to shell:

  • useOrganizationIdState(initialId, { setOrganizationCookie, deleteOrganizationCookie }) → returns { organizationId, setOrganizationId, hasOrganizationContext }. Owns the cookie + localStorage + state precedence machine + transient-retry on cookie writes.
  • useInvalidateQueriesOnOrgChange(organizationId) — effect that calls queryClient.invalidateQueries() when the selected org changes (skips initial mount).
  • useClearOrgOnUserChange(userId, setOrganizationId) — effect that resets the selected org when the user switches.
  • runWithTransientRetry, isLikelyTransientNetworkError — small fetch retry helper.

Each app's context/erp-provider.tsx now composes these — dropped from 471 → 311 lines (~160 dedup × 2 apps).

Phase 5a ✅ — Pure navigation chrome

8 self-contained sidebar pieces (no app context, no per-app navigation data): SidebarProvider + useSidebar, SidebarContent, SidebarFooter, SidebarTrigger, HeaderBackground, useSidebarToggle (zustand), TapMenu, plus navigation-helpers (isPathActive). Consumer shims at shared/components/ui/navigation/*.{ts,tsx} re-export from the shell.

Phase 5b ✅ — Navigation-data split + data-shape-coupled components

Shell now owns the shapes + helpers for navigation; apps own the data.

  • Types: Module, SubPage, SubModule (with approvalCountKey?: string slot for app narrowing), Tool, Company, NavigationData.
  • Helpers: canAccessNavPermissionRoute, filterNavigationToolsByPermission, filterNavigationModulesByPermission, getActiveModule(pathname, modules), getActiveSubModule(pathname, modules), buildUrlWithCode(code, url) (absolute http(s):// URLs pass through; {code} placeholder is replaced).
  • Components: NavTools (reads Tool[] + buildUrlWithCode internally), AppBreadcrumb (accepts labels prop so per-app Thai overrides stay local).

Each app's navigation-data.ts now (1) imports types/helpers from shell, (2) declares its own navigationData const + breadcrumbLabels, (3) wraps getActiveModule/getActiveSubModule to keep the 1-arg signature, and (4) re-exports the shell types so existing callsites don't change.

Phase 5c ✅ — Context-coupled chrome (props-driven)

Five components moved to shell as props-driven components. Each consumer app keeps a thin local wrapper that pulls from context (useERP, usePermissionRules, useApprovalCounts, etc.) and passes the data in as props:

  • NavUser — accepts organization?, profileHref?, selectOrganizationHref?, signInRedirectHref?. Reads useSession
    • useTheme internally.
  • NavMain — accepts items, orgCode?, closeOnNavigation?, approvalCounts? (HR uses the last one for queue badges).
  • OrganizationSwitcher — accepts activeOrganization, organizations, isContextLoading?, isLoadingOrgs?, setOrganizationCookie, registerOrganizationHref?. Server action injected per app.
  • SidebarHeader — generic slot (children), so each app drops its own <OrganizationSwitcher> (or any brand) in.
  • AppSidebar — orchestrator that takes modules, tools, activeModule, orgCode, approvalCounts, plus header + footer slots.

Each app's shared/components/ui/navigation/{nav-user,nav-main,organization-switcher,sidebar-header,app-sidebar}.tsx shrank from a combined ~956 lines to ~220 lines of pure wrapper code (shell holds the ~700 line implementation once, instead of duplicated across 2 apps).

session-monitor.tsx (1541 lines) was deleted in both repos — it was a dead diagnostics page with no consumer imports.

Phase 3a ✅ — Pure / session-only hooks + jwt-utils

  • decodeJWT — base64url-safe JWT payload decode
  • useSessionRefresh + SessionRefreshTrigger — periodic NextAuth session.update() to keep the Keycloak access token fresh
  • useKeycloakRoles — parses the groups claim into organization/role pairs with hasRole / hasRoleInOrganization / belongsToOrganization / etc.

Consumer apps' @/shared/lib/jwt-utils, @/shared/hooks/use-session-refresh, @/shared/hooks/use-keycloak-roles shims now re-export from this package.

Roadmap

| Phase | Scope | Files to move from each app | |-------|-----------------------------------------------------------------------|------------------------------------------------------------------| | 1 | ✅ HTTP client | client/http/*, client/utils/organization-id.ts | | 2a | ✅ Lightweight providers | context/{theme,feature-flags,react-query}-provider.tsx | | 2b | ✅ Heavy providers — DI design: PermissionRulesProvider (generic), SessionBackedProfileCacheSeeder<T> (generic), erp-provider state-machine + side-effect hooks | context/{permission-rules-context,session-backed-profile-cache-seeder,erp-provider}.tsx | | 3a | ✅ Pure / session hooks + jwt-utils | shared/hooks/{use-session-refresh,use-keycloak-roles}.ts, shared/lib/jwt-utils.ts | | 3b | Permission hooks (after Phase 2b) | shared/hooks/{use-permission,use-api-policy-access,use-route-access}.ts (each depends on permission-rules-context and/or erp-provider) | | 4 | Data table primitives — already lives in @eintrek/erp-theme. erp-accounting migrated to shim; erp-hr blocked on bumping @eintrek/erp-theme 1.0.4 → 1.4.0 (separate effort, not via erp-shell). | shared/components/data-table/** (no shell move needed) | | 5a | ✅ Pure sidebar pieces (provider, content/footer/trigger, tap-menu, header-background, useSidebarToggle, navigation-helpers) | shared/components/ui/navigation/{sidebar-provider,sidebar-content,sidebar-footer,sidebar-trigger,header-background,use-sidebar-toggle,tap-menu,navigation-helpers}.* | | 5b | ✅ Navigation-data split + 2 data-shape-coupled components (NavTools, AppBreadcrumb) | shell now owns types + filter helpers; per-app navigation-data.ts only owns rows + Thai labels | | 5c | ✅ Context-coupled chrome (props-driven): NavUser, NavMain (HR badges), OrganizationSwitcher, SidebarHeader (slot), AppSidebar (orchestrator). session-monitor skipped — dead file. | each app keeps a thin wrapper at shared/components/ui/navigation/* |

App-specific files that stay in each app:

  • navigation-data.ts (menu lists differ per app)
  • domains/** (sales / hr / project / etc.)
  • API clients for app-specific endpoints

Development

Requires Node 22.13+ and pnpm 11 (same as other ERP packages).

pnpm install
pnpm build         # rollup → dist/{index.js,index.esm.js,index.d.ts}
pnpm dev           # watch
pnpm pack          # produce .tgz for local consumer testing

Local linking into a consumer (during migration)

Easiest dev loop: tarball install (no symlinks, no peer-dep surprises):

# 1) Build + pack here
cd /path/to/erp-shell
pnpm pack          # produces eintrek-erp-shell-0.1.0.tgz

# 2) Install in consumer
cd /path/to/erp-accounting
pnpm add file:../erp-shell/eintrek-erp-shell-0.1.0.tgz

Or use pnpm workspace: protocol once the apps + packages share a workspace root.

Migration recipe for consumers

// Before
import axiosInstance, { getAxiosErrorMessage, ApiError } from "@/client/axios"
import { getOrganizationId } from "@/client/utils/organization-id"

// After (Phase 1)
import {
  axiosInstance,
  getAxiosErrorMessage,
  ApiError,
  getOrganizationId,
} from "@eintrek/erp-shell"

Once every callsite migrates, delete the corresponding files under client/http/, client/utils/organization-id.ts, and the client/axios.ts barrel.