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

@open-truss/open-truss

v1.0.1

Published

Framework for building internal tools

Downloads

20,047

Readme

@open-truss/open-truss

React renderer for the v1 Open Truss configuration specification. This package takes a YAML config that describes a workflow of UI components and renders it using React components you provide.

Quick start

import { RenderConfig } from '@open-truss/open-truss'
import { YourComponent } from './your-components'

const config = `
workflow:
  version: 1
  frames:
    - frame:
      view:
        component: YourComponent
        props:
          title: Hello from OT
`

function App() {
  return (
    <RenderConfig
      config={config}
      components={{ YourComponent }}
    />
  )
}

How it works

Open Truss configs are YAML documents describing a tree of frames. Each frame references a component by name and optionally provides props, data configurations, and nested sub-frames. The <RenderConfig /> component parses the YAML, validates it against Zod schemas, resolves component references from your registry, and recursively renders the frame tree.

Defining components

Components are standard React components, although we suggest adding Zod types for them.

With Zod props (recommended)

Export both a default (the component function) and Props (a Zod schema). Props are validated at runtime and enable features like auto-generated UIs, signal wiring, and config-time validation errors.

import {
  BaseOpenTrussComponentV1PropsShape,
  withChildren,
  z,
  type BaseOpenTrussComponentV1,
} from '@open-truss/open-truss'

export const Props = BaseOpenTrussComponentV1PropsShape.extend({
  ...withChildren,
  title: z.string().default('Default Title'),
  color: z.string().default('blue'),
  headerElement: z.enum(['h1', 'h2', 'h3', 'h4']).default('h1'),
})

const MyComponent: BaseOpenTrussComponentV1<z.infer<typeof Props>> = ({
  title,
  color,
  headerElement: Tag,
  children,
}) => {
  return (
    <Tag style={{ color }}>
      {title}
      {children}
    </Tag>
  )
}

export default MyComponent

Without Zod

import { type BaseOpenTrussComponentV1 } from '@open-truss/open-truss'

const MyComponent: BaseOpenTrussComponentV1 = ({ data, config }) => {
  return <div>{JSON.stringify(data)}</div>
}

export default MyComponent

Signal props

Components can accept typed signals as props. Signals allow components to share state across the workflow. Use the built-in signal types or define your own:

import {
  BaseOpenTrussComponentV1PropsShape,
  NumberSignal,
  NavigateFrameSignal,
  SignalType,
  z,
} from '@open-truss/open-truss'

// Built-in signal types: NumberSignal, StringSignal, BooleanSignal,
// NumbersSignal, StringsSignal, BooleansSignal, UnknownSignal,
// NavigateFrameSignal

// Custom signal types can be defined in your app:
export const AccountIDSignal = SignalType<number>(
  'AccountID',
  z.number().default(0),
)

export const Props = BaseOpenTrussComponentV1PropsShape.extend({
  integer: NumberSignal,
  submit: NavigateFrameSignal,
})

Signals are declared in the YAML config under workflow.signals and wired to components via :signalName syntax in props. The signal's .value property can be read and written from any component that receives it.

Registering components

Pass components via the components prop on <RenderConfig />. There are two accepted shapes:

Record of components (legacy)

const components = {
  MyComponent,
  OtherComponent,
}

Record of component exports (with Zod Props)

Each value is a namespace module (imported with import * as) that exports default and Props:

import * as MyComponent from './MyComponent'
import * as OtherComponent from './OtherComponent'

const components = {
  MyComponent,
  OtherComponent,
}

Or re-export a barrel of components:

export * as MyComponent from './MyComponent'
export * as OtherComponent from './OtherComponent'
import * as _components from './components'
import { RenderConfig, type COMPONENTS } from '@open-truss/open-truss'

const components = _components as unknown as COMPONENTS

And then pass this into <RenderConfig />:

function App() {
  return (
    <RenderConfig
      config={config}
      components={{ YourComponent }}
    />
  )
}

Passing configs

Configs are YAML strings. See the v1 config spec for more information, but the top-level shape is:

workflow:
  version: 1
  id: optional-unique-id
  debug: false
  frameWrapper: OptionalFrameWrapperName
  signals:
    integer: number
    runCalculation: NavigateFrame
    accountIds: AccountIDs
  renderFrames:
    type: all                      # or: inSequence
    next: :runCalculation          # only for inSequence
    back: :someBackSignal          # only for inSequence
  frames:
    - frame:
      view:
        component: ComponentName
        props:
          title: Hello
          link: <NextLink />
          countSignal: :integer
      data:
        # optional data specification
      frames:
        - frame:
          view:
            component: NestedComponent

