🏰 @statelyjs/schema
Type definitions and schema parsing for Stately applications

This package provides the foundational type system for Stately's frontend. It parses OpenAPI schemas into a typed AST (Abstract Syntax Tree) that powers the UI's form generation, validation, and type safety.
This 'schema' package is a low-level package used internally by Stately. See @statelyjs/stately for the user facing package.
Installation
pnpm add @statelyjs/schema
Note: Most users should use @statelyjs/ui which re-exports this package with additional UI integration. Use @statelyjs/schema directly only if building custom tooling or plugins without the UI layer.
Core Concepts
Schema Nodes
OpenAPI schemas are parsed into nodes - typed representations of each schema element:
// A string field
{ nodeType: 'string', nullable: false }
// An object with properties
{
nodeType: 'object',
properties: {
name: { nodeType: 'string', nullable: false },
age: { nodeType: 'integer', nullable: true },
},
required: ['name'],
}
// A reference to another entity
{ nodeType: 'link', inner: { nodeType: 'ref', $ref: '#/components/schemas/SourceConfig' } }
The Schemas Type
The Schemas type is the single source of truth for your application's type system. It combines:
- Config: Your OpenAPI-generated types (components, paths, operations)
- Plugins: Type augmentations from installed plugins
- Derived Types: EntityData, StateEntry, and other computed types
import type { StatelySchemas, DefineConfig } from '@statelyjs/schema';
import type { components, paths, operations } from './generated/types';
type MySchemas = StatelySchemas<DefineConfig<components, paths, operations, typeof PARSED_SCHEMAS>>;
Plugin System
Plugins extend the schema with additional node types:
import type { DefinePlugin, NodeMap, StatelySchemas } from '@statelyjs/schema';
// Define custom nodes
interface MyNodes extends NodeMap {
myCustomType: { nodeType: 'myCustomType'; value: string };
}
// Create plugin type
export type MyPlugin = DefinePlugin<'my-plugin', MyNodes>;
// Use with Schemas
type AppSchemas = StatelySchemas<MyConfig, readonly [MyPlugin]>;
Usage
Creating a Stately Runtime
import { createStately } from '@statelyjs/schema';
import { PARSED_SCHEMAS } from './generated/schemas';
import openapiDoc from './openapi.json';
const stately = createStately<MySchemas>(openapiDoc, PARSED_SCHEMAS);
// Access schema utilities
const entityNode = stately.utils.getNode('Pipeline');
const isValid = stately.utils.validate(data, 'Pipeline');
With Code Splitting
When using @statelyjs/codegen with entry points, some schemas are split into a separate runtime bundle for lazy loading. Configure the loader to enable this:
import { createStately } from '@statelyjs/schema';
import { PARSED_SCHEMAS } from './generated/schemas';
import openapiDoc from './openapi.json';
const stately = createStately<MySchemas>(openapiDoc, PARSED_SCHEMAS, {
// Enable lazy loading for code-split schemas
runtimeSchemas: () => import('./generated/schemas.runtime').then(m => m.RUNTIME_SCHEMAS),
});
This is optional - if no schemas.runtime.ts was generated, you don't need this option.
With Plugins
import { createStately } from '@statelyjs/schema';
import { filesPlugin } from '@statelyjs/files';
const stately = createStately<MySchemas>(openapiDoc, PARSED_SCHEMAS)
.withPlugin(filesPlugin());
// Plugin utilities are now available
stately.plugins.files.utils.formatPath('/some/path');
Type Helpers
DefineConfig
Combines your generated types into a schema configuration:
type MyConfig = DefineConfig<
components, // OpenAPI components
DefinePaths<paths>, // API paths
DefineOperations<operations>, // Operations
typeof PARSED_SCHEMAS // Generated schema nodes
>;
DefinePlugin
Declares a plugin's type augmentations:
type MyPlugin = DefinePlugin<
'my-plugin', // Unique name (string literal)
MyNodes, // Node map
MyTypes, // Additional types
MyData, // Data utilities
MyUtils // Utility functions
>;
import type { Schemas } from '@statelyjs/schema';
// Get the StateEntry union (entity type names)
type StateEntry = MySchemas['config']['components']['schemas']['StateEntry'];
// Get entity data types
type EntityData = MySchemas['types']['EntityData'];
// Get all nodes from plugins
type AllNodes = MySchemas['plugin']['AnyNode'];
Integration with @statelyjs/codegen
The @statelyjs/codegen CLI generates the files this package consumes:
pnpx @statelyjs/codegen ./openapi.json ./src/generated
This produces:
types.ts - TypeScript interfaces matching your OpenAPI spec
schemas.ts - Parsed PARSED_SCHEMAS object for runtime use
Validation
import type { ValidationResult } from '@statelyjs/schema';
const result: ValidationResult = stately.utils.validate(data, 'Pipeline');
if (!result.valid) {
console.log(result.errors);
// [{ path: ['name'], message: 'Required field missing' }]
}
API Reference
createStately(openapiDoc, parsedSchemas, createStatelyOptions)
Creates a Stately schema runtime.
isNodeOfType(node, nodeType)
Type guard for checking node types:
if (isNodeOfType(node, NodeType.Primitive)) {
// node is typed as Primitive
}
createOperationBindingsFactory()
Creates typed API operation bindings from paths.
License
Apache-2.0
Modules