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

@monithor/thorle

v1.0.1

Published

Server-driven UI component library for building dynamic dashboards and monitoring interfaces from JSON configurations

Downloads

24

Readme

Thorle

CI npm version License: MIT

Server-driven UI component library for building dynamic dashboards and monitoring interfaces from JSON configurations.

Why Thorle?

Thorle lets you build entire monitoring dashboards from backend JSON configs. Change your UI without redeploying frontend code. Define screens, widgets, layouts, data sources, visibility rules, and actions all in JSON -- Thorle renders them.

Key Benefits

  • Zero redeploy UI changes -- Update dashboards by changing JSON configs served from your API
  • Rich component system -- Charts, tables, stats, KPIs, cards, grids, tabs, forms, navigation, feedback
  • Zod-validated SDUI -- Every server-driven component is validated against Zod schemas before rendering
  • Conditional visibility -- Show/hide components based on runtime context (user role, feature flags, etc.)
  • Template interpolation -- Dynamic URLs and props with {{key}} syntax and nested object support
  • Data fetching built-in -- Per-component data sources with polling, caching, and retry logic
  • Action system -- Navigate, open URLs, dispatch custom events, or register custom handlers
  • Tree-shakeable -- Import only what you need via subpath exports

Installation

npm install thorle

Peer Dependencies

Thorle requires these peer dependencies:

npm install react react-dom @mantine/core @mantine/hooks @tanstack/react-query axios zod zustand

Optional peer dependencies (only install what you use):

npm install echarts echarts-for-react mantine-react-table react-grid-layout react-router-dom

Quick Start

1. Set up providers

import { MantineProvider } from '@mantine/core'
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'

const queryClient = new QueryClient()

function App({ children }: { children: React.ReactNode }) {
  return (
    <QueryClientProvider client={queryClient}>
      <MantineProvider>{children}</MantineProvider>
    </QueryClientProvider>
  )
}

2. Register components with the SDUI registry

import { sduiRegistry, Stat, Card } from 'thorle'
import { z } from 'zod'

sduiRegistry.register('Stat', {
  component: Stat,
  schema: z.object({
    label: z.string(),
    value: z.union([z.string(), z.number()]),
    trend: z.enum(['up', 'down', 'neutral']).optional(),
  }),
  metadata: {
    version: '1.0.0',
    category: 'data-display',
    description: 'KPI stat card with trend',
  },
})

sduiRegistry.register('Card', {
  component: Card,
  schema: z.object({
    title: z.string().optional(),
    children: z.any().optional(),
  }),
  metadata: {
    version: '1.0.0',
    category: 'layout',
    description: 'Card container',
  },
})

3. Render from JSON config

import { SDUIRenderer } from 'thorle'

const config = {
  version: '1.0.0',
  screen: 'overview',
  components: [
    {
      id: 'stat-1',
      type: 'Stat',
      props: { label: 'CPU Usage', value: 72, trend: 'up', trendValue: '+5%' },
    },
    {
      id: 'card-1',
      type: 'Card',
      props: { title: 'Server Metrics' },
      children: [
        {
          id: 'stat-2',
          type: 'Stat',
          props: { label: 'Memory', value: '4.2 GB' },
        },
      ],
    },
  ],
}

function DashboardPage() {
  return <SDUIRenderer config={config} />
}

4. Fetch config from API

import { useState, useEffect } from 'react'
import { SDUIRenderer, validateSDUIConfig } from 'thorle'

function DynamicPage({ screenId }: { screenId: string }) {
  const [config, setConfig] = useState(null)

  useEffect(() => {
    fetch(`/api/screens/${screenId}`)
      .then((res) => res.json())
      .then((data) => {
        const validation = validateSDUIConfig(data)
        if (validation.valid) {
          setConfig(data)
        } else {
          console.error('Invalid config:', validation.errors)
        }
      })
  }, [screenId])

  if (!config) return <div>Loading...</div>
  return <SDUIRenderer config={config} />
}

Subpath Exports

import { SDUIRenderer, sduiRegistry } from 'thorle/sdui'
import { Stat, Badge, Progress, Button, Card, Grid, Tabs } from 'thorle/components'
import { interpolateTemplate, evaluateVisibility } from 'thorle/utils'
import { useWidgetData, contextService } from 'thorle/services'
import type { SDUIConfig, WidgetConfig, ApiDataSource } from 'thorle/types'

Component Catalog

Layout

| Component | Description | |-----------|-------------| | Stack | Flexbox container (row/column) | | Container | Responsive page container | | Card | Card wrapper with title/subtitle | | Grid | Responsive CSS grid with breakpoints | | Tabs | Tabbed interface |

Data Display

| Component | Description | |-----------|-------------| | Stat | KPI stat card with trend indicators | | Badge | Status/label badges | | Progress | Progress bar with percentage | | Chart | ECharts-based charts (line, bar, area, gauge) | | Table | Data table with sorting & pagination | | Data | Multi-layout display (stat, list, kpi) |

Input

| Component | Description | |-----------|-------------| | Button | Action button with variants | | Select | Dropdown select with search |

Navigation

| Component | Description | |-----------|-------------| | Breadcrumb | Breadcrumb navigation trail |

Feedback

| Component | Description | |-----------|-------------| | Toast | Notification toast |

Common

| Component | Description | |-----------|-------------| | Loading | Spinner loading indicator | | ErrorDisplay | Error display with retry | | Empty | Empty state placeholder |

SDUI Config Schema

interface SDUIConfig {
  version: string
  screen: string
  metadata?: { theme?: string; author?: string }
  components: SDUIComponent[]
}

interface SDUIComponent {
  id: string
  type: string
  props?: Record<string, any>
  children?: SDUIComponent[]
  actions?: SDUIAction[]
  dataSource?: { url: string; method?: string; polling?: { intervalMs: number } }
  visibility?: { contextKey: string; op: 'eq' | 'ne' | 'in' | 'gt' | 'lt'; value: any }[]
  style?: { className?: string; variant?: string }
}

Visibility Rules

Components can be conditionally shown based on context:

{
  "id": "admin-panel",
  "type": "Card",
  "visibility": [
    { "contextKey": "user.role", "op": "eq", "value": "admin" },
    { "contextKey": "features.dashboard", "op": "eq", "value": true }
  ]
}

Development

npm install
npm run storybook     # Start Storybook
npm run test          # Run tests
npm run test:watch    # Watch mode
npm run lint          # Lint check
npm run typecheck     # TypeScript check
npm run build         # Production build
npm run check         # Run all checks

License

MIT