Config format details

v1 config spec

  • workflow.version – (so far, this is always 1)
  • workflow.signals (optional) – declares signal names and their types. Values reference signal types defined via SignalType() in your app or built-in types (number, string, boolean, number[], string[], boolean[], unknown, NavigateFrame).
  • workflow.renderFrames (optional) – controls how child frames are displayed. all renders all frames simultaneously. inSequence renders one at a time with navigation signals.
  • frames[].view.component – the name of a registered component.
  • frames[].view.props (optional) – component props. Values can be:
    • Literal YAML values (strings, numbers, booleans, arrays, objects)
    • :signalName – wires a declared signal to the prop
    • <ComponentName /> – embeds a component as a prop value
  • frames[].data (optional) – data configuration passed as the data prop.
  • frames[].frames (optional) – nested sub-frames, rendered as children of the parent component.

Error handling

Errors during rendering are caught at the frame level and displayed inline with a red error message showing the component name and config path. Common errors include:

  • Missing component – the YAML references a component name not in the registry
  • Undefined signal – a component expects a signal that was not declared in workflow.signals
  • Prop validation failure – component props fail Zod validation (logged to console)

There is no React Error Boundary by default. If you want one, wrap <RenderConfig /> in your own.

The validateConfig prop (default true) controls whether the full config is validated against WorkflowV1Shape before rendering. Set it to false if you want to skip validation (e.g. in a visual editor):

<RenderConfig
  config={config}
  components={components}
  validateConfig={false}
/>

FrameWrapper

You can provide a custom FrameWrapper component to wrap every rendered frame. This is useful for adding UI chrome like delete buttons, drag handles, or navigation controls (the open-truss demo app's config builder does this):

import { type FrameWrapper } from '@open-truss/open-truss'

const MyFrameWrapper: FrameWrapper = ({ children }) => {
  return <div style={{ border: '1px solid #ddd', padding: '8px' }}>{children}</div>
}

export default MyFrameWrapper

Reference it in the config or pass it as a component that overrides the default:

workflow:
  version: 1
  frameWrapper: MyFrameWrapper

Frame rendering strategies

all (default)

All child frames render simultaneously as siblings.

inSequence

Frames render one at a time. Navigation is controlled by signals specified in next and back. The current frame index is persisted in localStorage keyed by workflow ID.

workflow:
  version: 1
  id: my-wizard
  renderFrames:
    type: inSequence
    next: :nextStep
    back: :previousStep
  signals:
    nextStep: NavigateFrame

Developing this library

Setup

npm install

Scripts

| Command | Description | |---------|-------------| | npm run build | Compile TypeScript to CJS (dist/cjs) and ESM (dist/mjs), copy CSS | | npm test | Run Jest tests | | npm run tsc:watch | Watch mode for compilation |

Project structure

src/
├── index.ts                         # Package entry point (barrel)
├── pages.ts                         # Page exports (PlaygroundPage, ExamplePage)
├── components/                      # Built-in components
│   ├── OTExampleComponent.tsx
│   ├── OTRestDataProvider.tsx
│   └── OTGraphqlDataProvider.tsx
├── configuration/
│   ├── RenderConfig.tsx             # Public RenderConfig
│   └── engine-v1/
│       ├── RenderConfig.tsx         # Engine v1 renderer core
│       ├── Frame.tsx                # Recursive frame renderer
│       └── config-schemas.tsx       # Zod schemas for the config spec
├── signals/
│   └── index.tsx                    # Signal system (SignalType, SIGNALS store)
├── hooks/
│   └── config-builder.tsx           # ConfigBuilder context provider
└── utils/
    ├── describe-zod.ts              # Describe Zod schemas for UI generation
    ├── format.ts                    # SQL formatting
    ├── template.ts                  # Lodash template resolution
    ├── yaml.ts                      # YAML parse/stringify
    └── misc.ts                      # Type guards

Testing

Tests use Jest with ts-jest. Test files live alongside their source files:

npm test
npm test -- --watch

The codebase is standard TypeScript with Zod for runtime validation. Key areas for testing include:

  • Zod schema validation (config-schemas.tsx)
  • YAML parsing utilities (yaml.ts)
  • Template resolution (template.ts)
  • SQL formatting (format.ts)
  • Zod description introspection (describe-zod.ts)
  • Frame rendering logic
  • Signal creation and wiring

Build

npm run build

Outputs CommonJS to dist/cjs/ and ESM to dist/mjs/. CSS files from src/ are copied to dist/styles/.

See also